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
10 changes: 9 additions & 1 deletion bend2/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,9 +505,17 @@ function cli_base(what?: string): void {
}

// An inline module script; an empty svg <script/> can't have content.
// HTMLRewriter keeps character references in attributes. Only ASCII
// references and HTML whitespace can turn a type into "module"; NBSP cannot.
function is_inline_module(el: HTMLRewriterTypes.Element): boolean {
const type = el.getAttribute("type")?.replace(
/&#(?:[xX]([\da-fA-F]+)|(\d+));?|&(Tab|NewLine);/g, (t, hex, dec, ws) => {
if (ws) return ws === "Tab" ? "\t" : "\n";
const n = parseInt(hex ?? dec, hex === undefined ? 10 : 16);
return n < 128 ? String.fromCharCode(n) : t;
});
return el.canHaveContent && !el.hasAttribute("src")
&& el.getAttribute("type")?.trim().toLowerCase() === "module";
&& type?.replace(/^[\t\n\f\r ]+|[\t\n\f\r ]+$/g, "").toLowerCase() === "module";
}

// cli_bundle builds a page. Bun skips inline module scripts (#1400), so the
Expand Down
12 changes: 12 additions & 0 deletions tests/io/html_script_types.bend
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# HTML character references and ASCII whitespace determine which scripts run.
import Base

def HTML.script_types() -> IO(U32):
import "./html_script_types.js"

def main() -> IO(Unit):
do IO<Unit>:
result : U32 <- HTML.script_types()
IO.print(U32.show(result))

#|0
47 changes: 47 additions & 0 deletions tests/io/html_script_types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
function html_script_types() {
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-html-types-"));
const options = { encoding: "utf8", timeout: 6000,
env: { ...process.env, HOME: dir, BEND_ORIGIN: "http://127.0.0.1:9",
BEND_NO_TELEMETRY: "1" },
};
try {
fs.writeFileSync(path.join(dir, "lib.bend"), "import Base\ndef value() -> U32:\n 42\n");
const cases = [
["module", "42\n"],
["mod&#117;le", "42\n"],
["&#x6d;odule", "42\n"],
["&Tab;MODULE&NewLine;", "42\n"],
["\u00a0module", ""],
["&#160;module", ""],
["&tab;module", ""],
];
for (const [i, [type, expected]] of cases.entries()) {
const page = path.join(dir, "index.html");
const out = path.join(dir, "out" + i);
fs.writeFileSync(page, `<script type="${type}">import lib from './lib.bend'; console.log(lib.value());</script>`);
const built = child.spawnSync(process.execPath,
[path.resolve("bend2/main.ts"), page, "-o", out], options);
if (built.status !== 0) return 1;
const entries = [];
new HTMLRewriter().on("script", { element(el) {
const src = el.getAttribute("src");
if (src !== null) entries.push(path.resolve(out, src));
}}).transform(fs.readFileSync(path.join(out, "index.html"), "utf8"));
let output = "";
for (const entry of entries) {
const run = child.spawnSync(process.execPath, [entry], options);
if (run.status !== 0) return 2;
output += run.stdout;
}
if (output !== expected) return 3 + i;
}
return 0;
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
}

io_eff(CID(HTML.script_types), html_script_types);