thunk juice

putting the "meth" in "programming methodology"

Long-term caching of static assets with Webpack, React, and Typescript


the problem

Suppose you’re building a web application using React/Typescript/Webpack (for example, maybe you work at Distribute). You recognize that you need caching and cache busters. You follow guides such as this, this, this, this (and some others I forgot), and because it works with regular JSX, you might end up with something like this:

// vendor.tsx

import * as React from 'react'
import * as ReactDOM from 'react-dom'
// app.tsx

import * as React from "react"
import * as ReactDOM from "react-dom"

ReactDOM.render(
  <h1>shit was so cache</h1>,
  document.getElementById("root")
)
// webpack.config.js

const path = require('path')
const webpack = require('webpack')
const WebpackMd5Hash = require('webpack-md5-hash')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin')

const config = {
  entry: {
    vendor: './src/vendor.tsx',
    main: './src/app.tsx'
  },
  output: {
    path: path.join(__dirname, '/public'),
    filename: '[name].[chunkhash].js',
    chunkFilename: '[name].[chunkhash].js'
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.json']
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: Infinity
    }),
    new WebpackMd5Hash(),
    new InlineManifestWebpackPlugin({
      name: 'webpackManifest'
    }),
    new HtmlWebpackPlugin({
      template: './src/index.ejs'
    })
  ],
  module: {
    rules: [{
      test: /\.tsx?$/,
      loader: 'awesome-typescript-loader'
    }]
  }

}
module.exports = [config]

But then when you try to build it

Hash: 0c0c96c0c8f921c2e280
Version: webpack 3.0.0
Child
    Hash: 0c0c96c0c8f921c2e280
    Time: 2259ms
                             Asset       Size  Chunks                    Chunk Names
      main.869bbf8c0a14609301a3.js     746 kB       0  [emitted]  [big]  main
    vendor.24563705cc4bb54fccd8.js    6.14 kB       1  [emitted]         vendor
                        index.html  289 bytes          [emitted]         
      [81] ./src/vendor.tsx 79 bytes {1} [built]
      [82] ./src/app.tsx 257 bytes {0} [built]
        + 183 hidden modules
    Child html-webpack-plugin for "index.html":
           [0] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.ejs 581 bytes {0} [built]
           [2] (webpack)/buildin/global.js 509 bytes {0} [built]
           [3] (webpack)/buildin/module.js 517 bytes {0} [built]
            + 1 hidden module

You get a gigantic main bundle with everything. And an empty vendor bundle. Fuck.

the solution

Export your vendor modules:

// vendor.tsx

import * as React from 'react'
import * as ReactDOM from 'react-dom'

export { React, ReactDOM }

And then:

Hash: 484954d50613815c9110
Version: webpack 3.0.0
Child
    Hash: 484954d50613815c9110
    Time: 2279ms
                             Asset       Size  Chunks                    Chunk Names
      main.c6ed251878b712466ff9.js  377 bytes       0  [emitted]         main
    vendor.9d1f36768857c94e8503.js     752 kB       1  [emitted]  [big]  vendor
                        index.html  289 bytes          [emitted]         
      [83] ./src/vendor.tsx 206 bytes {1} [built]
     [184] ./src/app.tsx 257 bytes {0} [built]
        + 183 hidden modules
    Child html-webpack-plugin for "index.html":
           [0] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.ejs 581 bytes {0} [built]
           [2] (webpack)/buildin/global.js 509 bytes {0} [built]
           [3] (webpack)/buildin/module.js 517 bytes {0} [built]
            + 1 hidden module

Hell yeah.

Maybe this is obvious to real frontend engineers who understand Webpack/React/ES6/Typescript intimately, but I’m a backend guy, and I couldn’t look up the answer. But now you can. It’s all about Giving Back To The People

The Rest is Up to You…

Posted June 19, 2017