Webpack 几个概念:
- module: 模块,在 webpack 眼里,任何能够被导入导出的文件都是一个模块。
-
chunk: chunk 是 webpack 拆分进去的:
- entry chunk:每个入口文件都是一个 chunk
- 通过 import、require 引入的代码
- children chunk:通过 splitChunks 拆分进去的代码
- commons chunk: 通过 CommonsChunkPlugin 创立进去的文件
- bundle: webpack 打包进去的文件,也能够了解为就是对 chunk 编译压缩打包等解决后的产出。
webpack 优化
问题剖析:
- 外围问题:多页利用打包后代码冗余,文件体积大。
- 根本原因:雷同模块在不同入口之间没有失去复用,bundle 之间比拟独立。
解决思路:
- 解决代码冗余。把不同入口之间,独特援用的模块,抽离进去,放到一个公共模块中。这样不论这个模块被多少个入口援用,都只会在最终打包后果中呈现一次。
- 减小文件体积。当把这些独特援用的模块都堆在一个模块中,这个文件可能异样微小,也是不利于网络申请和页面加载的。所以咱们须要把这个公共模块再依照肯定规定进一步拆分成几个模块文件。
如何拆分,形式因人而异,因我的项目而异。拆分准则有:
- 业务代码和第三方库拆散打包,实现代码宰割;
- 业务代码中的公共业务模块提取打包到一个模块;
- 第三方库最好也不要全副打包到一个文件中,因为第三方库加起来通常会很大。能够把特地大的库独立打包,剩下的加起来如果还很大,就把它依照肯定大小切割成若干模块。
提取公共模块
通过将公共模块拆出来,最终合成的文件在最开始的时候加载一次,便存到缓存中供后续应用。这个带来速度上的晋升,因为浏览器会迅速将公共的代码从缓存中取出来,而不是每次拜访一个新页面时,再去加载一个更大的文件。
webpack 提供了一个十分好的内置插件帮咱们实现这一需要:
CommonsChunkPlugin
。不过在 webpack4 中CommonsChunkPlugin
被删除,取而代之的是optimization.splitChunks
。
CommonsChunkPlugin
CommonsChunkPlugin
插件,是一个可选的用于建设一个独立文件 (又称作 chunk) 的性能,这个文件包含多个入口 chunk
的公共模块。
配置选项:
- name:能够是曾经存在的 chunk(个别指入口文件)对应的 name,那么就会把公共模块代码合并到这个 chunk 上;否则,会创立名字为 name 的 commons chunk 进行合并
- filename:指定 commons chunk 的文件名
- chunks:指定 source chunk,即指定从哪些 chunk 当中去找公共模块,省略该选项的时候,默认就是 entry chunks
-
children
- 指定为 true 的时候,就代表 source chunks 是通过 entry chunks(入口文件)进行 code split 进去的 children chunks
- children 和 chunks 不能同时设置,因为它们都是指定 source chunks 的
- children 能够用来把 entry chunk 创立的 children chunks 的共用模块合并到本身,但这会导致初始加载工夫较长
- async:即解决 children:true 时合并到 entry chunks 本身时初始加载工夫过长的问题。async 设为 true 时,commons chunk 将不会合并到本身,而是应用一个新的异步的 commons chunk。当这个 children chunk 被下载时,主动并行下载该 commons chunk
- minChunks:既能够是数字,也能够是函数,还能够是 Infinity,默认值是 2
minChunks 含意:
数字:模块被多少个 chunk 公共援用才被抽取进去成为 commons chunk
函数:承受 (module, count) 两个参数,返回一个布尔值,你能够在函数内进行你规定好的逻辑来决定某个模块是否提取成为 commons chunk
Infinity:只有当入口文件(entry chunks)>= 3 才失效,用来在第三方库中拆散自定义的公共模块
根本应用
1. 拆散出第三方库、自定义公共模块、webpack 运行文件,放在同一个文件中
批改 webpack.config.js 新增一个入口文件 vendor,应用 CommonsChunkPlugin 插件进行公共模块的提取:
const path = require("path");
const webpack = require("webpack");
const packageJson = require("./package.json");
module.exports = {
entry: {
first: './src/first.js',
second: './src/second.js',
// 新增一个入口文件 vendor
vendor: Object.keys(packageJson.dependencies)
},
output: {path: path.resolve(__dirname,'./dist'),
filename: '[name].js'
},
plugins: [
// 应用 CommonsChunkPlugin 插件进行公共模块的提取
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: '[name].js'
}),
]
}
生成 dist 文件夹下文件有:first.js, second.js, vendor.js。
通过查看 vendor.js 文件,发现 first.js 和 second.js 文件中依赖的第三方库和自定义公共模块都被打包进 vendor.js 中,同时还有 webpack 的运行文件。
2. 独自拆散出第三方库、自定义公共模块、webpack 运行文件
plugins: [
// 抽离第三方库与 webpack 运行文件
new webpack.optimize.CommonsChunkPlugin({name: ['vendor','runtime'],
// 创立 runtime.js 进行 webpack 运行文件的抽离,其中 source chunks 是 vendor.js
filename: '[name].js',
minChunks: Infinity
}),
// 抽离自定义公共模块
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
filename: '[name].js',
chunks: ['first','second'],// 从 first.js 和 second.js 中抽取 commons chunk
}),
]
生成 dist 文件夹下文件有:first.js, second.js, vendor.js, runtime.js, common.js
splitChunks
属性
- cacheGroups:
cacheGroups
是splitChunks
配置的外围,在cacheGroups
缓存组里配置代码的拆分规定。缓存组的每一个属性都是一个配置规定, 例如配置 default 属性,属性名能够不叫 default 能够本人定。属性的值是一个对象。 - name: 提取进去的公共模块将会以这个来命名,能够不配置,如果不配置,就会生成默认的文件名,大抵格局是
index/a.js
这样的。 - chunks: 指定哪些类型的 chunk 参加拆分,值能够是 string 能够是函数。如果是 string,能够是这个三个值之一:
all
,async
,initial
。all
代表所有模块,async
代表异步加载的模块,initial
代表初始化时就能获取的模块。如果是函数,则能够依据 chunk 参数的 name 等属性进行更粗疏的筛选。 -
minChunks:
splitChunks
是自带默认配置的,而缓存组默认会继承这些配置,其中有个minChunks
属性:- 它管制的是每个模块什么时候被抽离进来:当模块被不同 entry 援用的次数大于等于这个配置值时,才会被抽离进来。
- 它的默认值是 1。也就是任何模块都会被抽离进来(入口模块其实也会被 webpack 引入一次)。
- minSize
minSize 设置生成文件的最小大小,单位是字节。如果一个模块合乎之前所说的拆分规定,然而如果提取进去最初生成文件大小比 minSize 要小,那它不会被提取进去。这个属性能够在每个缓存组属性中设置,也能够在 splitChunks 属性中设置,在每个缓存组都会继承这个配置。
- priority
priority 设置拆分规定的优先级,属性值为数字,能够为正数。当某个模块同时合乎一个以上的规定时,通过优先级属性 priority 来决定应用哪个拆分规定。优先级高者执行。
- test
test 设置缓存组抉择的模块,与 chunks 属性的作用有一点像,然而维度不一样。test 的值能够是一个正则表达式,也能够是一个函数。它能够匹配模块的相对资源门路或 chunk 名称,匹配 chunk 名称时,将抉择 chunk 中的所有模块。
实例
1. 实现代码拆散:
//webpack.config.js
optimization: {
splitChunks: {
cacheGroups: {
default: {
name: 'common',
chunks: 'initial',
minChunks: 2, // 模块被援用 2 次以上的才抽离
}
}
}
}
进入 dist 目录查看:common.js
: 蕴含援用 2 次以上的所有模块
2. 拆散第三方库与自定义组件库
//webpack.config.js
optimization: {
splitChunks: {
minSize: 300, // 提取出的 chunk 的最小大小
cacheGroups: {
default: {
name: 'common',
chunks: 'initial',
minChunks: 2, // 模块被援用 2 次以上的才抽离
priority: -20,
},
// 拆分第三方库(通过 npm|yarn 装置的库)vendors: {test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'initial',priority: -10,
},// 拆分指定文件
locallib: {test: /(src\/locallib\.js)$/,
name: 'locallib',
chunks: 'initial',
priority: -9
}
}
}
}
进入 dist 目录查看:common.js
,vendor.js
蕴含第三方库代码,locallib.js
蕴含 locallib
模块的代码。
拆散动静库
什么是 DLL
DLL(Dynamic Link Library)文件为动态链接库文件。在 Windows 中,许多应用程序并不是一个残缺的可执行文件,它们被宰割成一些绝对独立的动态链接库,即 DLL 文件,搁置于零碎中。当咱们执行某一个程序时,相应的 DLL 文件就会被调用。
为什么应用 DLL
通常来说,咱们的代码都能够至多简略辨别成业务代码和第三方库。如果不做解决,每次构建时都须要把所有的代码从新构建一次,消耗大量的工夫。而后大部分状况下,很多第三方库的代码并不会产生变更(除非是版本升级),这时就能够用到 dll:把复用性较高的第三方模块打包到动态链接库中,在不降级这些库的状况下,动静库不须要从新打包,每次构建只从新打包业务代码。
如何应用
DllPlugin
和 DllReferencePlugin
用某种办法实现了拆分 bundles,大幅度晋升了构建的速度。应用 DLL 时,能够把构建过程分成 dll 构建过程和主构建过程,所以须要两个构建配置文件,例如叫做 webpack.config.js
和webpack.dll.config.js
。
1. 应用 DLLPlugin
打包须要拆散到动静库的模块
DllPlugin
是 webpack
内置的插件,不须要额定装置,间接配置 webpack.dll.config.js
文件。此插件用于在独自的 webpack 配置中创立一个 dll-only-bundle,会生成一个名为 manifest.json
的文件,这个文件是用于让 DllReferencePlugin
可能映射到相应的依赖上。
context
(可选):manifest 文件中申请的 context (默认值为 webpack 的 context)format
(boolean = false):如果为true
,则 manifest json 文件 (输入文件) 将被格式化。name
:暴露出的 DLL 的函数名(TemplatePaths:[fullhash]
&[name]
)path
:manifest.json 文件的 绝对路径(输入文件)entryOnly
(boolean = true):如果为true
,则仅裸露入口type
:dll bundle 的类型
咱们倡议 DllPlugin 只在
entryOnly: true
时应用,否则 DLL 中的 tree shaking 将无奈工作,因为所有 exports 均可应用。
// webpack.dll.config.js
module.exports = {
entry: {
// 第三方库
react: ['react', 'react-dom', 'react-redux']
},
output: {// 输入的动态链接库的文件名称,[name] 代表以后动态链接库的名称,filename: '[name].dll.js',
path: resolve('dist/dll'),
// library 必须和前面 dllplugin 中的 name 统一
library: '[name]_dll_[hash]'
},
plugins: [
new webpack.DllPlugin({
// 动态链接库的全局变量名称,须要和 output.library 中保持一致
// 该字段的值也就是输入的 manifest.json 文件 中 name 字段的值
name: '[name]_dll_[hash]',
// 形容动态链接库的 manifest.json 文件输入时的文件名称
path: path.join(__dirname, 'dist/dll', '[name].manifest.json')
}),
]
}
2. 在主构建配置文件应用 DllReferencePlugin 援用动静库文件
在 webpack.config.js
中应用 dll 要用到DllReferencePlugin
, 此插件会把 dll-only-bundles 援用到须要的预编译的依赖中。
context
:(绝对路径 )manifest (或者是内容属性) 中申请的上下文extensions
:用于解析 dll bundle 中模块的扩展名 (仅在应用 ‘scope’ 时应用)。manifest
:蕴含content
和name
的对象,或者是一个字符串 —— 编译时用于加载 JSON manifest 的绝对路径content
(可选):申请到模块 id 的映射(默认值为manifest.content
)name
(可选):dll 裸露中央的名称(默认值为manifest.name
)(可参考externals
)scope
(可选):dll 中内容的前缀sourceType
(可选):dll 是如何裸露的 (libraryTarget)
通过援用 dll 的 manifest 文件来把依赖的名称映射到模块的 id 上,之后再在须要的时候通过内置的 __webpack_require__
函数来 require
对应的模块。
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./dist/dll/react.manifest.json')
}),
第一步产出的 manifest
文件就用在这里,给主构建流程作为查找 dll 的根据:DllReferencePlugin 去 manifest.json 文件读取 name 字段的值,把值的内容作为在从全局变量中获取动态链接库中内容时的全局变量名,因而:在 webpack.dll.config.js 文件中,DllPlugin 中的 name 参数必须和 output.library 中保持一致。
3. 在入口文件引入 dll 文件
生成的 dll 暴露出的是全局函数,因而还须要在入口文件外面引入对应的 dll 文件。
<body>
<div id="app"></div>
<!-- 援用 dll 文件 -->
<script src="../../dist/dll/react.dll.js"></script>
</body>
应用 DLL 作用
1. 拆散代码,业务代码和第三方模块能够被打包到不同的文件里,这个有几个益处:
- 防止打包出单个文件的大小太大,不利于调试
- 将单个大文件拆成多个小文件之后,肯定状况下有利于加载(不超出浏览器一次性申请的文件数状况下,并行下载必定比串行快)
2. 晋升构建速度。第三方库没有变更时,因为咱们只构建业务相干代码,相比全副从新构建天然要快的多。
移除不必要的文件
moment.js
日期解决库,占用很大的体积,因为所有的 locale
文件都被引入,而这些文件在整个库的体积中占了大部分,因而当 webpack 打包时移除这部分内容会让打包文件的体积有所减小。
webpack 自带的两个库能够实现这个性能:
- IgnorePlugin
- ContextReplacementPlugin
IgnorePlugin
的应用办法如下:
// 插件配置
plugins: [
// 疏忽 moment.js 中所有的 locale 文件
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
// 应用形式
const moment = require('moment');
// 引入 zh-cn locale 文件
require('moment/locale/zh-cn');
moment.locale('zh-cn');
ContextReplacementPlugin
的应用办法如下:
// 插件配置
plugins: [
// 只加载 locale zh-cn 文件
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
],
// 应用形式
const moment = require('moment');
moment.locale('zh-cn');
模块化引入
在我的项目中应用了 lodash
这个很罕用的工具库,然而在应用这类工具库的时候往往只应用到了其中的很少的一部分性能,但却把整个库都引入了。因而这里也能够进一步优化,只援用须要的局部。
import {chain, cloneDeep} from 'lodash';
// 或者
import chain from 'lodash/chain';
import cloneDeep from 'lodash/cloneDeep';
压缩混同代码
咱们平时也会对代码进行压缩混同,能够通过 UglifyJS
等工具来对 js 代码进行压缩,同时能够去掉不必要的空格、正文、console 信息等,也能够无效的减小代码体积。
webpack 基本功能
webpack hash 区别
hash 个别是联合 CDN 缓存来应用,通过 webpack 构建之后,生成对应文件名主动带上对应的 MD5 值。如果文件内容扭转的话,那么对应文件哈希值也会扭转,对应的 HTML 援用的 URL 地址也会扭转,触发 CDN 服务器从源服务器上拉取对应数据,进而更新本地缓存。
- hash
hash 是跟整个我的项目的构建相干,只有我的项目里有文件更改,整个我的项目构建的 hash 值都会更改。同一次构建过程中生成的哈希都是一样的。
output:{path:path.join(__dirname, '/dist'),
filename: 'bundle.[name].[hash].js',
}
- chunkhash
依据不同的入口文件 (Entry) 进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。把一些公共库和程序入口文件辨别开,独自打包构建,接着咱们采纳 chunkhash 的形式生成哈希值,那么只有咱们不改变公共库的代码,就能够保障其哈希值不会受影响。
output:{path:path.join(__dirname, '/dist/js'),
filename: 'bundle.[name].[chunkhash].js',
}
采纳 chunkhash,我的项目主入口文件 Index.js 及其对应的依赖文件 Index.css 因为被打包在同一个模块,共用雷同的 chunkhash。因为公共库是不同的模块,有独自的 chunkhash。所以 Index 文件的更改不会影响公共库。如果 index.js 更改了代码,css 未扭转,因为该模块产生了扭转,导致 css 文件会反复构建。
- contenthash
依据文件内容创立出惟一 hash。当文件内容发生变化时,[contenthash] 才会发生变化。
output: {filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
path: path.resolve(__dirname, '../dist'),
}
模块热更新
模块热替换 (hot module replacement 或 HMR) 是 webpack 提供的最有用的性能之一。它容许在运行时更新所有类型的模块,而无需齐全刷新。
实现
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
{
devServer: {
contentBase: './dist',
hot: true, // DevServer 开启模块热替换模式
},
plugins: [new CleanWebpackPlugin(),
new HtmlWebpackPlugin({title: 'Hot Module Replacement',}),
],
}
filename 和 chunkFilename 的区别
filename
指 列在 entry
中,打包后输入的文件的名称。
chunkFilename
指 未列在 entry
中,却又须要被打包进去的文件的名称。默认应用 [id].js 或从 output.filename 中推断出的值([name] 会被事后替换为 [id] 或 [id].)。
// webpack.config.js
module.exports = {
entry: './src/index.js',
output: {filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
chunkFileName: '[name].bundle.js'
}
}
CSS Modules 模块化
现状
因为 css 不是编程语言,所以不能申明变量、函数,不能做判断、循环和计算,也不能嵌套。为了解决这个问题,衍生了两种拓展语言 less 与 sass,它们兼容 css,并且拓展了编程的性能,次要是带来了以下的个性:
- 能够申明变量、函数,能够进行一些简略的计算、判断、循环;
- 能够嵌套选择器,这样节俭了书写的内容,也更具浏览性;
@import
防止反复导入问题,因而能够放心大胆的导入其余文件。
从模块化的角度来讲,less 与 sass 只是裁减了 css 的性能,但并没有在语言的层面做模块化,因为全局命名抵触的问题仍然还在。
实现模块化
想要让 css 具备模块化性能,临时还不能从语言的层面来思考,所以只能从工具的角度来实现。目前比拟好的形式是应用 js
来加载 css
文件,并将 css
的内容导出为一个对象,应用 js
来渲染整个 dom 树和匹配相应的款式到对应的元素。
css 文件倡议遵循如下准则
- 不应用选择器与 id,只应用 class 名来定义款式(因为只有
.class
能力导出为对象的属性) - 不层叠多个 class,只应用一个 class 把所有款式定义好
- 所有款式通过
composes
组合来实现复用 - 不嵌套
- 举荐用
.className
书写,而非.class-name
(前者能够通过styles.className
拜访,后者须要通过styles['class-name']
能力拜访)。
实例
/* dialog.css */
.root {}
.confirm {}
.disabledConfirm {}
js 文件引入dialog.css
, 应用 classnames 库来操作 class 名:
/* dialog.jsx */
import classNames from 'classnames';
import styles from './dialog.css';
export default class Dialog extends React.Component {render() {
const cx = classNames({[styles.confirm]: !this.state.disabled,
[styles.disabledConfirm]: this.state.disabled
});
return <div className={styles.root}>
<a className={cx}>Confirm</a>
...
</div>
}
}
如果你不想频繁的输出
styles.**
,能够试一下 react-css-modules,它通过高阶函数的模式来防止反复输出styles.**
。
依赖 webpack:css-loader
这个性能须要构建工具的反对,如果应用 webpack,能够应用 css-loader,并设置 options.modules
为 true
,便可应用模块化的性能了。css-loader
解析 @import 和 url(),会 import/require()
后再解析 (resolve) 它们。css-loader
配置项:
名称 | 类型 | 默认值 | 形容 | |
---|---|---|---|---|
root | String | root 值将被增加到 URL 后面,而后再进行转译。因为对于以 / 结尾的 URL,默认行为是不转译。 |
||
url | Boolean | true | 启用 / 禁用解析 url() |
|
alias | Object | {} | 给 url 创立别名。用别名重写你的 URL,在难以扭转输出文件的 url 门路时,这会很有帮忙。 | |
import | Boolean | true | 启用 / 禁用 @import 解决 | |
minimize | Boolean\ | Object | false | 启用 / 禁用 压缩 |
sourceMap | Boolean | false | 启用 / 禁用 Sourcemap | |
importLoaders | Number | 0 | 在 css-loader 前利用的 loader 的数量 | |
modules | Boolean | false | 启用 / 禁用 CSS 模块 | |
camelCase | Boolean\ | String | false | 是否以驼峰化式命名导出类名 |
localIdentName | String | [hash:base64] | 配置生成的类名标识符(ident) |
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
loader: "css-loader",
options: {modules: true,},
},
],
},
};
loader 和 plugin 的区别
loader:
是一个转换器,将 A 文件进行编译成 B 文件,比方:将 A.less 转换为 A.css,单纯的文件转换过程。
是一个导出为 function 的 node 模块。能够将匹配到的文件进行一次转换,同时 loader 能够链式传递。
plugin:
是一个扩展器,通过钩子能够波及整个构建流程,能够做一些在构建范畴内的事件。
它并不间接操作文件,而是基于事件机制工作,会监听 webpack 打包过程中的某些节点,执行宽泛的工作。
罕用 loader
- 款式:style-loader、css-loader、less-loader、sass-loader 等
- 文件:raw-loader、file-loader、url-loader 等
- 编译:babel-loader、coffee-loader、ts-loader 等
- 校验测试:mocha-loader、jshint-loader、eslint-loader 等
sass-loader
转化 sass 为 css 文件,并且包一层 module.exports 成为一个 js module。
css-loader
解析 @import 和 url()。
style-loader
将创立一个 style 标签将 css 文件嵌入到 html 中。
vue-loader、coffee-loader、babel-loader
等能够将特定文件格式转成 js 模块、将其余语言转化为 js 语言和编译下一代 js 语言。
file-loader
能够解决资源,file-loader 能够复制和搁置资源地位,并能够指定文件名模板,用 hash 命名更好利用缓存。
url-loader
能够解决资源, 将小于配置 limit 大小的文件转换成内敛 Data Url 的形式,缩小申请。
raw-loader
能够将文件以字符串的模式返回
imports-loader、exports-loader
能够向模块注入变量或者提供导出模块性能。
罕用 Plugin
- webpack 内置
UglifyJsPlugin
,压缩和混同代码。 - webpack 内置
CommonsChunkPlugin
,将指定的模块或专用模块打包进去,缩小主 bundle 文件的体积,配合缓存策略,放慢利用访问速度。 - webpack 内置
DllPlugin
和DllReferencePlugin
相互配合,前置第三方包的构建,只构建业务代码,同时能解决 Externals 屡次援用问题。DllReferencePlugin 援用 DllPlugin 配置生成的 manifest.json 文件,manifest.json 蕴含了依赖模块和 module id 的映射关系 html-webpack-plugin
能够依据模板主动生成 html 代码,并主动援用 css 和 js 文件extract-text-webpack-plugin
将 js 文件中援用的款式独自抽离成 css 文件HotModuleReplacementPlugin
热更新optimize-css-assets-webpack-plugin
不同组件中反复的 css 能够疾速去重webpack-bundle-analyzer
一个 webpack 的 bundle 文件剖析工具,将 bundle 文件以可交互缩放的 treemap 的模式展现。compression-webpack-plugin
生产环境可采纳 gzip 压缩 JS 和 CSShappypack
:通过多过程模型,来减速代码构建clean-wenpack-plugin
清理每次打包后没有应用的文件
webpack 文件拆散思维
现状
为什么要拆散第三方库?
第三方库是比较稳定,不会轻易扭转的,利用浏览器缓存后,用户再次加载页面会缩小服务器申请,进步速度优化体验。提取多个利用(入口)公共模块的作用和他相似,公共局部会被缓存,所有利用都能够利用缓存内容从而进步性能。
拆散第三方库就能利用浏览器换缓存了么?
答案是否定的。导致无奈利用缓存的因素有很多,比方每次拆散的库文件从新打包都会失去不同的名称,后盾的共事给 js 文件设置的缓存过期工夫为 0,只有文件是齐全不变的,包含批改工夫,文件内容等,仍然会利用缓存。
浏览器缓存机制是什么样的?
HTTP1.1 给的策略是应用 Cache-control 配合 Etag。
Apache 中,ETag 的值默认是对文件的索引节(INode),大小(Size)和最初批改工夫(MTime)进行 Hash 后失去的。如果 Etag 雷同,仍然不会申请新资源,而会应用以前的文件。
文件拆散插件
CommonsChunkPlugin 与 SplitChunksPlugin
作用
将公共模块抽离。每次打包的时候都会从新打包,还是会去解决一些第三方依赖库,只是它能把第三方库文件和咱们的代码离开掉,生成一个独立的 js 文件。然而它还是 不能进步打包的速度。
自 webpack 4.0 上线之后,CommonsChunkPlugin 已被替换成 SplitChunksPlugin,旨在优化 chunk 的拆分。
CommonsChunkPlugin
设计思路:满足 minChunks 的援用次数时,都会将对应的模块抽离如一个新的 chunk 文件中,这个文件为所有的业务文件的父级。
这种设计思路带来了会造成模块打包冗余。总的来说会造成这么几个问题:
- 产出的 chunk 在引入时,会蕴含反复的代码;
- 无奈优化异步 chunk;
- 高优的 chunk 产出须要的 minchunks 配置比较复杂。
SplitChunksPlugin
SplitChunksPlugin 优化了 webpack 的打包策略,应用主动反复算法,会主动计算出各页面公共的包援用以及局部页面公共的包援用,当然,对于那些局部共有然而阈值过小的文件其不会创立独自的输入文件,因为其大小不值得去新开一个申请。(缓存策略配置在 cacheGroup 中)
SplitChunksPlugin 默认的分包策略基于以下 4 个条件:
- 新代码块能够被共享援用,或这些模块都是来自 node_modules;
- 新产出的 vendor-chunk 的大小得大于 30kb;
- 按需加载的代码块(vendor-chunk)并行申请的数量不多于 5 次;
- 初始加载的代码块,并行申请的数量不多于 3 次。
- SplitChunksPlugin 配合应用 RuntimeChunk 对运行时的 hash 变动做优化(相当于 CommonsChunkPlugin 的两次应用)
- 缩小
maxInitial/AsyncRequest
会加大 module 的冗余,然而会进一步的缩小申请。
DllPlugin 与 DllReferencePlugin
应用
DLLPlugin 这个插件是在一个额定独立的 webpack 设置中创立一个只有 dll 的 bundle,也就是说,除了 webpack.config.js,我的项目中还会新建一个 webpack.dll.config.js 文件来配置 dll 的打包。webpack.dll.config.js 作用是把所有的第三方库依赖打包到一个 bundle 的 dll 文件外面,还会生成一个名为 manifest.json 文件。该 manifest.json 的作用是用来让 DllReferencePlugin 映射到相干的依赖下来的。(可类比 CommonsChunkPlugin 的两次打包或者 RuntimeChunk 的运行包配置)
设计思路
DLLPlugin 是提前将公共的包构建进去,使得在 build 时过滤掉这些构建过的包,使得在正是构建时的速度缩短。所以其相对来说 打包速度会更快。
举荐应用策略
- 如果是单页利用,只用 DllPlugin 打包库文件即可,业务代码一个包搞定。
- 如果是多页利用,DllPlugin 打包库文件,如果有很多公共的业务代码而且可能随时变动,就须要应用 CommonsChunkPlugin 提取公共业务代码。在页面间切换时,公共局部还是会被缓存的。
参考:
webpack 文件拆散思维
webpack 运行机制
webpack 的运行过程能够简略概述为如下流程:
初始化配置参数 -> 绑定事件钩子回调 -> 确定 Entry 逐个遍历 -> 应用 loader 编译文件 -> 输入文件
webpack 事件流
什么是 webpack 事件流?
Webpack 就像一条生产线,要通过一系列解决流程后能力将源文件转换成输入后果。这条生产线上的每个解决流程的职责都是繁多的,多个流程之间有存在依赖关系,只有实现以后解决后能力交给下一个流程去解决。插件就像是一个插入到生产线中的一个性能,在特定的机会对生产线上的资源做解决。Webpack 通过 Tapable 来组织这条简单的生产线。Webpack 在运行过程中会播送事件,插件只须要监听它所关怀的事件,就能退出到这条生产线中,去扭转生产线的运作。Webpack 的事件流机制保障了插件的有序性,使得整个零碎扩展性很好。— 吴浩麟《深入浅出 webpack》
咱们将 webpack 事件流了解为 webpack 构建过程中的一系列事件,他们别离示意着不同的构建周期和状态,咱们能够像在浏览器上监听 click 事件一样监听事件流上的事件,并且为它们挂载事件回调。咱们也能够自定义事件并在适合机会进行播送,这一切都是应用了 webpack 自带的模块 Tapable
进行治理的。咱们不须要自行装置 Tapable
,在 webpack 被装置的同时它也会一并被装置,如需应用,咱们只须要在文件里间接 require
即可。
Tapable 的原理
Tapable 的原理其实就是咱们在前端进阶过程中都会经验的 EventEmit,通过发布者 - 订阅者模式实现,它的局部外围代码能够概括成上面这样:
class SyncHook{constructor(){this.hooks = [];
}
// 订阅事件
tap(name, fn){this.hooks.push(fn);
}
// 公布
call(){this.hooks.forEach(hook => hook(...arguments));
}
}
webpack 运行流程详解
- 首先,webpack 会读取你在命令行传入的配置以及我的项目里的
webpack.config.js
文件,初始化本次构建的配置参数,并且执行配置文件中的插件实例化语句,生成 Compiler 传入 plugin 的 apply 办法,为 webpack 事件流挂上自定义钩子。 - 接下来到了 entryOption 阶段,webpack 开始读取配置的 Entries,递归遍历所有的入口文件
- Webpack 进入其中一个入口文件,开始 compilation 过程。先应用用户配置好的 loader 对文件内容进行编译(buildModule),咱们能够从传入事件回调的 compilation 上拿到 module 的 resource(资源门路)、loaders(通过的 loaders)等信息;之后,再将编译好的文件内容应用 acorn 解析生成 AST 动态语法树(normalModuleLoader),剖析文件的依赖关系一一拉取依赖模块并反复上述过程,最初将所有模块中的
require
语法替换成__webpack_require__
来模仿模块化操作。 - emit 阶段,所有文件的编译及转化都曾经实现,蕴含了最终输入的资源,咱们能够在传入事件回调的
compilation.assets
上拿到所需数据,其中包含行将输入的资源、代码块 Chunk 等等信息。
参考:
Webpack 揭秘——走向高阶前端的必经之路