Tag Archives: Webpack

Add metadatas to your build files

Have you ever wondered if the file you’re been served from the server is up to date ? Why would you ?…

Not so long ago, you might have been programming in php and when something went wrong, the first thing you would say was “Try to clear your browser’s cache”.

Now that our assets filenames are hashed by default by bundlers like webpack, you could think that we shouldn’t run into such problems anymore. Since the filename of js/html (or any other files) will change based on its content, it will force the browser to retrieve the freshest version.

But between you and the server, there could be proxies / caching systems (like varnish / memcached). And more recently, on the client-side, we have ServiceWorkers that let us enable caching strategies.

So, to be 100% sure of which version of the site I’m running, for a few years now, I’ve been using a little routine that adds metadatas into the main generated files, based on:

  • infos inside the package.json (name, description, version, author, license)
  • git hash
  • date of the generation of the build

Example on an index.html:

<html>...</html><!--!
 * 
 * my-react-app-starter
 * 
 * create-react-app based project with a few pre-configured / installed features such as eslint or prettier
 * 
 * @version v1.0.0 - 2018-06-14T17:28:30+02:00
 * @revision #640ba4d - https://github.com/topheman/my-react-app-starter/tree/640ba4df20e3452acd2dd10b0f746a8300af0749
 * @author Christophe Rosset <tophe@topheman.com> (http://labs.topheman.com/)
 * @copyright 2018(c) Christophe Rosset <tophe@topheman.com> (http://labs.topheman.com/)
 * @license MIT
 * 
-->

That way, you can be sure of date / version (and other infos) of the generated file you’re been served with a simple curl.

Implementation

Whether you use create-react-app or have direct access to the webpack.config, first, copy/paste this common.js file to the root of your project, then:

npm install --save-dev moment git-rev-sync

Using create-react-app

I’ll explain bellow the implementation that you can see in this diff.

1) Copy/paste the following in a bin/expand-metadatas.js file in your project:

const { getBanner, getInfos } = require("../common");

process.env.REACT_APP_METADATAS_BANNER_HTML = getBanner("formatted");

process.env.REACT_APP_METADATAS_VERSION = getInfos().pkg.version;

2) Add --require ./bin/expand-metadatas.js to your package.json, that way, react-scripts will require bin/expand-metadatas.js before running and inject REACT_APP_METADATAS_BANNER_HTML and REACT_APP_METADATAS_VERSION as env vars:

"scripts": {
  "start": "react-scripts --require ./bin/expand-metadatas.js start",
  "build": "react-scripts --require ./bin/expand-metadatas.js build",
  "test": "react-scripts --require ./bin/expand-metadatas.js test --env=jsdom",
  "eject": "react-scripts eject"
}

3) At the end of public/index.html, append:

<!--!%REACT_APP_METADATAS_BANNER_HTML%
-->

You can also access process.env.REACT_APP_METADATAS_VERSION in the code of your app (to show the version number for example).

Using the webpack.config

Like in topheman/webpack-babel-starter, you can require common.js from your webpack.config and:

– access getBanner / getBannerHtml / getInfos
– use the return values with the HtmlWebpackPlugin

Conclusion

Next time your build is deployed on a server and you’re being told that your release doesn’t work, a simple curl will settle it … The front is not the problem ! 😉

Resources:

Automate AppCache offline support in your Webpack build

webpack-logo

Why would you use AppCache ? An API that is messy, not as advanced as service-workers and moreover, which is being removed from the Web Standards ?…

With the Progressive Web Apps, we hear a lot about service-workers. They are very powerfull for a lot of things (including offline support). Though, they’re not supported on IE nor Safari … 🙁

So, until the rest of the browser vendors catch up, if you want to provide some offline experience to all your users, you’ll have to use AppCache which is still widely supported.

AppCache in a few words

  • You have to provide a manifest.appcache file, served with the content type text/cache-manifest
  • This file will consist of three different parts:
    • CACHE: files that will be explicitly cached after they’re downloaded for the first time (this is the default section)
    • NETWORK: white-listed resources that require a connection to the server
    • FALLBACK: fallback pages the browser should use if a resource is inaccessible
  • You will reference this manifest.appcache as an attribute on the html tag of the page that will use it
  • If the manifest.appcache file is updated, the browser will download the resources listed in this manifest (if not, or offline, it will use the cached resources)

More infos on MDN

AppCache in a SPA

Note: Skip this part if you don’t bother about providing different index.html file whether your users are online or offline.

If you’re developing a SPA, and you want to provide a different index.html entry point whether you are online or offline, you’ll have to use a little trick. You won’t reference your manifest.appcache directly in your index.html but in an other html file that you’ll include in an iframe to your index.html.

That way, the index.html file won’t be cached by default (as the master entry) and you’ll be able to define a fallback in the manifest.appcache

index.html

...
<iframe src="./iframe-inject-appcache-manifest.html" style="display: none"></iframe>
...

iframe-inject-appcache-manifest.html

<html manifest="manifest.appcache"></html>

dummy manifest.appcache file

CACHE MANIFEST
# v1 (some version id)

assets/foo.png
assets/bundle.js
assets/style.css
# more assets ...

NETWORK:
*

# that way, you'll be able to force the fallback of index.html
# to an other file when you're offline
FALLBACK:
. offline.html

Automate AppCache

If your app relies on a large code base, with a build step, you will need to automate this task. You’ll also have to ensure that AppCache doesn’t mess with your development workflow (meaning disabling it when developing).

I will describe the steps I took to automate AppCache support on topheman/rxjs-experiments, a little project using RxJS (no other frameworks involved). The workflow of this project is based on a seed I made and open-sourced: topheman/webpack-babel-starter.

Checkout the App

Step 1 – Define if we should “activate” AppCache

When you’re running webpack in dev-server mode, that means you’re developing, so you want your sources to be kept up to date (you don’t want AppCache to cache them).

webpack.config.js

const MODE_DEV_SERVER = process.argv[1].indexOf('webpack-dev-server') > -1 ? true : false;
// ...
const APPCACHE = process.env.APPCACHE ? JSON.parse(process.env.APPCACHE) : !MODE_DEV_SERVER;// if false, nothing will be cached by AppCache

Continue reading

ES6+ code coverage with Babel plugin

babel-logo

Running unit tests against an ES6+ source code base has now become an almost trivial task, thanks to Babel and all the ecosystem around it. There are a lot of good resources explaining how to do that, with different tools and frameworks.

On the other hand, code coverage on this kind of tests is a rather less covered subject …

If you apply regular code coverage solutions to that case, you end up with reports based on the transpiled code, not your original source code in ES6+, which isn’t really relevant …

Here comes isparta

This problem was solved by isparta, a code coverage tool for ES6+, using Babel, which provides code coverage reports using istanbul (which is also a code coverage tool … 😉 more infos here).

Using isparta, you can generate code coverage from unit tests on ES6+ source code base, directly against your original source code (not the transpiled one). This works great, combined with tools like karma-coverage, you can output coverage reports under any format (html, lcov … that can be processed by various tools/services like Jenkins or coveralls.io …).

isparta not maintained anymore

No Maintenance Intended

About two weeks ago, @duglasduteil added the “No Maintenance Intended” badge to his module. This should remind us that behind every open source project there are developers maintaining them (most of the time on there free time).

So, now may be the time to find a replacement for isparta … I was looking for an alternative for a few days when I eventually ended up on twitter with @kentcdodds who mentionned dtinth/babel-plugin-__coverage__.

ES6+ code coverage using a Babel plugin

Since Babel v6, you can make your very own plugins that can do much more than simply transpile ES6+ to ES5.

Babel is a generic multi-purpose compiler for JavaScript. More than that it is a collection of modules that can be used for many different forms of static analysis.

Babel Plugin Handbook

That’s what @dtinth has done with babel-plugin-__coverage__. He made a babel plugin that instrument your code, injecting metadata that will be processed by istambul (which is used under the hood by karma-coverage and other coverage report tools).

The great thing is that, since you go through Babel anyway to transpile your source code, the only thing you have to do to get those infos to feed tools like karma-coverage (or any istambul-based coverage reporter) is to activate this plugin in your .babelrc file …

Using a babel plugin for coverage is a no-brainer.

@kentcdodds

You won’t have to add complex configuration or tools anymore. @dtinth‘s plugin is 300 LOC, it’s his first babel plugin that he made over two nights. As far as I have tested it, it works very well.

This is clear that using a babel plugin for that kind of purpose is the right way to proceed. Since it’s becoming easier to setup, we might see more projects using ES6+ including code coverage reports in a near futur.

You can see an example of setup on my project topheman/react-es6-redux.

Checkout it out

RxJS – first steps

rxjs-logo

I’ve been hearing about RxJS for a little more than 2 years in meetups and conferences, but never took the time to test it in a project (lots of other things came before on my list 😉 ).

At the end of last month, I decided to try it after watching the CycleJS Courses by André Staltz. Even if you’re more into React/Redux (like I am), I recommend it.

Before starting, I created a boilerplate for Webpack / Babel projects, gathering configurations, development & build workflow, good practices I used to setup each time I was creating one of my projects using Webpack. That way, everything would be in one place (and could be shared).

One thing RxJS is very good at is managing events (and multiple events) that have intermediate states (such as drag’n drop). So you will find the following features on topheman/rxjs-experiments (at least for the moment):

I could have coded those features in VanillaJS, but when you’ll take a look at the code, you’ll see how much simplier it is in RxJS.

Test the Demo!

I’m still a beginner at reactive programming. I can understand how it simplifies async event management. The number of operators is overwhelming though … It can be hard to find the right ones (or the right way) to use them – I’ve been said that at the end, you only use a handful of them – a little like when you switch from imperative to functional programming.

I’ll continue to add examples (for me to train) – if you’re an RxJS expert and find a better way to write my code, please consider to send me a PR.

Resources:

How to fail webpack build on error

webpack-logo

By default, webpack will tolerate any errors happening while bundling.

It’s useful when you’re running in webpack-dev-server mode, however, when you’re making your final build you might want it to fail if any error happens (the main use case being when you’re testing the build step on your CI).

For that, you can use the bail configuration option.

Here is an example of a minimal webpack.config.js setup that will fail your Travis CI tests if any error happens at build time:

const plugins = [];
const TRAVIS = process.env.TRAVIS ? JSON.parse(process.env.TRAVIS) : false;

if (TRAVIS) {
  console.log('TRAVIS mode (will fail on error)');
  plugins.push(new webpack.NoErrorsPlugin());
}

const config = {
  bail: TRAVIS,
  // ... the rest of your config
};

module.exports = config;

Resources: