问题列表
- webpack是用来解决什么问题的 ?
- 它有什么亮点 ?
- loader 和 plugin 有什么区别 ?
- 运行原理 ?
- 如何编写 Loader?
- Webpack 和 Rollup 有什么相同点与不同点?
webpack 作用
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
根据官网的高度概述,我们可以简单理解webpack是一个针对JavaScript项目的打包工具。

简单浏览一遍官方文档,再看看上面这张图,就会发现webpack的关键点在于模块处理和打包,在webpack构建的流程中,通过loader处理非 JS 文件,将其转换为 webpack 能够处理的有效模块,使用各式各样的plugin在构建时的关键步骤进行一些操作(比如按需加载,代码压缩等),最终输出浏览器能使用的静态资源。
这里思考一下为什么需要模块化呢?
那怎么理解模块化呢?
在webpack中,核心思想就是一切皆模块,比如说一张图片,一个SCSS文件。
这样做的好处就是能够清晰地描述出各个模块之间依赖关系,便于webpack对模块进行组合、打包。
为什么是 webpack
这些年前框框架、工具非常多,那为什么webpack更受青睐呢?
这里来分析一下其它的工具
基于任务执行的工具
这些工具能够自动执行指定的任务。它们简单高效,社区活跃,有着丰富的插件,可以方便打造各种工作流。
基于模块化打包的工具
比如说:browserify、webpack、rollup
在 Node 环境下,如果需要引用组件,使用
require关键字即可,这类工具就是这个模式,还可以实现按需加载、异步加载模块整合型工具
这类工具使用了多种技术栈实现的脚手架工具,好处是即开即用,缺点就是它们约束了技术选型,并且学习成本相对较高。
为什么选用 webpack
- 大多数团队在开发新项目时会采用紧跟时代的技术,这些技术几乎都会采用“模块化+新语言+新框架”,
webpack可以为这些新项目提供一站式的解决方案 webpack有良好的生态链和维护团队,能提供良好的开发体验和保证质量webpack被全世界的大量 Web 开发者使用和验证,能找到各个层面所需的教程和经验分享
核心概念
- entry: 入口
webpack执行构建的第一步就从入口开始,可以抽象理解为输入 - module: 模块
在webpack中一切皆模块,一个文件就是一个模块,webpack会从入口开始递归遍历找出所有依赖的模块 - chunk: 代码块
一个Chunk由多个模块组合而成,它是代码合并、分割的产物 - loader: 模块转换器
webpack使用它把原内容转换成浏览器能够使用的文件 - plugin: 插件拓展
webpack在构建流程中的特定时机注入扩展逻辑来改变构建结果 - output: 输出结果
webpack经过一系列处理后,输出的最终结果
loader
webpack 只能理解JavaScript和JSON文件,这是webpack开箱可用的自带能力。
loader让webpack能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。
用法
处理文件时可以使用多个loader,loader的执行顺序和配置中的顺序是相反的。也就是说最后一个loader先执行,它接收源文件作为参数,处理之后把处理结果传给下一个loader
常用的 loader
- css-loader
加载CSS、支持模块化、压缩、文件导入等特性 - style-loader
把CSS代码注入到JavaScript中,通过DOM操作去加载CSS - file-loader
把文件输出到一个文件夹中,在代码中通过相对URL去引用输出的文件 - url-loader
它和 filr-loader 类似,但是能在文件很小的情况下以base64的方式把文件内容注入到代码中 - babel-loader
让开发者能够在项目中使用新的JavaScript特性
plugin
插件是webpack的支柱功能。
插件目的在于解决loader无法实现的其他事。
webpack自身也是构建于插件系统之上。
原理
在webpack运行的生命周期中会广播出许多事件,插件可以监听这些事件,在合适的时机通过webpack提供的API改变输出结果,在这个过程中插件可以访问到compile和compilation,通过钩子拦截webpack的执行。
常用的 plugin
- clean-webpack-plugin
每次打包后自动清理output.path内的文件,只保留本次构建的结果 - html-webpack-plugin
创建一个html文件,并把webpack打包后的静态文件自动插入到这个html文件当中 - DefinePlugin
这是一个定义全局变量的插件,定义的变量可以在webpack打包范围内任意JavaScript环境内访问 - MiniCssExtractPlugin
将CSS单独打包成一个文件,它为每个包含CSS和JavaScript文件都创建一个CSS文件,结合html-webpack-plugin插件,以link的形式插入到html文档中 - webpack-merge
该插件用于合并配置 - webpack-bundle-analyzer
项目打包后进行性能分析
构建流程和原理
构建之前先了解几个名词:
- Compiler:编译管理器,
webpack启动后会创建compiler对象,该对象一直存活直到结束退出 - Complilation:单次编译过程的管理器,比如
watch=true时,运行过程中只有一个compiler,但每次文件变更触发重新编译时,都会创建一个新的compilation对象 - Dependence:依赖对象,
webpack基于该类型记录模块间依赖关系 - Module:
webpack内部所有资源都会以module对象形式存在,所有关于资源的操作、转译、合并都是以module为基本单位进行的 - Chunk:编译完成准备输出时,
webpack会将module按特定的规则组织成一个一个的chunk,这些chunk某种程度上跟最终输出一一对应
构建流程:
初始化阶段
- 初始化参数:从配置文件、配置对象、Shell参数中读取,进行错误检查,与默认配置结合得出最终的参数
- 创建编译器对象:用上一步得到的参数创建
Compiler对象 - 初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化RuleSet集合、加载配置的插件等
- 开始编译:执行
compiler对象的run方法 - 确定入口:根据配置中的
entry找出所有的入口文件,调用compilition.addEntry将入口文件转换为dependence对象
构建阶段
- 编译模块:根据
entry对应的dependence创建module对象,调用loader将模块转移为标准JS内容,调用JS解释器将内容转换为AST对象,从中找出该模块依赖的模块,再递归这个步骤直到所有入口依赖的文件都经过了本步骤的处理 - 完成模块编译:上一步递归处理所有能触达到的模块后,得到了每个模块被翻译后的内容以及它们之间的
依赖关系图
- 编译模块:根据
生成阶段
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的
Chunk,再把每个Chunk转换成一个单独的文件加入到输出列表。这个步骤是可以修改输出内容的最后机会 - 写入文件系统:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的
源码中的一些重要方法:
- webpack_require: 我们在模块化开发的时候,通常会使用ES Module或者CommonJS规范导入导出以来的模块,webpack在打包的时候,会统一替换成自己的__webpack_require__来实现模块的引入和导出,从而实现模块缓存机制,以及抹平不同模块规范之间的一些差异性
- webpack_modules:存放了编译后的各个文件模块的JS内容
- webpack_module_cache:用来做模块缓存
配置优化
webpack配置虽然简单,但是配置项非常多,每个配置项的取值可能也有很多的选择,故而优化的空间很大。
讲一些平常用的多的配置优化,主要分为以下三种:
优化构建速度
使用多进程多实例解析资源
- thread-loader
- happypack
使用
DLLPlugin对基础依赖库进行分包,减少编译次数DllPlugin动态链接库插件,其原理是把网页依赖的基础模块抽离出来打包到dll文件中,当需要导入的模块存在于某个dll中时,这个模块不再被打包,而是去dll中获取。dll中大多包含的是常用的第三方模块,如react、react-dom,所以只要这些模块版本不升级,就只需被编译一次。
externals,从输出的bundle中排除依赖
从 CDN 引入 jQuery,而不是把它打包
缩小范围
配置loader时,指定loader的作用目录或者需要排除的目录,通过使用include和exclude两个配置项
noParse,使用noParse进行忽略的模块文件中不会解析import、require等语法
使用缓存
- babel-loader
- cache-loader
优化构建结果
构建结果分析,使用
webpack-bundle-analyzer插件可以看到项目各模块大小,进行按需优化
stat表示文件的输入大小,parsed表示文件的输出大小,gzip表示通过gzip压缩运行解析的包/模块大小
webpack默认支持,使用
tree shaking擦除无用代码移除
JavaScript上下文中的未引用代码开启
scope hosting减少函数申明和内存开销这个名词直译过来就是作用域提升,它把所有的代码按照引用顺序放在一个函数作用域里面,适当的重命名变量防止冲突
优化运行时体验
入口分割点分割,多页打包
使用
splitChunksPlugin分离页面公共文件,抽取公共代码代码懒加载
prefetch,在浏览器空闲的时候进行资源的拉取
preload,提前加载后面会用到的关键资源
总结来看,可以使用大量插件对编译过程进行优化。
参考
书目:
- 深入浅出 Webpack