Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 10 additions & 19 deletions src/runtime/bake/BakeGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,26 @@ bakeModuleLoaderImportModule(JSC::JSGlobalObject* global,
bool deferred)
{
UNUSED_PARAM(deferred);
auto& vm = JSC::getVM(global);
auto scope = DECLARE_THROW_SCOPE(vm);

// Returning nullptr with an exception pending is fine: globalFuncImportModule rejects the import() promise with it.
WTF::String keyString = moduleNameValue->getString(global);
RETURN_IF_EXCEPTION(scope, nullptr);
if (keyString.startsWith("bake:/"_s)) {
auto& vm = JSC::getVM(global);
return JSC::importModule(global, JSC::Identifier::fromString(vm, keyString),
JSC::Identifier(), WTF::move(parameters), nullptr);
RELEASE_AND_RETURN(scope, JSC::importModule(global, JSC::Identifier::fromString(vm, keyString), JSC::Identifier(), WTF::move(parameters), nullptr));
}

if (!sourceOrigin.isNull() && sourceOrigin.string().startsWith("bake:/"_s)) {
auto& vm = JSC::getVM(global);
auto scope = DECLARE_THROW_SCOPE(vm);

WTF::String refererString = sourceOrigin.string();
WTF::String keyString = moduleNameValue->getString(global);

if (!keyString) {
auto promise = JSC::JSPromise::create(vm, global->promiseStructure());
promise->reject(vm, JSC::createError(global, "import() requires a string"_s));
return promise;
}

BunString result = BakeProdResolve(global, Bun::toString(refererString), Bun::toString(keyString));
RETURN_IF_EXCEPTION(scope, nullptr);

return JSC::importModule(global, JSC::Identifier::fromString(vm, result.toWTFString()),
JSC::Identifier(), WTF::move(parameters), nullptr);
RELEASE_AND_RETURN(scope, JSC::importModule(global, JSC::Identifier::fromString(vm, result.toWTFString()), JSC::Identifier(), WTF::move(parameters), nullptr));
}

// TODO: make static cast instead of jscast
return uncheckedDowncast<Zig::GlobalObject>(global)->moduleLoaderImportModule(global, moduleLoader, moduleNameValue, WTF::move(parameters), sourceOrigin, false);
RELEASE_AND_RETURN(scope, uncheckedDowncast<Zig::GlobalObject>(global)->moduleLoaderImportModule(global, moduleLoader, moduleNameValue, WTF::move(parameters), sourceOrigin, false));
}

JSC::Identifier bakeModuleLoaderResolve(JSC::JSGlobalObject* jsGlobal,
Expand Down Expand Up @@ -87,7 +78,7 @@ JSC::Identifier bakeModuleLoaderResolve(JSC::JSGlobalObject* jsGlobal,
}
}

return Zig::GlobalObject::moduleLoaderResolve(jsGlobal, loader, key, referrer, WTF::move(origin), useImportMap);
RELEASE_AND_RETURN(scope, Zig::GlobalObject::moduleLoaderResolve(jsGlobal, loader, key, referrer, WTF::move(origin), useImportMap));
}

static JSC::JSPromise* rejectedInternalPromise(JSC::JSGlobalObject* globalObject, JSC::JSValue value)
Expand Down Expand Up @@ -163,7 +154,7 @@ JSC::JSPromise* bakeModuleLoaderFetch(JSC::JSGlobalObject* globalObject,
#endif
JSString* bakePrefixRemovedString = jsNontrivialString(vm, bakePrefixRemoved);
JSValue bakePrefixRemovedJsvalue = bakePrefixRemovedString;
return Zig::GlobalObject::moduleLoaderFetch(globalObject, loader, bakePrefixRemovedJsvalue, WTF::move(parameters), WTF::move(script));
RELEASE_AND_RETURN(scope, Zig::GlobalObject::moduleLoaderFetch(globalObject, loader, bakePrefixRemovedJsvalue, WTF::move(parameters), WTF::move(script)));
}
return rejectedInternalPromise(globalObject, createTypeError(globalObject, "BakeGlobalObject does not have per-thread data configured"_s));
}
Expand Down
86 changes: 50 additions & 36 deletions src/runtime/bake/BakeSourceProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ extern "C" BunString BakeSourceProvider__getSourceSlice(SourceProvider* provider
return Bun::toStringView(provider->source());
}

// Rust calls the EncodedJSValue-returning functions below via `jsc::from_js_host_call`: empty return <=> exception pending.

extern "C" JSC::EncodedJSValue BakeLoadInitialServerCode(JSC::JSGlobalObject* global, BunString source, bool separateSSRGraph) {
auto& vm = JSC::getVM(global);
auto scope = DECLARE_THROW_SCOPE(vm);
Expand Down Expand Up @@ -51,11 +53,24 @@ extern "C" JSC::EncodedJSValue BakeLoadInitialServerCode(JSC::JSGlobalObject* gl
args.append(JSC::jsBoolean(separateSSRGraph)); // separateSSRGraph
args.append(Zig::ImportMetaObject::create(global, "bake://server-runtime.js"_s)); // importMeta

RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSC::profiledCall(global, JSC::ProfilingReason::API, fn, callData, JSC::jsUndefined(), args)));
// `JSC::call` returns undefined (not empty) when the callee throws.
JSC::JSValue result = JSC::profiledCall(global, JSC::ProfilingReason::API, fn, callData, JSC::jsUndefined(), args);
RETURN_IF_EXCEPTION(scope, {});
return JSC::JSValue::encode(result);
}

extern "C" JSC::JSPromise* BakeLoadModuleByKey(GlobalObject* global, JSC::JSString* key) {
return JSC::loadAndEvaluateModule(global, key->getString(global), nullptr, nullptr);
extern "C" JSC::EncodedJSValue BakeLoadModuleByKey(GlobalObject* global, JSC::EncodedJSValue keyValue) {
auto& vm = JSC::getVM(global);
auto scope = DECLARE_THROW_SCOPE(vm);

JSC::JSString* key = uncheckedDowncast<JSC::JSString>(JSC::JSValue::decode(keyValue));
String keyString = key->getString(global);
RETURN_IF_EXCEPTION(scope, {});

JSC::JSPromise* promise = JSC::loadAndEvaluateModule(global, keyString, nullptr, nullptr);
RETURN_IF_EXCEPTION(scope, {});
ASSERT(promise);
return JSC::JSValue::encode(promise);
}

extern "C" JSC::EncodedJSValue BakeLoadServerHmrPatch(GlobalObject* global, BunString source) {
Expand Down Expand Up @@ -108,64 +123,63 @@ extern "C" JSC::EncodedJSValue BakeLoadServerHmrPatchWithSourceMap(GlobalObject*
return JSC::JSValue::encode(result);
}

extern "C" JSC::EncodedJSValue BakeGetModuleNamespace(
JSC::JSGlobalObject* global,
JSC::JSValue keyValue
) {
JSC::JSString* key = uncheckedDowncast<JSC::JSString>(keyValue);
// keyValue must name a module that has already been evaluated; then nullptr means an exception is pending.
static JSC::JSModuleNamespaceObject* getModuleNamespace(JSC::JSGlobalObject* global, JSC::JSValue keyValue) {
auto& vm = JSC::getVM(global);
auto scope = DECLARE_THROW_SCOPE(vm);

JSC::JSString* key = uncheckedDowncast<JSC::JSString>(keyValue);
auto keyIdent = JSC::Identifier::fromString(vm, key->value(global));
RETURN_IF_EXCEPTION(scope, nullptr);

auto* entry = global->moduleLoader()->registryEntry(keyIdent);
ASSERT(entry); // should have called BakeLoadServerCode and wait for that promise
ASSERT(entry); // the caller waited for this module's evaluation promise
auto* module = entry ? entry->record() : nullptr;
ASSERT(module);
JSC::JSModuleNamespaceObject* namespaceObject = global->moduleLoader()->getModuleNamespaceObject(global, module);
RETURN_IF_EXCEPTION(scope, nullptr);
ASSERT(namespaceObject);
return JSC::JSValue::encode(namespaceObject);
return namespaceObject;
}

extern "C" JSC::EncodedJSValue BakeGetModuleNamespace(
JSC::JSGlobalObject* global,
JSC::EncodedJSValue keyValue
) {
return JSC::JSValue::encode(getModuleNamespace(global, JSC::JSValue::decode(keyValue)));
}

extern "C" JSC::EncodedJSValue BakeGetDefaultExportFromModule(
JSC::JSGlobalObject* global,
JSC::JSValue keyValue
JSC::EncodedJSValue keyValue
) {
auto& vm = JSC::getVM(global);
return JSC::JSValue::encode(uncheckedDowncast<JSC::JSModuleNamespaceObject>(JSC::JSValue::decode(BakeGetModuleNamespace(global, keyValue)))->get(global, vm.propertyNames->defaultKeyword));
auto scope = DECLARE_THROW_SCOPE(vm);

JSC::JSModuleNamespaceObject* namespaceObject = getModuleNamespace(global, JSC::JSValue::decode(keyValue));
RETURN_IF_EXCEPTION(scope, {});

JSC::JSValue defaultExport = namespaceObject->get(global, vm.propertyNames->defaultKeyword);
RETURN_IF_EXCEPTION(scope, {});
return JSC::JSValue::encode(defaultExport);
}

// There were issues when trying to use JSValue.get from zig
extern "C" JSC::EncodedJSValue BakeGetOnModuleNamespace(
JSC::JSGlobalObject* global,
JSC::JSModuleNamespaceObject* moduleNamespace,
JSC::EncodedJSValue moduleNamespaceValue,
const unsigned char* key,
size_t keyLength
) {
auto& vm = JSC::getVM(global);
auto scope = DECLARE_THROW_SCOPE(vm);

auto* moduleNamespace = uncheckedDowncast<JSC::JSModuleNamespaceObject>(JSC::JSValue::decode(moduleNamespaceValue));
const auto propertyString = String(StringImpl::createWithoutCopying({ key, keyLength }));
const auto identifier = JSC::Identifier::fromString(vm, propertyString);
const auto property = JSC::PropertyName(identifier);
return JSC::JSValue::encode(moduleNamespace->get(global, property));
}

extern "C" JSC::EncodedJSValue BakeRegisterProductionChunk(JSC::JSGlobalObject* global, BunString virtualPathName, BunString source) {
auto& vm = JSC::getVM(global);
auto scope = DECLARE_THROW_SCOPE(vm);

String string = virtualPathName.toWTFString();
JSC::JSString* key = JSC::jsString(vm, string);
JSC::SourceOrigin origin = JSC::SourceOrigin(WTF::URL(string));
JSC::SourceCode sourceCode = JSC::SourceCode(SourceProvider::create(
global,
source.toWTFString(),
origin,
WTF::move(string),
WTF::TextPosition(),
JSC::SourceProviderSourceType::Module
));

global->moduleLoader()->provideFetch(global, JSC::Identifier::fromString(vm, key->getString(global)), JSC::ScriptFetchParameters::Type::JavaScript, WTF::move(sourceCode));
JSC::JSValue value = moduleNamespace->get(global, property);
RETURN_IF_EXCEPTION(scope, {});

return JSC::JSValue::encode(key);
return JSC::JSValue::encode(value);
}

} // namespace Bake
135 changes: 71 additions & 64 deletions src/runtime/bake/production.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,10 +355,11 @@ fn build_with_vm(ctx: Context, cwd: &[u8], pt: &mut PerThread) -> crate::Result<
{
Unwrapped::Pending => unreachable!(),
Unwrapped::Fulfilled(_) => {
let default = BakeGetDefaultExportFromModule(
let default = c::bake_get_default_export_from_module(
global,
config_entry_point_string.to_js(global).map_err(js_err)?,
);
)
.map_err(js_err)?;

if !default.is_object() {
return Err(js_err(global.throw_invalid_arguments(format_args!(
Expand Down Expand Up @@ -843,17 +844,10 @@ fn build_with_vm(ctx: Context, cwd: &[u8], pt: &mut PerThread) -> crate::Result<

let server_file = router_type.server_file;
let server_entry_point = pt.load_bundled_module(server_file)?;
let server_render_func = 'brk: {
let Some(raw) = bake_get_on_module_namespace(global, server_entry_point, b"prerender")
else {
break 'brk None;
};
if !raw.is_callable() {
break 'brk None;
}
break 'brk Some(raw);
};
let Some(server_render_func) = server_render_func else {
let server_render_func =
c::bake_get_on_module_namespace(global, server_entry_point, b"prerender")
.map_err(js_err)?;
if !server_render_func.is_callable() {
bun_core::err_generic!("Framework does not support static site generation");
bun_core::note!(
"The file {} is missing the \"prerender\" export, which defines how to generate static files.",
Expand All @@ -863,34 +857,23 @@ fn build_with_vm(ctx: Context, cwd: &[u8], pt: &mut PerThread) -> crate::Result<
))
);
Global::crash();
};
}

let server_param_func = if router.dynamic_routes.count() > 0 {
let f = 'brk: {
let Some(raw) =
bake_get_on_module_namespace(global, server_entry_point, b"getParams")
else {
break 'brk None;
};
if !raw.is_callable() {
break 'brk None;
}
break 'brk Some(raw);
};
match f {
Some(f) => f,
None => {
bun_core::err_generic!("Framework does not support static site generation");
bun_core::note!(
"The file {} is missing the \"getParams\" export, which defines how to generate static files.",
bun_core::fmt::quote(resolve_path::relative(
cwd,
pt.input_file(server_file).abs_path()
))
);
Global::crash();
}
let f = c::bake_get_on_module_namespace(global, server_entry_point, b"getParams")
.map_err(js_err)?;
if !f.is_callable() {
bun_core::err_generic!("Framework does not support static site generation");
bun_core::note!(
"The file {} is missing the \"getParams\" export, which defines how to generate static files.",
bun_core::fmt::quote(resolve_path::relative(
cwd,
pt.input_file(server_file).abs_path()
))
);
Global::crash();
}
f
} else {
JSValue::NULL
};
Expand Down Expand Up @@ -1225,7 +1208,7 @@ fn load_module(
global: &JSGlobalObject,
key: JSValue,
) -> crate::Result<JSValue> {
let promise_value = BakeLoadModuleByKey(global, key);
let promise_value = c::bake_load_module_by_key(global, key).map_err(js_err)?;
let promise: *mut jsc::JSInternalPromise = match promise_value.as_any_promise().unwrap() {
AnyPromise::Internal(p) => p,
AnyPromise::Normal(_) => unreachable!(),
Expand All @@ -1252,38 +1235,62 @@ fn load_module(
let jsc_vm = vm_ref.as_mut().jsc_vm_mut();
match jsc::JSInternalPromise::opaque_mut(promise).unwrap(jsc_vm, UnwrapMode::MarkHandled) {
Unwrapped::Pending => unreachable!(),
Unwrapped::Fulfilled(_) => Ok(BakeGetModuleNamespace(global, key)),
Unwrapped::Fulfilled(_) => c::bake_get_module_namespace(global, key).map_err(js_err),
Unwrapped::Rejected(err) => Err(js_err(vm_ref.global().throw_value(err))),
}
}

// extern apis:
/// BakeSourceProvider.cpp entry points; each returns empty iff it threw (`from_js_host_call`).
mod c {
use super::*;

unsafe extern "C" {
safe fn BakeGetDefaultExportFromModule(global: &JSGlobalObject, key: JSValue) -> JSValue;
safe fn BakeGetModuleNamespace(global: &JSGlobalObject, key: JSValue) -> JSValue;
safe fn BakeLoadModuleByKey(global: &JSGlobalObject, key: JSValue) -> JSValue;
}

fn bake_get_on_module_namespace(
global: &JSGlobalObject,
module: JSValue,
property: &[u8],
) -> Option<JSValue> {
unsafe extern "C" {
// PRECONDITION: `ptr` must be readable for `len` bytes (C++ builds an
// `Identifier` from the slice). Cannot be `safe fn` — raw ptr+len pair
// carries a caller-side validity precondition.
#[link_name = "BakeGetOnModuleNamespace"]
fn f(global: *const JSGlobalObject, module: JSValue, ptr: *const u8, len: usize)
-> JSValue;
safe fn BakeLoadModuleByKey(global: &JSGlobalObject, key: JSValue) -> JSValue;
safe fn BakeGetModuleNamespace(global: &JSGlobalObject, key: JSValue) -> JSValue;
safe fn BakeGetDefaultExportFromModule(global: &JSGlobalObject, key: JSValue) -> JSValue;
/// Not `safe`: `ptr` must be readable for `len` bytes.
fn BakeGetOnModuleNamespace(
global: &JSGlobalObject,
module_namespace: JSValue,
ptr: *const u8,
len: usize,
) -> JSValue;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/// Returns the promise for evaluating the module that `key` (a JSString) names.
pub(super) fn bake_load_module_by_key(
global: &JSGlobalObject,
key: JSValue,
) -> JsResult<JSValue> {
jsc::from_js_host_call(global, || BakeLoadModuleByKey(global, key))
}

/// The promise from [`bake_load_module_by_key`] for `key` must already be fulfilled.
pub(super) fn bake_get_module_namespace(
global: &JSGlobalObject,
key: JSValue,
) -> JsResult<JSValue> {
jsc::from_js_host_call(global, || BakeGetModuleNamespace(global, key))
}

/// The module `key` names must already be evaluated (here: the config module).
pub(super) fn bake_get_default_export_from_module(
global: &JSGlobalObject,
key: JSValue,
) -> JsResult<JSValue> {
jsc::from_js_host_call(global, || BakeGetDefaultExportFromModule(global, key))
}

pub(super) fn bake_get_on_module_namespace(
global: &JSGlobalObject,
module_namespace: JSValue,
property: &[u8],
) -> JsResult<JSValue> {
// SAFETY: `property` is borrowed for the whole call, so `ptr` is readable for `len` bytes.
jsc::from_js_host_call(global, || unsafe {
BakeGetOnModuleNamespace(global, module_namespace, property.as_ptr(), property.len())
})
}
// SAFETY: `global` is a live `&JSGlobalObject`, `module` is a stack-held
// `JSValue`, and `property.as_ptr()`/`len()` describe a valid borrowed
// `&[u8]` for the call duration — discharges the ptr+len precondition above.
let result: JSValue = unsafe { f(global, module, property.as_ptr(), property.len()) };
debug_assert!(!result.is_empty());
Some(result)
}

// Renders all routes for static site generation by calling the JavaScript implementation.
Expand Down
Loading
Loading