In this article I describe how to use web workers with VueJS.
A starter project is available on my github repository
Web workers allow us to run scripts in background threads.
Maybe you are like me and you thought "why not use async
and await
instead ?".
You can't because javascript is single threaded, even when using async
and await
.
This means that even a heavy load asynchronous task will hog all the resources and prevent the browser from rendering properly, leading to the dreaded "A script on this page may be busy ...".
There are some limitations to using web workers :
Let's start by creating a Vue project with vue-cli.
Install it with npm install -g @vue/cli
if you haven't already.
We create our project with:
vue create starter-worker-vue
We choose the default presets and wait for the project to be created. Then we can move in the newly created directory cd starter-worker-vue
. We can run npm run serve
to verify that the project creation was successful.
To use web workers with ease, we will use the Google Chrome Labs worker plugin. Install it with:
npm install -D worker-plugin
The webpack configuration has to be updated to use the plugin. To do so we create a file vue.config.js
at the root of our project with the following content:
const WorkerPlugin = require('worker-plugin')
module.exports = {
configureWebpack: {
output: {
globalObject: "this"
},
plugins: [
new WorkerPlugin()
]
}
};
Our plugin is configured let's try using it.
We create a folder named long-sleep-worker
in src
with two files: index.js
and longSleepWorker.js
.
The longSleepWorker.js
file contains the worker logic. In our case, we will just sleep for a given time and then reverse an array given as a parameter:
// Demo how to import in the worker
import _ from 'lodash'
// Function to simulate a long work
// and demo the use of async / await in a worker
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
addEventListener("message", async event => {
let sleepTime = event.data.message.sleep
let arrayToReverse = event.data.message.array
// Sleep
await sleep(sleepTime);
// Reverse
let reversedArray = _.reverse(arrayToReverse)
// Send the reversed array
postMessage(reversedArray)
});
I added an import to Lodash in the worker code to show you that it is possible to use imports. Install lodash in your project with npm install --save lodash
.
The index.js
file declares our module. Here we export the function send
and the worker itsef.
const worker = new Worker('./longSleepWorker.js', { type: 'module' });
const send = message => worker.postMessage({
message
})
export default {
worker,
send
}
The function send
will be useful to start the worker and pass some parameters.
The worker
will be used to set up a listener to detect when the worker has finished its work. In our case we know that the worker has finished when it sends us the reversed array thanks to the function postMessage
.
Our worker code is written. Let's integrate it to our Vue application.
We create a new component src/components/WorkerDemo.vue
to use our web worker and we add it to our application in src/App.vue
.
In the script
we import WorkerDemo
:
import HelloWorld from './components/HelloWorld.vue'
import WorkerDemo from './components/WorkerDemo.vue'
export default {
name: 'App',
components: {
HelloWorld,
WorkerDemo
}
}
In the template
we replace the HelloWorld by our new component:
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<WorkerDemo />
</div>
To use our web worker we need to import the module long-sleep-worker
we created earlier. We can then start the worker by sending it a payload:
import longSleepWorker from '@/long-sleep-worker'
...
longSleepWorker.send({sleep: 500, array: [1,2,3]})
To detect when the worker has finished its work we will need to set up a listener with worker.onmessage
:
longSleepWorker.worker.onmessage = event => {
let message = event.data
if (message.status="finished") {
// do something
} else {
// maybe update the progress bar ...
}
}
We set up the listener in the mounted()
lifecycle hook. This listener could also be used to update a progress bar !
In the end our WorkerDemo.vue
file looks like this:
<template>
<div >
<div>Sleep time (ms)</div>
<input v-model="sleepTime" />
<div>Array to reverse</div>
<input v-model="arrayToReverse" />
<div>
<button @click="startWorker">Start worker</button>
</div>
<div>Worker message</div>
<div>{{workerMessage}}</div>
</div>
</template>
<script>
import longSleepWorker from '@/long-sleep-worker';
export default {
data() {
return {
workerMessage: "No message yet",
sleepTime: 1000,
arrayToReverse: "1,2,3,4"
}
},
mounted() {
longSleepWorker.worker.onmessage = event => {
this.workerMessage = event.data
}
},
methods: {
startWorker() {
longSleepWorker.send({
sleep: this.sleepTime,
array: this.arrayToReverse.split(',')
})
}
}
}
</script>
Our code is now complete. We can start the server with npm run serve
and click on the button Start worker
to verify that our code was executed.
A repository with the starter code is available here.