diff --git a/README.md b/README.md index 6b24b1dd0..a79df9a39 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ $ preact build --less, -l Build and compile LESS files. [default: false] --sass, -s Build and compile SASS files. [default: false] --prerender Pre-render static app content. [default: true] + --prerenderUrls Path to pre-render routes configuration. [default "prerender-urls.json"] --template Path to template file. --clean Clear output directory before building. [default: true] --json Generate build statistics for analysis. [default: false] @@ -144,6 +145,26 @@ export default function (config, env, helpers) { ``` See [WebpackConfigHelpers] docs for more info on ```helpers``` argument. +#### Prerender multiple routes + +The `--prerender` flag will prerender by default only the root of your application. +If you want to prerender other routes you can create a `prerender-urls.json` file, which contains the set of routes you want to render. +The format required for defining your routes is an array of objects with a `url` key and an optional `title` key. +```js +// prerender-urls.json +[{ + "url": "/", + "title": "Homepage" +}, { + "url": "/route/random" +}] +``` + +You can customise the path of `prerender-urls.json` by using the flag `--prerenderUrls`. +``` +preact build --prerenderUrls src/prerender-urls.json +``` + #### Template A template is used to render your page. diff --git a/src/commands/build.js b/src/commands/build.js index 1bab398cc..be3f55f4c 100644 --- a/src/commands/build.js +++ b/src/commands/build.js @@ -27,6 +27,10 @@ export default asyncCommand({ description: 'Pre-render static app content.', default: true }, + prerenderUrls: { + description: 'Path to pre-render routes configuration.', + default: 'prerender-urls.json' + }, clean: { description: 'Clear output directory before building.', default: true diff --git a/src/lib/webpack/webpack-base-config.js b/src/lib/webpack/webpack-base-config.js index 0f299b39b..c21e900ed 100644 --- a/src/lib/webpack/webpack-base-config.js +++ b/src/lib/webpack/webpack-base-config.js @@ -23,7 +23,7 @@ export function exists(file) { return false; } -function readJson(file) { +export function readJson(file) { if (file in readJson.cache) return readJson.cache[file]; let ret; try { ret = JSON.parse(readFileSync(file)); } diff --git a/src/lib/webpack/webpack-client-config.js b/src/lib/webpack/webpack-client-config.js index 4d74028e2..48ac4ca9b 100644 --- a/src/lib/webpack/webpack-client-config.js +++ b/src/lib/webpack/webpack-client-config.js @@ -17,7 +17,7 @@ import ScriptExtHtmlWebpackPlugin from 'script-ext-html-webpack-plugin'; import CopyWebpackPlugin from 'copy-webpack-plugin'; import SWPrecacheWebpackPlugin from 'sw-precache-webpack-plugin'; import PushManifestPlugin from './push-manifest'; -import baseConfig, { exists, helpers } from './webpack-base-config'; +import baseConfig, { exists, readJson, helpers } from './webpack-base-config'; import prerender from './prerender'; export default env => { @@ -164,9 +164,9 @@ const production = config => addPlugins([ }) ]); -const htmlPlugin = (config, outputDir) => addPlugins([ - new HtmlWebpackPlugin({ - filename: 'index.html', +const htmlPlugin = (config, outputDir) => { + const htmlWebpackConfig = ({ url, title }) => ({ + filename: resolve(outputDir, url.substring(1), 'index.html'), template: `!!ejs-loader!${config.template || resolve(__dirname, '../../resources/template.html')}`, minify: config.production && { collapseWhitespace: true, @@ -180,16 +180,21 @@ const htmlPlugin = (config, outputDir) => addPlugins([ inject: true, compile: true, preload: config.preload===true, - title: config.title || config.manifest.name || config.manifest.short_name || (config.pkg.name || '').replace(/^@[a-z]\//, '') || 'Preact App', + title: title || config.title || config.manifest.name || config.manifest.short_name || (config.pkg.name || '').replace(/^@[a-z]\//, '') || 'Preact App', excludeAssets: [/(bundle|polyfills)(\..*)?\.js$/], config, ssr(params) { - return config.prerender ? prerender(outputDir, params) : ''; + return config.prerender ? prerender(outputDir, { ...params, url }) : ''; } - }), - new HtmlWebpackExcludeAssetsPlugin(), - new ScriptExtHtmlWebpackPlugin({ - // inline: 'bundle.js', - defaultAttribute: 'defer' - }) -]); + }); + const pages = readJson(resolve(config.cwd, config.prerenderUrls || '')) || [{ url: "/" }]; + return addPlugins(pages + .map((page) => new HtmlWebpackPlugin(htmlWebpackConfig(page))) + .concat([ + new HtmlWebpackExcludeAssetsPlugin(), + new ScriptExtHtmlWebpackPlugin({ + // inline: 'bundle.js', + defaultAttribute: 'defer' + }) + ])); +}; diff --git a/tests/build.snapshot.js b/tests/build.snapshot.js index 77c80dbd0..8e17ba520 100644 --- a/tests/build.snapshot.js +++ b/tests/build.snapshot.js @@ -127,3 +127,23 @@ export const withCustomTemplate = ` `; + +export const multiplePrerenderingHome = ` + +
+
Home
+
+ + {{ ... }} + +`; + +export const multiplePrerenderingRoute = ` + +
+
Route66
+
+ + {{ ... }} + +`; \ No newline at end of file diff --git a/tests/build.test.js b/tests/build.test.js index 654135fbf..d0682be68 100644 --- a/tests/build.test.js +++ b/tests/build.test.js @@ -4,7 +4,7 @@ import htmlLooksLike from 'html-looks-like'; import { create, build } from './lib/cli'; import lsr from './lib/lsr'; import { setup, fromSubject } from './lib/output'; -import expectedOutputs, { sassPrerendered, withCustomTemplate } from './build.snapshot'; +import expectedOutputs, { sassPrerendered, withCustomTemplate, multiplePrerenderingHome, multiplePrerenderingRoute } from './build.snapshot'; import filesMatchSnapshot from './lib/filesMatchSnapshot'; describe('preact build', () => { @@ -40,6 +40,18 @@ describe('preact build', () => { expect(async () => await build(app)).not; }); + it(`should prerender the routes provided with prerenderUrls.`, async () => { + let app = await fromSubject('multiple-prerendering'); + await build(app); + + let output = await fs.readFile(resolve(app, './build/index.html'), 'utf-8'); + let html = output.match(/.*<\/body>/)[0]; + htmlLooksLike(html, multiplePrerenderingHome); + output = await fs.readFile(resolve(app, './build/route66/index.html'), 'utf-8'); + html = output.match(/.*<\/body>/)[0]; + htmlLooksLike(html, multiplePrerenderingRoute); + }); + it(`should use custom preact.config.js.`, async () => { // app with custom template set via preact.config.js let app = await fromSubject('custom-webpack'); diff --git a/tests/subjects/multiple-prerendering/index.js b/tests/subjects/multiple-prerendering/index.js new file mode 100644 index 000000000..6d08af5bf --- /dev/null +++ b/tests/subjects/multiple-prerendering/index.js @@ -0,0 +1,23 @@ +import { h, Component } from 'preact'; +import { Router } from 'preact-router'; + +const Home = () =>
Home
; + +const Route66 = () =>
Route66
; + +export default class App extends Component { + handleRoute = e => { + this.currentUrl = e.url; + }; + + render() { + return ( +
+ + + + +
+ ); + } +} diff --git a/tests/subjects/multiple-prerendering/package.json b/tests/subjects/multiple-prerendering/package.json new file mode 100644 index 000000000..9488b959c --- /dev/null +++ b/tests/subjects/multiple-prerendering/package.json @@ -0,0 +1,16 @@ +{ + "name": "preact-app", + "version": "0.0.1", + "private": true, + "dependencies": { + "preact": "latest", + "preact-compat": "latest", + "preact-router": "latest" + }, + "devDependencies": { + "eslint": "^4.1.1", + "eslint-config-synacor": "^1.0.1", + "if-env": "^1.0.0", + "preact-cli": "file:../../../" + } +} diff --git a/tests/subjects/multiple-prerendering/prerender-urls.json b/tests/subjects/multiple-prerendering/prerender-urls.json new file mode 100644 index 000000000..2f4106b0c --- /dev/null +++ b/tests/subjects/multiple-prerendering/prerender-urls.json @@ -0,0 +1 @@ +[{ "url": "/" }, { "url": "/route66", "title": "Route66"}] \ No newline at end of file