代码分离
代码分离是webpack中一个非常重要的特性:
- 它主要的目的是将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文件
- 比如默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页的加载速度
- 代码分离可以分出更小的bundle,以及控制资源加载的优先级,提供代码的加载性能
webpack中常用的代码分离有三种:
- 入口起点:使用entry配置手动分离代码
- 防止重复:使用Entry Dependencies或者SplitChunkPlugin去重和分离代码
- 动态导入:通过模块的内联函数调用来分离代码
入口起点
就是配置多入口:比如配置一个index.js和main.js的入口,他们分别有自己的代码逻辑
entry: {
index: './src/index.js',
main: './src/main.js'
}
output: {
path: path.resolve(__dirname, './build')
filename: '[name]-bundle.js'
}
防止重复:
假如我们的index.js和main.js都依赖两个库:axios,lodash:
- 如果我们进行单纯的入口分离,那么打包后的两个bundle都会有一份lodash和axios,事实我们可以对他们进行共享
entry: {
index: {
import: "./src/index.js",
dependOn: "shared"
},
main: {
import: "./src/main.js",
dependOn: "shared"
},
shared:["axios", "lodash"]
}
动态导入
另外一个代码拆分的方式是动态导入,webpack提供了两种实现动态导入的方式:
- 使用ECMAScript中的import()语法来完成,也是目前推荐的方式
- 使用webpack遗留的require.ensure,目前已经不推荐使用
举个栗子:
比如我们有一个模块bar.js:
- 该模块我们希望在代码运行过程中来加载它(比如判断一个条件成立时加载)
- 因为我们并不确定这个模块中的代码一定会用到,所以最好拆分成一个独立的js文件
- 这样可以保证不用到该内容时,浏览器不需要加载和处理该文件的js代码
- 这个时候我们就可以使用动态导入
注意:使用登台导入bar.js:
在webpack中,通过动态导入获取到一个对象,真正导出的内容,在该对象的default属性中,所以我们需要做一个简单的解构。
动态导入命名:魔法注释
SplitChunks
另外一种分包模式是SplitChunk,它底层使用的是SplitChunkPlugin来实现的。
因为该插件webpack已经默认安装和集成,所以我们并不需要单独安装可直接使用该插件,只需要提供splitChunkPlugin相关的配置信息即可。
webpack提供了splitchunkPlugin默认的配置,我们也可以手动来修改它的配置:
- 比如默认配置中,chunks仅仅针对于异步(async)请求,我们也可以设置为all
- splitChunks
- cacheGroups
- test:匹配符合规则的包
- name:拆分包的name属性
- filename:拆分包的名称
意思就是所有来自于node_modules下的包都拆分到filename对应的文件中,utils同理。
- chunkIds
用于告知webpack模块的id采用什么算法生成:
- natural:按照数字的顺序使用id
- named:development下的默认值,一个可读的名称的id
- deterministic:确定性的,在不同变异种不变的段数字id
- 在开发过程中,我们推荐使用named
- 打包过程中,我们推荐使用deteministic
- runtimeChunk
配置runtime相关的代码是否抽取到一个单独的chunk中:
- runtime相关的代码指的是在运行环境中,对模块进行解析、加载、模块信息相关的代码;
- 比如我们的component、bar两个通过import函数相关的代码加载,就是通过runtime代码完成的;
抽离出来后,有利于浏览器缓存的策略:
- 比如我们修改了业务代码(main),那么runtime和component、bar的chunk是不需要重新加载的;
- 比如我们修改了component、bar的代码,那么main中的代码是不需要重新加载的;
设置的值:
- Prefetch和Preload
webpack v4.6.0+ 增加了对预获取和预加载的支持。
- 在声明 import 时,使用下面这些内置指令,来告知浏览器:
- prefetch(预获取):将来某些导航下可能需要的资源
- preload(预加载):当前导航下可能需要资源
与 prefetch 指令相比,preload 指令有许多不同之处:
- preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
- preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
- preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
CDN
CSN称之为内容分发网络,它是指通过相互连接的网络系统,利用最靠近每个用户的服务器,更快、更可靠地将音乐、图片、视频、应用程序及其他文件发发送给用户,来提高性能、可扩展性及低成本的网络内容传递给用户。
在开发中,我们使用的CDN主要是两种方式:
- 方式一:打包所有的静态资源,放到CDN服务器,用户所有的资源都是通过CDN服务器加载的
- 方式二:一些第三方资源放到CDN服务器上
配置CDN服务器
可以直接修改publicPath,在打包时添加自己的CDN地址
配置第三方库的CDN服务器
通常一些比较出名的开源框架都会将打包后的源码放到一些比较出名的、免费的CDN服务器上:
- 国际上使用比较多的是unpkg、JSDelivr、cdnjs;
- 国内也有一个比较好用的CDN是bootcdn;
项目中,我们如何去引入这些CDN呢?
- 第一,在打包的时候我们不再需要对类似于lodash或者axios这些库进行打包;
我们可以通过webpaack配置,来排除一些库的打包
- 第二,在html模块中,我们需要自己加入对应的CDN服务器地址;
在html模块中,加入CDN服务器地址
提取CSS文件
MiniCssExtractPlugin可以帮助我们将css提取到一个独立的css文件中,该插件需要在webpack4+才可以使用。
安装 mini-css-extract-plugin:
npm install mini-css-extract-plugin -D
配置rules和plugins:
注意:
- style-loader打包样式是放到标签内联上的
- minicssExtractPlugin打包独立css文件然后link标签引入css
打包文件命名
在我们给打包的文件进行命名的时候,会使用placeholder,placeholder中有几个属性比较相似:hash、chunkhash、contenthash
hash
- hash本身是通过MD4的散列函数处理后,生成一个128位的hash值(32个十六进制);
hash值的生成和整个项目有关系:
- 比如我们现在有两个入口index.js和main.js;
- 它们分别会输出到不同的bundle文件中,并且在文件名称中我们有使用hash;
- 这个时候,如果修改了index.js文件中的内容,那么hash会发生变化;
- 那就意味着两个文件的名称都会发生变化;
chunkhash
chunkhash可以有效的解决上面的问题,它会根据不同的入口进行借来解析来生成hash值:
- 比如我们修改了index.js,那么main.js的chunkhash是不会发生改变的;
contenthash
contenthash表示生成的文件hash名称,只和内容有关系:
- 比如我们的index.js,引入了一个style.css,style.css又被抽取到一个独立的css文件中;
- 这个css文件在命名时,如果我们使用的是chunkhash;
- 那么当index.js文件的内容发生变化时,css文件的命名也会发生变化;
JS/CSS代码压缩
Terser介绍和安装
介绍
-
Terser是一个JavaScript的解释(Parser)、Mangler(绞肉机)/Compressor(压缩机)的工具集;
-
早期我们会使用 uglify-js来压缩、丑化我们的JavaScript代码,但是目前已经不再维护,并且不支持ES6+的语法;
-
Terser是从 uglify-es fork 过来的,并且保留它原来的大部分API以及适配 uglify-es和uglify-js@3等;
-
Terser可以帮助我们压缩、丑化我们的代码,让我们的bundle变得更小。
安装
- 全局安装 npm install terser -g
- 局部安装 npm install terser -D
Terser在webpack压缩JS
真实开发中,我们不需要手动的通过terser来处理我们的代码,我们可以直接通过webpack来处理:
- 在webpack中有一个minimizer属性,在production模式下,默认就是使用TerserPlugin来处理我们的代码的;
如果我们对默认的配置不满意,也可以自己来创建TerserPlugin的实例,并且覆盖相关的配置:
- 首先,我们需要打开minimize,让其对我们的代码进行压缩(默认production模式下已经打开了)
- 其次,我们可以在minimizer创建一个TerserPlugin:
- extractComments:默认值为true,表示会将注释抽取到一个单独的文件中;
- 在开发中,我们不希望保留这个注释时,可以设置为false;
- parallel:使用多进程并发运行提高构建的速度,默认值是true
- 并发运行的默认数量: os.cpus().length - 1;
- 我们也可以设置自己的个数,但是使用默认值即可;
- terserOptions:设置我们的terser相关的配置
- compress:设置压缩相关的选项;
- mangle:设置丑化相关的选项,可以直接设置为true;
- toplevel:顶层变量是否进行转换;
- keep_classnames:保留类的名称;
- keep_fnames:保留函数的名称;
Terser在webpack压缩CSS
- CSS压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等;
- CSS的压缩我们可以使用另外一个插件:css-minimizer-webpack-plugin;
- css-minimizer-webpack-plugin是使用cssnano工具来优化、压缩CSS(也可以单独使用);
安装 css-minimizer-webpack-plugin:
npm install css-minimizer-webpack-plugin -D
在optimization.minimizer中配置:
Tree Shaking
什么是Tree Shaking呢?
- Tree Shaking是一个术语,在计算机中表示消除死代码(dead_code);最早的想法起源于LISP,用于消除未调用的代码(纯函数无副作用,可以放心的消除,这也是为什么要求我们在进行函数式 编程时,尽量使用纯函数的原因之一);后来Tree Shaking也被应用于其他的语言,比如JavaScript、Dart;
JavaScript的Tree Shaking:
- 对JavaScript进行Tree Shaking是源自打包工具rollup(构建工具);
- 这是因为Tree Shaking依赖于ES Module的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系);
- webpack2正式内置支持了ES2015模块,和检测未使用模块的能力;
- 在webpack4正式扩展了这个能力,并且通过 package.json的 sideEffects属性作为标记,告知webpack在编译时,哪里文 件可以安全的删除掉;
- webpack5中,也提供了对部分CommonJS的tree shaking的支持;
JS实现Tree Shaking
webpack实现Tree Shaking采用了两种不同的方案:
- usedExports:通过标记某些函数是否被使用,之后通过Terser来进行优化的;
- sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用;
usedExports实现
- 将mode设置为development模式:
- 为了可以看到 usedExports带来的效果,我们需要设置为 development 模式
- 因为在 production 模式下,webpack默认的一些优化会带来很大的影响。
- 设置usedExports为true和false对比打包后的代码:
- 在usedExports设置为true时,会有一段注释:unused harmony export mul;
- 这段注释的意义是什么呢?告知Terser在优化时,可以删除掉这段代码;
- 这个时候,我们将 minimize设置true:
- usedExports设置为false时,mul函数没有被移除掉;
- usedExports设置为true时,mul函数被移除掉;
- usedExports实现Tree Shaking是结合Terser来完成的。
sideEffects实现
sideEffects用于告知webpack compiler哪些模块时有副作用的:
-
副作用的意思是这里面的代码有执行一些特殊的任务,不能仅仅通过export来判断这段代码的意义;
-
在package.json中设置sideEffects的值:
- 如果我们将sideEffects设置为false,就是告知webpack可以安全的删除未用到的exports;
- 如果有一些我们希望保留,可以设置为数组;
-
比如我们有一个format.js、style.css文件:
- 该文件在导入时没有使用任何的变量来接受;
- 那么打包后的文件,不会保留format.js、style.css相关的任何代码;
项目中最佳实践
在项目中如何对JavaScript的代码进行TreeShaking呢(生成环境)?
- 在optimization中配置usedExports为true,来帮助Terser进行优化;
- 在package.json中配置sideEffects,直接对模块进行优化;
CSS实现TreeShaking
CSS也可以进行Tree Shaking操作:
- CSS的Tree Shaking需要借助于一些其他的插件;
- 在早期的时候,我们会使用PurifyCss插件来完成CSS的tree shaking,但是目前该库已经不再维护了(最新更新也是在4年前 了);
- 目前我们可以使用另外一个库来完成CSS的Tree Shaking:
PurgeCSS
,也是一个帮助我们删除未使用的CSS的工具;
安装PurgeCss的webpack插件:
npm install purgecss-webpack-plugin -D
webpack配置PurgeCss
- paths:表示要检测哪些目录下的内容需要被分析,这里我们可以使用glob;
- 默认情况下,Purgecss会将我们的html标签的样式移除掉,如果我们希望保留,可以添加一个safelist的属性;
Scope Hoisting(作用域提升)
Scope Hoisting从webpack3开始增加的一个新功能。功能是对作用域进行提升,并且让webpack打包后的代码更小、运行更快。
- 默认情况下webpack打包会有很多的函数作用域,包括一些(比如最外层的)IIFE:
- 无论是从最开始的代码运行,还是加载一个模块,都需要执行一系列的函数;
- Scope Hoisting可以将函数合并到一个模块中来运行;
- 使用Scope Hoisting非常的简单,webpack已经内置了对应的模块:
- 在production模式下,默认这个模块就会启用;
- 在development模式下,我们需要自己来打开该模块;
Webpack对文件压缩
什么是HTTP压缩
HTTP压缩是一种内置在 服务器 和 客户端 之间的,以改进传输速度和带宽利用率的方式;
HTTP压缩的流程什么呢?
- 第一步:HTTP数据在服务器发送前就已经被压缩了;(可以在webpack中完成)
- 第二步:兼容的浏览器在向服务器发送请求时,会告知服务器自己支持哪些压缩格式;
- 第三步:服务器在浏览器支持的压缩格式下,直接返回对应的压缩后的文件,并且在响应头中告知浏览器;
目前的流行压缩格式
目前的压缩格式非常的多:
- compress – UNIX的“compress”程序的方法(历史性原因,不推荐大多数应用使用,应该使用gzip或deflate);
- deflate – 基于deflate算法(定义于RFC 1951)的压缩,使用zlib数据格式封装;
- gzip – GNU zip格式(定义于RFC 1952),是目前使用比较广泛的压缩算法;
- br – 一种新的开源压缩算法,专为HTTP内容的编码而设计;
Webpack配置文件压缩
- webpack中相当于是实现了HTTP压缩的第一步操作,我们可以使用CompressionPlugin。
- 第一步,安装CompressionPlugin:
- npm install compression-webpack-plugin -D
- 第二步,使用CompressionPlugin即可:
HTML代码压缩
Plugin。
- 第一步,安装CompressionPlugin:
- npm install compression-webpack-plugin -D
- 第二步,使用CompressionPlugin即可:
[外链图片转存中…(img-CyqewQfe-1691678675437)]
HTML代码压缩