webpack优化方向
关于webpack的优化,通常我们是划分为开发体验和输出代码质量两方面来考虑的
(一)优化开发体验
通常是从优化构建速度和使用体验
【开发环境】
- 缩小文件的搜索范围(loader配置include\exclude、module.noParse忽略对部分没有采用的模块化的文件递归解析,resolvemodules直接指定第三方模块的路径)
- 自动刷新(设置watch:true,但是会制动刷新浏览器)
- 热更新(网页不刷新,状态不会丢失module.hot)
- DllPlugin(对于依赖的第三方库打包成动态链接库)
【生产环境】
- 优化bable-loader(开启缓存)
- IgnorePlugin
- noParse
- happyPack(多线程打包)
- ParallelUglifyPlugin(多线程打包)
(2)优化输出质量
【减少用户能感知到的加载时间,也就是首屏加载时间】
- 区分环境
- 压缩代码(jscss图片)
- tree-shaking(除去没有用到的代码)
- 提取公共代码(多线程打包)
- 分割代码按需加载(多线程打包)
- CDN加速
【提升流畅度,也就是提升代码性能】
- ScopeHosting(让文件打包更小,运行更快)
- prepack(编译代码是提前将计算结果放到编译后的代码中,再直接运行结果输出)
简析相关原理
自动刷新
让webpack开启文件监听模式,只需要把wacth:true,wacthOptions可以设置不监听的文件
文件监听原理
- 文件监听原理,是定时获取这个文件最后编辑的时间,每次都存下最新的最后编辑时间,如果发现当前获取和最后一次保存的编辑时间不一致,认为文件发生了变化
- 多个文件的监听,webpack会从入口文件出发,递归所有依赖文件,将这些依赖文件都加入到监听列表中
- 保存文件和最后的编辑时间占内存,减少监听文件数量和降低检测频率
自动刷新的原理
- 借助浏览器拓展去通过浏览器提供的接口刷新
- 向开发的网页注入代理客户端代码,通过代理客户端去刷新正个页面
热更新
优势
- 实时预览
- 不刷新浏览器,可以保留当前网页的运行状态
原理
- 在项目中注入一个代理客户端来连接DevServer和项目
- DevServer在每次修改文件后,会生成一个用于替换老模板的补丁文件hot-update.js结尾,同时浏览器开发者工具也可以看到请求这个补丁包
- 但在编辑main.js的时候会发现整个网页刷新了,原因是在子模块发生更新的时候,更新事件会一层层向上传递,到最外层没有文件接收它,则会刷新网页
- css文件没有地方接收,但是修改所有的css文件都会触发热更新,原因是在于style-loader会注入接收css的代码
DllPlugin
对于依赖的第三方库,比如vue,vuex等这些不会修改的依赖,我们可以让它和我们自己编写的代码分开打包,这样做的好处是每次更改我本地代码的文件的时候,webpack只需要打包我项目本身的文件代码,而不会再去编译第三方库,那么第三方库在第一次打包的时候只打包一次,以后只要我们不升级第三方包的时候,那么webpack就不会对这些库去打包,这样的可以快速的提高打包的速度
- 将项目依赖的基础模块抽离出来,打包到一个个单独的动态链接库中
- 当需要导入的模块存在于某个动态链接库中,这个模块不能再被打包,直接再动态链接库中获取
- 项目依赖的所有动态链接库都需要被加载
bable-loader
- 作用是识别es6+的语法
- 通过js词法解析器进行解析,得到AST,然后进行遍历通过EStree规范生成新的AST,然后通过生成器转换es5代码
- 部分新增的原型方法(peoxy,set)babel是不会转译的需要引入polyfil解决
- cacheDirectory,开启缓存,避免之后的每次执行,可能产生的、高性能消耗的Bable重新编译过程
happyPack(多进程构建,减少总构建时间)
在webpack和loader之间多加了一层,webpack到了需要编译某个类型的资源模块之后,将该资源任务处理交给了HappyPack,由它在内部线程池中进行任务调度,分配一个线程调用处理改类型资源的Loader来处理这个资源
- 如果js和css文件的化,直接再module,rules,use['happyPack/loader?id=css']
- 也可以在plugins里配置,newHappyPack({id:css}),id标识文件类型
- 默认进程是3个,threads可以设置
webpack-parallel-uglify-plugin
并行处理多个子任务,多个子任务完成后,再将结果发到主进程中,会开启多个子进程,对多个js文件压缩工作分舵多个子进程去完成,可以删除所有的注释,console.log,提取出现多次,但是没有定义成变量的应用静态值
- uglifyJS原理,将代码解析成AST语法树,再用各种规则去处理它
- 在pulfgins里new一个parallel-uglify,uglifyJS配置输出紧凑,和删除所有注释,compress删除console.log一次用到的变量等
Tree-shaking
本质上是消除项目中不必要的代码,摇掉没有使用的模块称为DCE,达到删除无用的代码目的,依赖ES6的模块特性
DCE
- deadcodeeliminatiom
- 代码不会被执行,不可到达
- 代码执行的结果不会用到
- 代码只会影响变量(只写不读)
ES6模块的特点
- 只能作为模块顶层的预计出现
- import的模块名只能是字符串常量
- 对模块的引入是静态分析的,所以可以在编译的时候判断到底加载了什么代码,分析程序流,判断那些变量为被使用和引用,进而删除代码
缺点
- 只对ES6+的模块化语法生效,要在babelrc里关闭babel的模块转换功能
- js文件里,import一个资源,然后函数没有被使用,import不会去掉
提取公共代码
- 原因:相同的资源重复加载,浪费用户的流量和服务器成本,资源态度导致首加载屏缓慢
- 好处:减少网络传输量,降低服务器成本怎么提取
- 所有的页面需要用到的基础库,提取到一个独立的base.js文件(长缓存,静态文件名会附加文件内容计算处理的Hash值,通常不)
- 再找到所有页面依赖的公共部分的代码,提取到common.js中
- 为每个网页都生成独立的文件,不包含以上部分,各个页面单独需要的部分代码
- webpack内置了commonschunkPlugin
懒加载(按需加载)
- 将整个网站划分为一个个小功能
- 每个类合并为一个chunk,按需加载对应的chunk
- 不需要加载的用户首次打开网站是需要看到的画面对应的功能,将其放在执行入口所在的chunk中,减少用户感知的网页加载时间
- letTaskBtn=()=>import(/webpackChunkName:'task-btn'/'@/components/TaskBtn.vue');
开启ScopeHoisting
让webpack打包出来的代码文件更小,运行更快
- 代码体积更小,函数声明语句会产生大量的代码
- 代码在运行时创建的函数作用域变少,内存开销也变小
- 原理:分析模块之间的依赖关系,尽可能将打散的模块合并到一个函数中,但前提是不能造成冗余的代码,只有引用了一次的模块才能被合并
CDN加速
- 内容分发,加速网络传输,加快资源获取的速度
- 静态资源的文件名需要带上由文件内容算出来的Hash值,以防被缓存
- 不同类型的资源放到不同的域名cdn服务上,以防资源并行加载被阻塞