一、 webpack构建流程
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
- 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
- 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
- 确定入口:根据配置中的 entry 找出所有的入口文件
- 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
- 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
- 出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。
总结来说:
- 初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler
- 编译:从 Entry 出发,针对每个 Module 串行调用对应的 Loader 去翻译文件的内容,再找到该 Module 依赖的 Module,递归地进行编译处理
- 输出:将编译后的 Module 组合成 Chunk,将 Chunk 转换成文件,输出到文件系统
二、 webpack打包入口
入口可以使用 entry 字段来进行配置,webpack 支持配置多个入口来进行构建。
Webpack资源入口,表示它是从哪个JS文件开始打包的。Webpack要找到这个文件,是通过context和entry这两个参数。
context是一个绝对路径,是基础目录的意思。entry是一个相对路径,它与context拼接起来,就是Webpack打包的入口文件了。
Webpack的资源入口与出口是紧密相关的。
2.1 入口entry是字符串形式
module.exports = {
entry: './src/index.js'
}
// 上述配置等同于
module.exports = {
entry: {
main: './src/index.js'
}
}
2.2 入口entry是数组形式
module.exports = {
entry: ['core-js/stable', 'regenerator-runtime/runtime', './a.js'],
};
它表示的含义是数组最后一个文件是资源的入口文件,数组其余文件会预先构建到入口文件。
上面的配置和下面是等效的。
// a.js
import 'core-js/stable';
import 'regenerator-runtime/runtime';
// webpack.config.js
module.exports = {
entry: './a.js',
};
数组形式的入口本质还是一个入口。
2.3 入口entry是对象形式
入口entry是对象形式的又称之为多入口配置。之前我们讲的都是单入口的配置,本质上打包后生成一个JS文件。
多入口配置,本质上打包后生成多个JS文件。
const path = require('path');
module.exports = {
entry: {
app: ['core-js/stable', 'regenerator-runtime/runtime', './a.js'],
vendor: './vendor'
},
output: {
path: path.resolve(__dirname, ''),
filename: '[name].js'
},
mode: 'none'
};
上方的配置分别从两个入口文件打包,每个入口文件各自寻找自己依赖的文件模块打包成一个JS文件,最终得到两个JS文件。
2.4 入口entry函数形式
函数形式的入口,Webpack取函数返回值作为入口配置,返回值是上述3种之一即可。
函数形式的entry,可以用来做一些额外的逻辑处理,不过在自己搭脚手架的很少使用。
三、 webpack打包输出
output就是资源出口配置项。output是一个对象,它有几个重要的属性filename、path、publicPath和chunkFilename。
const path = require('path');
module.exports = {
output: {
// 目标输出目录 path 的绝对路径。
// path: '/dist',
path: path.resolve(__dirname, './dist'),
// filename 用于输出文件的文件名。
filename: '[name].[hash:8].js'
}
};
3.1 Webpack的output.filename
filename是打包后生成的资源名。
filename除了可以是一个文件名称,也可以是相对地址,例如’./js/bundle.js’。
最终打包输出的文件是path绝对路径与filename的拼接后的地址。
filename支持类似变量的方式生成动态文件名,例如:
- [hash]-bundle.js,其中方括号很像占位符,hash表示特定的动态值。
- [name]表示的是chunk的名称,chunk这个概念可以单独写一节,简单理解的话就是打包过程中,一个资源入口代表一个chunk,一个异步模块资源也代表一个chunk
其中字符串和数组形式的entry,output.filename的[name]值都是main。
特定动态值还有[hash]、[chunkhash]和[contenthash]等
- hash是根据打包中所有的文件计算出的hash值。在一次打包中,所有出口文件的filename获得的[hash]都是一样的。
- chunkhash是根据打包过程中当前chunk计算出的hash值。如果Webpack配置是多入口配置,那么通常会生成多个chunk,每个chunk对应的出口filename获得的[chunkhash]是不一样的。这样可以保证打包后每一个JS文件名都不一样
- contenthash有点像chunkhash,是根据打包时CSS内容计算出的hash值。一般在使用提取CSS的插件的时候,我们使用contenthash
plugins:[ new miniExtractPlugin({ filename: 'main.[contenthash:8].css' }) ]
3.2 Webpack的output.path
path表示资源打包后输出的位置,该位置地址要是绝对路径。如果你不设置它,webpack4默认为dist目录。
需要注意的是,path输出路径表示的是在磁盘上构建生成的真实文件存放地址。我们在开发时,一般会用webpack-dev-server开启一个本地服务器,这个服务器可以自动刷新和热加载等,它生成的文件是在内存中而不是在电脑磁盘。该内存中的文件路径,我们会用Webpack配置文件的devServer配置项的publicPath表示,它虚拟映射了电脑磁盘路径。
3.3 Webpack的output.publicPath
output中的publicPath表示的是资源访问路径。
- path: 资源输出位置表示的是本次打包完成后,资源存放的磁盘位置。
- publicPath:资源存放到磁盘了,浏览器如何知道该资源存放在什么位置呢?这个时候需要我们指定该资源的访问路径,这个访问路径就是用output.publicPath来表示的
output.publicPath的表示形式有两大类:相对路径与绝对路径。
3.3.1 相对路径
相对路径又可以分类两种情况,第一种,它相对于当前浏览的HTML页面路径取值的。
1.)output.publicPath以"./“或”…/"等开头,表示要访问的资源以当前页面url作为基础路径。
publicPath: "" // 资源的访问地址是https://www.apple.com/ipad/bundle-3fa2.js
publicPath: "../dist/" // 资源的访问地址是https://www.apple.com/dist/bundle-3fa2.js
publicPath: "./dist/" // 资源的访问地址是https://www.apple.com/ipad/dist/bundle-3fa2.js
2.)output.publicPath以"/"开头,表示要访问的资源以当前页面的服务器地址作为基础路径。
publicPath: "/" // 资源的访问地址是https://www.apple.com/bundle-3fa2.js。
publicPath: "/dist/" // 资源的访问地址是https://www.apple.com/dist/bundle-3fa2.js。
3.3.2 绝对路径
output.publicPath的值以HTTP协议名称开始。一般在使用CDN的时候,因为CDN的域名与我们自己服务器的域名不一样,我们会采用这种方式。
Web中常见的协议名称有http和https,例如我的网站 https://www.jiangruitao.com/ 的协议名称就是https。
还有一种叫做相对协议的形式,它以 // 开头,也就是省略了前面的https:或http:。
在使用相对协议的时候,浏览器会对前页面使用的协议名称与相对协议拼接。
下面看一下output.publicPath的值以协议开始的例子,在以协议开始的publicPath时,资源的访问地址是publicPath代表的绝对路径加上资源名称。
publicPath: "https://cdn.apple.net/" // 资源的访问地址是https://cdn.apple.net/bundle-3fa2.js
publicPath: "http://cdn.apple.net/" // 资源的访问地址是http://cdn.apple.net/bundle-3fa2.js
publicPath: "//cdn.apple.net/dist/" // 资源的访问地址是https://cdn.apple.net/dist/bundle-3fa2.js
3.4 Webpack的output.chunkFilename
chunkFilename也是用来表示打包后生成的文件名,那它和filename有什么区别呢?
它表示的是打包过程中非入口文件的chunk名称,通常在使用异步模块的时候,会生成非入口文件的chunk。
四、 Webpack预处理器loader
预处理器loader本质是一个函数,它接受一个资源模块,然后将其处理成Webpack核心能使用的形式。
webpack 中提供一种处理多种文件格式的机制,便是使用 loader。我们可以把 loader 理解为是一个转换器,负责把某种文件格式的内容转换成 webpack 可以支持打包的模块。
4.1 postcss-loader
自动检测兼容性给各个浏览器加个内核前缀的插件
配置 .browserslistrc
,标明支持的浏览器:
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
not dead
IE 9-11
Firefox >= 38
Chrome >= 43
Edge 13
Safari >= 8
配置 postcss.config.js
,配置 postcss-loader
参数:
module.exports = {
plugins: [
require('autoprefixer')
]
}
4.2 css-loader
加载.css
文件
4.3 style-loader
使用<style>
将css-loader内部样式注入到我们的HTML页面
4.4 less-loader
加载 .less
文件
4.5 MiniCssExtractPlugin.loader
配合 MiniCssExtractPlugin
插件,将 css 提取到文件中。
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
{
loader: 'css-loader'
},
{
// 自动检测兼容性给各个浏览器加个内核前缀的插件
loader: 'postcss-loader'
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin(
{
filename: '[name].[hash:8].css',
chunkFilename: '[id].css'
}
),
]
};
4.6 babel-loader
Babel是一个JavaScript编译器,能够让我们放心的使用新一代JS语法。
配置 .babelrc 或者 options
使用时需要安装相关相关依赖:
npm install babel-loader @babel/core @babel/plugin-proposal-class-properties @babel/plugin-transform-runtime @babel/preset-env @babel/runtime -D
相关配置如下:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
"@babel/preset-env"
],
plugins: [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-syntax-dynamic-import"
]
}
}
]
}
]
}
};
4.7 html-loader
解析 .html
4.8 url-loader
url-loader
会把我们的图片使用base64的形式编码成另外一种字符串,网页是可以识别这种编码的东西的,这样的好处是,它减少了图片的请求,你只要请求回了这个页面,图片也就过来了,可以减少网络的请求,但是如果图片过大,这个字符串就会变得特变大,让加载的文件变得特别大。
webpack 5已经弃用url-loader、file-loader,使用asset modules代替(https://blog.csdn.net/gtLBTNq9mr3/article/details/117173678)
4.9 parser
可用作 url-loader
的功能。
{
test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot|webp)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 1024 * 10
}
}
}
五、 Webpack插件plugin
通常plugins数组每一个元素都是插件构造函数New出来的一个实例,根据每一个插件的特点,可能会需要向其参数里传递各种配置参数,这个时候需要参阅该插件的文档来进行配置。
现在广泛使用的插件都有默认的参数,可以免去配置,只有在需要特殊处理时,我们进行手动配置参数。
5.1 CleanWebpackPlugin
删除文件
默认删除dist
目录,也可使用自定义配置:
const CleanWebpackPlugin = require('clean-webpack-plugin');
new CleanWebpackPlugin(
['dist/main.*.js','dist/manifest.*.js'], // 匹配删除的文件
{
root: path.resolve(__dirname), // 根目录
verbose: true, // 开启在控制台输出信息
dry: false // 启用删除文件
}
)
5.2 HtmlWebpackPlugin
将 webpack中entry
配置的相关入口chunk 和 extract-text-webpack-plugin
抽取的css样式
插入到该插件提供的template
或者templateContent
配置项指定的内容基础上生成一个html文件,具体插入方式是将样式link
插入到head
元素中,script
插入到head
或者body
中。
const HtmlWebpackPlugin = require('html-webpack-plugin');
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
favicon: `node_modules/@cube/cf-theme/${helpers.consoleTheme}/theme/default/images/favicon.ico`,
framework_resource_dir: helpers.frameworkServerBase,
business_resource_dir: helpers.getPublicServerBase(isProd),
inject: false,
// html压缩
minify: {
collapseWhitespace: true, // 移除空格
removeComments: true, //移除注释
}
})
- title: 用来生成页面的 title 元素
- filename: 输出的 HTML 文件名,默认是 index.html, 也可以直接配置带有子目录。
- template: 模板文件路径,支持加载器,比如 html!./index.html
- inject: true | ‘head’ | ‘body’ | false ,注入所有的资源到特定的 template 或者 templateContent 中,如果设置为 true 或者 body,所有的 javascript 资源将被放置到 body 元素的底部,‘head’ 将放置到 head 元素中。
- favicon: 添加特定的 favicon 路径到输出的 HTML 文件中。
- minify: {} | false , 传递 html-minifier 选项给 minify 输出
- hash: true | false, 如果为 true, 将添加一个唯一的 webpack 编译 hash 到所有包含的脚本和 CSS 文件,对于解除 cache 很有用。
- cache: true | false,如果为 true, 这是默认值,仅仅在文件修改之后才会发布文件。
- showErrors: true | false, 如果为 true, 这是默认值,错误信息会写入到 HTML 页面中
- chunks: 允许只添加某些块 (比如,仅仅 unit test 块)
- chunksSortMode: 允许控制块在添加到页面之前的排序方式,支持的值:‘none’ | ‘default’ | {function}-default:‘auto’
- excludeChunks: 允许跳过某些块,(比如,跳过单元测试的块)
minify各配置项用法说明:
- removeComments(默认值false):清理html中的注释
- collapseWhitespace(默认值false):清理html中的空格、换行符
- minifyCSS(默认值false):压缩html内的样式
- minifyJS(默认值false):压缩html内的js
- removeEmptyElements(默认值false)清理内容为空的元素,但这个功能慎用,空元素可能用于占位或在js逻辑里有填充动作
- caseSensitive(默认值false):以区分大小写的方式处理自定义标签内的属性
- removeScriptTypeAttributes(默认值false):去掉script标签的type属性
- removeStyleLinkTypeAttributes(默认值false):去掉style和link标签的type属性
5.3 MiniCssExtractPlugin
提取 css 放到文件中,需要配合 MiniCssExtractPlugin.loader 使用
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
new MiniCssExtractPlugin(
{
filename: '[name].[hash:8].css',
chunkFilename: '[id].css'
}
)
5.4 OptimizeCssAssetsPlugin
优化 css 资源,压缩 css 文件
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
new OptimizeCssAssetsPlugin();
5.5 ProvidePlugin
webpack配置ProvidePlugin后,在使用时将不再需要import和require进行引入,直接使用即可。
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
});
const time = require('../time/time.js');
import './hello.word.css';
function writeToHtml(){
const world = 'hello world';
console.log(world);
console.log(time);
const htmlElement = $('.container');
const htmlElement_2 = jQuery('.container');
console.log(htmlElement);
console.log(htmlElement_2);
}
writeToHtml();
5.6 CopyWebpackPlugin
单个文件或整个目录复制到构建目录
const CopyWebpackPlugin = require('copy-webpack-plugin');
new CopyWebpackPlugin({
patterns: [
{
from: 'src/config'
},
{
from: 'src/img',
to: 'img'
}
]
}),
5.7 DefinePlugin
允许创建一个在编译时可以配置的全局常量
new webpack.DefinePlugin({
VERSION: JSON.stringify('1-2-4')
})
const time = require('../time/time.js');
import './hello.word.css';
function writeToHtml(){
const world = 'hello world';
console.log(world);
console.log(time);
const htmlElement = $('.container');
const htmlElement_2 = jQuery('.container');
console.log(htmlElement);
console.log(htmlElement_2);
console.log(VERSION);
}
writeToHtml();
5.8 体积分析:使用webpack-bundle-analyzer
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
};
- 构建完成后会在 8888 端口展示大小
- 了解更多
- 可以分析哪些问题?
- 依赖的第三方模块文件大小
- 业务里面的组件代码大小
5.9 速度分析:使用speed-measure-webpack-plugin
使用:将默认配置文件包裹起来
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
//将默认的webpack配置文件包裹起来
const webpackConfig = smp.wrap({
plugins: [
new MyPlugin(),
new MyOtherPlugin()
]
});
- 了解更多
- 速度分析插件作用
- 分析整个打包总耗时
- 每个插件和loader的耗时情况
六、 webpack优化配置optimization
6.1 配置js压缩
配置了 OptimizeCssAssetsPlugin
ccs压缩后,webpack就不会自动压缩js,需自己配置。
module.exports = {
// 优化配置
optimization: {
// 自己配置 js 压缩,配置了 OptimizeCssAssetsPlugin ccs压缩后要想压缩js,必须配置此处
minimize: true,
minimizer: [
new TerserPlugin()
]
}
};