diff --git a/packages/kit/src/core/env.js b/packages/kit/src/core/env.js index a51c5948993b..835a3f78dbf1 100644 --- a/packages/kit/src/core/env.js +++ b/packages/kit/src/core/env.js @@ -1,4 +1,5 @@ import { GENERATED_COMMENT } from '../constants.js'; +import { dedent } from './sync/utils.js'; import { runtime_base } from './utils.js'; /** @@ -50,10 +51,13 @@ export function create_dynamic_module(type, dev_values) { export function create_static_types(id, env) { const declarations = Object.keys(env[id]) .filter((k) => valid_identifier.test(k)) - .map((k) => `\texport const ${k}: string;`) - .join('\n'); + .map((k) => `export const ${k}: string;`); - return `declare module '$env/static/${id}' {\n${declarations}\n}`; + return dedent` + declare module '$env/static/${id}' { + ${declarations.join('\n')} + } + `; } /** @@ -65,19 +69,24 @@ export function create_static_types(id, env) { export function create_dynamic_types(id, env, prefix) { const properties = Object.keys(env[id]) .filter((k) => valid_identifier.test(k)) - .map((k) => `\t\t${k}: string;`); + .map((k) => `${k}: string;`); const prefixed = `[key: \`${prefix}\${string}\`]`; if (id === 'private') { - properties.push(`\t\t${prefixed}: undefined;`); - properties.push(`\t\t[key: string]: string | undefined;`); + properties.push(`${prefixed}: undefined;`); + properties.push(`[key: string]: string | undefined;`); } else { - properties.push(`\t\t${prefixed}: string | undefined;`); + properties.push(`${prefixed}: string | undefined;`); } - const declaration = `export const env: {\n${properties.join('\n')}\n\t}`; - return `declare module '$env/dynamic/${id}' {\n\t${declaration}\n}`; + return dedent` + declare module '$env/dynamic/${id}' { + export const env: { + ${properties.join('\n')} + } + } + `; } export const reserved = new Set([ diff --git a/packages/kit/src/core/generate_manifest/index.js b/packages/kit/src/core/generate_manifest/index.js index 2e59d145113d..32410c28c0db 100644 --- a/packages/kit/src/core/generate_manifest/index.js +++ b/packages/kit/src/core/generate_manifest/index.js @@ -3,6 +3,7 @@ import { get_mime_lookup } from '../utils.js'; import { resolve_symlinks } from '../../exports/vite/build/utils.js'; import { compact } from '../../utils/array.js'; import { join_relative } from '../../utils/filesystem.js'; +import { dedent } from '../sync/utils.js'; /** * Generates the data used to write the server-side manifest.js file. This data is used in the Vite @@ -80,37 +81,41 @@ export function generate_manifest({ build_data, relative_path, routes }) { // prettier-ignore // String representation of /** @type {import('types').SSRManifest} */ - return `{ - appDir: ${s(build_data.app_dir)}, - appPath: ${s(build_data.app_path)}, - assets: new Set(${s(assets)}), - mimeTypes: ${s(get_mime_lookup(build_data.manifest_data))}, - _: { - client: ${s(build_data.client)}, - nodes: [ - ${(node_paths).map(loader).join(',\n\t\t\t\t')} - ], - routes: [ - ${routes.map(route => { - route.params.forEach(param => { - if (param.matcher) matchers.add(param.matcher); - }); + return dedent` + { + appDir: ${s(build_data.app_dir)}, + appPath: ${s(build_data.app_path)}, + assets: new Set(${s(assets)}), + mimeTypes: ${s(get_mime_lookup(build_data.manifest_data))}, + _: { + client: ${s(build_data.client)}, + nodes: [ + ${(node_paths).map(loader).join(',\n')} + ], + routes: [ + ${routes.map(route => { + route.params.forEach(param => { + if (param.matcher) matchers.add(param.matcher); + }); - if (!route.page && !route.endpoint) return; + if (!route.page && !route.endpoint) return; - return `{ - id: ${s(route.id)}, - 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'} - }`; - }).filter(Boolean).join(',\n\t\t\t\t')} - ], - matchers: async () => { - ${Array.from(matchers).map(type => `const { match: ${type} } = await import ('${(join_relative(relative_path, `/entries/matchers/${type}.js`))}')`).join('\n\t\t\t\t')} - return { ${Array.from(matchers).join(', ')} }; + return dedent` + { + id: ${s(route.id)}, + 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'} + } + `; + }).filter(Boolean).join(',\n')} + ], + matchers: async () => { + ${Array.from(matchers).map(type => `const { match: ${type} } = await import ('${(join_relative(relative_path, `/entries/matchers/${type}.js`))}')`).join('\n')} + return { ${Array.from(matchers).join(', ')} }; + } } } - }`.replace(/^\t/gm, ''); + `; } diff --git a/packages/kit/src/core/sync/utils.js b/packages/kit/src/core/sync/utils.js index f475e4b98a7f..0e0181366f9d 100644 --- a/packages/kit/src/core/sync/utils.js +++ b/packages/kit/src/core/sync/utils.js @@ -25,9 +25,46 @@ export function write(file, code) { fs.writeFileSync(file, code); } -/** @param {string} str */ -export function trim(str) { - const indentation = /** @type {RegExpExecArray} */ (/\n?([ \t]*)/.exec(str))[1]; - const pattern = new RegExp(`^${indentation}`, 'gm'); - return str.replace(pattern, '').trim(); +/** @type {WeakMap} */ +const dedent_map = new WeakMap(); + +/** + * Allows indenting template strings without the extra indentation ending up in the result. + * Still allows indentation of lines relative to one another in the template string. + * @param {TemplateStringsArray} strings + * @param {any[]} values + */ +export function dedent(strings, ...values) { + let dedented = dedent_map.get(strings); + + if (!dedented) { + const indentation = /** @type {RegExpExecArray} */ (/\n?([ \t]*)/.exec(strings[0]))[1]; + const pattern = new RegExp(`^${indentation}`, 'gm'); + + dedented = { + strings: strings.map((str) => str.replace(pattern, '')), + indents: [] + }; + + let current = '\n'; + + for (let i = 0; i < values.length; i += 1) { + const string = dedented.strings[i]; + const match = /\n([ \t]*)$/.exec(string); + + if (match) current = match[0]; + dedented.indents[i] = current; + } + + dedent_map.set(strings, dedented); + } + + let str = dedented.strings[0]; + for (let i = 0; i < values.length; i += 1) { + str += String(values[i]).replace(/\n/g, dedented.indents[i]) + dedented.strings[i + 1]; + } + + str = str.trim(); + + return str; } diff --git a/packages/kit/src/core/sync/write_client_manifest.js b/packages/kit/src/core/sync/write_client_manifest.js index 36158be6ad25..df98c8cd7fa2 100644 --- a/packages/kit/src/core/sync/write_client_manifest.js +++ b/packages/kit/src/core/sync/write_client_manifest.js @@ -1,6 +1,6 @@ import { relative_path, resolve_entry } from '../../utils/filesystem.js'; import { s } from '../../utils/misc.js'; -import { trim, write_if_changed } from './utils.js'; +import { dedent, write_if_changed } from './utils.js'; /** * Writes the client manifest to disk. The manifest is used to power the router. It contains the @@ -45,75 +45,79 @@ export function write_client_manifest(kit, manifest_data, output, metadata) { write_if_changed(`${output}/nodes/${i}.js`, generate_node(node)); return `() => import('./nodes/${i}')`; }) - .join(',\n\t'); + .join(',\n'); const layouts_with_server_load = new Set(); - const dictionary = `{ - ${manifest_data.routes - .map((route) => { - if (route.page) { - const errors = route.page.errors.slice(1).map((n) => n ?? ''); - const layouts = route.page.layouts.slice(1).map((n) => n ?? ''); - - while (layouts.at(-1) === '') layouts.pop(); - while (errors.at(-1) === '') errors.pop(); - - let leaf_has_server_load = false; - if (route.leaf) { - if (metadata) { - const i = /** @type {number} */ (indices.get(route.leaf)); - - leaf_has_server_load = metadata[i].has_server_load; - } else if (route.leaf.server) { - leaf_has_server_load = true; + const dictionary = dedent` + { + ${manifest_data.routes + .map((route) => { + if (route.page) { + const errors = route.page.errors.slice(1).map((n) => n ?? ''); + const layouts = route.page.layouts.slice(1).map((n) => n ?? ''); + + while (layouts.at(-1) === '') layouts.pop(); + while (errors.at(-1) === '') errors.pop(); + + let leaf_has_server_load = false; + if (route.leaf) { + if (metadata) { + const i = /** @type {number} */ (indices.get(route.leaf)); + + leaf_has_server_load = metadata[i].has_server_load; + } else if (route.leaf.server) { + leaf_has_server_load = true; + } } - } - // Encode whether or not the route uses server data - // using the ones' complement, to save space - const array = [`${leaf_has_server_load ? '~' : ''}${route.page.leaf}`]; + // Encode whether or not the route uses server data + // using the ones' complement, to save space + const array = [`${leaf_has_server_load ? '~' : ''}${route.page.leaf}`]; - // Encode whether or not the layout uses server data. - // It's a different method compared to pages because layouts - // are reused across pages, so we save space by doing it this way. - route.page.layouts.forEach((layout) => { - if (layout == undefined) return; + // Encode whether or not the layout uses server data. + // It's a different method compared to pages because layouts + // are reused across pages, so we save space by doing it this way. + route.page.layouts.forEach((layout) => { + if (layout == undefined) return; - let layout_has_server_load = false; + let layout_has_server_load = false; - if (metadata) { - layout_has_server_load = metadata[layout].has_server_load; - } else if (manifest_data.nodes[layout].server) { - layout_has_server_load = true; - } + if (metadata) { + layout_has_server_load = metadata[layout].has_server_load; + } else if (manifest_data.nodes[layout].server) { + layout_has_server_load = true; + } - if (layout_has_server_load) { - layouts_with_server_load.add(layout); - } - }); + if (layout_has_server_load) { + layouts_with_server_load.add(layout); + } + }); - // only include non-root layout/error nodes if they exist - if (layouts.length > 0 || errors.length > 0) array.push(`[${layouts.join(',')}]`); - if (errors.length > 0) array.push(`[${errors.join(',')}]`); + // only include non-root layout/error nodes if they exist + if (layouts.length > 0 || errors.length > 0) array.push(`[${layouts.join(',')}]`); + if (errors.length > 0) array.push(`[${errors.join(',')}]`); - return `${s(route.id)}: [${array.join(',')}]`; - } - }) - .filter(Boolean) - .join(',\n\t\t')} - }`.replace(/^\t/gm, ''); + return `${s(route.id)}: [${array.join(',')}]`; + } + }) + .filter(Boolean) + .join(',\n')} + } + `; const hooks_file = resolve_entry(kit.files.hooks.client); write_if_changed( `${output}/app.js`, - trim(` + dedent` ${hooks_file ? `import * as client_hooks from '${relative_path(output, hooks_file)}';` : ''} export { matchers } from './matchers.js'; - export const nodes = [${nodes}]; + export const nodes = [ + ${nodes} + ]; export const server_loads = [${[...layouts_with_server_load].join(',')}]; @@ -126,7 +130,7 @@ export function write_client_manifest(kit, manifest_data, output, metadata) { }; export { default as root } from '../root.svelte'; - `) + ` ); // write matchers to a separate module so that we don't diff --git a/packages/kit/src/core/sync/write_root.js b/packages/kit/src/core/sync/write_root.js index a82e2d00770f..7c5de637a04b 100644 --- a/packages/kit/src/core/sync/write_root.js +++ b/packages/kit/src/core/sync/write_root.js @@ -1,4 +1,4 @@ -import { trim, write_if_changed } from './utils.js'; +import { dedent, write_if_changed } from './utils.js'; /** * @param {import('types').ManifestData} manifest_data @@ -24,22 +24,20 @@ export function write_root(manifest_data, output) { let pyramid = ``; while (l--) { - pyramid = ` + pyramid = dedent` {#if constructors[${l + 1}]} - ${pyramid.replace(/\n/g, '\n\t\t\t\t\t')} + ${pyramid} {:else} {/if} - ` - .replace(/^\t\t\t/gm, '') - .trim(); + `; } write_if_changed( `${output}/root.svelte`, - trim(` + dedent` - ${pyramid.replace(/\n/g, '\n\t\t\t')} + ${pyramid} {#if mounted}
@@ -87,6 +85,6 @@ export function write_root(manifest_data, output) { {/if}
{/if} - `) + ` ); } 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 2fd8ec2f3052..285ce62eba51 100644 --- a/packages/kit/src/exports/vite/build/build_service_worker.js +++ b/packages/kit/src/exports/vite/build/build_service_worker.js @@ -1,5 +1,6 @@ import fs from 'node:fs'; import * as vite from 'vite'; +import { dedent } from '../../../core/sync/utils.js'; import { s } from '../../../utils/misc.js'; import { get_config_aliases } from '../utils.js'; import { assets_base } from './utils.js'; @@ -34,28 +35,26 @@ export async function build_service_worker( fs.writeFileSync( service_worker, - ` + dedent` export const build = [ ${Array.from(build) .map((file) => `${s(`${kit.paths.base}/${file}`)}`) - .join(',\n\t\t\t\t')} + .join(',\n')} ]; export const files = [ ${manifest_data.assets .filter((asset) => kit.serviceWorker.files(asset.file)) .map((asset) => `${s(`${kit.paths.base}/${asset.file}`)}`) - .join(',\n\t\t\t\t')} + .join(',\n')} ]; export const prerendered = [ - ${prerendered.paths.map((path) => s(path)).join(',\n\t\t\t\t')} + ${prerendered.paths.map((path) => s(path)).join(',\n')} ]; export const version = ${s(kit.version.name)}; ` - .replace(/^\t{3}/gm, '') - .trim() ); await vite.build({ diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 067f71cb6339..569064bb9a98 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -24,6 +24,7 @@ import prerender from '../../core/postbuild/prerender.js'; import analyse from '../../core/postbuild/analyse.js'; import { s } from '../../utils/misc.js'; import { hash } from '../../runtime/hash.js'; +import { dedent } from '../../core/sync/utils.js'; export { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; @@ -354,13 +355,16 @@ function kit({ svelte_config }) { switch (id) { case '\0$env/static/private': return create_static_module('$env/static/private', env.private); + case '\0$env/static/public': return create_static_module('$env/static/public', env.public); + case '\0$env/dynamic/private': return create_dynamic_module( 'private', vite_config_env.command === 'serve' ? env.private : undefined ); + case '\0$env/dynamic/public': // populate `$env/dynamic/public` from `window` if (browser) { @@ -371,6 +375,7 @@ function kit({ svelte_config }) { 'public', vite_config_env.command === 'serve' ? env.public : undefined ); + case '\0$service-worker': return create_service_worker_module(svelte_config); @@ -383,40 +388,47 @@ function kit({ svelte_config }) { // for the sake of things like Vitest which may import this module // outside the context of a page if (browser) { - return `export const base = ${global}?.base ?? ${s(base)}; -export const assets = ${global}?.assets ?? ${assets ? s(assets) : 'base'};`; + return dedent` + export const base = ${global}?.base ?? ${s(base)}; + export const assets = ${global}?.assets ?? ${assets ? s(assets) : 'base'}; + `; } - return `export let base = ${s(base)}; -export let assets = ${assets ? s(assets) : 'base'}; + return dedent` + export let base = ${s(base)}; + export let assets = ${assets ? s(assets) : 'base'}; -export const relative = ${svelte_config.kit.paths.relative}; + export const relative = ${svelte_config.kit.paths.relative}; -const initial = { base, assets }; + const initial = { base, assets }; -export function override(paths) { - base = paths.base; - assets = paths.assets; -} + export function override(paths) { + base = paths.base; + assets = paths.assets; + } -export function reset() { - base = initial.base; - assets = initial.assets; -} + export function reset() { + base = initial.base; + assets = initial.assets; + } -/** @param {string} path */ -export function set_assets(path) { - assets = initial.assets = path; -}`; + /** @param {string} path */ + export function set_assets(path) { + assets = initial.assets = path; + } + `; case '\0__sveltekit/environment': const { version } = svelte_config.kit; - return `export const version = ${s(version.name)}; -export let building = false; -export function set_building() { - building = true; -}`; + return dedent` + export const version = ${s(version.name)}; + export let building = false; + + export function set_building() { + building = true; + } + `; } } }; @@ -863,18 +875,18 @@ function find_overridden_config(config, resolved_config, enforced_config, path, /** * @param {import('types').ValidatedConfig} config */ -const create_service_worker_module = (config) => ` -if (typeof self === 'undefined' || self instanceof ServiceWorkerGlobalScope === false) { - throw new Error('This module can only be imported inside a service worker'); -} +const create_service_worker_module = (config) => dedent` + if (typeof self === 'undefined' || self instanceof ServiceWorkerGlobalScope === false) { + throw new Error('This module can only be imported inside a service worker'); + } -export const build = []; -export const files = [ - ${create_assets(config) - .filter((asset) => config.kit.serviceWorker.files(asset.file)) - .map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`) - .join(',\n\t\t\t\t')} -]; -export const prerendered = []; -export const version = ${s(config.kit.version.name)}; + export const build = []; + export const files = [ + ${create_assets(config) + .filter((asset) => config.kit.serviceWorker.files(asset.file)) + .map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`) + .join(',\n')} + ]; + export const prerendered = []; + export const version = ${s(config.kit.version.name)}; `;