阅读 1997

前端开发中常用的webpack优化和相关原理

webpack优化方向

关于webpack的优化,通常我们是划分为开发体验和输出代码质量两方面来考虑的

( 一 )优化开发体验

通常是从优化构建速度和使用体验

【开发环境】

  1. 缩小文件的搜索范围(loader配置include\exclude、module.noParse忽略对部分没有采用的模块话的文件递归解析,resolve modules直接指定第三方模块的路径)
  2. ** 自动刷新**(设置watch:true,但是会制动刷新浏览器)
  3. 热更新(网页不刷新,状态不会丢失module.hot)
  4. ** DllPlugin** (对于依赖的第三方库打包成动态链接库)

【生产环境】

  1. 优化bable-loader (开启缓存)
  2. IgnorePlugin
  3. noParse
  4. happyPack (多线程打包)
  5. ParallelUglifyPlugin(多线程打包)

( 2 )优化输出质量

【减少用户能感知到的加载 时间,也就是首屏加载时间】

  1. 区分环境
  2. 压缩代码 (js css 图片)
  3. **tree-shaking **(除去没有用到的代码)
  4. 提取公共代码 (多线程打包)
  5. 分割代码按需加载(多线程打包)
  6. CDN加速

【提升流畅度,也就是提升代码性能】

  1. Scope Hosting(让文件打包更小,运行更快)
  2. prepack(编译代码是提前将计算结果放到编译后的代码中,再直接运行结果输出)

简析相关原理


自动刷新

让webpack 开启文件监听模式,只需要把wacth:true,wacthOptions可以设置不监听的文件

文件监听原理

  1. 文件监听原理,是定时获取这个文件最后编辑的时间,每次都存下最新的最后编辑时间,如果发现当前获取和最后一次保存的编辑时间不一致,认为文件发生了变化
  2. 多个文件的监听,webpack会从入口文件出发,递归所有依赖文件,将这些依赖文件都加入到监听列表中
  3. 保存文件和最后的编辑时间占内存,减少监听文件数量和降低检测频率

自动刷新的原理

  1. 借助浏览器拓展去通过浏览器提供的接口刷新
  2. 向开发的网页注入代理客户端代码,通过代理客户端去刷新正个页面

热更新

优势

  1. 实时预览
  2. 不刷新浏览器,可以保留当前网页的运行状态

原理

  1. 在项目中注入一个代理客户端来连接DevServer和项目
  2. DevServer在每次修改文件后,会生成一个用于替换老模板的补丁文件hot-update.js结尾,同时浏览器开发者工具也可以看到请求这个补丁包
  3. 但在编辑main.js的时候会发现整个网页刷新了,原因是在子模块发生更新的时候,更新事件会一层层向上传递,到最外层没有文件接收它,则会刷新网页
  4. css文件没有地方接收,但是修改所有的css文件都会触发热更新,原因是在于style-loader会注入接收css的代码

DllPlugin

对于依赖的第三方库,比如vue,vuex等这些不会修改的依赖,我们可以让它和我们自己编写的代码分开打包,这样做的好处是每次更改我本地代码的文件的时候,webpack只需要打包我项目本身的文件代码,而不会再去编译第三方库,那么第三方库在第一次打包的时候只打包一次,以后只要我们不升级第三方包的时候,那么webpack就不会对这些库去打包,这样的可以快速的提高打包的速度

  1. 将项目依赖的基础模块抽离出来,打包到一个个单独的动态链接库中
  2. 当需要导入的模块存在于某个动态链接库中,这个模块不能再被打包,直接再动态链接库中获取
  3. 项目依赖的所有动态链接库都需要被加载

bable-loader

  1. 作用是识别es6+ 的语法
  2. 通过js词法解析器进行解析,得到AST,然后进行遍历通过EStree规范生成新的AST,然后通过生成器转换es5代码
  3. 部分新增的原型方法(peoxy,set)babel是不会转译的需要引入 polyfil解决
  4. cacheDirectory,开启缓存,避免之后的每次执行,可能产生的、高性能消耗的Bable重新编译过程

happyPack(多进程构建,减少总构建时间)

在webpack和loader之间多加了一层,webpack到了需要编译某个类型的资源模块之后,将该资源任务处理交给了HappyPack,由它在内部线程池中进行任务调度,分配一个线程调用处理改类型资源的Loader来处理这个资源

  1. 如果js和css文件的化,直接再module,rules,use['happyPack/loader?id=css']
  2. 也可以在 plugins里配置,new HappyPack({id:css}),id标识文件类型
  3. 默认进程是3个,threads可以设置

webpack-parallel-uglify-plugin

并行处理多个子任务,多个子任务完成后,再将结果发到主进程中,会开启多个子进程,对多个js文件压缩工作分舵多个子进程去完成,可以删除所有的注释,console.log,提取出现多次,但是没有定义成变量的应用静态值

  1. uglifyJS原理,将代码解析成AST语法树,再用各种规则去处理它
  2. 在pulfgins里new一个 parallel-uglify,uglifyJS配置输出紧凑,和删除所有注释,compress删除console.log一次用到的变量等

Tree-shaking

本质上是消除项目中不必要的代码,摇掉没有使用的模块称为DCE,达到删除无用的代码目的,依赖ES6的模块特性

DCE

  1. dead code eliminatiom
  2. 代码不会被执行,不可到达
  3. 代码执行的结果不会用到
  4. 代码只会影响变量(只写不读)

ES6模块的特点

  1. 只能作为模块顶层的预计出现
  2. import的模块名只能是字符串常量
  3. 对模块的引入是静态分析的,所以可以在编译的时候判断到底加载了什么代码,分析程序流,判断那些变量为被使用和引用,进而删除代码

缺点

  1. 只对ES6+的模块化语法生效,要在babelrc里关闭babel的模块转换功能
  2. js文件里,import 一个资源,然后函数没有被使用,import不会去掉

提取公共代码

  1. 原因:相同的资源重复加载,浪费用户的流量和服务器成本,资源态度导致首加载屏缓慢
  2. 好处:减少网络传输量,降低服务器成本

怎么提取

  1. 所有的页面需要用到的基础库,提取到一个独立的base.js文件(长缓存,静态文件名会附加文件内容计算处理的Hash值,通常不)
  2. 再找到所有页面依赖的公共部分的代码,提取到common.js中
  3. 为每个网页都生成独立的文件,不包含以上部分,各个页面单独需要的部分代码
  4. webpack内置了commonschunkPlugin

懒加载(按需加载)

  1. 将整个网站划分为一个个小功能
  2. 每个类合并为一个chunk,按需加载对应的chunk
  3. 不需要加载的用户首次打开网站是需要看到的画面对应的功能,将其放在执行入口所在的chunk中,减少用户感知的网页加载时间
  4. let TaskBtn = () => import(/ webpackChunkName: 'task-btn' / '@/components/TaskBtn.vue');

开启 Scope Hoisting

让webpack打包出来的代码文件更小,运行更快

  1. 代码体积更小,函数声明语句会产生大量的代码
  2. 代码在运行时创建的函数作用域变少,内存开销也变小
  3. 原理:分析模块之间的依赖关系,尽可能将打散的模块合并到一个函数中,但前提是不能造成冗余的代码,只有引用了一次的模块才能被合并

CDN加速

  1. 内容分发,加速网络传输,加快资源获取的速度
  2. 静态资源的文件名需要带上由文件内容算出来的Hash值,以防被缓存
  3. 不同类型的资源放到不同的域名cdn服务上,以防资源并行加载被阻塞