mirror of
https://github.com/Sevichecc/Urara-Blog.git
synced 2025-05-02 16:59:30 +08:00
788 lines
19 KiB
Text
788 lines
19 KiB
Text
import { readFileSync, writeFileSync } from 'fs';
|
|
import { join, dirname } from 'path';
|
|
import { URL as URL$1, pathToFileURL } from 'url';
|
|
import { w as walk, p as posixify, m as mkdirp } from './chunks/filesystem.js';
|
|
import { installPolyfills } from './node/polyfills.js';
|
|
import { l as logger } from './chunks/utils.js';
|
|
import { l as load_config } from './chunks/index.js';
|
|
import 'assert';
|
|
import 'net';
|
|
import 'http';
|
|
import 'stream';
|
|
import 'buffer';
|
|
import 'util';
|
|
import 'stream/web';
|
|
import 'perf_hooks';
|
|
import 'util/types';
|
|
import 'events';
|
|
import 'tls';
|
|
import 'async_hooks';
|
|
import 'console';
|
|
import 'zlib';
|
|
import 'node:http';
|
|
import 'node:https';
|
|
import 'node:zlib';
|
|
import 'node:stream';
|
|
import 'node:buffer';
|
|
import 'node:util';
|
|
import 'node:url';
|
|
import 'node:net';
|
|
import 'node:fs';
|
|
import 'node:path';
|
|
import 'crypto';
|
|
|
|
const absolute = /^([a-z]+:)?\/?\//;
|
|
const scheme = /^[a-z]+:/;
|
|
|
|
/**
|
|
* @param {string} base
|
|
* @param {string} path
|
|
*/
|
|
function resolve(base, path) {
|
|
if (scheme.test(path)) return path;
|
|
|
|
const base_match = absolute.exec(base);
|
|
const path_match = absolute.exec(path);
|
|
|
|
if (!base_match) {
|
|
throw new Error(`bad base path: "${base}"`);
|
|
}
|
|
|
|
const baseparts = path_match ? [] : base.slice(base_match[0].length).split('/');
|
|
const pathparts = path_match ? path.slice(path_match[0].length).split('/') : path.split('/');
|
|
|
|
baseparts.pop();
|
|
|
|
for (let i = 0; i < pathparts.length; i += 1) {
|
|
const part = pathparts[i];
|
|
if (part === '.') continue;
|
|
else if (part === '..') baseparts.pop();
|
|
else baseparts.push(part);
|
|
}
|
|
|
|
const prefix = (path_match && path_match[0]) || (base_match && base_match[0]) || '';
|
|
|
|
return `${prefix}${baseparts.join('/')}`;
|
|
}
|
|
|
|
/** @param {string} path */
|
|
function is_root_relative(path) {
|
|
return path[0] === '/' && path[1] !== '/';
|
|
}
|
|
|
|
/**
|
|
* @typedef {{
|
|
* fn: () => Promise<any>,
|
|
* fulfil: (value: any) => void,
|
|
* reject: (error: Error) => void
|
|
* }} Task
|
|
*/
|
|
|
|
/** @param {number} concurrency */
|
|
function queue(concurrency) {
|
|
/** @type {Task[]} */
|
|
const tasks = [];
|
|
|
|
let current = 0;
|
|
|
|
/** @type {(value?: any) => void} */
|
|
let fulfil;
|
|
|
|
/** @type {(error: Error) => void} */
|
|
let reject;
|
|
|
|
let closed = false;
|
|
|
|
const done = new Promise((f, r) => {
|
|
fulfil = f;
|
|
reject = r;
|
|
});
|
|
|
|
done.catch(() => {
|
|
// this is necessary in case a catch handler is never added
|
|
// to the done promise by the user
|
|
});
|
|
|
|
function dequeue() {
|
|
if (current < concurrency) {
|
|
const task = tasks.shift();
|
|
|
|
if (task) {
|
|
current += 1;
|
|
const promise = Promise.resolve(task.fn());
|
|
|
|
promise
|
|
.then(task.fulfil, (err) => {
|
|
task.reject(err);
|
|
reject(err);
|
|
})
|
|
.then(() => {
|
|
current -= 1;
|
|
dequeue();
|
|
});
|
|
} else if (current === 0) {
|
|
closed = true;
|
|
fulfil();
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
/** @param {() => any} fn */
|
|
add: (fn) => {
|
|
if (closed) throw new Error('Cannot add tasks to a queue that has ended');
|
|
|
|
const promise = new Promise((fulfil, reject) => {
|
|
tasks.push({ fn, fulfil, reject });
|
|
});
|
|
|
|
dequeue();
|
|
return promise;
|
|
},
|
|
|
|
done: () => {
|
|
if (current === 0) {
|
|
closed = true;
|
|
fulfil();
|
|
}
|
|
|
|
return done;
|
|
}
|
|
};
|
|
}
|
|
|
|
const DOCTYPE = 'DOCTYPE';
|
|
const CDATA_OPEN = '[CDATA[';
|
|
const CDATA_CLOSE = ']]>';
|
|
const COMMENT_OPEN = '--';
|
|
const COMMENT_CLOSE = '-->';
|
|
|
|
const TAG_OPEN = /[a-zA-Z]/;
|
|
const TAG_CHAR = /[a-zA-Z0-9]/;
|
|
const ATTRIBUTE_NAME = /[^\t\n\f />"'=]/;
|
|
|
|
const WHITESPACE = /[\s\n\r]/;
|
|
|
|
/** @param {string} html */
|
|
function crawl(html) {
|
|
/** @type {string[]} */
|
|
const hrefs = [];
|
|
|
|
let i = 0;
|
|
main: while (i < html.length) {
|
|
const char = html[i];
|
|
|
|
if (char === '<') {
|
|
if (html[i + 1] === '!') {
|
|
i += 2;
|
|
|
|
if (html.slice(i, i + DOCTYPE.length).toUpperCase() === DOCTYPE) {
|
|
i += DOCTYPE.length;
|
|
while (i < html.length) {
|
|
if (html[i++] === '>') {
|
|
continue main;
|
|
}
|
|
}
|
|
}
|
|
|
|
// skip cdata
|
|
if (html.slice(i, i + CDATA_OPEN.length) === CDATA_OPEN) {
|
|
i += CDATA_OPEN.length;
|
|
while (i < html.length) {
|
|
if (html.slice(i, i + CDATA_CLOSE.length) === CDATA_CLOSE) {
|
|
i += CDATA_CLOSE.length;
|
|
continue main;
|
|
}
|
|
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
// skip comments
|
|
if (html.slice(i, i + COMMENT_OPEN.length) === COMMENT_OPEN) {
|
|
i += COMMENT_OPEN.length;
|
|
while (i < html.length) {
|
|
if (html.slice(i, i + COMMENT_CLOSE.length) === COMMENT_CLOSE) {
|
|
i += COMMENT_CLOSE.length;
|
|
continue main;
|
|
}
|
|
|
|
i += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// parse opening tags
|
|
const start = ++i;
|
|
if (TAG_OPEN.test(html[start])) {
|
|
while (i < html.length) {
|
|
if (!TAG_CHAR.test(html[i])) {
|
|
break;
|
|
}
|
|
|
|
i += 1;
|
|
}
|
|
|
|
const tag = html.slice(start, i).toUpperCase();
|
|
|
|
if (tag === 'SCRIPT' || tag === 'STYLE') {
|
|
while (i < html.length) {
|
|
if (
|
|
html[i] === '<' &&
|
|
html[i + 1] === '/' &&
|
|
html.slice(i + 2, i + 2 + tag.length).toUpperCase() === tag
|
|
) {
|
|
continue main;
|
|
}
|
|
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
let href = '';
|
|
let rel = '';
|
|
|
|
while (i < html.length) {
|
|
const start = i;
|
|
|
|
const char = html[start];
|
|
if (char === '>') break;
|
|
|
|
if (ATTRIBUTE_NAME.test(char)) {
|
|
i += 1;
|
|
|
|
while (i < html.length) {
|
|
if (!ATTRIBUTE_NAME.test(html[i])) {
|
|
break;
|
|
}
|
|
|
|
i += 1;
|
|
}
|
|
|
|
const name = html.slice(start, i).toLowerCase();
|
|
|
|
while (WHITESPACE.test(html[i])) i += 1;
|
|
|
|
if (html[i] === '=') {
|
|
i += 1;
|
|
while (WHITESPACE.test(html[i])) i += 1;
|
|
|
|
let value;
|
|
|
|
if (html[i] === "'" || html[i] === '"') {
|
|
const quote = html[i++];
|
|
|
|
const start = i;
|
|
let escaped = false;
|
|
|
|
while (i < html.length) {
|
|
if (!escaped) {
|
|
const char = html[i];
|
|
|
|
if (html[i] === quote) {
|
|
break;
|
|
}
|
|
|
|
if (char === '\\') {
|
|
escaped = true;
|
|
}
|
|
}
|
|
|
|
i += 1;
|
|
}
|
|
|
|
value = html.slice(start, i);
|
|
} else {
|
|
const start = i;
|
|
while (html[i] !== '>' && !WHITESPACE.test(html[i])) i += 1;
|
|
value = html.slice(start, i);
|
|
|
|
i -= 1;
|
|
}
|
|
|
|
if (name === 'href') {
|
|
href = value;
|
|
} else if (name === 'rel') {
|
|
rel = value;
|
|
} else if (name === 'src') {
|
|
hrefs.push(value);
|
|
} else if (name === 'srcset') {
|
|
const candidates = [];
|
|
let insideURL = true;
|
|
value = value.trim();
|
|
for (let i = 0; i < value.length; i++) {
|
|
if (value[i] === ',' && (!insideURL || (insideURL && value[i + 1] === ' '))) {
|
|
candidates.push(value.slice(0, i));
|
|
value = value.substring(i + 1).trim();
|
|
i = 0;
|
|
insideURL = true;
|
|
} else if (value[i] === ' ') {
|
|
insideURL = false;
|
|
}
|
|
}
|
|
candidates.push(value);
|
|
for (const candidate of candidates) {
|
|
const src = candidate.split(WHITESPACE)[0];
|
|
hrefs.push(src);
|
|
}
|
|
}
|
|
} else {
|
|
i -= 1;
|
|
}
|
|
}
|
|
|
|
i += 1;
|
|
}
|
|
|
|
if (href && !/\bexternal\b/i.test(rel)) {
|
|
hrefs.push(href);
|
|
}
|
|
}
|
|
}
|
|
|
|
i += 1;
|
|
}
|
|
|
|
return hrefs;
|
|
}
|
|
|
|
/**
|
|
* Inside a script element, only `</script` and `<!--` hold special meaning to the HTML parser.
|
|
*
|
|
* The first closes the script element, so everything after is treated as raw HTML.
|
|
* The second disables further parsing until `-->`, so the script element might be unexpectedly
|
|
* kept open until until an unrelated HTML comment in the page.
|
|
*
|
|
* U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR are escaped for the sake of pre-2018
|
|
* browsers.
|
|
*
|
|
* @see tests for unsafe parsing examples.
|
|
* @see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
|
|
* @see https://html.spec.whatwg.org/multipage/syntax.html#cdata-rcdata-restrictions
|
|
* @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-state
|
|
* @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escaped-state
|
|
* @see https://github.com/tc39/proposal-json-superset
|
|
* @type {Record<string, string>}
|
|
*/
|
|
const render_json_payload_script_dict = {
|
|
'<': '\\u003C',
|
|
'\u2028': '\\u2028',
|
|
'\u2029': '\\u2029'
|
|
};
|
|
|
|
new RegExp(
|
|
`[${Object.keys(render_json_payload_script_dict).join('')}]`,
|
|
'g'
|
|
);
|
|
|
|
/**
|
|
* When inside a double-quoted attribute value, only `&` and `"` hold special meaning.
|
|
* @see https://html.spec.whatwg.org/multipage/parsing.html#attribute-value-(double-quoted)-state
|
|
* @type {Record<string, string>}
|
|
*/
|
|
const escape_html_attr_dict = {
|
|
'&': '&',
|
|
'"': '"'
|
|
};
|
|
|
|
const escape_html_attr_regex = new RegExp(
|
|
// special characters
|
|
`[${Object.keys(escape_html_attr_dict).join('')}]|` +
|
|
// high surrogate without paired low surrogate
|
|
'[\\ud800-\\udbff](?![\\udc00-\\udfff])|' +
|
|
// a valid surrogate pair, the only match with 2 code units
|
|
// we match it so that we can match unpaired low surrogates in the same pass
|
|
// TODO: use lookbehind assertions once they are widely supported: (?<![\ud800-udbff])[\udc00-\udfff]
|
|
'[\\ud800-\\udbff][\\udc00-\\udfff]|' +
|
|
// unpaired low surrogate (see previous match)
|
|
'[\\udc00-\\udfff]',
|
|
'g'
|
|
);
|
|
|
|
/**
|
|
* Formats a string to be used as an attribute's value in raw HTML.
|
|
*
|
|
* It escapes unpaired surrogates (which are allowed in js strings but invalid in HTML), escapes
|
|
* characters that are special in attributes, and surrounds the whole string in double-quotes.
|
|
*
|
|
* @param {string} str
|
|
* @returns {string} Escaped string surrounded by double-quotes.
|
|
* @example const html = `<tag data-value=${escape_html_attr('value')}>...</tag>`;
|
|
*/
|
|
function escape_html_attr(str) {
|
|
const escaped_str = str.replace(escape_html_attr_regex, (match) => {
|
|
if (match.length === 2) {
|
|
// valid surrogate pair
|
|
return match;
|
|
}
|
|
|
|
return escape_html_attr_dict[match] ?? `&#${match.charCodeAt(0)};`;
|
|
});
|
|
|
|
return `"${escaped_str}"`;
|
|
}
|
|
|
|
/**
|
|
* @typedef {import('types').PrerenderErrorHandler} PrerenderErrorHandler
|
|
* @typedef {import('types').Logger} Logger
|
|
*/
|
|
|
|
const [, , client_out_dir, results_path, manifest_path, verbose] = process.argv;
|
|
|
|
prerender();
|
|
|
|
/**
|
|
* @param {Parameters<PrerenderErrorHandler>[0]} details
|
|
* @param {import('types').ValidatedKitConfig} config
|
|
*/
|
|
function format_error({ status, path, referrer, referenceType }, config) {
|
|
const message =
|
|
status === 404 && !path.startsWith(config.paths.base)
|
|
? `${path} does not begin with \`base\`, which is configured in \`paths.base\` and can be imported from \`$app/paths\``
|
|
: path;
|
|
|
|
return `${status} ${message}${referrer ? ` (${referenceType} from ${referrer})` : ''}`;
|
|
}
|
|
|
|
/**
|
|
* @param {Logger} log
|
|
* @param {import('types').ValidatedKitConfig} config
|
|
* @returns {PrerenderErrorHandler}
|
|
*/
|
|
function normalise_error_handler(log, config) {
|
|
switch (config.prerender.onError) {
|
|
case 'continue':
|
|
return (details) => {
|
|
log.error(format_error(details, config));
|
|
};
|
|
case 'fail':
|
|
return (details) => {
|
|
throw new Error(format_error(details, config));
|
|
};
|
|
default:
|
|
return config.prerender.onError;
|
|
}
|
|
}
|
|
|
|
const OK = 2;
|
|
const REDIRECT = 3;
|
|
|
|
/**
|
|
* @param {import('types').Prerendered} prerendered
|
|
*/
|
|
const output_and_exit = (prerendered) => {
|
|
writeFileSync(
|
|
results_path,
|
|
JSON.stringify(prerendered, (_key, value) =>
|
|
value instanceof Map ? Array.from(value.entries()) : value
|
|
)
|
|
);
|
|
process.exit(0);
|
|
};
|
|
|
|
async function prerender() {
|
|
/** @type {import('types').Prerendered} */
|
|
const prerendered = {
|
|
pages: new Map(),
|
|
assets: new Map(),
|
|
redirects: new Map(),
|
|
paths: []
|
|
};
|
|
|
|
/** @type {import('types').ValidatedKitConfig} */
|
|
const config = (await load_config()).kit;
|
|
|
|
if (!config.prerender.enabled) {
|
|
output_and_exit(prerendered);
|
|
return;
|
|
}
|
|
|
|
/** @type {import('types').Logger} */
|
|
const log = logger({
|
|
verbose: verbose === 'true'
|
|
});
|
|
|
|
installPolyfills();
|
|
const { fetch } = globalThis;
|
|
globalThis.fetch = async (info, init) => {
|
|
/** @type {string} */
|
|
let url;
|
|
|
|
/** @type {RequestInit} */
|
|
let opts = {};
|
|
|
|
if (info instanceof Request) {
|
|
url = info.url;
|
|
|
|
opts = {
|
|
method: info.method,
|
|
headers: info.headers,
|
|
body: info.body,
|
|
mode: info.mode,
|
|
credentials: info.credentials,
|
|
cache: info.cache,
|
|
redirect: info.redirect,
|
|
referrer: info.referrer,
|
|
integrity: info.integrity
|
|
};
|
|
} else {
|
|
url = info.toString();
|
|
}
|
|
|
|
if (url.startsWith(config.prerender.origin + '/')) {
|
|
const request = new Request(url, opts);
|
|
const response = await server.respond(request, {
|
|
getClientAddress,
|
|
prerendering: {
|
|
dependencies: new Map()
|
|
}
|
|
});
|
|
|
|
const decoded = new URL$1(url).pathname;
|
|
|
|
save(
|
|
'dependencies',
|
|
response,
|
|
Buffer.from(await response.clone().arrayBuffer()),
|
|
decoded,
|
|
encodeURI(decoded),
|
|
null,
|
|
'fetched'
|
|
);
|
|
|
|
return response;
|
|
}
|
|
|
|
return fetch(info, init);
|
|
};
|
|
|
|
const server_root = join(config.outDir, 'output');
|
|
|
|
/** @type {import('types').ServerModule} */
|
|
const { Server, override } = await import(pathToFileURL(`${server_root}/server/index.js`).href);
|
|
|
|
/** @type {import('types').SSRManifest} */
|
|
const manifest = (await import(pathToFileURL(`${server_root}/server/manifest.js`).href)).manifest;
|
|
|
|
override({
|
|
paths: config.paths,
|
|
prerendering: true,
|
|
read: (file) => readFileSync(join(config.files.assets, file))
|
|
});
|
|
|
|
const server = new Server(manifest);
|
|
|
|
const error = normalise_error_handler(log, config);
|
|
|
|
const q = queue(config.prerender.concurrency);
|
|
|
|
/**
|
|
* @param {string} path
|
|
* @param {boolean} is_html
|
|
*/
|
|
function output_filename(path, is_html) {
|
|
const file = path.slice(config.paths.base.length + 1) || 'index.html';
|
|
|
|
if (is_html && !file.endsWith('.html')) {
|
|
return file + (file.endsWith('/') ? 'index.html' : '.html');
|
|
}
|
|
|
|
return file;
|
|
}
|
|
|
|
const files = new Set(walk(client_out_dir).map(posixify));
|
|
const seen = new Set();
|
|
const written = new Set();
|
|
|
|
/**
|
|
* @param {string | null} referrer
|
|
* @param {string} decoded
|
|
* @param {string} [encoded]
|
|
*/
|
|
function enqueue(referrer, decoded, encoded) {
|
|
if (seen.has(decoded)) return;
|
|
seen.add(decoded);
|
|
|
|
const file = decoded.slice(config.paths.base.length + 1);
|
|
if (files.has(file)) return;
|
|
|
|
return q.add(() => visit(decoded, encoded || encodeURI(decoded), referrer));
|
|
}
|
|
|
|
/**
|
|
* @param {string} decoded
|
|
* @param {string} encoded
|
|
* @param {string?} referrer
|
|
*/
|
|
async function visit(decoded, encoded, referrer) {
|
|
if (!decoded.startsWith(config.paths.base)) {
|
|
error({ status: 404, path: decoded, referrer, referenceType: 'linked' });
|
|
return;
|
|
}
|
|
|
|
/** @type {Map<string, import('types').PrerenderDependency>} */
|
|
const dependencies = new Map();
|
|
|
|
const response = await server.respond(new Request(config.prerender.origin + encoded), {
|
|
getClientAddress,
|
|
prerendering: {
|
|
dependencies
|
|
}
|
|
});
|
|
|
|
const body = Buffer.from(await response.arrayBuffer());
|
|
|
|
save('pages', response, body, decoded, encoded, referrer, 'linked');
|
|
|
|
for (const [dependency_path, result] of dependencies) {
|
|
// this seems circuitous, but using new URL allows us to not care
|
|
// whether dependency_path is encoded or not
|
|
const encoded_dependency_path = new URL$1(dependency_path, 'http://localhost').pathname;
|
|
const decoded_dependency_path = decodeURI(encoded_dependency_path);
|
|
|
|
const body = result.body ?? new Uint8Array(await result.response.arrayBuffer());
|
|
save(
|
|
'dependencies',
|
|
result.response,
|
|
body,
|
|
decoded_dependency_path,
|
|
encoded_dependency_path,
|
|
decoded,
|
|
'fetched'
|
|
);
|
|
}
|
|
|
|
if (config.prerender.crawl && response.headers.get('content-type') === 'text/html') {
|
|
for (const href of crawl(body.toString())) {
|
|
if (href.startsWith('data:') || href.startsWith('#')) continue;
|
|
|
|
const resolved = resolve(encoded, href);
|
|
if (!is_root_relative(resolved)) continue;
|
|
|
|
const { pathname, search } = new URL$1(resolved, 'http://localhost');
|
|
|
|
enqueue(decoded, decodeURI(pathname), pathname);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {'pages' | 'dependencies'} category
|
|
* @param {Response} response
|
|
* @param {string | Uint8Array} body
|
|
* @param {string} decoded
|
|
* @param {string} encoded
|
|
* @param {string | null} referrer
|
|
* @param {'linked' | 'fetched'} referenceType
|
|
*/
|
|
function save(category, response, body, decoded, encoded, referrer, referenceType) {
|
|
const response_type = Math.floor(response.status / 100);
|
|
const type = /** @type {string} */ (response.headers.get('content-type'));
|
|
const is_html = response_type === REDIRECT || type === 'text/html';
|
|
|
|
const file = output_filename(decoded, is_html);
|
|
const dest = `${config.outDir}/output/prerendered/${category}/${file}`;
|
|
|
|
if (written.has(file)) return;
|
|
|
|
if (response_type === REDIRECT) {
|
|
const location = response.headers.get('location');
|
|
|
|
if (location) {
|
|
const resolved = resolve(encoded, location);
|
|
if (is_root_relative(resolved)) {
|
|
enqueue(decoded, decodeURI(resolved), resolved);
|
|
}
|
|
|
|
if (!response.headers.get('x-sveltekit-normalize')) {
|
|
mkdirp(dirname(dest));
|
|
|
|
log.warn(`${response.status} ${decoded} -> ${location}`);
|
|
|
|
writeFileSync(
|
|
dest,
|
|
`<meta http-equiv="refresh" content=${escape_html_attr(`0;url=${location}`)}>`
|
|
);
|
|
|
|
written.add(file);
|
|
|
|
if (!prerendered.redirects.has(decoded)) {
|
|
prerendered.redirects.set(decoded, {
|
|
status: response.status,
|
|
location: resolved
|
|
});
|
|
|
|
prerendered.paths.push(decoded);
|
|
}
|
|
}
|
|
} else {
|
|
log.warn(`location header missing on redirect received from ${decoded}`);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (response.status === 200) {
|
|
mkdirp(dirname(dest));
|
|
|
|
log.info(`${response.status} ${decoded}`);
|
|
writeFileSync(dest, body);
|
|
written.add(file);
|
|
|
|
if (is_html) {
|
|
prerendered.pages.set(decoded, {
|
|
file
|
|
});
|
|
} else {
|
|
prerendered.assets.set(decoded, {
|
|
type
|
|
});
|
|
}
|
|
|
|
prerendered.paths.push(decoded);
|
|
} else if (response_type !== OK) {
|
|
error({ status: response.status, path: decoded, referrer, referenceType });
|
|
}
|
|
}
|
|
|
|
if (config.prerender.enabled) {
|
|
for (const entry of config.prerender.entries) {
|
|
if (entry === '*') {
|
|
/** @type {import('types').ManifestData} */
|
|
const { routes } = (await import(pathToFileURL(manifest_path).href)).manifest._;
|
|
const entries = routes
|
|
.map((route) => (route.type === 'page' ? route.path : ''))
|
|
.filter(Boolean);
|
|
|
|
for (const entry of entries) {
|
|
enqueue(null, config.paths.base + entry); // TODO can we pre-normalize these?
|
|
}
|
|
} else {
|
|
enqueue(null, config.paths.base + entry);
|
|
}
|
|
}
|
|
|
|
await q.done();
|
|
}
|
|
|
|
const rendered = await server.respond(new Request(config.prerender.origin + '/[fallback]'), {
|
|
getClientAddress,
|
|
prerendering: {
|
|
fallback: true,
|
|
dependencies: new Map()
|
|
}
|
|
});
|
|
|
|
const file = `${config.outDir}/output/prerendered/fallback.html`;
|
|
mkdirp(dirname(file));
|
|
writeFileSync(file, await rendered.text());
|
|
|
|
output_and_exit(prerendered);
|
|
}
|
|
|
|
/** @return {string} */
|
|
function getClientAddress() {
|
|
throw new Error('Cannot read clientAddress during prerendering');
|
|
}
|
|
|
|
export { prerender };
|