mirror of https://github.com/lukechilds/node.git
Browse Source
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>canary-base
Bradley Farias
8 years ago
46 changed files with 1578 additions and 40 deletions
@ -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 |
@ -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; |
@ -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; |
@ -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; |
@ -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 |
|||
}; |
@ -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; |
@ -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; |
|||
} |
|||
}; |
@ -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 {}); |
@ -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) |
@ -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_
|
@ -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 |
@ -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++; |
@ -0,0 +1,3 @@ |
|||
/* eslint-disable required-modules */ |
|||
'use strict'; |
|||
module.exports = 1; |
@ -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); |
@ -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); |
|||
}); |
@ -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); |
@ -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'); |
|||
} |
@ -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']); |
@ -0,0 +1,5 @@ |
|||
// Flags: --experimental-modules
|
|||
/* eslint-disable required-modules */ |
|||
|
|||
const isJs = true; |
|||
export default isJs; |
@ -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); |
@ -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); |
|||
}); |
@ -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; |
@ -0,0 +1,6 @@ |
|||
#! }]) // isn't js
|
|||
// Flags: --experimental-modules
|
|||
/* eslint-disable required-modules */ |
|||
|
|||
const isJs = true; |
|||
export default isJs; |
@ -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); |
@ -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); |
|||
}); |
@ -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') |
@ -0,0 +1,2 @@ |
|||
global.counter = global.counter || 0; |
|||
global.counter++; |
@ -0,0 +1 @@ |
|||
require('./counter'); |
@ -0,0 +1,2 @@ |
|||
// path
|
|||
import 'p%61th'; |
Loading…
Reference in new issue