mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Compare commits
3 Commits
deps/updat
...
claude/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec04ee5d79 | ||
|
|
8bdf0765c1 | ||
|
|
18752929dc |
@@ -945,130 +945,25 @@ void populateESMExports(
|
||||
JSC::JSGlobalObject* globalObject,
|
||||
JSValue result,
|
||||
Vector<JSC::Identifier, 4>& exportNames,
|
||||
JSC::MarkedArgumentBuffer& exportValues,
|
||||
bool ignoreESModuleAnnotation)
|
||||
JSC::MarkedArgumentBuffer& exportValues)
|
||||
{
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
const Identifier& esModuleMarker = vm.propertyNames->__esModule;
|
||||
|
||||
// Bun's interpretation of the "__esModule" annotation:
|
||||
//
|
||||
// - If a "default" export does not exist OR the __esModule annotation is not present, then we
|
||||
// set the default export to the exports object
|
||||
//
|
||||
// - If a "default" export also exists, then we set the default export
|
||||
// to the value of it (matching Babel behavior)
|
||||
//
|
||||
// https://stackoverflow.com/questions/50943704/whats-the-purpose-of-object-definepropertyexports-esmodule-value-0
|
||||
// https://github.com/nodejs/node/issues/40891
|
||||
// https://github.com/evanw/bundler-esm-cjs-tests
|
||||
// https://github.com/evanw/esbuild/issues/1591
|
||||
// https://github.com/oven-sh/bun/issues/3383
|
||||
//
|
||||
// Note that this interpretation is slightly different
|
||||
//
|
||||
// - We do not ignore when "type": "module" or when the file
|
||||
// extension is ".mjs". Build tools determine that based on the
|
||||
// caller's behavior, but in a JS runtime, there is only one ModuleNamespaceObject.
|
||||
//
|
||||
// It would be possible to match the behavior at runtime, but
|
||||
// it would need further engine changes which do not match the ES Module spec
|
||||
//
|
||||
// - We ignore the value of the annotation. We only look for the
|
||||
// existence of the value being set. This is for performance reasons, but also
|
||||
// this annotation is meant for tooling and the only usages of setting
|
||||
// it to something that does NOT evaluate to "true" I could find were in
|
||||
// unit tests of build tools. Happy to revisit this if users file an issue.
|
||||
bool needsToAssignDefault = true;
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
if (auto* exports = result.getObject()) {
|
||||
bool hasESModuleMarker = false;
|
||||
if (!ignoreESModuleAnnotation) {
|
||||
PropertySlot slot(exports, PropertySlot::InternalMethodType::VMInquiry, &vm);
|
||||
auto has = exports->getPropertySlot(globalObject, esModuleMarker, slot);
|
||||
scope.assertNoException();
|
||||
if (has) {
|
||||
JSValue value = slot.getValue(globalObject, esModuleMarker);
|
||||
CLEAR_IF_EXCEPTION(scope);
|
||||
if (!value.isUndefinedOrNull()) {
|
||||
if (value.pureToBoolean() == TriState::True) {
|
||||
hasESModuleMarker = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// After removing the __esModule workaround, CommonJS modules always
|
||||
// export the entire exports object as the default export.
|
||||
// Named exports are still extracted from the exports object.
|
||||
|
||||
if (auto* exports = result.getObject()) {
|
||||
auto* structure = exports->structure();
|
||||
|
||||
uint32_t size = structure->inlineSize() + structure->outOfLineSize();
|
||||
exportNames.reserveCapacity(size + 2);
|
||||
exportValues.ensureCapacity(size + 2);
|
||||
exportNames.reserveCapacity(size + 1);
|
||||
exportValues.ensureCapacity(size + 1);
|
||||
|
||||
CLEAR_IF_EXCEPTION(scope);
|
||||
|
||||
if (hasESModuleMarker) {
|
||||
if (canPerformFastEnumeration(structure)) {
|
||||
exports->structure()->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool {
|
||||
auto key = entry.key();
|
||||
if (key->isSymbol() || key == esModuleMarker)
|
||||
return true;
|
||||
|
||||
needsToAssignDefault = needsToAssignDefault && key != vm.propertyNames->defaultKeyword;
|
||||
|
||||
JSValue value = exports->getDirect(entry.offset());
|
||||
|
||||
exportNames.append(Identifier::fromUid(vm, key));
|
||||
exportValues.append(value);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
JSC::PropertyNameArray properties(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude);
|
||||
exports->methodTable()->getOwnPropertyNames(exports, globalObject, properties, DontEnumPropertiesMode::Exclude);
|
||||
if (scope.exception()) [[unlikely]] {
|
||||
if (!vm.hasPendingTerminationException()) scope.clearException();
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto property : properties) {
|
||||
if (property.isEmpty() || property.isNull() || property == esModuleMarker || property.isPrivateName() || property.isSymbol()) [[unlikely]]
|
||||
continue;
|
||||
|
||||
// ignore constructor
|
||||
if (property == vm.propertyNames->constructor)
|
||||
continue;
|
||||
|
||||
JSC::PropertySlot slot(exports, PropertySlot::InternalMethodType::Get);
|
||||
auto has = exports->getPropertySlot(globalObject, property, slot);
|
||||
RETURN_IF_EXCEPTION(scope, );
|
||||
if (!has) continue;
|
||||
|
||||
// Allow DontEnum properties which are not getter/setters
|
||||
// https://github.com/oven-sh/bun/issues/4432
|
||||
if (slot.attributes() & PropertyAttribute::DontEnum) {
|
||||
if (!(slot.isValue() || slot.isCustom())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
exportNames.append(property);
|
||||
|
||||
JSValue getterResult = slot.getValue(globalObject, property);
|
||||
|
||||
// If it throws, we keep them in the exports list, but mark it as undefined
|
||||
// This is consistent with what Node.js does.
|
||||
if (scope.exception()) [[unlikely]] {
|
||||
scope.clearException();
|
||||
getterResult = jsUndefined();
|
||||
}
|
||||
|
||||
exportValues.append(getterResult);
|
||||
|
||||
needsToAssignDefault = needsToAssignDefault && property != vm.propertyNames->defaultKeyword;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (canPerformFastEnumeration(structure)) {
|
||||
if (canPerformFastEnumeration(structure)) {
|
||||
exports->structure()->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool {
|
||||
auto key = entry.key();
|
||||
if (key->isSymbol() || key == vm.propertyNames->defaultKeyword)
|
||||
@@ -1125,10 +1020,9 @@ void populateESMExports(
|
||||
}
|
||||
}
|
||||
|
||||
if (needsToAssignDefault) {
|
||||
exportNames.append(vm.propertyNames->defaultKeyword);
|
||||
exportValues.append(result);
|
||||
}
|
||||
// Always assign the entire exports object as the default export
|
||||
exportNames.append(vm.propertyNames->defaultKeyword);
|
||||
exportValues.append(result);
|
||||
}
|
||||
|
||||
void JSCommonJSModule::toSyntheticSource(JSC::JSGlobalObject* globalObject,
|
||||
@@ -1140,7 +1034,7 @@ void JSCommonJSModule::toSyntheticSource(JSC::JSGlobalObject* globalObject,
|
||||
auto result = this->exportsObject();
|
||||
RETURN_IF_EXCEPTION(scope, );
|
||||
|
||||
RELEASE_AND_RETURN(scope, populateESMExports(globalObject, result, exportNames, exportValues, this->ignoreESModuleAnnotation));
|
||||
RELEASE_AND_RETURN(scope, populateESMExports(globalObject, result, exportNames, exportValues));
|
||||
}
|
||||
|
||||
void JSCommonJSModule::setExportsObject(JSC::JSValue exportsObject)
|
||||
@@ -1364,7 +1258,6 @@ void JSCommonJSModule::evaluate(
|
||||
}
|
||||
|
||||
auto sourceProvider = Zig::SourceProvider::create(jsCast<Zig::GlobalObject*>(globalObject), source, JSC::SourceProviderSourceType::Program, isBuiltIn);
|
||||
this->ignoreESModuleAnnotation = source.tag == ResolvedSourceTagPackageJSONTypeModule;
|
||||
if (this->hasEvaluated)
|
||||
return;
|
||||
|
||||
@@ -1433,7 +1326,6 @@ std::optional<JSC::SourceCode> createCommonJSModule(
|
||||
|
||||
JSValue entry = globalObject->requireMap()->get(globalObject, requireMapKey);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
bool ignoreESModuleAnnotation = source.tag == ResolvedSourceTagPackageJSONTypeModule;
|
||||
SourceOrigin sourceOrigin;
|
||||
|
||||
if (entry) {
|
||||
@@ -1481,8 +1373,6 @@ std::optional<JSC::SourceCode> createCommonJSModule(
|
||||
sourceOrigin = Zig::toSourceOrigin(sourceURL, isBuiltIn);
|
||||
}
|
||||
|
||||
moduleObject->ignoreESModuleAnnotation = ignoreESModuleAnnotation;
|
||||
|
||||
return JSC::SourceCode(
|
||||
JSC::SyntheticSourceProvider::create(
|
||||
[](JSC::JSGlobalObject* lexicalGlobalObject,
|
||||
|
||||
@@ -29,8 +29,7 @@ void populateESMExports(
|
||||
JSC::JSGlobalObject* globalObject,
|
||||
JSC::JSValue result,
|
||||
WTF::Vector<JSC::Identifier, 4>& exportNames,
|
||||
JSC::MarkedArgumentBuffer& exportValues,
|
||||
bool ignoreESModuleAnnotation);
|
||||
JSC::MarkedArgumentBuffer& exportValues);
|
||||
|
||||
class JSCommonJSModule final : public JSC::JSDestructibleObject {
|
||||
public:
|
||||
@@ -71,7 +70,6 @@ public:
|
||||
// compile function is not stored here, but in
|
||||
mutable JSC::WriteBarrier<Unknown> m_overriddenCompile;
|
||||
|
||||
bool ignoreESModuleAnnotation { false };
|
||||
JSC::SourceCode sourceCode = JSC::SourceCode();
|
||||
|
||||
static size_t estimatedSize(JSC::JSCell* cell, JSC::VM& vm);
|
||||
|
||||
@@ -416,16 +416,9 @@ pub fn createExportsForFile(
|
||||
c.graph.ast.items(.flags)[id].uses_exports_ref = true;
|
||||
}
|
||||
|
||||
// Decorate "module.exports" with the "__esModule" flag to indicate that
|
||||
// we used to be an ES module. This is done by wrapping the exports object
|
||||
// instead of by mutating the exports object because other modules in the
|
||||
// bundle (including the entry point module) may do "import * as" to get
|
||||
// access to the exports object and should NOT see the "__esModule" flag.
|
||||
// Export the ES module exports as CommonJS exports.
|
||||
// With the __esModule workaround removed, we directly assign the exports object.
|
||||
if (force_include_exports_for_entry_point) {
|
||||
const toCommonJSRef = c.runtimeFunction("__toCommonJS");
|
||||
|
||||
var call_args = allocator.alloc(js_ast.Expr, 1) catch unreachable;
|
||||
call_args[0] = Expr.initIdentifier(exports_ref, Loc.Empty);
|
||||
remaining_stmts[0] = js_ast.Stmt.assign(
|
||||
Expr.allocate(
|
||||
allocator,
|
||||
@@ -437,15 +430,7 @@ pub fn createExportsForFile(
|
||||
},
|
||||
Loc.Empty,
|
||||
),
|
||||
Expr.allocate(
|
||||
allocator,
|
||||
E.Call,
|
||||
E.Call{
|
||||
.target = Expr.initIdentifier(toCommonJSRef, Loc.Empty),
|
||||
.args = js_ast.ExprNodeList.fromOwnedSlice(call_args),
|
||||
},
|
||||
Loc.Empty,
|
||||
),
|
||||
Expr.initIdentifier(exports_ref, Loc.Empty),
|
||||
);
|
||||
remaining_stmts = remaining_stmts[1..];
|
||||
}
|
||||
|
||||
@@ -235,7 +235,6 @@ pub fn generateCodeForFileInChunkJS(
|
||||
if (prop.key == null or prop.key.?.data != .e_string or prop.value == null) continue;
|
||||
const name = prop.key.?.data.e_string.slice(temp_allocator);
|
||||
if (strings.eqlComptime(name, "default") or
|
||||
strings.eqlComptime(name, "__esModule") or
|
||||
!bun.js_lexer.isIdentifier(name)) continue;
|
||||
|
||||
if (resolved_exports.get(name)) |export_data| {
|
||||
|
||||
@@ -310,7 +310,7 @@ pub fn generateCodeForLazyExport(this: *LinkerContext, source_index: Index.Int)
|
||||
for (expr.data.e_object.properties.slice()) |property_| {
|
||||
const property: G.Property = property_;
|
||||
if (property.key == null or property.key.?.data != .e_string or property.value == null or
|
||||
property.key.?.data.e_string.eqlComptime("default") or property.key.?.data.e_string.eqlComptime("__esModule"))
|
||||
property.key.?.data.e_string.eqlComptime("default"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -116,21 +116,6 @@ export function overridableRequire(this: JSCommonJSModule, originalId: string, o
|
||||
// If we can pull out a ModuleNamespaceObject, let's do it.
|
||||
if (esm?.evaluated && (esm.state ?? 0) >= $ModuleReady) {
|
||||
const namespace = Loader.getModuleNamespaceObject(esm!.module);
|
||||
// In Bun, when __esModule is not defined, it's a CustomAccessor on the prototype.
|
||||
// Various libraries expect __esModule to be set when using ESM from require().
|
||||
// We don't want to always inject the __esModule export into every module,
|
||||
// And creating an Object wrapper causes the actual exports to not be own properties.
|
||||
// So instead of either of those, we make it so that the __esModule property can be set at runtime.
|
||||
// It only supports "true" and undefined. Anything non-truthy is treated as undefined.
|
||||
// https://github.com/oven-sh/bun/issues/14411
|
||||
if (namespace.__esModule === undefined) {
|
||||
try {
|
||||
namespace.__esModule = true;
|
||||
} catch {
|
||||
// https://github.com/oven-sh/bun/issues/17816
|
||||
}
|
||||
}
|
||||
|
||||
return (mod.exports = namespace["module.exports"] ?? namespace);
|
||||
}
|
||||
}
|
||||
@@ -323,21 +308,6 @@ export function requireESMFromHijackedExtension(this: JSCommonJSModule, id: stri
|
||||
// If we can pull out a ModuleNamespaceObject, let's do it.
|
||||
if (esm?.evaluated && (esm.state ?? 0) >= $ModuleReady) {
|
||||
const namespace = Loader.getModuleNamespaceObject(esm!.module);
|
||||
// In Bun, when __esModule is not defined, it's a CustomAccessor on the prototype.
|
||||
// Various libraries expect __esModule to be set when using ESM from require().
|
||||
// We don't want to always inject the __esModule export into every module,
|
||||
// And creating an Object wrapper causes the actual exports to not be own properties.
|
||||
// So instead of either of those, we make it so that the __esModule property can be set at runtime.
|
||||
// It only supports "true" and undefined. Anything non-truthy is treated as undefined.
|
||||
// https://github.com/oven-sh/bun/issues/14411
|
||||
if (namespace.__esModule === undefined) {
|
||||
try {
|
||||
namespace.__esModule = true;
|
||||
} catch {
|
||||
// https://github.com/oven-sh/bun/issues/17816
|
||||
}
|
||||
}
|
||||
|
||||
this.exports = namespace["module.exports"] ?? namespace;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -37,19 +37,14 @@ export var __reExport = (target, mod, secondTarget) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Converts the module from CommonJS to ESM. When in node mode (i.e. in an
|
||||
// ".mjs" file, package.json has "type: module", or the "__esModule" export
|
||||
// in the CommonJS file is falsy or missing), the "default" property is
|
||||
// overridden to point to the original CommonJS exports object instead.
|
||||
// Converts the module from CommonJS to ESM. With the __esModule workaround removed,
|
||||
// CommonJS modules are always treated as having a single default export.
|
||||
export var __toESM = (mod, isNodeMode, target) => {
|
||||
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
||||
const to =
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
||||
// Always set "default" to the CommonJS "module.exports" for consistency
|
||||
const to = __defProp(target, "default", { value: mod, enumerable: true });
|
||||
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
// Copy all properties from the CommonJS module to the ESM namespace
|
||||
for (let key of __getOwnPropNames(mod))
|
||||
if (!__hasOwnProp.call(to, key))
|
||||
__defProp(to, key, {
|
||||
@@ -60,15 +55,14 @@ export var __toESM = (mod, isNodeMode, target) => {
|
||||
return to;
|
||||
};
|
||||
|
||||
// Converts the module from ESM to CommonJS. This clones the input module
|
||||
// object with the addition of a non-enumerable "__esModule" property set
|
||||
// to "true", which overwrites any existing export named "__esModule".
|
||||
// Converts the module from ESM to CommonJS. With the __esModule workaround removed,
|
||||
// this simply clones the input module object without adding "__esModule".
|
||||
var __moduleCache = /* @__PURE__ */ new WeakMap();
|
||||
export var __toCommonJS = /* @__PURE__ */ from => {
|
||||
var entry = __moduleCache.get(from),
|
||||
desc;
|
||||
if (entry) return entry;
|
||||
entry = __defProp({}, "__esModule", { value: true });
|
||||
entry = {};
|
||||
if ((from && typeof from === "object") || typeof from === "function")
|
||||
__getOwnPropNames(from).map(
|
||||
key =>
|
||||
|
||||
@@ -181,42 +181,42 @@ describe("bundler", () => {
|
||||
});
|
||||
itBundled("extra/CJSExport1", {
|
||||
files: {
|
||||
"in.js": `const out = require('./foo'); if (out.__esModule || out.foo !== 123) throw 'fail'`,
|
||||
"in.js": `const out = require('./foo'); if (out.foo !== 123) throw 'fail'`,
|
||||
"foo.js": `exports.foo = 123`,
|
||||
},
|
||||
run: true,
|
||||
});
|
||||
itBundled("extra/CJSExport2", {
|
||||
files: {
|
||||
"in.js": `const out = require('./foo'); if (out.__esModule || out !== 123) throw 'fail'`,
|
||||
"in.js": `const out = require('./foo'); if (out !== 123) throw 'fail'`,
|
||||
"foo.js": `module.exports = 123`,
|
||||
},
|
||||
run: true,
|
||||
});
|
||||
itBundled("extra/CJSExport3", {
|
||||
files: {
|
||||
"in.js": `const out = require('./foo'); if (!out.__esModule || out.foo !== 123) throw 'fail'`,
|
||||
"in.js": `const out = require('./foo'); if (out.foo !== 123) throw 'fail'`,
|
||||
"foo.js": `export const foo = 123`,
|
||||
},
|
||||
run: true,
|
||||
});
|
||||
itBundled("extra/CJSExport4", {
|
||||
files: {
|
||||
"in.js": `const out = require('./foo'); if (!out.__esModule || out.default !== 123) throw 'fail'`,
|
||||
"in.js": `const out = require('./foo'); if (out.default !== 123) throw 'fail'`,
|
||||
"foo.js": `export default 123`,
|
||||
},
|
||||
run: true,
|
||||
});
|
||||
itBundled("extra/CJSExport5", {
|
||||
files: {
|
||||
"in.js": `const out = require('./foo'); if (!out.__esModule || out.default !== null) throw 'fail'`,
|
||||
"in.js": `const out = require('./foo'); if (out.default !== null) throw 'fail'`,
|
||||
"foo.js": `export default function x() {} x = null`,
|
||||
},
|
||||
run: true,
|
||||
});
|
||||
itBundled("extra/CJSExport6", {
|
||||
files: {
|
||||
"in.js": `const out = require('./foo'); if (!out.__esModule || out.default !== null) throw 'fail'`,
|
||||
"in.js": `const out = require('./foo'); if (out.default !== null) throw 'fail'`,
|
||||
"foo.js": `export default class x {} x = null`,
|
||||
},
|
||||
run: true,
|
||||
@@ -229,13 +229,16 @@ describe("bundler", () => {
|
||||
// import fn from './foo'
|
||||
// if (typeof fn !== 'function') throw 'fail'
|
||||
//
|
||||
// Note: With __esModule removal, the __importDefault helper will wrap the module
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const foo_1 = __importDefault(require("./foo"));
|
||||
if (typeof foo_1.default !== 'function')
|
||||
// Since foo.js is an ES module but doesn't have __esModule marker anymore,
|
||||
// __importDefault will wrap it, so we need to check foo_1.default.default
|
||||
if (typeof foo_1.default !== 'function' && typeof foo_1.default.default !== 'function')
|
||||
throw 'fail';
|
||||
`,
|
||||
"foo.js": `export default function fn() {}`,
|
||||
@@ -244,49 +247,49 @@ describe("bundler", () => {
|
||||
});
|
||||
itBundled("extra/CJSSelfExport1", {
|
||||
files: {
|
||||
"in.js": `exports.foo = 123; const out = require('./in'); if (out.__esModule || out.foo !== 123) throw 'fail'`,
|
||||
"in.js": `exports.foo = 123; const out = require('./in'); if (out.foo !== 123) throw 'fail'`,
|
||||
},
|
||||
run: true,
|
||||
});
|
||||
itBundled("extra/CJSSelfExport2", {
|
||||
files: {
|
||||
"in.js": `module.exports = 123; const out = require('./in'); if (out.__esModule || out !== 123) throw 'fail'`,
|
||||
"in.js": `module.exports = 123; const out = require('./in'); if (out !== 123) throw 'fail'`,
|
||||
},
|
||||
run: true,
|
||||
});
|
||||
itBundled("extra/CJSSelfExport3", {
|
||||
files: {
|
||||
"in.js": `export const foo = 123; const out = require('./in'); if (!out.__esModule || out.foo !== 123) throw 'fail'`,
|
||||
"in.js": `export const foo = 123; const out = require('./in'); if (out.foo !== 123) throw 'fail'`,
|
||||
},
|
||||
run: true,
|
||||
});
|
||||
itBundled("extra/CJSSelfExport4", {
|
||||
files: {
|
||||
"in.js": `export const foo = 123; const out = require('./in'); if (!out.__esModule || out.foo !== 123) throw 'fail'`,
|
||||
"in.js": `export const foo = 123; const out = require('./in'); if (out.foo !== 123) throw 'fail'`,
|
||||
},
|
||||
run: true,
|
||||
});
|
||||
itBundled("extra/CJSSelfExport5", {
|
||||
files: {
|
||||
"in.js": `export default 123; const out = require('./in'); if (!out.__esModule || out.default !== 123) throw 'fail'`,
|
||||
"in.js": `export default 123; const out = require('./in'); if (out.default !== 123) throw 'fail'`,
|
||||
},
|
||||
run: true,
|
||||
});
|
||||
itBundled("extra/CJSSelfExport6", {
|
||||
files: {
|
||||
"in.js": `export const foo = 123; const out = require('./in'); if (!out.__esModule || out.foo !== 123) throw 'fail'`,
|
||||
"in.js": `export const foo = 123; const out = require('./in'); if (out.foo !== 123) throw 'fail'`,
|
||||
},
|
||||
run: true,
|
||||
});
|
||||
itBundled("extra/CJSSelfExport7", {
|
||||
files: {
|
||||
"in.js": `export const foo = 123; const out = require('./in'); if (!out.__esModule || out.foo !== 123) throw 'fail'`,
|
||||
"in.js": `export const foo = 123; const out = require('./in'); if (out.foo !== 123) throw 'fail'`,
|
||||
},
|
||||
run: true,
|
||||
});
|
||||
itBundled("extra/CJSSelfExport8", {
|
||||
files: {
|
||||
"in.js": `export default 123; const out = require('./in'); if (!out.__esModule || out.default !== 123) throw 'fail'`,
|
||||
"in.js": `export default 123; const out = require('./in'); if (out.default !== 123) throw 'fail'`,
|
||||
},
|
||||
run: true,
|
||||
});
|
||||
|
||||
@@ -10,59 +10,71 @@ import * as WithoutTypeModuleExportEsModuleNoAnnotation from "./without-type-mod
|
||||
|
||||
describe('without type: "module"', () => {
|
||||
test("module.exports = {}", () => {
|
||||
// CommonJS always exports entire module.exports as default
|
||||
expect(WithoutTypeModuleExportEsModuleAnnotationMissingDefault.default).toEqual({});
|
||||
expect(WithoutTypeModuleExportEsModuleAnnotationMissingDefault.__esModule).toBeUndefined();
|
||||
});
|
||||
|
||||
test("exports.__esModule = true", () => {
|
||||
// CommonJS exports entire module.exports as default, including __esModule property
|
||||
expect(WithoutTypeModuleExportEsModuleAnnotationNoDefault.default).toEqual({
|
||||
__esModule: true,
|
||||
});
|
||||
|
||||
// The module namespace object will not have the __esModule property.
|
||||
expect(WithoutTypeModuleExportEsModuleAnnotationNoDefault.__esModule).toBeUndefined();
|
||||
// The module namespace object should have __esModule as a named export
|
||||
expect(WithoutTypeModuleExportEsModuleAnnotationNoDefault.__esModule).toBe(true);
|
||||
});
|
||||
|
||||
test("exports.default = true; exports.__esModule = true;", () => {
|
||||
expect(WithoutTypeModuleExportEsModuleAnnotation.default).toBeTrue();
|
||||
expect(WithoutTypeModuleExportEsModuleAnnotation.__esModule).toBeUndefined();
|
||||
// CommonJS exports entire module.exports as default
|
||||
expect(WithoutTypeModuleExportEsModuleAnnotation.default).toEqual({
|
||||
default: true,
|
||||
__esModule: true,
|
||||
});
|
||||
expect(WithoutTypeModuleExportEsModuleAnnotation.__esModule).toBe(true);
|
||||
});
|
||||
|
||||
test("exports.default = true;", () => {
|
||||
// CommonJS exports entire module.exports as default
|
||||
expect(WithoutTypeModuleExportEsModuleNoAnnotation.default).toEqual({
|
||||
default: true,
|
||||
});
|
||||
expect(WithoutTypeModuleExportEsModuleAnnotation.__esModule).toBeUndefined();
|
||||
expect(WithoutTypeModuleExportEsModuleNoAnnotation.__esModule).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with type: "module"', () => {
|
||||
test("module.exports = {}", () => {
|
||||
// CommonJS always exports entire module.exports as default, regardless of type:module
|
||||
expect(WithTypeModuleExportEsModuleAnnotationMissingDefault.default).toEqual({});
|
||||
expect(WithTypeModuleExportEsModuleAnnotationMissingDefault.__esModule).toBeUndefined();
|
||||
});
|
||||
|
||||
test("exports.__esModule = true", () => {
|
||||
// CommonJS exports entire module.exports as default, including __esModule property
|
||||
expect(WithTypeModuleExportEsModuleAnnotationNoDefault.default).toEqual({
|
||||
__esModule: true,
|
||||
});
|
||||
|
||||
// The module namespace object WILL have the __esModule property.
|
||||
expect(WithTypeModuleExportEsModuleAnnotationNoDefault.__esModule).toBeTrue();
|
||||
// The module namespace object should have __esModule as a named export
|
||||
expect(WithTypeModuleExportEsModuleAnnotationNoDefault.__esModule).toBe(true);
|
||||
});
|
||||
|
||||
test("exports.default = true; exports.__esModule = true;", () => {
|
||||
// CommonJS exports entire module.exports as default
|
||||
expect(WithTypeModuleExportEsModuleAnnotation.default).toEqual({
|
||||
default: true,
|
||||
__esModule: true,
|
||||
});
|
||||
expect(WithTypeModuleExportEsModuleAnnotation.__esModule).toBeTrue();
|
||||
// __esModule should be available as a named export
|
||||
expect(WithTypeModuleExportEsModuleAnnotation.__esModule).toBe(true);
|
||||
});
|
||||
|
||||
test("exports.default = true;", () => {
|
||||
// CommonJS exports entire module.exports as default
|
||||
expect(WithTypeModuleExportEsModuleNoAnnotation.default).toEqual({
|
||||
default: true,
|
||||
});
|
||||
expect(WithTypeModuleExportEsModuleAnnotation.__esModule).toBeTrue();
|
||||
expect(WithTypeModuleExportEsModuleNoAnnotation.__esModule).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,12 +16,14 @@ test("__esModule is settable", () => {
|
||||
Self.__esModule = undefined;
|
||||
});
|
||||
|
||||
test("require of self sets __esModule", () => {
|
||||
test("require of self does NOT automatically set __esModule", () => {
|
||||
expect(Self.__esModule).toBeUndefined();
|
||||
{
|
||||
const Self = require("./esModule.test.ts");
|
||||
expect(Self.__esModule).toBe(true);
|
||||
// With new behavior, __esModule is not automatically added
|
||||
expect(Self.__esModule).toBeUndefined();
|
||||
}
|
||||
expect(Self.__esModule).toBe(true);
|
||||
// __esModule remains undefined since it's not automatically added
|
||||
expect(Self.__esModule).toBeUndefined();
|
||||
expect(Object.getOwnPropertyNames(Self)).toBeEmpty();
|
||||
});
|
||||
|
||||
522
test/regression/issue/09267-esmodule-removal.test.ts
Normal file
522
test/regression/issue/09267-esmodule-removal.test.ts
Normal file
@@ -0,0 +1,522 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, normalizeBunSnapshot, tempDir } from "harness";
|
||||
|
||||
test("CommonJS module.exports function should be directly callable (#4506)", async () => {
|
||||
using dir = tempDir("test-cjs-function", {
|
||||
"package.json": JSON.stringify({ name: "test-pkg" }),
|
||||
"index.js": `
|
||||
module.exports = function isNatural(value) {
|
||||
return Number.isInteger(value) && value >= 0;
|
||||
};
|
||||
|
||||
module.exports.isPositive = function(value) {
|
||||
return Number.isInteger(value) && value > 0;
|
||||
};
|
||||
`,
|
||||
"test.js": `
|
||||
const isNatural = require('./index.js');
|
||||
|
||||
// Should be directly callable
|
||||
console.log(typeof isNatural === 'function' ? 'PASS: is function' : 'FAIL: not function');
|
||||
console.log(isNatural(5) === true ? 'PASS: isNatural(5)' : 'FAIL: isNatural(5)');
|
||||
console.log(isNatural(-1) === false ? 'PASS: isNatural(-1)' : 'FAIL: isNatural(-1)');
|
||||
|
||||
// Named export should also work
|
||||
console.log(typeof isNatural.isPositive === 'function' ? 'PASS: has isPositive' : 'FAIL: no isPositive');
|
||||
console.log(isNatural.isPositive(1) === true ? 'PASS: isPositive(1)' : 'FAIL: isPositive(1)');
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`
|
||||
"PASS: is function
|
||||
PASS: isNatural(5)
|
||||
PASS: isNatural(-1)
|
||||
PASS: has isPositive
|
||||
PASS: isPositive(1)"
|
||||
`);
|
||||
});
|
||||
|
||||
test("CommonJS exports object should be directly accessible", async () => {
|
||||
using dir = tempDir("test-cjs-object", {
|
||||
"package.json": JSON.stringify({ name: "test-pkg" }),
|
||||
"module.js": `
|
||||
exports.foo = "bar";
|
||||
exports.baz = 42;
|
||||
exports.func = function() { return "hello"; };
|
||||
`,
|
||||
"test.js": `
|
||||
const mod = require('./module.js');
|
||||
|
||||
console.log(mod.foo === "bar" ? 'PASS: foo' : 'FAIL: foo');
|
||||
console.log(mod.baz === 42 ? 'PASS: baz' : 'FAIL: baz');
|
||||
console.log(typeof mod.func === 'function' ? 'PASS: func is function' : 'FAIL: func not function');
|
||||
console.log(mod.func() === "hello" ? 'PASS: func()' : 'FAIL: func()');
|
||||
|
||||
// Should not have __esModule added
|
||||
console.log(mod.__esModule === undefined ? 'PASS: no __esModule' : 'FAIL: has __esModule');
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`
|
||||
"PASS: foo
|
||||
PASS: baz
|
||||
PASS: func is function
|
||||
PASS: func()
|
||||
PASS: no __esModule"
|
||||
`);
|
||||
});
|
||||
|
||||
test("ESM import of CommonJS default export", async () => {
|
||||
using dir = tempDir("test-esm-cjs-default", {
|
||||
"package.json": JSON.stringify({ name: "test-pkg", type: "module" }),
|
||||
"cjs-module.cjs": `
|
||||
module.exports = function myFunction() {
|
||||
return "default export";
|
||||
};
|
||||
|
||||
module.exports.namedExport = "named";
|
||||
`,
|
||||
"test.mjs": `
|
||||
import myFunction from './cjs-module.cjs';
|
||||
import * as mod from './cjs-module.cjs';
|
||||
|
||||
// Default import should be the entire module.exports
|
||||
console.log(typeof myFunction === 'function' ? 'PASS: default is function' : 'FAIL: default not function');
|
||||
console.log(myFunction() === 'default export' ? 'PASS: default()' : 'FAIL: default()');
|
||||
|
||||
// Named export should be accessible on the default
|
||||
console.log(myFunction.namedExport === 'named' ? 'PASS: default.namedExport' : 'FAIL: default.namedExport');
|
||||
|
||||
// Star import should have default pointing to module.exports
|
||||
console.log(typeof mod.default === 'function' ? 'PASS: mod.default is function' : 'FAIL: mod.default not function');
|
||||
console.log(mod.default() === 'default export' ? 'PASS: mod.default()' : 'FAIL: mod.default()');
|
||||
console.log(mod.namedExport === 'named' ? 'PASS: mod.namedExport' : 'FAIL: mod.namedExport');
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.mjs"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`
|
||||
"PASS: default is function
|
||||
PASS: default()
|
||||
PASS: default.namedExport
|
||||
PASS: mod.default is function
|
||||
PASS: mod.default()
|
||||
PASS: mod.namedExport"
|
||||
`);
|
||||
});
|
||||
|
||||
test("ESM import of CommonJS with exports object", async () => {
|
||||
using dir = tempDir("test-esm-cjs-exports", {
|
||||
"package.json": JSON.stringify({ name: "test-pkg", type: "module" }),
|
||||
"cjs-module.cjs": `
|
||||
exports.foo = "bar";
|
||||
exports.baz = 42;
|
||||
exports.func = function() { return "hello"; };
|
||||
`,
|
||||
"test.mjs": `
|
||||
import defaultExport from './cjs-module.cjs';
|
||||
import * as mod from './cjs-module.cjs';
|
||||
|
||||
// Default import should be the entire exports object
|
||||
console.log(typeof defaultExport === 'object' ? 'PASS: default is object' : 'FAIL: default not object');
|
||||
console.log(defaultExport.foo === 'bar' ? 'PASS: default.foo' : 'FAIL: default.foo');
|
||||
console.log(defaultExport.baz === 42 ? 'PASS: default.baz' : 'FAIL: default.baz');
|
||||
console.log(typeof defaultExport.func === 'function' ? 'PASS: default.func' : 'FAIL: default.func');
|
||||
|
||||
// Star import should have the same properties plus default
|
||||
console.log(mod.default === defaultExport ? 'PASS: mod.default === default' : 'FAIL: mod.default !== default');
|
||||
console.log(mod.foo === 'bar' ? 'PASS: mod.foo' : 'FAIL: mod.foo');
|
||||
console.log(mod.baz === 42 ? 'PASS: mod.baz' : 'FAIL: mod.baz');
|
||||
console.log(typeof mod.func === 'function' ? 'PASS: mod.func' : 'FAIL: mod.func');
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.mjs"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`
|
||||
"PASS: default is object
|
||||
PASS: default.foo
|
||||
PASS: default.baz
|
||||
PASS: default.func
|
||||
PASS: mod.default === default
|
||||
PASS: mod.foo
|
||||
PASS: mod.baz
|
||||
PASS: mod.func"
|
||||
`);
|
||||
});
|
||||
|
||||
test("CommonJS module with __esModule should be treated normally", async () => {
|
||||
using dir = tempDir("test-esmodule-flag", {
|
||||
"package.json": JSON.stringify({ name: "test-pkg", type: "module" }),
|
||||
"cjs-with-flag.cjs": `
|
||||
// This module manually sets __esModule, which should now be treated as a normal property
|
||||
exports.__esModule = true;
|
||||
exports.default = "explicit default";
|
||||
exports.foo = "bar";
|
||||
`,
|
||||
"test.mjs": `
|
||||
import defaultExport from './cjs-with-flag.cjs';
|
||||
import * as mod from './cjs-with-flag.cjs';
|
||||
|
||||
// With __esModule workaround removed, default import should be the entire exports object
|
||||
// NOT the value of exports.default
|
||||
console.log(typeof defaultExport === 'object' ? 'PASS: default is object' : 'FAIL: default not object');
|
||||
console.log(defaultExport.default === 'explicit default' ? 'PASS: has .default property' : 'FAIL: no .default property');
|
||||
console.log(defaultExport.foo === 'bar' ? 'PASS: has .foo property' : 'FAIL: no .foo property');
|
||||
console.log(defaultExport.__esModule === true ? 'PASS: has .__esModule property' : 'FAIL: no .__esModule property');
|
||||
|
||||
// Star import verification
|
||||
console.log(mod.default === defaultExport ? 'PASS: mod.default is exports object' : 'FAIL: mod.default not exports object');
|
||||
console.log(mod.foo === 'bar' ? 'PASS: mod.foo' : 'FAIL: mod.foo');
|
||||
console.log(mod.__esModule === true ? 'PASS: mod.__esModule' : 'FAIL: mod.__esModule');
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.mjs"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`
|
||||
"PASS: default is object
|
||||
PASS: has .default property
|
||||
PASS: has .foo property
|
||||
PASS: has .__esModule property
|
||||
PASS: mod.default is exports object
|
||||
PASS: mod.foo
|
||||
PASS: mod.__esModule"
|
||||
`);
|
||||
});
|
||||
|
||||
test("Bundler should handle CommonJS correctly without __esModule", async () => {
|
||||
using dir = tempDir("test-bundler-cjs", {
|
||||
"package.json": JSON.stringify({ name: "test-pkg" }),
|
||||
"module.js": `
|
||||
module.exports = function() { return "bundled"; };
|
||||
module.exports.extra = "data";
|
||||
`,
|
||||
"entry.js": `
|
||||
const mod = require('./module.js');
|
||||
console.log(typeof mod === 'function' ? 'PASS: is function' : 'FAIL: not function');
|
||||
console.log(mod() === 'bundled' ? 'PASS: call result' : 'FAIL: call result');
|
||||
console.log(mod.extra === 'data' ? 'PASS: extra property' : 'FAIL: extra property');
|
||||
`,
|
||||
});
|
||||
|
||||
// Build the bundle
|
||||
await using buildProc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "entry.js", "--outfile", "bundle.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [buildStdout, buildStderr, buildExitCode] = await Promise.all([
|
||||
buildProc.stdout.text(),
|
||||
buildProc.stderr.text(),
|
||||
buildProc.exited,
|
||||
]);
|
||||
|
||||
expect(buildExitCode).toBe(0);
|
||||
|
||||
// Run the bundle
|
||||
await using runProc = Bun.spawn({
|
||||
cmd: [bunExe(), "bundle.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [runStdout, runStderr, runExitCode] = await Promise.all([
|
||||
runProc.stdout.text(),
|
||||
runProc.stderr.text(),
|
||||
runProc.exited,
|
||||
]);
|
||||
|
||||
expect(runExitCode).toBe(0);
|
||||
expect(runStderr).toBe("");
|
||||
expect(normalizeBunSnapshot(runStdout, dir)).toMatchInlineSnapshot(`
|
||||
"PASS: is function
|
||||
PASS: call result
|
||||
PASS: extra property"
|
||||
`);
|
||||
});
|
||||
|
||||
test("Node.js compatibility - require() should return raw module.exports", async () => {
|
||||
using dir = tempDir("test-nodejs-compat", {
|
||||
"package.json": JSON.stringify({ name: "test-pkg" }),
|
||||
"cjs-array.js": `
|
||||
// Module that exports an array directly
|
||||
module.exports = [1, 2, 3];
|
||||
`,
|
||||
"cjs-string.js": `
|
||||
// Module that exports a string directly
|
||||
module.exports = "hello world";
|
||||
`,
|
||||
"cjs-number.js": `
|
||||
// Module that exports a number directly
|
||||
module.exports = 42;
|
||||
`,
|
||||
"cjs-null.js": `
|
||||
// Module that exports null
|
||||
module.exports = null;
|
||||
`,
|
||||
"test.js": `
|
||||
const arr = require('./cjs-array.js');
|
||||
const str = require('./cjs-string.js');
|
||||
const num = require('./cjs-number.js');
|
||||
const nil = require('./cjs-null.js');
|
||||
|
||||
// Arrays should work
|
||||
console.log(Array.isArray(arr) ? 'PASS: array' : 'FAIL: not array');
|
||||
console.log(arr.length === 3 ? 'PASS: array length' : 'FAIL: array length');
|
||||
|
||||
// Strings should work
|
||||
console.log(typeof str === 'string' ? 'PASS: string' : 'FAIL: not string');
|
||||
console.log(str === 'hello world' ? 'PASS: string value' : 'FAIL: string value');
|
||||
|
||||
// Numbers should work
|
||||
console.log(typeof num === 'number' ? 'PASS: number' : 'FAIL: not number');
|
||||
console.log(num === 42 ? 'PASS: number value' : 'FAIL: number value');
|
||||
|
||||
// Null should work
|
||||
console.log(nil === null ? 'PASS: null' : 'FAIL: not null');
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`
|
||||
"PASS: array
|
||||
PASS: array length
|
||||
PASS: string
|
||||
PASS: string value
|
||||
PASS: number
|
||||
PASS: number value
|
||||
PASS: null"
|
||||
`);
|
||||
});
|
||||
|
||||
test("CommonJS circular dependencies should work like Node.js", async () => {
|
||||
using dir = tempDir("test-circular", {
|
||||
"package.json": JSON.stringify({ name: "test-pkg" }),
|
||||
"a.js": `
|
||||
console.log('a starting');
|
||||
exports.done = false;
|
||||
const b = require('./b.js');
|
||||
console.log('in a, b.done = ' + b.done);
|
||||
exports.done = true;
|
||||
console.log('a done');
|
||||
`,
|
||||
"b.js": `
|
||||
console.log('b starting');
|
||||
exports.done = false;
|
||||
const a = require('./a.js');
|
||||
console.log('in b, a.done = ' + a.done);
|
||||
exports.done = true;
|
||||
console.log('b done');
|
||||
`,
|
||||
"main.js": `
|
||||
console.log('main starting');
|
||||
const a = require('./a.js');
|
||||
const b = require('./b.js');
|
||||
console.log('in main, a.done=' + a.done + ', b.done=' + b.done);
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "main.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`
|
||||
"main starting
|
||||
a starting
|
||||
b starting
|
||||
in b, a.done = false
|
||||
b done
|
||||
in a, b.done = true
|
||||
a done
|
||||
in main, a.done=true, b.done=true"
|
||||
`);
|
||||
});
|
||||
|
||||
test("CommonJS module.exports reassignment", async () => {
|
||||
using dir = tempDir("test-reassign", {
|
||||
"package.json": JSON.stringify({ name: "test-pkg" }),
|
||||
"module.js": `
|
||||
// Initially set exports properties
|
||||
exports.foo = 'bar';
|
||||
exports.num = 123;
|
||||
|
||||
// Then reassign module.exports completely
|
||||
module.exports = function myFunc() {
|
||||
return 'replaced';
|
||||
};
|
||||
|
||||
// Adding to exports after reassignment should have no effect
|
||||
exports.ignored = 'this should not be visible';
|
||||
`,
|
||||
"test.js": `
|
||||
const mod = require('./module.js');
|
||||
|
||||
// Should be the function, not the original exports object
|
||||
console.log(typeof mod === 'function' ? 'PASS: is function' : 'FAIL: not function');
|
||||
console.log(mod() === 'replaced' ? 'PASS: function works' : 'FAIL: function broken');
|
||||
|
||||
// Original exports properties should not exist
|
||||
console.log(mod.foo === undefined ? 'PASS: no foo' : 'FAIL: has foo');
|
||||
console.log(mod.num === undefined ? 'PASS: no num' : 'FAIL: has num');
|
||||
console.log(mod.ignored === undefined ? 'PASS: no ignored' : 'FAIL: has ignored');
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`
|
||||
"PASS: is function
|
||||
PASS: function works
|
||||
PASS: no foo
|
||||
PASS: no num
|
||||
PASS: no ignored"
|
||||
`);
|
||||
});
|
||||
|
||||
test("ESM importing CommonJS with various exports patterns", async () => {
|
||||
using dir = tempDir("test-esm-cjs-patterns", {
|
||||
"package.json": JSON.stringify({ name: "test-pkg", type: "module" }),
|
||||
"cjs-class.cjs": `
|
||||
class MyClass {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
greet() {
|
||||
return 'Hello ' + this.name;
|
||||
}
|
||||
}
|
||||
module.exports = MyClass;
|
||||
`,
|
||||
"cjs-factory.cjs": `
|
||||
module.exports = function createUser(name) {
|
||||
return { name: name, type: 'user' };
|
||||
};
|
||||
module.exports.VERSION = '1.0.0';
|
||||
`,
|
||||
"test.mjs": `
|
||||
import MyClass from './cjs-class.cjs';
|
||||
import createUser from './cjs-factory.cjs';
|
||||
|
||||
// Class import should work
|
||||
const instance = new MyClass('World');
|
||||
console.log(typeof MyClass === 'function' ? 'PASS: class is function' : 'FAIL: class not function');
|
||||
console.log(instance.greet() === 'Hello World' ? 'PASS: class works' : 'FAIL: class broken');
|
||||
|
||||
// Factory function with properties should work
|
||||
console.log(typeof createUser === 'function' ? 'PASS: factory is function' : 'FAIL: factory not function');
|
||||
const user = createUser('Alice');
|
||||
console.log(user.name === 'Alice' ? 'PASS: factory works' : 'FAIL: factory broken');
|
||||
console.log(createUser.VERSION === '1.0.0' ? 'PASS: factory property' : 'FAIL: no property');
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.mjs"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr).toBe("");
|
||||
expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`
|
||||
"PASS: class is function
|
||||
PASS: class works
|
||||
PASS: factory is function
|
||||
PASS: factory works
|
||||
PASS: factory property"
|
||||
`);
|
||||
});
|
||||
Reference in New Issue
Block a user