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 6, 2021
1 parent 36cc0ee commit 083a92d
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 6 deletions.
10 changes: 9 additions & 1 deletion doc/api/esm.md
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions doc/api/modules.md
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 Down
7 changes: 5 additions & 2 deletions lib/internal/modules/cjs/helpers.js
Expand Up @@ -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;
}
}
Expand Down
18 changes: 16 additions & 2 deletions lib/internal/modules/cjs/loader.js
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,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) {
Expand Down Expand Up @@ -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;
}

Expand Down
7 changes: 6 additions & 1 deletion lib/internal/modules/esm/translators.js
Expand Up @@ -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();
});
Expand Down
2 changes: 2 additions & 0 deletions test/es-module/test-esm-dynamic-import.js
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
@@ -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 083a92d

Please sign in to comment.