import fs from "node:fs/promises"; import path from "node:path"; import process from "node:process"; import { build } from "esbuild"; const [root, output] = process.argv.slice(2); if (!root || !output) throw new Error("usage: node build.mjs "); const browser = { bundle: true, format: "esm", legalComments: "none", minify: true, platform: "browser", target: "es2022", }; const fxNodeSource = path.join(root, "vendor", "fxnode", "src"); const fxNodeImports = { name: "yawn-fxnode-imports", setup(builder) { builder.onResolve({ filter: /^fxnode$/ }, () => ({ path: path.join(fxNodeSource, "index.ts"), })); builder.onResolve({ filter: /\?worker&url$/ }, () => ({ path: "fxnode-worker-url", namespace: "yawn-fxnode-worker", })); builder.onLoad({ filter: /.*/, namespace: "yawn-fxnode-worker" }, () => ({ contents: 'export default "./fxnode-worker.js";', loader: "js", })); }, }; const workerFiles = new Map([ [ path.join(root, "core", "index.js"), { url: "./worker.js", source: await fs.readFile(path.join(root, "core", "worker.js"), "utf8"), }, ], [ path.join(root, "addons", "handles", "src", "importers", "gltf.ts"), { url: "./importer-worker.js" }, ], [ path.join(root, "addons", "handles", "src", "bvh", "picking.ts"), { url: "./bvh-worker.js" }, ], ]); const workerURLs = { name: "yawn-worker-urls", setup(builder) { builder.onLoad({ filter: /\.(js|ts)$/ }, async ({ path: source }) => { const worker = workerFiles.get(path.normalize(source)); if (!worker) return; let contents = await fs.readFile(source, "utf8"); const transformed = contents.replace( /new Worker\(new URL\("\.\/worker\.(?:js|ts)", import\.meta\.url\),/, `createYawnWorker(new URL(${JSON.stringify(worker.url)}, import.meta.url), ${worker.source ? JSON.stringify(worker.source) : "undefined"},`, ); if (transformed === contents) throw new Error(`worker constructor not found in ${source}`); contents = ` function createYawnWorker(url, embeddedSource, options) { if (embeddedSource) { const moduleUrl = new URL("./pkg/yawn_core.js", url); const source = embeddedSource.replace("./pkg/yawn_core.js", moduleUrl.href); const bootstrap = URL.createObjectURL(new Blob([source], { type: "text/javascript" })); const worker = new Worker(bootstrap, options); URL.revokeObjectURL(bootstrap); return worker; } if (url.origin === globalThis.location.origin) return new Worker(url, options); const source = "import " + JSON.stringify(url.href) + ";"; const bootstrap = URL.createObjectURL(new Blob([source], { type: "text/javascript" })); const worker = new Worker(bootstrap, options); URL.revokeObjectURL(bootstrap); return worker; } ${transformed}`; return { contents, loader: path.extname(source) === ".ts" ? "ts" : "js" }; }); }, }; await Promise.all([ build({ ...browser, entryPoints: [path.join(root, "core", "index.js")], outfile: path.join(output, "core.js"), plugins: [workerURLs], }), build({ ...browser, entryPoints: [path.join(root, "addons", "handles", "src", "index.ts")], external: ["@yawn/core"], outfile: path.join(output, "handles.js"), plugins: [workerURLs], }), build({ ...browser, entryPoints: [path.join(root, "addons", "fxnode_rg", "src", "index.ts")], outfile: path.join(output, "fxnode-rg.js"), plugins: [fxNodeImports], }), build({ ...browser, entryPoints: [path.join(fxNodeSource, "worker", "fxnode.worker.ts")], outfile: path.join(output, "fxnode-worker.js"), }), build({ bundle: false, entryPoints: [path.join(root, "core", "worker.js")], format: "esm", legalComments: "none", minify: true, outfile: path.join(output, "worker.js"), target: "es2022", }), build({ ...browser, entryPoints: [ path.join(root, "addons", "handles", "src", "importers", "worker.ts"), ], outfile: path.join(output, "importer-worker.js"), }), build({ ...browser, entryPoints: [ path.join(root, "addons", "handles", "src", "bvh", "worker.ts"), ], outfile: path.join(output, "bvh-worker.js"), }), ]);