diff --git a/packages/create-svelte/templates/default/package.template.json b/packages/create-svelte/templates/default/package.template.json index 5f2fe096acf1..cd0485149860 100644 --- a/packages/create-svelte/templates/default/package.template.json +++ b/packages/create-svelte/templates/default/package.template.json @@ -8,8 +8,8 @@ "preview": "svelte-kit preview" }, "devDependencies": { - "@sveltejs/adapter-auto": "workspace:*", - "@sveltejs/kit": "workspace:*", + "@sveltejs/adapter-auto": "next", + "@sveltejs/kit": "next", "svelte": "^3.44.0" }, "type": "module", diff --git a/packages/kit/package.json b/packages/kit/package.json index 4d7cd7bb1699..681fbf884e35 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -11,7 +11,6 @@ "type": "module", "dependencies": { "@sveltejs/vite-plugin-svelte": "^1.0.0-next.32", - "cheap-watch": "^1.0.4", "sade": "^1.7.4", "vite": "^2.7.2" }, diff --git a/packages/kit/src/cli.js b/packages/kit/src/cli.js index efc787331901..3641406f225c 100644 --- a/packages/kit/src/cli.js +++ b/packages/kit/src/cli.js @@ -91,37 +91,24 @@ prog const { dev } = await import('./core/dev/index.js'); try { - const watcher = await dev({ port, host, https, config }); + const cwd = process.cwd(); - watcher.on('stdout', (data) => { - process.stdout.write(data); - }); - - watcher.on('stderr', (data) => { - process.stderr.write(data); + const { address_info, server_config } = await dev({ + cwd, + port, + host, + https, + config }); - if (!watcher.vite || !watcher.vite.httpServer) { - throw Error('Could not find server'); - } - // we never start the server on a socket path, so address will be of type AddressInfo - const address_info = /** @type {import('net').AddressInfo} */ ( - watcher.vite.httpServer.address() - ); - - const vite_config = config.kit.vite(); - - https = https || !!vite_config.server?.https; - open = open || !!vite_config.server?.open; - welcome({ port: address_info.port, host: address_info.address, - https, - open, - loose: vite_config.server?.fs?.strict === false, - allow: watcher.allowed_directories(), - cwd: watcher.cwd + https: !!(https || server_config.https), + open: open || !!server_config.open, + loose: server_config.fs.strict === false, + allow: server_config.fs.allow, + cwd }); } catch (error) { handle_error(error); diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js index 6f6a6a294177..06603143730d 100644 --- a/packages/kit/src/core/dev/index.js +++ b/packages/kit/src/core/dev/index.js @@ -1,551 +1,106 @@ -import { EventEmitter } from 'events'; -import fs from 'fs'; import path from 'path'; -import { URL } from 'url'; - import { svelte } from '@sveltejs/vite-plugin-svelte'; import amp_validator from 'amphtml-validator'; -import CheapWatch from 'cheap-watch'; -import colors from 'kleur'; import vite from 'vite'; - -import { respond } from '../../runtime/server/index.js'; import { rimraf } from '../../utils/filesystem.js'; import { deep_merge } from '../../utils/object.js'; -import { __fetch_polyfill } from '../../install-fetch.js'; - import { print_config_conflicts } from '../config/index.js'; -import { create_app } from '../create_app/index.js'; -import create_manifest_data from '../create_manifest_data/index.js'; -import { getRawBody } from '../node/index.js'; -import { SVELTE_KIT, SVELTE_KIT_ASSETS } from '../constants.js'; -import { copy_assets, get_mime_lookup, resolve_entry } from '../utils.js'; -import { coalesce_to_error } from '../../utils/error.js'; - -/** @typedef {{ cwd?: string, port: number, host?: string, https: boolean, config: import('types/config').ValidatedConfig }} Options */ +import { SVELTE_KIT } from '../constants.js'; +import { copy_assets } from '../utils.js'; +import { create_plugin } from './plugin.js'; + +/** @typedef {{ + * cwd: string, + * port: number, + * host?: string, + * https: boolean, + * config: import('types/config').ValidatedConfig + * }} Options */ /** @typedef {import('types/internal').SSRComponent} SSRComponent */ /** @param {Options} opts */ -export function dev(opts) { - __fetch_polyfill(); - - return new Watcher(opts).init(); -} +export async function dev({ cwd, port, host, https, config }) { + const output = path.resolve(cwd, `${SVELTE_KIT}/dev`); -class Watcher extends EventEmitter { - /** @param {Options} opts */ - constructor({ cwd = process.cwd(), port, host, https, config }) { - super(); + rimraf(output); + copy_assets(output); - /** @type {string} */ - this.cwd = cwd; - - /** @type {string} */ - this.dir = path.resolve(cwd, `${SVELTE_KIT}/dev`); - - this.port = port; - this.host = host; - this.https = https; - - /** @type {import('types/config').ValidatedConfig} */ - this.config = config; - - /** - * @type {vite.ViteDevServer | undefined} - */ - this.vite; - - process.on('exit', () => { - this.close(); - }); - } + process.env.VITE_SVELTEKIT_AMP = config.kit.amp ? 'true' : ''; - async init() { - rimraf(this.dir); - copy_assets(this.dir); - process.env.VITE_SVELTEKIT_AMP = this.config.kit.amp ? 'true' : ''; - - await this.init_filewatcher(); - this.update(); - - await this.init_server(); - - return this; - } - - async init_filewatcher() { - this.cheapwatch = new CheapWatch({ - dir: this.config.kit.files.routes, - /** @type {({ path }: { path: string }) => boolean} */ - filter: ({ path }) => path.split('/').every((part) => part[0] !== '_' || part[1] === '_') - }); - - await this.cheapwatch.init(); - - // not sure why TS doesn't understand that CheapWatch extends EventEmitter - this.cheapwatch.on('+', ({ isNew }) => { - if (isNew) this.update(); - }); - - this.cheapwatch.on('-', () => { - this.update(); - }); - } - - allowed_directories() { - return [ - ...new Set([ - this.config.kit.files.assets, - this.config.kit.files.lib, - this.config.kit.files.routes, - path.resolve(this.cwd, 'src'), - path.resolve(this.cwd, SVELTE_KIT), - path.resolve(this.cwd, 'node_modules'), - path.resolve(vite.searchForWorkspaceRoot(this.cwd), 'node_modules') - ]) - ]; - } - - async init_server() { - if (!this.manifest) throw new Error('Must call init() before init_server()'); - - /** @type {import('vite').UserConfig} */ - const vite_config = (this.config.kit.vite && this.config.kit.vite()) || {}; - - const default_config = { + const [vite_config] = deep_merge( + { server: { fs: { - allow: this.allowed_directories() + allow: [ + ...new Set([ + config.kit.files.assets, + config.kit.files.lib, + config.kit.files.routes, + path.resolve(cwd, 'src'), + path.resolve(cwd, SVELTE_KIT), + path.resolve(cwd, 'node_modules'), + path.resolve(vite.searchForWorkspaceRoot(cwd), 'node_modules') + ]) + ] }, strictPort: true } - }; - - // don't warn on overriding defaults - const [modified_vite_config] = deep_merge(default_config, vite_config); - - const kit_plugin = await create_plugin(this.config, this.dir, this.https, () => { - if (!this.manifest) { - throw new Error('Manifest is not available'); + }, + config.kit.vite() + ); + + /** @type {[any, string[]]} */ + const [merged_config, conflicts] = deep_merge(vite_config, { + configFile: false, + root: cwd, + resolve: { + alias: { + $app: path.resolve(`${output}/runtime/app`), + $lib: config.kit.files.lib } - - return this.manifest; - }); - - /** @type {[any, string[]]} */ - const [merged_config, conflicts] = deep_merge(modified_vite_config, { - configFile: false, - root: this.cwd, - resolve: { - alias: { - $app: path.resolve(`${this.dir}/runtime/app`), - $lib: this.config.kit.files.lib - } - }, - build: { - rollupOptions: { - // Vite dependency crawler needs an explicit JS entry point - // eventhough server otherwise works without it - input: path.resolve(`${this.dir}/runtime/internal/start.js`) + }, + plugins: [ + svelte({ + extensions: config.extensions, + emitCss: !config.kit.amp, + compilerOptions: { + hydratable: !!config.kit.hydrate } - }, - plugins: [ - svelte({ - extensions: this.config.extensions, - emitCss: !this.config.kit.amp, - compilerOptions: { - hydratable: !!this.config.kit.hydrate - } - }), - kit_plugin - ], - publicDir: this.config.kit.files.assets, - base: this.config.kit.paths.assets.startsWith('/') ? `${this.config.kit.paths.assets}/` : '/' - }); - - print_config_conflicts(conflicts, 'kit.vite.'); - - // optional config from command-line flags - // these should take precedence, but not print conflict warnings - if (this.host) { - merged_config.server.host = this.host; - } - // https is already enabled then do nothing. it could be an object and we - // don't want to overwrite with a boolean - if (this.https && !merged_config.server.https) { - merged_config.server.https = this.https; - } - if (this.port) { - merged_config.server.port = this.port; - } - - this.vite = await vite.createServer(merged_config); - await this.vite.listen(this.port); + }), + create_plugin(config, output, cwd, config.kit.amp && (await amp_validator.getInstance())) + ], + publicDir: config.kit.files.assets, + base: '/' + }); + + print_config_conflicts(conflicts, 'kit.vite.'); + + // optional config from command-line flags + // these should take precedence, but not print conflict warnings + if (host) { + merged_config.server.host = host; } - update() { - const manifest_data = create_manifest_data({ - config: this.config, - output: this.dir, - cwd: this.cwd - }); - - create_app({ - manifest_data, - output: this.dir, - cwd: this.cwd - }); - - /** @type {import('types/app').SSRManifest} */ - this.manifest = { - appDir: this.config.kit.appDir, - assets: new Set(manifest_data.assets.map((asset) => asset.file)), - _: { - mime: get_mime_lookup(manifest_data), - entry: { - file: `/${SVELTE_KIT}/dev/runtime/internal/start.js`, - css: [], - js: [] - }, - nodes: manifest_data.components.map((id) => { - return async () => { - const url = `/${id}`; - - if (!this.vite) throw new Error('Vite server has not been initialized'); - - const module = /** @type {SSRComponent} */ (await this.vite.ssrLoadModule(url)); - const node = await this.vite.moduleGraph.getModuleByUrl(url); - - if (!node) throw new Error(`Could not find node for ${url}`); - - const deps = new Set(); - find_deps(node, deps); - - const styles = new Set(); - - for (const dep of deps) { - const parsed = new URL(dep.url, 'http://localhost/'); - const query = parsed.searchParams; - - // TODO what about .scss files, etc? - if ( - dep.file.endsWith('.css') || - (query.has('svelte') && query.get('type') === 'style') - ) { - try { - const mod = await this.vite.ssrLoadModule(dep.url); - styles.add(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 { - module, - entry: url.endsWith('.svelte') ? url : url + '?import', - css: [], - js: [], - styles: Array.from(styles) - }; - }; - }), - routes: manifest_data.routes.map((route) => { - if (route.type === 'page') { - return { - type: 'page', - pattern: route.pattern, - params: get_params(route.params), - a: route.a.map((id) => manifest_data.components.indexOf(id)), - b: route.b.map((id) => manifest_data.components.indexOf(id)) - }; - } - - return { - type: 'endpoint', - pattern: route.pattern, - params: get_params(route.params), - load: async () => { - if (!this.vite) throw new Error('Vite server has not been initialized'); - const url = path.resolve(this.cwd, route.file); - return await this.vite.ssrLoadModule(url); - } - }; - }) - } - }; + // if https is already enabled then do nothing. it could be an object and we + // don't want to overwrite with a boolean + if (https && !merged_config.server.https) { + merged_config.server.https = https; } - close() { - if (!this.vite || !this.cheapwatch) { - throw new Error('Cannot close server before it is initialized'); - } - - if (this.closed) return; - this.closed = true; - - this.vite.close(); - this.cheapwatch.close(); + if (port) { + merged_config.server.port = port; } -} - -/** @param {string[]} array */ -function get_params(array) { - // given an array of params like `['x', 'y', 'z']` for - // src/routes/[x]/[y]/[z]/svelte, create a function - // that turns a RegExpExecArray into ({ x, y, z }) - - /** @param {RegExpExecArray} match */ - const fn = (match) => { - /** @type {Record} */ - const params = {}; - array.forEach((key, i) => { - if (key.startsWith('...')) { - params[key.slice(3)] = match[i + 1] || ''; - } else { - params[key] = match[i + 1]; - } - }); - return params; - }; - - return fn; -} - -/** - * @param {import('types/config').ValidatedConfig} config - * @param {string} dir - * @param {boolean} https - * @param {() => import('types/app').SSRManifest} get_manifest - */ -async function create_plugin(config, dir, https, get_manifest) { - /** - * @type {amp_validator.Validator?} - */ - const validator = config.kit.amp ? await amp_validator.getInstance() : null; - - /** - * @param {vite.ViteDevServer} vite - */ - function create_kit_middleware(vite) { - /** - * Use a named function for debugging - * @type {import('connect').NextHandleFunction} - */ - return async function svelteKitMiddleware(req, res) { - try { - if (!req.url || !req.method) throw new Error('Incomplete request'); - if (req.url === '/favicon.ico') return not_found(res); - - const parsed = new URL(req.url, 'http://localhost/'); - if (!parsed.pathname.startsWith(config.kit.paths.base)) return not_found(res); - - /** @type {Partial} */ - const user_hooks = resolve_entry(config.kit.files.hooks) - ? await vite.ssrLoadModule(`/${config.kit.files.hooks}`) - : {}; - - /** @type {import('types/internal').Hooks} */ - const hooks = { - getSession: user_hooks.getSession || (() => ({})), - handle: user_hooks.handle || (({ request, resolve }) => resolve(request)), - handleError: - user_hooks.handleError || - (({ /** @type {Error & { frame?: string }} */ error }) => { - console.error(colors.bold().red(error.message)); - if (error.frame) { - console.error(colors.gray(error.frame)); - } - if (error.stack) { - console.error(colors.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.'); - } - - const root = (await vite.ssrLoadModule(`/${dir}/generated/root.svelte`)).default; - - const paths = await vite.ssrLoadModule(`/${SVELTE_KIT}/dev/runtime/paths.js`); - paths.set_paths({ - base: config.kit.paths.base, - assets: config.kit.paths.assets ? SVELTE_KIT_ASSETS : config.kit.paths.base - }); + const server = await vite.createServer(merged_config); + await server.listen(port); - let body; - - try { - body = await getRawBody(req); - } catch (/** @type {any} */ err) { - res.statusCode = err.status || 400; - return res.end(err.reason || 'Invalid request body'); - } - - const rendered = await respond( - { - url: new URL(`${https ? 'https' : 'http'}://${req.headers.host}${req.url}`), - headers: /** @type {import('types/helper').RequestHeaders} */ (req.headers), - method: req.method, - rawBody: body - }, - { - amp: config.kit.amp, - dev: true, - floc: config.kit.floc, - get_stack: (error) => { - vite.ssrFixStacktrace(error); - return error.stack; - }, - handle_error: (error, request) => { - vite.ssrFixStacktrace(error); - hooks.handleError({ error, request }); - }, - hooks, - hydrate: config.kit.hydrate, - manifest: get_manifest(), - paths: { - base: config.kit.paths.base, - assets: config.kit.paths.assets ? SVELTE_KIT_ASSETS : config.kit.paths.base - }, - prefix: '', - prerender: config.kit.prerender.enabled, - read: (file) => fs.readFileSync(path.join(config.kit.files.assets, file)), - root, - router: config.kit.router, - ssr: config.kit.ssr, - target: config.kit.target, - template: ({ head, body }) => { - let rendered = fs - .readFileSync(config.kit.files.template, 'utf8') - .replace('%svelte.head%', () => head) - .replace('%svelte.body%', () => body); - - if (config.kit.amp && validator) { - const result = validator.validateString(rendered); - - if (result.status !== 'PASS') { - const lines = rendered.split('\n'); - - /** @param {string} str */ - const escape = (str) => - str.replace(/&/g, '&').replace(//g, '>'); - - rendered = ` - - - - - -

AMP validation failed

- - ${result.errors - .map( - (error) => ` -

${error.severity}

-

Line ${error.line}, column ${error.col}: ${error.message} (${ - error.code - })

-
${escape(lines[error.line - 1])}
- ` - ) - .join('\n\n')} - `; - } - } - - return rendered; - }, - trailing_slash: config.kit.trailingSlash - } - ); - - if (rendered) { - res.writeHead(rendered.status, rendered.headers); - if (rendered.body) res.write(rendered.body); - res.end(); - } else { - not_found(res); - } - } catch (e) { - const error = coalesce_to_error(e); - vite.ssrFixStacktrace(error); - res.statusCode = 500; - res.end(error.stack); - } - }; - } + const address_info = /** @type {import('net').AddressInfo} */ ( + /** @type {import('http').Server} */ (server.httpServer).address() + ); return { - name: 'vite-plugin-svelte-kit', - /** - * @param {import('vite').ViteDevServer} vite - */ - configureServer(vite) { - return () => { - remove_html_middlewares(vite.middlewares); - vite.middlewares.use(create_kit_middleware(vite)); - }; - } + address_info, + server_config: vite_config.server, + close: () => server.close() }; } - -/** @param {import('http').ServerResponse} res */ -function not_found(res) { - res.statusCode = 404; - res.end('Not found'); -} - -/** - * @param {import('connect').Server} server - */ -function remove_html_middlewares(server) { - const html_middlewares = [ - 'viteIndexHtmlMiddleware', - 'vite404Middleware', - 'viteSpaFallbackMiddleware' - ]; - for (let i = server.stack.length - 1; i > 0; i--) { - // @ts-expect-error using internals until https://github.com/vitejs/vite/pull/4640 is merged - if (html_middlewares.includes(server.stack[i].handle.name)) { - server.stack.splice(i, 1); - } - } -} - -/** - * @param {import('vite').ModuleNode} node - * @param {Set} deps - */ -function find_deps(node, deps) { - for (const dep of node.importedModules) { - if (!deps.has(dep)) { - deps.add(dep); - find_deps(dep, deps); - } - } -} diff --git a/packages/kit/src/core/dev/plugin.js b/packages/kit/src/core/dev/plugin.js new file mode 100644 index 000000000000..7ce3659ad7ee --- /dev/null +++ b/packages/kit/src/core/dev/plugin.js @@ -0,0 +1,352 @@ +import fs from 'fs'; +import path from 'path'; +import { URL } from 'url'; +import colors from 'kleur'; +import { respond } from '../../runtime/server/index.js'; +import { __fetch_polyfill } from '../../install-fetch.js'; +import { create_app } from '../create_app/index.js'; +import create_manifest_data from '../create_manifest_data/index.js'; +import { getRawBody } from '../node/index.js'; +import { SVELTE_KIT, SVELTE_KIT_ASSETS } from '../constants.js'; +import { get_mime_lookup, resolve_entry } from '../utils.js'; +import { coalesce_to_error } from '../../utils/error.js'; + +/** + * @param {import('types/config').ValidatedConfig} config + * @param {string} output + * @param {string} cwd + * @param {import('amphtml-validator').Validator | false} amp + * @returns {import('vite').Plugin} + */ +export function create_plugin(config, output, cwd, amp) { + return { + name: 'vite-plugin-svelte-kit', + + configureServer(vite) { + __fetch_polyfill(); + + /** @type {import('types/app').SSRManifest} */ + let manifest; + + function update_manifest() { + const manifest_data = create_manifest_data({ config, output, cwd }); + + create_app({ manifest_data, output, cwd }); + + manifest = { + appDir: config.kit.appDir, + assets: new Set(manifest_data.assets.map((asset) => asset.file)), + _: { + mime: get_mime_lookup(manifest_data), + entry: { + file: `/${SVELTE_KIT}/dev/runtime/internal/start.js`, + css: [], + js: [] + }, + nodes: manifest_data.components.map((id) => { + return async () => { + const url = `/${id}`; + + const module = /** @type {import('types/internal').SSRComponent} */ ( + await vite.ssrLoadModule(url) + ); + const node = await vite.moduleGraph.getModuleByUrl(url); + + if (!node) throw new Error(`Could not find node for ${url}`); + + const deps = new Set(); + find_deps(node, deps); + + const styles = new Set(); + + for (const dep of deps) { + const parsed = new URL(dep.url, 'http://localhost/'); + const query = parsed.searchParams; + + // TODO what about .scss files, etc? + if ( + dep.file.endsWith('.css') || + (query.has('svelte') && query.get('type') === 'style') + ) { + try { + const mod = await vite.ssrLoadModule(dep.url); + styles.add(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 { + module, + entry: url.endsWith('.svelte') ? url : url + '?import', + css: [], + js: [], + styles: Array.from(styles) + }; + }; + }), + routes: manifest_data.routes.map((route) => { + if (route.type === 'page') { + return { + type: 'page', + pattern: route.pattern, + params: get_params(route.params), + a: route.a.map((id) => manifest_data.components.indexOf(id)), + b: route.b.map((id) => manifest_data.components.indexOf(id)) + }; + } + + return { + type: 'endpoint', + pattern: route.pattern, + params: get_params(route.params), + load: async () => { + const url = path.resolve(cwd, route.file); + return await vite.ssrLoadModule(url); + } + }; + }) + } + }; + } + + update_manifest(); + + vite.watcher.on('add', update_manifest); + vite.watcher.on('remove', update_manifest); + + return () => { + remove_html_middlewares(vite.middlewares); + + vite.middlewares.use(async (req, res) => { + try { + if (!req.url || !req.method) throw new Error('Incomplete request'); + if (req.url === '/favicon.ico') return not_found(res); + + const parsed = new URL(req.url, 'http://localhost/'); + if (!parsed.pathname.startsWith(config.kit.paths.base)) return not_found(res); + + /** @type {Partial} */ + const user_hooks = resolve_entry(config.kit.files.hooks) + ? await vite.ssrLoadModule(`/${config.kit.files.hooks}`) + : {}; + + /** @type {import('types/internal').Hooks} */ + const hooks = { + getSession: user_hooks.getSession || (() => ({})), + handle: user_hooks.handle || (({ request, resolve }) => resolve(request)), + handleError: + user_hooks.handleError || + (({ /** @type {Error & { frame?: string }} */ error }) => { + console.error(colors.bold().red(error.message)); + if (error.frame) { + console.error(colors.gray(error.frame)); + } + if (error.stack) { + console.error(colors.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.'); + } + + const root = (await vite.ssrLoadModule(`/${output}/generated/root.svelte`)).default; + + const paths = await vite.ssrLoadModule(`/${SVELTE_KIT}/dev/runtime/paths.js`); + + paths.set_paths({ + base: config.kit.paths.base, + assets: config.kit.paths.assets ? SVELTE_KIT_ASSETS : config.kit.paths.base + }); + + let body; + + try { + body = await getRawBody(req); + } catch (/** @type {any} */ err) { + res.statusCode = err.status || 400; + return res.end(err.reason || 'Invalid request body'); + } + + const rendered = await respond( + { + url: new URL( + `${vite.config.server.https ? 'https' : 'http'}://${req.headers.host}${req.url}` + ), + headers: /** @type {import('types/helper').RequestHeaders} */ (req.headers), + method: req.method, + rawBody: body + }, + { + amp: config.kit.amp, + dev: true, + floc: config.kit.floc, + get_stack: (error) => { + vite.ssrFixStacktrace(error); + return error.stack; + }, + handle_error: (error, request) => { + vite.ssrFixStacktrace(error); + hooks.handleError({ error, request }); + }, + hooks, + hydrate: config.kit.hydrate, + manifest, + paths: { + base: config.kit.paths.base, + assets: config.kit.paths.assets ? SVELTE_KIT_ASSETS : config.kit.paths.base + }, + prefix: '', + prerender: config.kit.prerender.enabled, + read: (file) => fs.readFileSync(path.join(config.kit.files.assets, file)), + root, + router: config.kit.router, + ssr: config.kit.ssr, + target: config.kit.target, + template: ({ head, body }) => { + let rendered = fs + .readFileSync(config.kit.files.template, 'utf8') + .replace('%svelte.head%', () => head) + .replace('%svelte.body%', () => body); + + if (amp) { + const result = amp.validateString(rendered); + + if (result.status !== 'PASS') { + const lines = rendered.split('\n'); + + /** @param {string} str */ + const escape = (str) => + str.replace(/&/g, '&').replace(//g, '>'); + + rendered = ` + + + + + +

AMP validation failed

+ + ${result.errors + .map( + (error) => ` +

${error.severity}

+

Line ${error.line}, column ${error.col}: ${error.message} (${ + error.code + })

+
${escape(lines[error.line - 1])}
+ ` + ) + .join('\n\n')} + `; + } + } + + return rendered; + }, + trailing_slash: config.kit.trailingSlash + } + ); + + if (rendered) { + res.writeHead(rendered.status, rendered.headers); + if (rendered.body) res.write(rendered.body); + res.end(); + } else { + not_found(res); + } + } catch (e) { + const error = coalesce_to_error(e); + vite.ssrFixStacktrace(error); + res.statusCode = 500; + res.end(error.stack); + } + }); + }; + } + }; +} + +/** @param {string[]} array */ +function get_params(array) { + // given an array of params like `['x', 'y', 'z']` for + // src/routes/[x]/[y]/[z]/svelte, create a function + // that turns a RegExpExecArray into ({ x, y, z }) + + /** @param {RegExpExecArray} match */ + const fn = (match) => { + /** @type {Record} */ + const params = {}; + array.forEach((key, i) => { + if (key.startsWith('...')) { + params[key.slice(3)] = match[i + 1] || ''; + } else { + params[key] = match[i + 1]; + } + }); + return params; + }; + + return fn; +} + +/** @param {import('http').ServerResponse} res */ +function not_found(res) { + res.statusCode = 404; + res.end('Not found'); +} + +/** + * @param {import('connect').Server} server + */ +function remove_html_middlewares(server) { + const html_middlewares = [ + 'viteIndexHtmlMiddleware', + 'vite404Middleware', + 'viteSpaFallbackMiddleware' + ]; + for (let i = server.stack.length - 1; i > 0; i--) { + // @ts-expect-error using internals until https://github.com/vitejs/vite/pull/4640 is merged + if (html_middlewares.includes(server.stack[i].handle.name)) { + server.stack.splice(i, 1); + } + } +} + +/** + * @param {import('vite').ModuleNode} node + * @param {Set} deps + */ +function find_deps(node, deps) { + for (const dep of node.importedModules) { + if (!deps.has(dep)) { + deps.add(dep); + find_deps(dep, deps); + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 879f4d752482..b5ac5119ffeb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -223,7 +223,7 @@ importers: cookie: 0.4.1 devDependencies: '@sveltejs/adapter-auto': link:../../../adapter-auto - '@sveltejs/kit': 1.0.0-next.206_svelte@3.44.2 + '@sveltejs/kit': link:../../../kit svelte: 3.44.2 svelte-preprocess: 4.9.8_svelte@3.44.2+typescript@4.5.2 typescript: 4.5.2 @@ -239,7 +239,6 @@ importers: '@types/node': ^16.11.11 '@types/sade': ^1.7.3 amphtml-validator: ^1.0.35 - cheap-watch: ^1.0.4 cookie: ^0.4.1 devalue: ^2.0.1 eslint: ^8.3.0 @@ -261,7 +260,6 @@ importers: vite: ^2.7.2 dependencies: '@sveltejs/vite-plugin-svelte': 1.0.0-next.32_svelte@3.44.2+vite@2.7.2 - cheap-watch: 1.0.4 sade: 1.7.4 vite: 2.7.2 devDependencies: @@ -780,26 +778,7 @@ packages: dependencies: estree-walker: 2.0.2 picomatch: 2.3.0 - - /@sveltejs/kit/1.0.0-next.206_svelte@3.44.2: - resolution: {integrity: sha512-AQEOKZLG1GKkuBfWUKKxMs9A3AzE9tSHYc4Lu+AL/4BAs4erIFdOei2ldsxak2ICxxeaCloqVRPvIOtj7UEacg==} - engines: {node: '>=14.13'} - hasBin: true - peerDependencies: - svelte: ^3.44.0 - dependencies: - '@sveltejs/vite-plugin-svelte': 1.0.0-next.32_svelte@3.44.2+vite@2.7.2 - cheap-watch: 1.0.4 - sade: 1.7.4 - svelte: 3.44.2 - vite: 2.7.2 - transitivePeerDependencies: - - diff-match-patch - - less - - sass - - stylus - - supports-color - dev: true + dev: false /@sveltejs/vite-plugin-svelte/1.0.0-next.32_svelte@3.44.2+vite@2.7.2: resolution: {integrity: sha512-Lhf5BxVylosHIW6U2s6WDQA39ycd+bXivC8gHsXCJeLzxoHj7Pv7XAOk25xRSXT4wHg9DWFMBQh2DFU0DxHZ2g==} @@ -822,6 +801,7 @@ packages: vite: 2.7.2 transitivePeerDependencies: - supports-color + dev: false /@types/amphtml-validator/1.0.1: resolution: {integrity: sha512-DWE7fy6KtC+Uw0KV/HAmjuH2GB/o8yskXlvmVWR7mOVsLDybp+XrwkzEeRFU9wGjWKeRMBNGsx+5DRq7sUsAwA==} @@ -1396,10 +1376,6 @@ packages: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true - /cheap-watch/1.0.4: - resolution: {integrity: sha512-QR/9FrtRL5fjfUJBhAKCdi0lSRQ3rVRRum3GF9wDKp2TJbEIMGhUEr2yU8lORzm9Isdjx7/k9S0DFDx+z5VGtw==} - engines: {node: '>=8'} - /chokidar/3.5.2: resolution: {integrity: sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==} engines: {node: '>= 8.10.0'} @@ -1750,6 +1726,7 @@ packages: cpu: [arm64] os: [android] requiresBuild: true + dev: false optional: true /esbuild-darwin-64/0.13.15: @@ -1757,6 +1734,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: false optional: true /esbuild-darwin-arm64/0.13.15: @@ -1764,6 +1742,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: false optional: true /esbuild-freebsd-64/0.13.15: @@ -1771,6 +1750,7 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true + dev: false optional: true /esbuild-freebsd-arm64/0.13.15: @@ -1778,6 +1758,7 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true + dev: false optional: true /esbuild-linux-32/0.13.15: @@ -1785,6 +1766,7 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true + dev: false optional: true /esbuild-linux-64/0.13.15: @@ -1792,6 +1774,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: false optional: true /esbuild-linux-arm/0.13.15: @@ -1799,6 +1782,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: false optional: true /esbuild-linux-arm64/0.13.15: @@ -1806,6 +1790,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: false optional: true /esbuild-linux-mips64le/0.13.15: @@ -1813,6 +1798,7 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true + dev: false optional: true /esbuild-linux-ppc64le/0.13.15: @@ -1820,6 +1806,7 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true + dev: false optional: true /esbuild-netbsd-64/0.13.15: @@ -1827,6 +1814,7 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true + dev: false optional: true /esbuild-openbsd-64/0.13.15: @@ -1834,6 +1822,7 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true + dev: false optional: true /esbuild-sunos-64/0.13.15: @@ -1841,6 +1830,7 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true + dev: false optional: true /esbuild-windows-32/0.13.15: @@ -1848,6 +1838,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: false optional: true /esbuild-windows-64/0.13.15: @@ -1855,6 +1846,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: false optional: true /esbuild-windows-arm64/0.13.15: @@ -1862,6 +1854,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: false optional: true /esbuild/0.13.15: @@ -1886,6 +1879,7 @@ packages: esbuild-windows-32: 0.13.15 esbuild-windows-64: 0.13.15 esbuild-windows-arm64: 0.13.15 + dev: false /escalade/3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -3130,6 +3124,7 @@ packages: resolution: {integrity: sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + dev: false /natural-compare/1.4.0: resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=} @@ -3402,6 +3397,7 @@ packages: /picocolors/1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: false /picomatch/2.3.0: resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==} @@ -3497,6 +3493,7 @@ packages: nanoid: 3.1.30 picocolors: 1.0.0 source-map-js: 1.0.1 + dev: false /preferred-pm/3.0.3: resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==} @@ -3674,6 +3671,7 @@ packages: /require-relative/0.8.7: resolution: {integrity: sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=} + dev: false /resolve-from/4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} @@ -3882,6 +3880,7 @@ packages: /source-map-js/1.0.1: resolution: {integrity: sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==} engines: {node: '>=0.10.0'} + dev: false /source-map/0.7.3: resolution: {integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==} @@ -4066,6 +4065,7 @@ packages: svelte: '>=3.19.0' dependencies: svelte: 3.44.2 + dev: false /svelte-preprocess/4.9.8_svelte@3.44.2+typescript@4.4.4: resolution: {integrity: sha512-EQS/oRZzMtYdAprppZxY3HcysKh11w54MgA63ybtL+TAZ4hVqYOnhw41JVJjWN9dhPnNjjLzvbZ2tMhTsla1Og==} @@ -4452,6 +4452,7 @@ packages: rollup: 2.60.2 optionalDependencies: fsevents: 2.3.2 + dev: false /wcwidth/1.0.1: resolution: {integrity: sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=}