| 137 | return { |
| 138 | name: "third-party-licenses", |
| 139 | buildStart() { |
| 140 | try { |
| 141 | execSync("cargo run -p third-party-licenses", { stdio: "inherit" }); |
| 142 | } catch (e) { |
| 143 | throw new Error("Failed to generate third-party licenses", { cause: e }); |
| 144 | } |
| 145 | }, |
| 146 | writeBundle(options) { |
| 147 | copyFileSync(path.resolve(projectRootDir, "third-party-licenses.txt"), path.join(options.dir || "dist", "third-party-licenses.txt")); |
| 148 | }, |
| 149 | }; |
| 150 | } |
| 151 | |
| 152 | // Splits the Wasm binary into parts small enough for Cloudflare Pages' 25 MiB single-file limit, rejoined at runtime by `initWasm()` in `src/utility-functions/wasm-loader.ts`. |
| 153 | // Only active when the `SPLIT_WASM` environment variable is set, which CI does for deployments; local builds keep the single file. |
| 154 | function wasmSplitting(): PluginOption { |
| 155 | const PART_SIZE = 24 * 1024 * 1024; |
| 156 | |
| 157 | let partCount = 1; |
| 158 | |
| 159 | return { |
| 160 | name: "wasm-splitting", |
| 161 | config(_, { command }) { |
| 162 | // Measure the Wasm binary (already built by `cargo run build web` before Vite runs) to decide how many parts are needed |
| 163 | if (command === "build" && process.env.SPLIT_WASM) { |
| 164 | const wasmPath = path.resolve(projectRootDir, "wrapper/pkg/graphite_wasm_wrapper_bg.wasm"); |
| 165 | if (!existsSync(wasmPath)) throw new Error(`SPLIT_WASM is set but the Wasm binary is missing at ${wasmPath}`); |
| 166 | partCount = Math.ceil(statSync(wasmPath).size / PART_SIZE); |
| 167 | } |
| 168 | |
| 169 | // Bake the part count into the bundle so `initWasm()` knows how many parts to fetch and rejoin |
| 170 | return { define: { __WASM_PART_COUNT__: String(partCount) } }; |
| 171 | }, |
| 172 | // Synchronous so it completes before the `serviceWorker` plugin's `writeBundle` collects the precache manifest |
| 173 | writeBundle(options) { |
| 174 | if (partCount <= 1) return; |
| 175 | |
| 176 | const assetsDir = path.join(options.dir || "dist", "assets"); |
| 177 | const wasmFileName = readdirSync(assetsDir).find((name) => name.startsWith("graphite_wasm_wrapper_bg-") && name.endsWith(".wasm")); |
| 178 | if (!wasmFileName) throw new Error("Could not find the emitted Wasm asset to split"); |
| 179 | const wasmPath = path.join(assetsDir, wasmFileName); |
| 180 | |
| 181 | const contents = readFileSync(wasmPath); |
| 182 | if (Math.ceil(contents.length / PART_SIZE) !== partCount) throw new Error("Wasm binary size changed during the build, invalidating the baked-in part count"); |
| 183 | |
| 184 | // Replace the single Wasm file with its parts so only they get deployed and precached |
| 185 | for (let index = 0; index < partCount; index += 1) { |
| 186 | const partName = wasmFileName.replace(/\.wasm$/, `-part${index}.wasm`); |
| 187 | writeFileSync(path.join(assetsDir, partName), contents.subarray(index * PART_SIZE, (index + 1) * PART_SIZE)); |
| 188 | } |
| 189 | rmSync(wasmPath); |
| 190 | }, |
| 191 | }; |
| 192 | } |
| 193 | |
| 194 | function serviceWorker(): PluginOption { |
| 195 | // Files that should never be precached |
| 196 | const EXCLUDED_FILES = new Set(["service-worker.js"]); |