Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
19dad19
deps: float 15d7e79 from openssl
tniessen Jul 21, 2019
e334c1f
src: fix type name in comment
bnoordhuis Jun 20, 2019
386d5d7
lib: support min/max values in validateInteger()
cjihrig Jul 22, 2019
2262526
module: implement "exports" proposal for CommonJS
hybrist Jul 19, 2019
d0b1fb3
doc: api/stream.md typo from writeable to writable
imcotton Jul 23, 2019
e0e7763
crypto: increase maxmem range from 32 to 53 bits
tniessen Jul 21, 2019
a38fecd
tools: update certdata.txt
sam-github Jul 22, 2019
86f4c68
crypto: update root certificates
sam-github Jul 22, 2019
c0a0448
doc: fix type in NSS update instructions
sam-github Jul 22, 2019
24b9d29
build: `uname -m` is amd64 on freebsd, not x86_64
bnoordhuis Jul 22, 2019
3c30456
src : elevate v8 namespaces
HarshithaKP Jul 22, 2019
0667d0c
doc: add documentation for response.flushHeaders()
lpinca Jul 22, 2019
95b87ce
doc: claim NODE_MODULE_VERSION=76 for Electron 8
MarshallOfSound Jul 22, 2019
ae56a23
deps: backport b107214 from upstream V8
addaleax Jul 24, 2019
727ffe4
domain: use strong reference to domain while active
addaleax Jun 20, 2019
881e345
doc: describe why new Buffer() is problematic
sam-github Jul 23, 2019
860c0d8
n-api: add APIs for per-instance state management
Jul 15, 2019
454e879
doc: fix incorrect name in report docs
cjihrig Jul 24, 2019
e0951c8
report: loop over uv_cpu_info() results
cjihrig Jul 23, 2019
d3426ee
assert: avoid potentially misleading reference to object identity
addaleax Jul 23, 2019
b7c6ad5
crypto: add outputLength option to crypto.createHash
tniessen Jul 19, 2019
3a62202
crypto: fix handling of malicious getters (scrypt)
tniessen Jul 23, 2019
5d5c89a
policy: add dependencies map for resources
bmeck Jul 18, 2019
ffc7a00
doc: add documentation for stream.destroyed
ronag Jul 23, 2019
e6b3bfe
n-api: refactor a previous commit
Jul 18, 2019
cf071a0
stream: resolve perf regression introduced by V8 7.3
mcollina Jul 24, 2019
82f263d
build,tools: support building with Visual Studio 2019
targos Jul 20, 2019
f6051f9
test: specialize OOM check for AIX
sam-github Jul 24, 2019
8db43b1
src: move relative uptime init
Jul 24, 2019
c9c7256
http: reset parser.incoming when server response is finished
addaleax Jul 11, 2019
24b8f20
deps: remove backup files
AdamMajer Jul 26, 2019
9e7c662
build: ignore backup files
AdamMajer Jul 26, 2019
a7ef102
crypto: add null check to outputLength logic
cjihrig Jul 26, 2019
0b6a84a
test,report: relax CPU match requirements
addaleax Jul 28, 2019
84efadf
test, util: refactor isObject in test-util
RamirezAlex Jul 27, 2019
5533d48
doc: correct import statement
himself65 Jul 27, 2019
085eb48
doc: fixup esm resolver spec formatting
guybedford Jul 28, 2019
406c50c
src: read break_node_first_line from the inspect options
MarshallOfSound Jun 19, 2019
2142b6d
test: improve test-async-hooks-http-parser-destroy
Flarna Jun 16, 2019
b282c85
vm: increase code coverage of source_text_module.js
kball Jun 21, 2019
9b02f36
deps: dlloads node static linked executable
lal12 Jun 4, 2019
9dfa636
dgram: changed 'var' to 'let' and 'const'
mgochoa Jun 21, 2019
1b0d67b
src: fix OpenBSD build
devnexen Jun 22, 2019
d0d3149
http2: add constant to already destructured constants
dnalborczyk Jun 11, 2019
fa82cbc
http2: destructure constants from require call
dnalborczyk Jun 11, 2019
a28db5f
doc: add example of event close for child_process
ltciro Jun 21, 2019
32cf344
src: readlink("/proc/self/exe") -> uv_exename()
bnoordhuis Jun 21, 2019
a0f89a2
test: refactor test using assert instead of try/catch
juansb827 Jun 21, 2019
9b47f77
test: udpate test comment description
Angelfire Jun 21, 2019
048db38
benchmark: swap var for let in url benchmarks
RamirezAlex Jul 24, 2019
f2c1f36
benchmark: swap var for let in util benchmarks
RamirezAlex Jul 24, 2019
bbcf9f0
benchmark: swap var for let in buffer benchmarks
RamirezAlex Jul 26, 2019
cce2087
src: export v8.GetHeapCodeAndMetadataStatistics()
May 30, 2019
fb57bc4
build: do not mix spaces and tabs in Makefile
lpinca Jul 28, 2019
31aa33b
test: fix race in test-http2-origin
mildsunrise Jul 30, 2019
19070e4
test: fix nits in test/fixtures/tls-connect.js
lpinca Jul 28, 2019
25aa222
build: generate openssl config for BSD-x86
bnoordhuis Jul 22, 2019
3d51d30
src: large pages fix FreeBSD fix region size
devnexen Jul 17, 2019
470db47
build: remove support for s390 (but not s390x)
bnoordhuis Jul 28, 2019
391fe46
benchmark, http: refactor for code consistency
RamirezAlex Jul 21, 2019
464136f
lib: replace var with let in loaders.js
mbj36 Jun 5, 2019
43acce1
worker: handle calling terminate when kHandler is null
elyalvarado Jun 21, 2019
3e63429
doc: add example about emitter.emit in events documentation
felipedc09 Jun 21, 2019
b6e174b
test: use assert.throws() in test-require-json.js
alejandronanez Jun 21, 2019
d72b682
inspector: report all workers
eugeneo Jul 26, 2019
5f07f49
doc: revoke DEP0089
cjihrig Jul 29, 2019
d9084d2
module: unify package exports test for CJS and ESM
hybrist Jul 24, 2019
d601a0a
src: allow generic C++ callables in SetImmediate()
addaleax Jul 15, 2019
7c80963
doc: include "exports" resolver specification
guybedford Dec 11, 2018
c93df0c
n-api: refactoring napi_create_function testing
Jul 28, 2019
c42eb5d
test: refactoring test_error testing
himself65 Jul 30, 2019
efe9b97
test: refactor test-beforeexit-event-exit using mustNotCall
himself65 Jul 30, 2019
dcef7b8
build: include stubs in shared library
jeroen Jul 29, 2019
2236aff
module: exports error as MODULE_NOT_FOUND
guybedford Jul 31, 2019
c389526
test: add tests for spaces in folder names
PaulBags Jul 23, 2019
0ac6d28
doc: writableFinished is true before 'finish'
ronag Jul 23, 2019
f4abf17
doc: remove legacy mode deprecation in assert
Trott Jul 31, 2019
7d9eb17
http2: destroy when settingsFn throws an error
himself65 Jul 31, 2019
57f5d50
doc: fix sorting nit in sections of http.md
vsemozhetbyt Aug 2, 2019
4b91e4d
report: include network interfaces in report
cjihrig Jul 23, 2019
0f8f552
test: refactor test-fs-stat.js
Trott Aug 1, 2019
bdd442f
doc: describe NODE_OPTIONS interop w/cmd line opts
reasonablytall Aug 1, 2019
4a747f6
Revert "src: remove trace_sync_io_ from env"
ChALkeR Aug 1, 2019
3c52dbe
net: shallow copy option when create Server
himself65 Aug 1, 2019
02a50c3
doc: remove use of you
mhdawson Aug 1, 2019
1f82929
path: improve normalization performance
mscdex Aug 3, 2019
11470d5
deps: upgrade npm to 6.10.2
isaacs Jul 25, 2019
77d8f0c
2019-08-06, Version 12.8.0 (Current)
BridgeAR Aug 6, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
module: implement "exports" proposal for CommonJS
Refs: hybrist/proposal-pkg-exports#36
Refs: #28568

PR-URL: #28759
Reviewed-By: Guy Bedford <[email protected]>
Reviewed-By: Bradley Farias <[email protected]>
  • Loading branch information
hybrist authored and targos committed Aug 2, 2019
commit 2262526562e3ee95142a39b821680c2e81e8d3b4
7 changes: 7 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,13 @@ compiled with ICU support.

A given value is out of the accepted range.

<a id="ERR_PATH_NOT_EXPORTED"></a>
### ERR_PATH_NOT_EXPORTED

> Stability: 1 - Experimental

An attempt was made to load a protected path from a package using `exports`.

<a id="ERR_REQUIRE_ESM"></a>
### ERR_REQUIRE_ESM

Expand Down
33 changes: 33 additions & 0 deletions doc/api/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,39 @@ NODE_MODULES_PATHS(START)
5. return DIRS
```

If `--experimental-exports` is enabled,
node allows packages loaded via `LOAD_NODE_MODULES` to explicitly declare
which filepaths to expose and how they should be interpreted.
This expands on the control packages already had using the `main` field.
With this feature enabled, the `LOAD_NODE_MODULES` changes as follows:

```txt
LOAD_NODE_MODULES(X, START)
1. let DIRS = NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
a. let FILE_PATH = RESOLVE_BARE_SPECIFIER(DIR, X)
a. LOAD_AS_FILE(FILE_PATH)
b. LOAD_AS_DIRECTORY(FILE_PATH)

RESOLVE_BARE_SPECIFIER(DIR, X)
1. Try to interpret X as a combination of name and subpath where the name
may have a @scope/ prefix and the subpath begins with a slash (`/`).
2. If X matches this pattern and DIR/name/package.json is a file:
a. Parse DIR/name/package.json, and look for "exports" field.
b. If "exports" is null or undefined, GOTO 3.
c. Find the longest key in "exports" that the subpath starts with.
d. If no such key can be found, throw "not exported".
e. If the key matches the subpath entirely, return DIR/name/${exports[key]}.
f. If either the key or exports[key] do not end with a slash (`/`),
throw "not exported".
g. Return DIR/name/${exports[key]}${subpath.slice(key.length)}.
3. return DIR/X
```

`"exports"` is only honored when loading a package "name" as defined above. Any
`"exports"` values within nested directories and packages must be declared by
the `package.json` responsible for the "name".

## Caching

<!--type=misc-->
Expand Down
2 changes: 2 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,8 @@ E('ERR_OUT_OF_RANGE',
msg += ` It must be ${range}. Received ${received}`;
return msg;
}, RangeError);
E('ERR_PATH_NOT_EXPORTED',
'Package exports for \'%s\' do not define a \'%s\' subpath', Error);
E('ERR_REQUIRE_ESM', 'Must use import to load ES Module: %s', Error);
E('ERR_SCRIPT_EXECUTION_INTERRUPTED',
'Script execution was interrupted by `SIGINT`', Error);
Expand Down
105 changes: 96 additions & 9 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@

'use strict';

const { JSON, Object, Reflect } = primordials;
const {
JSON,
Object,
Reflect,
SafeMap,
StringPrototype,
} = primordials;

const { NativeModule } = require('internal/bootstrap/loaders');
const { pathToFileURL, fileURLToPath, URL } = require('internal/url');
Expand Down Expand Up @@ -54,10 +60,12 @@ const { compileFunction } = internalBinding('contextify');
const {
ERR_INVALID_ARG_VALUE,
ERR_INVALID_OPT_VALUE,
ERR_PATH_NOT_EXPORTED,
ERR_REQUIRE_ESM
} = require('internal/errors').codes;
const { validateString } = require('internal/validators');
const pendingDeprecation = getOptionValue('--pending-deprecation');
const experimentalExports = getOptionValue('--experimental-exports');

module.exports = Module;

Expand Down Expand Up @@ -183,12 +191,10 @@ Module._debug = deprecate(debug, 'Module._debug is deprecated.', 'DEP0077');

// Check if the directory is a package.json dir.
const packageMainCache = Object.create(null);
// Explicit exports from package.json files
const packageExportsCache = new SafeMap();

function readPackage(requestPath) {
const entry = packageMainCache[requestPath];
if (entry)
return entry;

function readPackageRaw(requestPath) {
const jsonPath = path.resolve(requestPath, 'package.json');
const json = internalModuleReadJSON(path.toNamespacedPath(jsonPath));

Expand All @@ -202,14 +208,44 @@ function readPackage(requestPath) {
}

try {
return packageMainCache[requestPath] = JSON.parse(json).main;
const parsed = JSON.parse(json);
packageMainCache[requestPath] = parsed.main;
if (experimentalExports) {
packageExportsCache.set(requestPath, parsed.exports);
}
return parsed;
} catch (e) {
e.path = jsonPath;
e.message = 'Error parsing ' + jsonPath + ': ' + e.message;
throw e;
}
}

function readPackage(requestPath) {
const entry = packageMainCache[requestPath];
if (entry)
return entry;

const pkg = readPackageRaw(requestPath);
if (pkg === false) return false;

return pkg.main;
}

function readExports(requestPath) {
if (packageExportsCache.has(requestPath)) {
return packageExportsCache.get(requestPath);
}

const pkg = readPackageRaw(requestPath);
if (!pkg) {
packageExportsCache.set(requestPath, null);
return null;
}

return pkg.exports;
}

function tryPackage(requestPath, exts, isMain, originalPath) {
const pkg = readPackage(requestPath);

Expand Down Expand Up @@ -298,8 +334,59 @@ function findLongestRegisteredExtension(filename) {
return '.js';
}

// This only applies to requests of a specific form:
// 1. name/.*
// 2. @scope/name/.*
const EXPORTS_PATTERN = /^((?:@[^./@\\][^/@\\]*\/)?[^@./\\][^/\\]*)(\/.*)$/;
function resolveExports(nmPath, request, absoluteRequest) {
// The implementation's behavior is meant to mirror resolution in ESM.
if (experimentalExports && !absoluteRequest) {
const [, name, expansion] =
StringPrototype.match(request, EXPORTS_PATTERN) || [];
if (!name) {
return path.resolve(nmPath, request);
}

const basePath = path.resolve(nmPath, name);
const pkgExports = readExports(basePath);

if (pkgExports != null) {
const mappingKey = `.${expansion}`;
const mapping = pkgExports[mappingKey];
if (typeof mapping === 'string') {
return fileURLToPath(new URL(mapping, `${pathToFileURL(basePath)}/`));
}

let dirMatch = '';
for (const [candidateKey, candidateValue] of Object.entries(pkgExports)) {
if (candidateKey[candidateKey.length - 1] !== '/') continue;
if (candidateValue[candidateValue.length - 1] !== '/') continue;
if (candidateKey.length > dirMatch.length &&
StringPrototype.startsWith(mappingKey, candidateKey)) {
dirMatch = candidateKey;
}
}

if (dirMatch !== '') {
const dirMapping = pkgExports[dirMatch];
const remainder = StringPrototype.slice(mappingKey, dirMatch.length);
const expectedPrefix =
new URL(dirMapping, `${pathToFileURL(basePath)}/`);
const resolved = new URL(remainder, expectedPrefix).href;
if (StringPrototype.startsWith(resolved, expectedPrefix.href)) {
return fileURLToPath(resolved);
}
}
throw new ERR_PATH_NOT_EXPORTED(basePath, mappingKey);
}
}

return path.resolve(nmPath, request);
}

Module._findPath = function(request, paths, isMain) {
if (path.isAbsolute(request)) {
const absoluteRequest = path.isAbsolute(request);
if (absoluteRequest) {
paths = [''];
} else if (!paths || paths.length === 0) {
return false;
Expand All @@ -323,7 +410,7 @@ Module._findPath = function(request, paths, isMain) {
// Don't search further if path doesn't exist
const curPath = paths[i];
if (curPath && stat(curPath) < 1) continue;
var basePath = path.resolve(curPath, request);
var basePath = resolveExports(curPath, request, absoluteRequest);
var filename;

var rc = stat(basePath);
Expand Down
2 changes: 1 addition & 1 deletion src/module_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -856,7 +856,7 @@ Maybe<URL> PackageExportsResolve(Environment* env,
std::string msg = "Package exports for '" +
URL(".", pjson_url).ToFilePath() + "' do not define a '" + pkg_subpath +
"' subpath, imported from " + base.ToFilePath();
node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
node::THROW_ERR_PATH_NOT_EXPORTED(env, msg.c_str());
return Nothing<URL>();
}

Expand Down
1 change: 1 addition & 0 deletions src/node_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ void PrintErrorString(const char* format, ...);
V(ERR_MISSING_PLATFORM_FOR_WORKER, Error) \
V(ERR_MODULE_NOT_FOUND, Error) \
V(ERR_OUT_OF_RANGE, RangeError) \
V(ERR_PATH_NOT_EXPORTED, Error) \
V(ERR_SCRIPT_EXECUTION_INTERRUPTED, Error) \
V(ERR_SCRIPT_EXECUTION_TIMEOUT, Error) \
V(ERR_STRING_TOO_LONG, Error) \
Expand Down
4 changes: 3 additions & 1 deletion src/node_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,9 @@ static void InternalModuleReadJSON(const FunctionCallbackInfo<Value>& args) {
}

const size_t size = offset - start;
if (size == 0 || size == SearchString(&chars[start], size, "\"main\"")) {
if (size == 0 || (
size == SearchString(&chars[start], size, "\"main\"") &&
size == SearchString(&chars[start], size, "\"exports\""))) {
return;
} else {
Local<String> chars_string =
Expand Down
1 change: 1 addition & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"experimental ES Module support and caching modules",
&EnvironmentOptions::experimental_modules,
kAllowedInEnvironment);
Implies("--experimental-modules", "--experimental-exports");
AddOption("--experimental-wasm-modules",
"experimental ES Module support for webassembly modules",
&EnvironmentOptions::experimental_wasm_modules,
Expand Down
5 changes: 3 additions & 2 deletions test/es-module/test-esm-exports.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Flags: --experimental-modules --experimental-exports
// Flags: --experimental-modules

import { mustCall } from '../common/index.mjs';
import { ok, strictEqual } from 'assert';

import { asdf, asdf2 } from '../fixtures/pkgexports.mjs';
import { asdf, asdf2, space } from '../fixtures/pkgexports.mjs';
import {
loadMissing,
loadFromNumber,
Expand All @@ -12,6 +12,7 @@ import {

strictEqual(asdf, 'asdf');
strictEqual(asdf2, 'asdf');
strictEqual(space, 'encoded path');

loadMissing().catch(mustCall((err) => {
ok(err.message.toString().startsWith('Package exports'));
Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/node_modules/pkgexports/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions test/fixtures/node_modules/pkgexports/sp ce.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/fixtures/pkgexports.mjs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as asdf } from 'pkgexports/asdf';
export { default as asdf2 } from 'pkgexports/sub/asdf.js';
export { default as space } from 'pkgexports/space';
47 changes: 47 additions & 0 deletions test/parallel/test-module-package-exports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Flags: --experimental-exports
'use strict';

require('../common');

const assert = require('assert');
const { createRequire } = require('module');
const path = require('path');

const fixtureRequire =
createRequire(path.resolve(__dirname, '../fixtures/imaginary.js'));

assert.strictEqual(fixtureRequire('pkgexports/valid-cjs'), 'asdf');

assert.strictEqual(fixtureRequire('baz/index'), 'eye catcher');

assert.strictEqual(fixtureRequire('pkgexports/sub/asdf.js'), 'asdf');

assert.strictEqual(fixtureRequire('pkgexports/space'), 'encoded path');

assert.throws(
() => fixtureRequire('pkgexports/not-a-known-entry'),
(e) => {
assert.strictEqual(e.code, 'ERR_PATH_NOT_EXPORTED');
return true;
});

assert.throws(
() => fixtureRequire('pkgexports-number/hidden.js'),
(e) => {
assert.strictEqual(e.code, 'ERR_PATH_NOT_EXPORTED');
return true;
});

assert.throws(
() => fixtureRequire('pkgexports/sub/not-a-file.js'),
(e) => {
assert.strictEqual(e.code, 'MODULE_NOT_FOUND');
return true;
});

assert.throws(
() => fixtureRequire('pkgexports/sub/./../asdf.js'),
(e) => {
assert.strictEqual(e.code, 'ERR_PATH_NOT_EXPORTED');
return true;
});