From c5565115834df42f0339ce415b6e8c2f78c9ffc4 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Thu, 23 Jun 2022 18:45:27 -0700 Subject: [PATCH] Convert build to Vite plugin and expose as `@sveltejs/kit/experimental/vite` (#5094) * Expose Vite plugin as @sveltejs/kit/vite * make svelte config optional * Implement build * export as experimental/vite for now * add plugin correct number of times with or without vite.config.js * add TODO * don't rename dev file for now * update API * cleanup * Update packages/kit/src/vite/plugin.js Co-authored-by: Rich Harris * Revert "Update packages/kit/src/vite/plugin.js" This reverts commit 4d600284cec19e0cb37f6cc256308b6ea875a3a1. * enable validation plugin * enable validation for real this time * add validation plugin to all builds * format * fix plugin handling * format * cleanup API * remove configFile: false * fix validation plugin * fix preview * Update healthy-carrots-cross.md * move dev/build/preview inside vite plugin * move adapt inside build * move all vite-related code into src/vite * move some more stuff around * fix some stuff * simplify stuff * remove unused plugins_internal * we can now simplify stuff a bunch * simplify * lint * update path in .prettierrc * revert accidental change * try fixing CI * try fixing CI * lint * lint * fix * appease typescript. not sure why this started happening * whyyy * simplify * simplify * simplify * respect user config in build * update turbo cache * format * resolveJsonModule * remove unnecessary configResolved hook * remove unused option * minor simplification * simplify * move some stuff closer to where its used * dry some stuff out, move some stuff to buildStart * do all config checking up-front * remove unused print_config_conflicts * remove unused import * simplify merging * remove unused import * remove blank line * i think these files were committed by mistake * set env vars earlier Co-authored-by: Rich Harris Co-authored-by: Rich Harris --- .changeset/healthy-carrots-cross.md | 5 + .prettierrc | 2 +- packages/kit/.gitignore | 2 +- packages/kit/package.json | 3 + packages/kit/rollup.config.js | 3 +- packages/kit/src/cli.js | 92 ++- packages/kit/src/core/build/build_client.js | 79 --- packages/kit/src/core/build/index.js | 91 --- packages/kit/src/core/config/index.js | 31 +- packages/kit/src/core/config/index.spec.js | 16 +- packages/kit/src/core/dev/plugin.js | 543 ------------------ packages/kit/src/core/preview/index.js | 197 ------- .../errors/duplicate-export/jsconfig.json | 2 +- .../kit/src/runtime/server/page/load_node.js | 3 +- packages/kit/src/utils/object.js | 96 ---- .../src/{core => vite/build}/adapt/builder.js | 4 +- .../build}/adapt/builder.spec.js | 0 .../.svelte-kit/output/client/routes/index.js | 0 .../output/server/.well-known/test.js | 0 .../basic/.svelte-kit/output/server/index.js | 0 .../adapt/fixtures/basic/static/answer.md | 0 .../src/{core => vite/build}/adapt/index.js | 0 .../src/{core => vite}/build/build_server.js | 27 +- .../build/build_service_worker.js | 14 +- .../{core => vite}/build/prerender/crawl.js | 0 .../build/prerender/crawl.spec.js | 0 .../prerender/fixtures/basic-href/input.html | 0 .../prerender/fixtures/basic-href/output.json | 0 .../prerender/fixtures/basic-src/input.html | 0 .../prerender/fixtures/basic-src/output.json | 0 .../fixtures/basic-srcset/input.html | 0 .../fixtures/basic-srcset/output.json | 0 .../fixtures/include-rel-external/input.html | 0 .../fixtures/include-rel-external/output.json | 0 .../fixtures/unquoted-attributes/input.html | 0 .../fixtures/unquoted-attributes/output.json | 0 .../build/prerender/prerender.js | 0 .../{core => vite}/build/prerender/queue.js | 0 .../build/prerender/queue.spec.js | 0 .../kit/src/{core => vite}/build/utils.js | 35 +- packages/kit/src/vite/dev/index.js | 458 +++++++++++++++ packages/kit/src/vite/index.js | 350 +++++++++++ packages/kit/src/vite/preview/index.js | 183 ++++++ packages/kit/src/vite/utils.js | 71 +++ .../object.spec.js => vite/utils.spec.js} | 27 +- packages/kit/test/apps/amp/svelte.config.js | 2 +- packages/kit/test/apps/amp/tsconfig.json | 4 +- .../kit/test/apps/basics/svelte.config.js | 2 +- packages/kit/test/apps/basics/tsconfig.json | 7 +- .../kit/test/apps/options-2/svelte.config.js | 2 +- .../kit/test/apps/options-2/tsconfig.json | 4 +- .../kit/test/apps/options/svelte.config.js | 2 +- .../test/prerendering/basics/svelte.config.js | 2 +- .../kit/test/prerendering/basics/test/test.js | 2 +- .../test/prerendering/basics/tsconfig.json | 4 +- .../prerendering/disabled/svelte.config.js | 2 +- .../prerendering/options/svelte.config.js | 2 +- .../test/prerendering/options/test/test.js | 2 +- .../test/prerendering/options/tsconfig.json | 4 +- .../prerendering/paths-base/svelte.config.js | 2 +- .../test/prerendering/paths-base/test/test.js | 2 +- .../prerendering/paths-base/tsconfig.json | 4 +- turbo.json | 2 +- 63 files changed, 1214 insertions(+), 1171 deletions(-) create mode 100644 .changeset/healthy-carrots-cross.md delete mode 100644 packages/kit/src/core/build/build_client.js delete mode 100644 packages/kit/src/core/build/index.js delete mode 100644 packages/kit/src/core/dev/plugin.js delete mode 100644 packages/kit/src/core/preview/index.js delete mode 100644 packages/kit/src/utils/object.js rename packages/kit/src/{core => vite/build}/adapt/builder.js (96%) rename packages/kit/src/{core => vite/build}/adapt/builder.spec.js (100%) rename packages/kit/src/{core => vite/build}/adapt/fixtures/basic/.svelte-kit/output/client/routes/index.js (100%) rename packages/kit/src/{core => vite/build}/adapt/fixtures/basic/.svelte-kit/output/server/.well-known/test.js (100%) rename packages/kit/src/{core => vite/build}/adapt/fixtures/basic/.svelte-kit/output/server/index.js (100%) rename packages/kit/src/{core => vite/build}/adapt/fixtures/basic/static/answer.md (100%) rename packages/kit/src/{core => vite/build}/adapt/index.js (100%) rename packages/kit/src/{core => vite}/build/build_server.js (92%) rename packages/kit/src/{core => vite}/build/build_service_worker.js (85%) rename packages/kit/src/{core => vite}/build/prerender/crawl.js (100%) rename packages/kit/src/{core => vite}/build/prerender/crawl.spec.js (100%) rename packages/kit/src/{core => vite}/build/prerender/fixtures/basic-href/input.html (100%) rename packages/kit/src/{core => vite}/build/prerender/fixtures/basic-href/output.json (100%) rename packages/kit/src/{core => vite}/build/prerender/fixtures/basic-src/input.html (100%) rename packages/kit/src/{core => vite}/build/prerender/fixtures/basic-src/output.json (100%) rename packages/kit/src/{core => vite}/build/prerender/fixtures/basic-srcset/input.html (100%) rename packages/kit/src/{core => vite}/build/prerender/fixtures/basic-srcset/output.json (100%) rename packages/kit/src/{core => vite}/build/prerender/fixtures/include-rel-external/input.html (100%) rename packages/kit/src/{core => vite}/build/prerender/fixtures/include-rel-external/output.json (100%) rename packages/kit/src/{core => vite}/build/prerender/fixtures/unquoted-attributes/input.html (100%) rename packages/kit/src/{core => vite}/build/prerender/fixtures/unquoted-attributes/output.json (100%) rename packages/kit/src/{core => vite}/build/prerender/prerender.js (100%) rename packages/kit/src/{core => vite}/build/prerender/queue.js (100%) rename packages/kit/src/{core => vite}/build/prerender/queue.spec.js (100%) rename packages/kit/src/{core => vite}/build/utils.js (79%) create mode 100644 packages/kit/src/vite/dev/index.js create mode 100644 packages/kit/src/vite/index.js create mode 100644 packages/kit/src/vite/preview/index.js create mode 100644 packages/kit/src/vite/utils.js rename packages/kit/src/{utils/object.spec.js => vite/utils.spec.js} (82%) diff --git a/.changeset/healthy-carrots-cross.md b/.changeset/healthy-carrots-cross.md new file mode 100644 index 000000000000..2ed5c7d3ac10 --- /dev/null +++ b/.changeset/healthy-carrots-cross.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Expose Vite plugin as @sveltejs/kit/experimental/vite diff --git a/.prettierrc b/.prettierrc index a87fcd7acf15..bf43738768cd 100644 --- a/.prettierrc +++ b/.prettierrc @@ -24,7 +24,7 @@ "packages/kit/src/packaging/test/fixtures/**/expected/**/*", "packages/kit/src/packaging/test/watch/expected/**/*", "packages/kit/src/packaging/test/watch/package/**/*", - "packages/kit/src/core/build/prerender/fixtures/**/*" + "packages/kit/src/vite/build/prerender/fixtures/**/*" ], "options": { "requirePragma": true diff --git a/packages/kit/.gitignore b/packages/kit/.gitignore index 82ad03c88b0e..a1519ebbd4e4 100644 --- a/packages/kit/.gitignore +++ b/packages/kit/.gitignore @@ -5,7 +5,7 @@ /docs /test/**/.svelte-kit /test/**/build -!/src/core/adapt/fixtures/*/.svelte-kit +!/src/vite/build/adapt/fixtures/*/.svelte-kit !/test/node_modules /test/apps/basics/test/errors.json .custom-out-dir \ No newline at end of file diff --git a/packages/kit/package.json b/packages/kit/package.json index 59f6322ce51b..eab8a4d444c6 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -88,6 +88,9 @@ }, "./hooks": { "import": "./dist/hooks.js" + }, + "./experimental/vite": { + "import": "./dist/vite.js" } }, "types": "types/index.d.ts", diff --git a/packages/kit/rollup.config.js b/packages/kit/rollup.config.js index feee1e87d5ad..7fa7819d249d 100644 --- a/packages/kit/rollup.config.js +++ b/packages/kit/rollup.config.js @@ -63,7 +63,8 @@ export default [ cli: 'src/cli.js', node: 'src/node/index.js', 'node/polyfills': 'src/node/polyfills.js', - hooks: 'src/hooks.js' + hooks: 'src/hooks.js', + vite: 'src/vite/index.js' }, output: { dir: 'dist', diff --git a/packages/kit/src/cli.js b/packages/kit/src/cli.js index ed950cea1cd6..1dcff209afd4 100755 --- a/packages/kit/src/cli.js +++ b/packages/kit/src/cli.js @@ -1,13 +1,12 @@ import chokidar from 'chokidar'; import fs from 'fs'; +import path from 'path'; import colors from 'kleur'; -import { relative } from 'path'; 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'; -import { logger } from './core/utils.js'; /** @param {unknown} e */ function handle_error(e) { @@ -62,15 +61,8 @@ prog let close; async function start() { - const { plugins } = await import('./core/dev/plugin.js'); const svelte_config = await load_config(); - const vite_config = await svelte_config.kit.vite(); - - /** @type {import('vite').UserConfig} */ - const config = { - ...vite_config, - plugins: [...(vite_config.plugins || []), plugins(svelte_config)] - }; + const config = await get_vite_config(svelte_config); config.server = config.server || {}; // optional config from command-line flags @@ -81,7 +73,7 @@ prog // 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) { + if (https && !config?.server?.https) { config.server.https = https; } @@ -158,31 +150,11 @@ prog .action(async ({ verbose }) => { try { process.env.NODE_ENV = process.env.NODE_ENV || 'production'; - const config = await load_config(); - - const log = logger({ verbose }); - - const { build } = await import('./core/build/index.js'); - const { build_data, prerendered } = await build(config, { log }); - - console.log( - `\nRun ${colors.bold().cyan('npm run preview')} to preview your production build locally.` - ); - - if (config.kit.adapter) { - const { adapt } = await import('./core/adapt/index.js'); - await adapt(config, build_data, prerendered, { log }); + process.env.VERBOSE = verbose; - // this is necessary to close any open db connections, etc - process.exit(0); - } - - console.log(colors.bold().yellow('\nNo adapter specified')); - - // prettier-ignore - console.log( - `See ${colors.bold().cyan('https://kit.svelte.dev/docs/adapters')} to learn how to configure your app to run on the platform of your choosing` - ); + const svelte_config = await load_config(); + const vite_config = await get_vite_config(svelte_config); + await vite.build(vite_config); // TODO when we get rid of config.kit.vite, this can just be vite.build() } catch (error) { handle_error(error); } @@ -202,30 +174,18 @@ prog process.env.NODE_ENV = process.env.NODE_ENV || 'production'; - const { sveltekit_plugin } = await import('./core/preview/index.js'); const svelte_config = await load_config(); - const vite_config = await svelte_config.kit.vite(); + const vite_config = await get_vite_config(svelte_config); - /** @type {import('vite').UserConfig} */ - const config = { - ...vite_config, - plugins: [...(vite_config.plugins || []), sveltekit_plugin] - }; - config.preview = config.preview || {}; + vite_config.preview = vite_config.preview || {}; // optional config from command-line flags // these should take precedence, but not print conflict warnings - if (host) { - config.preview.host = host; - } - if (https) { - config.preview.https = https; - } - if (port) { - config.preview.port = port; - } + if (host) vite_config.preview.host = host; + if (https) vite_config.preview.https = https; + if (port) vite_config.preview.port = port; - const preview_server = await vite.preview(config); + const preview_server = await vite.preview(vite_config); welcome({ port, host, https, open, base: preview_server.config.base }); } catch (error) { @@ -305,7 +265,7 @@ function welcome({ port, host, https, open, base, loose, allow, cwd }) { if (loose) { console.log(`\n ${colors.yellow('Serving with vite.server.fs.strict: false. Note that all files on your machine will be accessible to anyone on your network.')}`); } else if (allow?.length && cwd) { - console.log(`\n ${colors.yellow('Note that all files in the following directories will be accessible to anyone on your network: ' + allow.map(a => relative(cwd, a)).join(', '))}`); + console.log(`\n ${colors.yellow('Note that all files in the following directories will be accessible to anyone on your network: ' + allow.map(a => path.relative(cwd, a)).join(', '))}`); } } else { console.log(` ${colors.gray('network: not exposed')}`); @@ -320,3 +280,27 @@ function welcome({ port, host, https, open, base, loose, allow, cwd }) { console.log('\n'); } + +/** + * @param {import('types').ValidatedConfig} svelte_config + * @return {Promise} + */ +export async function get_vite_config(svelte_config) { + for (const file of ['vite.config.js', 'vite.config.mjs', 'vite.config.cjs']) { + if (fs.existsSync(file)) { + // TODO warn here if config.kit.vite was specified + const module = await import(path.resolve(file)); + return { + ...module.default, + configFile: false + }; + } + } + + const { sveltekit } = await import('./vite/index.js'); + + // TODO: stop reading Vite config from SvelteKit config or move to CLI + const vite_config = await svelte_config.kit.vite(); + vite_config.plugins = [...(vite_config.plugins || []), ...sveltekit()]; + return vite_config; +} diff --git a/packages/kit/src/core/build/build_client.js b/packages/kit/src/core/build/build_client.js deleted file mode 100644 index 9b8a793ec8bc..000000000000 --- a/packages/kit/src/core/build/build_client.js +++ /dev/null @@ -1,79 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { deep_merge } from '../../utils/object.js'; -import { print_config_conflicts } from '../config/index.js'; -import { create_build, find_deps, get_default_config } from './utils.js'; -import { posixify } from '../../utils/filesystem.js'; - -/** - * @param {{ - * cwd: string; - * config: import('types').ValidatedConfig; - * manifest_data: import('types').ManifestData; - * output_dir: string; - * client_entry_file: string; - * }} options - */ -export async function build_client(options) { - const { cwd, config, manifest_data, output_dir, client_entry_file } = options; - - process.env.VITE_SVELTEKIT_APP_VERSION = config.kit.version.name; - process.env.VITE_SVELTEKIT_APP_VERSION_FILE = `${config.kit.appDir}/version.json`; - process.env.VITE_SVELTEKIT_APP_VERSION_POLL_INTERVAL = `${config.kit.version.pollInterval}`; - - const client_out_dir = `${output_dir}/client/${config.kit.appDir}`; - - /** @type {Record} */ - const input = { - start: path.resolve(cwd, client_entry_file) - }; - - // This step is optional — Vite/Rollup will create the necessary chunks - // for everything regardless — but it means that entry chunks reflect - // their location in the source code, which is helpful for debugging - manifest_data.components.forEach((file) => { - const resolved = path.resolve(cwd, file); - const relative = path.relative(config.kit.files.routes, resolved); - - const name = relative.startsWith('..') - ? path.basename(file) - : posixify(path.join('pages', relative)); - input[name] = resolved; - }); - - /** @type {[any, string[]]} */ - const [merged_config, conflicts] = deep_merge( - await config.kit.vite(), - get_default_config({ ...options, client_out_dir, input, ssr: false }) - ); - - print_config_conflicts(conflicts, 'kit.vite.', 'build_client'); - - const { chunks, assets } = await create_build(merged_config); - - /** @type {import('vite').Manifest} */ - const vite_manifest = JSON.parse( - fs.readFileSync(`${client_out_dir}/immutable/manifest.json`, 'utf-8') - ); - - const entry = posixify(client_entry_file); - const entry_js = new Set(); - const entry_css = new Set(); - find_deps(entry, vite_manifest, entry_js, entry_css); - - fs.writeFileSync( - `${client_out_dir}/version.json`, - JSON.stringify({ version: process.env.VITE_SVELTEKIT_APP_VERSION }) - ); - - return { - assets, - chunks, - entry: { - file: vite_manifest[entry].file, - js: Array.from(entry_js), - css: Array.from(entry_css) - }, - vite_manifest - }; -} diff --git a/packages/kit/src/core/build/index.js b/packages/kit/src/core/build/index.js deleted file mode 100644 index 5868a66e3860..000000000000 --- a/packages/kit/src/core/build/index.js +++ /dev/null @@ -1,91 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { mkdirp, rimraf, posixify } from '../../utils/filesystem.js'; -import * as sync from '../sync/sync.js'; -import { get_runtime_path, resolve_entry } from '../utils.js'; -import { generate_manifest } from '../generate_manifest/index.js'; -import { build_service_worker } from './build_service_worker.js'; -import { build_client } from './build_client.js'; -import { build_server } from './build_server.js'; -import { prerender } from './prerender/prerender.js'; - -/** - * @param {import('types').ValidatedConfig} config - * @param {{ log: import('types').Logger }} opts - */ -export async function build(config, { log }) { - const cwd = process.cwd(); // TODO is this necessary? - - const build_dir = path.join(config.kit.outDir, 'build'); - rimraf(build_dir); - mkdirp(build_dir); - - const output_dir = path.join(config.kit.outDir, 'output'); - rimraf(output_dir); - mkdirp(output_dir); - - const { manifest_data } = sync.all(config); - - const options = { - cwd, - config, - build_dir, - manifest_data, - output_dir, - client_entry_file: path.relative(cwd, `${get_runtime_path(config.kit)}/client/start.js`), - service_worker_entry_file: resolve_entry(config.kit.files.serviceWorker) - }; - - const client = await build_client(options); - const server = await build_server(options, client); - - /** @type {import('types').BuildData} */ - const build_data = { - app_dir: config.kit.appDir, - manifest_data: options.manifest_data, - service_worker: options.service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable? - client, - server - }; - - const manifest = `export const manifest = ${generate_manifest({ - build_data, - relative_path: '.', - routes: options.manifest_data.routes - })};\n`; - fs.writeFileSync(`${output_dir}/server/manifest.js`, manifest); - - const static_files = options.manifest_data.assets.map((asset) => posixify(asset.file)); - - const files = new Set([ - ...static_files, - ...client.chunks.map((chunk) => `${config.kit.appDir}/immutable/${chunk.fileName}`), - ...client.assets.map((chunk) => `${config.kit.appDir}/immutable/${chunk.fileName}`) - ]); - - // TODO is this right? - static_files.forEach((file) => { - if (file.endsWith('/index.html')) { - files.add(file.slice(0, -11)); - } - }); - - const prerendered = await prerender({ - config: config.kit, - entries: options.manifest_data.routes - .map((route) => (route.type === 'page' ? route.path : '')) - .filter(Boolean), - files, - log - }); - - if (options.service_worker_entry_file) { - if (config.kit.paths.assets) { - throw new Error('Cannot use service worker alongside config.kit.paths.assets'); - } - - await build_service_worker(options, prerendered, client.vite_manifest); - } - - return { build_data, prerendered }; -} diff --git a/packages/kit/src/core/config/index.js b/packages/kit/src/core/config/index.js index ac9d39917875..f903bb086b05 100644 --- a/packages/kit/src/core/config/index.js +++ b/packages/kit/src/core/config/index.js @@ -1,7 +1,6 @@ import fs from 'fs'; import path from 'path'; import * as url from 'url'; -import { logger } from '../utils.js'; import options from './options.js'; /** @@ -47,14 +46,20 @@ export async function load_config({ cwd = process.cwd() } = {}) { const config_file = path.join(cwd, 'svelte.config.js'); if (!fs.existsSync(config_file)) { - throw new Error( - 'You need to create a svelte.config.js file. See https://kit.svelte.dev/docs/configuration' - ); + return process_config({}, { cwd }); } const config = await import(`${url.pathToFileURL(config_file).href}?ts=${Date.now()}`); - const validated = validate_config(config.default); + return process_config(config.default, { cwd }); +} + +/** + * @param {import('types').Config} config + * @returns {import('types').ValidatedConfig} + */ +function process_config(config, { cwd = process.cwd() } = {}) { + const validated = validate_config(config); validated.kit.outDir = path.resolve(cwd, validated.kit.outDir); @@ -79,19 +84,3 @@ export function validate_config(config) { return options(config, 'config'); } - -/** - * Ensures the user does not override any config values that SvelteKit must control. - * @param {string[]} conflicts - array of conflicts in dotted notation - * @param {string=} pathPrefix - prepended in front of the path - * @param {string=} scope - used to prefix the whole error message - */ -export function print_config_conflicts(conflicts, pathPrefix = '', scope) { - const prefix = scope ? scope + ': ' : ''; - const log = logger({ verbose: false }); - conflicts.forEach((conflict) => { - log.error( - `${prefix}The value for ${pathPrefix}${conflict} specified in svelte.config.js has been ignored. This option is controlled by SvelteKit.` - ); - }); -} diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js index 597a5ecf911b..6106d4ff3c25 100644 --- a/packages/kit/src/core/config/index.spec.js +++ b/packages/kit/src/core/config/index.spec.js @@ -2,12 +2,26 @@ import { join } from 'path'; import { fileURLToPath } from 'url'; import { test } from 'uvu'; import * as assert from 'uvu/assert'; -import { remove_keys } from '../../utils/object.js'; import { validate_config, load_config } from './index.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = join(__filename, '..'); +/** + * mutates and remove keys from an object when check callback returns true + * @param {Record} o any object + * @param {([key, value]: [string, any]) => boolean} check callback with access + * to the key-value pair and returns a boolean that decides the deletion of key + */ +function remove_keys(o, check) { + for (const key in o) { + if (!Object.hasOwnProperty.call(o, key)) continue; + if (check([key, o[key]])) delete o[key]; + const nested = typeof o[key] === 'object' && !Array.isArray(o[key]); + if (nested) remove_keys(o[key], check); + } +} + const get_defaults = (prefix = '') => ({ extensions: ['.svelte'], kit: { diff --git a/packages/kit/src/core/dev/plugin.js b/packages/kit/src/core/dev/plugin.js deleted file mode 100644 index bd5717329acd..000000000000 --- a/packages/kit/src/core/dev/plugin.js +++ /dev/null @@ -1,543 +0,0 @@ -import { svelte as svelte_plugin } from '@sveltejs/vite-plugin-svelte'; -import fs from 'fs'; -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_aliases, get_mime_lookup, get_runtime_path, resolve_entry } from '../utils.js'; -import { coalesce_to_error } from '../../utils/error.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 -const style_pattern = /\.(css|less|sass|scss|styl|stylus|pcss|postcss)$/; - -const cwd = process.cwd(); - -/** - * @param {import('types').ValidatedConfig} svelte_config - * @return {import('vite').Plugin} - */ -export const sveltekit = function (svelte_config) { - const kit_config = svelte_config.kit; - return { - name: 'vite-plugin-svelte-kit', - - async config() { - const [vite_config] = deep_merge( - { - server: { - fs: { - allow: [ - ...new Set([ - kit_config.files.lib, - kit_config.files.routes, - kit_config.outDir, - path.resolve(cwd, 'src'), - path.resolve(cwd, 'node_modules'), - path.resolve(searchForWorkspaceRoot(cwd), 'node_modules') - ]) - ] - }, - port: 3000, - strictPort: true, - watch: { - ignored: [ - // Ignore all siblings of config.kit.outDir/generated - `${posixify(kit_config.outDir)}/!(generated)` - ] - } - } - }, - await kit_config.vite() - ); - - /** @type {[any, string[]]} */ - const [merged_config, conflicts] = deep_merge(vite_config, { - configFile: false, - root: cwd, - resolve: { - alias: get_aliases(kit_config) - }, - build: { - rollupOptions: { - // Vite dependency crawler needs an explicit JS entry point - // eventhough server otherwise works without it - input: `${get_runtime_path(kit_config)}/client/start.js` - } - }, - base: '/' - }); - - print_config_conflicts(conflicts, 'kit.vite.'); - - return merged_config; - }, - - async configureServer(vite) { - installPolyfills(); - - sync.init(svelte_config); - - const runtime = get_runtime_path(kit_config); - - process.env.VITE_SVELTEKIT_APP_VERSION_POLL_INTERVAL = '0'; - - /** @type {import('types').Respond} */ - const respond = (await import(`${runtime}/server/index.js`)).respond; - - /** @type {import('types').SSRManifest} */ - let manifest; - - function update_manifest() { - const { manifest_data } = sync.update(svelte_config); - - manifest = { - appDir: kit_config.appDir, - assets: new Set(manifest_data.assets.map((asset) => asset.file)), - mimeTypes: get_mime_lookup(manifest_data), - _: { - entry: { - file: `/@fs${runtime}/client/start.js`, - css: [], - js: [] - }, - nodes: manifest_data.components.map((id, index) => { - return async () => { - const url = id.startsWith('..') ? `/@fs${path.posix.resolve(id)}` : `/${id}`; - - const module = /** @type {import('types').SSRComponent} */ ( - await vite.ssrLoadModule(url, { fixStacktrace: false }) - ); - const node = await vite.moduleGraph.getModuleByUrl(url); - - if (!node) throw new Error(`Could not find node for ${url}`); - - const deps = new Set(); - await find_deps(vite, node, deps); - - /** @type {Record} */ - const styles = {}; - - for (const dep of deps) { - const parsed = new URL(dep.url, 'http://localhost/'); - const query = parsed.searchParams; - - if ( - style_pattern.test(dep.file) || - (query.has('svelte') && query.get('type') === 'style') - ) { - try { - const mod = await vite.ssrLoadModule(dep.url, { fixStacktrace: false }); - styles[dep.url] = mod.default; - } catch { - // this can happen with dynamically imported modules, I think - // because the Vite module graph doesn't distinguish between - // static and dynamic imports? TODO investigate, submit fix - } - } - } - - return { - module, - index, - entry: url.endsWith('.svelte') ? url : url + '?import', - css: [], - js: [], - // in dev we inline all styles to avoid FOUC - styles - }; - }; - }), - routes: manifest_data.routes.map((route) => { - const { pattern, names, types } = parse_route_id(route.id); - - if (route.type === 'page') { - return { - type: 'page', - id: route.id, - pattern, - names, - types, - shadow: route.shadow - ? async () => { - const url = path.resolve(cwd, /** @type {string} */ (route.shadow)); - return await vite.ssrLoadModule(url, { fixStacktrace: false }); - } - : null, - a: route.a.map((id) => (id ? manifest_data.components.indexOf(id) : undefined)), - b: route.b.map((id) => (id ? manifest_data.components.indexOf(id) : undefined)) - }; - } - - return { - type: 'endpoint', - id: route.id, - pattern, - names, - types, - load: async () => { - const url = path.resolve(cwd, route.file); - return await vite.ssrLoadModule(url, { fixStacktrace: false }); - } - }; - }), - matchers: async () => { - /** @type {Record} */ - const matchers = {}; - - for (const key in manifest_data.matchers) { - const file = manifest_data.matchers[key]; - const url = path.resolve(cwd, file); - const module = await vite.ssrLoadModule(url, { fixStacktrace: false }); - - if (module.match) { - matchers[key] = module.match; - } else { - throw new Error(`${file} does not export a \`match\` function`); - } - } - - return matchers; - } - } - }; - } - - /** @param {Error} error */ - function fix_stack_trace(error) { - return error.stack ? vite.ssrRewriteStacktrace(error.stack) : error.stack; - } - - update_manifest(); - - for (const event of ['add', 'unlink']) { - vite.watcher.on(event, (file) => { - if (file.startsWith(kit_config.files.routes + path.sep)) { - update_manifest(); - } - }); - } - - const assets = kit_config.paths.assets ? SVELTE_KIT_ASSETS : kit_config.paths.base; - const asset_server = sirv(kit_config.files.assets, { - dev: true, - etag: true, - maxAge: 0, - extensions: [] - }); - - return () => { - const serve_static_middleware = vite.middlewares.stack.find( - (middleware) => - /** @type {function} */ (middleware.handle).name === 'viteServeStaticMiddleware' - ); - - remove_html_middlewares(vite.middlewares); - - vite.middlewares.use(async (req, res) => { - try { - if (!req.url || !req.method) throw new Error('Incomplete request'); - - const base = `${vite.config.server.https ? 'https' : 'http'}://${ - req.headers[':authority'] || req.headers.host - }`; - - const decoded = decodeURI(new URL(base + req.url).pathname); - - if (decoded.startsWith(assets)) { - const pathname = decoded.slice(assets.length); - const file = svelte_config.kit.files.assets + pathname; - - if (fs.existsSync(file) && !fs.statSync(file).isDirectory()) { - if (has_correct_case(file, svelte_config.kit.files.assets)) { - req.url = encodeURI(pathname); // don't need query/hash - asset_server(req, res); - return; - } - } - } - - 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(svelte_config.kit.files.hooks) - ? await vite.ssrLoadModule(`/${svelte_config.kit.files.hooks}`, { - fixStacktrace: false - }) - : {}; - - const handle = user_hooks.handle || (({ event, resolve }) => resolve(event)); - - /** @type {import('types').Hooks} */ - const hooks = { - getSession: user_hooks.getSession || (() => ({})), - handle, - handleError: - user_hooks.handleError || - (({ /** @type {Error & { frame?: string }} */ error }) => { - console.error(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.'); - } - - // TODO the / prefix will probably fail if outDir is outside the cwd (which - // could be the case in a monorepo setup), but without it these modules - // can get loaded twice via different URLs, which causes failures. Might - // require changes to Vite to fix - const { default: root } = await vite.ssrLoadModule( - `/${posixify( - path.relative(cwd, `${svelte_config.kit.outDir}/generated/root.svelte`) - )}`, - { fixStacktrace: false } - ); - - const paths = await vite.ssrLoadModule( - process.env.BUNDLED - ? `/${posixify(path.relative(cwd, `${svelte_config.kit.outDir}/runtime/paths.js`))}` - : `/@fs${runtime}/paths.js`, - { fixStacktrace: false } - ); - - paths.set_paths({ - base: svelte_config.kit.paths.base, - assets - }); - - let request; - - try { - request = await getRequest(base, req); - } catch (/** @type {any} */ err) { - res.statusCode = err.status || 400; - return res.end(err.reason || 'Invalid request body'); - } - - const template = load_template(cwd, svelte_config); - - const rendered = await respond( - request, - { - csp: svelte_config.kit.csp, - dev: true, - floc: svelte_config.kit.floc, - get_stack: (error) => { - return fix_stack_trace(error); - }, - handle_error: (error, event) => { - hooks.handleError({ - error: new Proxy(error, { - get: (target, property) => { - if (property === 'stack') { - return fix_stack_trace(error); - } - - return Reflect.get(target, property, target); - } - }), - event, - - // TODO remove for 1.0 - // @ts-expect-error - get request() { - throw new Error( - 'request in handleError has been replaced with event. See https://github.com/sveltejs/kit/pull/3384 for details' - ); - } - }); - }, - hooks, - hydrate: svelte_config.kit.browser.hydrate, - manifest, - method_override: svelte_config.kit.methodOverride, - paths: { - base: svelte_config.kit.paths.base, - assets - }, - prefix: '', - prerender: { - default: svelte_config.kit.prerender.default, - enabled: svelte_config.kit.prerender.enabled - }, - read: (file) => fs.readFileSync(path.join(svelte_config.kit.files.assets, file)), - root, - router: svelte_config.kit.browser.router, - template: ({ head, body, assets, nonce }) => { - return ( - template - .replace(/%sveltekit\.assets%/g, assets) - .replace(/%sveltekit\.nonce%/g, nonce) - // head and body must be replaced last, in case someone tries to sneak in %sveltekit.assets% etc - .replace('%sveltekit.head%', () => head) - .replace('%sveltekit.body%', () => body) - ); - }, - template_contains_nonce: template.includes('%sveltekit.nonce%'), - trailing_slash: svelte_config.kit.trailingSlash - }, - { - getClientAddress: () => { - const { remoteAddress } = req.socket; - if (remoteAddress) return remoteAddress; - throw new Error('Could not determine clientAddress'); - } - } - ); - - if (rendered.status === 404) { - // @ts-expect-error - serve_static_middleware.handle(req, res, () => { - setResponse(res, rendered); - }); - } else { - setResponse(res, rendered); - } - } catch (e) { - const error = coalesce_to_error(e); - vite.ssrFixStacktrace(error); - res.statusCode = 500; - res.end(error.stack); - } - }); - }; - } - }; -}; - -/** @param {import('http').ServerResponse} res */ -function not_found(res, message = 'Not found') { - res.statusCode = 404; - res.end(message); -} - -/** - * @param {import('connect').Server} server - */ -function remove_html_middlewares(server) { - const html_middlewares = [ - 'viteIndexHtmlMiddleware', - 'vite404Middleware', - 'viteSpaFallbackMiddleware', - 'viteServeStaticMiddleware' - ]; - 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').ViteDevServer} vite - * @param {import('vite').ModuleNode} node - * @param {Set} deps - */ -async function find_deps(vite, node, deps) { - // since `ssrTransformResult.deps` contains URLs instead of `ModuleNode`s, this process is asynchronous. - // instead of using `await`, we resolve all branches in parallel. - /** @type {Promise[]} */ - const branches = []; - - /** @param {import('vite').ModuleNode} node */ - async function add(node) { - if (!deps.has(node)) { - deps.add(node); - await find_deps(vite, node, deps); - } - } - - /** @param {string} url */ - async function add_by_url(url) { - const node = await vite.moduleGraph.getModuleByUrl(url); - - if (node) { - await add(node); - } - } - - if (node.ssrTransformResult) { - if (node.ssrTransformResult.deps) { - node.ssrTransformResult.deps.forEach((url) => branches.push(add_by_url(url))); - } - } else { - node.importedModules.forEach((node) => branches.push(add(node))); - } - - await Promise.all(branches); -} - -/** - * Determine if a file is being requested with the correct case, - * to ensure consistent behaviour between dev and prod and across - * operating systems. Note that we can't use realpath here, - * because we don't want to follow symlinks - * @param {string} file - * @param {string} assets - * @returns {boolean} - */ -function has_correct_case(file, assets) { - if (file === assets) return true; - - const parent = path.dirname(file); - - if (fs.readdirSync(parent).includes(path.basename(file))) { - return has_correct_case(parent, assets); - } - - return false; -} - -/** - * @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/src/core/preview/index.js b/packages/kit/src/core/preview/index.js deleted file mode 100644 index 4df9d39bbce7..000000000000 --- a/packages/kit/src/core/preview/index.js +++ /dev/null @@ -1,197 +0,0 @@ -import fs from 'fs'; -import { join } from 'path'; -import sirv from 'sirv'; -import { pathToFileURL } from 'url'; -import { getRequest, setResponse } from '../../node/index.js'; -import { installPolyfills } from '../../node/polyfills.js'; -import { SVELTE_KIT_ASSETS } from '../constants.js'; -import { load_config } from '../config/index.js'; - -/** @typedef {import('http').IncomingMessage} Req */ -/** @typedef {import('http').ServerResponse} Res */ -/** @typedef {(req: Req, res: Res, next: () => void) => void} Handler */ - -/** @type {import('types').ValidatedConfig} */ -let config; - -/** @type {boolean} */ -let https; - -/** @type {import('vite').Plugin} */ -export const sveltekit_plugin = { - name: 'vite-plugin-svelte-kit', - async config(vite_config) { - // defaults - vite_config.preview = vite_config.preview || {}; - vite_config.preview.strictPort = vite_config.preview.strictPort ?? true; - - config = await load_config(); - }, - async configResolved(vite_config) { - https = !!vite_config.preview.https; - }, - async configurePreviewServer(vite) { - installPolyfills(); - - const { paths } = config.kit; - const base = paths.base; - const assets = paths.assets ? SVELTE_KIT_ASSETS : paths.base; - - const etag = `"${Date.now()}"`; - - const index_file = join(config.kit.outDir, 'output/server/index.js'); - const manifest_file = join(config.kit.outDir, 'output/server/manifest.js'); - - /** @type {import('types').ServerModule} */ - const { Server, override } = await import(pathToFileURL(index_file).href); - const { manifest } = await import(pathToFileURL(manifest_file).href); - - override({ - paths: { base, assets }, - prerendering: false, - protocol: https ? 'https' : 'http', - read: (file) => fs.readFileSync(join(config.kit.files.assets, file)) - }); - - const server = new Server(manifest); - - return () => { - // files in `static` - vite.middlewares.use(scoped(assets, mutable(config.kit.files.assets))); - - // immutable generated client assets - vite.middlewares.use( - scoped( - assets, - sirv(join(config.kit.outDir, 'output/client'), { - setHeaders: (res, pathname) => { - // only apply to build directory, not e.g. version.json - if (pathname.startsWith(`/${config.kit.appDir}/immutable`)) { - res.setHeader('cache-control', 'public,max-age=31536000,immutable'); - } - } - }) - ) - ); - - vite.middlewares.use((req, res, next) => { - const original_url = /** @type {string} */ (req.url); - const { pathname } = new URL(original_url, 'http://dummy'); - - if (pathname.startsWith(base)) { - next(); - } else { - res.statusCode = 404; - res.end(`Not found (did you mean ${base + pathname}?)`); - } - }); - - // prerendered dependencies - vite.middlewares.use( - scoped(base, mutable(join(config.kit.outDir, 'output/prerendered/dependencies'))) - ); - - // prerendered pages (we can't just use sirv because we need to - // preserve the correct trailingSlash behaviour) - vite.middlewares.use( - scoped(base, (req, res, next) => { - let if_none_match_value = req.headers['if-none-match']; - - if (if_none_match_value?.startsWith('W/"')) { - if_none_match_value = if_none_match_value.substring(2); - } - - if (if_none_match_value === etag) { - res.statusCode = 304; - res.end(); - return; - } - - const { pathname } = new URL(/** @type {string} */ (req.url), 'http://dummy'); - - // only treat this as a page if it doesn't include an extension - if (pathname === '/' || /\/[^./]+\/?$/.test(pathname)) { - const file = join( - config.kit.outDir, - 'output/prerendered/pages' + - pathname + - (pathname.endsWith('/') ? 'index.html' : '.html') - ); - - if (fs.existsSync(file)) { - res.writeHead(200, { - 'content-type': 'text/html', - etag - }); - - fs.createReadStream(file).pipe(res); - return; - } - } - - next(); - }) - ); - - // SSR - vite.middlewares.use(async (req, res) => { - const protocol = https ? 'https' : 'http'; - const host = req.headers['host']; - - let request; - - try { - request = await getRequest(`${protocol}://${host}`, req); - } catch (/** @type {any} */ err) { - res.statusCode = err.status || 400; - return res.end(err.reason || 'Invalid request body'); - } - - setResponse( - res, - await server.respond(request, { - getClientAddress: () => { - const { remoteAddress } = req.socket; - if (remoteAddress) return remoteAddress; - throw new Error('Could not determine clientAddress'); - } - }) - ); - }); - }; - } -}; - -/** - * @param {string} dir - * @returns {Handler} - */ -const mutable = (dir) => - fs.existsSync(dir) - ? sirv(dir, { - etag: true, - maxAge: 0 - }) - : (req, res, next) => next(); - -/** - * @param {string} scope - * @param {Handler} handler - * @returns {Handler} - */ -function scoped(scope, handler) { - if (scope === '') return handler; - - return (req, res, next) => { - if (req.url?.startsWith(scope)) { - const original_url = req.url; - req.url = req.url.slice(scope.length); - handler(req, res, () => { - req.url = original_url; - next(); - }); - } else { - next(); - } - }; -} diff --git a/packages/kit/src/packaging/test/errors/duplicate-export/jsconfig.json b/packages/kit/src/packaging/test/errors/duplicate-export/jsconfig.json index 92575debb461..f29fde21bafc 100644 --- a/packages/kit/src/packaging/test/errors/duplicate-export/jsconfig.json +++ b/packages/kit/src/packaging/test/errors/duplicate-export/jsconfig.json @@ -3,7 +3,7 @@ "checkJs": true, "baseUrl": ".", "paths": { - "$lib/*": ["src/lib/*"] + "$lib/*": ["./src/lib/*"] } }, "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] diff --git a/packages/kit/src/runtime/server/page/load_node.js b/packages/kit/src/runtime/server/page/load_node.js index 4b0c54cc7b7c..b925a2776ad0 100644 --- a/packages/kit/src/runtime/server/page/load_node.js +++ b/packages/kit/src/runtime/server/page/load_node.js @@ -2,9 +2,8 @@ import * as cookie from 'cookie'; import * as set_cookie_parser from 'set-cookie-parser'; import { normalize } from '../../load.js'; import { respond } from '../index.js'; -import { is_root_relative, resolve } from '../../../utils/url.js'; +import { LoadURL, is_root_relative, resolve } from '../../../utils/url.js'; import { create_prerendering_url_proxy } from './utils.js'; -import { LoadURL } from '../../../utils/url.js'; import { is_pojo, lowercase_keys, normalize_request_method } from '../utils.js'; import { coalesce_to_error } from '../../../utils/error.js'; import { domain_matches, path_matches } from './cookie.js'; diff --git a/packages/kit/src/utils/object.js b/packages/kit/src/utils/object.js deleted file mode 100644 index ed4d192bbf46..000000000000 --- a/packages/kit/src/utils/object.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Takes zero or more objects and returns a new object that has all the values - * deeply merged together. None of the original objects will be mutated at any - * level, and the returned object will have no references to the original - * objects at any depth. If there's a conflict the last one wins, except for - * arrays which will be combined. - * @param {...Object} objects - * @returns {[Record, string[]]} a 2-tuple with the merged object, - * and a list of merge conflicts if there were any, in dotted notation - */ -export function deep_merge(...objects) { - const result = {}; - /** @type {string[]} */ - const conflicts = []; - objects.forEach((o) => merge_into(result, o, conflicts)); - return [result, conflicts]; -} - -/** - * normalize kit.vite.resolve.alias as an array - * @param {import('vite').AliasOptions} o - * @returns {import('vite').Alias[]} - */ -export function normalize_alias(o) { - if (Array.isArray(o)) return o; - return Object.entries(o).map(([find, replacement]) => ({ find, replacement })); -} - -/** - * mutates and remove keys from an object when check callback returns true - * @param {Record} o any object - * @param {([key, value]: [string, any]) => boolean} check callback with access - * to the key-value pair and returns a boolean that decides the deletion of key - */ -export function remove_keys(o, check) { - for (const key in o) { - if (!Object.hasOwnProperty.call(o, key)) continue; - if (check([key, o[key]])) delete o[key]; - const nested = typeof o[key] === 'object' && !Array.isArray(o[key]); - if (nested) remove_keys(o[key], check); - } -} - -/** - * Merges b into a, recursively, mutating a. - * @param {Record} a - * @param {Record} b - * @param {string[]} conflicts array to accumulate conflicts in - * @param {string[]} path array of property names representing the current - * location in the tree - */ -function merge_into(a, b, conflicts = [], path = []) { - /** - * Checks for "plain old Javascript object", typically made as an object - * literal. Excludes Arrays and built-in types like Buffer. - * @param {any} x - */ - const is_plain_object = (x) => typeof x === 'object' && x.constructor === Object; - - for (const prop in b) { - // normalize alias objects to array - if (prop === 'alias' && path[path.length - 1] === 'resolve') { - if (a[prop]) a[prop] = normalize_alias(a[prop]); - if (b[prop]) b[prop] = normalize_alias(b[prop]); - } - - if (is_plain_object(b[prop])) { - if (!is_plain_object(a[prop])) { - if (a[prop] !== undefined) { - conflicts.push([...path, prop].join('.')); - } - a[prop] = {}; - } - merge_into(a[prop], b[prop], conflicts, [...path, prop]); - } else if (Array.isArray(b[prop])) { - if (!Array.isArray(a[prop])) { - if (a[prop] !== undefined) { - conflicts.push([...path, prop].join('.')); - } - a[prop] = []; - } - a[prop].push(...b[prop]); - } else { - // Since we're inside a for/in loop which loops over enumerable - // properties only, we want parity here and to check if 'a' has - // enumerable-only property 'prop'. Using 'hasOwnProperty' to - // exclude inherited properties is close enough. It is possible - // that someone uses Object.defineProperty to create a direct, - // non-enumerable property but let's not worry about that. - if (Object.prototype.hasOwnProperty.call(a, prop)) { - conflicts.push([...path, prop].join('.')); - } - a[prop] = b[prop]; - } - } -} diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/vite/build/adapt/builder.js similarity index 96% rename from packages/kit/src/core/adapt/builder.js rename to packages/kit/src/vite/build/adapt/builder.js index 5e828c84aad3..c1f55c344a32 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/vite/build/adapt/builder.js @@ -1,5 +1,5 @@ -import { copy, rimraf, mkdirp } from '../../utils/filesystem.js'; -import { generate_manifest } from '../generate_manifest/index.js'; +import { copy, rimraf, mkdirp } from '../../../utils/filesystem.js'; +import { generate_manifest } from '../../../core/generate_manifest/index.js'; /** * Creates the Builder which is passed to adapters for building the application. diff --git a/packages/kit/src/core/adapt/builder.spec.js b/packages/kit/src/vite/build/adapt/builder.spec.js similarity index 100% rename from packages/kit/src/core/adapt/builder.spec.js rename to packages/kit/src/vite/build/adapt/builder.spec.js diff --git a/packages/kit/src/core/adapt/fixtures/basic/.svelte-kit/output/client/routes/index.js b/packages/kit/src/vite/build/adapt/fixtures/basic/.svelte-kit/output/client/routes/index.js similarity index 100% rename from packages/kit/src/core/adapt/fixtures/basic/.svelte-kit/output/client/routes/index.js rename to packages/kit/src/vite/build/adapt/fixtures/basic/.svelte-kit/output/client/routes/index.js diff --git a/packages/kit/src/core/adapt/fixtures/basic/.svelte-kit/output/server/.well-known/test.js b/packages/kit/src/vite/build/adapt/fixtures/basic/.svelte-kit/output/server/.well-known/test.js similarity index 100% rename from packages/kit/src/core/adapt/fixtures/basic/.svelte-kit/output/server/.well-known/test.js rename to packages/kit/src/vite/build/adapt/fixtures/basic/.svelte-kit/output/server/.well-known/test.js diff --git a/packages/kit/src/core/adapt/fixtures/basic/.svelte-kit/output/server/index.js b/packages/kit/src/vite/build/adapt/fixtures/basic/.svelte-kit/output/server/index.js similarity index 100% rename from packages/kit/src/core/adapt/fixtures/basic/.svelte-kit/output/server/index.js rename to packages/kit/src/vite/build/adapt/fixtures/basic/.svelte-kit/output/server/index.js diff --git a/packages/kit/src/core/adapt/fixtures/basic/static/answer.md b/packages/kit/src/vite/build/adapt/fixtures/basic/static/answer.md similarity index 100% rename from packages/kit/src/core/adapt/fixtures/basic/static/answer.md rename to packages/kit/src/vite/build/adapt/fixtures/basic/static/answer.md diff --git a/packages/kit/src/core/adapt/index.js b/packages/kit/src/vite/build/adapt/index.js similarity index 100% rename from packages/kit/src/core/adapt/index.js rename to packages/kit/src/vite/build/adapt/index.js diff --git a/packages/kit/src/core/build/build_server.js b/packages/kit/src/vite/build/build_server.js similarity index 92% rename from packages/kit/src/core/build/build_server.js rename to packages/kit/src/vite/build/build_server.js index ad5ac851b6c4..f335631e3674 100644 --- a/packages/kit/src/core/build/build_server.js +++ b/packages/kit/src/vite/build/build_server.js @@ -1,10 +1,10 @@ import fs from 'fs'; import path from 'path'; import { mkdirp, posixify } from '../../utils/filesystem.js'; -import { deep_merge } from '../../utils/object.js'; -import { load_template, print_config_conflicts } from '../config/index.js'; -import { get_runtime_path, resolve_entry } from '../utils.js'; -import { create_build, find_deps, get_default_config } from './utils.js'; +import { merge_vite_configs } from '../utils.js'; +import { load_template } from '../../core/config/index.js'; +import { get_runtime_path, resolve_entry } from '../../core/utils.js'; +import { create_build, find_deps, get_default_config, remove_svelte_kit } from './utils.js'; import { s } from '../../utils/misc.js'; /** @@ -104,6 +104,7 @@ export class Server { `; /** + * @param {import('vite').UserConfig} vite_config * @param {{ * cwd: string; * config: import('types').ValidatedConfig @@ -114,7 +115,7 @@ export class Server { * }} options * @param {{ vite_manifest: import('vite').Manifest, assets: import('rollup').OutputAsset[] }} client */ -export async function build_server(options, client) { +export async function build_server(vite_config, options, client) { const { cwd, config, manifest_data, build_dir, output_dir, service_worker_entry_file } = options; let hooks_file = resolve_entry(config.kit.files.hooks); @@ -174,9 +175,6 @@ export async function build_server(options, client) { }) ); - /** @type {import('vite').UserConfig} */ - const vite_config = await config.kit.vite(); - const default_config = { build: { target: 'node14.8' @@ -194,16 +192,13 @@ export async function build_server(options, client) { } }; - // don't warn on overriding defaults - const [modified_vite_config] = deep_merge(default_config, vite_config); - - /** @type {[any, string[]]} */ - const [merged_config, conflicts] = deep_merge( - modified_vite_config, - get_default_config({ ...options, input, ssr: true }) + const merged_config = merge_vite_configs( + default_config, + vite_config, + get_default_config({ config, input, ssr: true, outDir: `${output_dir}/server` }) ); - print_config_conflicts(conflicts, 'kit.vite.', 'build_server'); + remove_svelte_kit(merged_config); process.env.VITE_SVELTEKIT_ADAPTER_NAME = config.kit.adapter?.name; diff --git a/packages/kit/src/core/build/build_service_worker.js b/packages/kit/src/vite/build/build_service_worker.js similarity index 85% rename from packages/kit/src/core/build/build_service_worker.js rename to packages/kit/src/vite/build/build_service_worker.js index f10adf0da023..9a96dbea040d 100644 --- a/packages/kit/src/core/build/build_service_worker.js +++ b/packages/kit/src/vite/build/build_service_worker.js @@ -1,12 +1,12 @@ import fs from 'fs'; import * as vite from 'vite'; import { s } from '../../utils/misc.js'; -import { deep_merge } from '../../utils/object.js'; +import { merge_vite_configs } from '../utils.js'; import { normalize_path } from '../../utils/url.js'; -import { print_config_conflicts } from '../config/index.js'; -import { assets_base } from './utils.js'; +import { assets_base, remove_svelte_kit } from './utils.js'; /** + * @param {import('vite').UserConfig} vite_config * @param {{ * config: import('types').ValidatedConfig; * manifest_data: import('types').ManifestData; @@ -17,6 +17,7 @@ import { assets_base } from './utils.js'; * @param {import('vite').Manifest} client_manifest */ export async function build_service_worker( + vite_config, { config, manifest_data, output_dir, service_worker_entry_file }, prerendered, client_manifest @@ -66,12 +67,11 @@ export async function build_service_worker( .trim() ); - /** @type {[any, string[]]} */ - const [merged_config, conflicts] = deep_merge(await config.kit.vite(), { + const merged_config = merge_vite_configs(vite_config, { base: assets_base(config.kit), build: { lib: { - entry: service_worker_entry_file, + entry: /** @type {string} */ (service_worker_entry_file), name: 'app', formats: ['es'] }, @@ -91,7 +91,7 @@ export async function build_service_worker( } }); - print_config_conflicts(conflicts, 'kit.vite.', 'build_service_worker'); + remove_svelte_kit(merged_config); await vite.build(merged_config); } diff --git a/packages/kit/src/core/build/prerender/crawl.js b/packages/kit/src/vite/build/prerender/crawl.js similarity index 100% rename from packages/kit/src/core/build/prerender/crawl.js rename to packages/kit/src/vite/build/prerender/crawl.js diff --git a/packages/kit/src/core/build/prerender/crawl.spec.js b/packages/kit/src/vite/build/prerender/crawl.spec.js similarity index 100% rename from packages/kit/src/core/build/prerender/crawl.spec.js rename to packages/kit/src/vite/build/prerender/crawl.spec.js diff --git a/packages/kit/src/core/build/prerender/fixtures/basic-href/input.html b/packages/kit/src/vite/build/prerender/fixtures/basic-href/input.html similarity index 100% rename from packages/kit/src/core/build/prerender/fixtures/basic-href/input.html rename to packages/kit/src/vite/build/prerender/fixtures/basic-href/input.html diff --git a/packages/kit/src/core/build/prerender/fixtures/basic-href/output.json b/packages/kit/src/vite/build/prerender/fixtures/basic-href/output.json similarity index 100% rename from packages/kit/src/core/build/prerender/fixtures/basic-href/output.json rename to packages/kit/src/vite/build/prerender/fixtures/basic-href/output.json diff --git a/packages/kit/src/core/build/prerender/fixtures/basic-src/input.html b/packages/kit/src/vite/build/prerender/fixtures/basic-src/input.html similarity index 100% rename from packages/kit/src/core/build/prerender/fixtures/basic-src/input.html rename to packages/kit/src/vite/build/prerender/fixtures/basic-src/input.html diff --git a/packages/kit/src/core/build/prerender/fixtures/basic-src/output.json b/packages/kit/src/vite/build/prerender/fixtures/basic-src/output.json similarity index 100% rename from packages/kit/src/core/build/prerender/fixtures/basic-src/output.json rename to packages/kit/src/vite/build/prerender/fixtures/basic-src/output.json diff --git a/packages/kit/src/core/build/prerender/fixtures/basic-srcset/input.html b/packages/kit/src/vite/build/prerender/fixtures/basic-srcset/input.html similarity index 100% rename from packages/kit/src/core/build/prerender/fixtures/basic-srcset/input.html rename to packages/kit/src/vite/build/prerender/fixtures/basic-srcset/input.html diff --git a/packages/kit/src/core/build/prerender/fixtures/basic-srcset/output.json b/packages/kit/src/vite/build/prerender/fixtures/basic-srcset/output.json similarity index 100% rename from packages/kit/src/core/build/prerender/fixtures/basic-srcset/output.json rename to packages/kit/src/vite/build/prerender/fixtures/basic-srcset/output.json diff --git a/packages/kit/src/core/build/prerender/fixtures/include-rel-external/input.html b/packages/kit/src/vite/build/prerender/fixtures/include-rel-external/input.html similarity index 100% rename from packages/kit/src/core/build/prerender/fixtures/include-rel-external/input.html rename to packages/kit/src/vite/build/prerender/fixtures/include-rel-external/input.html diff --git a/packages/kit/src/core/build/prerender/fixtures/include-rel-external/output.json b/packages/kit/src/vite/build/prerender/fixtures/include-rel-external/output.json similarity index 100% rename from packages/kit/src/core/build/prerender/fixtures/include-rel-external/output.json rename to packages/kit/src/vite/build/prerender/fixtures/include-rel-external/output.json diff --git a/packages/kit/src/core/build/prerender/fixtures/unquoted-attributes/input.html b/packages/kit/src/vite/build/prerender/fixtures/unquoted-attributes/input.html similarity index 100% rename from packages/kit/src/core/build/prerender/fixtures/unquoted-attributes/input.html rename to packages/kit/src/vite/build/prerender/fixtures/unquoted-attributes/input.html diff --git a/packages/kit/src/core/build/prerender/fixtures/unquoted-attributes/output.json b/packages/kit/src/vite/build/prerender/fixtures/unquoted-attributes/output.json similarity index 100% rename from packages/kit/src/core/build/prerender/fixtures/unquoted-attributes/output.json rename to packages/kit/src/vite/build/prerender/fixtures/unquoted-attributes/output.json diff --git a/packages/kit/src/core/build/prerender/prerender.js b/packages/kit/src/vite/build/prerender/prerender.js similarity index 100% rename from packages/kit/src/core/build/prerender/prerender.js rename to packages/kit/src/vite/build/prerender/prerender.js diff --git a/packages/kit/src/core/build/prerender/queue.js b/packages/kit/src/vite/build/prerender/queue.js similarity index 100% rename from packages/kit/src/core/build/prerender/queue.js rename to packages/kit/src/vite/build/prerender/queue.js diff --git a/packages/kit/src/core/build/prerender/queue.spec.js b/packages/kit/src/vite/build/prerender/queue.spec.js similarity index 100% rename from packages/kit/src/core/build/prerender/queue.spec.js rename to packages/kit/src/vite/build/prerender/queue.spec.js diff --git a/packages/kit/src/core/build/utils.js b/packages/kit/src/vite/build/utils.js similarity index 79% rename from packages/kit/src/core/build/utils.js rename to packages/kit/src/vite/build/utils.js index 68f28fdd8755..5ce5e4252530 100644 --- a/packages/kit/src/core/build/utils.js +++ b/packages/kit/src/vite/build/utils.js @@ -1,6 +1,5 @@ -import { svelte } from '@sveltejs/vite-plugin-svelte'; import * as vite from 'vite'; -import { get_aliases } from '../utils.js'; +import { get_aliases } from '../../core/utils.js'; /** * @typedef {import('rollup').RollupOutput} RollupOutput @@ -13,7 +12,9 @@ import { get_aliases } from '../utils.js'; * @param {import('vite').UserConfig} config */ export async function create_build(config) { - const { output } = /** @type {RollupOutput} */ (await vite.build(config)); + const { output } = /** @type {RollupOutput} */ ( + await vite.build({ ...config, configFile: false }) + ); const chunks = output.filter( /** @returns {output is OutputChunk} */ (output) => output.type === 'chunk' @@ -51,21 +52,20 @@ export function find_deps(file, manifest, js, css) { /** * The Vite configuration that we use by default. * @param {{ - * client_out_dir?: string; * config: import('types').ValidatedConfig; * input: Record; - * output_dir: string; * ssr: boolean; + * outDir: string; * }} options * @return {import('vite').UserConfig} */ -export const get_default_config = function ({ client_out_dir, config, input, output_dir, ssr }) { +export const get_default_config = function ({ config, input, ssr, outDir }) { return { base: assets_base(config.kit), build: { cssCodeSplit: true, manifest: true, - outDir: ssr ? `${output_dir}/server` : `${client_out_dir}/immutable`, + outDir, polyfillDynamicImport: false, rollupOptions: { input, @@ -79,16 +79,6 @@ export const get_default_config = function ({ client_out_dir, config, input, out }, ssr }, - plugins: [ - svelte({ - ...config, - compilerOptions: { - ...config.compilerOptions, - hydratable: !!config.kit.browser.hydrate - }, - configFile: false - }) - ], // prevent Vite copying the contents of `config.kit.files.assets`, // if it happens to be 'public' instead of 'static' publicDir: false, @@ -109,3 +99,14 @@ export function assets_base(config) { const { base, assets } = config.paths; return `${assets || base}/${config.appDir}/immutable/`; } + +/** + * @param {import('vite').UserConfig} config + */ +export function remove_svelte_kit(config) { + // TODO i feel like there's a more elegant way to do this + // @ts-expect-error - it can't handle infinite type expansion + config.plugins = (config.plugins || []) + .flat(Infinity) + .filter((plugin) => plugin.name !== 'vite-plugin-svelte-kit'); +} diff --git a/packages/kit/src/vite/dev/index.js b/packages/kit/src/vite/dev/index.js new file mode 100644 index 000000000000..98ea61118503 --- /dev/null +++ b/packages/kit/src/vite/dev/index.js @@ -0,0 +1,458 @@ +import fs from 'fs'; +import colors from 'kleur'; +import path from 'path'; +import sirv from 'sirv'; +import { URL } from 'url'; +import { getRequest, setResponse } from '../../node/index.js'; +import { installPolyfills } from '../../node/polyfills.js'; +import { coalesce_to_error } from '../../utils/error.js'; +import { posixify } from '../../utils/filesystem.js'; +import { parse_route_id } from '../../utils/routing.js'; +import { load_template } from '../../core/config/index.js'; +import { SVELTE_KIT_ASSETS } from '../../core/constants.js'; +import * as sync from '../../core/sync/sync.js'; +import { get_mime_lookup, get_runtime_path, resolve_entry } from '../../core/utils.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 +const style_pattern = /\.(css|less|sass|scss|styl|stylus|pcss|postcss)$/; + +const cwd = process.cwd(); + +/** + * @param {import('vite').ViteDevServer} vite + * @param {import('types').ValidatedConfig} svelte_config + * @return {Promise void>>} + */ +export async function dev(vite, svelte_config) { + installPolyfills(); + + sync.init(svelte_config); + + const runtime = get_runtime_path(svelte_config.kit); + + process.env.VITE_SVELTEKIT_APP_VERSION_POLL_INTERVAL = '0'; + + /** @type {import('types').Respond} */ + const respond = (await import(`${runtime}/server/index.js`)).respond; + + /** @type {import('types').SSRManifest} */ + let manifest; + + function update_manifest() { + const { manifest_data } = sync.update(svelte_config); + + manifest = { + appDir: svelte_config.kit.appDir, + assets: new Set(manifest_data.assets.map((asset) => asset.file)), + mimeTypes: get_mime_lookup(manifest_data), + _: { + entry: { + file: `/@fs${runtime}/client/start.js`, + css: [], + js: [] + }, + nodes: manifest_data.components.map((id, index) => { + return async () => { + const url = id.startsWith('..') ? `/@fs${path.posix.resolve(id)}` : `/${id}`; + + const module = /** @type {import('types').SSRComponent} */ ( + await vite.ssrLoadModule(url, { fixStacktrace: false }) + ); + const node = await vite.moduleGraph.getModuleByUrl(url); + + if (!node) throw new Error(`Could not find node for ${url}`); + + const deps = new Set(); + await find_deps(vite, node, deps); + + /** @type {Record} */ + const styles = {}; + + for (const dep of deps) { + const parsed = new URL(dep.url, 'http://localhost/'); + const query = parsed.searchParams; + + if ( + style_pattern.test(dep.file) || + (query.has('svelte') && query.get('type') === 'style') + ) { + try { + const mod = await vite.ssrLoadModule(dep.url, { fixStacktrace: false }); + styles[dep.url] = mod.default; + } catch { + // this can happen with dynamically imported modules, I think + // because the Vite module graph doesn't distinguish between + // static and dynamic imports? TODO investigate, submit fix + } + } + } + + return { + module, + index, + entry: url.endsWith('.svelte') ? url : url + '?import', + css: [], + js: [], + // in dev we inline all styles to avoid FOUC + styles + }; + }; + }), + routes: manifest_data.routes.map((route) => { + const { pattern, names, types } = parse_route_id(route.id); + + if (route.type === 'page') { + return { + type: 'page', + id: route.id, + pattern, + names, + types, + shadow: route.shadow + ? async () => { + const url = path.resolve(cwd, /** @type {string} */ (route.shadow)); + return await vite.ssrLoadModule(url, { fixStacktrace: false }); + } + : null, + a: route.a.map((id) => (id ? manifest_data.components.indexOf(id) : undefined)), + b: route.b.map((id) => (id ? manifest_data.components.indexOf(id) : undefined)) + }; + } + + return { + type: 'endpoint', + id: route.id, + pattern, + names, + types, + load: async () => { + const url = path.resolve(cwd, route.file); + return await vite.ssrLoadModule(url, { fixStacktrace: false }); + } + }; + }), + matchers: async () => { + /** @type {Record} */ + const matchers = {}; + + for (const key in manifest_data.matchers) { + const file = manifest_data.matchers[key]; + const url = path.resolve(cwd, file); + const module = await vite.ssrLoadModule(url, { fixStacktrace: false }); + + if (module.match) { + matchers[key] = module.match; + } else { + throw new Error(`${file} does not export a \`match\` function`); + } + } + + return matchers; + } + } + }; + } + + /** @param {Error} error */ + function fix_stack_trace(error) { + return error.stack ? vite.ssrRewriteStacktrace(error.stack) : error.stack; + } + + update_manifest(); + + for (const event of ['add', 'unlink']) { + vite.watcher.on(event, (file) => { + if (file.startsWith(svelte_config.kit.files.routes + path.sep)) { + update_manifest(); + } + }); + } + + const assets = svelte_config.kit.paths.assets ? SVELTE_KIT_ASSETS : svelte_config.kit.paths.base; + const asset_server = sirv(svelte_config.kit.files.assets, { + dev: true, + etag: true, + maxAge: 0, + extensions: [] + }); + + return () => { + const serve_static_middleware = vite.middlewares.stack.find( + (middleware) => + /** @type {function} */ (middleware.handle).name === 'viteServeStaticMiddleware' + ); + + remove_html_middlewares(vite.middlewares); + + vite.middlewares.use(async (req, res) => { + try { + if (!req.url || !req.method) throw new Error('Incomplete request'); + + const base = `${vite.config.server.https ? 'https' : 'http'}://${ + req.headers[':authority'] || req.headers.host + }`; + + const decoded = decodeURI(new URL(base + req.url).pathname); + + if (decoded.startsWith(assets)) { + const pathname = decoded.slice(assets.length); + const file = svelte_config.kit.files.assets + pathname; + + if (fs.existsSync(file) && !fs.statSync(file).isDirectory()) { + if (has_correct_case(file, svelte_config.kit.files.assets)) { + req.url = encodeURI(pathname); // don't need query/hash + asset_server(req, res); + return; + } + } + } + + 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(svelte_config.kit.files.hooks) + ? await vite.ssrLoadModule(`/${svelte_config.kit.files.hooks}`, { + fixStacktrace: false + }) + : {}; + + const handle = user_hooks.handle || (({ event, resolve }) => resolve(event)); + + /** @type {import('types').Hooks} */ + const hooks = { + getSession: user_hooks.getSession || (() => ({})), + handle, + handleError: + user_hooks.handleError || + (({ /** @type {Error & { frame?: string }} */ error }) => { + console.error(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.'); + } + + // TODO the / prefix will probably fail if outDir is outside the cwd (which + // could be the case in a monorepo setup), but without it these modules + // can get loaded twice via different URLs, which causes failures. Might + // require changes to Vite to fix + const { default: root } = await vite.ssrLoadModule( + `/${posixify(path.relative(cwd, `${svelte_config.kit.outDir}/generated/root.svelte`))}`, + { fixStacktrace: false } + ); + + const paths = await vite.ssrLoadModule( + process.env.BUNDLED + ? `/${posixify(path.relative(cwd, `${svelte_config.kit.outDir}/runtime/paths.js`))}` + : `/@fs${runtime}/paths.js`, + { fixStacktrace: false } + ); + + paths.set_paths({ + base: svelte_config.kit.paths.base, + assets + }); + + let request; + + try { + request = await getRequest(base, req); + } catch (/** @type {any} */ err) { + res.statusCode = err.status || 400; + return res.end(err.reason || 'Invalid request body'); + } + + const template = load_template(cwd, svelte_config); + + const rendered = await respond( + request, + { + csp: svelte_config.kit.csp, + dev: true, + floc: svelte_config.kit.floc, + get_stack: (error) => { + return fix_stack_trace(error); + }, + handle_error: (error, event) => { + hooks.handleError({ + error: new Proxy(error, { + get: (target, property) => { + if (property === 'stack') { + return fix_stack_trace(error); + } + + return Reflect.get(target, property, target); + } + }), + event, + + // TODO remove for 1.0 + // @ts-expect-error + get request() { + throw new Error( + 'request in handleError has been replaced with event. See https://github.com/sveltejs/kit/pull/3384 for details' + ); + } + }); + }, + hooks, + hydrate: svelte_config.kit.browser.hydrate, + manifest, + method_override: svelte_config.kit.methodOverride, + paths: { + base: svelte_config.kit.paths.base, + assets + }, + prefix: '', + prerender: { + default: svelte_config.kit.prerender.default, + enabled: svelte_config.kit.prerender.enabled + }, + read: (file) => fs.readFileSync(path.join(svelte_config.kit.files.assets, file)), + root, + router: svelte_config.kit.browser.router, + template: ({ head, body, assets, nonce }) => { + return ( + template + .replace(/%sveltekit\.assets%/g, assets) + .replace(/%sveltekit\.nonce%/g, nonce) + // head and body must be replaced last, in case someone tries to sneak in %sveltekit.assets% etc + .replace('%sveltekit.head%', () => head) + .replace('%sveltekit.body%', () => body) + ); + }, + template_contains_nonce: template.includes('%sveltekit.nonce%'), + trailing_slash: svelte_config.kit.trailingSlash + }, + { + getClientAddress: () => { + const { remoteAddress } = req.socket; + if (remoteAddress) return remoteAddress; + throw new Error('Could not determine clientAddress'); + } + } + ); + + if (rendered.status === 404) { + // @ts-expect-error + serve_static_middleware.handle(req, res, () => { + setResponse(res, rendered); + }); + } else { + setResponse(res, rendered); + } + } catch (e) { + const error = coalesce_to_error(e); + vite.ssrFixStacktrace(error); + res.statusCode = 500; + res.end(error.stack); + } + }); + }; +} + +/** @param {import('http').ServerResponse} res */ +function not_found(res, message = 'Not found') { + res.statusCode = 404; + res.end(message); +} + +/** + * @param {import('connect').Server} server + */ +function remove_html_middlewares(server) { + const html_middlewares = [ + 'viteIndexHtmlMiddleware', + 'vite404Middleware', + 'viteSpaFallbackMiddleware', + 'viteServeStaticMiddleware' + ]; + 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').ViteDevServer} vite + * @param {import('vite').ModuleNode} node + * @param {Set} deps + */ +async function find_deps(vite, node, deps) { + // since `ssrTransformResult.deps` contains URLs instead of `ModuleNode`s, this process is asynchronous. + // instead of using `await`, we resolve all branches in parallel. + /** @type {Promise[]} */ + const branches = []; + + /** @param {import('vite').ModuleNode} node */ + async function add(node) { + if (!deps.has(node)) { + deps.add(node); + await find_deps(vite, node, deps); + } + } + + /** @param {string} url */ + async function add_by_url(url) { + const node = await vite.moduleGraph.getModuleByUrl(url); + + if (node) { + await add(node); + } + } + + if (node.ssrTransformResult) { + if (node.ssrTransformResult.deps) { + node.ssrTransformResult.deps.forEach((url) => branches.push(add_by_url(url))); + } + } else { + node.importedModules.forEach((node) => branches.push(add(node))); + } + + await Promise.all(branches); +} + +/** + * Determine if a file is being requested with the correct case, + * to ensure consistent behaviour between dev and prod and across + * operating systems. Note that we can't use realpath here, + * because we don't want to follow symlinks + * @param {string} file + * @param {string} assets + * @returns {boolean} + */ +function has_correct_case(file, assets) { + if (file === assets) return true; + + const parent = path.dirname(file); + + if (fs.readdirSync(parent).includes(path.basename(file))) { + return has_correct_case(parent, assets); + } + + return false; +} diff --git a/packages/kit/src/vite/index.js b/packages/kit/src/vite/index.js new file mode 100644 index 000000000000..8c96da350cf0 --- /dev/null +++ b/packages/kit/src/vite/index.js @@ -0,0 +1,350 @@ +import fs from 'fs'; +import path from 'path'; +import colors from 'kleur'; +import { svelte } from '@sveltejs/vite-plugin-svelte'; +import { searchForWorkspaceRoot } from 'vite'; +import { mkdirp, posixify, rimraf } from '../utils/filesystem.js'; +import * as sync from '../core/sync/sync.js'; +import { build_server } from './build/build_server.js'; +import { build_service_worker } from './build/build_service_worker.js'; +import { prerender } from './build/prerender/prerender.js'; +import { load_config } from '../core/config/index.js'; +import { dev } from './dev/index.js'; +import { generate_manifest } from '../core/generate_manifest/index.js'; +import { get_aliases, get_runtime_path, logger, resolve_entry } from '../core/utils.js'; +import { find_deps, get_default_config } from './build/utils.js'; +import { preview } from './preview/index.js'; + +const cwd = process.cwd(); + +const enforced_config = { + base: true, + build: { + cssCodeSplit: true, + emptyOutDir: true, + lib: { + entry: true, + name: true, + formats: true + }, + manifest: true, + outDir: true, + polyfillDynamicImport: true, + rollupOptions: { + input: true, + output: { + format: true, + entryFileNames: true, + chunkFileNames: true, + assetFileNames: true + }, + preserveEntrySignatures: true + }, + ssr: true + }, + publicDir: true, + resolve: { + alias: { + $app: true, + $lib: true, + '$service-worker': true + } + }, + root: true +}; + +/** + * @return {import('vite').Plugin[]} + */ +export function sveltekit() { + return [...svelte(), kit()]; +} + +/** + * @return {import('vite').Plugin} + */ +function kit() { + /** @type {import('types').ValidatedConfig} */ + let svelte_config; + + /** @type {import('vite').UserConfig} */ + let vite_user_config; + + /** @type {import('types').ManifestData} */ + let manifest_data; + + /** @type {{ + * build_dir: string; + * output_dir: string; + * client_out_dir: string; + * }} */ + let paths; + + return { + name: 'vite-plugin-svelte-kit', + + async config(config, { command }) { + const overridden = find_overridden_config(config, enforced_config); + + if (overridden.length > 0) { + console.log( + colors.bold().red('The following Vite config options will be overridden by SvelteKit:') + ); + console.log(overridden.map((key) => ` - ${key}`).join('\n')); + } + + vite_user_config = config; + svelte_config = await load_config(); + + paths = { + build_dir: `${svelte_config.kit.outDir}/build`, + output_dir: `${svelte_config.kit.outDir}/output`, + client_out_dir: `${svelte_config.kit.outDir}/output/client/${svelte_config.kit.appDir}` + }; + + if (command === 'build') { + process.env.VITE_SVELTEKIT_APP_VERSION = svelte_config.kit.version.name; + process.env.VITE_SVELTEKIT_APP_VERSION_FILE = `${svelte_config.kit.appDir}/version.json`; + process.env.VITE_SVELTEKIT_APP_VERSION_POLL_INTERVAL = `${svelte_config.kit.version.pollInterval}`; + + manifest_data = sync.all(svelte_config).manifest_data; + + /** @type {Record} */ + const input = { + start: `${get_runtime_path(svelte_config.kit)}/client/start.js` + }; + + // This step is optional — Vite/Rollup will create the necessary chunks + // for everything regardless — but it means that entry chunks reflect + // their location in the source code, which is helpful for debugging + manifest_data.components.forEach((file) => { + const resolved = path.resolve(cwd, file); + const relative = path.relative(svelte_config.kit.files.routes, resolved); + + const name = relative.startsWith('..') + ? path.basename(file) + : posixify(path.join('pages', relative)); + input[name] = resolved; + }); + + return get_default_config({ + config: svelte_config, + input, + ssr: false, + outDir: `${paths.client_out_dir}/immutable` + }); + } + + // dev and preview config can be shared + return { + base: '/', + build: { + rollupOptions: { + // Vite dependency crawler needs an explicit JS entry point + // eventhough server otherwise works without it + input: `${get_runtime_path(svelte_config.kit)}/client/start.js` + } + }, + preview: { + port: 3000, + strictPort: true + }, + resolve: { + alias: get_aliases(svelte_config.kit) + }, + root: cwd, + server: { + fs: { + allow: [ + ...new Set([ + svelte_config.kit.files.lib, + svelte_config.kit.files.routes, + svelte_config.kit.outDir, + path.resolve(cwd, 'src'), + path.resolve(cwd, 'node_modules'), + path.resolve(searchForWorkspaceRoot(cwd), 'node_modules') + ]) + ] + }, + port: 3000, + strictPort: true, + watch: { + ignored: [ + // Ignore all siblings of config.kit.outDir/generated + `${posixify(svelte_config.kit.outDir)}/!(generated)` + ] + } + }, + spa: false + }; + }, + + buildStart() { + rimraf(paths.build_dir); + mkdirp(paths.build_dir); + + rimraf(paths.output_dir); + mkdirp(paths.output_dir); + }, + + async writeBundle(_options, bundle) { + const log = logger({ verbose: !!process.env.VERBOSE }); + + /** @type {import('rollup').OutputChunk[]} */ + const chunks = []; + /** @type {import('rollup').OutputAsset[]} */ + const assets = []; + for (const key of Object.keys(bundle)) { + // collect asset and output chunks + if (bundle[key].type === 'asset') { + assets.push(/** @type {import('rollup').OutputAsset} */ (bundle[key])); + } else { + chunks.push(/** @type {import('rollup').OutputChunk} */ (bundle[key])); + } + } + + /** @type {import('vite').Manifest} */ + const vite_manifest = JSON.parse( + fs.readFileSync(`${paths.client_out_dir}/immutable/manifest.json`, 'utf-8') + ); + + const entry = posixify( + path.relative(cwd, `${get_runtime_path(svelte_config.kit)}/client/start.js`) + ); + const entry_js = new Set(); + const entry_css = new Set(); + find_deps(entry, vite_manifest, entry_js, entry_css); + + fs.writeFileSync( + `${paths.client_out_dir}/version.json`, + JSON.stringify({ version: process.env.VITE_SVELTEKIT_APP_VERSION }) + ); + const client = { + assets, + chunks, + entry: { + file: vite_manifest[entry].file, + js: Array.from(entry_js), + css: Array.from(entry_css) + }, + vite_manifest + }; + log.info(`Client build completed. Wrote ${chunks.length} chunks and ${assets.length} assets`); + + const options = { + cwd, + config: svelte_config, + build_dir: paths.build_dir, // TODO just pass `paths` + manifest_data, + output_dir: paths.output_dir, + service_worker_entry_file: resolve_entry(svelte_config.kit.files.serviceWorker) + }; + + log.info('Building server'); + + const server = await build_server(vite_user_config, options, client); + + process.env.SVELTEKIT_SERVER_BUILD_COMPLETED = 'true'; + + /** @type {import('types').BuildData} */ + const build_data = { + app_dir: svelte_config.kit.appDir, + manifest_data, + service_worker: options.service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable? + client, + server + }; + + const manifest = `export const manifest = ${generate_manifest({ + build_data, + relative_path: '.', + routes: manifest_data.routes + })};\n`; + fs.writeFileSync(`${paths.output_dir}/server/manifest.js`, manifest); + + const static_files = manifest_data.assets.map((asset) => posixify(asset.file)); + + const files = new Set([ + ...static_files, + ...chunks.map((chunk) => `${svelte_config.kit.appDir}/immutable/${chunk.fileName}`), + ...assets.map((chunk) => `${svelte_config.kit.appDir}/immutable/${chunk.fileName}`) + ]); + + // TODO is this right? + static_files.forEach((file) => { + if (file.endsWith('/index.html')) { + files.add(file.slice(0, -11)); + } + }); + + log.info('Prerendering'); + + const prerendered = await prerender({ + config: svelte_config.kit, + entries: manifest_data.routes + .map((route) => (route.type === 'page' ? route.path : '')) + .filter(Boolean), + files, + log + }); + + if (options.service_worker_entry_file) { + if (svelte_config.kit.paths.assets) { + throw new Error('Cannot use service worker alongside config.kit.paths.assets'); + } + + log.info('Building service worker'); + + await build_service_worker(vite_user_config, options, prerendered, client.vite_manifest); + } + + console.log( + `\nRun ${colors.bold().cyan('npm run preview')} to preview your production build locally.` + ); + + if (svelte_config.kit.adapter) { + const { adapt } = await import('./build/adapt/index.js'); + await adapt(svelte_config, build_data, prerendered, { log }); + + // this is necessary to close any open db connections, etc + process.exit(0); + } + + console.log(colors.bold().yellow('\nNo adapter specified')); + + // prettier-ignore + console.log( + `See ${colors.bold().cyan('https://kit.svelte.dev/docs/adapters')} to learn how to configure your app to run on the platform of your choosing` + ); + }, + + async configureServer(vite) { + return await dev(vite, svelte_config); + }, + + configurePreviewServer(vite) { + const protocol = vite_user_config.preview?.https ? 'https' : 'http'; + return preview(vite, svelte_config, protocol); + } + }; +} + +/** + * @param {Record} config + * @param {Record} enforced_config + * @param {string} path + * @param {string[]} out + */ +function find_overridden_config(config, enforced_config, path = '', out = []) { + for (const key in enforced_config) { + if (key in config) { + if (enforced_config[key] === true) { + out.push(path + key); + } else { + find_overridden_config(config[key], enforced_config[key], path + key + '.', out); + } + } + } + + return out; +} diff --git a/packages/kit/src/vite/preview/index.js b/packages/kit/src/vite/preview/index.js new file mode 100644 index 000000000000..078e6146ed3a --- /dev/null +++ b/packages/kit/src/vite/preview/index.js @@ -0,0 +1,183 @@ +import fs from 'fs'; +import { join } from 'path'; +import sirv from 'sirv'; +import { pathToFileURL } from 'url'; +import { getRequest, setResponse } from '../../node/index.js'; +import { installPolyfills } from '../../node/polyfills.js'; +import { SVELTE_KIT_ASSETS } from '../../core/constants.js'; + +/** @typedef {import('http').IncomingMessage} Req */ +/** @typedef {import('http').ServerResponse} Res */ +/** @typedef {(req: Req, res: Res, next: () => void) => void} Handler */ + +/** + * @param {{ + * middlewares: import('connect').Server; + * httpServer: import('http').Server; + * }} vite + * @param {import('types').ValidatedConfig} config + * @param {'http' | 'https'} protocol + */ +export async function preview(vite, config, protocol) { + installPolyfills(); + + const { paths } = config.kit; + const base = paths.base; + const assets = paths.assets ? SVELTE_KIT_ASSETS : paths.base; + + const etag = `"${Date.now()}"`; + + const index_file = join(config.kit.outDir, 'output/server/index.js'); + const manifest_file = join(config.kit.outDir, 'output/server/manifest.js'); + + /** @type {import('types').ServerModule} */ + const { Server, override } = await import(pathToFileURL(index_file).href); + const { manifest } = await import(pathToFileURL(manifest_file).href); + + override({ + paths: { base, assets }, + prerendering: false, + protocol, + read: (file) => fs.readFileSync(join(config.kit.files.assets, file)) + }); + + const server = new Server(manifest); + + return () => { + // files in `static` + vite.middlewares.use(scoped(assets, mutable(config.kit.files.assets))); + + // immutable generated client assets + vite.middlewares.use( + scoped( + assets, + sirv(join(config.kit.outDir, 'output/client'), { + setHeaders: (res, pathname) => { + // only apply to build directory, not e.g. version.json + if (pathname.startsWith(`/${config.kit.appDir}/immutable`)) { + res.setHeader('cache-control', 'public,max-age=31536000,immutable'); + } + } + }) + ) + ); + + vite.middlewares.use((req, res, next) => { + const original_url = /** @type {string} */ (req.url); + const { pathname } = new URL(original_url, 'http://dummy'); + + if (pathname.startsWith(base)) { + next(); + } else { + res.statusCode = 404; + res.end(`Not found (did you mean ${base + pathname}?)`); + } + }); + + // prerendered dependencies + vite.middlewares.use( + scoped(base, mutable(join(config.kit.outDir, 'output/prerendered/dependencies'))) + ); + + // prerendered pages (we can't just use sirv because we need to + // preserve the correct trailingSlash behaviour) + vite.middlewares.use( + scoped(base, (req, res, next) => { + let if_none_match_value = req.headers['if-none-match']; + + if (if_none_match_value?.startsWith('W/"')) { + if_none_match_value = if_none_match_value.substring(2); + } + + if (if_none_match_value === etag) { + res.statusCode = 304; + res.end(); + return; + } + + const { pathname } = new URL(/** @type {string} */ (req.url), 'http://dummy'); + + // only treat this as a page if it doesn't include an extension + if (pathname === '/' || /\/[^./]+\/?$/.test(pathname)) { + const file = join( + config.kit.outDir, + 'output/prerendered/pages' + + pathname + + (pathname.endsWith('/') ? 'index.html' : '.html') + ); + + if (fs.existsSync(file)) { + res.writeHead(200, { + 'content-type': 'text/html', + etag + }); + + fs.createReadStream(file).pipe(res); + return; + } + } + + next(); + }) + ); + + // SSR + vite.middlewares.use(async (req, res) => { + const host = req.headers['host']; + + let request; + + try { + request = await getRequest(`${protocol}://${host}`, req); + } catch (/** @type {any} */ err) { + res.statusCode = err.status || 400; + return res.end(err.reason || 'Invalid request body'); + } + + setResponse( + res, + await server.respond(request, { + getClientAddress: () => { + const { remoteAddress } = req.socket; + if (remoteAddress) return remoteAddress; + throw new Error('Could not determine clientAddress'); + } + }) + ); + }); + }; +} + +/** + * @param {string} dir + * @returns {Handler} + */ +const mutable = (dir) => + fs.existsSync(dir) + ? sirv(dir, { + etag: true, + maxAge: 0 + }) + : (req, res, next) => next(); + +/** + * @param {string} scope + * @param {Handler} handler + * @returns {Handler} + */ +function scoped(scope, handler) { + if (scope === '') return handler; + + return (req, res, next) => { + if (req.url?.startsWith(scope)) { + const original_url = req.url; + req.url = req.url.slice(scope.length); + handler(req, res, () => { + req.url = original_url; + next(); + }); + } else { + next(); + } + }; +} diff --git a/packages/kit/src/vite/utils.js b/packages/kit/src/vite/utils.js new file mode 100644 index 000000000000..5fc731ebb96a --- /dev/null +++ b/packages/kit/src/vite/utils.js @@ -0,0 +1,71 @@ +/** + * @param {...import('vite').UserConfig} configs + * @returns {import('vite').UserConfig} + */ +export function merge_vite_configs(...configs) { + return deep_merge( + ...configs.map((config) => ({ + ...config, + resolve: { + ...config.resolve, + alias: normalize_alias(config.resolve?.alias || {}) + } + })) + ); +} + +/** + * Takes zero or more objects and returns a new object that has all the values + * deeply merged together. None of the original objects will be mutated at any + * level, and the returned object will have no references to the original + * objects at any depth. If there's a conflict the last one wins, except for + * arrays which will be combined. + * @param {...Object} objects + * @returns {Record} the merged object + */ +export function deep_merge(...objects) { + const result = {}; + /** @type {string[]} */ + objects.forEach((o) => merge_into(result, o)); + return result; +} + +/** + * normalize kit.vite.resolve.alias as an array + * @param {import('vite').AliasOptions} o + * @returns {import('vite').Alias[]} + */ +function normalize_alias(o) { + if (Array.isArray(o)) return o; + return Object.entries(o).map(([find, replacement]) => ({ find, replacement })); +} + +/** + * Merges b into a, recursively, mutating a. + * @param {Record} a + * @param {Record} b + */ +function merge_into(a, b) { + /** + * Checks for "plain old Javascript object", typically made as an object + * literal. Excludes Arrays and built-in types like Buffer. + * @param {any} x + */ + const is_plain_object = (x) => typeof x === 'object' && x.constructor === Object; + + for (const prop in b) { + if (is_plain_object(b[prop])) { + if (!is_plain_object(a[prop])) { + a[prop] = {}; + } + merge_into(a[prop], b[prop]); + } else if (Array.isArray(b[prop])) { + if (!Array.isArray(a[prop])) { + a[prop] = []; + } + a[prop].push(...b[prop]); + } else { + a[prop] = b[prop]; + } + } +} diff --git a/packages/kit/src/utils/object.spec.js b/packages/kit/src/vite/utils.spec.js similarity index 82% rename from packages/kit/src/utils/object.spec.js rename to packages/kit/src/vite/utils.spec.js index e7e33932db82..46146aa83b94 100644 --- a/packages/kit/src/utils/object.spec.js +++ b/packages/kit/src/vite/utils.spec.js @@ -1,9 +1,9 @@ import { test } from 'uvu'; import * as assert from 'uvu/assert'; -import { deep_merge } from './object.js'; +import { deep_merge, merge_vite_configs } from './utils.js'; test('basic test no conflicts', async () => { - const [merged, conflicts] = deep_merge( + const merged = deep_merge( { version: 1, animalSounds: { @@ -17,6 +17,7 @@ test('basic test no conflicts', async () => { locale: 'en_US' } ); + assert.equal(merged, { version: 1, locale: 'en_US', @@ -25,11 +26,10 @@ test('basic test no conflicts', async () => { duck: 'quack' } }); - assert.equal(conflicts, []); }); test('three way merge no conflicts', async () => { - const [merged, conflicts] = deep_merge( + const merged = deep_merge( { animalSounds: { cow: 'moo' @@ -59,11 +59,10 @@ test('three way merge no conflicts', async () => { } } }); - assert.equal(conflicts, []); }); test('merge with conflicts', async () => { - const [merged, conflicts] = deep_merge( + const merged = deep_merge( { person: { firstName: 'John', @@ -90,11 +89,10 @@ test('merge with conflicts', async () => { address: '123 Main St, Seattle, WA' } }); - assert.equal(conflicts, ['person.address']); }); test('merge with arrays', async () => { - const [merged] = deep_merge( + const merged = deep_merge( { paths: ['/foo', '/bar'] }, @@ -108,7 +106,7 @@ test('merge with arrays', async () => { }); test('empty', async () => { - const [merged] = deep_merge(); + const merged = deep_merge(); assert.equal(merged, {}); }); @@ -135,7 +133,7 @@ test('mutability safety', () => { const snapshot1 = JSON.stringify(input1); const snapshot2 = JSON.stringify(input2); - const [merged] = deep_merge(input1, input2); + const merged = deep_merge(input1, input2); // Mess with the result merged.person.middleInitial = 'Z'; @@ -148,7 +146,7 @@ test('mutability safety', () => { }); test('merge buffer', () => { - const [merged, conflicts] = deep_merge( + const merged = deep_merge( { x: Buffer.from('foo', 'utf-8') }, @@ -157,11 +155,10 @@ test('merge buffer', () => { } ); assert.equal(Object.keys(merged), ['x', 'y']); - assert.equal(conflicts.length, 0); }); test('merge including toString', () => { - const [merged, conflicts] = deep_merge( + const merged = deep_merge( { toString: () => '', constructor: () => '' @@ -170,12 +167,11 @@ test('merge including toString', () => { y: 12345 } ); - assert.equal(conflicts.length, 0); assert.equal(Object.keys(merged), ['toString', 'constructor', 'y']); }); test('merge resolve.alias', () => { - const [merged, conflicts] = deep_merge( + const merged = merge_vite_configs( { resolve: { alias: [{ find: /foo/, replacement: 'bar' }] @@ -189,7 +185,6 @@ test('merge resolve.alias', () => { } } ); - assert.equal(conflicts.length, 0); assert.equal(merged, { resolve: { alias: [ diff --git a/packages/kit/test/apps/amp/svelte.config.js b/packages/kit/test/apps/amp/svelte.config.js index 9c3a2801c70a..9482e8972a83 100644 --- a/packages/kit/test/apps/amp/svelte.config.js +++ b/packages/kit/test/apps/amp/svelte.config.js @@ -1,4 +1,4 @@ -import path from 'path'; +import * as path from 'path'; /** @type {import('@sveltejs/kit').Config} */ const config = { diff --git a/packages/kit/test/apps/amp/tsconfig.json b/packages/kit/test/apps/amp/tsconfig.json index e969d35a695f..858415b26691 100644 --- a/packages/kit/test/apps/amp/tsconfig.json +++ b/packages/kit/test/apps/amp/tsconfig.json @@ -5,8 +5,8 @@ "noEmit": true, "paths": { "@sveltejs/kit": ["../../../types"], - "$lib": ["src/lib"], - "$lib/*": ["src/lib/*"], + "$lib": ["./src/lib"], + "$lib/*": ["./src/lib/*"], "types": ["../../../types/internal"] } }, diff --git a/packages/kit/test/apps/basics/svelte.config.js b/packages/kit/test/apps/basics/svelte.config.js index 68b1411a1fdc..a45140535e6d 100644 --- a/packages/kit/test/apps/basics/svelte.config.js +++ b/packages/kit/test/apps/basics/svelte.config.js @@ -1,4 +1,4 @@ -import path from 'path'; +import * as path from 'path'; /** @type {import('@sveltejs/kit').Config} */ const config = { diff --git a/packages/kit/test/apps/basics/tsconfig.json b/packages/kit/test/apps/basics/tsconfig.json index ead111316f65..a1e1e2da2142 100644 --- a/packages/kit/test/apps/basics/tsconfig.json +++ b/packages/kit/test/apps/basics/tsconfig.json @@ -6,10 +6,11 @@ "noEmit": true, "paths": { "@sveltejs/kit": ["../../../types"], - "$lib": ["src/lib"], - "$lib/*": ["src/lib/*"], + "$lib": ["./src/lib"], + "$lib/*": ["./src/lib/*"], "types": ["../../../types/internal"] - } + }, + "resolveJsonModule": true }, "extends": "./.svelte-kit/tsconfig.json" } diff --git a/packages/kit/test/apps/options-2/svelte.config.js b/packages/kit/test/apps/options-2/svelte.config.js index 8160cdf0cb8f..5d46d5f640fd 100644 --- a/packages/kit/test/apps/options-2/svelte.config.js +++ b/packages/kit/test/apps/options-2/svelte.config.js @@ -1,4 +1,4 @@ -import path from 'path'; +import * as path from 'path'; /** @type {import('@sveltejs/kit').Config} */ const config = { diff --git a/packages/kit/test/apps/options-2/tsconfig.json b/packages/kit/test/apps/options-2/tsconfig.json index e969d35a695f..858415b26691 100644 --- a/packages/kit/test/apps/options-2/tsconfig.json +++ b/packages/kit/test/apps/options-2/tsconfig.json @@ -5,8 +5,8 @@ "noEmit": true, "paths": { "@sveltejs/kit": ["../../../types"], - "$lib": ["src/lib"], - "$lib/*": ["src/lib/*"], + "$lib": ["./src/lib"], + "$lib/*": ["./src/lib/*"], "types": ["../../../types/internal"] } }, diff --git a/packages/kit/test/apps/options/svelte.config.js b/packages/kit/test/apps/options/svelte.config.js index 569ef37d3af6..dac417144b74 100644 --- a/packages/kit/test/apps/options/svelte.config.js +++ b/packages/kit/test/apps/options/svelte.config.js @@ -1,4 +1,4 @@ -import path from 'path'; +import * as path from 'path'; /** @type {import('@sveltejs/kit').Config} */ const config = { diff --git a/packages/kit/test/prerendering/basics/svelte.config.js b/packages/kit/test/prerendering/basics/svelte.config.js index bad007a33f62..64275704c962 100644 --- a/packages/kit/test/prerendering/basics/svelte.config.js +++ b/packages/kit/test/prerendering/basics/svelte.config.js @@ -1,4 +1,4 @@ -import path from 'path'; +import * as path from 'path'; import adapter from '../../../../adapter-static/index.js'; /** @type {import('@sveltejs/kit').Config} */ diff --git a/packages/kit/test/prerendering/basics/test/test.js b/packages/kit/test/prerendering/basics/test/test.js index 3e60ceff72dd..b7dd394892d2 100644 --- a/packages/kit/test/prerendering/basics/test/test.js +++ b/packages/kit/test/prerendering/basics/test/test.js @@ -1,4 +1,4 @@ -import fs from 'fs'; +import * as fs from 'fs'; import { fileURLToPath } from 'url'; import { test } from 'uvu'; import * as assert from 'uvu/assert'; diff --git a/packages/kit/test/prerendering/basics/tsconfig.json b/packages/kit/test/prerendering/basics/tsconfig.json index e969d35a695f..858415b26691 100644 --- a/packages/kit/test/prerendering/basics/tsconfig.json +++ b/packages/kit/test/prerendering/basics/tsconfig.json @@ -5,8 +5,8 @@ "noEmit": true, "paths": { "@sveltejs/kit": ["../../../types"], - "$lib": ["src/lib"], - "$lib/*": ["src/lib/*"], + "$lib": ["./src/lib"], + "$lib/*": ["./src/lib/*"], "types": ["../../../types/internal"] } }, diff --git a/packages/kit/test/prerendering/disabled/svelte.config.js b/packages/kit/test/prerendering/disabled/svelte.config.js index fd6c9adbfeda..692691aac8eb 100644 --- a/packages/kit/test/prerendering/disabled/svelte.config.js +++ b/packages/kit/test/prerendering/disabled/svelte.config.js @@ -1,4 +1,4 @@ -import path from 'path'; +import * as path from 'path'; import adapter from '../../../../adapter-static/index.js'; /** @type {import('@sveltejs/kit').Config} */ diff --git a/packages/kit/test/prerendering/options/svelte.config.js b/packages/kit/test/prerendering/options/svelte.config.js index dcd424c079a7..0e0bf7e3e6a2 100644 --- a/packages/kit/test/prerendering/options/svelte.config.js +++ b/packages/kit/test/prerendering/options/svelte.config.js @@ -1,4 +1,4 @@ -import path from 'path'; +import * as path from 'path'; import adapter from '../../../../adapter-static/index.js'; /** @type {import('@sveltejs/kit').Config} */ diff --git a/packages/kit/test/prerendering/options/test/test.js b/packages/kit/test/prerendering/options/test/test.js index 7b14ff16c6aa..7f104b4db614 100644 --- a/packages/kit/test/prerendering/options/test/test.js +++ b/packages/kit/test/prerendering/options/test/test.js @@ -1,4 +1,4 @@ -import fs from 'fs'; +import * as fs from 'fs'; import { fileURLToPath } from 'url'; import { test } from 'uvu'; import * as assert from 'uvu/assert'; diff --git a/packages/kit/test/prerendering/options/tsconfig.json b/packages/kit/test/prerendering/options/tsconfig.json index c4c541dfa353..91c5f17c7590 100644 --- a/packages/kit/test/prerendering/options/tsconfig.json +++ b/packages/kit/test/prerendering/options/tsconfig.json @@ -4,8 +4,8 @@ "checkJs": true, "noEmit": true, "paths": { - "$lib": ["src/lib"], - "$lib/*": ["src/lib/*"], + "$lib": ["./src/lib"], + "$lib/*": ["./src/lib/*"], "types": ["../../../types/internal"] } }, diff --git a/packages/kit/test/prerendering/paths-base/svelte.config.js b/packages/kit/test/prerendering/paths-base/svelte.config.js index 4e1535a7684e..a489865dc085 100644 --- a/packages/kit/test/prerendering/paths-base/svelte.config.js +++ b/packages/kit/test/prerendering/paths-base/svelte.config.js @@ -1,4 +1,4 @@ -import path from 'path'; +import * as path from 'path'; import adapter from '../../../../adapter-static/index.js'; /** @type {import('@sveltejs/kit').Config} */ diff --git a/packages/kit/test/prerendering/paths-base/test/test.js b/packages/kit/test/prerendering/paths-base/test/test.js index 19ad704892a9..d2a47907a178 100644 --- a/packages/kit/test/prerendering/paths-base/test/test.js +++ b/packages/kit/test/prerendering/paths-base/test/test.js @@ -1,4 +1,4 @@ -import fs from 'fs'; +import * as fs from 'fs'; import { fileURLToPath } from 'url'; import { test } from 'uvu'; import * as assert from 'uvu/assert'; diff --git a/packages/kit/test/prerendering/paths-base/tsconfig.json b/packages/kit/test/prerendering/paths-base/tsconfig.json index e969d35a695f..858415b26691 100644 --- a/packages/kit/test/prerendering/paths-base/tsconfig.json +++ b/packages/kit/test/prerendering/paths-base/tsconfig.json @@ -5,8 +5,8 @@ "noEmit": true, "paths": { "@sveltejs/kit": ["../../../types"], - "$lib": ["src/lib"], - "$lib/*": ["src/lib/*"], + "$lib": ["./src/lib"], + "$lib/*": ["./src/lib/*"], "types": ["../../../types/internal"] } }, diff --git a/turbo.json b/turbo.json index afcb8ae67afd..1316f3c80926 100644 --- a/turbo.json +++ b/turbo.json @@ -24,7 +24,7 @@ }, "check": { "dependsOn": ["sync"], - "inputs": ["src/**", "types/**", ".svelte-kit/types/**", ".custom-out-dir/types/**"], + "inputs": ["src/**", "types/**", ".svelte-kit/types/**", ".custom-out-dir/types/**", ".svelte-kit/tsconfig.json"], "outputs": [] }, "format": {},