Building a webpack plugin to validate your bundle files for IE11
I guess most developers don’t use IE11 as their developer environment. But we often have to support IE11 as an environment for our applications. These days, we write application code using TypeScript, ES2020, or JSX and compile them to ES5 JavaScript, so we don’t have to care about what ECMAScript features our support browsers support, because these are responsibilities of compilers like TypeScript and Babel.
But I still see that our application displays a blank page only on IE11. Why? The reason is that we don’t usually compile source code in node_modules. We merely bundle the source code. It means that your application doesn’t work on IE11 if any dependencies published their code written as >ES2015.
Fortunately, many npm packages publish code compiled to ES5. But there is no rule to have to compile to ES5 to publish an npm package. Node already supports >ES2015 features, so it doesn’t make sense to compile to ES5 if a package author intended to use the package in Node.js. Even a package for browsers, it might make sense to compile to ES2015, not ES5, because browsers except IE11 already support ES2015 features. In addition to that, it is tough to confirm what ES version each dependency package is using. To confirm this, we have to understand the compiler settings of the package.
In addition to that, it’s tough to build code for various targets properly. For example, `polished` accidentally published a version that includes code compiled to ES2015 and pointed it out at the `module` field in `package.json.` Fortunately, they have fixed quickly, but it seems to be a bit hard to find it.
https://github.com/styled-components/polished/issues/504
Of course, we could avoid this by compiling source code in `node_modules,` but it takes extra time. IIRC, there was a case to break code by compiling code that has already compiled, which is a double compiling. I have to dig into the case, but this time, I’ve taken another option.
To detect to include not ES5 compatible code in our builds, I’ve created `ecma-version-validator-webpack-plugin,` which is a webpack plugin to validate that build files are compatible with specific ES version or not. This plugin makes it possible to avoid shipping source code that is not ES5 compatible.
https://github.com/koba04/ecma-version-validator-webpack-plugin
const { ECMAVersionValidatorPlugin } = require("ecma-version-validator-webpack-plugin");
module.exports = {
// ...
plugins: [
new ECMAVersionValidatorPlugin(/* options */)
],
}
This plugin emits an error if the out file includes >=ES2015 syntax in the file. You can change the target ECMAScript version by `ecmaVersion` option.
This plugin can only detect syntax errors, cannot detect the usage of built-in objects and methods newly added, so it cannot avoid runtime errors entirely, but it’s still useful. I recommend running a test using this plugin on CI like the following.
The followings are tips to write a webpack plugin.
How to write a webpack plugin
webpack provides many hooks for plugins.
https://webpack.js.org/api/compiler-hooks/
`ecma-version-validator-webpack-plugin` have to take emitted files, so this plugin uses `emit` hook to validate build contents. This implementation is very simple, which validates assets and push errors if an error occurred.
apply(compiler: Compiler) {
compiler.hooks.emit.tap(this.name, (compilation) => {
try {
validate(compilation.assets, {
ecmaVersion: this.ecmaVersion,
test: this.test,
});
} catch (e) {
compilation.errors.push(e);
}
});
}
See more details to see how to create a webpack plugin.
https://webpack.js.org/contribute/writing-a-plugin/
How to test a webpack plugin
This plugin has some tests. Most of the tests are separated from `webpack` so we can write tests as regular unit testing. To write unit testing using a webpack compiler is also straightforward. See the example in the following.
First, I’ve created a webpack compiler using Node.js API of webpack.
Second, I’ve used `memfs` as the outputFileSystem because I don’t want to output an actual bundle file by this test; memfs is a simple in-memory filesystem.
import path from "path";
import webpack from "webpack";
import { fs } from "memfs";// This will be able to remove at webpack v5
// @ts-ignore https://github.com/webpack/webpack/pull/9251
fs.join = path.join;it("should emit an error if an input file is not valid", (done) => {
const compiler = webpack({
mode: "development",
entry: path.resolve(__dirname, "fixtures", "es2015.js"),
devtool: "nosources-source-map",
plugins: [new ECMAVersionValidatorPlugin()],
});
// @ts-ignore https://github.com/webpack/webpack/pull/9251
compiler.outputFileSystem = fs;
compiler.run((err, stats) => {
expect(err).toBeNull();
expect(stats.compilation.errors.length).toBe(1);
done();
});
});
The interface of outputFileSystem of webpack v4 expects to have a join()
method, which is not implemented on `memfs`. So we have to add path.join()
method into an instance of `memfs`. This is fixed at webpack v5.
Note: I have an issue `memfs` throws an unexpected error. I don’t dig into the issue yet, so I’ll work on it later.
https://github.com/kufu/smarthr-ui/pull/779#discussion_r425298223
Acknowledgement
After I’ve published the webpack plugin, gfx taught me that he also built a webpack that is the same purpose before😇.
https://github.com/bitjourney/check-es-version-webpack-plugin
I’ve learned the way to write unit tests with webpack compiler by the repository. Thank you!