From 083a92dc2a60da006811e62a1d21648eba826e84 Mon Sep 17 00:00:00 2001 From: ExE Boss <3889017+ExE-Boss@users.noreply.github.com> Date: Sat, 6 Feb 2021 12:10:00 +0100 Subject: [PATCH] =?UTF-8?q?module:=20add=C2=A0support=20for=C2=A0`node:`?= =?UTF-8?q?=E2=80=91prefixed=20`require(=E2=80=A6)`=C2=A0calls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: https://github.com/nodejs/node/issues/36098 Co-authored-by: Darshan Sen --- doc/api/esm.md | 10 +++++- doc/api/modules.md | 6 ++++ lib/internal/modules/cjs/helpers.js | 7 ++-- lib/internal/modules/cjs/loader.js | 18 ++++++++-- lib/internal/modules/esm/translators.js | 7 +++- test/es-module/test-esm-dynamic-import.js | 2 ++ test/parallel/test-require-node-prefix.js | 42 +++++++++++++++++++++++ 7 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 test/parallel/test-require-node-prefix.js diff --git a/doc/api/esm.md b/doc/api/esm.md index 8d1980a4e0c6ad..c97c3170b42ff6 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -204,16 +204,24 @@ import _ from 'data:application/json,"world!"'; added: - v14.13.1 - v12.20.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/37246 + description: Added `node:` import support to `require(...)`. --> `node:` URLs are supported as an alternative means to load Node.js builtin modules. This URL scheme allows for builtin modules to be referenced by valid absolute URL strings. -```js +```js esm import fs from 'node:fs/promises'; ``` +```js cjs +const fs = require('node:fs/promises'); +``` + ## Builtin modules [Core modules][] provide named exports of their public API. A diff --git a/doc/api/modules.md b/doc/api/modules.md index 5bca6a4553cb20..d43cfd05fd0b6d 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -280,6 +280,12 @@ irrespective of whether or not `./foo` and `./FOO` are the same file. ## Core modules + Node.js has several modules compiled into the binary. These modules are described in greater detail elsewhere in this documentation. diff --git a/lib/internal/modules/cjs/helpers.js b/lib/internal/modules/cjs/helpers.js index 163c854ac26d48..fb25ffdae921ad 100644 --- a/lib/internal/modules/cjs/helpers.js +++ b/lib/internal/modules/cjs/helpers.js @@ -32,11 +32,14 @@ let debug = require('internal/util/debuglog').debuglog('module', (fn) => { // TODO: Use this set when resolving pkg#exports conditions in loader.js. const cjsConditions = new SafeSet(['require', 'node', ...userConditions]); -function loadNativeModule(filename, request) { +function loadNativeModule(filename, request, skipCompile = false) { const mod = NativeModule.map.get(filename); if (mod) { debug('load native module %s', request); - mod.compileForPublicLoader(); + if (skipCompile) { + // compileForPublicLoader() throws if mod.canBeRequiredByUsers is false: + mod.compileForPublicLoader(); + } return mod; } } diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 5abfa465e0407d..468f1026c168fc 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -109,7 +109,8 @@ let hasLoadedAnyUserCJSModule = false; const { ERR_INVALID_ARG_VALUE, ERR_INVALID_MODULE_SPECIFIER, - ERR_REQUIRE_ESM + ERR_REQUIRE_ESM, + ERR_UNKNOWN_BUILTIN_MODULE, } = require('internal/errors').codes; const { validateString } = require('internal/validators'); const pendingDeprecation = getOptionValue('--pending-deprecation'); @@ -766,6 +767,18 @@ Module._load = function(request, parent, isMain) { } const filename = Module._resolveFilename(request, parent, isMain); + if (StringPrototypeStartsWith(filename, 'node:')) { + // Slice 'node:' prefix + const id = StringPrototypeSlice(filename, 5); + + // compileForPublicLoader() throws if mod.canBeRequiredByUsers is false: + const module = loadNativeModule(id, request, true); + if (!module?.canBeRequiredByUsers) { + throw new ERR_UNKNOWN_BUILTIN_MODULE(filename); + } + + return module.compileForPublicLoader(); + } const cachedModule = Module._cache[filename]; if (cachedModule !== undefined) { @@ -837,7 +850,8 @@ Module._load = function(request, parent, isMain) { }; Module._resolveFilename = function(request, parent, isMain, options) { - if (NativeModule.canBeRequiredByUsers(request)) { + if (StringPrototypeStartsWith(request, 'node:') || + NativeModule.canBeRequiredByUsers(request)) { return request; } diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index b685989345b962..487b5a351ce087 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -282,10 +282,15 @@ translators.set('builtin', async function builtinStrategy(url) { debug(`Translating BuiltinModule ${url}`); // Slice 'node:' scheme const id = StringPrototypeSlice(url, 5); + + // compileForPublicLoader() throws if mod.canBeRequiredByUsers is false: const module = loadNativeModule(id, url, true); - if (!StringPrototypeStartsWith(url, 'node:') || !module) { + if (!StringPrototypeStartsWith(url, 'node:') || + !module?.canBeRequiredByUsers) { throw new ERR_UNKNOWN_BUILTIN_MODULE(url); } + + module.compileForPublicLoader(); debug(`Loading BuiltinModule ${url}`); return module.getESMFacade(); }); diff --git a/test/es-module/test-esm-dynamic-import.js b/test/es-module/test-esm-dynamic-import.js index 6e64f86423c66b..3df2191e3ba06b 100644 --- a/test/es-module/test-esm-dynamic-import.js +++ b/test/es-module/test-esm-dynamic-import.js @@ -51,6 +51,8 @@ function expectFsNamespace(result) { expectModuleError(import('node:unknown'), 'ERR_UNKNOWN_BUILTIN_MODULE'); + expectModuleError(import('node:internal/test/binding'), + 'ERR_UNKNOWN_BUILTIN_MODULE'); expectModuleError(import('./not-an-existing-module.mjs'), 'ERR_MODULE_NOT_FOUND'); expectModuleError(import('http://example.com/foo.js'), diff --git a/test/parallel/test-require-node-prefix.js b/test/parallel/test-require-node-prefix.js new file mode 100644 index 00000000000000..957cabf13aeb11 --- /dev/null +++ b/test/parallel/test-require-node-prefix.js @@ -0,0 +1,42 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const fs = require('fs'); + +const errUnknownBuiltinModuleRE = /^No such built-in module: /u; + +// For direct use of require expressions inside of CJS modules, +// all kinds of specifiers should work without issue. +{ + assert.strictEqual(require('fs'), fs); + assert.strictEqual(require('node:fs'), fs); + + assert.throws( + () => require('node:unknown'), + { + code: 'ERR_UNKNOWN_BUILTIN_MODULE', + message: errUnknownBuiltinModuleRE, + }, + ); + + assert.throws( + () => require('node:internal/test/binding'), + { + code: 'ERR_UNKNOWN_BUILTIN_MODULE', + message: errUnknownBuiltinModuleRE, + }, + ); +} + +// `node:`-prefixed `require(...)` calls bypass the require cache: +{ + const fakeModule = {}; + + require.cache.fs = { exports: fakeModule }; + + assert.strictEqual(require('fs'), fakeModule); + assert.strictEqual(require('node:fs'), fs); + + delete require.cache.fs; +}