Holybasil / Blog

3 stars 0 forks source link

webpack4升级踩坑指南(vue) #11

Open Holybasil opened 4 years ago

Holybasil commented 4 years ago

webpack4升级踩坑指南(vue)

既然准备升级webpack,索性将vue-loader和babel全部升级了。

mode

由于webpack的CLI已经被单独集成一个包,所以在常规升级完webpack到4.x.x之后,记得顺手把webpack-cli也也安装到项目里。

webpack4提供了mode配置项,该项提供三个可选的值“none”,“development”,“production”。不同的mode会设置当前配置文件下的process.env.NODE_ENV,同时预设不同的插件,方便开发环境或者生产环境。该值可以在配置文件中写明,例如:

module.exports={
    mode: "development"
}

也可以通过webpack-cli,在参数中传递,例如package.json中:

"build": "webpack --mode production"

所以,我们可以移除原来用来定义process.env.NODE_ENV的DefinePlugin插件。

splitChunks

同时,webpack4在codeSplit这项重大功能上,废弃了CommonsChunkPlugin插件,取而代之的是optimization.splitChunks配置项。

我们可以看看这个 webpack optimization.splitChunks 也就是说,如果将mode设置为production,webpack默认按照以上规则打包项目。 那么上面四条规则分别是在说什么呢?

  1. 来自node_modelus文件夹的第三方依赖是被共享的。-> node_modules中的第三方依赖会被打包成一个chunk
  2. 新的chunk的大小在被各种ugly插件和被gzip处理之前一定大于30kb。 -> 一个模块被2个以上的文件所共享,但是这个模块没有超过30kb,那么他还是会被分别打包进各自的chunk里。
  3. 按需加载chunk时,并行请求数不会超过5个。
  4. 页面初始加载时,并行请求数不会超过3个。

注意最后两条规则,这就告诉我们,在打包项目的时候,要注意平衡chunk的细碎度和并行请求数。

具体关于splitChunks的配置,可以仔细研读官网文档split-chunks-plugin。这里,主要提一下文档里没有讲清楚的三个特殊值allinitalasync

所以大多数的文章,包括webpack本身,都建议,通常情况下

optimization: {
     splitChunks: {
      // include all types of chunks
      chunks: 'all'
    }
  }

这样的打包规则适用于大多数项目。

更加定制化的打包规则,则需要先了解cacheGroups,这个属性可以简单理解为:cacheGroups是一个对象,每个键值对都代表着一个chunk的打包规则。

例如,在我的项目里,node_modules里有很多公共的依赖,包括echarts这种大型库。但是,只有很少的页面需要展示图表,并且并非首页。所以我的cacheGroups里有这样两组规则:

vendor:{
    chunks: "initial",
    name: "chunk-vendor",
    test: /[\\/]node_modules[\\/]/,
    priority: 5,
},
echarts:{
    test: /[\\/]node_modules[\\/]echarts[\\/]/,
    name: "chunk-echarts",
    priority: 10
},

这两组规则表示,将echarts单独打包,node_modules中的其他同步引用模块统一打包。那么页面初始加载时,chunk-vender的size会减小很多,加载时间就会缩短,巧妙减少白屏时间。待按需加载的页面使用到echarts后,才会加载chunk-echart。对于一些只有某些页面用到的大型库,都可以这么处理。

同时,注意这里的priority,它表明了当前规则的应用优先级,意思是,打包时,先按第二组规则生成chunk-echarts,再按第一组规则生成chunk-vendor。由于默认规则的优先级均为负数,所以自定义规则的优先级需从正数开始。

关于cacheGroups内规则的其他属性,亦可参考上面的链接。

VueLoader 15

将vue-loader升级为15..之后,记得

const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  // ...
  plugins: [
    new VueLoaderPlugin()
  ]
}

需要显式的应用插件。

同时VueLoader15不再默认开启PostCss(提供style scoped功能),所以如果要使用PostCSS,记得配置postcss-loader。

Babel7

升级babel后,更换presets为@babel/preset-envbabel-preset-env)。

这个plugin会通过browserslist智能的预置一系列的babel转换规则。因此,coder可以任意使用js的新特性,不用考虑是否支持目标浏览器版本。

由于我的代码中使用了vue-router的LazyLoading,所以引入@babel/plugin-syntax-dynamic-import插件,确保正确解析import()

此时此刻,npm run build之后,报!错!了!import("**/*.vue")无法被解析!难道我装了个假babel?

这里插一句,升级webpack的时候

npm install webpack@latest

最新的webpack版本为4.35.4。

各种关键词搜索之后,我找到了一个issue parse import error ,看来还是有一些人遇到了这个问题。请直接滑到@sokra的回复那里,原因讲的很清楚了,这里不再复述。

而我选了最直接的解决办法,将webpack的版本固定为4.28.4,解决了。

MiniCssExtractPlugin

webpack4中,用MiniCssExtractPlugin替换了ExtractTextWebpackPlugin,能够支持异步加载,并且使用方式简单多了。

{
    test: /\.css$/,
    use: [MiniCssExtractPlugin.loader,'css-loader']
}

为了将打包后的css文件统一放置,MiniCssExtractPlugin中的filename和chunkFilename可以由路径和文件名组成,例如:“static/css/[id].css”意为打包后的css文件均在/dist/static/css文件夹下。MiniCssExtractPlugin的publicPath默认为output的publicPath。

TerserWebpackPlugin

webpack4默认压缩js的插件是TerserWebpackPlugin,是一个对ES6+支持的更好的压缩插件。注意,记得设置sourceMap: true,这样当productionSourceMap为true时,才会生成.map文件。

优化

在开发环境中,由于WebpackDevServer接手了服务,所以一系列的优化可以在devServer中配置。(如果支持的话)可以设置progress:true,感受项目启动的进度。FriendlyErrorsWebpackPlugin和quiet:true则去掉了一些我们不关心的输出。 在生产环境,项目打包完毕,会需要看chunk的一些信息,以及打包速度,之后有目的的优化。webpack提供stat配置项,允许细粒度的控制输出信息。常规输出的配置如下:

stats: {
    all: undefined,
    colors: true,
    modules: false,
    children: false,
    chunks: false
}

到目前为止,webpack这次的升级已经很成功了,项目的打包速度和打包之后的文件体积都有了很大的进步。但是一般都会配置devServer的open:true,这样每次启动服务,浏览器(Chrome)都会自己打开页面,很方便。但是每次启动服务,都会开启一个新的tab,这就麻烦了不是,本来我的显示器就小,分辨率也不高。

需求明确了,就是搜索怎么办了,关键词reusetabchrome,几经波折,从create-react-app的源码中,找到了一段applescript。配合同级目录下的openBrower.js,这不就是我想要的嘛。注意openBrower.js文件返回的是opn(url)(这里我把open换成了opn),所以该怎么用已经很明了了。

然后问题又来了,我怎么在项目里最方便的用他们呢?既然是开发环境,那么再去地毯式搜索了WebpackDevServer的配置项,after这个属性正好是我需要的。他是在所有内置中间件执行完毕后,将服务暴露出来,之后想做什么,做!于是我

after: function(app, server) {
     if(config.dev.autoOpenBrowser)
    server.middleware.waitUntilValid(()=>{
        const url = "http://"+ config.dev.host + ":" + process.env.PORT
        openBrower(url) 
    }) 
}

这里的openBrower当然就是刚才两个文件提供的神器了。

测试,后开启的服务果然找到了之前的tab,在原tab上刷新,不会打开新的tab了。

撒花 over

参考

To v4 from v3 built-in optimizations depending on mode Webpack (v4) Code Splitting using SplitChunksPlugin

g1eny0ung commented 4 years ago

😆👏