What I Learned About VueJS From Building A Chrome Extension

What I Learned About VueJS From Building A Chrome Extension

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!

Anthony Gore's Picture

About Anthony Gore

Anthony is a web developer and online course instructor with a passion for Javascript. He currently resides in Chiang Mai, Thailand.

Comments