基于webpack工程化的思考

2,287 阅读6分钟

本文章是在基于webpack多入口配置工程并且项目需要持续迭代作为基础去讨论的,单页面不作讨论。文章适合对webpack有一定了解的人看,主要讨论工程化中关于缓存利用率的问题。

一、如何打包文件让缓存利用率增高

在webpack中,我们通常会打包一个vendor和一个common文件作为基础包,vendor通常是将npm中的依赖打包、而common则是让达到一定引用次数的模块进行打包。具体配置如下:

vendor

common

以上的配置,在持续迭代中,是不利于持久性缓存的,因为需要持续迭代,不可避免的是业务文件的修改,前端做缓存主要依靠的是通过给文件名加上hash值去控制文件缓存,而webpack1中一旦修改了某个文件,会导致整体的文件hash值的变化,包括vendor和common的hash值,但是这一个缺点在webpack2以后就被处理得比较好了,可以用HashedModuleIdsPlugin稳定模块的id,以保证vendor在单个业务文件修改时,其他文件的hash值保持稳定。

但是在持续增长的业务中,这样的方式对持久化就是可靠的吗?如果你在增量迭代时,突然需要引入某一些npm包呢?导致vendor的hash值在这个时候也会发生变化,所有的用户不得不重新下载一个庞大的vendor文件。

根据这种场景我们可以如何去优化这个缓存呢?这里我采用的方案是,将整个项目依赖的基础包,单独放在vendor中进行手动配置打包,在入口配置中加入自己手动配置的vendor列表,这一个列表是整个项目最基础依赖的包。

例如:在一个多入口vue建立的项目中,我们项目几乎每个入口页面都要引入vue,那么我们的vendor列表中就可以配置上vue

webpack入口配置中加上这一个键值对

让模块抽取通过入口名为vendor的列表进行打包

那么vendor包中就只会将vue打包进vendor,而其余的模块则按照引用次数将包打包进common中,这么做的好处是什么?在项目迭代中,vendor的打包是我们可以控制的,也就是这个列表我们不修改,则vendor的hash值不变,也就是说在用户不清除缓存的情况下,我们的vendor包可以一直在缓存中,对于页面的性能来说是有保证的,变化的模块也仅仅在common模块和其他的业务文件。

比如,在vue的项目中,我们集成了vuex、rxjs、axios等库,在多页面开发中这些库基本在每一个入口中都会有引用到,那么我们是不是可以将vendor的列表配置成[ 'vue', 'vuex', 'rxjs', 'axios' ],通过commonPlugin则可以将列表中的模块打包成vendor,在迭代中,其他npm包的引入并不会影响到vendor hash值的改变,等到你需要更新vendor时,则可以手动往列表中添加依赖。

二、dll文件构建自动化

dll文件是在开发时,先对所有的npm包预先打包,每一个入口都会引入,之后根据manifest去对npm包资源进行引用,这样做,在开发时就不需要对npm包进行打包构建,节省一部分时间,具体可google。

dll

这里就会涉及到一个问题,每一次npm依赖更新了,都需要重新进行dll文件的打包。作为一个程序员,很显然,这么做不够偷懒。那我们可以如何去做dll文件构建的自动化呢?这里提供一下我的思路。

一般我们在开始时,通常使用npm script进行项目构建、而dll文件需要单独运行命令,之后再构建项目

webpack --config dll.config.js

webpack-dev-server --config dev.config.js

因为涉及到两条命令,所以我选择了使用shell对两个命令合并

// npm run start命令行执行dev.sh脚本
sh bin/dev.sh
#!/bin/bash
### 思路
###1、在首次构建时,需要生成依赖的数量,并将数据重定向到文件中,用于二次构建时依赖数量的匹配
###2、二次构建时,会先判断记录了依赖数量的文件是否存在,存在则读取数量进行现有依赖数量的匹配,若依赖数量无变化,则认为dll文件不需要构建,直接运行项目的构建,若依赖数量变化,则重新构建dll文件

### 用于本地开发时自动维护dll文件
rootPath=`pwd`
packagePath=${rootPath}'/package.json'
dllValidatePath=${rootPath}'/build/vendor.dll.validate.txt'

### 读取package.json的dependencies与devDependencies对应的行数
dependenciesRow=`grep -n "dependencies" $packagePath | cut  -d  ":"  -f  1`
devDependenciesRow=`grep -n "devDependencies" $packagePath | cut  -d  ":"  -f  1`

### 依赖行数(以此判断依赖是否增减)
rows=$[$devDependenciesRow-$dependenciesRow]

### 判断vendor.dll.validate.txt文件是否存在
if [ -e $dllValidatePath ]; then

  ### 获取之前的依赖行数
  oldRows=`cat $dllValidatePath`

  if [ $oldRows == $rows ]; then
    ### 依赖数量相等则直接构建
    npm run dev
  else
    echo $rows > $dllValidatePath
    ### 不等,重新创建vendor.dll.js,并将新的行数写到vendor.dll.validate.txt
    npm run dll
    npm run dev
  fi

else
  rm -rf build
  mkdir build
  touch $dllValidatePath
  echo $rows > $dllValidatePath
  npm run dll
  npm run dev
fi

上面是通过依赖数量的变化来做其实是有点bug的,如果依赖增加了之后再减少到相同数量,dll文件并不会重新构建,这时需要自己手动构建一次,所以,严谨一些应该是要对依赖列表进行前后对比才可以确定dll是否需要变化shell相应的也会更复杂一些吧,以后有时间优化一下这里的逻辑。

注意:dll文件,最好不要压缩,因为压缩插件会将console打印的语句去除,会导致开发时框架或者库的错误提示或者警告丢失。

三、库的引用

不知道大家有没一种情况,就是某一些库,只是那么两三个入口需要使用,但是却被打包进common中。比如一些即时聊天服务,通常需要引入一些script或者css,而你的common配置中,超过两个引用次数就会将包打进common中,导致common过大,而每一个入口通常都需要引入common,并且这些外部引用的库会随着common的打包变化而重新下载,这就会导致用户无法持久缓存这部分这种库。

当然,我们可以通过增加common的模块引用次数下限去将这种库排除在外,的确可以,但是这样对common的控制就会受限,这不是我想的,我想common的配置,与这种业务性非常强的库抽离出来。

先看一下HtmlWebpackPlugin的配置

html-webpack-plugin配置

scriptComponents

目录

标记

构建后

这里我们通过这个插件的自定义配置,在html中通过标记将script或者css文件以绝对路径的方式输出到结果中,这样我们就不需要通过webpack对这种稍微大一些又不想打包进common的包进行引用了,直接通过全局script标签的方式进行引入,以一定的规范放置文件,根据文件的版本做缓存的控制。

四、总结

以上是我对于webpack多入口配置的一些思考以及建议,如有错误,欢迎及时指正。下面是github项目地址webpack3-vue-cli