mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
module.children and Module.runMain (#18343)
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com> Co-authored-by: 190n <ben@bun.sh>
This commit is contained in:
@@ -118,7 +118,7 @@ Some methods are not optimized yet.
|
||||
|
||||
### [`node:module`](https://nodejs.org/api/module.html)
|
||||
|
||||
🟡 Missing `runMain` `syncBuiltinESMExports`, `Module#load()`. Overriding `require.cache` is supported for ESM & CJS modules. `module._extensions`, `module._pathCache`, `module._cache` are no-ops. `module.register` is not implemented and we recommend using a [`Bun.plugin`](https://bun.sh/docs/runtime/plugins) in the meantime.
|
||||
🟡 Missing `syncBuiltinESMExports`, `Module#load()`. Overriding `require.cache` is supported for ESM & CJS modules. `module._extensions`, `module._pathCache`, `module._cache` are no-ops. `module.register` is not implemented and we recommend using a [`Bun.plugin`](https://bun.sh/docs/runtime/plugins) in the meantime.
|
||||
|
||||
### [`node:net`](https://nodejs.org/api/net.html)
|
||||
|
||||
|
||||
@@ -257,12 +257,22 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionEvaluateCommonJSModule, (JSGlobalObject * lex
|
||||
RELEASE_AND_RETURN(throwScope, JSValue::encode(jsUndefined()));
|
||||
}
|
||||
|
||||
UNUSED_PARAM(referrer);
|
||||
JSValue returnValue = jsNull();
|
||||
if (LIKELY(referrer)) {
|
||||
if (UNLIKELY(referrer->m_childrenValue)) {
|
||||
// It's too hard to append from native code:
|
||||
// referrer.children.indexOf(moduleObject) === -1 && referrer.children.push(moduleObject)
|
||||
returnValue = referrer->m_childrenValue.get();
|
||||
} else {
|
||||
referrer->m_children.append(WriteBarrier<Unknown>());
|
||||
referrer->m_children.last().set(vm, referrer, moduleObject);
|
||||
}
|
||||
}
|
||||
|
||||
moduleObject->load(vm, globalObject);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
|
||||
RELEASE_AND_RETURN(throwScope, JSValue::encode(jsUndefined()));
|
||||
RELEASE_AND_RETURN(throwScope, JSValue::encode(returnValue));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(requireResolvePathsFunction, (JSGlobalObject * globalObject, CallFrame* callframe))
|
||||
@@ -450,6 +460,62 @@ JSC_DEFINE_CUSTOM_GETTER(getterPaths, (JSC::JSGlobalObject * globalObject, JSC::
|
||||
return JSValue::encode(thisObject->m_paths.get());
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_SETTER(setterChildren,
|
||||
(JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue,
|
||||
JSC::EncodedJSValue value, JSC::PropertyName propertyName))
|
||||
{
|
||||
JSCommonJSModule* thisObject = jsDynamicCast<JSCommonJSModule*>(JSValue::decode(thisValue));
|
||||
if (!thisObject)
|
||||
return false;
|
||||
thisObject->m_children.clear();
|
||||
thisObject->m_childrenValue.set(globalObject->vm(), thisObject, JSValue::decode(value));
|
||||
return true;
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(getterChildren, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName))
|
||||
{
|
||||
JSCommonJSModule* mod = jsDynamicCast<JSCommonJSModule*>(JSValue::decode(thisValue));
|
||||
if (UNLIKELY(!mod)) {
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
if (!mod->m_childrenValue) {
|
||||
auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm());
|
||||
MarkedArgumentBuffer children;
|
||||
children.ensureCapacity(mod->m_children.size());
|
||||
|
||||
// Deduplicate children while preserving insertion order.
|
||||
JSCommonJSModule* last = nullptr;
|
||||
int n = -1;
|
||||
for (WriteBarrier<Unknown> childBarrier : mod->m_children) {
|
||||
JSCommonJSModule* child = jsCast<JSCommonJSModule*>(childBarrier.get());
|
||||
// Check the last module since duplicate imports, if any, will
|
||||
// probably be adjacent. Then just do a linear scan.
|
||||
if (UNLIKELY(last == child)) continue;
|
||||
int i = 0;
|
||||
while (i < n) {
|
||||
if (UNLIKELY(children.at(i).asCell() == child)) goto next;
|
||||
i += 1;
|
||||
}
|
||||
children.append(child);
|
||||
last = child;
|
||||
n += 1;
|
||||
next: {
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the array
|
||||
JSArray* array = JSC::constructArray(globalObject, static_cast<ArrayAllocationProfile*>(nullptr), children);
|
||||
mod->m_childrenValue.set(globalObject->vm(), mod, array);
|
||||
|
||||
mod->m_children.clear();
|
||||
|
||||
return JSValue::encode(array);
|
||||
}
|
||||
|
||||
return JSValue::encode(mod->m_childrenValue.get());
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(getterLoaded, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName))
|
||||
{
|
||||
JSCommonJSModule* thisObject = jsDynamicCast<JSCommonJSModule*>(JSValue::decode(thisValue));
|
||||
@@ -510,7 +576,6 @@ JSC_DEFINE_CUSTOM_SETTER(setterParent,
|
||||
thisObject->m_overriddenParent.clear();
|
||||
} else {
|
||||
thisObject->m_parent = {};
|
||||
thisObject->m_overriddenParent.set(globalObject->vm(), thisObject, JSValue::decode(value));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -528,11 +593,6 @@ JSC_DEFINE_CUSTOM_SETTER(setterLoaded,
|
||||
return true;
|
||||
}
|
||||
|
||||
static JSValue createChildren(VM& vm, JSObject* object)
|
||||
{
|
||||
return constructEmptyArray(object->globalObject(), nullptr, 0);
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(functionCommonJSModuleRecord_compile, (JSGlobalObject * globalObject, CallFrame* callframe))
|
||||
{
|
||||
auto* moduleObject = jsDynamicCast<JSCommonJSModule*>(callframe->thisValue());
|
||||
@@ -596,7 +656,7 @@ JSC_DEFINE_HOST_FUNCTION(functionCommonJSModuleRecord_compile, (JSGlobalObject *
|
||||
|
||||
static const struct HashTableValue JSCommonJSModulePrototypeTableValues[] = {
|
||||
{ "_compile"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, functionCommonJSModuleRecord_compile, 2 } },
|
||||
{ "children"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, createChildren } },
|
||||
{ "children"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, getterChildren, setterChildren } },
|
||||
{ "filename"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterFilename, setterFilename } },
|
||||
{ "id"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterId, setterId } },
|
||||
{ "loaded"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterLoaded, setterLoaded } },
|
||||
@@ -1016,6 +1076,8 @@ void JSCommonJSModule::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
||||
visitor.appendHidden(thisObject->m_dirname);
|
||||
visitor.appendHidden(thisObject->m_paths);
|
||||
visitor.appendHidden(thisObject->m_overriddenParent);
|
||||
visitor.appendHidden(thisObject->m_childrenValue);
|
||||
visitor.appendValues(thisObject->m_children.data(), thisObject->m_children.size());
|
||||
}
|
||||
|
||||
DEFINE_VISIT_CHILDREN(JSCommonJSModule);
|
||||
|
||||
@@ -44,6 +44,17 @@ public:
|
||||
mutable JSC::WriteBarrier<JSString> m_dirname;
|
||||
// Initialized lazily; can be overridden.
|
||||
mutable JSC::WriteBarrier<Unknown> m_paths;
|
||||
// Children must always be tracked in case the script decides to access
|
||||
// `module.children`. In that case, all children may also need their
|
||||
// children fields to exist, recursively. To avoid allocating a *JSArray for
|
||||
// each module, the children array is constructed internally as a
|
||||
// Vector of pointers. If accessed, deduplication happens and array is
|
||||
// moved into JavaScript. These two fields add 16 bytes to JSCommonJSModule.
|
||||
// `m_childrenValue` can be set to any value via the user-exposed setter,
|
||||
// but Bun does not test that behavior besides ensuring it does not crash.
|
||||
mutable JSC::WriteBarrier<Unknown> m_childrenValue;
|
||||
// This must be WriteBarrier<Unknown> to compile; always JSCommonJSModule
|
||||
WTF::Vector<WriteBarrier<Unknown>> m_children;
|
||||
|
||||
// Visited by the GC. When the module is assigned a non-JSCommonJSModule
|
||||
// parent, it is assigned to this field.
|
||||
|
||||
@@ -304,7 +304,7 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSyncPrivate(JSC::JSGlo
|
||||
if (LIKELY(globalObject)) {
|
||||
if (UNLIKELY(globalObject->hasOverriddenModuleResolveFilenameFunction)) {
|
||||
auto overrideHandler = jsCast<JSObject*>(globalObject->m_moduleResolveFilenameFunction.getInitializedOnMainThread(globalObject));
|
||||
if (UNLIKELY(overrideHandler)) {
|
||||
if (LIKELY(overrideHandler)) {
|
||||
ASSERT(overrideHandler->isCallable());
|
||||
JSValue parentModuleObject = globalObject->requireMap()->get(globalObject, from);
|
||||
|
||||
|
||||
@@ -4014,6 +4014,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
||||
visitor.append(thisObject->m_currentNapiHandleScopeImpl);
|
||||
|
||||
thisObject->m_moduleResolveFilenameFunction.visit(visitor);
|
||||
thisObject->m_moduleRunMainFunction.visit(visitor);
|
||||
thisObject->m_nodeModuleConstructor.visit(visitor);
|
||||
thisObject->m_asyncBoundFunctionStructure.visit(visitor);
|
||||
thisObject->m_bunObject.visit(visitor);
|
||||
|
||||
@@ -398,6 +398,7 @@ public:
|
||||
mutable WriteBarrier<JSFunction> m_readableStreamToFormData;
|
||||
|
||||
LazyProperty<JSGlobalObject, JSCell> m_moduleResolveFilenameFunction;
|
||||
LazyProperty<JSGlobalObject, JSCell> m_moduleRunMainFunction;
|
||||
LazyProperty<JSGlobalObject, JSObject> m_nodeModuleConstructor;
|
||||
|
||||
mutable WriteBarrier<Unknown> m_nextTickQueue;
|
||||
@@ -632,6 +633,8 @@ public:
|
||||
bool hasOverriddenModuleResolveFilenameFunction = false;
|
||||
// De-optimization once `require("module").wrapper` or `require("module").wrap` is written to
|
||||
bool hasOverriddenModuleWrapper = false;
|
||||
// De-optimization once `require("module").runMain` is written to
|
||||
bool hasOverriddenModuleRunMain = false;
|
||||
|
||||
WTF::Vector<std::unique_ptr<napi_env__>> m_napiEnvs;
|
||||
napi_env makeNapiEnv(const napi_module&);
|
||||
|
||||
@@ -3179,19 +3179,7 @@ JSC__JSModuleLoader__loadAndEvaluateModule(JSC__JSGlobalObject* globalObject,
|
||||
JSC::JSNativeStdFunction* resolverFunction = JSC::JSNativeStdFunction::create(
|
||||
vm, globalObject, 1, String(), resolverFunctionCallback);
|
||||
|
||||
auto result = promise->then(globalObject, resolverFunction, nullptr);
|
||||
|
||||
// if (promise->status(globalObject->vm()) ==
|
||||
// JSC::JSPromise::Status::Fulfilled) {
|
||||
// return reinterpret_cast<JSC::JSInternalPromise*>(
|
||||
// JSC::JSInternalPromise::resolvedPromise(
|
||||
// globalObject,
|
||||
// doLink(globalObject, promise->result(globalObject->vm()))
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
|
||||
return result;
|
||||
return promise->then(globalObject, resolverFunction, nullptr);
|
||||
}
|
||||
#pragma mark - JSC::JSPromise
|
||||
|
||||
|
||||
@@ -829,6 +829,7 @@ pub const VirtualMachine = struct {
|
||||
|
||||
/// Used by bun:test to set global hooks for beforeAll, beforeEach, etc.
|
||||
is_in_preload: bool = false,
|
||||
has_patched_run_main: bool = false,
|
||||
|
||||
transpiler_store: JSC.RuntimeTranspilerStore,
|
||||
|
||||
@@ -865,7 +866,7 @@ pub const VirtualMachine = struct {
|
||||
|
||||
rare_data: ?*JSC.RareData = null,
|
||||
is_us_loop_entered: bool = false,
|
||||
pending_internal_promise: *JSInternalPromise = undefined,
|
||||
pending_internal_promise: ?*JSInternalPromise = null,
|
||||
entry_point_result: struct {
|
||||
value: JSC.Strong = .empty,
|
||||
cjs_set_value: bool = false,
|
||||
@@ -1270,7 +1271,7 @@ pub const VirtualMachine = struct {
|
||||
}
|
||||
|
||||
pub fn handlePendingInternalPromiseRejection(this: *JSC.VirtualMachine) void {
|
||||
var promise = this.pending_internal_promise;
|
||||
var promise = this.pending_internal_promise.?;
|
||||
if (promise.status(this.global.vm()) == .rejected and !promise.isHandled(this.global.vm())) {
|
||||
_ = this.unhandledRejection(this.global, promise.result(this.global.vm()), promise.asValue());
|
||||
promise.setHandled(this.global.vm());
|
||||
@@ -2998,12 +2999,12 @@ pub const VirtualMachine = struct {
|
||||
// pending_internal_promise can change if hot module reloading is enabled
|
||||
if (this.isWatcherEnabled()) {
|
||||
this.eventLoop().performGC();
|
||||
switch (this.pending_internal_promise.status(this.global.vm())) {
|
||||
switch (this.pending_internal_promise.?.status(this.global.vm())) {
|
||||
.pending => {
|
||||
while (this.pending_internal_promise.status(this.global.vm()) == .pending) {
|
||||
while (this.pending_internal_promise.?.status(this.global.vm()) == .pending) {
|
||||
this.eventLoop().tick();
|
||||
|
||||
if (this.pending_internal_promise.status(this.global.vm()) == .pending) {
|
||||
if (this.pending_internal_promise.?.status(this.global.vm()) == .pending) {
|
||||
this.eventLoop().autoTick();
|
||||
}
|
||||
}
|
||||
@@ -3056,11 +3057,26 @@ pub const VirtualMachine = struct {
|
||||
}
|
||||
|
||||
if (!this.transpiler.options.disable_transpilation) {
|
||||
if (try this.loadPreloads()) |promise| {
|
||||
JSValue.fromCell(promise).ensureStillAlive();
|
||||
JSValue.fromCell(promise).protect();
|
||||
this.pending_internal_promise = promise;
|
||||
return promise;
|
||||
if (this.preload.len > 0) {
|
||||
if (try this.loadPreloads()) |promise| {
|
||||
JSValue.fromCell(promise).ensureStillAlive();
|
||||
JSValue.fromCell(promise).protect();
|
||||
this.pending_internal_promise = promise;
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Check if Module.runMain was patched
|
||||
const prev = this.pending_internal_promise;
|
||||
if (this.has_patched_run_main) {
|
||||
@branchHint(.cold);
|
||||
this.pending_internal_promise = null;
|
||||
const ret = NodeModuleModule__callOverriddenRunMain(this.global, bun.String.createUTF8ForJS(this.global, main_file_name));
|
||||
if (this.pending_internal_promise == prev or this.pending_internal_promise == null) {
|
||||
this.pending_internal_promise = JSInternalPromise.resolvedPromise(this.global, ret);
|
||||
return this.pending_internal_promise.?;
|
||||
}
|
||||
return (this.pending_internal_promise orelse prev).?;
|
||||
}
|
||||
}
|
||||
|
||||
const promise = if (!this.main_is_html_entrypoint)
|
||||
@@ -3080,6 +3096,18 @@ pub const VirtualMachine = struct {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn NodeModuleModule__callOverriddenRunMain(global: *JSGlobalObject, argv1: JSValue) JSValue;
|
||||
export fn Bun__VirtualMachine__setOverrideModuleRunMain(vm: *VirtualMachine, is_patched: bool) void {
|
||||
if (vm.is_in_preload) {
|
||||
vm.has_patched_run_main = is_patched;
|
||||
}
|
||||
}
|
||||
export fn Bun__VirtualMachine__setOverrideModuleRunMainPromise(vm: *VirtualMachine, promise: *JSInternalPromise) void {
|
||||
if (vm.pending_internal_promise == null) {
|
||||
vm.pending_internal_promise = promise;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reloadEntryPointForTestRunner(this: *VirtualMachine, entry_path: []const u8) !*JSInternalPromise {
|
||||
this.has_loaded = false;
|
||||
this.main = entry_path;
|
||||
@@ -3118,7 +3146,7 @@ pub const VirtualMachine = struct {
|
||||
return error.WorkerTerminated;
|
||||
}
|
||||
}
|
||||
return this.pending_internal_promise;
|
||||
return this.pending_internal_promise.?;
|
||||
}
|
||||
|
||||
pub fn loadEntryPointForTestRunner(this: *VirtualMachine, entry_path: string) anyerror!*JSInternalPromise {
|
||||
@@ -3127,12 +3155,12 @@ pub const VirtualMachine = struct {
|
||||
// pending_internal_promise can change if hot module reloading is enabled
|
||||
if (this.isWatcherEnabled()) {
|
||||
this.eventLoop().performGC();
|
||||
switch (this.pending_internal_promise.status(this.global.vm())) {
|
||||
switch (this.pending_internal_promise.?.status(this.global.vm())) {
|
||||
.pending => {
|
||||
while (this.pending_internal_promise.status(this.global.vm()) == .pending) {
|
||||
while (this.pending_internal_promise.?.status(this.global.vm()) == .pending) {
|
||||
this.eventLoop().tick();
|
||||
|
||||
if (this.pending_internal_promise.status(this.global.vm()) == .pending) {
|
||||
if (this.pending_internal_promise.?.status(this.global.vm()) == .pending) {
|
||||
this.eventLoop().autoTick();
|
||||
}
|
||||
}
|
||||
@@ -3150,7 +3178,7 @@ pub const VirtualMachine = struct {
|
||||
|
||||
this.eventLoop().autoTick();
|
||||
|
||||
return this.pending_internal_promise;
|
||||
return this.pending_internal_promise.?;
|
||||
}
|
||||
|
||||
pub fn loadEntryPoint(this: *VirtualMachine, entry_path: string) anyerror!*JSInternalPromise {
|
||||
@@ -3159,12 +3187,12 @@ pub const VirtualMachine = struct {
|
||||
// pending_internal_promise can change if hot module reloading is enabled
|
||||
if (this.isWatcherEnabled()) {
|
||||
this.eventLoop().performGC();
|
||||
switch (this.pending_internal_promise.status(this.global.vm())) {
|
||||
switch (this.pending_internal_promise.?.status(this.global.vm())) {
|
||||
.pending => {
|
||||
while (this.pending_internal_promise.status(this.global.vm()) == .pending) {
|
||||
while (this.pending_internal_promise.?.status(this.global.vm()) == .pending) {
|
||||
this.eventLoop().tick();
|
||||
|
||||
if (this.pending_internal_promise.status(this.global.vm()) == .pending) {
|
||||
if (this.pending_internal_promise.?.status(this.global.vm()) == .pending) {
|
||||
this.eventLoop().autoTick();
|
||||
}
|
||||
}
|
||||
@@ -3180,7 +3208,7 @@ pub const VirtualMachine = struct {
|
||||
this.waitForPromise(.{ .internal = promise });
|
||||
}
|
||||
|
||||
return this.pending_internal_promise;
|
||||
return this.pending_internal_promise.?;
|
||||
}
|
||||
|
||||
pub fn addListeningSocketForWatchMode(this: *VirtualMachine, socket: bun.FileDescriptor) void {
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
#include <JavaScriptCore/FunctionPrototype.h>
|
||||
#include <JavaScriptCore/LazyPropertyInlines.h>
|
||||
#include <JavaScriptCore/VMTrapsInlines.h>
|
||||
#include <JavaScriptCore/CallData.h>
|
||||
#include <JavaScriptCore/JSInternalPromise.h>
|
||||
#include "JavaScriptCore/Completion.h"
|
||||
#include "JavaScriptCore/JSNativeStdFunction.h"
|
||||
|
||||
#include "PathInlines.h"
|
||||
#include "ZigGlobalObject.h"
|
||||
@@ -372,7 +376,6 @@ JSC_DEFINE_CUSTOM_GETTER(nodeModuleResolveFilename,
|
||||
EncodedJSValue thisValue,
|
||||
PropertyName propertyName))
|
||||
{
|
||||
|
||||
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
|
||||
return JSValue::encode(
|
||||
globalObject->m_moduleResolveFilenameFunction.getInitializedOnMainThread(
|
||||
@@ -724,11 +727,76 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionLoad, (JSGlobalObject * globalObject, JSC::Ca
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsFunctionRunMain, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
static JSC::EncodedJSValue resolverFunctionCallback(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame)
|
||||
{
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
|
||||
extern "C" void Bun__VirtualMachine__setOverrideModuleRunMainPromise(void* bunVM, JSInternalPromise* promise);
|
||||
JSC_DEFINE_HOST_FUNCTION(jsFunctionRunMain, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
auto arg1 = callFrame->argument(0);
|
||||
auto name = makeAtomString(arg1.toWTFString(globalObject));
|
||||
|
||||
auto* promise = JSC::loadAndEvaluateModule(globalObject, name, JSC::jsUndefined(), JSC::jsUndefined());
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
JSC::JSNativeStdFunction* resolverFunction = JSC::JSNativeStdFunction::create(
|
||||
vm, globalObject, 1, String(), resolverFunctionCallback);
|
||||
|
||||
auto result = promise->then(globalObject, resolverFunction, nullptr);
|
||||
Bun__VirtualMachine__setOverrideModuleRunMainPromise(defaultGlobalObject(globalObject)->bunVM(), result);
|
||||
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(moduleRunMain,
|
||||
(JSGlobalObject * lexicalGlobalObject,
|
||||
EncodedJSValue thisValue,
|
||||
PropertyName propertyName))
|
||||
{
|
||||
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
|
||||
|
||||
return JSValue::encode(
|
||||
globalObject->m_moduleRunMainFunction.getInitializedOnMainThread(
|
||||
globalObject));
|
||||
}
|
||||
|
||||
extern "C" void Bun__VirtualMachine__setOverrideModuleRunMain(void* bunVM, bool isOriginal);
|
||||
extern "C" JSC::EncodedJSValue NodeModuleModule__callOverriddenRunMain(Zig::GlobalObject* global, JSValue argv1)
|
||||
{
|
||||
auto overrideHandler = jsCast<JSObject*>(global->m_moduleRunMainFunction.get(global));
|
||||
MarkedArgumentBuffer args;
|
||||
args.append(argv1);
|
||||
return JSC::JSValue::encode(JSC::profiledCall(global, JSC::ProfilingReason::API, overrideHandler, JSC::getCallData(overrideHandler), global, args));
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_SETTER(setModuleRunMain,
|
||||
(JSGlobalObject * lexicalGlobalObject,
|
||||
EncodedJSValue thisValue, EncodedJSValue encodedValue,
|
||||
PropertyName propertyName))
|
||||
{
|
||||
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
|
||||
auto value = JSValue::decode(encodedValue);
|
||||
if (value.isCell()) {
|
||||
bool isOriginal = false;
|
||||
if (value.isCallable()) {
|
||||
JSC::CallData callData = JSC::getCallData(value);
|
||||
if (callData.type == JSC::CallData::Type::Native) {
|
||||
if (callData.native.function.untaggedPtr() == &jsFunctionRunMain) {
|
||||
isOriginal = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Bun__VirtualMachine__setOverrideModuleRunMain(globalObject->bunVM(), !isOriginal);
|
||||
globalObject->m_moduleRunMainFunction.set(
|
||||
lexicalGlobalObject->vm(), globalObject, value.asCell());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsFunctionPreloadModules,
|
||||
(JSGlobalObject * globalObject,
|
||||
JSC::CallFrame* callFrame))
|
||||
@@ -785,13 +853,13 @@ builtinModules getBuiltinModulesObject PropertyCallback
|
||||
constants getConstantsObject PropertyCallback
|
||||
createRequire jsFunctionNodeModuleCreateRequire Function 1
|
||||
enableCompileCache jsFunctionEnableCompileCache Function 0
|
||||
findSourceMap jsFunctionFindSourceMap Function 0
|
||||
findSourceMap jsFunctionFindSourceMap Function 0
|
||||
getCompileCacheDir jsFunctionGetCompileCacheDir Function 0
|
||||
globalPaths getGlobalPathsObject PropertyCallback
|
||||
isBuiltin jsFunctionIsBuiltinModule Function 1
|
||||
prototype getModulePrototypeObject PropertyCallback
|
||||
register jsFunctionRegister Function 1
|
||||
runMain jsFunctionRunMain Function 0
|
||||
runMain moduleRunMain CustomAccessor
|
||||
SourceMap getSourceMapFunction PropertyCallback
|
||||
syncBuiltinESMExports jsFunctionSyncBuiltinESMExports Function 0
|
||||
wrap jsFunctionWrap Function 1
|
||||
@@ -866,6 +934,15 @@ void addNodeModuleConstructorProperties(JSC::VM& vm,
|
||||
init.set(moduleConstructor);
|
||||
});
|
||||
|
||||
globalObject->m_moduleRunMainFunction.initLater(
|
||||
[](const Zig::GlobalObject::Initializer<JSCell>& init) {
|
||||
JSFunction* runMainFunction = JSFunction::create(
|
||||
init.vm, init.owner, 2, "runMain"_s,
|
||||
jsFunctionRunMain, JSC::ImplementationVisibility::Public,
|
||||
JSC::NoIntrinsic, jsFunctionRunMain);
|
||||
init.set(runMainFunction);
|
||||
});
|
||||
|
||||
globalObject->m_moduleResolveFilenameFunction.initLater(
|
||||
[](const Zig::GlobalObject::Initializer<JSCell>& init) {
|
||||
JSFunction* resolveFilenameFunction = JSFunction::create(
|
||||
|
||||
28
src/cli.zig
28
src/cli.zig
@@ -221,6 +221,7 @@ pub const Arguments = struct {
|
||||
clap.parseParam("--no-clear-screen Disable clearing the terminal screen on reload when --hot or --watch is enabled") catch unreachable,
|
||||
clap.parseParam("--smol Use less memory, but run garbage collection more often") catch unreachable,
|
||||
clap.parseParam("-r, --preload <STR>... Import a module before other modules are loaded") catch unreachable,
|
||||
clap.parseParam("--require <STR>... Alias of --preload, for Node.js compatibility") catch unreachable,
|
||||
clap.parseParam("--inspect <STR>? Activate Bun's debugger") catch unreachable,
|
||||
clap.parseParam("--inspect-wait <STR>? Activate Bun's debugger, wait for a connection before executing") catch unreachable,
|
||||
clap.parseParam("--inspect-brk <STR>? Activate Bun's debugger, set breakpoint on first line of code and wait") catch unreachable,
|
||||
@@ -399,10 +400,10 @@ pub const Arguments = struct {
|
||||
if (config_path_.len == 0 and (user_config_path_ != null or
|
||||
Command.Tag.always_loads_config.get(cmd) or
|
||||
(cmd == .AutoCommand and
|
||||
// "bun"
|
||||
(ctx.positionals.len == 0 or
|
||||
// "bun file.js"
|
||||
ctx.positionals.len > 0 and options.defaultLoaders.has(std.fs.path.extension(ctx.positionals[0]))))))
|
||||
// "bun"
|
||||
(ctx.positionals.len == 0 or
|
||||
// "bun file.js"
|
||||
ctx.positionals.len > 0 and options.defaultLoaders.has(std.fs.path.extension(ctx.positionals[0]))))))
|
||||
{
|
||||
config_path_ = "bunfig.toml";
|
||||
auto_loaded = true;
|
||||
@@ -672,6 +673,7 @@ pub const Arguments = struct {
|
||||
// runtime commands
|
||||
if (cmd == .AutoCommand or cmd == .RunCommand or cmd == .TestCommand or cmd == .RunAsNodeCommand) {
|
||||
const preloads = args.options("--preload");
|
||||
const preloads2 = args.options("--require");
|
||||
|
||||
if (args.flag("--hot")) {
|
||||
ctx.debug.hot_reload = .hot;
|
||||
@@ -751,13 +753,23 @@ pub const Arguments = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.preloads.len > 0 and preloads.len > 0) {
|
||||
var all = std.ArrayList(string).initCapacity(ctx.allocator, ctx.preloads.len + preloads.len) catch unreachable;
|
||||
if (ctx.preloads.len > 0 and (preloads.len > 0 or preloads2.len > 0)) {
|
||||
var all = std.ArrayList(string).initCapacity(ctx.allocator, ctx.preloads.len + preloads.len + preloads2.len) catch unreachable;
|
||||
all.appendSliceAssumeCapacity(ctx.preloads);
|
||||
all.appendSliceAssumeCapacity(preloads);
|
||||
all.appendSliceAssumeCapacity(preloads2);
|
||||
ctx.preloads = all.items;
|
||||
} else if (preloads.len > 0) {
|
||||
ctx.preloads = preloads;
|
||||
if (preloads2.len > 0) {
|
||||
var all = std.ArrayList(string).initCapacity(ctx.allocator, preloads.len + preloads2.len) catch unreachable;
|
||||
all.appendSliceAssumeCapacity(preloads);
|
||||
all.appendSliceAssumeCapacity(preloads2);
|
||||
ctx.preloads = all.items;
|
||||
} else {
|
||||
ctx.preloads = preloads;
|
||||
}
|
||||
} else if (preloads2.len > 0) {
|
||||
ctx.preloads = preloads2;
|
||||
}
|
||||
|
||||
if (args.option("--print")) |script| {
|
||||
@@ -2134,7 +2146,7 @@ pub const Command = struct {
|
||||
|
||||
const use_bunx = !HardcodedNonBunXList.has(template_name) and
|
||||
(!strings.containsComptime(template_name, "/") or
|
||||
strings.startsWithChar(template_name, '@')) and
|
||||
strings.startsWithChar(template_name, '@')) and
|
||||
example_tag != CreateCommandExample.Tag.local_folder;
|
||||
|
||||
if (use_bunx) {
|
||||
|
||||
2
src/js/builtins.d.ts
vendored
2
src/js/builtins.d.ts
vendored
@@ -489,7 +489,7 @@ declare function $createCommonJSModule(
|
||||
declare function $evaluateCommonJSModule(
|
||||
moduleToEvaluate: CommonJSModuleRecord,
|
||||
sourceModule: CommonJSModuleRecord
|
||||
): void;
|
||||
): CommonJSModuleRecord[];
|
||||
|
||||
declare function $overridableRequire(this: CommonJSModuleRecord, id: string): any;
|
||||
|
||||
|
||||
@@ -20,7 +20,10 @@ export function overridableRequire(this: CommonJSModuleRecord, originalId: strin
|
||||
// read the require cache. Though they never write to it, which is so silly.
|
||||
const existing = $requireMap.$get(originalId);
|
||||
if (existing) {
|
||||
$evaluateCommonJSModule(existing, this);
|
||||
const c = $evaluateCommonJSModule(existing, this);
|
||||
if (c && c.indexOf(existing) === -1) {
|
||||
c.push(existing);
|
||||
}
|
||||
return existing.exports;
|
||||
}
|
||||
}
|
||||
@@ -46,7 +49,10 @@ export function overridableRequire(this: CommonJSModuleRecord, originalId: strin
|
||||
// we evaluate it "early", we'll get an empty object instead of the module
|
||||
// exports.
|
||||
//
|
||||
$evaluateCommonJSModule(existing, this);
|
||||
const c = $evaluateCommonJSModule(existing, this);
|
||||
if (c && c.indexOf(existing) === -1) {
|
||||
c.push(existing);
|
||||
}
|
||||
return existing.exports;
|
||||
}
|
||||
}
|
||||
@@ -129,7 +135,10 @@ export function overridableRequire(this: CommonJSModuleRecord, originalId: strin
|
||||
}
|
||||
}
|
||||
|
||||
$evaluateCommonJSModule(mod, this);
|
||||
const c = $evaluateCommonJSModule(mod, this);
|
||||
if (c && c.indexOf(mod) === -1) {
|
||||
c.push(mod);
|
||||
}
|
||||
return mod.exports;
|
||||
}
|
||||
|
||||
|
||||
110
test/bake/client-fixture.mjs
generated
110
test/bake/client-fixture.mjs
generated
@@ -24,9 +24,13 @@ let expectingReload = false;
|
||||
let webSockets = [];
|
||||
let pendingReload = null;
|
||||
let pendingReloadTimer = null;
|
||||
let updatingTimer = null;
|
||||
let isUpdating = null;
|
||||
|
||||
function reset() {
|
||||
if (isUpdating !== null) {
|
||||
clearImmediate(isUpdating);
|
||||
isUpdating = null;
|
||||
}
|
||||
for (const ws of webSockets) {
|
||||
ws.onclose = () => {};
|
||||
ws.onerror = () => {};
|
||||
@@ -57,21 +61,6 @@ function createWindow(windowUrl) {
|
||||
height: 768,
|
||||
});
|
||||
|
||||
let hasReadyEventListener = false;
|
||||
window["bun do not use this outside of internal testing or else i'll cry"] = ({ onEvent }) => {
|
||||
onEvent("bun:afterUpdate", () => {
|
||||
setTimeout(() => {
|
||||
process.send({ type: "received-hmr-event", args: [] });
|
||||
}, 50);
|
||||
});
|
||||
hasReadyEventListener = true;
|
||||
onEvent("bun:ready", () => {
|
||||
setTimeout(() => {
|
||||
process.send({ type: "received-hmr-event", args: [] });
|
||||
}, 50);
|
||||
});
|
||||
};
|
||||
|
||||
window.fetch = async function (url, options) {
|
||||
if (typeof url === "string") {
|
||||
url = new URL(url, windowUrl).href;
|
||||
@@ -87,14 +76,11 @@ function createWindow(windowUrl) {
|
||||
webSockets.push(this);
|
||||
this.addEventListener("message", event => {
|
||||
const data = new Uint8Array(event.data);
|
||||
if (data[0] === "e".charCodeAt(0)) {
|
||||
if (updatingTimer) {
|
||||
clearTimeout(updatingTimer);
|
||||
}
|
||||
updatingTimer = setTimeout(() => {
|
||||
if (data[0] === "u".charCodeAt(0) || data[0] === "e".charCodeAt(0)) {
|
||||
isUpdating = setImmediate(() => {
|
||||
process.send({ type: "received-hmr-event", args: [] });
|
||||
updatingTimer = null;
|
||||
}, 250);
|
||||
isUpdating = null;
|
||||
});
|
||||
}
|
||||
if (!allowWebSocketMessages) {
|
||||
const allowedTypes = ["n", "r"];
|
||||
@@ -134,11 +120,75 @@ function createWindow(windowUrl) {
|
||||
info: (...args) => {
|
||||
if (args[0]?.startsWith("[Bun] Hot-module-reloading socket connected")) {
|
||||
// Wait for all CSS assets to be fully loaded before emitting the event
|
||||
if (!hasReadyEventListener) {
|
||||
setTimeout(() => {
|
||||
process.send({ type: "received-hmr-event", args: [] });
|
||||
}, 50);
|
||||
}
|
||||
let checkAttempts = 0;
|
||||
const MAX_CHECK_ATTEMPTS = 20; // Prevent infinite waiting
|
||||
|
||||
const checkCSSLoaded = () => {
|
||||
checkAttempts++;
|
||||
|
||||
// Get all link elements with rel="stylesheet"
|
||||
const styleLinks = window.document.querySelectorAll('link[rel="stylesheet"]');
|
||||
// Get all style elements
|
||||
const styleTags = window.document.querySelectorAll("style");
|
||||
// Check for adoptedStyleSheets
|
||||
const adoptedSheets = window.document.adoptedStyleSheets || [];
|
||||
|
||||
// If no stylesheets of any kind, just emit the event
|
||||
if (styleLinks.length === 0 && styleTags.length === 0 && adoptedSheets.length === 0) {
|
||||
process.nextTick(() => {
|
||||
process.send({ type: "received-hmr-event", args: [] });
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if all stylesheets are loaded
|
||||
let allLoaded = true;
|
||||
let pendingCount = 0;
|
||||
|
||||
// Check link elements
|
||||
for (const link of styleLinks) {
|
||||
// If the stylesheet is not loaded yet
|
||||
if (!link.sheet) {
|
||||
allLoaded = false;
|
||||
pendingCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check style elements - these should be loaded immediately
|
||||
for (const style of styleTags) {
|
||||
if (!style.sheet) {
|
||||
allLoaded = false;
|
||||
pendingCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check adoptedStyleSheets - these should be loaded immediately
|
||||
for (const sheet of adoptedSheets) {
|
||||
if (!sheet.cssRules) {
|
||||
allLoaded = false;
|
||||
pendingCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (allLoaded || checkAttempts >= MAX_CHECK_ATTEMPTS) {
|
||||
// All CSS is loaded or we've reached max attempts, emit the event
|
||||
if (checkAttempts >= MAX_CHECK_ATTEMPTS && !allLoaded) {
|
||||
console.warn("[W] Reached maximum CSS load check attempts, proceeding anyway");
|
||||
}
|
||||
process.nextTick(() => {
|
||||
process.send({ type: "received-hmr-event", args: [] });
|
||||
});
|
||||
} else {
|
||||
// Wait a bit and check again
|
||||
console.info(
|
||||
`[I] Waiting for ${pendingCount} CSS assets to load (attempt ${checkAttempts}/${MAX_CHECK_ATTEMPTS})...`,
|
||||
);
|
||||
setTimeout(checkCSSLoaded, 50);
|
||||
}
|
||||
};
|
||||
|
||||
// Start checking for CSS loaded state
|
||||
checkCSSLoaded();
|
||||
}
|
||||
if (args[0]?.startsWith("[WS] receive message")) return;
|
||||
if (args[0]?.startsWith("Updated modules:")) return;
|
||||
@@ -154,10 +204,6 @@ function createWindow(windowUrl) {
|
||||
};
|
||||
|
||||
window.location.reload = async () => {
|
||||
if (updatingTimer) {
|
||||
clearTimeout(updatingTimer);
|
||||
}
|
||||
console.info("[I] location.reload()");
|
||||
reset();
|
||||
if (expectingReload) {
|
||||
// Permission already granted, proceed with reload
|
||||
|
||||
@@ -46,11 +46,12 @@ describe.skipIf(isBroken && isIntelMacOS)("files transpiled and loaded don't lea
|
||||
"require-cache-bug-leak-fixture.js": `
|
||||
const path = require.resolve("./index.js");
|
||||
const gc = global.gc || globalThis?.Bun?.gc || (() => {});
|
||||
const noChildren = module.children = { indexOf() { return 0; } }; // disable children tracking
|
||||
function bust() {
|
||||
const mod = require.cache[path];
|
||||
if (mod) {
|
||||
mod.parent = null;
|
||||
mod.children = [];
|
||||
mod.children = noChildren;
|
||||
delete require.cache[path];
|
||||
}
|
||||
}
|
||||
@@ -72,7 +73,7 @@ describe.skipIf(isBroken && isIntelMacOS)("files transpiled and loaded don't lea
|
||||
console.log("RSS diff", (diff / 1024 / 1024) | 0, "MB");
|
||||
console.log("RSS", (diff / 1024 / 1024) | 0, "MB");
|
||||
if (diff > 100 * 1024 * 1024) {
|
||||
// Bun v1.1.21 reported 844 MB here on macoS arm64.
|
||||
// Bun v1.1.21 reported 844 MB here on macOS arm64.
|
||||
throw new Error("Memory leak detected");
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ test("raise ignoring panic handler does not trigger the panic handler", async ()
|
||||
|
||||
await proc.exited;
|
||||
|
||||
/// Wait two seconds for a slow http request, or continue immediatly once the request is heard.
|
||||
/// Wait two seconds for a slow http request, or continue immediately once the request is heard.
|
||||
await Promise.race([resolve_handler.promise, Bun.sleep(2000)]);
|
||||
|
||||
expect(proc.exited).resolves.not.toBe(0);
|
||||
|
||||
@@ -27,7 +27,7 @@ const words: Record<string, { reason: string; limit?: number; regex?: boolean }>
|
||||
"alloc.ptr !=": { reason: "The std.mem.Allocator context pointer can be undefined, which makes this comparison undefined behavior" },
|
||||
"== alloc.ptr": { reason: "The std.mem.Allocator context pointer can be undefined, which makes this comparison undefined behavior" },
|
||||
"!= alloc.ptr": { reason: "The std.mem.Allocator context pointer can be undefined, which makes this comparison undefined behavior" },
|
||||
[String.raw`: [a-zA-Z0-9_\.\*\?\[\]\(\)]+ = undefined,`]: { reason: "Do not default a struct field to undefined", limit: 249, regex: true },
|
||||
[String.raw`: [a-zA-Z0-9_\.\*\?\[\]\(\)]+ = undefined,`]: { reason: "Do not default a struct field to undefined", limit: 248, regex: true },
|
||||
"usingnamespace": { reason: "This brings Bun away from incremental / faster compile times.", limit: 496 },
|
||||
};
|
||||
const words_keys = [...Object.keys(words)];
|
||||
|
||||
30
test/js/node/module/children-fixture/a.cjs
Normal file
30
test/js/node/module/children-fixture/a.cjs
Normal file
@@ -0,0 +1,30 @@
|
||||
require("./b.cjs");
|
||||
require("./d.cjs");
|
||||
require("./b.cjs");
|
||||
if (process.argv.includes("--access-early")) {
|
||||
module.children;
|
||||
}
|
||||
require("./b.cjs");
|
||||
require("./b.cjs");
|
||||
require("./f.cjs");
|
||||
require("./g.cjs");
|
||||
|
||||
let seen = new Set();
|
||||
function iter(module, indent = 0) {
|
||||
if (require.cache[module.filename] !== module) {
|
||||
throw new Error("module.filename is not the same as require.cache[module.filename]");
|
||||
}
|
||||
let isSeen = seen.has(module);
|
||||
console.log(
|
||||
`${" ".repeat(indent)}${module.id === module.filename ? module.id : `${module.id} (${module.filename})`}${isSeen ? " (seen)" : ""}`
|
||||
.replaceAll(__dirname, ".")
|
||||
.replaceAll("\\", "/"),
|
||||
);
|
||||
seen.add(module);
|
||||
if (isSeen) return;
|
||||
for (let child of module.children) {
|
||||
iter(child, indent + 1);
|
||||
}
|
||||
}
|
||||
|
||||
iter(module);
|
||||
3
test/js/node/module/children-fixture/b.cjs
Normal file
3
test/js/node/module/children-fixture/b.cjs
Normal file
@@ -0,0 +1,3 @@
|
||||
require("./a.cjs");
|
||||
require("./b.cjs");
|
||||
require("./c.cjs");
|
||||
1
test/js/node/module/children-fixture/c.cjs
Normal file
1
test/js/node/module/children-fixture/c.cjs
Normal file
@@ -0,0 +1 @@
|
||||
require("./d.cjs");
|
||||
1
test/js/node/module/children-fixture/d.cjs
Normal file
1
test/js/node/module/children-fixture/d.cjs
Normal file
@@ -0,0 +1 @@
|
||||
require("./d.cjs");
|
||||
4
test/js/node/module/children-fixture/f.cjs
Normal file
4
test/js/node/module/children-fixture/f.cjs
Normal file
@@ -0,0 +1,4 @@
|
||||
if (process.argv.includes("--access-early")) {
|
||||
module.children;
|
||||
}
|
||||
require("./d.cjs");
|
||||
3
test/js/node/module/children-fixture/g.cjs
Normal file
3
test/js/node/module/children-fixture/g.cjs
Normal file
@@ -0,0 +1,3 @@
|
||||
require("./b.cjs");
|
||||
require("./a.cjs");
|
||||
require("./h.cjs");
|
||||
3
test/js/node/module/children-fixture/h.cjs
Normal file
3
test/js/node/module/children-fixture/h.cjs
Normal file
@@ -0,0 +1,3 @@
|
||||
require("./i.cjs");
|
||||
require("./j.cjs");
|
||||
require("./k.cjs");
|
||||
1
test/js/node/module/children-fixture/i.cjs
Normal file
1
test/js/node/module/children-fixture/i.cjs
Normal file
@@ -0,0 +1 @@
|
||||
require("./j.cjs");
|
||||
3
test/js/node/module/children-fixture/j.cjs
Normal file
3
test/js/node/module/children-fixture/j.cjs
Normal file
@@ -0,0 +1,3 @@
|
||||
require("./i.cjs");
|
||||
require("./j.cjs");
|
||||
require("./k.cjs");
|
||||
1
test/js/node/module/children-fixture/k.cjs
Normal file
1
test/js/node/module/children-fixture/k.cjs
Normal file
@@ -0,0 +1 @@
|
||||
require("./j.cjs");
|
||||
@@ -178,3 +178,56 @@ test("require cache node builtins specifier", () => {
|
||||
test("require a cjs file uses the 'module.exports' export", () => {
|
||||
expect(require("./esm_to_cjs_interop.mjs")).toEqual(Symbol.for("meow"));
|
||||
});
|
||||
|
||||
test("Module.runMain", () => {
|
||||
const { stdout, exitCode } = Bun.spawnSync({
|
||||
cmd: [bunExe(), "--require", path.join(import.meta.dir, "overwrite-module-run-main-1.cjs"), path.join(import.meta.dir, "overwrite-module-run-main-2.cjs")],
|
||||
env: bunEnv,
|
||||
stderr: "inherit",
|
||||
});
|
||||
|
||||
expect(stdout.toString().trim()).toBe("pass");
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
test("Module.runMain 2", () => {
|
||||
const { stdout, exitCode } = Bun.spawnSync({
|
||||
cmd: [bunExe(), "--require", path.join(import.meta.dir, "overwrite-module-run-main-3.cjs"), path.join(import.meta.dir, "overwrite-module-run-main-2.cjs")],
|
||||
env: bunEnv,
|
||||
stderr: "inherit",
|
||||
});
|
||||
|
||||
expect(stdout.toString().trim()).toBe("pass");
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
test.each([
|
||||
"no args",
|
||||
"--access-early",
|
||||
])("children, %s", arg => {
|
||||
const { stdout, exitCode } = Bun.spawnSync({
|
||||
cmd: [bunExe(), path.join(import.meta.dir, "children-fixture/a.cjs"), arg],
|
||||
env: bunEnv,
|
||||
stderr: "inherit",
|
||||
});
|
||||
expect(stdout.toString().trim()).toBe(`. (./a.cjs)
|
||||
./b.cjs
|
||||
. (./a.cjs) (seen)
|
||||
./b.cjs (seen)
|
||||
./c.cjs
|
||||
./d.cjs
|
||||
./d.cjs (seen)
|
||||
./d.cjs (seen)
|
||||
./f.cjs
|
||||
./d.cjs (seen)
|
||||
./g.cjs
|
||||
./b.cjs (seen)
|
||||
. (./a.cjs) (seen)
|
||||
./h.cjs
|
||||
./i.cjs
|
||||
./j.cjs
|
||||
./i.cjs (seen)
|
||||
./j.cjs (seen)
|
||||
./k.cjs
|
||||
./j.cjs (seen)
|
||||
./j.cjs (seen)
|
||||
./k.cjs (seen)`);
|
||||
});
|
||||
6
test/js/node/module/overwrite-module-run-main-1.cjs
Normal file
6
test/js/node/module/overwrite-module-run-main-1.cjs
Normal file
@@ -0,0 +1,6 @@
|
||||
const Module = require("module");
|
||||
const old = Module.runMain;
|
||||
Module.runMain = (...args) => {
|
||||
process.stdout.write("pa");
|
||||
return old(...args);
|
||||
};
|
||||
1
test/js/node/module/overwrite-module-run-main-2.cjs
Normal file
1
test/js/node/module/overwrite-module-run-main-2.cjs
Normal file
@@ -0,0 +1 @@
|
||||
console.log("ss");
|
||||
5
test/js/node/module/overwrite-module-run-main-3.cjs
Normal file
5
test/js/node/module/overwrite-module-run-main-3.cjs
Normal file
@@ -0,0 +1,5 @@
|
||||
const Module = require("module");
|
||||
const old = Module.runMain;
|
||||
Module.runMain = (...args) => {
|
||||
process.stdout.write("pass");
|
||||
};
|
||||
3
test/js/node/test/.gitignore
vendored
3
test/js/node/test/.gitignore
vendored
@@ -6,7 +6,4 @@ fixtures/source-map
|
||||
fixtures/snapshot
|
||||
fixtures/repl*
|
||||
.tmp.*
|
||||
*shadow-realm*
|
||||
!test-shadow-realm-prepare-stack-trace.js
|
||||
!test-shadow-realm.js
|
||||
**/fails.txt
|
||||
|
||||
15
test/js/node/test/fixtures/es-module-shadow-realm/custom-loaders.js
vendored
Normal file
15
test/js/node/test/fixtures/es-module-shadow-realm/custom-loaders.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// This fixture is used to test that custom loaders are not enabled in the ShadowRealm.
|
||||
|
||||
'use strict';
|
||||
const assert = require('assert');
|
||||
|
||||
async function workInChildProcess() {
|
||||
// Assert that the process is running with a custom loader.
|
||||
const moduleNamespace = await import('file:///42.mjs');
|
||||
assert.strictEqual(moduleNamespace.default, 42);
|
||||
|
||||
const realm = new ShadowRealm();
|
||||
await assert.rejects(realm.importValue('file:///42.mjs', 'default'), TypeError);
|
||||
}
|
||||
|
||||
workInChildProcess();
|
||||
9
test/js/node/test/fixtures/es-module-shadow-realm/preload-main.js
vendored
Normal file
9
test/js/node/test/fixtures/es-module-shadow-realm/preload-main.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// This fixture is used to test that --require preload modules are not enabled in the ShadowRealm.
|
||||
|
||||
'use strict';
|
||||
const assert = require('assert');
|
||||
|
||||
assert.strictEqual(globalThis.preload, 42);
|
||||
const realm = new ShadowRealm();
|
||||
const value = realm.evaluate(`globalThis.preload`);
|
||||
assert.strictEqual(value, undefined);
|
||||
1
test/js/node/test/fixtures/es-module-shadow-realm/preload.js
vendored
Normal file
1
test/js/node/test/fixtures/es-module-shadow-realm/preload.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
globalThis.preload = 42;
|
||||
3
test/js/node/test/fixtures/es-module-shadow-realm/re-export-state-counter.mjs
vendored
Normal file
3
test/js/node/test/fixtures/es-module-shadow-realm/re-export-state-counter.mjs
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
// This module verifies that the module specifier is resolved relative to the
|
||||
// current module and not the current working directory in the ShadowRealm.
|
||||
export { getCounter } from "./state-counter.mjs";
|
||||
4
test/js/node/test/fixtures/es-module-shadow-realm/state-counter.mjs
vendored
Normal file
4
test/js/node/test/fixtures/es-module-shadow-realm/state-counter.mjs
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
let counter = 0;
|
||||
export const getCounter = () => {
|
||||
return counter++;
|
||||
};
|
||||
1
test/js/node/test/fixtures/throws_error.js
vendored
1
test/js/node/test/fixtures/throws_error.js
vendored
@@ -19,4 +19,5 @@
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
module.exports = {};
|
||||
throw new Error('blah');
|
||||
|
||||
14
test/js/node/test/parallel/test-module-children.js
Normal file
14
test/js/node/test/parallel/test-module-children.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// Flags: --no-deprecation
|
||||
'use strict';
|
||||
require('../common');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
|
||||
const dir = fixtures.path('GH-7131');
|
||||
const b = require(path.join(dir, 'b'));
|
||||
const a = require(path.join(dir, 'a'));
|
||||
|
||||
assert.strictEqual(a.length, 1);
|
||||
assert.strictEqual(b.length, 0);
|
||||
assert.deepStrictEqual(a[0].exports, b);
|
||||
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
// This tests that module.runMain can be monkey patched using --require.
|
||||
// TODO(joyeecheung): This probably should be deprecated.
|
||||
|
||||
require('../common');
|
||||
const { path } = require('../common/fixtures');
|
||||
const assert = require('assert');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const child = spawnSync(process.execPath, [
|
||||
'--require',
|
||||
path('monkey-patch-run-main.js'),
|
||||
path('semicolon.js'),
|
||||
]);
|
||||
|
||||
assert.strictEqual(child.status, 0);
|
||||
assert(child.stdout.toString().includes('runMain is monkey patched!'));
|
||||
18
test/js/node/test/parallel/test-shadow-realm-gc-module.js
Normal file
18
test/js/node/test/parallel/test-shadow-realm-gc-module.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// Flags: --experimental-shadow-realm --max-old-space-size=20
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Verifying modules imported by ShadowRealm instances can be correctly
|
||||
* garbage collected.
|
||||
*/
|
||||
|
||||
const common = require('../common');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const { runAndBreathe } = require('../common/gc');
|
||||
|
||||
const mod = fixtures.fileURL('es-module-shadow-realm', 'state-counter.mjs');
|
||||
|
||||
runAndBreathe(async () => {
|
||||
const realm = new ShadowRealm();
|
||||
await realm.importValue(mod, 'getCounter');
|
||||
}, 100).then(common.mustCall());
|
||||
29
test/js/node/test/parallel/test-shadow-realm-module.js
Normal file
29
test/js/node/test/parallel/test-shadow-realm-module.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// Flags: --experimental-shadow-realm
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const assert = require('assert');
|
||||
|
||||
async function main() {
|
||||
const realm = new ShadowRealm();
|
||||
const mod = fixtures.fileURL('es-module-shadow-realm', 'state-counter.mjs');
|
||||
const getCounter = await realm.importValue(mod, 'getCounter');
|
||||
assert.strictEqual(getCounter(), 0);
|
||||
const getCounter1 = await realm.importValue(mod, 'getCounter');
|
||||
// Returned value is a newly wrapped function.
|
||||
assert.notStrictEqual(getCounter, getCounter1);
|
||||
// Verify that the module state is shared between two `importValue` calls.
|
||||
assert.strictEqual(getCounter1(), 1);
|
||||
assert.strictEqual(getCounter(), 2);
|
||||
|
||||
const { getCounter: getCounterThisRealm } = await import(mod);
|
||||
assert.notStrictEqual(getCounterThisRealm, getCounter);
|
||||
// Verify that the module state is not shared between two realms.
|
||||
assert.strictEqual(getCounterThisRealm(), 0);
|
||||
assert.strictEqual(getCounter(), 3);
|
||||
|
||||
// Verify that shadow realm rejects to import a non-existing module.
|
||||
await assert.rejects(realm.importValue('non-exists', 'exports'), TypeError);
|
||||
}
|
||||
|
||||
main().then(common.mustCall());
|
||||
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
|
||||
|
||||
const commonArgs = [
|
||||
'--experimental-shadow-realm',
|
||||
];
|
||||
|
||||
async function main() {
|
||||
// Verifies that --require preload modules are not enabled in the ShadowRealm.
|
||||
spawnSyncAndExitWithoutError(process.execPath, [
|
||||
...commonArgs,
|
||||
'--require',
|
||||
fixtures.path('es-module-shadow-realm', 'preload.js'),
|
||||
fixtures.path('es-module-shadow-realm', 'preload-main.js'),
|
||||
]);
|
||||
}
|
||||
|
||||
main().then(common.mustCall());
|
||||
Reference in New Issue
Block a user