mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
module: add __esModule to require()'d ESM
Tooling in the ecosystem have been using the __esModule property to
recognize transpiled ESM in consuming code. For example, a 'log'
package written in ESM:
export function log(val) { console.log(val); }
Can be transpiled as:
exports.__esModule = true;
exports.default = function log(val) { console.log(val); }
The consuming code may be written like this in ESM:
import log from 'log'
Which gets transpiled to:
const _mod = require('log');
const log = _mod.__esModule ? _mod.default : _mod;
So to allow transpiled consuming code to recognize require()'d real ESM
as ESM and pick up the default exports, we add a __esModule property by
building a source text module facade for any module that has a default
export and add .__esModule = true to the exports. We don't do this to
modules that don't have default exports to avoid the unnecessary
overhead. This maintains the enumerability of the re-exported names
and the live binding of the exports.
The source of the facade is defined as a constant per-isolate property
required_module_facade_source_string, which looks like this
export * from 'original';
export { default } from 'original';
export const __esModule = true;
And the 'original' module request is always resolved by
createRequiredModuleFacade() to wrap which is a ModuleWrap wrapping
over the original module.
PR-URL: https://github.com/nodejs/node/pull/52166
Refs: https://github.com/nodejs/node/issues/52134
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
This commit is contained in:
parent
9f6dbfedd1
commit
e77aac2a5f
|
|
@ -192,29 +192,47 @@ the module name space object. In this case it is similar to dynamic
|
|||
`import()` but is run synchronously and returns the name space object
|
||||
directly.
|
||||
|
||||
With the following ES Modules:
|
||||
|
||||
```mjs
|
||||
// distance.mjs
|
||||
export function distance(a, b) { return (b.x - a.x) ** 2 + (b.y - a.y) ** 2; }
|
||||
```
|
||||
|
||||
```mjs
|
||||
// point.mjs
|
||||
export function distance(a, b) { return (b.x - a.x) ** 2 + (b.y - a.y) ** 2; }
|
||||
class Point {
|
||||
constructor(x, y) { this.x = x; this.y = y; }
|
||||
}
|
||||
export default Point;
|
||||
```
|
||||
|
||||
A CommonJS module can load them with `require()` under `--experimental-detect-module`:
|
||||
|
||||
```cjs
|
||||
const required = require('./point.mjs');
|
||||
const distance = require('./distance.mjs');
|
||||
console.log(distance);
|
||||
// [Module: null prototype] {
|
||||
// default: [class Point],
|
||||
// distance: [Function: distance]
|
||||
// }
|
||||
console.log(required);
|
||||
|
||||
(async () => {
|
||||
const imported = await import('./point.mjs');
|
||||
console.log(imported === required); // true
|
||||
})();
|
||||
const point = require('./point.mjs');
|
||||
console.log(point);
|
||||
// [Module: null prototype] {
|
||||
// default: [class Point],
|
||||
// __esModule: true,
|
||||
// }
|
||||
```
|
||||
|
||||
For interoperability with existing tools that convert ES Modules into CommonJS,
|
||||
which could then load real ES Modules through `require()`, the returned namespace
|
||||
would contain a `__esModule: true` property if it has a `default` export so that
|
||||
consuming code generated by tools can recognize the default exports in real
|
||||
ES Modules. If the namespace already defines `__esModule`, this would not be added.
|
||||
This property is experimental and can change in the future. It should only be used
|
||||
by tools converting ES modules into CommonJS modules, following existing ecosystem
|
||||
conventions. Code authored directly in CommonJS should avoid depending on it.
|
||||
|
||||
If the module being `require()`'d contains top-level `await`, or the module
|
||||
graph it `import`s contains top-level `await`,
|
||||
[`ERR_REQUIRE_ASYNC_MODULE`][] will be thrown. In this case, users should
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ const {
|
|||
ObjectFreeze,
|
||||
ObjectGetOwnPropertyDescriptor,
|
||||
ObjectGetPrototypeOf,
|
||||
ObjectHasOwn,
|
||||
ObjectKeys,
|
||||
ObjectPrototype,
|
||||
ObjectPrototypeHasOwnProperty,
|
||||
|
|
@ -71,7 +72,7 @@ const {
|
|||
},
|
||||
} = internalBinding('util');
|
||||
|
||||
const { kEvaluated } = internalBinding('module_wrap');
|
||||
const { kEvaluated, createRequiredModuleFacade } = internalBinding('module_wrap');
|
||||
|
||||
// Internal properties for Module instances.
|
||||
/**
|
||||
|
|
@ -1333,9 +1334,55 @@ function loadESMFromCJS(mod, filename) {
|
|||
// ESM won't be accessible via process.mainModule.
|
||||
setOwnProperty(process, 'mainModule', undefined);
|
||||
} else {
|
||||
// TODO(joyeecheung): we may want to invent optional special handling for default exports here.
|
||||
// For now, it's good enough to be identical to what `import()` returns.
|
||||
mod.exports = cascadedLoader.importSyncForRequire(mod, filename, source, isMain, mod[kModuleParent]);
|
||||
const {
|
||||
wrap,
|
||||
namespace,
|
||||
} = cascadedLoader.importSyncForRequire(mod, filename, source, isMain, mod[kModuleParent]);
|
||||
// Tooling in the ecosystem have been using the __esModule property to recognize
|
||||
// transpiled ESM in consuming code. For example, a 'log' package written in ESM:
|
||||
//
|
||||
// export default function log(val) { console.log(val); }
|
||||
//
|
||||
// Can be transpiled as:
|
||||
//
|
||||
// exports.__esModule = true;
|
||||
// exports.default = function log(val) { console.log(val); }
|
||||
//
|
||||
// The consuming code may be written like this in ESM:
|
||||
//
|
||||
// import log from 'log'
|
||||
//
|
||||
// Which gets transpiled to:
|
||||
//
|
||||
// const _mod = require('log');
|
||||
// const log = _mod.__esModule ? _mod.default : _mod;
|
||||
//
|
||||
// So to allow transpiled consuming code to recognize require()'d real ESM
|
||||
// as ESM and pick up the default exports, we add a __esModule property by
|
||||
// building a source text module facade for any module that has a default
|
||||
// export and add .__esModule = true to the exports. This maintains the
|
||||
// enumerability of the re-exported names and the live binding of the exports,
|
||||
// without incurring a non-trivial per-access overhead on the exports.
|
||||
//
|
||||
// The source of the facade is defined as a constant per-isolate property
|
||||
// required_module_default_facade_source_string, which looks like this
|
||||
//
|
||||
// export * from 'original';
|
||||
// export { default } from 'original';
|
||||
// export const __esModule = true;
|
||||
//
|
||||
// And the 'original' module request is always resolved by
|
||||
// createRequiredModuleFacade() to `wrap` which is a ModuleWrap wrapping
|
||||
// over the original module.
|
||||
|
||||
// We don't do this to modules that don't have default exports to avoid
|
||||
// the unnecessary overhead. If __esModule is already defined, we will
|
||||
// also skip the extension to allow users to override it.
|
||||
if (!ObjectHasOwn(namespace, 'default') || ObjectHasOwn(namespace, '__esModule')) {
|
||||
mod.exports = namespace;
|
||||
} else {
|
||||
mod.exports = createRequiredModuleFacade(wrap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ class ModuleLoader {
|
|||
* @param {string} source Source code. TODO(joyeecheung): pass the raw buffer.
|
||||
* @param {string} isMain Whether this module is a main module.
|
||||
* @param {CJSModule|undefined} parent Parent module, if any.
|
||||
* @returns {{ModuleWrap}}
|
||||
* @returns {{wrap: ModuleWrap, namespace: ModuleNamespaceObject}}
|
||||
*/
|
||||
importSyncForRequire(mod, filename, source, isMain, parent) {
|
||||
const url = pathToFileURL(filename).href;
|
||||
|
|
@ -305,7 +305,7 @@ class ModuleLoader {
|
|||
}
|
||||
throw new ERR_REQUIRE_CYCLE_MODULE(message);
|
||||
}
|
||||
return job.module.getNamespaceSync();
|
||||
return { wrap: job.module, namespace: job.module.getNamespaceSync() };
|
||||
}
|
||||
// TODO(joyeecheung): refactor this so that we pre-parse in C++ and hit the
|
||||
// cache here, or use a carrier object to carry the compiled module script
|
||||
|
|
@ -317,7 +317,7 @@ class ModuleLoader {
|
|||
job = new ModuleJobSync(this, url, kEmptyObject, wrap, isMain, inspectBrk);
|
||||
this.loadCache.set(url, kImplicitTypeAttribute, job);
|
||||
mod[kRequiredModuleSymbol] = job.module;
|
||||
return job.runSync().namespace;
|
||||
return { wrap: job.module, namespace: job.runSync().namespace };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1057,6 +1057,8 @@ class Environment : public MemoryRetainer {
|
|||
std::vector<std::string> supported_hash_algorithms;
|
||||
#endif // HAVE_OPENSSL
|
||||
|
||||
v8::Global<v8::Module> temporary_required_module_facade_original;
|
||||
|
||||
private:
|
||||
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>,
|
||||
v8::Local<v8::Value>),
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@
|
|||
V(openssl_error_stack, "opensslErrorStack") \
|
||||
V(options_string, "options") \
|
||||
V(order_string, "order") \
|
||||
V(original_string, "original") \
|
||||
V(output_string, "output") \
|
||||
V(overlapped_string, "overlapped") \
|
||||
V(parse_error_string, "Parse Error") \
|
||||
|
|
@ -289,6 +290,11 @@
|
|||
V(regexp_string, "regexp") \
|
||||
V(rename_string, "rename") \
|
||||
V(replacement_string, "replacement") \
|
||||
V(required_module_facade_url_string, \
|
||||
"node:internal/require_module_default_facade") \
|
||||
V(required_module_facade_source_string, \
|
||||
"export * from 'original'; export { default } from 'original'; export " \
|
||||
"const __esModule = true;") \
|
||||
V(require_string, "require") \
|
||||
V(resource_string, "resource") \
|
||||
V(retry_string, "retry") \
|
||||
|
|
|
|||
|
|
@ -1019,6 +1019,69 @@ void ModuleWrap::CreateCachedData(const FunctionCallbackInfo<Value>& args) {
|
|||
}
|
||||
}
|
||||
|
||||
// This v8::Module::ResolveModuleCallback simply links `import 'original'`
|
||||
// to the env->temporary_required_module_facade_original() which is stashed
|
||||
// right before this callback is called and will be restored as soon as
|
||||
// v8::Module::Instantiate() returns.
|
||||
MaybeLocal<Module> LinkRequireFacadeWithOriginal(
|
||||
Local<Context> context,
|
||||
Local<String> specifier,
|
||||
Local<FixedArray> import_attributes,
|
||||
Local<Module> referrer) {
|
||||
Environment* env = Environment::GetCurrent(context);
|
||||
Isolate* isolate = context->GetIsolate();
|
||||
CHECK(specifier->Equals(context, env->original_string()).ToChecked());
|
||||
CHECK(!env->temporary_required_module_facade_original.IsEmpty());
|
||||
return env->temporary_required_module_facade_original.Get(isolate);
|
||||
}
|
||||
|
||||
// Wraps an existing source text module with a facade that adds
|
||||
// .__esModule = true to the exports.
|
||||
// See env->required_module_facade_source_string() for the source.
|
||||
void ModuleWrap::CreateRequiredModuleFacade(
|
||||
const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
Environment* env = Environment::GetCurrent(context);
|
||||
CHECK(args[0]->IsObject()); // original module
|
||||
Local<Object> wrap = args[0].As<Object>();
|
||||
ModuleWrap* original;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&original, wrap);
|
||||
|
||||
// Use the same facade source and URL to hit the compilation cache.
|
||||
ScriptOrigin origin(env->required_module_facade_url_string(),
|
||||
0, // line offset
|
||||
0, // column offset
|
||||
true, // is cross origin
|
||||
-1, // script id
|
||||
Local<Value>(), // source map URL
|
||||
false, // is opaque (?)
|
||||
false, // is WASM
|
||||
true); // is ES Module
|
||||
ScriptCompiler::Source source(env->required_module_facade_source_string(),
|
||||
origin);
|
||||
|
||||
// The module facade instantiation simply links `import 'original'` in the
|
||||
// facade with the original module and should never fail.
|
||||
Local<Module> facade =
|
||||
ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
|
||||
// Stash the original module in temporary_required_module_facade_original
|
||||
// for the LinkRequireFacadeWithOriginal() callback to pick it up.
|
||||
CHECK(env->temporary_required_module_facade_original.IsEmpty());
|
||||
env->temporary_required_module_facade_original.Reset(
|
||||
isolate, original->module_.Get(isolate));
|
||||
CHECK(facade->InstantiateModule(context, LinkRequireFacadeWithOriginal)
|
||||
.IsJust());
|
||||
env->temporary_required_module_facade_original.Reset();
|
||||
|
||||
// The evaluation of the facade is synchronous.
|
||||
Local<Value> evaluated = facade->Evaluate(context).ToLocalChecked();
|
||||
CHECK(evaluated->IsPromise());
|
||||
CHECK_EQ(evaluated.As<Promise>()->State(), Promise::PromiseState::kFulfilled);
|
||||
|
||||
args.GetReturnValue().Set(facade->GetModuleNamespace());
|
||||
}
|
||||
|
||||
void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data,
|
||||
Local<ObjectTemplate> target) {
|
||||
Isolate* isolate = isolate_data->isolate();
|
||||
|
|
@ -1051,6 +1114,10 @@ void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data,
|
|||
target,
|
||||
"setInitializeImportMetaObjectCallback",
|
||||
SetInitializeImportMetaObjectCallback);
|
||||
SetMethod(isolate,
|
||||
target,
|
||||
"createRequiredModuleFacade",
|
||||
CreateRequiredModuleFacade);
|
||||
}
|
||||
|
||||
void ModuleWrap::CreatePerContextProperties(Local<Object> target,
|
||||
|
|
@ -1091,6 +1158,8 @@ void ModuleWrap::RegisterExternalReferences(
|
|||
registry->Register(GetStatus);
|
||||
registry->Register(GetError);
|
||||
|
||||
registry->Register(CreateRequiredModuleFacade);
|
||||
|
||||
registry->Register(SetImportModuleDynamicallyCallback);
|
||||
registry->Register(SetInitializeImportMetaObjectCallback);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,9 @@ class ModuleWrap : public BaseObject {
|
|||
std::optional<v8::ScriptCompiler::CachedData*> user_cached_data,
|
||||
bool* cache_rejected);
|
||||
|
||||
static void CreateRequiredModuleFacade(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
private:
|
||||
ModuleWrap(Realm* realm,
|
||||
v8::Local<v8::Object> object,
|
||||
|
|
|
|||
|
|
@ -964,9 +964,14 @@ function getPrintedStackTrace(stderr) {
|
|||
* @param {object} mod result returned by require()
|
||||
* @param {object} expectation shape of expected namespace.
|
||||
*/
|
||||
function expectRequiredModule(mod, expectation) {
|
||||
function expectRequiredModule(mod, expectation, checkESModule = true) {
|
||||
const clone = { ...mod };
|
||||
if (Object.hasOwn(mod, 'default') && checkESModule) {
|
||||
assert.strictEqual(mod.__esModule, true);
|
||||
delete clone.__esModule;
|
||||
}
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
assert.deepStrictEqual({ ...mod }, { ...expectation });
|
||||
assert.deepStrictEqual(clone, { ...expectation });
|
||||
}
|
||||
|
||||
const common = {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
// Flags: --experimental-require-module
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const { expectRequiredModule } = require('../common');
|
||||
const assert = require('assert');
|
||||
const { isModuleNamespaceObject } = require('util/types');
|
||||
|
||||
const mod = require('../fixtures/es-modules/package-default-extension/index.mjs');
|
||||
assert.deepStrictEqual({ ...mod }, { entry: 'mjs' });
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
expectRequiredModule(mod, { entry: 'mjs' });
|
||||
|
||||
assert.throws(() => {
|
||||
const mod = require('../fixtures/es-modules/package-default-extension');
|
||||
|
|
|
|||
23
test/es-module/test-require-module-defined-esmodule.js
Normal file
23
test/es-module/test-require-module-defined-esmodule.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Flags: --experimental-require-module
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
// If an ESM already defines __esModule to be something else,
|
||||
// require(esm) should allow the user override.
|
||||
{
|
||||
const mod = require('../fixtures/es-modules/export-es-module.mjs');
|
||||
common.expectRequiredModule(
|
||||
mod,
|
||||
{ default: { hello: 'world' }, __esModule: 'test' },
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const mod = require('../fixtures/es-modules/export-es-module-2.mjs');
|
||||
common.expectRequiredModule(
|
||||
mod,
|
||||
{ default: { hello: 'world' }, __esModule: false },
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
|
@ -21,8 +21,7 @@ const { pathToFileURL } = require('url');
|
|||
const url = pathToFileURL(path.resolve(__dirname, id));
|
||||
const imported = await import(url);
|
||||
const required = require(id);
|
||||
assert.strictEqual(imported, required,
|
||||
`import()'ed and require()'ed result of ${id} was not reference equal`);
|
||||
common.expectRequiredModule(required, imported);
|
||||
}
|
||||
|
||||
const id = '../fixtures/es-modules/data-import.mjs';
|
||||
|
|
|
|||
|
|
@ -21,8 +21,7 @@ const path = require('path');
|
|||
const url = pathToFileURL(path.resolve(__dirname, id));
|
||||
const required = require(id);
|
||||
const imported = await import(url);
|
||||
assert.strictEqual(imported, required,
|
||||
`import()'ed and require()'ed result of ${id} was not reference equal`);
|
||||
common.expectRequiredModule(required, imported);
|
||||
}
|
||||
|
||||
const id = '../fixtures/es-modules/data-import.mjs';
|
||||
|
|
|
|||
|
|
@ -5,10 +5,9 @@
|
|||
// be loaded by dynamic import().
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
(async () => {
|
||||
const required = require('../fixtures/es-modules/require-and-import/load.cjs');
|
||||
const imported = await import('../fixtures/es-modules/require-and-import/load.mjs');
|
||||
assert.deepStrictEqual({ ...required }, { ...imported });
|
||||
common.expectRequiredModule(required, imported);
|
||||
})().then(common.mustCall());
|
||||
|
|
|
|||
|
|
@ -5,10 +5,9 @@
|
|||
// be loaded by require().
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
(async () => {
|
||||
const imported = await import('../fixtures/es-modules/require-and-import/load.mjs');
|
||||
const required = require('../fixtures/es-modules/require-and-import/load.cjs');
|
||||
assert.deepStrictEqual({ ...required }, { ...imported });
|
||||
common.expectRequiredModule(required, imported);
|
||||
})().then(common.mustCall());
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@
|
|||
|
||||
// Tests that require()ing modules without explicit module type information
|
||||
// warns and errors.
|
||||
require('../common');
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { isModuleNamespaceObject } = require('util/types');
|
||||
|
||||
assert.throws(() => {
|
||||
require('../fixtures/es-modules/package-without-type/noext-esm');
|
||||
|
|
@ -28,6 +27,5 @@ assert.throws(() => {
|
|||
code: 'MODULE_NOT_FOUND'
|
||||
});
|
||||
const mod = require(`${id}.mjs`);
|
||||
assert.deepStrictEqual({ ...mod }, { hello: 'world' });
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
common.expectRequiredModule(mod, { hello: 'world' });
|
||||
}
|
||||
|
|
|
|||
30
test/es-module/test-require-module-transpiled.js
Normal file
30
test/es-module/test-require-module-transpiled.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
'use strict';
|
||||
require('../common');
|
||||
const { spawnSyncAndAssert } = require('../common/child_process');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
// This is a minimum integration test for CJS transpiled from ESM that tries to load real ESM.
|
||||
|
||||
spawnSyncAndAssert(process.execPath, [
|
||||
'--experimental-require-module',
|
||||
fixtures.path('es-modules', 'transpiled-cjs-require-module', 'dist', 'import-both.cjs'),
|
||||
], {
|
||||
trim: true,
|
||||
stdout: 'import both',
|
||||
});
|
||||
|
||||
spawnSyncAndAssert(process.execPath, [
|
||||
'--experimental-require-module',
|
||||
fixtures.path('es-modules', 'transpiled-cjs-require-module', 'dist', 'import-named.cjs'),
|
||||
], {
|
||||
trim: true,
|
||||
stdout: 'import named',
|
||||
});
|
||||
|
||||
spawnSyncAndAssert(process.execPath, [
|
||||
'--experimental-require-module',
|
||||
fixtures.path('es-modules', 'transpiled-cjs-require-module', 'dist', 'import-default.cjs'),
|
||||
], {
|
||||
trim: true,
|
||||
stdout: 'import default',
|
||||
});
|
||||
|
|
@ -1,18 +1,14 @@
|
|||
// Flags: --experimental-require-module --experimental-detect-module
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const { isModuleNamespaceObject } = require('util/types');
|
||||
const common = require('../common');
|
||||
|
||||
{
|
||||
const mod = require('../fixtures/es-modules/loose.js');
|
||||
assert.deepStrictEqual({ ...mod }, { default: 'module' });
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
common.expectRequiredModule(mod, { default: 'module' });
|
||||
}
|
||||
|
||||
{
|
||||
const mod = require('../fixtures/es-modules/package-without-type/noext-esm');
|
||||
assert.deepStrictEqual({ ...mod }, { default: 'module' });
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
common.expectRequiredModule(mod, { default: 'module' });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { isModuleNamespaceObject } = require('util/types');
|
||||
|
||||
common.expectWarning(
|
||||
'ExperimentalWarning',
|
||||
|
|
@ -14,22 +13,19 @@ common.expectWarning(
|
|||
// Test named exports.
|
||||
{
|
||||
const mod = require('../fixtures/es-module-loaders/module-named-exports.mjs');
|
||||
assert.deepStrictEqual({ ...mod }, { foo: 'foo', bar: 'bar' });
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
common.expectRequiredModule(mod, { foo: 'foo', bar: 'bar' });
|
||||
}
|
||||
|
||||
// Test ESM that import ESM.
|
||||
{
|
||||
const mod = require('../fixtures/es-modules/import-esm.mjs');
|
||||
assert.deepStrictEqual({ ...mod }, { hello: 'world' });
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
common.expectRequiredModule(mod, { hello: 'world' });
|
||||
}
|
||||
|
||||
// Test ESM that import CJS.
|
||||
{
|
||||
const mod = require('../fixtures/es-modules/cjs-exports.mjs');
|
||||
assert.deepStrictEqual({ ...mod }, {});
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
common.expectRequiredModule(mod, { });
|
||||
}
|
||||
|
||||
// Test ESM that require() CJS.
|
||||
|
|
@ -39,24 +35,20 @@ common.expectWarning(
|
|||
// re-export everything from the CJS version.
|
||||
assert.strictEqual(common.mustCall, mjs.mustCall);
|
||||
assert.strictEqual(common.localIPv6Hosts, mjs.localIPv6Hosts);
|
||||
assert(!isModuleNamespaceObject(common));
|
||||
assert(isModuleNamespaceObject(mjs));
|
||||
}
|
||||
|
||||
// Test "type": "module" and "main" field in package.json.
|
||||
// Also, test default export.
|
||||
{
|
||||
const mod = require('../fixtures/es-modules/package-type-module');
|
||||
assert.deepStrictEqual({ ...mod }, { default: 'package-type-module' });
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
common.expectRequiredModule(mod, { default: 'package-type-module' });
|
||||
}
|
||||
|
||||
// Test data: import.
|
||||
{
|
||||
const mod = require('../fixtures/es-modules/data-import.mjs');
|
||||
assert.deepStrictEqual({ ...mod }, {
|
||||
common.expectRequiredModule(mod, {
|
||||
data: { hello: 'world' },
|
||||
id: 'data:text/javascript,export default %7B%20hello%3A%20%22world%22%20%7D'
|
||||
});
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
}
|
||||
|
|
|
|||
2
test/fixtures/es-modules/export-es-module-2.mjs
vendored
Normal file
2
test/fixtures/es-modules/export-es-module-2.mjs
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export const __esModule = false;
|
||||
export default { hello: 'world' };
|
||||
2
test/fixtures/es-modules/export-es-module.mjs
vendored
Normal file
2
test/fixtures/es-modules/export-es-module.mjs
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export const __esModule = 'test';
|
||||
export default { hello: 'world' };
|
||||
27
test/fixtures/es-modules/transpiled-cjs-require-module/dist/import-both.cjs
vendored
Normal file
27
test/fixtures/es-modules/transpiled-cjs-require-module/dist/import-both.cjs
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var logger_1 = __importStar(require("logger"));
|
||||
(0, logger_1.log)(new logger_1.default(), 'import both');
|
||||
7
test/fixtures/es-modules/transpiled-cjs-require-module/dist/import-default.cjs
vendored
Normal file
7
test/fixtures/es-modules/transpiled-cjs-require-module/dist/import-default.cjs
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var logger_1 = __importDefault(require("logger"));
|
||||
new logger_1.default().log('import default');
|
||||
4
test/fixtures/es-modules/transpiled-cjs-require-module/dist/import-named.cjs
vendored
Normal file
4
test/fixtures/es-modules/transpiled-cjs-require-module/dist/import-named.cjs
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var logger_1 = require("logger");
|
||||
(0, logger_1.log)(console, 'import named');
|
||||
2
test/fixtures/es-modules/transpiled-cjs-require-module/node_modules/logger/logger.mjs
generated
vendored
Normal file
2
test/fixtures/es-modules/transpiled-cjs-require-module/node_modules/logger/logger.mjs
generated
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export default class Logger { log(val) { console.log(val); } }
|
||||
export function log(logger, val) { logger.log(val) };
|
||||
4
test/fixtures/es-modules/transpiled-cjs-require-module/node_modules/logger/package.json
generated
vendored
Normal file
4
test/fixtures/es-modules/transpiled-cjs-require-module/node_modules/logger/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "logger",
|
||||
"main": "logger.mjs"
|
||||
}
|
||||
2
test/fixtures/es-modules/transpiled-cjs-require-module/src/import-both.mjs
vendored
Normal file
2
test/fixtures/es-modules/transpiled-cjs-require-module/src/import-both.mjs
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import Logger, { log } from 'logger';
|
||||
log(new Logger(), 'import both');
|
||||
2
test/fixtures/es-modules/transpiled-cjs-require-module/src/import-default.mjs
vendored
Normal file
2
test/fixtures/es-modules/transpiled-cjs-require-module/src/import-default.mjs
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import Logger from 'logger';
|
||||
new Logger().log('import default');
|
||||
2
test/fixtures/es-modules/transpiled-cjs-require-module/src/import-named.mjs
vendored
Normal file
2
test/fixtures/es-modules/transpiled-cjs-require-module/src/import-named.mjs
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import { log } from 'logger';
|
||||
log(console, 'import named');
|
||||
23
test/fixtures/es-modules/transpiled-cjs-require-module/transpile.cjs
vendored
Normal file
23
test/fixtures/es-modules/transpiled-cjs-require-module/transpile.cjs
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
// This script is used to transpile ESM fixtures from the src/ directory
|
||||
// to CJS modules in dist/. The transpiled CJS files are used to test
|
||||
// integration of transpiled CJS modules loading real ESM.
|
||||
|
||||
const { readFileSync, writeFileSync, readdirSync } = require('node:fs');
|
||||
|
||||
// We use typescript.js because it's already in the code base as a fixture.
|
||||
// Most ecosystem tools follow a similar pattern, and this produces a bare
|
||||
// minimum integration test for existing patterns.
|
||||
const ts = require('../../snapshot/typescript');
|
||||
const { join } = require('node:path');
|
||||
const sourceDir = join(__dirname, 'src');
|
||||
const files = readdirSync(sourceDir);
|
||||
for (const filename of files) {
|
||||
const filePath = join(sourceDir, filename);
|
||||
const source = readFileSync(filePath, 'utf8');
|
||||
const { outputText } = ts.transpileModule(source, {
|
||||
compilerOptions: { module: ts.ModuleKind.NodeNext }
|
||||
});
|
||||
writeFileSync(join(__dirname, 'dist', filename.replace('.mjs', '.cjs')), outputText, 'utf8');
|
||||
}
|
||||
|
|
@ -338,7 +338,7 @@ test('ESM mocking with namedExports option', async (t) => {
|
|||
assert.strictEqual(mocked.default, 'mock default');
|
||||
assert.strictEqual(mocked.val1, 'mock value');
|
||||
t.mock.reset();
|
||||
assert.strictEqual(original, require(fixture));
|
||||
common.expectRequiredModule(require(fixture), original);
|
||||
});
|
||||
|
||||
await t.test('throws if named exports cannot be applied to defaultExport as CJS', async (t) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user