import rust from "@wasm-tool/rollup-plugin-rust";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import copy from "rollup-plugin-copy";
import path from "node:path";
const emitWorkerHelpers = (label) => ({
name: `emit-worker-helpers-${label}`,
generateBundle(_, bundle) {
let cargoChunkName = null;
for (const [name, chunk] of Object.entries(bundle)) {
if (chunk.type !== "chunk") continue;
if (
chunk.code &&
chunk.code.includes("wbg_rayon_start_worker") &&
name.startsWith("Cargo-")
) {
cargoChunkName = name;
break;
}
}
if (!cargoChunkName) {
this.warn(
`[emit-worker-helpers/${label}] no Cargo-*.js chunk found with wbg_rayon_start_worker`
);
return;
}
const shim = `// Auto-generated by rollup.config.js (emit-worker-helpers).
// Spawned by wasm-bindgen-rayon via:
// new Worker(new URL('./workerHelpers.js', import.meta.url), {type:'module'})
// Imports the sibling Cargo chunk to get __wbg_init (default) and
// wbg_rayon_start_worker, then mirrors the message protocol from
// wasm-bindgen-rayon/src/workerHelpers.js.
function waitForMsgType(target, type) {
return new Promise(resolve => {
target.addEventListener('message', function onMsg({ data }) {
if (data?.type !== type) return;
target.removeEventListener('message', onMsg);
resolve(data);
});
});
}
waitForMsgType(self, 'wasm_bindgen_worker_init').then(async ({ init, receiver }) => {
const pkg = await import('./${cargoChunkName}');
// Our build exports __wbg_init by name (the [remove-wasm-tla] plugin adds
// it back so loadWasm() can call it explicitly). Stock wasm-bindgen-rayon
// expects pkg.default — not present here, since rollup-plugin-rust's
// bundler-mode output doesn't synthesize a default. Try named first, then
// fall back to default for resilience.
const initWasm = pkg.__wbg_init || pkg.default;
if (typeof initWasm !== 'function') throw new Error('Cargo-*.js: no __wbg_init or default export');
await initWasm(init);
postMessage({ type: 'wasm_bindgen_worker_ready' });
pkg.wbg_rayon_start_worker(receiver);
});
`;
this.emitFile({
type: "asset",
fileName: "workerHelpers.js",
source: shim,
});
console.log(`[emit-worker-helpers/${label}] -> ${cargoChunkName}`);
},
});
const rewriteWorkerWasmImport = {
name: "rewrite-worker-wasm-import",
resolveId(source, importer) {
if (
source === "../../dist/wasm.js" &&
importer &&
importer.includes("web-client-methods-worker.js")
) {
return path.resolve(
path.dirname(importer),
"..",
"..",
distDir,
"wasm.js"
);
}
return null;
},
};
const wasmBindgenRayonSnippetResolver = {
name: "wasm-bindgen-rayon-snippet-resolver",
resolveId: {
order: "pre",
handler(source, importer) {
if (
importer &&
importer.includes("wasm-bindgen-rayon") &&
importer.endsWith("workerHelpers.js")
) {
console.log(`[wbr-resolver] source="${source}" importer="${importer}"`);
if (source === "../../..") {
const target = path.resolve(
path.dirname(importer),
"../../..",
"index.js"
);
console.log(`[wbr-resolver] REWRITE -> ${target}`);
return target;
}
}
return null;
},
},
};
const variant = process.env.MIDEN_BUILD_VARIANT || "st";
if (variant !== "st" && variant !== "mt") {
throw new Error(
`MIDEN_BUILD_VARIANT must be "st" or "mt" (got "${variant}")`
);
}
const distDir = `dist/${variant}`;
const isMt = variant === "mt";
if (!isMt) {
process.env.RUSTUP_TOOLCHAIN = "stable";
}
const mtTargetRustflags = [
"-C",
"target-feature=+atomics,+bulk-memory,+mutable-globals,+simd128",
"-C",
"llvm-args=-vectorize-loops=false",
"-C",
"llvm-args=-vectorize-slp=false",
"-C",
"link-arg=--shared-memory",
"-C",
"link-arg=--import-memory",
"-C",
"link-arg=--export=__wasm_init_tls",
"-C",
"link-arg=--export=__tls_size",
"-C",
"link-arg=--export=__tls_align",
"-C",
"link-arg=--export=__tls_base",
"-C",
"link-arg=--max-memory=4294967296",
"-C",
"panic=abort",
"--cfg",
'getrandom_backend="wasm_js"',
];
const devMode = process.env.MIDEN_WEB_DEV === "true";
const fastBuild = process.env.MIDEN_FAST_BUILD === "true";
const cargoArgsUseDebugSymbols = ["--config", "profile.release.debug='full'"];
const cargoArgsLineTablesDebug = [
"--config",
"profile.release.debug='line-tables-only'",
"--config",
"profile.release.strip='none'",
];
const cargoArgsFastBuild = [
"--config",
"profile.release.lto=false",
"--config",
"profile.release.codegen-units=16",
];
const wasmOptArgs = [
"--strip-dwarf",
devMode ? "-O0" : "-O3",
"--enable-bulk-memory",
"--enable-nontrapping-float-to-int",
"--enable-threads",
"--enable-simd",
"--debuginfo",
];
const mtOnlyCargoArgs = isMt
? [
"-Z",
"build-std=std,panic_abort",
"--features",
"mt-threads",
"--config",
`target.wasm32-unknown-unknown.rustflags=${JSON.stringify(mtTargetRustflags)}`,
]
: [];
const baseCargoArgs = [
"--features",
"browser,testing",
"--no-default-features",
...cargoArgsLineTablesDebug,
]
.concat(mtOnlyCargoArgs)
.concat(devMode ? cargoArgsUseDebugSymbols : [])
.concat(fastBuild ? cargoArgsFastBuild : []);
export default [
{
input: ["./js/wasm.js", "./js/index.js", "./js/eager.js"],
output: {
dir: distDir,
format: "es",
sourcemap: true,
assetFileNames: "assets/[name][extname]",
},
plugins: [
wasmBindgenRayonSnippetResolver,
rust({
verbose: true,
extraArgs: {
cargo: [...baseCargoArgs],
wasmOpt: fastBuild ? [] : wasmOptArgs,
wasmBindgen: ["--keep-debug"],
},
experimental: {
typescriptDeclarationDir: `${distDir}/crates`,
},
optimize: { release: true, rustc: !devMode },
}),
resolve(),
commonjs(),
emitWorkerHelpers(distDir),
{
name: "remove-wasm-tla",
generateBundle(_, bundle) {
for (const [name, chunk] of Object.entries(bundle)) {
if (chunk.type !== "chunk" || !chunk.code) continue;
if (!chunk.code.includes("__wbg_init")) continue;
const before = chunk.code.length;
chunk.code = chunk.code.replace(
/\n\s*await __wbg_init\([^)]*\);\s*\n/g,
"\n"
);
if (chunk.code.length !== before) {
const exportListRe = /export \{([^}]+)\};(\s*)$/m;
const m = chunk.code.match(exportListRe);
const alreadyExported = m && /\b__wbg_init\b/.test(m[1]);
if (!alreadyExported) {
chunk.code = chunk.code.replace(
exportListRe,
"export { $1, __wbg_init, module$$1 as __wasm_url };$2"
);
}
console.log(
`[remove-wasm-tla] Stripped TLA from ${name} (added wbg_init export: ${!alreadyExported})`
);
}
}
},
},
],
},
{
input: "./js/workers/web-client-methods-worker.js",
output: {
dir: `${distDir}/workers`,
format: "es",
sourcemap: true,
inlineDynamicImports: true,
},
plugins: [
rewriteWorkerWasmImport,
resolve(),
commonjs(),
copy({
targets: [
{
src: `${distDir}/assets/*.wasm`,
dest: `${distDir}/workers/assets`,
},
],
verbose: true,
}),
{
name: "wrap-worker-classic",
generateBundle(_, bundle) {
for (const [, chunk] of Object.entries(bundle)) {
if (chunk.type !== "chunk" || !chunk.code) continue;
chunk.code = chunk.code.replace(
/import\.meta\.url/g,
"self.location.href"
);
chunk.code = chunk.code.replace(/import\.meta\.env/g, "undefined");
chunk.code = chunk.code.replace(/^export\s*\{[^}]*\};?\s*$/gm, "");
chunk.code = chunk.code.replace(
/^export\s+default\s+/gm,
"var _default = "
);
chunk.code = chunk.code.replace(
/^export\s+(const|let|var|function|class|async)\s/gm,
"$1 "
);
chunk.code = "(async function() {\n" + chunk.code + "\n})();";
}
},
},
],
},
{
input: "./js/workers/web-client-methods-worker.js",
output: {
dir: `${distDir}/workers`,
format: "es",
sourcemap: true,
entryFileNames: "[name].module.js",
},
plugins: [
rewriteWorkerWasmImport,
resolve(),
commonjs(),
emitWorkerHelpers(`${distDir}/workers`),
],
},
];