接着上一篇文章继续
一、关于Tree Shaking
问题的引出:创建一个js文件,文件中有两个方法,但是在index.js中只引入一个方法
前面说过了npm run start 在执行时,会启动服务器,并将打包好的dist目录存在内存中,所以项目中看不到,要想看到dist文件就要用npx webpack,在打包后的结果中我么看到:即使只用了一个方法,结果webpack将两个方法都打包了,这样使文件的大小大大增加了
所以这时候我们就会发现,这样是没有必要的,所谓tree shaking 就是树的摇动,一个模块相当于一棵树,吧没有用到的东西都摇晃掉了,注意的是tree shaking仅支持ES Moudle引入,不支持commonJS
接下来进行配置,首先在development开发模式下
//webpack.config.js
optimization:{
usedExports:true
}
//package.json
"sideEffects":false,
如果你想让一个css文件不遵循不使用就不引入的规则,那就修改配置项
//package.json
"sideEffects":[
"*.css"
]
但如果development环境变成prodution下,就不用写optimization了
二、关于development模式和production模式的区别
现在我们分别配置两个在不同模式下的webpack.config.js文件,并且修改打包的命令,最后我们将两个文件中相同的部分提取到一个共同的文件webpack.common.js,但是我们需要一个第三方的插件讲这些配置文件进行合并
npm install webpack-merge -D
并在文件中引入,使用他
接下来,将这三个配置文件放在build文件夹下
需要修改一下package.json文件夹下的打包命令
"scripts": {
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
},
但是,这样修改完也不行,因为我们发现打包的dist文件被打包到了build目录下,因为好多配置项我们没有同步的变更,所以现在修改一下:因为十一打包的配置文件为基准来生成目录的,因为我们的webpack.common.js是在build目录下,所以认为生成的dist目录也要放在配置文件同一级的build文件下,所以在网上以及才是我们要的位置
output:{
filename: '[name].js',
path:path.resolve(__dirname,'../dist')
}
三、SplitChunksPlugin (Webpack 和 Code Splitting)
将webpack 和code splitting进行了绑定,在webpack中直接加参数就可以,以前我们都需要自己去考虑哪一项是公用的代码,然后进行分割,webpack他可以自己去识别,例如引入一个lodash第三方库,这样我们每次改变index.js中的内容,lodash.js就不用二次加载了
npm install lodash --save
引入babel官网提供的动态引入的插件
npm install --save-dev @babel/plugin-syntax-dynamic-import
在.babelrc中进行配置
{
"plugins": [["@babel/plugin-transform-runtime",{
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false
}]],
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
在index.js中分别举一个同步载入模块的例子,一个异步载入模块的例子
//test.js中的内容
export default {
name:'aaa'
}
//index.js文件中的代码
//同步引入
import test from './test.js'
console.log(test.name);
//异步模块引入
function getComponent(){
//注意下边的注释,是自己规定了打包后文件的名字,例如这样写完后他就变成vendors~lodash.js
return import(/* webpackChunkName:"lodash" */ 'lodash').then( ({ default: _ } ) => {
var element = document.createElement('div');
element.innerHTML = _.join(['l','y','y'], '-');
return element;
})
}
getComponent().then(element => {
document.body.appendChild(element);
})
接下来我们来一一解释一下webpack官网上的所有参数(webpack.common.js)
optimization:{
splitChunks: {
chunks: 'all',
minSize: 0,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name:'vendors'
},
default: {
minChunks: 1,
priority: -20,
reuseExistingChunk: true,
name:'test'
}
}
}
},
chunks: 'async',代表只有异步载入模块时才做代码分割,chunks: 'all'表示不管异步还是同步都进行代码分割
minSize: 30000,只有当引入的模块大于30KB时才会进行代码分割
minChunks: 1,模块至少被引用了一次,进行代码分割
automaticNameDelimiter: '~',如果没有配置特定的输出文件名他都是vendors~lodash.js这些文件的连接符
maxAsyncRequests: 5,
maxInitialRequests: 3表示拆分成几个文件,一般不改变参数
name:底下的自定义的name是否可以生效
cacheGroups:缓冲组,所有的拆分模块都遵循以下的原则
vendors中的配置项:表示凡是你引入的模块在node_modules文件中,引入的模块均打包到lodash.js中,这样写上name的特点是,不管你引入多少个第三方的模块,均打包到一个叫lodash.js的文件中,priority是优先级,数字越大优先级越高,按照优先级高的要求来打包
reuseExistingChunk:如果以前已经打包过A这个模块,B文件有引入了这个模块,则B中的A就不在被打包了,直接在缓存中拿取
总结一下:
(1)首先我们先打包test.js这个同步的模块,将下边的异步模块注释掉,我们发现他不在node_modules中,所以遵循default的打包规范,最终打包出一个叫test.js的文件
(2)接下来将异步模块打开,将同步模块注释掉,他便遵循vendors中的规则,最终打包到vendors.js文件中
四、打包分析 Prefetching/Preloading modules
可以查看相应的官方文档
进行配置,在打包命令哪一行加点东西
"scripts": {
"dev-build": "webpack --profile --json > stats.json --config ./build/webpack.dev.js",
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
},
再次打包,会发现多处一个stats.json文件,这个文件中记录了整个打包的流程,当然了不是让我们自己来看这个文件分析,是借助一些工具进行分析,点开官网上面的那个链接会跳转到相应的分析工具页面,将刚才打包生成的json文件放进去就可以了
当然了,除了github官网上给出来的工具我们也可以打开webpack官网 DOCUMENTION-----GUIDES------Code Splitting-----Bundle Annlyse 右侧有一些webpack提供的工具也可以使用
接下来介绍一下Prefetching/Preloading modules同样是在官方文档的相同位置
由一个例子引出这两个概念,将index.js中的内容变成
document.addEventListener('click', () => {
const element = document.createElement('div');
element.innerHTML = '123';
document.body.appendChild(element);
});
打包后打开dist/index.html,在浏览器控制台部分快捷键 ctrl+shift+p输入coverage点开第一个,启动最左边的原点录制按钮,刷新一次页面,看到下边的内容
点开下面的index.js文件我们会发现我箭头的部分是红色的,红色代表的是未执行的代码当我们点击了页面之后,屏幕输出‘123’并且变成绿色了,表示这个代码已经执行了 ,后面的百分之多少代表的是代码利用率
name问题来了,这样加载文件是不是会影响网页的性能呢,这个还未点击之前我们便将这大段的代码加载进来了,完全没有必要,那万一用户就一直不点击网页,一直不需要执行这段代码,那你一开始就加载进来有什么用呢?反倒会浪费网页的资源,我们只需要在用户点击了页面之后在加载这部分代码即可,接下来进行改进
我们在创建一个异步文件,让他一开始不加载,只有在用户点击时才加载
这时候我们在打开网页,还是按照上边的操作步骤
一开始刷新过后不会加载click.js文件,只有点击了之后才会加载click.js文件
通过以上的例子我们应该明白其实提升性能并不是通过提取缓存中的内容,而是按需加载,即需要时才加载,也就是代码的使用率,代码的利用率越接近100%越说明这个页面性能越好
举一个例子,比如说你登录一个网站,你一开始不需要登录,那么一进来页面就不需要加载登录这块的逻辑代码,但是那如果我需要的时候在加载岂不是很慢,还要等一会,所以最好的解决办法是先把首页需要的加载完,然后在偷偷的加载登录代码,这样就在点击登录的时候能看到一个立刻弹出的效果了,这个功能的实现就引出了我们要介绍的Prefetching/Preloading modules了,我们配置以下,在我们刚刚写的index.js的import后加这样一行代码
document.addEventListener('click', () => {
//这里的default后的fun便是click.js文件导出的那个方法
import(/* webpackPrefetch:true */'./click.js').then( ({default: func}) => {
func();
})
});
五、css模块的分离打包
经过上边的配置,我们发现所引入的css文件会被打包到index.js中但是我们希望他被单独打包出一个css文件,接下来就进行配置
npm install --save-dev mini-css-extract-plugin
按照官网进行引入和配置:注意的点,不仅plugins要配置,loader的style-loader部分也要替换成他给的内容
有一个点需要注意下,之前我们配过 tree shaking ,这里我们要在package.json文件中将sideEffects中的false改成.css这样就不会将css文件摇下去了
{
"name": "webpack-test",
"sideEffects": [
"*.css"
],
"version": "1.0.0",
"description": "",
"scripts": {
"dev-build": "webpack --config ./build/webpack.dev.js",
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
},
打包后我们发现dist文件下多了一个main.css文件,这里css文件的名字是entry入口的名字
加入我们想将两个css文件合并到一个文件中,并且压缩css文件,那就需要另一个插件
npm install optimize-css-assets-webpack-plugin -D
参照官网进行配置,但是如果想合并代码必须要在生产环境中才其效果 我们这里的配置就是运行npm run build
六、webpack与缓存
正常情况下,用户第一次进页面会在本地将加载的文件进行缓存,当你不是强制刷新而是普通刷新的时候,页面会自动去找缓存中的同名文件,如果缓存中有就不在重新请求加载了,只有这个文件没有的时候才会去加载
假如一个场景,用户在第一次打开页面之后,我们该了一下页面,如果保证用户刷新便能重新加载新文件,那就需要给修改过的文件一个hash值,凡是文件的内容变了,文件的hash值也变了,
注意这个场景模拟的四打包上线,所以用npm run build,并且配置文件,将webpck.common.js中的output重新在两个不同的文件中配置
当index.js中的内容不变时,两次打包生成的hash值相同,当改变index.js中的内容时,此时的hash值就会变化了
七、shimming垫片
垫片用到的场景非常多,如果我们想改变webpack自身的属性就需要配置他了
先抛出一个例子,加入我们想用jquery这个库
先安装jquery
npm install jquery -D
然后在需要的模块中引入
var $ = require('jquery'); // import $ from 'jquery';