Skip to content

Commit 3bb4cce

Browse files
authored
feat: Log prerendered routes upon build completion (#11)
* feat: Add logs to build completion, showing which routes have been prerendered * fix: Take logLevel into account * test: Add a test for new output
1 parent 81de06a commit 3bb4cce

9 files changed

Lines changed: 141 additions & 3 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"format": "prettier --write --ignore-path .gitignore ."
2727
},
2828
"dependencies": {
29+
"kolorist": "^1.8.0",
2930
"magic-string": "^0.30.6",
3031
"node-html-parser": "^6.1.12",
3132
"simple-code-frame": "^1.3.0",

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/plugins/prerender-plugin.js

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
11
import path from 'node:path';
22
import { promises as fs } from 'node:fs';
33

4+
import { createLogger } from 'vite';
45
import MagicString from 'magic-string';
56
import { parse as htmlParse } from 'node-html-parser';
67
import { SourceMapConsumer } from 'source-map';
78
import { parse as StackTraceParse } from 'stack-trace';
89
import { createCodeFrame } from 'simple-code-frame';
10+
import * as kl from 'kolorist';
911

1012
/**
1113
* @typedef {import('vite').Rollup.OutputChunk} OutputChunk
1214
* @typedef {import('vite').Rollup.OutputAsset} OutputAsset
1315
*/
1416

17+
/**
18+
* @param {import('./types.d.ts').PrerenderedRoute[]} routes
19+
*/
20+
export function prerenderedRoutes(routes) {
21+
return routes.reduce((s, r) => {
22+
s += `\n ${r.url}`;
23+
if (r._discoveredBy) s += kl.dim(` [from ${r._discoveredBy.url}]`);
24+
return s;
25+
}, '');
26+
}
27+
1528
/**
1629
* @param {string} str
1730
*/
@@ -62,6 +75,9 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere
6275
let viteConfig = {};
6376
let userEnabledSourceMaps;
6477

78+
/** @type {import('./types.d.ts').PrerenderedRoute[]} */
79+
let routes = [];
80+
6581
renderTarget ||= 'body';
6682
additionalPrerenderRoutes ||= [];
6783

@@ -113,6 +129,28 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere
113129
config(config) {
114130
userEnabledSourceMaps = !!config.build?.sourcemap;
115131

132+
if (!config.customLogger) {
133+
const logger = createLogger(config.logLevel || 'info');
134+
const loggerInfo = logger.info;
135+
136+
config.customLogger = {
137+
...logger,
138+
info: (msg, options) => {
139+
loggerInfo(msg, options);
140+
if (msg.includes('built in')) {
141+
loggerInfo(
142+
kl.bold(
143+
`Prerendered ${routes.length} ${
144+
routes.length > 1 ? 'pages' : 'page'
145+
}:`,
146+
) + prerenderedRoutes(routes),
147+
options,
148+
);
149+
}
150+
},
151+
};
152+
}
153+
116154
// Enable sourcemaps for generating more actionable error messages
117155
config.build ??= {};
118156
config.build.sourcemap = true;
@@ -345,8 +383,7 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere
345383
// Links discovered during pre-rendering get pushed into the list of routes.
346384
const seen = new Set(['/', ...additionalPrerenderRoutes]);
347385

348-
/** @type {import('./types.d.ts').PrerenderedRoute[]} */
349-
let routes = [...seen].map((link) => ({ url: link }));
386+
routes = [...seen].map((link) => ({ url: link }));
350387

351388
for (const route of routes) {
352389
if (!route.url) continue;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
</head>
6+
<body>
7+
<script prerender type="module" src="/src/index.js"></script>
8+
</body>
9+
</html>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export async function prerender({ url }) {
2+
let links;
3+
if (url == '/') {
4+
links = new Set(['/foo']);
5+
} else if (url == '/foo') {
6+
links = new Set(['/bar']);
7+
}
8+
9+
return {
10+
html: `<h1>Simple Test Result</h1>`,
11+
links,
12+
};
13+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { defineConfig } from 'vite';
2+
import { vitePrerenderPlugin } from 'vite-prerender-plugin';
3+
4+
export default defineConfig({
5+
plugins: [vitePrerenderPlugin()],
6+
});

tests/lib/lifecycle.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import path from 'node:path';
22
import url from 'node:url';
3+
import childProcess from 'node:child_process';
34
import { promises as fs } from 'node:fs';
45
import tmp from 'tmp-promise';
56
import { build } from 'vite';
67

7-
import { copyDependencies } from './utils.js';
8+
import { copyDependencies, stripColors } from './utils.js';
89

910
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
1011

@@ -36,10 +37,45 @@ export async function loadFixture(name, env) {
3637
await copyDependencies(env.tmp.path);
3738
}
3839

40+
/**
41+
* @param {string} cwd
42+
*/
3943
export async function viteBuild(cwd) {
4044
await build({
4145
logLevel: 'silent',
4246
root: cwd,
4347
configFile: path.join(cwd, 'vite.config.js'),
4448
});
4549
}
50+
51+
/**
52+
* @param {string} cwd
53+
*/
54+
export async function viteBuildCli(cwd) {
55+
const child = childProcess.spawn('node', ['node_modules/vite/bin/vite.js', 'build'], {
56+
cwd,
57+
});
58+
59+
const out = {
60+
stdout: [],
61+
stderr: [],
62+
code: 0,
63+
};
64+
65+
child.stdout.on('data', (buffer) => {
66+
const lines = stripColors(buffer.toString());
67+
out.stdout.push(lines);
68+
});
69+
child.stderr.on('data', (buffer) => {
70+
const lines = stripColors(buffer.toString());
71+
out.stderr.push(lines);
72+
});
73+
74+
out.done = new Promise((resolve) => {
75+
child.on('close', (code) => {
76+
resolve((out.code = code));
77+
});
78+
});
79+
80+
return out;
81+
}

tests/lib/utils.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export async function copyDependencies(cwd) {
3434
await copyNodeModule('simple-code-frame');
3535
await copyNodeModule('source-map');
3636
await copyNodeModule('stack-trace');
37+
await copyNodeModule('kolorist');
3738
}
3839

3940
/**
@@ -71,3 +72,5 @@ export async function outputFileExists(dir, file) {
7172
export async function writeFixtureFile(dir, filePath, content) {
7273
await fs.writeFile(path.join(dir, filePath), content);
7374
}
75+
76+
export const stripColors = (str) => str.replace(/\x1b\[(?:[0-9]{1,3}(?:;[0-9]{1,3})*)?[m|K]/g, '');

tests/logs.test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { test } from 'uvu';
2+
import * as assert from 'uvu/assert';
3+
4+
import { setupTest, teardownTest, loadFixture, viteBuildCli } from './lib/lifecycle.js';
5+
6+
let env;
7+
test.before.each(async () => {
8+
env = await setupTest();
9+
});
10+
11+
test.after.each(async () => {
12+
await teardownTest(env);
13+
});
14+
15+
test('Should support the `prerenderScript` plugin option', async () => {
16+
await loadFixture('logs/prerendered-routes', env);
17+
const output = await viteBuildCli(env.tmp.path);
18+
await output.done;
19+
20+
const idx = output.stdout.findIndex((line) => line.includes('Prerendered'));
21+
// The prerender info is pushed as a single log line
22+
const stdout = output.stdout.slice(idx)[0];
23+
24+
assert.match(stdout, 'Prerendered 3 pages:\n');
25+
assert.match(stdout, '/\n');
26+
assert.match(stdout, '/foo [from /]\n');
27+
assert.match(stdout, '/bar [from /foo]\n');
28+
});
29+
30+
test.run();

0 commit comments

Comments
 (0)