Building a Vue.js Project from Scratch with Webpack 5

This guide walks through manually configuring a Vue.js application using Webpack 5—bypassing vue-cli to gain deeper insight into the underlying toolchain. The setup targets modern development workflows, emphasizing modularity, performance, and maintainability.

Core Webpack Configuration

Webpack 5 introduces built-in asset modules and drops legacy loaders like file-loader and url-loader. Its five foundational concepts remain unchanged:

  • Entry: Starting point(s) for dependency graph traversal.
  • Output: Where bundled assets are emitted (requires absolute paths).
  • Loader: Transforms files before adding them to the dependency graph.
  • Plugin: Extends webpack’s capabilities beyond module transformation.
  • Mode: Determines built-in optimizations (development or production).

A minimal configuration skeleton:

const path = require('path');

module.exports = {
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'assets/js/bundle.[contenthash:8].js',
    clean: true
  },
  module: {
    rules: []
  },
  plugins: [],
  mode: 'development'
};

Handling Styles

CSS Integration

Use css-loader to interpret @import and url(), and style-loader to inject styles into the DOM during development:

npm install --save-dev css-loader style-loader
{
  test: /\.css$/i,
  use: ['style-loader', 'css-loader']
}

Extracting CSS into Separate Files

In production, extract CSS into standalone files using MiniCssExtractPlugin:

npm install --save-dev mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// In rules:
{
  test: /\.css$/i,
  use: [MiniCssExtractPlugin.loader, 'css-loader']
}

// In plugins:
new MiniCssExtractPlugin({
  filename: 'assets/css/styles.[contenthash:8].css'
})

PostCSS & Browser Compatibility

Add vendor prefixing and modern feature fallbacks via PostCSS:

npm install --save-dev postcss-loader postcss postcss-preset-env
{
  test: /\.css$/i,
  use: [
    MiniCssExtractPlugin.loader,
    'css-loader',
    {
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins: ['postcss-preset-env']
        }
      }
    }
  ]
}

Define target browsers in package.json:

"browserslist": ["last 2 versions", "> 1%", "not dead"]

CSS Minification

Enable compression in production:

npm install --save-dev css-minimizer-webpack-plugin
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
  // ...
  optimization: {
    minimizer: [
      new CssMinimizerPlugin()
    ]
  }
};

Preprocessor Support

For .less, .scss, or .sass files:

npm install --save-dev less-loader sass-loader sass stylus-loader
{
  test: /\.less$/i,
  use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
},
{
  test: /\.s[ac]ss$/i,
  use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
}

Asset Management

Images & Fonts

Leverage Webpack 5’s native asset module types:

// Inline small images as Base64
{
  test: /\.(png|jpe?g|gif|webp)$/i,
  type: 'asset',
  parser: {
    dataUrlCondition: { maxSize: 10 * 1024 }
  },
  generator: {
    filename: 'assets/images/[name].[contenthash:6][ext]'
  }
},

// Copy larger assets and fonts as-is
{
  test: /\.(woff2?|ttf|eot|svg)$/i,
  type: 'asset/resource',
  generator: {
    filename: 'assets/fonts/[name].[contenthash:8][ext]'
  }
}

JavaScript Tooling

Type Safety & Linting

Integrate ESLint for code quality enforcement:

npm install --save-dev eslint-webpack-plugin eslint
const ESLintPlugin = require('eslint-webpack-plugin');

module.exports = {
  // ...
  plugins: [
    new ESLintPlugin({
      context: path.resolve(__dirname, 'src'),
      cache: true,
      cacheLocation: path.resolve(__dirname, 'node_modules/.cache/eslint')
    })
  ]
};

Sample .eslintrc.cjs:

module.exports = {
  root: true,
  env: { browser: true, es2021: true, node: true },
  extends: ['eslint:recommended', 'plugin:vue/vue3-essential'],
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
    parser: '@babel/eslint-parser'
  },
  rules: {
    'vue/multi-word-component-names': 'off',
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
  }
};

Babel Transpilation

Support modern JavaScript syntax across environments:

npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime
{
  test: /\.js$/i,
  include: path.resolve(__dirname, 'src'),
  use: {
    loader: 'babel-loader',
    options: {
      presets: ['@babel/preset-env'],
      plugins: ['@babel/plugin-transform-runtime']
    }
  }
}

HTML Integration

Automatically generate and inject bundle references:

npm install --save-dev html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ]
};

Development Experience

Hot Module Replacement (HMR)

Enable live updates without full page reloads:

devServer: {
  host: 'localhost',
  port: 8080,
  open: true,
  hot: true,
  compress: true
}

Source Maps

Map generated code back to original sources:

// Development
devtool: 'cheap-module-source-map'

// Production
devtool: 'source-map'

Build Performance Optimizations

  • Threaded Loaders: Use thread-loader with os.cpus().length workers.
  • Caching: Enable cacheDirectory in Babel and cache in ESLint.
  • OneOf Rules: Prevent redundant rule matching with oneOf.

Vue-Specific Setup

Vue Loader & Runtime

Install core Vue tooling:

npm install vue
npm install --save-dev vue-loader vue-template-compiler @vue/compiler-sfc
const { VueLoaderPlugin } = require('vue-loader');

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.vue$/i,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [new VueLoaderPlugin()],
  resolve: {
    extensions: ['.vue', '.js', '.ts'],
    alias: {
      '@': path.resolve(__dirname, 'src')
    }
  }
};

Style Scoping & Preprocessors

Replace style-loader with vue-style-loader for scoped styles:

npm install --save-dev vue-style-loader
{
  test: /\.vue$/i,
  use: ['vue-style-loader', 'css-loader', 'postcss-loader']
}

Environment Definitions

Expose Vue-spceific flags at compile time:

npm install --save-dev webpack DefinePlugin
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      __VUE_OPTIONS_API__: JSON.stringify(true),
      __VUE_PROD_DEVTOOLS__: JSON.stringify(false)
    })
  ]
};

Production Enhancements

  • Tree Shaking: Enabled by default for ES modules; ensure all dependencies export properly.
  • Code Splitting: Use dynamic import() for route-based or component-level chunks.
  • PWA Support: Integrate workbox-webpack-plugin for offline capability.

For full implementation details and working examples, refer to the companion repository.

Tags: webpack5 vuejs css-modules ESLint postcss

Posted on Mon, 29 Jun 2026 17:03:47 +0000 by stephenk