diff --git a/doc/api/esm.md b/doc/api/esm.md index 459f877718..f20b6516b3 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -45,6 +45,10 @@ The `import.meta` metaproperty is an `Object` that contains the following property: * `url` {string} The absolute `file:` URL of the module. +* `require` {Function} To require CommonJS modules. This function enables + interoperability between CJS and ESM. See [`require()`]. None of the + properties generally exposed on require are available via + `import.meta.require`. ### Unsupported @@ -256,3 +260,4 @@ in the import tree. [Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md [addons]: addons.html [dynamic instantiate hook]: #esm_dynamic_instantiate_hook +[`require()`]: modules.html#modules_require diff --git a/lib/internal/process/esm_loader.js b/lib/internal/process/esm_loader.js index c622084415..dfae71dae6 100644 --- a/lib/internal/process/esm_loader.js +++ b/lib/internal/process/esm_loader.js @@ -1,12 +1,16 @@ 'use strict'; +const { makeRequireFunction } = require('internal/modules/cjs/helpers'); +const Module = require('module'); const { internalBinding } = require('internal/bootstrap/loaders'); + const { setImportModuleDynamicallyCallback, setInitializeImportMetaObjectCallback } = internalBinding('module_wrap'); +const { defineProperty } = Object; -const { getURLFromFilePath } = require('internal/url'); +const { getURLFromFilePath, getPathFromURL } = require('internal/url'); const Loader = require('internal/modules/esm/loader'); const path = require('path'); const { URL } = require('url'); @@ -27,6 +31,22 @@ function initializeImportMetaObject(wrap, meta) { if (vmModule === undefined) { // This ModuleWrap belongs to the Loader. meta.url = wrap.url; + let req; + defineProperty(meta, 'require', { + enumerable: true, + configurable: true, + get() { + if (req !== undefined) + return req; + const url = new URL(meta.url); + const path = getPathFromURL(url); + const mod = new Module(path, null); + mod.filename = path; + mod.paths = Module._nodeModulePaths(path); + req = makeRequireFunction(mod).bind(null); + return req; + } + }); } else { const initializeImportMeta = initImportMetaMap.get(vmModule); if (initializeImportMeta !== undefined) { diff --git a/test/es-module/test-esm-import-meta.mjs b/test/es-module/test-esm-import-meta.mjs index c17e0e20d4..cc4d729ef9 100644 --- a/test/es-module/test-esm-import-meta.mjs +++ b/test/es-module/test-esm-import-meta.mjs @@ -3,20 +3,47 @@ import '../common'; import assert from 'assert'; +const fixtures = import.meta.require('../common/fixtures'); + assert.strictEqual(Object.getPrototypeOf(import.meta), null); -const keys = ['url']; +const keys = ['url', 'require']; assert.deepStrictEqual(Reflect.ownKeys(import.meta), keys); const descriptors = Object.getOwnPropertyDescriptors(import.meta); + for (const descriptor of Object.values(descriptors)) { delete descriptor.value; // Values are verified below. - assert.deepStrictEqual(descriptor, { - enumerable: true, - writable: true, - configurable: true - }); } +assert.deepStrictEqual(descriptors.url, { + enumerable: true, + writable: true, + configurable: true +}); + +assert.deepStrictEqual(descriptors.require, { + get: descriptors.require.get, + set: undefined, + enumerable: true, + configurable: true +}); + +assert.strictEqual(import.meta.require.cache, undefined); +assert.strictEqual(import.meta.require.extensions, undefined); +assert.strictEqual(import.meta.require.main, undefined); +assert.strictEqual(import.meta.require.paths, undefined); + const urlReg = /^file:\/\/\/.*\/test\/es-module\/test-esm-import-meta\.mjs$/; assert(import.meta.url.match(urlReg)); + +const a = import.meta.require( + fixtures.path('module-require', 'relative', 'dot.js') +); +const b = import.meta.require( + fixtures.path('module-require', 'relative', 'dot-slash.js') +); + +assert.strictEqual(a.value, 42); +// require(".") should resolve like require("./") +assert.strictEqual(a, b);