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.
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.
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.
While extremely useful, modules systems and bundlers have become another complex area of web development.
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.
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.
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>
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.
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.
Uncaught ReferenceError: process is not defined## 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-->
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:
If you know of any other use cases for browser modules, leave a comment!
#vue-js #javascript