跳到主要内容

Webpack 之从 0 搭建一个 Vue 开发环境

· 阅读需 30 分钟

介绍

Webpack 是一个模块打包器(module bundler),提供了一个核心,核心提供了很多开箱即用的功能,同时它可以用 loader 和 plugin 来扩展。webpack 本身结构精巧,基于 tapable 的插件架构,扩展性强,众多的 loader 或者 plugin 让 Webpack 稍显复杂。

环境

本案例所依赖的 npm 包默认都是最新版,vue 使用的是 2.0 版本

  • node 15.2.0
  • webpack 5.52.0
  • vue 2.6.14

准备

初始化项目

mkdir webpack-vue-app
cd webpack-vue-app
npm init -y # -y 是指遇到确认自动选择 yes

安装 webpack

npm i webpack webpack-cli -D

webpack-cli 是 Webpack 的命令行工具包

创建目录

  webpack-vue-app
|- package.json
+ |- public
+ |- index.html
+ |- /src
+ |- index.js
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>webpack-vue-app</title>
</head>
<body>
<!-- ../dist/main.js 是 webpack 默认打包的相对路径 -->
<script src="../dist/main.js"></script>
</body>
</html>
src/index.js
const element = document.createElement('h1');
element.innerHTML = 'Hello Webpack';
document.body.appendChild(element);

package.jsonscripts 脚本增加一行

提示

本案例所有的代码修改都会以 背景色 突出为准

package.json
{
"name": "vue-webpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^5.52.0",
"webpack-cli": "^4.8.0"
}
}

启动

执行 npm start,就可以开始编译我们的入口文件了,然后把 html 文件在浏览器中打开

修改 js 文件中的信息,重新执行 npm start,再刷新浏览器

就这?啥也不是!

别急,这只是完成了我们最基本的一个 webpack 配置

进阶

配置文件

根目录下创建 webpack.config.js

webpack.config.js
const path = require('path');

/** @type {(import('webpack').Configuration} */
const config = {
entry: './src/index.js', // 默认入口,如果一样可以不设置
output: {
// 出口
path: path.resolve(__dirname, 'dist'), // 默认打包文件夹,如果一样可以不设置
filename: 'main.js' // 默认打包目录文件名,如果一样可以不设置
},
mode: 'development' // 指定当前运行环境,必须设置:development":开发, "production":构建
};

module.exports = config;

开发服务器

webpack-dev-server 可以让我们启动一个本地服务器

npm i webpack-dev-server -D
提示

-D 参数是 --dev 的简写,所有加了 -D 的安装依赖包,最终都不被打包进生产环境中

一般一些编译库,如:类型库,测试库,格式化库(eslint,prettier) 等会添加 -D 参数

不加 -D 的包都是会在生产环境用到的,如:主框架库,工具库

webpack.config.js
const path = require('path');

/** @type {(import('webpack').Configuration} */
const config = {
entry: './src/index.js', // 默认入口,如果一样可以不设置
output: {
// 出口
path: path.resolve(__dirname, 'dist'), // 默认打包文件夹,如果一样可以不设置
filename: 'main.js' // 默认打包目录文件名,如果一样可以不设置
},
mode: 'development', // 指定当前运行环境,必须设置:development":开发, "production":构建
devServer: {
port: 8000, // 指定端口
open: true, // 自启浏览器
hot: true, // 热更新
historyApiFallback: true, // 支持 history 模式路由
client: {
logging: 'none' // 清除浏览器 webpack 输出日志
}
// proxy: { // 代理跨域 如果需要的话
// "/api": {
// target:
// "https://www.baidu.com/api", // 目标代理接口地址
// changeOrigin: true, // 是否跨域
// pathRewrite: {
// "^/api": "/",
// },
// },
// },
},
stats: 'minimal' // 清除控制台 webpack 杂乱信息,仅在警告、错误和重新编译时输出
};

module.exports = config;

修改 package.json 启动脚本

  "scripts": {
- "start": "webpack",
+ "start": "webpack serve",
+ "build": "webpack",
"test": "echo \"Error: no test specified\" && exit 1"
},

html-webpack-plugin

html-webpack-plugin 是一个 Webpack 插件,可以让我们的 html 文件自动去获取开发环境入口文件的位置

npm install html-webpack-plugin -D

配置插件

webpack.config.js
 const config = {
// ...
plugins: [
new HtmlWebpackPlugin({ template: 'public/index.html' }),
];
};

module.exports = config;

同时,我们可以删除掉 public/index.html 中的 script 标签了

public/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>webpack-vue-app</title>
</head>
<body>
<!-- <script src="../dist/main.js"></script> -->
</body>
</html>

重新执行 npm start 即可,然后任意修改 src/index.js 入口文件的打印,同时观察浏览器控制台的输出内容,会自动刷新

Vue 开发环境

安装 Vue

npm i vue

修改入口文件全部替换为

src/index.js
import Vue from 'vue';

new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
template: '<div>{{message}}</div>'
});

因为我们挂载了 Vue 实例到 id 为 app 的元素上,所以我们要在 html 中创建这个元素

public/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>webpack-vue-app</title>
</head>
<body>
<div id="app"></div>
</body>
</html>

再运行 npm start,观察控制台应该会发现一个警告:

&nbsp;

[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.

大致意思是 Vue 的包默认导出的是运行时版本,这个版本无法使用模板语法,我们需要去包含能够编译模板的版本

alias 别名

所以我们可以将 import Vue from 'vue'; 调整为 import Vue from 'vue/dist/vue.js';,但是这样每次写起来很麻烦并且不够清晰,所以接下来用到 Webpack 别名

修改配置文件

webpack.config.js
const config = {
// ...
resolve: {
alias: {
vue: 'vue/dist/vue.js',
'@': path.resolve(__dirname, 'src') // 同时配置 '@' 符号作为 src 的绝对路径别名方便后续开发
}
}
};

module.exports = config;

这样所以导入 vue 的地方都变成了 vue/dist/vue.js 这个路径

再次运行 npm start,页面上应该能看到 Hello Vue! 字样了

单文件组件支持

这个模板是纯字符串,没有任何提示也不好看,Vue 支持一种叫 SFC 的单文件组件支持,配合编辑器能够高效的写出更优雅的代码

在 src 下添加 App.vue 文件

  webpack-vue-app
|- package.json
|- public
|- index.html
- |- /src
+ |- App.vue
|- index.js

添加以下代码

src/App.vue
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
message: 'Hello Vue!'
};
}
};
</script>

同时修改入口文件,引入 App.vue

src/index.js
import Vue from 'vue';
import App from '@/App.vue';

new Vue({
el: '#app',
render: h => h(App)
});

重新执行 npm start,观察网页会发现又出了新的报错:

Error:

Module parse failed: Unexpected token (1:0) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

意思是 Webpack 无法解析 .vue 的文件(默认只能处理 .js 文件),我们需要全装对应的 loader 来处理

loader

什么是 loader ?

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。

用白话来说 loader 的作用就是借助 Webpack 的拓展能力,让第三方去处理 Webpack 自身无法处理的文件

Vue 同样为其单文件组件准备了对应的 vue-loader

vue-loader

npm i vue-loader vue-template-compiler css-loader -D
  • css-loader 处理 .css 结尾的文件
  • vue-template-compilervue 的模板解析器
&nbsp;

vue-template-compiler 需要独立安装的原因是你可以单独指定其版本。

每个 vue 包的新版本发布时,一个相应版本的 vue-template-compiler 也会随之发布。编译器的版本必须和基本的 vue 包保持同步,这样 vue-loader 就会生成兼容运行时的代码。这意味着你每次升级项目中的 vue 包时,也应该匹配升级 vue-template-compiler

修改配置文件

webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');

const config = {
// ...
module: {
rules: [
{
test: /\.vue$/, // 通过正则匹配所有 .vue 结尾的文件使用
loader: 'vue-loader', // 使用 vue-loader
},
// 它会应用到普通的 `.css` 文件
// 以及 `.vue` 文件中的 `<style>` 块
{
test: /\.css$/,
use: [ // 多个 loader 以数组形式并使用 use 作为 key
'vue-style-loader', // 这个 loader 是 vue-loader 已经集成的
'css-loader',
],
},
],
},
plugins: [
new HtmlWebpackPlugin({ template: 'public/index.html' }),
new VueLoaderPlugin(),
];
};

module.exports = config;

Vue Loader 的配置和其它的 loader 不太一样。除了通过一条规则将 vue-loader 应用到所有扩展名为 .vue 的文件上之外,还需要在 webpack 配置中添加 Vue Loader

&nbsp;

vue-loader 的作用是将你定义过的其它规则复制并应用到 .vue 文件里相应语言的块。例如,如果有一条匹配 /\.js$/ 的规则,那么它会应用到 .vue 文件里的 <script> 里面

重新执行 npm start.vue 文件可以正常通过编译,并且可以使用 style 标签添加 css 属性了

less

上述也仅支持原生 css 而已,原生 css 已经无法满足我们的正常需求了。嵌套、变量、函数、混合等功能也越来越变得刚需了。

如 less/scss/stylus/css-module 等众多 css 库已经成为了开发的标配,所以我们还需要对其配置一下

本文以 less 为例 添加 less 安装包,less 和 less-loader 就像 vue 和 vue-loader 的关系,一个是本体,一个是加载工具

npm i less less-loader -D

修改配置文件

webpack.config.js
const config = {
// ...
module: {
rules: [
// ...
{
test: /\.(css|less)$/,
use: ['vue-style-loader', 'css-loader', 'less-loader']
}
]
}
};

module.exports = config;

style-loader

loader 的顺序很重要,如果 loader 有多个,那 执行的顺序是从后往前相反的,所以编译规则是:

  1. less-loader: 使用 Less loader 把 less 转化为普通 css
  2. css-loader: 解决 css 中所有带 importurl(...)的地方(浏览器并不不支持这些方法,就像 js 解析 import/require() 一样)
  3. style-loader:把 css 处理成 style 标签并插入到 DOM 中
&nbsp;

可以看到我们上述用了 vue-style-loader 而不是 常规的 style-loader,这里其实是 Vue 把 style-loader 复制了一份,然后解决了一些服务端渲染的问题

如何测试能正常使用 less?替换 App.vue 为以下代码,当嵌套发生作用时,说明配置成功

src/App.vue
<template>
<div class="container">
<h1>{{ message }}</h1>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
message: 'Hello Vue!'
};
}
};
</script>
<style lang="less">
.container {
h1 {
background-color: #ccc;
}
}
</style>

图片/字体

Assetmodules 是一种 webpack 模块类型,允许使用资产文件(字体、图标等)而不需要配置其他 loader。

&nbsp;

webpack4 及之前处理图片、字体文件需要引用到 file-loaderurl-loaderraw-loader 等插件

修改配置文件

webpack.config.js
const config = {
entry: './src/index.js', // 默认入口,如果一样可以不设置
output: {
// 出口
path: path.resolve(__dirname, 'dist'), // 默认打包文件夹,如果一样可以不设置
filename: 'js/[name].bundle.js', // 默认打包目录文件名,如果一样可以不设置
assetModuleFilename: 'images/[name].[ext][query]', // 图片文件需要存放的位置,如果不设置会全部打包进根目录下
clean: true // 打包时每次清空目录
},
// ...
module: {
rules: [
// ...
{
test: /\.(png|jpe?g|gif)$/,
type: 'asset/resource'
},
{
test: /\.(woff(2)?|eot|ttf|otf|svg|)$/,
type: 'asset/inline'
}
]
}
};

module.exports = config;

可以正常加载图片了

eslint

eslint 可以帮助我们在开发过程中即时检测一些语法错误拼写错误,甚至部分能够帮我们自动修复

npm i eslint eslint eslint-plugin-vue eslint-webpack-plugin -D
  • eslint eslint 核心库,对基础语法的校验和修复功能
  • eslint-plugin-vue vue 官方开发的针对 vue 语法 的 eslint 插件
  • eslint-webpack-plugin webpack 的 eslint 插件,用于开发时控制台提示出语法错误信息

在根目录下新建 .eslintrc 配置文件,我们的配置可以完全继承于 eslint-plugin-vue

.eslintrc
{
"env": {
// 指定我们的开发宿主环节有哪些
"node": true // eslint-plugin-vue 中已经包含了 "browser" 和 "es6",添加 node 是为了让我们正常使用 node 方法比如 require
},
"extends": ["eslint:recommended", "plugin:vue/recommended"] // 继承 eslint 的推荐配置(常规 js 文件) 和 vue 的推荐配置(.vue 文件)
}

编辑器提示

确保安装了微软官方的 vscode eslint 插件,在项目下新建 .vsocde 目录并在里面新建 .settings.json 文件,配置如下代码

{
"eslint.validate": ["javascript", "javascriptreact", "vue"]
}

如果安装了 Vetur 插件还需要设置 "vetur.validation.template": false 避免冲突

配置好以后编辑器中的报错应该都消失了

eslintignore

设置 eslint 忽略文件以排除不必要的文件校验,在根目录下新建 .eslintignore

!**/.eslintrc*
node_modules*
dist
public
*.svg
*.ico
*.json
.gitignore
*.md
*.log
*.lock

eslint-webpack-plugin

上述只是编辑器的一个提示,控制台或浏览器还不能正常提示(但其实对大部分人来说也足够,并且控制台的报错影响开发体验),不需要的可以跳过

修改 webpack 配置文件

webpack.config.js
// ...
const EslintWebpackPlugin = require('eslint-webpack-plugin');

const config = {
// ...
plugins: [
// ...
new EslintWebpackPlugin({
fix: true, // fix 参数能够自动修复部分错误
extensions: ['js', 'vue'], // 需要检测的文件类型
// 更多参数可以去查看 https://webpack.docschina.org/plugins/eslint-webpack-plugin/#root
}),
],

module.exports = config;

extensions 后缀解析

&nbsp;

extensions(解析)功能会尝试按顺序解析特定后缀名。如果有多个文件有相同的名字,但后缀名不同,webpack 会解析列在数组首位的后缀的文件 并跳过其余的后缀

我们目前的路径还需要输入完整的后缀名, extensions 配置能帮我们省去

webpack.config.js
// ...
const config = {
// ...
resolve: {
alias: {
vue: 'vue/dist/vue.js',
'@': path.resolve(__dirname, 'src')
},
extensions: ['.vue', '.js', '.json']
}
};

module.exports = config;

babel

babel 的作用是把我们的 es6 或更高的代码转为 es5,使得能够兼容低版本的浏览器,如 箭头函数、Promiseclass 类等

安装 babel 依赖

npm i @babel/core @babel/preset-env babel-loader -D

修改配置文件

webpack.config.js
//...
const config = {
// ...
module: {
rules: [
// ...
{
test: /\.js$/, // 对所有 .vue 结尾的文件使用 vue-loader
exclude: /node_modules/, // 排除 node_modules 目录
use: [
{
loader: 'babel-loader'
}
]
}
]
}
};

module.exports = config;

同时还需要配置 babel,虽然也可以写在 webpack 配置文件 babel-loader 的 options 中,为了更加清爽直观,我们选择在单独的配置文件中配置它

根目录下创建 .babelrc 文件,babel-loader 会自动找到这个路径读取 babel 配置

.babelrc
{
"presets": ["@babel/env", "@vue/babel-preset-jsx"]
}

jsx

jsx 在灵活性和智能提示上有时候比模板更好用,vue 官方也对 jsx 专门出了一个对应的 babel 插件 @vue/babel-preset-jsx

npm i @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props -D

因为是 babel 插件,所以也要修改下 babel 的配置

.babelrc
{
"presets": ["@babel/env", "@vue/babel-preset-jsx"]
}

为了 eslint 能够正常识别 jsx 语法,因此也要对应的设置一下

.eslintrc
{
"env": {
"node": true
},
"parserOptions": {
"ecmaFeatures": {
"jsx": true // 开启 jsx
}
},
"extends": ["eslint:recommended", "plugin:vue/recommended"],
"rules": {
"no-unused-vars": "warn",
"semi": "warn"
}
}

现在,App.vue 中就可以正常使用 jsx 语法了

src/App.vue
<script>
export default {
name: 'App',
data() {
return {
message: 'Hello Vue!'
};
},
render() {
return (
<div class="container">
<h1>{this.message}</h1>
<img src={require('./assets/logo.png')} />
</div>
);
}
};
</script>
<style lang="less" scoped>
.container {
h1 {
background-color: #ccc;
}
}
</style>

sourcemap

尝试 在 src/App.vue 文件中打任意打印,然后在浏览器控制台显示的文件名和行号都不对,这是因为运行代码是源代码编译或者加密混淆之后的代码,所以不管是开发时还是线上时调试的文件名称和行号等信息,都无法准确定位。

source-map 的基本原理是,在编译处理的过程中,在生成产物代码的同时生成产物代码中被转换的部分与源代码中相应部分的映射关系表。

Webpack 中,通过设置 devtool 属性来选择 source map 的类型,包含了 "eval" "cheap" "module" "inline" "hidden" "nosource" "source-map" 等关键字的组合

修改 webpack 配置文件

webpack.config.js
// ...
const config = {
// ...
devtool: 'source-map'
// ...
};
module.exports = config;

重新执行 npm start 就可以清楚的看到打印信息来源了,报错信息也如此

生产环境

目前所有的配置都是基于开发环境,在构建生产环境的时候,我们需要一系列的优化。

配置文件抽离

所以开发环境和生产环境的配置也有所不同,为了便于维护,我们将对开发环境和生产环境的配置文件单独维护

webpack-vue-app
|- config
|- webpack.common.config.js
|- webpack.dev.config.js
|- webpack.prod.config.js
  • webpack.common.config.js: 提取公共的配置
  • webpack.dev.config.js: 开发环境的配置
  • webpack.prod.config.js: 生产环境的配置

修改后的代码如下

config/webpack.common.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');
const EslintWebpackPlugin = require('eslint-webpack-plugin');

/** @type {(import('webpack').Configuration} */
const config = {
entry: path.resolve(__dirname, '../src/index.js'), // 默认入口,如果一样可以不设置
output: {
// 出口
path: path.resolve(__dirname, '../dist'), // 默认打包文件夹,如果一样可以不设置
filename: 'js/[name].bundle.js', // 默认打包目录文件名,如果一样可以不设置,
assetModuleFilename: 'images/[name].[ext][query]', // 图片文件需要存放的位置,如果不设置会全部打包进根目录下
clean: true // 打包时每次清空目录
},
module: {
rules: [
{
test: /\.vue$/, // 对所有 .vue 结尾的文件使用 vue-loader
loader: 'vue-loader'
},
{
test: /\.js$/, // 对所有 .vue 结尾的文件使用 vue-loader
exclude: /node_modules/, // 排除 node_modules 目录
use: [
{
loader: 'babel-loader'
}
]
},
{
test: /\.(css|less)$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true // 启用 css sourceMap
}
},
'less-loader'
]
},
{
test: /\.(png|jpe?g|gif|svg)$/,
type: 'asset/resource'
},
{
test: /\.(woff(2)?|eot|ttf|otf|svg|)$/,
type: 'asset/inline'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'public/index.html'
}),
new VueLoaderPlugin(),
new EslintWebpackPlugin({
fix: true,
extensions: ['js', 'vue']
})
],
resolve: {
alias: {
vue: 'vue/dist/vue.js',
'@': path.resolve(__dirname, '../src') // 同时配置 src 路径别名方便后续开发
},
extensions: ['.vue', '.js', '.json']
},
stats: 'minimal'
};

module.exports = config;
config/webpack.dev.config.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.config');

module.exports = merge(common, {
mode: 'development',
devtool: 'source-map',
devServer: {
port: 8000, // 指定端口
open: false, // 自启浏览器
hot: true, // 局部热更新
historyApiFallback: true, // 支持 history 模式路由
client: {
logging: 'none'
}
// proxy: { // 代理跨域 如果需要的话
// "/api": {
// target:
// "https://www.baidu.com/api", // 目标代理接口地址
// changeOrigin: true, // 是否跨域
// pathRewrite: {
// "^/api": "/",
// },
// },
// },
}
});
config/webpack.prod.config.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.config');

module.exports = merge(common, {
mode: 'production',
devtool: false, // 生产环境不需要 source-map
output: {
filename: 'js/[name].[contenthash].bundle.js', // 文件名哈希
assetModuleFilename: 'images/[name].[contenthash][ext][query]' // 图片文件需要存放的位置,如果不设置会全部打包进根目录下
}
});

可以看出 生产环境中移除了 devServer 和 devtool 选项

webpack-merge

为了避免耦合,我们对公共代码部分进行了抽离,并且引入了一个新的插件 webpack-merge,作用就是合并 webpack 配置

npm i webpack-merge -D

现在我们没有使用默认的配置文件 webpack.config.js ,webpack 自然也加载不出来,因此在 package.json webpack 脚本中添加 --config 参数参数以修改配置文件路径,

{
"scripts": {
"start": "webpack serve --config config/webpack.dev.config.js",
"build": "webpack --config config/webpack.prod.config.js",
"lint": "eslint \"src/**/*.{js,vue}\"",
"test": "echo \"Error: no test specified\" && exit 1"
}
}

JS 代码压缩

为了生产环境的优化,js 一般会压缩所有的换行和不必要的空格,变成一行,以及代码混淆(没错就是变量名变成 a,b,c 那种)

js 代码压缩要用到 terser-webpack-plugin,但 webpack 5 内置了这个插件可一键开启(如果是 webpack 4 则需要单独安装)

修改生产环境的配置文件

config/webpack.prod.config.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.config');

module.exports = merge(common, {
mode: 'production',
devtool: false, // 生产环境不需要 source-map
optimization: {
minimize: true
// minimizer: [...] 如果想要自定压缩的话
}
});

如果想要自定义压缩的话,仍需引入 terser-webpack-plugin

Webpack v5 comes with the latest terser-webpack-plugin out of the box. If you are using Webpack v5 or above and wish to customize the options, you will still need to install terser-webpack-plugin. Using Webpack v4, you have to install terser-webpack-plugin v4.

CSS 代码抽离

目前的 css 代码都是含在 js 中通过 js 动态插入到 html 中,生产环境 不应该如此。

mini-css-extract-plugin 插件会将 css 提取到单独的文件中,为每个包含 css 的 js 文件创建一个 css 文件。

npm i mini-css-extract-plugin -D

config/webpack.common.config.js 中的 css 规则迁移到 config/webpack.dev.config.js 中,修改 config/webpack.prod.config.js 单独为 生产环境制定 css 优化

config/webpack.dev.config.js
// ...
module.exports = merge(common, {
// ...
module: {
rules: [
{
test: /\.(css|less)$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true // 启用 css sourceMap
}
},
'less-loader'
]
}
]
}
});
config/webpack.prod.config.js
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = merge(common, {
// ...
module: {
rules: [
{
test: /\.(css|less)$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
}
]
},
plugins: [
// 启用 css 压缩插件
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash].css'
})
]
});

CSS 压缩

js 压缩了换行和空合,那 css 自然也少不了css-minimizer-webpack-plugin 用于 css 的文件压缩

npm i css-minimizer-webpack-plugin -D
config/webpack.prod.config.js
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = merge(common, {
// ...
optimization: {
minimize: true,
minimizer: [
`...`, // 在 webpack@5 中,使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`)
new CssMinimizerPlugin()
]
}
});

部署

publicPath

publicPath

默认情况下,webpack 会假设你的应用是被部署在一个域名的根路径上,例如 https://www.my-app.com。 如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.my-app.com/my-app/ ,则设置 publicPath 为 /my-app/

config/webpack.prod.config.js
// ...
module.exports = merge(common, {
// ...
output: {
publicPath: '/子路径/'
}
});

其他

文件名 hash 值

如果每次构建输出的文件名称是一样的,会给在客户端做长缓存带来很大的麻烦。因此,我们需要在输出的文件名中添加一些 hash 值,使得每次构建输出的文件名称是不一样的。

假设有如下配置:

const config = {
output: {
path: PATHS.build,
filename: '[name].[contenthash].js'
}
};

Webpack 构建输出的文件名为:

main.d587bbd6e38337f5accd.jsvendor.dc746a5db4ed650296e1.js

此时,如果文件内容发生变化,则 [contenthash] 也会相应的变化,此时,浏览器缓存就会失效,浏览器就会重新发起一个请求来加载变化了的文件。即,如果仅仅只要 main.js 发生了变化,那么浏览器只会重新请求加载 main.js。

我们还可以将 hash 值加载请求参数中,比如 main.js?d587bbd6e38337f5accd, 这样的话,输出文件名不变,通过查询参数的变化来使缓存失效。

react 环境

如果你是一个 react 开发者,参考如下配置

  1. 将 vue-loader 移除,并删除对应规则
  2. 添加 @babel/preset-react 插件到 babel 配置中,babel 的规则修改为 test: /\.jsx?$/
  3. vue-style-loader 替换成 style-loader 并安装
  4. 所有涉及 .vue 后缀的地方替换成 .jsx
  5. eslint 安装 eslint-plugin-react,并将 eslint 配置文件中的 "plugin:vue/recommended" 替换成 "plugin:react/recommended"

然后就是正常的安装 react react-dom 等依赖了

完整代码

完整代码见 https://gitlab.hyyar.com/root/webpack-vue-app