diff --git a/src/core/create_template.ts b/src/core/create_template.ts index 84eb5dc..3ca5da9 100644 --- a/src/core/create_template.ts +++ b/src/core/create_template.ts @@ -34,59 +34,6 @@ export default function create_templates() { return template.replace(/%sapper\.(\w+)%/g, (match, key) => { return key in data ? data[key] : ''; }); - }, - stream: (req: any, res: any, data: Record>) => { - let i = 0; - - let body = ''; - - function stream_inner(): Promise { - if (i >= template.length) { - return; - } - - const start = template.indexOf('%sapper', i); - - if (start === -1) { - const chunk = template.slice(i); - body += chunk; - res.end(chunk); - - if (process.send) { - process.send({ - __sapper__: true, - url: req.url, - method: req.method, - type: 'text/html', - body - }); - } - - return; - } - - const chunk = template.slice(i, start); - body += chunk; - res.write(chunk); - - const end = template.indexOf('%', start + 1); - if (end === -1) { - throw new Error(`Bad template`); // TODO validate ahead of time - } - - const tag = template.slice(start + 1, end); - const match = /sapper\.(\w+)/.exec(tag); - if (!match || !(match[1] in data)) throw new Error(`Bad template`); // TODO ditto - - return Promise.resolve(data[match[1]]).then(chunk => { - body += chunk; - res.write(chunk); - i = end + 1; - return stream_inner(); - }); - } - - return Promise.resolve().then(stream_inner); } }; } \ No newline at end of file diff --git a/src/middleware/index.ts b/src/middleware/index.ts index 53f2360..c55ba9d 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -19,6 +19,7 @@ type Assets = { } type RouteObject = { + id: string; type: 'page' | 'route'; pattern: RegExp; params: (match: RegExpMatchArray) => Record; @@ -125,7 +126,6 @@ function get_route_handler(chunks: Record, routes: RouteObject[] const mod = route.module; if (route.type === 'page') { - // for page routes, we're going to serve some HTML res.setHeader('Content-Type', 'text/html'); // preload main.js and current route @@ -134,33 +134,34 @@ function get_route_handler(chunks: Record, routes: RouteObject[] const data = { params: req.params, query: req.query }; - if (mod.preload) { - const promise = Promise.resolve(mod.preload(req)).then(preloaded => { - const serialized = try_serialize(preloaded); - Object.assign(data, preloaded); + let redirect: { statusCode: number, location: string }; + let error; - return { rendered: mod.render(data), serialized }; - }); + Promise.resolve( + mod.preload ? mod.preload.call({ + redirect: (statusCode: number, location: string) => { + redirect = { statusCode, location }; + } + }, req) : {} + ).then(preloaded => { + if (redirect) { + res.statusCode = redirect.statusCode; + res.setHeader('Location', redirect.location); + res.end(); - return template.stream(req, res, { - scripts: promise.then(({ serialized }) => { - const main = ``; + return; + } - if (serialized) { - return `${main}`; - } + const serialized = try_serialize(preloaded); // TODO bail on non-POJOs + Object.assign(data, preloaded); - return main; - }), - html: promise.then(({ rendered }) => rendered.html), - head: promise.then(({ rendered }) => `${rendered.head}`), - styles: promise.then(({ rendered }) => (rendered.css && rendered.css.code ? `` : '')) - }); - } else { const { html, head, css } = mod.render(data); + let scripts = ``; + scripts = `${scripts}`; + const page = template.render({ - scripts: ``, + scripts, html, head: `${head}`, styles: (css && css.code ? `` : '') @@ -178,7 +179,7 @@ function get_route_handler(chunks: Record, routes: RouteObject[] body: page }); } - } + }); } else { diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 2db8247..7ac8f8e 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -74,17 +74,23 @@ function render(Component: ComponentConstructor, data: any, scroll: ScrollPositi } function prepare_route(Component: ComponentConstructor, data: RouteData) { + let redirect: { statusCode: number, location: string } = null; + if (!Component.preload) { - return { Component, data }; + return { Component, data, redirect }; } if (!component && window.__SAPPER__ && window.__SAPPER__.preloaded) { - return { Component, data: Object.assign(data, window.__SAPPER__.preloaded) }; + return { Component, data: Object.assign(data, window.__SAPPER__.preloaded), redirect }; } - return Promise.resolve(Component.preload(data)).then(preloaded => { + return Promise.resolve(Component.preload.call({ + redirect: (statusCode: number, location: string) => { + redirect = { statusCode, location }; + } + }, data)).then(preloaded => { Object.assign(data, preloaded) - return { Component, data }; + return { Component, data, redirect }; }); } @@ -110,7 +116,8 @@ function navigate(target: Target, id: number) { const token = current_token = {}; - return loaded.then(({ Component, data }) => { + return loaded.then(({ Component, data, redirect }) => { + if (redirect) return goto(redirect.location, { replaceState: true }); render(Component, data, scroll_history[id], token); }); } diff --git a/test/app/routes/_components/Nav.html b/test/app/routes/_components/Nav.html index d17c646..2eb7da4 100644 --- a/test/app/routes/_components/Nav.html +++ b/test/app/routes/_components/Nav.html @@ -3,6 +3,7 @@
  • home
  • about
  • slow preload
  • +
  • redirect
  • blog
  • diff --git a/test/app/routes/redirect-from.html b/test/app/routes/redirect-from.html new file mode 100644 index 0000000..ecd5fb9 --- /dev/null +++ b/test/app/routes/redirect-from.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/test/app/routes/redirect-to.html b/test/app/routes/redirect-to.html new file mode 100644 index 0000000..eeb0dfc --- /dev/null +++ b/test/app/routes/redirect-to.html @@ -0,0 +1 @@ +

    redirected

    \ No newline at end of file diff --git a/test/common/test.js b/test/common/test.js index 5bf9e2c..049badb 100644 --- a/test/common/test.js +++ b/test/common/test.js @@ -297,6 +297,33 @@ function run(env) { assert.ok(matches); }); }); + + it('redirects on server', () => { + return nightmare.goto(`${base}/redirect-from`) + .path() + .then(path => { + assert.equal(path, '/redirect-to'); + }) + .then(() => nightmare.page.title()) + .then(title => { + assert.equal(title, 'redirected'); + }); + }); + + it('redirects on client', () => { + return nightmare.goto(base) + .wait('[href="/redirect-from"]') + .click('[href="/redirect-from"]') + .wait(200) + .path() + .then(path => { + assert.equal(path, '/redirect-to'); + }) + .then(() => nightmare.page.title()) + .then(title => { + assert.equal(title, 'redirected'); + }); + }); }); describe('headers', () => {