Skip to content

Commit 5a54eb4

Browse files
[fix] symlink routes (#6796)
* [fix] symlink routes Fixes #6303 * add test * support symlinks * lint * allow symlinked endpoints * try this * ugh Co-authored-by: Rich Harris <hello@rich-harris.dev>
1 parent ff056c8 commit 5a54eb4

11 files changed

Lines changed: 68 additions & 16 deletions

File tree

.changeset/silent-jeans-vanish.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
[fix] symlink routes

packages/kit/src/core/generate_manifest/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { s } from '../../utils/misc.js';
22
import { get_mime_lookup } from '../utils.js';
3+
import { resolve_symlinks } from '../../exports/vite/build/utils.js';
34

45
/**
56
* Generates the data used to write the server-side manifest.js file. This data is used in the Vite
@@ -65,7 +66,7 @@ export function generate_manifest({ build_data, relative_path, routes, format =
6566
names: ${s(route.names)},
6667
types: ${s(route.types)},
6768
page: ${route.page ? `{ layouts: ${get_nodes(route.page.layouts)}, errors: ${get_nodes(route.page.errors)}, leaf: ${route.page.leaf} }` : 'null'},
68-
endpoint: ${route.endpoint ? loader(`${relative_path}/${build_data.server.vite_manifest[route.endpoint.file].file}`) : 'null'}
69+
endpoint: ${route.endpoint ? loader(`${relative_path}/${resolve_symlinks(build_data.server.vite_manifest, route.endpoint.file).chunk.file}`) : 'null'}
6970
}`;
7071
}).filter(Boolean).join(',\n\t\t\t\t')}
7172
],

packages/kit/src/core/sync/create_manifest_data/index.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,16 @@ function create_routes_and_nodes(cwd, config, fallback) {
164164

165165
const dir = path.join(cwd, routes_base, id);
166166

167-
const files = fs.readdirSync(dir, {
168-
withFileTypes: true
169-
});
167+
// We can't use withFileTypes because of a NodeJs bug which returns wrong results
168+
// with isDirectory() in case of symlinks: https://github.com/nodejs/node/issues/30646
169+
const files = fs.readdirSync(dir).map((name) => ({
170+
is_dir: fs.statSync(path.join(dir, name)).isDirectory(),
171+
name
172+
}));
170173

171174
// process files first
172175
for (const file of files) {
173-
if (file.isDirectory()) continue;
176+
if (file.is_dir) continue;
174177
if (!file.name.startsWith('+')) continue;
175178
if (!valid_extensions.find((ext) => file.name.endsWith(ext))) continue;
176179

@@ -213,7 +216,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
213216

214217
// then handle children
215218
for (const file of files) {
216-
if (file.isDirectory()) {
219+
if (file.is_dir) {
217220
walk(depth + 1, path.posix.join(id, file.name), file.name, route);
218221
}
219222
}

packages/kit/src/exports/vite/build/build_server.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ import { mkdirp, posixify, resolve_entry } from '../../../utils/filesystem.js';
44
import { get_vite_config, merge_vite_configs } from '../utils.js';
55
import { load_error_page, load_template } from '../../../core/config/index.js';
66
import { runtime_directory } from '../../../core/utils.js';
7-
import { create_build, find_deps, get_default_build_config, is_http_method } from './utils.js';
7+
import {
8+
create_build,
9+
find_deps,
10+
get_default_build_config,
11+
is_http_method,
12+
resolve_symlinks
13+
} from './utils.js';
814
import { s } from '../../../utils/misc.js';
915

1016
/**
@@ -285,7 +291,7 @@ export async function build_server(options, client) {
285291

286292
exports.push(
287293
`export const component = async () => (await import('../${
288-
vite_manifest[node.component].file
294+
resolve_symlinks(vite_manifest, node.component).chunk.file
289295
}')).default;`,
290296
`export const file = '${entry.file}';` // TODO what is this?
291297
);

packages/kit/src/exports/vite/build/utils.js

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import fs from 'fs';
2+
import path from 'path';
13
import * as vite from 'vite';
24
import { get_aliases } from '../utils.js';
35

@@ -44,14 +46,14 @@ export function find_deps(manifest, entry, add_dynamic_css) {
4446
const stylesheets = new Set();
4547

4648
/**
47-
* @param {string} file
49+
* @param {string} current
4850
* @param {boolean} add_js
4951
*/
50-
function traverse(file, add_js) {
51-
if (seen.has(file)) return;
52-
seen.add(file);
52+
function traverse(current, add_js) {
53+
if (seen.has(current)) return;
54+
seen.add(current);
5355

54-
const chunk = manifest[file];
56+
const { chunk } = resolve_symlinks(manifest, current);
5557

5658
if (add_js) imports.add(chunk.file);
5759

@@ -68,15 +70,31 @@ export function find_deps(manifest, entry, add_dynamic_css) {
6870
}
6971
}
7072

71-
traverse(entry, true);
73+
const { chunk, file } = resolve_symlinks(manifest, entry);
74+
75+
traverse(file, true);
7276

7377
return {
74-
file: manifest[entry].file,
78+
file: chunk.file,
7579
imports: Array.from(imports),
7680
stylesheets: Array.from(stylesheets)
7781
};
7882
}
7983

84+
/**
85+
* @param {import('vite').Manifest} manifest
86+
* @param {string} file
87+
*/
88+
export function resolve_symlinks(manifest, file) {
89+
while (!manifest[file]) {
90+
file = path.relative('.', fs.realpathSync(file));
91+
}
92+
93+
const chunk = manifest[file];
94+
95+
return { chunk, file };
96+
}
97+
8098
/**
8199
* The Vite configuration that we use by default.
82100
* @param {{
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/test/errors.json
22
!/.env
3+
/src/routes/routing/symlink-from

packages/kit/test/apps/basics/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"build": "vite build",
88
"preview": "vite preview",
99
"check": "svelte-kit sync && tsc && svelte-check",
10-
"test": "npm run test:dev && npm run test:build",
10+
"test": "node test/setup.js && npm run test:dev && npm run test:build",
1111
"test:dev": "rimraf test/errors.json && cross-env DEV=true playwright test",
1212
"test:build": "rimraf test/errors.json && playwright test"
1313
},

packages/kit/test/apps/basics/src/routes/routing/+page.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
<a href="/routing/a">a</a>
88
<a href="/routing/ambiguous/ok.json" rel="external">ok</a>
9+
<a href="/routing/symlink-from">symlinked</a>
910
<a href="http://localhost:{$page.url.searchParams.get('port')}">elsewhere</a>
1011
<a href="/static.json">static.json</a>
1112

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<h1>symlinked</h1>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import fs from 'fs';
2+
3+
if (process.platform !== 'win32') {
4+
process.chdir('src/routes/routing');
5+
fs.rmSync('symlink-from', { recursive: true, force: true });
6+
fs.symlinkSync('symlink-to', 'symlink-from', 'dir');
7+
}

0 commit comments

Comments
 (0)