diff --git a/src/middleware.ts b/src/middleware.ts index bb2d0da..5ca900f 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -73,9 +73,18 @@ interface Component { preload: (data: any) => any | Promise } +const IGNORE = '__SAPPER__IGNORE__'; +function toIgnore(uri: string, val: any) { + if (Array.isArray(val)) return val.some(x => toIgnore(uri, x)); + if (val instanceof RegExp) return val.test(uri); + if (typeof val === 'function') return val(uri); + return uri.startsWith(val.charCodeAt(0) === 47 ? val : `/${val}`); +} + export default function middleware(opts: { manifest: Manifest, store: (req: Req) => Store, + ignore?: any, routes?: any // legacy }) { if (opts.routes) { @@ -84,12 +93,19 @@ export default function middleware(opts: { const output = locations.dest(); - const { manifest, store } = opts; + const { manifest, store, ignore } = opts; let emitted_basepath = false; const middleware = compose_handlers([ + ignore && ((req: Req, res: ServerResponse, next: () => void) => { + req[IGNORE] = toIgnore(req.path, ignore); + next(); + }), + (req: Req, res: ServerResponse, next: () => void) => { + if (req[IGNORE]) return next(); + if (req.baseUrl === undefined) { let { originalUrl } = req; if (req.url === '/' && originalUrl[originalUrl.length - 1] !== '/') { @@ -163,6 +179,8 @@ function serve({ prefix, pathname, cache_control }: { : (file: string) => (cache.has(file) ? cache : cache.set(file, fs.readFileSync(path.resolve(output, file)))).get(file) return (req: Req, res: ServerResponse, next: () => void) => { + if (req[IGNORE]) return next(); + if (filter(req)) { const type = lookup(req.path); @@ -245,6 +263,8 @@ function get_server_route_handler(routes: ServerRoute[]) { } return function find_route(req: Req, res: ServerResponse, next: () => void) { + if (req[IGNORE]) return next(); + for (const route of routes) { if (route.pattern.test(req.path)) { handle_route(route, req, res, next); @@ -494,7 +514,9 @@ function get_page_handler(manifest: Manifest, store_getter: (req: Req) => Store) }); } - return function find_route(req: Req, res: ServerResponse) { + return function find_route(req: Req, res: ServerResponse, next: () => void) { + if (req[IGNORE]) return next(); + if (!server_routes.some(route => route.pattern.test(req.path))) { for (const page of pages) { if (page.pattern.test(req.path)) { diff --git a/test/app/app/server.js b/test/app/app/server.js index 645699f..eb36e5a 100644 --- a/test/app/app/server.js +++ b/test/app/app/server.js @@ -91,8 +91,14 @@ const middlewares = [ return new Store({ title: 'Stored title' }); - } - }) + }, + ignore: [ + /foobar/i, + '/buzz', + 'fizz', + x => x === '/hello' + ] + }), ]; if (BASEPATH) { @@ -101,4 +107,8 @@ if (BASEPATH) { app.use(...middlewares); } -app.listen(PORT); \ No newline at end of file +['foobar', 'buzz', 'fizzer', 'hello'].forEach(uri => { + app.get('/'+uri, (req, res) => res.end(uri)); +}); + +app.listen(PORT); diff --git a/test/common/test.js b/test/common/test.js index 99aad94..300f94c 100644 --- a/test/common/test.js +++ b/test/common/test.js @@ -477,6 +477,42 @@ function run({ mode, basepath = '' }) { }); }); + // Ignores are meant for top-level escape. + // ~> Sapper **should** own the entire {basepath} when designated. + if (!basepath) { + it('respects `options.ignore` values (RegExp)', () => { + return nightmare.goto(`${base}/foobar`) + .evaluate(() => document.documentElement.textContent) + .then(text => { + assert.equal(text, 'foobar'); + }); + }); + + it('respects `options.ignore` values (String #1)', () => { + return nightmare.goto(`${base}/buzz`) + .evaluate(() => document.documentElement.textContent) + .then(text => { + assert.equal(text, 'buzz'); + }); + }); + + it('respects `options.ignore` values (String #2)', () => { + return nightmare.goto(`${base}/fizzer`) + .evaluate(() => document.documentElement.textContent) + .then(text => { + assert.equal(text, 'fizzer'); + }); + }); + + it('respects `options.ignore` values (Function)', () => { + return nightmare.goto(`${base}/hello`) + .evaluate(() => document.documentElement.textContent) + .then(text => { + assert.equal(text, 'hello'); + }); + }); + } + it('does not attempt client-side navigation to server routes', () => { return nightmare.goto(`${base}/blog/how-is-sapper-different-from-next`) .init()