参考链接:
1、Webpack认知
- webpack是什么?构建工具?静态资源打包器?
- 构建工具是指:将例如把less转化为css的小工具、将es6转化为es5的小工具等等打包构建成一个大工具,即webpack,此时我们只需要使用webpack一个工具就可以了。
- 静态资源打包器是指:它会以一个或多个文件作为打包的入口,将我们整个项目所有文件编译组合成一个或多个文件输出出去。输出的文件就是编译好的文件,就可以在浏览器段运行了。Webpack 输出的文件叫做 bundle。
webpack怎么打包的?
-
总体来说分为三个阶段:初始化阶段、编译阶段、输出文件阶段
-
初始化阶段:
- 初始化参数: 从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。这个过程中还会执行配置文件中的插件实例化语句 new Plugin()。
- 初始化默认参数配置: new WebpackOptionsDefaulter().process(options)
- 实例化Compiler对象:用上一步得到的参数初始化Compiler实例,Compiler负责文件监听和启动编译。Compiler实例中包含了完整的Webpack配置,全局只有一个Compiler实例。
- 加载插件: 依次调用插件的apply方法,让插件可以监听后续的所有事件节点。同时给插件传入compiler实例的引用,以方便插件通过compiler调用Webpack提供的API。
- 处理入口: 读取配置的Entrys,为每个Entry实例化一个对应的EntryPlugin,为后面该Entry的递归解析工作做准备。
-
编译阶段:
- run阶段:启动一次新的编译。this.hooks.run.callAsync。
- compile: 该事件是为了告诉插件一次新的编译将要启动,同时会给插件带上compiler对象。
- compilation: 当Webpack以开发模式运行时,每当检测到文件变化,一次新的Compilation将被创建。一个Compilation对象包含了当前的模块资源、编译生成资源、变化的文件等。Compilation对象也提供了很多事件回调供插件做扩展。
- make:一个新的 Compilation 创建完毕主开始编译 完毕主开始编译this.hooks.make.callAsync。
- addEntry: 即将从 Entry 开始读取文件。
- _addModuleChain: 根据依赖查找对应的工厂函数,并调用工厂函数的create来生成一个空的MultModule对象,并且把MultModule对象存入compilation的modules中后执行MultModule.build。
- buildModules: 使用对应的Loader去转换一个模块。开始编译模块,this.buildModule(module) buildModule(module, optional, origin,dependencies, thisCallback)。
- build: 开始真正编译模块。
- doBuild: 开始真正编译入口模块。
- normal-module-loader: 在用Loader对一个模块转换完后,使用acorn解析转换后的内容,输出对应的抽象语法树(AST),以方便Webpack后面对代码的分析。
- program: 从配置的入口模块开始,分析其AST,当遇到require等导入其它模块语句时,便将其加入到依赖的模块列表,同时对新找出的依赖模块递归分析,最终搞清所有模块的依赖关系。
-
输出文件阶段
- seal: 封装 compilation.seal seal(callback)。
- addChunk: 生成资源 addChunk(name)。
- createChunkAssets: 创建资源 this.createChunkAssets()。
- getRenderManifest: 获得要渲染的描述文件 getRenderManifest(options)。
- render: 渲染源码 source = fileManifest.render()。
- afterCompile: 编译结束 this.hooks.afterCompile。
- shouldEmit: 所有需要输出的文件已经生成好,询问插件哪些文件需要输出,哪些不需要。this.hooks.shouldEmit。
- emit: 确定好要输出哪些文件后,执行文件输出,可以在这里获取和修改输出内容。
- done: 全部完成 this.hooks.done.callAsync。
2、基础
2.1 基础概念
2.1.1 5个基础概念
-
entry(入口):Webpack 从哪个文件开始打包
-
output(输出):Webpack 打包完的文件输出到哪里去,如何命名等
-
loader(加载器):webpack 本身只能处理 js、json 等资源,其他资源借助 loader解析
-
plugins(插件):扩展 Webpack 的功能,可以执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量等
-
mode(模式):主要有开发模式(development)、生产模式(production) 两种模式
- 开发环境(development):让代码在本地调试运行的环境
- 生产环境(production):让代码优化上线运行的环境
在项目根目录下新建文件:webpack.config.js, Webpack 是基于 Node.js 运行的,所以采用 Common.js 模块化规范
2.1.2 简单配置文件
// Node.js的path模块来处理文件路径
const path = require("path");
module.exports = {
// 入口路径
entry: "./src/main.js",
// 输出
output: {
// path: 文件输出目录,必须是绝对路径
// path.resolve()方法返回一个绝对路径
// __dirname 表示当前文件的文件夹绝对路径
path: path.resolve(__dirname, "dist"),
// filename: 输出文件名
filename: "main.js",
},
// 加载器
module: {
rules: [],
},
// 插件
plugins: [],
// 模式
mode: "development", // 开发模式
};
2.1.3 运行指令
npx webpack
2.2 基础配置
2.2.1 样式资源处理
- 使用:一般利用"style-loader", "css-loader", "less-loader", "sass-loader", "stylus-loader"等,部分代码示例
module: {
rules: [
{
test: /.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
],
},
-
过程:
- sass-loader:加载 SASS / SCSS 文件并将其编译为 CSS
- css-loader:解析 css 代码中的 url、@import语法像import和require一样去处理css里面引入的模块
- style-loader:帮我们直接将css-loader解析后的内容挂载到html页面当中
-
注意点:loaders 是从右到左、从下到上执行的
2.2.2 js处理
Babel
-
作用:兼容es6语法,使代码能在当前和旧版本的浏览器中运行
-
使用:
-
配置文件:在项目根目录babel.config.js/.json或者 .babelrc(.js/.json)或者package.json 中 babel,
- 示例
module.exports = { // presets预设 // @babel/preset-env: 允许使用最新的 JavaScript // @babel/preset-react:用来编译 React jsx 语法的预设 // @babel/preset-typescript:用来编译 TypeScript 语法的预设 presets: ["@babel/preset-env"], }; -
webpack.config.js代码片段
{ test: /.js$/, exclude: /node_modules/, // 排除node_modules代码不编译 loader: "babel-loader", },
-
-
babel运行过程:
-
解析:接收代码并输出ast
- 词法分析:把字符串形式的代码转化为令牌流,把令牌看作是一个扁平的语法片段,数组每个type有一组属性来描述令牌,和ast节点一样,有start end和loc属性
- 语法分析:会把一个令牌流转换为AST形式。
-
转换:接收 AST 并对其进行遍历,在此过程中对节点进行添加、更新及移除等操作。
- Babel提供了@babel/traverse(遍历)方法维护这AST树的整体状态,并且可完成对其的替换,删除或者增加节点,这个方法的参数为原始AST和自定义的转换规则,返回结果为转换后的AST。
-
生成:深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串。
- Babel使用 @babel/generator 将修改后的 AST 转换成代码,生成过程可以对是否压缩以及是否删除注释等进行配置,并且支持 sourceMap。
-
Eslint
-
作用:检测 js 和 jsx 语法
-
使用:
-
配置文件:在项目根目录.eslintrc(.js/.json)或者 package.json 中 eslintConfig,
- 自行配置:parserOptions字段解析选项--设定ES 语法版本、是否开启jsx检查等,rules字段配置自定义的检查规则。
- extends 继承现有的规则,示例
// 例如在React项目中引入React-cli官方规则 module.exports = { extends: ["react-app"], rules: { // 自定义规则会覆盖继承规则 eqeqeq: ["warn", "smart"], }, }; -
webpack.config.js
const ESLintWebpackPlugin = require("eslint-webpack-plugin"); plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "src"), }), ], -
.eslintignore- 指定需要忽略不做语法检查的文件,例如dist
dist
-
-
eslint语法检查过程:
- 首先读取各种配置
- 加载插件,获取到插件的规则
- 读取parser配置,解析获取ast
- 深度优先遍历ast收集节点,每个节点会被收集两次,递一次归一次。
- 注册所有规则配置中的节点监听函数,同时注入一些context。
- 遍历前面收集到的ast节点,并且触发相应的节点监听函数,并获取所有的link问题。
- 根据注释中的禁用命令进行过滤。
- 最后修复。
2.2.3 html处理
- 使用:一般用html-webpack-plugin插件,部分代码示例
const HtmlWebpackPlugin = require("html-webpack-plugin");
plugins: [
new HtmlWebpackPlugin({
// 以 index.html 为模板创建文件,新的html文件内容和源文件一致,并且自动引入打包生成的js等资源
template: path.resolve(__dirname, "public/index.html"),
}),
],
-
过程:
-
其他:html-webpack-plugin提供了不同阶段的hook,方便在处理html文件过程中进行其他操作
2.2.4 其他资源处理
-
字体图标以及其他资源
- 利用webpack5内置的asset/resource进行处理
-
{ test: /.(ttf|woff2?|map4|map3|avi)$/, type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", }, },
2.2.5 搭建开发服务器
- 作用:自动编译,不用每次更新代码都手动运行查看效果
devServer:{
host:"localhost",//启动服务器域名
port:"8080",//启动服务器端口号
open:true,//是否自动打开浏览器
}
开发服务器在内存中编译打包,不会自动输出dist文件
2.2.6 css处理
-
提取css为单独文件
- 利用mini-css-extract-plugin
-
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module: { rules: [ { test: /.less$/, use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"], }, ], },
-
css兼容性
- 利用postcss-loader postcss postcss-preset-env
-
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module: { rules: [ { test: /.less$/, use: [MiniCssExtractPlugin.loader, "css-loader", { loader: "postcss-loader", options: { postcssOptions: { plugins: [ "postcss-preset-env", ], }, }, }, "less-loader"], }, ], },
-
css压缩
- 利用css-minimizer-webpack-plugin
-
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); plugins: [ new CssMinimizerPlugin(), ],
3、优化配置
3.1 SourceMap
-
作用:源代码映射:用来建立源代码和构建后代码一一映射的关系。
-
SourceMap可选值很多,主要关注:
- performance(构建和重构的速度)
- production(是否适合生产模式)
- quality(代码在devtool中的代码展示情况,有chunk代码包、分模块chunk代码包、定位到行、源代码定位到行等情况)。
-
对于开发环境,通常希望更快速的 source map,往往需要以添加到 bundle 中增加体积为代价,但是对于生产环境,则希望更精准的 source map,其打包速度往往较慢。
-
举例:
- 开发模式下:
cheap-module-source-map特点是打包编译速度快,只包含行映射,没有列映射 -
module.exports = { // 其他省略 mode: "development", devtool: "cheap-module-source-map", }; - 生产模式下:
hidden-source-map,特点是包含行/列映射,但是打包编译速度更慢,不会在 bundle 末尾追加注释
- 开发模式下:
-
module.exports = {
// 其他省略
mode: "production",
devtool: "hidden-source-map",
};
3.2 HotModuleReplace
devServer:{
host:"localhost",
port:"8080",
open:true,
hot:true,//打开热模式替换
}
- 开发模式下的style-loader帮我们做了css的热模块替换,所以我们配置就可以用了。
- js的热模块替换需要单独配置,有的并不支持,先用if进行判断,然后用module.hot.accpet函数。
if(module.hot){
module.hot.accpet("../js/a",callback())
module.hot.accpet("../js/b",callback())
}
- 实际开发中,如果用到vue、react,可以利用vue-loader、react-hot-loader来帮助自动完成热模块替换,不需要上述手动配置。
3.3 OneOf
- 作用:限制每个文件只能被一个loader处理,提高打包创建速度。
- 用法:将所有loader放在OneOf对象里即可
modules:{
rules:[{
oneOf:{
...loaders
}
}]
}
3.4 include/exclude
- include限定只处理某些文件,一般处理js,exclude设定排除某些文件不处理。例如:
include:path.resolve(__dirname,"../src"),//只处理src下的文件
exclude:/node_module/,//排除node_module不处理
3.5 cache
- 进行eslint和babel缓存。先缓存eslint检查和babel编译的结果,可以在后续(第2、3…次)打包时提高效率。
- 在babel中:
{
test: /.js$/,
loader: "babel-loader",
options: {
cacheDirectory: true, // 开启babel编译缓存
cacheCompression: false, // 缓存文件不要压缩
},
}
- 在eslint中:
new ESLintWebpackPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",
cache: true, // 开启eslint检查缓存
// 指定缓存目录
cacheLocation: path.resolve(__dirname,"../node_modules/.cache/.eslintcache"),
}),
3.6 Thead多进程打包
- 多进程打包是指开启电脑的多个进程,同时进行一套操作。主要是处理js文件,也就是针对eslint 、babel、Terser 三个工具提升它们的运行速度。
注意:仅适用于打包内容多、特别耗时的操作中,因为每个进程启动大约有600毫秒左右的启动时间。- 获取CPU核数:
//引用node.js OS模块
const os = require("os")
//获取CPU核数
const thread = os.cpus().length
const TerserPlugin = require("terser-webpack-plugin");
- 配置babel
{
test: /.js$/,
include: path.resolve(__dirname, "../src"), // 也可以用包含
use: [
{
loader: "thread-loader", // 开启多进程
options: {
workers: threads, // 数量
},
},
{
loader: "babel-loader",
options: {
cacheDirectory: true, // 开启babel编译缓存
},
},
],
},
- eslint开启多进程在plugin中加上threads属性即可。
new ESLintWebpackPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",
cache: true, // 开启缓存
// 缓存目录
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/.eslintcache"
),
threads, // 开启多进程
}),
3.7 codeSplit
3.7.1 多入口
3.7.1.1 多入口打包输出多个文件
module.exports = {
entry:{
name1:'./src/app.js',
name2:'./src/main.js',
},
output:{
path:path.resolve(__dirname,"dist"),
filename:"[name].js",//以chunk的name命名出口文件
}
}
3.7.1.2 提取公共模块
- 如果存在公共模块,可以提取公共模块,让公共代码只打包一份,优化代码运行性能。配置如下:
module.exports = {
//涉及到压缩代码优化的部分配置
optimization: {
splitChunks: {
chunks: "all", // 对所有模块都进行分割
// 以下是默认值,可以不写
//minSize: 20000, // 分割代码最小体积,单位为bt
//minRemainingSize: 0, // 类似于minSize,确保最后提取的文件大小不能为0
//minChunks: 1, // 至少被引用的次数,一次以上才会代码分割
//maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量。即:页面同时加载不超过30个,避免请求过多造成服务器负担过大。
//maxInitialRequests: 30, // 入口js文件最大并行请求数量
//enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
cacheGroups: {// 组,哪些模块要打包到一个组
// defaultVendors: { // 组名
// test: /[/]node_modules[/]/, // 指定要打包到该组的模块
// priority: -10, // 优先级权重
// reuseExistingChunk: true, // 是否复用
// },
default: {
// 其他没有写的配置会使用上面的默认值
minChunks: 2,//至少被不同入口引用两次
priority: -20,// 优先级权重
reuseExistingChunk: true,
},
},
},
},
};
3.7.1.3 多入口按需加载
- 使用import 动态导入,webpack会自动将动态导入的文件代码分割,在使用时才加载对应的文件,即使只被引用了一次,也会代码分割。 示例:
document.getElementById("btn").onclick = function () {
//export default
import("./test1.js").then((res) => {
alert("模块加载成功",res.default('arg'));
});
//export
import(/* webpackChunkName: "test2" */"./test2.js").then(({res}) => {
alert("模块加载成功",res('arg'));
});
};
3.7.2 单入口
- 单入口一般只需要下述配置,webpack默认配置中存在将node_modules的代码单独打包的配置,并且会在使用import动态导入时自动进行代码分割。例如我们进行路由懒加载时就是用到了这个默认配置。
module.exports = {
entry: "./src/main.js",// 单入口
optimization: {
splitChunks: {
chunks: "all", // 对所有模块都进行分割
},
};
3.7.3 模块命名
-
一般采用统一命名,filename、chunkFilename、assetModuleFilename分别用作入口文件打包输出资源命名、动态导入输出文件命名和静态资源命名。
-
其中:
- filename的name单文件默认为main,多文件则是对象写法中的key;
- chunkFilename的name在动态导入时采用
webpackChunkName: "name"来进行命名 - assetModuleFilename同理,可用hash也可用 contenthash 分别对应模块和模块内容的hash值。
module.exports = {
output: {
path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
filename: "static/js/[name].js", // 入口文件打包输出资源命名方式
chunkFilename: "static/js/[name].chunk.js", // 动态导入输出资源命名方式
assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等静态资源统一命名
clean: true,
},
},
3.9 Preload/Prefetch
- 作用:用于提前加载资源,
preload立即加载,prefetch在空闲时间加载。 - 区别:
preload只能加载当前页面的资源,且其优先级高于prefetch,prefetch可以加载下个页面的资源。 - 使用场景:使用了代码分割并按需加载资源,但用户点击对应按键才加载资源,如果资源体积较大可能导致卡顿。
- 存在问题:
兼容性不好,具体兼容性可查看 "preload" | Can I use... Support tables for HTML5, CSS3, etc "Prefetch" | Can I use... Support tables for HTML5, CSS3, etc
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
module.exports = {
plugins: [
new PreloadWebpackPlugin({
rel: "preload", // 'prefetch'
as: "script",
}),
],
};
3.10 Core-js
-
作用:处理ES6 以及以上 API 的 兼容性,例如 async 函数、promise 对象、数组的一些方法(includes)等 。
-
自动按需引入:
- 首先下载
core-js,然后在babel.config.js中配置core.js,webpack会根据代码中使用的语法对应引入core.js中的模块,而不引入全部的core.js
module.exports = { presets: [ [ "@babel/preset-env", // 按需加载core-js的polyfill { useBuiltIns: "usage", corejs: { version: "3", proposals: true } }, ], ], }; - 首先下载
3.11 Network Cache
-
作用:缓存静态资源,使得二次请求资源速度更快 。
-
问题:如果前后输出的文件名一样,浏览器会直接读取缓存,不会加载新资源,项目也就没法更新了,因此要确保更新前后文件名不一致。
-
使用hash 值:
-
示例
output: { path: path.resolve(__dirname, "../dist"), // [contenthash:8]使用contenthash,取8位长度 filename: "static/js/[name].[contenthash:8].js", chunkFilename: "static/js/[name].[contenthash:8].chunk.js", }, -
css文件命名
new MiniCssExtractPlugin({ // 定义输出文件名和目录 filename: "static/css/[name].[contenthash:8].css", chunkFilename: "static/css/[name].[contenthash:8].chunk.css", }), -
其他问题:文件存在引用关系时,一个文件名发生了变化,间接导致引用它的文件 文件名也发生了变化
-
解决办法:将 hash 值单独保管在一个 runtime 文件中。在optimization中写入runtimeChunk
runtimeChunk: { name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名规则 },
-
小结:
-
从性能优化来看
HotModuleReplacement、OneOf、Include、exclude、Cache、Thead可以用来提升打包构建速度;Code Split、Preload/Prefetch、Core.js、Network Cache、PWA用来提升性能;- 此外还有
webpack自带的treeshaking、插件image-minimizer-webpack-plugin等可以减少代码体积。
4、vue配置示例
- 更新中……
5、自定义loader和plugin
5.1 loader
5.1.1 loader 执行顺序
-
4 类 loader 的执行优级为:
pre > normal > inline > post。 -
相同优先级的 loader 执行顺序为:
从右到左,从下到上。 -
inline loader
-
用法:
import Styles from 'style-loader!css-loader?modules!./styles.css';是指使用css-loader和style-loader处理styles.css文件 -
也通过添加不同前缀,跳过其他类型 loader。
!跳过 normal loader。-!跳过 pre 和 normal loader。!!跳过 pre、 normal 和 post loader。
-
5.1.2 loader怎么工作的?
loader处理获取到的资源并返回传递给下一个loader
1. 同步 loader
module.exports = function (content, map, meta) {
this.callback(null, content, map, meta);
return; // 当调用 callback() 函数时,总是返回 undefined
};
它接受要处理的源码作为参数,输出转换后的 js 代码。
-
loader 接受的参数
content源文件的内容mapSourceMap 数据meta数据,可以是任何内容
-
this.callback传递多个参数,不仅仅是content,还有map让source-map不中断,meta让下一个loader接收到其他参数。
2. 异步 loader
module.exports = function (content, map, meta) {
const callback = this.async();
// 进行异步操作
setTimeout(() => {
callback(null, result, map, meta);
}, 1000);
};
- 由于同步计算过于耗时,在 Node.js 这样的单线程环境下进行此操作并不是好的方案,我们建议尽可能地使你的 loader 异步化。但如果计算量很小,同步 loader 也是可以的。
3. Raw Loader
- 默认情况下,资源文件会被转化为 UTF-8 字符串,然后传给 loader。通过设置 raw 为 true,loader 可以接收原始的 Buffer。
module.exports = function (content) {
// content是一个Buffer数据
return content;
};
module.exports.raw = true; // 开启 Raw Loader
4. Pitching Loader
module.exports = function (content) {
return content;
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
console.log("do somethings");
};
- webpack 会先从左到右执行 loader 链中的每个 loader 上的 pitch 方法(如果有),然后再从右到左执行 loader 链中的每个 loader 上的普通 loader 方法。
- 在这个过程中如果任何 pitch 有返回值,则 loader 链被阻断。webpack 会跳过后面所有的的 pitch 和 loader,直接进入上一个 loader 。
5.1.3 loader API
5.1.4 自定义 style-loader
- 作用:动态创建 style 标签,插入 js 中的样式代码,使样式生效。
const styleLoader = () => {};
styleLoader.pitch = function (remainingRequest) {
const relativeRequest = remainingRequest
.split("!")
.map((part) => {
// 将路径转化为相对路径
const relativePath = this.utils.contextify(this.context, part);
return relativePath;
})
.join("!");
const script = `
import style from "!!${relativeRequest}"
const styleEl = document.createElement('style')
styleEl.innerHTML = style
document.head.appendChild(styleEl)
`;
return script;
};
module.exports = styleLoader;
5.1 plugin
5.1.1 plugin是怎么工作的?
- webpack 在编译代码过程中,会触发一系列
Tapable钩子事件,plugin所做的,就是找到相应的钩子,往上面挂上自己的任务,也就是注册事件。这样,当 webpack 构建的时候,plugin注册的事件就会随着钩子的触发而执行了。
Tapable
-
Tapable为 webpack 提供了统一的插件接口(钩子)类型定义。它是 webpack 的核心功能库。webpack 中目前有十种
hooks,在Tapable源码中可以看到:github.com/webpack/tap… -
Tapable还统一暴露了三个方法给插件,用于注入不同类型的自定义构建行为:tap:可以注册同步钩子和异步钩子。tapAsync:回调方式注册异步钩子。tapPromise:Promise 方式注册异步钩子。
Compiler
-
compiler 对象中保存着完整的 Webpack 环境配置,它在首次启动 Webpack 时创建,可以通过 compiler 对象上访问到 loader 、 plugin 等等配置信息。
-
compiler主要属性:
compiler.options可以访问本次启动 webpack 时候所有的配置文件,包括但不限于 loaders 、 entry 、 output 、 plugin 等等完整配置信息。compiler.inputFileSystem和compiler.outputFileSystem可以进行文件操作,相当于 Nodejs 中 fs。compiler.hooks可以注册 tapable 的不同种类 Hook,从而可以在 compiler 生命周期中植入不同的逻辑。- compiler hook文档:webpack.docschina.org/api/compile…
Compilation
-
compilation 对象进行一次资源的构建和编译,当有多种资源时,compilation 实例会被多次创建,能够访问所有的模块和它们的依赖。
-
compilation 对象编译包括模块被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)。
-
compilation 主要属性:
compilation.modules可以访问所有模块,打包的每一个文件都是一个模块。compilation.chunkschunk 即是多个 modules 组成而来的一个代码块。入口文件引入的资源组成一个 chunk,通过代码分割的模块又是另外的 chunk。compilation.assets可以访问本次打包生成所有文件的结果。compilation.hooks可以注册 tapable 的不同种类 Hook,用于在 compilation 编译模块阶段进行逻辑添加以及修改。- compilation hook文档:webpack.docschina.org/api/compila…
5.1.2 自定义BannerWebpackPlugin
-
作用:给打包输出文件添加注释。
-
开发思路:
- 需要打包输出前添加注释:需要使用
compiler.hooks.emit钩子, 它是打包输出前触发。 - 如何获取打包输出的资源?
compilation.assets可以获取所有即将输出的资源文件。
- 需要打包输出前添加注释:需要使用
-
实现:
// plugins/banner-webpack-plugin.js class BannerWebpackPlugin { constructor(options = {}) { this.options = options; } apply(compiler) { // 需要处理文件 const extensions = ["js", "css"]; // emit是异步串行钩子 compiler.hooks.emit.tapAsync("BannerWebpackPlugin", (compilation, callback) => { // compilation.assets包含所有即将输出的资源 // 通过过滤只保留需要处理的文件 const assetPaths = Object.keys(compilation.assets).filter((path) => { const splitted = path.split("."); return extensions.includes(splitted[splitted.length - 1]); }); assetPaths.forEach((assetPath) => { const asset = compilation.assets[assetPath]; const source = `/* * Author: ${this.options.author} */\n${asset.source()}`; // 覆盖资源 compilation.assets[assetPath] = { // 资源内容 source() { return source; }, // 资源大小 size() { return source.length; }, }; }); callback(); }); } } module.exports = BannerWebpackPlugin;