DEV Community

Cover image for How to setup a vue project with webpack.
Temi Kara
Temi Kara

Posted on • Updated on

How to setup a vue project with webpack.

This post gives a step by step guide to setting up vue.js using webpack. You’ll need to have node installed on your computer, you’ll also need a basic knowledge of how vue works, and of course a code editor.

Creating a folder and a package json file
Installation of dependencies
File/Folder structure
Configure webpack to use babel loader, and vue loader
Write scripts to start your server
Loaders, Plugins, and Code Splitting
Final webpack configuration and Observation

Creating a folder and a package json file

In your terminal, use the mkdir command to create a project folder and use the cd command to change directory into the folder created.
Alt Text
In the file you created, run the command npm init –y to create a package.json file

Installation of dependencies

Now that we have a package.json file to keep track of our dependencies, we can go ahead to install them.

  • Dependencies: first we install vue, vue-router and core-js as dependencies. Run npm install vue vue-router core-js --save this would install the three packages as dependencies.
  • Dev-dependencies: Now we install webpack, webpack-cli, webpack-dev-server, babel-loader, @babel/core, @babel/preset-env, vue-loader, vue-template-compiler. Run npm install webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env vue-loader vue-template-compiler -D this would install all these packages as dev-dependencies. Our package.json file should look like this after the installation process
{
  "name": "vue-webpack-setup",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "core-js": "^3.6.5",
    "vue": "^2.6.12",
    "vue-router": "^3.4.3"
  },
  "devDependencies": {
    "@babel/core": "^7.11.6",
    "@babel/preset-env": "^7.11.5",
    "babel-loader": "^8.1.0",
    "vue-loader": "^15.9.3",
    "vue-template-compiler": "^2.6.12",
    "webpack": "^4.44.1",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0"
  },
}
Enter fullscreen mode Exit fullscreen mode

File/Folder structure

Our folder structure would be similar to the default folder structure we get when using the vue cli to create a project. So lets create a public folder and an src folder in the root of our project. In the public folder, add a favicon.ico file and create an index.html file. In the index.html file, add this boilerplate

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Vue app</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

In our src folder lets create an App.vue file and a main.js file.
N.B: App.vue file starts with a capital letter.
In our App.vue file, add this code

<template>
  <div id="app">
    <div class="nav">
      <router-link to="/">Home</router-link>|<router-link to="/about"
        >About</router-link
      >
    </div>
    <router-view />
  </div>
</template>

<style lang="scss">
// @import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap");

// :root {
//   --font: Roboto, sans-serif;
//   --textColor: #374961;
//   --linkActiveColor: #41b783;
// }

// #app {
//   font-family: var(--font);
//   -webkit-font-smoothing: antialiased;
//   -moz-osx-font-smoothing: grayscale;
//   text-align: center;
//   color: var(--textColor);

//   .logo {
//     width: 20%;
//   }
// }

// .nav {
//   padding: 30px 0 100px 0;

//   a {
//     font-weight: 500;
//     color: var(--textColor);
//     margin: 0 5px;
//   }

//   a.router-link-exact-active {
//     color: var(--linkActiveColor);
//   }
// }
</style>

Enter fullscreen mode Exit fullscreen mode

the scss style is commented out because we don't have a loader to process .scss files yet.

In our main.js file, add this code

import Vue from "vue";
import App from "./App.vue";
import router from "./router";

new Vue({
  router,
  render: (h) => h(App),
}).$mount("#app");
Enter fullscreen mode Exit fullscreen mode

Now we create three folders in our src folder namely, assets, router, views. In the assets folder, lets add an image and call it logo.png. In the router folder, create an index.js file and add this code in the index.js file

import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home,
  },
  {
    path: "/about",
    name: "About",
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/About.vue"),
  },
];

const router = new VueRouter({
  mode: "history",
  routes,
});

export default router;
Enter fullscreen mode Exit fullscreen mode

notice how we imported the about component in the router, this kind of import tells webpack to lazyload the about component.

In our views folder, lets create a file called Home.vue.
N.B: File names should start with capital letter.
Now, let's add this code in our Home.vue file

<template>
  <div id="home">
    <!-- <img class="logo" src="../assets/logo.png" alt="logo" /> -->

    <h1>👋Hello world🌎</h1>
  </div>
</template>

Enter fullscreen mode Exit fullscreen mode

the image is commented out because we don't have a loader to process such file yet.

then add this to our About.vue file

<template>
  <div>
    <h1>This is the about page</h1>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

If done correctly, we should have a folder structure that looks like this
Alt Text

Configure webpack to use babel loader and vue loader

Babel loader helps transpile ECMAScript 2015+ code into JavaScript that can be run by older JavaScript engines. While vue loader helps transform vue components into plain JavaScript module.

To configure webpack to use these loaders, we need to create two files namely, babel.config.js, and webpack.config.js.
In the babel.config.js file lets add this code

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        useBuiltIns: "usage",
        corejs: 3,
      },
    ],
  ],
};
Enter fullscreen mode Exit fullscreen mode

@babel/preset-env helps to detect browsers we want to support so babel loader knows how to go about transpiling our JavaScript code. we would need to add a browserslist option in our package.json file so babel knows what browsers we want to support. So in our package.json file, let's add

  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
Enter fullscreen mode Exit fullscreen mode

Ideally you would want to compile as little code as possible, so support only relevant browsers. The useBuiltIns and corejs options are for polyfill imports, you can read more about it here.
In our webpack.config.js file lets add this code

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

module.exports = {
  entry: {
    main: "./src/main.js",
  },
  output: {
    path: path.resolve(__dirname, "dist"),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        },
      },
      {
        test: /\.vue$/,
        loader: "vue-loader",
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
  ],
  resolve: {
    alias: {
      vue$: "vue/dist/vue.runtime.esm.js",
    },
    extensions: ["*", ".js", ".vue", ".json"],
  },
};
Enter fullscreen mode Exit fullscreen mode

From the code above we import VueLoaderPlugin from vue-loader and the path module which we will be using to configure our entry and output point so webpack will know where to start compiling from and where to put our compiled code after compiling. You can read more about VueLoaderPlugin here.

In the code above we see a module option where we define some rules, the first rule tells webpack to use babel loader to transpile all files having a .js extension excluding everything in the node_modules folder, while the second rule tells webpack to apply the vue loader to any file with a .vue extension.

The resolve option in the code above has an alias and extension key value pair, alias has a value which defines a vue alias and helps us import vue packages without using relative path, while extension has a value which tells webpack how to resolve imports and enables us import files without the extension, you can read more about it here.

Write scripts to start your server

To see our setup work, we’ll need to write scripts in our package.json file to run the webpack-dev-server. So go into the package.json file and add these to the scripts object.

"build": "webpack --mode production",
"start": "webpack-dev-server --mode development"
Enter fullscreen mode Exit fullscreen mode

Now we can go back to our terminal and run npm run start to start up webpack development server, our project should compile successfully else you can go through the steps again or drop a comment, I’ll be glad to help you out.

N.B: We won't be able to view our project in the browser yet because we haven't configured the htmlWebpackPlugin and webpack doesn't know where to insert our bundle files.

Loaders, Plugins, and Code Splitting

Loaders and plugins are third-party extensions used in handling files with various extensions. Just like we used vue-loader to handling files with .vue extension, we have loaders and plugins for .scss files, .html files, images, etc.

Basically when you import/require files or modules, webpack tests the path against all loaders and passes the file to whichever loader passes the test. you can read more about loaders here

In this post, we will make use of file-loader, sass-loader, css-loader, style-loader, CleanWebpackPlugin, MiniCssExtractPlugin, htmlWebpackPlugin, autoprefixer. To install these loaders and plugin, we run npm install file-loader sass sass-loader css-loader style-loader postcss postcss-loader autoprefixer clean-webpack-plugin html-webpack-plugin mini-css-extract-plugin -D

  • file-loader: The file-loader is used to process files like images, videos, fonts. To use file-loader, insert this code into webpack.config.js file
      {
        test: /\.(eot|ttf|woff|woff2)(\?\S*)?$/,
        loader: "file-loader",
        options: {
          name: "[name][contenthash:8].[ext]",
        },
      },
      {
        test: /\.(png|jpe?g|gif|webm|mp4|svg)$/,
        loader: "file-loader",
        options: {
          outputPath: "assets",
          esModule: false,
        },
      },
Enter fullscreen mode Exit fullscreen mode
  • working with .css and .scss files: For webpack to correctly process .css and .scss files, the order in which you arrange loaders is important. first we need to import MiniCssExtractPlugin and autoprefixer in our webpack.config.js file like this

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const autoprefixer = require("autoprefixer");

then we add this code to the module of our webpack.config.js file

      {
        test: /\.s?css$/,
        use: [
          "style-loader",
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              plugins: () => [autoprefixer()],
            },
          },
          "sass-loader",
        ],
      },
Enter fullscreen mode Exit fullscreen mode

we also need to enable the MiniCssExtractPlugin in the plugin section of our webpack.config.js file like this.

    new MiniCssExtractPlugin(),
Enter fullscreen mode Exit fullscreen mode

sass-loader: The sass-loader is first used to process all .scss files and compiles them to .css files.

postcss-loader: After sass-loader is done converting .scss files to .css files, postcss-loader and autoprefixer is then used to process the .css files and add vendor prefix to the css postcss.

css-loader: The css-loader then helps to return the css in .css files that are imported or required in the project.

style-loader: style-loader takes the css returned by css-loader and inserts it into the page.

MiniCssExtractPlugin: The MiniCssExtractPlugin helps create a separate css file from .css file imports, it is helpful for code splitting.

  • htmlWebpackPlugin: this plugin helps to automatically generate an index.html file and inserts our JavaScript bundle into the html body. To use the htmlWebpackPlugin, we first import it into our webpack.config.js file like this

const htmlWebpackPlugin = require("html-webpack-plugin");

then enable the plugin in the plugin section by adding this code

    new htmlWebpackPlugin({
      template: path.resolve(__dirname, "public", "index.html"),
      favicon: "./public/favicon.ico",
    }),
Enter fullscreen mode Exit fullscreen mode
  • CleanWebpackPlugin: This plugin helps to erase outdated bundle files so it can be replace with the recent file while building. To use this plugin we first import it into our webpack.config.js file like this

const { CleanWebpackPlugin } = require("clean-webpack-plugin");

then enable the plugin in the plugin section by adding this code

new CleanWebpackPlugin(),
Enter fullscreen mode Exit fullscreen mode

Now we can uncomment the image tag in the Home.vue file and also the scss style in the App.vue file, start our development server and view our project in the browser.
Alt Text
The image above shows our bundle size when we build our project, presently our bundles does not have a random hash which helps with browser cache and we also need to split our code further by creating a vendor chunk and a runtime chunk.

To hash our css bundles, we pass an object

{
filename: "[name].[contenthash:8].css",
chunkFilename: "[name].[contenthash:8].css",
}

as params to the MiniCssExtractPlugin.

To hash our files we need to add

name: "[name][contenthash:8].[ext]",

to the options object of our file loader.

To hash our bundles we need to add

filename: "[name].[contenthash:8].js",

chunkFilename: "[name].[contenthash:8].js",

in our webpack output section.

If we build our project now, our bundles would have a random hash.
Alt Text

  • Code Splitting is an optimization technique used in reducing bundle size into smaller chunks, which helps to reduce the load time of our app. To configure webpack to split our bundle into chunks, we need to add an optimization section to our webpack.config.js file.
  optimization: {
    moduleIds: "hashed",
    runtimeChunk: "single",
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          priority: -10,
          chunks: "all",
        },
      },
    },
  }
Enter fullscreen mode Exit fullscreen mode

The code above tells webpack to create a runtime chunk, vendor chunk from our node_modules folder, and hash them. Now we should see a runtime bundle and a vendor bundle when we build the project again.
Alt Text

Final webpack configuration and Observation

Our final webpack.config.js file should look like this

const { VueLoaderPlugin } = require("vue-loader");
const htmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const autoprefixer = require("autoprefixer");
const path = require("path");

module.exports = {
  entry: {
    main: "./src/main.js",
  },
  output: {
    filename: "[name].[contenthash:8].js",
    path: path.resolve(__dirname, "dist"),
    chunkFilename: "[name].[contenthash:8].js",
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        },
      },
      {
        test: /\.vue$/,
        loader: "vue-loader",
      },
      {
        test: /\.(eot|ttf|woff|woff2)(\?\S*)?$/,
        loader: "file-loader",
        options: {
          name: "[name][contenthash:8].[ext]",
        },
      },
      {
        test: /\.(png|jpe?g|gif|webm|mp4|svg)$/,
        loader: "file-loader",
        options: {
          name: "[name][contenthash:8].[ext]",
          outputPath: "assets/img",
          esModule: false,
        },
      },
      {
        test: /\.s?css$/,
        use: [
          "style-loader",
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              plugins: () => [autoprefixer()],
            },
          },
          "sass-loader",
        ],
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: "[name].[contenthash:8].css",
      chunkFilename: "[name].[contenthash:8].css",
    }),
    new htmlWebpackPlugin({
      template: path.resolve(__dirname, "public", "index.html"),
      favicon: "./public/favicon.ico",
    }),
  ],
  resolve: {
    alias: {
      vue$: "vue/dist/vue.runtime.esm.js",
    },
    extensions: ["*", ".js", ".vue", ".json"],
  },
  optimization: {
    moduleIds: "hashed",
    runtimeChunk: "single",
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          priority: -10,
          chunks: "all",
        },
      },
    },
  },
  devServer: {
    historyApiFallback: true,
  },
};
Enter fullscreen mode Exit fullscreen mode

You can checkout the repo here

  • Observation: After I was done with the setup, I created a fresh vue3 project using the vue-cli and compared the bundle size of the vue3 project with the one I just set up and realised there wasn't a significant difference in the bundle size of both project.

This goes to show that there's really no point going through the stress of setting up your webpack. Except you absolutely need to change something, just use the vue-cli.

I should also add that setting up webpack yourself is not a bad idea because in the end, no knowledge is lost.👌

Top comments (18)

Collapse
 
mrcod3rir profile image
mrcod3r-ir • Edited

hi
tnx for post
I have issue even use your repo when take build version
in local no page content showing and when run xampp for local server , page content loaded but not showing logo.png as well as not nav menu routing work correctly
and when click on about link see below error in console :

http://localhost/about.a1c31bf5.js net::ERR_ABORTED 404 (Not Found)
ChunkLoadError: Loading chunk 0 failed.
(error: http://localhost/about.a1c31bf5.js)

so how fix them ?

and notice that I got problem when doing tutorial but solved !
pls remind users in tutorial when installing to use special version's together :
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"

tnx

Collapse
 
lavikara profile image
Temi Kara

Yes, i will update the article. Thanks

Collapse
 
dgloriaweb profile image
dgloriaweb

Hi Temi,

Your github repo works like charm, and I laughed so hard when I read the last 'observation' bit, because I came here, because my team told me webpack is better than cli so I should use that. Maybe that observation can also go to the top of the article. <3

Thread Thread
 
lavikara profile image
Temi Kara

Aside from helping people learn how to setup with webpack, that myth is the main reason I wrote this article. A lot of people tend to push their opinion as best practice... always find out why before accepting it.

Collapse
 
dgloriaweb profile image
dgloriaweb

I think you forgot to add install in here, but I am not sure.
npm file-loader sass sass-loader css-loader style-loader postcss postcss-loader autoprefixer clean-webpack-plugin html-webpack-plugin mini-css-extract-plugin -D

Collapse
 
lavikara profile image
Temi Kara

thanks for noting this.

Collapse
 
dgloriaweb profile image
dgloriaweb • Edited

I think the webpack config code should go under module/rules?
Update: Just saw you have a repo, maybe it would be useful to put it in top of the article? So if someone lame like me gets stuck, they can just check the repo?

Collapse
 
lavikara profile image
Temi Kara

😂😂 the idea is to have people read all the way to the end.

Collapse
 
dgloriaweb profile image
dgloriaweb

why?

Collapse
 
sergioeanx profile image
SergioEanX • Edited

Hi, thanks for the post very interesting.
I tried to reproduce your workflow but using latest packages.
I had to modify:
optimization.moduleIds:"deterministic"
And
{
test: /.s?css$/,
use: [
"style-loader",
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [autoprefixer()]
}
},
},
"sass-loader",
],
},

But even with this changes I got 4 warnings as:

warninng

Collapse
 
lavikara profile image
Temi Kara

hmmmm, i'll have to look into this

Collapse
 
dgloriaweb profile image
dgloriaweb • Edited

Hi! Got this when running server: Error: Cannot find module 'webpack-cli/bin/config-yargs'. config:

"webpack": "^5.44.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2"

Repo: github.com/dgloriaweb/vue-webpack-...

Update: tried to change "serve": "webpack serve" as they recommended, but there's another error:
[webpack-cli] Failed to load 'D:\Dokumentumaim\Projects\Vue\Webpack\vue-webpack-setup\webpack.config.js' config
[webpack-cli] Error: vue-loader requires @vue/compiler-sfc to be present in the dependency tree.

Upate2: I've installed npm i @vue/compiler-sfc and server is running, but gives all sorts of weird info in the CL. And in the browser I see the folder list.

Collapse
 
lavikara profile image
Temi Kara • Edited

most of the errors you'll get (if other things are done correctly), are usually due to webpack dependencies (webpack, webpack-cli, webpack-dev-server) not being compatible with each other. you'll need to use versions that are compatible with each other. running npm install would install the latest versions and this might not be compatible with each other. You can use the exert versions on the repo to avoid this problem.

Collapse
 
nilan profile image
Nilanchal

nice post

Collapse
 
ioanstoianov profile image
Ioan Stoianov

Really good post. Thanks!

Collapse
 
leonardoks16 profile image
Leonardo Kwieciński

Nice, do you know how to use it vuetify?

Collapse
 
lavikara profile image
Temi Kara

No i don't, i've actually never used vuetify.

Collapse
 
samuellealb profile image
Samuel Leal

Great post, thank you!

I'm having a problem to compile sass, thou. It´s like sass-loader isn´t working.

Had this kind of issue already?