slidev-prerender 0.0.1-alpha.3 → 0.0.1-beta.0
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/README.md +54 -4
- package/dist/index.d.mts +13 -10
- package/dist/run.mjs +90 -30
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -52,7 +52,6 @@ By default, this will:
|
|
|
52
52
|
|
|
53
53
|
- Read from `./dist` (your Slidev build output)
|
|
54
54
|
- Generate pre-rendered pages in `./dist-prerender`
|
|
55
|
-
- Create one HTML file per slide (1.html, 2.html, 3.html, etc.)
|
|
56
55
|
|
|
57
56
|
## ⚙️ Configuration
|
|
58
57
|
|
|
@@ -74,7 +73,7 @@ export default defineConfig({
|
|
|
74
73
|
// Configuration for individual pages
|
|
75
74
|
pages: [
|
|
76
75
|
{
|
|
77
|
-
|
|
76
|
+
slug: "1",
|
|
78
77
|
meta: {
|
|
79
78
|
title: "Welcome to My Presentation",
|
|
80
79
|
description: "An introduction to the main topics",
|
|
@@ -87,7 +86,7 @@ export default defineConfig({
|
|
|
87
86
|
},
|
|
88
87
|
},
|
|
89
88
|
{
|
|
90
|
-
|
|
89
|
+
slug: "2",
|
|
91
90
|
meta: {
|
|
92
91
|
title: "Understanding the Key Concepts",
|
|
93
92
|
description: "Deep dive into the core ideas",
|
|
@@ -112,12 +111,13 @@ export default defineConfig({
|
|
|
112
111
|
| `outDir` | `string` | `"./dist-prerender"` | Output directory for pre-rendered pages |
|
|
113
112
|
| `port` | `number` | `4173` | Port for the local server during rendering |
|
|
114
113
|
| `pages` | `PageConfig[]` | `[]` | Configuration for individual slides |
|
|
114
|
+
| `plugins` | `PluginFunction[]` | `[]` | Array of plugins to transform HTML output |
|
|
115
115
|
|
|
116
116
|
#### `PageConfig`
|
|
117
117
|
|
|
118
118
|
| Option | Type | Description |
|
|
119
119
|
| ---------- | ------------------ | ------------------------------------------------------------ |
|
|
120
|
-
| `
|
|
120
|
+
| `slug` | `string` (required) | Slide file name without extension (e.g., "1", "2", "3") |
|
|
121
121
|
| `meta` | `BuildHeadOptions` | Metadata configuration for the slide (optional) |
|
|
122
122
|
|
|
123
123
|
#### `BuildHeadOptions`
|
|
@@ -148,6 +148,56 @@ export default defineConfig({
|
|
|
148
148
|
| `twitterImage` | `string` | Twitter image URL |
|
|
149
149
|
| `twitterUrl` | `string` | Twitter URL |
|
|
150
150
|
|
|
151
|
+
### Plugins
|
|
152
|
+
|
|
153
|
+
Plugins allow you to transform the generated HTML for each slide. You can use plugins to inject custom scripts, modify content, or add analytics tracking.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { defineConfig } from "slidev-prerender";
|
|
157
|
+
|
|
158
|
+
export default defineConfig({
|
|
159
|
+
plugins: [
|
|
160
|
+
// Example: Add Google Analytics
|
|
161
|
+
async (html, pageConfig, pageIndex, logger) => {
|
|
162
|
+
logger.info(`Processing slide ${pageIndex + 1}: ${pageConfig.slug}`);
|
|
163
|
+
|
|
164
|
+
const analyticsScript = `
|
|
165
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
|
|
166
|
+
<script>
|
|
167
|
+
window.dataLayer = window.dataLayer || [];
|
|
168
|
+
function gtag(){dataLayer.push(arguments);}
|
|
169
|
+
gtag('js', new Date());
|
|
170
|
+
gtag('config', 'GA_MEASUREMENT_ID');
|
|
171
|
+
</script>
|
|
172
|
+
`;
|
|
173
|
+
|
|
174
|
+
return html.replace('</head>', `${analyticsScript}</head>`);
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
// Example: Add custom meta tag
|
|
178
|
+
(html) => {
|
|
179
|
+
return html.replace(
|
|
180
|
+
'</head>',
|
|
181
|
+
'<meta name="custom-tag" content="custom-value" /></head>'
|
|
182
|
+
);
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### Plugin Function Signature
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
type PluginFunction = (
|
|
192
|
+
html: string, // Current HTML content
|
|
193
|
+
pageConfig: PageConfig, // Configuration for the current page
|
|
194
|
+
pageIndex: number, // Zero-based index of the page
|
|
195
|
+
logger: ConsolaInstance // Logger instance for output
|
|
196
|
+
) => string | Promise<string>;
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
For more details on plugins, see the [Plugins Guide](./docs/guide/plugins.md) and [Plugins Reference](./docs/reference/plugins.md).
|
|
200
|
+
|
|
151
201
|
## 🌐 Deployment
|
|
152
202
|
|
|
153
203
|
Deploy the generated `dist-prerender` folder like any static site:
|
package/dist/index.d.mts
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
|
+
import { ConsolaInstance } from "consola";
|
|
1
2
|
import { SeoMeta } from "@slidev/types";
|
|
2
3
|
import { ResolvableLink } from "unhead/types";
|
|
3
4
|
|
|
4
5
|
//#region src/build/handle-head.d.ts
|
|
5
6
|
type BuildHeadOptions = {
|
|
6
7
|
lang?: string;
|
|
7
|
-
title
|
|
8
|
-
description?: string
|
|
9
|
-
canonicalUrl?: string
|
|
10
|
-
ogImage?: string
|
|
11
|
-
twitterCard?: string
|
|
12
|
-
favicon?: string
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
canonicalUrl?: string;
|
|
11
|
+
ogImage?: string;
|
|
12
|
+
twitterCard?: string;
|
|
13
|
+
favicon?: string;
|
|
13
14
|
webFonts?: ResolvableLink[];
|
|
14
15
|
seoMeta?: Partial<SeoMeta>;
|
|
15
16
|
};
|
|
16
17
|
//#endregion
|
|
17
18
|
//#region src/config/loadConfig.d.ts
|
|
19
|
+
type Page = {
|
|
20
|
+
slug: string;
|
|
21
|
+
meta?: BuildHeadOptions;
|
|
22
|
+
};
|
|
18
23
|
type UserConfig = {
|
|
19
24
|
slidevDist?: string;
|
|
20
25
|
outDir?: string;
|
|
21
|
-
pages?:
|
|
22
|
-
fileName: string;
|
|
23
|
-
meta?: BuildHeadOptions;
|
|
24
|
-
}[];
|
|
26
|
+
pages?: Page[];
|
|
25
27
|
port?: number;
|
|
28
|
+
plugins?: ((html: string, currentPageConfig: Page, pageIndex: number, log: ConsolaInstance) => string | Promise<string>)[];
|
|
26
29
|
};
|
|
27
30
|
//#endregion
|
|
28
31
|
//#region src/config/defineConfig.d.ts
|
package/dist/run.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { createHead, transformHtmlTemplate } from "unhead/server";
|
|
|
8
8
|
import { parseHtmlForUnheadExtraction } from "unhead/parser";
|
|
9
9
|
import { pathToFileURL } from "node:url";
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
11
|
+
import consola from "consola";
|
|
11
12
|
|
|
12
13
|
//#region src/build/handle-dist.ts
|
|
13
14
|
async function removeDist(outDir) {
|
|
@@ -24,12 +25,27 @@ async function serveDist(distPath, port) {
|
|
|
24
25
|
});
|
|
25
26
|
const server = http.createServer((req, res) => serve(req, res));
|
|
26
27
|
await new Promise((resolve$1, reject) => {
|
|
27
|
-
|
|
28
|
-
server.
|
|
28
|
+
const onError = (err) => reject(err);
|
|
29
|
+
server.once("error", onError);
|
|
30
|
+
server.listen(port, () => {
|
|
31
|
+
server.off("error", onError);
|
|
32
|
+
resolve$1();
|
|
33
|
+
});
|
|
29
34
|
});
|
|
35
|
+
let disposed = false;
|
|
30
36
|
return {
|
|
31
37
|
origin: `http://localhost:${port}`,
|
|
32
|
-
close:
|
|
38
|
+
close: async () => {
|
|
39
|
+
if (disposed) return;
|
|
40
|
+
disposed = true;
|
|
41
|
+
new Promise((resolve$1, reject) => {
|
|
42
|
+
try {
|
|
43
|
+
server.close((err) => err ? reject(err) : resolve$1());
|
|
44
|
+
} catch (e) {
|
|
45
|
+
reject(e);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
33
49
|
};
|
|
34
50
|
}
|
|
35
51
|
async function getPageHtml(page, pageUrl) {
|
|
@@ -54,12 +70,12 @@ async function copyDir(src, dst) {
|
|
|
54
70
|
function toAttrValue(unsafe) {
|
|
55
71
|
return JSON.stringify(escapeHtml(String(unsafe)));
|
|
56
72
|
}
|
|
57
|
-
async function applyHead(html, opt) {
|
|
73
|
+
async function applyHead(html, slug, opt) {
|
|
58
74
|
const extracted = parseHtmlForUnheadExtraction(html).input;
|
|
59
75
|
const description = opt.description ? toAttrValue(opt.description) : null;
|
|
60
76
|
return await transformHtmlTemplate(createHead({ init: [extracted, {
|
|
61
77
|
htmlAttrs: opt.lang ? { lang: opt.lang } : void 0,
|
|
62
|
-
title: opt.title,
|
|
78
|
+
title: opt.title ?? slug,
|
|
63
79
|
link: [
|
|
64
80
|
opt.favicon ? {
|
|
65
81
|
rel: "icon",
|
|
@@ -140,10 +156,11 @@ async function loadConfig(cwd = process.cwd()) {
|
|
|
140
156
|
//#endregion
|
|
141
157
|
//#region src/build/getConfig.ts
|
|
142
158
|
const DEFAULT_CONFIG = {
|
|
143
|
-
slidevDist: "dist",
|
|
144
|
-
outDir: "dist-prerender",
|
|
159
|
+
slidevDist: "./dist",
|
|
160
|
+
outDir: "./dist-prerender",
|
|
145
161
|
pages: [],
|
|
146
|
-
port: 4173
|
|
162
|
+
port: 4173,
|
|
163
|
+
plugins: []
|
|
147
164
|
};
|
|
148
165
|
async function getConfig() {
|
|
149
166
|
const config = await loadConfig();
|
|
@@ -151,44 +168,87 @@ async function getConfig() {
|
|
|
151
168
|
slidevDist: config.slidevDist ?? DEFAULT_CONFIG.slidevDist,
|
|
152
169
|
outDir: config.outDir ?? DEFAULT_CONFIG.outDir,
|
|
153
170
|
pages: config.pages ?? DEFAULT_CONFIG.pages,
|
|
154
|
-
port: config.port ?? DEFAULT_CONFIG.port
|
|
171
|
+
port: config.port ?? DEFAULT_CONFIG.port,
|
|
172
|
+
plugins: config.plugins ?? DEFAULT_CONFIG.plugins
|
|
155
173
|
};
|
|
156
174
|
}
|
|
157
175
|
|
|
158
176
|
//#endregion
|
|
159
177
|
//#region src/build/index.ts
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
178
|
+
const log = consola.withTag("slidev-prerender");
|
|
179
|
+
async function build() {
|
|
180
|
+
const startedAt = performance.now();
|
|
181
|
+
log.start("Generating pages...");
|
|
182
|
+
consola.log("");
|
|
183
|
+
const { slidevDist, outDir, pages, port, plugins } = await getConfig();
|
|
184
|
+
log.info(`Input (slidevDist): ${slidevDist}`);
|
|
185
|
+
log.info(`Output (outDir): ${outDir}`);
|
|
186
|
+
log.info(`Pages: ${pages.length}`);
|
|
187
|
+
log.info(`Port: ${port}`);
|
|
188
|
+
consola.log("");
|
|
189
|
+
if (!await checkExistSlidevDist(slidevDist)) {
|
|
190
|
+
log.fatal(`Slidev dist directory not found: ${slidevDist}`);
|
|
191
|
+
log.fatal(`Run: slidev build`);
|
|
192
|
+
throw new Error(`Slidev dist directory not found: ${slidevDist}`);
|
|
166
193
|
}
|
|
194
|
+
log.start("Preparing output directory...");
|
|
167
195
|
await removeDist(outDir);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
196
|
+
await copyDir(slidevDist, outDir);
|
|
197
|
+
log.success("Prepared output directory");
|
|
198
|
+
consola.log("");
|
|
199
|
+
log.start("Starting local server...");
|
|
173
200
|
const { origin, close: serverClose } = await serveDist(slidevDist, port);
|
|
174
|
-
|
|
175
|
-
|
|
201
|
+
let browser;
|
|
202
|
+
log.success(`Server started: ${origin}`);
|
|
203
|
+
consola.log("");
|
|
204
|
+
let generatedPageCount = 0;
|
|
176
205
|
try {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
206
|
+
log.start("Launching browser...");
|
|
207
|
+
browser = await chromium.launch();
|
|
208
|
+
const page = await browser.newPage();
|
|
209
|
+
log.success("Browser launched");
|
|
210
|
+
consola.log("");
|
|
211
|
+
log.start("Rendering pages...");
|
|
212
|
+
for (const [i, pageConfig] of pages.entries()) {
|
|
213
|
+
log.debug(`Render [${i + 1}/${pages.length}]: ${pageConfig.slug}`);
|
|
214
|
+
const originalHtml = await getPageHtml(page, `${origin}/${pageConfig.slug}`);
|
|
215
|
+
const head = pageConfig.meta ?? {};
|
|
216
|
+
const html = await applyHead(originalHtml, pageConfig.slug, head);
|
|
217
|
+
const processedHtml = await plugins.reduce(async (prevPromise, plugin) => {
|
|
218
|
+
const pluginLog = log.withTag(`plugin-${i}`);
|
|
219
|
+
return plugin(await prevPromise, pageConfig, i, pluginLog);
|
|
220
|
+
}, Promise.resolve(html));
|
|
221
|
+
await fs.writeFile(path.join(outDir, `${pageConfig.slug}.html`), processedHtml);
|
|
222
|
+
generatedPageCount++;
|
|
181
223
|
}
|
|
224
|
+
log.success(`Rendered ${generatedPageCount} pages`);
|
|
225
|
+
consola.log("");
|
|
226
|
+
} catch (err) {
|
|
227
|
+
log.error(`Build failed after generating ${generatedPageCount}/${pages.length} pages`);
|
|
228
|
+
log.error(err);
|
|
229
|
+
consola.log("");
|
|
182
230
|
} finally {
|
|
183
|
-
|
|
184
|
-
|
|
231
|
+
log.start("Closing browser and stopping local server...");
|
|
232
|
+
await browser?.close();
|
|
233
|
+
await serverClose();
|
|
234
|
+
log.success("Cleanup done");
|
|
235
|
+
consola.log("");
|
|
236
|
+
}
|
|
237
|
+
const ms = performance.now() - startedAt;
|
|
238
|
+
log.success(`Done in ${(ms / 1e3).toFixed(2)}s`);
|
|
239
|
+
}
|
|
240
|
+
async function checkExistSlidevDist(slidevDist) {
|
|
241
|
+
try {
|
|
242
|
+
await fs.access(slidevDist);
|
|
243
|
+
} catch {
|
|
244
|
+
return false;
|
|
185
245
|
}
|
|
186
|
-
|
|
246
|
+
return true;
|
|
187
247
|
}
|
|
188
248
|
|
|
189
249
|
//#endregion
|
|
190
250
|
//#region src/run.ts
|
|
191
|
-
await
|
|
251
|
+
await build();
|
|
192
252
|
|
|
193
253
|
//#endregion
|
|
194
254
|
export { };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slidev-prerender",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.1-
|
|
4
|
+
"version": "0.0.1-beta.0",
|
|
5
5
|
"description": "",
|
|
6
6
|
"author": "petaxa <soccer.i_y@icloud.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"@playwright/test": "^1.57.0",
|
|
32
32
|
"markdown-it": "^14.1.0",
|
|
33
33
|
"unhead": "^2.1.1",
|
|
34
|
-
"sirv": "^3.0.2"
|
|
34
|
+
"sirv": "^3.0.2",
|
|
35
|
+
"consola": "^3.4.2"
|
|
35
36
|
},
|
|
36
37
|
"scripts": {
|
|
37
38
|
"build": "tsdown",
|