diff --git a/.changeset/cyan-files-hammer.md b/.changeset/cyan-files-hammer.md new file mode 100644 index 000000000000..634729aff714 --- /dev/null +++ b/.changeset/cyan-files-hammer.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +[chore] provide Vite config via plugin diff --git a/packages/kit/src/cli.js b/packages/kit/src/cli.js index 8ed551c49292..e68877c70ef0 100755 --- a/packages/kit/src/cli.js +++ b/packages/kit/src/cli.js @@ -1,9 +1,10 @@ -import sade from 'sade'; -import colors from 'kleur'; +import chokidar from 'chokidar'; import fs from 'fs'; +import colors from 'kleur'; import { relative } from 'path'; import * as ports from 'port-authority'; -import chokidar from 'chokidar'; +import sade from 'sade'; +import * as vite from 'vite'; import { load_config } from './core/config/index.js'; import { networkInterfaces, release } from 'os'; import { coalesce_to_error } from './utils/error.js'; @@ -62,28 +63,49 @@ prog let close; async function start() { - const { dev } = await import('./core/dev/index.js'); + const svelte_config = await load_config(); + const { plugins } = await import('./core/dev/plugin.js'); + const vite_config = await svelte_config.kit.vite(); + + /** @type {import('vite').UserConfig} */ + const config = { + plugins: [...(vite_config.plugins || []), plugins(svelte_config)] + }; + config.server = {}; + + // optional config from command-line flags + // these should take precedence, but not print conflict warnings + if (host) { + config.server.host = host; + } - const { server, config } = await dev({ - port, - host, - https - }); + // 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 && !vite_config?.server?.https) { + config.server.https = https; + } + + if (port) { + config.server.port = port; + } + + const server = await vite.createServer(config); + await server.listen(port); const address_info = /** @type {import('net').AddressInfo} */ ( /** @type {import('http').Server} */ (server.httpServer).address() ); - const vite_config = server.config; + const resolved_config = server.config; welcome({ port: address_info.port, host: address_info.address, - https: !!(https || vite_config.server.https), - open: first && (open || !!vite_config.server.open), - base: config.kit.paths.base, - loose: vite_config.server.fs.strict === false, - allow: vite_config.server.fs.allow + https: !!(https || resolved_config.server.https), + open: first && (open || !!resolved_config.server.open), + base: svelte_config.kit.paths.base, + loose: resolved_config.server.fs.strict === false, + allow: resolved_config.server.fs.allow }); first = false; @@ -91,6 +113,7 @@ prog return server.close; } + // TODO: we should probably replace this with something like vite-plugin-restart async function relaunch() { const id = uid; relaunching = true; diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js deleted file mode 100644 index 734f626f55ee..000000000000 --- a/packages/kit/src/core/dev/index.js +++ /dev/null @@ -1,104 +0,0 @@ -import path from 'path'; -import { svelte } from '@sveltejs/vite-plugin-svelte'; -import * as vite from 'vite'; -import { deep_merge } from '../../utils/object.js'; -import { load_config, print_config_conflicts } from '../config/index.js'; -import { get_aliases, get_runtime_path } from '../utils.js'; -import { create_plugin } from './plugin.js'; - -const cwd = process.cwd(); - -/** - * @typedef {{ - * port: number, - * host?: string, - * https: boolean, - * }} Options - * @typedef {import('types').SSRComponent} SSRComponent - */ - -/** @param {Options} opts */ -export async function dev({ port, host, https }) { - /** @type {import('types').ValidatedConfig} */ - const config = await load_config(); - - const [vite_config] = deep_merge( - { - server: { - fs: { - allow: [ - ...new Set([ - config.kit.files.lib, - config.kit.files.routes, - config.kit.outDir, - path.resolve(cwd, 'src'), - path.resolve(cwd, 'node_modules'), - path.resolve(vite.searchForWorkspaceRoot(cwd), 'node_modules') - ]) - ] - }, - port: 3000, - strictPort: true, - watch: { - ignored: [`${config.kit.outDir}/**`, `!${config.kit.outDir}/generated/**`] - } - } - }, - await config.kit.vite() - ); - - /** @type {[any, string[]]} */ - const [merged_config, conflicts] = deep_merge(vite_config, { - configFile: false, - root: cwd, - resolve: { - alias: get_aliases(config) - }, - build: { - rollupOptions: { - // Vite dependency crawler needs an explicit JS entry point - // eventhough server otherwise works without it - input: `${get_runtime_path(config)}/client/start.js` - } - }, - plugins: [ - svelte({ - ...config, - emitCss: true, - compilerOptions: { - ...config.compilerOptions, - hydratable: !!config.kit.browser.hydrate - }, - configFile: false - }), - await create_plugin(config) - ], - 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; - } - - // 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; - } - - if (port) { - merged_config.server.port = port; - } - - const server = await vite.createServer(merged_config); - await server.listen(port); - - return { - server, - config - }; -} diff --git a/packages/kit/src/core/dev/plugin.js b/packages/kit/src/core/dev/plugin.js index f58135eeba30..3bad7819042f 100644 --- a/packages/kit/src/core/dev/plugin.js +++ b/packages/kit/src/core/dev/plugin.js @@ -1,17 +1,20 @@ +import { svelte as svelte_plugin } from '@sveltejs/vite-plugin-svelte'; import fs from 'fs'; -import path from 'path'; -import { URL } from 'url'; import colors from 'kleur'; +import path from 'path'; import sirv from 'sirv'; +import { URL } from 'url'; +import { searchForWorkspaceRoot } from 'vite'; import { installPolyfills } from '../../node/polyfills.js'; import * as sync from '../sync/sync.js'; import { getRequest, setResponse } from '../../node/index.js'; import { SVELTE_KIT_ASSETS } from '../constants.js'; -import { get_mime_lookup, get_runtime_path, resolve_entry } from '../utils.js'; +import { get_aliases, get_mime_lookup, get_runtime_path, resolve_entry } from '../utils.js'; import { coalesce_to_error } from '../../utils/error.js'; -import { load_template } from '../config/index.js'; +import { load_template, print_config_conflicts } from '../config/index.js'; import { posixify } from '../../utils/filesystem.js'; import { parse_route_id } from '../../utils/routing.js'; +import { deep_merge } from '../../utils/object.js'; // Vite doesn't expose this so we just copy the list for now // https://github.com/vitejs/vite/blob/3edd1af56e980aef56641a5a51cf2932bb580d41/packages/vite/src/node/plugins/css.ts#L96 @@ -20,19 +23,70 @@ const style_pattern = /\.(css|less|sass|scss|styl|stylus|pcss|postcss)$/; const cwd = process.cwd(); /** - * @param {import('types').ValidatedConfig} config - * @returns {Promise} + * @param {import('types').ValidatedConfig} svelte_config + * @return {import('vite').Plugin} */ -export async function create_plugin(config) { +export const sveltekit = function (svelte_config) { return { name: 'vite-plugin-svelte-kit', + async config() { + const [vite_config] = deep_merge( + { + server: { + fs: { + allow: [ + ...new Set([ + svelte_config.kit.files.lib, + svelte_config.kit.files.routes, + svelte_config.kit.outDir, + path.resolve(cwd, 'src'), + path.resolve(cwd, 'node_modules'), + path.resolve(searchForWorkspaceRoot(cwd), 'node_modules') + ]) + ] + }, + port: 3000, + strictPort: true, + watch: { + ignored: [ + `${svelte_config.kit.outDir}/**`, + `!${svelte_config.kit.outDir}/generated/**` + ] + } + } + }, + await svelte_config.kit.vite() + ); + + /** @type {[any, string[]]} */ + const [merged_config, conflicts] = deep_merge(vite_config, { + configFile: false, + root: cwd, + resolve: { + alias: get_aliases(svelte_config) + }, + build: { + rollupOptions: { + // Vite dependency crawler needs an explicit JS entry point + // eventhough server otherwise works without it + input: `${get_runtime_path(svelte_config)}/client/start.js` + } + }, + base: '/' + }); + + print_config_conflicts(conflicts, 'kit.vite.'); + + return merged_config; + }, + async configureServer(vite) { installPolyfills(); - sync.init(config); + sync.init(svelte_config); - const runtime = get_runtime_path(config); + const runtime = get_runtime_path(svelte_config); process.env.VITE_SVELTEKIT_APP_VERSION_POLL_INTERVAL = '0'; @@ -43,10 +97,10 @@ export async function create_plugin(config) { let manifest; function update_manifest() { - const { manifest_data } = sync.update(config); + const { manifest_data } = sync.update(svelte_config); manifest = { - appDir: config.kit.appDir, + appDir: svelte_config.kit.appDir, assets: new Set(manifest_data.assets.map((asset) => asset.file)), mimeTypes: get_mime_lookup(manifest_data), _: { @@ -165,14 +219,16 @@ export async function create_plugin(config) { for (const event of ['add', 'unlink']) { vite.watcher.on(event, (file) => { - if (file.startsWith(config.kit.files.routes + path.sep)) { + if (file.startsWith(svelte_config.kit.files.routes + path.sep)) { update_manifest(); } }); } - const assets = config.kit.paths.assets ? SVELTE_KIT_ASSETS : config.kit.paths.base; - const asset_server = sirv(config.kit.files.assets, { + const assets = svelte_config.kit.paths.assets + ? SVELTE_KIT_ASSETS + : svelte_config.kit.paths.base; + const asset_server = sirv(svelte_config.kit.files.assets, { dev: true, etag: true, maxAge: 0, @@ -199,7 +255,7 @@ export async function create_plugin(config) { if (decoded.startsWith(assets)) { const pathname = decoded.slice(assets.length); - const file = config.kit.files.assets + pathname; + const file = svelte_config.kit.files.assets + pathname; if (fs.existsSync(file) && !fs.statSync(file).isDirectory()) { const has_correct_case = fs.realpathSync.native(file) === path.resolve(file); @@ -212,13 +268,18 @@ export async function create_plugin(config) { } } - if (!decoded.startsWith(config.kit.paths.base)) { - return not_found(res, `Not found (did you mean ${config.kit.paths.base + req.url}?)`); + if (!decoded.startsWith(svelte_config.kit.paths.base)) { + return not_found( + res, + `Not found (did you mean ${svelte_config.kit.paths.base + req.url}?)` + ); } /** @type {Partial} */ - const user_hooks = resolve_entry(config.kit.files.hooks) - ? await vite.ssrLoadModule(`/${config.kit.files.hooks}`, { fixStacktrace: false }) + const user_hooks = resolve_entry(svelte_config.kit.files.hooks) + ? await vite.ssrLoadModule(`/${svelte_config.kit.files.hooks}`, { + fixStacktrace: false + }) : {}; const handle = user_hooks.handle || (({ event, resolve }) => resolve(event)); @@ -258,19 +319,21 @@ export async function create_plugin(config) { // can get loaded twice via different URLs, which causes failures. Might // require changes to Vite to fix const { default: root } = await vite.ssrLoadModule( - `/${posixify(path.relative(cwd, `${config.kit.outDir}/generated/root.svelte`))}`, + `/${posixify( + path.relative(cwd, `${svelte_config.kit.outDir}/generated/root.svelte`) + )}`, { fixStacktrace: false } ); const paths = await vite.ssrLoadModule( process.env.BUNDLED - ? `/${posixify(path.relative(cwd, `${config.kit.outDir}/runtime/paths.js`))}` + ? `/${posixify(path.relative(cwd, `${svelte_config.kit.outDir}/runtime/paths.js`))}` : `/@fs${runtime}/paths.js`, { fixStacktrace: false } ); paths.set_paths({ - base: config.kit.paths.base, + base: svelte_config.kit.paths.base, assets }); @@ -283,14 +346,14 @@ export async function create_plugin(config) { return res.end(err.reason || 'Invalid request body'); } - const template = load_template(cwd, config); + const template = load_template(cwd, svelte_config); const rendered = await respond( request, { - csp: config.kit.csp, + csp: svelte_config.kit.csp, dev: true, - floc: config.kit.floc, + floc: svelte_config.kit.floc, get_stack: (error) => { return fix_stack_trace(error); }, @@ -317,21 +380,21 @@ export async function create_plugin(config) { }); }, hooks, - hydrate: config.kit.browser.hydrate, + hydrate: svelte_config.kit.browser.hydrate, manifest, - method_override: config.kit.methodOverride, + method_override: svelte_config.kit.methodOverride, paths: { - base: config.kit.paths.base, + base: svelte_config.kit.paths.base, assets }, prefix: '', prerender: { - default: config.kit.prerender.default, - enabled: config.kit.prerender.enabled + default: svelte_config.kit.prerender.default, + enabled: svelte_config.kit.prerender.enabled }, - read: (file) => fs.readFileSync(path.join(config.kit.files.assets, file)), + read: (file) => fs.readFileSync(path.join(svelte_config.kit.files.assets, file)), root, - router: config.kit.browser.router, + router: svelte_config.kit.browser.router, template: ({ head, body, assets, nonce }) => { return ( template @@ -343,7 +406,7 @@ export async function create_plugin(config) { ); }, template_contains_nonce: template.includes('%sveltekit.nonce%'), - trailing_slash: config.kit.trailingSlash + trailing_slash: svelte_config.kit.trailingSlash }, { getClientAddress: () => { @@ -372,7 +435,7 @@ export async function create_plugin(config) { }; } }; -} +}; /** @param {import('http').ServerResponse} res */ function not_found(res, message = 'Not found') { @@ -436,3 +499,26 @@ async function find_deps(vite, node, deps) { await Promise.all(branches); } + +/** + * @param {import('types').ValidatedConfig} svelte_config + * @return {import('vite').Plugin[]} + */ +export const svelte = function (svelte_config) { + return svelte_plugin({ + ...svelte_config, + compilerOptions: { + ...svelte_config.compilerOptions, + hydratable: !!svelte_config.kit.browser.hydrate + }, + configFile: false + }); +}; + +/** + * @param {import('types').ValidatedConfig} svelte_config + * @return {import('vite').Plugin[]} + */ +export const plugins = function (svelte_config) { + return [...svelte(svelte_config), sveltekit(svelte_config)]; +}; diff --git a/packages/kit/tsconfig.json b/packages/kit/tsconfig.json index 0e095db709ce..d00de5e9c245 100644 --- a/packages/kit/tsconfig.json +++ b/packages/kit/tsconfig.json @@ -5,7 +5,7 @@ "noEmit": true, "strict": true, "target": "es2020", - "module": "es2020", + "module": "es2022", "moduleResolution": "node", "allowSyntheticDefaultImports": true, "paths": {