webpack4搭建vue项目:踩坑及优化(项目总结)

4,059 阅读10分钟

第一次写掘金,之前都是看别人的文章,这次主要是想记录下近期项目中遇到的问题,和自己的一些成长。

先简述下最近项目的技术选型,公司在两个月前前端技术方面进行升级,之后的项目均要采用主流框架来开发。在做了三个 vue 的中型项目后,有一些坑和爬坑的过程,也是对这三个项目的进行一些系统的总结吧,如文章有哪些不足之处,评论区多多交流,互相学习。更希望各位同学看过后能有一些感触和收获。

一. 项目概述:使用vue全家桶开发,但没有使用vue-cli,至于原因,下面会详细解释。
二. 项目坑点:做了三个中型项目后,业务逻辑层并没有踩什么坑,坑点主要在以下几个方面
  • webpack4 + vue搭建配置踩坑
  • webpack打包性能检测和优化
  • 涉及权限系统,前端要做一套权限验证,生成动态路由表

1. 为什么不用vue-cli脚手架?

公司主项目还是最传统的jQ + php的架构,坐标在帝都的互联网公司,技术上其实已经与大多互联网公司差了一大截。所以在进行新项目选型时,身为公司唯一一个前端,当即决定使用 vue 来做,真正做到前后端分离。但确定了主框架,那到底使用vue-cli呢,还是vue+webpack自己搭呢?(产品给的项目排期是5天研发并测试,5天后上线),还得考虑到工期问题。

经过斟酌,还是选择了自己搭建,原因有以下几点:

  1. 自己定制的脚手架哪出了问题自己心里清楚,从而也能培养一些前端架构的能力
  2. 更熟练的掌握webpack配置的能力,以及webpack3.0升级为4.0踩坑的能力
  3. 如果后期项目转型为服务端渲染(SSR),能更快的更改配置,这个是 vue-cli不具备的能力

除了以上几点,个人觉得,是否具有webpack配置的能力,是衡量一个前端的水平高低的标准,笔者面试很多前端,三年经验的前端能从0配置webpage的几乎很少,更不要说性能优化或是自己写loader和plugin了。

2. 配置webpack篇

entryoutputloaderplugins,因为本文不是具体讲如何配置webpack,所以就不一一赘述了,网上的此类教程也是比比皆是。这里主要记录下我在配置过程中踩过的坑和值得总结的地方。

先简单聊聊webpack3.0版本和4.0版本的主要的区别吧

1. webpack4必须安装webpack-cli
2. webpack4中设置加了一个mode配置,只有两个值development | production,对不同的环境会提供不同的一些默认配置
3. 拆分单独模块使用optimization.splitChunks替代了CommonsChunkPlugin
4. 提取单个css文件使用MiniCssExtractPlugin替代extract-text-webpack-plugin
5. 如果是开发vue项目,基础配置文件必须引入vue-loader/lib/plugin
6. 不在使用UglifyJsPlugin压缩js文件,而是通过optimization.minimize设置为true来压缩代码,mode为production时,其默认为ture
7. 引入了Tree Shaking,不打包无用代码

以上就是4.0版本主要的升级,当然还有很多,在此附上官方change log

注意:
MiniCssExtractPlugin仅仅会把js中的css提取到单独的css文件中,但并不会对其进行压缩
压缩css需要单独使用optimize-css-assets-webpack-plugin,并在optimization选项中进行配置
optimization: {
    minimizer: [
      <!--压缩css-->
      new OptimizeCSSAssetsPlugin({
        assetNameRegExp: /\.css$/
      })
    ]
}

项目配置的大致思路就是根据不同的环境配置不同的config.js,通过webpack-merge合并基础配置,package.json中,根据不同的命令进行相应操作即可。看图说话:

"dev": "webpack-dev-server --config build/webpack.dev.config.js",
"build": "webpack --config build/webpack.prod.config.js",
"build:dll": "webpack -p --progress --config build/webpack.dll.config.js"

3. webpack优化篇

一. webpack4中production模式自带优化项

1. Tree Shaking

tree shaking 是一个术语通常用于打包时移出Javascript中为引用的代码,它依赖于ES6模块系统中的 importexport的静态结构特性。

开发时引入一个模块后,如果只使用其中一个功能,上线打包时只会把用到的功能打包进对应的bundle,其他没用的功能都不会打包进来,从而实现最基础的优化。

前提是使用了importexport的语法进行导入导出。什么意思呢?意思就是 如果一个模块使用了ES6的导入导出,那在打包线上代码时,就会shaking掉无用代码;但是!注意咯,如果使用的是require这种commonJS规范的导入导出,那么它不会进行shaking

为什么require的模块不会被加入shaking行列呢?原因在于require是动态导入,在webpack打包时,如果是动态导入的模块,它并不知道被导入的模块内是否有无用代码,所以不会被shaking掉。

2. scope hoisting作用于提升

scope hoisting 的作用是将模块之间的关系进行结果推测(预编译),可以让webpack打包出来的代码文件更小,运行更快。

scope hoisting的实现原理其实很简单:分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,但前提是不能造成代码冗余,因此,只有那些被引用了一次的模块才能被合并。

由于scope hoisting需要分析出模块之间的依赖关系,因此源码必须采用ES6模块话语句,这点和tree shaking一样。

3. 代码压缩

production模式下,默认会对js代码进行压缩和提取(SplitChunksPlugin)

二. 自定义优化

1. 尽可能的少用loader

webpack打包耗时的大部分时间是因为loader预编译这一阶段,所以尽可能的少用loader,如下图

可见,loader耗时占用了整体打包时间的2/3。那么问题来了,什么叫“尽可能少用”?

  • 比如,项目中使用到less-loader,那就不要再配置sass-loader, stylus-loader,尽可能只使用一种css预处理器
  • 再比如,处理图片的loader,一般会用到file-loader,url-loader,img-loader. 但是url-loader官方声明:url-loader works like file-loader,意思是url-loader类似于file-loader,那就不要再使用file-loader来配置(除非图片过大,超出url-loader中配置的limit值)

2. 动态导入(懒加载)

webpack默认是允许 import 语法动态导入的,但是需要babel的插件支持,即 在.babelrc配置文件中添加@babel/plugin-syntax-dynamic-import插件

动态导入的最大好处就是实现了懒加载,用到哪个模块才会加载哪个模块,可以提高SPA应用程序的首屏加载速度

3. noParse

在引入一些第三方模块时,例如jQuery,element-ui等,我们知道其内部不会再依赖其他模块,因为我们最终用到的只是一个单独的js文件,所以此时webpack如果再去解析jQuery内部的依赖关系,是非常耗时的。

可以在webpack配置文件中的module节点下加上noParse,并配置正则来告诉webpack,我不需要再去解析这些模块

module: {
    noParse: /jquery|element-ui/
}

坑点来啦: element-ui如果 按需引入,注意,这里不能使用 noParse 来做element-ui的优化,因为按需引入的element,内部会有一系列依赖关系,使用noParse,webpack并不会去解析其依赖

下面看看优化结果,打包速度提升了接近4倍

4. IgnorePlugin

项目中使用了element-ui和moment.js,这两个插件内部都有很多语言包,尤其是moment.js。而语言包打包时会比较占用空间,而我们的项目只需要中文语言包,这时就应该忽略掉所有的语言包,改为按需引入,从而使得构建效率更高,打包生成的文件更小

  • 首先找到第三方库中依赖的语言包是什么(在第三方库中package.json文件中,找到主入口文件,在入口文件中找到引用的语言包)
  • 使用webpack.IgnorePlugin插件忽略其依赖
  • 按需引入所依赖的语言包

下面看看打包耗时和dll包的大小,结果还是不错的

5. 通过减少文件搜索范围来提高性能

什么叫减少文件搜索范围?这块需要先理解什么是webpack基本原理。webpack通过配置文件,将项目中引入的第三方和公共模块进行提取,每一个提取出的模块都有一个id,在引用到该模块的主文件中,通过id进行映射查找。所以如果能提升文件查找速度,对webpack打包性能会有一定提升。

  • 方式一:resolve.modules 通过配置,告诉webpack在解析模块时应该搜索哪些目录。
import vue from 'vue'
import myCommonJs from '../src/myCommonJs'

上面代码,分别引用了第三方库和公共js,默认的webpack配置,会在当前路径下向上递归的搜索文件进行打包引用。此时就可以使用resolve.modules来告诉webpack,我是要到node_modules目录下和src目录下去找这两个文件。

  • 方式二: include & exclude

在配置loader时,通过配置include 和exclude更精确指定要处理的目录,这可以减少不必要的遍历,从而减少性能损失。这个是更为简单的方式,这里就不进行赘述了

6. Happypack多进程进行打包构建

webpack打包构建默认是单进程进行打包,当一个babel-loader需要处理多个不同类型的资源文件时,node的单进程读取文件特性就有耗性能了,这时候就需要用到 Happypack了。但可能是项目规模和文件资源较为单一的原因,笔者通过配置Happypack,实际打包速度并没有得到提升,反而速度多了2-3s,之后0需要再仔细研究下。

7. 设置babel-loader 配置项cacheDirectory为true

设置后,给定目录将用于缓存加载器的结果。之后webpack构建将尝试从缓存中读取,以避免在每次运行时运行可能昂贵的Babel重新编译过程

8. DllPlugin 和 DllReferencePlugin

DllPlugin为webpack提供的打包动态链接库,一般用来打包一些版本不需要经常更新的第三方库。单独提供一份打包动态链接库的配置文件(即上图中的webpack.dll.config.js),生成环境打包之前先打包动态链接库,打包后会生成vendor_dll.js和vendor-manifest.json,它包含从引入请求到模块ID的映射

DllReferencePlugin,该插件为生产环境配置文件(webpack.prod.config.js)中使用,其中的manifest选项,require DllPlugin打包生成的manifest.json文件,将依赖项名称映射到模块ID,然后使用内部__webpack_require__函数根据需要使用它们

主要思想在于,将一些不做修改的依赖文件提前打包,这样我们在开发代码发布的时候,就不需要再对这部分代码进行打包,从而节省了打包时间

实际项目中配置此项,可提升6-8s的打包速度

坑点:
1. 生成的vendor_dll.js必须手动的引入index.html中,或者通过add-asset-html-webpack-plugin将vendor_dll.js动态的添加到html文件中
2. 再使用add-asset-html-webpack-plugin时动态引入动态链接库生成映射后的js时,因为打包生成环境时,使用了
   speed-measure-webpack-plugin进行耗时分析,从而导致打包出错,报错如下。
   猜测应该是两个插件不兼容吧,有大佬也遇到过此类问题和解决的方法,求分享

9. 引用cdn链接引入第三方库

总结完毕,网上此类的总结有很多,但通过自身配置和通过webpack-bundle-analyzer和speed-measure-webpack-plugin来观测打包性能,并进行优化总结,又一次加深了对webpack配置及原理的理解.