使用webpack时非常简单,就是提供了一个webpack config,然后执行webpack提供的全局方法,就可以打包编译了。
整个编译打包过程:
module graph
mudule graph
分离为initial chunk
、async chunk
、runtime chunk
、common chunk
generator
为每个chunk的module构建内容,然后再为chunk构建内容构建module graph过程:
module
对象loader
处理parser
解析loader
处理的内容dependencies
、blocks
中收集的依赖,重复2-5,直到所有的依赖处理完成构建module的过程:
绝对路径
、loader
、parser
、generator
module
对象loader
提供的方法、读取源文件内容、使用loader
处理源文件内容、使用parser
解析源文件内容、收集依赖、处理依赖chunks分类:
lazy module
对应的chunkoptomization.runtimeChunk:true
从initial chunk
中分离出来,负责安装chunk
、安装module
、加载lazy chunk
optimization.splitChunks
策略分离出来的chunks构建bundle:
chunk
的类型,获取对应的template
output.filename
构建bundle
的文件名chunks
的module
,使用generator
为每一个module
构建输出内容template
,结合module
的构建内容,构建chunk的输出内容bundle
文件并输出到output
指定位置hash:
module
的源文件内容、模块ID生成chunk
的name
、所有module
的module hash
生成chunk.contentHash.javascript
:chunk中所有的js内容生成的hashchunk.contentHash["css/mini-extract"]
:chunk中所有的css内容生成的hashchunk hash
信息生成[name].[hash].js
中的 hash 使用的是 compilation hash
,所有的 bundle 都一样;
触发条件:
devServer.hot: true
inline
模式module.hot.accept(url, callback)
热更新的工作原理:
webSocket
对象,注册message
事件chunk
文件,chunk
文件中包含更新的module
,然后通过webSocket
通知浏览器更新webSocket
对象触发message
事件,会收到一个hash值和一个ok信息,然后通过动态添加script
元素,加载新的chunk
文件module id
在应用缓存中找到之前缓存的module
,然后以module
为基础,递归遍历module.parent
属性,查找定义module.hot.accept
的parent module
。如果没有找到,热更新不起作用,只能通过reload
页面来显示更新。在递归过程中,我们会把遇到的module id
存储起来module.hot.accept
的parent module
之后,根据4收集的module id
,将installedModules
中对应的module
清除,然后根据module.hot.accept(url, callbcak) 中的url,重新安装关联的moduleloader就是一个node模块,它输出了一个函数。当某种资源需要用这个loader转换时,这个函数就会被调用。并且这个函数可以通过提供给它的this上下文访问Loader API。reverse-txt-loader
解析顺序:从下到上,从右往左
// 定义
module.exports = function(src) {
//src是原文件内容(abcde),下面对内容进行处理,这里是反转
var result = src.split('').reverse().join('');
//返回JavaScript源码,必须是String或者Buffer
return `module.exports = '${result}'`;
}
//使用
{
test: /\.txt$/,
loader: 'reverse-txt-loader'
}
UglifyJsPlugin
require.ensure
=bcakground-image
,background-position
,width
,height
控制显示部分背景: 项目中,有一个入口文件,相当于一棵树的主干,入口文件由很多依赖的模块,相当于树枝。实际情况中,虽然依赖了某个模块,但其实只使用了其中的某些功能。通过tree-shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的。
思路: 基于es6提供的模块系统对代码进行静态分析,并将代码中的死代码移除的一种技术。因此,利用tree shaking技术可以很方便地实现我们代码上的优化,减少代码体积。
tree-shaking删除代码的原理: webpack基于es6提供的模块系统,对代码的依赖树进行静态分析,把import & export标记为3类
为何基于es6模块实现(es6 module特点):
条件:
配置方法:
在package.json里添加一个属性
{
// sideEffects如果设为false,webpack就会认为所有没用到的函数都是没副作用的,即删了也没关系。
"sideEffects": false,
// 设置黑名单,用于防止误删代码
"sideEffects": [
// 数组里列出黑名单,禁止shaking下列代码
"@babel/polly-fill",
"*.less",
// 其它有副作用的模块
"./src/some-side-effectful-file.js"
],
}
tree-shaking 摇掉代码中未使用的代码在生产模式下自动开启,tree-shaking并不是webpack中的某一配置选项,是一组功能搭配使用后的优化效果。
// 在开发模式下,设置 usedExports: true ,打包时只会标记出哪些模块没有被使用,不会删除,因为可能会影响 source-map的标记位置的准确性。
{
mode: 'develpoment',
optimization: {
// 优化导出的模块
usedExports: true
},
}
// 在生产模式下默认开启 usedExports: true ,打包压缩时就会将没用到的代码移除
{
mode: 'production',
// 这个属性的作用就是集中配置webpack内部的优化功能
optimizition: {
// 只导出外部使用的模块成员 负责标记枯树叶
usedExports: true,
minimize: true, // 自动压缩代码 负责摇掉枯树叶
/**
* webpack打包默认会将一个模块单独打包到一个闭包中
* webpack3中新增的API 将所有模块都放在一个函数中 ,尽可能将所有模块合并在一起,
* 提升效率,减少体积 达到作用域提升的效果
*/
concatenateModules: true,
},
}
使用tree-shaking的注意事项:
tree-shaking & babel使用babel-loader处理js代码会导致tree-shaking失效的原因:
解决方法:
收到配置preset-env的modules:false
,确保不会开启自动转换的插件
presets: [
['@babel/preset-env', {module: 'commonjs'}]
]
主要从以下几个方面进行优化:
1)缩小文件的搜索范围
loader
include表示哪些目录中的文件需要进行babel-loader,exclude表示哪些目录文件不进行babel-loader。这是因为在引入第三方模块的时候,很多模块已经是打包好的,不需要再处理。如果不设置include/exclude就会被loader处理,增加打包时间。
{
rules: [{
test: /\.js$/,
use: {
loader: 'babel-loader'
},
// exclude: /node_modules/,
include: [path.resolve(__dirname, 'src')]
}]
}
noParse
对于JQuery、lodash、chartjs等一些库,庞大且没用采用模块化标准,因此我们可以选择不解析它们。如果一下第三方模块没用使用AMD/CommonJs规范,可以使用noParse来标记这个模块,这样webpack在导入模块时,就不进行解析转换,可以提升webpack的构建速度。
⚠️noParse可以接受正则表达式 or 一个函数:
{
module: {
// noParse: /jquery|lodash|chartjs/,
noParse: function(content){
return /jquery|lodash|chartjs/.test(content)
}
}
}
resolve.modules
modules告诉webpack去哪些目录下查找引用的恶模块,默认值[“node_modules”]。我们的代码中也会有大量的模块被其他模块依赖和引入,由于分布不固定,有的路径比较长,我们就利用modules进行优化。
{
resolve: {
modules: [
path.resolve(__dirname, "src"),
path.resolve(__dirname, "node_modules"),
"node_modules",
],
},
}
resolve.alias
alias通过创建import或者require的别名,把原理导入模块的路径映射成一个新的导入路径。它和resolve.modules不同的是,它的作用是用别名代替前面的路径,不是省略。这样的好处激素webpack直接会去对应别名的目录查找模块,减少了搜索时间。
{
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
}
resolve.mainFields
mainFields
用来告诉webpack使用第三方模块中的哪个字段来导入模块。第三方模块中都会有一个package.json文件用来描述这个模块的一些属性,比如模块名(name)、版本号(version)、作者(auth)等,其中最重要的就是有多个特殊的字段用来告诉webpack导入文件的位置,有多个字段的原因是因为有些模块可以同时用于多个环境,而每个环境使用不同的文件。
{
resolve: {
mainFields: ["main"],
}
}
resolve.extensions
extensions字段用来在导入模块时,自动带入后缀尝试去匹配对应文件
{
resolve: {
extensions: ['.js', '.json']
}
}
也就是说我们在require('./utils')
时,Webpack先匹配utils.js
,匹配不到再去匹配utils.json
,如果还找不到就报错。
因此extensions数组越长,或者正确后缀的文件越靠后,匹配的次数越多也就越耗时,所以需要优化:
2)减少打包文件
定位体积大的文件
使用webpack-bundle-analyzer
插件可以可视化的查看webpack打包出来的各个文件体积大小,以便我们定位大文件,进行体积优化。
提取公共的代码模块、文件
在optimization.splitChunks
中配置
{
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all"
}
}
},
runtimeChunk: {
name: 'manifest'
}
}
}