CodeL
以前端为翼,以 AI 为脑,向全栈而行
2026-03-31

从零搭建 Webpack 5 + Vue 3 项目:完整配置指南

从零搭建 Webpack 5 + Vue 3 项目:完整配置指南 一篇文章搞定 Webpack + Vue 项目配置,从基础搭建到生产优化,手把手教你每一个配置项的作用。 目录 一、核心概念:Webpack 是什么? 二...

从零搭建 Webpack 5 + Vue 3 项目:完整配置指南 #

一篇文章搞定 Webpack + Vue 项目配置,从基础搭建到生产优化,手把手教你每一个配置项的作用。


目录 #


一、核心概念:Webpack 是什么? #

1.1 大白话解释 #

想象你在做一道菜:

  • 原材料 = 你的源代码(.vue、.js、.css、图片等)
  • 厨具 = Webpack
  • 成品菜 = 浏览器能运行的文件(.html、.js、.css)

Webpack 的工作就是:把你写的各种源代码,打包成浏览器认识的文件。

为什么需要打包?因为浏览器:

  • 不认识 .vue 文件
  • 不认识 import './style.scss'
  • 不认识 TypeScript
  • 不能直接用 ES6+ 新语法(老浏览器)

Webpack 把这些都"翻译"成浏览器能读懂的东西。

1.2 核心名词速查 #

名词 大白话解释 举例
Entry(入口) Webpack 从哪个文件开始打包 main.js
Output(输出) 打包后的文件放哪里、叫什么 dist/bundle.js
Loader(加载器) 翻译官,把非 JS 文件转成 JS vue-loader 处理 .vue
Plugin(插件) 增强功能,做 Loader 做不了的事 生成 HTML 文件
Mode(模式) 开发模式还是生产模式 development / production
Module(模块) 一个文件就是一个模块 import App from './App.vue'
Bundle(包) 打包后的文件 bundle.js
Chunk(代码块) 拆分出的代码片段 懒加载的组件

1.3 Webpack vs Vite #

对比项 Webpack Vite
启动速度 较慢(需打包) 极快(原生 ESM)
热更新 较慢 极快
生态成熟度 非常成熟 快速发展
配置复杂度 较复杂 开箱即用
生产构建 Webpack Rollup
适用场景 复杂企业项目 新项目、快速开发

为什么还要学 Webpack?

  1. 大量老项目在用 Webpack
  2. 复杂项目需要精细控制打包过程
  3. 面试必考
  4. 理解 Webpack 后,学其他构建工具更容易

二、项目初始化 #

2.1 创建项目目录 #

# 创建项目文件夹
mkdir webpack-vue-demo
 
# 进入项目
cd webpack-vue-demo
 
# 初始化 package.json
npm init -y

2.2 安装 Webpack #

# 安装 webpack 核心和 CLI
npm install webpack webpack-cli -D
 
# 安装 webpack 开发服务器
npm install webpack-dev-server -D

2.3 安装 Vue 相关依赖 #

# 安装 Vue 3
npm install vue
 
# 安装 Vue 单文件组件加载器
npm install vue-loader @vue/compiler-sfc -D
 
# 安装 Vue 模板编译器(Vue 3 需要单独安装)
# @vue/compiler-sfc 已经包含在上面

2.4 安装其他必要依赖 #

# CSS 相关
npm install css-loader style-loader -D
 
# 处理 less/sass(可选,根据需要安装)
npm install less less-loader -D
# npm install sass sass-loader -D
 
# 处理图片等资源
npm install file-loader url-loader -D
# 注意:Webpack 5 内置了 Asset Modules,可以不用 file-loader
 
# 处理 Babel(ES6+ 转 ES5)
npm install babel-loader @babel/core @babel/preset-env -D
 
# 生成 HTML 文件
npm install html-webpack-plugin -D
 
# 清理 dist 目录
npm install clean-webpack-plugin -D

2.5 项目结构 #

webpack-vue-demo/
├── public/
│   └── index.html          # HTML 模板
├── src/
│   ├── App.vue             # 根组件
│   └── main.js             # 入口文件
├── webpack.config.js       # Webpack 配置
└── package.json

三、基础配置:让项目跑起来 #

3.1 创建最简单的 Webpack 配置 #

创建 webpack.config.js

// webpack.config.js
const path = require('path');
 
module.exports = {
  // 1. 模式:开发模式
  mode: 'development',
  
  // 2. 入口:从哪个文件开始打包
  entry: './src/main.js',
  
  // 3. 输出:打包后的文件
  output: {
    filename: 'bundle.js',           // 输出文件名
    path: path.resolve(__dirname, 'dist'),  // 输出目录(绝对路径)
  },
  
  // 4. 模块:配置各种 Loader
  module: {
    rules: [
      // 处理 .vue 文件
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      // 处理 CSS
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']  // 从右往左执行
      },
      // 处理 JS(Babel)
      {
        test: /\.js$/,
        exclude: /node_modules/,  // 排除 node_modules
        loader: 'babel-loader'
      }
    ]
  },
  
  // 5. 插件
  plugins: []
};

3.2 创建入口文件和组件 #

创建 src/main.js

// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
 
// 创建 Vue 应用并挂载
createApp(App).mount('#app');

创建 src/App.vue

<!-- src/App.vue -->
<template>
  <div class="app">
    <h1>{{ message }}</h1>
    <button @click="count++">点击次数: {{ count }}</button>
  </div>
</template>
 
<script>
import { ref } from 'vue';
 
export default {
  name: 'App',
  setup() {
    const message = ref('Hello Webpack + Vue!');
    const count = ref(0);
    
    return {
      message,
      count
    };
  }
};
</script>
 
<style scoped>
.app {
  text-align: center;
  padding: 20px;
}
 
h1 {
  color: #42b983;  /* Vue 绿 */
}
 
button {
  padding: 10px 20px;
  font-size: 16px;
  cursor: pointer;
  border: 1px solid #42b983;
  border-radius: 4px;
  background: white;
  color: #42b983;
}
 
button:hover {
  background: #42b983;
  color: white;
}
</style>

创建 public/index.html

<!-- public/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Webpack + Vue Demo</title>
</head>
<body>
  <!-- Vue 挂载点 -->
  <div id="app"></div>
</body>
</html>

3.3 配置 Babel #

创建 babel.config.js.babelrc

// babel.config.js
module.exports = {
  presets: [
    '@babel/preset-env'  // 自动根据目标浏览器转换语法
  ]
};

3.4 添加 npm scripts #

修改 package.json

{
  "scripts": {
    "dev": "webpack serve --config webpack.config.js",
    "build": "webpack --config webpack.config.js"
  }
}

3.5 运行项目 #

# 开发模式运行
npm run dev
 
# 浏览器打开 http://localhost:8080

四、Vue 项目专属配置 #

4.1 添加 Vue Loader 插件 #

vue-loader 需要配合 VueLoaderPlugin 使用:

// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader');
 
module.exports = {
  // ... 其他配置
  
  plugins: [
    new VueLoaderPlugin()  // 必须添加!
  ]
};

4.2 生成 HTML 文件 #

使用 html-webpack-plugin 自动生成 HTML:

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
 
module.exports = {
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html',  // 模板文件
      filename: 'index.html',           // 输出文件名
      title: 'Webpack + Vue App',       // 页面标题
      inject: 'body'                    // JS 注入位置
    })
  ]
};

4.3 处理 Less/Sass 样式 #

如果使用 Less:

// webpack.config.js - module.rules 中添加
{
  test: /\.less$/,
  use: [
    'style-loader',   // 将 CSS 注入到 style 标签
    'css-loader',     // 处理 CSS 中的 @import 和 url()
    'less-loader'     // 将 Less 编译为 CSS
  ]
}

如果使用 Sass:

{
  test: /\.scss$/,
  use: ['style-loader', 'css-loader', 'sass-loader']
}

4.4 处理图片和字体 #

Webpack 5 内置了 Asset Modules,推荐使用:

// webpack.config.js - module.rules 中添加
{
  test: /\.(png|jpe?g|gif|svg|webp)$/,
  type: 'asset',
  parser: {
    dataUrlCondition: {
      maxSize: 8 * 1024  // 小于 8KB 转 base64
    }
  },
  generator: {
    filename: 'images/[name].[hash:8][ext]'  // 输出路径和文件名
  }
},
{
  test: /\.(woff2?|eot|ttf|otf)$/,
  type: 'asset/resource',
  generator: {
    filename: 'fonts/[name].[hash:8][ext]'
  }
}

在 Vue 中使用图片:

<template>
  <div>
    <!-- 方式1:直接引用 -->
    <img src="../assets/logo.png" alt="Logo">
    
    <!-- 方式2:动态绑定 -->
    <img :src="logoUrl" alt="Logo">
  </div>
</template>
 
<script>
import logoUrl from '../assets/logo.png';
 
export default {
  data() {
    return {
      logoUrl
    };
  }
};
</script>

五、开发体验优化 #

5.1 配置开发服务器 #

// webpack.config.js
module.exports = {
  // ... 其他配置
  
  devServer: {
    static: {
      directory: path.join(__dirname, 'public'),  // 静态文件目录
    },
    compress: true,          // 启用 gzip 压缩
    port: 8080,              // 端口号
    open: true,              // 自动打开浏览器
    hot: true,               // 热更新
    historyApiFallback: true, // Vue Router history 模式支持
    proxy: {
      // 代理配置
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        pathRewrite: { '^/api': '' }
      }
    }
  }
};

5.2 配置 source map(调试用) #

// webpack.config.js
module.exports = {
  // 开发环境推荐:eval-cheap-module-source-map
  // 生产环境推荐:source-map 或 false
  devtool: 'eval-cheap-module-source-map'
};

source map 类型对比:

类型 构建速度 重建速度 生产环境 品质
(none) 最快 最快 安全
eval 最快 不安全 生成代码
eval-cheap-source-map 较快 不安全 转换代码(行)
eval-cheap-module-source-map 中等 不安全 源代码(行)
source-map 最慢 安全 源代码

5.3 配置别名(@ 符号) #

// webpack.config.js
const path = require('path');
 
module.exports = {
  resolve: {
    // 配置别名
    alias: {
      '@': path.resolve(__dirname, 'src'),
      'components': path.resolve(__dirname, 'src/components'),
      'views': path.resolve(__dirname, 'src/views'),
      'assets': path.resolve(__dirname, 'src/assets')
    },
    // 省略后缀名
    extensions: ['.js', '.vue', '.json']
  }
};

使用示例:

// 之前
import App from '../../src/App.vue';
 
// 配置别名后
import App from '@/App.vue';
import Header from '@/components/Header.vue';

5.4 区分开发和生产环境 #

创建三个配置文件:

webpack/
├── webpack.common.js   # 公共配置
├── webpack.dev.js      # 开发环境配置
└── webpack.prod.js     # 生产环境配置

安装 webpack-merge:

npm install webpack-merge -D

公共配置 webpack.common.js:

// webpack/webpack.common.js
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
 
module.exports = {
  entry: './src/main.js',
  
  module: {
    rules: [
      { test: /\.vue$/, loader: 'vue-loader' },
      { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' },
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
      {
        test: /\.(png|jpe?g|gif|svg)$/,
        type: 'asset',
        parser: { dataUrlCondition: { maxSize: 8 * 1024 } },
        generator: { filename: 'images/[name].[hash:8][ext]' }
      }
    ]
  },
  
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html',
      filename: 'index.html'
    })
  ],
  
  resolve: {
    alias: {
      '@': path.resolve(__dirname, '../src')
    },
    extensions: ['.js', '.vue', '.json']
  }
};

开发配置 webpack.dev.js:

// webpack/webpack.dev.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
 
module.exports = merge(common, {
  mode: 'development',
  
  output: {
    filename: 'js/[name].js',
    path: path.resolve(__dirname, '../dist')
  },
  
  devtool: 'eval-cheap-module-source-map',
  
  devServer: {
    static: './dist',
    hot: true,
    port: 8080,
    open: true,
    historyApiFallback: true
  }
});

生产配置 webpack.prod.js:

// webpack/webpack.prod.js
const { merge } = require('webpack-merge');
const path = require('path');
const common = require('./webpack.common.js');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
 
module.exports = merge(common, {
  mode: 'production',
  
  output: {
    filename: 'js/[name].[contenthash:8].js',
    path: path.resolve(__dirname, '../dist'),
    publicPath: '/'
  },
  
  devtool: 'source-map',
  
  plugins: [
    new CleanWebpackPlugin()  // 每次构建前清理 dist
  ],
  
  optimization: {
    splitChunks: {
      chunks: 'all'  // 代码分割
    }
  }
});

更新 package.json:

{
  "scripts": {
    "dev": "webpack serve --config webpack/webpack.dev.js",
    "build": "webpack --config webpack/webpack.prod.js"
  }
}

六、生产环境优化 #

6.1 代码分割(Code Splitting) #

为什么要代码分割?

  • 减小单个文件体积
  • 利用浏览器缓存(第三方库单独打包)
  • 按需加载(懒加载路由组件)

配置示例:

// webpack.prod.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',  // 对所有模块进行分割
      cacheGroups: {
        // 第三方库单独打包
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: 10
        },
        // 公共代码提取
        common: {
          name: 'common',
          minChunks: 2,  // 至少被引用 2 次
          chunks: 'all',
          priority: 5,
          reuseExistingChunk: true
        }
      }
    },
    
    // 运行时代码单独打包
    runtimeChunk: {
      name: 'runtime'
    }
  }
};

打包结果:

dist/
├── js/
│   ├── runtime.[hash].js      # Webpack 运行时
│   ├── vendors.[hash].js      # 第三方库(vue、vue-router 等)
│   ├── common.[hash].js       # 公共代码
│   └── main.[hash].js         # 业务代码
└── index.html

6.2 路由懒加载 #

配合 Vue Router 实现路由懒加载:

// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
 
const routes = [
  {
    path: '/',
    name: 'Home',
    // 懒加载:访问时才加载
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/views/About.vue')
  },
  {
    path: '/user/:id',
    name: 'User',
    component: () => import(/* webpackChunkName: "user" */ '@/views/User.vue')
  }
];
 
const router = createRouter({
  history: createWebHistory(),
  routes
});
 
export default router;

魔法注释 /* webpackChunkName: "user" */ 可以指定 chunk 名称。

6.3 压缩代码 #

Webpack 5 生产模式默认使用 Terser 压缩 JS:

// webpack.prod.js
const TerserPlugin = require('terser-webpack-plugin');
 
module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,  // 移除 console
            drop_debugger: true  // 移除 debugger
          }
        }
      })
    ]
  }
};

6.4 提取 CSS 到单独文件 #

安装 mini-css-extract-plugin

npm install mini-css-extract-plugin -D

配置:

// webpack.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,  // 替代 style-loader
          'css-loader'
        ]
      }
    ]
  },
  
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css',
      chunkFilename: 'css/[id].[contenthash:8].css'
    })
  ]
};

6.5 CSS 压缩 #

安装 css-minimizer-webpack-plugin

npm install css-minimizer-webpack-plugin -D

配置:

// webpack.prod.js
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
 
module.exports = {
  optimization: {
    minimizer: [
      '...',  // 保留默认的 JS 压缩
      new CssMinimizerPlugin()
    ]
  }
};

6.6 Gzip 压缩 #

安装 compression-webpack-plugin

npm install compression-webpack-plugin -D

配置:

// webpack.prod.js
const CompressionPlugin = require('compression-webpack-plugin');
 
module.exports = {
  plugins: [
    new CompressionPlugin({
      test: /\.(js|css|html)$/,  // 压缩的文件类型
      threshold: 10240,          // 大于 10KB 才压缩
      algorithm: 'gzip'          // 压缩算法
    })
  ]
};

6.7 分析打包体积 #

安装 webpack-bundle-analyzer

npm install webpack-bundle-analyzer -D

配置:

// webpack.prod.js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
 
module.exports = {
  plugins: [
    // 打包后自动打开分析页面
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',  // 生成静态 HTML 文件
      openAnalyzer: false      // 不自动打开浏览器
    })
  ]
};

七、完整配置文件 #

7.1 公共配置 #

// webpack/webpack.common.js
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
 
module.exports = {
  entry: {
    main: './src/main.js'
  },
  
  module: {
    rules: [
      // Vue 单文件组件
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      
      // JavaScript + Babel
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      },
      
      // CSS
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      
      // Less
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      
      // 图片
      {
        test: /\.(png|jpe?g|gif|svg|webp)$/i,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024  // 8KB
          }
        },
        generator: {
          filename: 'images/[name].[hash:8][ext]'
        }
      },
      
      // 字体
      {
        test: /\.(woff2?|eot|ttf|otf)$/i,
        type: 'asset/resource',
        generator: {
          filename: 'fonts/[name].[hash:8][ext]'
        }
      },
      
      // 媒体文件
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)$/i,
        type: 'asset/resource',
        generator: {
          filename: 'media/[name].[hash:8][ext]'
        }
      }
    ]
  },
  
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html',
      filename: 'index.html',
      title: 'Vue App',
      favicon: './public/favicon.ico',
      minify: false
    })
  ],
  
  resolve: {
    alias: {
      '@': path.resolve(__dirname, '../src'),
      'components': path.resolve(__dirname, '../src/components'),
      'views': path.resolve(__dirname, '../src/views'),
      'assets': path.resolve(__dirname, '../src/assets'),
      'api': path.resolve(__dirname, '../src/api'),
      'utils': path.resolve(__dirname, '../src/utils'),
      'store': path.resolve(__dirname, '../src/store')
    },
    extensions: ['.js', '.vue', '.json'],
    symlinks: false
  }
};

7.2 开发配置 #

// webpack/webpack.dev.js
const { merge } = require('webpack-merge');
const path = require('path');
const common = require('./webpack.common.js');
 
module.exports = merge(common, {
  mode: 'development',
  
  output: {
    filename: 'js/[name].js',
    path: path.resolve(__dirname, '../dist'),
    publicPath: '/'
  },
  
  devtool: 'eval-cheap-module-source-map',
  
  devServer: {
    static: {
      directory: path.join(__dirname, '../public')
    },
    compress: true,
    host: '0.0.0.0',
    port: 8080,
    open: true,
    hot: true,
    historyApiFallback: true,
    client: {
      overlay: {
        errors: true,
        warnings: false
      }
    },
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        pathRewrite: { '^/api': '' }
      }
    }
  },
  
  cache: {
    type: 'memory'  // 内存缓存,加速构建
  }
});

7.3 生产配置 #

// webpack/webpack.prod.js
const { merge } = require('webpack-merge');
const path = require('path');
const common = require('./webpack.common.js');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
 
module.exports = merge(common, {
  mode: 'production',
  
  output: {
    filename: 'js/[name].[contenthash:8].js',
    chunkFilename: 'js/[name].[contenthash:8].chunk.js',
    path: path.resolve(__dirname, '../dist'),
    publicPath: '/',
    assetModuleFilename: 'assets/[name].[hash:8][ext]'
  },
  
  devtool: 'source-map',
  
  module: {
    rules: [
      // 生产环境提取 CSS
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      },
      {
        test: /\.less$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
      }
    ]
  },
  
  plugins: [
    new CleanWebpackPlugin(),
    
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css',
      chunkFilename: 'css/[name].[contenthash:8].chunk.css'
    }),
    
    new CompressionPlugin({
      test: /\.(js|css|html|svg)$/,
      threshold: 10240,
      algorithm: 'gzip'
    }),
    
    // 打包分析(按需开启)
    // new BundleAnalyzerPlugin()
  ],
  
  optimization: {
    minimize: true,
    
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
            drop_debugger: true
          }
        },
        parallel: true  // 多进程并行
      }),
      new CssMinimizerPlugin()
    ],
    
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: Infinity,
      minSize: 20000,
      maxSize: 244000,
      cacheGroups: {
        vue: {
          test: /[\\/]node_modules[\\/](vue|vue-router|pinia|vuex)[\\/]/,
          name: 'vue-vendors',
          chunks: 'all',
          priority: 20
        },
        elementPlus: {
          test: /[\\/]node_modules[\\/](element-plus|@element-plus)[\\/]/,
          name: 'element-plus',
          chunks: 'all',
          priority: 15
        },
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name(module) {
            const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
            return `npm.${packageName.replace('@', '')}`;
          },
          chunks: 'all',
          priority: 10
        },
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'all',
          priority: 5,
          reuseExistingChunk: true
        }
      }
    },
    
    runtimeChunk: {
      name: 'runtime'
    }
  },
  
  performance: {
    hints: 'warning',
    maxAssetSize: 512000,       // 500KB
    maxEntrypointSize: 512000   // 500KB
  }
});

八、常见问题 #

Q1: 为什么修改代码后页面没有更新? #

原因:热更新没生效或缓存问题

解决

// 确保 devServer.hot 为 true
devServer: {
  hot: true
}
 
// 或者在入口文件添加热更新代码
if (module.hot) {
  module.hot.accept();
}

Q2: 打包后图片/字体找不到? #

原因publicPath 配置不正确

解决

output: {
  publicPath: process.env.NODE_ENV === 'production' ? '/你的部署路径/' : '/'
}

Q3: Vue 组件样式不生效? #

原因:Loader 顺序错误或缺少配置

解决

// Loader 执行顺序:从右到左,从下到上
// 正确顺序:style-loader -> css-loader -> less-loader
{
  test: /\.less$/,
  use: ['style-loader', 'css-loader', 'less-loader']
}

Q4: 打包后文件太大怎么办? #

解决步骤

  1. 开启代码分割splitChunks
  2. 路由懒加载() => import('./views/xxx.vue')
  3. 第三方库按需引入
    // 全量引入
    import _ from 'lodash';
     
    // 按需引入
    import debounce from 'lodash/debounce';
  4. 使用 Bundle Analyzer 分析:找出大文件
  5. 开启 Gzip 压缩

Q5: 开发环境跨域怎么解决? #

解决:配置 devServer 代理

devServer: {
  proxy: {
    '/api': {
      target: 'https://example.com',
      changeOrigin: true,
      pathRewrite: { '^/api': '' }
    }
  }
}

Q6: 如何配置环境变量? #

方法1:使用 DefinePlugin

const webpack = require('webpack');
 
plugins: [
  new webpack.DefinePlugin({
    'process.env': JSON.stringify(process.env),
    'process.env.API_URL': JSON.stringify('https://api.example.com')
  })
]

方法2:使用 .env 文件

# 安装 dotenv
npm install dotenv -D
// webpack.config.js
const dotenv = require('dotenv');
dotenv.config({ path: './.env.development' });

九、总结速记 #

Loader 速查表 #

Loader 作用 示例
vue-loader 处理 .vue 文件 { test: /\.vue$/, loader: 'vue-loader' }
babel-loader ES6+ 转 ES5 { test: /\.js$/, loader: 'babel-loader' }
css-loader 处理 CSS 文件 解析 @import 和 url()
style-loader 将 CSS 注入页面 配合 css-loader 使用
less-loader Less 转 CSS 需要 less 依赖
sass-loader Sass 转 CSS 需要 sass 依赖
file-loader 处理文件(Webpack 4) 输出文件到目录
url-loader 小文件转 base64 需要配置 limit
ts-loader 处理 TypeScript 需要 typescript

Plugin 速查表 #

Plugin 作用
VueLoaderPlugin vue-loader 必需插件
HtmlWebpackPlugin 生成 HTML 文件
CleanWebpackPlugin 清理输出目录
MiniCssExtractPlugin 提取 CSS 到单独文件
CssMinimizerPlugin 压缩 CSS
TerserPlugin 压缩 JS
CompressionPlugin Gzip 压缩
BundleAnalyzerPlugin 分析打包体积
DefinePlugin 定义环境变量

常用命令 #

命令 作用
npm run dev 启动开发服务器
npm run build 生产环境打包
webpack --config xxx.js 指定配置文件
webpack --watch 监听文件变化
webpack --profile --json > stats.json 输出打包分析数据

最佳实践清单 #

项目 建议
模式 开发用 development,生产用 production
source map 开发用 eval-cheap-module-source-map
缓存 使用 [contenthash] 命名
代码分割 开启 splitChunks
懒加载 路由组件懒加载
压缩 生产环境开启 JS/CSS 压缩
分析 定期使用 Bundle Analyzer 分析

附录:推荐资源 #

资源 链接
Webpack 官方文档 https://webpack.js.org
Webpack 中文文档 https://webpack.docschina.org
Vue Loader 文档 https://vue-loader.vuejs.org
Babel 文档 https://babeljs.io

最后更新:2026-03-28