Vue-CLI2專案從 babel 6 + webpack 3.x 升級到 babel7 + webpack4.x 踩坑

hhh1991發表於2019-04-19

序言

      當前專案是通過當時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,而且現在這款外掛已經不更新了,所以,最好還是使用 mini-css-extract-plugin
    參考:github.com/webpack-con…
    解決:

    plugins: [
        new ExtractTextPlugun({
     -       filename: '[name].[contenthash:8].css'
     +       filename: '[md5:contenthash:hex:20]'
        }})
    ]
    複製程式碼
  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']
    複製程式碼

相關文章