2021年必须要掌握的webpack5最佳配置
前言
2021年啦,对webpack构建还是cli一揽子计划(啥也不懂),结合官网,决定系统性的学习一下webpack,了解一下webpack之美(生活所迫)
1、webpack是什么
本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部构建一个 依赖图(dependency graph),此依赖图对应映射到项目所需的每个模块,并生成一个或多个 bundle。
module -> chunk -> bundle
2、初始化项目
新建项目名,任意位置,任意名称(建议英文),新建文件夹 mulei
初始化项目
npm init -y
安装 webpack
npm i webpack webpack-cli -D
当前安装版本
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
入口文件默认是 ./src/index.js,也可以在 webpack.config.js 配置
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
// 把所有依赖的模块合并输出到一个 bundle.js 文件
filename: 'main.js',
// 输出文件都放到 dist 目录下
path: path.resolve(__dirname, './dist'),
}
}
从 v4.0.0 开始,webpack 可以不用再引入一个配置文件来打包项目
直接运行 webpack 即可走默认输出 dist/main.js
- 运行当前目录下的
webpack需要使用npm内置的包处理器npx,npx webpack会直接找项目的/node_modules/.bin/里面的命令执行 npx webpack不指定模式mode的情况下,默认走的是production,开发环境需要执行npx webpack --mode development
3、拆分配置和 merge
development(开发环境) 和 production(生产环境) 这两个环境下的构建目标存在着巨大差异。在开发环境中,我们需要:强大的 source map 和一个有着 live reloading(实时重新加载) 或 hot module replacement(热模块替换) 能力的 localhost server。而生产环境目标则转移至其他方面,关注点在于压缩 bundle、更轻量的 source map、资源优化等,通过这些优化方式改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。
防止不同环境下配置有重复的配置,需要抽离出来公共的配置,然后使用 webpack-merge 工具,将公共配置和环境特有配置进行合并。
在根目录下新建 build 文件夹,里面新建公共配置 webpack.common.js、开发环境配置webpack.dev.js、生产环境配置 webpack.prod.js。
安装 webpack-merge
npm i webpack-merge -D
配置webpack.dev.js
const { merge } = require("webpack-merge")
const webpackCommon = require('./webpack.common.js')
module.exports = merge(webpackCommon, {
mode: "development"
})
那怎么才能运行 webpack.dev.js呢?为了方便,需要在 package.js 的 scripts中配置
"scripts": {
"dev": "webpack --config build/webpack.dev.js",
"build": "webpack --config build/webpack.prod.js"
},
4、html自动构建
html-webpack-plugin
在根目录新建文件夹 public,里面新建 index.html 文件,那我们怎么把 index.html 打包到 dist 文件夹内,并引入输出的 main.js 呢?通过使用 html-webpack-plugin 插件生成一个 HTML5 文件, 在 body 中使用 script 标签引入你所有 webpack 生成的 bundle。
安装 html-webpack-plugin
npm i html-webpack-plugin -D
由于无论开发环境还是生产环境都需要构建 html,所以把 html-webpack-plugin 配置在公共配置 webpack.common.js 内
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [
new HtmlWebpackPlugin()
]
}
html-webpack-plugin插件在没有任何配置下,默认在 dist 文件夹内生成 index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- <title>Webpack App</title> -->
<title><%= htmlWebpackPlugin.options.title %></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script defer src="main.js?e68f6906a7f78bef340d"></script>
</head>
<body>
</body>
</html>
但是生成的默认的 index.html 和我们自己创建的 index.html 还是有区别的,此时需要配置参数 template
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html",
hash: true, // 为CSS文件和JS文件引入时,添加唯一的hash,破环缓存非常有用
title: "动态生成标题"
})
]
}
在 HTML 模板文件中,我们可以使用 <%= htmlWebpackPlugin.options.title %> 占位符获取title参数来动态生成页面标题。
copy-webpack-plugin
将已存在的单个文件或整个目录复制到构建目录,比如网站图标
安装 copy-webpack-plugin
npm i copy-webpack-plugin -D
具体配置
// webpack.common.js
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{
from: "public/favicon.ico",
to: "favicon.ico"
}
]
}),
]
}
5、编译JS文件
使用 Babel 加载 ES2015+ 代码并将其转换为 ES5
需要安装以下插件
npm install babel-loader @babel/core @babel/preset-env -D
babel 配置既可以在配置文件中 webpack.common.js 配置,也可以在 .babelrc 中配置
// webpack.common.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader?cacheDirectory',
options: {
"presets": ["@babel/preset-env"]
}
},
exclude: /node_modules/
},
]
}
}
// .babelrc
{
"presets": ["@babel/preset-env"]
}
其中有2个提升构建速度的知识点:
include包含、exclude排除文件,减少编译文件cacheDirectory用来缓存 loader 的执行结果。之后的 webpack 构建,将会尝试读取缓存,默认的缓存目录node_modules/.cache/babel-loader
6、CSS 预处理语言和 CSS
编译
webpack是不能直接编译样式文件的,需要使用loader
预处理语言以 less 为例,主要分为4步:
less-loader:webpack将Less编译为CSS的loader安装less和less-loader
npm i less less-loader -D
postcss-loader:使用PostCSS处理CSS的loader,postcss-preset-env自动添加兼容浏览器样式前缀,需要设置兼容浏览器版本
postcss-loader 的属性 postcssOptions 允许设置插件,也可以在 postcss.config.js中配置
安装 postcss-loader 和 postcss-preset-env
npm i postcss-loader postcss-preset-env -D
css-loader:对@import和url()进行处理 安装css-loader
npm i css-loader -D
style-loader:把CSS插入到DOM中,通过style标签的方式添加到了html文件的head标签中 安装style-loader
npm i style-loader -D
为了方便大家可以一并安装:
npm i less less-loader postcss-loader postcss-preset-env css-loader style-loader -D
执行顺序从右到左,从下到上
详细配置:
// webpack.dev.js
const { merge } = require("webpack-merge")
const webpackCommon = require('./webpack.common.js')
module.exports = merge(webpackCommon, {
mode: "development",
module: {
rules: [
{
test: /\.css$/,
use: [
"style-loader",
"css-loader",
"postcss-loader"
// {
// loader: "postcss-loader",
// options: {
// postcssOptions: {
// plugins: [
// [
// "postcss-preset-env",
// {
// browsers: ["last 1 version", "> 1%", "IE 10"]
// }
// ]
// ]
// }
// }
// }
]
},
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
"postcss-loader",
// {
// loader: "postcss-loader",
// options: {
// postcssOptions: {
// plugins: [
// [
// "postcss-preset-env",
// {
// browsers: ["last 1 version", "> 1%", "IE 10"]
// }
// ]
// ]
// }
// }
// },
"less-loader"
]
}
]
}
})
// postcss.config.js
module.exports = {
plugins: [
[
"postcss-preset-env",
{
browsers: ["last 1 version", "> 1%", "IE 10"]
}
]
]
}
为了保证不同工具公用一套浏览器兼容版本,建议在根目录新建 .browserslistrc 文件
last 1 version
> 1%
IE 10 # sorry
抽取 CSS
随着项目越来越复杂,更多的css插入到head中,开发环境还可以忍受,生产环境就太杂乱无章了,所以生产环境借用 mini-css-extract-plugin 插件将 CSS 提取到单独的文件中
安装 mini-css-extract-plugin
npm i mini-css-extract-plugin -D
配置如下:
// webpack.prod.js
const { merge } = require("webpack-merge")
const webpackCommon = require('./webpack.common.js')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = merge(webpackCommon, {
mode: "production",
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader"
]
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
"less-loader"
]
},
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/style.[contenthash:8].css'
})
]
})
PS:
style-loader不能与MiniCssExtractPlugin.loader同时使用。
CSS 压缩
现在可以把样式单独抽离了,但是代码并没有压缩,使用插件 css-minimizer-webpack-plugin 启动 CSS 压缩
安装 css-minimizer-webpack-plugin
npm i css-minimizer-webpack-plugin -D
通过配置 optimization.minimizer来添加插件 css-minimizer-webpack-plugin,但它会覆盖默认的压缩工具,所以需要在 webpack@5 中,可以使用 ... 语法来扩展现有的 minimizer(即 terser-webpack-plugin)
具体配置
// webpack.prod.js
const { merge } = require("webpack-merge")
const webpackCommon = require('./webpack.common.js')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = merge(webpackCommon, {
mode: "production",
optimization: {
minimizer: [
// 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
`...`,
new CssMinimizerPlugin(),
],
},
})
7、资源模块
资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。
包括四种类型:
asset/resource发送一个单独的文件并导出URL。之前通过使用file-loader实现。asset/inline导出一个资源的data URI。之前通过使用url-loader实现。asset/source导出资源的源代码。之前通过使用raw-loader实现。asset在导出一个data URI和发送一个单独的文件之间自动选择。之前通过使用url-loader,并且配置资源体积限制实现。
它们替换了 url-loader 和 file-loader 处理字体图片的能力。
我设置的资源类型为 asset,默认以 images/[hash][ext][query] 格式输出在dist文件夹内,那它在哪里配置呢?两种方式如下:
- 设置
output.assetModuleFilename来修改默认输出位置 - 设置
Rule.generator.filename,给不同的类型文件设置不同的输出位置,仅适用于asset和asset/resource模块类型。
webpack 将按照默认条件,自动地在 resource 和 inline 之间进行选择:小于 8kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类型。
可以通过在 webpack 配置的 module rule 层级中,设置 Rule.parser.dataUrlCondition.maxSize 选项来修改此条件
具体配置
// webpack.common.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
output: {
filename: "[name].[contenthash:8].js",
clean: true,
// assetModuleFilename: 'images/[hash][ext][query]'
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader?cacheDirectory',
},
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2)$/,
type: 'asset',
generator: {
// 与 output.assetModuleFilename 相同,并且仅适用于 asset 和 asset/resource 模块类型
// filename: "images/[hash][ext][query]"
},
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 默认是8kb
}
},
exclude: /node_modules/
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html",
hash: true, // 为CSS文件和JS文件引入时,添加唯一的hash,破环缓存非常有用
})
]
}
8、入口配置
entry 属性是 Webpack 配置文件中的一个重要选项,用于配置应用程序的入口点。entry 可以是一个字符串、一个数组或者一个对象,下面对这三种类型的用法进行详细说明。
- 字符串类型
当 entry 是一个字符串类型时,表示应用程序只有一个入口点,Webpack 会以该字符串为入口文件构建应用程序。示例:
module.exports = {
entry: './src/index.js'
};
- 数组类型
当 entry 是一个数组类型时,表示应用程序有多个入口点,Webpack 会根据数组中的每个字符串为入口文件,构建多个应用程序,并将它们打包到一个文件中。示例:
module.exports = {
entry: ['./src/index.js', './src/vendor.js']
};
- 对象类型
当 entry 是一个对象类型时,表示应用程序有多个入口点,每个入口点可以通过一个键值对的方式进行配置,其中键表示入口名称,值表示入口文件路径。Webpack 会根据对象中的每个键值对为入口文件,构建多个应用程序,并将它们打包到不同的文件中。示例:
module.exports = {
entry: {
main: './src/index.js',
vendor: './src/vendor.js'
}
};
9、出口配置
output.filename
output.filename 决定每个输出bundle的名称,一般以 bundle.[contenthash:8].js 命名;多入口的情况,一般以 [name].[contenthash:8].js
添加 contenthash,当内容有修改的情况,它是随时变化的,这样浏览器在访问资源的时候,就不会走缓存。
output.clean
设置为 true,打包前清空输出目录
排除某些文件使用 keep 属性
module.exports = {
//...
output: {
clean: {
keep: /assets/
},
},
};
// or
module.exports = {
//...
output: {
clean: {
keep (asset) {
// 如果文件较多,可以设置一个白名单
return asset.includes('assets');
},
},
},
};
10、开发服务器
webpack-dev-server
webpack-dev-server 是一个基于 Webpack 的开发服务器,用于在开发过程中快速启动 Web 应用程序,并提供实时重载、热替换和调试功能。
使用 webpack-dev-server 可以在本地快速启动一个开发服务器,自动构建和打包项目文件,并将这些文件提供给浏览器访问。在开发过程中,只需要修改源代码,webpack-dev-server 就会自动重新编译和打包项目文件,并自动刷新浏览器页面,让开发者可以实时看到修改后的效果。
webpack-dev-server 主要有以下几个作用:
- 提供一个本地开发服务器,方便开发人员进行开发和调试。
- 支持实时重载,自动刷新浏览器页面,让开发者可以实时看到修改后的效果。
- 支持热替换(Hot Module Replacement,简称 HMR),可以在不刷新浏览器页面的情况下,更新修改的模块,提高开发效率。
- 支持 source map,可以在浏览器中方便地进行调试。
安装 webpack-dev-server
npm i webpack-dev-server -D
更改 package.json 文件的 scripts,将 dev 改为 webpack serve --config build/webpack.dev.js,运行 npm run dev,访问 http://localhost:8080/ 可以看到自己的页面
以下属性比较常用:
port设置端口号proxy启用代理
module.exports = {
devServer: {
proxy: {
'/api': 'http://localhost:3000',
},
},
};
对 /api/users 的请求会将请求代理到 http://localhost:3000/api/users
如果接口没有 /api,则需要重写路径
module.exports = {
devServer: {
static: './dist', // web server以哪个目录作为根目录
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: { '^/api': '' },
},
},
},
};
hot启用webpack的Hot Module Replacement功能 要完全启用HMR,需要webpack.HotModuleReplacementPlugin。如果使用--hot选项启动webpack或webpack-dev-server,该插件将自动添加
HRM
模块热替换,允许在运行时更新所有类型的模块,而无需完全刷新。 具体设置:
// webpack.dev.js
const { merge } = require("webpack-merge")
const webpackCommon = require('./webpack.common.js')
const webpack = require("webpack")
module.exports = merge(webpackCommon, {
mode: "development",
module: {
rules: [
{
test: /\.css$/,
use: [
"style-loader",
"css-loader",
"postcss-loader"
]
},
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
"postcss-loader",
"less-loader"
]
}
]
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
devServer: {
hot: true,
proxy: {
'/api': 'http://localhost:3000',
},
}
})
此时热更新并不能生效,需要设置
target为web,因为当存在.browserslistrc文件时会根据配置推荐出运行环境
之后修改样式文件不会重新刷新页面,修改js文件,页面又重新刷新了
需要在 index.js 配置
if(module && module.hot) {
module.hot.accept() // 接受自更新
}
live reloading 和 HMR 的区别
live reloading 和 HMR(Hot Module Replacement) 都是在开发过程中用于提高开发效率的技术,但它们的工作方式和使用场景略有不同。
live reloading 通常是通过监视文件系统中的源代码文件,并在文件发生更改时重新构建应用程序,并自动刷新浏览器页面,以显示最新的更改。这种方式需要重新加载整个页面,即使只有少量的代码更改,也需要重新加载整个页面。
相比之下,HMR 可以在不刷新整个页面的情况下,通过更新模块来应用更改。这意味着开发者可以在应用程序运行时实时地更改模块,而无需重新加载整个页面。这样可以极大地提高开发效率,尤其是在大型应用程序中,其中代码更改的时间和成本可能会很高。
另外,HMR 还可以保存应用程序的状态,并在应用程序的模块发生更改时保留该状态,这可以确保在应用程序的开发过程中不会丢失用户的当前状态或数据。
因此,live reloading 适用于简单的应用程序或项目,而 HMR 则适用于复杂的应用程序或项目,可以极大地提高开发效率。
10、devtool配置
当代码中出现错误时,却找不到出现错误的具体文件和具体行,这时就需要选择一种 source map 格式来增强调试过程。
开发环境,需要知道具体的报错文件和具体行,并需要源代码快速的找到报错具体位置,所以配置 eval-cheap-module-source-map
生产环境,需要知道具体的报错文件和具体行,但并不需要暴露源代码,所以配置 nosources-source-map
参考
结语
到此,webpack一些基础的配置已经完成,第一次研究 webpack,很多内容理解的可能不够深刻,欢迎大家批评指正。
码字不易,点赞、评论、收藏三连哟!
转载自:https://juejin.cn/post/6983901110058614815