diff --git a/connect.js b/connect.js index 60af11b..7e7e791 100644 --- a/connect.js +++ b/connect.js @@ -8,7 +8,7 @@ const mkdirp = require('mkdirp'); const create_routes = require('./lib/utils/create_routes.js'); const templates = require('./lib/templates.js'); const create_app = require('./lib/utils/create_app.js'); -const create_webpack_compiler = require('./lib/utils/create_webpack_compiler.js'); +const create_compiler = require('./lib/utils/create_compiler.js'); const escape_html = require('escape-html'); const { src, dest, dev } = require('./lib/config.js'); @@ -26,7 +26,7 @@ module.exports = function connect(opts) { create_app(src, dest, routes, opts); - const webpack_compiler = create_webpack_compiler( + const compiler = create_compiler( dest, routes, dev @@ -35,81 +35,101 @@ module.exports = function connect(opts) { return async function(req, res, next) { const url = req.url.replace(/\?.+/, ''); - if (url === '/service-worker.js' || url === '/index.html' || url.startsWith('/client/')) { - await webpack_compiler.ready; + if (url === '/service-worker.js') { + await compiler.ready; res.set({ - 'Content-Type': url === '/index.html' ? 'text/html' : 'application/javascript' + 'Content-Type': 'application/javascript', + 'Cache-Control': 'max-age=600' }); - fs.createReadStream(`${dest}${url}`).pipe(res); - return; + res.end(compiler.service_worker); } - // whatever happens, we're going to serve some HTML - res.set({ - 'Content-Type': 'text/html' - }); + else if (url === '/index.html') { + await compiler.ready; + res.set({ + 'Content-Type': 'text/html', + 'Cache-Control': 'max-age=600' + }); + res.end(compiler.shell); + } - try { - for (const route of routes) { - if (route.test(url)) { - await webpack_compiler.ready; + else if (url.startsWith('/client/')) { + await compiler.ready; + res.set({ + 'Content-Type': 'application/javascript', + 'Cache-Control': 'max-age=31536000' + }); + res.end(compiler.asset_cache[url]); + } - req.params = route.exec(url); + else { + // whatever happens, we're going to serve some HTML + res.set({ + 'Content-Type': 'text/html' + }); - const chunk = webpack_compiler.chunks[route.id]; - const mod = require(path.resolve(dest, 'server', chunk)); + try { + for (const route of routes) { + if (route.test(url)) { + await compiler.ready; - if (route.type === 'page') { - let data = { params: req.params, query: req.query }; - if (mod.default.preload) data = Object.assign(data, await mod.default.preload(data)); + req.params = route.exec(url); - const { html, head, css } = mod.default.render(data); + const chunk = compiler.chunks[route.id]; + const mod = require(path.resolve(dest, 'server', chunk)); - const page = templates.render(200, { - main: webpack_compiler.client_main, - html, - head: `${head}`, - styles: (css && css.code ? `` : '') - }); + if (route.type === 'page') { + let data = { params: req.params, query: req.query }; + if (mod.default.preload) data = Object.assign(data, await mod.default.preload(data)); - res.status(200); - res.end(page); - } + const { html, head, css } = mod.default.render(data); - else { - const handler = mod[req.method.toLowerCase()]; - if (handler) { - if (handler.length === 2) { - handler(req, res); - } else { - const data = await handler(req); + const page = templates.render(200, { + main: compiler.client_main, + html, + head: `${head}`, + styles: (css && css.code ? `` : '') + }); - // TODO headers, error handling - if (typeof data === 'string') { - res.end(data); + res.status(200); + res.end(page); + } + + else { + const handler = mod[req.method.toLowerCase()]; + if (handler) { + if (handler.length === 2) { + handler(req, res); } else { - res.end(JSON.stringify(data)); + const data = await handler(req); + + // TODO headers, error handling + if (typeof data === 'string') { + res.end(data); + } else { + res.end(JSON.stringify(data)); + } } } } + + return; } - - return; } - } - res.status(404).end(templates.render(404, { - title: 'Not found', - status: 404, - method: req.method, - url - })); - } catch(err) { - res.status(500).end(templates.render(500, { - title: err.name || 'Internal server error', - url, - error: escape_html(err.details || err.message || err || 'Unknown error') - })); + res.status(404).end(templates.render(404, { + title: 'Not found', + status: 404, + method: req.method, + url + })); + } catch(err) { + res.status(500).end(templates.render(500, { + title: err.name || 'Internal server error', + url, + error: escape_html(err.details || err.message || err || 'Unknown error') + })); + } } }; }; \ No newline at end of file diff --git a/lib/utils/create_app.js b/lib/utils/create_app.js index 9e2fc34..f7a3fe9 100644 --- a/lib/utils/create_app.js +++ b/lib/utils/create_app.js @@ -13,7 +13,7 @@ module.exports = function create_app(src, dest, routes, options) { '{}' : `{ ${route.dynamic.map((part, i) => `${part}: match[${i + 1}]`).join(', ') } }`; - return `{ pattern: ${route.pattern}, params: match => (${params}), load: () => import('${src}/${route.file}') }` + return `{ pattern: ${route.pattern}, params: match => (${params}), load: () => import(/* webpackChunkName: "${route.id}" */ '${src}/${route.file}') }` }) .join(',\n\t'); diff --git a/lib/utils/create_webpack_compiler.js b/lib/utils/create_compiler.js similarity index 81% rename from lib/utils/create_webpack_compiler.js rename to lib/utils/create_compiler.js index 36e38fb..23b6b06 100644 --- a/lib/utils/create_webpack_compiler.js +++ b/lib/utils/create_compiler.js @@ -44,6 +44,11 @@ module.exports = function create_webpack_compiler(dest, routes, dev) { compiler.client_main = `/client/${info.assetsByChunkName.main}`; compiler.assets = info.assets.map(asset => `/client/${asset.name}`); + compiler.asset_cache = {}; + compiler.assets.forEach(file => { + compiler.asset_cache[file] = fs.readFileSync(path.join(dest, file), 'utf-8'); + }); + fulfil(); }); }), @@ -59,7 +64,6 @@ module.exports = function create_webpack_compiler(dest, routes, dev) { } compiler.chunks = info.assetsByChunkName; - fulfil(); }); }) @@ -73,22 +77,22 @@ module.exports = function create_webpack_compiler(dest, routes, dev) { .join(', ') }]`; - const service_worker = fs.readFileSync('templates/service-worker.js', 'utf-8') + compiler.service_worker = fs.readFileSync('templates/service-worker.js', 'utf-8') .replace('__timestamp__', Date.now()) .replace('__assets__', JSON.stringify(assets)) .replace('__shell__', JSON.stringify(compiler.assets.concat('/index.html'))) .replace('__routes__', route_code); - fs.writeFileSync(path.resolve(dest, 'service-worker.js'), service_worker); - - const shell = templates.render(200, { + compiler.shell = templates.render(200, { styles: '', head: '', html: '', main: compiler.client_main }); - fs.writeFileSync(path.resolve(dest, 'index.html'), shell); + // useful for debugging, but the files are served from memory + fs.writeFileSync(path.resolve(dest, 'service-worker.js'), compiler.service_worker); + fs.writeFileSync(path.resolve(dest, 'index.html'), compiler.shell); }); compiler.get_chunk = async id => { diff --git a/webpack/config.js b/webpack/config.js index 2bb1499..f459a63 100644 --- a/webpack/config.js +++ b/webpack/config.js @@ -16,7 +16,7 @@ module.exports = { return { path: `${dest}/client`, filename: '[name].[hash].js', - chunkFilename: '[name].[id].js', + chunkFilename: '[name].[id].[hash].js', publicPath: '/client/' }; } @@ -35,7 +35,7 @@ module.exports = { return { path: `${dest}/server`, filename: '[name].[hash].js', - chunkFilename: '[name].[id].js', + chunkFilename: '[name].[id].[hash].js', libraryTarget: 'commonjs2' }; }