diff --git a/.changeset/hip-walls-flash.md b/.changeset/hip-walls-flash.md new file mode 100644 index 000000000000..0e7eeb381617 --- /dev/null +++ b/.changeset/hip-walls-flash.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Add kit.routes config to customise public/private modules diff --git a/documentation/docs/01-routing.md b/documentation/docs/01-routing.md index 344b71c2e323..8af291a249b8 100644 --- a/documentation/docs/01-routing.md +++ b/documentation/docs/01-routing.md @@ -175,7 +175,7 @@ export default { ### Private modules -A filename that has a segment with a leading underscore, such as `src/routes/foo/_Private.svelte` or `src/routes/bar/_utils/cool-util.js`, is hidden from the router, but can be imported by files that are not. +Files and directories with a leading `_` or `.` (other than [`.well-known`](https://en.wikipedia.org/wiki/Well-known_URI)) are private by default, meaning that they do not create routes (but can be imported by files that do). You can configure which modules are considered public or private with the [`routes`](#configuration-routes) configuration. ### Advanced diff --git a/documentation/docs/14-configuration.md b/documentation/docs/14-configuration.md index 95e3b2f422f5..9b144f2c8433 100644 --- a/documentation/docs/14-configuration.md +++ b/documentation/docs/14-configuration.md @@ -58,6 +58,7 @@ const config = { onError: 'fail' }, router: true, + routes: (filepath) => !/(?:(?:^_|\/_)|(?:^\.|\/\.)(?!well-known))/.test(filepath), serviceWorker: { register: true, files: (filepath) => !/\.DS_STORE/.test(filepath) @@ -224,6 +225,10 @@ See [Prerendering](#page-options-prerender). An object containing zero or more o Enables or disables the client-side [router](#page-options-router) app-wide. +### routes + +A `(filepath: string) => boolean` function that determines which files create routes and which are treated as [private modules](#routing-private-modules). + ### serviceWorker An object containing zero or more of the following values: diff --git a/documentation/migrating/04-pages.md b/documentation/migrating/04-pages.md index f06db769e0b9..8986d513f161 100644 --- a/documentation/migrating/04-pages.md +++ b/documentation/migrating/04-pages.md @@ -4,7 +4,7 @@ title: Pages and layouts ### Renamed files -Your custom error page component should be renamed from `_error.svelte` to `__error.svelte`. Any `_layout.svelte` files should likewise be renamed `__layout.svelte`. The double underscore prefix is reserved for SvelteKit; your own private modules are still denoted with a single `_` prefix. +Your custom error page component should be renamed from `_error.svelte` to `__error.svelte`. Any `_layout.svelte` files should likewise be renamed `__layout.svelte`. The double underscore prefix is reserved for SvelteKit; your own [private modules](#routing-private-modules) are still denoted with a single `_` prefix (configurable via [`routes`](docs#configuration-routes) config). ### Imports diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js index bda10e27568e..06b8c426c371 100644 --- a/packages/kit/src/core/config/options.js +++ b/packages/kit/src/core/config/options.js @@ -243,6 +243,8 @@ const options = object( router: boolean(true), + routes: fun((filepath) => !/(?:(?:^_|\/_)|(?:^\.|\/\.)(?!well-known))/.test(filepath)), + serviceWorker: object({ register: boolean(true), files: fun((filename) => !/\.DS_STORE/.test(filename)) diff --git a/packages/kit/src/core/create_manifest_data/index.js b/packages/kit/src/core/create_manifest_data/index.js index 12eafda7d940..273617842dd3 100644 --- a/packages/kit/src/core/create_manifest_data/index.js +++ b/packages/kit/src/core/create_manifest_data/index.js @@ -87,17 +87,16 @@ export default function create_manifest_data({ } }); - if (name[0] === '_') { - if (name[1] === '_' && !specials.has(name)) { - throw new Error(`Files and directories prefixed with __ are reserved (saw ${file})`); - } - - return; + if (basename.startsWith('__') && !specials.has(name)) { + throw new Error(`Files and directories prefixed with __ are reserved (saw ${file})`); } - if (basename[0] === '.' && basename !== '.well-known') return null; if (!is_dir && !/^(\.[a-z0-9]+)+$/i.test(ext)) return null; // filter out tmp files etc + if (!config.kit.routes(file)) { + return; + } + const segment = is_dir ? basename : name; if (/\]\[/.test(segment)) { diff --git a/packages/kit/src/core/create_manifest_data/index.spec.js b/packages/kit/src/core/create_manifest_data/index.spec.js index 349237633d5e..dc1a81cc399a 100644 --- a/packages/kit/src/core/create_manifest_data/index.spec.js +++ b/packages/kit/src/core/create_manifest_data/index.spec.js @@ -3,29 +3,20 @@ import { fileURLToPath } from 'url'; import { test } from 'uvu'; import * as assert from 'uvu/assert'; import create_manifest_data from './index.js'; +import options from '../config/options.js'; const cwd = fileURLToPath(new URL('./test', import.meta.url)); /** * @param {string} dir - * @param {string[]} [extensions] + * @param {import('types/config').Config} config * @returns */ -const create = (dir, extensions = ['.svelte']) => { - /** @type {import('types/config').Config} */ - const initial = { - extensions, - kit: { - files: { - assets: path.resolve(cwd, 'static'), - routes: path.resolve(cwd, dir) - }, - appDir: '_app', - serviceWorker: { - files: (filepath) => !/\.DS_STORE/.test(filepath) - } - } - }; +const create = (dir, config = {}) => { + const initial = options(config, 'config'); + + initial.kit.files.assets = path.resolve(cwd, 'static'); + initial.kit.files.routes = path.resolve(cwd, dir); return create_manifest_data({ config: /** @type {import('types/config').ValidatedConfig} */ (initial), @@ -265,6 +256,83 @@ test('ignores files and directories with leading dots except .well-known', () => ]); }); +test('ignores files by `kit.excludes` config w/RegExp', () => { + const { routes } = create('samples/hidden-by-excludes-config', { + kit: { + routes: (filepath) => !filepath.endsWith('.test.js') && !filepath.endsWith('.spec.js') + } + }); + + assert.equal( + routes + .map((r) => r.type === 'endpoint' && r.file) + .filter(Boolean) + .sort(), + [ + 'samples/hidden-by-excludes-config/.a.js', + 'samples/hidden-by-excludes-config/.well-known/dnt-policy.txt.js', + 'samples/hidden-by-excludes-config/_a.js', + 'samples/hidden-by-excludes-config/a.js', + 'samples/hidden-by-excludes-config/a.md', + 'samples/hidden-by-excludes-config/subdir/.a.js', + 'samples/hidden-by-excludes-config/subdir/_a.js', + 'samples/hidden-by-excludes-config/subdir/.well-known/dnt-policy.txt.js', + 'samples/hidden-by-excludes-config/subdir/a.js', + 'samples/hidden-by-excludes-config/subdir/a.md' + ].sort() + ); +}); + +test('ignores files by `kit.excludes` config w/string', () => { + const { routes } = create('samples/hidden-by-excludes-config', { + kit: { + routes: (filepath) => !filepath.split('/').includes('a.js') + } + }); + + assert.equal( + routes + .map((r) => r.type === 'endpoint' && r.file) + .filter(Boolean) + .sort(), + [ + 'samples/hidden-by-excludes-config/.a.js', + 'samples/hidden-by-excludes-config/.well-known/dnt-policy.txt.js', + 'samples/hidden-by-excludes-config/_a.js', + 'samples/hidden-by-excludes-config/a.md', + 'samples/hidden-by-excludes-config/a.spec.js', + 'samples/hidden-by-excludes-config/subdir/.a.js', + 'samples/hidden-by-excludes-config/subdir/.well-known/dnt-policy.txt.js', + 'samples/hidden-by-excludes-config/subdir/_a.js', + 'samples/hidden-by-excludes-config/subdir/a.md', + 'samples/hidden-by-excludes-config/subdir/a.spec.js' + ].sort() + ); +}); + +test('ignores files by `kit.excludes` config w/function', () => { + const { routes } = create('samples/hidden-by-excludes-config', { + kit: { + routes: (filepath) => !filepath.startsWith('samples/hidden-by-excludes-config/subdir') + } + }); + + assert.equal( + routes + .map((r) => r.type === 'endpoint' && r.file) + .filter(Boolean) + .sort(), + [ + 'samples/hidden-by-excludes-config/.a.js', + 'samples/hidden-by-excludes-config/.well-known/dnt-policy.txt.js', + 'samples/hidden-by-excludes-config/_a.js', + 'samples/hidden-by-excludes-config/a.js', + 'samples/hidden-by-excludes-config/a.md', + 'samples/hidden-by-excludes-config/a.spec.js' + ].sort() + ); +}); + test('allows multiple slugs', () => { const { routes } = create('samples/multiple-slugs'); @@ -303,12 +371,9 @@ test('ignores things that look like lockfiles', () => { }); test('works with custom extensions', () => { - const { components, routes } = create('samples/custom-extension', [ - '.jazz', - '.beebop', - '.funk', - '.svelte' - ]); + const { components, routes } = create('samples/custom-extension', { + extensions: ['.jazz', '.beebop', '.funk', '.svelte'] + }); const index = 'samples/custom-extension/index.funk'; const about = 'samples/custom-extension/about.jazz'; diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/.a.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/.a.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/.well-known/dnt-policy.txt.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/.well-known/dnt-policy.txt.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/__error.svelte b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/__error.svelte new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/__layout.svelte b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/__layout.svelte new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/_a.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/_a.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/a.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/a.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/a.md b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/a.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/a.spec.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/a.spec.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/.a.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/.a.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/.well-known/dnt-policy.txt.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/.well-known/dnt-policy.txt.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/__error.svelte b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/__error.svelte new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/__layout.svelte b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/__layout.svelte new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/_a.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/_a.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/a.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/a.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/a.md b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/a.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/a.spec.js b/packages/kit/src/core/create_manifest_data/test/samples/hidden-by-excludes-config/subdir/a.spec.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/kit/types/config.d.ts b/packages/kit/types/config.d.ts index 4fadf2ee41c5..19b86cd05206 100644 --- a/packages/kit/types/config.d.ts +++ b/packages/kit/types/config.d.ts @@ -155,6 +155,7 @@ export interface Config { onError?: PrerenderOnErrorValue; }; router?: boolean; + routes?: (filepath: string) => boolean; serviceWorker?: { register?: boolean; files?: (filepath: string) => boolean;