diff --git a/.changeset/dirty-candles-peel.md b/.changeset/dirty-candles-peel.md new file mode 100644 index 000000000000..5ea2f31ae981 --- /dev/null +++ b/.changeset/dirty-candles-peel.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +[fix] allow missing routes folder diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js index e9cd7205e690..852a02a869f8 100644 --- a/packages/kit/src/core/sync/create_manifest_data/index.js +++ b/packages/kit/src/core/sync/create_manifest_data/index.js @@ -225,97 +225,115 @@ function create_routes_and_nodes(cwd, config, fallback) { walk(0, '', '', null); const root = /** @type {import('types').RouteData} */ (route_map.get('')); - - // TODO remove for 1.0 if (route_map.size === 1) { if (!root.leaf && !root.error && !root.layout && !root.endpoint) { throw new Error( + // TODO adjust this error message for 1.0 + // 'No routes found. If you are using a custom src/routes directory, make sure it is specified in svelte.config.js' 'The filesystem router API has changed, see https://github.com/sveltejs/kit/discussions/5774 for details' ); } } + } else { + // If there's no routes directory, we'll just create a single empty route. This ensures the root layout and + // error components are included in the manifest, which is needed for subsequent build/dev commands to work + route_map.set('', { + id: '', + segment: '', + pattern: /^$/, + names: [], + types: [], + parent: null, + layout: null, + error: null, + leaf: null, + page: null, + endpoint: null + }); + } - if (!root.layout?.component) { - if (!root.layout) root.layout = { depth: 0, child_pages: [] }; - root.layout.component = posixify(path.relative(cwd, `${fallback}/layout.svelte`)); - } + const root = /** @type {import('types').RouteData} */ (route_map.get('')); - if (!root.error?.component) { - if (!root.error) root.error = { depth: 0 }; - root.error.component = posixify(path.relative(cwd, `${fallback}/error.svelte`)); - } + if (!root.layout?.component) { + if (!root.layout) root.layout = { depth: 0, child_pages: [] }; + root.layout.component = posixify(path.relative(cwd, `${fallback}/layout.svelte`)); + } - // we do layouts/errors first as they are more likely to be reused, - // and smaller indexes take fewer bytes. also, this guarantees that - // the default error/layout are 0/1 - route_map.forEach((route) => { - if (route.layout) nodes.push(route.layout); - if (route.error) nodes.push(route.error); - }); + if (!root.error?.component) { + if (!root.error) root.error = { depth: 0 }; + root.error.component = posixify(path.relative(cwd, `${fallback}/error.svelte`)); + } - /** @type {Map} */ - const conflicts = new Map(); + // we do layouts/errors first as they are more likely to be reused, + // and smaller indexes take fewer bytes. also, this guarantees that + // the default error/layout are 0/1 + route_map.forEach((route) => { + if (route.layout) nodes.push(route.layout); + if (route.error) nodes.push(route.error); + }); - route_map.forEach((route) => { - if (!route.leaf) return; + /** @type {Map} */ + const conflicts = new Map(); - nodes.push(route.leaf); + route_map.forEach((route) => { + if (!route.leaf) return; - const normalized = route.id.split('/').filter(affects_path).join('/'); + nodes.push(route.leaf); - if (conflicts.has(normalized)) { - throw new Error(`${conflicts.get(normalized)} and ${route.id} occupy the same route`); - } + const normalized = route.id.split('/').filter(affects_path).join('/'); - conflicts.set(normalized, route.id); - }); + if (conflicts.has(normalized)) { + throw new Error(`${conflicts.get(normalized)} and ${route.id} occupy the same route`); + } - const indexes = new Map(nodes.map((node, i) => [node, i])); + conflicts.set(normalized, route.id); + }); - route_map.forEach((route) => { - if (!route.leaf) return; + const indexes = new Map(nodes.map((node, i) => [node, i])); - route.page = { - layouts: [], - errors: [], - leaf: /** @type {number} */ (indexes.get(route.leaf)) - }; + route_map.forEach((route) => { + if (!route.leaf) return; - /** @type {import('types').RouteData | null} */ - let current_route = route; - let current_node = route.leaf; - let parent_id = route.leaf.parent_id; - - while (current_route) { - if (parent_id === undefined || current_route.segment === parent_id) { - if (current_route.layout || current_route.error) { - route.page.layouts.unshift( - current_route.layout ? indexes.get(current_route.layout) : undefined - ); - route.page.errors.unshift( - current_route.error ? indexes.get(current_route.error) : undefined - ); - } + route.page = { + layouts: [], + errors: [], + leaf: /** @type {number} */ (indexes.get(route.leaf)) + }; - if (current_route.layout) { - /** @type {import('types').PageNode[]} */ (current_route.layout.child_pages).push( - route.leaf - ); - current_node.parent = current_node = current_route.layout; - parent_id = current_node.parent_id; - } else { - parent_id = undefined; - } + /** @type {import('types').RouteData | null} */ + let current_route = route; + let current_node = route.leaf; + let parent_id = route.leaf.parent_id; + + while (current_route) { + if (parent_id === undefined || current_route.segment === parent_id) { + if (current_route.layout || current_route.error) { + route.page.layouts.unshift( + current_route.layout ? indexes.get(current_route.layout) : undefined + ); + route.page.errors.unshift( + current_route.error ? indexes.get(current_route.error) : undefined + ); } - current_route = current_route.parent; + if (current_route.layout) { + /** @type {import('types').PageNode[]} */ (current_route.layout.child_pages).push( + route.leaf + ); + current_node.parent = current_node = current_route.layout; + parent_id = current_node.parent_id; + } else { + parent_id = undefined; + } } - if (parent_id !== undefined) { - throw new Error(`${current_node.component} references missing segment "${parent_id}"`); - } - }); - } + current_route = current_route.parent; + } + + if (parent_id !== undefined) { + throw new Error(`${current_node.component} references missing segment "${parent_id}"`); + } + }); const routes = Array.from(route_map.values()).sort((a, b) => compare(a, b, segment_map)); diff --git a/packages/kit/src/core/sync/create_manifest_data/index.spec.js b/packages/kit/src/core/sync/create_manifest_data/index.spec.js index f1a863077cec..031071a4dfb4 100644 --- a/packages/kit/src/core/sync/create_manifest_data/index.spec.js +++ b/packages/kit/src/core/sync/create_manifest_data/index.spec.js @@ -173,8 +173,16 @@ test('creates routes with layout', () => { test('succeeds when routes does not exist', () => { const { nodes, routes } = create('samples/basic/routes'); - assert.equal(nodes, []); - assert.equal(routes, []); + assert.equal(nodes.map(simplify_node), [ + { component: 'layout.svelte' }, + { component: 'error.svelte' } + ]); + assert.equal(routes.map(simplify_route), [ + { + id: '', + pattern: '/^$/' + } + ]); }); // TODO some characters will need to be URL-encoded in the filename