Webpack的介绍
webpack 是一个模块打包工具(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
webpack的作用
- 通过webpack可以将零散的js文件打包到一个文件中;
- 通过webpack Loader实现对代码的编译转换,兼容低版本浏览器
- 支持不同种类(js/css/img)的模块资源的打包
- 具备代码拆分能力:所有模块按需分块打包,一般会把应用初次加载必须的模块打包到一起,其他模块单独打包,等到应用工作过程中再异步加载这个模块(渐进式加载就不用担心打包单文件过大而加载慢的问题)
核心概念
webpack的核心有以下几点:
- Entry:入口
- Output:出口
- Loader:加载器
- Plugin:插件
- Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。
- Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
具体可查阅:webpack安装使用
Loader和Plugin的区别
Loader 本质就是一个函数,主要用于对特殊类型资源的加载,转换输出成webpack识别的格式(接受文件作为参数,返回转化后的结构)。webpack自身只支持js和json这两种格式的文件,对于其他文件需要通过loader将其转换为commonJS规范的文件后,webpack才能解析到。
Plugin 就是插件,包含apply方法。apply方法会被 webpack的 compiler(编译器)对象调用,并且 compiler 对象可在整个 compilation(编译)生命周期内访问。
Plugin可以理解为扩展webpack,实现各种自动化构建任务。在webpack打包编译过程里,针对loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于webpack的事件机制(webpack在每一个工作环境都预留了合适的钩子),会监听webpack打包过程中的某些节点,挂载并执行任务。
开发一个Plugin
在插件项目中定义一个插件名类,在该类中通过compiler的hooks属性访问到xx钩子,再通过tap方法注册一个钩子函数(挂载要执行的函数在钩子上),返回处理后的新内容。
1 | ///src/plugins/helloPlugin.js |
实现loader
loader 的处理策略:
- CSS:转换成 js 的模块,执行模块会在 DOM 中创建 style 标签并且插入CSS内容
- Img:转换成图片路径
- JSON:转化成 js 模块, default export = json
loader的执行顺序是从右到左,从下到上。而loader的加载顺序是从左到右,从上到下。
babel-loader的主要原理:
调动@babel/core这个包下面的transform方法,将源码通过presets预设来进行转换,然后生成新的代码、map和ast语法树传给下一个loader。这里的presets,比如@babel/preset-env这个预设其实就是各类插件的集合,基本上一个插件转换一个语法,比如箭头函数转换,有箭头函数转换的插件,这些插件集合就组成了预设。
1 | // myloader替换尖括号 |
webpack钩子
- beforeRun: 在编译器执行前
- run: 在编译器开始读取记录前执行
- compilation: 创建compilation后
- beforeCompile: 在编译前
- compile: 创建compilation前
- make: 编译完成前
- done: 编译完成后
- emit: 文件提交到dist目录前
- afterEmit: 文件提交到dist目录后
source map配置
在webpack配置文件中,指定devtool: “source-map”等字段可以输出对应的.map文件,方便在浏览器中调试,sourceMap对应的模式有很多,建议:
- 开发环境:cheap-module-eval-source-map,报错可以定位到源码的行信息,且构建速度比较快
- 生产环境: nosources-source-map,可以定位到报错的位置,且不会暴露源代码
webpack的特性
HMR(热更新)Tree-shaking(摇树):生产环境自动启动,打包时去掉没有用到的代码成员可以通过配置webpack的 optimization 对象控制:
- usedExports 打包结果中只导出外部用到的成员;
- minimize 压缩打包结果,去掉无效代码
sideEffects:开启可以打包时无视无效模块中的副作用代码(需要在package.json中同时配置)code splitting:通过把项目资源模块按照我们的规则打包到不同的bundle中,降低应用成本,提高响应速度webpack实现
分包方式:- 根据业务不同配置多个打包入口,输出多个打包结果(适用于传统多页应用)
- 结合ES Module的动态导入特性,按需加载模块(import(module path).then())
webpack的扩展方式
loader、plugin、minimizer
构建流程/打包流程
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
- 初始化:
- 初始化参数:启动打包流程,读取并合并配置参数
- 创建编译器对象:载入webpack核心模块,传入配置项,创建Compiler对象
- 开始编译:使用Compiler对象开始编译项目
- 编译:
- 确定入口:从入口文件(entry)开始解析,并且找到其导入的依赖模块,递归遍历分析,形成依赖关系树;
- 编译模块:将每个模块交给Loader;对不同文件类型的依赖模块文件使用对应的
Loader进行编译,最终转为JS模块文件(对于无法被转成js的资源文件,Loader一般会将它们单独作为资源文件拷贝到输出目标中,将资源文件的路径作为导出成员)
- 输出:将编译后的模块组合成 Chunk,将 Chunk 转换成文件,输出到文件目录中
PS:
编译的过程中,webpack会通过发布订阅模式,向外抛出一些hooks,而webpack的插件即可通过监听这些关键的事件节点,执行插件任务进而达到干预输出结果的目的。所以Plugin应该在webpack生命周期开始之前注册,才能监控到事件节点。
打包原理
webpack通过模拟module,exports,require变量,将我们的模块代码打包成一个IIFE(立即执行函数),,函数参数是我们写的各个模块被包装之后组成的数组,浏览器执行这个立即执行函数就可以运行我们的模块代码。
- 根据入口文件,先逐级递归识别依赖,构建依赖图谱
- 将代码转化成AST抽象语法树
- 在AST阶段中处理代码
- 把AST抽象语法树变成浏览器可以识别的代码, 然后输出
打包优化
常见的优化就是拆包、分块、压缩等,主要是两个优化点:
- 减少打包时间
- 减少打包大小
减少打包时间
- 优化解析时间:开启多进程打包
- thread-loader(webpack4 官方推荐):把这个 loader 放置在其他 loader 之前, 之后的 loader 就会在一个单独的 worker 池里运行,一个worker 就是一个nodeJS 进程。
- HappyPack(将不再维护):将loader的同步执行转为并行,减少loader的编译等待时间
- 合理利用缓存
- 缓存已编译过的文件(如设置babel-loader的cacheDirectory)
- cache-loader:对性能开销较大的 loader 使用。可以将使用了该loader的结果缓存到磁盘里,显著提升二次构建速度
- 优化压缩时间:webpack4内置默认使用
terser-webpack-plugin插件压缩优化代码。 - 优化搜索时间:缩小文件搜索范围 减小不必要的编译工作
- 优化 loader 文件搜索范围:使用 Loader 时可以通过 test 、 include 、 exclude 三个配置项来命中 Loader 要应用规则的文件
- 优化resolve下的配置
减少打包大小
- 启用gzip压缩
- 按需加载:首页加载文件越小越好,将每个页面单独打包为一个文件
- 在webpack4中production下打包默认开启UglifyJS压缩代码
- 开启Tree-shaking等去掉无效代码
热更新(HMR)
热更新(Hot Module Replacement)是指能够不用刷新浏览器而将新变更的模块替换掉旧的模块(不用刷新整个页面)。热更新实现主要分为几部分功能:
- 服务器构建、推送更新消息
- 浏览器模块更新
- 模块更新后页面渲染
原理:
- 通过webpack-dev-server创建两个服务器:提供静态资源的服务(express)和Socket服务
- 使用 webpack-dev-server 托管静态资源,同时以 Runtime 方式注入 HMR 客户端代码;
- 浏览器加载页面后,与 WDS 建立 WebSocket 连接
- Webpack 监听到文件变化后,增量构建发生变更的模块,并通过 WebSocket 发送 hash 事件
- 浏览器接收到 hash 事件后,请求 manifest 资源文件,确认增量变更范围
- 浏览器加载发生变更的增量模块
- Webpack 运行时触发变更模块的 module.hot.accept 回调,执行代码变更逻辑
简单理解:dev server 启动以后,会 watch 源文件的变化。当源文件发生变化后,Webpack 会重新编译打包, 再通过 ws 连接通知浏览器去获取新的打包文件,然后对页面做局部更新。
webpack和rollup的选择
Rollup 是一个 JavaScript 模块打包器(ES Module打包器),和webpack类似,但小巧的多,它可以将小块代码编译成整块代码,从而使得这些模块更好的运行在浏览器或nodeJS环境。Rollup中只能通过插件扩展。
Rollup优点:
- 输出结果更加
扁平,执行效率更高 - 良好的tree-shaking, 自动移除未引用代码
- 打包结果可读
Rollup缺点:
- 如要加载非ES Module第三方模块会比较复杂
- 模块最终会打包到全局,不能HMR
- 浏览器环境中,代码拆分模块需要用require.js这样的AMD库
webpack 拆分代码, 按需加载;Rollup 所有资源放在同一个地方,一次性加载,利用 tree-shake 特性来剔除项目中未使用的代码,减少冗余。
Rollup偏向应用于js库、框架;webpack偏向应用于应用开发。如果应用场景中只是js代码,希望做ES转换,模块解析,可以使用Rollup。如果应用场景中涉及到css、html,涉及到复杂的代码拆分合并,建议使用webpack。
组件库选择rollup: webpack 无法构建出 esm 格式的 js 文件。 即使借助一些插件实现了,产出代码也比不上 rollup 简洁、干净。
gulp和webpack
webpack5和webpack4区别
webpack5通过优化 Tree Shaking 和代码生成来减小Bundle体积(更好的处理嵌套 tree-shaking)
压缩代码:
webpack4需要安装terser-webpack-plugin 插件webpack5: 内置了 terser-webpack-plugin 插件,在 mode=“production” 的时候会自动开启 js 压缩功能
1
2
3
4
5
6
7// webpack.config.js中
module.exports = {
optimization: {
usedExports: true, //只导出被使用的模块
minimize : true // 启动压缩
}
}缓存设置:
webpack5 内部内置了 cache 缓存机制, cache 会在开发模式下被设置成 type: memory 而且会在生产模式把cache 给禁用掉1
2
3
4
5
6
7
8// webpack.config.js
module.exports= {
// 使用持久化缓存
cache: {
type: 'filesystem',
cacheDirectory: path.join(__dirname, 'node_modules/.cac/webpack')
}
}启动服务:
webpack4通过 webpack-dev-server 启动服务webpack5内置使用 webpack serve 启动
模块依赖关系构建
打包体积更小


