twynejs 1.0.2 → 1.0.3
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 +129 -91
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -542,15 +542,20 @@ declare module "virtual:pages" {
|
|
|
542
542
|
}>;
|
|
543
543
|
}
|
|
544
544
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
545
|
+
// Note: there is intentionally no exported "LiquidPage" variant here.
|
|
546
|
+
// Liquid/EJS template pages are rendered fully at build time as
|
|
547
|
+
// standalone static documents; when one of them also has a sibling
|
|
548
|
+
// client script (\`page.[tj]s\`), it is exposed through \`virtual:pages\`
|
|
549
|
+
// as an ordinary ComponentPage (\`type: "component"\`) so the app can
|
|
550
|
+
// mount it like any other page. Without a sibling script it never
|
|
551
|
+
// appears in \`pages\` at all. A runtime object shaped like
|
|
552
|
+
// \`{ type: "liquid", ... }\` is never actually produced, so including
|
|
553
|
+
// it in this union previously made every \`pages[id]\` access widen to
|
|
554
|
+
// \`ComponentPage | LiquidPage\` after narrowing out "markdown", even
|
|
555
|
+
// though \`LiquidPage\` has no \`load\` — causing spurious
|
|
556
|
+
// "Property 'load' does not exist on type 'LiquidPage'" errors in
|
|
557
|
+
// consumers under \`tsc -b\`.
|
|
558
|
+
export type PageEntry = ComponentPage | MarkdownPage;
|
|
554
559
|
|
|
555
560
|
export const pages: Record<string, PageEntry>;
|
|
556
561
|
}
|
|
@@ -953,16 +958,18 @@ ${ctx.content}
|
|
|
953
958
|
return;
|
|
954
959
|
}
|
|
955
960
|
const entryJsChunk = jsChunk;
|
|
956
|
-
/*
|
|
957
|
-
* Single-file export:
|
|
958
|
-
*
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
963
|
-
*
|
|
964
|
-
*
|
|
965
|
-
*
|
|
961
|
+
/*
|
|
962
|
+
* Single-file export: skip the normal per-page HTML emission
|
|
963
|
+
* entirely — the actual `index.html` is assembled in `writeBundle`
|
|
964
|
+
* instead (see below), once every other plugin's `generateBundle`
|
|
965
|
+
* hook (including Vite's own internal dynamic-import / preload
|
|
966
|
+
* handling, e.g. its `__VITE_PRELOAD__` substitution) has already
|
|
967
|
+
* run. Reading `entryJsChunk.code` here, in this plugin's own
|
|
968
|
+
* `generateBundle` (registered with `enforce: "pre"`, so it can run
|
|
969
|
+
* *before* Vite's internal build plugins), would risk capturing
|
|
970
|
+
* unfinished code and inlining an unresolved `__VITE_PRELOAD__`
|
|
971
|
+
* placeholder straight into the page.
|
|
972
|
+
*/
|
|
966
973
|
if (singleFile) {
|
|
967
974
|
const skippedPages = pages.filter((page) => page.type === "liquid" || page.type === "ejs");
|
|
968
975
|
if (skippedPages.length > 0) {
|
|
@@ -970,78 +977,6 @@ ${ctx.content}
|
|
|
970
977
|
.map((page) => page.id)
|
|
971
978
|
.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
979
|
}
|
|
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
980
|
return;
|
|
1046
981
|
}
|
|
1047
982
|
for (const page of pages) {
|
|
@@ -1208,6 +1143,109 @@ ${inlineJs}
|
|
|
1208
1143
|
});
|
|
1209
1144
|
}
|
|
1210
1145
|
},
|
|
1146
|
+
/*
|
|
1147
|
+
* Runs strictly after every plugin's `generateBundle` hook has
|
|
1148
|
+
* finished and Rollup has already written the bundle to disk — so by
|
|
1149
|
+
* now Vite's own internal transforms (dynamic-import preload
|
|
1150
|
+
* handling, `__VITE_PRELOAD__` substitution, etc.) are guaranteed to
|
|
1151
|
+
* be fully resolved in `bundle[...].code`. This is what makes it safe
|
|
1152
|
+
* to read the entry chunk's final code and inline it, unlike doing
|
|
1153
|
+
* the same thing inside `generateBundle` (see the comment there).
|
|
1154
|
+
*/
|
|
1155
|
+
async writeBundle(outputOptions, bundle) {
|
|
1156
|
+
if (!singleFile) {
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
const entryJsChunk = Object.values(bundle).find((item) => item.type === "chunk" &&
|
|
1160
|
+
item.isEntry &&
|
|
1161
|
+
item.fileName.endsWith(".js"));
|
|
1162
|
+
if (!entryJsChunk || entryJsChunk.type !== "chunk") {
|
|
1163
|
+
this.error("[vite-plugin-pages-ssg] singleFile: could not find the generated entry .js chunk while writing the bundle.");
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
const otherChunkCount = Object.values(bundle).filter((item) => item.type === "chunk" && item !== entryJsChunk).length;
|
|
1167
|
+
if (otherChunkCount > 0) {
|
|
1168
|
+
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".`);
|
|
1169
|
+
}
|
|
1170
|
+
const cssParts = [];
|
|
1171
|
+
const cssFileNames = [];
|
|
1172
|
+
for (const [fileName, item] of Object.entries(bundle)) {
|
|
1173
|
+
if (item.type === "asset" && fileName.toLowerCase().endsWith(".css")) {
|
|
1174
|
+
const source = item.source;
|
|
1175
|
+
cssParts.push(typeof source === "string"
|
|
1176
|
+
? source
|
|
1177
|
+
: Buffer.from(source).toString("utf8"));
|
|
1178
|
+
cssFileNames.push(fileName);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
const cssContent = cssParts.join("\n");
|
|
1182
|
+
const bundledPages = pages.filter((page) => page.type === "component" || page.type === "markdown");
|
|
1183
|
+
const rootId = bundledPages.some((page) => page.id === "index")
|
|
1184
|
+
? "index"
|
|
1185
|
+
: (bundledPages[0]?.id ?? "index");
|
|
1186
|
+
const title = getTitle(rootId);
|
|
1187
|
+
const head = getHead(rootId);
|
|
1188
|
+
const bootstrapScript = singleFileBootstrapScript(rootId);
|
|
1189
|
+
const inlineJs = escapeScriptClose(entryJsChunk.code);
|
|
1190
|
+
let html = `<!doctype html>
|
|
1191
|
+
<html lang="en">
|
|
1192
|
+
<head>
|
|
1193
|
+
<meta charset="UTF-8" />
|
|
1194
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1195
|
+
<title>${escapeHtml(title)}</title>
|
|
1196
|
+
${cssContent ? `<style>\n${cssContent}\n </style>` : ""}
|
|
1197
|
+
${head}
|
|
1198
|
+
</head>
|
|
1199
|
+
<body>
|
|
1200
|
+
<div id="app"></div>
|
|
1201
|
+
|
|
1202
|
+
<script>
|
|
1203
|
+
${bootstrapScript}
|
|
1204
|
+
</script>
|
|
1205
|
+
|
|
1206
|
+
<script type="module">
|
|
1207
|
+
${inlineJs}
|
|
1208
|
+
</script>
|
|
1209
|
+
</body>
|
|
1210
|
+
</html>
|
|
1211
|
+
`;
|
|
1212
|
+
if (options.minify) {
|
|
1213
|
+
html = await minify(html, {
|
|
1214
|
+
collapseWhitespace: true,
|
|
1215
|
+
removeComments: true,
|
|
1216
|
+
removeRedundantAttributes: true,
|
|
1217
|
+
removeEmptyAttributes: true,
|
|
1218
|
+
useShortDoctype: true,
|
|
1219
|
+
minifyCSS: true,
|
|
1220
|
+
minifyJS: true,
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
const outDir = path.resolve(config.root, outputOptions.dir ?? config.build.outDir ?? "dist");
|
|
1224
|
+
fs.writeFileSync(path.join(outDir, "index.html"), html, "utf8");
|
|
1225
|
+
// The entry chunk and every CSS asset are now embedded directly in
|
|
1226
|
+
// index.html (already written above) — remove the now-redundant
|
|
1227
|
+
// loose files from disk so only index.html remains.
|
|
1228
|
+
const filesToRemove = [entryJsChunk.fileName, ...cssFileNames];
|
|
1229
|
+
const dirsTouched = new Set();
|
|
1230
|
+
for (const fileName of filesToRemove) {
|
|
1231
|
+
const filePath = path.join(outDir, fileName);
|
|
1232
|
+
if (fs.existsSync(filePath)) {
|
|
1233
|
+
fs.rmSync(filePath);
|
|
1234
|
+
dirsTouched.add(path.dirname(filePath));
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
// Clean up asset directories (e.g. "assets/") left empty behind.
|
|
1238
|
+
for (const dir of dirsTouched) {
|
|
1239
|
+
if (dir !== outDir &&
|
|
1240
|
+
fs.existsSync(dir) &&
|
|
1241
|
+
fs.readdirSync(dir).length === 0) {
|
|
1242
|
+
fs.rmdirSync(dir);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
if (verbose) {
|
|
1246
|
+
console.log(`[vite-plugin-pages-ssg] Emitted single-file build: index.html (${bundledPages.length} page(s) bundled)`);
|
|
1247
|
+
}
|
|
1248
|
+
},
|
|
1211
1249
|
};
|
|
1212
1250
|
}
|
|
1213
1251
|
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.3",
|
|
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",
|