@sveltejs/kit 1.0.0-next.539 → 1.0.0-next.540

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.0.0-next.539",
3
+ "version": "1.0.0-next.540",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -1,6 +1,6 @@
1
1
  This module provides access to runtime environment variables, as defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/master/packages/adapter-node) (or running [`vite preview`](https://kit.svelte.dev/docs/cli)), this is equivalent to `process.env`. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#env).
2
2
 
3
- This module cannot be imported into public-facing code.
3
+ This module cannot be imported into client-side code.
4
4
 
5
5
  ```ts
6
6
  import { env } from '$env/dynamic/private';
@@ -1,4 +1,4 @@
1
- Environment variables [loaded by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files) from `.env` files and `process.env`. Like [`$env/dynamic/private`](https://kit.svelte.dev/docs/modules#$env-dynamic-private), this module cannot be imported into public-facing code. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#env).
1
+ Environment variables [loaded by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files) from `.env` files and `process.env`. Like [`$env/dynamic/private`](https://kit.svelte.dev/docs/modules#$env-dynamic-private), this module cannot be imported into client-side code. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://kit.svelte.dev/docs/configuration#env).
2
2
 
3
3
  _Unlike_ [`$env/dynamic/private`](https://kit.svelte.dev/docs/modules#$env-dynamic-private), the values exported from this module are statically injected into your bundle at build time, enabling optimisations like dead code elimination.
4
4
 
@@ -2,4 +2,4 @@ This is a simple alias to `src/lib`, or whatever directory is specified as [`con
2
2
 
3
3
  #### `$lib/server`
4
4
 
5
- A subdirectory of `$lib`. SvelteKit will prevent you from importing any modules in `$lib/server` into public-facing code. See [server-only modules](/docs/server-only-modules).
5
+ A subdirectory of `$lib`. SvelteKit will prevent you from importing any modules in `$lib/server` into client-side code. See [server-only modules](/docs/server-only-modules).
@@ -122,6 +122,15 @@ export async function setResponse(res, response) {
122
122
  return;
123
123
  }
124
124
 
125
+ if (response.body.locked) {
126
+ res.write(
127
+ 'Fatal error: Response body is locked. ' +
128
+ `This can happen when the response was already read (for example through 'response.json()' or 'response.text()').`
129
+ );
130
+ res.end();
131
+ return;
132
+ }
133
+
125
134
  const reader = response.body.getReader();
126
135
 
127
136
  if (res.destroyed) {
@@ -11,9 +11,7 @@ import { load_error_page, load_template } from '../../../core/config/index.js';
11
11
  import { SVELTE_KIT_ASSETS } from '../../../constants.js';
12
12
  import * as sync from '../../../core/sync/sync.js';
13
13
  import { get_mime_lookup, runtime_base, runtime_prefix } from '../../../core/utils.js';
14
- import { prevent_illegal_vite_imports } from '../graph_analysis/index.js';
15
14
  import { compact } from '../../../utils/array.js';
16
- import { normalizePath } from 'vite';
17
15
 
18
16
  // Vite doesn't expose this so we just copy the list for now
19
17
  // https://github.com/vitejs/vite/blob/3edd1af56e980aef56641a5a51cf2932bb580d41/packages/vite/src/node/plugins/css.ts#L96
@@ -43,8 +41,6 @@ export async function dev(vite, vite_config, svelte_config) {
43
41
  /** @type {import('types').SSRManifest} */
44
42
  let manifest;
45
43
 
46
- const extensions = [...svelte_config.extensions, ...svelte_config.kit.moduleExtensions];
47
-
48
44
  /** @param {string} id */
49
45
  async function resolve(id) {
50
46
  const url = id.startsWith('..') ? `/@fs${path.posix.resolve(id)}` : `/${id}`;
@@ -94,12 +90,6 @@ export async function dev(vite, vite_config, svelte_config) {
94
90
  module_nodes.push(module_node);
95
91
  result.file = url.endsWith('.svelte') ? url : url + '?import'; // TODO what is this for?
96
92
 
97
- prevent_illegal_vite_imports(
98
- module_node,
99
- normalizePath(svelte_config.kit.files.lib),
100
- extensions
101
- );
102
-
103
93
  return module.default;
104
94
  };
105
95
  }
@@ -110,12 +100,6 @@ export async function dev(vite, vite_config, svelte_config) {
110
100
  module_nodes.push(module_node);
111
101
 
112
102
  result.shared = module;
113
-
114
- prevent_illegal_vite_imports(
115
- module_node,
116
- normalizePath(svelte_config.kit.files.lib),
117
- extensions
118
- );
119
103
  }
120
104
 
121
105
  if (node.server) {
@@ -1,277 +1,107 @@
1
1
  import path from 'path';
2
- import { normalizePath } from 'vite';
3
- import { remove_query_from_id, get_module_types } from './utils.js';
2
+ import { posixify } from '../../../utils/filesystem.js';
4
3
 
5
- /** @typedef {import('./types').ImportGraph} ImportGraph */
6
-
7
- const CWD_ID = normalizePath(process.cwd());
8
- const NODE_MODULES_ID = normalizePath(path.resolve(process.cwd(), 'node_modules'));
9
- const ILLEGAL_IMPORTS = new Set([
10
- '/@id/__x00__$env/dynamic/private', //dev
11
- '\0$env/dynamic/private', // prod
12
- '/@id/__x00__$env/static/private', // dev
13
- '\0$env/static/private' // prod
14
- ]);
4
+ const ILLEGAL_IMPORTS = new Set(['\0$env/dynamic/private', '\0$env/static/private']);
15
5
  const ILLEGAL_MODULE_NAME_PATTERN = /.*\.server\..+/;
16
6
 
17
- export class IllegalModuleGuard {
18
- /** @type {string} */
19
- #lib_dir;
20
-
21
- /** @type {string} */
22
- #server_dir;
23
-
24
- /** @type {Array<ImportGraph>} */
25
- #chain = [];
26
-
27
- /**
28
- * @param {string} lib_dir
29
- */
30
- constructor(lib_dir) {
31
- this.#lib_dir = normalizePath(lib_dir);
32
- this.#server_dir = normalizePath(path.resolve(lib_dir, 'server'));
33
- }
7
+ /**
8
+ * Checks if given id imports a module that is not allowed to be imported into client-side code.
9
+ * @param {string} id
10
+ * @param {{
11
+ * cwd: string;
12
+ * node_modules: string;
13
+ * server: string;
14
+ * }} dirs
15
+ */
16
+ export function is_illegal(id, dirs) {
17
+ if (ILLEGAL_IMPORTS.has(id)) return true;
18
+ if (!id.startsWith(dirs.cwd) || id.startsWith(dirs.node_modules)) return false;
19
+ return ILLEGAL_MODULE_NAME_PATTERN.test(path.basename(id)) || id.startsWith(dirs.server);
20
+ }
34
21
 
35
- /**
36
- * Assert that a node imports no illegal modules.
37
- * @param {ImportGraph} node
38
- * @returns {void}
39
- */
40
- assert_legal(node) {
41
- this.#chain.push(node);
42
- for (const child of node.children) {
43
- if (this.#is_illegal(child.id)) {
44
- this.#chain.push(child);
45
- const error = this.#format_illegal_import_chain(this.#chain);
46
- this.#chain = []; // Reset the chain in case we want to reuse this guard
47
- throw new Error(error);
48
- }
49
- this.assert_legal(child);
50
- }
51
- this.#chain.pop();
52
- }
22
+ /**
23
+ * Creates a guard that checks that no id imports a module that is not allowed to be imported into client-side code.
24
+ * @param {import('rollup').PluginContext} context
25
+ * @param {{ cwd: string, lib: string }} paths
26
+ */
27
+ export function module_guard(context, { cwd, lib }) {
28
+ /** @type {Set<string>} */
29
+ const seen = new Set();
53
30
 
54
- /**
55
- * `true` if the provided ID represents a server-only module, else `false`.
56
- * @param {string} module_id
57
- * @returns {boolean}
58
- */
59
- #is_illegal(module_id) {
60
- if (this.#is_kit_illegal(module_id) || this.#is_user_illegal(module_id)) return true;
61
- return false;
62
- }
31
+ const dirs = {
32
+ // ids will be posixified, so we need to posixify these, too
33
+ cwd: posixify(cwd),
34
+ node_modules: posixify(path.join(cwd, 'node_modules')),
35
+ server: posixify(path.join(lib, 'server'))
36
+ };
63
37
 
64
38
  /**
65
- * `true` if the provided ID represents a Kit-defined server-only module, else `false`.
66
- * @param {string} module_id
67
- * @returns {boolean}
39
+ * @param {string} id
40
+ * @param {Array<{ id: string, dynamic: boolean }>} chain
68
41
  */
69
- #is_kit_illegal(module_id) {
70
- return ILLEGAL_IMPORTS.has(module_id);
71
- }
42
+ function follow(id, chain) {
43
+ if (seen.has(id)) return;
44
+ seen.add(id);
72
45
 
73
- /**
74
- * `true` if the provided ID represents a user-defined server-only module, else `false`.
75
- * @param {string} module_id
76
- * @returns {boolean}
77
- */
78
- #is_user_illegal(module_id) {
79
- if (module_id.startsWith(this.#server_dir)) return true;
46
+ if (is_illegal(id, dirs)) {
47
+ chain.shift(); // discard the entry point
48
+ id = normalize_id(id, lib, cwd);
80
49
 
81
- // files outside the project root are ignored
82
- if (!module_id.startsWith(CWD_ID)) return false;
50
+ const pyramid =
51
+ chain.map(({ id, dynamic }, i) => {
52
+ id = normalize_id(id, lib, cwd);
83
53
 
84
- // so are files inside node_modules
85
- if (module_id.startsWith(NODE_MODULES_ID)) return false;
54
+ return `${repeat(' ', i * 2)}- ${id} ${dynamic ? 'dynamically imports' : 'imports'}\n`;
55
+ }) + `${repeat(' ', chain.length)}- ${id}`;
86
56
 
87
- return ILLEGAL_MODULE_NAME_PATTERN.test(path.basename(module_id));
88
- }
57
+ const message = `Cannot import ${id} into client-side code:\n${pyramid}`;
89
58
 
90
- /**
91
- * @param {string} str
92
- * @param {number} times
93
- */
94
- #repeat(str, times) {
95
- return new Array(times + 1).join(str);
96
- }
59
+ throw new Error(message);
60
+ }
97
61
 
98
- /**
99
- * Create a formatted error for an illegal import.
100
- * @param {Array<ImportGraph>} stack
101
- */
102
- #format_illegal_import_chain(stack) {
103
- const dev_virtual_prefix = '/@id/__x00__';
104
- const prod_virtual_prefix = '\0';
62
+ const module = context.getModuleInfo(id);
105
63
 
106
- stack = stack.map((graph) => {
107
- if (graph.id.startsWith(dev_virtual_prefix)) {
108
- return { ...graph, id: graph.id.replace(dev_virtual_prefix, '') };
109
- }
110
- if (graph.id.startsWith(prod_virtual_prefix)) {
111
- return { ...graph, id: graph.id.replace(prod_virtual_prefix, '') };
112
- }
113
- if (graph.id.startsWith(this.#lib_dir)) {
114
- return { ...graph, id: graph.id.replace(this.#lib_dir, '$lib') };
64
+ if (module) {
65
+ for (const child of module.importedIds) {
66
+ follow(child, [...chain, { id, dynamic: false }]);
115
67
  }
116
68
 
117
- return { ...graph, id: path.relative(process.cwd(), graph.id) };
118
- });
119
-
120
- const pyramid = stack
121
- .map(
122
- (file, i) =>
123
- `${this.#repeat(' ', i * 2)}- ${file.id} ${
124
- file.dynamic ? '(imported by parent dynamically)' : ''
125
- }`
126
- )
127
- .join('\n');
128
-
129
- return `Cannot import ${stack.at(-1)?.id} into public-facing code:\n${pyramid}`;
130
- }
131
- }
132
-
133
- /** @implements {ImportGraph} */
134
- export class RollupImportGraph {
135
- /** @type {(id: string) => import('rollup').ModuleInfo | null} */
136
- #node_getter;
137
-
138
- /** @type {import('rollup').ModuleInfo} */
139
- #module_info;
140
-
141
- /** @type {string} */
142
- id;
143
-
144
- /** @type {boolean} */
145
- dynamic;
146
-
147
- /** @type {Set<string>} */
148
- #seen;
149
-
150
- /**
151
- * @param {(id: string) => import('rollup').ModuleInfo | null} node_getter
152
- * @param {import('rollup').ModuleInfo} node
153
- */
154
- constructor(node_getter, node) {
155
- this.#node_getter = node_getter;
156
- this.#module_info = node;
157
- this.id = remove_query_from_id(normalizePath(node.id));
158
- this.dynamic = false;
159
- this.#seen = new Set();
160
- }
161
-
162
- /**
163
- * @param {(id: string) => import('rollup').ModuleInfo | null} node_getter
164
- * @param {import('rollup').ModuleInfo} node
165
- * @param {boolean} dynamic
166
- * @param {Set<string>} seen;
167
- * @returns {RollupImportGraph}
168
- */
169
- static #new_internal(node_getter, node, dynamic, seen) {
170
- const instance = new RollupImportGraph(node_getter, node);
171
- instance.dynamic = dynamic;
172
- instance.#seen = seen;
173
- return instance;
174
- }
175
-
176
- get children() {
177
- return this.#children();
69
+ for (const child of module.dynamicallyImportedIds) {
70
+ follow(child, [...chain, { id, dynamic: true }]);
71
+ }
72
+ }
178
73
  }
179
74
 
180
- *#children() {
181
- if (this.#seen.has(this.id)) return;
182
- this.#seen.add(this.id);
183
- for (const id of this.#module_info.importedIds) {
184
- const child = this.#node_getter(id);
185
- if (child === null) return;
186
- yield RollupImportGraph.#new_internal(this.#node_getter, child, false, this.#seen);
187
- }
188
- for (const id of this.#module_info.dynamicallyImportedIds) {
189
- const child = this.#node_getter(id);
190
- if (child === null) return;
191
- yield RollupImportGraph.#new_internal(this.#node_getter, child, true, this.#seen);
75
+ return {
76
+ /** @param {string} id should be posixified */
77
+ check: (id) => {
78
+ follow(id, []);
192
79
  }
193
- }
80
+ };
194
81
  }
195
82
 
196
- /** @implements {ImportGraph} */
197
- export class ViteImportGraph {
198
- /** @type {Set<string>} */
199
- #module_types;
200
-
201
- /** @type {import('vite').ModuleNode} */
202
- #module_info;
203
-
204
- /** @type {string} */
205
- id;
206
-
207
- /** @type {Set<string>} */
208
- #seen;
209
-
210
- /**
211
- * @param {Set<string>} module_types Module types to analyze, eg '.js', '.ts', etc.
212
- * @param {import('vite').ModuleNode} node
213
- */
214
- constructor(module_types, node) {
215
- this.#module_types = module_types;
216
- this.#module_info = node;
217
- this.id = remove_query_from_id(normalizePath(node.id ?? ''));
218
- this.#seen = new Set();
219
- }
220
-
221
- /**
222
- * @param {Set<string>} module_types Module types to analyze, eg '.js', '.ts', etc.
223
- * @param {import('vite').ModuleNode} node
224
- * @param {Set<string>} seen
225
- * @returns {ViteImportGraph}
226
- */
227
- static #new_internal(module_types, node, seen) {
228
- const instance = new ViteImportGraph(module_types, node);
229
- instance.#seen = seen;
230
- return instance;
231
- }
232
-
233
- get dynamic() {
234
- return false;
83
+ /**
84
+ * Removes cwd/lib path from the start of the id
85
+ * @param {string} id
86
+ * @param {string} lib
87
+ * @param {string} cwd
88
+ */
89
+ export function normalize_id(id, lib, cwd) {
90
+ if (id.startsWith(lib)) {
91
+ id = id.replace(lib, '$lib');
235
92
  }
236
93
 
237
- get children() {
238
- return this.#children();
94
+ if (id.startsWith(cwd)) {
95
+ id = path.relative(cwd, id);
239
96
  }
240
97
 
241
- *#children() {
242
- if (this.#seen.has(this.id)) return;
243
- this.#seen.add(this.id);
244
- for (const child of this.#module_info.importedModules) {
245
- if (!this.#module_types.has(path.extname(this.id))) {
246
- continue;
247
- }
248
- yield ViteImportGraph.#new_internal(this.#module_types, child, this.#seen);
249
- }
250
- }
251
- }
252
-
253
- /**
254
- * Throw an error if a private module is imported from a client-side node.
255
- * @param {(id: string) => import('rollup').ModuleInfo | null} node_getter
256
- * @param {import('rollup').ModuleInfo} node
257
- * @param {string} lib_dir
258
- * @returns {void}
259
- */
260
- export function prevent_illegal_rollup_imports(node_getter, node, lib_dir) {
261
- const graph = new RollupImportGraph(node_getter, node);
262
- const guard = new IllegalModuleGuard(lib_dir);
263
- guard.assert_legal(graph);
98
+ return posixify(id);
264
99
  }
265
100
 
266
101
  /**
267
- * Throw an error if a private module is imported from a client-side node.
268
- * @param {import('vite').ModuleNode} node
269
- * @param {string} lib_dir
270
- * @param {Iterable<string>} module_types File extensions to analyze in addition to the defaults: `.ts`, `.js`, etc.
271
- * @returns {void}
102
+ * @param {string} str
103
+ * @param {number} times
272
104
  */
273
- export function prevent_illegal_vite_imports(node, lib_dir, module_types) {
274
- const graph = new ViteImportGraph(get_module_types(module_types), node);
275
- const guard = new IllegalModuleGuard(lib_dir);
276
- guard.assert_legal(graph);
105
+ function repeat(str, times) {
106
+ return new Array(times + 1).join(str);
277
107
  }
@@ -4,27 +4,3 @@ const query_pattern = /\?.*$/s;
4
4
  export function remove_query_from_id(path) {
5
5
  return path.replace(query_pattern, '');
6
6
  }
7
-
8
- /**
9
- * Vite does some weird things with import trees in dev
10
- * for example, a Tailwind app.css will appear to import
11
- * every file in the project. This isn't a problem for
12
- * Rollup during build.
13
- * @param {Iterable<string>} config_module_types
14
- */
15
- export const get_module_types = (config_module_types) => {
16
- return new Set([
17
- '',
18
- '.ts',
19
- '.js',
20
- '.svelte',
21
- '.mts',
22
- '.mjs',
23
- '.cts',
24
- '.cjs',
25
- '.svelte.md',
26
- '.svx',
27
- '.md',
28
- ...config_module_types
29
- ]);
30
- };
@@ -15,9 +15,9 @@ import { runtime_directory, logger } from '../../core/utils.js';
15
15
  import { find_deps, get_default_build_config } from './build/utils.js';
16
16
  import { preview } from './preview/index.js';
17
17
  import { get_config_aliases, get_app_aliases, get_env } from './utils.js';
18
- import { prevent_illegal_rollup_imports } from './graph_analysis/index.js';
19
18
  import { fileURLToPath } from 'node:url';
20
19
  import { create_static_module, create_dynamic_module } from '../../core/env.js';
20
+ import { is_illegal, module_guard, normalize_id } from './graph_analysis/index.js';
21
21
 
22
22
  const cwd = process.cwd();
23
23
 
@@ -292,7 +292,22 @@ function kit() {
292
292
  if (id.startsWith('$env/')) return `\0${id}`;
293
293
  },
294
294
 
295
- async load(id) {
295
+ async load(id, options) {
296
+ if (options?.ssr === false) {
297
+ const normalized_cwd = vite.normalizePath(cwd);
298
+ const normalized_lib = vite.normalizePath(svelte_config.kit.files.lib);
299
+ if (
300
+ is_illegal(id, {
301
+ cwd: normalized_cwd,
302
+ node_modules: vite.normalizePath(path.join(cwd, 'node_modules')),
303
+ server: vite.normalizePath(path.join(normalized_lib, 'server'))
304
+ })
305
+ ) {
306
+ const relative = normalize_id(id, normalized_lib, normalized_cwd);
307
+ throw new Error(`Cannot import ${relative} into client-side code`);
308
+ }
309
+ }
310
+
296
311
  switch (id) {
297
312
  case '\0$env/static/private':
298
313
  return create_static_module('$env/static/private', env.private);
@@ -350,20 +365,17 @@ function kit() {
350
365
  return;
351
366
  }
352
367
 
368
+ const guard = module_guard(this, {
369
+ cwd: vite.normalizePath(process.cwd()),
370
+ lib: vite.normalizePath(svelte_config.kit.files.lib)
371
+ });
372
+
353
373
  manifest_data.nodes.forEach((_node, i) => {
354
374
  const id = vite.normalizePath(
355
375
  path.resolve(svelte_config.kit.outDir, `generated/nodes/${i}.js`)
356
376
  );
357
377
 
358
- const module_node = this.getModuleInfo(id);
359
-
360
- if (module_node) {
361
- prevent_illegal_rollup_imports(
362
- this.getModuleInfo.bind(this),
363
- module_node,
364
- vite.normalizePath(svelte_config.kit.files.lib)
365
- );
366
- }
378
+ guard.check(id);
367
379
  });
368
380
 
369
381
  const verbose = vite_config.logLevel === 'info';
@@ -100,27 +100,24 @@ export async function preview(vite, vite_config, svelte_config) {
100
100
 
101
101
  const { pathname } = new URL(/** @type {string} */ (req.url), 'http://dummy');
102
102
 
103
- // only treat this as a page if it doesn't include an extension
104
- if (pathname === '/' || /\/[^./]+\/?$/.test(pathname)) {
105
- const file = join(
106
- svelte_config.kit.outDir,
107
- 'output/prerendered/pages' +
108
- pathname +
109
- (pathname.endsWith('/') ? 'index.html' : '.html')
110
- );
111
-
112
- if (fs.existsSync(file)) {
113
- res.writeHead(200, {
114
- 'content-type': 'text/html',
115
- etag
116
- });
117
-
118
- fs.createReadStream(file).pipe(res);
119
- return;
120
- }
103
+ let filename = join(svelte_config.kit.outDir, 'output/prerendered/pages' + pathname);
104
+ let prerendered = is_file(filename);
105
+
106
+ if (!prerendered) {
107
+ filename += filename.endsWith('/') ? 'index.html' : '.html';
108
+ prerendered = is_file(filename);
121
109
  }
122
110
 
123
- next();
111
+ if (prerendered) {
112
+ res.writeHead(200, {
113
+ 'content-type': 'text/html',
114
+ etag
115
+ });
116
+
117
+ fs.createReadStream(filename).pipe(res);
118
+ } else {
119
+ next();
120
+ }
124
121
  })
125
122
  );
126
123
 
@@ -187,3 +184,8 @@ function scoped(scope, handler) {
187
184
  }
188
185
  };
189
186
  }
187
+
188
+ /** @param {string} path */
189
+ function is_file(path) {
190
+ return fs.existsSync(path) && !fs.statSync(path).isDirectory();
191
+ }
@@ -373,6 +373,8 @@ export function create_client({ target, base, trailing_slash }) {
373
373
 
374
374
  /** @param {import('./types').NavigationFinished} result */
375
375
  function initialize(result) {
376
+ if (__SVELTEKIT_DEV__ && document.querySelector('vite-error-overlay')) return;
377
+
376
378
  current = result.state;
377
379
 
378
380
  const style = document.querySelector('style[data-sveltekit]');
@@ -485,6 +487,12 @@ export function create_client({ target, base, trailing_slash }) {
485
487
  };
486
488
 
487
489
  // TODO remove this for 1.0
490
+ Object.defineProperty(result.props.page, 'routeId', {
491
+ get() {
492
+ throw new Error('$page.routeId has been replaced by $page.route.id');
493
+ },
494
+ enumerable: false
495
+ });
488
496
  /**
489
497
  * @param {string} property
490
498
  * @param {string} replacement
@@ -599,8 +607,8 @@ export function create_client({ target, base, trailing_slash }) {
599
607
 
600
608
  // prerendered pages may be served from any origin, so `initial_fetch` urls shouldn't be resolved
601
609
  return started
602
- ? subsequent_fetch(resolved, init)
603
- : initial_fetch(requested, resolved, init);
610
+ ? subsequent_fetch(requested, resolved, init)
611
+ : initial_fetch(requested, init);
604
612
  },
605
613
  setHeaders: () => {}, // noop
606
614
  depends,
@@ -1353,9 +1361,9 @@ export function create_client({ target, base, trailing_slash }) {
1353
1361
 
1354
1362
  /** @param {Event} event */
1355
1363
  const trigger_prefetch = (event) => {
1356
- const { url, options } = find_anchor(event);
1357
- if (url && options.prefetch) {
1358
- if (is_external_url(url)) return;
1364
+ const { url, options, has } = find_anchor(event);
1365
+ if (url && options.prefetch && !is_external_url(url)) {
1366
+ if (options.reload || has.rel_external || has.target || has.download) return;
1359
1367
  prefetch(url);
1360
1368
  }
1361
1369
  };
@@ -1387,7 +1395,7 @@ export function create_client({ target, base, trailing_slash }) {
1387
1395
  if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
1388
1396
  if (event.defaultPrevented) return;
1389
1397
 
1390
- const { a, url, options } = find_anchor(event);
1398
+ const { a, url, options, has } = find_anchor(event);
1391
1399
  if (!a || !url) return;
1392
1400
 
1393
1401
  const is_svg_a_element = a instanceof SVGAElement;
@@ -1407,15 +1415,10 @@ export function create_client({ target, base, trailing_slash }) {
1407
1415
  )
1408
1416
  return;
1409
1417
 
1410
- if (a.hasAttribute('download')) return;
1418
+ if (has.download) return;
1411
1419
 
1412
1420
  // Ignore the following but fire beforeNavigate
1413
- const rel = (a.getAttribute('rel') || '').split(/\s+/);
1414
- if (
1415
- rel.includes('external') ||
1416
- options.reload ||
1417
- (is_svg_a_element ? a.target.baseVal : a.target)
1418
- ) {
1421
+ if (options.reload || has.rel_external || has.target) {
1419
1422
  const navigation = before_navigate({ url, type: 'link' });
1420
1423
  if (!navigation) {
1421
1424
  event.preventDefault();
@@ -63,24 +63,17 @@ const cache = new Map();
63
63
  * Should be called on the initial run of load functions that hydrate the page.
64
64
  * Saves any requests with cache-control max-age to the cache.
65
65
  * @param {RequestInfo | URL} resource
66
- * @param {string} resolved
67
66
  * @param {RequestInit} [opts]
68
67
  */
69
- export function initial_fetch(resource, resolved, opts) {
70
- const url = JSON.stringify(resource instanceof Request ? resource.url : resource);
71
-
72
- let selector = `script[data-sveltekit-fetched][data-url=${url}]`;
73
-
74
- if (opts?.body && (typeof opts.body === 'string' || ArrayBuffer.isView(opts.body))) {
75
- selector += `[data-hash="${hash(opts.body)}"]`;
76
- }
68
+ export function initial_fetch(resource, opts) {
69
+ const selector = build_selector(resource, opts);
77
70
 
78
71
  const script = document.querySelector(selector);
79
72
  if (script?.textContent) {
80
73
  const { body, ...init } = JSON.parse(script.textContent);
81
74
 
82
75
  const ttl = script.getAttribute('data-ttl');
83
- if (ttl) cache.set(resolved, { body, init, ttl: 1000 * Number(ttl) });
76
+ if (ttl) cache.set(selector, { body, init, ttl: 1000 * Number(ttl) });
84
77
 
85
78
  return Promise.resolve(new Response(body, init));
86
79
  }
@@ -90,18 +83,39 @@ export function initial_fetch(resource, resolved, opts) {
90
83
 
91
84
  /**
92
85
  * Tries to get the response from the cache, if max-age allows it, else does a fetch.
86
+ * @param {RequestInfo | URL} resource
93
87
  * @param {string} resolved
94
88
  * @param {RequestInit} [opts]
95
89
  */
96
- export function subsequent_fetch(resolved, opts) {
97
- const cached = cache.get(resolved);
98
- if (cached) {
99
- if (performance.now() < cached.ttl) {
100
- return new Response(cached.body, cached.init);
90
+ export function subsequent_fetch(resource, resolved, opts) {
91
+ if (cache.size > 0) {
92
+ const selector = build_selector(resource, opts);
93
+ const cached = cache.get(selector);
94
+ if (cached) {
95
+ if (performance.now() < cached.ttl) {
96
+ return new Response(cached.body, cached.init);
97
+ }
98
+
99
+ cache.delete(selector);
101
100
  }
102
-
103
- cache.delete(resolved);
104
101
  }
105
102
 
106
103
  return native_fetch(resolved, opts);
107
104
  }
105
+
106
+ /**
107
+ * Build the cache key for a given request
108
+ * @param {RequestInfo | URL} resource
109
+ * @param {RequestInit} [opts]
110
+ */
111
+ function build_selector(resource, opts) {
112
+ const url = JSON.stringify(resource instanceof Request ? resource.url : resource);
113
+
114
+ let selector = `script[data-sveltekit-fetched][data-url=${url}]`;
115
+
116
+ if (opts?.body && (typeof opts.body === 'string' || ArrayBuffer.isView(opts.body))) {
117
+ selector += `[data-hash="${hash(opts.body)}"]`;
118
+ }
119
+
120
+ return selector;
121
+ }
@@ -58,7 +58,14 @@ export function find_anchor(event) {
58
58
  noscroll,
59
59
  prefetch,
60
60
  reload
61
- }
61
+ },
62
+ has: a
63
+ ? {
64
+ rel_external: (a.getAttribute('rel') || '').split(/\s+/).includes('external'),
65
+ download: a.hasAttribute('download'),
66
+ target: !!(a instanceof SVGAElement ? a.target.baseVal : a.target)
67
+ }
68
+ : {}
62
69
  };
63
70
  }
64
71
 
@@ -72,11 +72,6 @@ export function parse_route_id(id) {
72
72
  return (
73
73
  content // allow users to specify characters on the file system in an encoded manner
74
74
  .normalize()
75
- // We use [ and ] to denote parameters, so users must encode these on the file
76
- // system to match against them. We don't decode all characters since others
77
- // can already be epressed and so that '%' can be easily used directly in filenames
78
- .replace(/%5[Bb]/g, '[')
79
- .replace(/%5[Dd]/g, ']')
80
75
  // '#', '/', and '?' can only appear in URL path segments in an encoded manner.
81
76
  // They will not be touched by decodeURI so need to be encoded here, so
82
77
  // that we can match against them.