Skip to content
Closed
Show file tree
Hide file tree
Changes from 10 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
12 changes: 8 additions & 4 deletions src/bundler/bundle_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7015,13 +7015,17 @@ pub mod bv2_impl {
>(
self.transpiler.fs().top_level_dir, path.text
);
if loader == Loader::Html && entry.kind == bake_types::CacheKind::Asset
// A cached json or text module is an `Asset` with no asset URL.
let asset_hash = if loader == Loader::Html
&& entry.kind == bake_types::CacheKind::Asset
{
dev_server.asset_hash(path.text)
} else {
None
};
if let Some(hash) = asset_hash {
// Overload `path.text` to point to the final URL
// This information cannot be queried while printing because a lock wouldn't get held.
let hash = dev_server
.asset_hash(path.text)
.expect("cached asset not found");
import_record.path.text = path.text;
import_record.path.namespace = b"file";
// SAFETY: `alloc_str` returns into the bundler arena which
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/bake/dev_server/incremental_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub(crate) enum Content {
/// When stale, the code is "", otherwise it contains at least one
/// non-whitespace character (empty chunks contain a function wrapper).
Js(Box<[u8]>),
/// A module whose loader is not JavaScript-like. Only a copied file is in `DevServer::assets`.
Asset(Box<[u8]>),
/// First file in a CSS bundle (the one HTML/JS points into). Re-bundles
/// of any downstream `CssChild` re-queue the root.
Expand Down Expand Up @@ -1393,6 +1394,7 @@ impl<const SIDE: bake::Side> IncrementalGraph<SIDE> {
if !found_existing {
self.edge_lists.push(EdgeLists::default());
self.ensure_stale_bit_capacity(true)?;
self.stale_files.set(idx);
Comment thread
robobun marked this conversation as resolved.
Resolved
}
Ok(InsertEmptyResult {
index: FileIndex::init(idx as u32),
Expand Down
59 changes: 59 additions & 0 deletions test/bake/dev/css.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,65 @@ devTest("css hot update carries the edited stylesheet when another root fails in
}
},
});
// The stale bitset of the incremental graph grows in steps of 512 files.
const modulesPastFirstBitsetStep = Array.from({ length: 520 }, (_, i) => `m${i}`);
devTest("css file that is a css import first and a script import of a second page later", {
files: {
"first.html": emptyHtmlFile({
styles: ["first.css"],
scripts: ["first.ts"],
}),
"second.html": emptyHtmlFile({
scripts: ["second.ts"],
}),
"first.css": `
.first { color: red; }
`,
"child.css": `
.child { color: blue; }
`,
"first.ts": `
console.log("first");
import.meta.hot.accept();
`,
"second.ts": `
import "./child.css";
console.log("second");
`,
...Object.fromEntries(modulesPastFirstBitsetStep.map(name => [`modules/${name}.ts`, `export default "${name}";`])),
},
async test(dev) {
await using c1 = await dev.client("/first");
await c1.expectMessage("first");

// A bundle with no css in it takes the client graph past 512 files, so
// the bitset grows at the end of the bundle and the new bits say "cached".
await dev.write(
"first.ts",
[
...modulesPastFirstBitsetStep.map(name => `import "./modules/${name}";`),
`console.log("first with modules");`,
`import.meta.hot.accept();`,
].join("\n"),
);
await c1.expectMessage("first with modules");

// "child.css" enters the client graph as a css import. It has no stylesheet of its own.
await dev.write(
"first.css",
`
@import "./child.css";
.first { color: red; }
`,
);
await c1.style(".child").color.expect.toBe("#00f");

// The script import must bundle "child.css" as a stylesheet of this page.
await using c2 = await dev.client("/second");
await c2.expectMessage("second");
await c2.style(".child").color.expect.toBe("#00f");
},
});
devTest("multiple stylesheets importing same dependency", {
files: {
"first.html": emptyHtmlFile({
Expand Down
65 changes: 65 additions & 0 deletions test/bake/dev/html.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// HTML tests are tests relating to HTML files themselves.
import { expect } from "bun:test";
import { devTest, emptyHtmlFile } from "../bake-harness";

devTest("html file is watched", {
Expand Down Expand Up @@ -128,6 +129,70 @@ devTest("image import in JS", {
// await dev.fetch(img1).expect404();
},
});
devTest("html references a json file that is cached as a module", {
// "manifest.json" is bundled as a module first. It is not in the asset
// store, so a later HTML reference to it must not look up an asset URL.
files: {
"index.html": `
<!DOCTYPE html><html><head></head><body>
<script type="module" src="script.ts"></script>
</body></html>
`,
"script.ts": `
import manifest from "./manifest.json";
console.log(manifest.name);
`,
"manifest.json": `{ "name": "app" }`,
},
async test(dev) {
await using c = await dev.client("/");
await c.expectMessage("app");

await c.expectReload(async () => {
await dev.patch("index.html", {
find: "<head></head>",
replace: '<head><link rel="manifest" href="./manifest.json"></head>',
});
});
await c.expectMessage("app");

// One file is one module in the dev server. Until it can also be an
// asset, the script keeps the json module and the page is served without the link.
const html = await (await dev.fetch("/")).text();
expect(html).toInclude("<script");
expect(html).not.toInclude('rel="manifest"');
},
});
devTest("html of a second page references a json file that a script of the first page imports", {
// The same as above with no edit: the first page caches "manifest.json" as a module.
files: {
"first.html": `
<!DOCTYPE html><html><head></head><body>
<script type="module" src="first.ts"></script>
</body></html>
`,
"second.html": `
<!DOCTYPE html><html><head><link rel="manifest" href="./manifest.json"></head><body>
<script type="module" src="second.ts"></script>
</body></html>
`,
"first.ts": `
import manifest from "./manifest.json";
console.log(manifest.name);
`,
"second.ts": `
console.log("second");
`,
"manifest.json": `{ "name": "app" }`,
},
async test(dev) {
await using c1 = await dev.client("/first");
await c1.expectMessage("app");

await using c2 = await dev.client("/second");
await c2.expectMessage("second");
},
});
devTest("import then create", {
files: {
"index.html": `
Expand Down
Loading