import esbuild from "esbuild"; import process from "process"; import builtins from "builtin-modules"; import { readFileSync } from 'fs'; import { join } from 'path'; import { polyfillNode } from 'esbuild-plugin-polyfill-node' import { sassPlugin } from 'esbuild-sass-plugin' const banner = `/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin */ `; // Plugin to handle WASM files const wasmPlugin = { name: 'wasm', setup(build) { // Handle .wasm files build.onResolve({ filter: /\.wasm$/ }, args => { if (args.resolveDir === '') return; return { path: join(args.resolveDir, args.path), namespace: 'wasm-binary', }; }); build.onLoad({ filter: /\.wasm$/, namespace: 'wasm-binary' }, async (args) => { const contents = readFileSync(args.path); const wasmBase64 = contents.toString('base64'); return { contents: ` const wasmBase64 = "${wasmBase64}"; const wasmBinary = Uint8Array.from(atob(wasmBase64), c => c.charCodeAt(0)); export default wasmBinary; `, loader: 'js', }; }); // Handle SQLite bundler-friendly module build.onResolve({ filter: /sqlite3-bundler-friendly\.mjs$/ }, args => { if (args.path.startsWith('@sqlite.org/sqlite-wasm')) { // Resolve to the actual file path in node_modules return { path: join(process.cwd(), 'node_modules', args.path), namespace: 'sqlite-bundler', }; } return { path: join(args.resolveDir, args.path), namespace: 'sqlite-bundler', }; }); build.onLoad({ filter: /sqlite3-bundler-friendly\.mjs$/, namespace: 'sqlite-bundler' }, async (args) => { const moduleContents = readFileSync(args.path, 'utf8'); const wasmPath = args.path.replace('sqlite3-bundler-friendly.mjs', 'sqlite3.wasm'); const wasmContents = readFileSync(wasmPath); const wasmBase64 = wasmContents.toString('base64'); // Replace the findWasmBinary function to return embedded WASM const modifiedContents = moduleContents // Replace the entire findWasmBinary function .replace( /function\s+findWasmBinary\s*\(\s*\)\s*\{[\s\S]*?return\s+new\s+URL\s*\(\s*['"`]sqlite3\.wasm['"`]\s*,\s*import\.meta\.url\s*\)\.href\s*;?\s*\}/g, `function findWasmBinary() { return "data:application/wasm;base64,${wasmBase64}"; }` ) // Also replace any direct new URL(...) patterns as fallback .replace( /new\s+URL\s*\(\s*['"`]sqlite3\.wasm['"`]\s*,\s*import\.meta\.url\s*\)\.href/g, `"data:application/wasm;base64,${wasmBase64}"` ) .replace( /new\s+URL\s*\(\s*['"`]sqlite3\.wasm['"`]\s*,\s*import\.meta\.url\s*\)/g, `"data:application/wasm;base64,${wasmBase64}"` ); return { contents: modifiedContents, loader: 'js', }; }); } }; // Plugin to inject worker code const workerPlugin = { name: 'worker', setup(build) { // Handle sqlocal worker code build.onResolve({ filter: /^virtual:sqlocal-worker-code$/ }, args => ({ path: args.path, namespace: 'sqlocal-worker-code', })); build.onLoad({ filter: /.*/, namespace: 'sqlocal-worker-code' }, async () => { // Create a plugin for the worker that resolves virtual imports const workerVirtualPlugin = { name: 'worker-virtual', setup(build) { // Handle virtual WASM URL for wa-sqlite build.onResolve({ filter: /^virtual:wa-sqlite-wasm-url$/ }, args => ({ path: args.path, namespace: 'wa-sqlite-wasm-url', })); build.onLoad({ filter: /.*/, namespace: 'wa-sqlite-wasm-url' }, async () => { const wasmPath = join(process.cwd(), 'node_modules/wa-sqlite/dist/wa-sqlite-async.wasm'); const wasmContents = readFileSync(wasmPath); const wasmBase64 = wasmContents.toString('base64'); const wasmDataUrl = `data:application/wasm;base64,${wasmBase64}`; return { contents: `export default ${JSON.stringify(wasmDataUrl)};`, loader: 'js', }; }); } }; // Build sqlocal worker code const result = await esbuild.build({ entryPoints: ['src/modules/database/sqlocal/sqlocalWorkerDatabase.ts'], bundle: true, write: false, format: 'iife', target: 'es2020', external: ['fs', 'path', 'obsidian'], plugins: [wasmPlugin, workerVirtualPlugin, polyfillNode({ })], minify: process.argv[2] === 'production', define: { 'process.env.NODE_ENV': JSON.stringify(process.argv[2] === 'production' ? 'production' : 'development'), '__dirname': '"/"' } }); return { contents: ` const sqlocalWorkerCode = ${JSON.stringify(result.outputFiles[0].text)}; export default sqlocalWorkerCode; `, loader: 'js', }; }); // Handle virtual WASM URL for wa-sqlite build.onResolve({ filter: /^virtual:wa-sqlite-wasm-url$/ }, args => ({ path: args.path, namespace: 'wa-sqlite-wasm-url', })); build.onLoad({ filter: /.*/, namespace: 'wa-sqlite-wasm-url' }, async () => { const wasmPath = join(process.cwd(), 'node_modules/wa-sqlite/dist/wa-sqlite-async.wasm'); const wasmContents = readFileSync(wasmPath); const wasmBase64 = wasmContents.toString('base64'); const wasmDataUrl = `data:application/wasm;base64,${wasmBase64}`; return { contents: `export default ${JSON.stringify(wasmDataUrl)};`, loader: 'js', }; }); }, }; const prod = (process.argv[2] === "production"); const context = await esbuild.context({ banner: { js: banner, }, entryPoints: ["src/main.ts"], bundle: true, external: [ "obsidian", "electron", "@codemirror/autocomplete", "@codemirror/collab", "@codemirror/commands", "@codemirror/language", "@codemirror/lint", "@codemirror/search", "@codemirror/state", "@codemirror/view", "@lezer/common", "@lezer/highlight", "@lezer/lr", ...builtins ], format: "cjs", target: "ES2020", logLevel: "info", sourcemap: prod ? false : "inline", treeShaking: true, outfile: "main.js", loader: { '.svg': 'text' }, plugins: [ wasmPlugin, workerPlugin ], define: { 'process.env.NODE_ENV': JSON.stringify(prod ? 'production' : 'development'), '__dirname': '"/"' } }); const stylesContext = await esbuild.context({ entryPoints: ['src/styles/main.scss'], outfile: './styles.css', bundle: true, minify: true, sourcemap: true, plugins: [sassPlugin({ sourceMap: true })], logLevel: 'info' }) if (prod) { await context.rebuild(); await stylesContext.rebuild(); process.exit(0); } else { await Promise.all([ context.watch(), stylesContext.watch() ]) }