Ibiyemi Adewakun Ibiyemi is a full-stack developer from Lagos. When she's not writing code, she likes to read, listen to music, and put cute outfits together.

Using vue-loader to create single-page applications

6 min read 1683

vue-loader Tutorial

Single-page applications are all the rave now. These applications react to users dynamically by changing page content as users interact rather than loading new pages with new data. They have grown in popularity because of their speed, performance, and cross-platform functionality.

Vue.js is one of several JavaScript frameworks that can build single-page applications. In this tutorial, we will explore setting up a Vue.js single-page application using vue-loader.

What is vue-loader, you ask? It’s a webpack loader that supports defining Vue.js components in single files known as single-file components (SFCs). These files have the extension .vue and the vue-loader transpiles them into JavaScript so the browser can understand.

To understand how to use vue-loader, we’ll build a simple to-do list app.

Tutorial prerequisites

To follow along you’ll need to install the following:

Setting up our new Vue.js project

To start our project, create a project directory by running the following:

~$ mkdir todo-list

Next, go into the project directory to initialize npm using npm init and follow the prompts. This creates our package.json file, which stores project metadata and keeps track of our project’s dependencies.

Now that we have our package.json, we must install our dependencies. We’ll start with our main dependency, Vue, by running the following:

# ~/todo-list

$ yarn add vue

Then, we must run our development dependencies:

# ~/todo-list

$ yarn add -D vue-loader babel-loader @babel/core @babel/preset-env css-loader vue-style-loader vue-template-compiler webpack webpack-cli webpack-dev-server

Note that we added the -D flag to our yarn add command to indicate that these libraries should be installed as development dependencies. We only need them for development to transpile our code into a single JavaScript bundle.

We made a custom demo for .
No really. Click here to check it out.

Now, let’s take a moment to understand our dependencies and their importance in our project.

  • vue-loader and vue-template-compiler transpile Vue files into JavaScript
  • babel-loader converts ES6 code to browser-friendly ES5 code
    • Uses @babel/preset-env and @babel/core polyfills that extend browser Javascript features
  • css-loader interprets and resolves @import and url() CSS imports
  • vue-style-loader injects CSS into SFCs as the style tags
  • webpack transforms and bundles JavaScript files to use in browsers
  • webpack-cli is the CLI that runs commands for webpack projects
  • webpack-dev-server provides a basic web server and the ability to use live reloading

Now that we have our dependencies installed, we can configure our webpack project in the webpack.config.js configuration file:

# ~/todo-list
$ touch webpack.config.js

In our webpack.config.js we must set all our installed plugins and configure them to build our Vue project. The configuration is how we set what files we want our plugins to work on and how we want our final JavaScript build to be outputted:

# ~/todo-list/webpack.config.js

const path = require("path");
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
    mode: 'development',
    entry: './src/main.js',
    module: {
      rules: [
          test: /\.vue$/,
          loader: 'vue-loader'
        // this will apply to both plain `.js` files
        // AND `<script>` blocks in `.vue` files
          test: /\.js$/,
          exclude: /node_modules/,
          loader: 'babel-loader',
          options: {
            presets: [
              ['@babel/preset-env', { targets: "defaults" }]
        // this will apply to both plain `.css` files
        // AND `<style>` blocks in `.vue` files
          test: /\.css$/,
          use: [
    // Where to compile the bundle
    // By default the output directory is `dist`
    output: {
        path: path.join(__dirname, "dist"),
        filename: "bundle.js",
        publicPath: '/dist/'
    devServer: {
        static: {
            directory: path.join(__dirname, 'public'),
            watch: true,
        port: 3000,
        compress: true,
    plugins: [
        // make sure to include the plugin for the magic
        new VueLoaderPlugin()

In the webpack.config.js file, we referenced a dist directory for our bundled JavaScript and a public directory as our project entry point. We’ll create these directories as we go along in the article.

Creating our directories and Vue.js files

At this point, we have our project dependencies installed so we can set up our directories.

We must create three directories, src, dist, and public, to hold our Vue components’ code, built JavaScript files, and project entry (index.html), respectively:

$ mkdir src
$ mkdir dist
$ mkdir public

In our src directory, create main.js as our project’s starting point. In it, initialize Vue and register our parent component:

// main.js

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),

Our parent component is the top-level component, App.vue. Through this, other components can be added to the page based on our user’s interactions. Next, create our entry HTML page in the public directory, which loads our Vue application:

// public/index.html

    <title>Iby's Todo App</title>
    <div id="app"></div>
    <script src="../dist/bundle.js" type="text/javascript"></script>

By placing our index.html in the public directory, we can serve our app from it without exposing our Vue code.

With our app skeleton in place, we can add some build commands to our package.json to run and view the application in a browser. In the scripts section of package.json, add the following:

// package.json

"scripts": {
    "build": "webpack --mode production",
    "start": "webpack-dev-server --mode development"

Now we can run our app using yarn run start and visit it on a browser using http://localhost:3000/.

Note that our start script runs our app in development mode, which provides hot reload, our application’s ability to reload when we edit our code without needing to restart it.

Adding the to-do list functionality

We now have a skeleton Vue.js single-page application that we can visit in our browser. But, what’s the fun in stopping there? Instead, let’s turn this skeleton into a simple to-do list application.

Creating SFCs

A to-do list is simply a list of text that often contains checkboxes to indicate which items have been completed. Since our to-do list will have several text and checkbox items, we will create a component for each item.

Let’s create a components directory in src and create our TodoItem component:

// src/components/TodoItem.vue

    <li class="list-group-item">
        <div class="d-flex form-check pt-2">
            <input class="form-check-input" type="checkbox" v-model="isChecked" />
            <p class="px-3">{{text}}</p>

            <!-- Button to delete item -->
            <span class="btn align-right" @click="$emit('removeTodo', index)">
                <i class="fa fa-trash"></i>
export default {
    name: 'TodoItem',
    props: {
        index: Number,
        text: String,
        checked: Boolean, 
    data: () => {
        return {
            isChecked: false
    watch: {
        isChecked(value) {
            this.$emit('updateTodo', {value, index: this.index})
    mounted() {
        this.isChecked = this.checked
    updated() {
        this.isChecked = this.checked

Here, our TodoItem SFC has the functionality to display, check as done or delete the to-do content.

Remember that when using vue-loader, we can create a single file to house our component’s functionality, and through our plugins, we can build our component into simple JavaScript that browsers can understand.

Connecting components

Next, let’s add the TodoItem component to our page through our parent component, App.vue:

// src/App.vue

    <div class="container pt-5 text-center">
        <div class="row">
            <div class="col-md-8">
                <p class="d-inline-block">Today's Todos!</p>
                <!-- Button to add new todo -->
                <button class="btn btn-sm btn-primary mx-5" @click="addTodo">Add to list</button>
        <div class="row">
            <div class="col-md-8">
                <div class="justify-content-md-center pt-5">
                    <ul class="list-group">
                        <todo-item v-for="(todo, index) in todos" 
                            :key="index" :index="index" :checked="todo.checked" 
                            :text="todo.text" @removeTodo="removeTodo" @updateTodo="updateTodo"/>

        <!-- Using our modal component -->
        <modal name="new-todo-modal">
            <div class="container">
                <div class="row mt-5">
                    <div class="col-md-12">
                        <form @submit.prevent="" autocomplete="off">
                            <div class="mb-3">
                                <label for="newTodoText" class="form-label">Type your todo</label>
                                <input type="text" class="form-control" id="newTodoText" v-model="newTodo">
                            <button @click="saveTodo" class="btn btn-primary">Save</button>
import TodoItem from './components/TodoItem.vue'
export default {
    name: 'App',
    components: { TodoItem },
    data: () => {
        return {
            todos: [{text: 'Buy an apple', checked: false}, {text: 'Go to the bank', checked: true}],
            newTodo: '',
    methods: {
        removeTodo(index) {
            this.todos.splice(index, 1)
        updateTodo({index, value}) {
            const updatedTodo = this.todos[index]
            updatedTodo.checked = value
            this.todos.splice(index, 1, updatedTodo)
        addTodo() {
        saveTodo() {
            this.todos.push({ text: this.newTodo, checked: false })
            this.newTodo = ''

To implement our modal for typing in new to-do items, we will install vue-js-modal, which gives us the VModal component. Then, we must register VModal as a global component in our main.js by adding the following:

import Vue from 'vue'
import VModal from 'vue-js-modal'


This gives us a VModal global component that we use to enter new to-do items.

And when we run our app, we’ll see our fully functional to-do checklist that we built using vue-loader!

Final To-Do Checklist, Showing Adding A To-Do Item, Checking One As Complete, And Deleting One


In this article, we have walked through setting up a Vue app from scratch with webpack and registering our loaders. This gives us a clearer understanding of how Vue.js apps are transpiled to JavaScript and served to users on browsers via vue-loader.

You can find the entire code from this article here.

I hope you found this article useful. Please share any thoughts or questions you might have in the comment section!

Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - .

Ibiyemi Adewakun Ibiyemi is a full-stack developer from Lagos. When she's not writing code, she likes to read, listen to music, and put cute outfits together.

Testing accessibility with Storybook

One big challenge when building a component library is prioritizing accessibility. Accessibility is usually seen as one of those “nice-to-have” features, and unfortunately, we’re...
Laura Carballo
4 min read

Leave a Reply