Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exclude routes #3576

Merged
merged 15 commits into from
Jan 28, 2022
5 changes: 5 additions & 0 deletions .changeset/hip-walls-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

Add kit.routes config to customise public/private modules
2 changes: 1 addition & 1 deletion documentation/docs/01-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 5 additions & 0 deletions documentation/docs/14-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion documentation/migrating/04-pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions packages/kit/src/core/config/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
13 changes: 6 additions & 7 deletions packages/kit/src/core/create_manifest_data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
109 changes: 87 additions & 22 deletions packages/kit/src/core/create_manifest_data/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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');

Expand Down Expand Up @@ -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';
Expand Down
1 change: 1 addition & 0 deletions packages/kit/types/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ export interface Config {
onError?: PrerenderOnErrorValue;
};
router?: boolean;
routes?: (filepath: string) => boolean;
serviceWorker?: {
register?: boolean;
files?: (filepath: string) => boolean;
Expand Down