From 1ec306443cea92f8097a7b12d9da9bfedd431bda Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Sat, 16 Jul 2022 21:28:49 -0600 Subject: [PATCH 01/95] feat: Add env config object --- packages/kit/src/core/config/options.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js index b19a3fa842bc..92718f71f8df 100644 --- a/packages/kit/src/core/config/options.js +++ b/packages/kit/src/core/config/options.js @@ -123,6 +123,10 @@ const options = object( (keypath) => `${keypath} has been renamed to config.kit.moduleExtensions` ), + env: object({ + publicPrefix: string('PUBLIC_') + }), + files: object({ assets: string('static'), hooks: string(join('src', 'hooks')), From 2b5e0c45d396bdfc6fdf7c0641b696efc2f99cc2 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Sat, 16 Jul 2022 21:31:49 -0600 Subject: [PATCH 02/95] feat: Added types --- packages/kit/types/index.d.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 97ff605ca3ec..bf9ea4322140 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -107,6 +107,9 @@ export interface KitConfig { directives?: CspDirectives; reportOnly?: CspDirectives; }; + env?: { + publicPrefix: string; + }; moduleExtensions?: string[]; files?: { assets?: string; From 91d04135630ea4100ac120848079fa65226943e5 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Sun, 17 Jul 2022 21:26:29 -0600 Subject: [PATCH 03/95] feat: Add .d.ts import to tsconfig --- packages/kit/src/core/sync/write_tsconfig.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js index 02bfa06a0c73..4956b89f3d38 100644 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ b/packages/kit/src/core/sync/write_tsconfig.js @@ -36,6 +36,7 @@ export function write_tsconfig(config, cwd = process.cwd()) { include.push(config_relative(`${dir}/**/*.ts`)); include.push(config_relative(`${dir}/**/*.svelte`)); }); + include.push('types/ambient.d.ts'); /** @type {Record} */ const paths = {}; From 12a3fd1128d89a5f21223b31c68c9790171700f4 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Sun, 17 Jul 2022 23:14:59 -0600 Subject: [PATCH 04/95] feat: Generate env var dump code --- packages/kit/src/core/sync/sync.js | 2 + packages/kit/src/core/sync/utils.js | 29 +++++ packages/kit/src/core/sync/write_env.js | 122 +++++++++++++++++++ packages/kit/src/core/sync/write_tsconfig.js | 2 +- 4 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 packages/kit/src/core/sync/write_env.js diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js index b88af92c7a0b..71d0555e127f 100644 --- a/packages/kit/src/core/sync/sync.js +++ b/packages/kit/src/core/sync/sync.js @@ -6,6 +6,7 @@ import { write_matchers } from './write_matchers.js'; import { write_root } from './write_root.js'; import { write_tsconfig } from './write_tsconfig.js'; import { write_types } from './write_types.js'; +import { write_env } from './write_env'; /** @param {import('types').ValidatedConfig} config */ export function init(config) { @@ -24,6 +25,7 @@ export function update(config) { write_root(manifest_data, output); write_matchers(manifest_data, output); write_types(config, manifest_data); + write_env(config.kit); return { manifest_data }; } diff --git a/packages/kit/src/core/sync/utils.js b/packages/kit/src/core/sync/utils.js index be1862cd1927..a5b7c3c8cb40 100644 --- a/packages/kit/src/core/sync/utils.js +++ b/packages/kit/src/core/sync/utils.js @@ -31,3 +31,32 @@ export function trim(str) { const pattern = new RegExp(`^${indentation}`, 'gm'); return str.replace(pattern, '').trim(); } + +/** + * Adapted from https://github.com/joliss/js-string-escape/blob/master/index.js + * @param {string} str + * @returns + */ +export function escape(str) { + return str.replace(/["'\\\n\r\u2028\u2029]/g, (character) => { + // Escape all characters not included in SingleStringCharacters and + // DoubleStringCharacters on + // http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4 + switch (character) { + case '"': + case "'": + case '\\': + return '\\' + character; + // Four possible LineTerminator characters need to be escaped: + case '\n': + return '\\n'; + case '\r': + return '\\r'; + case '\u2028': + return '\\u2028'; + case '\u2029': + return '\\u2029'; + } + return ''; + }); +} diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js new file mode 100644 index 000000000000..fbb96fb14bf4 --- /dev/null +++ b/packages/kit/src/core/sync/write_env.js @@ -0,0 +1,122 @@ +import { write_if_changed } from './utils'; +import path from 'path'; +import { escape } from './utils'; +import fs from 'fs'; + +const autogen_comment = '// this section is auto-generated'; + +/** + * @param {boolean} pub + * @param {Record} env + * @returns {string} + */ +function const_declaration_template(pub, env) { + return `${autogen_comment} +${ + pub + ? '' + : `if (!import.meta.env.SSR) { + throw new Error('Cannot import $app/env/private into client-side code'); +} +` +} +${Object.entries(env) + .map( + ([k, v]) => `/** + * @type {import('$app/env${pub ? '' : '/private'}').${k}} + */ +export const ${k} = "${escape(v)}"; +` + ) + .join('\n')} +`; +} + +/** + * @param {boolean} pub + * @param {Record} env + * @returns {string} + */ +function type_declaration_template(pub, env) { + return `declare module '$app/env${pub ? '' : '/private'}' { + ${Object.entries(env) + .map(([k]) => `export const ${k}: string;`) + .join('\n ')} +}`; +} + +/** + * Writes the existing environment variables in process.env to + * $app/env and $app/env/public + * @param {import('types').ValidatedKitConfig} config + */ +export function write_env(config) { + const pub_out = path.join(config.outDir, 'runtime/app/env.js'); + const priv_out = path.join(config.outDir, 'runtime/app/env/private.js'); + const type_declaration_out = path.join(config.outDir, 'types/ambient.d.ts'); + + // public is a little difficult since we append to an + // already-existing file + const pub = resolve_public_env(config.env.publicPrefix); + let pub_content = const_declaration_template(true, pub); + const old_pub_content = fs.readFileSync(pub_out).toString(); + const autogen_content_start = old_pub_content.indexOf(autogen_comment); + if (autogen_content_start === -1) { + pub_content = old_pub_content + '\n' + pub_content; + } else { + pub_content = old_pub_content.slice(0, autogen_content_start) + '\n' + pub_content; + } + write_if_changed(pub_out, pub_content); + + // private is easy since it has its own file: just write if changed + const priv = resolve_private_env(config.env.publicPrefix); + const priv_content = const_declaration_template(false, priv); + write_if_changed(priv_out, priv_content); + + const pub_declaration = type_declaration_template(true, pub); + const priv_declaration = type_declaration_template(false, priv); + write_if_changed(type_declaration_out, `${priv_declaration}\n${pub_declaration}`); +} + +/** + * Generate a Record of private environment variables + * (those that do not begin with public_prefix) + * @param {string} public_prefix + * @returns {Record} + */ +function resolve_private_env(public_prefix) { + return resolve_env(false, public_prefix); +} + +/** + * Generate a Record of public environment variables + * (those that do begin with public_prefix) + * @param {string} public_prefix + * @returns {Record} + */ +function resolve_public_env(public_prefix) { + return resolve_env(true, public_prefix); +} + +/** + * Generate a Record containing environment + * variable entries. + * @param {boolean} pub + * @param {string} public_prefix + * @returns {Record} + */ +function resolve_env(pub, public_prefix) { + /** @type {Record} */ + const resolved = {}; + + // I'm smart enough to do this with reduce, + // but I'm also smart enough not to force + // someone else to read this with reduce later + Object.entries(process.env) + .filter(([k]) => (pub ? k.startsWith(public_prefix) : !k.startsWith(public_prefix))) + .forEach(([k, v]) => { + resolved[k] = v ?? ''; + }); + + return resolved; +} diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js index 4956b89f3d38..bb0fe540ce1b 100644 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ b/packages/kit/src/core/sync/write_tsconfig.js @@ -79,7 +79,7 @@ export function write_tsconfig(config, cwd = process.cwd()) { target: 'esnext' }, include, - exclude: [config_relative('node_modules/**'), './**'] + exclude: [config_relative('node_modules/**'), './[!types/ambient.d.ts]**'] }, null, '\t' From 498d4f229ee3c538254c08589c2808a8bf8319de Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Wed, 20 Jul 2022 23:13:59 -0600 Subject: [PATCH 05/95] fix: imports --- packages/kit/src/core/sync/sync.js | 2 +- packages/kit/src/core/sync/write_env.js | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js index 71d0555e127f..3bf25dbeb79c 100644 --- a/packages/kit/src/core/sync/sync.js +++ b/packages/kit/src/core/sync/sync.js @@ -6,7 +6,7 @@ import { write_matchers } from './write_matchers.js'; import { write_root } from './write_root.js'; import { write_tsconfig } from './write_tsconfig.js'; import { write_types } from './write_types.js'; -import { write_env } from './write_env'; +import { write_env } from './write_env.js'; /** @param {import('types').ValidatedConfig} config */ export function init(config) { diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js index fbb96fb14bf4..2b88d4738fda 100644 --- a/packages/kit/src/core/sync/write_env.js +++ b/packages/kit/src/core/sync/write_env.js @@ -1,6 +1,6 @@ -import { write_if_changed } from './utils'; +import { write_if_changed } from './utils.js'; import path from 'path'; -import { escape } from './utils'; +import { escape } from './utils.js'; import fs from 'fs'; const autogen_comment = '// this section is auto-generated'; @@ -59,12 +59,14 @@ export function write_env(config) { // already-existing file const pub = resolve_public_env(config.env.publicPrefix); let pub_content = const_declaration_template(true, pub); - const old_pub_content = fs.readFileSync(pub_out).toString(); - const autogen_content_start = old_pub_content.indexOf(autogen_comment); - if (autogen_content_start === -1) { - pub_content = old_pub_content + '\n' + pub_content; - } else { - pub_content = old_pub_content.slice(0, autogen_content_start) + '\n' + pub_content; + if (fs.existsSync(pub_out)) { + const old_pub_content = fs.readFileSync(pub_out).toString(); + const autogen_content_start = old_pub_content.indexOf(autogen_comment); + if (autogen_content_start === -1) { + pub_content = old_pub_content + '\n' + pub_content; + } else { + pub_content = old_pub_content.slice(0, autogen_content_start) + '\n' + pub_content; + } } write_if_changed(pub_out, pub_content); From 08600dfdd8cda36cfb865b5fd99f7f05b30dd355 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 21 Jul 2022 00:27:44 -0600 Subject: [PATCH 06/95] fix: Remove old error mechanism --- packages/kit/src/core/sync/write_env.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js index 2b88d4738fda..434f590e2832 100644 --- a/packages/kit/src/core/sync/write_env.js +++ b/packages/kit/src/core/sync/write_env.js @@ -12,14 +12,7 @@ const autogen_comment = '// this section is auto-generated'; */ function const_declaration_template(pub, env) { return `${autogen_comment} -${ - pub - ? '' - : `if (!import.meta.env.SSR) { - throw new Error('Cannot import $app/env/private into client-side code'); -} -` -} + ${Object.entries(env) .map( ([k, v]) => `/** From d5baa479cd7d481ee0e4d451eeac125d724215dc Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 21 Jul 2022 01:14:21 -0600 Subject: [PATCH 07/95] feat: Rough edges of server-side module protection --- packages/kit/src/vite/dev/index.js | 47 +++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/vite/dev/index.js b/packages/kit/src/vite/dev/index.js index b118afc23070..4a088be97402 100644 --- a/packages/kit/src/vite/dev/index.js +++ b/packages/kit/src/vite/dev/index.js @@ -60,6 +60,10 @@ export async function dev(vite, vite_config, svelte_config) { await vite.ssrLoadModule(url) ); + const node = await vite.moduleGraph.getModuleByUrl(url); + if (!node) throw new Error(`Could not find node for ${url}`); + throw_if_illegal_private_import(node); + return { module, index, @@ -68,10 +72,6 @@ export async function dev(vite, vite_config, svelte_config) { stylesheets: [], // in dev we inline all styles to avoid FOUC inline_styles: async () => { - const node = await vite.moduleGraph.getModuleByUrl(url); - - if (!node) throw new Error(`Could not find node for ${url}`); - const deps = new Set(); await find_deps(vite, node, deps); @@ -465,3 +465,42 @@ function has_correct_case(file, assets) { return false; } + +const illegal_import_names /** @type {Array} */ = [ + '.svelte-kit/runtime/app/env/private.js' +]; + +/** + * Mmmm, spicy recursion. + * Using a depth-first search rather than a breadth-first search because it makes + * making a pretty error easier + * @param {import('vite').ModuleNode} node + * @param {Array} illegal_module_stack + */ +function throw_if_illegal_private_import_recursive(node, illegal_module_stack = []) { + if (node.importedModules.size === 0) { + return; + } + illegal_module_stack.push(node.file ?? 'unknown'); + node.importedModules.forEach((childNode) => { + if (illegal_import_names.some((name) => childNode.file?.endsWith(name))) { + illegal_module_stack.push((childNode?.file ?? 'unknown') + ' (server-side only module)'); + const stringified_stack = illegal_module_stack + .map((mod, i) => ` ${i}: ${mod}`) + .join(', which imports:\n'); + throw new Error( + `Found an illegal import originating from: ${illegal_module_stack[0]}. It imports:\n${stringified_stack}` + ); + } + throw_if_illegal_private_import_recursive(childNode, illegal_module_stack); + }); + illegal_module_stack.pop(); +} + +/** + * Throw an error if a private module is imported from a client-side node. + * @param {import('vite').ModuleNode} node + */ +function throw_if_illegal_private_import(node) { + throw_if_illegal_private_import_recursive(node); +} From 87b3760a8f89b06d220dca1728221e49c69e7b34 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 21 Jul 2022 13:32:50 -0600 Subject: [PATCH 08/95] fix: Performance and add Vite overlay --- packages/kit/src/vite/dev/index.js | 46 ++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/packages/kit/src/vite/dev/index.js b/packages/kit/src/vite/dev/index.js index 4a088be97402..225b76d1f29d 100644 --- a/packages/kit/src/vite/dev/index.js +++ b/packages/kit/src/vite/dev/index.js @@ -62,7 +62,7 @@ export async function dev(vite, vite_config, svelte_config) { const node = await vite.moduleGraph.getModuleByUrl(url); if (!node) throw new Error(`Could not find node for ${url}`); - throw_if_illegal_private_import(node); + throw_if_illegal_private_import(vite.ws, node); return { module, @@ -474,33 +474,63 @@ const illegal_import_names /** @type {Array} */ = [ * Mmmm, spicy recursion. * Using a depth-first search rather than a breadth-first search because it makes * making a pretty error easier + * + * It uses a set to prevent import cycles and a list to keep track of the current import + * "stack". This (roughly) doubles the memory footprint, but it's unlikely to be large, + * and it prevents the O(n) operation of having to check Array.prototype.includes. + * @param {import('vite').WebSocketServer} ws * @param {import('vite').ModuleNode} node * @param {Array} illegal_module_stack + * @param {Set} illegal_module_set */ -function throw_if_illegal_private_import_recursive(node, illegal_module_stack = []) { - if (node.importedModules.size === 0) { +function throw_if_illegal_private_import_recursive( + ws, + node, + illegal_module_stack = [], + illegal_module_set = new Set() +) { + const file = node.file ?? 'unknown'; + // This prevents cyclical imports creating a stack overflow + if (illegal_module_set.has(file) || node.importedModules.size === 0) { return; } - illegal_module_stack.push(node.file ?? 'unknown'); + illegal_module_set.add(file); + illegal_module_stack.push(file); node.importedModules.forEach((childNode) => { if (illegal_import_names.some((name) => childNode.file?.endsWith(name))) { illegal_module_stack.push((childNode?.file ?? 'unknown') + ' (server-side only module)'); const stringified_stack = illegal_module_stack .map((mod, i) => ` ${i}: ${mod}`) .join(', which imports:\n'); - throw new Error( + const err = new Error( `Found an illegal import originating from: ${illegal_module_stack[0]}. It imports:\n${stringified_stack}` ); + ws.send({ + type: 'error', + err: { + message: err.message, + stack: err.stack ?? '', + plugin: 'vite-plugin-svelte', + id: file + } + }); } - throw_if_illegal_private_import_recursive(childNode, illegal_module_stack); + throw_if_illegal_private_import_recursive( + ws, + childNode, + illegal_module_stack, + illegal_module_set + ); }); + illegal_module_set.delete(file); illegal_module_stack.pop(); } /** * Throw an error if a private module is imported from a client-side node. + * @param {import('vite').WebSocketServer} ws * @param {import('vite').ModuleNode} node */ -function throw_if_illegal_private_import(node) { - throw_if_illegal_private_import_recursive(node); +function throw_if_illegal_private_import(ws, node) { + throw_if_illegal_private_import_recursive(ws, node); } From 27b7cee62801eeed4d4dd26f1bcc7fe2f833a6e7 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 21 Jul 2022 17:10:34 -0600 Subject: [PATCH 09/95] feat: First full draft of static environment vars --- packages/kit/src/vite/dev/index.js | 78 ++------------- packages/kit/src/vite/index.js | 19 +++- packages/kit/src/vite/utils.js | 154 +++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 72 deletions(-) diff --git a/packages/kit/src/vite/dev/index.js b/packages/kit/src/vite/dev/index.js index 225b76d1f29d..d6a247b4a6c5 100644 --- a/packages/kit/src/vite/dev/index.js +++ b/packages/kit/src/vite/dev/index.js @@ -12,7 +12,11 @@ import { load_template } from '../../core/config/index.js'; import { SVELTE_KIT_ASSETS } from '../../core/constants.js'; import * as sync from '../../core/sync/sync.js'; import { get_mime_lookup, get_runtime_prefix } from '../../core/utils.js'; -import { resolve_entry } from '../utils.js'; +import { + resolve_entry, + web_socket_server_to_error_handler, + throw_if_illegal_private_import_vite +} from '../utils.js'; // Vite doesn't expose this so we just copy the list for now // https://github.com/vitejs/vite/blob/3edd1af56e980aef56641a5a51cf2932bb580d41/packages/vite/src/node/plugins/css.ts#L96 @@ -62,7 +66,8 @@ export async function dev(vite, vite_config, svelte_config) { const node = await vite.moduleGraph.getModuleByUrl(url); if (!node) throw new Error(`Could not find node for ${url}`); - throw_if_illegal_private_import(vite.ws, node); + const error_handler = web_socket_server_to_error_handler(vite.ws); + throw_if_illegal_private_import_vite(error_handler, node); return { module, @@ -465,72 +470,3 @@ function has_correct_case(file, assets) { return false; } - -const illegal_import_names /** @type {Array} */ = [ - '.svelte-kit/runtime/app/env/private.js' -]; - -/** - * Mmmm, spicy recursion. - * Using a depth-first search rather than a breadth-first search because it makes - * making a pretty error easier - * - * It uses a set to prevent import cycles and a list to keep track of the current import - * "stack". This (roughly) doubles the memory footprint, but it's unlikely to be large, - * and it prevents the O(n) operation of having to check Array.prototype.includes. - * @param {import('vite').WebSocketServer} ws - * @param {import('vite').ModuleNode} node - * @param {Array} illegal_module_stack - * @param {Set} illegal_module_set - */ -function throw_if_illegal_private_import_recursive( - ws, - node, - illegal_module_stack = [], - illegal_module_set = new Set() -) { - const file = node.file ?? 'unknown'; - // This prevents cyclical imports creating a stack overflow - if (illegal_module_set.has(file) || node.importedModules.size === 0) { - return; - } - illegal_module_set.add(file); - illegal_module_stack.push(file); - node.importedModules.forEach((childNode) => { - if (illegal_import_names.some((name) => childNode.file?.endsWith(name))) { - illegal_module_stack.push((childNode?.file ?? 'unknown') + ' (server-side only module)'); - const stringified_stack = illegal_module_stack - .map((mod, i) => ` ${i}: ${mod}`) - .join(', which imports:\n'); - const err = new Error( - `Found an illegal import originating from: ${illegal_module_stack[0]}. It imports:\n${stringified_stack}` - ); - ws.send({ - type: 'error', - err: { - message: err.message, - stack: err.stack ?? '', - plugin: 'vite-plugin-svelte', - id: file - } - }); - } - throw_if_illegal_private_import_recursive( - ws, - childNode, - illegal_module_stack, - illegal_module_set - ); - }); - illegal_module_set.delete(file); - illegal_module_stack.pop(); -} - -/** - * Throw an error if a private module is imported from a client-side node. - * @param {import('vite').WebSocketServer} ws - * @param {import('vite').ModuleNode} node - */ -function throw_if_illegal_private_import(ws, node) { - throw_if_illegal_private_import_recursive(ws, node); -} diff --git a/packages/kit/src/vite/index.js b/packages/kit/src/vite/index.js index c170cc9984f0..b94f02954055 100644 --- a/packages/kit/src/vite/index.js +++ b/packages/kit/src/vite/index.js @@ -14,7 +14,7 @@ import { generate_manifest } from '../core/generate_manifest/index.js'; import { get_runtime_directory, logger } from '../core/utils.js'; import { find_deps, get_default_config as get_default_build_config } from './build/utils.js'; import { preview } from './preview/index.js'; -import { get_aliases, resolve_entry } from './utils.js'; +import { get_aliases, resolve_entry, throw_if_illegal_private_import_rollup } from './utils.js'; const cwd = process.cwd(); @@ -267,6 +267,23 @@ function kit() { * then use this hook to kick off builds for the server and service worker. */ async writeBundle(_options, bundle) { + for (const id of this.getModuleIds()) { + const found = manifest_data.components.find((comp) => id.endsWith(comp)); + if (found) { + const module_info = this.getModuleInfo(id); + if (module_info === null) { + throw new Error(`Failed to locate module info for ${module_info}`); + } + throw_if_illegal_private_import_rollup( + this.getModuleInfo.bind(this), + (id, err) => { + throw err; + }, + module_info + ); + } + } + log = logger({ verbose: vite_config.logLevel === 'info' }); diff --git a/packages/kit/src/vite/utils.js b/packages/kit/src/vite/utils.js index 1a7eafaf92f0..29ea893c09d6 100644 --- a/packages/kit/src/vite/utils.js +++ b/packages/kit/src/vite/utils.js @@ -134,3 +134,157 @@ export function resolve_entry(entry) { return null; } + +/** + * Derive an error handler from a web socket server. + * @param {import('vite').WebSocketServer} ws + */ +export function web_socket_server_to_error_handler(ws) { + /** @type {(id: string, err: Error) => void} */ + return (id, err) => + ws.send({ + type: 'error', + err: { + message: err.message, + stack: err.stack ?? '', + plugin: 'vite-plugin-svelte', + id + } + }); +} + +const illegal_import_names /** @type {Array} */ = [ + '.svelte-kit/runtime/app/env/private.js' +]; + +/** + * Create a formatted error for an illegal import. + * @param {string} error_module_id + * @param {Array} stack + * @param {(id: string, err: Error) => void} error_handler + */ +function handle_illegal_import_error(error_module_id, stack, error_handler) { + stack.push(error_module_id + ' (server-side only module)'); + const stringified_stack = stack.map((mod, i) => ` ${i}: ${mod}`).join(', which imports:\n'); + const err = new Error( + `Found an illegal import originating from: ${stack[0]}. It imports:\n${stringified_stack}` + ); + error_handler(error_module_id, err); + return; +} + +/** + * Recurse through the ModuleNode graph, throwing if the module + * imports a private module. + * @param {(id: string, err: Error) => void} error_handler + * @param {import('vite').ModuleNode} node + * @param {Array} illegal_module_stack + * @param {Set} illegal_module_set + */ +function throw_if_illegal_private_import_recursive_vite( + node, + error_handler = (id, err) => { + throw err; + }, + illegal_module_stack = [], + illegal_module_set = new Set() +) { + const file = node.file ?? 'unknown'; + // This prevents cyclical imports creating a stack overflow + if (illegal_module_set.has(file) || node.importedModules.size === 0) { + return; + } + illegal_module_set.add(file); + illegal_module_stack.push(file); + node.importedModules.forEach((childNode) => { + if (illegal_import_names.some((name) => childNode.file?.endsWith(name))) { + handle_illegal_import_error( + childNode?.file ?? 'unknown', + illegal_module_stack, + error_handler + ); + return; + } + throw_if_illegal_private_import_recursive_vite( + childNode, + error_handler, + illegal_module_stack, + illegal_module_set + ); + }); + illegal_module_set.delete(file); + illegal_module_stack.pop(); +} + +/** + * Throw an error if a private module is imported from a client-side node. + * @param {(id: string, err: Error) => void} error_handler + * @param {import('vite').ModuleNode} node + */ +export function throw_if_illegal_private_import_vite(error_handler, node) { + throw_if_illegal_private_import_recursive_vite(node, error_handler); +} + +/** + * Recurse through the ModuleInfo graph, throwing if the module + * imports a private module. + * @param {import('rollup').GetModuleInfo} node_getter + * @param {import('rollup').ModuleInfo} node + * @param {(id: string, err: Error) => void} error_handler + * @param {Array} illegal_module_stack + * @param {Set} illegal_module_set + */ +function throw_if_illegal_private_import_recursive_rollup( + node_getter, + node, + error_handler = (id, err) => { + throw err; + }, + illegal_module_stack = [], + illegal_module_set = new Set() +) { + const id = node.id; + // This prevents cyclical imports creating a stack overflow + if ( + illegal_module_set.has(id) || + (node.importedIds.length === 0 && node.dynamicImporters.length === 0) + ) { + return; + } + illegal_module_set.add(id); + illegal_module_stack.push(id); + /** @type {(child_id: string) => void} */ + const recursiveChildNodeHandler = (childId) => { + const childNode = node_getter(childId); + if (childNode === null) { + const err = new Error(`Failed to find module info for ${childId}`); + error_handler(childId, err); + return; + } + if (illegal_import_names.some((name) => childId.endsWith(name))) { + handle_illegal_import_error(childId, illegal_module_stack, error_handler); + return; + } + throw_if_illegal_private_import_recursive_rollup( + node_getter, + childNode, + error_handler, + illegal_module_stack, + illegal_module_set + ); + }; + node.importedIds.forEach(recursiveChildNodeHandler); + node.dynamicallyImportedIds.forEach(recursiveChildNodeHandler); + illegal_module_set.delete(id); + illegal_module_stack.pop(); +} + +/** + * Throw an error if a private module is imported from a client-side node. + * @param {import('rollup').GetModuleInfo} node_getter + * @param {(id: string, err: Error) => void} error_handler + * @param {import('rollup').ModuleInfo} module_info + */ +export function throw_if_illegal_private_import_rollup(node_getter, error_handler, module_info) { + throw_if_illegal_private_import_recursive_rollup(node_getter, module_info, error_handler); +} From 42ac833a2c0aa25c4095d0073beb011076f724dc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 21 Jul 2022 19:28:44 -0400 Subject: [PATCH 10/95] fix unit tests --- packages/kit/src/core/config/index.spec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js index ad9973ec12dd..1467c74bb779 100644 --- a/packages/kit/src/core/config/index.spec.js +++ b/packages/kit/src/core/config/index.spec.js @@ -73,6 +73,9 @@ const get_defaults = (prefix = '') => ({ reportOnly: directive_defaults }, endpointExtensions: undefined, + env: { + publicPrefix: 'PUBLIC_' + }, files: { assets: join(prefix, 'static'), hooks: join(prefix, 'src/hooks'), From 6522e56f7021d5408f554f45abecddec6bbe67b0 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 21 Jul 2022 18:24:46 -0600 Subject: [PATCH 11/95] fix: Rich is smarter than I am --- packages/kit/src/core/sync/utils.js | 29 ------------------------- packages/kit/src/core/sync/write_env.js | 7 +++--- 2 files changed, 3 insertions(+), 33 deletions(-) diff --git a/packages/kit/src/core/sync/utils.js b/packages/kit/src/core/sync/utils.js index a5b7c3c8cb40..be1862cd1927 100644 --- a/packages/kit/src/core/sync/utils.js +++ b/packages/kit/src/core/sync/utils.js @@ -31,32 +31,3 @@ export function trim(str) { const pattern = new RegExp(`^${indentation}`, 'gm'); return str.replace(pattern, '').trim(); } - -/** - * Adapted from https://github.com/joliss/js-string-escape/blob/master/index.js - * @param {string} str - * @returns - */ -export function escape(str) { - return str.replace(/["'\\\n\r\u2028\u2029]/g, (character) => { - // Escape all characters not included in SingleStringCharacters and - // DoubleStringCharacters on - // http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4 - switch (character) { - case '"': - case "'": - case '\\': - return '\\' + character; - // Four possible LineTerminator characters need to be escaped: - case '\n': - return '\\n'; - case '\r': - return '\\r'; - case '\u2028': - return '\\u2028'; - case '\u2029': - return '\\u2029'; - } - return ''; - }); -} diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js index 434f590e2832..f8a322be9bbe 100644 --- a/packages/kit/src/core/sync/write_env.js +++ b/packages/kit/src/core/sync/write_env.js @@ -1,6 +1,5 @@ import { write_if_changed } from './utils.js'; import path from 'path'; -import { escape } from './utils.js'; import fs from 'fs'; const autogen_comment = '// this section is auto-generated'; @@ -18,7 +17,7 @@ ${Object.entries(env) ([k, v]) => `/** * @type {import('$app/env${pub ? '' : '/private'}').${k}} */ -export const ${k} = "${escape(v)}"; +export const ${k} = "${JSON.stringify(v)}"; ` ) .join('\n')} @@ -32,8 +31,8 @@ export const ${k} = "${escape(v)}"; */ function type_declaration_template(pub, env) { return `declare module '$app/env${pub ? '' : '/private'}' { - ${Object.entries(env) - .map(([k]) => `export const ${k}: string;`) + ${Object.keys(env) + .map((k) => `export const ${k}: string;`) .join('\n ')} }`; } From 736e1f3c7b0666b588a6a97f3f8251d6d0713f50 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 21 Jul 2022 19:35:01 -0600 Subject: [PATCH 12/95] feat: Let know about Vite mode --- packages/kit/src/core/sync/sync.js | 33 ++++++++++++++++++------- packages/kit/src/core/sync/write_env.js | 31 +++++++++++++++-------- packages/kit/src/vite/dev/index.js | 4 +-- packages/kit/src/vite/index.js | 2 +- 4 files changed, 48 insertions(+), 22 deletions(-) diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js index 3bf25dbeb79c..bf03872647ad 100644 --- a/packages/kit/src/core/sync/sync.js +++ b/packages/kit/src/core/sync/sync.js @@ -8,14 +8,23 @@ import { write_tsconfig } from './write_tsconfig.js'; import { write_types } from './write_types.js'; import { write_env } from './write_env.js'; -/** @param {import('types').ValidatedConfig} config */ -export function init(config) { +/** + * Initialize SvelteKit's generated files. + * @param {import('types').ValidatedConfig} config + * @param {string | undefined} mode + */ +export function init(config, mode = undefined) { copy_assets(path.join(config.kit.outDir, 'runtime')); write_tsconfig(config.kit); } -/** @param {import('types').ValidatedConfig} config */ -export function update(config) { +/** + * Update SvelteKit's generated files. + * @param {import('types').ValidatedConfig} config + * @param {string | undefined} mode + * The Vite mode + */ +export function update(config, mode = undefined) { const manifest_data = create_manifest_data({ config }); const output = path.join(config.kit.outDir, 'generated'); @@ -25,13 +34,19 @@ export function update(config) { write_root(manifest_data, output); write_matchers(manifest_data, output); write_types(config, manifest_data); - write_env(config.kit); + write_env(config.kit, mode); return { manifest_data }; } -/** @param {import('types').ValidatedConfig} config */ -export function all(config) { - init(config); - return update(config); +/** + * Run sync.init and sync.update in series, returning the result from + * sync.update. + * @param {import('types').ValidatedConfig} config + * @param {string | undefined} mode + * The Vite mode + */ +export function all(config, mode = undefined) { + init(config, mode); + return update(config, mode); } diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js index f8a322be9bbe..2bae3479fde5 100644 --- a/packages/kit/src/core/sync/write_env.js +++ b/packages/kit/src/core/sync/write_env.js @@ -1,6 +1,7 @@ import { write_if_changed } from './utils.js'; import path from 'path'; import fs from 'fs'; +import { loadEnv } from 'vite'; const autogen_comment = '// this section is auto-generated'; @@ -17,7 +18,7 @@ ${Object.entries(env) ([k, v]) => `/** * @type {import('$app/env${pub ? '' : '/private'}').${k}} */ -export const ${k} = "${JSON.stringify(v)}"; +export const ${k} = ${JSON.stringify(v)}; ` ) .join('\n')} @@ -41,15 +42,17 @@ function type_declaration_template(pub, env) { * Writes the existing environment variables in process.env to * $app/env and $app/env/public * @param {import('types').ValidatedKitConfig} config + * @param {string | undefined} mode + * The Vite mode. */ -export function write_env(config) { +export function write_env(config, mode = undefined) { const pub_out = path.join(config.outDir, 'runtime/app/env.js'); const priv_out = path.join(config.outDir, 'runtime/app/env/private.js'); const type_declaration_out = path.join(config.outDir, 'types/ambient.d.ts'); // public is a little difficult since we append to an // already-existing file - const pub = resolve_public_env(config.env.publicPrefix); + const pub = resolve_public_env(config.env.publicPrefix, mode); let pub_content = const_declaration_template(true, pub); if (fs.existsSync(pub_out)) { const old_pub_content = fs.readFileSync(pub_out).toString(); @@ -63,7 +66,7 @@ export function write_env(config) { write_if_changed(pub_out, pub_content); // private is easy since it has its own file: just write if changed - const priv = resolve_private_env(config.env.publicPrefix); + const priv = resolve_private_env(config.env.publicPrefix, mode); const priv_content = const_declaration_template(false, priv); write_if_changed(priv_out, priv_content); @@ -76,20 +79,22 @@ export function write_env(config) { * Generate a Record of private environment variables * (those that do not begin with public_prefix) * @param {string} public_prefix + * @param {string | undefined} mode * @returns {Record} */ -function resolve_private_env(public_prefix) { - return resolve_env(false, public_prefix); +function resolve_private_env(public_prefix, mode = undefined) { + return resolve_env(false, public_prefix, mode); } /** * Generate a Record of public environment variables * (those that do begin with public_prefix) * @param {string} public_prefix + * @param {string | undefined} mode * @returns {Record} */ -function resolve_public_env(public_prefix) { - return resolve_env(true, public_prefix); +function resolve_public_env(public_prefix, mode = undefined) { + return resolve_env(true, public_prefix, mode); } /** @@ -97,16 +102,22 @@ function resolve_public_env(public_prefix) { * variable entries. * @param {boolean} pub * @param {string} public_prefix + * @param {string} mode * @returns {Record} */ -function resolve_env(pub, public_prefix) { +function resolve_env(pub, public_prefix, mode = 'development') { /** @type {Record} */ const resolved = {}; + const env = { + ...process.env, + ...loadEnv(mode, process.cwd(), '') + }; + // I'm smart enough to do this with reduce, // but I'm also smart enough not to force // someone else to read this with reduce later - Object.entries(process.env) + Object.entries(env) .filter(([k]) => (pub ? k.startsWith(public_prefix) : !k.startsWith(public_prefix))) .forEach(([k, v]) => { resolved[k] = v ?? ''; diff --git a/packages/kit/src/vite/dev/index.js b/packages/kit/src/vite/dev/index.js index d6a247b4a6c5..d59a7e4ae587 100644 --- a/packages/kit/src/vite/dev/index.js +++ b/packages/kit/src/vite/dev/index.js @@ -33,7 +33,7 @@ const cwd = process.cwd(); export async function dev(vite, vite_config, svelte_config) { installPolyfills(); - sync.init(svelte_config); + sync.init(svelte_config, vite_config.mode); const runtime = get_runtime_prefix(svelte_config.kit); @@ -44,7 +44,7 @@ export async function dev(vite, vite_config, svelte_config) { let manifest; function update_manifest() { - const { manifest_data } = sync.update(svelte_config); + const { manifest_data } = sync.update(svelte_config, vite_config.mode); manifest = { appDir: svelte_config.kit.appDir, diff --git a/packages/kit/src/vite/index.js b/packages/kit/src/vite/index.js index b94f02954055..8e298933f7f4 100644 --- a/packages/kit/src/vite/index.js +++ b/packages/kit/src/vite/index.js @@ -184,7 +184,7 @@ function kit() { }; if (is_build) { - manifest_data = sync.all(svelte_config).manifest_data; + manifest_data = sync.all(svelte_config, config_env.mode).manifest_data; const new_config = vite_client_build_config(); From 9c1381a61f0202abf4940fe0ce45abcdbe947803 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 21 Jul 2022 19:40:11 -0600 Subject: [PATCH 13/95] feat: Added to sync CLI --- packages/kit/src/cli.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/cli.js b/packages/kit/src/cli.js index 3f1a28f3764e..bb4bf7edb1af 100755 --- a/packages/kit/src/cli.js +++ b/packages/kit/src/cli.js @@ -38,7 +38,12 @@ prog prog .command('sync') .describe('Synchronise generated files') - .action(async () => { + .option( + '--mode', + 'Specify a mode for loading environment variables (default: "development")', + undefined + ) + .action(async ({ mode }) => { if (!fs.existsSync('svelte.config.js')) { console.warn('Missing svelte.config.js — skipping'); return; @@ -47,7 +52,7 @@ prog try { const config = await load_config(); const sync = await import('./core/sync/sync.js'); - sync.all(config); + sync.all(config, mode); } catch (error) { handle_error(error); } From 351c4cbcda7a7320383db9282d49ad5a5717e5f2 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 21 Jul 2022 22:33:20 -0600 Subject: [PATCH 14/95] feat: Added runtime variable support --- packages/adapter-node/src/handler.js | 1 + packages/kit/src/core/sync/write_env.js | 62 ++++++++++++++++++++- packages/kit/src/vite/build/build_server.js | 6 ++ packages/kit/types/ambient.d.ts | 12 ++++ packages/kit/types/index.d.ts | 5 ++ 5 files changed, 83 insertions(+), 3 deletions(-) diff --git a/packages/adapter-node/src/handler.js b/packages/adapter-node/src/handler.js index 4e3cfe1eeef3..e5e67bfa3af9 100644 --- a/packages/adapter-node/src/handler.js +++ b/packages/adapter-node/src/handler.js @@ -11,6 +11,7 @@ import { env } from './env.js'; /* global ENV_PREFIX */ const server = new Server(manifest); +server.init({ env: process.env }); const origin = env('ORIGIN', undefined); const xff_depth = parseInt(env('XFF_DEPTH', '1')); diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js index 2bae3479fde5..5ed745fe0236 100644 --- a/packages/kit/src/core/sync/write_env.js +++ b/packages/kit/src/core/sync/write_env.js @@ -38,17 +38,39 @@ function type_declaration_template(pub, env) { }`; } +function runtime_env_template() { + return `/** @type {App.RuntimeEnv} */ +export let env = {}; + +/** @type {(environment: Record) => void} */ +export function set_env(environment) { + env = environment; +}`; +} + /** * Writes the existing environment variables in process.env to - * $app/env and $app/env/public + * $app/env and $app/env/private * @param {import('types').ValidatedKitConfig} config * @param {string | undefined} mode * The Vite mode. */ export function write_env(config, mode = undefined) { + const pub = write_public_env(config, mode); + const priv = write_private_env(config, mode); + write_runtime_env(config); + write_typedef(config, pub, priv); +} + +/** + * Writes the existing environment variables prefixed with config.kit.env.publicPrefix + * in process.env to $app/env + * @param {import('types').ValidatedKitConfig} config + * @param {string | undefined} mode + * The Vite mode. + */ +function write_public_env(config, mode = undefined) { const pub_out = path.join(config.outDir, 'runtime/app/env.js'); - const priv_out = path.join(config.outDir, 'runtime/app/env/private.js'); - const type_declaration_out = path.join(config.outDir, 'types/ambient.d.ts'); // public is a little difficult since we append to an // already-existing file @@ -64,12 +86,46 @@ export function write_env(config, mode = undefined) { } } write_if_changed(pub_out, pub_content); + return pub; +} + +/** + * Writes the existing environment variables not prefixed with config.kit.env.publicPrefix + * in process.env to $app/env/private + * @param {import('types').ValidatedKitConfig} config + * @param {string | undefined} mode + * The Vite mode. + */ +function write_private_env(config, mode = undefined) { + const priv_out = path.join(config.outDir, 'runtime/app/env/private.js'); // private is easy since it has its own file: just write if changed const priv = resolve_private_env(config.env.publicPrefix, mode); const priv_content = const_declaration_template(false, priv); write_if_changed(priv_out, priv_content); + return priv; +} + +/** + * Writes a blank export to $app/env/runtime. This should be populated by + * Server.init whenever an adapter calls it + * @param {import('types').ValidatedKitConfig} config + */ +function write_runtime_env(config) { + const runtime_out = path.join(config.outDir, 'runtime/app/env/runtime.js'); + + write_if_changed(runtime_out, runtime_env_template()); +} +/** + * Writes the type definitions for the environment variable files + * to types/ambient.d.ts + * @param {import('types').ValidatedKitConfig} config + * @param {Record} pub + * @param {Record} priv + */ +function write_typedef(config, pub, priv) { + const type_declaration_out = path.join(config.outDir, 'types/ambient.d.ts'); const pub_declaration = type_declaration_template(true, pub); const priv_declaration = type_declaration_template(false, priv); write_if_changed(type_declaration_out, `${priv_declaration}\n${pub_declaration}`); diff --git a/packages/kit/src/vite/build/build_server.js b/packages/kit/src/vite/build/build_server.js index db7908b0e20a..a1f2135411b9 100644 --- a/packages/kit/src/vite/build/build_server.js +++ b/packages/kit/src/vite/build/build_server.js @@ -27,6 +27,7 @@ import root from '__GENERATED__/root.svelte'; import { respond } from '${runtime}/server/index.js'; import { set_paths, assets, base } from '${runtime}/paths.js'; import { set_prerendering } from '${runtime}/env.js'; +import { set_env } from '${runtime}/app/env/runtime.js'; const template = ({ head, body, assets, nonce }) => ${s(template) .replace('%sveltekit.head%', '" + head + "') @@ -88,6 +89,11 @@ export class Server { }; } + init(options = { env: {} }) { + set_env(options.env); + return Promise.resolve(); + } + async respond(request, options = {}) { if (!(request instanceof Request)) { throw new Error('The first argument to server.respond must be a Request object. See https://github.com/sveltejs/kit/pull/3384 for details'); diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts index 0662f091dacd..b755862cbda6 100644 --- a/packages/kit/types/ambient.d.ts +++ b/packages/kit/types/ambient.d.ts @@ -61,6 +61,11 @@ declare namespace App { * The interface that defines `stuff`, as input or output to [`load`](/docs/loading) or as the value of the `stuff` property of the [page store](/docs/modules#$app-stores). */ export interface Stuff {} + + /** + * The interface that defines the runtime environment variables exported from '$app/env/runtime'. + */ + export interface RuntimeEnv extends Record {} } /** @@ -85,6 +90,13 @@ declare module '$app/env' { export const prerendering: boolean; } +declare module '$app/env/runtime' { + /** + * The runtime environment variables, as defined by the adapter. + */ + export let env: App.RuntimeEnv; +} + /** * ```ts * import { diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index c3d79edf0c6d..3995824b0c16 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -271,9 +271,14 @@ export type ResponseBody = JSONValue | Uint8Array | ReadableStream | Error; export class Server { constructor(manifest: SSRManifest); + init(options: ServerInitOptions): Promise; respond(request: Request, options: RequestOptions): Promise; } +export interface ServerInitOptions { + env: Record; +} + export interface SSRManifest { appDir: string; assets: Set; From 944ff734f3f913a1a8a46f099a01f069087d4c6b Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 21 Jul 2022 22:37:30 -0600 Subject: [PATCH 15/95] feat: Make server.init multicalls a noop --- packages/kit/src/vite/build/build_server.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/vite/build/build_server.js b/packages/kit/src/vite/build/build_server.js index a1f2135411b9..7101c4578bfe 100644 --- a/packages/kit/src/vite/build/build_server.js +++ b/packages/kit/src/vite/build/build_server.js @@ -36,6 +36,7 @@ const template = ({ head, body, assets, nonce }) => ${s(template) .replace(/%sveltekit\.nonce%/g, '" + nonce + "')}; let read = null; +let initialized = false; set_paths(${s(config.kit.paths)}); @@ -90,7 +91,10 @@ export class Server { } init(options = { env: {} }) { - set_env(options.env); + if (!initialized) { + set_env(options.env); + initialized = true; + } return Promise.resolve(); } From a04007830a3fe2ada319aa17fb5c74f9aff7414d Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 21 Jul 2022 23:19:00 -0600 Subject: [PATCH 16/95] feat: Set runtime env in preview --- packages/kit/src/vite/preview/index.js | 6 ++++++ packages/kit/types/internal.d.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/packages/kit/src/vite/preview/index.js b/packages/kit/src/vite/preview/index.js index 078e6146ed3a..697e363a5b9e 100644 --- a/packages/kit/src/vite/preview/index.js +++ b/packages/kit/src/vite/preview/index.js @@ -42,6 +42,12 @@ export async function preview(vite, config, protocol) { }); const server = new Server(manifest); + server.init({ + // This just makes sure no values are undefined + env: Object.entries(process.env).reduce((prev, [k, v]) => { + return { ...prev, [k]: v ?? '' }; + }, {}) + }); return () => { // files in `static` diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index f7a88de58f37..952bd877ac0e 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -11,6 +11,7 @@ import { RequestHandler, ResolveOptions, Server, + ServerInitOptions, SSRManifest } from './index.js'; import { @@ -92,6 +93,7 @@ export interface Hooks { } export class InternalServer extends Server { + init(options: ServerInitOptions): Promise; respond( request: Request, options: RequestOptions & { From 34801e1cd2b2cffbf3565e169605a91548a485ee Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 21 Jul 2022 23:19:33 -0600 Subject: [PATCH 17/95] this doesn't work?!: Set runtime env in dev --- packages/kit/src/vite/dev/index.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/kit/src/vite/dev/index.js b/packages/kit/src/vite/dev/index.js index d59a7e4ae587..4bd4d237fa48 100644 --- a/packages/kit/src/vite/dev/index.js +++ b/packages/kit/src/vite/dev/index.js @@ -240,6 +240,16 @@ export async function dev(vite, vite_config, svelte_config) { ? await vite.ssrLoadModule(`/${svelte_config.kit.files.hooks}`) : {}; + /** @type {(env: Record) => void} */ + const set_env = (await import(`${runtime}/app/env/runtime.js`)).set_env; + + set_env( + // This just makes sure no values are undefined + Object.entries(process.env).reduce((prev, [k, v]) => { + return { ...prev, [k]: v ?? '' }; + }, {}) + ); + const handle = user_hooks.handle || (({ event, resolve }) => resolve(event)); /** @type {import('types').Hooks} */ From 035a2cd72fcde8bf69e330f32d397be4bbaaa549 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 21 Jul 2022 23:22:23 -0600 Subject: [PATCH 18/95] feat: Added runtime env handler to cloudflare for testing --- packages/adapter-cloudflare/src/worker.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/adapter-cloudflare/src/worker.js b/packages/adapter-cloudflare/src/worker.js index 1ba1bf8d145a..20993ee11beb 100644 --- a/packages/adapter-cloudflare/src/worker.js +++ b/packages/adapter-cloudflare/src/worker.js @@ -9,6 +9,7 @@ const prefix = `/${manifest.appDir}/`; /** @type {import('worktop/cfw').Module.Worker<{ ASSETS: import('worktop/cfw.durable').Durable.Object }>} */ const worker = { async fetch(req, env, context) { + server.init({ env }); // skip cache if "cache-control: no-cache" in request let pragma = req.headers.get('cache-control') || ''; let res = !pragma.includes('no-cache') && (await Cache.lookup(req)); From 48f8cba448f014a6880bf3d421fbbfb9c6d7b1b5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 11:06:59 -0400 Subject: [PATCH 19/95] Update packages/kit/src/cli.js --- packages/kit/src/cli.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/cli.js b/packages/kit/src/cli.js index bb4bf7edb1af..1202838d843c 100755 --- a/packages/kit/src/cli.js +++ b/packages/kit/src/cli.js @@ -40,8 +40,8 @@ prog .describe('Synchronise generated files') .option( '--mode', - 'Specify a mode for loading environment variables (default: "development")', - undefined + 'Specify a mode for loading environment variables', + 'development' ) .action(async ({ mode }) => { if (!fs.existsSync('svelte.config.js')) { From 61e3a67bfccc2fed3f9549ffb4a14291936c68de Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 11:08:40 -0400 Subject: [PATCH 20/95] Update packages/kit/src/core/sync/write_env.js --- packages/kit/src/core/sync/write_env.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js index 5ed745fe0236..641369f60172 100644 --- a/packages/kit/src/core/sync/write_env.js +++ b/packages/kit/src/core/sync/write_env.js @@ -74,7 +74,7 @@ function write_public_env(config, mode = undefined) { // public is a little difficult since we append to an // already-existing file - const pub = resolve_public_env(config.env.publicPrefix, mode); + const pub = loadEnv(mode, process.cwd(), config.env.publicPrefix); let pub_content = const_declaration_template(true, pub); if (fs.existsSync(pub_out)) { const old_pub_content = fs.readFileSync(pub_out).toString(); From 1e6906a85c75a6ef41d0eb313cf6d0ae2e192bb9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 11:08:55 -0400 Subject: [PATCH 21/95] Update packages/kit/src/core/sync/write_env.js --- packages/kit/src/core/sync/write_env.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js index 641369f60172..efb820f9f697 100644 --- a/packages/kit/src/core/sync/write_env.js +++ b/packages/kit/src/core/sync/write_env.js @@ -100,7 +100,7 @@ function write_private_env(config, mode = undefined) { const priv_out = path.join(config.outDir, 'runtime/app/env/private.js'); // private is easy since it has its own file: just write if changed - const priv = resolve_private_env(config.env.publicPrefix, mode); + const priv = loadEnv(mode, process.cwd(), ''); const priv_content = const_declaration_template(false, priv); write_if_changed(priv_out, priv_content); return priv; From 1f70c4db93de6607badd1ae4db88552d55e5bba3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 11:09:08 -0400 Subject: [PATCH 22/95] Update packages/kit/src/vite/build/build_server.js --- packages/kit/src/vite/build/build_server.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kit/src/vite/build/build_server.js b/packages/kit/src/vite/build/build_server.js index 7101c4578bfe..7da158889027 100644 --- a/packages/kit/src/vite/build/build_server.js +++ b/packages/kit/src/vite/build/build_server.js @@ -95,7 +95,6 @@ export class Server { set_env(options.env); initialized = true; } - return Promise.resolve(); } async respond(request, options = {}) { From 5f41062c5ec2cb662031535fe11fea3f877621df Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 11:09:23 -0400 Subject: [PATCH 23/95] Update packages/kit/src/core/sync/write_env.js --- packages/kit/src/core/sync/write_env.js | 51 ------------------------- 1 file changed, 51 deletions(-) diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js index efb820f9f697..1c5cafd70312 100644 --- a/packages/kit/src/core/sync/write_env.js +++ b/packages/kit/src/core/sync/write_env.js @@ -130,54 +130,3 @@ function write_typedef(config, pub, priv) { const priv_declaration = type_declaration_template(false, priv); write_if_changed(type_declaration_out, `${priv_declaration}\n${pub_declaration}`); } - -/** - * Generate a Record of private environment variables - * (those that do not begin with public_prefix) - * @param {string} public_prefix - * @param {string | undefined} mode - * @returns {Record} - */ -function resolve_private_env(public_prefix, mode = undefined) { - return resolve_env(false, public_prefix, mode); -} - -/** - * Generate a Record of public environment variables - * (those that do begin with public_prefix) - * @param {string} public_prefix - * @param {string | undefined} mode - * @returns {Record} - */ -function resolve_public_env(public_prefix, mode = undefined) { - return resolve_env(true, public_prefix, mode); -} - -/** - * Generate a Record containing environment - * variable entries. - * @param {boolean} pub - * @param {string} public_prefix - * @param {string} mode - * @returns {Record} - */ -function resolve_env(pub, public_prefix, mode = 'development') { - /** @type {Record} */ - const resolved = {}; - - const env = { - ...process.env, - ...loadEnv(mode, process.cwd(), '') - }; - - // I'm smart enough to do this with reduce, - // but I'm also smart enough not to force - // someone else to read this with reduce later - Object.entries(env) - .filter(([k]) => (pub ? k.startsWith(public_prefix) : !k.startsWith(public_prefix))) - .forEach(([k, v]) => { - resolved[k] = v ?? ''; - }); - - return resolved; -} From 069e3dce3696cd0ea77730ee76278bfa40ca51fb Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 11:11:49 -0400 Subject: [PATCH 24/95] make mode required --- packages/kit/src/core/sync/sync.js | 10 ++++------ packages/kit/src/core/sync/write_env.js | 12 ++++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js index bf03872647ad..47e6bd8ba159 100644 --- a/packages/kit/src/core/sync/sync.js +++ b/packages/kit/src/core/sync/sync.js @@ -21,10 +21,9 @@ export function init(config, mode = undefined) { /** * Update SvelteKit's generated files. * @param {import('types').ValidatedConfig} config - * @param {string | undefined} mode - * The Vite mode + * @param {string} mode The Vite mode */ -export function update(config, mode = undefined) { +export function update(config, mode) { const manifest_data = create_manifest_data({ config }); const output = path.join(config.kit.outDir, 'generated'); @@ -43,10 +42,9 @@ export function update(config, mode = undefined) { * Run sync.init and sync.update in series, returning the result from * sync.update. * @param {import('types').ValidatedConfig} config - * @param {string | undefined} mode - * The Vite mode + * @param {string} mode The Vite mode */ -export function all(config, mode = undefined) { +export function all(config, mode) { init(config, mode); return update(config, mode); } diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js index 1c5cafd70312..c2b3314847ca 100644 --- a/packages/kit/src/core/sync/write_env.js +++ b/packages/kit/src/core/sync/write_env.js @@ -52,10 +52,10 @@ export function set_env(environment) { * Writes the existing environment variables in process.env to * $app/env and $app/env/private * @param {import('types').ValidatedKitConfig} config - * @param {string | undefined} mode + * @param {string} mode * The Vite mode. */ -export function write_env(config, mode = undefined) { +export function write_env(config, mode) { const pub = write_public_env(config, mode); const priv = write_private_env(config, mode); write_runtime_env(config); @@ -66,10 +66,10 @@ export function write_env(config, mode = undefined) { * Writes the existing environment variables prefixed with config.kit.env.publicPrefix * in process.env to $app/env * @param {import('types').ValidatedKitConfig} config - * @param {string | undefined} mode + * @param {string} mode * The Vite mode. */ -function write_public_env(config, mode = undefined) { +function write_public_env(config, mode) { const pub_out = path.join(config.outDir, 'runtime/app/env.js'); // public is a little difficult since we append to an @@ -93,10 +93,10 @@ function write_public_env(config, mode = undefined) { * Writes the existing environment variables not prefixed with config.kit.env.publicPrefix * in process.env to $app/env/private * @param {import('types').ValidatedKitConfig} config - * @param {string | undefined} mode + * @param {string} mode * The Vite mode. */ -function write_private_env(config, mode = undefined) { +function write_private_env(config, mode) { const priv_out = path.join(config.outDir, 'runtime/app/env/private.js'); // private is easy since it has its own file: just write if changed From 5774f08e3e929b4998bdc18573162a748455e972 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 11:13:31 -0400 Subject: [PATCH 25/95] move env/runtime to a file --- packages/kit/rollup.config.js | 1 + packages/kit/src/core/sync/write_env.js | 22 --------------------- packages/kit/src/runtime/app/env/runtime.js | 7 +++++++ 3 files changed, 8 insertions(+), 22 deletions(-) create mode 100644 packages/kit/src/runtime/app/env/runtime.js diff --git a/packages/kit/rollup.config.js b/packages/kit/rollup.config.js index 7fa7819d249d..0cab93621d98 100644 --- a/packages/kit/rollup.config.js +++ b/packages/kit/rollup.config.js @@ -23,6 +23,7 @@ export default [ 'app/stores': 'src/runtime/app/stores.js', 'app/paths': 'src/runtime/app/paths.js', 'app/env': 'src/runtime/app/env.js', + 'app/env/runtime': 'src/runtime/app/env/runtime.js', paths: 'src/runtime/paths.js', env: 'src/runtime/env.js' }, diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js index c2b3314847ca..5b9eb79ea8c5 100644 --- a/packages/kit/src/core/sync/write_env.js +++ b/packages/kit/src/core/sync/write_env.js @@ -38,16 +38,6 @@ function type_declaration_template(pub, env) { }`; } -function runtime_env_template() { - return `/** @type {App.RuntimeEnv} */ -export let env = {}; - -/** @type {(environment: Record) => void} */ -export function set_env(environment) { - env = environment; -}`; -} - /** * Writes the existing environment variables in process.env to * $app/env and $app/env/private @@ -58,7 +48,6 @@ export function set_env(environment) { export function write_env(config, mode) { const pub = write_public_env(config, mode); const priv = write_private_env(config, mode); - write_runtime_env(config); write_typedef(config, pub, priv); } @@ -106,17 +95,6 @@ function write_private_env(config, mode) { return priv; } -/** - * Writes a blank export to $app/env/runtime. This should be populated by - * Server.init whenever an adapter calls it - * @param {import('types').ValidatedKitConfig} config - */ -function write_runtime_env(config) { - const runtime_out = path.join(config.outDir, 'runtime/app/env/runtime.js'); - - write_if_changed(runtime_out, runtime_env_template()); -} - /** * Writes the type definitions for the environment variable files * to types/ambient.d.ts diff --git a/packages/kit/src/runtime/app/env/runtime.js b/packages/kit/src/runtime/app/env/runtime.js new file mode 100644 index 000000000000..5a609006220b --- /dev/null +++ b/packages/kit/src/runtime/app/env/runtime.js @@ -0,0 +1,7 @@ +/** @type {App.RuntimeEnv} */ +export let env = {}; + +/** @type {(environment: Record) => void} */ +export function set_env(environment) { + env = environment; +} From 9091d2c0e91d780f0eb6924bdb6989e3293233f2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 11:40:28 -0400 Subject: [PATCH 26/95] simplify write_env --- packages/kit/src/core/sync/write_env.js | 128 ++++++++---------------- 1 file changed, 42 insertions(+), 86 deletions(-) diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js index 5b9eb79ea8c5..8ec89839b3c2 100644 --- a/packages/kit/src/core/sync/write_env.js +++ b/packages/kit/src/core/sync/write_env.js @@ -1,42 +1,8 @@ -import { write_if_changed } from './utils.js'; import path from 'path'; -import fs from 'fs'; import { loadEnv } from 'vite'; +import { write_if_changed } from './utils.js'; -const autogen_comment = '// this section is auto-generated'; - -/** - * @param {boolean} pub - * @param {Record} env - * @returns {string} - */ -function const_declaration_template(pub, env) { - return `${autogen_comment} - -${Object.entries(env) - .map( - ([k, v]) => `/** - * @type {import('$app/env${pub ? '' : '/private'}').${k}} - */ -export const ${k} = ${JSON.stringify(v)}; -` - ) - .join('\n')} -`; -} - -/** - * @param {boolean} pub - * @param {Record} env - * @returns {string} - */ -function type_declaration_template(pub, env) { - return `declare module '$app/env${pub ? '' : '/private'}' { - ${Object.keys(env) - .map((k) => `export const ${k}: string;`) - .join('\n ')} -}`; -} +const autogen_comment = '// this file is generated — do not edit it\n'; /** * Writes the existing environment variables in process.env to @@ -46,65 +12,55 @@ function type_declaration_template(pub, env) { * The Vite mode. */ export function write_env(config, mode) { - const pub = write_public_env(config, mode); - const priv = write_private_env(config, mode); - write_typedef(config, pub, priv); -} + const entries = Object.entries(loadEnv(mode, process.cwd(), '')); + const pub = Object.fromEntries(entries.filter(([k]) => k.startsWith(config.env.publicPrefix))); + const prv = Object.fromEntries(entries.filter(([k]) => !k.startsWith(config.env.publicPrefix))); -/** - * Writes the existing environment variables prefixed with config.kit.env.publicPrefix - * in process.env to $app/env - * @param {import('types').ValidatedKitConfig} config - * @param {string} mode - * The Vite mode. - */ -function write_public_env(config, mode) { - const pub_out = path.join(config.outDir, 'runtime/app/env.js'); + // TODO when testing src, `$app` points at `src/runtime/app`... will + // probably need to fiddle with aliases + write_if_changed( + path.join(config.outDir, 'runtime/app/env/public/index.js'), + create_module('$app/env/public', pub) + ); - // public is a little difficult since we append to an - // already-existing file - const pub = loadEnv(mode, process.cwd(), config.env.publicPrefix); - let pub_content = const_declaration_template(true, pub); - if (fs.existsSync(pub_out)) { - const old_pub_content = fs.readFileSync(pub_out).toString(); - const autogen_content_start = old_pub_content.indexOf(autogen_comment); - if (autogen_content_start === -1) { - pub_content = old_pub_content + '\n' + pub_content; - } else { - pub_content = old_pub_content.slice(0, autogen_content_start) + '\n' + pub_content; - } - } - write_if_changed(pub_out, pub_content); - return pub; + write_if_changed( + path.join(config.outDir, 'runtime/app/env/private/index.js'), + create_module('$app/env/private', prv) + ); + + write_if_changed( + path.join(config.outDir, 'types/ambient.d.ts'), + autogen_comment + + create_types('$app/env/public', pub) + + '\n\n' + + create_types('$app/env/private', prv) + ); } /** - * Writes the existing environment variables not prefixed with config.kit.env.publicPrefix - * in process.env to $app/env/private - * @param {import('types').ValidatedKitConfig} config - * @param {string} mode - * The Vite mode. + * @param {string} id + * @param {Record} env + * @returns {string} */ -function write_private_env(config, mode) { - const priv_out = path.join(config.outDir, 'runtime/app/env/private.js'); +function create_module(id, env) { + const declarations = Object.entries(env) + .map( + ([k, v]) => `/** @type {import('${id}'}').${k}} */\nexport const ${k} = ${JSON.stringify(v)};` + ) + .join('\n\n'); - // private is easy since it has its own file: just write if changed - const priv = loadEnv(mode, process.cwd(), ''); - const priv_content = const_declaration_template(false, priv); - write_if_changed(priv_out, priv_content); - return priv; + return autogen_comment + declarations; } /** - * Writes the type definitions for the environment variable files - * to types/ambient.d.ts - * @param {import('types').ValidatedKitConfig} config - * @param {Record} pub - * @param {Record} priv + * @param {string} id + * @param {Record} env + * @returns {string} */ -function write_typedef(config, pub, priv) { - const type_declaration_out = path.join(config.outDir, 'types/ambient.d.ts'); - const pub_declaration = type_declaration_template(true, pub); - const priv_declaration = type_declaration_template(false, priv); - write_if_changed(type_declaration_out, `${priv_declaration}\n${pub_declaration}`); +function create_types(id, env) { + const declarations = Object.keys(env) + .map((k) => `\texport const ${k}: string;`) + .join('\n'); + + return `declare module '${id}' {\n${declarations}\n}`; } From cf09ed72af204dd001b4986bc4b35ebe0e4da1cc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 12:00:37 -0400 Subject: [PATCH 27/95] Update packages/kit/src/vite/preview/index.js --- packages/kit/src/vite/preview/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/kit/src/vite/preview/index.js b/packages/kit/src/vite/preview/index.js index 697e363a5b9e..b2205cb07fe9 100644 --- a/packages/kit/src/vite/preview/index.js +++ b/packages/kit/src/vite/preview/index.js @@ -44,9 +44,7 @@ export async function preview(vite, config, protocol) { const server = new Server(manifest); server.init({ // This just makes sure no values are undefined - env: Object.entries(process.env).reduce((prev, [k, v]) => { - return { ...prev, [k]: v ?? '' }; - }, {}) + env: process.env }); return () => { From b7b1fca3358c774e6d4fa32c7bca8bdb83f47967 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 12:03:46 -0400 Subject: [PATCH 28/95] remove unnecessary index.js suffix --- packages/kit/src/core/sync/write_env.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/core/sync/write_env.js b/packages/kit/src/core/sync/write_env.js index 8ec89839b3c2..11f0bca86c54 100644 --- a/packages/kit/src/core/sync/write_env.js +++ b/packages/kit/src/core/sync/write_env.js @@ -19,12 +19,12 @@ export function write_env(config, mode) { // TODO when testing src, `$app` points at `src/runtime/app`... will // probably need to fiddle with aliases write_if_changed( - path.join(config.outDir, 'runtime/app/env/public/index.js'), + path.join(config.outDir, 'runtime/app/env/public.js'), create_module('$app/env/public', pub) ); write_if_changed( - path.join(config.outDir, 'runtime/app/env/private/index.js'), + path.join(config.outDir, 'runtime/app/env/private.js'), create_module('$app/env/private', prv) ); From b4a1ee45f0a1837a2ec9e82e9a53c6f452118e3d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 12:03:51 -0400 Subject: [PATCH 29/95] simplify --- packages/kit/src/core/sync/write_tsconfig.js | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/kit/src/core/sync/write_tsconfig.js b/packages/kit/src/core/sync/write_tsconfig.js index bb0fe540ce1b..c75cce8e5adc 100644 --- a/packages/kit/src/core/sync/write_tsconfig.js +++ b/packages/kit/src/core/sync/write_tsconfig.js @@ -24,19 +24,13 @@ export function write_tsconfig(config, cwd = process.cwd()) { /** @param {string} file */ const config_relative = (file) => posixify(path.relative(config.outDir, file)); - const dirs = new Set([ - project_relative(path.dirname(config.files.routes)), - project_relative(path.dirname(config.files.lib)) - ]); - - /** @type {string[]} */ - const include = []; - dirs.forEach((dir) => { - include.push(config_relative(`${dir}/**/*.js`)); - include.push(config_relative(`${dir}/**/*.ts`)); - include.push(config_relative(`${dir}/**/*.svelte`)); - }); - include.push('types/ambient.d.ts'); + const include = ['types/ambient.d.ts']; + for (const dir of [config.files.routes, config.files.lib]) { + const relative = project_relative(path.dirname(dir)); + include.push(config_relative(`${relative}/**/*.js`)); + include.push(config_relative(`${relative}/**/*.ts`)); + include.push(config_relative(`${relative}/**/*.svelte`)); + } /** @type {Record} */ const paths = {}; From 905ef240e723dea16c6b5ab242693d936b8fc733 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 12:04:48 -0400 Subject: [PATCH 30/95] tabs --- packages/kit/src/vite/build/build_server.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/kit/src/vite/build/build_server.js b/packages/kit/src/vite/build/build_server.js index 7da158889027..6d022b20e4e8 100644 --- a/packages/kit/src/vite/build/build_server.js +++ b/packages/kit/src/vite/build/build_server.js @@ -90,12 +90,12 @@ export class Server { }; } - init(options = { env: {} }) { - if (!initialized) { - set_env(options.env); - initialized = true; - } - } + init(options = { env: {} }) { + if (!initialized) { + set_env(options.env); + initialized = true; + } + } async respond(request, options = {}) { if (!(request instanceof Request)) { From d2352b366fe7ed2bb22b25557cb9f6eded37024f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 12:07:37 -0400 Subject: [PATCH 31/95] make init sync, make options required --- packages/kit/src/vite/build/build_server.js | 4 ++-- packages/kit/types/index.d.ts | 2 +- packages/kit/types/internal.d.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/vite/build/build_server.js b/packages/kit/src/vite/build/build_server.js index 6d022b20e4e8..f2b9f179c4ea 100644 --- a/packages/kit/src/vite/build/build_server.js +++ b/packages/kit/src/vite/build/build_server.js @@ -90,9 +90,9 @@ export class Server { }; } - init(options = { env: {} }) { + init({ env }) { if (!initialized) { - set_env(options.env); + set_env(env); initialized = true; } } diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 3995824b0c16..23b4e86af7a1 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -271,7 +271,7 @@ export type ResponseBody = JSONValue | Uint8Array | ReadableStream | Error; export class Server { constructor(manifest: SSRManifest); - init(options: ServerInitOptions): Promise; + init(options: ServerInitOptions): void; respond(request: Request, options: RequestOptions): Promise; } diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index 952bd877ac0e..bb7b815dc20b 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -93,7 +93,7 @@ export interface Hooks { } export class InternalServer extends Server { - init(options: ServerInitOptions): Promise; + init(options: ServerInitOptions): void; respond( request: Request, options: RequestOptions & { From a858d193f04a50745c7b32a83862d9998217ab42 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 12:30:11 -0400 Subject: [PATCH 32/95] format --- packages/kit/src/cli.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/kit/src/cli.js b/packages/kit/src/cli.js index 1202838d843c..7168b2a67d67 100755 --- a/packages/kit/src/cli.js +++ b/packages/kit/src/cli.js @@ -38,11 +38,7 @@ prog prog .command('sync') .describe('Synchronise generated files') - .option( - '--mode', - 'Specify a mode for loading environment variables', - 'development' - ) + .option('--mode', 'Specify a mode for loading environment variables', 'development') .action(async ({ mode }) => { if (!fs.existsSync('svelte.config.js')) { console.warn('Missing svelte.config.js — skipping'); From 17ffba0542391cd044e7f826fe8d3f1b030a85d8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 12:30:53 -0400 Subject: [PATCH 33/95] cast process.env --- packages/kit/src/vite/preview/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/vite/preview/index.js b/packages/kit/src/vite/preview/index.js index b2205cb07fe9..2fe9f61bb33a 100644 --- a/packages/kit/src/vite/preview/index.js +++ b/packages/kit/src/vite/preview/index.js @@ -44,7 +44,7 @@ export async function preview(vite, config, protocol) { const server = new Server(manifest); server.init({ // This just makes sure no values are undefined - env: process.env + env: /** @type {Record} */ (process.env) }); return () => { From af2b1aaef4991abba1b285173bd9a58d4f654c78 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 12:32:38 -0400 Subject: [PATCH 34/95] remove the overlay stuff, just throw an error --- packages/kit/src/vite/dev/index.js | 11 ++++------- packages/kit/src/vite/utils.js | 18 ------------------ 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/packages/kit/src/vite/dev/index.js b/packages/kit/src/vite/dev/index.js index 4bd4d237fa48..033431631c63 100644 --- a/packages/kit/src/vite/dev/index.js +++ b/packages/kit/src/vite/dev/index.js @@ -12,11 +12,7 @@ import { load_template } from '../../core/config/index.js'; import { SVELTE_KIT_ASSETS } from '../../core/constants.js'; import * as sync from '../../core/sync/sync.js'; import { get_mime_lookup, get_runtime_prefix } from '../../core/utils.js'; -import { - resolve_entry, - web_socket_server_to_error_handler, - throw_if_illegal_private_import_vite -} from '../utils.js'; +import { resolve_entry, throw_if_illegal_private_import_vite } from '../utils.js'; // Vite doesn't expose this so we just copy the list for now // https://github.com/vitejs/vite/blob/3edd1af56e980aef56641a5a51cf2932bb580d41/packages/vite/src/node/plugins/css.ts#L96 @@ -66,8 +62,9 @@ export async function dev(vite, vite_config, svelte_config) { const node = await vite.moduleGraph.getModuleByUrl(url); if (!node) throw new Error(`Could not find node for ${url}`); - const error_handler = web_socket_server_to_error_handler(vite.ws); - throw_if_illegal_private_import_vite(error_handler, node); + throw_if_illegal_private_import_vite((err) => { + throw err; + }, node); return { module, diff --git a/packages/kit/src/vite/utils.js b/packages/kit/src/vite/utils.js index 29ea893c09d6..42531bf422fe 100644 --- a/packages/kit/src/vite/utils.js +++ b/packages/kit/src/vite/utils.js @@ -135,24 +135,6 @@ export function resolve_entry(entry) { return null; } -/** - * Derive an error handler from a web socket server. - * @param {import('vite').WebSocketServer} ws - */ -export function web_socket_server_to_error_handler(ws) { - /** @type {(id: string, err: Error) => void} */ - return (id, err) => - ws.send({ - type: 'error', - err: { - message: err.message, - stack: err.stack ?? '', - plugin: 'vite-plugin-svelte', - id - } - }); -} - const illegal_import_names /** @type {Array} */ = [ '.svelte-kit/runtime/app/env/private.js' ]; From b0477aa6abcaae728fa17a92504f147f7d9bee44 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 12:38:26 -0400 Subject: [PATCH 35/95] just throw --- packages/kit/src/vite/dev/index.js | 5 ++-- packages/kit/src/vite/index.js | 8 +----- packages/kit/src/vite/utils.js | 44 ++++++++---------------------- 3 files changed, 15 insertions(+), 42 deletions(-) diff --git a/packages/kit/src/vite/dev/index.js b/packages/kit/src/vite/dev/index.js index 033431631c63..0a8d8d7d7398 100644 --- a/packages/kit/src/vite/dev/index.js +++ b/packages/kit/src/vite/dev/index.js @@ -62,9 +62,8 @@ export async function dev(vite, vite_config, svelte_config) { const node = await vite.moduleGraph.getModuleByUrl(url); if (!node) throw new Error(`Could not find node for ${url}`); - throw_if_illegal_private_import_vite((err) => { - throw err; - }, node); + + throw_if_illegal_private_import_vite(node); return { module, diff --git a/packages/kit/src/vite/index.js b/packages/kit/src/vite/index.js index 8e298933f7f4..312109fcacfc 100644 --- a/packages/kit/src/vite/index.js +++ b/packages/kit/src/vite/index.js @@ -274,13 +274,7 @@ function kit() { if (module_info === null) { throw new Error(`Failed to locate module info for ${module_info}`); } - throw_if_illegal_private_import_rollup( - this.getModuleInfo.bind(this), - (id, err) => { - throw err; - }, - module_info - ); + throw_if_illegal_private_import_rollup(this.getModuleInfo.bind(this), module_info); } } diff --git a/packages/kit/src/vite/utils.js b/packages/kit/src/vite/utils.js index 42531bf422fe..26b8e18902da 100644 --- a/packages/kit/src/vite/utils.js +++ b/packages/kit/src/vite/utils.js @@ -143,31 +143,25 @@ const illegal_import_names /** @type {Array} */ = [ * Create a formatted error for an illegal import. * @param {string} error_module_id * @param {Array} stack - * @param {(id: string, err: Error) => void} error_handler */ -function handle_illegal_import_error(error_module_id, stack, error_handler) { +function handle_illegal_import_error(error_module_id, stack) { stack.push(error_module_id + ' (server-side only module)'); const stringified_stack = stack.map((mod, i) => ` ${i}: ${mod}`).join(', which imports:\n'); - const err = new Error( + + return new Error( `Found an illegal import originating from: ${stack[0]}. It imports:\n${stringified_stack}` ); - error_handler(error_module_id, err); - return; } /** * Recurse through the ModuleNode graph, throwing if the module * imports a private module. - * @param {(id: string, err: Error) => void} error_handler * @param {import('vite').ModuleNode} node * @param {Array} illegal_module_stack * @param {Set} illegal_module_set */ function throw_if_illegal_private_import_recursive_vite( node, - error_handler = (id, err) => { - throw err; - }, illegal_module_stack = [], illegal_module_set = new Set() ) { @@ -180,16 +174,10 @@ function throw_if_illegal_private_import_recursive_vite( illegal_module_stack.push(file); node.importedModules.forEach((childNode) => { if (illegal_import_names.some((name) => childNode.file?.endsWith(name))) { - handle_illegal_import_error( - childNode?.file ?? 'unknown', - illegal_module_stack, - error_handler - ); - return; + throw handle_illegal_import_error(childNode?.file ?? 'unknown', illegal_module_stack); } throw_if_illegal_private_import_recursive_vite( childNode, - error_handler, illegal_module_stack, illegal_module_set ); @@ -200,11 +188,10 @@ function throw_if_illegal_private_import_recursive_vite( /** * Throw an error if a private module is imported from a client-side node. - * @param {(id: string, err: Error) => void} error_handler * @param {import('vite').ModuleNode} node */ -export function throw_if_illegal_private_import_vite(error_handler, node) { - throw_if_illegal_private_import_recursive_vite(node, error_handler); +export function throw_if_illegal_private_import_vite(node) { + throw_if_illegal_private_import_recursive_vite(node); } /** @@ -212,16 +199,12 @@ export function throw_if_illegal_private_import_vite(error_handler, node) { * imports a private module. * @param {import('rollup').GetModuleInfo} node_getter * @param {import('rollup').ModuleInfo} node - * @param {(id: string, err: Error) => void} error_handler * @param {Array} illegal_module_stack * @param {Set} illegal_module_set */ function throw_if_illegal_private_import_recursive_rollup( node_getter, node, - error_handler = (id, err) => { - throw err; - }, illegal_module_stack = [], illegal_module_set = new Set() ) { @@ -239,18 +222,16 @@ function throw_if_illegal_private_import_recursive_rollup( const recursiveChildNodeHandler = (childId) => { const childNode = node_getter(childId); if (childNode === null) { - const err = new Error(`Failed to find module info for ${childId}`); - error_handler(childId, err); - return; + throw new Error(`Failed to find module info for ${childId}`); } + if (illegal_import_names.some((name) => childId.endsWith(name))) { - handle_illegal_import_error(childId, illegal_module_stack, error_handler); - return; + throw handle_illegal_import_error(childId, illegal_module_stack); } + throw_if_illegal_private_import_recursive_rollup( node_getter, childNode, - error_handler, illegal_module_stack, illegal_module_set ); @@ -264,9 +245,8 @@ function throw_if_illegal_private_import_recursive_rollup( /** * Throw an error if a private module is imported from a client-side node. * @param {import('rollup').GetModuleInfo} node_getter - * @param {(id: string, err: Error) => void} error_handler * @param {import('rollup').ModuleInfo} module_info */ -export function throw_if_illegal_private_import_rollup(node_getter, error_handler, module_info) { - throw_if_illegal_private_import_recursive_rollup(node_getter, module_info, error_handler); +export function throw_if_illegal_private_import_rollup(node_getter, module_info) { + throw_if_illegal_private_import_recursive_rollup(node_getter, module_info); } From 490f3fc46af92f17994d4111c62bfe3d697ed897 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 13:35:22 -0400 Subject: [PATCH 36/95] simplify traversal --- packages/kit/src/vite/utils.js | 161 +++++++++++++++------------------ 1 file changed, 72 insertions(+), 89 deletions(-) diff --git a/packages/kit/src/vite/utils.js b/packages/kit/src/vite/utils.js index 26b8e18902da..088a898867ee 100644 --- a/packages/kit/src/vite/utils.js +++ b/packages/kit/src/vite/utils.js @@ -135,55 +135,31 @@ export function resolve_entry(entry) { return null; } -const illegal_import_names /** @type {Array} */ = [ - '.svelte-kit/runtime/app/env/private.js' -]; +// TODO remove hardcoded path +// TODO are module IDs absolute paths on Windows as well? +const illegal_import_names = new Set([path.resolve('.svelte-kit/runtime/app/env/private.js')]); +/** + * + * @param {string} str + * @param {number} times + * @returns + */ +function repeat(str, times) { + return new Array(times + 1).join(str); +} /** * Create a formatted error for an illegal import. - * @param {string} error_module_id * @param {Array} stack */ -function handle_illegal_import_error(error_module_id, stack) { - stack.push(error_module_id + ' (server-side only module)'); - const stringified_stack = stack.map((mod, i) => ` ${i}: ${mod}`).join(', which imports:\n'); - - return new Error( - `Found an illegal import originating from: ${stack[0]}. It imports:\n${stringified_stack}` +function handle_illegal_import_error(stack) { + stack = stack.map((file) => + path.relative(process.cwd(), file).replace('.svelte-kit/runtime/app', '$app') ); -} -/** - * Recurse through the ModuleNode graph, throwing if the module - * imports a private module. - * @param {import('vite').ModuleNode} node - * @param {Array} illegal_module_stack - * @param {Set} illegal_module_set - */ -function throw_if_illegal_private_import_recursive_vite( - node, - illegal_module_stack = [], - illegal_module_set = new Set() -) { - const file = node.file ?? 'unknown'; - // This prevents cyclical imports creating a stack overflow - if (illegal_module_set.has(file) || node.importedModules.size === 0) { - return; - } - illegal_module_set.add(file); - illegal_module_stack.push(file); - node.importedModules.forEach((childNode) => { - if (illegal_import_names.some((name) => childNode.file?.endsWith(name))) { - throw handle_illegal_import_error(childNode?.file ?? 'unknown', illegal_module_stack); - } - throw_if_illegal_private_import_recursive_vite( - childNode, - illegal_module_stack, - illegal_module_set - ); - }); - illegal_module_set.delete(file); - illegal_module_stack.pop(); + const pyramid = stack.map((file, i) => `${repeat(' ', i * 2)}- ${file}`).join('\n'); + + return new Error(`Cannot import ${stack.at(-1)} into client-side code:\n${pyramid}`); } /** @@ -191,62 +167,69 @@ function throw_if_illegal_private_import_recursive_vite( * @param {import('vite').ModuleNode} node */ export function throw_if_illegal_private_import_vite(node) { - throw_if_illegal_private_import_recursive_vite(node); -} + const seen = new Set(); -/** - * Recurse through the ModuleInfo graph, throwing if the module - * imports a private module. - * @param {import('rollup').GetModuleInfo} node_getter - * @param {import('rollup').ModuleInfo} node - * @param {Array} illegal_module_stack - * @param {Set} illegal_module_set - */ -function throw_if_illegal_private_import_recursive_rollup( - node_getter, - node, - illegal_module_stack = [], - illegal_module_set = new Set() -) { - const id = node.id; - // This prevents cyclical imports creating a stack overflow - if ( - illegal_module_set.has(id) || - (node.importedIds.length === 0 && node.dynamicImporters.length === 0) - ) { - return; - } - illegal_module_set.add(id); - illegal_module_stack.push(id); - /** @type {(child_id: string) => void} */ - const recursiveChildNodeHandler = (childId) => { - const childNode = node_getter(childId); - if (childNode === null) { - throw new Error(`Failed to find module info for ${childId}`); + /** + * @param {import('vite').ModuleNode} node + * @returns {string[] | null} + */ + function find(node) { + if (!node.id) return null; // TODO when does this happen? + + if (seen.has(node.id)) return null; + seen.add(node.id); + + if (node.id && illegal_import_names.has(node.id)) { + return [node.id]; } - if (illegal_import_names.some((name) => childId.endsWith(name))) { - throw handle_illegal_import_error(childId, illegal_module_stack); + for (const child of node.importedModules) { + const chain = child && find(child); + if (chain) return [node.id, ...chain]; } - throw_if_illegal_private_import_recursive_rollup( - node_getter, - childNode, - illegal_module_stack, - illegal_module_set - ); - }; - node.importedIds.forEach(recursiveChildNodeHandler); - node.dynamicallyImportedIds.forEach(recursiveChildNodeHandler); - illegal_module_set.delete(id); - illegal_module_stack.pop(); + return null; + } + + const chain = find(node); + + if (chain) { + throw handle_illegal_import_error(chain); + } } /** * Throw an error if a private module is imported from a client-side node. * @param {import('rollup').GetModuleInfo} node_getter - * @param {import('rollup').ModuleInfo} module_info + * @param {import('rollup').ModuleInfo} node */ -export function throw_if_illegal_private_import_rollup(node_getter, module_info) { - throw_if_illegal_private_import_recursive_rollup(node_getter, module_info); +export function throw_if_illegal_private_import_rollup(node_getter, node) { + const seen = new Set(); + + /** + * @param {import('rollup').ModuleInfo} node + * @returns {string[] | null} + */ + function find(node) { + if (seen.has(node.id)) return null; + seen.add(node.id); + + if (illegal_import_names.has(node.id)) { + return [node.id]; + } + + for (const id of [...node.importedIds, ...node.dynamicallyImportedIds]) { + const child = node_getter(id); + const chain = child && find(child); + if (chain) return [node.id, ...chain]; + } + + return null; + } + + const chain = find(node); + + if (chain) { + throw handle_illegal_import_error(chain); + } } From 13bc94a48a292454c60b7af5325b5eadb68c01f7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 14:07:18 -0400 Subject: [PATCH 37/95] make build-time traversal more efficient --- packages/kit/src/vite/dev/index.js | 5 +-- packages/kit/src/vite/index.js | 45 ++++++++++++++++++++------ packages/kit/src/vite/utils.js | 51 ++++-------------------------- 3 files changed, 44 insertions(+), 57 deletions(-) diff --git a/packages/kit/src/vite/dev/index.js b/packages/kit/src/vite/dev/index.js index 0a8d8d7d7398..8483bc422c51 100644 --- a/packages/kit/src/vite/dev/index.js +++ b/packages/kit/src/vite/dev/index.js @@ -24,9 +24,10 @@ const cwd = process.cwd(); * @param {import('vite').ViteDevServer} vite * @param {import('vite').ResolvedConfig} vite_config * @param {import('types').ValidatedConfig} svelte_config + * @param {Set} illegal_imports * @return {Promise void>>} */ -export async function dev(vite, vite_config, svelte_config) { +export async function dev(vite, vite_config, svelte_config, illegal_imports) { installPolyfills(); sync.init(svelte_config, vite_config.mode); @@ -63,7 +64,7 @@ export async function dev(vite, vite_config, svelte_config) { const node = await vite.moduleGraph.getModuleByUrl(url); if (!node) throw new Error(`Could not find node for ${url}`); - throw_if_illegal_private_import_vite(node); + throw_if_illegal_private_import_vite(node, illegal_imports); return { module, diff --git a/packages/kit/src/vite/index.js b/packages/kit/src/vite/index.js index 312109fcacfc..6b3f13c7b00d 100644 --- a/packages/kit/src/vite/index.js +++ b/packages/kit/src/vite/index.js @@ -14,7 +14,7 @@ import { generate_manifest } from '../core/generate_manifest/index.js'; import { get_runtime_directory, logger } from '../core/utils.js'; import { find_deps, get_default_config as get_default_build_config } from './build/utils.js'; import { preview } from './preview/index.js'; -import { get_aliases, resolve_entry, throw_if_illegal_private_import_rollup } from './utils.js'; +import { get_aliases, resolve_entry, format_illegal_import_chain } from './utils.js'; const cwd = process.cwd(); @@ -100,6 +100,9 @@ function kit() { /** @type {import('types').BuildData} */ let build_data; + /** @type {Set} */ + let illegal_imports; + /** * @type {{ * build_dir: string; @@ -183,6 +186,8 @@ function kit() { client_out_dir: `${svelte_config.kit.outDir}/output/client/` }; + illegal_imports = new Set([`${svelte_config.kit.outDir}/runtime/app/env/private.js`]); + if (is_build) { manifest_data = sync.all(svelte_config, config_env.mode).manifest_data; @@ -267,14 +272,34 @@ function kit() { * then use this hook to kick off builds for the server and service worker. */ async writeBundle(_options, bundle) { - for (const id of this.getModuleIds()) { - const found = manifest_data.components.find((comp) => id.endsWith(comp)); - if (found) { - const module_info = this.getModuleInfo(id); - if (module_info === null) { - throw new Error(`Failed to locate module info for ${module_info}`); - } - throw_if_illegal_private_import_rollup(this.getModuleInfo.bind(this), module_info); + const seen = new Set(); + + /** + * @param {import('rollup').ModuleInfo} node + * @returns {string[] | undefined} + */ + const find_illegal_imports = (node) => { + if (seen.has(node.id)) return; + seen.add(node.id); + + if (illegal_imports.has(node.id)) { + return [node.id]; + } + + for (const id of [...node.importedIds, ...node.dynamicallyImportedIds]) { + const child = this.getModuleInfo(id); + const chain = child && find_illegal_imports(child); + if (chain) return [node.id, ...chain]; + } + }; + + for (const file of manifest_data.components) { + const id = path.resolve(file); + const node = this.getModuleInfo(id); + + if (node) { + const chain = find_illegal_imports(node); + if (chain) throw new Error(format_illegal_import_chain(chain)); } } @@ -399,7 +424,7 @@ function kit() { * @see https://vitejs.dev/guide/api-plugin.html#configureserver */ async configureServer(vite) { - return await dev(vite, vite_config, svelte_config); + return await dev(vite, vite_config, svelte_config, illegal_imports); }, /** diff --git a/packages/kit/src/vite/utils.js b/packages/kit/src/vite/utils.js index 088a898867ee..f57c0792e0e2 100644 --- a/packages/kit/src/vite/utils.js +++ b/packages/kit/src/vite/utils.js @@ -135,10 +135,6 @@ export function resolve_entry(entry) { return null; } -// TODO remove hardcoded path -// TODO are module IDs absolute paths on Windows as well? -const illegal_import_names = new Set([path.resolve('.svelte-kit/runtime/app/env/private.js')]); - /** * * @param {string} str @@ -152,21 +148,22 @@ function repeat(str, times) { * Create a formatted error for an illegal import. * @param {Array} stack */ -function handle_illegal_import_error(stack) { +export function format_illegal_import_chain(stack) { stack = stack.map((file) => path.relative(process.cwd(), file).replace('.svelte-kit/runtime/app', '$app') ); const pyramid = stack.map((file, i) => `${repeat(' ', i * 2)}- ${file}`).join('\n'); - return new Error(`Cannot import ${stack.at(-1)} into client-side code:\n${pyramid}`); + return `Cannot import ${stack.at(-1)} into client-side code:\n${pyramid}`; } /** * Throw an error if a private module is imported from a client-side node. * @param {import('vite').ModuleNode} node + * @param {Set} illegal_imports */ -export function throw_if_illegal_private_import_vite(node) { +export function throw_if_illegal_private_import_vite(node, illegal_imports) { const seen = new Set(); /** @@ -179,7 +176,7 @@ export function throw_if_illegal_private_import_vite(node) { if (seen.has(node.id)) return null; seen.add(node.id); - if (node.id && illegal_import_names.has(node.id)) { + if (node.id && illegal_imports.has(node.id)) { return [node.id]; } @@ -194,42 +191,6 @@ export function throw_if_illegal_private_import_vite(node) { const chain = find(node); if (chain) { - throw handle_illegal_import_error(chain); - } -} - -/** - * Throw an error if a private module is imported from a client-side node. - * @param {import('rollup').GetModuleInfo} node_getter - * @param {import('rollup').ModuleInfo} node - */ -export function throw_if_illegal_private_import_rollup(node_getter, node) { - const seen = new Set(); - - /** - * @param {import('rollup').ModuleInfo} node - * @returns {string[] | null} - */ - function find(node) { - if (seen.has(node.id)) return null; - seen.add(node.id); - - if (illegal_import_names.has(node.id)) { - return [node.id]; - } - - for (const id of [...node.importedIds, ...node.dynamicallyImportedIds]) { - const child = node_getter(id); - const chain = child && find(child); - if (chain) return [node.id, ...chain]; - } - - return null; - } - - const chain = find(node); - - if (chain) { - throw handle_illegal_import_error(chain); + throw new Error(format_illegal_import_chain(chain)); } } From 40a6eaae193591ae46f6881f8864107cd2e9ea09 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 14:16:55 -0400 Subject: [PATCH 38/95] move dev code into dev module --- packages/kit/src/vite/dev/index.js | 41 ++++++++++++++++++++++++++++-- packages/kit/src/vite/utils.js | 37 --------------------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/packages/kit/src/vite/dev/index.js b/packages/kit/src/vite/dev/index.js index 8483bc422c51..4f16f503636f 100644 --- a/packages/kit/src/vite/dev/index.js +++ b/packages/kit/src/vite/dev/index.js @@ -12,7 +12,7 @@ import { load_template } from '../../core/config/index.js'; import { SVELTE_KIT_ASSETS } from '../../core/constants.js'; import * as sync from '../../core/sync/sync.js'; import { get_mime_lookup, get_runtime_prefix } from '../../core/utils.js'; -import { resolve_entry, throw_if_illegal_private_import_vite } from '../utils.js'; +import { format_illegal_import_chain, resolve_entry } from '../utils.js'; // Vite doesn't expose this so we just copy the list for now // https://github.com/vitejs/vite/blob/3edd1af56e980aef56641a5a51cf2932bb580d41/packages/vite/src/node/plugins/css.ts#L96 @@ -64,7 +64,7 @@ export async function dev(vite, vite_config, svelte_config, illegal_imports) { const node = await vite.moduleGraph.getModuleByUrl(url); if (!node) throw new Error(`Could not find node for ${url}`); - throw_if_illegal_private_import_vite(node, illegal_imports); + prevent_illegal_imports(node, illegal_imports); return { module, @@ -457,6 +457,43 @@ async function find_deps(vite, node, deps) { await Promise.all(branches); } +/** + * Throw an error if a private module is imported from a client-side node. + * @param {import('vite').ModuleNode} node + * @param {Set} illegal_imports + */ +export function prevent_illegal_imports(node, illegal_imports) { + const seen = new Set(); + + /** + * @param {import('vite').ModuleNode} node + * @returns {string[] | null} + */ + function find(node) { + if (!node.id) return null; // TODO when does this happen? + + if (seen.has(node.id)) return null; + seen.add(node.id); + + if (node.id && illegal_imports.has(node.id)) { + return [node.id]; + } + + for (const child of node.importedModules) { + const chain = child && find(child); + if (chain) return [node.id, ...chain]; + } + + return null; + } + + const chain = find(node); + + if (chain) { + throw new Error(format_illegal_import_chain(chain)); + } +} + /** * Determine if a file is being requested with the correct case, * to ensure consistent behaviour between dev and prod and across diff --git a/packages/kit/src/vite/utils.js b/packages/kit/src/vite/utils.js index f57c0792e0e2..a4e3766ed9a6 100644 --- a/packages/kit/src/vite/utils.js +++ b/packages/kit/src/vite/utils.js @@ -157,40 +157,3 @@ export function format_illegal_import_chain(stack) { return `Cannot import ${stack.at(-1)} into client-side code:\n${pyramid}`; } - -/** - * Throw an error if a private module is imported from a client-side node. - * @param {import('vite').ModuleNode} node - * @param {Set} illegal_imports - */ -export function throw_if_illegal_private_import_vite(node, illegal_imports) { - const seen = new Set(); - - /** - * @param {import('vite').ModuleNode} node - * @returns {string[] | null} - */ - function find(node) { - if (!node.id) return null; // TODO when does this happen? - - if (seen.has(node.id)) return null; - seen.add(node.id); - - if (node.id && illegal_imports.has(node.id)) { - return [node.id]; - } - - for (const child of node.importedModules) { - const chain = child && find(child); - if (chain) return [node.id, ...chain]; - } - - return null; - } - - const chain = find(node); - - if (chain) { - throw new Error(format_illegal_import_chain(chain)); - } -} From 312a7bc6e51f6ccc448b25c032fa19477a6c3ea3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 14:18:59 -0400 Subject: [PATCH 39/95] always set env --- packages/kit/src/vite/build/build_server.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/kit/src/vite/build/build_server.js b/packages/kit/src/vite/build/build_server.js index f2b9f179c4ea..7cb6b6152ee2 100644 --- a/packages/kit/src/vite/build/build_server.js +++ b/packages/kit/src/vite/build/build_server.js @@ -36,7 +36,6 @@ const template = ({ head, body, assets, nonce }) => ${s(template) .replace(/%sveltekit\.nonce%/g, '" + nonce + "')}; let read = null; -let initialized = false; set_paths(${s(config.kit.paths)}); @@ -91,10 +90,7 @@ export class Server { } init({ env }) { - if (!initialized) { - set_env(env); - initialized = true; - } + set_env(env); } async respond(request, options = {}) { From 672eb85e73f40e014a9088dcc77233500905882c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 14:19:51 -0400 Subject: [PATCH 40/95] simplify --- packages/kit/src/vite/dev/index.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/kit/src/vite/dev/index.js b/packages/kit/src/vite/dev/index.js index b241ece45ed8..615c85223a8e 100644 --- a/packages/kit/src/vite/dev/index.js +++ b/packages/kit/src/vite/dev/index.js @@ -239,13 +239,7 @@ export async function dev(vite, vite_config, svelte_config, illegal_imports) { /** @type {(env: Record) => void} */ const set_env = (await import(`${runtime}/app/env/runtime.js`)).set_env; - - set_env( - // This just makes sure no values are undefined - Object.entries(process.env).reduce((prev, [k, v]) => { - return { ...prev, [k]: v ?? '' }; - }, {}) - ); + set_env(/** @type {Record} */ (process.env)); const handle = user_hooks.handle || (({ event, resolve }) => resolve(event)); From 8ce2740ca6e2abb36bd5d10263272a454d6bcc37 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 14:25:40 -0400 Subject: [PATCH 41/95] remove hard-coded .svelte-kit --- packages/kit/src/vite/dev/index.js | 7 ++++--- packages/kit/src/vite/index.js | 2 +- packages/kit/src/vite/utils.js | 12 ++++++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/kit/src/vite/dev/index.js b/packages/kit/src/vite/dev/index.js index 615c85223a8e..4ac46a7705a3 100644 --- a/packages/kit/src/vite/dev/index.js +++ b/packages/kit/src/vite/dev/index.js @@ -64,7 +64,7 @@ export async function dev(vite, vite_config, svelte_config, illegal_imports) { const node = await vite.moduleGraph.getModuleByUrl(url); if (!node) throw new Error(`Could not find node for ${url}`); - prevent_illegal_imports(node, illegal_imports); + prevent_illegal_imports(node, svelte_config.kit, illegal_imports); return { module, @@ -455,9 +455,10 @@ async function find_deps(vite, node, deps) { /** * Throw an error if a private module is imported from a client-side node. * @param {import('vite').ModuleNode} node + * @param {import('types').ValidatedKitConfig} config * @param {Set} illegal_imports */ -export function prevent_illegal_imports(node, illegal_imports) { +export function prevent_illegal_imports(node, config, illegal_imports) { const seen = new Set(); /** @@ -485,7 +486,7 @@ export function prevent_illegal_imports(node, illegal_imports) { const chain = find(node); if (chain) { - throw new Error(format_illegal_import_chain(chain)); + throw new Error(format_illegal_import_chain(chain, config)); } } diff --git a/packages/kit/src/vite/index.js b/packages/kit/src/vite/index.js index 1dca0b650db8..81c817e840b4 100644 --- a/packages/kit/src/vite/index.js +++ b/packages/kit/src/vite/index.js @@ -305,7 +305,7 @@ function kit() { if (node) { const chain = find_illegal_imports(node); - if (chain) throw new Error(format_illegal_import_chain(chain)); + if (chain) throw new Error(format_illegal_import_chain(chain, svelte_config.kit)); } } diff --git a/packages/kit/src/vite/utils.js b/packages/kit/src/vite/utils.js index a4e3766ed9a6..af927b4bf013 100644 --- a/packages/kit/src/vite/utils.js +++ b/packages/kit/src/vite/utils.js @@ -147,11 +147,15 @@ function repeat(str, times) { /** * Create a formatted error for an illegal import. * @param {Array} stack + * @param {import('types').ValidatedKitConfig} config */ -export function format_illegal_import_chain(stack) { - stack = stack.map((file) => - path.relative(process.cwd(), file).replace('.svelte-kit/runtime/app', '$app') - ); +export function format_illegal_import_chain(stack, config) { + const app = path.join(config.outDir, 'runtime/app'); + + stack = stack.map((file) => { + if (file.startsWith(app)) return file.replace(app, '$app'); + return path.relative(process.cwd(), file); + }); const pyramid = stack.map((file, i) => `${repeat(' ', i * 2)}- ${file}`).join('\n'); From 65ee08c83944b02e22dd88d54d4111ff10ff627a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 15:02:04 -0400 Subject: [PATCH 42/95] rename env/runtime to env/platform and get it working in dev --- packages/kit/rollup.config.js | 2 +- .../src/runtime/app/env/{runtime.js => platform.js} | 0 packages/kit/src/vite/build/build_server.js | 2 +- packages/kit/src/vite/dev/index.js | 12 +++++++++--- packages/kit/src/vite/index.js | 5 ++++- packages/kit/types/ambient.d.ts | 4 ++-- 6 files changed, 17 insertions(+), 8 deletions(-) rename packages/kit/src/runtime/app/env/{runtime.js => platform.js} (100%) diff --git a/packages/kit/rollup.config.js b/packages/kit/rollup.config.js index 0cab93621d98..7bc1d56bed9e 100644 --- a/packages/kit/rollup.config.js +++ b/packages/kit/rollup.config.js @@ -23,7 +23,7 @@ export default [ 'app/stores': 'src/runtime/app/stores.js', 'app/paths': 'src/runtime/app/paths.js', 'app/env': 'src/runtime/app/env.js', - 'app/env/runtime': 'src/runtime/app/env/runtime.js', + 'app/env/platform': 'src/runtime/app/env/platform.js', paths: 'src/runtime/paths.js', env: 'src/runtime/env.js' }, diff --git a/packages/kit/src/runtime/app/env/runtime.js b/packages/kit/src/runtime/app/env/platform.js similarity index 100% rename from packages/kit/src/runtime/app/env/runtime.js rename to packages/kit/src/runtime/app/env/platform.js diff --git a/packages/kit/src/vite/build/build_server.js b/packages/kit/src/vite/build/build_server.js index 7cb6b6152ee2..f68fb0ccf049 100644 --- a/packages/kit/src/vite/build/build_server.js +++ b/packages/kit/src/vite/build/build_server.js @@ -27,7 +27,7 @@ import root from '__GENERATED__/root.svelte'; import { respond } from '${runtime}/server/index.js'; import { set_paths, assets, base } from '${runtime}/paths.js'; import { set_prerendering } from '${runtime}/env.js'; -import { set_env } from '${runtime}/app/env/runtime.js'; +import { set_env } from '${runtime}/app/env/platform.js'; const template = ({ head, body, assets, nonce }) => ${s(template) .replace('%sveltekit.head%', '" + head + "') diff --git a/packages/kit/src/vite/dev/index.js b/packages/kit/src/vite/dev/index.js index 4ac46a7705a3..00591376f699 100644 --- a/packages/kit/src/vite/dev/index.js +++ b/packages/kit/src/vite/dev/index.js @@ -237,9 +237,15 @@ export async function dev(vite, vite_config, svelte_config, illegal_imports) { ? await vite.ssrLoadModule(`/${svelte_config.kit.files.hooks}`) : {}; - /** @type {(env: Record) => void} */ - const set_env = (await import(`${runtime}/app/env/runtime.js`)).set_env; - set_env(/** @type {Record} */ (process.env)); + const { set_env } = await vite.ssrLoadModule( + process.env.BUNDLED + ? `/${posixify( + path.relative(cwd, `${svelte_config.kit.outDir}/runtime/app/env/platform.js`) + )}` + : `/@fs${runtime}/app/env/platform.js` + ); + + set_env(process.env); const handle = user_hooks.handle || (({ event, resolve }) => resolve(event)); diff --git a/packages/kit/src/vite/index.js b/packages/kit/src/vite/index.js index 81c817e840b4..525d5046e06e 100644 --- a/packages/kit/src/vite/index.js +++ b/packages/kit/src/vite/index.js @@ -189,7 +189,10 @@ function kit() { client_out_dir: `${svelte_config.kit.outDir}/output/client/` }; - illegal_imports = new Set([`${svelte_config.kit.outDir}/runtime/app/env/private.js`]); + illegal_imports = new Set([ + `${svelte_config.kit.outDir}/runtime/app/env/platform.js`, + `${svelte_config.kit.outDir}/runtime/app/env/private.js` + ]); if (is_build) { manifest_data = sync.all(svelte_config, config_env.mode).manifest_data; diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts index b755862cbda6..3e360e57644b 100644 --- a/packages/kit/types/ambient.d.ts +++ b/packages/kit/types/ambient.d.ts @@ -63,7 +63,7 @@ declare namespace App { export interface Stuff {} /** - * The interface that defines the runtime environment variables exported from '$app/env/runtime'. + * The interface that defines the dynamic environment variables exported from '$app/env/platform'. */ export interface RuntimeEnv extends Record {} } @@ -90,7 +90,7 @@ declare module '$app/env' { export const prerendering: boolean; } -declare module '$app/env/runtime' { +declare module '$app/env/platform' { /** * The runtime environment variables, as defined by the adapter. */ From 133cc7a9a9bea1cafa00bd111bae8ce1f0e5eb82 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 15:05:25 -0400 Subject: [PATCH 43/95] remove outdated comment --- packages/kit/src/vite/preview/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kit/src/vite/preview/index.js b/packages/kit/src/vite/preview/index.js index d5e588360650..240db53c436b 100644 --- a/packages/kit/src/vite/preview/index.js +++ b/packages/kit/src/vite/preview/index.js @@ -43,7 +43,6 @@ export async function preview(vite, config, protocol) { const server = new Server(manifest); server.init({ - // This just makes sure no values are undefined env: /** @type {Record} */ (process.env) }); From e56bf28ce962daacaa908cd0f5fac4a21bc52395 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 22 Jul 2022 16:56:01 -0400 Subject: [PATCH 44/95] docs --- documentation/docs/15-configuration.md | 15 ++- documentation/faq/60-env-vars.md | 6 +- packages/kit/scripts/extract-types.js | 120 ++++++++++-------- .../scripts/special-types/$app+env+private.md | 7 + .../scripts/special-types/$app+env+public.md | 7 + packages/kit/scripts/special-types/$lib.md | 1 + packages/kit/types/ambient.d.ts | 37 ++++-- .../src/lib/docs/server/modules.js | 6 +- 8 files changed, 121 insertions(+), 78 deletions(-) create mode 100644 packages/kit/scripts/special-types/$app+env+private.md create mode 100644 packages/kit/scripts/special-types/$app+env+public.md create mode 100644 packages/kit/scripts/special-types/$lib.md diff --git a/documentation/docs/15-configuration.md b/documentation/docs/15-configuration.md index c51d158e81b5..d48e3a3d2255 100644 --- a/documentation/docs/15-configuration.md +++ b/documentation/docs/15-configuration.md @@ -29,7 +29,9 @@ const config = { // ... } }, - moduleExtensions: ['.js', '.ts'], + env: { + publicPrefix: 'PUBLIC_' + }, files: { assets: 'static', hooks: 'src/hooks', @@ -44,6 +46,7 @@ const config = { parameter: '_method', allowed: [] }, + moduleExtensions: ['.js', '.ts'], outDir: '.svelte-kit', package: { dir: 'package', @@ -157,9 +160,11 @@ When pages are prerendered, the CSP header is added via a `` ta > Note that most [Svelte transitions](https://svelte.dev/tutorial/transition) work by creating an inline `