From baff3c900e3c908f62b321eb84b726876aded0b3 Mon Sep 17 00:00:00 2001
From: dave caruso
Date: Tue, 3 Dec 2024 22:15:59 -0800
Subject: [PATCH] bake: fix the big regressions (#15544)
Co-authored-by: paperdave
---
.vscode/launch.json | 2 +-
src/bake/BakeGlobalObject.cpp | 61 +++-
src/bake/BakeGlobalObject.h | 4 +
src/bake/BakeSourceProvider.cpp | 6 +-
src/bake/BakeSourceProvider.h | 8 +-
src/bake/DevServer.zig | 6 +-
src/bake/bake.d.ts | 4 +-
src/bake/bake.zig | 18 +-
src/bake/production.zig | 308 ++++++++++++------
src/bun.js/ConsoleObject.zig | 11 +-
src/bun.js/api/server.zig | 5 +-
src/bun.js/bindings/bindings.cpp | 5 +-
src/bun.js/bindings/bindings.zig | 9 +-
src/bundler/bundle_v2.zig | 22 +-
src/cli.zig | 4 +
src/js/builtins/Bake.ts | 5 +-
src/js_ast.zig | 5 +-
src/js_parser.zig | 13 +-
src/options.zig | 58 ++--
test/bake/dev-server-harness.ts | 78 +++--
test/bake/dev/ecosystem.test.ts | 20 +-
test/bake/dev/esm.test.ts | 71 +++-
.../svelte-component-islands/bun.app.ts | 8 +
.../framework/client.ts | 14 +
.../framework/index.ts | 68 ++++
.../framework/server.ts | 71 ++++
.../pages/_Counter.svelte | 24 ++
.../pages/index.svelte | 18 +
test/bun.lockb | Bin 375426 -> 380850 bytes
test/js/bun/plugin/plugins.test.ts | 16 +-
.../third_party/svelte/bun-loader-svelte.ts | 3 +-
test/js/third_party/svelte/svelte.test.ts | 14 +-
test/package.json | 3 +-
33 files changed, 748 insertions(+), 214 deletions(-)
create mode 100644 test/bake/fixtures/svelte-component-islands/bun.app.ts
create mode 100644 test/bake/fixtures/svelte-component-islands/framework/client.ts
create mode 100644 test/bake/fixtures/svelte-component-islands/framework/index.ts
create mode 100644 test/bake/fixtures/svelte-component-islands/framework/server.ts
create mode 100644 test/bake/fixtures/svelte-component-islands/pages/_Counter.svelte
create mode 100644 test/bake/fixtures/svelte-component-islands/pages/index.svelte
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 00f72d4ddf..dc019a5445 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1257,4 +1257,4 @@
"description": "Usage: bun test [...]",
},
],
-}
+}
\ No newline at end of file
diff --git a/src/bake/BakeGlobalObject.cpp b/src/bake/BakeGlobalObject.cpp
index 44ee0e1854..4bd4a4647e 100644
--- a/src/bake/BakeGlobalObject.cpp
+++ b/src/bake/BakeGlobalObject.cpp
@@ -1,10 +1,12 @@
#include "BakeGlobalObject.h"
+#include "BakeSourceProvider.h"
#include "JSNextTickQueue.h"
#include "JavaScriptCore/GlobalObjectMethodTable.h"
#include "JavaScriptCore/JSInternalPromise.h"
#include "headers-handwritten.h"
#include "JavaScriptCore/JSModuleLoader.h"
#include "JavaScriptCore/Completion.h"
+#include "JavaScriptCore/JSSourceCode.h"
extern "C" BunString BakeProdResolve(JSC::JSGlobalObject*, BunString a, BunString b);
@@ -72,6 +74,58 @@ JSC::Identifier bakeModuleLoaderResolve(JSC::JSGlobalObject* jsGlobal,
return Zig::GlobalObject::moduleLoaderResolve(jsGlobal, loader, key, referrer, origin);
}
+static JSC::JSInternalPromise* rejectedInternalPromise(JSC::JSGlobalObject* globalObject, JSC::JSValue value)
+{
+ JSC::VM& vm = globalObject->vm();
+ JSC::JSInternalPromise* promise = JSC::JSInternalPromise::create(vm, globalObject->internalPromiseStructure());
+ promise->internalField(JSC::JSPromise::Field::ReactionsOrResult).set(vm, promise, value);
+ promise->internalField(JSC::JSPromise::Field::Flags).set(vm, promise, JSC::jsNumber(promise->internalField(JSC::JSPromise::Field::Flags).get().asUInt32AsAnyInt() | JSC::JSPromise::isFirstResolvingFunctionCalledFlag | static_cast(JSC::JSPromise::Status::Rejected)));
+ return promise;
+}
+
+static JSC::JSInternalPromise* resolvedInternalPromise(JSC::JSGlobalObject* globalObject, JSC::JSValue value)
+{
+ JSC::VM& vm = globalObject->vm();
+ JSC::JSInternalPromise* promise = JSC::JSInternalPromise::create(vm, globalObject->internalPromiseStructure());
+ promise->internalField(JSC::JSPromise::Field::ReactionsOrResult).set(vm, promise, value);
+ promise->internalField(JSC::JSPromise::Field::Flags).set(vm, promise, JSC::jsNumber(promise->internalField(JSC::JSPromise::Field::Flags).get().asUInt32AsAnyInt() | JSC::JSPromise::isFirstResolvingFunctionCalledFlag | static_cast(JSC::JSPromise::Status::Fulfilled)));
+ return promise;
+}
+
+extern "C" BunString BakeProdLoad(ProductionPerThread* perThreadData, BunString a);
+
+JSC::JSInternalPromise* bakeModuleLoaderFetch(JSC::JSGlobalObject* globalObject,
+ JSC::JSModuleLoader* loader, JSC::JSValue key,
+ JSC::JSValue parameters, JSC::JSValue script)
+{
+ Bake::GlobalObject* global = jsCast(globalObject);
+ JSC::VM& vm = globalObject->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ auto moduleKey = key.toWTFString(globalObject);
+ if (UNLIKELY(scope.exception()))
+ return rejectedInternalPromise(globalObject, scope.exception()->value());
+
+ if (moduleKey.startsWith("bake:/"_s)) {
+ if (LIKELY(global->m_perThreadData)) {
+ BunString source = BakeProdLoad(global->m_perThreadData, Bun::toString(moduleKey));
+ if (source.tag != BunStringTag::Dead) {
+ JSC::SourceOrigin origin = JSC::SourceOrigin(WTF::URL(moduleKey));
+ JSC::SourceCode sourceCode = JSC::SourceCode(Bake::SourceProvider::create(
+ source.toWTFString(),
+ origin,
+ WTFMove(moduleKey),
+ WTF::TextPosition(),
+ JSC::SourceProviderSourceType::Module));
+ return resolvedInternalPromise(globalObject, JSC::JSSourceCode::create(vm, WTFMove(sourceCode)));
+ }
+ return rejectedInternalPromise(globalObject, createTypeError(globalObject, makeString("Bundle does not have \""_s, moduleKey, "\". This is a bug in Bun's bundler."_s)));
+ }
+ return rejectedInternalPromise(globalObject, createTypeError(globalObject, "BakeGlobalObject does not have per-thread data configured"_s));
+ }
+
+ return Zig::GlobalObject::moduleLoaderFetch(globalObject, loader, key, parameters, script);
+}
+
#define INHERIT_HOOK_METHOD(name) \
Zig::GlobalObject::s_globalObjectMethodTable.name
@@ -83,7 +137,7 @@ const JSC::GlobalObjectMethodTable GlobalObject::s_globalObjectMethodTable = {
INHERIT_HOOK_METHOD(shouldInterruptScriptBeforeTimeout),
bakeModuleLoaderImportModule,
bakeModuleLoaderResolve,
- INHERIT_HOOK_METHOD(moduleLoaderFetch),
+ bakeModuleLoaderFetch,
INHERIT_HOOK_METHOD(moduleLoaderCreateImportMetaProperties),
INHERIT_HOOK_METHOD(moduleLoaderEvaluate),
INHERIT_HOOK_METHOD(promiseRejectionTracker),
@@ -155,4 +209,9 @@ extern "C" GlobalObject* BakeCreateProdGlobal(void* console)
return global;
}
+extern "C" void BakeGlobalObject__attachPerThreadData(GlobalObject* global, ProductionPerThread* perThreadData)
+{
+ global->m_perThreadData = perThreadData;
+}
+
}; // namespace Bake
diff --git a/src/bake/BakeGlobalObject.h b/src/bake/BakeGlobalObject.h
index af2b3490f9..0ac902422e 100644
--- a/src/bake/BakeGlobalObject.h
+++ b/src/bake/BakeGlobalObject.h
@@ -4,10 +4,14 @@
namespace Bake {
+struct ProductionPerThread;
+
class GlobalObject : public Zig::GlobalObject {
public:
using Base = Zig::GlobalObject;
+ ProductionPerThread* m_perThreadData;
+
template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
diff --git a/src/bake/BakeSourceProvider.cpp b/src/bake/BakeSourceProvider.cpp
index cf7ef839ab..2a51ffa0aa 100644
--- a/src/bake/BakeSourceProvider.cpp
+++ b/src/bake/BakeSourceProvider.cpp
@@ -21,7 +21,7 @@ extern "C" JSC::EncodedJSValue BakeLoadInitialServerCode(GlobalObject* global, B
String string = "bake://server-runtime.js"_s;
JSC::SourceOrigin origin = JSC::SourceOrigin(WTF::URL(string));
- JSC::SourceCode sourceCode = JSC::SourceCode(DevSourceProvider::create(
+ JSC::SourceCode sourceCode = JSC::SourceCode(SourceProvider::create(
source.toWTFString(),
origin,
WTFMove(string),
@@ -54,7 +54,7 @@ extern "C" JSC::EncodedJSValue BakeLoadServerHmrPatch(GlobalObject* global, BunS
String string = "bake://server.patch.js"_s;
JSC::SourceOrigin origin = JSC::SourceOrigin(WTF::URL(string));
- JSC::SourceCode sourceCode = JSC::SourceCode(DevSourceProvider::create(
+ JSC::SourceCode sourceCode = JSC::SourceCode(SourceProvider::create(
source.toWTFString(),
origin,
WTFMove(string),
@@ -117,7 +117,7 @@ extern "C" JSC::EncodedJSValue BakeRegisterProductionChunk(JSC::JSGlobalObject*
String string = virtualPathName.toWTFString();
JSC::JSString* key = JSC::jsString(vm, string);
JSC::SourceOrigin origin = JSC::SourceOrigin(WTF::URL(string));
- JSC::SourceCode sourceCode = JSC::SourceCode(DevSourceProvider::create(
+ JSC::SourceCode sourceCode = JSC::SourceCode(SourceProvider::create(
source.toWTFString(),
origin,
WTFMove(string),
diff --git a/src/bake/BakeSourceProvider.h b/src/bake/BakeSourceProvider.h
index 2d821fc401..3a3706af85 100644
--- a/src/bake/BakeSourceProvider.h
+++ b/src/bake/BakeSourceProvider.h
@@ -6,20 +6,20 @@
namespace Bake {
-class DevSourceProvider final : public JSC::StringSourceProvider {
+class SourceProvider final : public JSC::StringSourceProvider {
public:
- static Ref create(
+ static Ref create(
const String& source,
const JSC::SourceOrigin& sourceOrigin,
String&& sourceURL,
const TextPosition& startPosition,
JSC::SourceProviderSourceType sourceType
) {
- return adoptRef(*new DevSourceProvider(source, sourceOrigin, WTFMove(sourceURL), startPosition, sourceType));
+ return adoptRef(*new SourceProvider(source, sourceOrigin, WTFMove(sourceURL), startPosition, sourceType));
}
private:
- DevSourceProvider(
+ SourceProvider(
const String& source,
const JSC::SourceOrigin& sourceOrigin,
String&& sourceURL,
diff --git a/src/bake/DevServer.zig b/src/bake/DevServer.zig
index efabe71838..cae853a63d 100644
--- a/src/bake/DevServer.zig
+++ b/src/bake/DevServer.zig
@@ -20,7 +20,7 @@ pub const Options = struct {
// Debugging features
dump_sources: ?[]const u8 = if (Environment.isDebug) ".bake-debug" else null,
- dump_state_on_crash: ?bool = false,
+ dump_state_on_crash: ?bool = null,
verbose_watcher: bool = false,
};
@@ -904,6 +904,7 @@ fn startAsyncBundle(
.framework = dev.framework,
.client_bundler = &dev.client_bundler,
.ssr_bundler = &dev.ssr_bundler,
+ .plugins = dev.bundler_options.plugin,
} else @panic("TODO: support non-server components"),
allocator,
.{ .js = dev.vm.eventLoop() },
@@ -912,7 +913,6 @@ fn startAsyncBundle(
heap,
);
bv2.bun_watcher = dev.bun_watcher;
- bv2.plugins = dev.bundler_options.plugin;
bv2.asynchronous = true;
{
@@ -4352,7 +4352,7 @@ fn dumpStateDueToCrash(dev: *DevServer) !void {
const filepath = std.fmt.bufPrintZ(&filepath_buf, "incremental-graph-crash-dump.{d}.html", .{std.time.timestamp()}) catch "incremental-graph-crash-dump.html";
const file = std.fs.cwd().createFileZ(filepath, .{}) catch |err| {
bun.handleErrorReturnTrace(err, @errorReturnTrace());
- Output.warn("Could not open directory for dumping sources: {}", .{err});
+ Output.warn("Could not open file for dumping incremental graph: {}", .{err});
return;
};
defer file.close();
diff --git a/src/bake/bake.d.ts b/src/bake/bake.d.ts
index 6836ec63ad..3e5a6f9ad0 100644
--- a/src/bake/bake.d.ts
+++ b/src/bake/bake.d.ts
@@ -421,7 +421,7 @@ declare module "bun" {
type GetParamIterator =
| AsyncIterable, GetParamsFinalOpts>
| Iterable, GetParamsFinalOpts>
- | ({ pages: Array> } & GetParamsFinalOpts);
+ | ({ pages: Array> } & GetParamsFinalOpts);
type GetParamsFinalOpts = void | null | {
/**
@@ -516,7 +516,7 @@ declare module "bun" {
* Inject a module into the development server's runtime, to be loaded
* before all other user code.
*/
- addPreload(module: string, side: 'client' | 'server'): void;
+ addPreload(...args: any): void;
}
declare interface OnLoadArgs {
diff --git a/src/bake/bake.zig b/src/bake/bake.zig
index 7159b6d3e0..134acce3d9 100644
--- a/src/bake/bake.zig
+++ b/src/bake/bake.zig
@@ -466,7 +466,7 @@ pub const Framework = struct {
} else if (exts_js.isArray()) {
var it_2 = exts_js.arrayIterator(global);
var i_2: usize = 0;
- const extensions = try arena.alloc([]const u8, array.getLength(global));
+ const extensions = try arena.alloc([]const u8, exts_js.getLength(global));
while (it_2.next()) |array_item| : (i_2 += 1) {
const slice = refs.track(try array_item.toSlice2(global, arena));
if (bun.strings.eqlComptime(slice, "*"))
@@ -600,9 +600,13 @@ pub const Framework = struct {
out.options.framework = framework;
- // In development mode, source maps must always be `linked`
- // In production, TODO: follow user configuration
- out.options.source_map = .linked;
+ out.options.source_map = switch (mode) {
+ // Source maps must always be linked, as DevServer special cases the
+ // linking and part of the generation of these.
+ .development => .external,
+ // TODO: follow user configuration
+ else => .none,
+ };
out.configureLinker();
try out.configureDefines();
@@ -615,8 +619,10 @@ pub const Framework = struct {
});
if (mode != .development) {
- out.options.entry_naming = "[name]-[hash].[ext]";
- out.options.chunk_naming = "chunk-[name]-[hash].[ext]";
+ // Hide information about the source repository, at the cost of debugging quality.
+ out.options.entry_naming = "_bun/[hash].[ext]";
+ out.options.chunk_naming = "_bun/[hash].[ext]";
+ out.options.asset_naming = "_bun/[hash].[ext]";
}
out.resolver.opts = out.options;
diff --git a/src/bake/production.zig b/src/bake/production.zig
index a8fadb8d59..edeedd1de1 100644
--- a/src/bake/production.zig
+++ b/src/bake/production.zig
@@ -85,7 +85,8 @@ pub fn buildCommand(ctx: bun.CLI.Command.Context) !void {
buildWithVm(ctx, cwd, vm) catch |err| switch (err) {
error.JSError => |e| {
bun.handleErrorReturnTrace(err, @errorReturnTrace());
- vm.printErrorLikeObjectToConsole(vm.global.takeException(e));
+ const err_value = vm.global.takeException(e);
+ vm.printErrorLikeObjectToConsole(err_value.toError() orelse err_value);
if (vm.exit_handler.exit_code == 0) {
vm.exit_handler.exit_code = 1;
}
@@ -103,7 +104,10 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
Output.prettyErrorln("Loading configuration", .{});
Output.flush();
- const unresolved_config_entry_point = if (ctx.args.entry_points.len > 0) ctx.args.entry_points[0] else "./bun.app";
+ var unresolved_config_entry_point = if (ctx.args.entry_points.len > 0) ctx.args.entry_points[0] else "./bun.app";
+ if (bun.resolver.isPackagePath(unresolved_config_entry_point)) {
+ unresolved_config_entry_point = try std.fmt.allocPrint(ctx.allocator, "./{s}", .{unresolved_config_entry_point});
+ }
const config_entry_point = b.resolver.resolve(cwd, unresolved_config_entry_point, .entry_point) catch |err| {
if (err == error.ModuleNotFound) {
@@ -132,6 +136,7 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
return error.JSError;
};
+ config_promise.setHandled(vm.jsc);
vm.waitForPromise(.{ .internal = config_promise });
var options = switch (config_promise.unwrap(vm.jsc, .mark_handled)) {
.pending => unreachable,
@@ -150,12 +155,7 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
break :config try bake.UserOptions.fromJS(app, vm.global);
},
.rejected => |err| {
- // dont run on rejected since we fail the build here
- vm.printErrorLikeObjectToConsole(err);
- if (vm.exit_handler.exit_code == 0) {
- vm.exit_handler.exit_code = 1;
- }
- vm.globalExit();
+ return global.throwValue(err.toError() orelse err);
},
};
@@ -180,6 +180,17 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
try framework.initBundler(allocator, vm.log, .production_static, .ssr, &ssr_bundler);
}
+ if (ctx.bundler_options.bake_debug_disable_minify) {
+ for ([_]*bun.bundler.Bundler{ &client_bundler, &server_bundler, &ssr_bundler }) |bundler| {
+ bundler.options.minify_syntax = false;
+ bundler.options.minify_identifiers = false;
+ bundler.options.minify_whitespace = false;
+ bundler.resolver.opts.entry_naming = "_bun/[dir]/[name].[hash].[ext]";
+ bundler.resolver.opts.chunk_naming = "_bun/[dir]/[name].[hash].chunk.[ext]";
+ bundler.resolver.opts.asset_naming = "_bun/[dir]/[name].[hash].asset.[ext]";
+ }
+ }
+
// these share pointers right now, so setting NODE_ENV == production on one should affect all
bun.assert(server_bundler.env == client_bundler.env);
@@ -244,6 +255,7 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
.framework = framework.*,
.client_bundler = &client_bundler,
.ssr_bundler = if (separate_ssr_graph) &ssr_bundler else &server_bundler,
+ .plugins = options.bundler_options.plugin,
},
allocator,
.{ .js = vm.event_loop },
@@ -259,15 +271,14 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
var css_chunks_count: usize = 0;
var css_chunks_first: usize = 0;
- const all_server_files = JSValue.createEmptyArray(global, entry_points.files.count());
-
// Index all bundled outputs.
// Client files go to disk.
// Server files get loaded in memory.
// Populate indexes in `entry_points` to be looked up during prerendering
- const js_module_keys = try vm.allocator.alloc(?*JSC.JSString, entry_points.files.count());
- @memset(js_module_keys, null);
- const paths = entry_points.slice(bundled_outputs, js_module_keys);
+ const module_keys = try vm.allocator.alloc(bun.String, entry_points.files.count());
+ const output_indexes = entry_points.files.values();
+ var output_module_map: bun.StringArrayHashMapUnmanaged(OutputFile.Index) = .{};
+ @memset(module_keys, bun.String.dead);
for (bundled_outputs, 0..) |file, i| {
log("{s} - {s} : {s} - {?d}\n", .{
if (file.side) |s| @tagName(s) else "null",
@@ -281,20 +292,27 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
}
if (file.entry_point_index) |entry_point| {
- if (entry_point < paths.output_indexes.len) {
- paths.output_indexes[entry_point] = OutputFile.Index.init(@intCast(i));
+ if (entry_point < output_indexes.len) {
+ output_indexes[entry_point] = OutputFile.Index.init(@intCast(i));
}
}
- switch (file.side orelse .client) {
+ switch (file.side orelse continue) {
.client => {
// Client-side resources will be written to disk for usage in on the client side
- _ = try file.writeToDisk(root_dir, root_dir_path);
+ _ = file.writeToDisk(root_dir, ".") catch |err| {
+ bun.handleErrorReturnTrace(err, @errorReturnTrace());
+ Output.err(err, "Failed to write {} to output directory", .{bun.fmt.quote(file.dest_path)});
+ };
},
.server => {
// For Debugging
- if (ctx.bundler_options.bake_debug_dump_server)
- _ = try file.writeToDisk(root_dir, root_dir_path);
+ if (ctx.bundler_options.bake_debug_dump_server) {
+ _ = file.writeToDisk(root_dir, ".") catch |err| {
+ bun.handleErrorReturnTrace(err, @errorReturnTrace());
+ Output.err(err, "Failed to write {} to output directory", .{bun.fmt.quote(file.dest_path)});
+ };
+ }
switch (file.output_kind) {
.@"entry-point", .chunk => {
@@ -304,24 +322,19 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
else
file.dest_path;
- // TODO: later we can lazily register modules
- const module_key = BakeRegisterProductionChunk(
- global,
- try bun.String.createFormat("bake:/{s}", .{without_prefix}),
- file.value.toBunString(),
- ) catch |err| {
- Output.errGeneric("could not load bundled chunk {} for server-side rendering", .{
- bun.fmt.quote(without_prefix),
- });
- return err;
- };
- bun.assert(module_key.isString());
if (file.entry_point_index) |entry_point_index| {
- if (entry_point_index < paths.js_module_keys.len) {
- paths.js_module_keys[entry_point_index] = module_key.uncheckedPtrCast(JSC.JSString);
- all_server_files.putIndex(global, entry_point_index, module_key);
+ if (entry_point_index < module_keys.len) {
+ var str = try bun.String.createFormat("bake:/{s}", .{without_prefix});
+ str.toThreadSafe();
+ module_keys[entry_point_index] = str;
}
}
+
+ try output_module_map.put(
+ allocator,
+ try std.fmt.allocPrint(allocator, "bake:/{s}", .{without_prefix}),
+ OutputFile.Index.init(@intCast(i)),
+ );
},
.asset => {},
.bytecode => {},
@@ -331,6 +344,17 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
}
}
+ const per_thread_options: PerThread.Options = .{
+ .input_files = entry_points.files.keys(),
+ .bundled_outputs = bundled_outputs,
+ .output_indexes = output_indexes,
+ .module_keys = module_keys,
+ .module_map = output_module_map,
+ };
+
+ var pt = try PerThread.init(vm, per_thread_options);
+ pt.attach();
+
// Static site generator
const server_render_funcs = JSValue.createEmptyArray(global, router.types.len);
const server_param_funcs = JSValue.createEmptyArray(global, router.types.len);
@@ -340,16 +364,14 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
if (router_type.client_file.unwrap()) |client_file| {
const str = (try bun.String.createFormat("{s}{s}", .{
public_path,
- paths.outputFile(client_file).dest_path,
+ pt.outputFile(client_file).dest_path,
})).toJS(global);
client_entry_urls.putIndex(global, @intCast(i), str);
} else {
client_entry_urls.putIndex(global, @intCast(i), .null);
}
- const server_entry_module_key = paths.jsModuleKey(router_type.server_file);
- bun.assert(server_entry_module_key != .undefined);
- const server_entry_point = try loadModule(vm, global, server_entry_module_key);
+ const server_entry_point = try pt.loadBundledModule(router_type.server_file);
const server_render_func = brk: {
const raw = BakeGetOnModuleNamespace(global, server_entry_point, "prerender") orelse
break :brk null;
@@ -365,20 +387,23 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
bun.Global.crash();
};
- const server_param_func = brk: {
- const raw = BakeGetOnModuleNamespace(global, server_entry_point, "getParams") orelse
- break :brk null;
- if (!raw.isCallable(vm.jsc)) {
- break :brk null;
+ const server_param_func = if (router.dynamic_routes.count() > 0)
+ brk: {
+ const raw = BakeGetOnModuleNamespace(global, server_entry_point, "getParams") orelse
+ break :brk null;
+ if (!raw.isCallable(vm.jsc)) {
+ break :brk null;
+ }
+ break :brk raw;
+ } orelse {
+ Output.errGeneric("Framework does not support static site generation", .{});
+ Output.note("The file {s} is missing the \"getParams\" export, which defines how to generate static files.", .{
+ bun.fmt.quote(bun.path.relative(cwd, entry_points.files.keys()[router_type.server_file.get()].absPath())),
+ });
+ bun.Global.crash();
}
- break :brk raw;
- } orelse {
- Output.errGeneric("Framework does not support static site generation", .{});
- Output.note("The file {s} is missing the \"getParams\" export, which defines how to generate static files.", .{
- bun.fmt.quote(bun.path.relative(cwd, entry_points.files.keys()[router_type.server_file.get()].absPath())),
- });
- bun.Global.crash();
- };
+ else
+ JSValue.null;
server_render_funcs.putIndex(global, @intCast(i), server_render_func);
server_param_funcs.putIndex(global, @intCast(i), server_param_func);
}
@@ -411,7 +436,7 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
const route = router.routePtr(route_index);
const main_file_route_index = route.file_page.unwrap().?;
- const main_file = paths.outputFile(main_file_route_index);
+ const main_file = pt.outputFile(main_file_route_index);
// Count how many JS+CSS files associated with this route and prepare `pattern`
pattern.prependPart(route.part);
@@ -427,7 +452,7 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
var file_count: u32 = 1;
var css_file_count: u32 = @intCast(main_file.referenced_css_files.len);
if (route.file_layout.unwrap()) |file| {
- css_file_count += @intCast(paths.outputFile(file).referenced_css_files.len);
+ css_file_count += @intCast(pt.outputFile(file).referenced_css_files.len);
file_count += 1;
}
var next: ?FrameworkRouter.Route.Index = route.parent.unwrap();
@@ -444,7 +469,7 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
else => {},
}
if (parent.file_layout.unwrap()) |file| {
- css_file_count += @intCast(paths.outputFile(file).referenced_css_files.len);
+ css_file_count += @intCast(pt.outputFile(file).referenced_css_files.len);
file_count += 1;
}
next = parent.parent.unwrap();
@@ -457,14 +482,14 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
next = route.parent.unwrap();
file_count = 1;
css_file_count = 0;
- file_list.putIndex(global, 0, JSValue.jsNumberFromInt32(@intCast(main_file_route_index.get())));
+ file_list.putIndex(global, 0, pt.preloadBundledModule(main_file_route_index));
for (main_file.referenced_css_files) |ref| {
styles.putIndex(global, css_file_count, css_chunk_js_strings[ref.get() - css_chunks_first]);
css_file_count += 1;
}
if (route.file_layout.unwrap()) |file| {
- file_list.putIndex(global, file_count, JSValue.jsNumberFromInt32(@intCast(file.get())));
- for (paths.outputFile(file).referenced_css_files) |ref| {
+ file_list.putIndex(global, file_count, pt.preloadBundledModule(file));
+ for (pt.outputFile(file).referenced_css_files) |ref| {
styles.putIndex(global, css_file_count, css_chunk_js_strings[ref.get() - css_chunks_first]);
css_file_count += 1;
}
@@ -474,8 +499,8 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
while (next) |parent_index| {
const parent = router.routePtr(parent_index);
if (parent.file_layout.unwrap()) |file| {
- file_list.putIndex(global, file_count, JSValue.jsNumberFromInt32(@intCast(file.get())));
- for (paths.outputFile(file).referenced_css_files) |ref| {
+ file_list.putIndex(global, file_count, pt.preloadBundledModule(file));
+ for (pt.outputFile(file).referenced_css_files) |ref| {
styles.putIndex(global, css_file_count, css_chunk_js_strings[ref.get() - css_chunks_first]);
css_file_count += 1;
}
@@ -489,7 +514,7 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
defer pattern_string.deref();
route_patterns.putIndex(global, @intCast(nav_index), pattern_string.toJS(global));
- var src_path = bun.String.createUTF8(bun.path.relative(cwd, paths.inputFile(main_file_route_index).absPath()));
+ var src_path = bun.String.createUTF8(bun.path.relative(cwd, pt.inputFile(main_file_route_index).absPath()));
route_source_files.putIndex(global, @intCast(nav_index), src_path.transferToJS(global));
route_nested_files.putIndex(global, @intCast(nav_index), file_list);
@@ -512,7 +537,7 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
const render_promise = BakeRenderRoutesForProdStatic(
global,
bun.String.init(root_dir_path),
- all_server_files,
+ pt.all_server_files,
server_render_funcs,
server_param_funcs,
client_entry_urls,
@@ -524,6 +549,7 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
route_param_info,
route_style_references,
);
+ render_promise.setHandled(vm.jsc);
vm.waitForPromise(.{ .normal = render_promise });
switch (render_promise.unwrap(vm.jsc, .mark_handled)) {
.pending => unreachable,
@@ -541,6 +567,7 @@ pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMa
/// quits the process on exception
fn loadModule(vm: *VirtualMachine, global: *JSC.JSGlobalObject, key: JSValue) !JSValue {
const promise = BakeLoadModuleByKey(global, key).asAnyPromise().?.internal;
+ promise.setHandled(vm.jsc);
vm.waitForPromise(.{ .internal = promise });
switch (promise.unwrap(vm.jsc, .mark_handled)) {
.pending => unreachable,
@@ -674,45 +701,6 @@ pub const EntryPointMap = struct {
};
};
- /// Convenience structure
- pub const Slice = struct {
- files: []InputFile,
- output_indexes: []OutputFile.Index,
- /// From bundle_v2
- bundled_outputs: []OutputFile,
- /// Not wrapped with JSC.Strong because JSModuleLoader
- /// always contains a reference to this string.
- js_module_keys: []?*JSC.JSString,
-
- pub fn outputIndex(s: Slice, id: OpaqueFileId) OutputFile.Index {
- return s.output_indexes[id.get()];
- }
-
- pub fn inputFile(s: Slice, id: OpaqueFileId) InputFile {
- return s.files[id.get()];
- }
-
- pub fn outputFile(s: Slice, id: OpaqueFileId) *OutputFile {
- return &s.bundled_outputs[s.outputIndex(id).get()];
- }
-
- pub fn jsModuleKey(s: Slice, id: OpaqueFileId) JSValue {
- return (s.js_module_keys[id.get()] orelse
- Output.panic("Internal Error: {} did not get loaded", .{
- bun.fmt.quote(s.outputFile(id).dest_path),
- })).toJS();
- }
- };
-
- pub fn slice(map: EntryPointMap, bundled_outputs: []OutputFile, js_module_keys: []?*JSC.JSString) Slice {
- return .{
- .files = map.files.keys(),
- .output_indexes = map.files.values(),
- .js_module_keys = js_module_keys,
- .bundled_outputs = bundled_outputs,
- };
- }
-
pub fn getOrPutEntryPoint(map: *EntryPointMap, abs_path: []const u8, side: bake.Side) !OpaqueFileId {
const k = InputFile.init(abs_path, side);
const gop = try map.files.getOrPut(map.allocator, k);
@@ -742,6 +730,126 @@ pub const EntryPointMap = struct {
}
};
+/// Data used on each rendering thread. Contains all information in the bundle needed to render.
+/// This is referred to as `pt` in variable/field naming, and Bake::ProductionPerThread in C++
+pub const PerThread = struct {
+ // Shared Data
+ input_files: []const EntryPointMap.InputFile,
+ bundled_outputs: []const OutputFile,
+ /// Indexed by entry point index (OpaqueFileId)
+ output_indexes: []const OutputFile.Index,
+ /// Indexed by entry point index (OpaqueFileId)
+ module_keys: []const bun.String,
+ /// Unordered
+ module_map: bun.StringArrayHashMapUnmanaged(OutputFile.Index),
+
+ // Thread-local
+ vm: *JSC.VirtualMachine,
+ /// Indexed by entry point index (OpaqueFileId)
+ loaded_files: bun.bit_set.AutoBitSet,
+ /// JSArray of JSString, indexed by entry point index (OpaqueFileId)
+ all_server_files: JSC.JSValue,
+
+ /// Sent to other threads for rendering
+ pub const Options = struct {
+ input_files: []const EntryPointMap.InputFile,
+ bundled_outputs: []const OutputFile,
+ /// Indexed by entry point index (OpaqueFileId)
+ output_indexes: []const OutputFile.Index,
+ /// Indexed by entry point index (OpaqueFileId)
+ module_keys: []const bun.String,
+ /// Unordered
+ module_map: bun.StringArrayHashMapUnmanaged(OutputFile.Index),
+ };
+
+ extern fn BakeGlobalObject__attachPerThreadData(global: *JSC.JSGlobalObject, pt: ?*PerThread) void;
+
+ /// After initializing, call `attach`
+ pub fn init(vm: *VirtualMachine, opts: Options) !PerThread {
+ const loaded_files = try bun.bit_set.AutoBitSet.initEmpty(vm.allocator, opts.output_indexes.len);
+ errdefer loaded_files.deinit(vm.allocator);
+
+ const all_server_files = JSValue.createEmptyArray(vm.global, opts.output_indexes.len);
+ all_server_files.protect();
+
+ return .{
+ .input_files = opts.input_files,
+ .bundled_outputs = opts.bundled_outputs,
+ .output_indexes = opts.output_indexes,
+ .module_keys = opts.module_keys,
+ .module_map = opts.module_map,
+ .vm = vm,
+ .loaded_files = loaded_files,
+ .all_server_files = all_server_files,
+ };
+ }
+
+ pub fn attach(pt: *PerThread) void {
+ BakeGlobalObject__attachPerThreadData(pt.vm.global, pt);
+ }
+
+ pub fn deinit(pt: *PerThread) void {
+ BakeGlobalObject__attachPerThreadData(pt.vm.global, null);
+ pt.all_server_files.unprotect();
+ }
+
+ pub fn outputIndex(s: PerThread, id: OpaqueFileId) OutputFile.Index {
+ return s.output_indexes[id.get()];
+ }
+
+ pub fn inputFile(s: PerThread, id: OpaqueFileId) EntryPointMap.InputFile {
+ return s.input_files[id.get()];
+ }
+
+ pub fn outputFile(s: PerThread, id: OpaqueFileId) *const OutputFile {
+ return &s.bundled_outputs[s.outputIndex(id).get()];
+ }
+
+ // Must be run at the top of the event loop
+ pub fn loadBundledModule(pt: *PerThread, id: OpaqueFileId) bun.JSError!JSValue {
+ return try loadModule(
+ pt.vm,
+ pt.vm.global,
+ pt.module_keys[id.get()].toJS(pt.vm.global),
+ );
+ }
+
+ /// The JSString entries in `all_server_files` is generated lazily. When
+ /// multiple rendering threads are used, unreferenced files will contain
+ /// holes in the array used. Returns a JSValue of the "FileIndex" type
+ //
+ // What could be done here is generating a new index type, which is
+ // specifically for referenced files. This would remove the holes, but make
+ // it harder to pre-allocate. It's probably worth it.
+ pub fn preloadBundledModule(pt: *PerThread, id: OpaqueFileId) JSValue {
+ if (!pt.loaded_files.isSet(id.get())) {
+ pt.loaded_files.set(id.get());
+ pt.all_server_files.putIndex(
+ pt.vm.global,
+ @intCast(id.get()),
+ pt.module_keys[id.get()].toJS(pt.vm.global),
+ );
+ }
+
+ return JSValue.jsNumberFromInt32(@intCast(id.get()));
+ }
+};
+
+/// Given a key, returns the source code to load.
+export fn BakeProdLoad(pt: *PerThread, key: bun.String) bun.String {
+ var sfa = std.heap.stackFallback(4096, bun.default_allocator);
+ const allocator = sfa.get();
+ const utf8 = key.toUTF8(allocator);
+ defer utf8.deinit();
+ if (pt.module_map.get(utf8.slice())) |value| {
+ return pt.bundled_outputs[value.get()].value.toBunString();
+ }
+ for (pt.module_map.keys()) |keys| {
+ std.debug.print("key that does exist: {s}\n", .{keys});
+ }
+ return bun.String.dead;
+}
+
const TypeAndFlags = packed struct(i32) {
type: u8,
unused: u24 = 0,
diff --git a/src/bun.js/ConsoleObject.zig b/src/bun.js/ConsoleObject.zig
index 295b64f3aa..5f08103018 100644
--- a/src/bun.js/ConsoleObject.zig
+++ b/src/bun.js/ConsoleObject.zig
@@ -2492,8 +2492,15 @@ pub const Formatter = struct {
return this.printAs(.Object, Writer, writer_, value, .Event, enable_ansi_colors);
},
.NativeCode => {
- this.addForNewLine("[native code]".len);
- writer.writeAll("[native code]");
+ if (value.getClassInfoName()) |class_name| {
+ this.addForNewLine("[native code: ]".len + class_name.len);
+ writer.writeAll("[native code: ");
+ writer.writeAll(class_name);
+ writer.writeAll("]");
+ } else {
+ this.addForNewLine("[native code]".len);
+ writer.writeAll("[native code]");
+ }
},
.Promise => {
if (!this.single_line and this.goodTimeForANewLine()) {
diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig
index 1cd310d23b..5a37980083 100644
--- a/src/bun.js/api/server.zig
+++ b/src/bun.js/api/server.zig
@@ -1949,8 +1949,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
}
fn renderMissingInvalidResponse(ctx: *RequestContext, value: JSC.JSValue) void {
- var class_name = value.getClassInfoName() orelse bun.String.empty;
- defer class_name.deref();
+ const class_name = value.getClassInfoName() orelse "";
if (ctx.server) |server| {
const globalThis: *JSC.JSGlobalObject = server.globalThis;
@@ -1958,7 +1957,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
Output.enableBuffering();
var writer = Output.errorWriter();
- if (class_name.eqlComptime("Response")) {
+ if (bun.strings.eqlComptime(class_name, "Response")) {
Output.errGeneric("Expected a native Response object, but received a polyfilled Response object. Bun.serve() only supports native Response objects.", .{});
} else if (value != .zero and !globalThis.hasException()) {
var formatter = JSC.ConsoleObject.Formatter{
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index 79993209e9..d2b353aa34 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -4750,10 +4750,11 @@ void JSC__JSValue__getClassName(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1
*arg2 = Zig::toZigString(view);
}
-bool JSC__JSValue__getClassInfoName(JSValue value, BunString* out)
+bool JSC__JSValue__getClassInfoName(JSValue value, const uint8_t** outPtr, size_t* outLen)
{
if (auto info = value.classInfoOrNull()) {
- *out = Bun::toString(info->className);
+ *outPtr = info->className.span8().data();
+ *outLen = info->className.span8().size();
return true;
}
return false;
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index 6caba4d5d5..fcd4b72254 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -6175,13 +6175,14 @@ pub const JSValue = enum(i64) {
return Bun__ProxyObject__getInternalField(this, field);
}
- extern fn JSC__JSValue__getClassInfoName(value: JSValue, out: *bun.String) bool;
+ extern fn JSC__JSValue__getClassInfoName(value: JSValue, out: *[*:0]const u8, len: *usize) bool;
/// For native C++ classes extending JSCell, this retrieves s_info's name
- pub fn getClassInfoName(this: JSValue) ?bun.String {
+ /// This is a readonly ASCII string.
+ pub fn getClassInfoName(this: JSValue) ?[:0]const u8 {
if (!this.isCell()) return null;
- var out: bun.String = bun.String.empty;
- if (!JSC__JSValue__getClassInfoName(this, &out)) return null;
+ var out: [:0]const u8 = "";
+ if (!JSC__JSValue__getClassInfoName(this, &out.ptr, &out.len)) return null;
return out;
}
};
diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig
index 4d7d94e190..555a5157d8 100644
--- a/src/bundler/bundle_v2.zig
+++ b/src/bundler/bundle_v2.zig
@@ -325,7 +325,11 @@ const Watcher = bun.JSC.NewHotReloader(BundleV2, EventLoop, true);
fn genericPathWithPrettyInitialized(path: Fs.Path, target: options.Target, top_level_dir: string, allocator: std.mem.Allocator) !Fs.Path {
// TODO: outbase
var buf: bun.PathBuffer = undefined;
- if (path.isFile()) {
+
+ // "file" namespace should use the relative file path for its display name.
+ // the "node" namespace is also put through this code path so that the
+ // "node:" prefix is not emitted.
+ if (path.isFile() or bun.strings.eqlComptime(path.namespace, "node")) {
const rel = bun.path.relativePlatform(top_level_dir, path.text, .loose, false);
var path_clone = path;
// stack-allocated temporary is not leaked because dupeAlloc on the path will
@@ -400,6 +404,7 @@ pub const BundleV2 = struct {
framework: bake.Framework,
client_bundler: *Bundler,
ssr_bundler: *Bundler,
+ plugins: ?*JSC.API.JSBundler.Plugin,
};
const debug = Output.scoped(.Bundle, false);
@@ -907,6 +912,7 @@ pub const BundleV2 = struct {
this.ssr_bundler = bo.ssr_bundler;
this.framework = bo.framework;
this.linker.framework = &this.framework.?;
+ this.plugins = bo.plugins;
bun.assert(bundler.options.server_components);
bun.assert(this.client_bundler.options.server_components);
if (bo.framework.server_components.?.separate_ssr_graph)
@@ -2939,7 +2945,9 @@ pub const BundleV2 = struct {
graph.ast.set(result.source.index.get(), result.ast);
// For files with use directives, index and prepare the other side.
- if (result.use_directive != .none and
+ if (result.use_directive != .none and if (this.framework.?.server_components.?.separate_ssr_graph)
+ ((result.use_directive == .client) == (result.ast.target == .browser))
+ else
((result.use_directive == .client) != (result.ast.target == .browser)))
{
if (result.use_directive == .server)
@@ -11078,7 +11086,7 @@ pub const LinkerContext = struct {
const items = try allocator.alloc(Expr, st.items.len);
for (st.items, items) |item, *str| {
- str.* = Expr.init(E.String, .{ .data = item.original_name }, item.name.loc);
+ str.* = Expr.init(E.String, .{ .data = item.alias }, item.name.loc);
}
break :call Expr.init(E.Call, .{
@@ -12352,7 +12360,9 @@ pub const LinkerContext = struct {
.is_executable = chunk.is_executable,
.source_map_index = source_map_index,
.bytecode_index = bytecode_index,
- .side = switch (c.graph.ast.items(.target)[chunk.entry_point.source_index]) {
+ .side = if (chunk.content == .css)
+ .client
+ else switch (c.graph.ast.items(.target)[chunk.entry_point.source_index]) {
.browser => .client,
else => .server,
},
@@ -12761,7 +12771,9 @@ pub const LinkerContext = struct {
.data = .{
.saved = 0,
},
- .side = switch (c.graph.ast.items(.target)[chunk.entry_point.source_index]) {
+ .side = if (chunk.content == .css)
+ .client
+ else switch (c.graph.ast.items(.target)[chunk.entry_point.source_index]) {
.browser => .client,
else => .server,
},
diff --git a/src/cli.zig b/src/cli.zig
index 02a8abf23d..8510a5137b 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -290,6 +290,7 @@ pub const Arguments = struct {
clap.parseParam("--server-components (EXPERIMENTAL) Enable server components") catch unreachable,
} ++ if (FeatureFlags.bake_debugging_features) [_]ParamType{
clap.parseParam("--debug-dump-server-files When --app is set, dump all server files to disk even when building statically") catch unreachable,
+ clap.parseParam("--debug-no-minify When --app is set, do not minify anything") catch unreachable,
} else .{};
pub const build_params = build_only_params ++ transpiler_params_ ++ base_params_;
@@ -797,6 +798,8 @@ pub const Arguments = struct {
ctx.bundler_options.bake = true;
ctx.bundler_options.bake_debug_dump_server = bun.FeatureFlags.bake_debugging_features and
args.flag("--debug-dump-server-files");
+ ctx.bundler_options.bake_debug_disable_minify = bun.FeatureFlags.bake_debugging_features and
+ args.flag("--debug-no-minify");
}
// TODO: support --format=esm
@@ -1457,6 +1460,7 @@ pub const Command = struct {
bake: bool = false,
bake_debug_dump_server: bool = false,
+ bake_debug_disable_minify: bool = false,
};
pub fn create(allocator: std.mem.Allocator, log: *logger.Log, comptime command: Command.Tag) anyerror!Context {
diff --git a/src/js/builtins/Bake.ts b/src/js/builtins/Bake.ts
index ca85b8eadc..9faf064bf8 100644
--- a/src/js/builtins/Bake.ts
+++ b/src/js/builtins/Bake.ts
@@ -52,8 +52,9 @@ export function renderRoutesForProdStatic(
// Call the framework's rendering function
const callback = renderStatic[type];
$assert(callback != null && $isCallable(callback));
+ let client = clientEntryUrl[type];
const results = await callback({
- modules: [clientEntryUrl[type]],
+ modules: client ? [client] : [],
modulepreload: [],
styles: styles[i],
layouts,
@@ -116,7 +117,7 @@ export function renderRoutesForProdStatic(
[pageModule, ...layouts] = anyPromise ? await Promise.all(loaded) : loaded;
} else {
const id = fileList[0];
- pageModule = loadedModules[id] ?? (loadedModules[id] = await import(allServerFiles[fileList[0]]));
+ pageModule = loadedModules[id] ?? (loadedModules[id] = await import(allServerFiles[id]));
layouts = [];
}
diff --git a/src/js_ast.zig b/src/js_ast.zig
index c7713a3dde..ce24d226f5 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -8196,14 +8196,13 @@ pub const Macro = struct {
.String => this.coerce(value, .String),
.Promise => this.coerce(value, .Promise),
else => brk: {
- var name = value.getClassInfoName() orelse bun.String.init("unknown");
- defer name.deref();
+ const name = value.getClassInfoName() orelse "unknown";
this.log.addErrorFmt(
this.source,
this.caller.loc,
this.allocator,
- "cannot coerce {} ({s}) to Bun's AST. Please return a simpler type",
+ "cannot coerce {s} ({s}) to Bun's AST. Please return a simpler type",
.{ name, @tagName(value.jsType()) },
) catch unreachable;
break :brk error.MacroFailed;
diff --git a/src/js_parser.zig b/src/js_parser.zig
index 812ff3580f..e0fe7510c6 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -9062,7 +9062,7 @@ fn NewParser_(
if (symbol.namespace_alias == null) {
symbol.namespace_alias = .{
.namespace_ref = stmt.namespace_ref,
- .alias = name,
+ .alias = item.alias,
.import_record_index = stmt.import_record_index,
};
}
@@ -23977,7 +23977,7 @@ pub const ConvertESMExportsForHmr = struct {
return; // do not emit a statement here
},
.s_export_from => |st| stmt: {
- for (st.items) |item| {
+ for (st.items) |*item| {
const ref = item.name.ref.?;
const symbol = &p.symbols.items[ref.innerIndex()];
if (symbol.namespace_alias == null) {
@@ -23988,6 +23988,15 @@ pub const ConvertESMExportsForHmr = struct {
};
}
try ctx.visitRefToExport(p, ref, item.alias, item.name.loc, true);
+
+ // imports and export statements have their alias +
+ // original_name swapped. this is likely a design bug in
+ // the parser but since everything uses these
+ // assumptions, this hack is simpler than making it
+ // proper
+ const alias = item.alias;
+ item.alias = item.original_name;
+ item.original_name = alias;
}
const gop = try ctx.imports_seen.getOrPut(p.allocator, st.import_record_index);
diff --git a/src/options.zig b/src/options.zig
index d1f669b821..4bccea93e6 100644
--- a/src/options.zig
+++ b/src/options.zig
@@ -2071,42 +2071,58 @@ pub const OutputFile = struct {
}
/// Given the `--outdir` as root_dir, this will return the relative path to display in terminal
- pub fn writeToDisk(f: OutputFile, root_dir: std.fs.Dir, root_dir_path: []const u8) ![]const u8 {
+ pub fn writeToDisk(f: OutputFile, root_dir: std.fs.Dir, longest_common_path: []const u8) ![]const u8 {
switch (f.value) {
.saved => {
var rel_path = f.dest_path;
- if (f.dest_path.len > root_dir_path.len) {
- rel_path = resolve_path.relative(root_dir_path, f.dest_path);
+ if (f.dest_path.len > longest_common_path.len) {
+ rel_path = resolve_path.relative(longest_common_path, f.dest_path);
}
return rel_path;
},
.buffer => |value| {
var rel_path = f.dest_path;
- if (f.dest_path.len > root_dir_path.len) {
- rel_path = resolve_path.relative(root_dir_path, f.dest_path);
+
+ if (f.dest_path.len > longest_common_path.len) {
+ rel_path = resolve_path.relative(longest_common_path, f.dest_path);
if (std.fs.path.dirname(rel_path)) |parent| {
- if (parent.len > root_dir_path.len) {
+ if (parent.len > longest_common_path.len) {
try root_dir.makePath(parent);
}
}
}
- var path_buf: bun.PathBuffer = undefined;
- _ = try JSC.Node.NodeFS.writeFileWithPathBuffer(&path_buf, .{
- .data = .{ .buffer = .{
- .buffer = .{
- .ptr = @constCast(value.bytes.ptr),
- .len = value.bytes.len,
- .byte_len = value.bytes.len,
+ var handled_file_not_found = false;
+ while (true) {
+ var path_buf: bun.PathBuffer = undefined;
+ JSC.Node.NodeFS.writeFileWithPathBuffer(&path_buf, .{
+ .data = .{ .buffer = .{
+ .buffer = .{
+ .ptr = @constCast(value.bytes.ptr),
+ .len = value.bytes.len,
+ .byte_len = value.bytes.len,
+ },
+ } },
+ .encoding = .buffer,
+ .mode = if (f.is_executable) 0o755 else 0o644,
+ .dirfd = bun.toFD(root_dir.fd),
+ .file = .{ .path = .{
+ .string = JSC.PathString.init(rel_path),
+ } },
+ }).unwrap() catch |err| switch (err) {
+ error.FileNotFound, error.ENOENT => {
+ if (handled_file_not_found) return err;
+ handled_file_not_found = true;
+ try root_dir.makePath(
+ std.fs.path.dirname(rel_path) orelse
+ return err,
+ );
+ continue;
},
- } },
- .encoding = .buffer,
- .mode = if (f.is_executable) 0o755 else 0o644,
- .dirfd = bun.toFD(root_dir.fd),
- .file = .{ .path = .{
- .string = JSC.PathString.init(rel_path),
- } },
- }).unwrap();
+ else => return err,
+ };
+ break;
+ }
return rel_path;
},
diff --git a/test/bake/dev-server-harness.ts b/test/bake/dev-server-harness.ts
index 947b37d132..49cf8f572e 100644
--- a/test/bake/dev-server-harness.ts
+++ b/test/bake/dev-server-harness.ts
@@ -27,7 +27,9 @@ export const minimalFramework: Bake.Framework = {
},
};
-export interface DevServerTest {
+export type DevServerTest = ({
+ /** Starting files */
+ files: FileObject;
/**
* Framework to use. Consider `minimalFramework` if possible.
* Provide this object or `files['bun.app.ts']` for a dynamic one.
@@ -38,8 +40,13 @@ export interface DevServerTest {
* combined with the `framework` option.
*/
pluginFile?: string;
- /** Starting files */
- files: FileObject;
+} | {
+ /**
+ * Copy all files from test/bake/fixtures/
+ * This directory must contain `bun.app.ts` to allow hacking on fixtures manually via `bun run .`
+ */
+ fixture: string;
+}) & {
test: (dev: Dev) => Promise;
}
@@ -327,31 +334,47 @@ export function devTest(description: string, options: T
jest.test(`DevServer > ${basename}.${count}: ${description}`, async () => {
const root = path.join(tempDir, basename + count);
- writeAll(root, options.files);
- if (options.files["bun.app.ts"] == undefined) {
- if (!options.framework) {
- throw new Error("Must specify a options.framework or provide a bun.app.ts file");
+ if ('files' in options) {
+ writeAll(root, options.files);
+ if (options.files["bun.app.ts"] == undefined) {
+ if (!options.framework) {
+ throw new Error("Must specify a options.framework or provide a bun.app.ts file");
+ }
+ if (options.pluginFile) {
+ fs.writeFileSync(path.join(root, "pluginFile.ts"), dedent(options.pluginFile));
+ }
+ fs.writeFileSync(
+ path.join(root, "bun.app.ts"),
+ dedent`
+ ${options.pluginFile ?
+ `import plugins from './pluginFile.ts';` : "let plugins = undefined;"
+ }
+ export default {
+ app: {
+ framework: ${JSON.stringify(options.framework)},
+ plugins,
+ },
+ };
+ `,
+ );
+ } else {
+ if (options.pluginFile) {
+ throw new Error("Cannot provide both bun.app.ts and pluginFile");
+ }
}
- if (options.pluginFile) {
- fs.writeFileSync(path.join(root, "pluginFile.ts"), dedent(options.pluginFile));
- }
- fs.writeFileSync(
- path.join(root, "bun.app.ts"),
- dedent`
- ${options.pluginFile ?
- `import plugins from './pluginFile.ts';` : "let plugins = undefined;"
- }
- export default {
- app: {
- framework: ${JSON.stringify(options.framework)},
- plugins,
- },
- };
- `,
- );
} else {
- if (options.pluginFile) {
- throw new Error("Cannot provide both bun.app.ts and pluginFile");
+ if (!options.fixture) {
+ throw new Error("Must provide either `fixture` or `files`");
+ }
+ const fixture = path.join(devTestRoot, "../fixtures", options.fixture);
+ fs.cpSync(fixture, root, { recursive: true });
+
+ if(!fs.existsSync(path.join(root, "bun.app.ts"))) {
+ throw new Error(`Fixture ${fixture} must contain a bun.app.ts file.`);
+ }
+ if (!fs.existsSync(path.join(root, "node_modules"))) {
+ // link the node_modules directory from test/node_modules to the temp directory
+ fs.symlinkSync(path.join(devTestRoot, "../../node_modules"), path.join(root, "node_modules"), "junction");
}
}
fs.writeFileSync(
@@ -359,8 +382,8 @@ export function devTest(description: string, options: T
dedent`
import appConfig from "./bun.app.ts";
export default {
+ ...appConfig,
port: 0,
- ...appConfig
};
`,
);
@@ -373,6 +396,7 @@ export function devTest(description: string, options: T
{
FORCE_COLOR: "1",
BUN_DEV_SERVER_TEST_RUNNER: "1",
+ BUN_DUMP_STATE_ON_CRASH: "1",
},
]),
stdio: ["pipe", "pipe", "pipe"],
diff --git a/test/bake/dev/ecosystem.test.ts b/test/bake/dev/ecosystem.test.ts
index 068795a875..bb0e58d89e 100644
--- a/test/bake/dev/ecosystem.test.ts
+++ b/test/bake/dev/ecosystem.test.ts
@@ -2,10 +2,22 @@
// should be preferred to write specific tests for the bugs that these libraries
// discovered, but it easy and still a reasonable idea to just test the library
// entirely.
+import { expect } from "bun:test";
import { devTest } from "../dev-server-harness";
-// TODO: svelte server component example project
// Bugs discovered thanks to Svelte:
-// - Valid circular import use.
-// - Re-export `.e_import_identifier`, including live bindings.
-// TODO: - something related to the wrong push function being called
\ No newline at end of file
+// - Circular import situations
+// - export { live_binding }
+// - export { x as y }
+devTest('svelte component islands example', {
+ fixture: 'svelte-component-islands',
+ async test(dev) {
+ const html = await dev.fetch('/').text()
+ if (html.includes('Bun__renderFallbackError')) throw new Error('failed');
+ expect(html).toContain('self.$islands={\"pages/_Counter.svelte\":[[0,\"default\",{initial:5}]]}');
+ expect(html).toContain(`This is my svelte server component (non-interactive)
Bun v${Bun.version}
`);
+ expect(html).toContain(`>This is a client component (interactive island)
`);
+ // TODO: puppeteer test for client-side interactivity, hmr.
+ // care must be taken to implement this in a way that is not flaky.
+ },
+});
diff --git a/test/bake/dev/esm.test.ts b/test/bake/dev/esm.test.ts
index 1864c5e387..b08fcf1418 100644
--- a/test/bake/dev/esm.test.ts
+++ b/test/bake/dev/esm.test.ts
@@ -135,4 +135,73 @@ devTest("export { x as y }", {
});
await dev.fetch("/").expect("Value: 3");
}
-});
\ No newline at end of file
+});
+devTest("import { x as y }", {
+ framework: minimalFramework,
+ files: {
+ "module.ts": `
+ export const x = 1;
+ `,
+ "routes/index.ts": `
+ import { x as y } from '../module';
+ export default function(req, meta) {
+ return new Response('Value: ' + y);
+ }
+ `,
+ },
+ async test(dev) {
+ await dev.fetch("/").expect("Value: 1");
+ await dev.patch("module.ts", {
+ find: "1",
+ replace: "2",
+ });
+ await dev.fetch("/").expect("Value: 2");
+ }
+});
+devTest("import { default as y }", {
+ framework: minimalFramework,
+ files: {
+ "module.ts": `
+ export default 1;
+ `,
+ "routes/index.ts": `
+ import { default as y } from '../module';
+ export default function(req, meta) {
+ return new Response('Value: ' + y);
+ }
+ `,
+ },
+ async test(dev) {
+ await dev.fetch("/").expect("Value: 1");
+ await dev.patch("module.ts", {
+ find: "1",
+ replace: "2",
+ });
+ await dev.fetch("/").expect("Value: 2");
+ }
+});
+devTest("export { default as y }", {
+ framework: minimalFramework,
+ files: {
+ "module.ts": `
+ export default 1;
+ `,
+ "middle.ts": `
+ export { default as y } from './module';
+ `,
+ "routes/index.ts": `
+ import { y } from '../middle';
+ export default function(req, meta) {
+ return new Response('Value: ' + y);
+ }
+ `,
+ },
+ async test(dev) {
+ await dev.fetch("/").expect("Value: 1");
+ await dev.patch("module.ts", {
+ find: "1",
+ replace: "2",
+ });
+ await dev.fetch("/").expect("Value: 2");
+ }
+});
diff --git a/test/bake/fixtures/svelte-component-islands/bun.app.ts b/test/bake/fixtures/svelte-component-islands/bun.app.ts
new file mode 100644
index 0000000000..9813768a22
--- /dev/null
+++ b/test/bake/fixtures/svelte-component-islands/bun.app.ts
@@ -0,0 +1,8 @@
+import svelte from "./framework";
+
+export default {
+ port: 3000,
+ app: {
+ framework: svelte(),
+ },
+};
diff --git a/test/bake/fixtures/svelte-component-islands/framework/client.ts b/test/bake/fixtures/svelte-component-islands/framework/client.ts
new file mode 100644
index 0000000000..aca37275e0
--- /dev/null
+++ b/test/bake/fixtures/svelte-component-islands/framework/client.ts
@@ -0,0 +1,14 @@
+import type { IslandMap } from "./server";
+import { hydrate } from 'svelte';
+
+declare var $islands: IslandMap;
+Object.entries($islands).forEach(async([moduleId, islands]) => {
+ const mod = await import(moduleId);
+ for(const [islandId, exportId, props] of islands) {
+ const elem = document.getElementById(`I:${islandId}`)!;
+ hydrate(mod[exportId], {
+ target: elem,
+ props,
+ });
+ }
+});
diff --git a/test/bake/fixtures/svelte-component-islands/framework/index.ts b/test/bake/fixtures/svelte-component-islands/framework/index.ts
new file mode 100644
index 0000000000..7cecf75358
--- /dev/null
+++ b/test/bake/fixtures/svelte-component-islands/framework/index.ts
@@ -0,0 +1,68 @@
+import type { Bake } from "bun";
+import * as path from "node:path";
+import * as fs from "node:fs/promises";
+import * as svelte from "svelte/compiler";
+
+export default function (): Bake.Framework {
+ return {
+ serverComponents: {
+ separateSSRGraph: false,
+ serverRuntimeImportSource: "./framework/server.ts",
+ },
+ fileSystemRouterTypes: [
+ {
+ root: "pages",
+ serverEntryPoint: "./framework/server.ts",
+ clientEntryPoint: "./framework/client.ts",
+ style: "nextjs-pages", // later, this will be fully programmable
+ extensions: [".svelte"],
+ },
+ ],
+ plugins: [
+ {
+ // This is missing a lot of code that a plugin like `esbuild-svelte`
+ // handles, but this is only an examplea of how such a plugin could
+ // have server-components at a minimal level.
+ name: "svelte-server-components",
+ setup(b) {
+ const cssMap = new Map();
+ b.onLoad({ filter: /.svelte$/ }, async (args) => {
+ const contents = await fs.readFile(args.path, "utf-8");
+ const result = svelte.compile(contents, {
+ filename: args.path,
+ css: "external",
+ cssOutputFilename: path.basename(args.path, ".svelte") + ".css",
+ hmr: true,
+ dev: true,
+ generate: args.side,
+ });
+ // If CSS is specified, add a CSS import
+ let jsCode = result.js.code;
+ if (result.css) {
+ cssMap.set(args.path, result.css.code);
+ jsCode = `import ${JSON.stringify("svelte-css:" + args.path)};` + jsCode;
+ }
+ // Extract a "use client" directive from the file.
+ const header = contents.match(/^\s*\s*("[^"\n]*"|'[^'\n]*')/)?.[1];
+ if (header) {
+ jsCode = header + ';' + jsCode;
+ }
+ return {
+ contents: jsCode,
+ loader: "js",
+ watchFiles: [args.path],
+ };
+ });
+
+ // Resolve CSS files
+ b.onResolve({ filter: /^svelte-css:/ }, async (args) => {
+ return { path: args.path.replace(/^svelte-css:/, ""), namespace: "svelte-css" };
+ });
+ b.onLoad({ filter: /./, namespace: "svelte-css" }, async (args) => {
+ return { contents: cssMap.get(args.path) ?? "", loader: "css" };
+ });
+ },
+ },
+ ],
+ };
+}
diff --git a/test/bake/fixtures/svelte-component-islands/framework/server.ts b/test/bake/fixtures/svelte-component-islands/framework/server.ts
new file mode 100644
index 0000000000..29253245f8
--- /dev/null
+++ b/test/bake/fixtures/svelte-component-islands/framework/server.ts
@@ -0,0 +1,71 @@
+///
+import type { Bake } from "bun";
+import * as svelte from "svelte/server";
+import { uneval } from "devalue";
+
+export function render(req: Request, meta: Bake.RouteMetadata) {
+ isInsideIsland = false;
+ islands = {};
+ const { body, head } = svelte.render(meta.pageModule.default, {
+ props: {
+ params: meta.params,
+ },
+ });
+
+ // Add stylesheets and preloaded modules to the head
+ const extraHead = meta.styles.map((style) => ``).join("")
+ + meta.modulepreload.map((style) => ``).join("");
+ // Script tags
+ const scripts = nextIslandId > 0
+ ? `` +
+ meta.modules.map((module) => ``).join("")
+ : ""; // If no islands, no JavaScript
+
+ return new Response(
+ "" + head + extraHead + ""
+ + body + "" + scripts + "",
+ { headers: { "content-type": "text/html" } },
+ );
+}
+
+// To allow static site generation, frameworks can specify a prerender function
+export function prerender(meta: Bake.RouteMetadata) {
+ return {
+ files: {
+ '/index.html': render(null!, meta),
+ },
+ };
+}
+
+let isInsideIsland = false;
+let nextIslandId = 0;
+let islands: IslandMap;
+export type IslandMap = Record;
+export type Island = [islandId: number, exportId: string, props: any];
+
+/**
+ * @param component The original export value, as is.
+ * @param clientModuleId A string that the browser will pass to `import()`.
+ * @param clientExportId The export ID from the imported module.
+ * @returns A wrapped value for the export.
+ */
+export function registerClientReference(
+ component: Function,
+ clientModuleId: string,
+ clientExportId: string,
+) {
+ return function Island(...args: any[]) {
+ if (isInsideIsland) {
+ return component(...args);
+ }
+ isInsideIsland = true;
+ const [payload, props] = args;
+ const islandId = nextIslandId++;
+ payload.out += ``;
+ const file = (islands[clientModuleId] ??= []);
+ file.push([islandId, clientExportId, props]);
+ component(...args);
+ payload.out += ``;
+ isInsideIsland = false;
+ };
+}
diff --git a/test/bake/fixtures/svelte-component-islands/pages/_Counter.svelte b/test/bake/fixtures/svelte-component-islands/pages/_Counter.svelte
new file mode 100644
index 0000000000..7c4a618edb
--- /dev/null
+++ b/test/bake/fixtures/svelte-component-islands/pages/_Counter.svelte
@@ -0,0 +1,24 @@
+
+
+
+
This is a client component (interactive island)
+
+
+
diff --git a/test/bake/fixtures/svelte-component-islands/pages/index.svelte b/test/bake/fixtures/svelte-component-islands/pages/index.svelte
new file mode 100644
index 0000000000..f5b8c0728c
--- /dev/null
+++ b/test/bake/fixtures/svelte-component-islands/pages/index.svelte
@@ -0,0 +1,18 @@
+
+
+ hello
+ This is my svelte server component (non-interactive)
+ Bun v{Bun.version}
+
+
+
\ No newline at end of file
diff --git a/test/bun.lockb b/test/bun.lockb
index 6a1061e911b76ab6b2492a1669ec0bb3ba2fb9ed..699279fcb23528b9d24e1f7fbf0fc08fe1dd6b90 100755
GIT binary patch
delta 47020
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
Khf!~*vh_dxwmxG3
delta 43704
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
G
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
diff --git a/test/js/bun/plugin/plugins.test.ts b/test/js/bun/plugin/plugins.test.ts
index 5222bc29a6..d544fb953b 100644
--- a/test/js/bun/plugin/plugins.test.ts
+++ b/test/js/bun/plugin/plugins.test.ts
@@ -187,15 +187,14 @@ plugin({
// This is to test that it works when imported from a separate file
import "../../third_party/svelte";
import "./module-plugins";
-import { bunEnv, bunExe, tempDirWithFiles } from "harness";
-import { filter } from "js/node/test/fixtures/aead-vectors";
+import { render as svelteRender } from 'svelte/server';
describe("require", () => {
it("SSRs `Hello world!
` with Svelte", () => {
const { default: App } = require("./hello.svelte");
- const { html } = App.render();
+ const { body } = svelteRender(App);
- expect(html).toBe("Hello world!
");
+ expect(body).toBe("Hello world!
");
});
it("beep:boop returns 42", () => {
@@ -295,9 +294,8 @@ describe("dynamic import", () => {
it("SSRs `Hello world!
` with Svelte", async () => {
const { default: App }: any = await import("./hello.svelte");
- const { html } = App.render();
-
- expect(html).toBe("Hello world!
");
+ const { body } = svelteRender(App);
+ expect(body).toBe("Hello world!
");
});
it("beep:boop returns 42", async () => {
@@ -326,9 +324,9 @@ import Hello from ${JSON.stringify(resolve(import.meta.dir, "hello2.svelte"))};
export default Hello;
`;
const { default: SvelteApp } = await import("delay:hello2.svelte");
- const { html } = SvelteApp.render();
+ const { body } = svelteRender(SvelteApp);
- expect(html).toBe("Hello world!
");
+ expect(body).toBe("Hello world!
");
});
});
diff --git a/test/js/third_party/svelte/bun-loader-svelte.ts b/test/js/third_party/svelte/bun-loader-svelte.ts
index c30a7bd11e..e90562b38f 100644
--- a/test/js/third_party/svelte/bun-loader-svelte.ts
+++ b/test/js/third_party/svelte/bun-loader-svelte.ts
@@ -11,7 +11,8 @@ await plugin({
readFileSync(path.substring(0, path.includes("?") ? path.indexOf("?") : path.length), "utf-8"),
{
filename: path,
- generate: "ssr",
+ generate: "server",
+ dev: false,
},
).js.code,
loader: "js",
diff --git a/test/js/third_party/svelte/svelte.test.ts b/test/js/third_party/svelte/svelte.test.ts
index 67167ecbe0..05a56e4c42 100644
--- a/test/js/third_party/svelte/svelte.test.ts
+++ b/test/js/third_party/svelte/svelte.test.ts
@@ -1,12 +1,13 @@
import { describe, expect, it } from "bun:test";
+import { render as svelteRender } from "svelte/server";
import "./bun-loader-svelte";
describe("require", () => {
it("SSRs `Hello world!
` with Svelte", () => {
const { default: App } = require("./hello.svelte");
- const { html } = App.render();
+ const { body } = svelteRender(App);
- expect(html).toBe("Hello world!
");
+ expect(body).toBe("Hello world!
");
});
it("works if you require it 1,000 times", () => {
@@ -14,7 +15,7 @@ describe("require", () => {
Bun.unsafe.gcAggressionLevel(0);
for (let i = 0; i < 1000; i++) {
const { default: App } = require("./hello.svelte?r" + i);
- expect(App.render).toBeFunction();
+ expect(App).toBeFunction();
}
Bun.gc(true);
Bun.unsafe.gcAggressionLevel(prev);
@@ -27,7 +28,7 @@ describe("dynamic import", () => {
Bun.unsafe.gcAggressionLevel(0);
for (let i = 0; i < 1000; i++) {
const { default: App } = await import("./hello.svelte?i" + i);
- expect(App.render).toBeFunction();
+ expect(App).toBeFunction();
}
Bun.gc(true);
Bun.unsafe.gcAggressionLevel(prev);
@@ -35,8 +36,7 @@ describe("dynamic import", () => {
it("SSRs `Hello world!
` with Svelte", async () => {
const { default: App }: any = await import("./hello.svelte");
- const { html } = App.render();
-
- expect(html).toBe("Hello world!
");
+ const { body } = svelteRender(App);
+ expect(body).toBe("Hello world!
");
});
});
diff --git a/test/package.json b/test/package.json
index 03a8717ce3..f643ef682d 100644
--- a/test/package.json
+++ b/test/package.json
@@ -21,6 +21,7 @@
"axios": "1.6.8",
"body-parser": "1.20.2",
"comlink": "4.4.1",
+ "devalue": "5.1.1",
"es-module-lexer": "1.3.0",
"esbuild": "0.18.6",
"express": "4.18.2",
@@ -59,7 +60,7 @@
"string-width": "7.0.0",
"stripe": "15.4.0",
"supertest": "6.3.3",
- "svelte": "3.55.1",
+ "svelte": "5.4.0",
"typescript": "5.0.2",
"undici": "5.20.0",
"verdaccio": "6.0.0",