diff options
-rw-r--r-- | src/bun.js/bindings/CommonJSModuleRecord.cpp | 276 | ||||
-rw-r--r-- | src/bun.js/bindings/CommonJSModuleRecord.h | 20 | ||||
-rw-r--r-- | src/bun.js/bindings/ModuleLoader.cpp | 6 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 42 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.h | 5 | ||||
-rw-r--r-- | src/bun.js/bindings/exports.zig | 3 | ||||
-rw-r--r-- | src/bun.js/bindings/headers-handwritten.h | 2 | ||||
-rw-r--r-- | src/bun.js/builtins/WebCoreJSBuiltins.cpp | 8 | ||||
-rw-r--r-- | src/bun.js/builtins/ts/ImportMetaObject.ts | 7 | ||||
-rw-r--r-- | src/bun.js/module_loader.zig | 31 | ||||
-rw-r--r-- | src/bundler.zig | 5 | ||||
-rw-r--r-- | src/js_ast.zig | 9 | ||||
-rw-r--r-- | src/js_parser.zig | 1061 | ||||
-rw-r--r-- | src/js_printer.zig | 15 | ||||
-rw-r--r-- | src/linker.zig | 2 | ||||
-rw-r--r-- | src/runtime.zig | 2 | ||||
-rw-r--r-- | test/js/bun/stream/direct-readable-stream.test.tsx | 2 |
17 files changed, 997 insertions, 499 deletions
diff --git a/src/bun.js/bindings/CommonJSModuleRecord.cpp b/src/bun.js/bindings/CommonJSModuleRecord.cpp new file mode 100644 index 000000000..c77027983 --- /dev/null +++ b/src/bun.js/bindings/CommonJSModuleRecord.cpp @@ -0,0 +1,276 @@ +#include "root.h" +#include "headers-handwritten.h" +#include "ZigGlobalObject.h" +#include "JavaScriptCore/JSSourceCode.h" +#include "JavaScriptCore/JSString.h" +#include "JavaScriptCore/JSValueInternal.h" +#include "JavaScriptCore/JSVirtualMachineInternal.h" +#include "JavaScriptCore/ObjectConstructor.h" +#include "JavaScriptCore/OptionsList.h" +#include "JavaScriptCore/ParserError.h" +#include "JavaScriptCore/ScriptExecutable.h" +#include "JavaScriptCore/SourceOrigin.h" +#include "JavaScriptCore/StackFrame.h" +#include "JavaScriptCore/StackVisitor.h" +#include "BunClientData.h" +#include "JavaScriptCore/Identifier.h" +#include "ImportMetaObject.h" + +#include "JavaScriptCore/TypedArrayInlines.h" +#include "JavaScriptCore/PropertyNameArray.h" +#include "JavaScriptCore/JSWeakMap.h" +#include "JavaScriptCore/JSWeakMapInlines.h" +#include "JavaScriptCore/JSWithScope.h" + +#include <JavaScriptCore/DFGAbstractHeap.h> +#include <JavaScriptCore/Completion.h> +#include <JavaScriptCore/JSMap.h> + +#include <JavaScriptCore/JSMapInlines.h> + +namespace Bun { +using namespace JSC; + +JSC::Structure* createCommonJSModuleStructure( + Zig::GlobalObject* globalObject) +{ + auto& vm = globalObject->vm(); + JSC::Structure* structure = globalObject->structureCache().emptyObjectStructureForPrototype( + globalObject, + globalObject->objectPrototype(), + 4); + + JSC::PropertyOffset offset; + auto clientData = WebCore::clientData(vm); + + structure = structure->addPropertyTransition( + vm, + structure, + JSC::Identifier::fromString(vm, "exports"_s), + 0, + offset); + + structure = structure->addPropertyTransition( + vm, + structure, + JSC::Identifier::fromString(vm, "id"_s), + 0, + offset); + + structure = structure->addPropertyTransition( + vm, + structure, + JSC::Identifier::fromString(vm, "fileName"_s), + 0, + offset); + + structure = structure->addPropertyTransition( + vm, + structure, + JSC::Identifier::fromString(vm, "require"_s), + JSC::PropertyAttribute::Builtin | JSC::PropertyAttribute::Function | 0, + offset); + + return structure; +} + +JSC::JSObject* createCommonJSModuleObject( + Zig::GlobalObject* globalObject, + const ResolvedSource& source, const WTF::String& sourceURL, JSC::JSValue exportsObjectValue, JSC::JSValue requireFunctionValue) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSC::JSObject* moduleObject = JSC::constructEmptyObject( + vm, + globalObject->CommonJSModuleObjectStructure()); + + RETURN_IF_EXCEPTION(scope, nullptr); + + moduleObject->putDirectOffset( + vm, + 0, + exportsObjectValue); + + auto* jsSourceURL = JSC::jsString(vm, sourceURL); + moduleObject->putDirectOffset( + vm, + 1, + jsSourceURL); + + moduleObject->putDirectOffset( + vm, + 2, + // TODO: filename should be substring + jsSourceURL); + + moduleObject->putDirectOffset( + vm, + 3, + requireFunctionValue); + + return moduleObject; +} + +static bool canPerformFastEnumeration(Structure* s) +{ + if (s->typeInfo().overridesGetOwnPropertySlot()) + return false; + if (s->typeInfo().overridesAnyFormOfGetOwnPropertyNames()) + return false; + if (hasIndexedProperties(s->indexingType())) + return false; + if (s->hasAnyKindOfGetterSetterProperties()) + return false; + if (s->isUncacheableDictionary()) + return false; + if (s->hasUnderscoreProtoPropertyExcludingOriginalProto()) + return false; + return true; +} + +JSC::SourceCode createCommonJSModule( + Zig::GlobalObject* globalObject, + ResolvedSource source) +{ + + auto sourceURL = Zig::toStringCopy(source.source_url); + + return JSC::SourceCode( + JSC::SyntheticSourceProvider::create( + [source, sourceURL](JSC::JSGlobalObject* lexicalGlobalObject, + JSC::Identifier moduleKey, + Vector<JSC::Identifier, 4>& exportNames, + JSC::MarkedArgumentBuffer& exportValues) -> void { + Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject); + auto& vm = globalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto sourceCodeString = Zig::toString(source.source_code); + JSC::SourceCode inputSource( + JSC::StringSourceProvider::create(sourceCodeString, + JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(sourceURL)), + sourceURL, TextPosition())); + + JSC::JSObject* scopeExtensionObject = JSC::constructEmptyObject( + vm, + globalObject->commonJSFunctionArgumentsStructure()); + + auto* requireFunction = Zig::ImportMetaObject::createRequireFunction(vm, globalObject, sourceURL); + + JSC::JSObject* exportsObject = source.commonJSExportsLen < 64 + ? JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), source.commonJSExportsLen) + : JSC::constructEmptyObject(globalObject, globalObject->objectPrototype()); + auto* moduleObject = createCommonJSModuleObject(globalObject, source, sourceURL, exportsObject, requireFunction); + scopeExtensionObject->putDirectOffset( + vm, + 0, + moduleObject); + + scopeExtensionObject->putDirectOffset( + vm, + 1, + exportsObject); + + scopeExtensionObject->putDirectOffset( + vm, + 2, + requireFunction); + + auto* executable = JSC::DirectEvalExecutable::create( + globalObject, inputSource, DerivedContextType::None, NeedsClassFieldInitializer::No, PrivateBrandRequirement::None, + false, false, EvalContextType::None, nullptr, nullptr, ECMAMode::sloppy()); + + RETURN_IF_EXCEPTION(throwScope, void()); + + if (UNLIKELY(!executable)) { + throwSyntaxError(globalObject, throwScope, "Failed to compile CommonJS module."_s); + return; + } + + auto* contextScope = JSC::JSWithScope::create(vm, globalObject, globalObject->globalScope(), scopeExtensionObject); + auto* requireMapKey = jsString(vm, sourceURL); + + globalObject->requireMap()->set(globalObject, requireMapKey, exportsObject); + + auto catchScope = DECLARE_CATCH_SCOPE(vm); + vm.interpreter.executeEval(executable, globalObject, contextScope); + if (UNLIKELY(catchScope.exception())) { + auto returnedException = catchScope.exception(); + catchScope.clearException(); + globalObject->requireMap()->remove(globalObject, requireMapKey); + JSC::throwException(globalObject, throwScope, returnedException); + } + + if (throwScope.exception()) + return; + + JSValue result = moduleObject->getDirect(0); + + if (result != exportsObject) + globalObject->requireMap()->set(globalObject, requireMapKey, result); + + exportNames.append(vm.propertyNames->defaultKeyword); + exportValues.append(result); + exportNames.append(Identifier::fromUid(vm.symbolRegistry().symbolForKey("CommonJS"_s))); + exportValues.append(jsNumber(0)); + + if (result.isObject()) { + auto* exports = asObject(result); + + auto* structure = exports->structure(); + uint32_t size = structure->inlineSize() + structure->outOfLineSize(); + exportNames.reserveCapacity(size); + exportValues.ensureCapacity(size); + + if (canPerformFastEnumeration(structure)) { + exports->structure()->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool { + auto key = entry.key(); + if (key->isSymbol() || key == vm.propertyNames->defaultKeyword || entry.attributes() & PropertyAttribute::DontEnum) + return true; + + exportNames.append(Identifier::fromUid(vm, key)); + exportValues.append(exports->getDirect(entry.offset())); + return true; + }); + } else { + JSC::PropertyNameArray properties(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude); + exports->methodTable()->getOwnPropertyNames(exports, globalObject, properties, DontEnumPropertiesMode::Exclude); + if (throwScope.exception()) + return; + + for (auto property : properties) { + if (UNLIKELY(property.isEmpty() || property.isNull())) + continue; + + // ignore constructor + if (property == vm.propertyNames->constructor) + continue; + + if (property.isSymbol() || property.isPrivateName() || property == vm.propertyNames->defaultKeyword) + continue; + + JSC::PropertySlot slot(exports, PropertySlot::InternalMethodType::Get); + if (!exports->getPropertySlot(globalObject, property, slot)) + 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 (catchScope.exception()) { + catchScope.clearException(); + getterResult = jsUndefined(); + } + + exportValues.append(getterResult); + } + } + } + }, + SourceOrigin(WTF::URL::fileURLWithFileSystemPath(sourceURL)), + sourceURL)); +} + +}
\ No newline at end of file diff --git a/src/bun.js/bindings/CommonJSModuleRecord.h b/src/bun.js/bindings/CommonJSModuleRecord.h new file mode 100644 index 000000000..86daf875d --- /dev/null +++ b/src/bun.js/bindings/CommonJSModuleRecord.h @@ -0,0 +1,20 @@ +#include "root.h" +#include "headers-handwritten.h" + +namespace Zig { +class GlobalObject; +} +namespace JSC { +class SourceCode; +} + +namespace Bun { + +JSC::Structure* createCommonJSModuleStructure( + Zig::GlobalObject* globalObject); + +JSC::SourceCode createCommonJSModule( + Zig::GlobalObject* globalObject, + ResolvedSource source); + +} // namespace Bun diff --git a/src/bun.js/bindings/ModuleLoader.cpp b/src/bun.js/bindings/ModuleLoader.cpp index 75981f5b3..f4e96130b 100644 --- a/src/bun.js/bindings/ModuleLoader.cpp +++ b/src/bun.js/bindings/ModuleLoader.cpp @@ -35,6 +35,7 @@ #include "../modules/NodeModuleModule.h" #include "../modules/TTYModule.h" #include "node_util_types.h" +#include "CommonJSModuleRecord.h" namespace Bun { using namespace Zig; @@ -475,6 +476,11 @@ static JSValue fetchSourceCode( Bun__transpileFile(bunVM, globalObject, specifier, referrer, res, false); } + if (res->success && res->result.value.commonJSExportsLen) { + auto source = Bun::createCommonJSModule(globalObject, res->result.value); + return rejectOrResolve(JSSourceCode::create(vm, WTFMove(source))); + } + if (!res->success) { throwException(scope, res->result.err, globalObject); auto* exception = scope.exception(); diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index f0ff9cf16..2a6be49b2 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -177,6 +177,7 @@ namespace JSCastingHelpers = JSC::JSCastingHelpers; #include "CallSite.h" #include "CallSitePrototype.h" #include "DOMWrapperWorld-class.h" +#include "CommonJSModuleRecord.h" constexpr size_t DEFAULT_ERROR_STACK_TRACE_LIMIT = 10; @@ -2607,6 +2608,45 @@ void GlobalObject::finishCreation(VM& vm) init.set(result.toObject(globalObject)); }); + m_commonJSModuleObjectStructure.initLater( + [](const Initializer<Structure>& init) { + init.set(Bun::createCommonJSModuleStructure(reinterpret_cast<Zig::GlobalObject*>(init.owner))); + }); + + m_commonJSFunctionArgumentsStructure.initLater( + [](const Initializer<Structure>& init) { + auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(init.owner); + JSC::Structure* structure = globalObject->structureCache().emptyObjectStructureForPrototype( + globalObject, + globalObject->objectPrototype(), + 3); + JSC::PropertyOffset offset; + auto& vm = globalObject->vm(); + + structure = structure->addPropertyTransition( + vm, + structure, + JSC::Identifier::fromString(vm, "module"_s), + 0, + offset); + + structure = structure->addPropertyTransition( + vm, + structure, + JSC::Identifier::fromString(vm, "exports"_s), + 0, + offset); + + structure = structure->addPropertyTransition( + vm, + structure, + JSC::Identifier::fromString(vm, "require"_s), + JSC::PropertyAttribute::Function | JSC::PropertyAttribute::Builtin | 0, + offset); + + init.set(structure); + }); + // Change prototype from null to object for synthetic modules. m_moduleNamespaceObjectStructure.initLater( [](const Initializer<Structure>& init) { @@ -3798,6 +3838,8 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_bunSleepThenCallback.visit(visitor); thisObject->m_lazyTestModuleObject.visit(visitor); thisObject->m_lazyPreloadTestModuleObject.visit(visitor); + thisObject->m_commonJSModuleObjectStructure.visit(visitor); + thisObject->m_commonJSFunctionArgumentsStructure.visit(visitor); thisObject->m_cachedGlobalObjectStructure.visit(visitor); thisObject->m_cachedGlobalProxyStructure.visit(visitor); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 67c3196c7..66853c909 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -260,6 +260,9 @@ public: Structure* globalProxyStructure() { return m_cachedGlobalProxyStructure.getInitializedOnMainThread(this); } JSObject* lazyTestModuleObject() { return m_lazyTestModuleObject.getInitializedOnMainThread(this); } JSObject* lazyPreloadTestModuleObject() { return m_lazyPreloadTestModuleObject.getInitializedOnMainThread(this); } + Structure* CommonJSModuleObjectStructure() { return m_commonJSModuleObjectStructure.getInitializedOnMainThread(this); } + + Structure* commonJSFunctionArgumentsStructure() { return m_commonJSFunctionArgumentsStructure.getInitializedOnMainThread(this); } JSWeakMap* vmModuleContextMap() { return m_vmModuleContextMap.getInitializedOnMainThread(this); } @@ -477,6 +480,8 @@ private: LazyProperty<JSGlobalObject, JSFunction> m_bunSleepThenCallback; LazyProperty<JSGlobalObject, Structure> m_cachedGlobalObjectStructure; LazyProperty<JSGlobalObject, Structure> m_cachedGlobalProxyStructure; + LazyProperty<JSGlobalObject, Structure> m_commonJSModuleObjectStructure; + LazyProperty<JSGlobalObject, Structure> m_commonJSFunctionArgumentsStructure; DOMGuardedObjectSet m_guardedObjects WTF_GUARDED_BY_LOCK(m_gcLock); void* m_bunVM; diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index 37fc7dabc..b993701fc 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -205,6 +205,9 @@ pub const ResolvedSource = extern struct { specifier: ZigString, source_code: ZigString, source_url: ZigString, + commonjs_exports: ?[*]ZigString = null, + commonjs_exports_len: u32 = 0, + hash: u32, allocator: ?*anyopaque, diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h index 673b366d1..d925fb4cd 100644 --- a/src/bun.js/bindings/headers-handwritten.h +++ b/src/bun.js/bindings/headers-handwritten.h @@ -25,6 +25,8 @@ typedef struct ResolvedSource { ZigString specifier; ZigString source_code; ZigString source_url; + ZigString* commonJSExports; + uint32_t commonJSExportsLen; uint32_t hash; void* allocator; uint64_t tag; diff --git a/src/bun.js/builtins/WebCoreJSBuiltins.cpp b/src/bun.js/builtins/WebCoreJSBuiltins.cpp index ab93a17ea..d572ad023 100644 --- a/src/bun.js/builtins/WebCoreJSBuiltins.cpp +++ b/src/bun.js/builtins/WebCoreJSBuiltins.cpp @@ -2196,17 +2196,17 @@ const char* const s_importMetaObjectLoadCJS2ESMCode = "(function (_){\"use stric const JSC::ConstructAbility s_importMetaObjectRequireESMCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; const JSC::ConstructorKind s_importMetaObjectRequireESMCodeConstructorKind = JSC::ConstructorKind::None; const JSC::ImplementationVisibility s_importMetaObjectRequireESMCodeImplementationVisibility = JSC::ImplementationVisibility::Public; -const int s_importMetaObjectRequireESMCodeLength = 382; +const int s_importMetaObjectRequireESMCodeLength = 406; static const JSC::Intrinsic s_importMetaObjectRequireESMCodeIntrinsic = JSC::NoIntrinsic; -const char* const s_importMetaObjectRequireESMCode = "(function (a){\"use strict\";var i=@Loader.registry.@get(a);if(!i||!i.evaluated)i=@loadCJS2ESM(a);if(!i||!i.evaluated||!i.module)@throwTypeError(`require() failed to evaluate module \"${a}\". This is an internal consistentency error.`);var E=@Loader.getModuleNamespaceObject(i.module),_=E.default,b=_\?.[@commonJSSymbol];if(b===0)return _;else if(b&&@isCallable(_))return _();return E})\n"; +const char* const s_importMetaObjectRequireESMCode = "(function (a){\"use strict\";var _=@Loader.registry.@get(a);if(!_||!_.evaluated)_=@loadCJS2ESM(a);if(!_||!_.evaluated||!_.module)@throwTypeError(`require() failed to evaluate module \"${a}\". This is an internal consistentency error.`);var i=@Loader.getModuleNamespaceObject(_.module),u=i.default,E=u\?.[@commonJSSymbol];if(E===0||i[@commonJSSymbol]===0)return u;else if(E&&@isCallable(u))return u();return i})\n"; // internalRequire const JSC::ConstructAbility s_importMetaObjectInternalRequireCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; const JSC::ConstructorKind s_importMetaObjectInternalRequireCodeConstructorKind = JSC::ConstructorKind::None; const JSC::ImplementationVisibility s_importMetaObjectInternalRequireCodeImplementationVisibility = JSC::ImplementationVisibility::Public; -const int s_importMetaObjectInternalRequireCodeLength = 569; +const int s_importMetaObjectInternalRequireCodeLength = 611; static const JSC::Intrinsic s_importMetaObjectInternalRequireCodeIntrinsic = JSC::NoIntrinsic; -const char* const s_importMetaObjectInternalRequireCode = "(function (n){\"use strict\";var _=@requireMap.@get(n);const i=n.substring(n.length-5);if(_){if(i===\".node\")return _.exports;return _}if(i===\".json\"){var S=globalThis[Symbol.for(\"_fs\")]||=@Bun.fs(),F=JSON.parse(S.readFileSync(n,\"utf8\"));return @requireMap.@set(n,F),F}else if(i===\".node\"){var b={exports:{}};return process.dlopen(b,n),@requireMap.@set(n,b),b.exports}else if(i===\".toml\"){var S=globalThis[Symbol.for(\"_fs\")]||=@Bun.fs(),F=@Bun.TOML.parse(S.readFileSync(n,\"utf8\"));return @requireMap.@set(n,F),F}else{var F=@requireESM(n);return @requireMap.@set(n,F),F}})\n"; +const char* const s_importMetaObjectInternalRequireCode = "(function (n){\"use strict\";var _=@requireMap.@get(n);const i=n.substring(n.length-5);if(_){if(i===\".node\")return _.exports;return _}if(i===\".json\"){var S=globalThis[Symbol.for(\"_fs\")]||=@Bun.fs(),F=JSON.parse(S.readFileSync(n,\"utf8\"));return @requireMap.@set(n,F),F}else if(i===\".node\"){var b={exports:{}};return process.dlopen(b,n),@requireMap.@set(n,b),b.exports}else if(i===\".toml\"){var S=globalThis[Symbol.for(\"_fs\")]||=@Bun.fs(),F=@Bun.TOML.parse(S.readFileSync(n,\"utf8\"));return @requireMap.@set(n,F),F}else{var F=@requireESM(n);const j=@requireMap.@get(n);if(j)return j;return @requireMap.@set(n,F),F}})\n"; // createRequireCache const JSC::ConstructAbility s_importMetaObjectCreateRequireCacheCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; diff --git a/src/bun.js/builtins/ts/ImportMetaObject.ts b/src/bun.js/builtins/ts/ImportMetaObject.ts index 9ce53c192..9255a591f 100644 --- a/src/bun.js/builtins/ts/ImportMetaObject.ts +++ b/src/bun.js/builtins/ts/ImportMetaObject.ts @@ -93,7 +93,7 @@ export function requireESM(this: ImportMetaObject, resolved) { var exports = Loader.getModuleNamespaceObject(entry.module); var commonJS = exports.default; var cjs = commonJS?.[$commonJSSymbol]; - if (cjs === 0) { + if (cjs === 0 || exports[$commonJSSymbol] === 0) { return commonJS; } else if (cjs && $isCallable(commonJS)) { return commonJS(); @@ -130,6 +130,11 @@ export function internalRequire(this: ImportMetaObject, resolved) { return exports; } else { var exports = $requireESM(resolved); + const cachedExports = $requireMap.$get(resolved); + if (cachedExports) { + return cachedExports; + } + $requireMap.$set(resolved, exports); return exports; } diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig index 6524c8084..635e0f4a9 100644 --- a/src/bun.js/module_loader.zig +++ b/src/bun.js/module_loader.zig @@ -820,11 +820,26 @@ pub const ModuleLoader = struct { return resolved_source; } + var commonjs_exports = try bun.default_allocator.alloc(ZigString, parse_result.ast.commonjs_export_names.len); + for (parse_result.ast.commonjs_export_names, commonjs_exports) |name, *out| { + out.* = ZigString.fromUTF8(name); + } + return ResolvedSource{ .allocator = null, .source_code = ZigString.init(try default_allocator.dupe(u8, printer.ctx.getWritten())), .specifier = ZigString.init(specifier), .source_url = ZigString.init(path.text), + .commonjs_exports = if (commonjs_exports.len > 0) + commonjs_exports.ptr + else + null, + .commonjs_exports_len = if (commonjs_exports.len > 0) + @truncate(u32, commonjs_exports.len) + else if (parse_result.ast.exports_kind == .cjs) + std.math.maxInt(u32) + else + 0, // // TODO: change hash to a bitfield // .hash = 1, @@ -943,6 +958,7 @@ pub const ModuleLoader = struct { .virtual_source = virtual_source, .hoist_bun_plugin = true, .dont_bundle_twice = true, + .allow_commonjs = true, .inject_jest_globals = jsc_vm.bundler.options.rewrite_jest_for_tests and jsc_vm.main.len == path.text.len and jsc_vm.main_hash == hash and @@ -1146,11 +1162,26 @@ pub const ModuleLoader = struct { return resolved_source; } + var commonjs_exports = try bun.default_allocator.alloc(ZigString, parse_result.ast.commonjs_export_names.len); + for (parse_result.ast.commonjs_export_names, commonjs_exports) |name, *out| { + out.* = ZigString.fromUTF8(name); + } + return .{ .allocator = null, .source_code = ZigString.init(try default_allocator.dupe(u8, printer.ctx.getWritten())), .specifier = ZigString.init(display_specifier), .source_url = ZigString.init(path.text), + .commonjs_exports = if (commonjs_exports.len > 0) + commonjs_exports.ptr + else + null, + .commonjs_exports_len = if (commonjs_exports.len > 0) + @truncate(u32, commonjs_exports.len) + else if (parse_result.ast.exports_kind == .cjs) + std.math.maxInt(u32) + else + 0, // // TODO: change hash to a bitfield // .hash = 1, diff --git a/src/bundler.zig b/src/bundler.zig index 9650f7f60..f3296134e 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -1203,6 +1203,7 @@ pub const Bundler = struct { .minify_syntax = bundler.options.minify_syntax, .minify_identifiers = bundler.options.minify_identifiers, .transform_only = bundler.options.transform_only, + .module_type = if (ast.exports_kind == .cjs) .cjs else .esm, }, enable_source_map, ), @@ -1224,6 +1225,7 @@ pub const Bundler = struct { .minify_syntax = bundler.options.minify_syntax, .minify_identifiers = bundler.options.minify_identifiers, .transform_only = bundler.options.transform_only, + .module_type = if (ast.exports_kind == .cjs) .cjs else .esm, }, enable_source_map, ), @@ -1287,6 +1289,7 @@ pub const Bundler = struct { inject_jest_globals: bool = false, dont_bundle_twice: bool = false, + allow_commonjs: bool = false, }; pub fn parse( @@ -1390,6 +1393,8 @@ pub const Bundler = struct { // @bun annotation opts.features.dont_bundle_twice = this_parse.dont_bundle_twice; + opts.features.commonjs_at_runtime = this_parse.allow_commonjs; + opts.can_import_from_bundle = bundler.options.node_modules_bundle != null; opts.tree_shaking = bundler.options.tree_shaking; diff --git a/src/js_ast.zig b/src/js_ast.zig index 83a035a4c..c3585ef38 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -4140,6 +4140,7 @@ pub const Expr = struct { e_this, e_class, e_require_string, + e_require_call_target, e_commonjs_export_identifier, @@ -4794,6 +4795,7 @@ pub const Expr = struct { e_require_string: E.RequireString, e_require_resolve_string: E.RequireResolveString, + e_require_call_target: void, e_missing: E.Missing, e_this: E.This, @@ -5935,6 +5937,11 @@ pub const Ast = struct { const_values: ConstValuesMap = .{}, + /// Not to be confused with `commonjs_named_exports` + /// This is a list of named exports that may exist in a CommonJS module + /// We use this with `commonjs_at_runtime` to re-export CommonJS + commonjs_export_names: []string = &([_]string{}), + pub const CommonJSNamedExport = struct { loc_ref: LocRef, needs_decl: bool = true, @@ -7604,6 +7611,7 @@ pub const Macro = struct { e_reg_exp: *E.RegExp, e_require_resolve_string: E.RequireResolveString, e_require_string: E.RequireString, + e_require_call_target: void, g_property: *G.Property, @@ -7651,6 +7659,7 @@ pub const Macro = struct { e_yield, e_if, e_require_resolve_string, + e_require_call_target, e_import, e_this, e_class, diff --git a/src/js_parser.zig b/src/js_parser.zig index c766cc58b..d1bf433b6 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -2774,7 +2774,7 @@ pub const Parser = struct { } break :brk .none; }; - return .{ .ast = try p.toAST(parts, exports_kind, null, "") }; + return .{ .ast = try p.toAST(parts, exports_kind, .{ .none = {} }, "") }; } pub fn parse(self: *Parser) !js_ast.Result { @@ -3351,7 +3351,7 @@ pub const Parser = struct { const uses_module_ref = p.symbols.items[p.module_ref.innerIndex()].use_count_estimate > 0; - var wrapper_expr: ?Expr = null; + var wrapper_expr: CommonJSWrapper = .{ .none = {} }; if (p.isDeoptimizedCommonJS()) { exports_kind = .cjs; @@ -3359,7 +3359,11 @@ pub const Parser = struct { exports_kind = .esm; } else if (uses_exports_ref or uses_module_ref or p.has_top_level_return) { exports_kind = .cjs; - if (!p.options.bundle and (!p.options.transform_only or p.options.features.dynamic_require)) { + if (p.options.features.commonjs_at_runtime) { + wrapper_expr = .{ + .bun_js = {}, + }; + } else if (!p.options.bundle and !p.options.features.commonjs_at_runtime and (!p.options.transform_only or p.options.features.dynamic_require)) { if (p.options.legacy_transform_require_to_import or (p.options.features.dynamic_require and !p.options.enable_legacy_bundling)) { var args = p.allocator.alloc(Expr, 2) catch unreachable; @@ -3368,7 +3372,7 @@ pub const Parser = struct { p.resolveGeneratedSymbol(&p.runtime_imports.__exportDefault.?); } - wrapper_expr = p.callRuntime(logger.Loc.Empty, "__cJS2eSM", args); + wrapper_expr = .{ .bun_dev = p.callRuntime(logger.Loc.Empty, "__cJS2eSM", args) }; p.resolveGeneratedSymbol(&p.runtime_imports.__cJS2eSM.?); // Disable HMR if we're wrapping it in CommonJS @@ -4778,16 +4782,9 @@ fn NewParser_( commonjs_replacement_stmts: StmtNodeList = &.{}, parse_pass_symbol_uses: ParsePassSymbolUsageType = undefined, - // duplicate_case_checker: void, - // non_bmp_identifiers: StringBoolMap, - // legacy_octal_literals: void, - // legacy_octal_literals: map[js_ast.E]logger.Range, - // For lowering private methods - // weak_map_ref: ?Ref, - // weak_set_ref: ?Ref, - // private_getters: RefRefMap, - // private_setters: RefRefMap, + /// Used by commonjs_at_runtime + commonjs_export_names: bun.StringArrayHashMapUnmanaged(void) = .{}, /// When this flag is enabled, we attempt to fold all expressions that /// TypeScript would consider to be "constant expressions". This flag is @@ -10046,14 +10043,13 @@ fn NewParser_( try p.skipTypeScriptObjectType(); } - // This assumes the caller has already parsed the "import" token - - fn importMetaRequire(p: *P, loc: logger.Loc) Expr { - return p.newExpr(E.Dot{ - .target = p.newExpr(E.ImportMeta{}, loc), - .name = "require", - .name_loc = loc, - }, loc); + fn importMetaRequire(_: *P, loc: logger.Loc) Expr { + return Expr{ + .data = .{ + .e_require_call_target = {}, + }, + .loc = loc, + }; } fn parseTypeScriptImportEqualsStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions, default_name_loc: logger.Loc, default_name: string) anyerror!Stmt { @@ -17472,7 +17468,7 @@ fn NewParser_( prop.flags.contains(Flags.Property.is_static) or // If it creates a new scope, we can't do this optimization right now // Our scope order verification stuff will get mad - // But we should let you do module.exports = { bar: foo(), baz: 123 }] + // But we should let you do module.exports = { bar: foo(), baz: 123 } // just not module.exports = { bar: function() {} } // just not module.exports = { bar() {} } switch (prop.value.?.data) { @@ -17595,40 +17591,45 @@ fn NewParser_( } if (comptime FeatureFlags.unwrap_commonjs_to_esm) { - if (!p.is_control_flow_dead and id.ref.eql(p.exports_ref) and !p.commonjs_named_exports_deoptimized) { - if (identifier_opts.is_delete_target) { - p.deoptimizeCommonJSNamedExports(); - return null; - } + if (!p.is_control_flow_dead and id.ref.eql(p.exports_ref)) { + if (!p.commonjs_named_exports_deoptimized) { + if (identifier_opts.is_delete_target) { + p.deoptimizeCommonJSNamedExports(); + return null; + } - var named_export_entry = p.commonjs_named_exports.getOrPut(p.allocator, name) catch unreachable; - if (!named_export_entry.found_existing) { - const new_ref = p.newSymbol( - .other, - std.fmt.allocPrint(p.allocator, "${any}", .{strings.fmtIdentifier(name)}) catch unreachable, - ) catch unreachable; - p.module_scope.generated.push(p.allocator, new_ref) catch unreachable; - named_export_entry.value_ptr.* = .{ - .loc_ref = LocRef{ - .loc = name_loc, - .ref = new_ref, - }, - .needs_decl = true, - }; - if (p.commonjs_named_exports_needs_conversion == std.math.maxInt(u32)) - p.commonjs_named_exports_needs_conversion = @truncate(u32, p.commonjs_named_exports.count() - 1); - } + var named_export_entry = p.commonjs_named_exports.getOrPut(p.allocator, name) catch unreachable; + if (!named_export_entry.found_existing) { + const new_ref = p.newSymbol( + .other, + std.fmt.allocPrint(p.allocator, "${any}", .{strings.fmtIdentifier(name)}) catch unreachable, + ) catch unreachable; + p.module_scope.generated.push(p.allocator, new_ref) catch unreachable; + named_export_entry.value_ptr.* = .{ + .loc_ref = LocRef{ + .loc = name_loc, + .ref = new_ref, + }, + .needs_decl = true, + }; + if (p.commonjs_named_exports_needs_conversion == std.math.maxInt(u32)) + p.commonjs_named_exports_needs_conversion = @truncate(u32, p.commonjs_named_exports.count() - 1); + } - const ref = named_export_entry.value_ptr.*.loc_ref.ref.?; - p.ignoreUsage(id.ref); - p.recordUsage(ref); + const ref = named_export_entry.value_ptr.*.loc_ref.ref.?; + p.ignoreUsage(id.ref); + p.recordUsage(ref); - return p.newExpr( - E.CommonJSExportIdentifier{ - .ref = ref, - }, - name_loc, - ); + return p.newExpr( + E.CommonJSExportIdentifier{ + .ref = ref, + }, + name_loc, + ); + } else if (p.options.features.commonjs_at_runtime) { + // Record this CommonJS export name for use later. + _ = p.commonjs_export_names.getOrPut(p.allocator, name) catch unreachable; + } } } @@ -20865,7 +20866,7 @@ fn NewParser_( p: *P, _parts: []js_ast.Part, exports_kind: js_ast.ExportsKind, - commonjs_wrapper_expr: ?Expr, + commonjs_wrapper_expr: CommonJSWrapper, hashbang: []const u8, ) !js_ast.Ast { const allocator = p.allocator; @@ -20892,7 +20893,7 @@ fn NewParser_( p.import_records_for_current_part.clearRetainingCapacity(); p.declared_symbols.clearRetainingCapacity(); - var result = try ImportScanner.scan(P, p, part.stmts, commonjs_wrapper_expr != null); + var result = try ImportScanner.scan(P, p, part.stmts, commonjs_wrapper_expr != .none); kept_import_equals = kept_import_equals or result.kept_import_equals; removed_import_equals = removed_import_equals or result.removed_import_equals; @@ -20953,504 +20954,575 @@ fn NewParser_( // p.treeShake(&parts, commonjs_wrapper_expr != null or p.options.features.hot_module_reloading or p.options.enable_legacy_bundling); // } - if (commonjs_wrapper_expr) |commonjs_wrapper| { - var require_function_args = allocator.alloc(Arg, 2) catch unreachable; - var final_part_stmts_count: usize = 0; + switch (commonjs_wrapper_expr) { + .bun_dev => |commonjs_wrapper| { + var require_function_args = allocator.alloc(Arg, 2) catch unreachable; + var final_part_stmts_count: usize = 0; - var imports_count: u32 = 0; - // We have to also move export from, since we will preserve those - var exports_from_count: u32 = 0; + var imports_count: u32 = 0; + // We have to also move export from, since we will preserve those + var exports_from_count: u32 = 0; - // Two passes. First pass just counts. - for (parts) |part| { - for (part.stmts) |stmt| { - imports_count += switch (stmt.data) { - .s_import => @as(u32, 1), - else => @as(u32, 0), - }; + // Two passes. First pass just counts. + for (parts) |part| { + for (part.stmts) |stmt| { + imports_count += switch (stmt.data) { + .s_import => @as(u32, 1), + else => @as(u32, 0), + }; - exports_from_count += switch (stmt.data) { - .s_export_star, .s_export_from => @as(u32, 1), - else => @as(u32, 0), - }; + exports_from_count += switch (stmt.data) { + .s_export_star, .s_export_from => @as(u32, 1), + else => @as(u32, 0), + }; - final_part_stmts_count += switch (stmt.data) { - .s_import, .s_export_star, .s_export_from => @as(usize, 0), - else => @as(usize, 1), - }; + final_part_stmts_count += switch (stmt.data) { + .s_import, .s_export_star, .s_export_from => @as(usize, 0), + else => @as(usize, 1), + }; + } } - } - var new_stmts_list = allocator.alloc(Stmt, exports_from_count + imports_count + 1) catch unreachable; - var final_stmts_list = allocator.alloc(Stmt, final_part_stmts_count) catch unreachable; - var remaining_final_stmts = final_stmts_list; - var imports_list = new_stmts_list[0..imports_count]; + var new_stmts_list = allocator.alloc(Stmt, exports_from_count + imports_count + 1) catch unreachable; + var final_stmts_list = allocator.alloc(Stmt, final_part_stmts_count) catch unreachable; + var remaining_final_stmts = final_stmts_list; + var imports_list = new_stmts_list[0..imports_count]; - var exports_list: []Stmt = if (exports_from_count > 0) new_stmts_list[imports_list.len + 1 ..] else &[_]Stmt{}; + var exports_list: []Stmt = if (exports_from_count > 0) new_stmts_list[imports_list.len + 1 ..] else &[_]Stmt{}; - require_function_args[0] = G.Arg{ .binding = p.b(B.Identifier{ .ref = p.module_ref }, logger.Loc.Empty) }; - require_function_args[1] = G.Arg{ .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty) }; + require_function_args[0] = G.Arg{ .binding = p.b(B.Identifier{ .ref = p.module_ref }, logger.Loc.Empty) }; + require_function_args[1] = G.Arg{ .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty) }; - var imports_list_i: u32 = 0; - var exports_list_i: u32 = 0; + var imports_list_i: u32 = 0; + var exports_list_i: u32 = 0; - for (parts) |part| { - for (part.stmts) |*stmt| { - switch (stmt.data) { - .s_import => { - imports_list[imports_list_i] = stmt.*; - stmt.loc = imports_list[imports_list_i].loc; - imports_list_i += 1; - }, + for (parts) |part| { + for (part.stmts) |*stmt| { + switch (stmt.data) { + .s_import => { + imports_list[imports_list_i] = stmt.*; + stmt.loc = imports_list[imports_list_i].loc; + imports_list_i += 1; + }, - .s_export_star, .s_export_from => { - exports_list[exports_list_i] = stmt.*; - stmt.loc = exports_list[exports_list_i].loc; - exports_list_i += 1; - }, - else => { - remaining_final_stmts[0] = stmt.*; - remaining_final_stmts = remaining_final_stmts[1..]; - }, + .s_export_star, .s_export_from => { + exports_list[exports_list_i] = stmt.*; + stmt.loc = exports_list[exports_list_i].loc; + exports_list_i += 1; + }, + else => { + remaining_final_stmts[0] = stmt.*; + remaining_final_stmts = remaining_final_stmts[1..]; + }, + } + stmt.* = Stmt.empty(); } - stmt.* = Stmt.empty(); } - } - commonjs_wrapper.data.e_call.args.ptr[0] = p.newExpr( - E.Function{ .func = G.Fn{ - .name = null, - .open_parens_loc = logger.Loc.Empty, - .args = require_function_args, - .body = .{ .loc = logger.Loc.Empty, .stmts = final_stmts_list }, - .flags = Flags.Function.init(.{ .is_export = true }), - } }, - logger.Loc.Empty, - ); - var sourcefile_name = p.source.path.pretty; - if (strings.lastIndexOf(sourcefile_name, "node_modules")) |node_modules_i| { - // 1 for the separator - const end = node_modules_i + 1 + "node_modules".len; - // If you were to name your file "node_modules.js" it shouldn't appear as ".js" - if (end < sourcefile_name.len) { - sourcefile_name = sourcefile_name[end..]; + commonjs_wrapper.data.e_call.args.ptr[0] = p.newExpr( + E.Function{ .func = G.Fn{ + .name = null, + .open_parens_loc = logger.Loc.Empty, + .args = require_function_args, + .body = .{ .loc = logger.Loc.Empty, .stmts = final_stmts_list }, + .flags = Flags.Function.init(.{ .is_export = true }), + } }, + logger.Loc.Empty, + ); + var sourcefile_name = p.source.path.pretty; + if (strings.lastIndexOf(sourcefile_name, "node_modules")) |node_modules_i| { + // 1 for the separator + const end = node_modules_i + 1 + "node_modules".len; + // If you were to name your file "node_modules.js" it shouldn't appear as ".js" + if (end < sourcefile_name.len) { + sourcefile_name = sourcefile_name[end..]; + } } - } - commonjs_wrapper.data.e_call.args.ptr[1] = p.newExpr(E.String{ .data = sourcefile_name }, logger.Loc.Empty); + commonjs_wrapper.data.e_call.args.ptr[1] = p.newExpr(E.String{ .data = sourcefile_name }, logger.Loc.Empty); - new_stmts_list[imports_list.len] = p.s( - S.ExportDefault{ - .value = .{ - .expr = commonjs_wrapper, + new_stmts_list[imports_list.len] = p.s( + S.ExportDefault{ + .value = .{ + .expr = commonjs_wrapper, + }, + .default_name = LocRef{ .ref = null, .loc = logger.Loc.Empty }, }, - .default_name = LocRef{ .ref = null, .loc = logger.Loc.Empty }, - }, - logger.Loc.Empty, - ); - parts[parts.len - 1].stmts = new_stmts_list; - } else if (p.options.features.hot_module_reloading and p.options.features.allow_runtime) { - var named_exports_count: usize = p.named_exports.count(); - const named_imports: js_ast.Ast.NamedImports = p.named_imports; - - // To transform to something HMR'able, we must: - // 1. Wrap the top level code in an IIFE - // 2. Move imports to the top of the file (preserving the order) - // 3. Remove export clauses (done during ImportScanner) - // 4. Move export * from and export from to the bottom of the file (or the top, it doesn't matter I don't think) - // 5. Export everything as getters in our HMR module - // 6. Call the HMRModule's exportAll function like so: - // __hmrModule.exportAll({ - // exportAlias: () => identifier, - // exportAlias: () => identifier, - // }); - // This has the unfortunate property of making property accesses of exports slower at runtime. - // But, I'm not sure there's a way to use regular properties without breaking stuff. - var imports_count: usize = 0; - // We have to also move export from, since we will preserve those - var exports_from_count: usize = 0; - // Two passes. First pass just counts. - for (parts[parts.len - 1].stmts) |stmt| { - imports_count += switch (stmt.data) { - .s_import => @as(usize, 1), - else => @as(usize, 0), + logger.Loc.Empty, + ); + parts[parts.len - 1].stmts = new_stmts_list; + }, + + // This becomes + // + // (function (module, exports, require) { + // + // })(module, exports, require); + .bun_js => { + var args = allocator.alloc(Arg, 3) catch unreachable; + args[0] = Arg{ + .binding = p.b(B.Identifier{ .ref = p.module_ref }, logger.Loc.Empty), }; - exports_from_count += switch (stmt.data) { - .s_export_star, .s_export_from => @as(usize, 1), - else => @as(usize, 0), + args[1] = Arg{ + .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty), }; - } - var part = &parts[parts.len - 1]; - - const end_iife_stmts_count = part.stmts.len - imports_count - exports_from_count + 1; - // Why 7? - // 1. HMRClient.activate(${isDebug}); - // 2. var __hmrModule = new HMMRModule(id, file_path), __exports = __hmrModule.exports; - // 3. (__hmrModule.load = function() { - // ${...end_iffe_stmts_count - 1} - // ${end_iffe_stmts_count} - // __hmrModule.exportAll({exportAlias: () => identifier}) <-- ${named_exports_count} - // (); - // 4. var __hmrExport_exportName = __hmrModule.exports.exportName, - // 5. export { __hmrExport_exportName as blah, ... } - // 6. __hmrModule.onSetExports = (newExports) => { - // $named_exports_count __hmrExport_exportName = newExports.exportName; <-- ${named_exports_count} - // } + args[2] = Arg{ + .binding = p.b(B.Identifier{ .ref = p.require_ref }, logger.Loc.Empty), + }; + var total_stmts_count: usize = 0; + for (parts) |part| { + total_stmts_count += part.stmts.len; + } - // if there are no exports: - // - there shouldn't be an export statement - // - we don't need the S.Local for wrapping the exports - // We still call exportAll just with an empty object. - const has_any_exports = named_exports_count > 0; + var stmts_to_copy = allocator.alloc(Stmt, total_stmts_count) catch unreachable; + var remaining_stmts = stmts_to_copy; + for (parts) |part| { + for (part.stmts, remaining_stmts[0..part.stmts.len]) |src, *dest| { + dest.* = src; + } + remaining_stmts = remaining_stmts[part.stmts.len..]; + } - const toplevel_stmts_count = 3 + (@intCast(usize, @boolToInt(has_any_exports)) * 2); - var _stmts = allocator.alloc( - Stmt, - end_iife_stmts_count + toplevel_stmts_count + (named_exports_count * 2) + imports_count + exports_from_count, - ) catch unreachable; - // Normally, we'd have to grow that inner function's stmts list by one - // But we can avoid that by just making them all use this same array. - var curr_stmts = _stmts; - - // in debug: crash in the printer due to undefined memory - // in release: print ";" instead. - // this should never happen regardless, but i'm just being cautious here. - if (comptime !Environment.isDebug) { - std.mem.set(Stmt, _stmts, Stmt.empty()); - } - - // Second pass: move any imports from the part's stmts array to the new stmts - var imports_list = curr_stmts[0..imports_count]; - curr_stmts = curr_stmts[imports_list.len..]; - var toplevel_stmts = curr_stmts[0..toplevel_stmts_count]; - curr_stmts = curr_stmts[toplevel_stmts.len..]; - var exports_from = curr_stmts[0..exports_from_count]; - curr_stmts = curr_stmts[exports_from.len..]; - // This is used for onSetExports - var update_function_stmts = curr_stmts[0..named_exports_count]; - curr_stmts = curr_stmts[update_function_stmts.len..]; - var export_all_function_body_stmts = curr_stmts[0..named_exports_count]; - curr_stmts = curr_stmts[export_all_function_body_stmts.len..]; - // This is the original part statements + 1 - var part_stmts = curr_stmts; - if (comptime Environment.allow_assert) assert(part_stmts.len == end_iife_stmts_count); - var part_stmts_i: usize = 0; - - var import_list_i: usize = 0; - var export_list_i: usize = 0; - - // We must always copy it into the new stmts array - for (part.stmts) |stmt| { - switch (stmt.data) { - .s_import => { - imports_list[import_list_i] = stmt; - import_list_i += 1; + const wrapper = p.newExpr( + E.Function{ + .func = G.Fn{ + .name = null, + .open_parens_loc = logger.Loc.Empty, + .args = args, + .body = .{ .loc = logger.Loc.Empty, .stmts = stmts_to_copy }, + .flags = Flags.Function.init(.{ .is_export = false }), + }, }, - .s_export_star, .s_export_from => { - exports_from[export_list_i] = stmt; - export_list_i += 1; + logger.Loc.Empty, + ); + var call_args = allocator.alloc(Expr, 3) catch unreachable; + call_args[0] = p.newExpr(E.Identifier{ .ref = p.module_ref }, logger.Loc.Empty); + call_args[1] = p.newExpr(E.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty); + call_args[2] = p.newExpr(E.Identifier{ .ref = p.require_ref }, logger.Loc.Empty); + + const call = p.newExpr( + E.Call{ + .target = wrapper, + .args = ExprNodeList.init(call_args), }, - else => { - part_stmts[part_stmts_i] = stmt; - part_stmts_i += 1; + logger.Loc.Empty, + ); + var only_stmt = try p.allocator.alloc(Stmt, 1); + only_stmt[0] = p.s( + S.SExpr{ + .value = call, }, - } - } + logger.Loc.Empty, + ); + parts[0].stmts = only_stmt; + parts.len = 1; + }, - const new_call_args_count: usize = if (p.options.features.react_fast_refresh) 3 else 2; - var call_args = try allocator.alloc(Expr, new_call_args_count + 1); - var new_call_args = call_args[0..new_call_args_count]; - var hmr_module_ident = p.newExpr(E.Identifier{ .ref = p.hmr_module.ref }, logger.Loc.Empty); + .none => { + if (p.options.features.hot_module_reloading and p.options.features.allow_runtime) { + var named_exports_count: usize = p.named_exports.count(); + const named_imports: js_ast.Ast.NamedImports = p.named_imports; + + // To transform to something HMR'able, we must: + // 1. Wrap the top level code in an IIFE + // 2. Move imports to the top of the file (preserving the order) + // 3. Remove export clauses (done during ImportScanner) + // 4. Move export * from and export from to the bottom of the file (or the top, it doesn't matter I don't think) + // 5. Export everything as getters in our HMR module + // 6. Call the HMRModule's exportAll function like so: + // __hmrModule.exportAll({ + // exportAlias: () => identifier, + // exportAlias: () => identifier, + // }); + // This has the unfortunate property of making property accesses of exports slower at runtime. + // But, I'm not sure there's a way to use regular properties without breaking stuff. + var imports_count: usize = 0; + // We have to also move export from, since we will preserve those + var exports_from_count: usize = 0; + // Two passes. First pass just counts. + for (parts[parts.len - 1].stmts) |stmt| { + imports_count += switch (stmt.data) { + .s_import => @as(usize, 1), + else => @as(usize, 0), + }; + exports_from_count += switch (stmt.data) { + .s_export_star, .s_export_from => @as(usize, 1), + else => @as(usize, 0), + }; + } + var part = &parts[parts.len - 1]; + + const end_iife_stmts_count = part.stmts.len - imports_count - exports_from_count + 1; + // Why 7? + // 1. HMRClient.activate(${isDebug}); + // 2. var __hmrModule = new HMMRModule(id, file_path), __exports = __hmrModule.exports; + // 3. (__hmrModule.load = function() { + // ${...end_iffe_stmts_count - 1} + // ${end_iffe_stmts_count} + // __hmrModule.exportAll({exportAlias: () => identifier}) <-- ${named_exports_count} + // (); + // 4. var __hmrExport_exportName = __hmrModule.exports.exportName, + // 5. export { __hmrExport_exportName as blah, ... } + // 6. __hmrModule.onSetExports = (newExports) => { + // $named_exports_count __hmrExport_exportName = newExports.exportName; <-- ${named_exports_count} + // } + + // if there are no exports: + // - there shouldn't be an export statement + // - we don't need the S.Local for wrapping the exports + // We still call exportAll just with an empty object. + const has_any_exports = named_exports_count > 0; + + const toplevel_stmts_count = 3 + (@intCast(usize, @boolToInt(has_any_exports)) * 2); + var _stmts = allocator.alloc( + Stmt, + end_iife_stmts_count + toplevel_stmts_count + (named_exports_count * 2) + imports_count + exports_from_count, + ) catch unreachable; + // Normally, we'd have to grow that inner function's stmts list by one + // But we can avoid that by just making them all use this same array. + var curr_stmts = _stmts; + + // in debug: crash in the printer due to undefined memory + // in release: print ";" instead. + // this should never happen regardless, but i'm just being cautious here. + if (comptime !Environment.isDebug) { + std.mem.set(Stmt, _stmts, Stmt.empty()); + } + + // Second pass: move any imports from the part's stmts array to the new stmts + var imports_list = curr_stmts[0..imports_count]; + curr_stmts = curr_stmts[imports_list.len..]; + var toplevel_stmts = curr_stmts[0..toplevel_stmts_count]; + curr_stmts = curr_stmts[toplevel_stmts.len..]; + var exports_from = curr_stmts[0..exports_from_count]; + curr_stmts = curr_stmts[exports_from.len..]; + // This is used for onSetExports + var update_function_stmts = curr_stmts[0..named_exports_count]; + curr_stmts = curr_stmts[update_function_stmts.len..]; + var export_all_function_body_stmts = curr_stmts[0..named_exports_count]; + curr_stmts = curr_stmts[export_all_function_body_stmts.len..]; + // This is the original part statements + 1 + var part_stmts = curr_stmts; + if (comptime Environment.allow_assert) assert(part_stmts.len == end_iife_stmts_count); + var part_stmts_i: usize = 0; + + var import_list_i: usize = 0; + var export_list_i: usize = 0; + + // We must always copy it into the new stmts array + for (part.stmts) |stmt| { + switch (stmt.data) { + .s_import => { + imports_list[import_list_i] = stmt; + import_list_i += 1; + }, + .s_export_star, .s_export_from => { + exports_from[export_list_i] = stmt; + export_list_i += 1; + }, + else => { + part_stmts[part_stmts_i] = stmt; + part_stmts_i += 1; + }, + } + } - new_call_args[0] = p.newExpr(E.Number{ .value = @intToFloat(f64, p.options.filepath_hash_for_hmr) }, logger.Loc.Empty); - // This helps us provide better error messages - new_call_args[1] = p.newExpr(E.String{ .data = p.source.path.pretty }, logger.Loc.Empty); - if (p.options.features.react_fast_refresh) { - new_call_args[2] = p.newExpr(E.Identifier{ .ref = p.jsx_refresh_runtime.ref }, logger.Loc.Empty); - } + const new_call_args_count: usize = if (p.options.features.react_fast_refresh) 3 else 2; + var call_args = try allocator.alloc(Expr, new_call_args_count + 1); + var new_call_args = call_args[0..new_call_args_count]; + var hmr_module_ident = p.newExpr(E.Identifier{ .ref = p.hmr_module.ref }, logger.Loc.Empty); - var toplevel_stmts_i: u8 = 0; + new_call_args[0] = p.newExpr(E.Number{ .value = @intToFloat(f64, p.options.filepath_hash_for_hmr) }, logger.Loc.Empty); + // This helps us provide better error messages + new_call_args[1] = p.newExpr(E.String{ .data = p.source.path.pretty }, logger.Loc.Empty); + if (p.options.features.react_fast_refresh) { + new_call_args[2] = p.newExpr(E.Identifier{ .ref = p.jsx_refresh_runtime.ref }, logger.Loc.Empty); + } - var decls = try allocator.alloc(G.Decl, 2 + named_exports_count); - var first_decl = decls[0..2]; - // We cannot rely on import.meta.url because if we import it within a blob: url, it will be nonsensical - // var __hmrModule = new HMRModule(123123124, "/index.js"), __exports = __hmrModule.exports; - const hmr_import_module_ = if (p.options.features.react_fast_refresh) - p.runtime_imports.__FastRefreshModule.? - else - p.runtime_imports.__HMRModule.?; - - const hmr_import_ref = hmr_import_module_.ref; - first_decl[0] = G.Decl{ - .binding = p.b(B.Identifier{ .ref = p.hmr_module.ref }, logger.Loc.Empty), - .value = p.newExpr(E.New{ - .args = ExprNodeList.init(new_call_args), - .target = p.newExpr( - E.Identifier{ - .ref = hmr_import_ref, - }, - logger.Loc.Empty, - ), - .close_parens_loc = logger.Loc.Empty, - }, logger.Loc.Empty), - }; - first_decl[1] = G.Decl{ - .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty), - .value = p.newExpr(E.Dot{ - .target = p.newExpr(E.Identifier{ .ref = p.hmr_module.ref }, logger.Loc.Empty), - .name = "exports", - .name_loc = logger.Loc.Empty, - }, logger.Loc.Empty), - }; + var toplevel_stmts_i: u8 = 0; - var export_clauses = try allocator.alloc(js_ast.ClauseItem, named_exports_count); - var named_export_i: usize = 0; - var named_exports_iter = p.named_exports.iterator(); - var export_properties = try allocator.alloc(G.Property, named_exports_count); + var decls = try allocator.alloc(G.Decl, 2 + named_exports_count); + var first_decl = decls[0..2]; + // We cannot rely on import.meta.url because if we import it within a blob: url, it will be nonsensical + // var __hmrModule = new HMRModule(123123124, "/index.js"), __exports = __hmrModule.exports; + const hmr_import_module_ = if (p.options.features.react_fast_refresh) + p.runtime_imports.__FastRefreshModule.? + else + p.runtime_imports.__HMRModule.?; - var export_name_string_length: usize = 0; - while (named_exports_iter.next()) |named_export| { - export_name_string_length += named_export.key_ptr.len + "$$hmr_".len; - } + const hmr_import_ref = hmr_import_module_.ref; + first_decl[0] = G.Decl{ + .binding = p.b(B.Identifier{ .ref = p.hmr_module.ref }, logger.Loc.Empty), + .value = p.newExpr(E.New{ + .args = ExprNodeList.init(new_call_args), + .target = p.newExpr( + E.Identifier{ + .ref = hmr_import_ref, + }, + logger.Loc.Empty, + ), + .close_parens_loc = logger.Loc.Empty, + }, logger.Loc.Empty), + }; + first_decl[1] = G.Decl{ + .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty), + .value = p.newExpr(E.Dot{ + .target = p.newExpr(E.Identifier{ .ref = p.hmr_module.ref }, logger.Loc.Empty), + .name = "exports", + .name_loc = logger.Loc.Empty, + }, logger.Loc.Empty), + }; - var export_name_string_all = try allocator.alloc(u8, export_name_string_length); - var export_name_string_remainder = export_name_string_all; - var hmr_module_exports_dot = p.newExpr( - E.Dot{ - .target = hmr_module_ident, - .name = "exports", - .name_loc = logger.Loc.Empty, - }, - logger.Loc.Empty, - ); - var exports_decls = decls[first_decl.len..]; - named_exports_iter = p.named_exports.iterator(); - var update_function_args = try allocator.alloc(G.Arg, 1); - var exports_ident = p.newExpr(E.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty); - update_function_args[0] = G.Arg{ .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty) }; - - while (named_exports_iter.next()) |named_export| { - const named_export_value = named_export.value_ptr.*; - - // Do not try to HMR export {foo} from 'bar'; - if (named_imports.get(named_export_value.ref)) |named_import| { - if (named_import.is_exported) continue; - } - - const named_export_symbol: Symbol = p.symbols.items[named_export_value.ref.innerIndex()]; - - var export_name_string = export_name_string_remainder[0 .. named_export.key_ptr.len + "$$hmr_".len]; - export_name_string_remainder = export_name_string_remainder[export_name_string.len..]; - bun.copy(u8, export_name_string, "$$hmr_"); - bun.copy(u8, export_name_string["$$hmr_".len..], named_export.key_ptr.*); - - var name_ref = try p.declareSymbol(.other, logger.Loc.Empty, export_name_string); - - var body_stmts = export_all_function_body_stmts[named_export_i .. named_export_i + 1]; - body_stmts[0] = p.s( - // was this originally a named import? - // preserve the identifier - S.Return{ .value = if (named_export_symbol.namespace_alias != null) - p.newExpr(E.ImportIdentifier{ - .ref = named_export_value.ref, - .was_originally_identifier = true, - }, logger.Loc.Empty) - else - p.newExpr(E.Identifier{ - .ref = named_export_value.ref, - }, logger.Loc.Empty) }, - logger.Loc.Empty, - ); - export_clauses[named_export_i] = js_ast.ClauseItem{ - .original_name = "", - .alias = named_export.key_ptr.*, - .alias_loc = named_export_value.alias_loc, - .name = .{ .ref = name_ref, .loc = logger.Loc.Empty }, - }; + var export_clauses = try allocator.alloc(js_ast.ClauseItem, named_exports_count); + var named_export_i: usize = 0; + var named_exports_iter = p.named_exports.iterator(); + var export_properties = try allocator.alloc(G.Property, named_exports_count); - var decl_value = p.newExpr( - E.Dot{ .target = hmr_module_exports_dot, .name = named_export.key_ptr.*, .name_loc = logger.Loc.Empty }, - logger.Loc.Empty, - ); - exports_decls[named_export_i] = G.Decl{ - .binding = p.b(B.Identifier{ .ref = name_ref }, logger.Loc.Empty), - .value = decl_value, - }; + var export_name_string_length: usize = 0; + while (named_exports_iter.next()) |named_export| { + export_name_string_length += named_export.key_ptr.len + "$$hmr_".len; + } - update_function_stmts[named_export_i] = Stmt.assign( - p.newExpr( - E.Identifier{ .ref = name_ref }, + var export_name_string_all = try allocator.alloc(u8, export_name_string_length); + var export_name_string_remainder = export_name_string_all; + var hmr_module_exports_dot = p.newExpr( + E.Dot{ + .target = hmr_module_ident, + .name = "exports", + .name_loc = logger.Loc.Empty, + }, logger.Loc.Empty, - ), - p.newExpr(E.Dot{ - .target = exports_ident, - .name = named_export.key_ptr.*, - .name_loc = logger.Loc.Empty, - }, logger.Loc.Empty), - allocator, - ); + ); + var exports_decls = decls[first_decl.len..]; + named_exports_iter = p.named_exports.iterator(); + var update_function_args = try allocator.alloc(G.Arg, 1); + var exports_ident = p.newExpr(E.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty); + update_function_args[0] = G.Arg{ .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty) }; + + while (named_exports_iter.next()) |named_export| { + const named_export_value = named_export.value_ptr.*; + + // Do not try to HMR export {foo} from 'bar'; + if (named_imports.get(named_export_value.ref)) |named_import| { + if (named_import.is_exported) continue; + } + + const named_export_symbol: Symbol = p.symbols.items[named_export_value.ref.innerIndex()]; + + var export_name_string = export_name_string_remainder[0 .. named_export.key_ptr.len + "$$hmr_".len]; + export_name_string_remainder = export_name_string_remainder[export_name_string.len..]; + bun.copy(u8, export_name_string, "$$hmr_"); + bun.copy(u8, export_name_string["$$hmr_".len..], named_export.key_ptr.*); + + var name_ref = try p.declareSymbol(.other, logger.Loc.Empty, export_name_string); + + var body_stmts = export_all_function_body_stmts[named_export_i .. named_export_i + 1]; + body_stmts[0] = p.s( + // was this originally a named import? + // preserve the identifier + S.Return{ .value = if (named_export_symbol.namespace_alias != null) + p.newExpr(E.ImportIdentifier{ + .ref = named_export_value.ref, + .was_originally_identifier = true, + }, logger.Loc.Empty) + else + p.newExpr(E.Identifier{ + .ref = named_export_value.ref, + }, logger.Loc.Empty) }, + logger.Loc.Empty, + ); + export_clauses[named_export_i] = js_ast.ClauseItem{ + .original_name = "", + .alias = named_export.key_ptr.*, + .alias_loc = named_export_value.alias_loc, + .name = .{ .ref = name_ref, .loc = logger.Loc.Empty }, + }; - export_properties[named_export_i] = G.Property{ - .key = p.newExpr(E.String{ .data = named_export.key_ptr.* }, logger.Loc.Empty), - .value = p.newExpr( - E.Arrow{ - .args = &[_]G.Arg{}, - .body = .{ - .stmts = body_stmts, - .loc = logger.Loc.Empty, - }, - .prefer_expr = true, - }, + var decl_value = p.newExpr( + E.Dot{ .target = hmr_module_exports_dot, .name = named_export.key_ptr.*, .name_loc = logger.Loc.Empty }, + logger.Loc.Empty, + ); + exports_decls[named_export_i] = G.Decl{ + .binding = p.b(B.Identifier{ .ref = name_ref }, logger.Loc.Empty), + .value = decl_value, + }; + + update_function_stmts[named_export_i] = Stmt.assign( + p.newExpr( + E.Identifier{ .ref = name_ref }, + logger.Loc.Empty, + ), + p.newExpr(E.Dot{ + .target = exports_ident, + .name = named_export.key_ptr.*, + .name_loc = logger.Loc.Empty, + }, logger.Loc.Empty), + allocator, + ); + + export_properties[named_export_i] = G.Property{ + .key = p.newExpr(E.String{ .data = named_export.key_ptr.* }, logger.Loc.Empty), + .value = p.newExpr( + E.Arrow{ + .args = &[_]G.Arg{}, + .body = .{ + .stmts = body_stmts, + .loc = logger.Loc.Empty, + }, + .prefer_expr = true, + }, + logger.Loc.Empty, + ), + }; + named_export_i += 1; + } + var export_all_args = call_args[new_call_args.len..]; + export_all_args[0] = p.newExpr( + E.Object{ .properties = Property.List.init(export_properties[0..named_export_i]) }, logger.Loc.Empty, - ), - }; - named_export_i += 1; - } - var export_all_args = call_args[new_call_args.len..]; - export_all_args[0] = p.newExpr( - E.Object{ .properties = Property.List.init(export_properties[0..named_export_i]) }, - logger.Loc.Empty, - ); + ); - part_stmts[part_stmts.len - 1] = p.s( - S.SExpr{ - .value = p.newExpr( - E.Call{ - .target = p.newExpr( - E.Dot{ - .target = hmr_module_ident, - .name = "exportAll", - .name_loc = logger.Loc.Empty, + part_stmts[part_stmts.len - 1] = p.s( + S.SExpr{ + .value = p.newExpr( + E.Call{ + .target = p.newExpr( + E.Dot{ + .target = hmr_module_ident, + .name = "exportAll", + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ), + .args = ExprNodeList.init(export_all_args), }, logger.Loc.Empty, ), - .args = ExprNodeList.init(export_all_args), }, logger.Loc.Empty, - ), - }, - logger.Loc.Empty, - ); - - toplevel_stmts[toplevel_stmts_i] = p.s( - S.Local{ - .decls = G.Decl.List.init(first_decl), - }, - logger.Loc.Empty, - ); + ); - toplevel_stmts_i += 1; + toplevel_stmts[toplevel_stmts_i] = p.s( + S.Local{ + .decls = G.Decl.List.init(first_decl), + }, + logger.Loc.Empty, + ); - const is_async = !p.top_level_await_keyword.isEmpty(); + toplevel_stmts_i += 1; - var func = p.newExpr( - E.Function{ - .func = .{ - .body = .{ .loc = logger.Loc.Empty, .stmts = part_stmts[0 .. part_stmts_i + 1] }, - .name = null, - .open_parens_loc = logger.Loc.Empty, - .flags = Flags.Function.init(.{ - .print_as_iife = true, - .is_async = is_async, - }), - }, - }, - logger.Loc.Empty, - ); + const is_async = !p.top_level_await_keyword.isEmpty(); - const call_load = p.newExpr( - E.Call{ - .target = Expr.assign( - p.newExpr( - E.Dot{ - .name = "_load", - .target = hmr_module_ident, - .name_loc = logger.Loc.Empty, + var func = p.newExpr( + E.Function{ + .func = .{ + .body = .{ .loc = logger.Loc.Empty, .stmts = part_stmts[0 .. part_stmts_i + 1] }, + .name = null, + .open_parens_loc = logger.Loc.Empty, + .flags = Flags.Function.init(.{ + .print_as_iife = true, + .is_async = is_async, + }), }, - logger.Loc.Empty, - ), - func, - allocator, - ), - }, - logger.Loc.Empty, - ); - // (__hmrModule._load = function())() - toplevel_stmts[toplevel_stmts_i] = p.s( - S.SExpr{ - .value = if (is_async) - p.newExpr(E.Await{ .value = call_load }, logger.Loc.Empty) - else - call_load, - }, - logger.Loc.Empty, - ); - - toplevel_stmts_i += 1; + }, + logger.Loc.Empty, + ); - if (has_any_exports) { - if (named_export_i > 0) { - toplevel_stmts[toplevel_stmts_i] = p.s( - S.Local{ - .decls = G.Decl.List.init(exports_decls[0..named_export_i]), + const call_load = p.newExpr( + E.Call{ + .target = Expr.assign( + p.newExpr( + E.Dot{ + .name = "_load", + .target = hmr_module_ident, + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ), + func, + allocator, + ), }, logger.Loc.Empty, ); - } else { + // (__hmrModule._load = function())() toplevel_stmts[toplevel_stmts_i] = p.s( - S.Empty{}, + S.SExpr{ + .value = if (is_async) + p.newExpr(E.Await{ .value = call_load }, logger.Loc.Empty) + else + call_load, + }, logger.Loc.Empty, ); - } - toplevel_stmts_i += 1; - } + toplevel_stmts_i += 1; - toplevel_stmts[toplevel_stmts_i] = p.s( - S.SExpr{ - .value = Expr.assign( - p.newExpr( - E.Dot{ - .name = "_update", - .target = hmr_module_ident, - .name_loc = logger.Loc.Empty, - }, - logger.Loc.Empty, - ), - p.newExpr( - E.Function{ - .func = .{ - .body = .{ .loc = logger.Loc.Empty, .stmts = if (named_export_i > 0) update_function_stmts[0..named_export_i] else &.{} }, - .name = null, - .args = update_function_args, - .open_parens_loc = logger.Loc.Empty, + if (has_any_exports) { + if (named_export_i > 0) { + toplevel_stmts[toplevel_stmts_i] = p.s( + S.Local{ + .decls = G.Decl.List.init(exports_decls[0..named_export_i]), }, - }, - logger.Loc.Empty, - ), - allocator, - ), - }, - logger.Loc.Empty, - ); - toplevel_stmts_i += 1; - if (has_any_exports) { - if (named_export_i > 0) { + logger.Loc.Empty, + ); + } else { + toplevel_stmts[toplevel_stmts_i] = p.s( + S.Empty{}, + logger.Loc.Empty, + ); + } + + toplevel_stmts_i += 1; + } + toplevel_stmts[toplevel_stmts_i] = p.s( - S.ExportClause{ - .items = export_clauses[0..named_export_i], + S.SExpr{ + .value = Expr.assign( + p.newExpr( + E.Dot{ + .name = "_update", + .target = hmr_module_ident, + .name_loc = logger.Loc.Empty, + }, + logger.Loc.Empty, + ), + p.newExpr( + E.Function{ + .func = .{ + .body = .{ .loc = logger.Loc.Empty, .stmts = if (named_export_i > 0) update_function_stmts[0..named_export_i] else &.{} }, + .name = null, + .args = update_function_args, + .open_parens_loc = logger.Loc.Empty, + }, + }, + logger.Loc.Empty, + ), + allocator, + ), }, logger.Loc.Empty, ); - } else { - toplevel_stmts[toplevel_stmts_i] = p.s( - S.Empty{}, - logger.Loc.Empty, - ); - } - } + toplevel_stmts_i += 1; + if (has_any_exports) { + if (named_export_i > 0) { + toplevel_stmts[toplevel_stmts_i] = p.s( + S.ExportClause{ + .items = export_clauses[0..named_export_i], + }, + logger.Loc.Empty, + ); + } else { + toplevel_stmts[toplevel_stmts_i] = p.s( + S.Empty{}, + logger.Loc.Empty, + ); + } + } - part.stmts = _stmts[0 .. imports_list.len + toplevel_stmts.len + exports_from.len]; - } else if (p.options.features.hot_module_reloading) {} + part.stmts = _stmts[0 .. imports_list.len + toplevel_stmts.len + exports_from.len]; + } + }, + } var top_level_symbols_to_parts = js_ast.Ast.TopLevelSymbolToParts{}; var top_level = &top_level_symbols_to_parts; @@ -21567,6 +21639,7 @@ fn NewParser_( // .top_Level_await_keyword = p.top_level_await_keyword, .bun_plugin = p.bun_plugin, .commonjs_named_exports = p.commonjs_named_exports, + .commonjs_export_names = p.commonjs_export_names.keys(), .hashbang = hashbang, @@ -21756,3 +21829,9 @@ pub fn newLazyExportAST( result.ast.has_lazy_export = true; return result.ast; } + +const CommonJSWrapper = union(enum) { + none: void, + bun_dev: Expr, + bun_js: void, +}; diff --git a/src/js_printer.zig b/src/js_printer.zig index af820520e..8a540edf9 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -1831,7 +1831,11 @@ fn NewPrinter( } if (comptime is_bun_platform) { - p.print("import.meta.require"); + if (p.options.module_type == .esm) { + p.print("import.meta.require"); + } else { + p.print("require"); + } } else { p.printSymbol(p.options.require_ref.?); } @@ -2183,6 +2187,15 @@ fn NewPrinter( p.print(")"); } }, + .e_require_call_target => { + p.addSourceMapping(expr.loc); + + if (p.options.module_type == .cjs or !is_bun_platform) { + p.print("require"); + } else { + p.print("import.meta.require"); + } + }, .e_require_string => |e| { if (rewrite_esm_to_cjs and p.importRecord(e.import_record_index).is_legacy_bundled) { p.printIndent(); diff --git a/src/linker.zig b/src/linker.zig index 6e99dbb5b..ca57e2f85 100644 --- a/src/linker.zig +++ b/src/linker.zig @@ -566,7 +566,7 @@ pub const Linker = struct { // If it's a namespace import, assume it's safe. // We can do this in the printer instead of creating a bunch of AST nodes here. // But we need to at least tell the printer that this needs to happen. - if (loader != .napi and resolved_import.shouldAssumeCommonJS(import_record.kind)) { + if (loader != .napi and resolved_import.shouldAssumeCommonJS(import_record.kind) and !is_bun) { import_record.do_commonjs_transform_in_printer = true; import_record.module_id = @truncate(u32, std.hash.Wyhash.hash(0, path.pretty)); } diff --git a/src/runtime.zig b/src/runtime.zig index 99d8ca102..f09e16378 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -324,6 +324,8 @@ pub const Runtime = struct { /// So we have a list of packages which we know are safe to do this with. unwrap_commonjs_packages: []const string = &.{}, + commonjs_at_runtime: bool = false, + pub fn shouldUnwrapRequire(this: *const Features, package_name: string) bool { return package_name.len > 0 and strings.indexAny(this.unwrap_commonjs_packages, package_name) != null; } diff --git a/test/js/bun/stream/direct-readable-stream.test.tsx b/test/js/bun/stream/direct-readable-stream.test.tsx index 1accb6d29..c06840947 100644 --- a/test/js/bun/stream/direct-readable-stream.test.tsx +++ b/test/js/bun/stream/direct-readable-stream.test.tsx @@ -12,7 +12,7 @@ import { expectMaxObjectTypeCount, gc } from "harness"; // @ts-ignore import { renderToReadableStream as renderToReadableStreamBrowser } from "react-dom/server.browser"; import { renderToReadableStream as renderToReadableStreamBun } from "react-dom/server"; -import React from "react"; +import * as React from "react"; if (!import.meta.resolveSync("react-dom/server").endsWith("server.bun.js")) { throw new Error("react-dom/server is not the correct version:\n " + import.meta.resolveSync("react-dom/server")); |