UNPKG

@arcjet/rollup-config

Version:

Custom rollup config for Arcjet projects

221 lines (201 loc) 7.28 kB
import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { isBuiltin } from "node:module"; import replace from "@rollup/plugin-replace"; import typescript from "@rollup/plugin-typescript"; function generateJs(wasm) { const disclaimer = ` /** * This file contains an Arcjet Wasm binary inlined as a base64 * [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs) * with the application/wasm MIME type. * * This was chosen to save on storage space over inlining the file directly as * a Uint8Array, which would take up ~3x the space of the Wasm file. See * https://blobfolio.com/2019/better-binary-batter-mixing-base64-and-uint8array/ * for more details. * * It is then decoded into an ArrayBuffer to be used directly via WebAssembly's * \`compile()\` function in our entry point file. * * This is all done to avoid trying to read or bundle the Wasm asset in various * ways based on the platform or bundler a user is targeting. One example being * that Next.js requires special \`asyncWebAssembly\` webpack config to load our * Wasm file if we don't do this. * * In the future, we hope to do away with this workaround when all bundlers * properly support consistent asset bundling techniques. */ `; return `// @generated by wasm2module - DO NOT EDIT /* eslint-disable */ // @ts-nocheck ${disclaimer} const wasmBase64 = "data:application/wasm;base64,${wasm.toString("base64")}"; /** * Returns a WebAssembly.Module for an Arcjet Wasm binary, decoded from a base64 * Data URL. */ // TODO: Switch back to top-level await when our platforms all support it export async function wasm() { // This uses fetch to decode the wasm data url, but disabling cache so files // larger than 2mb don't fail to parse in the Next.js App Router const wasmDecode = await fetch(wasmBase64, { cache: "no-store" }); const buf = await wasmDecode.arrayBuffer(); // And then we return it as a WebAssembly.Module return WebAssembly.compile(buf); } `; } function wasmToModule() { const idToWasmPath = new Map(); return { name: "base64-wasm", resolveId(source) { if (source.endsWith(".wasm?js")) { // Slice off the `?js` to make it a valid path const filepath = source.slice(0, -3); // Create a "virtual module", prefixed with `\0` as per the Rollup docs, // for our replacement import const id = `\0${filepath.replace(/\.wasm$/, ".js")}`; // Store the actual Wasm path against the virtual module ID. idToWasmPath.set(id, filepath); return id; } return null; }, async load(id) { const wasmPath = idToWasmPath.get(id); // If we resolved this `id` during the `resolveId` phase, generate the // JavaScript file with the base64 Wasm and loading helper if (wasmPath) { const wasm = await fs.promises.readFile(wasmPath); return generateJs(wasm); } return null; }, }; } function removeExtension(filename) { return path.basename(filename, path.extname(filename)); } function isTypeScript(file) { return ( file.isFile() && file.name.endsWith(".ts") && !file.name.endsWith(".d.ts") ); } export function createConfig(root, { plugins = [] } = {}) { const packageJson = fileURLToPath(new URL("./package.json", root)); const pkg = JSON.parse(fs.readFileSync(packageJson)); const dependencies = Object.keys(pkg.dependencies ?? {}); const devDependencies = Object.keys(pkg.devDependencies ?? {}); const peerDependencies = Object.keys(pkg.peerDependencies ?? {}); function isDependency(id) { return dependencies.some((dep) => id.startsWith(dep)); } function isDevDependency(id) { return devDependencies.some((dep) => id.startsWith(dep)); } function isPeerDependency(id) { return peerDependencies.some((dep) => id.startsWith(dep)); } function isBunBuiltin(id) { return id === "bun"; } function isSvelteKitBuiltin(id) { return id === "$env/dynamic/private"; } const rootDir = fileURLToPath(new URL(".", root)); const testDir = fileURLToPath(new URL("test/", root)); const typescriptDirs = [rootDir, testDir]; // Creates a Rollup input entry that can be used with `Object.fromEntries()` function toEntry(file) { return [ path.relative(rootDir, `${file.path}${removeExtension(file.name)}`), path.relative(rootDir, `${file.path}${file.name}`), ]; } // Find all TypeScript files in the specified directories const input = Object.fromEntries( typescriptDirs.flatMap((dir) => { // All the directories might not exist in every project try { const files = fs.readdirSync(dir, { withFileTypes: true }); return files.filter(isTypeScript).map(toEntry); } catch { return []; } }), ); return { input, output: { dir: ".", format: "es", // Hoist transitive imports for faster loading. For more details, see // https://rollupjs.org/faqs/#why-do-additional-imports-turn-up-in-my-entry-chunks-when-code-splitting hoistTransitiveImports: true, // Stop rollup from creating additional chunks that it thinks we need preserveModules: true, }, external(id) { return ( isBuiltin(id) || isDependency(id) || isDevDependency(id) || isPeerDependency(id) || isBunBuiltin(id) || isSvelteKitBuiltin(id) ); }, plugins: [ // Replace always comes first to ensure the replaced values exist for all // other plugins replace({ values: { // We always replace `__ARCJET_SDK_VERSION__` based on the version // in package.json __ARCJET_SDK_VERSION__: pkg.version, }, preventAssignment: true, }), typescript({ tsconfig: "./tsconfig.json", // Override the `excludes` specified in the tsconfig so we don't // generate definition files for our tests exclude: ["node_modules", "test/*.ts"], declaration: true, declarationDir: ".", noEmitOnError: true, }), typescript({ tsconfig: "./tsconfig.json", // Override the `includes` specified in the tsconfig so we don't // generate definition files for our tests include: "test/*.ts", noEmitOnError: true, }), { name: "externalize-wasm", resolveId(id) { // Vercel uses the `.wasm?module` suffix to make WebAssembly available // in their Vercel Functions product. // https://vercel.com/docs/functions/wasm#using-a-webassembly-file // // The Cloudflare docs say they support the `.wasm?module` suffix, but // that seems to no longer be the case with Wrangler 2 so we need to // have separate imports for just the `.wasm` files. // // https://developers.cloudflare.com/workers/runtime-apis/webassembly/javascript/#bundling if (id.endsWith(".wasm") || id.endsWith(".wasm?module")) { return { id, external: true }; } return null; }, }, wasmToModule(), ...plugins, ], }; }