简介
webpack是一个静态资源打包工具,会以某个文件为入口,将所有文件编译组合成一个或多个文件输出,让其能够在浏览器运行。
其本身功能非常局限,两种模式
1、开发模式:编译js
2、生产模式:编译js,压缩js
webpack五大核心概念
- 入口
- 输出
- 加载器
- 插件
- 模式
module.exports = {
// 入口
entry: "",
// 输出
output: {},
// 加载器
module: {
rules: [],
},
// 插件
plugins: [],
// 模式
mode: "",
};
基本概念
loader分为
- pre
- normal
- post
- inline
顺序为从下到上
- 4 类 loader 的执行优级为:
pre > normal > inline > post
。 - 相同优先级的 loader 执行顺序为:
从右到左,从下到上
。
实现原理
content
源文件的内容map
SourceMap 数据meta
数据,可以是任何内容
loader本质是一个函数,把要处理的文件作为参数,进行处理之后再返回一个文件
最简单的loader
// loaders/loader1.js
module.exports = function loader1(content) {
console.log("hello loader");
return content;
};
loader的定义方式
同步loader
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类似链表
异步loader
module.exports = function (content, map, meta) {
const callback = this.async();
// 进行异步操作
setTimeout(() => {
callback(null, result, map, meta);
}, 1000);
};
调用callback执行下一个loader,此时是异步操作。
Raw Loader
默认情况下,资源文件会被转化为 UTF-8 字符串,然后传给 loader。通过设置 raw 为 true,loader 可以接收原始的 Buffer
module.exports = function (content) {
// content是一个Buffer数据
return content;
};
module.exports.raw = true; // 开启 Raw Loader
Pitching 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");
};
loader API
方法名 | 含义 | 用法 |
---|---|---|
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) |
plugins
原理
Tapable
为 webpack 提供了统一的插件接口(钩子)类型定义,三个方法给插件,用于注入不同类型的自定义构建行为:
类似生命周期,在特定生命周期执行生命周期内的函数
tap
:可以注册同步钩子和异步钩子。tapAsync
:回调方式注册异步钩子。tapPromise
:Promise 方式注册异步钩子
compiler和compilation
compiler:
启动 webpack 构建时它都是一个独一无二唯一一个compiler对象,保存着完整的 Webpack 环境配置
主要有以下属性
compiler.options
可以访问本次启动 webpack 时候所有的配置文件,包括但不限于 loaders 、 entry 、 output 、 plugin 等等完整配置信息。compiler.inputFileSystem
和compiler.outputFileSystem
可以进行文件操作,相当于 Nodejs 中 fs。compiler.hooks
可以注册 tapable 的不同种类 Hook,从而可以在 compiler 生命周期中植入不同的逻辑。
Compilation:
代表一次资源的构建,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对象获取得到数据,修改数据操作。
处理各种资源
- 样式资源:
- 加载
- css-loader,
- style-loader,
- less-loader,
- sass-loader
- 优化
- 提取css为单独文件:MiniCssExtractPlugin
- 兼容性处理:postcss-loader
- 压缩:CssMinizerPlugin
- 加载
- 图片资源:file-loader,url-loader(现在用type:asset,其中加入parser{dataUrlConditon来压缩})
- 音频视频等:type:asset/resource
- js资源:
- Eslint插件(需要配置文件,直接extends属性用写好的)
- Babel-loader(需要配置文件,以及preset插件)
- html资源:HtmlWebpackPlugin(需要配置模板文件)
优化
- 提升开发体验
- webpack-dev-derver:开发服务器,自动化(需要在对象中加入devServer配置项)
- SourceMap:开启映射,
- 生产模式:devtool:“source-map”
- 开发模式:devetool:“cheap-module-source-map”
- 提升打包构建速度
- HMR:修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变
- css开启:devServer中添加hot
- js开启:vue-loader
-
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" ), })
-
- HMR:修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变
-
减少代码体积
-
压缩图片: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, }),
-
-
Webpack构建流程简单说一下
- 通过
yargs
解析config
与shell
中的配置项 webpack
初始化过程,首先会根据第一步的options
生成compiler
对象,然后初始化webpack
的内置插件及options
配置
run
代表编译的开始,会构建compilation
对象,用于存储这一次编译过程的所有数据make
执行真正的编译构建过程,从入口文件开始,构建模块,直到所有模块创建结束- 编译:入口文件开始递归解析依赖关系,构建整个模块依赖图。它会根据配置中的 Loader 对模块进行转换,并生成对应的 AST(抽象语法树)。然后,Webpack 会根据依赖图生成一个或多个 Chunk(代码块)。
- 构建:将生成的 Chunk 进一步处理,包括合并、拆分、优化等。它会根据配置中的插件对 Chunk 进行处理,例如压缩代码、提取公共模块、生成资源文件等。
seal
生成chunks
,对chunks
进行一系列的优化操作,并生成要输出的代码emit
被触发之后,webpack
会遍历compilation.assets
, 生成所有文件,然后触发任务点done
,结束构建流程
聊一聊Babel原理吧
大多数JavaScript Parser遵循 estree
规范,Babel 最初基于 acorn
项目(轻量级现代 JavaScript 解析器) Babel大概分为三大部分:
- 解析:将代码转换成 AST
- 词法分析:将代码(字符串)分割为token流,即语法单元成的数组
- 语法分析:分析token流(上面生成的数组)并生成 AST
- 转换:访问 AST 的节点进行变换操作生产新的 AST
- Taro就是利用 babel 完成的小程序语法转换
- 生成:以新的 AST 为基础生成代码
Webpack中的Chunk和Bundle
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就需要写