Vue-CLI2项目从 babel 6 + webpack 3.x 升级到 babel7 + webpack4.x 踩坑

4,423 阅读5分钟

序言

      当前项目是通过当时Vue-CLI 2.x生成使用,配置是babel 6编译、webpack 3.x打包。由于项目发展到某个阶段,需要升级优化。目标是打包速度更快、bundle体积更小。
      本文通过分享代码片段讲述部分版本升级后的不同以及可能发生的报错案例,达到让小伙伴们能找到对应的解决办法,减少升级版本的恐惧感。

正文

第一部分 Webpack

1. mode 模式

通过配置 mode,可选 production生产环境 和 development 开发环境。

2. 基本配置

  1. 移除 loaders,使用 rules 代替

    // webpack.base.config.js
    module: {
    -    loaders: {}
    +    rules: {}
    }
    
  2. 插件 CommonsChunkPlugin 替换成配置 optimization.splitChunksoptimization.runtimeChunk
    参考:webpack.js.org/plugins/spl…
    webpack 3 CommonsChunkPlugin插件:

     
     new webpack.optimize.CommonsChunkPlugin({
         name: 'vendor',
         minChunks: 2
     }),
     // webpack 相关代码打包到一个文件
     // 新模块加入给新模块加一个id
     // 规避长缓存问题
     new webpack.optimize.CommonsChunkPlugin({
         name: 'runtime'
     })
    

    webpack 4 替代者 splitChunks & runtimeChunk

    optimization: {
        splitChunks: {
            cacheGroups: {
                vendor: {
                    name: 'vendor',
                    minChunks: 2
                    // 可选 'initial | async | all',
                    // 分别代表,初始化时加载、异步加载、两者皆使用
                    chunks: 'all'  
                    // 代表权重值,值越大,打包优先级越高
                    priority: 10 
                }
            }
        },
        runtimeChunk: {
            name: 'runtime'
        }
    }
    

3. 生产环境 production

生产环境下简化配置,默认开启插件 UglifyJsPlugin

// webpack.prod.config.js 
module.exports = { 
+ mode: 'production', 
- plugins: [ 
-   new UglifyJsPlugin(/* ... */), 
-   new webpack.DefinePlugin({ 
-       "process.env.NODE_ENV": JSON.stringify("production") }), 
-   new webpack.optimize.ModuleConcatenationPlugin(), 
-   new webpack.NoEmitOnErrorsPlugin()
- ]
}

当然,生产环境依然可以通过配置关闭 UglifyJsPlugin

optimization: {
    minimize: false
}

4. 开发环境 development

  1. devServer
    webpack3 需要手写 dev-server.js
    webpack4 使用webpack配置
    // webpack.dev.config.js
    devServer: {
        open: true,              // 自动打开浏览器页面
        host: 'xxx.xxx.com',     // host
        openPage: 'xxx'          // 路径
        historyApiFallback: true // 启用 history模式
        proxy: 'xxx'             // 代理配置  
    }
    

5. package.json

由于开发环境的devServer使用方式有变化,所以 package.jsonscripts 也需要修改,否则会报如下错误:
ERROR in Entry module not found: Error: Can't resolve './src' in 'E:\workspace'
正确修改配置:

// package.json
{
    "scripts": {
-        "dev": "node build/dev-server.js",
+        "dev": "webpack-dev-server --config build/webpack.dev.config.js"        
    }
}

5. 插件安装/升级

如果小伙伴正在使用以下插件,请按详情变更使用,并且可能出现报错解决:

  1. vue-loader v15
  2. thread-loader
  3. eslint-loader v5
  4. copy-webpack-plugin
  5. html-webpack-plugin v4
  6. mini-csss-extract-plugin 替代 extract-text-webpack-plugin
  7. script-text-html-webpack-plugin 废弃

详细:

  1. vue-loader v15
    从 v14 迁移

    // webpack.base.config.js
    const VueLoaderPlugin = require('vue-loader/lib/plugin')
    
    module.exports = {
      // ...
        plugins: [
            new VueLoaderPlugin()
        ]
    }
    
  2. thread-loader
    本来打算使用 HappyPack ,可是vue-loader@15 不完全支持 HappyPack
    现在这个Issue已经关闭,所以小伙伴可以继续尝试用 HappyPack 进行优化,记得反馈哦。

  3. eslint-loader v5
    如有eslint报错如下:
    cannot read property 'eslint' of undefined
    添加配置:

    plugins: [
        new webpack.LoaderOptionsPlugin({ options: {} })
    ]
    

    如有警告如下:
    [ESLINT_LEGACY_OBJECT_REST_SPREAD] DeprecationWarning. The 'parserOptions.ecmaFeatures.experimentalObjectRestSpread' option is deprecated. Use 'parserOptions.ecmaVersion' instead
    修改配置:

    // .eslintrc.js
    parserOptions: {
        parse: 'babel-eslint',
        ecmaVersion: 8,
    -   ecmaFeatures: {
    -       experimentalObjectRestSpread: true
    -   }
    }
    
  4. copy-webpack-plugin
    如有报错如下: TypeError: compilation.contextDependencies.push is not a function 升级插件

    npm i copy-webpack-plugin@latest -D
    
  5. html-webpack-plugin v4 如果小伙伴使用 html-webpack-plugin 提供的钩子扩展了自己定义的插件,可能会发生以下错误:
    Plugin could not be registered at 'html-webpack-plugin-before-html-generation'. Hook was not found. BREAKING CHANGE: There need to exist a hook at 'this.hooks'.
    TypeError: callback is not a function
    this.htmlWebpackPlugin.getHooks is not a function
    原因:这三者都是因为升级版本或者使用hooks的方式改变了而导致。
    参考:
    www.npmjs.com/package/htm…
    github.com/jantimon/ht…
    解决:

    //plugin.js
    const HtmlWebpackPlugin = require('html-webpack-plugin');
     
    class MyPlugin {
      apply (compiler) {
        compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
          console.log('The compiler is starting a new compilation...')
     
          // Staic Plugin interface |compilation |HOOK NAME | register listener 
          HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(
            'MyPlugin', // <-- Set a meaningful name here for stacktraces
            (data, cb) => {
              // Manipulate the content
              data.html += 'The Magic Footer'
              // Tell webpack to move on
              cb(null, data)
            }
          )
        })
      }
    }
     
    module.exports = MyPlugin
    
  6. mini-csss-extract-plugin
    这是在我们的项目升级过程中,最容易出现问题的插件。
    ① 修改Vue-CLI 2 生成关于处理样式的 utils.js
    如下:

    // const ExtractTextPlugin = require("extract-text-webpack-plugin");
    const MiniCssExtractPlugin = require('mini-css-extract-plugin')
    ...
    // 找到:
    - if (options.extract) { 
    - //options.extract = NODE_ENV === 'production'
    -    return ExtractTextPlugin.extract({
    -        use: loaders,
    -        fallback: 'vue-style-loader'
    -    })
    - } else {
    -    return ['vue-style-lodaer'].concat(loaders))
    - }
    
    + return [options.extract ? 
    +    MiniCssExtractPlugin.loader : 
    +    'vue-style-loader'
    + ].concat(loaders)
    

    ② 报错:document is not defined
    解决:区分环境使用 mini-css-extract-plugin(production) 和 vue-style-loader(development),如 ① 配置即可

    ③ 报错:cannot read property pop of undefined
    原因:是webpack optimize时候产生的,小编也一知半解,不好解释。
    参考:

    1. github.com/webpack-con…
    2. github.com/webpack-con…

    ④ 警告:[mini-css-extract-plugin] Conflicting order between
    原因:不同CSS模块里,引入同一个CSS,而引入的顺序不一样产生的警告。
    参考:github.com/webpack-con…
    解决:屏蔽警告

    // 安装依赖
    npm i -D webpack-filter-warnings-plugin
    
    // 配置插件
    plugins: {
        new FilterWarningsPlugin({
            exclude: /mini-css-extract-plugin[^]*Conflicting order between:/,
        })
    }
    

    ⑤ 疑惑:打包后,产生很多小的css文件,能不能把他们都打包成一个css文件
    原因:vue-loader@15 会把 <style lang="less"> 当作 *.less
    参考:vue-loader.vuejs.org/zh/migratin…
    解决:弃用 mini-css-extract-plugin,重用 extract-text-webpack-plugin 并升级版本。且把utils.js的配置如原来升级前。

    npm i -D extract-text-webpack-plugin@next
    

    但是,这里可能会产生报错,如下: Error: Path variable [contenthash:8] not implemented in this context: [name]_[contenthash:8].css
    原因:插件的临时版本@next并不支持contenthash。 参考:github.com/webpack-con…
    解决:

    plugins: [
        new ExtractTextPlugun({
     -       filename: '[name].[contenthash:8].css'
     +       filename: '[md5:contenthash:hex:20]'
        }})
    ]
    

    注意,现在extract-text-webpack-plugin已经不更新了,所以最好还是使用官方推荐的 mini-css-extract-plugin

  7. script-text-html-webpack-plugin
    原因:插件暂不支持 webpack4,将会在 v2版 实现
    解决:使用preload-webpack-plugin

6. 其他警告或报错

  1. You may need an appropriate loader to handle this file type
    原因:package.json 里安装依赖变化了,但是由于 package-lock.json 存在。
    解决办法:
    全局配置一劳永逸:

    npm config set package-lock false
    

    解决当前项目:
    删除 package-lock.jsonnode_modules,重新执行命令 npm install

  2. __webpack_hmr 404 not found
    原因:webpack 配置 entry 数组里面有 build/dev-client 的配置
    解决:删除这个配置即可

  3. 部分文件内作用域 this = undefined
    原因:不明
    解决:this 改为 window

  4. warn: entrypoint = undefined
    解决:不用理会

第二部分 Babel

1. 版本升级

# 不安装到本地而是直接运行命令,npm 的新功能
npx babel-upgrade --write

# 或者常规方式
npm i babel-upgrade -g
babel-upgrade --write

# 更新 babel 配置 并且 安装依赖
npx babel-upgrade --write --install

2. 配置文件

这里区分需不需要编译 node_modules 里面的依赖。
如果需要,删除项目根目录下 .babelrc 改为使用 babel.config.js

3. polyfill

  1. 推荐使用 @babel/preset-env 并按需引入 polyfill
    babel 7: @babel/polyfill
    babel 6: babel-polyfill
  2. Promise等ES6语法,在 Android 4.4以下 和 IE 的兼容问题
    // node 环境
    require('@babel/polyfill')
    
    // ES6 main.js
    import('@babel/polyfill')
    
    // webpack.base.config.js
    entry: ['@babel/polyfill', 'main.js']