cover

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.


Boris Burkov

Written by Boris Burkov who lives in Moscow, Russia, loves to take part in development of cutting-edge technologies, reflects on how the world works and admires the giants of the past. You can follow me in Telegram