Skip to content

Commit

Permalink
module: add support for node:‑prefixed require(…) calls
Browse files Browse the repository at this point in the history
Fixes: #36098

Co-authored-by: Darshan Sen <raisinten@gmail.com>
  • Loading branch information
ExE-Boss and RaisinTen committed Feb 8, 2021
1 parent 72f9c53 commit f5cdabe
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 9 deletions.
4 changes: 4 additions & 0 deletions doc/api/esm.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ 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
Expand Down
30 changes: 26 additions & 4 deletions doc/api/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,12 @@ irrespective of whether or not `./foo` and `./FOO` are the same file.
## Core modules

<!--type=misc-->
<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37246
description: Added `node:` import support to `require(...)`.
-->

Node.js has several modules compiled into the binary. These modules are
described in greater detail elsewhere in this documentation.
Expand All @@ -288,8 +294,13 @@ The core modules are defined within the Node.js source and are located in the
`lib/` folder.

Core modules are always preferentially loaded if their identifier is
passed to `require()`. For instance, `require('http')` will always
return the built in HTTP module, even if there is a file by that name.
passed to `require()`. For instance, `require('node:http')` will always
return the built in HTTP module, even if there is a file or `require.cache`
entry by that name.

```js
const fs = require('node:fs/promises');
```

## Cycles

Expand Down Expand Up @@ -642,8 +653,19 @@ error.

Adding or replacing entries is also possible. This cache is checked before
native modules and if a name matching a native module is added to the cache,
no require call is
going to receive the native module anymore. Use with care!
only `node:`-prefixed require calls are going to receive the native module.
Use with care!

```js
const assert = require('assert');
const realFs = require('fs');

const fakeFs = {};
require.cache.fs = { exports: fakeFs };

assert.strictEqual(require('fs'), fakeFs);
assert.strictEqual(require('node:fs'), realFs);
```

#### `require.extensions`
<!-- YAML
Expand Down
3 changes: 2 additions & 1 deletion lib/internal/modules/cjs/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ const cjsConditions = new SafeSet(['require', 'node', ...userConditions]);

function loadNativeModule(filename, request) {
const mod = NativeModule.map.get(filename);
if (mod) {
if (mod?.canBeRequiredByUsers) {
debug('load native module %s', request);
// compileForPublicLoader() throws if mod.canBeRequiredByUsers is false:
mod.compileForPublicLoader();
return mod;
}
Expand Down
17 changes: 15 additions & 2 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -766,6 +767,17 @@ 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);

const module = loadNativeModule(id, request);
if (!module?.canBeRequiredByUsers) {
throw new ERR_UNKNOWN_BUILTIN_MODULE(filename);
}

return module.exports;
}

const cachedModule = Module._cache[filename];
if (cachedModule !== undefined) {
Expand Down Expand Up @@ -837,7 +849,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;
}

Expand Down
5 changes: 3 additions & 2 deletions lib/internal/modules/esm/translators.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,9 @@ translators.set('builtin', async function builtinStrategy(url) {
debug(`Translating BuiltinModule ${url}`);
// Slice 'node:' scheme
const id = StringPrototypeSlice(url, 5);
const module = loadNativeModule(id, url, true);
if (!StringPrototypeStartsWith(url, 'node:') || !module) {
const module = loadNativeModule(id, url);
if (!StringPrototypeStartsWith(url, 'node:') ||
!module?.canBeRequiredByUsers) {
throw new ERR_UNKNOWN_BUILTIN_MODULE(url);
}
debug(`Loading BuiltinModule ${url}`);
Expand Down
2 changes: 2 additions & 0 deletions test/es-module/test-esm-dynamic-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
42 changes: 42 additions & 0 deletions test/parallel/test-require-node-prefix.js
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit f5cdabe

Please sign in to comment.