webpack是一个静态资源打包工具,会以某个文件为入口,将所有文件编译组合成一个或多个文件输出,让其能够在浏览器运行。
其本身功能非常局限,两种模式
1、开发模式:编译js
2、生产模式:编译js,压缩js
module.exports = {
// 入口
entry: "",
// 输出
output: {},
// 加载器
module: {
rules: [],
},
// 插件
plugins: [],
// 模式
mode: "",
};
loader分为
顺序为从下到上
pre > normal > inline > post
。从右到左,从下到上
。content
源文件的内容map
SourceMap 数据meta
数据,可以是任何内容loader本质是一个函数,把要处理的文件作为参数,进行处理之后再返回一个文件
最简单的loader
// loaders/loader1.js
module.exports = function loader1(content) {
console.log("hello loader");
return content;
};
module.exports = function (content, map, meta) {
return content;
};
module.exports = function (content, map, meta) {
// 传递map,让source-map不中断
// 传递meta,让下一个loader接收到其他参数
//参数1、错误信息 剩下同上
this.callback(null, content, map, meta);
return; // 当调用 callback() 函数时,总是返回 undefined
};
this.callback是指调用下一个loader类似链表
module.exports = function (content, map, meta) {
const callback = this.async();
// 进行异步操作
setTimeout(() => {
callback(null, result, map, meta);
}, 1000);
};
调用callback执行下一个loader,此时是异步操作。
默认情况下,资源文件会被转化为 UTF-8 字符串,然后传给 loader。通过设置 raw 为 true,loader 可以接收原始的 Buffer
module.exports = function (content) {
// content是一个Buffer数据
return content;
};
module.exports.raw = true; // 开启 Raw Loader
webpack 会先从左到右执行 loader 链中的每个 loader 上的 pitch 方法(如果有),然后再从右到左执行 loader 链中的每个 loader 上的普通 loader 方法。
在这个过程中如果任何 pitch 有返回值,则 loader 链被阻断。webpack 会跳过后面所有的的 pitch 和 loader,直接进入上一个 loader 。
module.exports = function (content) {
return content;
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
console.log("do somethings");
};
方法名 | 含义 | 用法 |
---|---|---|
this.async | 异步回调 loader。返回 this.callback | const callback = this.async() |
this.callback | 可以同步或者异步调用的并返回多个结果的函数 | this.callback(err, content, sourceMap?, meta?) |
this.getOptions(schema) | 获取 loader 的 options | this.getOptions(schema) |
this.emitFile | 产生一个文件 | this.emitFile(name, content, sourceMap) |
this.utils.contextify | 返回一个相对路径 | this.utils.contextify(context, request) |
this.utils.absolutify | 返回一个绝对路径 | this.utils.absolutify(context, request) |
Tapable
为 webpack 提供了统一的插件接口(钩子)类型定义,三个方法给插件,用于注入不同类型的自定义构建行为:
类似生命周期,在特定生命周期执行生命周期内的函数
tap
:可以注册同步钩子和异步钩子。tapAsync
:回调方式注册异步钩子。tapPromise
:Promise 方式注册异步钩子启动 webpack 构建时它都是一个独一无二唯一一个compiler对象,保存着完整的 Webpack 环境配置
主要有以下属性
compiler.options
可以访问本次启动 webpack 时候所有的配置文件,包括但不限于 loaders 、 entry 、 output 、 plugin 等等完整配置信息。compiler.inputFileSystem
和 compiler.outputFileSystem
可以进行文件操作,相当于 Nodejs 中 fs。compiler.hooks
可以注册 tapable 的不同种类 Hook,从而可以在 compiler 生命周期中植入不同的逻辑。代表一次资源的构建,compilation 实例能够访问所有的模块和它们的依赖
它有以下主要属性:
compilation.modules
可以访问所有模块,打包的每一个文件都是一个模块。compilation.chunks
chunk 即是多个 modules 组成而来的一个代码块。入口文件引入的资源组成一个 chunk,通过代码分割的模块又是另外的 chunk。compilation.assets
可以访问本次打包生成所有文件的结果。compilation.hooks
可以注册 tapable 的不同种类 Hook,用于在 compilation 编译模块阶段进行逻辑添加以及修改。class TestPlugin {
constructor() {
console.log("TestPlugin constructor()");
}
// 1. webpack读取配置时,new TestPlugin() ,会执行插件 constructor 方法
// 2. webpack创建 compiler 对象
// 3. 遍历所有插件,调用插件的 apply 方法
apply(compiler) {
console.log("TestPlugin apply()");
// 从文档可知, compile hook 是 SyncHook, 也就是同步钩子, 只能用tap注册
compiler.hooks.compile.tap("TestPlugin", (compilationParams) => {
console.log("compiler.compile()");
});
// 从文档可知, make 是 AsyncParallelHook, 也就是异步并行钩子, 特点就是异步任务同时执行
// 可以使用 tap、tapAsync、tapPromise 注册。
// 如果使用tap注册的话,进行异步操作是不会等待异步操作执行完成的。
compiler.hooks.make.tap("TestPlugin", (compilation) => {
setTimeout(() => {
console.log("compiler.make() 111");
}, 2000);
});
// 使用tapAsync、tapPromise注册,进行异步操作会等异步操作做完再继续往下执行
compiler.hooks.make.tapAsync("TestPlugin", (compilation, callback) => {
setTimeout(() => {
console.log("compiler.make() 222");
// 必须调用
callback();
}, 1000);
});
compiler.hooks.make.tapPromise("TestPlugin", (compilation) => {
console.log("compiler.make() 333");
// 必须返回promise
return new Promise((resolve) => {
resolve();
});
});
// 从文档可知, emit 是 AsyncSeriesHook, 也就是异步串行钩子,特点就是异步任务顺序执行
compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {
setTimeout(() => {
console.log("compiler.emit() 111");
callback();
}, 3000);
});
compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {
setTimeout(() => {
console.log("compiler.emit() 222");
callback();
}, 2000);
});
compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {
setTimeout(() => {
console.log("compiler.emit() 333");
callback();
}, 1000);
});
}
}
module.exports = TestPlugin;
在这对文件修改需要compilation对象获取得到数据,修改数据操作。
Cache:Eslint,Babel开启缓存
babel:
options: {
cacheDirectory: true, // 开启babel编译缓存
cacheCompression: false, // 缓存文件不要压缩
}
Eslint:
new ESLintWebpackPlugin({
// 指定检查文件的根目录
context: path.resolve(__dirname, "../src"),
exclude: "node_modules", // 默认值
cache: true, // 开启缓存
// 缓存目录
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/.eslintcache"
),
})
减少代码体积
压缩图片:ImageMinizerPlugin
Babe按需引入:@babel/plugin-transform-runtime
: 禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime
并且使所有辅助代码从这里引用
优化代码性能
Code SPlit:在统一与拆分中取得均衡
功能
分割文件
按需加载
使用:对象中加入
optimization: {
// 代码分割配置
splitChunks: {
chunks: "all", // 对所有模块都进行分割
},
}
Preload
preload插件,允许预先加载资源(new PreloadWebpackPlugin)
PWA
支持离线功能(通过service Woekers实现)
new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何“旧的” ServiceWorkers
clientsClaim: true,
skipWaiting: true,
}),
yargs
解析 config
与 shell
中的配置项webpack
初始化过程,首先会根据第一步的 options
生成 compiler
对象,然后初始化 webpack
的内置插件及 options
配置run
代表编译的开始,会构建 compilation
对象,用于存储这一次编译过程的所有数据make
执行真正的编译构建过程,从入口文件开始,构建模块,直到所有模块创建结束
seal
生成 chunks
,对 chunks
进行一系列的优化操作,并生成要输出的代码emit
被触发之后,webpack
会遍历 compilation.assets
, 生成所有文件,然后触发任务点 done
,结束构建流程大多数JavaScript Parser遵循 estree
规范,Babel 最初基于 acorn
项目(轻量级现代 JavaScript 解析器) Babel大概分为三大部分:
chunk:打包过程中的代码块,一堆module的集合
bundle:是打包完成生成的代码
chunk 是 webpack 处理过程中的一组模块,bundle 是一个或多个 chunk 组成的集合。
module:不同文件类型的模块。Webpack 就是用来对模块进行打包的工具,这些模块各种各样,比如:js 模块、css 模块、sass 模块、vue 模块等等不同文件类型的模块。这些文件都会被 loader 转换为有效的模块,然后被应用所使用并且加入到依赖关系图中。相对于一个完整的程序代码,模块化的好处在于,模块化将程序分散成小的功能块,这就提供了可靠的抽象能力以及封装的边界,让设计更加连贯、目的更加明确。而不是将所有东西都揉在一块,既难以理解也难以管理。
chunk:数据块。
a. 一种是非初始化的:例如在打包时,对于一些动态导入的异步代码,webpack 会帮你分割出共用的代码,可以是自己写的代码模块,也可以是第三方库(node_modules 文件夹里的),这些被分割的代码文件就可以理解为 chunk。
b. 还有一种是初始化的:就是写在入口文件处的各种文件,就是 chunk ,它们最终会被捆在一起打包成一个 main.js (当然输出文件名你可以自己指定),这个 main.js 可以理解为 bundle,当然它其实也是 chunk。
bundle:捆绑好的最终文件。如果说,chunk 是各种片段,那么 bundle 就是一堆 chunk 组成的“集大成者”,比如上面说的 main.js 就属于 bundle。当然它也类似于电路上原先是各种散乱的零件,最终组成一个集成块的感觉。它经历了加载和编译的过程,是源文件的最终版本。
在entry入口中配置的入口文件有三种类型 :
单个文件 : 只会产生一个chunk
数组格式 : 也只会产生一个chunk , 会把数组中所有的所有的源码都打包到一个chunk里 , 最终只生成一个bundle文件
对象格式 : 一个属性名对应一个入口文件 , 就会生成多个chunk , 在outputPath中 filename就需要写