diff --git a/.changeset/vite-web-generator.md b/.changeset/vite-web-generator.md
new file mode 100644
index 00000000..49a47ea6
--- /dev/null
+++ b/.changeset/vite-web-generator.md
@@ -0,0 +1,6 @@
+---
+'@node-core/doc-kit': minor
+---
+
+Make the `web` generator bundler-neutral through a custom adapter contract,
+with Vite 8.1 provided as the default adapter.
diff --git a/.prettierignore b/.prettierignore
index e3d545c2..4cc96956 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -12,6 +12,7 @@ out/
# Docs-site content, assembled by scripts/build-docs-content.mjs
www/content/
+www/out/
# Generated Files
src/generators/metadata/maps/mdn.json
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 67be361c..79d5205b 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -10,7 +10,11 @@ export default defineConfig([
importX.flatConfigs.recommended,
react.configs.recommended,
{
- ignores: ['out/', 'src/generators/api-links/__tests__/fixtures/'],
+ ignores: [
+ 'www/out',
+ 'out/',
+ 'src/generators/api-links/__tests__/fixtures/',
+ ],
},
{
files: ['**/*.{mjs,jsx,js}'],
@@ -97,10 +101,6 @@ export default defineConfig([
languageOptions: {
globals: {
...globals.browser,
- // SERVER and CLIENT denote server-only and client-only
- // codepaths in our web generator
- CLIENT: 'readonly',
- SERVER: 'readonly',
},
ecmaVersion: 'latest',
},
diff --git a/package-lock.json b/package-lock.json
index e0ce748d..e258460f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,12 +9,13 @@
"version": "1.4.3",
"dependencies": {
"@actions/core": "^3.0.0",
+ "@fontsource-variable/open-sans": "^5.3.0",
+ "@fontsource/ibm-plex-mono": "^5.3.0",
"@heroicons/react": "^2.2.0",
"@node-core/rehype-shiki": "^1.4.2",
"@node-core/ui-components": "^1.7.2",
"@orama/orama": "^3.1.18",
"@orama/ui": "^1.5.4",
- "@rollup/plugin-virtual": "^3.0.2",
"@swc/html-wasm": "^1.15.43",
"acorn": "^8.17.0",
"commander": "^15.0.0",
@@ -25,7 +26,6 @@
"glob-parent": "^6.0.2",
"hast-util-to-string": "^3.0.1",
"hastscript": "^9.0.1",
- "lightningcss-wasm": "^1.32.0",
"mdast-util-slice-markdown": "^2.0.1",
"oxc-parser": "^0.139.0",
"piscina": "^5.2.0",
@@ -41,7 +41,6 @@
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"remark-stringify": "^11.0.0",
- "rolldown": "1.1.3",
"semver": "^7.8.5",
"shiki": "^4.3.0",
"tinyglobby": "^0.2.17",
@@ -52,6 +51,7 @@
"unist-util-remove": "^4.0.0",
"unist-util-select": "^5.1.0",
"unist-util-visit": "^5.1.0",
+ "vite": "~8.1.5",
"yaml": "^2.9.0"
},
"bin": {
@@ -781,6 +781,24 @@
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
"license": "MIT"
},
+ "node_modules/@fontsource-variable/open-sans": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@fontsource-variable/open-sans/-/open-sans-5.3.0.tgz",
+ "integrity": "sha512-VOfhZfLIOBVa6K/dTEu7scqQXYdGa4jBX8quFpQ0S6574B+W2EOBJf9PkqyVtMEI7WWIWNEbpvPc3vY1mN2mmQ==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@fontsource/ibm-plex-mono": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-mono/-/ibm-plex-mono-5.3.0.tgz",
+ "integrity": "sha512-eTgnZjZEGk1QtD3ZstF+Vclo2HLAni8YMy34/DxllwZvyz1lR/1RF/xTiAquOBO7MvqBx8D2Ig2WCPMVfdZu7Q==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
"node_modules/@heroicons/react": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
@@ -1940,9 +1958,9 @@
}
},
"node_modules/@oxc-project/types": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz",
- "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==",
+ "version": "0.139.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
+ "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
@@ -2766,9 +2784,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz",
- "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
+ "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==",
"cpu": [
"arm64"
],
@@ -2782,9 +2800,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz",
- "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz",
+ "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==",
"cpu": [
"arm64"
],
@@ -2798,9 +2816,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz",
- "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz",
+ "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==",
"cpu": [
"x64"
],
@@ -2814,9 +2832,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz",
- "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz",
+ "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==",
"cpu": [
"x64"
],
@@ -2830,9 +2848,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz",
- "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz",
+ "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==",
"cpu": [
"arm"
],
@@ -2846,12 +2864,15 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz",
- "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz",
+ "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==",
"cpu": [
"arm64"
],
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -2862,12 +2883,15 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz",
- "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz",
+ "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==",
"cpu": [
"arm64"
],
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -2878,12 +2902,15 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz",
- "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz",
+ "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==",
"cpu": [
"ppc64"
],
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -2894,12 +2921,15 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz",
- "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz",
+ "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==",
"cpu": [
"s390x"
],
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -2910,12 +2940,15 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz",
- "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz",
+ "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==",
"cpu": [
"x64"
],
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -2926,12 +2959,15 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz",
- "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz",
+ "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==",
"cpu": [
"x64"
],
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -2942,9 +2978,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz",
- "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz",
+ "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==",
"cpu": [
"arm64"
],
@@ -2958,9 +2994,9 @@
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz",
- "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz",
+ "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==",
"cpu": [
"wasm32"
],
@@ -2976,9 +3012,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz",
- "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz",
+ "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==",
"cpu": [
"arm64"
],
@@ -2992,9 +3028,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz",
- "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz",
+ "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==",
"cpu": [
"x64"
],
@@ -3013,23 +3049,6 @@
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"license": "MIT"
},
- "node_modules/@rollup/plugin-virtual": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz",
- "integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==",
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
- },
- "peerDependenciesMeta": {
- "rollup": {
- "optional": true
- }
- }
- },
"node_modules/@shikijs/core": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.3.1.tgz",
@@ -3249,7 +3268,7 @@
"version": "24.10.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.15.tgz",
"integrity": "sha512-BgjLoRuSr0MTI5wA6gMw9Xy0sFudAaUuvrnjgGx9wZ522fYYLA5SYJ+1Y30vTcJEG+DRCyDHx/gzQVfofYzSdg==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
@@ -4738,6 +4757,15 @@
"node": ">=8"
}
},
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -7044,17 +7072,47 @@
"node": ">= 0.8.0"
}
},
- "node_modules/lightningcss-wasm": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-wasm/-/lightningcss-wasm-1.32.0.tgz",
- "integrity": "sha512-SteAkCtRuSCDYPGHKhLV/dDs5Bk+7I4QUxWxfk4xwsTI1rQk8MQyYtpGcd3NECsUGzK0q2/KqoVS+YHCqKHUTQ==",
- "bundleDependencies": [
- "napi-wasm"
- ],
+ "node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
"license": "MPL-2.0",
"dependencies": {
- "napi-wasm": "^1.0.1"
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
},
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.33.0",
+ "lightningcss-darwin-arm64": "1.33.0",
+ "lightningcss-darwin-x64": "1.33.0",
+ "lightningcss-freebsd-x64": "1.33.0",
+ "lightningcss-linux-arm-gnueabihf": "1.33.0",
+ "lightningcss-linux-arm64-gnu": "1.33.0",
+ "lightningcss-linux-arm64-musl": "1.33.0",
+ "lightningcss-linux-x64-gnu": "1.33.0",
+ "lightningcss-linux-x64-musl": "1.33.0",
+ "lightningcss-win32-arm64-msvc": "1.33.0",
+ "lightningcss-win32-x64-msvc": "1.33.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
"node": ">= 12.0.0"
},
@@ -7063,10 +7121,217 @@
"url": "https://opencollective.com/parcel"
}
},
- "node_modules/lightningcss-wasm/node_modules/napi-wasm": {
- "version": "1.1.3",
- "inBundle": true,
- "license": "MIT"
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
+ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
+ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
+ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
+ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
+ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
+ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
+ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
+ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
+ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
+ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
},
"node_modules/lint-staged": {
"version": "17.0.8",
@@ -8450,6 +8715,24 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
+ "node_modules/nanoid": {
+ "version": "3.3.16",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
+ "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
"node_modules/napi-postinstall": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
@@ -8715,15 +8998,6 @@
"@oxc-parser/binding-win32-x64-msvc": "0.139.0"
}
},
- "node_modules/oxc-parser/node_modules/@oxc-project/types": {
- "version": "0.139.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
- "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/Boshen"
- }
- },
"node_modules/p-filter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz",
@@ -8908,7 +9182,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
@@ -8987,6 +9260,34 @@
"node": ">= 0.4"
}
},
+ "node_modules/postcss": {
+ "version": "8.5.22",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.22.tgz",
+ "integrity": "sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.16",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
"node_modules/preact": {
"version": "10.29.4",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.29.4.tgz",
@@ -9611,12 +9912,12 @@
"license": "MIT"
},
"node_modules/rolldown": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz",
- "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
+ "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==",
"license": "MIT",
"dependencies": {
- "@oxc-project/types": "=0.137.0",
+ "@oxc-project/types": "=0.139.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -9626,21 +9927,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.1.3",
- "@rolldown/binding-darwin-arm64": "1.1.3",
- "@rolldown/binding-darwin-x64": "1.1.3",
- "@rolldown/binding-freebsd-x64": "1.1.3",
- "@rolldown/binding-linux-arm-gnueabihf": "1.1.3",
- "@rolldown/binding-linux-arm64-gnu": "1.1.3",
- "@rolldown/binding-linux-arm64-musl": "1.1.3",
- "@rolldown/binding-linux-ppc64-gnu": "1.1.3",
- "@rolldown/binding-linux-s390x-gnu": "1.1.3",
- "@rolldown/binding-linux-x64-gnu": "1.1.3",
- "@rolldown/binding-linux-x64-musl": "1.1.3",
- "@rolldown/binding-openharmony-arm64": "1.1.3",
- "@rolldown/binding-wasm32-wasi": "1.1.3",
- "@rolldown/binding-win32-arm64-msvc": "1.1.3",
- "@rolldown/binding-win32-x64-msvc": "1.1.3"
+ "@rolldown/binding-android-arm64": "1.1.5",
+ "@rolldown/binding-darwin-arm64": "1.1.5",
+ "@rolldown/binding-darwin-x64": "1.1.5",
+ "@rolldown/binding-freebsd-x64": "1.1.5",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.1.5",
+ "@rolldown/binding-linux-arm64-gnu": "1.1.5",
+ "@rolldown/binding-linux-arm64-musl": "1.1.5",
+ "@rolldown/binding-linux-ppc64-gnu": "1.1.5",
+ "@rolldown/binding-linux-s390x-gnu": "1.1.5",
+ "@rolldown/binding-linux-x64-gnu": "1.1.5",
+ "@rolldown/binding-linux-x64-musl": "1.1.5",
+ "@rolldown/binding-openharmony-arm64": "1.1.5",
+ "@rolldown/binding-wasm32-wasi": "1.1.5",
+ "@rolldown/binding-win32-arm64-msvc": "1.1.5",
+ "@rolldown/binding-win32-x64-msvc": "1.1.5"
}
},
"node_modules/run-parallel": {
@@ -9964,6 +10265,15 @@
"node": ">= 12"
}
},
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/space-separated-tokens": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
@@ -10582,7 +10892,7 @@
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/unified": {
@@ -10902,6 +11212,97 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/vite": {
+ "version": "8.1.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz",
+ "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==",
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.5",
+ "postcss": "^8.5.17",
+ "rolldown": "~1.1.5",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.3.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/web-namespaces": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
diff --git a/package.json b/package.json
index b043ceb6..5a4d144b 100644
--- a/package.json
+++ b/package.json
@@ -62,12 +62,13 @@
},
"dependencies": {
"@actions/core": "^3.0.0",
+ "@fontsource-variable/open-sans": "^5.3.0",
+ "@fontsource/ibm-plex-mono": "^5.3.0",
"@heroicons/react": "^2.2.0",
"@node-core/rehype-shiki": "^1.4.2",
"@node-core/ui-components": "^1.7.2",
"@orama/orama": "^3.1.18",
"@orama/ui": "^1.5.4",
- "@rollup/plugin-virtual": "^3.0.2",
"@swc/html-wasm": "^1.15.43",
"acorn": "^8.17.0",
"commander": "^15.0.0",
@@ -78,7 +79,6 @@
"glob-parent": "^6.0.2",
"hast-util-to-string": "^3.0.1",
"hastscript": "^9.0.1",
- "lightningcss-wasm": "^1.32.0",
"mdast-util-slice-markdown": "^2.0.1",
"oxc-parser": "^0.139.0",
"piscina": "^5.2.0",
@@ -94,7 +94,6 @@
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"remark-stringify": "^11.0.0",
- "rolldown": "1.1.3",
"semver": "^7.8.5",
"shiki": "^4.3.0",
"tinyglobby": "^0.2.17",
@@ -105,6 +104,7 @@
"unist-util-remove": "^4.0.0",
"unist-util-select": "^5.1.0",
"unist-util-visit": "^5.1.0",
+ "vite": "~8.1.5",
"yaml": "^2.9.0"
}
}
diff --git a/src/generators/web/README.md b/src/generators/web/README.md
index bd9b33b7..22af6691 100644
--- a/src/generators/web/README.md
+++ b/src/generators/web/README.md
@@ -1,27 +1,31 @@
# `web` Generator
-The `web` generator transforms JSX AST entries into complete web bundles, producing server-side rendered HTML pages, client-side JavaScript with code splitting, and bundled CSS styles.
+The `web` generator transforms JSX AST entries into complete web bundles. Its
+bundler adapter builds server-rendered HTML and client-side JavaScript, CSS, and
+imported assets, then writes the complete static site to `output`. Vite is the
+default adapter, but projects can supply an adapter for webpack or another
+bundler. The generator is output-only and does not return an in-memory copy of
+its HTML or CSS.
## Configuring
The `web` generator accepts the following configuration options:
-| Name | Type | Default | Description |
-| ----------------- | --------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
-| `output` | `string` | - | The directory where HTML, JavaScript, and CSS files will be written |
-| `templatePath` | `string` | `'template.html'` | Path to the HTML template file |
-| `project` | `string` | `'Node.js'` | Project name used in page titles and the version selector |
-| `title` | `string` | `'{project} v{version} Documentation'` | Title template for HTML pages (supports `{project}`, `{version}`) |
-| `useAbsoluteURLs` | `boolean` | `false` | When `true`, all internal links use absolute URLs based on `baseURL` |
-| `editURL` | `string` | `'${GITHUB_EDIT_URL}/doc/api{path}.md'` | URL template for "edit this page" links |
-| `pageURL` | `string` | `'{baseURL}/latest-{version}/api{path}.html'` | URL template for documentation page links |
-| `remoteConfigUrl` | `string` | `'https://nodejs.org/site.json'` | URL fetched client-side at runtime for remote site config (currently used to power the announcement banner) |
-| `head` | `object` | See below | Configurable ``, ``, and raw markup for the document head |
-| `lightningcss` | `object` | `{}` | Options spread into LightningCSS while bundling CSS (see below) |
-| `imports` | `object` | See below | Object mapping `#theme/` aliases to component paths for customization |
-| `virtualImports` | `object` | `{}` | Additional virtual module mappings merged into the build |
-| `components` | `object` | `{}` | Maps JSX tag names to component imports, enabling JSX-in-MDX (see below) |
-| `rolldown` | `object` | `{}` | Options merged into the Rolldown build — extra plugins, etc. (see below) |
+| Name | Type | Default | Description |
+| ----------------- | ------------ | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
+| `output` | `string` | Required | The directory where HTML and bundled client output are written |
+| `templatePath` | `string` | `'template.html'` | Path to the HTML template file |
+| `project` | `string` | `'Node.js'` | Project name used in page titles and the version selector |
+| `title` | `string` | `'{project} v{version} Documentation'` | Title template for HTML pages (supports `{project}`, `{version}`) |
+| `useAbsoluteURLs` | `boolean` | `false` | When `true`, all internal links use absolute URLs based on `baseURL` |
+| `editURL` | `string` | `'${GITHUB_EDIT_URL}/doc/api{path}.md'` | URL template for "edit this page" links |
+| `pageURL` | `string` | `'{baseURL}/latest-{version}/api{path}.html'` | URL template for documentation page links |
+| `remoteConfigUrl` | `string` | `'https://nodejs.org/site.json'` | URL fetched client-side at runtime for remote site config (currently used to power the announcement banner) |
+| `head` | `object` | See below | Configurable ``, ``, and raw markup for the document head |
+| `imports` | `object` | See below | Object mapping `#theme/` aliases to component paths for customization |
+| `virtualImports` | `object` | `{}` | Additional virtual module mappings supplied to the server and client builds |
+| `components` | `object` | `{}` | Maps JSX tag names to component imports, enabling JSX-in-MDX (see below) |
+| `bundler` | `WebBundler` | Vite adapter | Adapter that renders server entries and writes the client and HTML output (see below) |
### `head`
@@ -64,96 +68,113 @@ export default {
```
> Structural and theme-bound tags are emitted by the template itself rather than
-> via `head`: `og:title` (mirrors the per-page title), `og:type`, and the font
-> preconnects/stylesheet the bundled UI components rely on.
+> via `head`, including `og:title` (which mirrors the per-page title) and
+> `og:type`. The UI stylesheet bundles its fonts locally.
-### Custom LightningCSS options
+### Bundler adapters
-The `lightningcss` object is spread directly into [LightningCSS][lightningcss]
-while CSS is bundled, so any of its options — `visitor` (custom plugins),
-`customAtRules`, `targets`, `drafts`, and so on — are supported. The generator
-manages `filename`, `code`, `cssModules`, and `resolver`, so those are ignored.
+The `bundler` option accepts a small Doc Kit adapter rather than configuration
+for a particular build system:
+
+| Method | Responsibility |
+| ------------ | ----------------------------------------------------------------------------------------- |
+| `getEntryId` | Return the module identifier placed in the populated HTML for an API name |
+| `render` | Bundle and execute the server `entries`, returning a `Map` of API name to rendered HTML |
+| `build` | Bundle the client `entries`, process the populated `pages`, and write the complete output |
+
+Both `render` and `build` receive `{ entries, virtualImports, config }`; `build`
+also receives `pages`. Entry maps use `${api}.jsx` keys, rendered server results
+use `api` keys, and page maps use output-relative HTML file names. `config` is
+the resolved web configuration.
+
+The adapter must compile the generated Preact JSX and CSS imports and resolve
+the supplied theme aliases and virtual modules. The generated `#theme/config`
+module exports `server` as `true` for the server build and `false` for the
+client build.
+
+A webpack integration can live entirely in project configuration without
+adding webpack to Doc Kit:
+
+```js
+// webpack-bundler.mjs
+export const createWebpackBundler = webpackOptions => ({
+ getEntryId: api => `virtual:doc-kit/client/${api}.jsx`,
+
+ async render({ entries, virtualImports, config }) {
+ // Materialize or load the in-memory modules, run webpack's server target,
+ // execute each emitted entry, and return Map.
+ },
+
+ async build({ entries, virtualImports, pages, config }) {
+ // Run webpack's browser target, inject its emitted assets into `pages`,
+ // and write the HTML and assets to config.output.
+ },
+});
+```
```js
// doc-kit.config.mjs
+import { createWebpackBundler } from './webpack-bundler.mjs';
+
export default {
web: {
- lightningcss: {
- customAtRules: {
- mixin: { prelude: '', body: 'style-block' },
- },
- visitor: {
- Color(color) {
- // e.g. transform every color through a design-token map
- return color;
- },
- },
- },
+ bundler: createWebpackBundler({
+ // Project-owned webpack configuration.
+ }),
},
};
```
-To apply more than one visitor, compose them with LightningCSS's
-`composeVisitors` helper and pass the result as `visitor`.
-
-[lightningcss]: https://lightningcss.dev/transforms.html
-
-### Custom Rolldown options
-
-The `rolldown` object is merged into the [Rolldown][rolldown] `build` call used
-for **both** the client and server bundles, so you can register extra plugins,
-inject compile-time constants, add module aliases, or set any other Rolldown
-option. The same config is applied to both builds — branch inside a plugin (or
-read the `SERVER`/`CLIENT` defines) if you need per-target behavior.
+### Vite adapter
-The merge follows these rules:
-
-- **`plugins`** are registered after the built-in virtual-module plugin (so
- they see the in-memory entry modules) and before the CSS loader.
-- **`transform.define`** and **`resolve.alias`** are merged key-by-key, so you
- can add entries without dropping the generator's. The built-in `SERVER`/
- `CLIENT` defines and the `react`/`react-dom` → `preact/compat` aliases
- (plus anything from `imports`) always win.
-- **All other options** (e.g. `output.minify`, `treeshake`, `external`,
- `platform`, `logLevel`) override the generator's defaults.
-- **`input`** and the built-in plugins are managed by the generator and cannot
- be overridden.
-
-> Functions (plugins, hooks, alias resolvers, etc.) are allowed here because the
-> `web` generator runs entirely on the main thread. Unlike the chunked
-> generators, its config is never serialized to a worker, so values that can't
-> be structured-cloned are safe. Keep this in mind if `web` ever gains parallel
-> processing: function-valued options would then need a serializable form.
+When `bundler` is omitted, the generator imports and uses
+`createViteBundler()` automatically. To customize Vite, import the adapter
+directly and pass Vite's `UserConfig` to it:
```js
// doc-kit.config.mjs
-import myRolldownPlugin from './my-rolldown-plugin.mjs';
+import { createViteBundler } from '@node-core/doc-kit/src/generators/web/bundlers/vite.mjs';
+import myVitePlugin from './my-vite-plugin.mjs';
export default {
web: {
- rolldown: {
- // Register additional plugins (run after the built-in ones).
- plugins: [myRolldownPlugin()],
-
- // Inject extra compile-time constants (merged with SERVER/CLIENT).
- transform: {
- define: {
- 'process.env.ANALYTICS_ID': JSON.stringify('UA-XXXXX'),
- },
+ bundler: createViteBundler({
+ plugins: [myVitePlugin()],
+ define: {
+ 'process.env.ANALYTICS_ID': JSON.stringify('UA-XXXXX'),
},
-
- // Extend module resolution with custom aliases.
resolve: {
alias: {
'@components': './src/components',
},
},
- },
+ css: {
+ lightningcss: {
+ targets: {
+ chrome: 100 << 16,
+ },
+ },
+ },
+ }),
},
};
```
-[rolldown]: https://rolldown.rs/
+The generator owns the fields required to coordinate its builds: config-file
+loading, app type and base, virtual inputs, Preact compatibility aliases and
+automatic JSX runtime, the Lightning CSS transformer, output/write mode, SSR
+format and temporary output, and SSR dependency bundling. Values supplied for
+those fields are replaced after configuration is merged. User plugins are
+registered after the generator's virtual-module plugin; other Vite options are
+preserved.
+
+Vite manifests are optional. Pass `build: { manifest: true }` or a manifest file
+name to `createViteBundler` when another tool needs one. The generated HTML
+already references the correct hashed scripts, stylesheets, imported assets,
+and module preloads.
+
+Function-valued plugins and hooks are supported because the `web` generator
+runs on the main thread and does not serialize the bundler to a worker.
### Default `imports`
@@ -254,6 +275,7 @@ All scalar (non-object) configuration values are automatically exported. The def
| `baseURL` | `string` | Base URL for the documentation site (used when `useAbsoluteURLs` is `true`) |
| `languageDisplayNameMap` | `Map` | Shiki language alias → display name map for code blocks |
| `remoteConfigUrl` | `string` | Mirrors the configured `remoteConfigUrl` (fetched client-side by `RemoteLoadableBanner` to load announcement banners) |
+| `server` | `boolean` | Whether the current bundle is the server build |
### Usage in custom components
@@ -300,9 +322,9 @@ The HTML template file (set via `templatePath`) uses JavaScript template literal
| ------------------ | -------- | ----------------------------------------------------------------- |
| `title` | `string` | Fully resolved page title (e.g. `'File system \| Node.js v22.x'`) |
| `dehydrated` | `string` | Server-rendered HTML for the page content |
-| `importMap` | `string` | JSON import map for client-side module resolution |
-| `entrypoint` | `string` | Client-side entry point filename with cache-bust query |
+| `entrypoint` | `string` | Adapter-provided module identifier for this page's hydration |
| `speculationRules` | `string` | Speculation rules JSON for prefetching |
+| `themeScript` | `string` | Inline script that applies the saved theme before paint |
| `root` | `string` | Relative or absolute path to the site root |
| `metadata` | `object` | Full page metadata (frontmatter, path, heading, etc.) |
| `config` | `object` | The resolved web generator configuration |
@@ -312,8 +334,9 @@ Since the template supports arbitrary JS expressions, you can use conditionals a
```html
${title}
-
-
+
```
+
+The configured adapter processes each populated page. It must replace or
+resolve `entrypoint`, include that page's scripts and stylesheets, and write the
+final HTML.
diff --git a/src/generators/web/__tests__/generate.test.mjs b/src/generators/web/__tests__/generate.test.mjs
index e5f70c76..94583d07 100644
--- a/src/generators/web/__tests__/generate.test.mjs
+++ b/src/generators/web/__tests__/generate.test.mjs
@@ -1,4 +1,7 @@
import assert from 'node:assert/strict';
+import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+import { dirname, join } from 'node:path';
import { describe, it } from 'node:test';
import { jsx, toJs } from 'estree-util-to-js';
@@ -6,6 +9,7 @@ import { jsx, toJs } from 'estree-util-to-js';
import { setConfig } from '../../../utils/configuration/index.mjs';
import buildContent from '../../jsx-ast/utils/buildContent.mjs';
import { buildNotFoundPage } from '../../jsx-ast/utils/synthetic/404.mjs';
+import { createViteBundler } from '../bundlers/vite.mjs';
import { generate } from '../generate.mjs';
/**
@@ -44,33 +48,50 @@ const createEntry = (api, name) => {
};
};
-describe('web generate', () => {
- it('omits View As links for synthetic pages only', async () => {
- await setConfig({});
+const createTestConfiguration = async context => {
+ const output = await mkdtemp(join(tmpdir(), 'doc-kit-web-test-'));
+ context.after(() => rm(output, { recursive: true, force: true }));
+
+ const config = await setConfig({
+ output,
+ version: 'v22.0.0',
+ changelog: [],
+ generators: {
+ web: {},
+ },
+ });
+ return { config, output };
+};
+
+describe('web generate', () => {
+ it('writes bundled HTML and omits View As links for synthetic pages', async context => {
+ const { output } = await createTestConfiguration(context);
const fs = createEntry('fs', 'File system');
+ fs.path = '/api/fs';
const notFoundPage = buildNotFoundPage();
const contents = await Promise.all([
buildContent([fs], fs),
buildContent(notFoundPage.entries, notFoundPage.head),
]);
- const input = contents.map(toCodeItem);
- const [fsPage, notFoundResult] = await generate(input);
+ await generate(contents.map(toCodeItem));
- assert.match(fsPage.html, /View As/);
- assert.match(fsPage.html, /href=fs\.json/);
- assert.match(fsPage.html, /href=fs\.md/);
+ const [fsHTML, notFoundHTML] = await Promise.all([
+ readFile(join(output, 'api/fs.html'), 'utf8'),
+ readFile(join(output, '404.html'), 'utf8'),
+ ]);
- assert.doesNotMatch(notFoundResult.html, /View As/);
- assert.doesNotMatch(notFoundResult.html, /href=404\.json/);
- assert.doesNotMatch(notFoundResult.html, /href=404\.md/);
+ assert.match(fsHTML, /View As/);
+ assert.match(fsHTML, /href=fs\.json/);
+ assert.match(fsHTML, /href=fs\.md/);
+ assert.doesNotMatch(notFoundHTML, /View As/);
+ assert.match(fsHTML, /src=\.\.\/assets\//);
+ assert.match(notFoundHTML, /src=\.\/assets\//);
});
- it('renders the configurable head without hardcoded defaults', async () => {
- // `setConfig` resolves generator defaults; mutate the live config to apply
- // per-generator overrides (the same object `getConfig('web')` returns).
- const config = await setConfig({});
+ it('renders the configurable head without hardcoded defaults', async context => {
+ const { config, output } = await createTestConfiguration(context);
config.web.head = {
meta: [
{ name: 'description', content: 'Custom project docs' },
@@ -81,18 +102,107 @@ describe('web generate', () => {
};
const fs = createEntry('fs', 'File system');
- const [fsPage] = await generate([toCodeItem(await buildContent([fs], fs))]);
+ await generate([toCodeItem(await buildContent([fs], fs))]);
+ const html = await readFile(join(output, 'fs.html'), 'utf8');
+
+ assert.match(html, /Custom project docs/);
+ assert.match(html, /https:\/\/example\.com\/og\.png/);
+ assert.match(html, /href=https:\/\/example\.com\/favicon\.ico/);
+ assert.match(html, /content=#abcdef/);
+ assert.doesNotMatch(html, /nodejs\.org/);
+ assert.match(html, /property=og:type content=website/);
+ });
+
+ it('uses Vite base URLs for absolute client assets', async context => {
+ const { config, output } = await createTestConfiguration(context);
+ config.web.useAbsoluteURLs = true;
+ config.web.baseURL = 'https://example.com/docs';
+
+ const notFoundPage = buildNotFoundPage();
+ const content = await buildContent(notFoundPage.entries, notFoundPage.head);
+ await generate([toCodeItem(content)]);
+ const html = await readFile(join(output, '404.html'), 'utf8');
+
+ assert.match(html, /src=https:\/\/example\.com\/docs\/assets\//);
+ assert.match(html, /href=https:\/\/example\.com\/docs\/assets\//);
+ });
+
+ it('applies configured Vite plugins', async context => {
+ const { config, output } = await createTestConfiguration(context);
+ config.web.bundler = createViteBundler({
+ plugins: [
+ {
+ name: 'test-html-transform',
+ transformIndexHtml() {
+ return [
+ {
+ tag: 'meta',
+ attrs: { name: 'vite-plugin', content: 'enabled' },
+ injectTo: 'head',
+ },
+ ];
+ },
+ },
+ ],
+ });
+
+ const fs = createEntry('fs', 'File system');
+ await generate([toCodeItem(await buildContent([fs], fs))]);
+ const html = await readFile(join(output, 'fs.html'), 'utf8');
+
+ assert.match(html, /name=vite-plugin/);
+ });
- assert.match(fsPage.html, /Custom project docs/);
- assert.match(fsPage.html, /https:\/\/example\.com\/og\.png/);
- assert.match(fsPage.html, /href=https:\/\/example\.com\/favicon\.ico/);
- assert.match(fsPage.html, /content=#abcdef/);
+ it('uses a custom bundler adapter for server and client output', async context => {
+ const { config, output } = await createTestConfiguration(context);
+ const calls = [];
- // Project-branding `head` config no longer leaks Node.js defaults.
- assert.doesNotMatch(fsPage.html, /nodejs\.org/);
+ config.web.bundler = {
+ getEntryId(api) {
+ calls.push(`entry:${api}`);
+ return `/custom/${api}.js`;
+ },
+
+ async render({ entries, virtualImports, config: receivedConfig }) {
+ calls.push('server');
+ assert.strictEqual(receivedConfig, config.web);
+ assert.ok(entries.has('fs.jsx'));
+ assert.match(virtualImports['#theme/config'], /export const pages/);
+ assert.match(
+ virtualImports['#theme/config'],
+ /export const server = true;/
+ );
+
+ return new Map([
+ ['fs', 'Custom SSR'],
+ ]);
+ },
+
+ async build({ entries, virtualImports, pages, config: receivedConfig }) {
+ calls.push('client');
+ assert.strictEqual(receivedConfig, config.web);
+ assert.ok(entries.has('fs.jsx'));
+ assert.match(
+ virtualImports['#theme/config'],
+ /export const server = false;/
+ );
+
+ await Promise.all(
+ [...pages].map(async ([fileName, html]) => {
+ const path = join(output, fileName);
+ await mkdir(dirname(path), { recursive: true });
+ await writeFile(path, html);
+ })
+ );
+ },
+ };
+
+ const fs = createEntry('fs', 'File system');
+ await generate([toCodeItem(await buildContent([fs], fs))]);
+ const html = await readFile(join(output, 'fs.html'), 'utf8');
- // Structural/theme tags stay hardcoded in the template regardless.
- assert.match(fsPage.html, /property=og:type content=website/);
- assert.match(fsPage.html, /href=https:\/\/fonts\.googleapis\.com/);
+ assert.match(html, /data-custom-ssr/);
+ assert.match(html, /src="\/custom\/fs\.js"/);
+ assert.deepStrictEqual(calls, ['server', 'entry:fs', 'client']);
});
});
diff --git a/src/generators/web/bundlers/__tests__/vite.test.mjs b/src/generators/web/bundlers/__tests__/vite.test.mjs
new file mode 100644
index 00000000..fd17c5ae
--- /dev/null
+++ b/src/generators/web/bundlers/__tests__/vite.test.mjs
@@ -0,0 +1,136 @@
+import assert from 'node:assert/strict';
+import { access, mkdtemp } from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+import { join, resolve } from 'node:path';
+import { describe, it } from 'node:test';
+
+import {
+ default as getConfig,
+ setConfig,
+} from '../../../../utils/configuration/index.mjs';
+import {
+ createVirtualModulesPlugin,
+ createViteConfig,
+ render,
+} from '../vite.mjs';
+
+const output = join(tmpdir(), 'doc-kit-vite-test-output');
+
+await setConfig({
+ output,
+ version: 'v22.0.0',
+ changelog: [],
+ generators: {
+ web: {},
+ },
+});
+
+describe('Vite virtual modules', () => {
+ it('resolves and loads only exact in-memory module identifiers', () => {
+ const htmlId = resolve('api/fs.html');
+ const plugin = createVirtualModulesPlugin(
+ new Map([
+ ['virtual:entry', 'export default 42;'],
+ [htmlId, ''],
+ ])
+ );
+
+ const entryId = plugin.resolveId('virtual:entry');
+ assert.ok(entryId);
+ assert.strictEqual(plugin.load(entryId), 'export default 42;');
+ assert.strictEqual(plugin.resolveId('virtual:missing'), undefined);
+ assert.strictEqual(plugin.resolveId(htmlId), htmlId);
+ assert.strictEqual(
+ plugin.load(htmlId),
+ ''
+ );
+ });
+});
+
+describe('Vite configuration', () => {
+ it('uses the generated client entries and configured output', () => {
+ const vite = {
+ base: '/custom/',
+ build: {
+ outDir: 'custom-output',
+ manifest: true,
+ rolldownOptions: {
+ input: 'custom-entry.js',
+ },
+ },
+ };
+
+ const input = { fs: 'virtual:doc-kit/client/fs.jsx' };
+ const config = createViteConfig({
+ sources: new Map(),
+ input,
+ server: false,
+ config: getConfig('web'),
+ vite,
+ });
+
+ assert.strictEqual(config.base, './');
+ assert.strictEqual(config.build.outDir, output);
+ assert.strictEqual(config.build.manifest, true);
+ assert.strictEqual(config.build.rolldownOptions.input, input);
+ });
+
+ it('keeps the temporary SSR build self-contained', () => {
+ const vite = {
+ ssr: {
+ external: ['preact'],
+ noExternal: false,
+ },
+ build: {
+ minify: true,
+ rolldownOptions: {
+ external: ['preact'],
+ },
+ },
+ };
+
+ const input = { fs: 'virtual:doc-kit/server/fs.jsx' };
+ const serverOutput = join(tmpdir(), 'doc-kit-vite-ssr-test');
+ const config = createViteConfig({
+ sources: new Map(),
+ input,
+ server: true,
+ serverOutDir: serverOutput,
+ config: getConfig('web'),
+ vite,
+ });
+
+ assert.strictEqual(config.build.ssr, true);
+ assert.strictEqual(config.build.outDir, serverOutput);
+ assert.strictEqual(config.build.minify, false);
+ assert.deepStrictEqual(config.build.rolldownOptions.external, []);
+ assert.deepStrictEqual(config.ssr.external, []);
+ assert.strictEqual(config.ssr.noExternal, true);
+ });
+});
+
+describe('Vite SSR temporary output', () => {
+ it('always removes temporary output after a renderer throws', async () => {
+ const temporaryDirectory = await mkdtemp(
+ join(tmpdir(), 'doc-kit-vite-cleanup-test-')
+ );
+
+ await assert.rejects(
+ render({
+ entries: new Map([
+ [
+ 'broken.jsx',
+ 'export default () => { throw new Error("render failed"); };',
+ ],
+ ]),
+ virtualImports: {},
+ config: getConfig('web'),
+ vite: {},
+ createTemporaryDirectory: async () => temporaryDirectory,
+ }),
+ /render failed/
+ );
+
+ await assert.rejects(access(temporaryDirectory), { code: 'ENOENT' });
+ });
+});
diff --git a/src/generators/web/bundlers/index.mjs b/src/generators/web/bundlers/index.mjs
new file mode 100644
index 00000000..7b426ea1
--- /dev/null
+++ b/src/generators/web/bundlers/index.mjs
@@ -0,0 +1,14 @@
+/**
+ * Returns the configured bundler or lazily creates the default Vite adapter.
+ *
+ * @param {import('../types').WebBundler|undefined} bundler
+ * @returns {Promise}
+ */
+export const resolveBundler = async bundler => {
+ if (bundler) {
+ return bundler;
+ }
+
+ const { createViteBundler } = await import('./vite.mjs');
+ return createViteBundler();
+};
diff --git a/src/generators/web/bundlers/vite.mjs b/src/generators/web/bundlers/vite.mjs
new file mode 100644
index 00000000..20efeb21
--- /dev/null
+++ b/src/generators/web/bundlers/vite.mjs
@@ -0,0 +1,398 @@
+import { mkdtemp, rm } from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+import { basename, isAbsolute, join, resolve } from 'node:path';
+import { pathToFileURL } from 'node:url';
+
+import {
+ build as viteBuild,
+ defaultClientConditions,
+ defaultServerConditions,
+ mergeConfig,
+} from 'vite';
+
+import { minifyHTML } from '../../../utils/html-minifier.mjs';
+
+const VIRTUAL_PREFIX = 'virtual:doc-kit/';
+const RESOLVED_VIRTUAL_PREFIX = '\0doc-kit:';
+
+/**
+ * Returns the virtual Vite client entry imported by one HTML page.
+ *
+ * @param {string} api
+ * @returns {string}
+ */
+const getViteEntryId = api => `${VIRTUAL_PREFIX}client/${api}.jsx`;
+
+/**
+ * Resolves relative theme aliases against Vite's configured project root.
+ *
+ * @param {Record} aliases
+ * @param {string} root
+ * @returns {Record}
+ */
+const resolveThemeAliases = (aliases, root) =>
+ Object.fromEntries(
+ Object.entries(aliases).map(([find, replacement]) => [
+ find,
+ replacement.startsWith('.') ? resolve(root, replacement) : replacement,
+ ])
+ );
+
+/**
+ * Creates a Vite plugin that serves an exact map of in-memory modules and HTML
+ * entries. HTML keeps its absolute identifier so Vite emits it at the matching
+ * path relative to the configured root.
+ *
+ * @param {Map} sources
+ * @returns {import('vite').Plugin}
+ */
+export const createVirtualModulesPlugin = sources => {
+ return {
+ name: 'doc-kit:virtual-modules',
+ enforce: 'pre',
+ /**
+ * Resolves an exact in-memory identifier.
+ *
+ * @param {string} id
+ * @returns {string|undefined}
+ */
+ resolveId(id) {
+ if (sources.has(id)) {
+ return isAbsolute(id) && id.endsWith('.html')
+ ? id
+ : `${RESOLVED_VIRTUAL_PREFIX}${id}`;
+ }
+ },
+ /**
+ * Loads an exact resolved identifier.
+ *
+ * @param {string} id
+ * @returns {string|undefined}
+ */
+ load(id) {
+ return sources.get(
+ id.startsWith(RESOLVED_VIRTUAL_PREFIX)
+ ? id.slice(RESOLVED_VIRTUAL_PREFIX.length)
+ : id
+ );
+ },
+ };
+};
+
+/**
+ * Finalizes Vite's generated HTML before its normal write phase.
+ *
+ * @returns {import('vite').Plugin}
+ */
+const createHTMLFinalizerPlugin = () => ({
+ name: 'doc-kit:finalize-html',
+ /**
+ * Minifies every generated HTML entry after Vite has injected its scripts,
+ * stylesheets, and module preloads.
+ */
+ generateBundle: {
+ order: 'post',
+ /**
+ * @param {object} _
+ * @param {Record} bundle
+ */
+ async handler(_, bundle) {
+ await Promise.all(
+ Object.values(bundle)
+ .filter(
+ item => item.type === 'asset' && item.fileName.endsWith('.html')
+ )
+ .map(async asset => {
+ const source =
+ typeof asset.source === 'string'
+ ? asset.source
+ : Buffer.from(asset.source).toString('utf8');
+
+ asset.source = await minifyHTML(source);
+ })
+ );
+ },
+ },
+});
+
+/**
+ * Converts generated page programs into named Vite inputs and virtual modules.
+ *
+ * @param {Map} codeMap
+ * @param {'client'|'server'} environment
+ */
+const createBuildEntries = (codeMap, environment) => {
+ const input = {};
+ const sources = new Map();
+
+ for (const [fileName, code] of codeMap) {
+ const name = basename(fileName, '.jsx');
+ const id =
+ environment === 'client'
+ ? getViteEntryId(name)
+ : `${VIRTUAL_PREFIX}server/${fileName}`;
+
+ input[name] = id;
+ sources.set(id, code);
+ }
+
+ return { input, sources };
+};
+
+/**
+ * Produces the complete inline Vite config for one generator build.
+ * User configuration is merged first; generator invariants are then applied.
+ *
+ * @param {object} options
+ * @param {Map} options.sources
+ * @param {Record|Array} options.input
+ * @param {boolean} options.server
+ * @param {string} [options.serverOutDir]
+ * @param {import('../types').ResolvedWebConfiguration} options.config
+ * @param {import('vite').UserConfig} options.vite
+ * @returns {import('vite').InlineConfig}
+ */
+export const createViteConfig = ({
+ sources,
+ input,
+ server,
+ serverOutDir,
+ config: webConfig,
+ vite = {},
+}) => {
+ const root = resolve(vite.root ?? process.cwd());
+ const conditions = server ? defaultServerConditions : defaultClientConditions;
+
+ return {
+ ...vite,
+
+ // The generator is the complete Vite configuration boundary.
+ configFile: false,
+ root,
+ base: webConfig.useAbsoluteURLs
+ ? String(webConfig.baseURL).replace(/\/?$/, '/')
+ : './',
+ appType: 'custom',
+ publicDir: false,
+ clearScreen: vite.clearScreen ?? false,
+ logLevel: vite.logLevel ?? 'warn',
+
+ // Virtual entries must resolve before user plugins, while user plugins can
+ // still transform every module and generated HTML page.
+ plugins: [
+ createVirtualModulesPlugin(sources),
+ ...(vite.plugins ?? []),
+ ...(!server && webConfig.minify ? [createHTMLFinalizerPlugin()] : []),
+ ],
+
+ resolve: mergeConfig(
+ { resolve: vite.resolve },
+ {
+ resolve: {
+ conditions: ['rolldown', ...conditions],
+ dedupe: ['preact'],
+
+ // Vite applies string aliases to matching package subpaths too.
+ alias: {
+ react: 'preact/compat',
+ 'react-dom': 'preact/compat',
+ ...resolveThemeAliases(webConfig.imports, root),
+ },
+ },
+ }
+ ).resolve,
+
+ // Oxc supplies Preact's automatic JSX runtime in both builds.
+ oxc: {
+ ...vite.oxc,
+ jsx: {
+ ...vite.oxc?.jsx,
+ runtime: 'automatic',
+ importSource: 'preact',
+ },
+ },
+
+ // CSS imports, modules, URLs, splitting, and minification are all Vite
+ // responsibilities. Native Lightning CSS options remain configurable.
+ css: {
+ ...vite.css,
+ transformer: 'lightningcss',
+ },
+
+ build: {
+ ...vite.build,
+
+ // Both builds are complete Vite outputs. SSR uses a private directory
+ // because its entries can share chunks; the client writes the final site.
+ outDir: server ? serverOutDir : resolve(webConfig.output),
+ write: true,
+ emptyOutDir: false,
+ copyPublicDir: false,
+ watch: null,
+ lib: false,
+
+ ...(server ? { manifest: false } : {}),
+ ssr: server,
+
+ // Browser output follows the generator's minification setting. Temporary
+ // server output stays readable and disappears immediately after render.
+ minify: server ? false : (vite.build?.minify ?? webConfig.minify),
+
+ rolldownOptions: {
+ ...vite.build?.rolldownOptions,
+ input,
+ ...(server ? { external: [] } : {}),
+ output: {
+ ...vite.build?.rolldownOptions?.output,
+ format: 'es',
+ ...(server
+ ? {
+ entryFileNames: '[name].mjs',
+ chunkFileNames: 'assets/[name]-[hash].mjs',
+ }
+ : {}),
+ },
+ },
+ },
+
+ ...(server
+ ? {
+ ssr: {
+ ...vite.ssr,
+ external: [],
+ noExternal: true,
+ resolve: {
+ ...vite.ssr?.resolve,
+ conditions: [
+ 'rolldown',
+ ...defaultServerConditions,
+ ...(vite.ssr?.resolve?.conditions ?? []),
+ ],
+ },
+ },
+ }
+ : {}),
+ };
+};
+
+/**
+ * Builds and executes the server entries through Vite's SSR pipeline.
+ *
+ * @param {object} options
+ * @param {Map} options.entries
+ * @param {Record} options.virtualImports
+ * @param {import('../types').ResolvedWebConfiguration} options.config
+ * @param {import('vite').UserConfig} options.vite
+ * @param {() => Promise} [options.createTemporaryDirectory]
+ * @returns {Promise