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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/bundler/ParseTask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2555,7 +2555,6 @@ pub mod parse_worker {
opts.framework = topts.framework.map(|f| {
// `Framework` is bump-allocated below, so `Drop` never runs — use arena-owned slices.
let projected = js_parser::options::Framework {
is_built_in_react: f.is_built_in_react,
server_components: f.server_components.as_ref().map(|sc| {
js_parser::options::FrameworkServerComponents {
separate_ssr_graph: sc.separate_ssr_graph,
Expand Down
5 changes: 0 additions & 5 deletions src/bundler/bundle_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,9 +466,6 @@ pub mod bv2_impl {
/// (`js_parser/ast/Parser.rs:1997` resolves `framework.react_fast_refresh
/// .import_source`) when `features.react_fast_refresh` is on.
pub(crate) react_fast_refresh: Option<ReactFastRefresh>,
/// Mirrors `Framework.is_built_in_react` — read by
/// `linker_context::generateChunksInParallel` to gate `BakeExtra`.
pub(crate) is_built_in_react: bool,
}
impl Framework {
/// Construct the bundler-side TYPE_ONLY view. Called from
Expand All @@ -479,13 +476,11 @@ pub mod bv2_impl {
built_in_modules: bun_collections::StringArrayHashMap<BuiltInModule>,
server_components: Option<ServerComponents>,
react_fast_refresh: Option<ReactFastRefresh>,
is_built_in_react: bool,
) -> Self {
Self {
built_in_modules,
server_components,
react_fast_refresh,
is_built_in_react,
}
}
}
Expand Down
17 changes: 10 additions & 7 deletions src/bundler/linker_context/generateChunksInParallel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1260,10 +1260,10 @@ pub(crate) fn generate_chunks_in_parallel<const IS_DEV_SERVER: bool>(
crate::chunk::Content::Html => Box::default(),
},
bake_extra: 'brk: {
if c.framework.is_none() || IS_DEV_SERVER {
let Some(framework) = c.framework else {
break 'brk BakeExtra::default();
}
if !c.framework.unwrap().is_built_in_react {
};
if IS_DEV_SERVER {
break 'brk BakeExtra::default();
}

Expand All @@ -1276,12 +1276,15 @@ pub(crate) fn generate_chunks_in_parallel<const IS_DEV_SERVER: bool>(
if output_kind == options::OutputKind::EntryPoint
&& side == options::Side::Server
{
extra.route = if static_route_visitor
.has_transitive_use_client(chunk.entry_point.source_index())
// Without server components there are no "use client"
// boundaries, so the visitor would call every route static.
Comment thread
robobun marked this conversation as resolved.
extra.route = if framework.server_components.is_some()
&& !static_route_visitor
.has_transitive_use_client(chunk.entry_point.source_index())
{
BakeRouteKind::Route
} else {
BakeRouteKind::FullyStaticRoute
} else {
BakeRouteKind::Route
};
}

Expand Down
1 change: 0 additions & 1 deletion src/js_parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ pub mod options {
/// §Forbidden re: `&'static [T]` for arena-freed data.
#[derive(Clone, Default)]
pub struct Framework {
pub is_built_in_react: bool,
pub server_components: Option<FrameworkServerComponents>,
pub react_fast_refresh: Option<ReactFastRefresh>,
}
Expand Down
7 changes: 1 addition & 6 deletions src/runtime/bake/bake_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1072,12 +1072,7 @@ impl Framework {
.map(|rfr| bt::ReactFastRefresh {
import_source: rfr.import_source.into(),
});
bt::Framework::new(
built_in_modules,
server_components,
react_fast_refresh,
self.is_built_in_react,
)
bt::Framework::new(built_in_modules, server_components, react_fast_refresh)
}

pub fn init_transpiler_with_options<'a>(
Expand Down
7 changes: 1 addition & 6 deletions src/runtime/bake/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,7 @@ impl Framework {
.map(|rfr| bt::ReactFastRefresh {
import_source: rfr.import_source.as_ref().into(),
});
bt::Framework::new(
built_in_modules,
server_components,
react_fast_refresh,
self.is_built_in_react,
)
bt::Framework::new(built_in_modules, server_components, react_fast_refresh)
}

/// Sets up a per-graph
Expand Down
14 changes: 5 additions & 9 deletions src/runtime/bake/production.rs
Original file line number Diff line number Diff line change
Expand Up @@ -591,8 +591,8 @@ fn build_with_vm(ctx: Context, cwd: &[u8], pt: &mut PerThread) -> crate::Result<
)?;

// `bake_body::Framework` is the runtime-side superset; the bundler reads only
// `built_in_modules` / `server_components` / `react_fast_refresh` /
// `is_built_in_react` via its lower-tier `bake_types::Framework` view.
// `built_in_modules` / `server_components` / `react_fast_refresh`
// via its lower-tier `bake_types::Framework` view.
Comment thread
robobun marked this conversation as resolved.
// Project once here via the shared helper so the field-shape (e.g.
// `BuiltInModule` `&'static [u8]` → `Box<[u8]>`) stays in one place.
// (The two Framework types could only merge if `FileSystemRouterType` /
Expand Down Expand Up @@ -784,13 +784,9 @@ fn build_with_vm(ctx: Context, cwd: &[u8], pt: &mut PerThread) -> crate::Result<
}
}
}
// Write the runtime file to disk if there are any client chunks
{
let Some(runtime_file_index) = maybe_runtime_file_index else {
Output::panic(format_args!(
"Runtime file not found. This is an unexpected bug in Bun. Please file a bug report on GitHub."
));
};
// Write the runtime file to disk if there are any client chunks. The runtime
// is absent when no bundled module uses any of its helpers.
Comment thread
robobun marked this conversation as resolved.
if let Some(runtime_file_index) = maybe_runtime_file_index {
let any_client_chunks = bundled_outputs_list.iter().any(|file| {
file.side == Some(bun_bundler::options::Side::Client)
&& file.src_path.text != b"bun-framework-react/client.tsx"
Expand Down
55 changes: 54 additions & 1 deletion test/bake/dev/production.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test";
import { existsSync } from "fs";
import { bunEnv, bunExe } from "harness";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
import path from "path";
import { tempDirWithBakeDeps } from "../bake-harness";

Expand Down Expand Up @@ -659,4 +659,57 @@ export default function IndexPage() {
// Verify NO JavaScript imports are included in the HTML
expect(htmlContent).not.toContain('<script type="module"');
});

// https://github.com/oven-sh/bun/issues/32142
test("custom framework builds without the built-in react framework", async () => {
const dir = tempDirWithFiles("bake-production-custom-framework", {
"bun.app.ts": `export default {
app: {
framework: {
fileSystemRouterTypes: [
{
root: "pages",
style: "nextjs-pages",
serverEntryPoint: "./server-entry.ts",
clientEntryPoint: "./client-entry.ts",
},
],
},
},
};`,
"pages/index.tsx": `export default function Home() { return "homepage"; }`,
"server-entry.ts": `export function render(req: Request, meta: any) {
return new Response(String(meta.pageModule.default()));
}
export async function prerender(meta: any) {
const scripts = meta.modules.map(m => '<script type="module" src="' + m + '"></script>').join("");
const body = String(meta.pageModule.default());
return { files: { "index.html": "<!DOCTYPE html><html><body>" + body + scripts + "</body></html>" } };
}`,
"client-entry.ts": `console.log("client loaded");`,
});

const { exitCode, stderr } = await Bun.$`${bunExe()} build --app ./bun.app.ts`
.cwd(dir)
.env({ ...bunEnv, BUN_FEATURE_FLAG_EXPERIMENTAL_BAKE: "1" })
.throws(false);

const htmlPage = path.join(dir, "dist", "index.html");
expect(existsSync(htmlPage), stderr.toString()).toBe(true);

const htmlContent = await Bun.file(htmlPage).text();
expect(htmlContent).toContain("homepage");

// A framework without server components has no "use client" boundaries,
// so its routes must not be treated as fully static: the client entry
// point is still passed to prerender via `meta.modules`.
const scriptMatch = htmlContent.match(/<script type="module" src="([^"]*_bun[^"]*\.js)"><\/script>/);
expect(scriptMatch).toBeTruthy();

// The referenced client bundle was written to disk.
const scriptSrc = scriptMatch![1].replace(/^\//, "");
expect(existsSync(path.join(dir, "dist", scriptSrc))).toBe(true);

expect(exitCode).toBe(0);
});
});
Loading