Minimal, unopinionated static site generator powered by webpack.
Provide a series of paths to be rendered, and a matching set of index.html
files will be rendered in your output directory by executing your own custom, webpack-compiled render function.
This plugin works particularly well with universal libraries like React and React Router since it allows you to prerender your routes at build time, rather than requiring a Node server in production.
$ npm install --save-dev static-site-generator-webpack-plugin
Ensure you have webpack installed, e.g. npm install -g webpack
const StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin');
const paths = [
'/hello/',
'/world/'
];
module.exports = {
entry: {
'main': './index.js'
},
output: {
filename: 'index.js',
path: 'dist',
/* IMPORTANT!
* You must compile to UMD or CommonJS
* so it can be required in a Node context: */
libraryTarget: 'umd'
},
plugins: [
new StaticSiteGeneratorPlugin('main', paths, {
// Properties here are merged into `locals`
// passed to the exported render function
greet: 'Hello'
})
]
};
module.exports = function render(locals) {
return '<html>' + locals.greet + ' from ' + locals.path + '</html>';
};
module.exports = function render(locals, callback) {
callback(null, '<html>' + locals.greet + ' from ' + locals.path + '</html>');
};
module.exports = function render(locals) {
return Promise.resolve('<html>' + locals.greet + ' from ' + locals.path + '</html>');
};
// The path currently being rendered:
locals.path;
// An object containing all assets:
locals.assets;
// Advanced: Webpack's stats object:
locals.webpackStats;
Any additional locals provided in your config are also available.
By providing paths that end in .html
, you can generate custom file names other than the default index.html
. Please note that this may break compatibility with your router, if you're using one.
module.exports = {
...
plugins: [
new StaticSiteGeneratorPlugin('main', [
'/index.html',
'/news.html',
'/about.html'
], locals)
]
};
If required, you can provide an object that will exist in the global scope when executing your render function. This is particularly useful if certain libraries or tooling you're using assumes a browser environment.
For example, when using Webpack's require.ensure
, which assumes that window
exists:
const scope = { window: {} };
module.exports = {
...,
plugins: [
new StaticSiteGeneratorPlugin('main', paths, locals, scope)
]
}
Generated files can be compressed with compression-webpack-plugin, but first ensure that this plugin appears before compression-webpack-plugin in your plugins array:
const StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
...
plugins: [
new StaticSiteGeneratorPlugin(...),
new CompressionPlugin(...)
]
};
The following example uses React Router v1.0.0-rc1 with history.
import React from 'react';
import ReactDOM from 'react-dom';
import ReactDOMServer from 'react-dom/server';
import { createHistory, createMemoryHistory } from 'history';
import { Router, RoutingContext, match } from 'react-router';
import routes from './routes';
import template from './template.ejs';
// Client render (optional):
if (typeof document !== 'undefined') {
const history = createHistory();
const outlet = document.getElementById('outlet');
ReactDOM.render(<Router history={history} routes={routes} />, outlet);
}
// Exported static site renderer:
export default (locals, callback) => {
const history = createMemoryHistory();
const location = history.createLocation(locals.path);
match({ routes, location }, (error, redirectLocation, renderProps) => {
callback(null, template({
html: ReactDOMServer.renderToString(<RoutingContext {...renderProps} />),
assets: locals.assets
}));
});
};
- react-router-to-array - useful for avoiding hardcoded lists of routes to render
- gatsby - opinionated static site generator built on top of this plugin