Prerequisites

$ npm -v

3.5.2

$ node -v

v8.10.0

If you have followed Part 1, you should now have an extension project that looks something like this:

The core of our extension is made by a single html file app.html and script in app.js that gives our extension some basic functionality.

That means we can use all the tools we are used to when developing a regular website.

If you are not familiar with Webpack or Laravel Mix, don’t worry, just follow the steps and I believe you’ll catch up once we will get to the use of Vue itself.

Now let’s initialize package.json and pull in Laravel Mix:

npm init -y

npm i --save-dev laravel-mix

Laravel Mix comes with a default configuration file so let’s copy that to our root directory.

cp node_modules/laravel-mix/setup/webpack.mix.js .

Our extension now looks like this:

If you check Laravel Mix’s installation guide, there are scripts recommended for package.json so let's add these too:

{

...

"main": "app.js",

"scripts": {

"dev": "NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",

"watch": "NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",

"hot": "NODE_ENV=development webpack-dev-server --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",

"production": "NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"

},

"keywords": [],

...

}

Let’s do some quick cleaning and move the app.js into the assets/js folder. We need to properly adjust out path to app.js in app.html as well but we'll get to that later.

If you open the webpack.mix.js file you will see that it contains the complete API available from Laravel Mix. We will need only one method call and that is the following:

let mix = require('laravel-mix');

mix.js('assets/js/app.js', 'dist/');

Now if you run

npm run dev

You should see your build passing and a dist folder should appear in your root folder:

The dist version of app.js is the one we will link in app.html .

Note: dist is short for distribution by the way. Laravel Mix takes care of minifying our JS code, as well as performing other tasks that may be required, such as transpiling down from ES6/7/8 to ES5, so that older browsers can handle the code. Let us know if what we have just said sounds a little confusing, and maybe we’ll write an article to explain it better 🤓

Let's do that now and while in that file, let's clear that file a bit too:

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

</head>

<body>

<div id="my-app"></div>

<script src="dist/app.js"></script>

</body>

</html>

As you can see I removed the button and the list from the Part 1. The only thing we need right now is a wrapper for our Vue instance to be mounted on, which in this case is the div with an id of my-app . Also pay attention to the path to app.js which now correctly points to built version in the dist directory.

Vueify!

First, pull in Vue as a dependency:

npm i --save-dev vue

Now we have everything ready to create a Vue instance and mount it. Since the app.js is an entry point to our app, let's do it there. We can completely wipe out the previous code in there, since it's not that important right now.

Our assets/js/app.js will now look something like this

import Vue from 'vue';

import App from './components/app.vue' new Vue({

el: '#my-app',

components: {

App

},

render(h) {

return h('app');

}

});

We need to take use of Vue render function because Firefox won’t allow Vue to inject rendering scripts. So let’s create that root app.vue component:

In assets/js/components/app.vue :

<template>

<div>

Hello firefox extension. I am Vue!

</div>

</template>

<script>

export default { }

</script>

Your project structure should look like this:

Don’t forget to run npm run dev so the changes apply to your Javascript. Or run npm run watch and your assets will get compiled automatically every time you make a change.

Test it out and you should see very similar result as in the first part of this article.

Now You’ve successfully created your second Firefox extension. This time using Vue. Great job! 🎉

Almost there!

Actually, this could be the end of this article, because if you know Vue, you can do pretty much anything you want right now.

But I am gonna show you the functionality from Part 1 in Vue anyway. To refresh our memory, we were accessing the browsers information about opened tabs in the current window and appending their titles into an unordered list.

It’s not that complicated, so let’s do that.

All our work will be pretty much focused in app.vue right now, because there's no need modifying app.js .

First, let’s prepare the HTML:

<template>

<div>

<ul class="tab-list">

<li class="tab-item">This is</li>

<li class="tab-item">where the tabs</li>

<li class="tab-item">will come</li>

</ul>

<button @click="getTabs()" class="button">Get tabs!</button>

</div>

</template>

<script>

...

</script>

<style lang="scss">

...

</style>

This would work, but I am gonna add little bit of styling so it does not look that default.

<template>

...

</template>

<script>

...

</script>

<style lang="scss">

* { box-sizing: border-box; }

body { min-width: 300px; font-family: sans-serif; padding: 1em; } .tab-list {

list-style: none;

margin: 0 0 1em 0;

padding: 0;

} .tab-item {

padding: .6em;

border-bottom: 1px dashed #eee;

} button {

border-radius: 2px;

background: dodgerblue;

color: white;

font-weight: bold;

text-align: center;

padding: .6em .8em;

border: 0;

box-shadow: 0 1px 2px gray;

}

</style>

So now it looks like this:

This is great, but now the list items are hardcoded. Let’s fix that by creating a data property with an array of tabs and list them in template.

<template>

<div>

<ul class="tab-list">

<li class="tab-item" v-for="tab in tabs">{{ tab }}</li>

</ul>

<button @click="getTabs()" class="button">Get tabs!</button>

</div>

</template>

<script>

export default {

data() {

return {

tabs: []

}

}

}

</script>

<style lang="scss">

...

</style>

Great and now we can finally create the getTabs method, that will fetch the tabs from the browser and display them:

<template>

...

</template>

<script>

export default {

data() {

return {

tabs: []

}

},

methods: {

async getTabs() {

try {

// Await the tabs (since query method returns a promise)

const fetchedTabs = await browser.tabs.query({ currentWindow: true });

// Clear current tabs

this.tabs = [];

// Push new tabs

fetchedTabs.forEach(tab => {

this.tabs.push(tab.title);

})

} catch (e) { console.log(e); }

}

}

}

</script>

<style lang="scss">

...

</style>

The getTabs method is pretty much the same as in first part of this article but this time I decided to handle the promise that query method returns with an async/await approach.

This is pretty much it. Now you have a fully functional Firefox extension with Vue!

But what if we want to publish our extension to the world? Well, let’s take a look at that too!

How to publish your Firefox extension

First you need to create a single .zip archive from your project. Do not include the node_modules directory, which contains only dependencies needed during development and is pretty big. You also do not need to include the assets/js folder, because all the necessary scripts have been compiled into the dist folder.

Using terminal I would create a zip archive like this:

zip -r my-extension.zip app.html assets/img dist manifest.json

This would create a .zip archive called my-extension.zip

Go to addons page for developers on mozilla.org a hit the “Submit a New Add-on” button.

Here you can choose if you want to distribute your extension using Firefox Add-ons Manager or you choose to distribute it manually.