From eeecbfa7907c44f18b179a3995800ea71e01cd7c Mon Sep 17 00:00:00 2001 From: Zack Radisic <56137411+zackradisic@users.noreply.github.com> Date: Fri, 5 Sep 2025 15:58:38 -0700 Subject: [PATCH] okie dokie --- src/ast/visitExpr.zig | 7 + src/bun.js/bindings/JSBakeResponse.cpp | 291 ++++++++++-------------- src/bun.js/bindings/JSBakeResponse.h | 59 ++++- src/bun.js/bindings/JSValue.zig | 5 - src/bun.js/bindings/ZigGlobalObject.h | 3 +- src/bun.js/bindings/bindings.cpp | 294 ------------------------- src/bun.js/webcore/Response.zig | 73 +++--- src/js/builtins/BakeSSRResponse.ts | 15 +- src/js/builtins/BunBuiltinNames.h | 10 + 9 files changed, 230 insertions(+), 527 deletions(-) diff --git a/src/ast/visitExpr.zig b/src/ast/visitExpr.zig index da6d1f722c..405c99f348 100644 --- a/src/ast/visitExpr.zig +++ b/src/ast/visitExpr.zig @@ -93,6 +93,11 @@ pub fn VisitExpr( } // Transform Response -> BakeResponse for server-side code + // TODO: this does the incorrect thing in this case: + // ``` + // const Response = 'ooga booga!' + // console.log(Response) + // ``` if (p.options.features.server_components.isServerSide() and bun.strings.eqlComptime(name, "Response")) { @@ -101,6 +106,8 @@ pub fn VisitExpr( e_.ref = bake_response_result.ref; e_.must_keep_due_to_with_stmt = bake_response_result.is_inside_with_scope; + std.debug.print("{s}: Response -> SSRResponse\n", .{p.source.path.pretty}); + // Handle the rest of the identifier processing with the new ref return p.handleIdentifier(expr.loc, e_, "SSRResponse", IdentifierOpts{ .assign_target = in.assign_target, diff --git a/src/bun.js/bindings/JSBakeResponse.cpp b/src/bun.js/bindings/JSBakeResponse.cpp index f65c14cc27..ba5d53f19e 100644 --- a/src/bun.js/bindings/JSBakeResponse.cpp +++ b/src/bun.js/bindings/JSBakeResponse.cpp @@ -23,9 +23,6 @@ namespace Bun { -static JSC_DECLARE_HOST_FUNCTION(jsBakeResponseConstructorRender); -static JSC_DECLARE_HOST_FUNCTION(jsBakeResponseConstructorRedirect); - static JSC_DECLARE_CUSTOM_GETTER(jsBakeResponsePrototypeGetSymbolFor); static JSC_DECLARE_CUSTOM_GETTER(jsBakeResponsePrototypeGetType); static JSC_DECLARE_CUSTOM_GETTER(jsBakeResponsePrototypeGetKey); @@ -36,36 +33,114 @@ static JSC_DECLARE_CUSTOM_GETTER(jsBakeResponsePrototypeGetDebugInfo); static JSC_DECLARE_CUSTOM_GETTER(jsBakeResponsePrototypeGetDebugStack); static JSC_DECLARE_CUSTOM_GETTER(jsBakeResponsePrototypeGetDebugTask); -extern JSC_CALLCONV void* JSC_HOST_CALL_ATTRIBUTES ResponseClass__constructForSSR(JSC::JSGlobalObject*, JSC::CallFrame*, JSC::EncodedJSValue); +extern JSC_CALLCONV void* JSC_HOST_CALL_ATTRIBUTES ResponseClass__constructForSSR(JSC::JSGlobalObject*, JSC::CallFrame*, JSC::EncodedJSValue, int*); extern "C" SYSV_ABI JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ResponseClass__constructError(JSC::JSGlobalObject*, JSC::CallFrame*) SYSV_ABI; extern "C" SYSV_ABI JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ResponseClass__constructJSON(JSC::JSGlobalObject*, JSC::CallFrame*) SYSV_ABI; +extern "C" SYSV_ABI JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ResponseClass__constructRender(JSC::JSGlobalObject*, JSC::CallFrame*) SYSV_ABI; +extern "C" SYSV_ABI JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ResponseClass__constructRedirect(JSC::JSGlobalObject*, JSC::CallFrame*) SYSV_ABI; extern JSC_CALLCONV size_t Response__estimatedSize(void* ptr); +bool 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); + + // TODO: primitive values (strings, numbers, booleans, null, undefined) are also valid + 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" bool JSC__JSValue__isJSXElement(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* globalObject) +{ + return isJSXElement(JSValue0, globalObject); +} + +extern JSC_CALLCONV JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES Response__createForSSR(Zig::GlobalObject* globalObject, void* ptr, uint8_t kind) +{ + Structure* structure = globalObject->JSBakeResponseStructure(); + printf("Creating JSBakeResponse for kind: %d\n", kind); + + JSBakeResponse* instance = JSBakeResponse::create(globalObject->vm(), globalObject, structure, ptr); + + if (kind == JSBakeResponseKind::Render) { + instance->kind(JSBakeResponseKind::Render); + } else if (kind == JSBakeResponseKind::Redirect) { + instance->kind(JSBakeResponseKind::Redirect); + } else { + // Should not be called with JSBakeResponseKind::Regular or anything + // else + UNREACHABLE(); + } + + instance->setToThrow(globalObject, globalObject->vm()); + + return JSValue::encode(instance); +} + static const HashTableValue JSBakeResponseConstructorTableValues[] = { { "error"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ResponseClass__constructError, 0 } }, { "json"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ResponseClass__constructJSON, 0 } }, - { "render"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBakeResponseConstructorRender, 1 } }, - { "redirect"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBakeResponseConstructorRedirect, 1 } }, + { "redirect"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ResponseClass__constructRedirect, 0 } }, + { "render"_s, static_cast(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ResponseClass__constructRender, 0 } } }; -static const HashTableValue JSBakeResponsePrototypeTableValues[] = { - { "$$typeof"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsBakeResponsePrototypeGetSymbolFor, nullptr } }, - { "type"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsBakeResponsePrototypeGetType, nullptr } }, - { "key"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsBakeResponsePrototypeGetKey, nullptr } }, - { "props"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsBakeResponsePrototypeGetProps, nullptr } }, - { "_store"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsBakeResponsePrototypeGetStore, nullptr } }, - { "_owner"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsBakeResponsePrototypeGetOwner, nullptr } }, - { "_debugInfo"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsBakeResponsePrototypeGetDebugInfo, nullptr } }, - { "_debugStack"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsBakeResponsePrototypeGetDebugStack, nullptr } }, - { "_debugTask"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsBakeResponsePrototypeGetDebugTask, nullptr } } -}; - -JSBakeResponse* JSBakeResponse::create(JSC::VM& vm, JSC::Structure* structure, void* ctx) +JSBakeResponse* JSBakeResponse::create(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::Structure* structure, void* ctx) { JSBakeResponse* ptr = new (NotNull, JSC::allocateCell(vm)) JSBakeResponse(vm, structure, ctx); ptr->finishCreation(vm); + + auto builtinNames = WebCore::builtinNames(vm); + + // $$typeof = Symbol.for("react.transitional.element") + ptr->putDirect(vm, builtinNames.$$typeofPublicName(), JSC::Symbol::create(vm, vm.symbolRegistry().symbolForKey("react.transitional.element"_s)), 0); + // type = false + ptr->putDirect(vm, builtinNames.typePublicName(), JSC::jsNull(), 0); + // key = null + ptr->putDirect(vm, builtinNames.keyPublicName(), JSC::jsNull(), 0); + // props = {} + ptr->putDirect(vm, builtinNames.propsPublicName(), JSC::constructEmptyObject(globalObject), 0); + + // _store = { _validated: 0 } + JSObject* storeObject = JSC::constructEmptyObject(globalObject); + auto validatedIdent = JSC::Identifier::fromString(vm, "validated"_s); + storeObject->putDirect(vm, builtinNames.validatedPublicName(), jsNumber(0), 0); + ptr->putDirect(vm, builtinNames._storePublicName(), storeObject, 0); + + // _owner = null + ptr->putDirect(vm, builtinNames._ownerPublicName(), JSC::jsNull(), 0); + // _debugInfo = null + ptr->putDirect(vm, builtinNames._debugInfoPublicName(), JSC::jsNull(), 0); + // _debugStack = null + ptr->putDirect(vm, builtinNames._debugStackPublicName(), JSC::jsNull(), 0); + // _debugTask = null + ptr->putDirect(vm, builtinNames._debugTaskPublicName(), JSC::jsNull(), 0); + return ptr; } @@ -103,47 +178,6 @@ void JSBakeResponse::visitChildrenImpl(JSCell* cell, Visitor& visitor) DEFINE_VISIT_CHILDREN(JSBakeResponse); -class JSBakeResponsePrototype final : public JSNonFinalObject { -public: - using Base = JSNonFinalObject; - - static JSBakeResponsePrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) - { - auto* ptr = new (NotNull, JSC::allocateCell(vm)) JSBakeResponsePrototype(vm, structure); - ptr->finishCreation(vm, globalObject); - return ptr; - } - - static Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) - { - auto* structure = Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info(), NonArray); - structure->setMayBePrototype(true); - return structure; - } - - DECLARE_INFO; - - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSBakeResponsePrototype, Base); - return &vm.plainObjectSpace(); - } - -private: - JSBakeResponsePrototype(JSC::VM& vm, JSC::Structure* structure) - : Base(vm, structure) - { - } - - void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) - { - Base::finishCreation(vm); - reifyStaticProperties(vm, JSBakeResponse::info(), JSBakeResponsePrototypeTableValues, *this); - JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); - } -}; - JSC_DECLARE_HOST_FUNCTION(callBakeResponse); JSC_DECLARE_HOST_FUNCTION(constructBakeResponse); @@ -159,8 +193,8 @@ public: return constructor; } - // DECLARE_INFO; - DECLARE_EXPORT_INFO; + DECLARE_INFO; + // DECLARE_EXPORT_INFO; // Must be defined for each specialization class. static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) @@ -169,7 +203,7 @@ public: JSC::VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); JSObject* newTarget = asObject(callFrame->newTarget()); - auto* constructor = globalObject->JSResponseConstructor(); + auto* constructor = globalObject->JSBakeResponseConstructor(); Structure* structure = globalObject->JSBakeResponseStructure(); if (constructor != newTarget) [[unlikely]] { auto* functionGlobalObject = defaultGlobalObject( @@ -180,9 +214,10 @@ public: RETURN_IF_EXCEPTION(scope, {}); } - JSBakeResponse* instance = JSBakeResponse::create(vm, structure, nullptr); + JSBakeResponse* instance = JSBakeResponse::create(vm, globalObject, structure, nullptr); - void* ptr = ResponseClass__constructForSSR(globalObject, callFrame, JSValue::encode(instance)); + int arg_was_jsx = 0; + void* ptr = ResponseClass__constructForSSR(globalObject, callFrame, JSValue::encode(instance), &arg_was_jsx); if (scope.exception()) [[unlikely]] { ASSERT_WITH_MESSAGE(!ptr, "Memory leak detected: new SSRResponse() allocated memory without checking for exceptions."); return JSValue::encode(JSC::jsUndefined()); @@ -190,6 +225,12 @@ public: instance->m_ctx = ptr; + if (arg_was_jsx == 1 && callFrame->argumentCount() > 0) { + JSValue arg = callFrame->argument(0); + JSValue responseOptions = callFrame->argumentCount() > 1 ? callFrame->argument(1) : JSC::jsUndefined(); + instance->wrapInnerComponent(globalObject, vm, arg, responseOptions); + } + auto size = Response__estimatedSize(ptr); vm.heap.reportExtraMemoryAllocated(instance, size); @@ -204,9 +245,9 @@ public: auto scope = DECLARE_THROW_SCOPE(vm); Structure* structure = globalObject->JSBakeResponseStructure(); - JSBakeResponse* instance = JSBakeResponse::create(vm, structure, nullptr); + JSBakeResponse* instance = JSBakeResponse::create(vm, globalObject, structure, nullptr); - void* ptr = ResponseClass__constructForSSR(globalObject, callFrame, JSValue::encode(instance)); + void* ptr = ResponseClass__constructForSSR(globalObject, callFrame, JSValue::encode(instance), nullptr); if (scope.exception()) [[unlikely]] { ASSERT_WITH_MESSAGE(!ptr, "Memory leak detected: new SSRResponse() allocated memory without checking for exceptions."); return JSValue::encode(JSC::jsUndefined()); @@ -247,130 +288,32 @@ private: } }; -const JSC::ClassInfo JSBakeResponsePrototype::s_info = { "SSRResponse"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBakeResponsePrototype) }; -const JSC::ClassInfo JSBakeResponse::s_info = { "SSRResponse"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBakeResponse) }; -const JSC::ClassInfo JSBakeResponseConstructor::s_info = { "SSRResponse"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBakeResponseConstructor) }; +const JSC::ClassInfo JSBakeResponse::s_info = { "SSRResponse"_s, &JSResponse::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBakeResponse) }; +const JSC::ClassInfo JSBakeResponseConstructor::s_info = { ""_s, &JSC::InternalFunction::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBakeResponseConstructor) }; -JSC_DEFINE_CUSTOM_GETTER(jsBakeResponsePrototypeGetSymbolFor, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +Structure* createJSBakeResponseStructure(JSC::VM& vm, Zig::GlobalObject* globalObject, JSObject* prototype) { - JSBakeResponse* response = jsDynamicCast(JSValue::decode(thisValue)); - if (!response) - return JSValue::encode(jsUndefined()); - auto& vm = globalObject->vm(); - auto symbolKey = "react.transitional.element"_s; - return JSValue::encode(JSC::Symbol::create(vm, vm.symbolRegistry().symbolForKey(symbolKey))); -} + auto structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, 0), JSBakeResponse::info(), NonArray, 0); -JSC_DEFINE_CUSTOM_GETTER(jsBakeResponsePrototypeGetType, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - JSBakeResponse* response = jsDynamicCast(JSValue::decode(thisValue)); - if (!response) - return JSValue::encode(jsUndefined()); + // Unfortunately we cannot use structure->addPropertyTransition as it does + // not with with JSC::JSNonFinalObject - printf("m_ctx: %p\n", response->m_ctx); - - // auto& type = response->type(); - // auto typeValue = type.get(); - // return JSValue::encode(typeValue); - String wtfstring = "Hello"_s; - auto* jsstring = JSC::jsString(globalObject->vm(), wtfstring); - return JSValue::encode(jsstring); -} - -JSC_DEFINE_CUSTOM_GETTER(jsBakeResponsePrototypeGetKey, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - return JSValue::encode(jsNull()); -} - -JSC_DEFINE_CUSTOM_GETTER(jsBakeResponsePrototypeGetProps, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - JSBakeResponse* response = jsDynamicCast(JSValue::decode(thisValue)); - if (!response) - return JSValue::encode(jsUndefined()); - - return JSValue::encode(JSC::constructEmptyObject(globalObject)); -} - -JSC_DEFINE_CUSTOM_GETTER(jsBakeResponsePrototypeGetStore, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - JSBakeResponse* response = jsDynamicCast(JSValue::decode(thisValue)); - if (!response) - return JSValue::encode(jsUndefined()); - - auto& vm = globalObject->vm(); - JSObject* storeObject = JSC::constructEmptyObject(globalObject); - auto validatedIdent = JSC::Identifier::fromString(vm, "validated"_s); - storeObject->putDirect(vm, validatedIdent, jsNumber(0), 0); - return JSValue::encode(storeObject); -} - -JSC_DEFINE_CUSTOM_GETTER(jsBakeResponsePrototypeGetOwner, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - return JSValue::encode(jsNull()); -} - -JSC_DEFINE_CUSTOM_GETTER(jsBakeResponsePrototypeGetDebugInfo, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - return JSValue::encode(jsNull()); -} - -JSC_DEFINE_CUSTOM_GETTER(jsBakeResponsePrototypeGetDebugStack, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - return JSValue::encode(jsNull()); -} - -JSC_DEFINE_CUSTOM_GETTER(jsBakeResponsePrototypeGetDebugTask, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - return JSValue::encode(jsNull()); -} - -JSC_DEFINE_HOST_FUNCTION(jsBakeResponseConstructorRender, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - return JSValue::encode(jsUndefined()); -} - -JSC_DEFINE_HOST_FUNCTION(jsBakeResponseConstructorRedirect, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - return JSValue::encode(jsUndefined()); -} - -JSC_DEFINE_HOST_FUNCTION(callBakeResponse, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - auto& vm = globalObject->vm(); - auto throwScope = DECLARE_THROW_SCOPE(vm); - throwScope.throwException(globalObject, createTypeError(globalObject, "BakeResponse constructor cannot be called as a function"_s)); - return JSValue::encode(jsUndefined()); -} - -JSC_DEFINE_HOST_FUNCTION(constructBakeResponse, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - auto& vm = globalObject->vm(); - auto* zigGlobalObject = defaultGlobalObject(globalObject); - - auto* structure = createJSBakeResponseStructure(vm, zigGlobalObject); - - return JSValue::encode(JSBakeResponse::create(vm, structure, nullptr)); + return structure; } void setupJSBakeResponseClassStructure(JSC::LazyClassStructure::Initializer& init) { auto* zigGlobal = reinterpret_cast(init.global); - auto* prototypeStructure = JSBakeResponsePrototype::createStructure(init.vm, init.global, zigGlobal->JSResponsePrototype()); - auto* prototype = JSBakeResponsePrototype::create(init.vm, init.global, prototypeStructure); + auto* prototype = JSC::constructEmptyObject(zigGlobal, zigGlobal->JSResponsePrototype()); auto* constructorStructure = JSBakeResponseConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()); auto* constructor = JSBakeResponseConstructor::create(init.vm, constructorStructure, prototype); - auto* structure = JSBakeResponse::createStructure(init.vm, init.global, prototype); + auto* structure = createJSBakeResponseStructure(init.vm, zigGlobal, prototype); init.setPrototype(prototype); init.setStructure(structure); init.setConstructor(constructor); } -Structure* createJSBakeResponseStructure(JSC::VM& vm, Zig::GlobalObject* globalObject) -{ - return globalObject->JSBakeResponseStructure(); -} - } // namespace Bun diff --git a/src/bun.js/bindings/JSBakeResponse.h b/src/bun.js/bindings/JSBakeResponse.h index 1e971bae8b..c1d2c5bf66 100644 --- a/src/bun.js/bindings/JSBakeResponse.h +++ b/src/bun.js/bindings/JSBakeResponse.h @@ -8,14 +8,24 @@ namespace Bun { using namespace JSC; using namespace WebCore; +enum JSBakeResponseKind : uint8_t { + Regular = 0, + Redirect = 1, + Render = 2, +}; + class JSBakeResponse : public JSResponse { public: using Base = JSResponse; - static JSBakeResponse* create(JSC::VM& vm, JSC::Structure* structure, void* ctx); + DECLARE_VISIT_CHILDREN; + DECLARE_INFO; + + static JSBakeResponse* create(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::Structure* structure, void* ctx); static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype); - // JSC::Strong& type() { return m_type; } + JSBakeResponseKind kind() const { return m_kind; } + void kind(JSBakeResponseKind kind) { m_kind = kind; } template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) @@ -29,17 +39,54 @@ public: static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); - DECLARE_VISIT_CHILDREN; - DECLARE_INFO; + void setToThrow(JSC::JSGlobalObject* globalObject, JSC::VM& vm) + { + JSC::JSFunction* wrapComponentFn = JSC::JSFunction::create(vm, globalObject, bakeSSRResponseWrapComponentCodeGenerator(vm), globalObject); + + JSC::MarkedArgumentBuffer args; + // component + args.append(jsUndefined()); + // responseObject + args.append(this); + // responseOptions + args.append(jsUndefined()); + // kind + args.append(JSC::jsNumber(static_cast(this->kind()))); + + auto callData = JSC::getCallData(wrapComponentFn); + JSC::JSValue wrappedComponent = JSC::call(globalObject, wrapComponentFn, callData, JSC::jsUndefined(), args); + + this->putDirect(vm, WebCore::builtinNames(vm).typePublicName(), wrappedComponent, 0); + } + + void wrapInnerComponent(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSValue component, JSValue responseOptions) + { + this->kind(JSBakeResponseKind::Regular); + JSC::JSFunction* wrapComponentFn = JSC::JSFunction::create(vm, globalObject, bakeSSRResponseWrapComponentCodeGenerator(vm), globalObject); + + JSC::MarkedArgumentBuffer args; + // component + args.append(component); + // responseObject + args.append(this); + // responseOptions + args.append(responseOptions); + // kind + args.append(JSC::jsNumber(static_cast(JSBakeResponseKind::Regular))); + + auto callData = JSC::getCallData(wrapComponentFn); + JSC::JSValue wrappedComponent = JSC::call(globalObject, wrapComponentFn, callData, JSC::jsUndefined(), args); + + this->putDirect(vm, WebCore::builtinNames(vm).typePublicName(), wrappedComponent, 0); + } private: JSBakeResponse(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr); void finishCreation(JSC::VM& vm); - // JSC::Strong m_type; + JSBakeResponseKind m_kind; }; -JSC::Structure* createJSBakeResponseStructure(JSC::VM&, Zig::GlobalObject*); void setupJSBakeResponseClassStructure(JSC::LazyClassStructure::Initializer& init); } // namespace Bun diff --git a/src/bun.js/bindings/JSValue.zig b/src/bun.js/bindings/JSValue.zig index e8060b940f..3526ee372d 100644 --- a/src/bun.js/bindings/JSValue.zig +++ b/src/bun.js/bindings/JSValue.zig @@ -67,11 +67,6 @@ pub const JSValue = enum(i64) { JSC__JSValue__transformToReactElement(responseValue, componentValue, globalThis); } - extern fn JSC__JSValue__transformToReactElementWithOptions(responseValue: JSValue, componentValue: JSValue, responseOptions: JSValue, globalObject: *JSGlobalObject) void; - pub fn transformToReactElementWithOptions(responseValue: JSValue, componentValue: JSValue, responseOptions: JSValue, globalThis: *jsc.JSGlobalObject) JSError!void { - return bun.jsc.fromJSHostCallGeneric(globalThis, @src(), JSC__JSValue__transformToReactElementWithOptions, .{ responseValue, componentValue, responseOptions, 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); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index c5c932c6f6..816351fb70 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -234,9 +234,8 @@ public: JSC::JSValue HTTPSResponseSinkPrototype() const { return m_JSHTTPSResponseSinkClassStructure.prototypeInitializedOnMainThread(this); } JSC::JSValue JSReadableHTTPSResponseSinkControllerPrototype() const { return m_JSHTTPSResponseControllerPrototype.getInitializedOnMainThread(this); } + JSC::JSObject* JSBakeResponseConstructor() const { return m_JSBakeResponseClassStructure.constructorInitializedOnMainThread(this); } JSC::Structure* JSBakeResponseStructure() const { return m_JSBakeResponseClassStructure.getInitializedOnMainThread(this); } - JSC::JSObject* JSBakeResponseConstructor() { return m_JSBakeResponseClassStructure.constructorInitializedOnMainThread(this); } - JSC::JSValue JSBakeResponsePrototype() const { return m_JSBakeResponseClassStructure.prototypeInitializedOnMainThread(this); } JSC::Structure* NetworkSinkStructure() const { return m_JSNetworkSinkClassStructure.getInitializedOnMainThread(this); } JSC::JSObject* NetworkSink() { return m_JSNetworkSinkClassStructure.constructorInitializedOnMainThread(this); } diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 68fa2d91e1..a357dd41a6 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -5888,300 +5888,6 @@ restart: JSC__JSValue__forEachPropertyImpl(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); - - // TODO: primitive values (strings, numbers, booleans, null, undefined) are also valid - 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; -} - -// Forward declaration -extern "C" void JSC__JSValue__transformToReactElementWithOptions(JSC::EncodedJSValue responseValue, JSC::EncodedJSValue componentValue, JSC::EncodedJSValue responseOptionsValue, JSC::JSGlobalObject* globalObject); - -extern "C" void JSC__JSValue__transformToReactElement(JSC::EncodedJSValue responseValue, JSC::EncodedJSValue componentValue, JSC::JSGlobalObject* globalObject) -{ - // Call the version with options, passing undefined for options - JSC__JSValue__transformToReactElementWithOptions(responseValue, componentValue, JSC::JSValue::encode(JSC::jsUndefined()), globalObject); -} - -/// TODO: This could just be a builtin function and be 10x less lines of code why is it in C++ -/// TODO: this should actually just be a special Response sub-class and the transpiler rewrites the code to use this -/// -/// What this function does is make a Response object pretend to be a React -/// component. To do this we have to put a couple properties on it: -/// -/// ```ts -/// response["$$typeof"] = REACT_ELEMENT_TYPE; -/// response.type = Component; -/// response.key = null; -/// response.props = {}; - -/// // Add the _store object that React expects in dev mode -/// response._store = {}; -/// Object.defineProperty(response._store, 'validated', { -/// configurable: false, -/// enumerable: false, -/// writable: true, -/// value: 0 // or 1 to mark it as already validated -/// }); - -/// // Add debug properties expected in dev mode -/// response._owner = null; -/// response._debugInfo = null; -/// response._debugStack = null; // or new Error() if you want a stack trace -/// response._debugTask = null; -/// ``` -extern "C" void JSC__JSValue__transformToReactElementWithOptions(JSC::EncodedJSValue responseValue, JSC::EncodedJSValue componentValue, JSC::EncodedJSValue responseOptionsValue, 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); - JSC::JSValue responseOptions = JSC::JSValue::decode(responseOptionsValue); - - 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)); - - // If we have response options, we need to wrap the component - // For now, we'll store the response options directly on the response object - // The actual wrapping with AsyncLocalStorage update will happen when rendered - if (!responseOptions.isUndefinedOrNull() && responseOptions.isObject()) { - // Store the response options on the response object for later use - // This will be accessed when the component is rendered - auto responseOptionsIdentifier = JSC::Identifier::fromString(vm, "__responseOptions"_s); - responseObject->putDirect(vm, responseOptionsIdentifier, responseOptions, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0); - } - - // Transform the Response object itself into a React element - // The component parameter is what will be stored in the 'type' field - - // Set $$typeof property to mark this as a React element - auto typeofIdentifier = JSC::Identifier::fromString(vm, "$$typeof"_s); - responseObject->putDirect(vm, typeofIdentifier, react_element_symbol, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0); - - // Set type property - auto typeIdentifier = JSC::Identifier::fromString(vm, "type"_s); - - // TODO: this is fucking awful - // Check if this is a render redirect (Response.render() case) or regular redirect (Response.redirect() case) - // We detect this by checking if component is the special marker string "__bun_render_redirect__" or "__bun_redirect__" - bool isRenderRedirect = false; - bool isRedirect = false; - if (component.isString()) { - JSC::JSString* componentString = component.toString(globalObject); - if (componentString) { - String componentStr = componentString->value(globalObject); - if (componentStr == "__bun_render_redirect__"_s) { - isRenderRedirect = true; - } else if (componentStr == "__bun_redirect__"_s) { - isRedirect = true; - } - } - } - - // Handle Response.render() case - use BakeSSRResponse builtin to create wrapper - if (isRenderRedirect) { - // Get the render path and params from the response object - auto renderPathIdentifier = JSC::Identifier::fromString(vm, "__renderPath"_s); - auto renderParamsIdentifier = JSC::Identifier::fromString(vm, "__renderParams"_s); - - JSC::JSValue renderPath = responseObject->get(globalObject, renderPathIdentifier); - JSC::JSValue renderParams = responseObject->get(globalObject, renderParamsIdentifier); - - // Use the BakeSSRResponse builtin's wrapComponent function - JSC::JSFunction* wrapComponentFn = JSC::JSFunction::create(vm, globalObject, bakeSSRResponseWrapComponentCodeGenerator(vm), globalObject); - - // Call wrapComponent(path, params, true, responseObject) where true indicates this is a render redirect - JSC::MarkedArgumentBuffer args; - args.append(renderPath); - args.append(renderParams); - args.append(JSC::jsBoolean(true)); // This is a render redirect - args.append(response); // Pass the Response object - - // Call the wrapComponent function - auto callData = JSC::getCallData(wrapComponentFn); - JSC::JSValue wrappedComponent = JSC::call(globalObject, wrapComponentFn, callData, JSC::jsUndefined(), args); - - if (!scope.exception() && !wrappedComponent.isUndefinedOrNull()) { - responseObject->putDirect(vm, typeIdentifier, wrappedComponent, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0); - } else { - // If there was an error, clear it and set type to null - scope.clearException(); - responseObject->putDirect(vm, typeIdentifier, JSC::jsNull(), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0); - } - } - // Handle Response.redirect() case - use BakeSSRResponse builtin to create wrapper - else if (isRedirect) { - // Use the BakeSSRResponse builtin's wrapComponent function - JSC::JSFunction* wrapComponentFn = JSC::JSFunction::create(vm, globalObject, bakeSSRResponseWrapComponentCodeGenerator(vm), globalObject); - - // Call wrapComponent(undefined, undefined, "redirect", responseObject) - // For redirect, we pass "redirect" as the third parameter and the Response object as the fourth - JSC::MarkedArgumentBuffer args; - args.append(JSC::jsUndefined()); // No component for redirect - args.append(JSC::jsUndefined()); // No response options for redirect - - // Pass "redirect" string as the third parameter to indicate this is a redirect - // The responseOptions parameter from transformToReactElementWithOptions contains "redirect" - args.append(responseOptions); // This should be the "redirect" string - args.append(response); // Pass the Response object - - // Call the wrapComponent function - auto callData = JSC::getCallData(wrapComponentFn); - JSC::JSValue wrappedComponent = JSC::call(globalObject, wrapComponentFn, callData, JSC::jsUndefined(), args); - - if (!scope.exception() && !wrappedComponent.isUndefinedOrNull()) { - responseObject->putDirect(vm, typeIdentifier, wrappedComponent, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0); - } else { - // If there was an error, clear it and set type to null - scope.clearException(); - responseObject->putDirect(vm, typeIdentifier, JSC::jsNull(), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0); - } - } - // TODO: this is stupid - // Check if the component is a JSX element (has $$typeof property) - // If it is, we need to wrap it in a function for React to work properly - // because `` is already the result of calling Component() - else if (component.isObject()) { - JSC::JSObject* componentObject = component.getObject(); - auto typeofProperty = JSC::Identifier::fromString(vm, "$$typeof"_s); - JSC::JSValue typeofValue = componentObject->get(globalObject, typeofProperty); - - if (!scope.exception() && typeofValue.isSymbol()) { - // It's a JSX element - wrap it in a function that returns it - // If we have response options, use the BakeSSRResponse builtin to wrap the component - // so it can update AsyncLocalStorage when rendered - - if (!responseOptions.isUndefinedOrNull() && responseOptions.isObject()) { - // Use the BakeSSRResponse builtin's wrapComponent function - // This will create a wrapper that updates AsyncLocalStorage before returning the component - - // Create the wrapComponent function from the BakeSSRResponse builtin - // The pattern is: CodeGenerator - // So for BakeSSRResponse.ts with export function wrapComponent: - JSC::JSFunction* wrapComponentFn = JSC::JSFunction::create(vm, globalObject, bakeSSRResponseWrapComponentCodeGenerator(vm), globalObject); - - // Call wrapComponent(component, responseOptions, false, undefined) - JSC::MarkedArgumentBuffer args; - args.append(component); - args.append(responseOptions); - args.append(JSC::jsBoolean(false)); // Not a render redirect - args.append(JSC::jsUndefined()); // No response object for regular case - - // Call the wrapComponent function - auto callData = JSC::getCallData(wrapComponentFn); - JSC::JSValue wrappedComponent = JSC::call(globalObject, wrapComponentFn, callData, JSC::jsUndefined(), args); - - if (!scope.exception() && !wrappedComponent.isUndefinedOrNull()) { - responseObject->putDirect(vm, typeIdentifier, wrappedComponent, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0); - } else { - // Fall back to simple wrapper if there was an exception or undefined result - scope.clearException(); - JSC::Strong strongComponent(vm, component); - auto* wrapperFunction = JSC::JSNativeStdFunction::create( - vm, - globalObject, - 0, // arity - String(), // name - [strongComponent](JSC::JSGlobalObject* execGlobalObject, JSC::CallFrame* callFrame) -> JSC::EncodedJSValue { - return JSC::JSValue::encode(strongComponent.get()); - }); - - responseObject->putDirect(vm, typeIdentifier, wrapperFunction, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0); - } - } else { - // No response options - create a simple wrapper - JSC::Strong strongComponent(vm, component); - - auto* wrapperFunction = JSC::JSNativeStdFunction::create( - vm, - globalObject, - 0, // arity - String(), // name - [strongComponent](JSC::JSGlobalObject* execGlobalObject, JSC::CallFrame* callFrame) -> JSC::EncodedJSValue { - return JSC::JSValue::encode(strongComponent.get()); - }); - - responseObject->putDirect(vm, typeIdentifier, wrapperFunction, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0); - } - } else { - // It's not a JSX element, use it directly - responseObject->putDirect(vm, typeIdentifier, component, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | 0); - } - } else { - // It's not an object (could be a function or primitive), use it directly - responseObject->putDirect(vm, typeIdentifier, component, 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 - auto propsIdentifier = JSC::Identifier::fromString(vm, "props"_s); - 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); - // TODO: we should put an error here if we want a stack trace apparently - 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(JSValue0, globalObject, arg2, iter); diff --git a/src/bun.js/webcore/Response.zig b/src/bun.js/webcore/Response.zig index d801cd65a8..d1c26e2eca 100644 --- a/src/bun.js/webcore/Response.zig +++ b/src/bun.js/webcore/Response.zig @@ -66,6 +66,21 @@ pub fn toJS(this: *Response, globalObject: *JSGlobalObject) JSValue { return js.toJSUnchecked(globalObject, this); } +/// Corresponds to `JSBakeResponseKind` in +/// `src/bun.js/bindings/JSBakeResponse.h` +const SSRKind = enum(u8) { + regular = 0, + redirect = 1, + render = 2, +}; + +extern "C" fn Response__createForSSR(globalObject: *JSGlobalObject, this: *Response, kind: SSRKind) JSValue; + +pub fn toJSForSSR(this: *Response, globalObject: *JSGlobalObject, kind: SSRKind) JSValue { + this.calculateEstimatedByteSize(); + return Response__createForSSR(globalObject, this, kind); +} + pub fn getBodyValue( this: *Response, ) *Body.Value { @@ -515,25 +530,19 @@ pub fn constructRedirect( try headers_ref.put(.Location, url_string_slice.slice(), globalThis); const ptr = bun.new(Response, response); - const response_js = ptr.toJS(globalThis); - - // Check if dev_server_async_local_storage is set (indicating we're in Bun dev server) const vm = globalThis.bunVM(); + // Check if dev_server_async_local_storage is set (indicating we're in Bun dev server) if (vm.dev_server_async_local_storage.get()) |async_local_storage| { - // Mark this as a redirect Response that should be handled specially - // when used in a React component - const redirect_marker = ZigString.init("__bun_redirect__").toJS(globalThis); - - // Transform the Response to act as a React element with special redirect handling - // Pass "redirect" as the third parameter to indicate this is a redirect - const redirect_flag = ZigString.init("redirect").toJS(globalThis); try assertStreamingDisabled(globalThis, async_local_storage, "Response.redirect"); - try JSValue.transformToReactElementWithOptions(response_js, redirect_marker, redirect_flag, globalThis); + return ptr.toJSForSSR(globalThis, .redirect); } + const response_js = ptr.toJS(globalThis); + return response_js; } +/// This function is only available on JSBakeResponse pub fn constructRender( globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame, @@ -582,25 +591,9 @@ pub fn constructRender( }, }); - const response_js = response.toJS(globalThis); + const response_js = response.toJSForSSR(globalThis, .render); response_js.ensureStillAlive(); - // Store the render path and params on the response for later use - // When React tries to render this component, we'll check for these and throw RenderAbortError - response_js.put(globalThis, "__renderPath", path_arg); - const params_arg = if (arguments.len >= 2) arguments.ptr[1] else JSValue.jsNull(); - response_js.put(globalThis, "__renderParams", params_arg); - - // TODO: this is terrible - // Create a simple wrapper function that will be called by React - // This needs to be handled specially in transformToReactElementWithOptions - // We'll pass a special marker as the component to indicate this is a render redirect - const render_marker = ZigString.init("__bun_render_redirect__").toJS(globalThis); - - // Transform the Response to act as a React element - // The C++ code will need to check for this special marker - try JSValue.transformToReactElementWithOptions(response_js, render_marker, params_arg, globalThis); - return response_js; } @@ -624,11 +617,11 @@ pub fn constructError( } pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame, this_value: jsc.JSValue) bun.JSError!*Response { - return constructorImpl(globalThis, callframe, this_value, false); + return constructorImpl(globalThis, callframe, this_value, null); } -pub fn ResponseClass__constructForSSR(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame, thisValue: jsc.JSValue) callconv(jsc.conv) ?*anyopaque { - return @as(*Response, Response.constructor(globalObject, callFrame, thisValue) catch |err| switch (err) { +pub fn ResponseClass__constructForSSR(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame, thisValue: jsc.JSValue, bake_ssr_has_jsx: ?*c_int) callconv(jsc.conv) ?*anyopaque { + return @as(*Response, Response.constructorForSSR(globalObject, callFrame, thisValue, bake_ssr_has_jsx) catch |err| switch (err) { error.JSError => return null, error.OutOfMemory => { globalObject.throwOutOfMemory() catch {}; @@ -641,11 +634,11 @@ comptime { @export(&ResponseClass__constructForSSR, .{ .name = "ResponseClass__constructForSSR" }); } -pub fn constructorForSSR(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame, this_value: jsc.JSValue) bun.JSError!*Response { - return constructorImpl(globalThis, callframe, this_value); +pub fn constructorForSSR(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame, this_value: jsc.JSValue, bake_ssr_has_jsx: ?*c_int) bun.JSError!*Response { + return constructorImpl(globalThis, callframe, this_value, bake_ssr_has_jsx); } -pub fn constructorImpl(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame, this_value: jsc.JSValue, bake_ssr_response_enabled: bool) bun.JSError!*Response { +pub fn constructorImpl(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame, this_value: jsc.JSValue, bake_ssr_has_jsx: ?*c_int) bun.JSError!*Response { var arguments = callframe.argumentsAsArray(2); if (!arguments[0].isUndefinedOrNull() and arguments[0].isObject()) { @@ -683,7 +676,15 @@ pub fn constructorImpl(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFram // Special case for bake: allow `return new Response( ... , { ... }` // inside of a react component - if (bake_ssr_response_enabled and globalThis.allowJSXInResponseConstructor()) { + if (bake_ssr_has_jsx != null) { + bake_ssr_has_jsx.?.* = 0; + if (globalThis.allowJSXInResponseConstructor() and try arguments[0].isJSXElement(globalThis)) { + const vm = globalThis.bunVM(); + if (vm.dev_server_async_local_storage.get()) |async_local_storage| { + try assertStreamingDisabled(globalThis, async_local_storage, "new Response(, { ... })"); + } + bake_ssr_has_jsx.?.* = 1; + } _ = this_value; // const arg = arguments[0]; // // Check if it's a JSX element (object with $$typeof) @@ -695,7 +696,7 @@ pub fn constructorImpl(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFram // // Pass the response options (arguments[1]) to transformToReactElement // // so it can store them for later use when the component is rendered - // const responseOptions = if (arguments[1].isObject()) arguments[1] else .js_undefined; + // const responseOptions = if (arguments[1].isObject()) adirectrguments[1] else .js_undefined; // try JSValue.transformToReactElementWithOptions(this_value, arg, responseOptions, globalThis); // } } diff --git a/src/js/builtins/BakeSSRResponse.ts b/src/js/builtins/BakeSSRResponse.ts index ccc33df0d3..efc4a4a992 100644 --- a/src/js/builtins/BakeSSRResponse.ts +++ b/src/js/builtins/BakeSSRResponse.ts @@ -1,14 +1,9 @@ -// Used to make a Response fake being a component -// When this is called, it will render the component and then update async local -// storage with the options of the Response -// For Response.render(), we pass the response as strongComponent and need a 4th parameter -// For Response.redirect(), isRenderRedirect will be "redirect" instead of true -export function wrapComponent(strongComponent, responseOptions, isRenderRedirect, responseObject) { +export function wrapComponent(component, responseObject, responseOptions, kind) { const bakeGetAsyncLocalStorage = $newZigFunction("bun.js/webcore/Response.zig", "bakeGetAsyncLocalStorage", 0); return function () { // For Response.redirect(), we need to throw a RedirectAbortError - if (isRenderRedirect === "redirect") { + if (kind === 1 /* JSBakeResponseKind.Redirect */) { // responseObject is the Response from Response.redirect() const RedirectAbortError = globalThis.RedirectAbortError; if (RedirectAbortError) { @@ -19,12 +14,12 @@ export function wrapComponent(strongComponent, responseOptions, isRenderRedirect } // For Response.render(), we need to throw a RenderAbortError - if (isRenderRedirect === true || isRenderRedirect === "render") { + if (kind === 2 /* JSBakeResponseKind.Render */) { // strongComponent is the path string, responseOptions is params, responseObject is the Response // We need to get the RenderAbortError from the global const RenderAbortError = globalThis.RenderAbortError; if (RenderAbortError) { - throw new RenderAbortError(strongComponent, responseOptions, responseObject); + throw new RenderAbortError(component, responseOptions, responseObject); } // Fallback if RenderAbortError is not available throw new Error("RenderAbortError not available this is a bug"); @@ -38,6 +33,6 @@ export function wrapComponent(strongComponent, responseOptions, isRenderRedirect store.responseOptions = responseOptions; } } - return strongComponent; + return component; }; } diff --git a/src/js/builtins/BunBuiltinNames.h b/src/js/builtins/BunBuiltinNames.h index 0b71dee346..b80bf6897f 100644 --- a/src/js/builtins/BunBuiltinNames.h +++ b/src/js/builtins/BunBuiltinNames.h @@ -281,6 +281,16 @@ using namespace JSC; macro(writer) \ macro(writing) \ macro(written) \ + macro($$typeof) \ + macro(type) \ + macro(key) \ + macro(props) \ + macro(validated) \ + macro(_store) \ + macro(_owner) \ + macro(_debugInfo) \ + macro(_debugStack) \ + macro(_debugTask) \ BUN_ADDITIONAL_BUILTIN_NAMES(macro) // --- END of BUN_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME ---