使用webpack时非常简单,就是提供了一个webpack config,然后执行webpack提供的全局方法,就可以打包编译了。
整个编译打包过程:
module graphmudule graph分离为initial chunk、async chunk、runtime chunk、common chunkgenerator为每个chunk的module构建内容,然后再为chunk构建内容构建module graph过程:
module对象loader处理parser解析loader处理的内容dependencies、blocks中收集的依赖,重复2-5,直到所有的依赖处理完成构建module的过程:
绝对路径、loader、parser、generatormodule对象loader提供的方法、读取源文件内容、使用loader处理源文件内容、使用parser解析源文件内容、收集依赖、处理依赖chunks分类:
lazy module对应的chunkoptomization.runtimeChunk:true 从initial chunk中分离出来,负责安装chunk、安装module、加载lazy chunkoptimization.splitChunks策略分离出来的chunks构建bundle:
chunk的类型,获取对应的templateoutput.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: trueinline模式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'
}
UglifyJsPluginrequire.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'
}
}
}