Webpack
1. Webpack简介
为什么需要Webpack
- Webpack默认支持多种模块标准,包括AMD、CommonJS以及最新的ES6模块
- Webpack有完备的代码分割解决方案
- Webpack可以处理各种类型的资源
- Webpack有庞大的社区支持
安装
- npm install webpack webpack-cli --save-dev
打包第一个应用
- 注意点: 直接使用Webpack开发和使用webpack-dev-server又一个很大的区别,前者每次都会生成budnle.js,而weboack-dev-server只会将打包结果放在内存中,并不会写入实际的bunde.js
2. 模块打包
CommonJS与ES6 Module的区别
- CommonJS 与ES6 Module最本质的区别在于前者对模块依赖的解决是动态的,而后者是静态的。在这里动态的含义是,模块依赖关系的建立发生在代码运行阶段,而静态则是模块依赖关系的建立发生在代码编译阶段。
- require的模块路径可以动态指定,支持传入一个表达式,我们甚至可以通过if语句判断是否加载某个模块,因此,在CommonJS模块被执行钱,并没有办法明确依赖关系,模块的导入、导出发生在代码的运行阶段;ES6 Module的导入、导出语句都是声明式的,它不支持导入的路径是一个表达式,并且导入导出语句必须位于模块的顶层作用域,因此ES6 Module是一种静态的模块结构,在ES6代码的编译阶段就可以分析出模块的依赖关系
-
ES6 Modue的优势
- 死代码检测和排除
- 模块变量类型检查
- 编译器优化
在导入一个模块时,对于CommonJS来说获取的是一份导出值的拷贝;而在ES6 Module中则是值的动态映射,并且这个值只是可读的。
模块打包原理
-
bundle结构
- 最外层立即执行函数,它用来包裹整个bundle,并构成自身的作用域
- installedModules对象。每个模块只在第一次被加载的时候执行,之后其导出值就被存储到这个对象里面,当再次被加载的时候直接从这里取值,而不会重新执行。
- webpack_require函数。对模块加载的实现,唉浏览器中可以通过调用webpack_require(module_id)来完成模块的导入。
- modules对象。工程中所有产生了依赖关系的模块都会以key-value的形式放在这里。key可以理解为一个陌路爱的id,由数字活着一个很短的hash字符串构成;value则是又一个匿名函数包裹的模块实体,匿名函数的参数则赋予了每个模块导出和导入的能力
-
bundle执行
- 最外层的匿名函数中会初始化浏览器执行环境,包括定义installedModules对象、webpack_require函数等,为模块的加载和执行做一些准备。
- 加载入口模块。
- 执行模块代码。如果执行到了module.exports则记录下陌路爱的导出值;如果中间遇到require函数(准确地说是webpack_require),则会暂时交出执行权,进入webpack_require函数体内进行加载其他模块的逻辑。
- 在webpack_require中会判断即将加载的模块是否存在与installedModules中。如果存在则直接取值,否则回到上述步骤,执行该模块的代码来获取导出值。
- 所有依赖的模块都已经执行完毕,最后执行权又回到入口模块。
3. 资源输入输出
配置资源入口
-
context
- 资源路口的标识前缀,在配置时要求必须使用绝对路径的形式
- 配置context的主要目的时让entry的编写更加简洁。context可以省略,默认值为当前工程的根目录。
-
entry
-
注意点:当一个bundle大于250kb时(压缩前)会认为这个bundle已经过大了,会在打包时发生警告。
- 提取vendor
-
- 确定了入口模块的位置,告诉webpack从哪里开始打包。 2. 定义chunk name。如果工程只有一个入口,那么默认其chunk name为main,如果工程有多个入口,我们需要为每个入口定义chunk name,来作为chunk的唯一标识。
配置资源出口
-
filenam
【hash】:指代webapck此次打包所有资源生成的hash
【chunkhash】: 指代当前chunk内容的hash
【id】:指代当前chunk的id
【query】: 指代filename配置项的query
-
上述变量主要有两种作用
- 控制客户端华缓存
- 当有多个chunk存在时对不同的chunk进行区分。如【chunk】、【chunkhash】、【id】,它们对于每个chunk来说都是不同的。
-
path
- 指定资源输出位置,要求值必须为绝对路径
-
publicPath
-
与path的差异
-
从功能上来说,path用来指定资源输出的位置,而publicPath则用来指定资源请求的位置。
- 输出位置: 打包完成后资源产生的位置
- 请求位置: 由JS活着CSS所请求的间接资源路径
-
HTML
Host
CDN相关
-
4. 预处理器
loader概述
-
每个loader本质上都是一个函数
- webpack4之前,函数的输入和输出都是字符串
- webpack4之后,loader也同时支持抽象语法树AST的传递,以减少代码的重复解析。
loader的配置
-
常用配置
- test
- use
- options
-
额外配置
exclude与include
-
resource与issuer
- 在webpack中哦你,我们认为被加载模块是resource,而加载者是issuer。
enforce
常用loader介绍
-
babel-loader
babel-loader:是babel与webpack协同工作的模块
@babel/core:babel编译器的核心模块
@babel/preset-env:babel官方推荐的预置器,可根据用户设置的目标环境自动添加所需的插件和补丁来编译ES6+代码
-
注意点
- 由于babel-loader通常属于对所有JS后缀文件设置的规则,所以需要在exclude中添加node_modules,否则会令babel-loader编译其中所有的模块,这将严重拖慢打包的速度,并且有可能改变第三方模块的原有行为。
- 对于babel-loader本身我们添加了cacheDirectory配置项,它会启用缓存机制,在重复打包未改变过的模块时防止二次编译,这个值也可以为true
- 由于@babe/preset-env会将ES6 Module转化为CommonJS的形式,这回导致Webpack中的tree-shaking特性失效,将@babel/prset-env的modules配置项设置为false会禁用模块语句的转化,而将ES6 Module的语法交给Webpack本身处理
- babel-loader支持从.babelrc文件中读取babel配置,因此可以将presets和plugins从Webpack配置文件中提取出来,也能达到相同的效果
ts-loader
-
html-loader
- 用于将HTML文件格式化为字符串并进行格式化,这使得我们可以把一个HTML片段通过JS加载进来
-
handlebars-loader
- 用于处理handlebars模版
-
file-loader
- 用于打包摁键类型的资源,并返回其publicPath
-
url-loader
- 与file-loader作用类似,唯一的不同在于用户可以设置一个文件大小的阈值,当大于该阈值时与file-loader一样放回publicPath,而小于则放回文件的base64形式编码
vue-loader
自定义loader
5. 样式处理
分离样式文件
extract-text-webpack-plugin
多样式文件处理
mini-css-extract-plugin
-
extract-text-webpack-plugin 与 mini-css-extract-plugin的差异点
- loader规则设置的形式不同,并且mini-css-extract-plugin支持配置publicPath,用来指定异步CSS的加载路径
- 不需要shezhifallback
- 在plugins设置中,除了指定同步加载的CSS资源名,还需要指定异步加载的CSS资源名
样式预处理
- sass和scss
- less
PostCss
- PostCSS和Webpack
- 自动前缀
- stylelint
- CSSNext
Css Module
10. 更多JavaScript打包工具
Rollup
Parcel
打包工具发展趋势
- 子主题 1
9. 开发环境调优
Webpack开发效率插件
模块热替换
8. 打包优化
HappyPack
减少打包作用域
动态链接库与DllPlugin
tree shaking
7. 生产环境配置
环境配置的封装
开启production模式
环境变量
source map
资源压缩
缓存
6. 代码片段
通过入口划分代码
CommonsChunkPlugin
-
使用commonschunkplugin的收益
- 合理分片后的代码可以更有效地利用客户端缓存
- 开发过程中减少了重复模块打包,可以提升开发速度
- 减少整体资源体积
-
配置
name:用于指定公共chunk的名字
filename:提取后的资源文件名
chunk: 设置提取范围
-
设置提取规则
- 数字
- Infinity
- 函数
-
设置长缓存
- 注意️:manifest的CommonsChunkPlugin必须出现在最后,否则Webpack将无法正常提取模块
-
不足
- 一个CommonsChunkPlugin只能提取一个vendor
- manfifest实际上会使浏览器多加载一个资源,这对于页面渲染速度是不友好的
- 由于内部设计缺陷,CommonChunkPlugin在提取公共模块的时候会破坏原有Chunk中模块的依赖关系,导致难以进行更多的优化。
optimization SplitChunks
-
与CommonChunkPlugin的不同
- 使用optimization.splitChunks代替额CommonsChunkPlugin,并指定了chunks值为all,这个配置项的含义是,SplitChunks将会对所有的chunk生效(默认只对异步生效)
- mode是webpack4中新增的配置项,可以针对当前是开发环境还是生产环境自动添加对应的一些Webpack配置
-
默认情形下的提取条件
- 提取后的chunk可被共享或来自node_modules目录
- 提取后的js代码体积大于30KB,css chunk体积大于50kb
- 在按需加载过程中,并行请求的资源最大值小于等于5
- 在首次加载时,并行请求的资源数最大值小于等于3
-
配置
- 匹配模式
- 匹配条件
- 命名
- cacheGroups
资源异步加载
- 资源异步加载主要解决的问题是,当模块数量过多、资源体积过大时,可以把一些暂时使用不到的模块延迟加载。这样使页面初次渲染的时候用户下载的资源尽可能小,后续的模块等到恰当的时机再去触发加载。因此一般也把这种方法叫作按需加载