diff --git a/.changeset/nasty-impalas-wear.md b/.changeset/nasty-impalas-wear.md new file mode 100644 index 000000000000..14fef49885f0 --- /dev/null +++ b/.changeset/nasty-impalas-wear.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: resolve paths using the Vite config `root` option instead of `process.cwd()` to better support monorepo configurations such as Vitest workspaces diff --git a/package.json b/package.json index 7426bc24bb67..00bd89d7b612 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "@svitejs/changesets-changelog-github-compact": "catalog:", "eslint": "catalog:", "prettier": "catalog:", - "prettier-plugin-svelte": "catalog:" + "prettier-plugin-svelte": "catalog:", + "vitest": "catalog:" }, "packageManager": "pnpm@10.30.3+sha512.c961d1e0a2d8e354ecaa5166b822516668b7f44cb5bd95122d590dd81922f606f5473b6d23ec4a5be05e7fcd18e8488d47d978bbe981872f1145d06e9a740017", "engines": { diff --git a/packages/adapter-auto/vitest.config.js b/packages/adapter-auto/vitest.config.js new file mode 100644 index 000000000000..a15b3a470a6d --- /dev/null +++ b/packages/adapter-auto/vitest.config.js @@ -0,0 +1,5 @@ +// we need this file to prevent Vitest from resolving a Vitest config from another directory + +import { defineConfig } from 'vitest/config'; + +export default defineConfig({}); diff --git a/packages/adapter-cloudflare/tsconfig.json b/packages/adapter-cloudflare/tsconfig.json index 6b387d40f5f3..8e8b455d2fd2 100644 --- a/packages/adapter-cloudflare/tsconfig.json +++ b/packages/adapter-cloudflare/tsconfig.json @@ -20,6 +20,7 @@ "utils.js", "utils.spec.js", "rolldown.config.js", + "vitest.config.js", "test/utils.js", "internal.d.ts", "src/worker.js" diff --git a/packages/adapter-cloudflare/vitest.config.js b/packages/adapter-cloudflare/vitest.config.js new file mode 100644 index 000000000000..a15b3a470a6d --- /dev/null +++ b/packages/adapter-cloudflare/vitest.config.js @@ -0,0 +1,5 @@ +// we need this file to prevent Vitest from resolving a Vitest config from another directory + +import { defineConfig } from 'vitest/config'; + +export default defineConfig({}); diff --git a/packages/adapter-netlify/vitest.config.js b/packages/adapter-netlify/vitest.config.js new file mode 100644 index 000000000000..a15b3a470a6d --- /dev/null +++ b/packages/adapter-netlify/vitest.config.js @@ -0,0 +1,5 @@ +// we need this file to prevent Vitest from resolving a Vitest config from another directory + +import { defineConfig } from 'vitest/config'; + +export default defineConfig({}); diff --git a/packages/adapter-node/tsconfig.json b/packages/adapter-node/tsconfig.json index f97ac86855b3..e075f510dcb2 100644 --- a/packages/adapter-node/tsconfig.json +++ b/packages/adapter-node/tsconfig.json @@ -17,6 +17,7 @@ "include": [ "index.js", "rolldown.config.js", + "vitest.config.js", "src/**/*.js", "tests/**/*.js", "tests/**/*.ts", diff --git a/packages/adapter-node/vitest.config.js b/packages/adapter-node/vitest.config.js new file mode 100644 index 000000000000..a15b3a470a6d --- /dev/null +++ b/packages/adapter-node/vitest.config.js @@ -0,0 +1,5 @@ +// we need this file to prevent Vitest from resolving a Vitest config from another directory + +import { defineConfig } from 'vitest/config'; + +export default defineConfig({}); diff --git a/packages/adapter-vercel/vitest.config.js b/packages/adapter-vercel/vitest.config.js new file mode 100644 index 000000000000..a15b3a470a6d --- /dev/null +++ b/packages/adapter-vercel/vitest.config.js @@ -0,0 +1,5 @@ +// we need this file to prevent Vitest from resolving a Vitest config from another directory + +import { defineConfig } from 'vitest/config'; + +export default defineConfig({}); diff --git a/packages/enhanced-img/tsconfig.json b/packages/enhanced-img/tsconfig.json index 0ff94d9e3f38..9dd4dc4fd688 100644 --- a/packages/enhanced-img/tsconfig.json +++ b/packages/enhanced-img/tsconfig.json @@ -15,5 +15,5 @@ "noUnusedLocals": true, "noUnusedParameters": true }, - "include": ["src/**/*", "types/**/*", "test/**/*"] + "include": ["src/**/*", "types/**/*", "test/**/*", "vitest.config.js"] } diff --git a/packages/enhanced-img/vitest.config.js b/packages/enhanced-img/vitest.config.js new file mode 100644 index 000000000000..a15b3a470a6d --- /dev/null +++ b/packages/enhanced-img/vitest.config.js @@ -0,0 +1,5 @@ +// we need this file to prevent Vitest from resolving a Vitest config from another directory + +import { defineConfig } from 'vitest/config'; + +export default defineConfig({}); diff --git a/packages/kit/scripts/generate-version.js b/packages/kit/scripts/generate-version.js index ecf715c9a15c..1ab7960f55ab 100644 --- a/packages/kit/scripts/generate-version.js +++ b/packages/kit/scripts/generate-version.js @@ -1,8 +1,11 @@ import fs from 'node:fs'; +import path from 'node:path'; -const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8')); +const pkg = JSON.parse( + fs.readFileSync(path.join(import.meta.dirname, '..', 'package.json'), 'utf-8') +); fs.writeFileSync( - './src/version.js', + path.join(import.meta.dirname, '..', 'src', 'version.js'), `// generated during release, do not modify\n\n/** @type {string} */\nexport const VERSION = '${pkg.version}';\n` ); diff --git a/packages/kit/src/cli.js b/packages/kit/src/cli.js index f1efe75e8bb5..4b810ae9f697 100755 --- a/packages/kit/src/cli.js +++ b/packages/kit/src/cli.js @@ -81,7 +81,7 @@ if (command === 'sync') { } try { - const config = await load_config(); + const config = await load_config({ cwd: process.cwd() }); const sync = await import('./core/sync/sync.js'); sync.all_types(config, values.mode); } catch (error) { diff --git a/packages/kit/src/core/adapt/builder.js b/packages/kit/src/core/adapt/builder.js index 52114110c0f8..0c064eed4975 100644 --- a/packages/kit/src/core/adapt/builder.js +++ b/packages/kit/src/core/adapt/builder.js @@ -147,7 +147,8 @@ export function create_builder({ prerendered: [], relative_path: relativePath, routes: Array.from(filtered), - remotes + remotes, + root: vite_config.root }) }); } @@ -157,7 +158,8 @@ export function create_builder({ findServerAssets(route_data) { return find_server_assets( build_data, - route_data.map((route) => /** @type {import('types').RouteData} */ (lookup.get(route))) + route_data.map((route) => /** @type {import('types').RouteData} */ (lookup.get(route))), + vite_config.root ); }, @@ -167,7 +169,8 @@ export function create_builder({ const fallback = await generate_fallback({ manifest_path, - env: { ...env.private, ...env.public } + env: { ...env.private, ...env.public }, + root: vite_config.root }); if (existsSync(dest)) { @@ -197,7 +200,8 @@ export function create_builder({ routes: subset ? subset.map((route) => /** @type {import('types').RouteData} */ (lookup.get(route))) : route_data.filter((route) => prerender_map.get(route.id) !== true), - remotes + remotes, + root: vite_config.root }); }, diff --git a/packages/kit/src/core/config/index.js b/packages/kit/src/core/config/index.js index ae35a708132b..e3f0e0798b88 100644 --- a/packages/kit/src/core/config/index.js +++ b/packages/kit/src/core/config/index.js @@ -1,6 +1,5 @@ import fs from 'node:fs'; import path from 'node:path'; -import process from 'node:process'; import * as url from 'node:url'; import options from './options.js'; @@ -58,10 +57,10 @@ export function load_error_page(config) { /** * Loads and validates Svelte config file - * @param {{ cwd?: string }} options + * @param {{ cwd: string }} options * @returns {Promise} */ -export async function load_config({ cwd = process.cwd() } = {}) { +export async function load_config({ cwd }) { const config_files = ['js', 'ts'] .map((ext) => path.join(cwd, `svelte.config.${ext}`)) .filter((f) => fs.existsSync(f)); @@ -93,11 +92,13 @@ export async function load_config({ cwd = process.cwd() } = {}) { /** * @param {import('@sveltejs/kit').Config} config + * @param {{ cwd: string }} options * @returns {import('types').ValidatedConfig} */ -function process_config(config, { cwd = process.cwd() } = {}) { +export function process_config(config, { cwd }) { const validated = validate_config(config); + validated.kit.env.dir = path.resolve(cwd, validated.kit.env.dir); validated.kit.outDir = path.resolve(cwd, validated.kit.outDir); for (const key in validated.kit.files) { diff --git a/packages/kit/src/core/env.js b/packages/kit/src/core/env.js index 455eb0f44b68..fcc8ec66779f 100644 --- a/packages/kit/src/core/env.js +++ b/packages/kit/src/core/env.js @@ -1,6 +1,6 @@ import { GENERATED_COMMENT } from '../constants.js'; import { dedent } from './sync/utils.js'; -import { runtime_base } from './utils.js'; +import { get_runtime_base } from './utils.js'; /** * @typedef {'public' | 'private'} EnvType @@ -32,15 +32,16 @@ export function create_static_module(id, env) { /** * @param {EnvType} type * @param {Record | undefined} dev_values If in a development mode, values to pre-populate the module with. + * @param {string} root */ -export function create_dynamic_module(type, dev_values) { +export function create_dynamic_module(type, dev_values, root) { if (dev_values) { const keys = Object.entries(dev_values).map( ([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}` ); return `export const env = {\n${keys.join(',\n')}\n}`; } - return `export { ${type}_env as env } from '${runtime_base}/shared-server.js';`; + return `export { ${type}_env as env } from '${get_runtime_base(root)}/shared-server.js';`; } /** diff --git a/packages/kit/src/core/generate_manifest/find_server_assets.js b/packages/kit/src/core/generate_manifest/find_server_assets.js index 044db8419107..393941919b54 100644 --- a/packages/kit/src/core/generate_manifest/find_server_assets.js +++ b/packages/kit/src/core/generate_manifest/find_server_assets.js @@ -4,8 +4,9 @@ import { find_deps } from '../../exports/vite/build/utils.js'; * Finds all the assets that are imported by server files associated with `routes` * @param {import('types').BuildData} build_data * @param {import('types').RouteData[]} routes + * @param {string} root */ -export function find_server_assets(build_data, routes) { +export function find_server_assets(build_data, routes, root) { /** * All nodes actually used in the routes definition (prerendered routes are omitted). * Root layout/error is always included as they are needed for 404 and root errors. @@ -19,7 +20,7 @@ export function find_server_assets(build_data, routes) { /** @param {string} id */ function add_assets(id) { if (id in build_data.server_manifest) { - const deps = find_deps(build_data.server_manifest, id, false); + const deps = find_deps(build_data.server_manifest, id, false, root); for (const asset of deps.assets) { server_assets.add(asset); } diff --git a/packages/kit/src/core/generate_manifest/index.js b/packages/kit/src/core/generate_manifest/index.js index 6b2445aa2107..4bbc78509bdf 100644 --- a/packages/kit/src/core/generate_manifest/index.js +++ b/packages/kit/src/core/generate_manifest/index.js @@ -20,9 +20,17 @@ import { uneval } from 'devalue'; * relative_path: string; * routes: import('types').RouteData[]; * remotes: RemoteChunk[]; + * root: string; * }} opts */ -export function generate_manifest({ build_data, prerendered, relative_path, routes, remotes }) { +export function generate_manifest({ + build_data, + prerendered, + relative_path, + routes, + remotes, + root +}) { /** * @type {Map} The new index of each node in the filtered nodes array */ @@ -34,7 +42,7 @@ export function generate_manifest({ build_data, prerendered, relative_path, rout */ const used_nodes = new Set([0, 1]); - const server_assets = find_server_assets(build_data, routes); + const server_assets = find_server_assets(build_data, routes, root); for (const route of routes) { if (route.page) { @@ -119,7 +127,7 @@ export function generate_manifest({ build_data, prerendered, relative_path, rout pattern: ${route.pattern}, params: ${s(route.params)}, page: ${route.page ? `{ layouts: ${get_nodes(route.page.layouts)}, errors: ${get_nodes(route.page.errors)}, leaf: ${reindexed.get(route.page.leaf)} }` : 'null'}, - endpoint: ${route.endpoint ? loader(join_relative(relative_path, resolve_symlinks(build_data.server_manifest, route.endpoint.file).chunk.file)) : 'null'} + endpoint: ${route.endpoint ? loader(join_relative(relative_path, resolve_symlinks(build_data.server_manifest, route.endpoint.file, root).chunk.file)) : 'null'} } `; }).filter(Boolean).join(',\n')} diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js index 76936b253e24..184478d94b4e 100644 --- a/packages/kit/src/core/postbuild/analyse.js +++ b/packages/kit/src/core/postbuild/analyse.js @@ -25,6 +25,7 @@ export default forked(import.meta.url, analyse); * out: string; * output_config: import('types').RecursiveRequired; * remotes: RemoteChunk[]; + * root: string; * }} opts */ async function analyse({ @@ -36,13 +37,14 @@ async function analyse({ env, out, output_config, - remotes + remotes, + root }) { /** @type {import('@sveltejs/kit').SSRManifest} */ const manifest = (await import(pathToFileURL(manifest_path).href)).manifest; /** @type {import('types').ValidatedKitConfig} */ - const config = (await load_config()).kit; + const config = (await load_config({ cwd: root })).kit; const server_root = join(config.outDir, 'output'); @@ -63,7 +65,17 @@ async function analyse({ internal.set_read_implementation((file) => createReadableStream(`${server_root}/server/${file}`)); // first, build server nodes without the client manifest so we can analyse it - build_server_nodes(out, config, manifest_data, server_manifest, null, null, null, output_config); + build_server_nodes( + out, + config, + manifest_data, + server_manifest, + null, + null, + null, + output_config, + root + ); /** @type {import('types').ServerMetadata} */ const metadata = { diff --git a/packages/kit/src/core/postbuild/fallback.js b/packages/kit/src/core/postbuild/fallback.js index ef0d0530955f..66fa0c6379e7 100644 --- a/packages/kit/src/core/postbuild/fallback.js +++ b/packages/kit/src/core/postbuild/fallback.js @@ -9,12 +9,13 @@ export default forked(import.meta.url, generate_fallback); /** * @param {{ * manifest_path: string; - * env: Record + * env: Record; + * root: string; * }} opts */ -async function generate_fallback({ manifest_path, env }) { +async function generate_fallback({ manifest_path, env, root }) { /** @type {import('types').ValidatedKitConfig} */ - const config = (await load_config()).kit; + const config = (await load_config({ cwd: root })).kit; const server_root = join(config.outDir, 'output'); diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index 6acf8e47dff4..f8d8603bdb32 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -31,10 +31,11 @@ const SPECIAL_HASHLINKS = new Set(['', 'top']); * manifest_path: string; * metadata: import('types').ServerMetadata; * verbose: boolean; - * env: Record + * env: Record; + * root: string; * }} opts */ -async function prerender({ hash, out, manifest_path, metadata, verbose, env }) { +async function prerender({ hash, out, manifest_path, metadata, verbose, env, root }) { /** @type {import('@sveltejs/kit').SSRManifest} */ const manifest = (await import(pathToFileURL(manifest_path).href)).manifest; @@ -99,12 +100,13 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) { const prerendered_routes = new Set(); /** @type {import('types').ValidatedKitConfig} */ - const config = (await load_config()).kit; + const config = (await load_config({ cwd: root })).kit; if (hash) { const fallback = await generate_fallback({ manifest_path, - env + env, + root }); const file = output_filename('/', true); diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js index 27dc4f8cba5d..d7ffe012d6bd 100644 --- a/packages/kit/src/core/sync/create_manifest_data/index.js +++ b/packages/kit/src/core/sync/create_manifest_data/index.js @@ -1,7 +1,6 @@ import { lookup } from 'mrmime'; import fs from 'node:fs'; import path from 'node:path'; -import process from 'node:process'; import { styleText } from 'node:util'; import { posixify, resolve_entry } from '../../../utils/filesystem.js'; import { parse_route_id } from '../../../utils/routing.js'; @@ -17,14 +16,14 @@ import { * @param {{ * config: import('types').ValidatedConfig; * fallback?: string; - * cwd?: string; + * cwd: string; * }} opts * @returns {import('types').ManifestData} */ export default function create_manifest_data({ config, fallback = `${runtime_directory}/components`, - cwd = process.cwd() + cwd }) { const assets = create_assets(config); const hooks = create_hooks(config, cwd); @@ -114,8 +113,8 @@ function create_matchers(config, cwd) { } /** - * @param {import('types').ValidatedConfig} config * @param {string} cwd + * @param {import('types').ValidatedConfig} config * @param {string} fallback */ function create_routes_and_nodes(cwd, config, fallback) { @@ -418,7 +417,7 @@ function create_routes_and_nodes(cwd, config, fallback) { const indexes = new Map(nodes.map((node, i) => [node, i])); - const node_analyser = create_node_analyser(); + const node_analyser = create_node_analyser(cwd); for (const route of routes) { if (!route.leaf) continue; @@ -470,7 +469,7 @@ function create_routes_and_nodes(cwd, config, fallback) { for (const route of routes) { if (route.endpoint) { - route.endpoint.page_options = get_page_options(route.endpoint.file); + route.endpoint.page_options = get_page_options(route.endpoint.file, cwd); } } diff --git a/packages/kit/src/core/sync/create_manifest_data/index.spec.js b/packages/kit/src/core/sync/create_manifest_data/index.spec.js index 09f732bed8f9..6a7a1b1b1c4c 100644 --- a/packages/kit/src/core/sync/create_manifest_data/index.spec.js +++ b/packages/kit/src/core/sync/create_manifest_data/index.spec.js @@ -1,12 +1,11 @@ import fs from 'node:fs'; import path from 'node:path'; -import { fileURLToPath } from 'node:url'; import { assert, expect, test } from 'vitest'; import create_manifest_data from './index.js'; import { sort_routes } from './sort.js'; import { validate_config } from '../../config/index.js'; -const cwd = fileURLToPath(new URL('./test', import.meta.url)); +const cwd = path.join(import.meta.dirname, 'test'); /** * @param {string} dir @@ -87,7 +86,7 @@ test('creates routes', () => { { id: '/blog.json', pattern: '/^/blog.json/?$/', - endpoint: { file: 'samples/basic/blog.json/+server.js', page_options: null } + endpoint: { file: 'samples/basic/blog.json/+server.js', page_options: {} } }, { id: '/blog', @@ -99,7 +98,7 @@ test('creates routes', () => { pattern: '/^/blog/([^/]+?).json/?$/', endpoint: { file: 'samples/basic/blog/[slug].json/+server.ts', - page_options: null + page_options: {} } }, { @@ -310,7 +309,7 @@ test('allows rest parameters inside segments', () => { pattern: '/^/([^]*?).json/?$/', endpoint: { file: 'samples/rest-prefix-suffix/[...rest].json/+server.js', - page_options: null + page_options: {} } } ]); @@ -348,7 +347,7 @@ test('optional parameters', () => { { id: '/[[foo]]bar', pattern: '/^/([^/]*)?bar/?$/', - endpoint: { file: 'samples/optional/[[foo]]bar/+server.js', page_options: null } + endpoint: { file: 'samples/optional/[[foo]]bar/+server.js', page_options: {} } }, { id: '/nested', pattern: '/^/nested/?$/' }, { @@ -481,7 +480,7 @@ test('allows multiple slugs', () => { pattern: '/^/([^/]+?).([^/]+?)/?$/', endpoint: { file: 'samples/multiple-slugs/[file].[ext]/+server.js', - page_options: null + page_options: {} } } ]); @@ -506,7 +505,7 @@ test('ignores things that look like lockfiles', () => { pattern: '/^/foo/?$/', endpoint: { file: 'samples/lockfiles/foo/+server.js', - page_options: null + page_options: {} } } ]); @@ -542,7 +541,7 @@ test('works with custom extensions', () => { pattern: '/^/blog.json/?$/', endpoint: { file: 'samples/custom-extension/blog.json/+server.js', - page_options: null + page_options: {} } }, { @@ -555,7 +554,7 @@ test('works with custom extensions', () => { pattern: '/^/blog/([^/]+?).json/?$/', endpoint: { file: 'samples/custom-extension/blog/[slug].json/+server.js', - page_options: null + page_options: {} } }, { diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js index d8643e989128..bdcddc338a76 100644 --- a/packages/kit/src/core/sync/sync.js +++ b/packages/kit/src/core/sync/sync.js @@ -1,4 +1,5 @@ import path from 'node:path'; +import process from 'node:process'; import create_manifest_data from './create_manifest_data/index.js'; import { write_client_manifest } from './write_client_manifest.js'; import { write_root } from './write_root.js'; @@ -16,25 +17,27 @@ import { * Initialize SvelteKit's generated files that only depend on the config and mode. * @param {import('types').ValidatedConfig} config * @param {string} mode + * @param {string} root The project root directory */ -export function init(config, mode) { - write_tsconfig(config.kit); +export function init(config, mode, root) { + write_tsconfig(config.kit, root); write_ambient(config.kit, mode); } /** * Update SvelteKit's generated files * @param {import('types').ValidatedConfig} config + * @param {string} root The project root directory */ -export function create(config) { - const manifest_data = create_manifest_data({ config }); +export function create(config, root) { + const manifest_data = create_manifest_data({ config, cwd: root }); const output = path.join(config.kit.outDir, 'generated'); write_client_manifest(config.kit, manifest_data, `${output}/client`); - write_server(config, output); + write_server(config, output, root); write_root(manifest_data, output); - write_all_types(config, manifest_data); + write_all_types(config, manifest_data, root); write_non_ambient(config.kit, manifest_data); return { manifest_data }; @@ -47,9 +50,10 @@ export function create(config) { * @param {import('types').ValidatedConfig} config * @param {import('types').ManifestData} manifest_data * @param {string} file + * @param {string} root The project root directory */ -export function update(config, manifest_data, file) { - const node_analyser = create_node_analyser(); +export function update(config, manifest_data, file, root) { + const node_analyser = create_node_analyser(root); for (const node of manifest_data.nodes) { node.page_options = node_analyser.get_page_options(node); @@ -57,11 +61,11 @@ export function update(config, manifest_data, file) { for (const route of manifest_data.routes) { if (route.endpoint) { - route.endpoint.page_options = get_page_options(route.endpoint.file); + route.endpoint.page_options = get_page_options(route.endpoint.file, root); } } - write_types(config, manifest_data, file); + write_types(config, manifest_data, file, root); write_non_ambient(config.kit, manifest_data); } @@ -69,10 +73,11 @@ export function update(config, manifest_data, file) { * Run sync.init and sync.create in series, returning the result from sync.create. * @param {import('types').ValidatedConfig} config * @param {string} mode The Vite mode + * @param {string} root The project root directory */ -export function all(config, mode) { - init(config, mode); - return create(config); +export function all(config, mode, root) { + init(config, mode, root); + return create(config, root); } /** @@ -81,16 +86,18 @@ export function all(config, mode) { * @param {string} mode The Vite mode */ export function all_types(config, mode) { - init(config, mode); - const manifest_data = create_manifest_data({ config }); - write_all_types(config, manifest_data); + const cwd = process.cwd(); + init(config, mode, cwd); + const manifest_data = create_manifest_data({ config, cwd }); + write_all_types(config, manifest_data, cwd); write_non_ambient(config.kit, manifest_data); } /** * Regenerate __SERVER__/internal.js in response to src/{app.html,error.html,service-worker.js} changing * @param {import('types').ValidatedConfig} config + * @param {string} root The project root directory */ -export function server(config) { - write_server(config, path.join(config.kit.outDir, 'generated')); +export function server(config, root) { + write_server(config, path.join(config.kit.outDir, 'generated'), root); } diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js index 5ef57288b1f6..c062a262d971 100644 --- a/packages/kit/src/core/sync/write_server.js +++ b/packages/kit/src/core/sync/write_server.js @@ -1,5 +1,4 @@ import path from 'node:path'; -import process from 'node:process'; import { styleText } from 'node:util'; import { hash } from '../../utils/hash.js'; import { posixify, resolve_entry } from '../../utils/filesystem.js'; @@ -101,8 +100,9 @@ export { set_assets, set_building, set_manifest, set_prerendering, set_private_e * Write server configuration to disk * @param {import('types').ValidatedConfig} config * @param {string} output + * @param {string} root The project root directory */ -export function write_server(config, output) { +export function write_server(config, output, root) { const server_hooks_file = resolve_entry(config.kit.files.hooks.server); const universal_hooks_file = resolve_entry(config.kit.files.hooks.universal); @@ -133,7 +133,7 @@ export function write_server(config, output) { has_service_worker: config.kit.serviceWorker.register && !!resolve_entry(config.kit.files.serviceWorker), runtime_directory: relative(runtime_directory), - template: load_template(process.cwd(), config), + template: load_template(root, config), error_page: load_error_page(config) }) ); diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js index 9782950bd2a3..9c038bde2b25 100644 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ b/packages/kit/src/core/sync/write_tsconfig.js @@ -1,6 +1,5 @@ import fs from 'node:fs'; import path from 'node:path'; -import process from 'node:process'; import { styleText } from 'node:util'; import { posixify } from '../../utils/filesystem.js'; import { write_if_changed } from './utils.js'; @@ -17,10 +16,11 @@ function maybe_file(cwd, file) { } /** + * @param {string} cwd * @param {string} file */ -function project_relative(file) { - return posixify(path.relative('.', file)); +function project_relative(cwd, file) { + return posixify(path.relative(cwd, file)); } /** @@ -37,21 +37,23 @@ function remove_trailing_slashstar(file) { /** * Generates the tsconfig that the user's tsconfig inherits from. * @param {import('types').ValidatedKitConfig} kit + * @param {string} cwd */ -export function write_tsconfig(kit, cwd = process.cwd()) { +export function write_tsconfig(kit, cwd) { const out = path.join(kit.outDir, 'tsconfig.json'); const user_config = load_user_tsconfig(cwd); if (user_config) validate_user_config(cwd, out, user_config); - write_if_changed(out, JSON.stringify(get_tsconfig(kit), null, '\t')); + write_if_changed(out, JSON.stringify(get_tsconfig(kit, cwd), null, '\t')); } /** * Generates the tsconfig that the user's tsconfig inherits from. * @param {import('types').ValidatedKitConfig} kit + * @param {string} cwd */ -export function get_tsconfig(kit) { +export function get_tsconfig(kit, cwd) { /** @param {string} file */ const config_relative = (file) => posixify(path.relative(kit.outDir, file)); @@ -75,11 +77,11 @@ export function get_tsconfig(kit) { // Test folder is a special case - we advocate putting tests in a top-level test folder // and it's not configurable (should we make it?) - const test_folder = project_relative('test'); + const test_folder = project_relative(cwd, 'test'); include.add(config_relative(`${test_folder}/**/*.js`)); include.add(config_relative(`${test_folder}/**/*.ts`)); include.add(config_relative(`${test_folder}/**/*.svelte`)); - const tests_folder = project_relative('tests'); + const tests_folder = project_relative(cwd, 'tests'); include.add(config_relative(`${tests_folder}/**/*.js`)); include.add(config_relative(`${tests_folder}/**/*.ts`)); include.add(config_relative(`${tests_folder}/**/*.svelte`)); @@ -102,7 +104,7 @@ export function get_tsconfig(kit) { compilerOptions: { // generated options paths: { - ...get_tsconfig_paths(kit), + ...get_tsconfig_paths(kit, cwd), '$app/types': ['./types/index.d.ts'] }, rootDirs: [config_relative('.'), './types'], @@ -176,7 +178,7 @@ function validate_user_config(cwd, out, config) { ); } } else { - let relative = posixify(path.relative('.', out)); + let relative = posixify(path.relative(cwd, out)); if (!relative.startsWith('./')) relative = './' + relative; console.warn( @@ -199,8 +201,9 @@ const value_regex = /^(.*?)((\/\*)|(\.\w+))?$/; * Related to vite alias creation. * * @param {import('types').ValidatedKitConfig} config + * @param {string} cwd */ -function get_tsconfig_paths(config) { +function get_tsconfig_paths(config, cwd) { /** @param {string} file */ const config_relative = (file) => { let relative_path = path.relative(config.outDir, file); @@ -211,8 +214,8 @@ function get_tsconfig_paths(config) { }; const alias = { ...config.alias }; - if (fs.existsSync(project_relative(config.files.lib))) { - alias['$lib'] = project_relative(config.files.lib); + if (fs.existsSync(project_relative(cwd, config.files.lib))) { + alias['$lib'] = project_relative(cwd, config.files.lib); } /** @type {Record} */ diff --git a/packages/kit/src/core/sync/write_tsconfig.spec.js b/packages/kit/src/core/sync/write_tsconfig.spec.js index 90cec2d223b1..47e03dac5273 100644 --- a/packages/kit/src/core/sync/write_tsconfig.spec.js +++ b/packages/kit/src/core/sync/write_tsconfig.spec.js @@ -15,7 +15,7 @@ test('Creates tsconfig path aliases from kit.alias', () => { } }); - const { compilerOptions } = get_tsconfig(kit); + const { compilerOptions } = get_tsconfig(kit, '.'); // $lib isn't part of the outcome because there's a "path exists" // check in the implementation @@ -42,7 +42,7 @@ test('Allows generated tsconfig to be mutated', () => { } }); - const config = get_tsconfig(kit); + const config = get_tsconfig(kit, '.'); // @ts-expect-error assert.equal(config.extends, 'some/other/tsconfig.json'); @@ -60,7 +60,7 @@ test('Allows generated tsconfig to be replaced', () => { } }); - const config = get_tsconfig(kit); + const config = get_tsconfig(kit, '.'); // @ts-expect-error assert.equal(config.extends, 'some/other/tsconfig.json'); @@ -75,7 +75,7 @@ test('Creates tsconfig include from kit.files', () => { } }); - const { include } = get_tsconfig(kit); + const { include } = get_tsconfig(kit, '.'); expect(include).toEqual([ 'ambient.d.ts', diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js index a7f48109548d..95f493810a52 100644 --- a/packages/kit/src/core/sync/write_types/index.js +++ b/packages/kit/src/core/sync/write_types/index.js @@ -1,6 +1,5 @@ import fs from 'node:fs'; import path from 'node:path'; -import process from 'node:process'; import MagicString from 'magic-string'; import { posixify, rimraf, walk } from '../../../utils/filesystem.js'; import { compact } from '../../../utils/array.js'; @@ -25,21 +24,20 @@ const is_whitespace = (/** @type {string} */ char) => /\s/.test(char); * @typedef {Map} RoutesMap */ -const cwd = process.cwd(); - /** * Creates types for the whole manifest * @param {import('types').ValidatedConfig} config * @param {import('types').ManifestData} manifest_data + * @param {string} root The project root directory */ -export function write_all_types(config, manifest_data) { +export function write_all_types(config, manifest_data, root) { if (!ts) return; const types_dir = `${config.kit.outDir}/types`; // empty out files that no longer need to exist const routes_dir = remove_relative_parent_traversals( - posixify(path.relative('.', config.kit.files.routes)) + posixify(path.relative(root, config.kit.files.routes)) ); const expected_directories = new Set( manifest_data.routes.map((route) => path.join(routes_dir, route.id)) @@ -109,7 +107,7 @@ export function write_all_types(config, manifest_data) { const source_last_updated = Math.max( // ctimeMs includes move operations whereas mtimeMs does not - ...input_files.map((file) => fs.statSync(file).ctimeMs) + ...input_files.map((file) => fs.statSync(path.resolve(root, file)).ctimeMs) ); const types_last_updated = Math.max(...output_files.map((file) => file.updated)); @@ -124,7 +122,7 @@ export function write_all_types(config, manifest_data) { if (should_generate) { // track which old files end up being surplus to requirements const to_delete = new Set(output_files.map((file) => file.name)); - update_types(config, routes_map, route, to_delete); + update_types(config, routes_map, route, root, to_delete); meta_data[route.id] = input_files; } } @@ -138,8 +136,9 @@ export function write_all_types(config, manifest_data) { * @param {import('types').ValidatedConfig} config * @param {import('types').ManifestData} manifest_data * @param {string} file + * @param {string} root The project root directory */ -export function write_types(config, manifest_data, file) { +export function write_types(config, manifest_data, file, root) { if (!ts) return; if (!path.basename(file).startsWith('+')) { @@ -153,7 +152,7 @@ export function write_types(config, manifest_data, file) { if (!route) return; if (!route.leaf && !route.layout && !route.endpoint) return; // nothing to do - update_types(config, create_routes_map(manifest_data), route); + update_types(config, create_routes_map(manifest_data), route, root); } /** @@ -176,11 +175,12 @@ function create_routes_map(manifest_data) { * @param {import('types').ValidatedConfig} config * @param {RoutesMap} routes * @param {import('types').RouteData} route + * @param {string} root The project root directory * @param {Set} [to_delete] */ -function update_types(config, routes, route, to_delete = new Set()) { +function update_types(config, routes, route, root, to_delete = new Set()) { const routes_dir = remove_relative_parent_traversals( - posixify(path.relative('.', config.kit.files.routes)) + posixify(path.relative(root, config.kit.files.routes)) ); const outdir = path.join(config.kit.outDir, 'types', routes_dir, route.id); @@ -255,7 +255,7 @@ function update_types(config, routes, route, to_delete = new Set()) { declarations: d, exports: e, proxies - } = process_node(route.leaf, outdir, true, route_info.proxies); + } = process_node(route.leaf, outdir, true, route_info.proxies, root); exports.push(...e); declarations.push(...d); @@ -304,7 +304,7 @@ function update_types(config, routes, route, to_delete = new Set()) { layout_params.push({ ...param, optional: true }); } - ensureProxies(page, leaf.proxies); + ensureProxies(page, leaf.proxies, root); if ( // Be defensive - if a proxy doesn't exist (because it couldn't be created), assume a load function exists. @@ -340,6 +340,7 @@ function update_types(config, routes, route, to_delete = new Set()) { outdir, false, { server: null, universal: null }, + root, all_pages_have_load ); @@ -379,9 +380,10 @@ function update_types(config, routes, route, to_delete = new Set()) { * @param {string} outdir * @param {boolean} is_page * @param {Proxies} proxies + * @param {string} root The project root directory * @param {boolean} [all_pages_have_load] */ -function process_node(node, outdir, is_page, proxies, all_pages_have_load = true) { +function process_node(node, outdir, is_page, proxies, root, all_pages_have_load = true) { const params = `${is_page ? 'Route' : 'Layout'}Params`; const prefix = is_page ? 'Page' : 'Layout'; @@ -397,7 +399,7 @@ function process_node(node, outdir, is_page, proxies, all_pages_have_load = true /** @type {string} */ let data; - ensureProxies(node, proxies); + ensureProxies(node, proxies, root); if (node.server) { const basename = path.basename(node.server); @@ -430,7 +432,7 @@ function process_node(node, outdir, is_page, proxies, all_pages_have_load = true // The advantage is that type updates are reflected without saving. const from = proxy.modified ? `./proxy${replace_ext_with_js(basename)}` - : path_to_original(outdir, node.server); + : path_to_original(outdir, node.server, root); exports.push( 'type ExcludeActionFailure = T extends Kit.ActionFailure ? never : T extends void ? never : T;', @@ -499,7 +501,7 @@ function process_node(node, outdir, is_page, proxies, all_pages_have_load = true // The advantage is that type updates are reflected without saving. const from = proxy.modified ? `./proxy${replace_ext_with_js(path.basename(file_path))}` - : path_to_original(outdir, file_path); + : path_to_original(outdir, file_path, root); const type = `Kit.LoadProperties>>`; return expand ? `Expand>>` : type; } else { @@ -519,24 +521,26 @@ function process_node(node, outdir, is_page, proxies, all_pages_have_load = true * * @param {import('types').PageNode} node * @param {Proxies} proxies + * @param {string} root The project root directory */ -function ensureProxies(node, proxies) { +function ensureProxies(node, proxies, root) { if (node.server && !proxies.server) { - proxies.server = createProxy(node.server, true); + proxies.server = createProxy(node.server, true, root); } if (node.universal && !proxies.universal) { - proxies.universal = createProxy(node.universal, false); + proxies.universal = createProxy(node.universal, false, root); } } /** * @param {string} file_path * @param {boolean} is_server + * @param {string} root The project root directory * @returns {Proxy} */ -function createProxy(file_path, is_server) { - const proxy = tweak_types(fs.readFileSync(file_path, 'utf8'), is_server); +function createProxy(file_path, is_server, root) { + const proxy = tweak_types(fs.readFileSync(path.resolve(root, file_path), 'utf8'), is_server); if (proxy) { return { ...proxy, @@ -581,9 +585,10 @@ function get_parent_type(node, type) { /** * @param {string} outdir * @param {string} file_path + * @param {string} root The project root directory */ -function path_to_original(outdir, file_path) { - return posixify(path.relative(outdir, path.join(cwd, replace_ext_with_js(file_path)))); +function path_to_original(outdir, file_path, root) { + return posixify(path.relative(outdir, path.join(root, replace_ext_with_js(file_path)))); } /** diff --git a/packages/kit/src/core/sync/write_types/index.spec.js b/packages/kit/src/core/sync/write_types/index.spec.js index 647ceff6611e..15f79e4c15bb 100644 --- a/packages/kit/src/core/sync/write_types/index.spec.js +++ b/packages/kit/src/core/sync/write_types/index.spec.js @@ -2,7 +2,6 @@ import { execSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; -import { fileURLToPath } from 'node:url'; import { assert, expect, test } from 'vitest'; import { rimraf } from '../../../utils/filesystem.js'; import create_manifest_data from '../create_manifest_data/index.js'; @@ -10,7 +9,7 @@ import { tweak_types, write_all_types } from './index.js'; import { write_non_ambient } from '../write_non_ambient.js'; import { validate_config } from '../../config/index.js'; -const cwd = fileURLToPath(new URL('./test', import.meta.url)); +const cwd = path.join(import.meta.dirname, 'test'); /** * @param {string} dir @@ -23,13 +22,16 @@ function run_test(dir) { initial.kit.files.assets = path.resolve(cwd, 'static'); initial.kit.files.params = path.resolve(cwd, dir, 'params'); initial.kit.files.routes = path.resolve(cwd, dir); - initial.kit.outDir = path.resolve(cwd, path.join(dir, '.svelte-kit')); + initial.kit.outDir = path.resolve(cwd, dir, '.svelte-kit'); + + const root = path.join(cwd, dir); const manifest = create_manifest_data({ - config: /** @type {import('types').ValidatedConfig} */ (initial) + config: /** @type {import('types').ValidatedConfig} */ (initial), + cwd: root }); - write_all_types(initial, manifest); + write_all_types(initial, manifest, root); write_non_ambient(initial.kit, manifest); } diff --git a/packages/kit/src/core/sync/write_types/test/actions/+page.server.js b/packages/kit/src/core/sync/write_types/test/actions/+page.server.js index 3347ea120fac..453d08708447 100644 --- a/packages/kit/src/core/sync/write_types/test/actions/+page.server.js +++ b/packages/kit/src/core/sync/write_types/test/actions/+page.server.js @@ -39,7 +39,7 @@ export const actions = { /** * Ordinarily this would live in a +page.svelte, but to make it easy to run the tests, we put it here. * The `export` is so that eslint doesn't throw a hissy fit about the unused variable - * @type {import('./.svelte-kit/types/src/core/sync/write_types/test/actions/$types').SubmitFunction} + * @type {import('./.svelte-kit/types/$types').SubmitFunction} */ export const submit = () => { return ({ result }) => { diff --git a/packages/kit/src/core/sync/write_types/test/layout-advanced/(main)/+page.js b/packages/kit/src/core/sync/write_types/test/layout-advanced/(main)/+page.js index 2368bd47160f..37854020e94f 100644 --- a/packages/kit/src/core/sync/write_types/test/layout-advanced/(main)/+page.js +++ b/packages/kit/src/core/sync/write_types/test/layout-advanced/(main)/+page.js @@ -1,6 +1,6 @@ // test to see if layout adjusts correctly if +page.js exists, but no load function -/** @type {import('../.svelte-kit/types/src/core/sync/write_types/test/layout-advanced/(main)/$types').PageData} */ +/** @type {import('../.svelte-kit/types/(main)/$types').PageData} */ const data = { root: '' }; diff --git a/packages/kit/src/core/sync/write_types/test/layout-advanced/(main)/sub/+page.js b/packages/kit/src/core/sync/write_types/test/layout-advanced/(main)/sub/+page.js index 908adae6e5df..05cf07858240 100644 --- a/packages/kit/src/core/sync/write_types/test/layout-advanced/(main)/sub/+page.js +++ b/packages/kit/src/core/sync/write_types/test/layout-advanced/(main)/sub/+page.js @@ -1,4 +1,4 @@ -/** @type {import('../../.svelte-kit/types/src/core/sync/write_types/test/layout-advanced/(main)/sub/$types').PageLoad} */ +/** @type {import('../../.svelte-kit/types/(main)/sub/$types').PageLoad} */ export async function load({ parent }) { const p = await parent(); p.main; @@ -10,7 +10,7 @@ export async function load({ parent }) { }; } -/** @type {import('../../.svelte-kit/types/src/core/sync/write_types/test/layout-advanced/(main)/sub/$types').PageData} */ +/** @type {import('../../.svelte-kit/types/(main)/sub/$types').PageData} */ const data = { main: '', root: '', diff --git a/packages/kit/src/core/sync/write_types/test/layout/+layout.js b/packages/kit/src/core/sync/write_types/test/layout/+layout.js index c8bc95024f98..8643783872c6 100644 --- a/packages/kit/src/core/sync/write_types/test/layout/+layout.js +++ b/packages/kit/src/core/sync/write_types/test/layout/+layout.js @@ -1,4 +1,4 @@ -/** @type {import('./.svelte-kit/types/src/core/sync/write_types/test/layout/$types').LayoutLoad} */ +/** @type {import('./.svelte-kit/types/$types').LayoutLoad} */ export function load({ data }) { data.server; // @ts-expect-error diff --git a/packages/kit/src/core/sync/write_types/test/layout/+layout.server.js b/packages/kit/src/core/sync/write_types/test/layout/+layout.server.js index 77a2a9780b5b..f3b024c96c5b 100644 --- a/packages/kit/src/core/sync/write_types/test/layout/+layout.server.js +++ b/packages/kit/src/core/sync/write_types/test/layout/+layout.server.js @@ -1,4 +1,4 @@ -/** @type {import('./.svelte-kit/types/src/core/sync/write_types/test/layout/$types').LayoutServerLoad} */ +/** @type {import('./.svelte-kit/types/$types').LayoutServerLoad} */ export function load() { return { server: 'server' diff --git a/packages/kit/src/core/sync/write_types/test/layout/+page.js b/packages/kit/src/core/sync/write_types/test/layout/+page.js index 4abd01995480..63b0672410d7 100644 --- a/packages/kit/src/core/sync/write_types/test/layout/+page.js +++ b/packages/kit/src/core/sync/write_types/test/layout/+page.js @@ -1,4 +1,4 @@ -/** @type {import('./.svelte-kit/types/src/core/sync/write_types/test/layout/$types').PageLoad} */ +/** @type {import('./.svelte-kit/types/$types').PageLoad} */ export function load({ data }) { data.pageServer; // @ts-expect-error @@ -8,7 +8,7 @@ export function load({ data }) { }; } -/** @type {import('./.svelte-kit/types/src/core/sync/write_types/test/layout/$types').PageData} */ +/** @type {import('./.svelte-kit/types/$types').PageData} */ const data = { shared: 'asd', pageShared: 'asd' diff --git a/packages/kit/src/core/sync/write_types/test/param-type-inference/optional/[[optionalNarrowedParam=narrowed]]/+page.js b/packages/kit/src/core/sync/write_types/test/param-type-inference/optional/[[optionalNarrowedParam=narrowed]]/+page.js index 024289bef97e..cbb0d64378c9 100644 --- a/packages/kit/src/core/sync/write_types/test/param-type-inference/optional/[[optionalNarrowedParam=narrowed]]/+page.js +++ b/packages/kit/src/core/sync/write_types/test/param-type-inference/optional/[[optionalNarrowedParam=narrowed]]/+page.js @@ -1,6 +1,6 @@ /* eslint-disable */ -/** @type {import('../../.svelte-kit/types/src/core/sync/write_types/test/param-type-inference/optional/[[optionalNarrowedParam=narrowed]]/$types').PageLoad} */ +/** @type {import('../../.svelte-kit/types/optional/[[optionalNarrowedParam=narrowed]]/$types').PageLoad} */ export function load({ params }) { if (params.optionalNarrowedParam) { /** @type {"a" | "b"} */ diff --git a/packages/kit/src/core/sync/write_types/test/param-type-inference/required/+layout.js b/packages/kit/src/core/sync/write_types/test/param-type-inference/required/+layout.js index 5a67f890cd8c..3c27fddc8492 100644 --- a/packages/kit/src/core/sync/write_types/test/param-type-inference/required/+layout.js +++ b/packages/kit/src/core/sync/write_types/test/param-type-inference/required/+layout.js @@ -1,6 +1,6 @@ /* eslint-disable */ -/** @type {import('../.svelte-kit/types/src/core/sync/write_types/test/param-type-inference/required/$types').LayoutLoad} */ +/** @type {import('../.svelte-kit/types/required/$types').LayoutLoad} */ export function load({ params }) { if (params.narrowedParam) { /** @type {"a" | "b"} */ diff --git a/packages/kit/src/core/sync/write_types/test/param-type-inference/required/[narrowedParam=narrowed]/+page.js b/packages/kit/src/core/sync/write_types/test/param-type-inference/required/[narrowedParam=narrowed]/+page.js index 395ae659aa1d..76d9483badff 100644 --- a/packages/kit/src/core/sync/write_types/test/param-type-inference/required/[narrowedParam=narrowed]/+page.js +++ b/packages/kit/src/core/sync/write_types/test/param-type-inference/required/[narrowedParam=narrowed]/+page.js @@ -1,6 +1,6 @@ /* eslint-disable */ -/** @type {import('../../.svelte-kit/types/src/core/sync/write_types/test/param-type-inference/required/[narrowedParam=narrowed]/$types').PageLoad} */ +/** @type {import('../../.svelte-kit/types/required/[narrowedParam=narrowed]/$types').PageLoad} */ export function load({ params }) { /** @type {"a" | "b"} */ let a; diff --git a/packages/kit/src/core/sync/write_types/test/param-type-inference/required/[regularParam=not_narrowed]/+page.js b/packages/kit/src/core/sync/write_types/test/param-type-inference/required/[regularParam=not_narrowed]/+page.js index 6fd50653a023..bd803fe94597 100644 --- a/packages/kit/src/core/sync/write_types/test/param-type-inference/required/[regularParam=not_narrowed]/+page.js +++ b/packages/kit/src/core/sync/write_types/test/param-type-inference/required/[regularParam=not_narrowed]/+page.js @@ -1,6 +1,6 @@ /* eslint-disable */ -/** @type {import('../../.svelte-kit/types/src/core/sync/write_types/test/param-type-inference/required/[regularParam=not_narrowed]/$types').PageLoad} */ +/** @type {import('../../.svelte-kit/types/required/[regularParam=not_narrowed]/$types').PageLoad} */ export function load({ params }) { /** @type {string} a*/ const a = params.regularParam; diff --git a/packages/kit/src/core/sync/write_types/test/param-type-inference/spread/[...spread=narrowed]/+page.js b/packages/kit/src/core/sync/write_types/test/param-type-inference/spread/[...spread=narrowed]/+page.js index f26d10e3aa74..393446edfb18 100644 --- a/packages/kit/src/core/sync/write_types/test/param-type-inference/spread/[...spread=narrowed]/+page.js +++ b/packages/kit/src/core/sync/write_types/test/param-type-inference/spread/[...spread=narrowed]/+page.js @@ -1,6 +1,6 @@ /* eslint-disable */ -/** @type {import('../../.svelte-kit/types/src/core/sync/write_types/test/param-type-inference/spread/[...spread=narrowed]/$types').PageLoad} */ +/** @type {import('../../.svelte-kit/types/spread/[...spread=narrowed]/$types').PageLoad} */ export function load({ params }) { /** @type {"a" | "b"} */ let a; diff --git a/packages/kit/src/core/sync/write_types/test/simple-page-server-and-shared/+page.js b/packages/kit/src/core/sync/write_types/test/simple-page-server-and-shared/+page.js index 9d11e44f0552..e9b7de888b7f 100644 --- a/packages/kit/src/core/sync/write_types/test/simple-page-server-and-shared/+page.js +++ b/packages/kit/src/core/sync/write_types/test/simple-page-server-and-shared/+page.js @@ -1,4 +1,4 @@ -/** @type {import('./.svelte-kit/types/src/core/sync/write_types/test/simple-page-server-and-shared/$types').PageLoad} */ +/** @type {import('./.svelte-kit/types/$types').PageLoad} */ export function load({ data }) { data.server; // @ts-expect-error @@ -8,7 +8,7 @@ export function load({ data }) { }; } -/** @type {import('./.svelte-kit/types/src/core/sync/write_types/test/simple-page-server-and-shared/$types').PageData} */ +/** @type {import('./.svelte-kit/types/$types').PageData} */ const data = { shared: 'asd' }; diff --git a/packages/kit/src/core/sync/write_types/test/simple-page-server-only/+page.server.js b/packages/kit/src/core/sync/write_types/test/simple-page-server-only/+page.server.js index cfdb779689e4..b33e149baf62 100644 --- a/packages/kit/src/core/sync/write_types/test/simple-page-server-only/+page.server.js +++ b/packages/kit/src/core/sync/write_types/test/simple-page-server-only/+page.server.js @@ -8,7 +8,7 @@ export const actions = { default: () => ({ action: 'bar' }) }; -/** @type {import('./.svelte-kit/types/src/core/sync/write_types/test/simple-page-server-only/$types').PageData} */ +/** @type {import('./.svelte-kit/types/$types').PageData} */ const data = { foo: 'asd' }; @@ -16,7 +16,7 @@ data.foo; // @ts-expect-error data.bar; -/** @type {import('./.svelte-kit/types/src/core/sync/write_types/test/simple-page-server-only/$types').ActionData} */ +/** @type {import('./.svelte-kit/types/$types').ActionData} */ const actionData = { action: 'bar' }; actionData.action; // @ts-expect-error diff --git a/packages/kit/src/core/sync/write_types/test/simple-page-server-only/sub/+page.server.js b/packages/kit/src/core/sync/write_types/test/simple-page-server-only/sub/+page.server.js index 58b4c36ac541..836f9d5e0d67 100644 --- a/packages/kit/src/core/sync/write_types/test/simple-page-server-only/sub/+page.server.js +++ b/packages/kit/src/core/sync/write_types/test/simple-page-server-only/sub/+page.server.js @@ -1,4 +1,4 @@ -/** @type {import('../.svelte-kit/types/src/core/sync/write_types/test/simple-page-server-only/sub/$types').PageServerLoad} */ +/** @type {import('../.svelte-kit/types/sub/$types').PageServerLoad} */ export function load() { if (Math.random() > 0.5) { return { @@ -7,7 +7,7 @@ export function load() { } } -/** @type {import('../.svelte-kit/types/src/core/sync/write_types/test/simple-page-server-only/sub/$types').PageData} */ +/** @type {import('../.svelte-kit/types/sub/$types').PageData} */ const data = /** @type {any} */ ({ foo: 'bar' }); // the any cast prevents TypeScript from narrowing this to foo being defined diff --git a/packages/kit/src/core/sync/write_types/test/simple-page-shared-only/+page.js b/packages/kit/src/core/sync/write_types/test/simple-page-shared-only/+page.js index 900e9369bd13..03eee6efca6b 100644 --- a/packages/kit/src/core/sync/write_types/test/simple-page-shared-only/+page.js +++ b/packages/kit/src/core/sync/write_types/test/simple-page-shared-only/+page.js @@ -4,7 +4,7 @@ export function load() { }; } -/** @type {import('./.svelte-kit/types/src/core/sync/write_types/test/simple-page-shared-only/$types').PageData} */ +/** @type {import('./.svelte-kit/types/$types').PageData} */ const data = { shared: 'asd' }; diff --git a/packages/kit/src/core/sync/write_types/test/simple-page-shared-only/sub/+page.js b/packages/kit/src/core/sync/write_types/test/simple-page-shared-only/sub/+page.js index b1d06b96d262..0ffaf618e561 100644 --- a/packages/kit/src/core/sync/write_types/test/simple-page-shared-only/sub/+page.js +++ b/packages/kit/src/core/sync/write_types/test/simple-page-shared-only/sub/+page.js @@ -1,4 +1,4 @@ -/** @type {import('../.svelte-kit/types/src/core/sync/write_types/test/simple-page-shared-only/sub/$types').PageLoad} */ +/** @type {import('../.svelte-kit/types/sub/$types').PageLoad} */ export function load() { if (Math.random() > 0.5) { return { @@ -7,7 +7,7 @@ export function load() { } } -/** @type {import('../.svelte-kit/types/src/core/sync/write_types/test/simple-page-shared-only/sub/$types').PageData} */ +/** @type {import('../.svelte-kit/types/sub/$types').PageData} */ const data = /** @type {any} */ ({ foo: 'bar' }); // the any cast prevents TypeScript from narrowing this to foo being defined diff --git a/packages/kit/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/+layout.js b/packages/kit/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/+layout.js index f0ae674a8f07..44f1d712d137 100644 --- a/packages/kit/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/+layout.js +++ b/packages/kit/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/+layout.js @@ -1,4 +1,4 @@ -/** @type {import('./.svelte-kit/types/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/$types').LayoutLoad} */ +/** @type {import('./.svelte-kit/types/$types').LayoutLoad} */ export function load({ params }) { params.rest; params.slug; diff --git a/packages/kit/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/nested/+layout.js b/packages/kit/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/nested/+layout.js index d41974fea45b..563f8f2c81f3 100644 --- a/packages/kit/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/nested/+layout.js +++ b/packages/kit/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/nested/+layout.js @@ -1,4 +1,4 @@ -/** @type {import('../.svelte-kit/types/src/core/sync/write_types/test/slugs-layout-not-all-pages-have-load/nested/$types').LayoutLoad} */ +/** @type {import('../.svelte-kit/types/nested/$types').LayoutLoad} */ export function load({ params }) { params.rest; // @ts-expect-error diff --git a/packages/kit/src/core/sync/write_types/test/slugs/+layout.js b/packages/kit/src/core/sync/write_types/test/slugs/+layout.js index 8eea8cfcc18f..88e9d5dba3cd 100644 --- a/packages/kit/src/core/sync/write_types/test/slugs/+layout.js +++ b/packages/kit/src/core/sync/write_types/test/slugs/+layout.js @@ -1,4 +1,4 @@ -/** @type {import('./.svelte-kit/types/src/core/sync/write_types/test/slugs/$types').LayoutLoad} */ +/** @type {import('./.svelte-kit/types/$types').LayoutLoad} */ export function load({ params }) { params.optional; params.rest; diff --git a/packages/kit/src/core/sync/write_types/test/slugs/[...rest]/+page.js b/packages/kit/src/core/sync/write_types/test/slugs/[...rest]/+page.js index cdebe4d37303..067a12c5c1d0 100644 --- a/packages/kit/src/core/sync/write_types/test/slugs/[...rest]/+page.js +++ b/packages/kit/src/core/sync/write_types/test/slugs/[...rest]/+page.js @@ -1,4 +1,4 @@ -/** @type {import('../.svelte-kit/types/src/core/sync/write_types/test/slugs/[...rest]/$types').PageLoad} */ +/** @type {import('../.svelte-kit/types/[...rest]/$types').PageLoad} */ export function load({ params }) { params.rest.charAt(1); // @ts-expect-error diff --git a/packages/kit/src/core/sync/write_types/test/slugs/[slug]/+page.js b/packages/kit/src/core/sync/write_types/test/slugs/[slug]/+page.js index 9a82f5ab5782..0f0777d66937 100644 --- a/packages/kit/src/core/sync/write_types/test/slugs/[slug]/+page.js +++ b/packages/kit/src/core/sync/write_types/test/slugs/[slug]/+page.js @@ -1,4 +1,4 @@ -/** @type {import('../.svelte-kit/types/src/core/sync/write_types/test/slugs/[slug]/$types').PageLoad} */ +/** @type {import('../.svelte-kit/types/[slug]/$types').PageLoad} */ export function load({ params }) { params.slug.charAt(1); // @ts-expect-error diff --git a/packages/kit/src/core/sync/write_types/test/slugs/x/[[optional]]/+page.js b/packages/kit/src/core/sync/write_types/test/slugs/x/[[optional]]/+page.js index 4085ce7295cb..55c3ddcd0ec3 100644 --- a/packages/kit/src/core/sync/write_types/test/slugs/x/[[optional]]/+page.js +++ b/packages/kit/src/core/sync/write_types/test/slugs/x/[[optional]]/+page.js @@ -1,4 +1,4 @@ -/** @type {import('../../.svelte-kit/types/src/core/sync/write_types/test/slugs/x/[[optional]]/$types').PageLoad} */ +/** @type {import('../../.svelte-kit/types/x/[[optional]]/$types').PageLoad} */ export async function load({ parent, params }) { const p = await parent(); /** @type {NonNullable} */ diff --git a/packages/kit/src/core/utils.js b/packages/kit/src/core/utils.js index ff6b7a460639..73ded00b82fb 100644 --- a/packages/kit/src/core/utils.js +++ b/packages/kit/src/core/utils.js @@ -1,6 +1,5 @@ import fs from 'node:fs'; import path from 'node:path'; -import process from 'node:process'; import { fileURLToPath } from 'node:url'; import { styleText } from 'node:util'; import { posixify, to_fs } from '../utils/filesystem.js'; @@ -19,10 +18,14 @@ export const runtime_directory = posixify(fileURLToPath(new URL('../runtime', im * This allows us to import SvelteKit internals that aren't exposed via `pkg.exports` in a * way that works whether `@sveltejs/kit` is installed inside the project's `node_modules` * or in a workspace root + * @param {string} root + * @returns {string} */ -export const runtime_base = runtime_directory.startsWith(process.cwd()) - ? `/${path.relative('.', runtime_directory)}` - : to_fs(runtime_directory); +export function get_runtime_base(root) { + return runtime_directory.startsWith(root) + ? `/${path.relative(root, runtime_directory)}` + : to_fs(runtime_directory); +} function noop() {} diff --git a/packages/kit/src/exports/vite/build/build_server.js b/packages/kit/src/exports/vite/build/build_server.js index 379e0be23e3e..10ace8a58762 100644 --- a/packages/kit/src/exports/vite/build/build_server.js +++ b/packages/kit/src/exports/vite/build/build_server.js @@ -15,6 +15,7 @@ import { fix_css_urls } from '../../../utils/css.js'; * @param {string | null} assets_path * @param {import('vite').Rollup.RollupOutput['output'] | null} client_chunks * @param {import('types').RecursiveRequired} output_config + * @param {string} root */ export function build_server_nodes( out, @@ -24,7 +25,8 @@ export function build_server_nodes( client_manifest, assets_path, client_chunks, - output_config + output_config, + root ) { mkdirp(`${out}/server/nodes`); mkdirp(`${out}/server/stylesheets`); @@ -113,7 +115,7 @@ export function build_server_nodes( exports.push( 'let component_cache;', `export const component = async () => component_cache ??= (await import('../${ - resolve_symlinks(server_manifest, node.component).chunk.file + resolve_symlinks(server_manifest, node.component, root).chunk.file }')).default;` ); } @@ -123,7 +125,7 @@ export function build_server_nodes( exports.push(`export const universal = ${s(node.page_options, null, 2)};`); } else { imports.push( - `import * as universal from '../${resolve_symlinks(server_manifest, node.universal).chunk.file}';` + `import * as universal from '../${resolve_symlinks(server_manifest, node.universal, root).chunk.file}';` ); // TODO: when building for analysis, explain why the file was loaded on the server if we fail to load it exports.push('export { universal };'); @@ -133,7 +135,7 @@ export function build_server_nodes( if (node.server) { imports.push( - `import * as server from '../${resolve_symlinks(server_manifest, node.server).chunk.file}';` + `import * as server from '../${resolve_symlinks(server_manifest, node.server, root).chunk.file}';` ); exports.push('export { server };'); exports.push(`export const server_id = ${s(node.server)};`); @@ -145,7 +147,7 @@ export function build_server_nodes( output_config.bundleStrategy === 'split' ) { const entry_path = `${normalizePath(kit.outDir)}/generated/client-optimized/nodes/${i}.js`; - const entry = find_deps(client_manifest, entry_path, true); + const entry = find_deps(client_manifest, entry_path, true, root); // Eagerly load client stylesheets and fonts imported by the SSR-ed page to avoid FOUC. // However, if it is not used during SSR (not present in the server manifest), @@ -154,13 +156,13 @@ export function build_server_nodes( /** @type {import('types').AssetDependencies | undefined} */ let component; if (node.component) { - component = find_deps(server_manifest, node.component, true); + component = find_deps(server_manifest, node.component, true, root); } /** @type {import('types').AssetDependencies | undefined} */ let universal; if (node.universal) { - universal = find_deps(server_manifest, node.universal, true); + universal = find_deps(server_manifest, node.universal, true, root); } /** @type {Set} */ diff --git a/packages/kit/src/exports/vite/build/build_service_worker.js b/packages/kit/src/exports/vite/build/build_service_worker.js index aa9b6e3a6ac1..d9ab47f331f1 100644 --- a/packages/kit/src/exports/vite/build/build_service_worker.js +++ b/packages/kit/src/exports/vite/build/build_service_worker.js @@ -1,4 +1,3 @@ -import process from 'node:process'; import * as vite from 'vite'; import { dedent } from '../../../core/sync/utils.js'; import { s } from '../../../utils/misc.js'; @@ -84,7 +83,7 @@ export async function build_service_worker( return create_static_module('$env/static/public', env.public); } - const normalized_cwd = vite.normalizePath(process.cwd()); + const normalized_cwd = vite.normalizePath(vite_config.root); const normalized_lib = vite.normalizePath(kit.files.lib); const relative = normalize_id(id, normalized_lib, normalized_cwd); const stripped = strip_virtual_prefix(relative); @@ -116,7 +115,7 @@ export async function build_service_worker( publicDir: false, plugins: [sw_virtual_modules], resolve: { - alias: [...get_config_aliases(kit)] + alias: [...get_config_aliases(kit, vite_config.root)] }, experimental: { renderBuiltUrl(filename) { diff --git a/packages/kit/src/exports/vite/build/utils.js b/packages/kit/src/exports/vite/build/utils.js index 8da2e7020c6a..86a0dd33b0b1 100644 --- a/packages/kit/src/exports/vite/build/utils.js +++ b/packages/kit/src/exports/vite/build/utils.js @@ -8,9 +8,10 @@ import { s } from '../../../utils/misc.js'; * @param {import('vite').Manifest} manifest * @param {string} entry * @param {boolean} add_dynamic_css + * @param {string} root * @returns {import('types').AssetDependencies} */ -export function find_deps(manifest, entry, add_dynamic_css) { +export function find_deps(manifest, entry, add_dynamic_css, root) { /** @type {Set} */ const seen = new Set(); @@ -36,7 +37,7 @@ export function find_deps(manifest, entry, add_dynamic_css) { if (seen.has(current)) return; seen.add(current); - const { chunk } = resolve_symlinks(manifest, current); + const { chunk } = resolve_symlinks(manifest, current, root); if (add_js) imports.add(chunk.file); @@ -82,7 +83,7 @@ export function find_deps(manifest, entry, add_dynamic_css) { } } - const { chunk, file } = resolve_symlinks(manifest, entry); + const { chunk, file } = resolve_symlinks(manifest, entry, root); traverse(file, true, entry, 0); @@ -102,10 +103,11 @@ export function find_deps(manifest, entry, add_dynamic_css) { /** * @param {import('vite').Manifest} manifest * @param {string} file + * @param {string} root */ -export function resolve_symlinks(manifest, file) { +export function resolve_symlinks(manifest, file, root) { while (!manifest[file]) { - const next = normalizePath(path.relative('.', fs.realpathSync(file))); + const next = normalizePath(path.relative(root, fs.realpathSync(file))); if (next === file) throw new Error(`Could not find file "${file}" in Vite manifest`); file = next; } diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 7313f237d353..52923f56e880 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -1,6 +1,5 @@ import fs from 'node:fs'; import path from 'node:path'; -import process from 'node:process'; import { URL } from 'node:url'; import { AsyncLocalStorage } from 'node:async_hooks'; import { styleText } from 'node:util'; @@ -12,14 +11,13 @@ import { from_fs, posixify, resolve_entry, to_fs } from '../../../utils/filesyst import { load_error_page } from '../../../core/config/index.js'; import { SVELTE_KIT_ASSETS } from '../../../constants.js'; import * as sync from '../../../core/sync/sync.js'; -import { get_mime_lookup, runtime_base } from '../../../core/utils.js'; +import { get_mime_lookup, get_runtime_base } from '../../../core/utils.js'; import { compact } from '../../../utils/array.js'; import { not_found } from '../utils.js'; import { SCHEME } from '../../../utils/url.js'; import { check_feature } from '../../../utils/features.js'; import { escape_html } from '../../../utils/escape.js'; -const cwd = process.cwd(); // vite-specifc queries that we should skip handling for css urls const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/; @@ -28,9 +26,10 @@ const vite_css_query_regex = /(?:\?|&)(?:raw|url|inline)(?:&|$)/; * @param {import('vite').ResolvedConfig} vite_config * @param {import('types').ValidatedConfig} svelte_config * @param {() => Array<{ hash: string, file: string }>} get_remotes + * @param {string} root The project root directory * @return {Promise void>>} */ -export async function dev(vite, vite_config, svelte_config, get_remotes) { +export async function dev(vite, vite_config, svelte_config, get_remotes, root) { const async_local_storage = new AsyncLocalStorage(); globalThis.__SVELTEKIT_TRACK__ = (label) => { @@ -51,7 +50,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) { return fetch(info, init); }; - sync.init(svelte_config, vite_config.mode); + sync.init(svelte_config, vite_config.mode, root); /** @type {import('types').ManifestData} */ let manifest_data; @@ -103,7 +102,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) { function update_manifest() { try { - ({ manifest_data } = sync.create(svelte_config)); + ({ manifest_data } = sync.create(svelte_config, root)); if (manifest_error) { manifest_error = null; @@ -131,7 +130,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) { mimeTypes: get_mime_lookup(manifest_data), _: { client: { - start: `${runtime_base}/client/entry.js`, + start: `${get_runtime_base(root)}/client/entry.js`, app: `${to_fs(svelte_config.kit.outDir)}/generated/client/app.js`, imports: [], stylesheets: [], @@ -274,7 +273,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) { page: route.page, endpoint: endpoint ? async () => { - const url = path.resolve(cwd, endpoint.file); + const url = path.resolve(root, endpoint.file); return await loud_ssr_load_module(url); } : null, @@ -288,7 +287,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) { for (const key in manifest_data.matchers) { const file = manifest_data.matchers[key]; - const url = path.resolve(cwd, file); + const url = path.resolve(root, file); const module = await vite.ssrLoadModule(url, { fixStacktrace: true }); if (module.match) { @@ -358,7 +357,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) { // Don't run for a single file if the whole manifest is about to get updated // Unless it's a file where the trailing slash page option might have changed if (timeout || restarting || !/\+(page|layout|server).*$/.test(file)) return; - sync.update(svelte_config, manifest_data, file); + sync.update(svelte_config, manifest_data, file, root); }); const { appTemplate, errorTemplate, serviceWorker, hooks } = svelte_config.kit.files; @@ -381,7 +380,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) { file.startsWith(serviceWorker) || file.startsWith(hooks.server) ) { - sync.server(svelte_config); + sync.server(svelte_config, root); } }); @@ -453,7 +452,9 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) { }`; const decoded = decodeURI(new URL(base + req.url).pathname); - const file = posixify(path.resolve(decoded.slice(svelte_config.kit.paths.base.length + 1))); + const file = posixify( + path.resolve(root, decoded.slice(svelte_config.kit.paths.base.length + 1)) + ); const is_file = fs.existsSync(file) && !fs.statSync(file).isDirectory(); const allowed = !vite_config.server.fs.strict || @@ -496,11 +497,13 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) { // we have to import `Server` before calling `set_assets` const { Server } = /** @type {import('types').ServerModule} */ ( - await vite.ssrLoadModule(`${runtime_base}/server/index.js`, { fixStacktrace: true }) + await vite.ssrLoadModule(`${get_runtime_base(root)}/server/index.js`, { + fixStacktrace: true + }) ); const { set_fix_stack_trace } = await vite.ssrLoadModule( - `${runtime_base}/shared-server.js` + `${get_runtime_base(root)}/shared-server.js` ); set_fix_stack_trace(fix_stack_trace); diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 96ccab11bb78..162d45121dde 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -10,7 +10,6 @@ import { create_static_module, create_dynamic_module } from '../../core/env.js'; import * as sync from '../../core/sync/sync.js'; import { create_assets } from '../../core/sync/create_manifest_data/index.js'; import { runtime_directory, logger } from '../../core/utils.js'; -import { load_config } from '../../core/config/index.js'; import { generate_manifest } from '../../core/generate_manifest/index.js'; import { build_server_nodes } from './build/build_server.js'; import { build_service_worker } from './build/build_service_worker.js'; @@ -43,8 +42,12 @@ import { import { import_peer } from '../../utils/import.js'; import { compact } from '../../utils/array.js'; import { should_ignore, has_children } from './static_analysis/utils.js'; +import { load_config } from '../../core/config/index.js'; + +const cwd = process.cwd(); -const cwd = posixify(process.cwd()); +/** @type {string} */ +let root; /** @type {import('./types.js').EnforcedConfig} */ const enforced_config = { @@ -79,8 +82,7 @@ const enforced_config = { $lib: true, '$service-worker': true } - }, - root: true + } }; const options_regex = /(export\s+const\s+(prerender|csr|ssr|trailingSlash))\s*=/s; @@ -100,7 +102,7 @@ const warning_preprocessor = { const fixed = basename.replace('.svelte', '(.server).js/ts'); const message = - `\n${styleText(['bold', 'red'], path.relative('.', filename))}\n` + + `\n${styleText(['bold', 'red'], path.relative(root, filename))}\n` + `\`${match[1]}\` will be ignored — move it to ${fixed} instead. See https://svelte.dev/docs/kit/page-options for more information.`; if (!warned.has(message)) { @@ -117,7 +119,7 @@ const warning_preprocessor = { if (basename.startsWith('+layout.') && !has_children(content, true)) { const message = - `\n${styleText(['bold', 'red'], path.relative('.', filename))}\n` + + `\n${styleText(['bold', 'red'], path.relative(root, filename))}\n` + '`` or `{@render ...}` tag' + ' missing — inner content will not be rendered'; @@ -129,36 +131,88 @@ const warning_preprocessor = { } }; +/** @type {import('@sveltejs/vite-plugin-svelte')} */ +let vite_plugin_svelte; + /** * Returns the SvelteKit Vite plugins. * @returns {Promise} */ export async function sveltekit() { - const svelte_config = await load_config(); - - /** @type {import('@sveltejs/vite-plugin-svelte').Options['preprocess']} */ - let preprocess = svelte_config.preprocess; - if (Array.isArray(preprocess)) { - preprocess = [...preprocess, warning_preprocessor]; - } else if (preprocess) { - preprocess = [preprocess, warning_preprocessor]; - } else { - preprocess = warning_preprocessor; - } + // the config options will be set only after the Vite `config` hook runs + // because we need to find `svelte.config.js` relative to `vite.config.root` + const svelte_config = /** @type {import('types').ValidatedConfig} */ ({}); /** @type {import('@sveltejs/vite-plugin-svelte').Options} */ const vite_plugin_svelte_options = { - configFile: false, - extensions: svelte_config.extensions, - preprocess, - onwarn: svelte_config.onwarn, - compilerOptions: { ...svelte_config.compilerOptions }, - ...svelte_config.vitePlugin + // we don't want vite-plugin-svelte to load the config file itself because + // it will try to validate it without knowing that kit options are valid + configFile: false }; - const { svelte } = await import_peer('@sveltejs/vite-plugin-svelte'); + /** @type {import('@sveltejs/vite-plugin-svelte')} */ + vite_plugin_svelte = await import_peer('@sveltejs/vite-plugin-svelte', cwd); + + return [ + plugin_svelte_config({ vite_plugin_svelte_options, svelte_config }), + ...vite_plugin_svelte.svelte(vite_plugin_svelte_options), + ...kit({ svelte_config }) + ]; +} - return [...svelte(vite_plugin_svelte_options), ...(await kit({ svelte_config }))]; +/** @param {import('vite').UserConfig | import('vite').ResolvedConfig} vite_config */ +function resolve_root(vite_config) { + return posixify(vite_config.root ? path.resolve(vite_config.root) : cwd); +} + +/** + * Resolves the Svelte config using the `vite.config.root` setting before any + * of our other plugins try to access the config objects + * @param {{ + * vite_plugin_svelte_options: import('@sveltejs/vite-plugin-svelte').Options; + * svelte_config: import('types').ValidatedConfig; + * }} options + * @return {import('vite').Plugin} + */ +function plugin_svelte_config({ vite_plugin_svelte_options, svelte_config }) { + return { + name: 'vite-plugin-sveltekit-resolve-svelte-config', + // make sure it runs first + enforce: 'pre', + config: { + order: 'pre', + async handler(config) { + root = resolve_root(config); + + const user_svelte_config = await load_config({ cwd: root }); + + /** @type {import('@sveltejs/vite-plugin-svelte').Options['preprocess']} */ + let preprocess = user_svelte_config.preprocess; + if (Array.isArray(preprocess)) { + preprocess = [...preprocess, warning_preprocessor]; + } else if (preprocess) { + preprocess = [preprocess, warning_preprocessor]; + } else { + preprocess = warning_preprocessor; + } + + vite_plugin_svelte_options.extensions = user_svelte_config.extensions; + vite_plugin_svelte_options.preprocess = preprocess; + vite_plugin_svelte_options.onwarn = user_svelte_config.onwarn; + vite_plugin_svelte_options.compilerOptions = { ...user_svelte_config.compilerOptions }; + Object.assign(vite_plugin_svelte_options, user_svelte_config.vitePlugin); + + Object.assign(svelte_config, user_svelte_config); + } + }, + // TODO: do we even need to set `root` based on the final Vite config? + configResolved: { + order: 'pre', + handler(config) { + root = resolve_root(config); + } + } + }; } // These variables live outside the `kit()` function because it is re-invoked by each Vite build @@ -171,15 +225,6 @@ let manifest_data; /** @type {import('types').ServerMetadata | undefined} only set at build time once analysis is finished */ let build_metadata = undefined; -/** - * TODO: SvelteKit 4 - replace with RegExp.escape - available only in Node 24 - * @param {string} str - * @returns - */ -const reg_exp_escape = function (str) { - return str.replace(/[-[\]{}()*+!<=:?.\\/\\^$|#\s,]/g, '\\$&'); -}; - /** * Returns the SvelteKit Vite plugin. Vite executes Rollup hooks as well as some of its own. * Background reading is available at: @@ -191,16 +236,19 @@ const reg_exp_escape = function (str) { * - https://rollupjs.org/guide/en/#output-generation-hooks * * @param {{ svelte_config: import('types').ValidatedConfig }} options - * @return {Promise} + * @return {import('vite').Plugin[]} */ -async function kit({ svelte_config }) { +function kit({ svelte_config }) { /** @type {import('vite')} */ - const vite = await import_peer('vite'); + let vite; - const { kit } = svelte_config; - const out = `${kit.outDir}/output`; + /** @type {import('types').ValidatedKitConfig} */ + let kit; + /** @type {string} */ + let out; - const version_hash = hash(kit.version.name); + /** @type {string} */ + let version_hash; /** @type {import('vite').ResolvedConfig} */ let vite_config; @@ -220,13 +268,17 @@ async function kit({ svelte_config }) { /** @type {import('vite').UserConfig} */ let initial_config; - const service_worker_entry_file = resolve_entry(kit.files.serviceWorker); - const parsed_service_worker = path.parse(kit.files.serviceWorker); - - const normalized_cwd = vite.normalizePath(cwd); - const normalized_lib = vite.normalizePath(kit.files.lib); - const normalized_node_modules = vite.normalizePath(path.resolve('node_modules')); - + /** @type {string | null} */ + let service_worker_entry_file; + /** @type {import('node:path').ParsedPath} */ + let parsed_service_worker; + + /** @type {string} */ + let normalized_cwd; + /** @type {string} */ + let normalized_lib; + /** @type {string} */ + let normalized_node_modules; /** * A map showing which features (such as `$app/server:read`) are defined * in which chunks, so that we can later determine which routes use which features @@ -240,25 +292,38 @@ async function kit({ svelte_config }) { /** @type {import('vite').Plugin} */ const plugin_setup = { name: 'vite-plugin-sveltekit-setup', - /** * Build the SvelteKit-provided Vite config to be merged with the user's vite.config.js file. * @see https://vitejs.dev/guide/api-plugin.html#config */ - config(config, config_env) { + async config(config, config_env) { initial_config = config; vite_config_env = config_env; is_build = config_env.command === 'build'; + ({ kit } = svelte_config); + out = `${kit.outDir}/output`; + + version_hash = hash(kit.version.name); + env = get_env(kit.env, vite_config_env.mode); + service_worker_entry_file = resolve_entry(kit.files.serviceWorker); + parsed_service_worker = path.parse(kit.files.serviceWorker); + + vite = await import_peer('vite', root); + + normalized_cwd = vite.normalizePath(root); + normalized_lib = vite.normalizePath(kit.files.lib); + normalized_node_modules = vite.normalizePath(path.resolve(root, 'node_modules')); + const allow = new Set([ kit.files.lib, kit.files.routes, kit.outDir, - path.resolve('src'), // TODO this isn't correct if user changed all his files to sth else than src (like in test/options) - path.resolve('node_modules'), - path.resolve(vite.searchForWorkspaceRoot(cwd), 'node_modules') + path.resolve(root, kit.files.src), + path.resolve(root, 'node_modules'), + path.resolve(cwd, 'node_modules') ]); // We can only add directories to the allow list, so we find out @@ -275,10 +340,9 @@ async function kit({ svelte_config }) { alias: [ { find: '__SERVER__', replacement: `${generated}/server` }, { find: '$app', replacement: `${runtime_directory}/app` }, - ...get_config_aliases(kit) + ...get_config_aliases(kit, root) ] }, - root: cwd, server: { cors: { preflightContinue: true }, fs: { @@ -375,7 +439,7 @@ async function kit({ svelte_config }) { }; if (!secondary_build_started) { - manifest_data = sync.all(svelte_config, config_env.mode).manifest_data; + manifest_data = sync.all(svelte_config, config_env.mode, root).manifest_data; // During the initial server build we don't know yet new_config.define.__SVELTEKIT_HAS_SERVER_LOAD__ = 'true'; new_config.define.__SVELTEKIT_HAS_UNIVERSAL_LOAD__ = 'true'; @@ -491,7 +555,8 @@ async function kit({ svelte_config }) { case env_dynamic_private: return create_dynamic_module( 'private', - vite_config_env.command === 'serve' ? env.private : undefined + vite_config_env.command === 'serve' ? env.private : undefined, + root ); case env_dynamic_public: { @@ -506,7 +571,8 @@ async function kit({ svelte_config }) { return create_dynamic_module( 'public', - vite_config_env.command === 'serve' ? env.public : undefined + vite_config_env.command === 'serve' ? env.public : undefined, + root ); } @@ -605,15 +671,8 @@ async function kit({ svelte_config }) { exactRegex(env_static_private), exactRegex(env_dynamic_private), exactRegex(app_server), - prefixRegex(`${normalized_lib}/server/`), - // skip .server.js files outside the cwd or in node_modules, as the filename might not mean 'server-only module' in this context - // should be equivalent to: (id.startsWith(normalized_cwd) && !id.startsWith(normalized_node_modules) && server_only_pattern.test(path.basename(id)) - // TODO: address https://github.com/sveltejs/kit/issues/12529 - // if we decide to do it then remove the CWD portion of the regex - // if we decide not to do it then this regex is complicated enough that it should be refactored out and independently tested - new RegExp( - `^(?!${reg_exp_escape(normalized_node_modules)})${reg_exp_escape(normalized_cwd)}${server_only_pattern.source}$` - ) + /\/server\//, + new RegExp(`${server_only_pattern.source}$`) ] }, handler(id, options) { @@ -622,8 +681,27 @@ async function kit({ svelte_config }) { return; } + // skip .server.js files outside the cwd or in node_modules, as the filename might not mean 'server-only module' in this context + const is_internal = + id.startsWith(normalized_cwd) && !id.startsWith(normalized_node_modules); + + const normalized = normalize_id(id, normalized_lib, normalized_cwd); + + const is_server_only = + normalized === '$env/static/private' || + normalized === '$env/dynamic/private' || + normalized === '$app/server' || + normalized.startsWith('$lib/server/') || + (is_internal && server_only_pattern.test(path.basename(id))); + + // skip .server.js files outside the cwd or in node_modules, as the filename might not mean 'server-only module' in this context + // TODO: address https://github.com/sveltejs/kit/issues/12529 + if (!is_server_only) { + return; + } + // in dev, this doesn't exist, so we need to create it - manifest_data ??= sync.all(svelte_config, vite_config_env.mode).manifest_data; + manifest_data ??= sync.all(svelte_config, vite_config_env.mode, root).manifest_data; /** @type {Set} */ const entrypoints = new Set(); @@ -635,7 +713,6 @@ async function kit({ svelte_config }) { if (manifest_data.hooks.client) entrypoints.add(manifest_data.hooks.client); if (manifest_data.hooks.universal) entrypoints.add(manifest_data.hooks.universal); - const normalized = normalize_id(id, normalized_lib, normalized_cwd); const chain = [normalized]; let current = normalized; @@ -716,6 +793,10 @@ async function kit({ svelte_config }) { id: prefixRegex('\0sveltekit-remote:') }, handler(id) { + if (!kit.experimental.remoteFunctions) { + return null; + } + // On-the-fly generated entry point for remote file just forwards the original module // We're not using manualChunks because it can cause problems with circular dependencies // (e.g. https://github.com/sveltejs/kit/issues/14679) and module ordering in general @@ -728,35 +809,42 @@ async function kit({ svelte_config }) { }, configureServer(_dev_server) { + if (!kit.experimental.remoteFunctions) { + return; + } + dev_server = _dev_server; }, - transform: { - filter: { - id: new RegExp( - `\\.remote(?:${svelte_config.kit.moduleExtensions.map((e) => e.replaceAll('.', '\\.')).join('|')})(?:\\?.*)?$` - ) - }, - async handler(code, id, opts) { - const file = posixify(path.relative(cwd, id)); - const remote = { - hash: hash(file), - file - }; + async transform(code, id, opts) { + if (!kit.experimental.remoteFunctions) { + return; + } - remotes.push(remote); + const normalized = normalize_id(id, normalized_lib, normalized_cwd); + if (!svelte_config.kit.moduleExtensions.some((ext) => normalized.endsWith(`.remote${ext}`))) { + return; + } + + const file = posixify(path.relative(root, id)); + const remote = { + hash: hash(file), + file + }; - if (opts?.ssr) { - // we need to add an `await Promise.resolve()` because if the user imports this function - // on the client AND in a load function when loading the client module we will trigger - // an ssrLoadModule during dev. During a link preload, the module can be mistakenly - // loaded and transformed twice and the first time all its exports would be undefined - // triggering a dev server error. By adding a microtask we ensure that the module is fully loaded + remotes.push(remote); - // Extra newlines to prevent syntax errors around missing semicolons or comments - code += - '\n\n' + - dedent` + if (opts?.ssr) { + // we need to add an `await Promise.resolve()` because if the user imports this function + // on the client AND in a load function when loading the client module we will trigger + // an ssrLoadModule during dev. During a link preload, the module can be mistakenly + // loaded and transformed twice and the first time all its exports would be undefined + // triggering a dev server error. By adding a microtask we ensure that the module is fully loaded + + // Extra newlines to prevent syntax errors around missing semicolons or comments + code += + '\n\n' + + dedent` import * as $$_self_$$ from './${path.basename(id)}'; import { init_remote_functions as $$_init_$$ } from '@sveltejs/kit/internal'; @@ -770,70 +858,69 @@ async function kit({ svelte_config }) { } `; - // Emit a dedicated entry chunk for this remote in SSR builds (prod only) - if (!dev_server) { - remote_original_by_hash.set(remote.hash, id); - if (!emitted_remote_hashes.has(remote.hash)) { - this.emitFile({ - type: 'chunk', - id: `\0sveltekit-remote:${remote.hash}`, - name: `remote-${remote.hash}` - }); - emitted_remote_hashes.add(remote.hash); - } + // Emit a dedicated entry chunk for this remote in SSR builds (prod only) + if (!dev_server) { + remote_original_by_hash.set(remote.hash, id); + if (!emitted_remote_hashes.has(remote.hash)) { + this.emitFile({ + type: 'chunk', + id: `\0sveltekit-remote:${remote.hash}`, + name: `remote-${remote.hash}` + }); + emitted_remote_hashes.add(remote.hash); } - - return code; } - // For the client, read the exports and create a new module that only contains fetch functions with the correct metadata + return code; + } - /** @type {Map} */ - const map = new Map(); + // For the client, read the exports and create a new module that only contains fetch functions with the correct metadata - // in dev, load the server module here (which will result in this hook - // being called again with `opts.ssr === true` if the module isn't - // already loaded) so we can determine what it exports - if (dev_server) { - const module = await dev_server.ssrLoadModule(id); + /** @type {Map} */ + const map = new Map(); - for (const [name, value] of Object.entries(module)) { - const type = value?.__?.type; - if (type) { - map.set(name, type); - } + // in dev, load the server module here (which will result in this hook + // being called again with `opts.ssr === true` if the module isn't + // already loaded) so we can determine what it exports + if (dev_server) { + const module = await dev_server.ssrLoadModule(id); + + for (const [name, value] of Object.entries(module)) { + const type = value?.__?.type; + if (type) { + map.set(name, type); } } + } - // in prod, we already built and analysed the server code before - // building the client code, so `remote_exports` is populated - else if (build_metadata?.remotes) { - const exports = build_metadata?.remotes.get(remote.hash); - if (!exports) throw new Error('Expected to find metadata for remote file ' + id); + // in prod, we already built and analysed the server code before + // building the client code, so `remote_exports` is populated + else if (build_metadata?.remotes) { + const exports = build_metadata?.remotes.get(remote.hash); + if (!exports) throw new Error('Expected to find metadata for remote file ' + id); - for (const [name, value] of exports) { - map.set(name, value.type); - } + for (const [name, value] of exports) { + map.set(name, value.type); } + } - let namespace = '__remote'; - let uid = 1; - while (map.has(namespace)) namespace = `__remote${uid++}`; + let namespace = '__remote'; + let uid = 1; + while (map.has(namespace)) namespace = `__remote${uid++}`; - const exports = Array.from(map).map(([name, type]) => { - return `export const ${name} = ${namespace}.${type}('${remote.hash}/${name}');`; - }); - - let result = `import * as ${namespace} from '__sveltekit/remote';\n\n${exports.join('\n')}\n`; + const exports = Array.from(map).map(([name, type]) => { + return `export const ${name} = ${namespace}.${type}('${remote.hash}/${name}');`; + }); - if (dev_server) { - result += `\nimport.meta.hot?.accept();\n`; - } + let result = `import * as ${namespace} from '__sveltekit/remote';\n\n${exports.join('\n')}\n`; - return { - code: result - }; + if (dev_server) { + result += `\nimport.meta.hot?.accept();\n`; } + + return { + code: result + }; } }; @@ -866,7 +953,7 @@ async function kit({ svelte_config }) { // add entry points for every endpoint... manifest_data.routes.forEach((route) => { if (route.endpoint) { - const resolved = path.resolve(route.endpoint.file); + const resolved = path.resolve(root, route.endpoint.file); const relative = decodeURIComponent(path.relative(kit.files.routes, resolved)); const name = posixify(path.join('entries/endpoints', relative.replace(/\.js$/, ''))); input[name] = resolved; @@ -877,7 +964,7 @@ async function kit({ svelte_config }) { manifest_data.nodes.forEach((node) => { for (const file of [node.component, node.universal, node.server]) { if (file) { - const resolved = path.resolve(file); + const resolved = path.resolve(root, file); const relative = decodeURIComponent(path.relative(kit.files.routes, resolved)); const name = relative.startsWith('..') @@ -891,15 +978,15 @@ async function kit({ svelte_config }) { // ...and every matcher Object.entries(manifest_data.matchers).forEach(([key, file]) => { const name = posixify(path.join('entries/matchers', key)); - input[name] = path.resolve(file); + input[name] = path.resolve(root, file); }); // ...and the hooks files if (manifest_data.hooks.server) { - input['entries/hooks.server'] = path.resolve(manifest_data.hooks.server); + input['entries/hooks.server'] = path.resolve(root, manifest_data.hooks.server); } if (manifest_data.hooks.universal) { - input['entries/hooks.universal'] = path.resolve(manifest_data.hooks.universal); + input['entries/hooks.universal'] = path.resolve(root, manifest_data.hooks.universal); } // ...and the server instrumentation file @@ -1016,7 +1103,7 @@ async function kit({ svelte_config }) { * @see https://vitejs.dev/guide/api-plugin.html#configureserver */ async configureServer(vite) { - return await dev(vite, vite_config, svelte_config, () => remotes); + return await dev(vite, vite_config, svelte_config, () => remotes, root); }, /** @@ -1100,7 +1187,8 @@ async function kit({ svelte_config }) { prerendered: [], relative_path: '.', routes: manifest_data.routes, - remotes + remotes, + root })};\n` ); @@ -1115,7 +1203,8 @@ async function kit({ svelte_config }) { env: { ...env.private, ...env.public }, out, output_config: svelte_config.output, - remotes + remotes, + root }); build_metadata = metadata; @@ -1210,7 +1299,7 @@ async function kit({ svelte_config }) { * @param {boolean} [add_dynamic_css] */ const deps_of = (entry, add_dynamic_css = false) => - find_deps(client_manifest, posixify(path.relative('.', entry)), add_dynamic_css); + find_deps(client_manifest, posixify(path.relative(root, entry)), add_dynamic_css, root); if (svelte_config.kit.output.bundleStrategy === 'split') { const start = deps_of(`${runtime_directory}/client/entry.js`); @@ -1237,7 +1326,8 @@ async function kit({ svelte_config }) { const deps = deps_of(entry, true); const file = resolve_symlinks( client_manifest, - `${kit.outDir}/generated/client-optimized/nodes/${i}.js` + `${kit.outDir}/generated/client-optimized/nodes/${i}.js`, + root ).chunk.file; return { file, css: deps.stylesheets }; @@ -1301,7 +1391,8 @@ async function kit({ svelte_config }) { prerendered: [], relative_path: '.', routes: manifest_data.routes, - remotes + remotes, + root })};\n` ); @@ -1314,7 +1405,8 @@ async function kit({ svelte_config }) { client_manifest, assets_path, client_chunks, - svelte_config.kit.output + svelte_config.kit.output, + root ); // ...and prerender @@ -1324,7 +1416,8 @@ async function kit({ svelte_config }) { manifest_path, metadata, verbose, - env: { ...env.private, ...env.public } + env: { ...env.private, ...env.public }, + root }); // generate a new manifest that doesn't include prerendered pages @@ -1335,7 +1428,8 @@ async function kit({ svelte_config }) { prerendered: prerendered.paths, relative_path: '.', routes: manifest_data.routes.filter((route) => prerender_map.get(route.id) !== true), - remotes + remotes, + root })};\n` ); @@ -1408,13 +1502,9 @@ async function kit({ svelte_config }) { } }; - return [ - plugin_setup, - kit.experimental.remoteFunctions && plugin_remote, - plugin_virtual_modules, - plugin_guard, - plugin_compile - ].filter((p) => !!p); + return [plugin_setup, plugin_remote, plugin_virtual_modules, plugin_guard, plugin_compile].filter( + (p) => !!p + ); } /** diff --git a/packages/kit/src/exports/vite/static_analysis/index.js b/packages/kit/src/exports/vite/static_analysis/index.js index 6d4248032da5..015a58c45b84 100644 --- a/packages/kit/src/exports/vite/static_analysis/index.js +++ b/packages/kit/src/exports/vite/static_analysis/index.js @@ -1,3 +1,4 @@ +import path from 'node:path'; import { tsPlugin } from '@sveltejs/acorn-typescript'; import { Parser } from 'acorn'; import { read } from '../../../utils/filesystem.js'; @@ -213,11 +214,13 @@ function get_name(node) { /** * Reads and statically analyses a file for page options * @param {string} filepath + * @param {string} root The project root directory * @returns {PageOptions | null} Returns the page options for the file or `null` if unanalysable */ -export function get_page_options(filepath) { +export function get_page_options(filepath, root) { + const input = read(path.resolve(root, filepath)); + try { - const input = read(filepath); const page_options = statically_analyse_page_options(filepath, input); if (page_options === null) { return null; @@ -229,7 +232,10 @@ export function get_page_options(filepath) { } } -export function create_node_analyser() { +/** + * @param {string} root + */ +export function create_node_analyser(root) { const static_exports = new Map(); /** @@ -273,7 +279,7 @@ export function create_node_analyser() { } if (node.server) { - const server_page_options = get_page_options(node.server); + const server_page_options = get_page_options(node.server, root); if (server_page_options === null) { cache(key, null); return null; @@ -282,7 +288,7 @@ export function create_node_analyser() { } if (node.universal) { - const universal_page_options = get_page_options(node.universal); + const universal_page_options = get_page_options(node.universal, root); if (universal_page_options === null) { cache(key, null); return null; diff --git a/packages/kit/src/exports/vite/utils.js b/packages/kit/src/exports/vite/utils.js index 77743bda77c9..117ce6575c15 100644 --- a/packages/kit/src/exports/vite/utils.js +++ b/packages/kit/src/exports/vite/utils.js @@ -20,8 +20,9 @@ import { * Related to tsconfig path alias creation. * * @param {import('types').ValidatedKitConfig} config + * @param {string} root * */ -export function get_config_aliases(config) { +export function get_config_aliases(config, root) { /** @type {import('vite').Alias[]} */ const alias = [ // For now, we handle `$lib` specially here rather than make it a default value for @@ -38,16 +39,16 @@ export function get_config_aliases(config) { // Doing just `{ find: key.slice(0, -2) ,..}` would mean `import .. from "key"` would also be matched, which we don't want alias.push({ find: new RegExp(`^${escape_for_regexp(key.slice(0, -2))}\\/(.+)$`), - replacement: `${path.resolve(value)}/$1` + replacement: `${path.resolve(root, value)}/$1` }); } else if (key + '/*' in config.alias) { // key and key/* both exist -> the replacement for key needs to happen _only_ on import .. from "key" alias.push({ find: new RegExp(`^${escape_for_regexp(key)}$`), - replacement: path.resolve(value) + replacement: path.resolve(root, value) }); } else { - alias.push({ find: key, replacement: path.resolve(value) }); + alias.push({ find: key, replacement: path.resolve(root, value) }); } } diff --git a/packages/kit/src/exports/vite/utils.spec.js b/packages/kit/src/exports/vite/utils.spec.js index 90c9ee0c91e1..7878487573fe 100644 --- a/packages/kit/src/exports/vite/utils.spec.js +++ b/packages/kit/src/exports/vite/utils.spec.js @@ -18,7 +18,7 @@ test('transform kit.alias to resolve.alias', () => { } }); - const aliases = get_config_aliases(config.kit); + const aliases = get_config_aliases(config.kit, '.'); const transformed = aliases.map((entry) => { const replacement = posixify(path.relative('.', entry.replacement)); diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index 3394bb038070..2257328df8d4 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -223,9 +223,8 @@ let relative = (file) => file; if (DEV) { try { const path = await import('node:path'); - const process = await import('node:process'); - relative = (file) => path.relative(process.cwd(), file); + relative = (file) => path.relative('.', file); } catch { // do nothing } diff --git a/packages/kit/src/utils/import.js b/packages/kit/src/utils/import.js index 3ed96fa46484..da80140d9b19 100644 --- a/packages/kit/src/utils/import.js +++ b/packages/kit/src/utils/import.js @@ -1,16 +1,16 @@ -import process from 'node:process'; import fs from 'node:fs'; import path from 'node:path'; /** - * Resolves a peer dependency relative to the current CWD. Duplicated with `packages/adapter-auto` + * Resolves a peer dependency relative to the current working directory. Duplicated with `packages/adapter-auto` * @param {string} dependency + * @param {string} root */ -function resolve_peer(dependency) { +function resolve_peer(dependency, root) { let [name, ...parts] = dependency.split('/'); if (name[0] === '@') name += `/${parts.shift()}`; - let dir = process.cwd(); + let dir = root; while (!fs.existsSync(`${dir}/node_modules/${name}/package.json`)) { if (dir === (dir = path.dirname(dir))) { @@ -42,10 +42,11 @@ function resolve_peer(dependency) { * Resolve a dependency relative to the current working directory, * rather than relative to this package (but falls back to trying that, if necessary) * @param {string} dependency + * @param {string} root */ -export async function import_peer(dependency) { +export async function import_peer(dependency, root) { try { - return await import(resolve_peer(dependency)); + return await import(resolve_peer(dependency, root)); } catch { return await import(dependency); } diff --git a/packages/kit/src/version.spec.js b/packages/kit/src/version.spec.js index fa44692da9ff..fd4073523494 100644 --- a/packages/kit/src/version.spec.js +++ b/packages/kit/src/version.spec.js @@ -4,16 +4,18 @@ import { assert, describe, it } from 'vitest'; // runs the version generation as a side-effect of importing import '../scripts/generate-version.js'; +import { join } from 'node:path'; -describe('@sveltejs/kit VERSION', async () => { - it('should be the exact version from package.json'); - const { VERSION } = await import('./version.js'); - const pkg = JSON.parse( - readFileSync(fileURLToPath(new URL('../package.json', import.meta.url)), 'utf-8') - ); - assert.equal( - VERSION, - pkg.version, - 'VERSION export in src/version.js does not equal version in package.json' - ); +describe('@sveltejs/kit VERSION', () => { + it('should be the exact version from package.json', async () => { + const { VERSION } = await import(join(import.meta.dirname, 'version.js')); + const pkg = JSON.parse( + readFileSync(fileURLToPath(new URL('../package.json', import.meta.url)), 'utf-8') + ); + assert.equal( + VERSION, + pkg.version, + 'VERSION export in src/version.js does not equal version in package.json' + ); + }); }); diff --git a/packages/kit/test/apps/basics/svelte.config.js b/packages/kit/test/apps/basics/svelte.config.js index 8630e784a24d..61af6dff4fd5 100644 --- a/packages/kit/test/apps/basics/svelte.config.js +++ b/packages/kit/test/apps/basics/svelte.config.js @@ -72,6 +72,12 @@ const config = { router: { resolution: /** @type {'client' | 'server'} */ (process.env.ROUTER_RESOLUTION) || 'client' + }, + + typescript: { + config: (config) => { + config.include.push('../unit-test'); + } } }, diff --git a/packages/kit/test/apps/basics/vite.config.js b/packages/kit/test/apps/basics/vite.config.js index 7352ce2054df..415957fac6da 100644 --- a/packages/kit/test/apps/basics/vite.config.js +++ b/packages/kit/test/apps/basics/vite.config.js @@ -21,20 +21,12 @@ export default defineConfig({ }, test: { expect: { requireAssertions: true }, - projects: [ - { - extends: './vite.config.js', - test: { - name: 'client', - browser: { - enabled: true, - provider: playwright(), - instances: [{ browser: 'chromium' }], - headless: true - }, - include: ['unit-test/**/*.spec.js'] - } - } - ] + browser: { + enabled: true, + provider: playwright(), + instances: [{ browser: 'chromium' }], + headless: true + }, + include: ['unit-test/**/*.spec.js'] } }); diff --git a/packages/kit/test/build-errors/vitest.config.js b/packages/kit/test/build-errors/vitest.config.js new file mode 100644 index 000000000000..a15b3a470a6d --- /dev/null +++ b/packages/kit/test/build-errors/vitest.config.js @@ -0,0 +1,5 @@ +// we need this file to prevent Vitest from resolving a Vitest config from another directory + +import { defineConfig } from 'vitest/config'; + +export default defineConfig({}); diff --git a/packages/kit/test/prerendering/basics/globalSetup.js b/packages/kit/test/prerendering/basics/globalSetup.js index 3175a7093312..240a2e05bc8e 100644 --- a/packages/kit/test/prerendering/basics/globalSetup.js +++ b/packages/kit/test/prerendering/basics/globalSetup.js @@ -1,5 +1,5 @@ import { execSync } from 'node:child_process'; export default function setup() { - execSync('svelte-kit sync && pnpm run build'); + execSync('svelte-kit sync && pnpm run build', { cwd: import.meta.dirname }); } diff --git a/packages/kit/test/prerendering/basics/vite.config.js b/packages/kit/test/prerendering/basics/vite.config.js index 701326176284..253a50dc5b65 100644 --- a/packages/kit/test/prerendering/basics/vite.config.js +++ b/packages/kit/test/prerendering/basics/vite.config.js @@ -24,7 +24,7 @@ const config = { }, test: { - globalSetup: './globalSetup.js' + globalSetup: path.join(import.meta.dirname, 'globalSetup.js') } }; diff --git a/packages/package/vitest.config.js b/packages/package/vitest.config.js new file mode 100644 index 000000000000..a15b3a470a6d --- /dev/null +++ b/packages/package/vitest.config.js @@ -0,0 +1,5 @@ +// we need this file to prevent Vitest from resolving a Vitest config from another directory + +import { defineConfig } from 'vitest/config'; + +export default defineConfig({}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6457a939b64f..c5225c6244f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -134,6 +134,9 @@ importers: prettier-plugin-svelte: specifier: 'catalog:' version: 3.5.1(prettier@3.8.1)(svelte@5.53.5) + vitest: + specifier: 'catalog:' + version: 4.1.0-beta.4(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(@vitest/browser-playwright@4.1.0-beta.4)(vite@8.0.0-beta.15(@types/node@24.10.13)(esbuild@0.27.3)(jiti@2.4.2)(yaml@2.8.0)) packages/adapter-auto: devDependencies: diff --git a/scripts/sync-all.js b/scripts/sync-all.js index 08cf8bcf808c..b6d0e9876ce4 100644 --- a/scripts/sync-all.js +++ b/scripts/sync-all.js @@ -14,12 +14,12 @@ for (const directories of [ for (const dir of fs.readdirSync(directories)) { const cwd = path.join(directories, dir); - if (!fs.existsSync(path.join(cwd,'svelte.config.js'))) { + if (!fs.existsSync(path.join(cwd, 'svelte.config.js'))) { continue; } chdir(cwd); - syncAll(await load_config({ cwd }), 'development'); + syncAll(await load_config({ cwd }), 'development', cwd); } } diff --git a/tsconfig.json b/tsconfig.json index d7ccc88d75da..451d37bf30b3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,5 +6,5 @@ "target": "ESNext", "moduleResolution": "bundler" }, - "include": ["eslint.config.js", ".eslint/*.js", "scripts"] + "include": ["eslint.config.js", ".eslint/*.js", "scripts", "vitest.config.js"] } diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 000000000000..36b774f3b08c --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,28 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + projects: [ + 'packages/*', + // prevent Vitest from crawling nested Vite apps in the kit test directory + // which do not use Vitest but have a vite.config.js file + '!packages/kit', + { + extends: 'packages/kit/kit.vitest.config.js', + root: 'packages/kit' + }, + { + extends: 'packages/kit/test/apps/basics/vite.config.js', + root: 'packages/kit/test/apps/basics' + }, + 'packages/kit/test/build-errors', + { + extends: 'packages/kit/test/prerendering/basics/vite.config.js', + root: 'packages/kit/test/prerendering/basics', + test: { + name: 'kit-prerendering-basics' + } + } + ] + } +});