What I Learned About VueJS From Building A Chrome Extension
Anthony Gore | May 8th, 2017 | 3 min read
I wanted to experiment with building a Google Chrome extension with Vue.js so I decided to take the Vue TodoMVC and try to make it accessible from my browser toolbar:
Building a browser extension with Vue is a very similar process to building a regular web page with Vue, but there are a few key differences which I'll discuss in this article.
No templates
One thing we love about Vue is the ability to use templates either in a file:
<body>
<div id="app">
<div>{{ message }}</div>
</div>
</body>
Or in a string:
new Vue({
template: `<div>{{ message }}</div>`
});
One small problem: you can't use templates like this in Chrome extensions!
But before you give up and go back to jQuery, it's worth understanding why this limitation exists and how Vue can help you work around it.
Browser extensions are like web pages only different
Browser extensions use HTML, CSS and JavaScript just like regular web pages. But there are APIs that extensions can access that web pages can't, allowing them to extend the features of bookmarks, dev tools and other browser aspects.
This additional access makes users more vulnerable to security holes though, so extensions require a Content Security Policy to make them more secure by disabling potentially unsafe features.
Content Security Policy (CSP)
No one likes to read about policies, so I'll keep this brief: among other things, the CSP imposes restrictions on the kind of code that your extension can include:
- Inline scripts are disabled e.g.
<button onclick="...">
- Content must be loaded locally i.e. no scripts via CDN
eval
functions are disabled e.g.eval("alert(('hi')")
It's this last restriction on eval
function that affects us Vue users.
Note:
eval
is considered unsafe as it can be used to run arbitrary code and make your application vulnerable to cross-scripting attacks.
How Vue templates are compiled
At runtime, Vue's internal template compiler will parse the document or template string and generate a JavaScript represention of the template.
Vue's efficiency gains are partly due to its ability to make manipulations in JavaScript before making them directly to the page.
Unfortunately the template compiler relies on eval
functions to perform this compilation task, and these are not allowed under the CSP.
Solution 1: Allow "unsafe-eval" (not recommended)
You can actually override the eval
restriction in your extension's CSP, and that would solve the problem. However, it is not recommended as it now makes your extension vulnerable to cross-script attacks.
Solution 2: Don't compile templates at runtime
We can actually build a Vue app just fine without the runtime template compiler (FYI the compiler is an internal library called vue-template-compiler
that can be used standalone).
If you've used Vue as an ES6 module then you may have already been doing this but perhaps didn't realise that's what you were doing!
As discussed, Vue's template compiler is used whenever you use a template
string e.g.
new Vue({
template: `<div>{{ message }}</div>`
});
Or, when you mount to a template using el
:
new Vue({
el: '#app'
});
<body>
<div id="app">
<div>{{ message }}</div>
</div>
</body>
In both those scenarios Vue must parse the string <div>{{ message }}</div>
and that's where an eval
function is used.
Render functions
Render functions are JavaScript functions that can be used to generate a template. If you use a render function to create your template, the template compiler is not needed:
new Vue({
render (createElement) {
return createElement('div', this.message)
}
}).$mount('#app');
<body>
<div id="app"></div>
</body>
Note: using an empty node to mount to does not invoke the template compiler.
But...render functions are hard to use
It's true, render functions are not an intuitive way to create templates.
But don't worry, you won't have to manually write your render functions. Instead, you can use the template compiler in development to pre-compile your templates into render functions.
Obviously the CSP doesn't mind if you compile a template, it's doing it at runtime with eval
that's the issue.
Note: you can use JSX to make your render functions if you're that way inclined.
Single file components to the rescue
There's yet another reason why single file components (SFCs) are awesome: they're pre-compiled and therefore CSP compliant.
When you use vue-loader
to process your .vue
file, one of the things it does is use vue-template-compiler
to turn your component's template into a render function.
So if you have a SFC with this template:
<template>
<div id="app">{{ message }}</div>
</template>
After you build, look in your Webpack bundle and you'll see something like this:
render: function () {
return this.$createElement("div", {attrs: {id: "app"}}, [this.message])
}
If your whole app is comprised of single file components Vue will not need to do any runtime template compilation.
Runtime-only build
You may have noticed in the Vue docs something about a "full build" and a "runtime-only" build. If you're like me you probably skipped that part!
The runtime-only build is the same as the full build only without vue-template-compiler
. If your app templates have been pre-compiled, you should use this runtime-only build. Not only is it CSP compliant, but it's also 30% lighter!
In an ES5 setup you can load the runtime-only Vue library like this:
<script src="vue.runtime.min.js"></script>
But more likely you'll be using an ES6 setup with Webpack and you'll want this:
import Vue from 'vue'
to refer to the runtime build, not the full build. Fortunately it does this by default!
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...