BurkovBA.github.io is online!
December 14, 2017 6 min read
I've been procrastinating over my blog for almost a year. Initially I wrote it in Angular in early 2017 and re-wrote everything in React in the last couple of weeks. At last, following Github's "ship early - ship often" motto, I shipped it today. Probably the most challenging aspect of the whole work was to make Github pages play nice with React SPA - I'll tell you how in this post.
My blog makes use of a pretty minimalistic toolchain by modern standards:
- React 16
- React Router v4
- Codebase scaffolded in spirit of this post from Medium (no create-react-app or Yeoman)
- No state management like Redux/Flux/MobX
- Scripts in EcmaScript 6 transpiled with Babel (no Flow or Typescript)
- Styles in SASS, copy-pasted from a nice Homer theme for Bootstrap 3
- NPM package.json for dependencies, no Yarn (or Bower obviously)
- NPM scripts to build it all (no Gulp/Grunt)
- Webpack 2 to bundle it all (with devServer for local development)
- Github pages to "host" and serve it
Project needs to be served in 2 environments. In development it needs to be rebuilt upon every change (or, to be precise, each part of it that ) and served from local machine with WebpackDevServer. In production it is served with with Github pages web server.
This causes several problems.
How to serve index.html
not from project root, but from /dist
or /build
folder?
My webpack setup works as follows: all the source files are located in /src
folder, while files generated
from them go to /dist
. Those initially included /dist/index.html
, generated from a template
in /src/index.html
by HtmlWebpackPlugin.
Thing is, Github pages won't serve index.html
from subdirectory and there's no way to customize its location
for per-user or per-organisation sites (although, per-repository sites can serve files from /docs
folder). Also project github pages can serve files from branches other than master, e.g. gh-pages
branch,
but again this is not an option for per-user or per-organiation sites.
Thus my first solution was to create a proxy /index.html
file in root folder of repository that
redirected to /dist/index.html
<html>
<head>
<meta http-equiv="refresh" content="0; url=https://BurkovBA.github.io/dist/index.html" />
</head>
</html>
However, this solution is not optimal: if somebody wanted to share a url, such as BurkovBA.github.io/blog/2017-12-14-1, they'll get 404 error.
Hence, in production I configured HtmlWebpackPlugin to put the built index.html
into the root
folder instead of /dist
, but that required additional tweaks of WebpackDevServer, which expected
it in dist. Thus I had to create a conditional build and set configuration variables in webpack.
How to run different webpack configurations for production and development?
Webpack 2 can consume environment variables from command line, in the form of arguments --env.variable
. I
use them to conditionally switch some webpack configuration settings in production/development in npm scripts.
var path = require('path'); | |
var webpack = require('webpack'); | |
const HtmlWebpackPlugin = require('html-webpack-plugin'); | |
module.exports = function(env) { | |
// set variables, depending on whether this is dev or prod, see: https://github.com/webpack/webpack/issues/2254 | |
var indexFilename = (env && env.prod) ? path.join(__dirname, "index.html") : "index.html"; | |
var publicPath = (env && env.prod) ? '/dist/' : '/'; | |
return { | |
entry: path.join(__dirname, 'src', 'app.jsx'), | |
output: { | |
path: path.join(__dirname, 'dist'), | |
publicPath: publicPath, | |
filename: 'app.[hash:7].js' | |
}, | |
resolve: { | |
modules: [path.join(__dirname, 'src'), path.join(__dirname, 'node_modules')] | |
}, | |
plugins: [ | |
new webpack.HotModuleReplacementPlugin(), | |
new HtmlWebpackPlugin({ | |
inject: "body", | |
template: "src/index.html", | |
filename: indexFilename | |
}), | |
new webpack.ProvidePlugin({ | |
$: 'jquery', | |
jQuery: 'jquery' | |
}) | |
], | |
module: { | |
rules: [ | |
{ | |
test: /\.jsx?$/, | |
loader: 'babel-loader', | |
exclude: /node_modules/, | |
query: { | |
presets: ['es2015', 'react'], | |
plugins: ['transform-es2015-destructuring', 'transform-object-rest-spread'] | |
} | |
}, | |
{ | |
test: /\.(scss|sass)$/, | |
use: [ | |
{ loader: 'style-loader' }, | |
{ loader: 'css-loader', options: {sourceMap: true} }, | |
{ loader: 'sass-loader', options: {sourceMap: true} } | |
] | |
}, | |
{ | |
test: /\.css$/, | |
use: ['style-loader', 'css-loader'] | |
}, | |
{ | |
test: /\.(png|jpe?g|gif)(\?v=\d+\.\d+\.\d+)?$/, | |
loader: 'url-loader?limit=100000' | |
}, | |
{ | |
test: /\.(eot|com|json|ttf|woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, | |
loader: 'url-loader?limit=10000&mimetype=application/octet-stream' | |
}, | |
{ | |
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, | |
loader: 'url-loader?limit=10000&mimetype=image/svg+xml' | |
} | |
] | |
}, | |
devServer: { | |
publicPath: '/', | |
contentBase: './', | |
hot: true | |
}, | |
devtool: "source-map" | |
}; | |
}; |
How to make Github serve direct links to SPA pages (e.g. BurkovBA.github.io/blog/2017-12-14-1)?
Ok, this still doesn't solve the problem of direct links. I found a brilliant solution on Github by Rafael Pedicini.
It works as follows: when you request some url of your SPA, which doesn't correspond to any file in your repository, Github server returns 404 error.
Here's the catch: you can customize your 404.html
page! So, you create a custom 404 error with
a script. That script parses url that you passed, transforms path components into URL params (aka GET params) and
redirects to index.html
. For instance, https://BurkovBA.github.io/blog?category=programming
is
transformed into https://BurkovBA.github.io?p=blog&category=programming
. index.html
parses
this path p
parameter back into a proper URL and initializes React SPA.