module: Allow runMain to be ESM
This follows the EPS an allows the node CLI to have ESM as an entry point. `node ./example.mjs`. A newer V8 is needed for `import()` so that is not included. `import.meta` is still in specification stage so that also is not included. PR-URL: https://github.com/nodejs/node/pull/14369 Author: Bradley Farias <bradley.meck@gmail.com> Author: Guy Bedford <guybedford@gmail.com> Author: Jan Krems <jan.krems@groupon.com> Author: Timothy Gu <timothygu99@gmail.com> Author: Michaël Zasso <targos@protonmail.com> Author: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
This commit is contained in:
parent
46133b5beb
commit
c8a389e19f
@ -10,6 +10,11 @@ env:
|
|||||||
parserOptions:
|
parserOptions:
|
||||||
ecmaVersion: 2017
|
ecmaVersion: 2017
|
||||||
|
|
||||||
|
overrides:
|
||||||
|
- files: ["doc/api/esm.md", "*.mjs"]
|
||||||
|
parserOptions:
|
||||||
|
sourceType: module
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
# Possible Errors
|
# Possible Errors
|
||||||
# http://eslint.org/docs/rules/#possible-errors
|
# http://eslint.org/docs/rules/#possible-errors
|
||||||
|
4
Makefile
4
Makefile
@ -150,7 +150,7 @@ coverage-build: all
|
|||||||
"$(CURDIR)/testing/coverage/gcovr-patches.diff"); fi
|
"$(CURDIR)/testing/coverage/gcovr-patches.diff"); fi
|
||||||
if [ -d lib_ ]; then $(RM) -r lib; mv lib_ lib; fi
|
if [ -d lib_ ]; then $(RM) -r lib; mv lib_ lib; fi
|
||||||
mv lib lib_
|
mv lib lib_
|
||||||
$(NODE) ./node_modules/.bin/nyc instrument lib_/ lib/
|
$(NODE) ./node_modules/.bin/nyc instrument --extension .js --extension .mjs lib_/ lib/
|
||||||
$(MAKE)
|
$(MAKE)
|
||||||
|
|
||||||
coverage-test: coverage-build
|
coverage-test: coverage-build
|
||||||
@ -886,7 +886,7 @@ JSLINT_TARGETS = benchmark doc lib test tools
|
|||||||
|
|
||||||
jslint:
|
jslint:
|
||||||
@echo "Running JS linter..."
|
@echo "Running JS linter..."
|
||||||
$(NODE) tools/eslint/bin/eslint.js --cache --rulesdir=tools/eslint-rules --ext=.js,.md \
|
$(NODE) tools/eslint/bin/eslint.js --cache --rulesdir=tools/eslint-rules --ext=.js,.mjs,.md \
|
||||||
$(JSLINT_TARGETS)
|
$(JSLINT_TARGETS)
|
||||||
|
|
||||||
jslint-ci:
|
jslint-ci:
|
||||||
|
88
doc/api/esm.md
Normal file
88
doc/api/esm.md
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# ECMAScript Modules
|
||||||
|
|
||||||
|
<!--introduced_in=v9.x.x-->
|
||||||
|
|
||||||
|
> Stability: 1 - Experimental
|
||||||
|
|
||||||
|
<!--name=esm-->
|
||||||
|
|
||||||
|
Node contains support for ES Modules based upon the [the Node EP for ES Modules][].
|
||||||
|
|
||||||
|
Not all features of the EP are complete and will be landing as both VM support and implementation is ready. Error messages are still being polished.
|
||||||
|
|
||||||
|
## Enabling
|
||||||
|
|
||||||
|
<!-- type=misc -->
|
||||||
|
|
||||||
|
The `--experimental-modules` flag can be used to enable features for loading ESM modules.
|
||||||
|
|
||||||
|
Once this has been set, files ending with `.mjs` will be able to be loaded as ES Modules.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
node --experimental-modules my-app.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
<!-- type=misc -->
|
||||||
|
|
||||||
|
### Supported
|
||||||
|
|
||||||
|
Only the CLI argument for the main entry point to the program can be an entry point into an ESM graph. In the future `import()` can be used to create entry points into ESM graphs at run time.
|
||||||
|
|
||||||
|
### Unsupported
|
||||||
|
|
||||||
|
| Feature | Reason |
|
||||||
|
| --- | --- |
|
||||||
|
| `require('./foo.mjs')` | ES Modules have differing resolution and timing, use language standard `import()` |
|
||||||
|
| `import()` | pending newer V8 release used in Node.js |
|
||||||
|
| `import.meta` | pending V8 implementation |
|
||||||
|
| Loader Hooks | pending Node.js EP creation/consensus |
|
||||||
|
|
||||||
|
## Notable differences between `import` and `require`
|
||||||
|
|
||||||
|
### No NODE_PATH
|
||||||
|
|
||||||
|
`NODE_PATH` is not part of resolving `import` specifiers. Please use symlinks if this behavior is desired.
|
||||||
|
|
||||||
|
### No `require.extensions`
|
||||||
|
|
||||||
|
`require.extensions` is not used by `import`. The expectation is that loader hooks can provide this workflow in the future.
|
||||||
|
|
||||||
|
### No `require.cache`
|
||||||
|
|
||||||
|
`require.cache` is not used by `import`. It has a separate cache.
|
||||||
|
|
||||||
|
### URL based paths
|
||||||
|
|
||||||
|
ESM are resolved and cached based upon [URL](url.spec.whatwg.org) semantics. This means that files containing special characters such as `#` and `?` need to be escaped.
|
||||||
|
|
||||||
|
Modules will be loaded multiple times if the `import` specifier used to resolve them have a different query or fragment.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import './foo?query=1'; // loads ./foo with query of "?query=1"
|
||||||
|
import './foo?query=2'; // loads ./foo with query of "?query=2"
|
||||||
|
```
|
||||||
|
|
||||||
|
For now, only modules using the `file:` protocol can be loaded.
|
||||||
|
|
||||||
|
## Interop with existing modules
|
||||||
|
|
||||||
|
All CommonJS, JSON, and C++ modules can be used with `import`.
|
||||||
|
|
||||||
|
Modules loaded this way will only be loaded once, even if their query or fragment string differs between `import` statements.
|
||||||
|
|
||||||
|
When loaded via `import` these modules will provide a single `default` export representing the value of `module.exports` at the time they finished evaluating.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import fs from 'fs';
|
||||||
|
fs.readFile('./foo.txt', (err, body) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
} else {
|
||||||
|
console.log(body);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
[the Node EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md
|
7
lib/internal/bootstrap_node.js
vendored
7
lib/internal/bootstrap_node.js
vendored
@ -109,6 +109,13 @@
|
|||||||
'DeprecationWarning', 'DEP0062', startup, true);
|
'DeprecationWarning', 'DEP0062', startup, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.binding('config').experimentalModules) {
|
||||||
|
process.emitWarning(
|
||||||
|
'The ESM module loader is experimental.',
|
||||||
|
'ExperimentalWarning', undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// There are various modes that Node can run in. The most common two
|
// There are various modes that Node can run in. The most common two
|
||||||
// are running from a script and running the REPL - but there are a few
|
// are running from a script and running the REPL - but there are a few
|
||||||
// others like the debugger or running --eval arguments. Here we decide
|
// others like the debugger or running --eval arguments. Here we decide
|
||||||
|
@ -229,6 +229,9 @@ E('ERR_IPC_ONE_PIPE', 'Child process can have only one IPC pipe');
|
|||||||
E('ERR_IPC_SYNC_FORK', 'IPC cannot be used with synchronous forks');
|
E('ERR_IPC_SYNC_FORK', 'IPC cannot be used with synchronous forks');
|
||||||
E('ERR_METHOD_NOT_IMPLEMENTED', 'The %s method is not implemented');
|
E('ERR_METHOD_NOT_IMPLEMENTED', 'The %s method is not implemented');
|
||||||
E('ERR_MISSING_ARGS', missingArgs);
|
E('ERR_MISSING_ARGS', missingArgs);
|
||||||
|
E('ERR_MISSING_MODULE', 'Cannot find module %s');
|
||||||
|
E('ERR_MODULE_RESOLUTION_LEGACY', '%s not found by import in %s.' +
|
||||||
|
'Legacy behavior in require would have found it at %s');
|
||||||
E('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times');
|
E('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times');
|
||||||
E('ERR_NAPI_CONS_FUNCTION', 'Constructor must be a function');
|
E('ERR_NAPI_CONS_FUNCTION', 'Constructor must be a function');
|
||||||
E('ERR_NAPI_CONS_PROTOTYPE_OBJECT', 'Constructor.prototype must be an object');
|
E('ERR_NAPI_CONS_PROTOTYPE_OBJECT', 'Constructor.prototype must be an object');
|
||||||
@ -237,6 +240,7 @@ E('ERR_NO_ICU', '%s is not supported on Node.js compiled without ICU');
|
|||||||
E('ERR_NO_LONGER_SUPPORTED', '%s is no longer supported');
|
E('ERR_NO_LONGER_SUPPORTED', '%s is no longer supported');
|
||||||
E('ERR_OUTOFMEMORY', 'Out of memory');
|
E('ERR_OUTOFMEMORY', 'Out of memory');
|
||||||
E('ERR_PARSE_HISTORY_DATA', 'Could not parse history data in %s');
|
E('ERR_PARSE_HISTORY_DATA', 'Could not parse history data in %s');
|
||||||
|
E('ERR_REQUIRE_ESM', 'Must use import to load ES Module: %s');
|
||||||
E('ERR_SERVER_ALREADY_LISTEN',
|
E('ERR_SERVER_ALREADY_LISTEN',
|
||||||
'Listen method has been called more than once without closing.');
|
'Listen method has been called more than once without closing.');
|
||||||
E('ERR_SOCKET_ALREADY_BOUND', 'Socket is already bound');
|
E('ERR_SOCKET_ALREADY_BOUND', 'Socket is already bound');
|
||||||
|
75
lib/internal/loader/Loader.js
Normal file
75
lib/internal/loader/Loader.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { URL } = require('url');
|
||||||
|
const { getURLFromFilePath } = require('internal/url');
|
||||||
|
|
||||||
|
const {
|
||||||
|
getNamespaceOfModuleWrap
|
||||||
|
} = require('internal/loader/ModuleWrap');
|
||||||
|
|
||||||
|
const ModuleMap = require('internal/loader/ModuleMap');
|
||||||
|
const ModuleJob = require('internal/loader/ModuleJob');
|
||||||
|
const resolveRequestUrl = require('internal/loader/resolveRequestUrl');
|
||||||
|
const errors = require('internal/errors');
|
||||||
|
|
||||||
|
function getBase() {
|
||||||
|
try {
|
||||||
|
return getURLFromFilePath(`${process.cwd()}/`);
|
||||||
|
} catch (e) {
|
||||||
|
e.stack;
|
||||||
|
// If the current working directory no longer exists.
|
||||||
|
if (e.code === 'ENOENT') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Loader {
|
||||||
|
constructor(base = getBase()) {
|
||||||
|
this.moduleMap = new ModuleMap();
|
||||||
|
if (typeof base !== 'undefined' && base instanceof URL !== true) {
|
||||||
|
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'base', 'URL');
|
||||||
|
}
|
||||||
|
this.base = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolve(specifier) {
|
||||||
|
const request = resolveRequestUrl(this.base, specifier);
|
||||||
|
if (request.url.protocol !== 'file:') {
|
||||||
|
throw new errors.Error('ERR_INVALID_PROTOCOL',
|
||||||
|
request.url.protocol, 'file:');
|
||||||
|
}
|
||||||
|
return request.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getModuleJob(dependentJob, specifier) {
|
||||||
|
if (!this.moduleMap.has(dependentJob.url)) {
|
||||||
|
throw new errors.Error('ERR_MISSING_MODULE', dependentJob.url);
|
||||||
|
}
|
||||||
|
const request = await resolveRequestUrl(dependentJob.url, specifier);
|
||||||
|
const url = `${request.url}`;
|
||||||
|
if (this.moduleMap.has(url)) {
|
||||||
|
return this.moduleMap.get(url);
|
||||||
|
}
|
||||||
|
const dependencyJob = new ModuleJob(this, request);
|
||||||
|
this.moduleMap.set(url, dependencyJob);
|
||||||
|
return dependencyJob;
|
||||||
|
}
|
||||||
|
|
||||||
|
async import(specifier) {
|
||||||
|
const request = await resolveRequestUrl(this.base, specifier);
|
||||||
|
const url = `${request.url}`;
|
||||||
|
let job;
|
||||||
|
if (this.moduleMap.has(url)) {
|
||||||
|
job = this.moduleMap.get(url);
|
||||||
|
} else {
|
||||||
|
job = new ModuleJob(this, request);
|
||||||
|
this.moduleMap.set(url, job);
|
||||||
|
}
|
||||||
|
const module = await job.run();
|
||||||
|
return getNamespaceOfModuleWrap(module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Object.setPrototypeOf(Loader.prototype, null);
|
||||||
|
module.exports = Loader;
|
116
lib/internal/loader/ModuleJob.js
Normal file
116
lib/internal/loader/ModuleJob.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { SafeSet, SafePromise } = require('internal/safe_globals');
|
||||||
|
const resolvedPromise = SafePromise.resolve();
|
||||||
|
const resolvedArrayPromise = SafePromise.resolve([]);
|
||||||
|
const { ModuleWrap } = require('internal/loader/ModuleWrap');
|
||||||
|
|
||||||
|
const NOOP = () => { /* No-op */ };
|
||||||
|
class ModuleJob {
|
||||||
|
/**
|
||||||
|
* @param {module: ModuleWrap?, compiled: Promise} moduleProvider
|
||||||
|
*/
|
||||||
|
constructor(loader, moduleProvider, url) {
|
||||||
|
this.url = `${moduleProvider.url}`;
|
||||||
|
this.moduleProvider = moduleProvider;
|
||||||
|
this.loader = loader;
|
||||||
|
this.error = null;
|
||||||
|
this.hadError = false;
|
||||||
|
|
||||||
|
if (moduleProvider instanceof ModuleWrap !== true) {
|
||||||
|
// linked == promise for dependency jobs, with module populated,
|
||||||
|
// module wrapper linked
|
||||||
|
this.modulePromise = this.moduleProvider.createModule();
|
||||||
|
this.module = undefined;
|
||||||
|
const linked = async () => {
|
||||||
|
const dependencyJobs = [];
|
||||||
|
this.module = await this.modulePromise;
|
||||||
|
this.module.link(async (dependencySpecifier) => {
|
||||||
|
const dependencyJobPromise =
|
||||||
|
this.loader.getModuleJob(this, dependencySpecifier);
|
||||||
|
dependencyJobs.push(dependencyJobPromise);
|
||||||
|
const dependencyJob = await dependencyJobPromise;
|
||||||
|
return dependencyJob.modulePromise;
|
||||||
|
});
|
||||||
|
return SafePromise.all(dependencyJobs);
|
||||||
|
};
|
||||||
|
this.linked = linked();
|
||||||
|
|
||||||
|
// instantiated == deep dependency jobs wrappers instantiated,
|
||||||
|
//module wrapper instantiated
|
||||||
|
this.instantiated = undefined;
|
||||||
|
} else {
|
||||||
|
const getModuleProvider = async () => moduleProvider;
|
||||||
|
this.modulePromise = getModuleProvider();
|
||||||
|
this.moduleProvider = { finish: NOOP };
|
||||||
|
this.module = moduleProvider;
|
||||||
|
this.linked = resolvedArrayPromise;
|
||||||
|
this.instantiated = this.modulePromise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instantiate() {
|
||||||
|
if (this.instantiated) {
|
||||||
|
return this.instantiated;
|
||||||
|
}
|
||||||
|
return this.instantiated = new Promise(async (resolve, reject) => {
|
||||||
|
const jobsInGraph = new SafeSet();
|
||||||
|
let jobsReadyToInstantiate = 0;
|
||||||
|
// (this must be sync for counter to work)
|
||||||
|
const queueJob = (moduleJob) => {
|
||||||
|
if (jobsInGraph.has(moduleJob)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jobsInGraph.add(moduleJob);
|
||||||
|
moduleJob.linked.then((dependencyJobs) => {
|
||||||
|
for (const dependencyJob of dependencyJobs) {
|
||||||
|
queueJob(dependencyJob);
|
||||||
|
}
|
||||||
|
checkComplete();
|
||||||
|
}, (e) => {
|
||||||
|
if (!this.hadError) {
|
||||||
|
this.error = e;
|
||||||
|
this.hadError = true;
|
||||||
|
}
|
||||||
|
checkComplete();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const checkComplete = () => {
|
||||||
|
if (++jobsReadyToInstantiate === jobsInGraph.size) {
|
||||||
|
// I believe we only throw once the whole tree is finished loading?
|
||||||
|
// or should the error bail early, leaving entire tree to still load?
|
||||||
|
if (this.hadError) {
|
||||||
|
reject(this.error);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
this.module.instantiate();
|
||||||
|
for (const dependencyJob of jobsInGraph) {
|
||||||
|
dependencyJob.instantiated = resolvedPromise;
|
||||||
|
}
|
||||||
|
resolve(this.module);
|
||||||
|
} catch (e) {
|
||||||
|
e.stack;
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
queueJob(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const module = await this.instantiate();
|
||||||
|
try {
|
||||||
|
module.evaluate();
|
||||||
|
} catch (e) {
|
||||||
|
e.stack;
|
||||||
|
this.hadError = true;
|
||||||
|
this.error = e;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Object.setPrototypeOf(ModuleJob.prototype, null);
|
||||||
|
module.exports = ModuleJob;
|
33
lib/internal/loader/ModuleMap.js
Normal file
33
lib/internal/loader/ModuleMap.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const ModuleJob = require('internal/loader/ModuleJob');
|
||||||
|
const { SafeMap } = require('internal/safe_globals');
|
||||||
|
const debug = require('util').debuglog('esm');
|
||||||
|
const errors = require('internal/errors');
|
||||||
|
|
||||||
|
// Tracks the state of the loader-level module cache
|
||||||
|
class ModuleMap extends SafeMap {
|
||||||
|
get(url) {
|
||||||
|
if (typeof url !== 'string') {
|
||||||
|
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'url', 'string');
|
||||||
|
}
|
||||||
|
return super.get(url);
|
||||||
|
}
|
||||||
|
set(url, job) {
|
||||||
|
if (typeof url !== 'string') {
|
||||||
|
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'url', 'string');
|
||||||
|
}
|
||||||
|
if (job instanceof ModuleJob !== true) {
|
||||||
|
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'job', 'ModuleJob');
|
||||||
|
}
|
||||||
|
debug(`Storing ${url} in ModuleMap`);
|
||||||
|
return super.set(url, job);
|
||||||
|
}
|
||||||
|
has(url) {
|
||||||
|
if (typeof url !== 'string') {
|
||||||
|
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'url', 'string');
|
||||||
|
}
|
||||||
|
return super.has(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = ModuleMap;
|
61
lib/internal/loader/ModuleWrap.js
Normal file
61
lib/internal/loader/ModuleWrap.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { ModuleWrap } = process.binding('module_wrap');
|
||||||
|
const debug = require('util').debuglog('esm');
|
||||||
|
const ArrayJoin = Function.call.bind(Array.prototype.join);
|
||||||
|
const ArrayMap = Function.call.bind(Array.prototype.map);
|
||||||
|
|
||||||
|
const getNamespaceOfModuleWrap = (m) => {
|
||||||
|
const tmp = new ModuleWrap('import * as _ from "";_;', '');
|
||||||
|
tmp.link(async () => m);
|
||||||
|
tmp.instantiate();
|
||||||
|
return tmp.evaluate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const createDynamicModule = (exports, url = '', evaluate) => {
|
||||||
|
debug(
|
||||||
|
`creating ESM facade for ${url} with exports: ${ArrayJoin(exports, ', ')}`
|
||||||
|
);
|
||||||
|
const names = ArrayMap(exports, (name) => `${name}`);
|
||||||
|
// sanitized ESM for reflection purposes
|
||||||
|
const src = `export let executor;
|
||||||
|
${ArrayJoin(ArrayMap(names, (name) => `export let $${name}`), ';\n')}
|
||||||
|
;(() => [
|
||||||
|
fn => executor = fn,
|
||||||
|
{ exports: { ${
|
||||||
|
ArrayJoin(ArrayMap(names, (name) => `${name}: {
|
||||||
|
get: () => $${name},
|
||||||
|
set: v => $${name} = v
|
||||||
|
}`), ',\n')
|
||||||
|
} } }
|
||||||
|
]);
|
||||||
|
`;
|
||||||
|
const reflectiveModule = new ModuleWrap(src, `cjs-facade:${url}`);
|
||||||
|
reflectiveModule.instantiate();
|
||||||
|
const [setExecutor, reflect] = reflectiveModule.evaluate()();
|
||||||
|
// public exposed ESM
|
||||||
|
const reexports = `import { executor,
|
||||||
|
${ArrayMap(names, (name) => `$${name}`)}
|
||||||
|
} from "";
|
||||||
|
export {
|
||||||
|
${ArrayJoin(ArrayMap(names, (name) => `$${name} as ${name}`), ', ')}
|
||||||
|
}
|
||||||
|
// add await to this later if top level await comes along
|
||||||
|
typeof executor === "function" ? executor() : void 0;`;
|
||||||
|
if (typeof evaluate === 'function') {
|
||||||
|
setExecutor(() => evaluate(reflect));
|
||||||
|
}
|
||||||
|
const runner = new ModuleWrap(reexports, `${url}`);
|
||||||
|
runner.link(async () => reflectiveModule);
|
||||||
|
runner.instantiate();
|
||||||
|
return {
|
||||||
|
module: runner,
|
||||||
|
reflect
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createDynamicModule,
|
||||||
|
getNamespaceOfModuleWrap,
|
||||||
|
ModuleWrap
|
||||||
|
};
|
104
lib/internal/loader/resolveRequestUrl.js
Normal file
104
lib/internal/loader/resolveRequestUrl.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { URL } = require('url');
|
||||||
|
const internalCJSModule = require('internal/module');
|
||||||
|
const internalURLModule = require('internal/url');
|
||||||
|
const internalFS = require('internal/fs');
|
||||||
|
const NativeModule = require('native_module');
|
||||||
|
const { extname } = require('path');
|
||||||
|
const { realpathSync } = require('fs');
|
||||||
|
const preserveSymlinks = !!process.binding('config').preserveSymlinks;
|
||||||
|
const {
|
||||||
|
ModuleWrap,
|
||||||
|
createDynamicModule
|
||||||
|
} = require('internal/loader/ModuleWrap');
|
||||||
|
const errors = require('internal/errors');
|
||||||
|
|
||||||
|
const search = require('internal/loader/search');
|
||||||
|
const asyncReadFile = require('util').promisify(require('fs').readFile);
|
||||||
|
const debug = require('util').debuglog('esm');
|
||||||
|
|
||||||
|
const realpathCache = new Map();
|
||||||
|
|
||||||
|
class ModuleRequest {
|
||||||
|
constructor(url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Object.setPrototypeOf(ModuleRequest.prototype, null);
|
||||||
|
|
||||||
|
// Strategy for loading a standard JavaScript module
|
||||||
|
class StandardModuleRequest extends ModuleRequest {
|
||||||
|
async createModule() {
|
||||||
|
const source = `${await asyncReadFile(this.url)}`;
|
||||||
|
debug(`Loading StandardModule ${this.url}`);
|
||||||
|
return new ModuleWrap(internalCJSModule.stripShebang(source),
|
||||||
|
`${this.url}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy for loading a node-style CommonJS module
|
||||||
|
class CJSModuleRequest extends ModuleRequest {
|
||||||
|
async createModule() {
|
||||||
|
const ctx = createDynamicModule(['default'], this.url, (reflect) => {
|
||||||
|
debug(`Loading CJSModule ${this.url.pathname}`);
|
||||||
|
const CJSModule = require('module');
|
||||||
|
const pathname = internalURLModule.getPathFromURL(this.url);
|
||||||
|
CJSModule._load(pathname);
|
||||||
|
});
|
||||||
|
this.finish = (module) => {
|
||||||
|
ctx.reflect.exports.default.set(module.exports);
|
||||||
|
};
|
||||||
|
return ctx.module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy for loading a node builtin CommonJS module that isn't
|
||||||
|
// through normal resolution
|
||||||
|
class NativeModuleRequest extends CJSModuleRequest {
|
||||||
|
async createModule() {
|
||||||
|
const ctx = createDynamicModule(['default'], this.url, (reflect) => {
|
||||||
|
debug(`Loading NativeModule ${this.url.pathname}`);
|
||||||
|
const exports = require(this.url.pathname);
|
||||||
|
reflect.exports.default.set(exports);
|
||||||
|
});
|
||||||
|
return ctx.module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeBaseURL = (baseURLOrString) => {
|
||||||
|
if (baseURLOrString instanceof URL) return baseURLOrString;
|
||||||
|
if (typeof baseURLOrString === 'string') return new URL(baseURLOrString);
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveRequestUrl = (baseURLOrString, specifier) => {
|
||||||
|
if (NativeModule.nonInternalExists(specifier)) {
|
||||||
|
return new NativeModuleRequest(new URL(`node:${specifier}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseURL = normalizeBaseURL(baseURLOrString);
|
||||||
|
let url = search(specifier, baseURL);
|
||||||
|
|
||||||
|
if (url.protocol !== 'file:') {
|
||||||
|
throw new errors.Error('ERR_INVALID_PROTOCOL', url.protocol, 'file:');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!preserveSymlinks) {
|
||||||
|
const real = realpathSync(internalURLModule.getPathFromURL(url), {
|
||||||
|
[internalFS.realpathCacheKey]: realpathCache
|
||||||
|
});
|
||||||
|
const old = url;
|
||||||
|
url = internalURLModule.getURLFromFilePath(real);
|
||||||
|
url.search = old.search;
|
||||||
|
url.hash = old.hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = extname(url.pathname);
|
||||||
|
if (ext === '.mjs') {
|
||||||
|
return new StandardModuleRequest(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CJSModuleRequest(url);
|
||||||
|
};
|
||||||
|
module.exports = resolveRequestUrl;
|
33
lib/internal/loader/search.js
Normal file
33
lib/internal/loader/search.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { URL } = require('url');
|
||||||
|
const CJSmodule = require('module');
|
||||||
|
const errors = require('internal/errors');
|
||||||
|
const { resolve } = process.binding('module_wrap');
|
||||||
|
|
||||||
|
module.exports = (target, base) => {
|
||||||
|
target = `${target}`;
|
||||||
|
if (base === undefined) {
|
||||||
|
// We cannot search without a base.
|
||||||
|
throw new errors.Error('ERR_MISSING_MODULE', target);
|
||||||
|
}
|
||||||
|
base = `${base}`;
|
||||||
|
try {
|
||||||
|
return resolve(target, base);
|
||||||
|
} catch (e) {
|
||||||
|
e.stack; // cause V8 to generate stack before rethrow
|
||||||
|
let error = e;
|
||||||
|
try {
|
||||||
|
const questionedBase = new URL(base);
|
||||||
|
const tmpMod = new CJSmodule(questionedBase.pathname, null);
|
||||||
|
tmpMod.paths = CJSmodule._nodeModulePaths(
|
||||||
|
new URL('./', questionedBase).pathname);
|
||||||
|
const found = CJSmodule._resolveFilename(target, tmpMod);
|
||||||
|
error = new errors.Error('ERR_MODULE_RESOLUTION_LEGACY', target,
|
||||||
|
base, found);
|
||||||
|
} catch (problemChecking) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
26
lib/internal/safe_globals.js
Normal file
26
lib/internal/safe_globals.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const copyProps = (unsafe, safe) => {
|
||||||
|
for (const key of [...Object.getOwnPropertyNames(unsafe),
|
||||||
|
...Object.getOwnPropertySymbols(unsafe)
|
||||||
|
]) {
|
||||||
|
if (!Object.getOwnPropertyDescriptor(safe, key)) {
|
||||||
|
Object.defineProperty(
|
||||||
|
safe,
|
||||||
|
key,
|
||||||
|
Object.getOwnPropertyDescriptor(unsafe, key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const makeSafe = (unsafe, safe) => {
|
||||||
|
copyProps(unsafe.prototype, safe.prototype);
|
||||||
|
copyProps(unsafe, safe);
|
||||||
|
Object.setPrototypeOf(safe.prototype, null);
|
||||||
|
Object.freeze(safe.prototype);
|
||||||
|
Object.freeze(safe);
|
||||||
|
return safe;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.SafeMap = makeSafe(Map, class SafeMap extends Map {});
|
||||||
|
exports.SafeSet = makeSafe(Set, class SafeSet extends Set {});
|
||||||
|
exports.SafePromise = makeSafe(Promise, class SafePromise extends Promise {});
|
@ -1377,6 +1377,12 @@ function getPathFromURL(path) {
|
|||||||
return isWindows ? getPathFromURLWin32(path) : getPathFromURLPosix(path);
|
return isWindows ? getPathFromURLWin32(path) : getPathFromURLPosix(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getURLFromFilePath(filepath) {
|
||||||
|
const tmp = new URL('file://');
|
||||||
|
tmp.pathname = filepath;
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
function NativeURL(ctx) {
|
function NativeURL(ctx) {
|
||||||
this[context] = ctx;
|
this[context] = ctx;
|
||||||
}
|
}
|
||||||
@ -1405,6 +1411,7 @@ setURLConstructor(constructUrl);
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
toUSVString,
|
toUSVString,
|
||||||
getPathFromURL,
|
getPathFromURL,
|
||||||
|
getURLFromFilePath,
|
||||||
URL,
|
URL,
|
||||||
URLSearchParams,
|
URLSearchParams,
|
||||||
domainToASCII,
|
domainToASCII,
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
const NativeModule = require('native_module');
|
const NativeModule = require('native_module');
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
const internalModule = require('internal/module');
|
const internalModule = require('internal/module');
|
||||||
|
const { getURLFromFilePath } = require('internal/url');
|
||||||
const vm = require('vm');
|
const vm = require('vm');
|
||||||
const assert = require('assert').ok;
|
const assert = require('assert').ok;
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@ -32,6 +33,14 @@ const path = require('path');
|
|||||||
const internalModuleReadFile = process.binding('fs').internalModuleReadFile;
|
const internalModuleReadFile = process.binding('fs').internalModuleReadFile;
|
||||||
const internalModuleStat = process.binding('fs').internalModuleStat;
|
const internalModuleStat = process.binding('fs').internalModuleStat;
|
||||||
const preserveSymlinks = !!process.binding('config').preserveSymlinks;
|
const preserveSymlinks = !!process.binding('config').preserveSymlinks;
|
||||||
|
const experimentalModules = !!process.binding('config').experimentalModules;
|
||||||
|
|
||||||
|
const errors = require('internal/errors');
|
||||||
|
|
||||||
|
const Loader = require('internal/loader/Loader');
|
||||||
|
const ModuleJob = require('internal/loader/ModuleJob');
|
||||||
|
const { createDynamicModule } = require('internal/loader/ModuleWrap');
|
||||||
|
const ESMLoader = new Loader();
|
||||||
|
|
||||||
function stat(filename) {
|
function stat(filename) {
|
||||||
filename = path._makeLong(filename);
|
filename = path._makeLong(filename);
|
||||||
@ -412,7 +421,36 @@ Module._load = function(request, parent, isMain) {
|
|||||||
debug('Module._load REQUEST %s parent: %s', request, parent.id);
|
debug('Module._load REQUEST %s parent: %s', request, parent.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
var filename = Module._resolveFilename(request, parent, isMain);
|
var filename = null;
|
||||||
|
|
||||||
|
if (isMain) {
|
||||||
|
let err;
|
||||||
|
try {
|
||||||
|
filename = Module._resolveFilename(request, parent, isMain);
|
||||||
|
} catch (e) {
|
||||||
|
// try to keep stack
|
||||||
|
e.stack;
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
if (experimentalModules) {
|
||||||
|
if (filename === null || /\.mjs$/.test(filename)) {
|
||||||
|
try {
|
||||||
|
ESMLoader.import(request).catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
// well, it isn't ESM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filename = Module._resolveFilename(request, parent, isMain);
|
||||||
|
}
|
||||||
|
|
||||||
var cachedModule = Module._cache[filename];
|
var cachedModule = Module._cache[filename];
|
||||||
if (cachedModule) {
|
if (cachedModule) {
|
||||||
@ -482,6 +520,19 @@ Module.prototype.load = function(filename) {
|
|||||||
if (!Module._extensions[extension]) extension = '.js';
|
if (!Module._extensions[extension]) extension = '.js';
|
||||||
Module._extensions[extension](this, filename);
|
Module._extensions[extension](this, filename);
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
|
|
||||||
|
if (experimentalModules) {
|
||||||
|
const url = getURLFromFilePath(filename);
|
||||||
|
if (ESMLoader.moduleMap.has(`${url}`) !== true) {
|
||||||
|
const ctx = createDynamicModule(['default'], url);
|
||||||
|
ctx.reflect.exports.default.set(this.exports);
|
||||||
|
ESMLoader.moduleMap.set(`${url}`,
|
||||||
|
new ModuleJob(ESMLoader, ctx.module));
|
||||||
|
} else {
|
||||||
|
ESMLoader.moduleMap.get(`${url}`).moduleProvider.finish(
|
||||||
|
Module._cache[filename]);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -578,6 +629,11 @@ Module._extensions['.node'] = function(module, filename) {
|
|||||||
return process.dlopen(module, path._makeLong(filename));
|
return process.dlopen(module, path._makeLong(filename));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (experimentalModules) {
|
||||||
|
Module._extensions['.mjs'] = function(module, filename) {
|
||||||
|
throw new errors.Error('ERR_REQUIRE_ESM', filename);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// bootstrap main module.
|
// bootstrap main module.
|
||||||
Module.runMain = function() {
|
Module.runMain = function() {
|
||||||
|
9
node.gyp
9
node.gyp
@ -91,6 +91,13 @@
|
|||||||
'lib/internal/http.js',
|
'lib/internal/http.js',
|
||||||
'lib/internal/inspector_async_hook.js',
|
'lib/internal/inspector_async_hook.js',
|
||||||
'lib/internal/linkedlist.js',
|
'lib/internal/linkedlist.js',
|
||||||
|
'lib/internal/loader/Loader.js',
|
||||||
|
'lib/internal/loader/ModuleMap.js',
|
||||||
|
'lib/internal/loader/ModuleJob.js',
|
||||||
|
'lib/internal/loader/ModuleWrap.js',
|
||||||
|
'lib/internal/loader/resolveRequestUrl.js',
|
||||||
|
'lib/internal/loader/search.js',
|
||||||
|
'lib/internal/safe_globals.js',
|
||||||
'lib/internal/net.js',
|
'lib/internal/net.js',
|
||||||
'lib/internal/module.js',
|
'lib/internal/module.js',
|
||||||
'lib/internal/os.js',
|
'lib/internal/os.js',
|
||||||
@ -177,6 +184,7 @@
|
|||||||
'src/fs_event_wrap.cc',
|
'src/fs_event_wrap.cc',
|
||||||
'src/handle_wrap.cc',
|
'src/handle_wrap.cc',
|
||||||
'src/js_stream.cc',
|
'src/js_stream.cc',
|
||||||
|
'src/module_wrap.cc',
|
||||||
'src/node.cc',
|
'src/node.cc',
|
||||||
'src/node_api.cc',
|
'src/node_api.cc',
|
||||||
'src/node_api.h',
|
'src/node_api.h',
|
||||||
@ -230,6 +238,7 @@
|
|||||||
'src/env-inl.h',
|
'src/env-inl.h',
|
||||||
'src/handle_wrap.h',
|
'src/handle_wrap.h',
|
||||||
'src/js_stream.h',
|
'src/js_stream.h',
|
||||||
|
'src/module_wrap.h',
|
||||||
'src/node.h',
|
'src/node.h',
|
||||||
'src/node_http2_core.h',
|
'src/node_http2_core.h',
|
||||||
'src/node_http2_core-inl.h',
|
'src/node_http2_core-inl.h',
|
||||||
|
531
src/module_wrap.cc
Normal file
531
src/module_wrap.cc
Normal file
@ -0,0 +1,531 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <limits.h> // PATH_MAX
|
||||||
|
#include <sys/stat.h> // S_IFDIR
|
||||||
|
#include "module_wrap.h"
|
||||||
|
|
||||||
|
#include "env.h"
|
||||||
|
#include "node_url.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "util-inl.h"
|
||||||
|
|
||||||
|
namespace node {
|
||||||
|
namespace loader {
|
||||||
|
|
||||||
|
using node::url::URL;
|
||||||
|
using node::url::URL_FLAGS_FAILED;
|
||||||
|
using v8::Context;
|
||||||
|
using v8::EscapableHandleScope;
|
||||||
|
using v8::Function;
|
||||||
|
using v8::FunctionCallbackInfo;
|
||||||
|
using v8::FunctionTemplate;
|
||||||
|
using v8::Integer;
|
||||||
|
using v8::IntegrityLevel;
|
||||||
|
using v8::Isolate;
|
||||||
|
using v8::JSON;
|
||||||
|
using v8::Local;
|
||||||
|
using v8::MaybeLocal;
|
||||||
|
using v8::Module;
|
||||||
|
using v8::Object;
|
||||||
|
using v8::Persistent;
|
||||||
|
using v8::Promise;
|
||||||
|
using v8::ScriptCompiler;
|
||||||
|
using v8::ScriptOrigin;
|
||||||
|
using v8::String;
|
||||||
|
using v8::Value;
|
||||||
|
|
||||||
|
static const char* EXTENSIONS[] = {".mjs", ".js", ".json", ".node"};
|
||||||
|
std::map<int, std::vector<ModuleWrap*>*> ModuleWrap::module_map_;
|
||||||
|
|
||||||
|
ModuleWrap::ModuleWrap(Environment* env,
|
||||||
|
Local<Object> object,
|
||||||
|
Local<Module> module,
|
||||||
|
Local<String> url) : BaseObject(env, object) {
|
||||||
|
Isolate* iso = Isolate::GetCurrent();
|
||||||
|
module_.Reset(iso, module);
|
||||||
|
url_.Reset(iso, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModuleWrap::~ModuleWrap() {
|
||||||
|
Local<Module> module = module_.Get(Isolate::GetCurrent());
|
||||||
|
std::vector<ModuleWrap*>* same_hash = module_map_[module->GetIdentityHash()];
|
||||||
|
auto it = std::find(same_hash->begin(), same_hash->end(), this);
|
||||||
|
|
||||||
|
if (it != same_hash->end()) {
|
||||||
|
same_hash->erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
|
||||||
|
Isolate* iso = args.GetIsolate();
|
||||||
|
|
||||||
|
if (!args.IsConstructCall()) {
|
||||||
|
env->ThrowError("constructor must be called using new");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Length() != 2) {
|
||||||
|
env->ThrowError("constructor must have exactly 2 arguments "
|
||||||
|
"(string, string)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args[0]->IsString()) {
|
||||||
|
env->ThrowError("first argument is not a string");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto source_text = args[0].As<String>();
|
||||||
|
|
||||||
|
if (!args[1]->IsString()) {
|
||||||
|
env->ThrowError("second argument is not a string");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<String> url = args[1].As<String>();
|
||||||
|
|
||||||
|
Local<Module> mod;
|
||||||
|
|
||||||
|
// compile
|
||||||
|
{
|
||||||
|
ScriptOrigin origin(url,
|
||||||
|
Integer::New(iso, 0),
|
||||||
|
Integer::New(iso, 0),
|
||||||
|
False(iso),
|
||||||
|
Integer::New(iso, 0),
|
||||||
|
FIXED_ONE_BYTE_STRING(iso, ""),
|
||||||
|
False(iso),
|
||||||
|
False(iso),
|
||||||
|
True(iso));
|
||||||
|
ScriptCompiler::Source source(source_text, origin);
|
||||||
|
auto maybe_mod = ScriptCompiler::CompileModule(iso, &source);
|
||||||
|
if (maybe_mod.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mod = maybe_mod.ToLocalChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto that = args.This();
|
||||||
|
auto ctx = that->CreationContext();
|
||||||
|
auto url_str = FIXED_ONE_BYTE_STRING(iso, "url");
|
||||||
|
|
||||||
|
if (!that->Set(ctx, url_str, url).FromMaybe(false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModuleWrap* obj =
|
||||||
|
new ModuleWrap(Environment::GetCurrent(ctx), that, mod, url);
|
||||||
|
|
||||||
|
if (ModuleWrap::module_map_.count(mod->GetIdentityHash()) == 0) {
|
||||||
|
ModuleWrap::module_map_[mod->GetIdentityHash()] =
|
||||||
|
new std::vector<ModuleWrap*>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ModuleWrap::module_map_[mod->GetIdentityHash()]->push_back(obj);
|
||||||
|
Wrap(that, obj);
|
||||||
|
|
||||||
|
that->SetIntegrityLevel(ctx, IntegrityLevel::kFrozen);
|
||||||
|
args.GetReturnValue().Set(that);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
Isolate* iso = args.GetIsolate();
|
||||||
|
EscapableHandleScope handle_scope(iso);
|
||||||
|
if (!args[0]->IsFunction()) {
|
||||||
|
env->ThrowError("first argument is not a function");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Function> resolver_arg = args[0].As<Function>();
|
||||||
|
|
||||||
|
auto that = args.This();
|
||||||
|
ModuleWrap* obj = Unwrap<ModuleWrap>(that);
|
||||||
|
auto mod_context = that->CreationContext();
|
||||||
|
if (obj->linked_) return;
|
||||||
|
obj->linked_ = true;
|
||||||
|
Local<Module> mod(obj->module_.Get(iso));
|
||||||
|
|
||||||
|
// call the dependency resolve callbacks
|
||||||
|
for (int i = 0; i < mod->GetModuleRequestsLength(); i++) {
|
||||||
|
Local<String> specifier = mod->GetModuleRequest(i);
|
||||||
|
Utf8Value specifier_utf(env->isolate(), specifier);
|
||||||
|
std::string specifier_std(*specifier_utf, specifier_utf.length());
|
||||||
|
|
||||||
|
Local<Value> argv[] = {
|
||||||
|
specifier
|
||||||
|
};
|
||||||
|
|
||||||
|
MaybeLocal<Value> maybe_resolve_return_value =
|
||||||
|
resolver_arg->Call(mod_context, that, 1, argv);
|
||||||
|
if (maybe_resolve_return_value.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Local<Value> resolve_return_value =
|
||||||
|
maybe_resolve_return_value.ToLocalChecked();
|
||||||
|
if (!resolve_return_value->IsPromise()) {
|
||||||
|
env->ThrowError("linking error, expected resolver to return a promise");
|
||||||
|
}
|
||||||
|
Local<Promise> resolve_promise = resolve_return_value.As<Promise>();
|
||||||
|
obj->resolve_cache_[specifier_std] = new Persistent<Promise>();
|
||||||
|
obj->resolve_cache_[specifier_std]->Reset(iso, resolve_promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.GetReturnValue().Set(handle_scope.Escape(that));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
auto iso = args.GetIsolate();
|
||||||
|
auto that = args.This();
|
||||||
|
auto ctx = that->CreationContext();
|
||||||
|
|
||||||
|
ModuleWrap* obj = Unwrap<ModuleWrap>(that);
|
||||||
|
Local<Module> mod = obj->module_.Get(iso);
|
||||||
|
bool ok = mod->Instantiate(ctx, ModuleWrap::ResolveCallback);
|
||||||
|
|
||||||
|
// clear resolve cache on instantiate
|
||||||
|
obj->resolve_cache_.clear();
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
auto iso = args.GetIsolate();
|
||||||
|
auto that = args.This();
|
||||||
|
auto ctx = that->CreationContext();
|
||||||
|
ModuleWrap* obj = Unwrap<ModuleWrap>(that);
|
||||||
|
auto result = obj->module_.Get(iso)->Evaluate(ctx);
|
||||||
|
|
||||||
|
if (result.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ret = result.ToLocalChecked();
|
||||||
|
args.GetReturnValue().Set(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
MaybeLocal<Module> ModuleWrap::ResolveCallback(Local<Context> context,
|
||||||
|
Local<String> specifier,
|
||||||
|
Local<Module> referrer) {
|
||||||
|
Environment* env = Environment::GetCurrent(context);
|
||||||
|
Isolate* iso = Isolate::GetCurrent();
|
||||||
|
if (ModuleWrap::module_map_.count(referrer->GetIdentityHash()) == 0) {
|
||||||
|
env->ThrowError("linking error, unknown module");
|
||||||
|
return MaybeLocal<Module>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ModuleWrap*>* possible_deps =
|
||||||
|
ModuleWrap::module_map_[referrer->GetIdentityHash()];
|
||||||
|
ModuleWrap* dependent = nullptr;
|
||||||
|
|
||||||
|
for (auto possible_dep : *possible_deps) {
|
||||||
|
if (possible_dep->module_ == referrer) {
|
||||||
|
dependent = possible_dep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dependent == nullptr) {
|
||||||
|
env->ThrowError("linking error, null dep");
|
||||||
|
return MaybeLocal<Module>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Utf8Value specifier_utf(env->isolate(), specifier);
|
||||||
|
std::string specifier_std(*specifier_utf, specifier_utf.length());
|
||||||
|
|
||||||
|
if (dependent->resolve_cache_.count(specifier_std) != 1) {
|
||||||
|
env->ThrowError("linking error, not in local cache");
|
||||||
|
return MaybeLocal<Module>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Promise> resolve_promise =
|
||||||
|
dependent->resolve_cache_[specifier_std]->Get(iso);
|
||||||
|
|
||||||
|
if (resolve_promise->State() != Promise::kFulfilled) {
|
||||||
|
env->ThrowError("linking error, dependency promises must be resolved on "
|
||||||
|
"instantiate");
|
||||||
|
return MaybeLocal<Module>();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto module_object = resolve_promise->Result().As<Object>();
|
||||||
|
if (module_object.IsEmpty() || !module_object->IsObject()) {
|
||||||
|
env->ThrowError("linking error, expected a valid module object from "
|
||||||
|
"resolver");
|
||||||
|
return MaybeLocal<Module>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ModuleWrap* mod;
|
||||||
|
ASSIGN_OR_RETURN_UNWRAP(&mod, module_object, MaybeLocal<Module>());
|
||||||
|
return mod->module_.Get(env->isolate());
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
URL __init_cwd() {
|
||||||
|
std::string specifier = "file://";
|
||||||
|
#ifdef _WIN32
|
||||||
|
// MAX_PATH is in characters, not bytes. Make sure we have enough headroom.
|
||||||
|
char buf[MAX_PATH * 4];
|
||||||
|
#else
|
||||||
|
char buf[PATH_MAX];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
size_t cwd_len = sizeof(buf);
|
||||||
|
int err = uv_cwd(buf, &cwd_len);
|
||||||
|
if (err) {
|
||||||
|
return URL("");
|
||||||
|
}
|
||||||
|
specifier += buf;
|
||||||
|
specifier += "/";
|
||||||
|
return URL(specifier);
|
||||||
|
}
|
||||||
|
static URL INITIAL_CWD(__init_cwd());
|
||||||
|
inline bool is_relative_or_absolute_path(std::string specifier) {
|
||||||
|
auto len = specifier.length();
|
||||||
|
if (len <= 0) {
|
||||||
|
return false;
|
||||||
|
} else if (specifier[0] == '/') {
|
||||||
|
return true;
|
||||||
|
} else if (specifier[0] == '.') {
|
||||||
|
if (len == 1 || specifier[1] == '/') {
|
||||||
|
return true;
|
||||||
|
} else if (specifier[1] == '.') {
|
||||||
|
if (len == 2 || specifier[2] == '/') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
struct read_result {
|
||||||
|
bool had_error = false;
|
||||||
|
std::string source;
|
||||||
|
} read_result;
|
||||||
|
inline const struct read_result read_file(uv_file file) {
|
||||||
|
struct read_result ret;
|
||||||
|
std::string src;
|
||||||
|
uv_fs_t req;
|
||||||
|
void* base = malloc(4096);
|
||||||
|
if (base == nullptr) {
|
||||||
|
ret.had_error = true;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
uv_buf_t buf = uv_buf_init(static_cast<char*>(base), 4096);
|
||||||
|
uv_fs_read(uv_default_loop(), &req, file, &buf, 1, 0, nullptr);
|
||||||
|
while (req.result > 0) {
|
||||||
|
src += std::string(static_cast<const char*>(buf.base), req.result);
|
||||||
|
uv_fs_read(uv_default_loop(), &req, file, &buf, 1, src.length(), nullptr);
|
||||||
|
}
|
||||||
|
ret.source = src;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
struct file_check {
|
||||||
|
bool failed = true;
|
||||||
|
uv_file file;
|
||||||
|
} file_check;
|
||||||
|
inline const struct file_check check_file(URL search,
|
||||||
|
bool close = false,
|
||||||
|
bool allow_dir = false) {
|
||||||
|
struct file_check ret;
|
||||||
|
uv_fs_t fs_req;
|
||||||
|
std::string path = search.ToFilePath();
|
||||||
|
if (path.empty()) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
uv_fs_open(nullptr, &fs_req, path.c_str(), O_RDONLY, 0, nullptr);
|
||||||
|
auto fd = fs_req.result;
|
||||||
|
if (fd < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (!allow_dir) {
|
||||||
|
uv_fs_fstat(nullptr, &fs_req, fd, nullptr);
|
||||||
|
if (fs_req.statbuf.st_mode & S_IFDIR) {
|
||||||
|
uv_fs_close(nullptr, &fs_req, fd, nullptr);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret.failed = false;
|
||||||
|
ret.file = fd;
|
||||||
|
if (close) uv_fs_close(nullptr, &fs_req, fd, nullptr);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
URL resolve_extensions(URL search, bool check_exact = true) {
|
||||||
|
if (check_exact) {
|
||||||
|
auto check = check_file(search, true);
|
||||||
|
if (!check.failed) {
|
||||||
|
return search;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto extension : EXTENSIONS) {
|
||||||
|
URL guess(search.path() + extension, &search);
|
||||||
|
auto check = check_file(guess, true);
|
||||||
|
if (!check.failed) {
|
||||||
|
return guess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return URL("");
|
||||||
|
}
|
||||||
|
inline URL resolve_index(URL search) {
|
||||||
|
return resolve_extensions(URL("index", &search), false);
|
||||||
|
}
|
||||||
|
URL resolve_main(URL search) {
|
||||||
|
URL pkg("package.json", &search);
|
||||||
|
auto check = check_file(pkg);
|
||||||
|
if (!check.failed) {
|
||||||
|
auto iso = Isolate::GetCurrent();
|
||||||
|
auto ctx = iso->GetCurrentContext();
|
||||||
|
auto read = read_file(check.file);
|
||||||
|
uv_fs_t fs_req;
|
||||||
|
// if we fail to close :-/
|
||||||
|
uv_fs_close(nullptr, &fs_req, check.file, nullptr);
|
||||||
|
if (read.had_error) return URL("");
|
||||||
|
std::string pkg_src = read.source;
|
||||||
|
Local<String> src =
|
||||||
|
String::NewFromUtf8(iso, pkg_src.c_str(),
|
||||||
|
String::kNormalString, pkg_src.length());
|
||||||
|
if (src.IsEmpty()) return URL("");
|
||||||
|
auto maybe_pkg_json = JSON::Parse(ctx, src);
|
||||||
|
if (maybe_pkg_json.IsEmpty()) return URL("");
|
||||||
|
auto pkg_json_obj = maybe_pkg_json.ToLocalChecked().As<Object>();
|
||||||
|
if (!pkg_json_obj->IsObject()) return URL("");
|
||||||
|
auto maybe_pkg_main = pkg_json_obj->Get(
|
||||||
|
ctx, FIXED_ONE_BYTE_STRING(iso, "main"));
|
||||||
|
if (maybe_pkg_main.IsEmpty()) return URL("");
|
||||||
|
auto pkg_main_str = maybe_pkg_main.ToLocalChecked().As<String>();
|
||||||
|
if (!pkg_main_str->IsString()) return URL("");
|
||||||
|
Utf8Value main_utf8(iso, pkg_main_str);
|
||||||
|
std::string main_std(*main_utf8, main_utf8.length());
|
||||||
|
if (!is_relative_or_absolute_path(main_std)) {
|
||||||
|
main_std.insert(0, "./");
|
||||||
|
}
|
||||||
|
return Resolve(main_std, &search);
|
||||||
|
}
|
||||||
|
return URL("");
|
||||||
|
}
|
||||||
|
URL resolve_module(std::string specifier, URL* base) {
|
||||||
|
URL parent(".", base);
|
||||||
|
URL dir("");
|
||||||
|
do {
|
||||||
|
dir = parent;
|
||||||
|
auto check = Resolve("./node_modules/" + specifier, &dir, true);
|
||||||
|
if (!(check.flags() & URL_FLAGS_FAILED)) {
|
||||||
|
const auto limit = specifier.find('/');
|
||||||
|
const auto spec_len = limit == std::string::npos ?
|
||||||
|
specifier.length() :
|
||||||
|
limit + 1;
|
||||||
|
std::string chroot =
|
||||||
|
dir.path() + "node_modules/" + specifier.substr(0, spec_len);
|
||||||
|
if (check.path().substr(0, chroot.length()) != chroot) {
|
||||||
|
return URL("");
|
||||||
|
}
|
||||||
|
return check;
|
||||||
|
} else {
|
||||||
|
// TODO(bmeck) PREVENT FALLTHROUGH
|
||||||
|
}
|
||||||
|
parent = URL("..", &dir);
|
||||||
|
} while (parent.path() != dir.path());
|
||||||
|
return URL("");
|
||||||
|
}
|
||||||
|
|
||||||
|
URL resolve_directory(URL search, bool read_pkg_json) {
|
||||||
|
if (read_pkg_json) {
|
||||||
|
auto main = resolve_main(search);
|
||||||
|
if (!(main.flags() & URL_FLAGS_FAILED)) return main;
|
||||||
|
}
|
||||||
|
return resolve_index(search);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
|
||||||
|
URL Resolve(std::string specifier, URL* base, bool read_pkg_json) {
|
||||||
|
URL pure_url(specifier);
|
||||||
|
if (!(pure_url.flags() & URL_FLAGS_FAILED)) {
|
||||||
|
return pure_url;
|
||||||
|
}
|
||||||
|
if (specifier.length() == 0) {
|
||||||
|
return URL("");
|
||||||
|
}
|
||||||
|
if (is_relative_or_absolute_path(specifier)) {
|
||||||
|
URL resolved(specifier, base);
|
||||||
|
auto file = resolve_extensions(resolved);
|
||||||
|
if (!(file.flags() & URL_FLAGS_FAILED)) return file;
|
||||||
|
if (specifier.back() != '/') {
|
||||||
|
resolved = URL(specifier + "/", base);
|
||||||
|
}
|
||||||
|
return resolve_directory(resolved, read_pkg_json);
|
||||||
|
} else {
|
||||||
|
return resolve_module(specifier, base);
|
||||||
|
}
|
||||||
|
return URL("");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModuleWrap::Resolve(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
|
||||||
|
if (args.IsConstructCall()) {
|
||||||
|
env->ThrowError("resolve() must not be called as a constructor");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (args.Length() != 2) {
|
||||||
|
env->ThrowError("resolve must have exactly 2 arguments (string, string)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args[0]->IsString()) {
|
||||||
|
env->ThrowError("first argument is not a string");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Utf8Value specifier_utf(env->isolate(), args[0]);
|
||||||
|
|
||||||
|
if (!args[1]->IsString()) {
|
||||||
|
env->ThrowError("second argument is not a string");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Utf8Value url_utf(env->isolate(), args[1]);
|
||||||
|
URL url(*url_utf, url_utf.length());
|
||||||
|
|
||||||
|
if (url.flags() & URL_FLAGS_FAILED) {
|
||||||
|
env->ThrowError("second argument is not a URL string");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
URL result = node::loader::Resolve(*specifier_utf, &url, true);
|
||||||
|
if (result.flags() & URL_FLAGS_FAILED) {
|
||||||
|
std::string msg = "module ";
|
||||||
|
msg += *specifier_utf;
|
||||||
|
msg += " not found";
|
||||||
|
env->ThrowError(msg.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
args.GetReturnValue().Set(result.ToObject(env));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModuleWrap::Initialize(Local<Object> target,
|
||||||
|
Local<Value> unused,
|
||||||
|
Local<Context> context) {
|
||||||
|
Environment* env = Environment::GetCurrent(context);
|
||||||
|
Isolate* isolate = env->isolate();
|
||||||
|
|
||||||
|
Local<FunctionTemplate> tpl = env->NewFunctionTemplate(New);
|
||||||
|
tpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "ModuleWrap"));
|
||||||
|
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
|
|
||||||
|
env->SetProtoMethod(tpl, "link", Link);
|
||||||
|
env->SetProtoMethod(tpl, "instantiate", Instantiate);
|
||||||
|
env->SetProtoMethod(tpl, "evaluate", Evaluate);
|
||||||
|
|
||||||
|
target->Set(FIXED_ONE_BYTE_STRING(isolate, "ModuleWrap"), tpl->GetFunction());
|
||||||
|
env->SetMethod(target, "resolve", node::loader::ModuleWrap::Resolve);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace loader
|
||||||
|
} // namespace node
|
||||||
|
|
||||||
|
NODE_MODULE_CONTEXT_AWARE_BUILTIN(module_wrap,
|
||||||
|
node::loader::ModuleWrap::Initialize)
|
58
src/module_wrap.h
Normal file
58
src/module_wrap.h
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#ifndef SRC_MODULE_WRAP_H_
|
||||||
|
#define SRC_MODULE_WRAP_H_
|
||||||
|
|
||||||
|
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "node_url.h"
|
||||||
|
#include "base-object.h"
|
||||||
|
#include "base-object-inl.h"
|
||||||
|
|
||||||
|
namespace node {
|
||||||
|
namespace loader {
|
||||||
|
|
||||||
|
node::url::URL Resolve(std::string specifier, node::url::URL* base,
|
||||||
|
bool read_pkg_json = false);
|
||||||
|
|
||||||
|
class ModuleWrap : public BaseObject {
|
||||||
|
public:
|
||||||
|
static const std::string EXTENSIONS[];
|
||||||
|
static void Initialize(v8::Local<v8::Object> target,
|
||||||
|
v8::Local<v8::Value> unused,
|
||||||
|
v8::Local<v8::Context> context);
|
||||||
|
|
||||||
|
private:
|
||||||
|
ModuleWrap(node::Environment* env,
|
||||||
|
v8::Local<v8::Object> object,
|
||||||
|
v8::Local<v8::Module> module,
|
||||||
|
v8::Local<v8::String> url);
|
||||||
|
~ModuleWrap();
|
||||||
|
|
||||||
|
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void Link(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void Instantiate(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void Evaluate(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void GetUrl(v8::Local<v8::String> property,
|
||||||
|
const v8::PropertyCallbackInfo<v8::Value>& info);
|
||||||
|
static void Resolve(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static v8::MaybeLocal<v8::Module> ResolveCallback(
|
||||||
|
v8::Local<v8::Context> context,
|
||||||
|
v8::Local<v8::String> specifier,
|
||||||
|
v8::Local<v8::Module> referrer);
|
||||||
|
|
||||||
|
v8::Persistent<v8::Module> module_;
|
||||||
|
v8::Persistent<v8::String> url_;
|
||||||
|
bool linked_ = false;
|
||||||
|
std::map<std::string, v8::Persistent<v8::Promise>*> resolve_cache_;
|
||||||
|
|
||||||
|
static std::map<int, std::vector<ModuleWrap*>*> module_map_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace loader
|
||||||
|
} // namespace node
|
||||||
|
|
||||||
|
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
|
||||||
|
#endif // SRC_MODULE_WRAP_H_
|
@ -225,6 +225,11 @@ bool trace_warnings = false;
|
|||||||
// that is used by lib/module.js
|
// that is used by lib/module.js
|
||||||
bool config_preserve_symlinks = false;
|
bool config_preserve_symlinks = false;
|
||||||
|
|
||||||
|
// Set in node.cc by ParseArgs when --experimental-modules is used.
|
||||||
|
// Used in node_config.cc to set a constant on process.binding('config')
|
||||||
|
// that is used by lib/module.js
|
||||||
|
bool config_experimental_modules = false;
|
||||||
|
|
||||||
// Set by ParseArgs when --pending-deprecation or NODE_PENDING_DEPRECATION
|
// Set by ParseArgs when --pending-deprecation or NODE_PENDING_DEPRECATION
|
||||||
// is used.
|
// is used.
|
||||||
bool config_pending_deprecation = false;
|
bool config_pending_deprecation = false;
|
||||||
@ -3711,6 +3716,7 @@ static void PrintHelp() {
|
|||||||
" note: linked-in ICU data is present\n"
|
" note: linked-in ICU data is present\n"
|
||||||
#endif
|
#endif
|
||||||
" --preserve-symlinks preserve symbolic links when resolving\n"
|
" --preserve-symlinks preserve symbolic links when resolving\n"
|
||||||
|
" --experimental-modules experimental ES Module support\n"
|
||||||
" and caching modules\n"
|
" and caching modules\n"
|
||||||
#endif
|
#endif
|
||||||
"\n"
|
"\n"
|
||||||
@ -3947,6 +3953,8 @@ static void ParseArgs(int* argc,
|
|||||||
Revert(cve);
|
Revert(cve);
|
||||||
} else if (strcmp(arg, "--preserve-symlinks") == 0) {
|
} else if (strcmp(arg, "--preserve-symlinks") == 0) {
|
||||||
config_preserve_symlinks = true;
|
config_preserve_symlinks = true;
|
||||||
|
} else if (strcmp(arg, "--experimental-modules") == 0) {
|
||||||
|
config_experimental_modules = true;
|
||||||
} else if (strcmp(arg, "--prof-process") == 0) {
|
} else if (strcmp(arg, "--prof-process") == 0) {
|
||||||
prof_process = true;
|
prof_process = true;
|
||||||
short_circuit = true;
|
short_circuit = true;
|
||||||
|
@ -65,6 +65,9 @@ static void InitConfig(Local<Object> target,
|
|||||||
if (config_preserve_symlinks)
|
if (config_preserve_symlinks)
|
||||||
READONLY_BOOLEAN_PROPERTY("preserveSymlinks");
|
READONLY_BOOLEAN_PROPERTY("preserveSymlinks");
|
||||||
|
|
||||||
|
if (config_experimental_modules)
|
||||||
|
READONLY_BOOLEAN_PROPERTY("experimentalModules");
|
||||||
|
|
||||||
if (config_pending_deprecation)
|
if (config_pending_deprecation)
|
||||||
READONLY_BOOLEAN_PROPERTY("pendingDeprecation");
|
READONLY_BOOLEAN_PROPERTY("pendingDeprecation");
|
||||||
|
|
||||||
|
@ -86,6 +86,10 @@ extern bool config_preserve_symlinks;
|
|||||||
|
|
||||||
// Set in node.cc by ParseArgs when --expose-http2 is used.
|
// Set in node.cc by ParseArgs when --expose-http2 is used.
|
||||||
extern bool config_expose_http2;
|
extern bool config_expose_http2;
|
||||||
|
// Set in node.cc by ParseArgs when --experimental-modules is used.
|
||||||
|
// Used in node_config.cc to set a constant on process.binding('config')
|
||||||
|
// that is used by lib/module.js
|
||||||
|
extern bool config_experimental_modules;
|
||||||
|
|
||||||
// Set in node.cc by ParseArgs when --expose-internals or --expose_internals is
|
// Set in node.cc by ParseArgs when --expose-internals or --expose_internals is
|
||||||
// used.
|
// used.
|
||||||
|
@ -2080,6 +2080,69 @@ static void DomainToUnicode(const FunctionCallbackInfo<Value>& args) {
|
|||||||
v8::NewStringType::kNormal).ToLocalChecked());
|
v8::NewStringType::kNormal).ToLocalChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string URL::ToFilePath() {
|
||||||
|
if (context_.scheme != "file:") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
const char* slash = "\\";
|
||||||
|
auto is_slash = [] (char ch) {
|
||||||
|
return ch == '/' || ch == '\\';
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
const char* slash = "/";
|
||||||
|
auto is_slash = [] (char ch) {
|
||||||
|
return ch == '/';
|
||||||
|
};
|
||||||
|
if ((context_.flags & URL_FLAGS_HAS_HOST) &&
|
||||||
|
context_.host.length() > 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
std::string decoded_path;
|
||||||
|
for (std::string& part : context_.path) {
|
||||||
|
std::string decoded;
|
||||||
|
PercentDecode(part.c_str(), part.length(), &decoded);
|
||||||
|
for (char& ch : decoded) {
|
||||||
|
if (is_slash(ch)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decoded_path += slash + decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// TODO(TimothyGu): Use "\\?\" long paths on Windows.
|
||||||
|
|
||||||
|
// If hostname is set, then we have a UNC path. Pass the hostname through
|
||||||
|
// ToUnicode just in case it is an IDN using punycode encoding. We do not
|
||||||
|
// need to worry about percent encoding because the URL parser will have
|
||||||
|
// already taken care of that for us. Note that this only causes IDNs with an
|
||||||
|
// appropriate `xn--` prefix to be decoded.
|
||||||
|
if ((context_.flags & URL_FLAGS_HAS_HOST) &&
|
||||||
|
context_.host.length() > 0) {
|
||||||
|
std::string unicode_host;
|
||||||
|
if (!ToUnicode(&context_.host, &unicode_host)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return "\\\\" + unicode_host + decoded_path;
|
||||||
|
}
|
||||||
|
// Otherwise, it's a local path that requires a drive letter.
|
||||||
|
if (decoded_path.length() < 3) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (decoded_path[2] != ':' ||
|
||||||
|
!IsASCIIAlpha(decoded_path[1])) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// Strip out the leading '\'.
|
||||||
|
return decoded_path.substr(1);
|
||||||
|
#else
|
||||||
|
return decoded_path;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// This function works by calling out to a JS function that creates and
|
// This function works by calling out to a JS function that creates and
|
||||||
// returns the JS URL object. Be mindful of the JS<->Native boundary
|
// returns the JS URL object. Be mindful of the JS<->Native boundary
|
||||||
// crossing that is required.
|
// crossing that is required.
|
||||||
|
@ -163,6 +163,10 @@ class URL {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the path of the file: URL in a format consumable by native file system
|
||||||
|
// APIs. Returns an empty string if something went wrong.
|
||||||
|
std::string ToFilePath();
|
||||||
|
|
||||||
const Local<Value> ToObject(Environment* env) const;
|
const Local<Value> ToObject(Environment* env) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -79,3 +79,28 @@ TEST_F(URLTest, Base3) {
|
|||||||
EXPECT_EQ(simple.host(), "example.org");
|
EXPECT_EQ(simple.host(), "example.org");
|
||||||
EXPECT_EQ(simple.path(), "/baz");
|
EXPECT_EQ(simple.path(), "/baz");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(URLTest, ToFilePath) {
|
||||||
|
#define T(url, path) EXPECT_EQ(path, URL(url).ToFilePath())
|
||||||
|
T("http://example.org/foo/bar", "");
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
T("file:///C:/Program%20Files/", "C:\\Program Files\\");
|
||||||
|
T("file:///C:/a/b/c?query#fragment", "C:\\a\\b\\c");
|
||||||
|
T("file://host/path/a/b/c?query#fragment", "\\\\host\\path\\a\\b\\c");
|
||||||
|
T("file://xn--weird-prdj8vva.com/host/a", "\\\\wͪ͊eiͬ͋rd.com\\host\\a");
|
||||||
|
T("file:///C:/a%2Fb", "");
|
||||||
|
T("file:///", "");
|
||||||
|
T("file:///home", "");
|
||||||
|
#else
|
||||||
|
T("file:///", "/");
|
||||||
|
T("file:///home/user?query#fragment", "/home/user");
|
||||||
|
T("file:///home/user/?query#fragment", "/home/user/");
|
||||||
|
T("file:///home/user/%20space", "/home/user/ space");
|
||||||
|
T("file:///home/us%5Cer", "/home/us\\er");
|
||||||
|
T("file:///home/us%2Fer", "");
|
||||||
|
T("file://host/path", "");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#undef T
|
||||||
|
}
|
||||||
|
7
test/es-module/es-module.status
Normal file
7
test/es-module/es-module.status
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
prefix parallel
|
||||||
|
|
||||||
|
# To mark a test as flaky, list the test name in the appropriate section
|
||||||
|
# below, without ".js", followed by ": PASS,FLAKY". Example:
|
||||||
|
# sample-test : PASS,FLAKY
|
||||||
|
|
||||||
|
[true] # This section applies to all platforms
|
5
test/es-module/esm-snapshot-mutator.js
Normal file
5
test/es-module/esm-snapshot-mutator.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/* eslint-disable required-modules */
|
||||||
|
'use strict';
|
||||||
|
const shouldSnapshotFilePath = require.resolve('./esm-snapshot.js');
|
||||||
|
require('./esm-snapshot.js');
|
||||||
|
require.cache[shouldSnapshotFilePath].exports++;
|
3
test/es-module/esm-snapshot.js
Normal file
3
test/es-module/esm-snapshot.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/* eslint-disable required-modules */
|
||||||
|
'use strict';
|
||||||
|
module.exports = 1;
|
8
test/es-module/test-esm-basic-imports.mjs
Normal file
8
test/es-module/test-esm-basic-imports.mjs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Flags: --experimental-modules
|
||||||
|
import '../common';
|
||||||
|
import assert from 'assert';
|
||||||
|
import ok from './test-esm-ok.mjs';
|
||||||
|
import okShebang from './test-esm-shebang.mjs';
|
||||||
|
|
||||||
|
assert(ok);
|
||||||
|
assert(okShebang);
|
10
test/es-module/test-esm-encoded-path-native.js
Normal file
10
test/es-module/test-esm-encoded-path-native.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
|
||||||
|
const native = `${common.fixturesDir}/es-module-url/native.mjs`;
|
||||||
|
const child = spawn(process.execPath, ['--experimental-modules', native]);
|
||||||
|
child.on('exit', (code) => {
|
||||||
|
assert.strictEqual(code, 1);
|
||||||
|
});
|
7
test/es-module/test-esm-encoded-path.mjs
Normal file
7
test/es-module/test-esm-encoded-path.mjs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Flags: --experimental-modules
|
||||||
|
import '../common';
|
||||||
|
import assert from 'assert';
|
||||||
|
// ./test-esm-ok.mjs
|
||||||
|
import ok from './test-%65%73%6d-ok.mjs';
|
||||||
|
|
||||||
|
assert(ok);
|
24
test/es-module/test-esm-forbidden-globals.mjs
Normal file
24
test/es-module/test-esm-forbidden-globals.mjs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Flags: --experimental-modules
|
||||||
|
/* eslint-disable required-modules */
|
||||||
|
|
||||||
|
if (typeof arguments !== 'undefined') {
|
||||||
|
throw new Error('not an ESM');
|
||||||
|
}
|
||||||
|
if (typeof this !== 'undefined') {
|
||||||
|
throw new Error('not an ESM');
|
||||||
|
}
|
||||||
|
if (typeof exports !== 'undefined') {
|
||||||
|
throw new Error('not an ESM');
|
||||||
|
}
|
||||||
|
if (typeof require !== 'undefined') {
|
||||||
|
throw new Error('not an ESM');
|
||||||
|
}
|
||||||
|
if (typeof module !== 'undefined') {
|
||||||
|
throw new Error('not an ESM');
|
||||||
|
}
|
||||||
|
if (typeof __filename !== 'undefined') {
|
||||||
|
throw new Error('not an ESM');
|
||||||
|
}
|
||||||
|
if (typeof __dirname !== 'undefined') {
|
||||||
|
throw new Error('not an ESM');
|
||||||
|
}
|
7
test/es-module/test-esm-namespace.mjs
Normal file
7
test/es-module/test-esm-namespace.mjs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Flags: --experimental-modules
|
||||||
|
/* eslint-disable required-modules */
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
|
assert.deepStrictEqual(Object.keys(fs), ['default']);
|
5
test/es-module/test-esm-ok.mjs
Normal file
5
test/es-module/test-esm-ok.mjs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Flags: --experimental-modules
|
||||||
|
/* eslint-disable required-modules */
|
||||||
|
|
||||||
|
const isJs = true;
|
||||||
|
export default isJs;
|
8
test/es-module/test-esm-pkg-over-ext.mjs
Normal file
8
test/es-module/test-esm-pkg-over-ext.mjs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Flags: --experimental-modules
|
||||||
|
/* eslint-disable required-modules */
|
||||||
|
|
||||||
|
import resolved from '../fixtures/module-pkg-over-ext/inner';
|
||||||
|
import expected from '../fixtures/module-pkg-over-ext/inner/package.json';
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
|
assert.strictEqual(resolved, expected);
|
38
test/es-module/test-esm-preserve-symlinks.js
Normal file
38
test/es-module/test-esm-preserve-symlinks.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Flags: --experimental-modules
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
const assert = require('assert');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
common.refreshTmpDir();
|
||||||
|
const tmpDir = common.tmpDir;
|
||||||
|
|
||||||
|
const entry = path.join(tmpDir, 'entry.js');
|
||||||
|
const real = path.join(tmpDir, 'real.js');
|
||||||
|
const link_absolute_path = path.join(tmpDir, 'link.js');
|
||||||
|
|
||||||
|
fs.writeFileSync(entry, `
|
||||||
|
const assert = require('assert');
|
||||||
|
global.x = 0;
|
||||||
|
require('./real.js');
|
||||||
|
assert.strictEqual(x, 1);
|
||||||
|
require('./link.js');
|
||||||
|
assert.strictEqual(x, 2);
|
||||||
|
`);
|
||||||
|
fs.writeFileSync(real, 'x++;');
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.symlinkSync(real, link_absolute_path);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code !== 'EPERM') throw err;
|
||||||
|
common.skip('insufficient privileges for symlinks');
|
||||||
|
}
|
||||||
|
|
||||||
|
spawn(process.execPath,
|
||||||
|
['--experimental-modules', '--preserve-symlinks', entry],
|
||||||
|
{ stdio: 'inherit' }).on('exit', (code) => {
|
||||||
|
assert.strictEqual(code, 0);
|
||||||
|
});
|
7
test/es-module/test-esm-require-cache.mjs
Normal file
7
test/es-module/test-esm-require-cache.mjs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Flags: --experimental-modules
|
||||||
|
import '../common';
|
||||||
|
import '../fixtures/es-module-require-cache/preload.js';
|
||||||
|
import '../fixtures/es-module-require-cache/counter.js';
|
||||||
|
import assert from 'assert';
|
||||||
|
assert.strictEqual(global.counter, 1);
|
||||||
|
delete global.counter;
|
6
test/es-module/test-esm-shebang.mjs
Normal file
6
test/es-module/test-esm-shebang.mjs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#! }]) // isn't js
|
||||||
|
// Flags: --experimental-modules
|
||||||
|
/* eslint-disable required-modules */
|
||||||
|
|
||||||
|
const isJs = true;
|
||||||
|
export default isJs;
|
7
test/es-module/test-esm-snapshot.mjs
Normal file
7
test/es-module/test-esm-snapshot.mjs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Flags: --experimental-modules
|
||||||
|
/* eslint-disable required-modules */
|
||||||
|
import './esm-snapshot-mutator';
|
||||||
|
import one from './esm-snapshot';
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
|
assert.strictEqual(one, 1);
|
48
test/es-module/test-esm-symlink.js
Normal file
48
test/es-module/test-esm-symlink.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
const assert = require('assert');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
common.refreshTmpDir();
|
||||||
|
const tmpDir = common.tmpDir;
|
||||||
|
|
||||||
|
const entry = path.join(tmpDir, 'entry.mjs');
|
||||||
|
const real = path.join(tmpDir, 'index.mjs');
|
||||||
|
const link_absolute_path = path.join(tmpDir, 'absolute');
|
||||||
|
const link_relative_path = path.join(tmpDir, 'relative');
|
||||||
|
const link_ignore_extension = path.join(tmpDir,
|
||||||
|
'ignore_extension.json');
|
||||||
|
const link_directory = path.join(tmpDir, 'directory');
|
||||||
|
|
||||||
|
fs.writeFileSync(real, 'export default [];');
|
||||||
|
fs.writeFileSync(entry, `
|
||||||
|
import assert from 'assert';
|
||||||
|
import real from './index.mjs';
|
||||||
|
import absolute from './absolute';
|
||||||
|
import relative from './relative';
|
||||||
|
import ignoreExtension from './ignore_extension.json';
|
||||||
|
import directory from './directory';
|
||||||
|
|
||||||
|
assert.strictEqual(absolute, real);
|
||||||
|
assert.strictEqual(relative, real);
|
||||||
|
assert.strictEqual(ignoreExtension, real);
|
||||||
|
assert.strictEqual(directory, real);
|
||||||
|
`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.symlinkSync(real, link_absolute_path);
|
||||||
|
fs.symlinkSync(path.basename(real), link_relative_path);
|
||||||
|
fs.symlinkSync(real, link_ignore_extension);
|
||||||
|
fs.symlinkSync(path.dirname(real), link_directory);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code !== 'EPERM') throw err;
|
||||||
|
common.skip('insufficient privileges for symlinks');
|
||||||
|
}
|
||||||
|
|
||||||
|
spawn(process.execPath, ['--experimental-modules', entry],
|
||||||
|
{ stdio: 'inherit' }).on('exit', (code) => {
|
||||||
|
assert.strictEqual(code, 0);
|
||||||
|
});
|
6
test/es-module/testcfg.py
Normal file
6
test/es-module/testcfg.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import sys, os
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
import testpy
|
||||||
|
|
||||||
|
def GetConfiguration(context, root):
|
||||||
|
return testpy.SimpleTestConfiguration(context, root, 'es-module')
|
2
test/fixtures/es-module-require-cache/counter.js
vendored
Normal file
2
test/fixtures/es-module-require-cache/counter.js
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
global.counter = global.counter || 0;
|
||||||
|
global.counter++;
|
1
test/fixtures/es-module-require-cache/preload.js
vendored
Normal file
1
test/fixtures/es-module-require-cache/preload.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
require('./counter');
|
0
test/fixtures/es-module-url/empty.js
vendored
Normal file
0
test/fixtures/es-module-url/empty.js
vendored
Normal file
2
test/fixtures/es-module-url/native.mjs
vendored
Normal file
2
test/fixtures/es-module-url/native.mjs
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// path
|
||||||
|
import 'p%61th';
|
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
import test
|
import test
|
||||||
import os
|
import os
|
||||||
from os.path import join, dirname, exists
|
from os.path import join, dirname, exists, splitext
|
||||||
import re
|
import re
|
||||||
import ast
|
import ast
|
||||||
|
|
||||||
@ -109,18 +109,17 @@ class SimpleTestConfiguration(test.TestConfiguration):
|
|||||||
self.additional_flags = []
|
self.additional_flags = []
|
||||||
|
|
||||||
def Ls(self, path):
|
def Ls(self, path):
|
||||||
def SelectTest(name):
|
return [f for f in os.listdir(path) if re.match('^test-.*\.m?js$', f)]
|
||||||
return name.startswith('test-') and name.endswith('.js')
|
|
||||||
return [f[:-3] for f in os.listdir(path) if SelectTest(f)]
|
|
||||||
|
|
||||||
def ListTests(self, current_path, path, arch, mode):
|
def ListTests(self, current_path, path, arch, mode):
|
||||||
all_tests = [current_path + [t] for t in self.Ls(join(self.root))]
|
all_tests = [current_path + [t] for t in self.Ls(join(self.root))]
|
||||||
result = []
|
result = []
|
||||||
for test in all_tests:
|
for test in all_tests:
|
||||||
if self.Contains(path, test):
|
if self.Contains(path, test):
|
||||||
file_path = join(self.root, reduce(join, test[1:], "") + ".js")
|
file_path = join(self.root, reduce(join, test[1:], ""))
|
||||||
result.append(SimpleTestCase(test, file_path, arch, mode, self.context,
|
test_name = test[:-1] + [splitext(test[-1])[0]]
|
||||||
self, self.additional_flags))
|
result.append(SimpleTestCase(test_name, file_path, arch, mode,
|
||||||
|
self.context, self, self.additional_flags))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def GetBuildRequirements(self):
|
def GetBuildRequirements(self):
|
||||||
|
@ -13,6 +13,7 @@ const path = require('path');
|
|||||||
module.exports = function(context) {
|
module.exports = function(context) {
|
||||||
// trim required module names
|
// trim required module names
|
||||||
var requiredModules = context.options;
|
var requiredModules = context.options;
|
||||||
|
const isESM = context.parserOptions.sourceType === 'module';
|
||||||
|
|
||||||
const foundModules = [];
|
const foundModules = [];
|
||||||
|
|
||||||
@ -39,39 +40,35 @@ module.exports = function(context) {
|
|||||||
return node.callee.type === 'Identifier' && node.callee.name === 'require';
|
return node.callee.type === 'Identifier' && node.callee.name === 'require';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to check if the path is a required module and return its name.
|
||||||
|
* @param {String} str The path to check
|
||||||
|
* @returns {undefined|String} required module name or undefined
|
||||||
|
*/
|
||||||
|
function getRequiredModuleName(str) {
|
||||||
|
var value = path.basename(str);
|
||||||
|
|
||||||
|
// check if value is in required modules array
|
||||||
|
return requiredModules.indexOf(value) !== -1 ? value : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to check if a node has an argument that is a required module and
|
* Function to check if a node has an argument that is a required module and
|
||||||
* return its name.
|
* return its name.
|
||||||
* @param {ASTNode} node The node to check
|
* @param {ASTNode} node The node to check
|
||||||
* @returns {undefined|String} required module name or undefined
|
* @returns {undefined|String} required module name or undefined
|
||||||
*/
|
*/
|
||||||
function getRequiredModuleName(node) {
|
function getRequiredModuleNameFromCall(node) {
|
||||||
var moduleName;
|
|
||||||
|
|
||||||
// node has arguments and first argument is string
|
// node has arguments and first argument is string
|
||||||
if (node.arguments.length && isString(node.arguments[0])) {
|
if (node.arguments.length && isString(node.arguments[0])) {
|
||||||
var argValue = path.basename(node.arguments[0].value.trim());
|
return getRequiredModuleName(node.arguments[0].value.trim());
|
||||||
|
|
||||||
// check if value is in required modules array
|
|
||||||
if (requiredModules.indexOf(argValue) !== -1) {
|
|
||||||
moduleName = argValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return moduleName;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const rules = {
|
||||||
'CallExpression': function(node) {
|
'Program:exit'(node) {
|
||||||
if (isRequireCall(node)) {
|
|
||||||
var requiredModuleName = getRequiredModuleName(node);
|
|
||||||
|
|
||||||
if (requiredModuleName) {
|
|
||||||
foundModules.push(requiredModuleName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'Program:exit': function(node) {
|
|
||||||
if (foundModules.length < requiredModules.length) {
|
if (foundModules.length < requiredModules.length) {
|
||||||
var missingModules = requiredModules.filter(
|
var missingModules = requiredModules.filter(
|
||||||
function(module) {
|
function(module) {
|
||||||
@ -88,6 +85,27 @@ module.exports = function(context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isESM) {
|
||||||
|
rules.ImportDeclaration = (node) => {
|
||||||
|
var requiredModuleName = getRequiredModuleName(node.source.value);
|
||||||
|
if (requiredModuleName) {
|
||||||
|
foundModules.push(requiredModuleName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
rules.CallExpression = (node) => {
|
||||||
|
if (isRequireCall(node)) {
|
||||||
|
var requiredModuleName = getRequiredModuleNameFromCall(node);
|
||||||
|
|
||||||
|
if (requiredModuleName) {
|
||||||
|
foundModules.push(requiredModuleName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.schema = {
|
module.exports.schema = {
|
||||||
|
@ -279,9 +279,7 @@ class TapProgressIndicator(SimpleProgressIndicator):
|
|||||||
# hard to decipher what test is running when only the filename is printed.
|
# hard to decipher what test is running when only the filename is printed.
|
||||||
prefix = abspath(join(dirname(__file__), '../test')) + os.sep
|
prefix = abspath(join(dirname(__file__), '../test')) + os.sep
|
||||||
command = output.command[-1]
|
command = output.command[-1]
|
||||||
if command.endswith('.js'): command = command[:-3]
|
command = NormalizePath(command, prefix)
|
||||||
if command.startswith(prefix): command = command[len(prefix):]
|
|
||||||
command = command.replace('\\', '/')
|
|
||||||
|
|
||||||
if output.UnexpectedOutput():
|
if output.UnexpectedOutput():
|
||||||
status_line = 'not ok %i %s' % (self._done, command)
|
status_line = 'not ok %i %s' % (self._done, command)
|
||||||
@ -352,9 +350,7 @@ class DeoptsCheckProgressIndicator(SimpleProgressIndicator):
|
|||||||
# hard to decipher what test is running when only the filename is printed.
|
# hard to decipher what test is running when only the filename is printed.
|
||||||
prefix = abspath(join(dirname(__file__), '../test')) + os.sep
|
prefix = abspath(join(dirname(__file__), '../test')) + os.sep
|
||||||
command = output.command[-1]
|
command = output.command[-1]
|
||||||
if command.endswith('.js'): command = command[:-3]
|
command = NormalizePath(command, prefix)
|
||||||
if command.startswith(prefix): command = command[len(prefix):]
|
|
||||||
command = command.replace('\\', '/')
|
|
||||||
|
|
||||||
stdout = output.output.stdout.strip()
|
stdout = output.output.stdout.strip()
|
||||||
printed_file = False
|
printed_file = False
|
||||||
@ -1509,12 +1505,16 @@ def SplitPath(s):
|
|||||||
stripped = [ c.strip() for c in s.split('/') ]
|
stripped = [ c.strip() for c in s.split('/') ]
|
||||||
return [ Pattern(s) for s in stripped if len(s) > 0 ]
|
return [ Pattern(s) for s in stripped if len(s) > 0 ]
|
||||||
|
|
||||||
def NormalizePath(path):
|
def NormalizePath(path, prefix='test/'):
|
||||||
# strip the extra path information of the specified test
|
# strip the extra path information of the specified test
|
||||||
if path.startswith('test/'):
|
prefix = prefix.replace('\\', '/')
|
||||||
path = path[5:]
|
path = path.replace('\\', '/')
|
||||||
|
if path.startswith(prefix):
|
||||||
|
path = path[len(prefix):]
|
||||||
if path.endswith('.js'):
|
if path.endswith('.js'):
|
||||||
path = path[:-3]
|
path = path[:-3]
|
||||||
|
elif path.endswith('.mjs'):
|
||||||
|
path = path[:-4]
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def GetSpecialCommandProcessor(value):
|
def GetSpecialCommandProcessor(value):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user