slidev-prerender 0.0.1-alpha.2 → 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 +91 -32
- 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) {
|
|
@@ -40,6 +56,7 @@ async function getPageHtml(page, pageUrl) {
|
|
|
40
56
|
//#endregion
|
|
41
57
|
//#region src/utils/file.ts
|
|
42
58
|
async function copyDir(src, dst) {
|
|
59
|
+
await fs.access(src);
|
|
43
60
|
await fs.mkdir(dst, { recursive: true });
|
|
44
61
|
for (const e of await fs.readdir(src, { withFileTypes: true })) {
|
|
45
62
|
const s = path.join(src, e.name);
|
|
@@ -53,12 +70,12 @@ async function copyDir(src, dst) {
|
|
|
53
70
|
function toAttrValue(unsafe) {
|
|
54
71
|
return JSON.stringify(escapeHtml(String(unsafe)));
|
|
55
72
|
}
|
|
56
|
-
async function applyHead(html, opt) {
|
|
73
|
+
async function applyHead(html, slug, opt) {
|
|
57
74
|
const extracted = parseHtmlForUnheadExtraction(html).input;
|
|
58
75
|
const description = opt.description ? toAttrValue(opt.description) : null;
|
|
59
76
|
return await transformHtmlTemplate(createHead({ init: [extracted, {
|
|
60
77
|
htmlAttrs: opt.lang ? { lang: opt.lang } : void 0,
|
|
61
|
-
title: opt.title,
|
|
78
|
+
title: opt.title ?? slug,
|
|
62
79
|
link: [
|
|
63
80
|
opt.favicon ? {
|
|
64
81
|
rel: "icon",
|
|
@@ -139,10 +156,11 @@ async function loadConfig(cwd = process.cwd()) {
|
|
|
139
156
|
//#endregion
|
|
140
157
|
//#region src/build/getConfig.ts
|
|
141
158
|
const DEFAULT_CONFIG = {
|
|
142
|
-
slidevDist: "dist",
|
|
143
|
-
outDir: "dist-prerender",
|
|
159
|
+
slidevDist: "./dist",
|
|
160
|
+
outDir: "./dist-prerender",
|
|
144
161
|
pages: [],
|
|
145
|
-
port: 4173
|
|
162
|
+
port: 4173,
|
|
163
|
+
plugins: []
|
|
146
164
|
};
|
|
147
165
|
async function getConfig() {
|
|
148
166
|
const config = await loadConfig();
|
|
@@ -150,46 +168,87 @@ async function getConfig() {
|
|
|
150
168
|
slidevDist: config.slidevDist ?? DEFAULT_CONFIG.slidevDist,
|
|
151
169
|
outDir: config.outDir ?? DEFAULT_CONFIG.outDir,
|
|
152
170
|
pages: config.pages ?? DEFAULT_CONFIG.pages,
|
|
153
|
-
port: config.port ?? DEFAULT_CONFIG.port
|
|
171
|
+
port: config.port ?? DEFAULT_CONFIG.port,
|
|
172
|
+
plugins: config.plugins ?? DEFAULT_CONFIG.plugins
|
|
154
173
|
};
|
|
155
174
|
}
|
|
156
175
|
|
|
157
176
|
//#endregion
|
|
158
177
|
//#region src/build/index.ts
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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}`);
|
|
165
193
|
}
|
|
194
|
+
log.start("Preparing output directory...");
|
|
166
195
|
await removeDist(outDir);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
} catch {
|
|
172
|
-
console.warn(`Assets directory not found: ${assetsPath}, skipping...`);
|
|
173
|
-
}
|
|
196
|
+
await copyDir(slidevDist, outDir);
|
|
197
|
+
log.success("Prepared output directory");
|
|
198
|
+
consola.log("");
|
|
199
|
+
log.start("Starting local server...");
|
|
174
200
|
const { origin, close: serverClose } = await serveDist(slidevDist, port);
|
|
175
|
-
|
|
176
|
-
|
|
201
|
+
let browser;
|
|
202
|
+
log.success(`Server started: ${origin}`);
|
|
203
|
+
consola.log("");
|
|
204
|
+
let generatedPageCount = 0;
|
|
177
205
|
try {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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++;
|
|
182
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("");
|
|
183
230
|
} finally {
|
|
184
|
-
|
|
185
|
-
|
|
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;
|
|
186
245
|
}
|
|
187
|
-
|
|
246
|
+
return true;
|
|
188
247
|
}
|
|
189
248
|
|
|
190
249
|
//#endregion
|
|
191
250
|
//#region src/run.ts
|
|
192
|
-
await
|
|
251
|
+
await build();
|
|
193
252
|
|
|
194
253
|
//#endregion
|
|
195
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",
|