Skip to content

Commit

Permalink
Exclude routes (#3576)
Browse files Browse the repository at this point in the history
* [feat] Add kit.excludes config

* [docs] Add kit.excludes config

* [feat] Add kit.excludes config (tests)

* [chore] Add changeset for kit.excludes config

* make the option always be a function

* lint

* dry out

* change kit.excludes to kit.routes, reverse polarity

* remove .only

* update docs

* update migration guide

* update changeset

* simplify

Co-authored-by: Tony Trinh <tony19@gmail.com>
  • Loading branch information
Rich-Harris and tony19 committed Jan 28, 2022
1 parent a5683c2 commit 4857a79
Show file tree
Hide file tree
Showing 24 changed files with 108 additions and 31 deletions.
5 changes: 5 additions & 0 deletions .changeset/hip-walls-flash.md
@@ -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
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
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
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
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
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
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
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
1 change: 1 addition & 0 deletions packages/kit/types/config.d.ts
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

0 comments on commit 4857a79

Please sign in to comment.