Skip to content
Merged
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
72 changes: 63 additions & 9 deletions bend2/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,16 +481,70 @@ function cli_base(what?: string): void {
cli_say(1, want.join("\n\n") + "\n");
}

// cli_bundle builds a page. Bun skips inline module scripts (#1400), so the
// inline plugin moves each to a virtual file beside its page, padded so an
// error points at the page's own line and column.
async function cli_bundle(page: string, dir: string): Promise<void> {
const out = await Bun.build({
entrypoints: [page],
outdir: dir,
target: "browser",
minify: true,
plugins: [PLUGIN],
});
for (const a of out.outputs) {
cli_say(1, a.path + " (" + (a.size / 1024).toFixed(1) + "kb)\n");
const code = new Map<string, string>();
const inline: BunPlugin = {
name: "bend inline",
setup(build) {
build.onResolve({ filter: /\.bend-inline-\d+\.js$/ }, (a) => {
const id = path.resolve(a.resolveDir, a.path);
return code.has(id) ? { path: id, namespace: "inline" } : undefined;
});
build.onLoad({ filter: /.*/, namespace: "inline" }, (a) =>
({ contents: code.get(a.path)!, loader: "js" }));
build.onLoad({ filter: /\.html$/ }, async (a) => {
const src = await Bun.file(a.path).text();
const mods: { id: string; text: string }[] = [];
let mod: { id: string; text: string } | null = null;
const html = new HTMLRewriter().on("script", {
element(el) {
const type = el.getAttribute("type")?.trim().toLowerCase();
mod = null;
if (type === "module" && !el.hasAttribute("src")) {
const name = "." + path.basename(a.path) + ".bend-inline-"
+ mods.length + ".js";
mod = { id: path.join(path.dirname(a.path), name), text: "" };
mods.push(mod);
el.setAttribute("src", "./" + name);
}
},
text(t) {
if (mod) {
mod.text += t.text;
t.remove();
}
},
}).transform(src);
let end = 0;
let line = 0;
for (const m of mods) {
const at = src.indexOf(m.text, end);
line += src.slice(end, at).split("\n").length - 1;
const col = at - src.lastIndexOf("\n", at - 1) - 1;
code.set(m.id, "\n".repeat(line) + " ".repeat(col) + m.text);
line += m.text.split("\n").length - 1;
end = at + m.text.length;
}
return { contents: html, loader: "html" };
});
},
};
try {
const out = await Bun.build({
entrypoints: [page],
outdir: dir,
target: "browser",
minify: true,
plugins: [inline, PLUGIN],
});
for (const a of out.outputs) {
cli_say(1, a.path + " (" + (a.size / 1024).toFixed(1) + "kb)\n");
}
} catch (e) {
throw book_err(e).replace(/\.([^/\\]+)\.bend-inline-\d+\.js/g, "$1");
}
}

Expand Down
13 changes: 13 additions & 0 deletions tests/io/bundle_inline.bend
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# An inline module script in a page is bundled with its .bend imports, and
# an error in it points at the page's own line (#1400).
import Base

def Bundle.inline() -> IO(U32):
import "./bundle_inline.js"

def main() -> IO(Unit):
do IO<Unit>:
value : U32 <- Bundle.inline()
IO.print(U32.show(value))

#|0
33 changes: 33 additions & 0 deletions tests/io/bundle_inline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
function bundle_inline() {
const fs = require("node:fs");
const path = require("node:path");
const child = require("node:child_process");
const dir = fs.mkdtempSync(path.join(process.cwd(), ".bend-page-inline-"));
const run = (...args) => child.spawnSync(process.execPath, args, {
encoding: "utf8",
env: { ...process.env, HOME: dir, BEND_ORIGIN: "http://127.0.0.1:9" },
});
const put = (name, text) => fs.writeFileSync(path.join(dir, name), text);
const build = (page) => run(path.resolve("bend2/main.ts"),
path.join(dir, page), "-o", path.join(dir, "out"));
try {
put("m.bend", "import Base\n\nlaw main:\n U32\n\ndef main():\n 42\n");
put("ok.html", '<script type="module">\nimport M from "./m.bend";\n'
+ "console.log(M.main());\n</script>\n");
put("bad.html", '<p>\n<script\n type="module">\nconst x = ;\n</script>\n');
if (build("ok.html").status !== 0) {
return 1;
}
const out = path.join(dir, "out");
const js = fs.readdirSync(out).find((f) => f.endsWith(".js"));
if (run(path.join(out, js)).stdout.trim() !== "42") {
return 2;
}
const bad = build("bad.html");
return bad.status === 1 && bad.stderr.includes("bad.html:4:") ? 0 : 3;
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
}

io_eff(CID(Bundle.inline), bundle_inline);