New in Vue: ES Module Browser Build
Anthony Gore | February 4th, 2019 | 4 min read
A new features arriving in Vue version 2.6 is the inclusion of an ES Module Browser Build, allowing you to load Vue on your page like this:
<script type="module">
import Vue from 'https://unpkg.com/vue@2.6.0/dist/vue.esm.browser.min.js';
new Vue({
...
});
</script>
This means you can write modular Vue apps which will run in the browser without a build step (in supported browsers, of course).
Let's explore the reasons behind this feature and see how to use it.
A little background on JavaScript modules
When JavaScript was created, it wasn't obvious to most people that it would end up being the world's most popular programming language.
It was mostly meant for triggering alert windows and blinking text, so it was assumed that writing all the code in one script would be sufficient.
But in this era of Node.js and mega SPAs, JavaScript apps can have hundreds of dependencies and thousands of lines of code.
To develop a scaled-up app like this, it's almost a requirement that code can be split into modules and allow a scalable way to import dependencies, and make code maintainable and efficient.
Module systems
Eventually, JavaScript module systems were created, initially just for Node.js. The use case for client-side modules was also strong, but the difficulty there was that every browser would have to support them if they were to be of any use.
Browser module loaders attempted to solve this problem, but the prevailing solution ended up being to compile modularized JavaScript back into a non-modularized form using a module bundler like Webpack or Rollup.
Modules were finally added to the ECMA standard in ES2015, and in 2019, modern browsers support ES modules natively, allowing you to write modular JavaScript that runs directly in the browser without need for compilation.
Here's an example:
index.html (client-side)
<script type="module">
import {addTextToBody} from './utils.mjs';
addTextToBody('Modules are pretty cool.');
</script>
utils.mjs (server-side)
export function addTextToBody(text) {
const div = document.createElement('div');
div.textContent = text;
document.body.appendChild(div);
}
Code example from ECMAScript modules in browsers by Jake Archibald.
Vue.js builds
Let's change tack for a moment and discuss Vue builds.
Since there are a number of different environments and use cases for the Vue.js library, so there are a number of builds available, including the UMD build, CommonJS build, and ES module build.
For example, if you want to use Vue directly in a browser, you can use the UMD build:
index.html
<script src="https://mycdn.com/vue.js"></script>
<script>
new Vue();
</script>
The UMD build declares the Vue object in the global namespace, making it available to any script declared after the Vue script is loaded and parsed.
This is the "classic" way of including a JS library in a project, but it has a number of downsides e.g. scripts must be loaded in the order they're used, two conflicting version can be added to the same page accidentially etc.
But it's handy for rapid prototyping as it doesn't require a build step.
Vue as a module
The CommonJS and ES module builds export Vue as a module based on different module standards. Their use is for bundling tool like Webpack or Rollup. For example, users would create an "entry file" like this:
app.js
import Vue from "vue";
new Vue();
And the bundler would compile this script, and the Vue module, into a single build file, say /dist/bundle.js, which gets used client-side like this:
index.html
<script src="/dist/bundle.js"></script>
Using Vue as a module in the browser
If an ES module build is provided, can't we use it in the browser?
If you try to use the Vue 2.5 ES module build in the browser, i.e.
index.html
<script type="module" src="vue.esm.js"></script>
It won't work. Sure, it'll load, but you'll be met with a console error like this:
Uncaught ReferenceError: process is not defined
That's because the ES module build in version <= 2.5.x was only intended to be used by a bundler.
But isn't ES Modules a standard? Why would it work on the server and not in the browser?
Yes, but the build included references to Node.js globals like process
, as these would help optimize the bundled version of Vue, and would be stripped out in the bundling process. This hasn't been considered a problem until now, because nobody was using a ES modules in the browser!
But as of Vue 2.6, there's now another build available in the Vue package specifically for the browser, vue.esm.browser.js.
How to use the browser module build
This new ES module build of Vue can be loaded into the browser without a bundler. Here's an example:
index.html
<!DOCTYPE html>
<html>
<head>
<title>Vue.js ESM</title>
</head>
<body>
<div id="app">
{{ message }}
</div>
<script type="module" src="vue.mjs"></script>
<script type="module" src="app.mjs"></script>
</body>
</html>
app.mjs
import Vue from './vue.mjs';
new Vue({
el: '#app',
data: {
message: 'Hello Vue 2.6.0-beta1 ESM Browser Build!'
}
});
To make this work, you'll need to be statically serving app.mjs and vue.mjs. The latter file would be aliased to the Vue ES browser build i.e. node_modules/vue/dist/vue.esm.browser.js.
Using the .mjs extension is not required but this Google Developers article recommends it to distinguish JavaScript modules from classic non-module scripts.
Fallback
If you're going to use the ES module build, you'll probabaly need to supply a fallback, as only modern browsers support ES modules.
Following the above example, you can set up a very simple Webpack config to bundle this code in parallel:
webpack.config.js
module.exports = {
entry: './app.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
// add Babel here if needed
},
resolve: {
alias: {
'./vue.js': './node_modules/vue/dist/vue.esm.browser.js'
}
}
};
The bundle can now be loaded in the page using the nomodule
attribute. Browsers that support modules will know not to load the nomodule
script, while browsers that don't recognize modules will skip the first two scripts and only load the fallback.
index.html
<script type="module" src="vue.mjs"></script>
<script type="module" src="app.js"></script>
<script nomodule src="/dist/build.js"></script> <!--Fallback-->
I've written about this more extensively in an article from 2017 called Vue.js Single-File JavaScript Components In The Browser.
What benefit is there of using Vue as an ES module?
After coming all this way through the article, you may be disappointed at the answer: not much.
The reason is that browser module loading and parsing is currently less efficient than code-split classic scripts. And since you're probably still going to have to use a bundler for linting, TypeScript transpiling, tree shaking etc, it's not like this will simplify your setup much either.
While I'm not a WordPress plugin developer, I've been told that a browser module build can be useful there, since a single WordPress site may have multiple plugins with conflicting Vue versions. A classic script will pollute the global namespace and potentially cause problems, where a module will not.
But if you don't have that use case, why bother with browser modules?
Here are a few (admittedly abstract) reasons:
It's not good to have the world of JavaScript development resting entirely on Webpack and Rollup, as it's a significant bottleneck and vulnerable point of failure.
Having a native, standardized solution will simplify web development over time. We can deprecate all the competing module systems like CommonJS. Perhaps even Webpack and Rollup will go quietly into the night as a standardized bundling solution is being proposed now that modules have been standardized.
Browser implementation of ES modules is a platform to build upon. Right now, bundling as a classic script is better, but that doesn't mean it always will be. Check out What's next for JS modules for some specifics of what's planned by Google.
If you know of any other use cases for browser modules, leave a comment!
About Anthony Gore
If you enjoyed this article, show your support by buying me a coffee. You might also enjoy taking one of my online courses!
Click to load comments...