mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
WIP
This commit is contained in:
@@ -2307,6 +2307,11 @@ pub fn finalizeBundle(
|
||||
});
|
||||
defer dev.allocator.free(server_bundle);
|
||||
|
||||
// TODO: is this the best place to set this? Would it be better to
|
||||
// transpile the server modules to replace `new Response(...)` with `new
|
||||
// ResponseBake(...)`??
|
||||
dev.vm.setAllowJSXInResponseConstructor(true);
|
||||
|
||||
const server_modules = if (bun.take(&source_map_json)) |json| blk: {
|
||||
// This memory will be owned by the `DevServerSourceProvider` in C++
|
||||
// from here on out
|
||||
|
||||
@@ -64,9 +64,6 @@ export async function render(request: Request, meta: Bake.RouteMetadata): Promis
|
||||
// This is signaled by `client.tsx` via the `Accept` header.
|
||||
const skipSSR = request.headers.get("Accept")?.includes("text/x-component");
|
||||
|
||||
// Check if the page module has a streaming export, default to false
|
||||
const streaming = meta.pageModule.streaming ?? false;
|
||||
|
||||
// Do not render <link> tags if the request is skipping SSR.
|
||||
const page = getPage(meta, skipSSR ? [] : meta.styles);
|
||||
|
||||
@@ -110,45 +107,11 @@ export async function render(request: Request, meta: Bake.RouteMetadata): Promis
|
||||
}
|
||||
|
||||
// The RSC payload is rendered into HTML
|
||||
if (streaming) {
|
||||
// Stream the response as before
|
||||
return new Response(renderToHtml(rscPayload, meta.modules, signal), {
|
||||
headers: {
|
||||
"Content-Type": "text/html; charset=utf8",
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// TODO: this seems shitty
|
||||
// Buffer the entire response and return it all at once
|
||||
const htmlStream = renderToHtml(rscPayload, meta.modules, signal);
|
||||
const chunks: Uint8Array[] = [];
|
||||
const reader = htmlStream.getReader();
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
chunks.push(value);
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
|
||||
// Combine all chunks into a single response
|
||||
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
||||
const result = new Uint8Array(totalLength);
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
result.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
|
||||
return new Response(result, {
|
||||
headers: {
|
||||
"Content-Type": "text/html; charset=utf8",
|
||||
},
|
||||
});
|
||||
}
|
||||
return new Response(renderToHtml(rscPayload, meta.modules, signal), {
|
||||
headers: {
|
||||
"Content-Type": "text/html; charset=utf8",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// When a production build is performed, pre-rendering is invoked here. If this
|
||||
|
||||
@@ -196,6 +196,16 @@ has_mutated_built_in_extensions: u32 = 0,
|
||||
|
||||
initial_script_execution_context_identifier: i32,
|
||||
|
||||
allow_jsx_in_response_constructor: bool = false,
|
||||
|
||||
pub fn setAllowJSXInResponseConstructor(this: *VirtualMachine, value: bool) void {
|
||||
this.allow_jsx_in_response_constructor = value;
|
||||
}
|
||||
|
||||
pub fn allowJSXInResponseConstructor(this: *VirtualMachine) bool {
|
||||
return this.allow_jsx_in_response_constructor;
|
||||
}
|
||||
|
||||
pub const ProcessAutoKiller = @import("./ProcessAutoKiller.zig");
|
||||
pub const OnUnhandledRejection = fn (*VirtualMachine, globalObject: *JSGlobalObject, JSValue) void;
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@ pub const JSGlobalObject = opaque {
|
||||
return error.JSError;
|
||||
}
|
||||
|
||||
pub fn allowJSXInResponseConstructor(this: *JSGlobalObject) bool {
|
||||
return this.bunVM().allowJSXInResponseConstructor();
|
||||
}
|
||||
|
||||
extern fn JSGlobalObject__createOutOfMemoryError(this: *JSGlobalObject) JSValue;
|
||||
pub fn createOutOfMemoryError(this: *JSGlobalObject) JSValue {
|
||||
return JSGlobalObject__createOutOfMemoryError(this);
|
||||
|
||||
@@ -45,6 +45,21 @@ pub const JSValue = enum(i64) {
|
||||
return jsc.JSObject.getIndex(this, globalThis, i);
|
||||
}
|
||||
|
||||
extern fn JSC__JSValue__isJSXElement(JSValue, *JSGlobalObject) bool;
|
||||
pub fn isJSXElement(this: JSValue, globalThis: *jsc.JSGlobalObject) JSError!bool {
|
||||
return bun.jsc.fromJSHostCallGeneric(
|
||||
globalThis,
|
||||
@src(),
|
||||
JSC__JSValue__isJSXElement,
|
||||
.{ this, globalThis },
|
||||
);
|
||||
}
|
||||
|
||||
extern fn JSC__JSValue__transformToReactElement(responseValue: JSValue, componentValue: JSValue, globalObject: *JSGlobalObject) void;
|
||||
pub fn transformToReactElement(responseValue: JSValue, componentValue: JSValue, globalThis: *jsc.JSGlobalObject) void {
|
||||
JSC__JSValue__transformToReactElement(responseValue, componentValue, globalThis);
|
||||
}
|
||||
|
||||
extern fn JSC__JSValue__getDirectIndex(JSValue, *JSGlobalObject, u32) JSValue;
|
||||
pub fn getDirectIndex(this: JSValue, globalThis: *JSGlobalObject, i: u32) JSValue {
|
||||
return JSC__JSValue__getDirectIndex(this, globalThis, i);
|
||||
|
||||
@@ -5884,12 +5884,152 @@ restart:
|
||||
JSC__JSValue__forEachPropertyImpl<false>(JSValue0, globalObject, arg2, iter);
|
||||
}
|
||||
|
||||
extern "C" bool JSC__JSValue__isJSXElement(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* globalObject)
|
||||
{
|
||||
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
|
||||
// React does this:
|
||||
// export const REACT_LEGACY_ELEMENT_TYPE: symbol = Symbol.for('react.element');
|
||||
// export const REACT_ELEMENT_TYPE: symbol = renameElementSymbol
|
||||
// ? Symbol.for('react.transitional.element')
|
||||
// : REACT_LEGACY_ELEMENT_TYPE;
|
||||
|
||||
// TODO: cache these, i cri everytim
|
||||
auto react_legacy_element_symbol = JSC::Symbol::create(vm, vm.symbolRegistry().symbolForKey("react.element"_s));
|
||||
auto react_element_symbol = JSC::Symbol::create(vm, vm.symbolRegistry().symbolForKey("react.transitional.element"_s));
|
||||
|
||||
JSC::JSValue value = JSC::JSValue::decode(JSValue0);
|
||||
|
||||
// If it's a function (React component), call it to get the element
|
||||
if (value.isCallable()) {
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSC::JSObject* function = value.getObject();
|
||||
JSC::CallData callData = JSC::getCallData(function);
|
||||
JSC::MarkedArgumentBuffer args;
|
||||
|
||||
// Call the component function with no arguments
|
||||
JSC::JSValue result = JSC::call(globalObject, function, callData, JSC::jsUndefined(), args);
|
||||
RETURN_IF_EXCEPTION(scope, false);
|
||||
|
||||
// Now check if the result is a JSX element
|
||||
if (!result.isObject()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JSC::JSObject* resultObject = result.getObject();
|
||||
auto typeofProperty = JSC::Identifier::fromString(vm, "$$typeof"_s);
|
||||
JSC::JSValue typeofValue = resultObject->get(globalObject, typeofProperty);
|
||||
RETURN_IF_EXCEPTION(scope, false);
|
||||
|
||||
if (typeofValue.isSymbol() && (typeofValue == react_legacy_element_symbol || typeofValue == react_element_symbol)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// If it's already an object, check directly for $$typeof
|
||||
else if (value.isObject()) {
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSC::JSObject* object = value.getObject();
|
||||
auto typeofProperty = JSC::Identifier::fromString(vm, "$$typeof"_s);
|
||||
JSC::JSValue typeofValue = object->get(globalObject, typeofProperty);
|
||||
RETURN_IF_EXCEPTION(scope, false);
|
||||
|
||||
if (typeofValue.isSymbol() && (typeofValue == react_legacy_element_symbol || typeofValue == react_element_symbol)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
extern "C" void JSC__JSValue__transformToReactElement(JSC::EncodedJSValue responseValue, JSC::EncodedJSValue componentValue, JSC::JSGlobalObject* globalObject)
|
||||
{
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_CATCH_SCOPE(vm);
|
||||
|
||||
JSC::JSValue response = JSC::JSValue::decode(responseValue);
|
||||
JSC::JSValue component = JSC::JSValue::decode(componentValue);
|
||||
|
||||
if (!response.isObject()) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSC::JSObject* responseObject = response.getObject();
|
||||
|
||||
// Get the React element symbol - same as in isJSXElement
|
||||
// For now, use the transitional element symbol (React 19+)
|
||||
// TODO: check for legacy symbol as fallback
|
||||
auto react_element_symbol = JSC::Symbol::create(vm, vm.symbolRegistry().symbolForKey("react.transitional.element"_s));
|
||||
JSC::JSValue symbolToUse = react_element_symbol;
|
||||
|
||||
// Set $$typeof property
|
||||
auto typeofIdentifier = JSC::Identifier::fromString(vm, "$$typeof"_s);
|
||||
responseObject->putDirect(vm, typeofIdentifier, symbolToUse, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0);
|
||||
|
||||
// Set type property to the component if it's a function, otherwise keep the JSX element as-is
|
||||
auto typeIdentifier = JSC::Identifier::fromString(vm, "type"_s);
|
||||
if (component.isCallable()) {
|
||||
responseObject->putDirect(vm, typeIdentifier, component, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0);
|
||||
} else if (component.isObject()) {
|
||||
// If it's already a JSX element, extract its type
|
||||
JSC::JSObject* componentObject = component.getObject();
|
||||
JSC::JSValue typeValue = componentObject->get(globalObject, typeIdentifier);
|
||||
if (!scope.exception() && !typeValue.isUndefined()) {
|
||||
responseObject->putDirect(vm, typeIdentifier, typeValue, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Set key property to null
|
||||
auto keyIdentifier = JSC::Identifier::fromString(vm, "key"_s);
|
||||
responseObject->putDirect(vm, keyIdentifier, JSC::jsNull(), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0);
|
||||
|
||||
// Set props property to empty object (or extract from component if it's an element)
|
||||
auto propsIdentifier = JSC::Identifier::fromString(vm, "props"_s);
|
||||
if (component.isObject()) {
|
||||
JSC::JSObject* componentObject = component.getObject();
|
||||
JSC::JSValue propsValue = componentObject->get(globalObject, propsIdentifier);
|
||||
if (!scope.exception() && !propsValue.isUndefined()) {
|
||||
responseObject->putDirect(vm, propsIdentifier, propsValue, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0);
|
||||
} else {
|
||||
responseObject->putDirect(vm, propsIdentifier, JSC::constructEmptyObject(globalObject), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0);
|
||||
}
|
||||
} else {
|
||||
responseObject->putDirect(vm, propsIdentifier, JSC::constructEmptyObject(globalObject), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0);
|
||||
}
|
||||
|
||||
// Add _store object for dev mode
|
||||
auto storeIdentifier = JSC::Identifier::fromString(vm, "_store"_s);
|
||||
JSC::JSObject* storeObject = JSC::constructEmptyObject(globalObject);
|
||||
|
||||
// Add validated property to _store
|
||||
auto validatedIdentifier = JSC::Identifier::fromString(vm, "validated"_s);
|
||||
storeObject->putDirect(vm, validatedIdentifier, JSC::jsNumber(0), 0);
|
||||
|
||||
responseObject->putDirect(vm, storeIdentifier, storeObject, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0);
|
||||
|
||||
// Add debug properties (all set to null)
|
||||
auto ownerIdentifier = JSC::Identifier::fromString(vm, "_owner"_s);
|
||||
responseObject->putDirect(vm, ownerIdentifier, JSC::jsNull(), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0);
|
||||
|
||||
auto debugInfoIdentifier = JSC::Identifier::fromString(vm, "_debugInfo"_s);
|
||||
responseObject->putDirect(vm, debugInfoIdentifier, JSC::jsNull(), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0);
|
||||
|
||||
auto debugStackIdentifier = JSC::Identifier::fromString(vm, "_debugStack"_s);
|
||||
responseObject->putDirect(vm, debugStackIdentifier, JSC::jsNull(), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0);
|
||||
|
||||
auto debugTaskIdentifier = JSC::Identifier::fromString(vm, "_debugTask"_s);
|
||||
responseObject->putDirect(vm, debugTaskIdentifier, JSC::jsNull(), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0);
|
||||
}
|
||||
|
||||
extern "C" void JSC__JSValue__forEachPropertyNonIndexed(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* globalObject, void* arg2, void (*iter)(JSC::JSGlobalObject* arg0, void* ctx, ZigString* arg2, JSC::EncodedJSValue JSValue3, bool isSymbol, bool isPrivateSymbol))
|
||||
{
|
||||
JSC__JSValue__forEachPropertyImpl<true>(JSValue0, globalObject, arg2, iter);
|
||||
}
|
||||
|
||||
[[ZIG_EXPORT(check_slow)]] void JSC__JSValue__forEachPropertyOrdered(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* globalObject, void* arg2, void (*iter)([[ZIG_NONNULL]] JSC::JSGlobalObject* arg0, void* ctx, [[ZIG_NONNULL]] ZigString* arg2, JSC::EncodedJSValue JSValue3, bool isSymbol, bool isPrivateSymbol))
|
||||
|
||||
{
|
||||
JSC::JSValue value = JSC::JSValue::decode(JSValue0);
|
||||
JSC::JSObject* object = value.getObject();
|
||||
|
||||
@@ -199,8 +199,12 @@ pub fn getURL(
|
||||
|
||||
pub fn getResponseType(
|
||||
this: *Response,
|
||||
this_value: jsc.JSValue,
|
||||
globalThis: *jsc.JSGlobalObject,
|
||||
) jsc.JSValue {
|
||||
if (js.gc.jsxElement.get(this_value)) |jsx_element| {
|
||||
return jsx_element;
|
||||
}
|
||||
if (this.init.status_code < 200) {
|
||||
return bun.String.static("error").toJS(globalThis);
|
||||
}
|
||||
@@ -303,6 +307,7 @@ pub fn cloneValue(
|
||||
}
|
||||
|
||||
pub fn clone(this: *Response, globalThis: *JSGlobalObject) bun.JSError!*Response {
|
||||
// TODO: handle clone for jsxElement for bake?
|
||||
return bun.new(Response, try this.cloneValue(globalThis));
|
||||
}
|
||||
|
||||
@@ -519,7 +524,7 @@ pub fn constructError(
|
||||
return response.toJS(globalThis);
|
||||
}
|
||||
|
||||
pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!*Response {
|
||||
pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame, this_value: jsc.JSValue) bun.JSError!*Response {
|
||||
const arguments = callframe.argumentsAsArray(2);
|
||||
|
||||
if (!arguments[0].isUndefinedOrNull() and arguments[0].isObject()) {
|
||||
@@ -554,6 +559,15 @@ pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) b
|
||||
return bun.new(Response, response);
|
||||
}
|
||||
}
|
||||
|
||||
// Special case for bake: allow `return new Response(<jsx> ... </jsx>, { ... }`
|
||||
// inside of a react component
|
||||
if (globalThis.allowJSXInResponseConstructor() and try arguments[0].isJSXElement(globalThis)) {
|
||||
// Store the JSX element for later retrieval
|
||||
js.gc.jsxElement.set(this_value, globalThis, arguments[0]);
|
||||
// Transform the Response object to look like a React element
|
||||
JSValue.transformToReactElement(this_value, arguments[0], globalThis);
|
||||
}
|
||||
}
|
||||
var init: Init = (brk: {
|
||||
if (arguments[1].isUndefinedOrNull()) {
|
||||
|
||||
Reference in New Issue
Block a user