From 0fccd83992d16c6174fc9ebc6f8beb6dc0ffdddf Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Dec 2021 12:44:12 +0000 Subject: [PATCH 01/14] use vite.watcher instead of cheap-watch --- packages/kit/src/core/dev/index.js | 231 +++++++++++++---------------- 1 file changed, 104 insertions(+), 127 deletions(-) diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js index 6f6a6a294177..5d21438868d0 100644 --- a/packages/kit/src/core/dev/index.js +++ b/packages/kit/src/core/dev/index.js @@ -5,7 +5,6 @@ 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'; @@ -65,33 +64,11 @@ class Watcher extends EventEmitter { 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([ @@ -107,8 +84,6 @@ class Watcher extends EventEmitter { } 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()) || {}; @@ -125,11 +100,100 @@ class Watcher extends EventEmitter { 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'); - } + 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} */ + const 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); + } + }; + }) + } + }; - return this.manifest; + return manifest; }); /** @type {[any, string[]]} */ @@ -183,103 +247,8 @@ class Watcher extends EventEmitter { await this.vite.listen(this.port); } - 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); - } - }; - }) - } - }; - } - close() { - if (!this.vite || !this.cheapwatch) { + if (!this.vite) { throw new Error('Cannot close server before it is initialized'); } @@ -287,7 +256,6 @@ class Watcher extends EventEmitter { this.closed = true; this.vite.close(); - this.cheapwatch.close(); } } @@ -330,6 +298,15 @@ async function create_plugin(config, dir, https, get_manifest) { * @param {vite.ViteDevServer} vite */ function create_kit_middleware(vite) { + let manifest = get_manifest(); + + const update = () => { + manifest = get_manifest(); + }; + + vite.watcher.on('add', update); + vite.watcher.on('remove', update); + /** * Use a named function for debugging * @type {import('connect').NextHandleFunction} @@ -416,7 +393,7 @@ async function create_plugin(config, dir, https, get_manifest) { }, hooks, hydrate: config.kit.hydrate, - manifest: get_manifest(), + manifest, paths: { base: config.kit.paths.base, assets: config.kit.paths.assets ? SVELTE_KIT_ASSETS : config.kit.paths.base From 94fab5ce00ddecc67683ab0e3bac2245facd1af3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Dec 2021 15:07:55 +0000 Subject: [PATCH 02/14] remove some indirection --- packages/kit/src/core/dev/index.js | 36 +++++++++++++----------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js index 5d21438868d0..ea774db8152e 100644 --- a/packages/kit/src/core/dev/index.js +++ b/packages/kit/src/core/dev/index.js @@ -64,26 +64,6 @@ class Watcher extends EventEmitter { copy_assets(this.dir); process.env.VITE_SVELTEKIT_AMP = this.config.kit.amp ? 'true' : ''; - await this.init_server(); - - return this; - } - - 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() { /** @type {import('vite').UserConfig} */ const vite_config = (this.config.kit.vite && this.config.kit.vite()) || {}; @@ -245,6 +225,22 @@ class Watcher extends EventEmitter { this.vite = await vite.createServer(merged_config); await this.vite.listen(this.port); + + return this; + } + + 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') + ]) + ]; } close() { From 452985ad6efa36bac21a20043030be1396d58450 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Dec 2021 15:17:30 +0000 Subject: [PATCH 03/14] remove some more indirection --- packages/kit/src/core/dev/index.js | 286 ++++++++++++++--------------- 1 file changed, 138 insertions(+), 148 deletions(-) diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js index ea774db8152e..cbbda9cf6b60 100644 --- a/packages/kit/src/core/dev/index.js +++ b/packages/kit/src/core/dev/index.js @@ -290,134 +290,135 @@ async function create_plugin(config, dir, https, get_manifest) { */ const validator = config.kit.amp ? await amp_validator.getInstance() : null; - /** - * @param {vite.ViteDevServer} vite - */ - function create_kit_middleware(vite) { - let manifest = get_manifest(); - - const update = () => { - manifest = get_manifest(); - }; - - vite.watcher.on('add', update); - vite.watcher.on('remove', update); - + return { + name: 'vite-plugin-svelte-kit', /** - * Use a named function for debugging - * @type {import('connect').NextHandleFunction} + * @param {import('vite').ViteDevServer} vite */ - 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 - }; + configureServer(vite) { + return () => { + remove_html_middlewares(vite.middlewares); - 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' - ); - } + let manifest = get_manifest(); - if (/** @type {any} */ (hooks).serverFetch) { - // TODO remove this for 1.0 - throw new Error('The serverFetch hook has been renamed to externalFetch.'); - } + const update = () => { + manifest = get_manifest(); + }; - const root = (await vite.ssrLoadModule(`/${dir}/generated/root.svelte`)).default; + vite.watcher.on('add', update); + vite.watcher.on('remove', update); + + 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 + }; - const paths = await vite.ssrLoadModule(`/${SVELTE_KIT}/dev/runtime/paths.js`); + 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' + ); + } - paths.set_paths({ - base: config.kit.paths.base, - assets: config.kit.paths.assets ? SVELTE_KIT_ASSETS : config.kit.paths.base - }); + if (/** @type {any} */ (hooks).serverFetch) { + // TODO remove this for 1.0 + throw new Error('The serverFetch hook has been renamed to externalFetch.'); + } - let body; + const root = (await vite.ssrLoadModule(`/${dir}/generated/root.svelte`)).default; - try { - body = await getRawBody(req); - } catch (/** @type {any} */ err) { - res.statusCode = err.status || 400; - return res.end(err.reason || 'Invalid request body'); - } + const paths = await vite.ssrLoadModule(`/${SVELTE_KIT}/dev/runtime/paths.js`); - 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, - paths: { + paths.set_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 = ` + }); + + 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, + 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 = ` @@ -448,40 +449,29 @@ async function create_plugin(config, dir, https, get_manifest) { ) .join('\n\n')} `; - } - } + } + } - return rendered; - }, - trailing_slash: config.kit.trailingSlash + 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); } - ); - - 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); - } - }; - } - - 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)); + }); }; } }; From ce0f01f65f452319ca6a6958c43bb158cfcb9dc1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Dec 2021 15:23:36 +0000 Subject: [PATCH 04/14] more simplifying --- packages/kit/src/cli.js | 9 +- packages/kit/src/core/dev/index.js | 410 ++++++++++++++--------------- 2 files changed, 198 insertions(+), 221 deletions(-) diff --git a/packages/kit/src/cli.js b/packages/kit/src/cli.js index efc787331901..0835e048ecc3 100644 --- a/packages/kit/src/cli.js +++ b/packages/kit/src/cli.js @@ -93,17 +93,10 @@ prog try { const watcher = await dev({ port, host, https, config }); - watcher.on('stdout', (data) => { - process.stdout.write(data); - }); - - watcher.on('stderr', (data) => { - process.stderr.write(data); - }); - 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() diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js index cbbda9cf6b60..ee5fb3f80ef1 100644 --- a/packages/kit/src/core/dev/index.js +++ b/packages/kit/src/core/dev/index.js @@ -55,7 +55,11 @@ class Watcher extends EventEmitter { this.vite; process.on('exit', () => { - this.close(); + if (this.vite) { + this.vite.close(); + } else { + throw new Error(`Cannot close server before it is initialized`); + } }); } @@ -79,7 +83,9 @@ class Watcher extends EventEmitter { // 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, () => { + const { config, dir, https } = this; + + const get_manifest = () => { const manifest_data = create_manifest_data({ config: this.config, output: this.dir, @@ -174,7 +180,195 @@ class Watcher extends EventEmitter { }; return manifest; - }); + }; + + const validator = this.config.kit.amp ? await amp_validator.getInstance() : null; + + const kit_plugin = { + name: 'vite-plugin-svelte-kit', + /** + * @param {import('vite').ViteDevServer} vite + */ + configureServer(vite) { + return () => { + remove_html_middlewares(vite.middlewares); + + let manifest = get_manifest(); + + const update = () => { + manifest = get_manifest(); + }; + + vite.watcher.on('add', update); + vite.watcher.on('remove', update); + + 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(`/${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 + }); + + 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, + 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); + } + }); + }; + } + }; /** @type {[any, string[]]} */ const [merged_config, conflicts] = deep_merge(modified_vite_config, { @@ -242,17 +436,6 @@ class Watcher extends EventEmitter { ]) ]; } - - close() { - if (!this.vite) { - throw new Error('Cannot close server before it is initialized'); - } - - if (this.closed) return; - this.closed = true; - - this.vite.close(); - } } /** @param {string[]} array */ @@ -278,205 +461,6 @@ function get_params(array) { 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; - - return { - name: 'vite-plugin-svelte-kit', - /** - * @param {import('vite').ViteDevServer} vite - */ - configureServer(vite) { - return () => { - remove_html_middlewares(vite.middlewares); - - let manifest = get_manifest(); - - const update = () => { - manifest = get_manifest(); - }; - - vite.watcher.on('add', update); - vite.watcher.on('remove', update); - - 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(`/${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 - }); - - 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, - 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); - } - }); - }; - } - }; -} - /** @param {import('http').ServerResponse} res */ function not_found(res) { res.statusCode = 404; From aa1978d1950c36ffc838c3135b7c446b3d487d6b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Dec 2021 15:44:09 +0000 Subject: [PATCH 05/14] simplify --- packages/kit/src/cli.js | 27 +- packages/kit/src/core/dev/index.js | 726 ++++++++++++++--------------- 2 files changed, 358 insertions(+), 395 deletions(-) diff --git a/packages/kit/src/cli.js b/packages/kit/src/cli.js index 0835e048ecc3..cdc47292da01 100644 --- a/packages/kit/src/cli.js +++ b/packages/kit/src/cli.js @@ -91,31 +91,8 @@ prog const { dev } = await import('./core/dev/index.js'); try { - const watcher = await dev({ 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 - }); + const settings = await dev({ port, host, https, open, config }); + welcome(settings); } catch (error) { handle_error(error); } diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js index ee5fb3f80ef1..fb71ad2b4724 100644 --- a/packages/kit/src/core/dev/index.js +++ b/packages/kit/src/core/dev/index.js @@ -1,4 +1,3 @@ -import { EventEmitter } from 'events'; import fs from 'fs'; import path from 'path'; import { URL } from 'url'; @@ -21,421 +20,408 @@ 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 */ +/** @typedef {{ + * cwd?: string, + * port: number, + * host?: string, + * https: boolean, + * open: boolean, + * config: import('types/config').ValidatedConfig + * }} Options */ /** @typedef {import('types/internal').SSRComponent} SSRComponent */ /** @param {Options} opts */ -export function dev(opts) { +export async function dev({ cwd = process.cwd(), port, host, https, open, config }) { __fetch_polyfill(); - return new Watcher(opts).init(); -} - -class Watcher extends EventEmitter { - /** @param {Options} opts */ - constructor({ cwd = process.cwd(), port, host, https, config }) { - super(); - - /** @type {string} */ - this.cwd = cwd; - - /** @type {string} */ - this.dir = path.resolve(cwd, `${SVELTE_KIT}/dev`); - - this.port = port; - this.host = host; - this.https = https; + const dir = path.resolve(cwd, `${SVELTE_KIT}/dev`); + + rimraf(dir); + copy_assets(dir); + process.env.VITE_SVELTEKIT_AMP = config.kit.amp ? 'true' : ''; + + /** @type {import('vite').UserConfig} */ + const vite_config = (config.kit.vite && config.kit.vite()) || {}; + + const 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') + ]) + ]; - /** @type {import('types/config').ValidatedConfig} */ - this.config = config; + const default_config = { + server: { + fs: { + allow + }, + strictPort: true + } + }; - /** - * @type {vite.ViteDevServer | undefined} - */ - this.vite; + // don't warn on overriding defaults + const [modified_vite_config] = deep_merge(default_config, vite_config); - process.on('exit', () => { - if (this.vite) { - this.vite.close(); - } else { - throw new Error(`Cannot close server before it is initialized`); - } + const get_manifest = () => { + const manifest_data = create_manifest_data({ + config: config, + output: dir, + cwd: cwd }); - } - async init() { - rimraf(this.dir); - copy_assets(this.dir); - process.env.VITE_SVELTEKIT_AMP = this.config.kit.amp ? 'true' : ''; - - /** @type {import('vite').UserConfig} */ - const vite_config = (this.config.kit.vite && this.config.kit.vite()) || {}; + create_app({ + manifest_data, + output: dir, + cwd: cwd + }); - const default_config = { - server: { - fs: { - allow: this.allowed_directories() + /** @type {import('types/app').SSRManifest} */ + const 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: [] }, - strictPort: true - } - }; - - // don't warn on overriding defaults - const [modified_vite_config] = deep_merge(default_config, vite_config); - - const { config, dir, https } = this; - - const get_manifest = () => { - 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} */ - const 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 - } + nodes: manifest_data.components.map((id) => { + return async () => { + const url = `/${id}`; + + const module = /** @type {SSRComponent} */ (await server.ssrLoadModule(url)); + const node = await server.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 server.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', + 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), - 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); - } + a: route.a.map((id) => manifest_data.components.indexOf(id)), + b: route.b.map((id) => manifest_data.components.indexOf(id)) }; - }) - } - }; + } - return manifest; + return { + type: 'endpoint', + pattern: route.pattern, + params: get_params(route.params), + load: async () => { + if (!server) throw new Error('Vite server has not been initialized'); + const url = path.resolve(cwd, route.file); + return await server.ssrLoadModule(url); + } + }; + }) + } }; - const validator = this.config.kit.amp ? await amp_validator.getInstance() : null; + return manifest; + }; - const kit_plugin = { - name: 'vite-plugin-svelte-kit', - /** - * @param {import('vite').ViteDevServer} vite - */ - configureServer(vite) { - return () => { - remove_html_middlewares(vite.middlewares); + const validator = config.kit.amp ? await amp_validator.getInstance() : null; - let manifest = get_manifest(); + const kit_plugin = { + name: 'vite-plugin-svelte-kit', + /** + * @param {import('vite').ViteDevServer} vite + */ + configureServer(vite) { + return () => { + remove_html_middlewares(vite.middlewares); - const update = () => { - manifest = get_manifest(); - }; + let manifest = get_manifest(); - vite.watcher.on('add', update); - vite.watcher.on('remove', update); + const update = () => { + manifest = get_manifest(); + }; - 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' - ); - } + vite.watcher.on('add', update); + vite.watcher.on('remove', update); + + 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).serverFetch) { - // TODO remove this for 1.0 - throw new Error('The serverFetch hook has been renamed to externalFetch.'); - } + 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' + ); + } - const root = (await vite.ssrLoadModule(`/${dir}/generated/root.svelte`)).default; + if (/** @type {any} */ (hooks).serverFetch) { + // TODO remove this for 1.0 + throw new Error('The serverFetch hook has been renamed to externalFetch.'); + } - const paths = await vite.ssrLoadModule(`/${SVELTE_KIT}/dev/runtime/paths.js`); + const root = (await vite.ssrLoadModule(`/${dir}/generated/root.svelte`)).default; - paths.set_paths({ - base: config.kit.paths.base, - assets: config.kit.paths.assets ? SVELTE_KIT_ASSETS : config.kit.paths.base - }); + const paths = await vite.ssrLoadModule(`/${SVELTE_KIT}/dev/runtime/paths.js`); - let body; + paths.set_paths({ + base: config.kit.paths.base, + assets: config.kit.paths.assets ? SVELTE_KIT_ASSETS : config.kit.paths.base + }); - try { - body = await getRawBody(req); - } catch (/** @type {any} */ err) { - res.statusCode = err.status || 400; - return res.end(err.reason || 'Invalid request body'); - } + let 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 + 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 }); }, - { - 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 (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')} - `; - } + 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 (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); + return rendered; + }, + trailing_slash: config.kit.trailingSlash } - } catch (e) { - const error = coalesce_to_error(e); - vite.ssrFixStacktrace(error); - res.statusCode = 500; - res.end(error.stack); + ); + + if (rendered) { + res.writeHead(rendered.status, rendered.headers); + if (rendered.body) res.write(rendered.body); + res.end(); + } else { + not_found(res); } - }); - }; - } - }; - - /** @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: this.config.extensions, - emitCss: !this.config.kit.amp, - compilerOptions: { - hydratable: !!this.config.kit.hydrate + } catch (e) { + const error = coalesce_to_error(e); + vite.ssrFixStacktrace(error); + res.statusCode = 500; + res.end(error.stack); } - }), - 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); - - return this; + /** @type {[any, string[]]} */ + const [merged_config, conflicts] = deep_merge(modified_vite_config, { + configFile: false, + root: cwd, + resolve: { + alias: { + $app: path.resolve(`${dir}/runtime/app`), + $lib: config.kit.files.lib + } + }, + build: { + rollupOptions: { + // Vite dependency crawler needs an explicit JS entry point + // eventhough server otherwise works without it + input: path.resolve(`${dir}/runtime/internal/start.js`) + } + }, + plugins: [ + svelte({ + extensions: config.extensions, + emitCss: !config.kit.amp, + compilerOptions: { + hydratable: !!config.kit.hydrate + } + }), + kit_plugin + ], + publicDir: config.kit.files.assets, + base: config.kit.paths.assets.startsWith('/') ? `${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 (host) { + merged_config.server.host = host; + } + // 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; } - 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') - ]) - ]; + if (port) { + merged_config.server.port = port; } + + const server = await vite.createServer(merged_config); + await server.listen(port); + + process.on('exit', () => { + if (server) { + server.close(); + } else { + throw new Error(`Cannot close server before it is initialized`); + } + }); + + const address_info = /** @type {import('net').AddressInfo} */ ( + /** @type {import('http').Server} */ (server.httpServer).address() + ); + + return { + port: address_info.port, + host: address_info.address, + https: !!(https || vite_config.server?.https), + open: open || !!vite_config.server?.open, + loose: vite_config.server?.fs?.strict === false, + allow, + cwd + }; } /** @param {string[]} array */ From 4968348a60a28bebe758d68060db5a49008ab560 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Dec 2021 15:49:57 +0000 Subject: [PATCH 06/14] lint --- packages/kit/src/core/dev/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js index fb71ad2b4724..aad362d1b480 100644 --- a/packages/kit/src/core/dev/index.js +++ b/packages/kit/src/core/dev/index.js @@ -69,15 +69,15 @@ export async function dev({ cwd = process.cwd(), port, host, https, open, config const get_manifest = () => { const manifest_data = create_manifest_data({ - config: config, + config, output: dir, - cwd: cwd + cwd }); create_app({ manifest_data, output: dir, - cwd: cwd + cwd }); /** @type {import('types/app').SSRManifest} */ @@ -405,7 +405,7 @@ export async function dev({ cwd = process.cwd(), port, host, https, open, config if (server) { server.close(); } else { - throw new Error(`Cannot close server before it is initialized`); + throw new Error('Cannot close server before it is initialized'); } }); From 06f51b092b6d944d626ea172ed1fc57091fcac71 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Dec 2021 15:54:57 +0000 Subject: [PATCH 07/14] remove code that apparently doesnt do anything --- packages/kit/src/core/dev/index.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js index aad362d1b480..d83e26c0ebc0 100644 --- a/packages/kit/src/core/dev/index.js +++ b/packages/kit/src/core/dev/index.js @@ -401,14 +401,6 @@ export async function dev({ cwd = process.cwd(), port, host, https, open, config const server = await vite.createServer(merged_config); await server.listen(port); - process.on('exit', () => { - if (server) { - server.close(); - } else { - throw new Error('Cannot close server before it is initialized'); - } - }); - const address_info = /** @type {import('net').AddressInfo} */ ( /** @type {import('http').Server} */ (server.httpServer).address() ); From 5c481cc610545bb44e1a32473e7a4d44c202873d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Dec 2021 15:56:00 +0000 Subject: [PATCH 08/14] apparently no longer necessary --- packages/kit/src/core/dev/index.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js index d83e26c0ebc0..b6df43bcbfc0 100644 --- a/packages/kit/src/core/dev/index.js +++ b/packages/kit/src/core/dev/index.js @@ -360,13 +360,6 @@ export async function dev({ cwd = process.cwd(), port, host, https, open, config $lib: config.kit.files.lib } }, - build: { - rollupOptions: { - // Vite dependency crawler needs an explicit JS entry point - // eventhough server otherwise works without it - input: path.resolve(`${dir}/runtime/internal/start.js`) - } - }, plugins: [ svelte({ extensions: config.extensions, From 202bbb5fec86fb0156f69da51677117d8ed87c14 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Dec 2021 16:02:24 +0000 Subject: [PATCH 09/14] typecheck --- packages/kit/src/cli.js | 21 +++++++++++++++++++-- packages/kit/src/core/dev/index.js | 17 +++++++---------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/packages/kit/src/cli.js b/packages/kit/src/cli.js index cdc47292da01..15819bee3585 100644 --- a/packages/kit/src/cli.js +++ b/packages/kit/src/cli.js @@ -91,8 +91,25 @@ prog const { dev } = await import('./core/dev/index.js'); try { - const settings = await dev({ port, host, https, open, config }); - welcome(settings); + const cwd = process.cwd(); + + const { address_info, server_config, allow } = await dev({ + cwd, + port, + host, + https, + config + }); + + welcome({ + port: address_info.port, + host: address_info.address, + https: !!(https || server_config?.https), + open: open || !!server_config?.open, + loose: server_config?.fs?.strict === false, + 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 b6df43bcbfc0..2cfb99e6606c 100644 --- a/packages/kit/src/core/dev/index.js +++ b/packages/kit/src/core/dev/index.js @@ -21,17 +21,16 @@ import { copy_assets, get_mime_lookup, resolve_entry } from '../utils.js'; import { coalesce_to_error } from '../../utils/error.js'; /** @typedef {{ - * cwd?: string, + * cwd: string, * port: number, * host?: string, * https: boolean, - * open: boolean, * config: import('types/config').ValidatedConfig * }} Options */ /** @typedef {import('types/internal').SSRComponent} SSRComponent */ /** @param {Options} opts */ -export async function dev({ cwd = process.cwd(), port, host, https, open, config }) { +export async function dev({ cwd, port, host, https, config }) { __fetch_polyfill(); const dir = path.resolve(cwd, `${SVELTE_KIT}/dev`); @@ -371,7 +370,7 @@ export async function dev({ cwd = process.cwd(), port, host, https, open, config kit_plugin ], publicDir: config.kit.files.assets, - base: config.kit.paths.assets.startsWith('/') ? `${config.kit.paths.assets}/` : '/' + base: '/' }); print_config_conflicts(conflicts, 'kit.vite.'); @@ -381,7 +380,8 @@ export async function dev({ cwd = process.cwd(), port, host, https, open, config if (host) { merged_config.server.host = host; } - // https is already enabled then do nothing. it could be an object and we + + // 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; @@ -399,11 +399,8 @@ export async function dev({ cwd = process.cwd(), port, host, https, open, config ); return { - port: address_info.port, - host: address_info.address, - https: !!(https || vite_config.server?.https), - open: open || !!vite_config.server?.open, - loose: vite_config.server?.fs?.strict === false, + address_info, + server_config: vite_config.server, allow, cwd }; From 90deae4eda57630e070393a372351ba0ded4cdd9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Dec 2021 16:04:30 +0000 Subject: [PATCH 10/14] tests passing --- packages/kit/src/core/dev/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js index 2cfb99e6606c..a9dd164c1a9e 100644 --- a/packages/kit/src/core/dev/index.js +++ b/packages/kit/src/core/dev/index.js @@ -402,7 +402,9 @@ export async function dev({ cwd, port, host, https, config }) { address_info, server_config: vite_config.server, allow, - cwd + cwd, + + close: () => server.close() }; } From 7833faf16644937842fbea5a57aa88aa356087eb Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Dec 2021 16:40:07 +0000 Subject: [PATCH 11/14] move plugin into separate module --- packages/kit/src/cli.js | 10 +- packages/kit/src/core/dev/index.js | 418 ++-------------------------- packages/kit/src/core/dev/plugin.js | 352 +++++++++++++++++++++++ 3 files changed, 385 insertions(+), 395 deletions(-) create mode 100644 packages/kit/src/core/dev/plugin.js diff --git a/packages/kit/src/cli.js b/packages/kit/src/cli.js index 15819bee3585..3641406f225c 100644 --- a/packages/kit/src/cli.js +++ b/packages/kit/src/cli.js @@ -93,7 +93,7 @@ prog try { const cwd = process.cwd(); - const { address_info, server_config, allow } = await dev({ + const { address_info, server_config } = await dev({ cwd, port, host, @@ -104,10 +104,10 @@ prog welcome({ port: address_info.port, host: address_info.address, - https: !!(https || server_config?.https), - open: open || !!server_config?.open, - loose: server_config?.fs?.strict === false, - allow, + https: !!(https || server_config.https), + open: open || !!server_config.open, + loose: server_config.fs.strict === false, + allow: server_config.fs.allow, cwd }); } catch (error) { diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js index a9dd164c1a9e..06603143730d 100644 --- a/packages/kit/src/core/dev/index.js +++ b/packages/kit/src/core/dev/index.js @@ -1,24 +1,13 @@ -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 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'; +import { SVELTE_KIT } from '../constants.js'; +import { copy_assets } from '../utils.js'; +import { create_plugin } from './plugin.js'; /** @typedef {{ * cwd: string, @@ -31,331 +20,42 @@ import { coalesce_to_error } from '../../utils/error.js'; /** @param {Options} opts */ export async function dev({ cwd, port, host, https, config }) { - __fetch_polyfill(); + const output = path.resolve(cwd, `${SVELTE_KIT}/dev`); - const dir = path.resolve(cwd, `${SVELTE_KIT}/dev`); + rimraf(output); + copy_assets(output); - rimraf(dir); - copy_assets(dir); process.env.VITE_SVELTEKIT_AMP = config.kit.amp ? 'true' : ''; - /** @type {import('vite').UserConfig} */ - const vite_config = (config.kit.vite && config.kit.vite()) || {}; - - const 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') - ]) - ]; - - const default_config = { - server: { - fs: { - allow - }, - strictPort: true - } - }; - - // don't warn on overriding defaults - const [modified_vite_config] = deep_merge(default_config, vite_config); - - const get_manifest = () => { - const manifest_data = create_manifest_data({ - config, - output: dir, - cwd - }); - - create_app({ - manifest_data, - output: dir, - cwd - }); - - /** @type {import('types/app').SSRManifest} */ - const 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: [] + const [vite_config] = deep_merge( + { + server: { + fs: { + 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') + ]) + ] }, - nodes: manifest_data.components.map((id) => { - return async () => { - const url = `/${id}`; - - const module = /** @type {SSRComponent} */ (await server.ssrLoadModule(url)); - const node = await server.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 server.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 (!server) throw new Error('Vite server has not been initialized'); - const url = path.resolve(cwd, route.file); - return await server.ssrLoadModule(url); - } - }; - }) + strictPort: true } - }; - - return manifest; - }; - - const validator = config.kit.amp ? await amp_validator.getInstance() : null; - - const kit_plugin = { - name: 'vite-plugin-svelte-kit', - /** - * @param {import('vite').ViteDevServer} vite - */ - configureServer(vite) { - return () => { - remove_html_middlewares(vite.middlewares); - - let manifest = get_manifest(); - - const update = () => { - manifest = get_manifest(); - }; - - vite.watcher.on('add', update); - vite.watcher.on('remove', update); - - 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(`/${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 - }); - - 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, - 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); - } - }); - }; - } - }; + }, + config.kit.vite() + ); /** @type {[any, string[]]} */ - const [merged_config, conflicts] = deep_merge(modified_vite_config, { + const [merged_config, conflicts] = deep_merge(vite_config, { configFile: false, root: cwd, resolve: { alias: { - $app: path.resolve(`${dir}/runtime/app`), + $app: path.resolve(`${output}/runtime/app`), $lib: config.kit.files.lib } }, @@ -367,7 +67,7 @@ export async function dev({ cwd, port, host, https, config }) { hydratable: !!config.kit.hydrate } }), - kit_plugin + create_plugin(config, output, cwd, config.kit.amp && (await amp_validator.getInstance())) ], publicDir: config.kit.files.assets, base: '/' @@ -401,68 +101,6 @@ export async function dev({ cwd, port, host, https, config }) { return { address_info, server_config: vite_config.server, - allow, - cwd, - close: () => server.close() }; } - -/** @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/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); + } + } +} From aa438d3630f87138c71a0d2c9d211bd090ccd5a9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Dec 2021 16:43:46 +0000 Subject: [PATCH 12/14] remove cheap-watch --- packages/kit/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kit/package.json b/packages/kit/package.json index 7ce0fb3d0703..3b7b0f70ff05 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" }, From f9628606d62c4e9480bdb42d150ff307f6a9e92b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Dec 2021 16:44:31 +0000 Subject: [PATCH 13/14] update lockfile --- pnpm-lock.yaml | 55 +++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 27 deletions(-) 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=} From 969704d3f4b1b2da4c45f584aef16540c1eddce3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 30 Dec 2021 16:47:11 +0000 Subject: [PATCH 14/14] revert versions in create-svelte --- .../create-svelte/templates/default/package.template.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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",