twynejs 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +130 -91
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -70,6 +70,7 @@ export function pagesPlugin(options = {}) {
|
|
|
70
70
|
"/__devtools-vite/",
|
|
71
71
|
"/__devtools-rolldown/",
|
|
72
72
|
"/__devtools-oxc/",
|
|
73
|
+
"/__vitest__/",
|
|
73
74
|
...(options.ignoredPathnames ?? []),
|
|
74
75
|
].filter((prefix, index, prefixes) => prefixes.indexOf(prefix) === index);
|
|
75
76
|
const verbose = options.verbose ?? false;
|
|
@@ -542,15 +543,20 @@ declare module "virtual:pages" {
|
|
|
542
543
|
}>;
|
|
543
544
|
}
|
|
544
545
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
546
|
+
// Note: there is intentionally no exported "LiquidPage" variant here.
|
|
547
|
+
// Liquid/EJS template pages are rendered fully at build time as
|
|
548
|
+
// standalone static documents; when one of them also has a sibling
|
|
549
|
+
// client script (\`page.[tj]s\`), it is exposed through \`virtual:pages\`
|
|
550
|
+
// as an ordinary ComponentPage (\`type: "component"\`) so the app can
|
|
551
|
+
// mount it like any other page. Without a sibling script it never
|
|
552
|
+
// appears in \`pages\` at all. A runtime object shaped like
|
|
553
|
+
// \`{ type: "liquid", ... }\` is never actually produced, so including
|
|
554
|
+
// it in this union previously made every \`pages[id]\` access widen to
|
|
555
|
+
// \`ComponentPage | LiquidPage\` after narrowing out "markdown", even
|
|
556
|
+
// though \`LiquidPage\` has no \`load\` — causing spurious
|
|
557
|
+
// "Property 'load' does not exist on type 'LiquidPage'" errors in
|
|
558
|
+
// consumers under \`tsc -b\`.
|
|
559
|
+
export type PageEntry = ComponentPage | MarkdownPage;
|
|
554
560
|
|
|
555
561
|
export const pages: Record<string, PageEntry>;
|
|
556
562
|
}
|
|
@@ -953,16 +959,18 @@ ${ctx.content}
|
|
|
953
959
|
return;
|
|
954
960
|
}
|
|
955
961
|
const entryJsChunk = jsChunk;
|
|
956
|
-
/*
|
|
957
|
-
* Single-file export:
|
|
958
|
-
*
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
963
|
-
*
|
|
964
|
-
*
|
|
965
|
-
*
|
|
962
|
+
/*
|
|
963
|
+
* Single-file export: skip the normal per-page HTML emission
|
|
964
|
+
* entirely — the actual `index.html` is assembled in `writeBundle`
|
|
965
|
+
* instead (see below), once every other plugin's `generateBundle`
|
|
966
|
+
* hook (including Vite's own internal dynamic-import / preload
|
|
967
|
+
* handling, e.g. its `__VITE_PRELOAD__` substitution) has already
|
|
968
|
+
* run. Reading `entryJsChunk.code` here, in this plugin's own
|
|
969
|
+
* `generateBundle` (registered with `enforce: "pre"`, so it can run
|
|
970
|
+
* *before* Vite's internal build plugins), would risk capturing
|
|
971
|
+
* unfinished code and inlining an unresolved `__VITE_PRELOAD__`
|
|
972
|
+
* placeholder straight into the page.
|
|
973
|
+
*/
|
|
966
974
|
if (singleFile) {
|
|
967
975
|
const skippedPages = pages.filter((page) => page.type === "liquid" || page.type === "ejs");
|
|
968
976
|
if (skippedPages.length > 0) {
|
|
@@ -970,78 +978,6 @@ ${ctx.content}
|
|
|
970
978
|
.map((page) => page.id)
|
|
971
979
|
.join(", ")}) — they are static, build-time-rendered documents and are not compatible with the single-file hash router. Only "component" and "markdown" pages are bundled into index.html.`);
|
|
972
980
|
}
|
|
973
|
-
const otherChunkCount = Object.values(bundle).filter((item) => item.type === "chunk" && item !== entryJsChunk).length;
|
|
974
|
-
if (otherChunkCount > 0) {
|
|
975
|
-
this.warn(`[vite-plugin-pages-ssg] singleFile: found ${otherChunkCount} extra JS chunk(s) besides the entry chunk that could not be inlined into index.html. They will still be emitted as separate files, so the build is not a true single file — check for a custom "manualChunks"/"output" config that overrides "inlineDynamicImports".`);
|
|
976
|
-
}
|
|
977
|
-
const cssParts = [];
|
|
978
|
-
const cssFileNames = [];
|
|
979
|
-
for (const [fileName, item] of Object.entries(bundle)) {
|
|
980
|
-
if (item.type === "asset" &&
|
|
981
|
-
fileName.toLowerCase().endsWith(".css")) {
|
|
982
|
-
const source = item.source;
|
|
983
|
-
cssParts.push(typeof source === "string"
|
|
984
|
-
? source
|
|
985
|
-
: Buffer.from(source).toString("utf8"));
|
|
986
|
-
cssFileNames.push(fileName);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
const cssContent = cssParts.join("\n");
|
|
990
|
-
const bundledPages = pages.filter((page) => page.type === "component" || page.type === "markdown");
|
|
991
|
-
const rootId = bundledPages.some((page) => page.id === "index")
|
|
992
|
-
? "index"
|
|
993
|
-
: (bundledPages[0]?.id ?? "index");
|
|
994
|
-
const title = getTitle(rootId);
|
|
995
|
-
const head = getHead(rootId);
|
|
996
|
-
const bootstrapScript = singleFileBootstrapScript(rootId);
|
|
997
|
-
const inlineJs = escapeScriptClose(entryJsChunk.code);
|
|
998
|
-
let html = `<!doctype html>
|
|
999
|
-
<html lang="en">
|
|
1000
|
-
<head>
|
|
1001
|
-
<meta charset="UTF-8" />
|
|
1002
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1003
|
-
<title>${escapeHtml(title)}</title>
|
|
1004
|
-
${cssContent ? `<style>\n${cssContent}\n </style>` : ""}
|
|
1005
|
-
${head}
|
|
1006
|
-
</head>
|
|
1007
|
-
<body>
|
|
1008
|
-
<div id="app"></div>
|
|
1009
|
-
|
|
1010
|
-
<script>
|
|
1011
|
-
${bootstrapScript}
|
|
1012
|
-
</script>
|
|
1013
|
-
|
|
1014
|
-
<script type="module">
|
|
1015
|
-
${inlineJs}
|
|
1016
|
-
</script>
|
|
1017
|
-
</body>
|
|
1018
|
-
</html>
|
|
1019
|
-
`;
|
|
1020
|
-
if (options.minify) {
|
|
1021
|
-
html = await minify(html, {
|
|
1022
|
-
collapseWhitespace: true,
|
|
1023
|
-
removeComments: true,
|
|
1024
|
-
removeRedundantAttributes: true,
|
|
1025
|
-
removeEmptyAttributes: true,
|
|
1026
|
-
useShortDoctype: true,
|
|
1027
|
-
minifyCSS: true,
|
|
1028
|
-
minifyJS: true,
|
|
1029
|
-
});
|
|
1030
|
-
}
|
|
1031
|
-
// The entry chunk and every CSS asset are now embedded directly
|
|
1032
|
-
// in index.html — drop them so they don't also ship as loose files.
|
|
1033
|
-
delete bundle[entryJsChunk.fileName];
|
|
1034
|
-
for (const fileName of cssFileNames) {
|
|
1035
|
-
delete bundle[fileName];
|
|
1036
|
-
}
|
|
1037
|
-
this.emitFile({
|
|
1038
|
-
type: "asset",
|
|
1039
|
-
fileName: "index.html",
|
|
1040
|
-
source: html,
|
|
1041
|
-
});
|
|
1042
|
-
if (verbose) {
|
|
1043
|
-
console.log(`[vite-plugin-pages-ssg] Emitted single-file build: index.html (${bundledPages.length} page(s) bundled, ${skippedPages.length} skipped)`);
|
|
1044
|
-
}
|
|
1045
981
|
return;
|
|
1046
982
|
}
|
|
1047
983
|
for (const page of pages) {
|
|
@@ -1208,6 +1144,109 @@ ${inlineJs}
|
|
|
1208
1144
|
});
|
|
1209
1145
|
}
|
|
1210
1146
|
},
|
|
1147
|
+
/*
|
|
1148
|
+
* Runs strictly after every plugin's `generateBundle` hook has
|
|
1149
|
+
* finished and Rollup has already written the bundle to disk — so by
|
|
1150
|
+
* now Vite's own internal transforms (dynamic-import preload
|
|
1151
|
+
* handling, `__VITE_PRELOAD__` substitution, etc.) are guaranteed to
|
|
1152
|
+
* be fully resolved in `bundle[...].code`. This is what makes it safe
|
|
1153
|
+
* to read the entry chunk's final code and inline it, unlike doing
|
|
1154
|
+
* the same thing inside `generateBundle` (see the comment there).
|
|
1155
|
+
*/
|
|
1156
|
+
async writeBundle(outputOptions, bundle) {
|
|
1157
|
+
if (!singleFile) {
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
const entryJsChunk = Object.values(bundle).find((item) => item.type === "chunk" &&
|
|
1161
|
+
item.isEntry &&
|
|
1162
|
+
item.fileName.endsWith(".js"));
|
|
1163
|
+
if (!entryJsChunk || entryJsChunk.type !== "chunk") {
|
|
1164
|
+
this.error("[vite-plugin-pages-ssg] singleFile: could not find the generated entry .js chunk while writing the bundle.");
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
const otherChunkCount = Object.values(bundle).filter((item) => item.type === "chunk" && item !== entryJsChunk).length;
|
|
1168
|
+
if (otherChunkCount > 0) {
|
|
1169
|
+
this.warn(`[vite-plugin-pages-ssg] singleFile: found ${otherChunkCount} extra JS chunk(s) besides the entry chunk that could not be inlined into index.html. They will still be left as separate files, so the build is not a true single file — check for a custom "manualChunks"/"output" config that overrides "inlineDynamicImports".`);
|
|
1170
|
+
}
|
|
1171
|
+
const cssParts = [];
|
|
1172
|
+
const cssFileNames = [];
|
|
1173
|
+
for (const [fileName, item] of Object.entries(bundle)) {
|
|
1174
|
+
if (item.type === "asset" && fileName.toLowerCase().endsWith(".css")) {
|
|
1175
|
+
const source = item.source;
|
|
1176
|
+
cssParts.push(typeof source === "string"
|
|
1177
|
+
? source
|
|
1178
|
+
: Buffer.from(source).toString("utf8"));
|
|
1179
|
+
cssFileNames.push(fileName);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
const cssContent = cssParts.join("\n");
|
|
1183
|
+
const bundledPages = pages.filter((page) => page.type === "component" || page.type === "markdown");
|
|
1184
|
+
const rootId = bundledPages.some((page) => page.id === "index")
|
|
1185
|
+
? "index"
|
|
1186
|
+
: (bundledPages[0]?.id ?? "index");
|
|
1187
|
+
const title = getTitle(rootId);
|
|
1188
|
+
const head = getHead(rootId);
|
|
1189
|
+
const bootstrapScript = singleFileBootstrapScript(rootId);
|
|
1190
|
+
const inlineJs = escapeScriptClose(entryJsChunk.code);
|
|
1191
|
+
let html = `<!doctype html>
|
|
1192
|
+
<html lang="en">
|
|
1193
|
+
<head>
|
|
1194
|
+
<meta charset="UTF-8" />
|
|
1195
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1196
|
+
<title>${escapeHtml(title)}</title>
|
|
1197
|
+
${cssContent ? `<style>\n${cssContent}\n </style>` : ""}
|
|
1198
|
+
${head}
|
|
1199
|
+
</head>
|
|
1200
|
+
<body>
|
|
1201
|
+
<div id="app"></div>
|
|
1202
|
+
|
|
1203
|
+
<script>
|
|
1204
|
+
${bootstrapScript}
|
|
1205
|
+
</script>
|
|
1206
|
+
|
|
1207
|
+
<script type="module">
|
|
1208
|
+
${inlineJs}
|
|
1209
|
+
</script>
|
|
1210
|
+
</body>
|
|
1211
|
+
</html>
|
|
1212
|
+
`;
|
|
1213
|
+
if (options.minify) {
|
|
1214
|
+
html = await minify(html, {
|
|
1215
|
+
collapseWhitespace: true,
|
|
1216
|
+
removeComments: true,
|
|
1217
|
+
removeRedundantAttributes: true,
|
|
1218
|
+
removeEmptyAttributes: true,
|
|
1219
|
+
useShortDoctype: true,
|
|
1220
|
+
minifyCSS: true,
|
|
1221
|
+
minifyJS: true,
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
const outDir = path.resolve(config.root, outputOptions.dir ?? config.build.outDir ?? "dist");
|
|
1225
|
+
fs.writeFileSync(path.join(outDir, "index.html"), html, "utf8");
|
|
1226
|
+
// The entry chunk and every CSS asset are now embedded directly in
|
|
1227
|
+
// index.html (already written above) — remove the now-redundant
|
|
1228
|
+
// loose files from disk so only index.html remains.
|
|
1229
|
+
const filesToRemove = [entryJsChunk.fileName, ...cssFileNames];
|
|
1230
|
+
const dirsTouched = new Set();
|
|
1231
|
+
for (const fileName of filesToRemove) {
|
|
1232
|
+
const filePath = path.join(outDir, fileName);
|
|
1233
|
+
if (fs.existsSync(filePath)) {
|
|
1234
|
+
fs.rmSync(filePath);
|
|
1235
|
+
dirsTouched.add(path.dirname(filePath));
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
// Clean up asset directories (e.g. "assets/") left empty behind.
|
|
1239
|
+
for (const dir of dirsTouched) {
|
|
1240
|
+
if (dir !== outDir &&
|
|
1241
|
+
fs.existsSync(dir) &&
|
|
1242
|
+
fs.readdirSync(dir).length === 0) {
|
|
1243
|
+
fs.rmdirSync(dir);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
if (verbose) {
|
|
1247
|
+
console.log(`[vite-plugin-pages-ssg] Emitted single-file build: index.html (${bundledPages.length} page(s) bundled)`);
|
|
1248
|
+
}
|
|
1249
|
+
},
|
|
1211
1250
|
};
|
|
1212
1251
|
}
|
|
1213
1252
|
function getPageId(url, pages) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "twynejs",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "twine A Vite SSG plugin that turns .ts/.tsx pages, Markdown docs and Liquid/EJS templates into a multi-page static build with a virtual:pages module.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|