Skip to content

Commit 9809558

Browse files
committed
feat: add omit-api bundler variants
1 parent 1c24db8 commit 9809558

22 files changed

Lines changed: 21047 additions & 1868 deletions

‎README.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,22 @@ this package.
169169

170170
2. Run the build:
171171

172+
The Docker build writes the full upstream npm bundle to `src/bin`. During `npm run build`, the
173+
`split:bundler` prebuild step splits `sqlite3-bundler-friendly.mjs` into one shared core module
174+
and small optional initializer modules. The `/bundler` package subpaths all omit the deprecated
175+
Worker1 API and share the same generated core chunk:
176+
177+
- `@sqlite.org/sqlite-wasm/bundler/core`: core runtime only
178+
- `@sqlite.org/sqlite-wasm/bundler/vtab`: core plus virtual table helpers
179+
- `@sqlite.org/sqlite-wasm/bundler/kvvfs`: core plus kvvfs
180+
- `@sqlite.org/sqlite-wasm/bundler/opfs`: core plus OPFS
181+
- `@sqlite.org/sqlite-wasm/bundler/opfs-wl`: core plus OPFS WebLocks
182+
- `@sqlite.org/sqlite-wasm/bundler/sah-pool`: core plus OPFS SAH Pool
183+
184+
The split files are generated in `src/bin` from the upstream bundler-friendly artifact and are
185+
built into `dist/bundler/*` plus shared chunks. The default package entry remains the full
186+
backwards-compatible bundle.
187+
172188
**Unix (Linux/macOS):**
173189

174190
```bash

‎package.json‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,13 @@
3939
"browser": "./dist/index.mjs"
4040
},
4141
"./package.json": "./package.json",
42-
"./sqlite3.wasm": "./dist/sqlite3.wasm"
42+
"./sqlite3.wasm": "./dist/sqlite3.wasm",
43+
"./bundler/core": "./dist/bundler/core.mjs",
44+
"./bundler/vtab": "./dist/bundler/vtab.mjs",
45+
"./bundler/kvvfs": "./dist/bundler/kvvfs.mjs",
46+
"./bundler/opfs": "./dist/bundler/opfs.mjs",
47+
"./bundler/opfs-wl": "./dist/bundler/opfs-wl.mjs",
48+
"./bundler/sah-pool": "./dist/bundler/sah-pool.mjs"
4349
},
4450
"workspaces": [
4551
"demos/*"
@@ -49,6 +55,8 @@
4955
"README.md"
5056
],
5157
"scripts": {
58+
"split:bundler": "node scripts/split-bundler-friendly-apis.mjs",
59+
"prebuild": "npm run split:bundler",
5260
"test": "vitest",
5361
"test:node": "vitest --project node",
5462
"test:browser": "vitest --project browser",
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
2+
import path from 'node:path';
3+
import { fileURLToPath } from 'node:url';
4+
5+
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
6+
const sourcePath = path.join(rootDir, 'src/bin/sqlite3-bundler-friendly.mjs');
7+
const outputDir = path.join(rootDir, 'src/bin');
8+
9+
const generatedHeader = (sourceFile) =>
10+
`// Generated by scripts/split-bundler-friendly-apis.mjs from ${sourceFile}.\n// Do not edit this file directly.\n\n`;
11+
12+
const initRegistry = `const sqlite3BundlerFriendlyOptionalState =\n (globalThis.__sqlite3BundlerFriendlyOptional ??= {\n initializers: Object.create(null),\n initializersAsync: Object.create(null),\n });\n\nconst sqlite3BundlerFriendlyRegisterOptionalInitializer = (slot, initializer) => {\n if (globalThis.sqlite3ApiBootstrap?.initializers) {\n globalThis.sqlite3ApiBootstrap.initializers.push(initializer);\n } else {\n (sqlite3BundlerFriendlyOptionalState.initializers[slot] ??= []).push(initializer);\n }\n};\n\nconst sqlite3BundlerFriendlyRegisterOptionalAsyncInitializer = (slot, initializer) => {\n if (globalThis.sqlite3ApiBootstrap?.initializersAsync) {\n globalThis.sqlite3ApiBootstrap.initializersAsync.push(initializer);\n } else {\n (sqlite3BundlerFriendlyOptionalState.initializersAsync[slot] ??= []).push(initializer);\n }\n};\n\n`;
13+
14+
const baseHook = `const sqlite3BundlerFriendlyInstallOptionalInitializers = (slot) => {\n const optionalState = globalThis.__sqlite3BundlerFriendlyOptional;\n const initializers = optionalState?.initializers?.[slot];\n if (initializers?.length) {\n globalThis.sqlite3ApiBootstrap.initializers.push(...initializers);\n }\n const initializersAsync = optionalState?.initializersAsync?.[slot];\n if (initializersAsync?.length) {\n globalThis.sqlite3ApiBootstrap.initializersAsync.push(...initializersAsync);\n }\n};\n\n`;
15+
16+
const markerSpecs = {
17+
worker1: 'This file implements the initializer for SQLite\'s "Worker API #1"',
18+
helper: 'This file installs sqlite3.vfs, a namespace of helpers',
19+
vtab: 'This file installs sqlite3.vtab, a namespace of helpers',
20+
kvvfs: 'This file houses the "kvvfs" pieces of the SQLite3 JS API',
21+
opfsShared: 'This file holds code shared by sqlite3-vfs-opfs{,-wl}.c-pp.js',
22+
opfs: 'This file holds the synchronous half of an sqlite3_vfs',
23+
sahpool: 'This file holds a sqlite3_vfs backed by OPFS storage',
24+
opfsWl: 'This file is a reimplementation of the "opfs" VFS',
25+
tail: 'This file is the tail end of the sqlite3-api.js constellation',
26+
};
27+
28+
const findCommentStart = (source, marker) => {
29+
const markerIndex = source.indexOf(marker);
30+
if (markerIndex < 0) {
31+
throw new Error(`Unable to find marker: ${marker}`);
32+
}
33+
const commentStart = source.lastIndexOf('/*', markerIndex);
34+
if (commentStart < 0) {
35+
throw new Error(`Unable to find comment start for marker: ${marker}`);
36+
}
37+
return commentStart;
38+
};
39+
40+
const replaceAll = (value, replacements) => {
41+
let result = value;
42+
for (const [from, to] of replacements) {
43+
result = result.split(from).join(to);
44+
}
45+
return result;
46+
};
47+
48+
const source = await readFile(sourcePath, 'utf8');
49+
const markers = Object.fromEntries(
50+
Object.entries(markerSpecs).map(([name, marker]) => [name, findCommentStart(source, marker)]),
51+
);
52+
53+
const ranges = {
54+
worker1: [markers.worker1, markers.helper],
55+
helper: [markers.helper, markers.vtab],
56+
vtab: [markers.vtab, markers.kvvfs],
57+
kvvfs: [markers.kvvfs, markers.opfsShared],
58+
opfsShared: [markers.opfsShared, markers.opfs],
59+
opfs: [markers.opfs, markers.sahpool],
60+
sahpool: [markers.sahpool, markers.opfsWl],
61+
opfsWl: [markers.opfsWl, markers.tail],
62+
};
63+
64+
for (const [name, [start, end]] of Object.entries(ranges)) {
65+
if (start >= end) {
66+
throw new Error(`Invalid ${name} range: ${start} >= ${end}`);
67+
}
68+
}
69+
70+
const bootstrapAsyncMarker = 'globalThis.sqlite3ApiBootstrap.initializersAsync = [];\n';
71+
if (!source.includes(bootstrapAsyncMarker)) {
72+
throw new Error(`Unable to find bootstrap hook marker: ${bootstrapAsyncMarker.trim()}`);
73+
}
74+
75+
const strippedSource = Object.entries(ranges)
76+
.sort((a, b) => b[1][0] - a[1][0])
77+
.reduce(
78+
(current, [name, [start, end]]) =>
79+
current.slice(0, start) +
80+
`\n/* Optional initializer slot removed by scripts/split-bundler-friendly-apis.mjs: ${name} */\n` +
81+
`sqlite3BundlerFriendlyInstallOptionalInitializers('${name}');\n` +
82+
current.slice(end),
83+
source,
84+
)
85+
.replace(bootstrapAsyncMarker, bootstrapAsyncMarker + baseHook);
86+
87+
const outputFileNames = {
88+
worker1: 'sqlite3-bundler-optional-worker1.mjs',
89+
helper: 'sqlite3-bundler-optional-vfs-helper.mjs',
90+
vtab: 'sqlite3-bundler-optional-vtab.mjs',
91+
kvvfs: 'sqlite3-bundler-optional-kvvfs.mjs',
92+
opfsShared: 'sqlite3-bundler-optional-opfs-shared.mjs',
93+
opfs: 'sqlite3-bundler-optional-opfs.mjs',
94+
sahpool: 'sqlite3-bundler-optional-sah-pool.mjs',
95+
opfsWl: 'sqlite3-bundler-optional-opfs-wl.mjs',
96+
};
97+
98+
const toOptionalModule = (slot, chunk) =>
99+
generatedHeader('sqlite3-bundler-friendly.mjs') +
100+
initRegistry +
101+
replaceAll(chunk, [
102+
[
103+
'globalThis.sqlite3ApiBootstrap.initializers.push(',
104+
`sqlite3BundlerFriendlyRegisterOptionalInitializer('${slot}', `,
105+
],
106+
[
107+
'globalThis.sqlite3ApiBootstrap.initializersAsync.push(',
108+
`sqlite3BundlerFriendlyRegisterOptionalAsyncInitializer('${slot}', `,
109+
],
110+
]);
111+
112+
await mkdir(outputDir, { recursive: true });
113+
114+
await writeFile(
115+
path.join(outputDir, 'sqlite3-bundler-friendly.core.mjs'),
116+
generatedHeader('sqlite3-bundler-friendly.mjs') + strippedSource,
117+
);
118+
119+
await Promise.all(
120+
Object.entries(ranges)
121+
.filter(([name]) => name !== 'worker1')
122+
.map(([name, [start, end]]) =>
123+
writeFile(
124+
path.join(outputDir, outputFileNames[name]),
125+
toOptionalModule(name, source.slice(start, end)),
126+
),
127+
),
128+
);

0 commit comments

Comments
 (0)