mirror of
https://github.com/Sevichecc/Urara-Blog.git
synced 2025-05-05 14:09:30 +08:00
2513 lines
70 KiB
Text
2513 lines
70 KiB
Text
import { fork } from 'node:child_process';
|
|
import fs$1 from 'node:fs';
|
|
import path from 'node:path';
|
|
import { a as load_template, $, l as load_config } from './chunks/index.js';
|
|
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
|
import * as vite from 'vite';
|
|
import { loadEnv } from 'vite';
|
|
import { p as posixify, m as mkdirp, r as rimraf } from './chunks/filesystem.js';
|
|
import { g as get_aliases, r as resolve_entry, s, m as merge_vite_configs, a as get_vite_config, i as init, b as get_env, u as update, p as prevent_illegal_vite_imports, c as parse_route_id, d as all, e as prevent_illegal_rollup_imports } from './chunks/sync.js';
|
|
import * as fs from 'fs';
|
|
import fs__default, { readdirSync, statSync } from 'fs';
|
|
import path__default, { resolve, join, normalize } from 'path';
|
|
import { g as get_runtime_directory, a as get_runtime_prefix, b as get_mime_lookup, l as logger } from './chunks/utils.js';
|
|
import * as qs from 'querystring';
|
|
import { URL as URL$1, pathToFileURL } from 'url';
|
|
import { getRequest, setResponse } from './node.js';
|
|
import { installPolyfills } from './node/polyfills.js';
|
|
import { c as coalesce_to_error } from './chunks/error.js';
|
|
import { fileURLToPath } from 'node:url';
|
|
import './chunks/write_tsconfig.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:net';
|
|
import 'crypto';
|
|
|
|
/**
|
|
* @typedef {import('rollup').RollupOutput} RollupOutput
|
|
* @typedef {import('rollup').OutputChunk} OutputChunk
|
|
* @typedef {import('rollup').OutputAsset} OutputAsset
|
|
*/
|
|
|
|
/**
|
|
* Invokes Vite.
|
|
* @param {import('vite').UserConfig} config
|
|
*/
|
|
async function create_build(config) {
|
|
const { output } = /** @type {RollupOutput} */ (
|
|
await vite.build({ ...config, configFile: false })
|
|
);
|
|
|
|
const chunks = output.filter(
|
|
/** @returns {output is OutputChunk} */ (output) => output.type === 'chunk'
|
|
);
|
|
|
|
const assets = output.filter(
|
|
/** @returns {output is OutputAsset} */ (output) => output.type === 'asset'
|
|
);
|
|
|
|
return { chunks, assets };
|
|
}
|
|
|
|
/**
|
|
* Adds transitive JS and CSS dependencies to the js and css inputs.
|
|
* @param {import('vite').Manifest} manifest
|
|
* @param {string} entry
|
|
* @param {boolean} add_dynamic_css
|
|
*/
|
|
function find_deps$1(manifest, entry, add_dynamic_css) {
|
|
/** @type {Set<string>} */
|
|
const seen = new Set();
|
|
|
|
/** @type {Set<string>} */
|
|
const imports = new Set();
|
|
|
|
/** @type {Set<string>} */
|
|
const stylesheets = new Set();
|
|
|
|
/**
|
|
* @param {string} file
|
|
* @param {boolean} add_js
|
|
*/
|
|
function traverse(file, add_js) {
|
|
if (seen.has(file)) return;
|
|
seen.add(file);
|
|
|
|
const chunk = manifest[file];
|
|
|
|
if (add_js) imports.add(chunk.file);
|
|
|
|
if (chunk.css) {
|
|
chunk.css.forEach((file) => stylesheets.add(file));
|
|
}
|
|
|
|
if (chunk.imports) {
|
|
chunk.imports.forEach((file) => traverse(file, add_js));
|
|
}
|
|
|
|
if (add_dynamic_css && chunk.dynamicImports) {
|
|
chunk.dynamicImports.forEach((file) => traverse(file, false));
|
|
}
|
|
}
|
|
|
|
traverse(entry, true);
|
|
|
|
return {
|
|
file: manifest[entry].file,
|
|
imports: Array.from(imports),
|
|
stylesheets: Array.from(stylesheets)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The Vite configuration that we use by default.
|
|
* @param {{
|
|
* config: import('types').ValidatedConfig;
|
|
* input: Record<string, string>;
|
|
* ssr: boolean;
|
|
* outDir: string;
|
|
* }} options
|
|
* @return {import('vite').UserConfig}
|
|
*/
|
|
const get_default_config = function ({ config, input, ssr, outDir }) {
|
|
return {
|
|
appType: 'custom',
|
|
base: assets_base(config.kit),
|
|
build: {
|
|
cssCodeSplit: true,
|
|
manifest: true,
|
|
outDir,
|
|
polyfillModulePreload: false,
|
|
rollupOptions: {
|
|
input,
|
|
output: {
|
|
format: 'esm',
|
|
entryFileNames: ssr ? '[name].js' : `${config.kit.appDir}/immutable/[name]-[hash].js`,
|
|
chunkFileNames: `${config.kit.appDir}/immutable/chunks/[name]-[hash].js`,
|
|
assetFileNames: `${config.kit.appDir}/immutable/assets/[name]-[hash][extname]`
|
|
},
|
|
preserveEntrySignatures: 'strict'
|
|
},
|
|
ssr,
|
|
target: ssr ? 'node14.8' : undefined
|
|
},
|
|
define: {
|
|
__SVELTEKIT_ADAPTER_NAME__: JSON.stringify(config.kit.adapter?.name),
|
|
__SVELTEKIT_APP_VERSION__: JSON.stringify(config.kit.version.name),
|
|
__SVELTEKIT_APP_VERSION_FILE__: JSON.stringify(`${config.kit.appDir}/version.json`),
|
|
__SVELTEKIT_APP_VERSION_POLL_INTERVAL__: JSON.stringify(config.kit.version.pollInterval),
|
|
__SVELTEKIT_DEV__: 'false'
|
|
},
|
|
publicDir: ssr ? false : config.kit.files.assets,
|
|
resolve: {
|
|
alias: get_aliases(config.kit)
|
|
},
|
|
ssr: {
|
|
// when developing against the Kit src code, we want to ensure that
|
|
// our dependencies are bundled so that apps don't need to install
|
|
// them as peerDependencies
|
|
noExternal: []
|
|
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @param {import('types').ValidatedKitConfig} config
|
|
* @returns {string}
|
|
*/
|
|
function assets_base(config) {
|
|
// TODO this is so that Vite's preloading works. Unfortunately, it fails
|
|
// during `svelte-kit preview`, because we use a local asset path. This
|
|
// may be fixed in Vite 3: https://github.com/vitejs/vite/issues/2009
|
|
const { base, assets } = config.paths;
|
|
return `${assets || base}/`;
|
|
}
|
|
|
|
const method_names = new Set(['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']);
|
|
|
|
// If we'd written this in TypeScript, it could be easy...
|
|
/**
|
|
* @param {string} str
|
|
* @returns {str is import('types').HttpMethod}
|
|
*/
|
|
function is_http_method(str) {
|
|
return method_names.has(str);
|
|
}
|
|
|
|
/**
|
|
* @param {{
|
|
* hooks: string;
|
|
* config: import('types').ValidatedConfig;
|
|
* has_service_worker: boolean;
|
|
* runtime: string;
|
|
* template: string;
|
|
* }} opts
|
|
*/
|
|
const server_template = ({ config, hooks, has_service_worker, runtime, template }) => `
|
|
import root from '__GENERATED__/root.svelte';
|
|
import { respond } from '${runtime}/server/index.js';
|
|
import { set_paths, assets, base } from '${runtime}/paths.js';
|
|
import { set_prerendering } from '${runtime}/env.js';
|
|
import { set_private_env } from '${runtime}/env-private.js';
|
|
import { set_public_env } from '${runtime}/env-public.js';
|
|
|
|
const template = ({ head, body, assets, nonce }) => ${s(template)
|
|
.replace('%sveltekit.head%', '" + head + "')
|
|
.replace('%sveltekit.body%', '" + body + "')
|
|
.replace(/%sveltekit\.assets%/g, '" + assets + "')
|
|
.replace(/%sveltekit\.nonce%/g, '" + nonce + "')};
|
|
|
|
let read = null;
|
|
|
|
set_paths(${s(config.kit.paths)});
|
|
|
|
let default_protocol = 'https';
|
|
|
|
// allow paths to be globally overridden
|
|
// in svelte-kit preview and in prerendering
|
|
export function override(settings) {
|
|
default_protocol = settings.protocol || default_protocol;
|
|
set_paths(settings.paths);
|
|
set_prerendering(settings.prerendering);
|
|
read = settings.read;
|
|
}
|
|
|
|
export class Server {
|
|
constructor(manifest) {
|
|
this.options = {
|
|
csp: ${s(config.kit.csp)},
|
|
dev: false,
|
|
get_stack: error => String(error), // for security
|
|
handle_error: (error, event) => {
|
|
this.options.hooks.handleError({
|
|
error,
|
|
event,
|
|
|
|
// TODO remove for 1.0
|
|
// @ts-expect-error
|
|
get request() {
|
|
throw new Error('request in handleError has been replaced with event. See https://github.com/sveltejs/kit/pull/3384 for details');
|
|
}
|
|
});
|
|
error.stack = this.options.get_stack(error);
|
|
},
|
|
hooks: null,
|
|
hydrate: ${s(config.kit.browser.hydrate)},
|
|
manifest,
|
|
method_override: ${s(config.kit.methodOverride)},
|
|
paths: { base, assets },
|
|
prefix: assets + '/',
|
|
prerender: {
|
|
default: ${config.kit.prerender.default},
|
|
enabled: ${config.kit.prerender.enabled}
|
|
},
|
|
public_env: {},
|
|
read,
|
|
root,
|
|
service_worker: ${has_service_worker ? "base + '/service-worker.js'" : 'null'},
|
|
router: ${s(config.kit.browser.router)},
|
|
template,
|
|
template_contains_nonce: ${template.includes('%sveltekit.nonce%')},
|
|
trailing_slash: ${s(config.kit.trailingSlash)}
|
|
};
|
|
}
|
|
|
|
init({ env }) {
|
|
const entries = Object.entries(env);
|
|
|
|
const prv = Object.fromEntries(entries.filter(([k]) => !k.startsWith('${
|
|
config.kit.env.publicPrefix
|
|
}')));
|
|
|
|
const pub = Object.fromEntries(entries.filter(([k]) => k.startsWith('${
|
|
config.kit.env.publicPrefix
|
|
}')));
|
|
|
|
set_private_env(prv);
|
|
set_public_env(pub);
|
|
|
|
this.options.public_env = pub;
|
|
}
|
|
|
|
async respond(request, options = {}) {
|
|
if (!(request instanceof Request)) {
|
|
throw new Error('The first argument to server.respond must be a Request object. See https://github.com/sveltejs/kit/pull/3384 for details');
|
|
}
|
|
|
|
if (!this.options.hooks) {
|
|
const module = await import(${s(hooks)});
|
|
this.options.hooks = {
|
|
getSession: module.getSession || (() => ({})),
|
|
handle: module.handle || (({ event, resolve }) => resolve(event)),
|
|
handleError: module.handleError || (({ error }) => console.error(error.stack)),
|
|
externalFetch: module.externalFetch || fetch
|
|
};
|
|
}
|
|
|
|
return respond(request, this.options, options);
|
|
}
|
|
}
|
|
`;
|
|
|
|
/**
|
|
* @param {{
|
|
* cwd: string;
|
|
* config: import('types').ValidatedConfig;
|
|
* vite_config: import('vite').ResolvedConfig;
|
|
* vite_config_env: import('vite').ConfigEnv;
|
|
* manifest_data: import('types').ManifestData;
|
|
* build_dir: string;
|
|
* output_dir: string;
|
|
* service_worker_entry_file: string | null;
|
|
* }} options
|
|
* @param {{ vite_manifest: import('vite').Manifest, assets: import('rollup').OutputAsset[] }} client
|
|
*/
|
|
async function build_server(options, client) {
|
|
const {
|
|
cwd,
|
|
config,
|
|
vite_config,
|
|
vite_config_env,
|
|
manifest_data,
|
|
build_dir,
|
|
output_dir,
|
|
service_worker_entry_file
|
|
} = options;
|
|
|
|
let hooks_file = resolve_entry(config.kit.files.hooks);
|
|
if (!hooks_file || !fs__default.existsSync(hooks_file)) {
|
|
hooks_file = path__default.join(config.kit.outDir, 'build/hooks.js');
|
|
fs__default.writeFileSync(hooks_file, '');
|
|
}
|
|
|
|
/** @type {Record<string, string>} */
|
|
const input = {
|
|
index: `${build_dir}/index.js`
|
|
};
|
|
|
|
// add entry points for every endpoint...
|
|
manifest_data.routes.forEach((route) => {
|
|
const file = route.type === 'endpoint' ? route.file : route.shadow;
|
|
|
|
if (file) {
|
|
const resolved = path__default.resolve(cwd, file);
|
|
const relative = decodeURIComponent(path__default.relative(config.kit.files.routes, resolved));
|
|
const name = posixify(path__default.join('entries/endpoints', relative.replace(/\.js$/, '')));
|
|
input[name] = resolved;
|
|
}
|
|
});
|
|
|
|
// ...and every component used by pages...
|
|
manifest_data.components.forEach((file) => {
|
|
const resolved = path__default.resolve(cwd, file);
|
|
const relative = decodeURIComponent(path__default.relative(config.kit.files.routes, resolved));
|
|
|
|
const name = relative.startsWith('..')
|
|
? posixify(path__default.join('entries/fallbacks', path__default.basename(file)))
|
|
: posixify(path__default.join('entries/pages', relative));
|
|
input[name] = resolved;
|
|
});
|
|
|
|
// ...and every matcher
|
|
Object.entries(manifest_data.matchers).forEach(([key, file]) => {
|
|
const name = posixify(path__default.join('entries/matchers', key));
|
|
input[name] = path__default.resolve(cwd, file);
|
|
});
|
|
|
|
/** @type {(file: string) => string} */
|
|
const app_relative = (file) => {
|
|
const relative_file = path__default.relative(build_dir, path__default.resolve(cwd, file));
|
|
return relative_file[0] === '.' ? relative_file : `./${relative_file}`;
|
|
};
|
|
|
|
fs__default.writeFileSync(
|
|
input.index,
|
|
server_template({
|
|
config,
|
|
hooks: app_relative(hooks_file),
|
|
has_service_worker: config.kit.serviceWorker.register && !!service_worker_entry_file,
|
|
runtime: posixify(path__default.relative(build_dir, get_runtime_directory(config.kit))),
|
|
template: load_template(cwd, config)
|
|
})
|
|
);
|
|
|
|
const merged_config = merge_vite_configs(
|
|
get_default_config({ config, input, ssr: true, outDir: `${output_dir}/server` }),
|
|
await get_vite_config(vite_config, vite_config_env)
|
|
);
|
|
|
|
const { chunks } = await create_build(merged_config);
|
|
|
|
/** @type {import('vite').Manifest} */
|
|
const vite_manifest = JSON.parse(fs__default.readFileSync(`${output_dir}/server/manifest.json`, 'utf-8'));
|
|
|
|
mkdirp(`${output_dir}/server/nodes`);
|
|
mkdirp(`${output_dir}/server/stylesheets`);
|
|
|
|
const stylesheet_lookup = new Map();
|
|
|
|
client.assets.forEach((asset) => {
|
|
if (asset.fileName.endsWith('.css')) {
|
|
if (asset.source.length < config.kit.inlineStyleThreshold) {
|
|
const index = stylesheet_lookup.size;
|
|
const file = `${output_dir}/server/stylesheets/${index}.js`;
|
|
|
|
fs__default.writeFileSync(file, `// ${asset.fileName}\nexport default ${s(asset.source)};`);
|
|
stylesheet_lookup.set(asset.fileName, index);
|
|
}
|
|
}
|
|
});
|
|
|
|
manifest_data.components.forEach((component, i) => {
|
|
const entry = find_deps$1(client.vite_manifest, component, true);
|
|
|
|
const imports = [`import * as module from '../${vite_manifest[component].file}';`];
|
|
|
|
const exports = [
|
|
'export { module };',
|
|
`export const index = ${i};`,
|
|
`export const file = '${entry.file}';`,
|
|
`export const imports = ${s(entry.imports)};`,
|
|
`export const stylesheets = ${s(entry.stylesheets)};`
|
|
];
|
|
|
|
/** @type {string[]} */
|
|
const styles = [];
|
|
|
|
entry.stylesheets.forEach((file) => {
|
|
if (stylesheet_lookup.has(file)) {
|
|
const index = stylesheet_lookup.get(file);
|
|
const name = `stylesheet_${index}`;
|
|
imports.push(`import ${name} from '../stylesheets/${index}.js';`);
|
|
styles.push(`\t${s(file)}: ${name}`);
|
|
}
|
|
});
|
|
|
|
if (styles.length > 0) {
|
|
exports.push(`export const inline_styles = () => ({\n${styles.join(',\n')}\n});`);
|
|
}
|
|
|
|
const out = `${output_dir}/server/nodes/${i}.js`;
|
|
fs__default.writeFileSync(out, `${imports.join('\n')}\n\n${exports.join('\n')}\n`);
|
|
});
|
|
|
|
return {
|
|
chunks,
|
|
vite_manifest,
|
|
methods: get_methods(cwd, chunks, manifest_data)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {string} cwd
|
|
* @param {import('rollup').OutputChunk[]} output
|
|
* @param {import('types').ManifestData} manifest_data
|
|
*/
|
|
function get_methods(cwd, output, manifest_data) {
|
|
/** @type {Record<string, string[]>} */
|
|
const lookup = {};
|
|
output.forEach((chunk) => {
|
|
if (!chunk.facadeModuleId) return;
|
|
const id = chunk.facadeModuleId.slice(cwd.length + 1);
|
|
lookup[id] = chunk.exports;
|
|
});
|
|
|
|
/** @type {Record<string, import('types').HttpMethod[]>} */
|
|
const methods = {};
|
|
manifest_data.routes.forEach((route) => {
|
|
const file = route.type === 'endpoint' ? route.file : route.shadow;
|
|
|
|
if (file && lookup[file]) {
|
|
methods[file] = lookup[file].filter(is_http_method);
|
|
}
|
|
});
|
|
|
|
return methods;
|
|
}
|
|
|
|
/**
|
|
* @param {{
|
|
* config: import('types').ValidatedConfig;
|
|
* vite_config: import('vite').ResolvedConfig;
|
|
* vite_config_env: import('vite').ConfigEnv;
|
|
* manifest_data: import('types').ManifestData;
|
|
* output_dir: string;
|
|
* service_worker_entry_file: string | null;
|
|
* }} options
|
|
* @param {import('types').Prerendered} prerendered
|
|
* @param {import('vite').Manifest} client_manifest
|
|
*/
|
|
async function build_service_worker(
|
|
{ config, manifest_data, output_dir, service_worker_entry_file },
|
|
prerendered,
|
|
client_manifest
|
|
) {
|
|
const build = new Set();
|
|
for (const key in client_manifest) {
|
|
const { file, css = [], assets = [] } = client_manifest[key];
|
|
build.add(file);
|
|
css.forEach((file) => build.add(file));
|
|
assets.forEach((file) => build.add(file));
|
|
}
|
|
|
|
const service_worker = `${config.kit.outDir}/generated/service-worker.js`;
|
|
|
|
fs__default.writeFileSync(
|
|
service_worker,
|
|
`
|
|
// TODO remove for 1.0
|
|
export const timestamp = {
|
|
toString: () => {
|
|
throw new Error('\`timestamp\` has been removed from $service-worker. Use \`version\` instead');
|
|
}
|
|
};
|
|
|
|
export const build = [
|
|
${Array.from(build)
|
|
.map((file) => `${s(`${config.kit.paths.base}/${file}`)}`)
|
|
.join(',\n\t\t\t\t')}
|
|
];
|
|
|
|
export const files = [
|
|
${manifest_data.assets
|
|
.filter((asset) => config.kit.serviceWorker.files(asset.file))
|
|
.map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`)
|
|
.join(',\n\t\t\t\t')}
|
|
];
|
|
|
|
export const prerendered = [
|
|
${prerendered.paths.map((path) => s(path)).join(',\n\t\t\t\t')}
|
|
];
|
|
|
|
export const version = ${s(config.kit.version.name)};
|
|
`
|
|
.replace(/^\t{3}/gm, '')
|
|
.trim()
|
|
);
|
|
|
|
await vite.build({
|
|
base: assets_base(config.kit),
|
|
build: {
|
|
lib: {
|
|
entry: /** @type {string} */ (service_worker_entry_file),
|
|
name: 'app',
|
|
formats: ['es']
|
|
},
|
|
rollupOptions: {
|
|
output: {
|
|
entryFileNames: 'service-worker.js'
|
|
}
|
|
},
|
|
outDir: `${output_dir}/client`,
|
|
emptyOutDir: false
|
|
},
|
|
configFile: false,
|
|
resolve: {
|
|
alias: {
|
|
'$service-worker': service_worker,
|
|
$lib: config.kit.files.lib
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function totalist(dir, callback, pre='') {
|
|
dir = resolve('.', dir);
|
|
let arr = readdirSync(dir);
|
|
let i=0, abs, stats;
|
|
for (; i < arr.length; i++) {
|
|
abs = join(dir, arr[i]);
|
|
stats = statSync(abs);
|
|
stats.isDirectory()
|
|
? totalist(abs, callback, join(pre, arr[i]))
|
|
: callback(join(pre, arr[i]), abs, stats);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @typedef ParsedURL
|
|
* @type {import('.').ParsedURL}
|
|
*/
|
|
|
|
/**
|
|
* @typedef Request
|
|
* @property {string} url
|
|
* @property {ParsedURL} _parsedUrl
|
|
*/
|
|
|
|
/**
|
|
* @param {Request} req
|
|
* @returns {ParsedURL|void}
|
|
*/
|
|
function parse(req) {
|
|
let raw = req.url;
|
|
if (raw == null) return;
|
|
|
|
let prev = req._parsedUrl;
|
|
if (prev && prev.raw === raw) return prev;
|
|
|
|
let pathname=raw, search='', query;
|
|
|
|
if (raw.length > 1) {
|
|
let idx = raw.indexOf('?', 1);
|
|
|
|
if (idx !== -1) {
|
|
search = raw.substring(idx);
|
|
pathname = raw.substring(0, idx);
|
|
if (search.length > 1) {
|
|
query = qs.parse(search.substring(1));
|
|
}
|
|
}
|
|
}
|
|
|
|
return req._parsedUrl = { pathname, search, query, raw };
|
|
}
|
|
|
|
const mimes = {
|
|
"ez": "application/andrew-inset",
|
|
"aw": "application/applixware",
|
|
"atom": "application/atom+xml",
|
|
"atomcat": "application/atomcat+xml",
|
|
"atomdeleted": "application/atomdeleted+xml",
|
|
"atomsvc": "application/atomsvc+xml",
|
|
"dwd": "application/atsc-dwd+xml",
|
|
"held": "application/atsc-held+xml",
|
|
"rsat": "application/atsc-rsat+xml",
|
|
"bdoc": "application/bdoc",
|
|
"xcs": "application/calendar+xml",
|
|
"ccxml": "application/ccxml+xml",
|
|
"cdfx": "application/cdfx+xml",
|
|
"cdmia": "application/cdmi-capability",
|
|
"cdmic": "application/cdmi-container",
|
|
"cdmid": "application/cdmi-domain",
|
|
"cdmio": "application/cdmi-object",
|
|
"cdmiq": "application/cdmi-queue",
|
|
"cu": "application/cu-seeme",
|
|
"mpd": "application/dash+xml",
|
|
"davmount": "application/davmount+xml",
|
|
"dbk": "application/docbook+xml",
|
|
"dssc": "application/dssc+der",
|
|
"xdssc": "application/dssc+xml",
|
|
"es": "application/ecmascript",
|
|
"ecma": "application/ecmascript",
|
|
"emma": "application/emma+xml",
|
|
"emotionml": "application/emotionml+xml",
|
|
"epub": "application/epub+zip",
|
|
"exi": "application/exi",
|
|
"fdt": "application/fdt+xml",
|
|
"pfr": "application/font-tdpfr",
|
|
"geojson": "application/geo+json",
|
|
"gml": "application/gml+xml",
|
|
"gpx": "application/gpx+xml",
|
|
"gxf": "application/gxf",
|
|
"gz": "application/gzip",
|
|
"hjson": "application/hjson",
|
|
"stk": "application/hyperstudio",
|
|
"ink": "application/inkml+xml",
|
|
"inkml": "application/inkml+xml",
|
|
"ipfix": "application/ipfix",
|
|
"its": "application/its+xml",
|
|
"jar": "application/java-archive",
|
|
"war": "application/java-archive",
|
|
"ear": "application/java-archive",
|
|
"ser": "application/java-serialized-object",
|
|
"class": "application/java-vm",
|
|
"js": "application/javascript",
|
|
"mjs": "application/javascript",
|
|
"json": "application/json",
|
|
"map": "application/json",
|
|
"json5": "application/json5",
|
|
"jsonml": "application/jsonml+json",
|
|
"jsonld": "application/ld+json",
|
|
"lgr": "application/lgr+xml",
|
|
"lostxml": "application/lost+xml",
|
|
"hqx": "application/mac-binhex40",
|
|
"cpt": "application/mac-compactpro",
|
|
"mads": "application/mads+xml",
|
|
"webmanifest": "application/manifest+json",
|
|
"mrc": "application/marc",
|
|
"mrcx": "application/marcxml+xml",
|
|
"ma": "application/mathematica",
|
|
"nb": "application/mathematica",
|
|
"mb": "application/mathematica",
|
|
"mathml": "application/mathml+xml",
|
|
"mbox": "application/mbox",
|
|
"mscml": "application/mediaservercontrol+xml",
|
|
"metalink": "application/metalink+xml",
|
|
"meta4": "application/metalink4+xml",
|
|
"mets": "application/mets+xml",
|
|
"maei": "application/mmt-aei+xml",
|
|
"musd": "application/mmt-usd+xml",
|
|
"mods": "application/mods+xml",
|
|
"m21": "application/mp21",
|
|
"mp21": "application/mp21",
|
|
"mp4s": "application/mp4",
|
|
"m4p": "application/mp4",
|
|
"doc": "application/msword",
|
|
"dot": "application/msword",
|
|
"mxf": "application/mxf",
|
|
"nq": "application/n-quads",
|
|
"nt": "application/n-triples",
|
|
"cjs": "application/node",
|
|
"bin": "application/octet-stream",
|
|
"dms": "application/octet-stream",
|
|
"lrf": "application/octet-stream",
|
|
"mar": "application/octet-stream",
|
|
"so": "application/octet-stream",
|
|
"dist": "application/octet-stream",
|
|
"distz": "application/octet-stream",
|
|
"pkg": "application/octet-stream",
|
|
"bpk": "application/octet-stream",
|
|
"dump": "application/octet-stream",
|
|
"elc": "application/octet-stream",
|
|
"deploy": "application/octet-stream",
|
|
"exe": "application/octet-stream",
|
|
"dll": "application/octet-stream",
|
|
"deb": "application/octet-stream",
|
|
"dmg": "application/octet-stream",
|
|
"iso": "application/octet-stream",
|
|
"img": "application/octet-stream",
|
|
"msi": "application/octet-stream",
|
|
"msp": "application/octet-stream",
|
|
"msm": "application/octet-stream",
|
|
"buffer": "application/octet-stream",
|
|
"oda": "application/oda",
|
|
"opf": "application/oebps-package+xml",
|
|
"ogx": "application/ogg",
|
|
"omdoc": "application/omdoc+xml",
|
|
"onetoc": "application/onenote",
|
|
"onetoc2": "application/onenote",
|
|
"onetmp": "application/onenote",
|
|
"onepkg": "application/onenote",
|
|
"oxps": "application/oxps",
|
|
"relo": "application/p2p-overlay+xml",
|
|
"xer": "application/patch-ops-error+xml",
|
|
"pdf": "application/pdf",
|
|
"pgp": "application/pgp-encrypted",
|
|
"asc": "application/pgp-signature",
|
|
"sig": "application/pgp-signature",
|
|
"prf": "application/pics-rules",
|
|
"p10": "application/pkcs10",
|
|
"p7m": "application/pkcs7-mime",
|
|
"p7c": "application/pkcs7-mime",
|
|
"p7s": "application/pkcs7-signature",
|
|
"p8": "application/pkcs8",
|
|
"ac": "application/pkix-attr-cert",
|
|
"cer": "application/pkix-cert",
|
|
"crl": "application/pkix-crl",
|
|
"pkipath": "application/pkix-pkipath",
|
|
"pki": "application/pkixcmp",
|
|
"pls": "application/pls+xml",
|
|
"ai": "application/postscript",
|
|
"eps": "application/postscript",
|
|
"ps": "application/postscript",
|
|
"provx": "application/provenance+xml",
|
|
"cww": "application/prs.cww",
|
|
"pskcxml": "application/pskc+xml",
|
|
"raml": "application/raml+yaml",
|
|
"rdf": "application/rdf+xml",
|
|
"owl": "application/rdf+xml",
|
|
"rif": "application/reginfo+xml",
|
|
"rnc": "application/relax-ng-compact-syntax",
|
|
"rl": "application/resource-lists+xml",
|
|
"rld": "application/resource-lists-diff+xml",
|
|
"rs": "application/rls-services+xml",
|
|
"rapd": "application/route-apd+xml",
|
|
"sls": "application/route-s-tsid+xml",
|
|
"rusd": "application/route-usd+xml",
|
|
"gbr": "application/rpki-ghostbusters",
|
|
"mft": "application/rpki-manifest",
|
|
"roa": "application/rpki-roa",
|
|
"rsd": "application/rsd+xml",
|
|
"rss": "application/rss+xml",
|
|
"rtf": "application/rtf",
|
|
"sbml": "application/sbml+xml",
|
|
"scq": "application/scvp-cv-request",
|
|
"scs": "application/scvp-cv-response",
|
|
"spq": "application/scvp-vp-request",
|
|
"spp": "application/scvp-vp-response",
|
|
"sdp": "application/sdp",
|
|
"senmlx": "application/senml+xml",
|
|
"sensmlx": "application/sensml+xml",
|
|
"setpay": "application/set-payment-initiation",
|
|
"setreg": "application/set-registration-initiation",
|
|
"shf": "application/shf+xml",
|
|
"siv": "application/sieve",
|
|
"sieve": "application/sieve",
|
|
"smi": "application/smil+xml",
|
|
"smil": "application/smil+xml",
|
|
"rq": "application/sparql-query",
|
|
"srx": "application/sparql-results+xml",
|
|
"gram": "application/srgs",
|
|
"grxml": "application/srgs+xml",
|
|
"sru": "application/sru+xml",
|
|
"ssdl": "application/ssdl+xml",
|
|
"ssml": "application/ssml+xml",
|
|
"swidtag": "application/swid+xml",
|
|
"tei": "application/tei+xml",
|
|
"teicorpus": "application/tei+xml",
|
|
"tfi": "application/thraud+xml",
|
|
"tsd": "application/timestamped-data",
|
|
"toml": "application/toml",
|
|
"trig": "application/trig",
|
|
"ttml": "application/ttml+xml",
|
|
"ubj": "application/ubjson",
|
|
"rsheet": "application/urc-ressheet+xml",
|
|
"td": "application/urc-targetdesc+xml",
|
|
"vxml": "application/voicexml+xml",
|
|
"wasm": "application/wasm",
|
|
"wgt": "application/widget",
|
|
"hlp": "application/winhlp",
|
|
"wsdl": "application/wsdl+xml",
|
|
"wspolicy": "application/wspolicy+xml",
|
|
"xaml": "application/xaml+xml",
|
|
"xav": "application/xcap-att+xml",
|
|
"xca": "application/xcap-caps+xml",
|
|
"xdf": "application/xcap-diff+xml",
|
|
"xel": "application/xcap-el+xml",
|
|
"xns": "application/xcap-ns+xml",
|
|
"xenc": "application/xenc+xml",
|
|
"xhtml": "application/xhtml+xml",
|
|
"xht": "application/xhtml+xml",
|
|
"xlf": "application/xliff+xml",
|
|
"xml": "application/xml",
|
|
"xsl": "application/xml",
|
|
"xsd": "application/xml",
|
|
"rng": "application/xml",
|
|
"dtd": "application/xml-dtd",
|
|
"xop": "application/xop+xml",
|
|
"xpl": "application/xproc+xml",
|
|
"xslt": "application/xml",
|
|
"xspf": "application/xspf+xml",
|
|
"mxml": "application/xv+xml",
|
|
"xhvml": "application/xv+xml",
|
|
"xvml": "application/xv+xml",
|
|
"xvm": "application/xv+xml",
|
|
"yang": "application/yang",
|
|
"yin": "application/yin+xml",
|
|
"zip": "application/zip",
|
|
"3gpp": "video/3gpp",
|
|
"adp": "audio/adpcm",
|
|
"amr": "audio/amr",
|
|
"au": "audio/basic",
|
|
"snd": "audio/basic",
|
|
"mid": "audio/midi",
|
|
"midi": "audio/midi",
|
|
"kar": "audio/midi",
|
|
"rmi": "audio/midi",
|
|
"mxmf": "audio/mobile-xmf",
|
|
"mp3": "audio/mpeg",
|
|
"m4a": "audio/mp4",
|
|
"mp4a": "audio/mp4",
|
|
"mpga": "audio/mpeg",
|
|
"mp2": "audio/mpeg",
|
|
"mp2a": "audio/mpeg",
|
|
"m2a": "audio/mpeg",
|
|
"m3a": "audio/mpeg",
|
|
"oga": "audio/ogg",
|
|
"ogg": "audio/ogg",
|
|
"spx": "audio/ogg",
|
|
"opus": "audio/ogg",
|
|
"s3m": "audio/s3m",
|
|
"sil": "audio/silk",
|
|
"wav": "audio/wav",
|
|
"weba": "audio/webm",
|
|
"xm": "audio/xm",
|
|
"ttc": "font/collection",
|
|
"otf": "font/otf",
|
|
"ttf": "font/ttf",
|
|
"woff": "font/woff",
|
|
"woff2": "font/woff2",
|
|
"exr": "image/aces",
|
|
"apng": "image/apng",
|
|
"avif": "image/avif",
|
|
"bmp": "image/bmp",
|
|
"cgm": "image/cgm",
|
|
"drle": "image/dicom-rle",
|
|
"emf": "image/emf",
|
|
"fits": "image/fits",
|
|
"g3": "image/g3fax",
|
|
"gif": "image/gif",
|
|
"heic": "image/heic",
|
|
"heics": "image/heic-sequence",
|
|
"heif": "image/heif",
|
|
"heifs": "image/heif-sequence",
|
|
"hej2": "image/hej2k",
|
|
"hsj2": "image/hsj2",
|
|
"ief": "image/ief",
|
|
"jls": "image/jls",
|
|
"jp2": "image/jp2",
|
|
"jpg2": "image/jp2",
|
|
"jpeg": "image/jpeg",
|
|
"jpg": "image/jpeg",
|
|
"jpe": "image/jpeg",
|
|
"jph": "image/jph",
|
|
"jhc": "image/jphc",
|
|
"jpm": "image/jpm",
|
|
"jpx": "image/jpx",
|
|
"jpf": "image/jpx",
|
|
"jxr": "image/jxr",
|
|
"jxra": "image/jxra",
|
|
"jxrs": "image/jxrs",
|
|
"jxs": "image/jxs",
|
|
"jxsc": "image/jxsc",
|
|
"jxsi": "image/jxsi",
|
|
"jxss": "image/jxss",
|
|
"ktx": "image/ktx",
|
|
"ktx2": "image/ktx2",
|
|
"png": "image/png",
|
|
"btif": "image/prs.btif",
|
|
"pti": "image/prs.pti",
|
|
"sgi": "image/sgi",
|
|
"svg": "image/svg+xml",
|
|
"svgz": "image/svg+xml",
|
|
"t38": "image/t38",
|
|
"tif": "image/tiff",
|
|
"tiff": "image/tiff",
|
|
"tfx": "image/tiff-fx",
|
|
"webp": "image/webp",
|
|
"wmf": "image/wmf",
|
|
"disposition-notification": "message/disposition-notification",
|
|
"u8msg": "message/global",
|
|
"u8dsn": "message/global-delivery-status",
|
|
"u8mdn": "message/global-disposition-notification",
|
|
"u8hdr": "message/global-headers",
|
|
"eml": "message/rfc822",
|
|
"mime": "message/rfc822",
|
|
"3mf": "model/3mf",
|
|
"gltf": "model/gltf+json",
|
|
"glb": "model/gltf-binary",
|
|
"igs": "model/iges",
|
|
"iges": "model/iges",
|
|
"msh": "model/mesh",
|
|
"mesh": "model/mesh",
|
|
"silo": "model/mesh",
|
|
"mtl": "model/mtl",
|
|
"obj": "model/obj",
|
|
"stpz": "model/step+zip",
|
|
"stpxz": "model/step-xml+zip",
|
|
"stl": "model/stl",
|
|
"wrl": "model/vrml",
|
|
"vrml": "model/vrml",
|
|
"x3db": "model/x3d+fastinfoset",
|
|
"x3dbz": "model/x3d+binary",
|
|
"x3dv": "model/x3d-vrml",
|
|
"x3dvz": "model/x3d+vrml",
|
|
"x3d": "model/x3d+xml",
|
|
"x3dz": "model/x3d+xml",
|
|
"appcache": "text/cache-manifest",
|
|
"manifest": "text/cache-manifest",
|
|
"ics": "text/calendar",
|
|
"ifb": "text/calendar",
|
|
"coffee": "text/coffeescript",
|
|
"litcoffee": "text/coffeescript",
|
|
"css": "text/css",
|
|
"csv": "text/csv",
|
|
"html": "text/html",
|
|
"htm": "text/html",
|
|
"shtml": "text/html",
|
|
"jade": "text/jade",
|
|
"jsx": "text/jsx",
|
|
"less": "text/less",
|
|
"markdown": "text/markdown",
|
|
"md": "text/markdown",
|
|
"mml": "text/mathml",
|
|
"mdx": "text/mdx",
|
|
"n3": "text/n3",
|
|
"txt": "text/plain",
|
|
"text": "text/plain",
|
|
"conf": "text/plain",
|
|
"def": "text/plain",
|
|
"list": "text/plain",
|
|
"log": "text/plain",
|
|
"in": "text/plain",
|
|
"ini": "text/plain",
|
|
"dsc": "text/prs.lines.tag",
|
|
"rtx": "text/richtext",
|
|
"sgml": "text/sgml",
|
|
"sgm": "text/sgml",
|
|
"shex": "text/shex",
|
|
"slim": "text/slim",
|
|
"slm": "text/slim",
|
|
"spdx": "text/spdx",
|
|
"stylus": "text/stylus",
|
|
"styl": "text/stylus",
|
|
"tsv": "text/tab-separated-values",
|
|
"t": "text/troff",
|
|
"tr": "text/troff",
|
|
"roff": "text/troff",
|
|
"man": "text/troff",
|
|
"me": "text/troff",
|
|
"ms": "text/troff",
|
|
"ttl": "text/turtle",
|
|
"uri": "text/uri-list",
|
|
"uris": "text/uri-list",
|
|
"urls": "text/uri-list",
|
|
"vcard": "text/vcard",
|
|
"vtt": "text/vtt",
|
|
"yaml": "text/yaml",
|
|
"yml": "text/yaml",
|
|
"3gp": "video/3gpp",
|
|
"3g2": "video/3gpp2",
|
|
"h261": "video/h261",
|
|
"h263": "video/h263",
|
|
"h264": "video/h264",
|
|
"m4s": "video/iso.segment",
|
|
"jpgv": "video/jpeg",
|
|
"jpgm": "image/jpm",
|
|
"mj2": "video/mj2",
|
|
"mjp2": "video/mj2",
|
|
"ts": "video/mp2t",
|
|
"mp4": "video/mp4",
|
|
"mp4v": "video/mp4",
|
|
"mpg4": "video/mp4",
|
|
"mpeg": "video/mpeg",
|
|
"mpg": "video/mpeg",
|
|
"mpe": "video/mpeg",
|
|
"m1v": "video/mpeg",
|
|
"m2v": "video/mpeg",
|
|
"ogv": "video/ogg",
|
|
"qt": "video/quicktime",
|
|
"mov": "video/quicktime",
|
|
"webm": "video/webm"
|
|
};
|
|
|
|
function lookup(extn) {
|
|
let tmp = ('' + extn).trim().toLowerCase();
|
|
let idx = tmp.lastIndexOf('.');
|
|
return mimes[!~idx ? tmp : tmp.substring(++idx)];
|
|
}
|
|
|
|
const noop = () => {};
|
|
|
|
function isMatch(uri, arr) {
|
|
for (let i=0; i < arr.length; i++) {
|
|
if (arr[i].test(uri)) return true;
|
|
}
|
|
}
|
|
|
|
function toAssume(uri, extns) {
|
|
let i=0, x, len=uri.length - 1;
|
|
if (uri.charCodeAt(len) === 47) {
|
|
uri = uri.substring(0, len);
|
|
}
|
|
|
|
let arr=[], tmp=`${uri}/index`;
|
|
for (; i < extns.length; i++) {
|
|
x = extns[i] ? `.${extns[i]}` : '';
|
|
if (uri) arr.push(uri + x);
|
|
arr.push(tmp + x);
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
|
|
function viaCache(cache, uri, extns) {
|
|
let i=0, data, arr=toAssume(uri, extns);
|
|
for (; i < arr.length; i++) {
|
|
if (data = cache[arr[i]]) return data;
|
|
}
|
|
}
|
|
|
|
function viaLocal(dir, isEtag, uri, extns) {
|
|
let i=0, arr=toAssume(uri, extns);
|
|
let abs, stats, name, headers;
|
|
for (; i < arr.length; i++) {
|
|
abs = normalize(join(dir, name=arr[i]));
|
|
if (abs.startsWith(dir) && fs.existsSync(abs)) {
|
|
stats = fs.statSync(abs);
|
|
if (stats.isDirectory()) continue;
|
|
headers = toHeaders(name, stats, isEtag);
|
|
headers['Cache-Control'] = isEtag ? 'no-cache' : 'no-store';
|
|
return { abs, stats, headers };
|
|
}
|
|
}
|
|
}
|
|
|
|
function is404(req, res) {
|
|
return (res.statusCode=404,res.end());
|
|
}
|
|
|
|
function send(req, res, file, stats, headers) {
|
|
let code=200, tmp, opts={};
|
|
headers = { ...headers };
|
|
|
|
for (let key in headers) {
|
|
tmp = res.getHeader(key);
|
|
if (tmp) headers[key] = tmp;
|
|
}
|
|
|
|
if (tmp = res.getHeader('content-type')) {
|
|
headers['Content-Type'] = tmp;
|
|
}
|
|
|
|
if (req.headers.range) {
|
|
code = 206;
|
|
let [x, y] = req.headers.range.replace('bytes=', '').split('-');
|
|
let end = opts.end = parseInt(y, 10) || stats.size - 1;
|
|
let start = opts.start = parseInt(x, 10) || 0;
|
|
|
|
if (start >= stats.size || end >= stats.size) {
|
|
res.setHeader('Content-Range', `bytes */${stats.size}`);
|
|
res.statusCode = 416;
|
|
return res.end();
|
|
}
|
|
|
|
headers['Content-Range'] = `bytes ${start}-${end}/${stats.size}`;
|
|
headers['Content-Length'] = (end - start + 1);
|
|
headers['Accept-Ranges'] = 'bytes';
|
|
}
|
|
|
|
res.writeHead(code, headers);
|
|
fs.createReadStream(file, opts).pipe(res);
|
|
}
|
|
|
|
const ENCODING = {
|
|
'.br': 'br',
|
|
'.gz': 'gzip',
|
|
};
|
|
|
|
function toHeaders(name, stats, isEtag) {
|
|
let enc = ENCODING[name.slice(-3)];
|
|
|
|
let ctype = lookup(name.slice(0, enc && -3)) || '';
|
|
if (ctype === 'text/html') ctype += ';charset=utf-8';
|
|
|
|
let headers = {
|
|
'Content-Length': stats.size,
|
|
'Content-Type': ctype,
|
|
'Last-Modified': stats.mtime.toUTCString(),
|
|
};
|
|
|
|
if (enc) headers['Content-Encoding'] = enc;
|
|
if (isEtag) headers['ETag'] = `W/"${stats.size}-${stats.mtime.getTime()}"`;
|
|
|
|
return headers;
|
|
}
|
|
|
|
function sirv (dir, opts={}) {
|
|
dir = resolve(dir || '.');
|
|
|
|
let isNotFound = opts.onNoMatch || is404;
|
|
let setHeaders = opts.setHeaders || noop;
|
|
|
|
let extensions = opts.extensions || ['html', 'htm'];
|
|
let gzips = opts.gzip && extensions.map(x => `${x}.gz`).concat('gz');
|
|
let brots = opts.brotli && extensions.map(x => `${x}.br`).concat('br');
|
|
|
|
const FILES = {};
|
|
|
|
let fallback = '/';
|
|
let isEtag = !!opts.etag;
|
|
let isSPA = !!opts.single;
|
|
if (typeof opts.single === 'string') {
|
|
let idx = opts.single.lastIndexOf('.');
|
|
fallback += !!~idx ? opts.single.substring(0, idx) : opts.single;
|
|
}
|
|
|
|
let ignores = [];
|
|
if (opts.ignores !== false) {
|
|
ignores.push(/[/]([A-Za-z\s\d~$._-]+\.\w+){1,}$/); // any extn
|
|
if (opts.dotfiles) ignores.push(/\/\.\w/);
|
|
else ignores.push(/\/\.well-known/);
|
|
[].concat(opts.ignores || []).forEach(x => {
|
|
ignores.push(new RegExp(x, 'i'));
|
|
});
|
|
}
|
|
|
|
let cc = opts.maxAge != null && `public,max-age=${opts.maxAge}`;
|
|
if (cc && opts.immutable) cc += ',immutable';
|
|
else if (cc && opts.maxAge === 0) cc += ',must-revalidate';
|
|
|
|
if (!opts.dev) {
|
|
totalist(dir, (name, abs, stats) => {
|
|
if (/\.well-known[\\+\/]/.test(name)) ; // keep
|
|
else if (!opts.dotfiles && /(^\.|[\\+|\/+]\.)/.test(name)) return;
|
|
|
|
let headers = toHeaders(name, stats, isEtag);
|
|
if (cc) headers['Cache-Control'] = cc;
|
|
|
|
FILES['/' + name.normalize().replace(/\\+/g, '/')] = { abs, stats, headers };
|
|
});
|
|
}
|
|
|
|
let lookup = opts.dev ? viaLocal.bind(0, dir, isEtag) : viaCache.bind(0, FILES);
|
|
|
|
return function (req, res, next) {
|
|
let extns = [''];
|
|
let pathname = parse(req).pathname;
|
|
let val = req.headers['accept-encoding'] || '';
|
|
if (gzips && val.includes('gzip')) extns.unshift(...gzips);
|
|
if (brots && /(br|brotli)/i.test(val)) extns.unshift(...brots);
|
|
extns.push(...extensions); // [...br, ...gz, orig, ...exts]
|
|
|
|
if (pathname.indexOf('%') !== -1) {
|
|
try { pathname = decodeURIComponent(pathname); }
|
|
catch (err) { /* malform uri */ }
|
|
}
|
|
|
|
let data = lookup(pathname, extns) || isSPA && !isMatch(pathname, ignores) && lookup(fallback, extns);
|
|
if (!data) return next ? next() : isNotFound(req, res);
|
|
|
|
if (isEtag && req.headers['if-none-match'] === data.headers['ETag']) {
|
|
res.writeHead(304);
|
|
return res.end();
|
|
}
|
|
|
|
if (gzips || brots) {
|
|
res.setHeader('Vary', 'Accept-Encoding');
|
|
}
|
|
|
|
setHeaders(res, pathname, data.stats);
|
|
send(req, res, data.abs, data.stats, data.headers);
|
|
};
|
|
}
|
|
|
|
// in `vite dev` and `vite preview`, we use a fake asset path so that we can
|
|
// serve local assets while verifying that requests are correctly prefixed
|
|
const SVELTE_KIT_ASSETS = '/_svelte_kit_assets';
|
|
|
|
// Vite doesn't expose this so we just copy the list for now
|
|
// https://github.com/vitejs/vite/blob/3edd1af56e980aef56641a5a51cf2932bb580d41/packages/vite/src/node/plugins/css.ts#L96
|
|
const style_pattern = /\.(css|less|sass|scss|styl|stylus|pcss|postcss)$/;
|
|
|
|
const cwd$1 = process.cwd();
|
|
|
|
/**
|
|
* @param {import('vite').ViteDevServer} vite
|
|
* @param {import('vite').ResolvedConfig} vite_config
|
|
* @param {import('types').ValidatedConfig} svelte_config
|
|
* @param {Set<string>} illegal_imports
|
|
* @return {Promise<Promise<() => void>>}
|
|
*/
|
|
async function dev(vite, vite_config, svelte_config, illegal_imports) {
|
|
installPolyfills();
|
|
|
|
init(svelte_config, vite_config.mode);
|
|
|
|
const runtime = get_runtime_prefix(svelte_config.kit);
|
|
|
|
/** @type {import('types').Respond} */
|
|
const respond = (await import(`${runtime}/server/index.js`)).respond;
|
|
|
|
/** @type {import('types').SSRManifest} */
|
|
let manifest;
|
|
|
|
function update_manifest() {
|
|
const { manifest_data } = update(svelte_config);
|
|
|
|
manifest = {
|
|
appDir: svelte_config.kit.appDir,
|
|
assets: new Set(manifest_data.assets.map((asset) => asset.file)),
|
|
mimeTypes: get_mime_lookup(manifest_data),
|
|
_: {
|
|
entry: {
|
|
file: `/@fs${runtime}/client/start.js`,
|
|
imports: [],
|
|
stylesheets: []
|
|
},
|
|
nodes: manifest_data.components.map((id, index) => {
|
|
return async () => {
|
|
const url = id.startsWith('..') ? `/@fs${path__default.posix.resolve(id)}` : `/${id}`;
|
|
|
|
const module = /** @type {import('types').SSRComponent} */ (
|
|
await vite.ssrLoadModule(url)
|
|
);
|
|
|
|
const node = await vite.moduleGraph.getModuleByUrl(url);
|
|
if (!node) throw new Error(`Could not find node for ${url}`);
|
|
|
|
prevent_illegal_vite_imports(
|
|
node,
|
|
illegal_imports,
|
|
[...svelte_config.extensions, ...svelte_config.kit.moduleExtensions],
|
|
svelte_config.kit.outDir
|
|
);
|
|
|
|
return {
|
|
module,
|
|
index,
|
|
file: url.endsWith('.svelte') ? url : url + '?import',
|
|
imports: [],
|
|
stylesheets: [],
|
|
// in dev we inline all styles to avoid FOUC
|
|
inline_styles: async () => {
|
|
const deps = new Set();
|
|
await find_deps(vite, node, deps);
|
|
|
|
/** @type {Record<string, string>} */
|
|
const styles = {};
|
|
|
|
for (const dep of deps) {
|
|
const parsed = new URL$1(dep.url, 'http://localhost/');
|
|
const query = parsed.searchParams;
|
|
|
|
if (
|
|
style_pattern.test(dep.file) ||
|
|
(query.has('svelte') && query.get('type') === 'style')
|
|
) {
|
|
try {
|
|
const mod = await vite.ssrLoadModule(dep.url);
|
|
styles[dep.url] = mod.default;
|
|
} catch {
|
|
// this can happen with dynamically imported modules, I think
|
|
// because the Vite module graph doesn't distinguish between
|
|
// static and dynamic imports? TODO investigate, submit fix
|
|
}
|
|
}
|
|
}
|
|
|
|
return styles;
|
|
}
|
|
};
|
|
};
|
|
}),
|
|
routes: manifest_data.routes.map((route) => {
|
|
const { pattern, names, types } = parse_route_id(route.id);
|
|
|
|
if (route.type === 'page') {
|
|
return {
|
|
type: 'page',
|
|
id: route.id,
|
|
pattern,
|
|
names,
|
|
types,
|
|
shadow: route.shadow
|
|
? async () => {
|
|
const url = path__default.resolve(cwd$1, /** @type {string} */ (route.shadow));
|
|
return await vite.ssrLoadModule(url);
|
|
}
|
|
: null,
|
|
a: route.a.map((id) => (id ? manifest_data.components.indexOf(id) : undefined)),
|
|
b: route.b.map((id) => (id ? manifest_data.components.indexOf(id) : undefined))
|
|
};
|
|
}
|
|
|
|
return {
|
|
type: 'endpoint',
|
|
id: route.id,
|
|
pattern,
|
|
names,
|
|
types,
|
|
load: async () => {
|
|
const url = path__default.resolve(cwd$1, route.file);
|
|
return await vite.ssrLoadModule(url);
|
|
}
|
|
};
|
|
}),
|
|
matchers: async () => {
|
|
/** @type {Record<string, import('types').ParamMatcher>} */
|
|
const matchers = {};
|
|
|
|
for (const key in manifest_data.matchers) {
|
|
const file = manifest_data.matchers[key];
|
|
const url = path__default.resolve(cwd$1, file);
|
|
const module = await vite.ssrLoadModule(url);
|
|
|
|
if (module.match) {
|
|
matchers[key] = module.match;
|
|
} else {
|
|
throw new Error(`${file} does not export a \`match\` function`);
|
|
}
|
|
}
|
|
|
|
return matchers;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/** @param {Error} error */
|
|
function fix_stack_trace(error) {
|
|
return error.stack ? vite.ssrRewriteStacktrace(error.stack) : error.stack;
|
|
}
|
|
|
|
update_manifest();
|
|
|
|
for (const event of ['add', 'unlink']) {
|
|
vite.watcher.on(event, (file) => {
|
|
if (file.startsWith(svelte_config.kit.files.routes + path__default.sep)) {
|
|
update_manifest();
|
|
}
|
|
});
|
|
}
|
|
|
|
const assets = svelte_config.kit.paths.assets ? SVELTE_KIT_ASSETS : svelte_config.kit.paths.base;
|
|
const asset_server = sirv(svelte_config.kit.files.assets, {
|
|
dev: true,
|
|
etag: true,
|
|
maxAge: 0,
|
|
extensions: []
|
|
});
|
|
|
|
vite.middlewares.use(async (req, res, next) => {
|
|
try {
|
|
const base = `${vite.config.server.https ? 'https' : 'http'}://${
|
|
req.headers[':authority'] || req.headers.host
|
|
}`;
|
|
|
|
const decoded = decodeURI(new URL$1(base + req.url).pathname);
|
|
|
|
if (decoded.startsWith(assets)) {
|
|
const pathname = decoded.slice(assets.length);
|
|
const file = svelte_config.kit.files.assets + pathname;
|
|
|
|
if (fs__default.existsSync(file) && !fs__default.statSync(file).isDirectory()) {
|
|
if (has_correct_case(file, svelte_config.kit.files.assets)) {
|
|
req.url = encodeURI(pathname); // don't need query/hash
|
|
asset_server(req, res);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
next();
|
|
} catch (e) {
|
|
const error = coalesce_to_error(e);
|
|
res.statusCode = 500;
|
|
res.end(fix_stack_trace(error));
|
|
}
|
|
});
|
|
|
|
return () => {
|
|
const serve_static_middleware = vite.middlewares.stack.find(
|
|
(middleware) =>
|
|
/** @type {function} */ (middleware.handle).name === 'viteServeStaticMiddleware'
|
|
);
|
|
|
|
remove_static_middlewares(vite.middlewares);
|
|
|
|
vite.middlewares.use(async (req, res) => {
|
|
try {
|
|
const base = `${vite.config.server.https ? 'https' : 'http'}://${
|
|
req.headers[':authority'] || req.headers.host
|
|
}`;
|
|
|
|
const decoded = decodeURI(new URL$1(base + req.url).pathname);
|
|
const file = posixify(path__default.resolve(decoded.slice(1)));
|
|
const is_file = fs__default.existsSync(file) && !fs__default.statSync(file).isDirectory();
|
|
const allowed =
|
|
!vite_config.server.fs.strict ||
|
|
vite_config.server.fs.allow.some((dir) => file.startsWith(dir));
|
|
|
|
if (is_file && allowed) {
|
|
// @ts-expect-error
|
|
serve_static_middleware.handle(req, res);
|
|
return;
|
|
}
|
|
|
|
if (!decoded.startsWith(svelte_config.kit.paths.base)) {
|
|
return not_found(
|
|
res,
|
|
`Not found (did you mean ${svelte_config.kit.paths.base + req.url}?)`
|
|
);
|
|
}
|
|
|
|
const runtime_base = true
|
|
? `/${posixify(path__default.relative(cwd$1, `${svelte_config.kit.outDir}/runtime`))}`
|
|
: `/@fs${runtime}`;
|
|
|
|
const { set_private_env } = await vite.ssrLoadModule(`${runtime_base}/env-private.js`);
|
|
const { set_public_env } = await vite.ssrLoadModule(`${runtime_base}/env-public.js`);
|
|
|
|
const env = get_env(vite_config.mode, svelte_config.kit.env.publicPrefix);
|
|
set_private_env(env.private);
|
|
set_public_env(env.public);
|
|
|
|
/** @type {Partial<import('types').Hooks>} */
|
|
const user_hooks = resolve_entry(svelte_config.kit.files.hooks)
|
|
? await vite.ssrLoadModule(`/${svelte_config.kit.files.hooks}`)
|
|
: {};
|
|
|
|
const handle = user_hooks.handle || (({ event, resolve }) => resolve(event));
|
|
|
|
/** @type {import('types').Hooks} */
|
|
const hooks = {
|
|
getSession: user_hooks.getSession || (() => ({})),
|
|
handle,
|
|
handleError:
|
|
user_hooks.handleError ||
|
|
(({ /** @type {Error & { frame?: string }} */ error }) => {
|
|
console.error($.bold().red(error.message));
|
|
if (error.frame) {
|
|
console.error($.gray(error.frame));
|
|
}
|
|
if (error.stack) {
|
|
console.error($.gray(error.stack));
|
|
}
|
|
}),
|
|
externalFetch: user_hooks.externalFetch || fetch
|
|
};
|
|
|
|
if (/** @type {any} */ (hooks).getContext) {
|
|
// TODO remove this for 1.0
|
|
throw new Error(
|
|
'The getContext hook has been removed. See https://kit.svelte.dev/docs/hooks'
|
|
);
|
|
}
|
|
|
|
if (/** @type {any} */ (hooks).serverFetch) {
|
|
// TODO remove this for 1.0
|
|
throw new Error('The serverFetch hook has been renamed to externalFetch.');
|
|
}
|
|
|
|
// TODO the / prefix will probably fail if outDir is outside the cwd (which
|
|
// could be the case in a monorepo setup), but without it these modules
|
|
// can get loaded twice via different URLs, which causes failures. Might
|
|
// require changes to Vite to fix
|
|
const { default: root } = await vite.ssrLoadModule(
|
|
`/${posixify(path__default.relative(cwd$1, `${svelte_config.kit.outDir}/generated/root.svelte`))}`
|
|
);
|
|
|
|
const paths = await vite.ssrLoadModule(`${runtime_base}/paths.js`);
|
|
|
|
paths.set_paths({
|
|
base: svelte_config.kit.paths.base,
|
|
assets
|
|
});
|
|
|
|
let request;
|
|
|
|
try {
|
|
request = await getRequest(base, req);
|
|
} catch (/** @type {any} */ err) {
|
|
res.statusCode = err.status || 400;
|
|
return res.end(err.reason || 'Invalid request body');
|
|
}
|
|
|
|
const template = load_template(cwd$1, svelte_config);
|
|
|
|
const rendered = await respond(
|
|
request,
|
|
{
|
|
csp: svelte_config.kit.csp,
|
|
dev: true,
|
|
get_stack: (error) => fix_stack_trace(error),
|
|
handle_error: (error, event) => {
|
|
hooks.handleError({
|
|
error: new Proxy(error, {
|
|
get: (target, property) => {
|
|
if (property === 'stack') {
|
|
return fix_stack_trace(error);
|
|
}
|
|
|
|
return Reflect.get(target, property, target);
|
|
}
|
|
}),
|
|
event,
|
|
|
|
// TODO remove for 1.0
|
|
// @ts-expect-error
|
|
get request() {
|
|
throw new Error(
|
|
'request in handleError has been replaced with event. See https://github.com/sveltejs/kit/pull/3384 for details'
|
|
);
|
|
}
|
|
});
|
|
},
|
|
hooks,
|
|
hydrate: svelte_config.kit.browser.hydrate,
|
|
manifest,
|
|
method_override: svelte_config.kit.methodOverride,
|
|
paths: {
|
|
base: svelte_config.kit.paths.base,
|
|
assets
|
|
},
|
|
prefix: '',
|
|
prerender: {
|
|
default: svelte_config.kit.prerender.default,
|
|
enabled: svelte_config.kit.prerender.enabled
|
|
},
|
|
public_env: env.public,
|
|
read: (file) => fs__default.readFileSync(path__default.join(svelte_config.kit.files.assets, file)),
|
|
root,
|
|
router: svelte_config.kit.browser.router,
|
|
template: ({ head, body, assets, nonce }) => {
|
|
return (
|
|
template
|
|
.replace(/%sveltekit\.assets%/g, assets)
|
|
.replace(/%sveltekit\.nonce%/g, nonce)
|
|
// head and body must be replaced last, in case someone tries to sneak in %sveltekit.assets% etc
|
|
.replace('%sveltekit.head%', () => head)
|
|
.replace('%sveltekit.body%', () => body)
|
|
);
|
|
},
|
|
template_contains_nonce: template.includes('%sveltekit.nonce%'),
|
|
trailing_slash: svelte_config.kit.trailingSlash
|
|
},
|
|
{
|
|
getClientAddress: () => {
|
|
const { remoteAddress } = req.socket;
|
|
if (remoteAddress) return remoteAddress;
|
|
throw new Error('Could not determine clientAddress');
|
|
}
|
|
}
|
|
);
|
|
|
|
if (rendered.status === 404) {
|
|
// @ts-expect-error
|
|
serve_static_middleware.handle(req, res, () => {
|
|
setResponse(res, rendered);
|
|
});
|
|
} else {
|
|
setResponse(res, rendered);
|
|
}
|
|
} catch (e) {
|
|
const error = coalesce_to_error(e);
|
|
res.statusCode = 500;
|
|
res.end(fix_stack_trace(error));
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
/** @param {import('http').ServerResponse} res */
|
|
function not_found(res, message = 'Not found') {
|
|
res.statusCode = 404;
|
|
res.end(message);
|
|
}
|
|
|
|
/**
|
|
* @param {import('connect').Server} server
|
|
*/
|
|
function remove_static_middlewares(server) {
|
|
// We don't use viteServePublicMiddleware because of the following issues:
|
|
// https://github.com/vitejs/vite/issues/9260
|
|
// https://github.com/vitejs/vite/issues/9236
|
|
// https://github.com/vitejs/vite/issues/9234
|
|
const static_middlewares = ['viteServePublicMiddleware', 'viteServeStaticMiddleware'];
|
|
for (let i = server.stack.length - 1; i > 0; i--) {
|
|
// @ts-expect-error using internals
|
|
if (static_middlewares.includes(server.stack[i].handle.name)) {
|
|
server.stack.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {import('vite').ViteDevServer} vite
|
|
* @param {import('vite').ModuleNode} node
|
|
* @param {Set<import('vite').ModuleNode>} deps
|
|
*/
|
|
async function find_deps(vite, node, deps) {
|
|
// since `ssrTransformResult.deps` contains URLs instead of `ModuleNode`s, this process is asynchronous.
|
|
// instead of using `await`, we resolve all branches in parallel.
|
|
/** @type {Promise<void>[]} */
|
|
const branches = [];
|
|
|
|
/** @param {import('vite').ModuleNode} node */
|
|
async function add(node) {
|
|
if (!deps.has(node)) {
|
|
deps.add(node);
|
|
await find_deps(vite, node, deps);
|
|
}
|
|
}
|
|
|
|
/** @param {string} url */
|
|
async function add_by_url(url) {
|
|
const node = await vite.moduleGraph.getModuleByUrl(url);
|
|
|
|
if (node) {
|
|
await add(node);
|
|
}
|
|
}
|
|
|
|
if (node.ssrTransformResult) {
|
|
if (node.ssrTransformResult.deps) {
|
|
node.ssrTransformResult.deps.forEach((url) => branches.push(add_by_url(url)));
|
|
}
|
|
|
|
if (node.ssrTransformResult.dynamicDeps) {
|
|
node.ssrTransformResult.dynamicDeps.forEach((url) => branches.push(add_by_url(url)));
|
|
}
|
|
} else {
|
|
node.importedModules.forEach((node) => branches.push(add(node)));
|
|
}
|
|
|
|
await Promise.all(branches);
|
|
}
|
|
|
|
/**
|
|
* Determine if a file is being requested with the correct case,
|
|
* to ensure consistent behaviour between dev and prod and across
|
|
* operating systems. Note that we can't use realpath here,
|
|
* because we don't want to follow symlinks
|
|
* @param {string} file
|
|
* @param {string} assets
|
|
* @returns {boolean}
|
|
*/
|
|
function has_correct_case(file, assets) {
|
|
if (file === assets) return true;
|
|
|
|
const parent = path__default.dirname(file);
|
|
|
|
if (fs__default.readdirSync(parent).includes(path__default.basename(file))) {
|
|
return has_correct_case(parent, assets);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Generates the data used to write the server-side manifest.js file. This data is used in the Vite
|
|
* build process, to power routing, etc.
|
|
* @param {{
|
|
* build_data: import('types').BuildData;
|
|
* relative_path: string;
|
|
* routes: import('types').RouteData[];
|
|
* format?: 'esm' | 'cjs'
|
|
* }} opts
|
|
*/
|
|
function generate_manifest({ build_data, relative_path, routes, format = 'esm' }) {
|
|
/** @typedef {{ index: number, path: string }} LookupEntry */
|
|
/** @type {Map<string, LookupEntry>} */
|
|
const bundled_nodes = new Map();
|
|
|
|
// 0 and 1 are special, they correspond to the root layout and root error nodes
|
|
bundled_nodes.set(build_data.manifest_data.components[0], {
|
|
path: `${relative_path}/nodes/0.js`,
|
|
index: 0
|
|
});
|
|
|
|
bundled_nodes.set(build_data.manifest_data.components[1], {
|
|
path: `${relative_path}/nodes/1.js`,
|
|
index: 1
|
|
});
|
|
|
|
routes.forEach((route) => {
|
|
if (route.type === 'page') {
|
|
[...route.a, ...route.b].forEach((component) => {
|
|
if (component && !bundled_nodes.has(component)) {
|
|
const i = build_data.manifest_data.components.indexOf(component);
|
|
|
|
bundled_nodes.set(component, {
|
|
path: `${relative_path}/nodes/${i}.js`,
|
|
index: bundled_nodes.size
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
/** @type {(path: string) => string} */
|
|
const load =
|
|
format === 'esm'
|
|
? (path) => `import('${path}')`
|
|
: (path) => `Promise.resolve().then(() => require('${path}'))`;
|
|
|
|
/** @type {(path: string) => string} */
|
|
const loader = (path) => `() => ${load(path)}`;
|
|
|
|
const assets = build_data.manifest_data.assets.map((asset) => asset.file);
|
|
if (build_data.service_worker) {
|
|
assets.push(build_data.service_worker);
|
|
}
|
|
|
|
/** @param {string | undefined} id */
|
|
const get_index = (id) => id && /** @type {LookupEntry} */ (bundled_nodes.get(id)).index;
|
|
|
|
const matchers = new Set();
|
|
|
|
// prettier-ignore
|
|
return `{
|
|
appDir: ${s(build_data.app_dir)},
|
|
assets: new Set(${s(assets)}),
|
|
mimeTypes: ${s(get_mime_lookup(build_data.manifest_data))},
|
|
_: {
|
|
entry: ${s(build_data.client.entry)},
|
|
nodes: [
|
|
${Array.from(bundled_nodes.values()).map(node => loader(node.path)).join(',\n\t\t\t\t')}
|
|
],
|
|
routes: [
|
|
${routes.map(route => {
|
|
const { pattern, names, types } = parse_route_id(route.id);
|
|
|
|
types.forEach(type => {
|
|
if (type) matchers.add(type);
|
|
});
|
|
|
|
if (route.type === 'page') {
|
|
return `{
|
|
type: 'page',
|
|
id: ${s(route.id)},
|
|
pattern: ${pattern},
|
|
names: ${s(names)},
|
|
types: ${s(types)},
|
|
path: ${route.path ? s(route.path) : null},
|
|
shadow: ${route.shadow ? loader(`${relative_path}/${build_data.server.vite_manifest[route.shadow].file}`) : null},
|
|
a: ${s(route.a.map(get_index))},
|
|
b: ${s(route.b.map(get_index))}
|
|
}`.replace(/^\t\t/gm, '');
|
|
} else {
|
|
if (!build_data.server.vite_manifest[route.file]) {
|
|
// this is necessary in cases where a .css file snuck in —
|
|
// perhaps it would be better to disallow these (and others?)
|
|
return null;
|
|
}
|
|
|
|
return `{
|
|
type: 'endpoint',
|
|
id: ${s(route.id)},
|
|
pattern: ${pattern},
|
|
names: ${s(names)},
|
|
types: ${s(types)},
|
|
load: ${loader(`${relative_path}/${build_data.server.vite_manifest[route.file].file}`)}
|
|
}`.replace(/^\t\t/gm, '');
|
|
}
|
|
}).filter(Boolean).join(',\n\t\t\t\t')}
|
|
],
|
|
matchers: async () => {
|
|
${Array.from(matchers).map(type => `const { match: ${type} } = await ${load(`${relative_path}/entries/matchers/${type}.js`)}`).join('\n\t\t\t\t')}
|
|
return { ${Array.from(matchers).join(', ')} };
|
|
}
|
|
}
|
|
}`.replace(/^\t/gm, '');
|
|
}
|
|
|
|
/** @typedef {import('http').IncomingMessage} Req */
|
|
/** @typedef {import('http').ServerResponse} Res */
|
|
/** @typedef {(req: Req, res: Res, next: () => void) => void} Handler */
|
|
|
|
/**
|
|
* @param {{
|
|
* middlewares: import('connect').Server;
|
|
* httpServer: import('http').Server;
|
|
* }} vite
|
|
* @param {import('vite').ResolvedConfig} vite_config
|
|
* @param {import('types').ValidatedConfig} svelte_config
|
|
*/
|
|
async function preview(vite, vite_config, svelte_config) {
|
|
installPolyfills();
|
|
|
|
const { paths } = svelte_config.kit;
|
|
const base = paths.base;
|
|
const assets = paths.assets ? SVELTE_KIT_ASSETS : paths.base;
|
|
|
|
const protocol = vite_config.preview.https ? 'https' : 'http';
|
|
|
|
const etag = `"${Date.now()}"`;
|
|
|
|
const index_file = join(svelte_config.kit.outDir, 'output/server/index.js');
|
|
const manifest_file = join(svelte_config.kit.outDir, 'output/server/manifest.js');
|
|
|
|
/** @type {import('types').ServerModule} */
|
|
const { Server, override } = await import(pathToFileURL(index_file).href);
|
|
const { manifest } = await import(pathToFileURL(manifest_file).href);
|
|
|
|
override({
|
|
paths: { base, assets },
|
|
prerendering: false,
|
|
protocol,
|
|
read: (file) => fs__default.readFileSync(join(svelte_config.kit.files.assets, file))
|
|
});
|
|
|
|
const server = new Server(manifest);
|
|
server.init({
|
|
env: loadEnv(vite_config.mode, process.cwd(), '')
|
|
});
|
|
|
|
return () => {
|
|
// generated client assets and the contents of `static`
|
|
vite.middlewares.use(
|
|
scoped(
|
|
assets,
|
|
sirv(join(svelte_config.kit.outDir, 'output/client'), {
|
|
setHeaders: (res, pathname) => {
|
|
// only apply to immutable directory, not e.g. version.json
|
|
if (pathname.startsWith(`/${svelte_config.kit.appDir}/immutable`)) {
|
|
res.setHeader('cache-control', 'public,max-age=31536000,immutable');
|
|
}
|
|
}
|
|
})
|
|
)
|
|
);
|
|
|
|
vite.middlewares.use((req, res, next) => {
|
|
const original_url = /** @type {string} */ (req.url);
|
|
const { pathname } = new URL(original_url, 'http://dummy');
|
|
|
|
if (pathname.startsWith(base)) {
|
|
next();
|
|
} else {
|
|
res.statusCode = 404;
|
|
res.end(`Not found (did you mean ${base + pathname}?)`);
|
|
}
|
|
});
|
|
|
|
// prerendered dependencies
|
|
vite.middlewares.use(
|
|
scoped(base, mutable(join(svelte_config.kit.outDir, 'output/prerendered/dependencies')))
|
|
);
|
|
|
|
// prerendered pages (we can't just use sirv because we need to
|
|
// preserve the correct trailingSlash behaviour)
|
|
vite.middlewares.use(
|
|
scoped(base, (req, res, next) => {
|
|
let if_none_match_value = req.headers['if-none-match'];
|
|
|
|
if (if_none_match_value?.startsWith('W/"')) {
|
|
if_none_match_value = if_none_match_value.substring(2);
|
|
}
|
|
|
|
if (if_none_match_value === etag) {
|
|
res.statusCode = 304;
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
const { pathname } = new URL(/** @type {string} */ (req.url), 'http://dummy');
|
|
|
|
// only treat this as a page if it doesn't include an extension
|
|
if (pathname === '/' || /\/[^./]+\/?$/.test(pathname)) {
|
|
const file = join(
|
|
svelte_config.kit.outDir,
|
|
'output/prerendered/pages' +
|
|
pathname +
|
|
(pathname.endsWith('/') ? 'index.html' : '.html')
|
|
);
|
|
|
|
if (fs__default.existsSync(file)) {
|
|
res.writeHead(200, {
|
|
'content-type': 'text/html',
|
|
etag
|
|
});
|
|
|
|
fs__default.createReadStream(file).pipe(res);
|
|
return;
|
|
}
|
|
}
|
|
|
|
next();
|
|
})
|
|
);
|
|
|
|
// SSR
|
|
vite.middlewares.use(async (req, res) => {
|
|
const host = req.headers['host'];
|
|
|
|
let request;
|
|
|
|
try {
|
|
request = await getRequest(`${protocol}://${host}`, req);
|
|
} catch (/** @type {any} */ err) {
|
|
res.statusCode = err.status || 400;
|
|
return res.end(err.reason || 'Invalid request body');
|
|
}
|
|
|
|
setResponse(
|
|
res,
|
|
await server.respond(request, {
|
|
getClientAddress: () => {
|
|
const { remoteAddress } = req.socket;
|
|
if (remoteAddress) return remoteAddress;
|
|
throw new Error('Could not determine clientAddress');
|
|
}
|
|
})
|
|
);
|
|
});
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {string} dir
|
|
* @returns {Handler}
|
|
*/
|
|
const mutable = (dir) =>
|
|
fs__default.existsSync(dir)
|
|
? sirv(dir, {
|
|
etag: true,
|
|
maxAge: 0
|
|
})
|
|
: (_req, _res, next) => next();
|
|
|
|
/**
|
|
* @param {string} scope
|
|
* @param {Handler} handler
|
|
* @returns {Handler}
|
|
*/
|
|
function scoped(scope, handler) {
|
|
if (scope === '') return handler;
|
|
|
|
return (req, res, next) => {
|
|
if (req.url?.startsWith(scope)) {
|
|
const original_url = req.url;
|
|
req.url = req.url.slice(scope.length);
|
|
handler(req, res, () => {
|
|
req.url = original_url;
|
|
next();
|
|
});
|
|
} else {
|
|
next();
|
|
}
|
|
};
|
|
}
|
|
|
|
const cwd = process.cwd();
|
|
|
|
/** @type {import('./types').EnforcedConfig} */
|
|
const enforced_config = {
|
|
appType: true,
|
|
base: true,
|
|
build: {
|
|
cssCodeSplit: true,
|
|
emptyOutDir: true,
|
|
lib: {
|
|
entry: true,
|
|
name: true,
|
|
formats: true
|
|
},
|
|
manifest: true,
|
|
outDir: true,
|
|
polyfillModulePreload: true,
|
|
rollupOptions: {
|
|
input: true,
|
|
output: {
|
|
format: true,
|
|
entryFileNames: true,
|
|
chunkFileNames: true,
|
|
assetFileNames: true
|
|
},
|
|
preserveEntrySignatures: true
|
|
},
|
|
ssr: true
|
|
},
|
|
publicDir: true,
|
|
resolve: {
|
|
alias: {
|
|
$app: true,
|
|
$lib: true,
|
|
'$service-worker': true
|
|
}
|
|
},
|
|
root: true
|
|
};
|
|
|
|
/**
|
|
* @return {import('vite').Plugin[]}
|
|
*/
|
|
function sveltekit() {
|
|
return [...svelte(), kit()];
|
|
}
|
|
|
|
/**
|
|
* Returns the SvelteKit Vite plugin. Vite executes Rollup hooks as well as some of its own.
|
|
* Background reading is available at:
|
|
* - https://vitejs.dev/guide/api-plugin.html
|
|
* - https://rollupjs.org/guide/en/#plugin-development
|
|
*
|
|
* You can get an idea of the lifecycle by looking at the flow charts here:
|
|
* - https://rollupjs.org/guide/en/#build-hooks
|
|
* - https://rollupjs.org/guide/en/#output-generation-hooks
|
|
*
|
|
* @return {import('vite').Plugin}
|
|
*/
|
|
function kit() {
|
|
/** @type {import('types').ValidatedConfig} */
|
|
let svelte_config;
|
|
|
|
/** @type {import('vite').ResolvedConfig} */
|
|
let vite_config;
|
|
|
|
/** @type {import('vite').ConfigEnv} */
|
|
let vite_config_env;
|
|
|
|
/** @type {import('types').ManifestData} */
|
|
let manifest_data;
|
|
|
|
/** @type {boolean} */
|
|
let is_build;
|
|
|
|
/** @type {import('types').Logger} */
|
|
let log;
|
|
|
|
/** @type {import('types').Prerendered} */
|
|
let prerendered;
|
|
|
|
/** @type {import('types').BuildData} */
|
|
let build_data;
|
|
|
|
/** @type {Set<string>} */
|
|
let illegal_imports;
|
|
|
|
/** @type {string | undefined} */
|
|
let deferred_warning;
|
|
|
|
/**
|
|
* @type {{
|
|
* build_dir: string;
|
|
* output_dir: string;
|
|
* client_out_dir: string;
|
|
* }}
|
|
*/
|
|
let paths;
|
|
|
|
let completed_build = false;
|
|
|
|
function vite_client_build_config() {
|
|
/** @type {Record<string, string>} */
|
|
const input = {
|
|
// Put unchanging assets in immutable directory. We don't set that in the
|
|
// outDir so that other plugins can add mutable assets to the bundle
|
|
start: `${get_runtime_directory(svelte_config.kit)}/client/start.js`
|
|
};
|
|
|
|
// This step is optional — Vite/Rollup will create the necessary chunks
|
|
// for everything regardless — but it means that entry chunks reflect
|
|
// their location in the source code, which is helpful for debugging
|
|
manifest_data.components.forEach((file) => {
|
|
const resolved = path.resolve(cwd, file);
|
|
const relative = decodeURIComponent(path.relative(svelte_config.kit.files.routes, resolved));
|
|
|
|
const name = relative.startsWith('..')
|
|
? path.basename(file)
|
|
: posixify(path.join('pages', relative));
|
|
input[name] = resolved;
|
|
});
|
|
|
|
return get_default_config({
|
|
config: svelte_config,
|
|
input,
|
|
ssr: false,
|
|
outDir: `${paths.client_out_dir}`
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {import('rollup').OutputAsset[]} assets
|
|
* @param {import('rollup').OutputChunk[]} chunks
|
|
*/
|
|
function client_build_info(assets, chunks) {
|
|
/** @type {import('vite').Manifest} */
|
|
const vite_manifest = JSON.parse(
|
|
fs$1.readFileSync(`${paths.client_out_dir}/manifest.json`, 'utf-8')
|
|
);
|
|
|
|
const entry_id = posixify(
|
|
path.relative(cwd, `${get_runtime_directory(svelte_config.kit)}/client/start.js`)
|
|
);
|
|
|
|
return {
|
|
assets,
|
|
chunks,
|
|
entry: find_deps$1(vite_manifest, entry_id, false),
|
|
vite_manifest
|
|
};
|
|
}
|
|
|
|
// TODO remove this for 1.0
|
|
check_vite_version();
|
|
|
|
return {
|
|
name: 'vite-plugin-svelte-kit',
|
|
|
|
/**
|
|
* Build the SvelteKit-provided Vite config to be merged with the user's vite.config.js file.
|
|
* @see https://vitejs.dev/guide/api-plugin.html#config
|
|
*/
|
|
async config(config, config_env) {
|
|
// The config is created in build_server for SSR mode and passed inline
|
|
if (config.build?.ssr) {
|
|
return;
|
|
}
|
|
|
|
vite_config_env = config_env;
|
|
svelte_config = await load_config();
|
|
is_build = config_env.command === 'build';
|
|
|
|
paths = {
|
|
build_dir: `${svelte_config.kit.outDir}/build`,
|
|
output_dir: `${svelte_config.kit.outDir}/output`,
|
|
client_out_dir: `${svelte_config.kit.outDir}/output/client/`
|
|
};
|
|
|
|
illegal_imports = new Set([
|
|
vite.normalizePath(`${svelte_config.kit.outDir}/runtime/env/dynamic/private.js`),
|
|
vite.normalizePath(`${svelte_config.kit.outDir}/runtime/env/static/private.js`)
|
|
]);
|
|
|
|
if (is_build) {
|
|
manifest_data = all(svelte_config, config_env.mode).manifest_data;
|
|
|
|
const new_config = vite_client_build_config();
|
|
|
|
const warning = warn_overridden_config(config, new_config);
|
|
if (warning) console.error(warning + '\n');
|
|
|
|
return new_config;
|
|
}
|
|
|
|
// dev and preview config can be shared
|
|
/** @type {import('vite').UserConfig} */
|
|
const result = {
|
|
appType: 'custom',
|
|
base: '/',
|
|
build: {
|
|
rollupOptions: {
|
|
// Vite dependency crawler needs an explicit JS entry point
|
|
// eventhough server otherwise works without it
|
|
input: `${get_runtime_directory(svelte_config.kit)}/client/start.js`
|
|
}
|
|
},
|
|
define: {
|
|
__SVELTEKIT_DEV__: 'true',
|
|
__SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0'
|
|
},
|
|
publicDir: svelte_config.kit.files.assets,
|
|
resolve: {
|
|
alias: get_aliases(svelte_config.kit)
|
|
},
|
|
root: cwd,
|
|
server: {
|
|
fs: {
|
|
allow: [
|
|
...new Set([
|
|
svelte_config.kit.files.lib,
|
|
svelte_config.kit.files.routes,
|
|
svelte_config.kit.outDir,
|
|
path.resolve(cwd, 'src'),
|
|
path.resolve(cwd, 'node_modules'),
|
|
path.resolve(vite.searchForWorkspaceRoot(cwd), 'node_modules')
|
|
])
|
|
]
|
|
},
|
|
watch: {
|
|
ignored: [
|
|
// Ignore all siblings of config.kit.outDir/generated
|
|
`${posixify(svelte_config.kit.outDir)}/!(generated)`
|
|
]
|
|
}
|
|
}
|
|
};
|
|
|
|
deferred_warning = warn_overridden_config(config, result);
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Stores the final config.
|
|
*/
|
|
configResolved(config) {
|
|
vite_config = config;
|
|
},
|
|
|
|
/**
|
|
* Clears the output directories.
|
|
*/
|
|
buildStart() {
|
|
if (vite_config.build.ssr) {
|
|
return;
|
|
}
|
|
|
|
// Reset for new build. Goes here because `build --watch` calls buildStart but not config
|
|
completed_build = false;
|
|
|
|
if (is_build) {
|
|
rimraf(paths.build_dir);
|
|
mkdirp(paths.build_dir);
|
|
|
|
rimraf(paths.output_dir);
|
|
mkdirp(paths.output_dir);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Vite builds a single bundle. We need three bundles: client, server, and service worker.
|
|
* The user's package.json scripts will invoke the Vite CLI to execute the client build. We
|
|
* then use this hook to kick off builds for the server and service worker.
|
|
*/
|
|
async writeBundle(_options, bundle) {
|
|
if (vite_config.build.ssr) {
|
|
return;
|
|
}
|
|
|
|
for (const file of manifest_data.components) {
|
|
const id = vite.normalizePath(path.resolve(file));
|
|
const node = this.getModuleInfo(id);
|
|
|
|
if (node) {
|
|
prevent_illegal_rollup_imports(
|
|
this.getModuleInfo.bind(this),
|
|
node,
|
|
illegal_imports,
|
|
svelte_config.kit.outDir
|
|
);
|
|
}
|
|
}
|
|
|
|
const verbose = vite_config.logLevel === 'info';
|
|
log = logger({
|
|
verbose
|
|
});
|
|
|
|
fs$1.writeFileSync(
|
|
`${paths.client_out_dir}/${svelte_config.kit.appDir}/version.json`,
|
|
JSON.stringify({ version: svelte_config.kit.version.name })
|
|
);
|
|
|
|
const { assets, chunks } = collect_output(bundle);
|
|
log.info(`Client build completed. Wrote ${chunks.length} chunks and ${assets.length} assets`);
|
|
|
|
log.info('Building server');
|
|
const options = {
|
|
cwd,
|
|
config: svelte_config,
|
|
vite_config,
|
|
vite_config_env,
|
|
build_dir: paths.build_dir, // TODO just pass `paths`
|
|
manifest_data,
|
|
output_dir: paths.output_dir,
|
|
service_worker_entry_file: resolve_entry(svelte_config.kit.files.serviceWorker)
|
|
};
|
|
const client = client_build_info(assets, chunks);
|
|
const server = await build_server(options, client);
|
|
|
|
/** @type {import('types').BuildData} */
|
|
build_data = {
|
|
app_dir: svelte_config.kit.appDir,
|
|
manifest_data,
|
|
service_worker: options.service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable?
|
|
client,
|
|
server
|
|
};
|
|
|
|
const manifest_path = `${paths.output_dir}/server/manifest.js`;
|
|
fs$1.writeFileSync(
|
|
manifest_path,
|
|
`export const manifest = ${generate_manifest({
|
|
build_data,
|
|
relative_path: '.',
|
|
routes: manifest_data.routes
|
|
})};\n`
|
|
);
|
|
|
|
log.info('Prerendering');
|
|
await new Promise((fulfil, reject) => {
|
|
const results_path = `${svelte_config.kit.outDir}/generated/prerendered.json`;
|
|
|
|
// do prerendering in a subprocess so any dangling stuff gets killed upon completion
|
|
const script = fileURLToPath(
|
|
new URL(
|
|
'./prerender.js' ,
|
|
import.meta.url
|
|
)
|
|
);
|
|
|
|
const child = fork(
|
|
script,
|
|
[vite_config.build.outDir, results_path, manifest_path, '' + verbose],
|
|
{
|
|
stdio: 'inherit'
|
|
}
|
|
);
|
|
|
|
child.on('exit', (code) => {
|
|
if (code) {
|
|
reject(new Error(`Prerendering failed with code ${code}`));
|
|
} else {
|
|
prerendered = JSON.parse(fs$1.readFileSync(results_path, 'utf8'), (key, value) => {
|
|
if (key === 'pages' || key === 'assets' || key === 'redirects') {
|
|
return new Map(value);
|
|
}
|
|
return value;
|
|
});
|
|
fulfil(undefined);
|
|
}
|
|
});
|
|
});
|
|
|
|
if (options.service_worker_entry_file) {
|
|
if (svelte_config.kit.paths.assets) {
|
|
throw new Error('Cannot use service worker alongside config.kit.paths.assets');
|
|
}
|
|
|
|
log.info('Building service worker');
|
|
|
|
await build_service_worker(options, prerendered, client.vite_manifest);
|
|
}
|
|
|
|
console.log(
|
|
`\nRun ${$.bold().cyan('npm run preview')} to preview your production build locally.`
|
|
);
|
|
|
|
completed_build = true;
|
|
},
|
|
|
|
/**
|
|
* Runs the adapter.
|
|
*/
|
|
async closeBundle() {
|
|
// vite calls closeBundle when dev-server restarts, ignore that,
|
|
// and only adapt when build successfully completes.
|
|
const is_restart = !completed_build;
|
|
if (vite_config.build.ssr || is_restart) {
|
|
return;
|
|
}
|
|
|
|
if (svelte_config.kit.adapter) {
|
|
const { adapt } = await import('./chunks/index3.js');
|
|
await adapt(svelte_config, build_data, prerendered, { log });
|
|
} else {
|
|
console.log($.bold().yellow('\nNo adapter specified'));
|
|
// prettier-ignore
|
|
console.log(
|
|
`See ${$.bold().cyan('https://kit.svelte.dev/docs/adapters')} to learn how to configure your app to run on the platform of your choosing`
|
|
);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Adds the SvelteKit middleware to do SSR in dev mode.
|
|
* @see https://vitejs.dev/guide/api-plugin.html#configureserver
|
|
*/
|
|
async configureServer(vite) {
|
|
// This method is called by Vite after clearing the screen.
|
|
// This patch ensures we can log any important messages afterwards for the user to see.
|
|
const print_urls = vite.printUrls;
|
|
vite.printUrls = function () {
|
|
print_urls.apply(this);
|
|
if (deferred_warning) console.error('\n' + deferred_warning);
|
|
};
|
|
|
|
return await dev(vite, vite_config, svelte_config, illegal_imports);
|
|
},
|
|
|
|
/**
|
|
* Adds the SvelteKit middleware to do SSR in preview mode.
|
|
* @see https://vitejs.dev/guide/api-plugin.html#configurepreviewserver
|
|
*/
|
|
configurePreviewServer(vite) {
|
|
return preview(vite, vite_config, svelte_config);
|
|
}
|
|
};
|
|
}
|
|
|
|
function check_vite_version() {
|
|
// TODO parse from kit peer deps and maybe do a full semver compare if we ever require feature releases a min
|
|
const min_required_vite_major = 3;
|
|
const vite_version = vite.version ?? '2.x'; // vite started exporting it's version in 3.0
|
|
const current_vite_major = parseInt(vite_version.split('.')[0], 10);
|
|
|
|
if (current_vite_major < min_required_vite_major) {
|
|
throw new Error(
|
|
`Vite version ${current_vite_major} is no longer supported. Please upgrade to version ${min_required_vite_major}`
|
|
);
|
|
}
|
|
}
|
|
|
|
/** @param {import('rollup').OutputBundle} bundle */
|
|
function collect_output(bundle) {
|
|
/** @type {import('rollup').OutputChunk[]} */
|
|
const chunks = [];
|
|
/** @type {import('rollup').OutputAsset[]} */
|
|
const assets = [];
|
|
for (const value of Object.values(bundle)) {
|
|
// collect asset and output chunks
|
|
if (value.type === 'asset') {
|
|
assets.push(value);
|
|
} else {
|
|
chunks.push(value);
|
|
}
|
|
}
|
|
return { assets, chunks };
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, any>} config
|
|
* @param {Record<string, any>} resolved_config
|
|
*/
|
|
function warn_overridden_config(config, resolved_config) {
|
|
const overridden = find_overridden_config(config, resolved_config, enforced_config, '', []);
|
|
|
|
if (overridden.length > 0) {
|
|
return (
|
|
$.bold().red('The following Vite config options will be overridden by SvelteKit:') +
|
|
overridden.map((key) => `\n - ${key}`).join('')
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, any>} config
|
|
* @param {Record<string, any>} resolved_config
|
|
* @param {import('./types').EnforcedConfig} enforced_config
|
|
* @param {string} path
|
|
* @param {string[]} out used locally to compute the return value
|
|
*/
|
|
function find_overridden_config(config, resolved_config, enforced_config, path, out) {
|
|
for (const key in enforced_config) {
|
|
if (typeof config === 'object' && config !== null && key in config) {
|
|
const enforced = enforced_config[key];
|
|
|
|
if (enforced === true) {
|
|
if (config[key] !== resolved_config[key]) {
|
|
out.push(path + key);
|
|
}
|
|
} else {
|
|
find_overridden_config(config[key], resolved_config[key], enforced, path + key + '.', out);
|
|
}
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
export { generate_manifest as g, sveltekit };
|