基于vue-cli2.0,webpack3升級(jí)為webpack4的踩坑之旅以及優(yōu)化

前言:由于項(xiàng)目是基于vue-cli2搭建的,使用的是webpack3.x版本。隨著時(shí)間的遷移,打包的速度越來(lái)越慢,痛下決定將webpack升級(jí)到4.0版本。

安裝依賴

首先先貼出升級(jí)完成后的package.json

"devDependencies": {
    "assets-webpack-plugin": "^3.9.10",
    "autoprefixer": "^7.1.2",
    "babel-core": "^6.22.1",
    "babel-helper-vue-jsx-merge-props": "^2.0.3",
    "babel-loader": "^7.1.4",
    "babel-plugin-syntax-jsx": "^6.18.0",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-plugin-transform-vue-jsx": "^3.7.0",
    "babel-preset-env": "^1.3.2",
    "babel-preset-stage-2": "^6.22.0",
    "chalk": "^2.0.1",
    "copy-webpack-plugin": "^5.0.4",
    "css-loader": "^3.2.0",
    "file-loader": "^4.2.0",
    "friendly-errors-webpack-plugin": "^1.7.0",
    "html-webpack-plugin": "^3.2.0",
    "kity": "^2.0.4",
    "less": "^3.0.1",
    "less-loader": "^5.0.0",
    "mini-css-extract-plugin": "^0.8.0",
    "node-notifier": "^5.1.2",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "ora": "^1.2.0",
    "portfinder": "^1.0.13",
    "postcss-import": "^11.0.0",
    "postcss-loader": "^3.0.0",
    "postcss-url": "^7.2.1",
    "progress-bar-webpack-plugin": "^1.11.0",
    "rimraf": "^2.6.0",
    "semver": "^5.3.0",
    "shelljs": "^0.7.6",
    "uglifyjs-webpack-plugin": "^1.1.1",
    "url-loader": "^2.1.0",
    "vue-loader": "^15.7.1",
    "vue-style-loader": "^4.1.2",
    "vue-template-compiler": "^2.5.21",
    "webpack": "^4.39.3",
    "webpack-cli": "^3.3.8",
    "webpack-dev-middleware": "^3.7.1",
    "webpack-dev-server": "^3.8.0",
    "webpack-merge": "^4.2.2",
  },
第一步

npm i -D webpack@4.39.0
出現(xiàn)報(bào)錯(cuò):connot find module 'webpack/bin/config-yargs'


報(bào)錯(cuò)原因:提示缺少依賴webpack-cli
解決方案:安裝依賴npm i -D webpack-cli@3.3.8

第二步

在安裝完webpack-cli之后,在繼續(xù)npm run dev
出現(xiàn)報(bào)錯(cuò):
var outputName = compilation.mainTemplate.applyPluginsWaterfall('asset-path', outputOptions.filename, {
^
TypeEorror: compilation.mainTemplate.applyPluginsWaterfall is not function


報(bào)錯(cuò)原因html-webpack-plugin版本過(guò)低
解決方案:安裝依賴npm i -D html-webpack-plugin@3.2.0

第三步

在升級(jí)完html-webpack-plugin之后,在繼續(xù)npm run dev
出現(xiàn)報(bào)錯(cuò): Module build failed (from ./node_module/vue-loader/index.js):
TypeError: Connot read property 'vue' of undefined.


根據(jù)上面描述,很明顯是vue-loader的依賴出了問(wèn)題,想要了解詳情的請(qǐng)點(diǎn)擊從v14遷移 | vue-loader
報(bào)錯(cuò)原因vue-loader版本過(guò)低
解決方案
安裝依賴npm i -D vue-loader@15.7.1
webpack.base.conf.js下修改
-- const vueLoaderConfig = require('./vue-loader.conf')
-- options: vueLoaderConfig
++ const { VueLoaderPlugin } = require('vue-loader')
++ new VueLoaderPlugin()
具體操作

//const vueLoaderConfig = require('./vue-loader.conf')
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
  ...,
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
       // options: vueLoaderConfig
      },
    ]
  },
 plugins: [
   new VueLoaderPlugin()
 ]
}
第四步

升級(jí)完后繼續(xù)dev
出現(xiàn)報(bào)錯(cuò):
Module parse failed: Unexpected token
File was process with loaders:
*./node_modules/vue-loaders/lib/index.js
You may need an additional loader to handle the result of these loaders.
.ht-Head-Search{
...
}


這時(shí)候會(huì)感到困惑,我剛剛升級(jí)了vue-loaderv15版本,怎么還是提示vue-loader的錯(cuò)誤,難道是升級(jí)的方式不對(duì)?
繼續(xù)往下看,發(fā)現(xiàn)多了點(diǎn)css樣式的提示,明白了可能是css-loadervue-style-loader也需要跟著升級(jí)
報(bào)錯(cuò)原因css-loader,vue-style-loader版本過(guò)低
解決方案:安裝依賴npm i -D css-loader@3.2.0 vue-style-loader@4.1.2

第五步

升級(jí)是考驗(yàn)?zāi)托牡氖虑?,不著急一個(gè)坑一個(gè)坑的解決,在升級(jí)完css-loadervue-style-loade后,繼續(xù)dev
出現(xiàn)報(bào)錯(cuò):
Module Warning (from ./node_module/postcss-loader/lib/index):
(Emitted value instead of an instace of Error)
PostCSS Loader
Previous source map found,but options.sourceMap isn't set.


報(bào)錯(cuò)原因postcss-loader版本過(guò)低
解決方案:安裝依賴npm i -D postcss-loader@3.0.0

解決development環(huán)境下的配置問(wèn)題

首先在webpack.dev.conf.js 文件下添加mode: 'development',
然后移除NamedModulesPlugin,NoEmitOnErrorsPlugin插件,webpack4已修改為內(nèi)置插件

module.exports = {
  ...,
 mode: 'development,
 ...,
 plugins: {
     //new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
    //new webpack.NoEmitOnErrorsPlugin(),
  }  
}

在這里終于看到了我們熟悉的localhost:8080,說(shuō)明我們的項(xiàng)目可以成功的在dev環(huán)境下運(yùn)行了,心想升級(jí)到webpack4,也是挺簡(jiǎn)單的嘛,迫不及待的在網(wǎng)頁(yè)打開(kāi)地址,想和項(xiàng)目進(jìn)一步的親近的時(shí)候。卻在控制臺(tái)看見(jiàn)了報(bào)錯(cuò),不說(shuō)了重新打開(kāi)編譯繼續(xù)回到正題。
出現(xiàn)報(bào)錯(cuò):
Uncaught TypeError:Connot assign to read only property 'exports' of object '#<Object>'
at Module.eval (BaseClient.js)
at Module../node_module/webpack-dev-srever/client/clients/BaseClient.js


報(bào)錯(cuò)原因:說(shuō)是es6import 和es5的module.export不能一起使用,項(xiàng)目全局查詢 module.export發(fā)現(xiàn)沒(méi)沒(méi)有一起使用,排除掉這個(gè)可能,真相只有一個(gè),那就是配置出了問(wèn)題
解決方案
一共有兩種解決方案

  • 第一種方法 移除resolve('node_modules/webpack-dev-server/client')
    webpack.base.conf.js
module.exports = {
  ...
  module: {
    rules: [  
      {
        test: /\.js$/,
        loader: 'babel-loader'
       // include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
        include: [resolve('src'),resolve('test')],
        exclude: /node_modules/
      },

    ]
  }
}
  • 第二種方法 移除transform-runtime
    .babelrc
{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],
   // "plugins": ["transform-vue-jsx", "transform-runtime"]
  "plugins": ["transform-vue-jsx"]
}

重新dev,打開(kāi)控制臺(tái)沒(méi)有報(bào)錯(cuò),在這里,終于可以和項(xiàng)目來(lái)一波深入親近了,一波功能亂點(diǎn)確認(rèn)無(wú)可疑報(bào)錯(cuò),開(kāi)始幻想如何build,生成我們最愛(ài)的dist文件

解決production環(huán)境下配置問(wèn)題

第一步

首先在webpack.prod.conf.js文件下,添加mode: 'production'

const webpackConfig = merge(baseWebpackConfig, {
  ...,
  mode: 'production'
})

然后選擇build
果然,build的過(guò)程沒(méi)有想象的那么一帆風(fēng)順,通過(guò)npm run build,
項(xiàng)目還未跑動(dòng)就被扼殺在搖籃
出現(xiàn)報(bào)錯(cuò):
thorw new RemovedPluginError(errorMessage);
Error: webpack.optimize.CommonsChunkPlugin has been removed, please use config.optimization.splitChunks instead.


報(bào)錯(cuò)原因:
webpack4移除了 webpack.optimize.CommonsChunkPlugin
解決方案:
webpack.prod.conf.js文件下將以下方法刪除

  • webpack.optimize.CommonsChunkPlugin
  • webpack.optimize.CommonsChunkPlugin
  • webpack.optimize.CommonsChunkPlugin
  • webpack.optimize.ModuleConcatenationPlugin
  • OptimizeCSSPlugin
  • UglifyJsPlugin
const webpackConfig = merge(baseWebpackConfig, {
   ...,
    plugins: [
      //new webpack.optimize.CommonsChunkPlugin(),
      //new webpack.optimize.CommonsChunkPlugin({}),
      //new webpack.optimize.CommonsChunkPlugin({}),
      //new webpack.optimize.ModuleConcatenationPlugin(),
      //new OptimizeCSSPlugin({}),
      //new UglifyJsPlugin({})
   ],
})

然后在webpack.prod.conf.js文件下添加以下代碼:

  const webpackConfig = merge(baseWebpackConfig, {
    ...,
   optimization: {
    //取代 new UglifyJsPlugin
    minimizer: [
      // 壓縮代碼
      new UglifyJsPlugin({
        uglifyOptions: {
          compress: {
            warnings: false,
            drop_debugger: true,//關(guān)閉debug
            drop_console: true,//關(guān)閉console
          }
        },
        sourceMap: config.build.productionSourceMap,
        parallel: true
      }),
      // 可自己配置,建議第一次升級(jí)先不配置
      new OptimizeCSSPlugin({
        // cssProcessorOptions: config.build.productionSourceMap
        //     ? {safe: true, map: {inline: false}, autoprefixer: false}
        //     : {safe: true}
      }),
    ],
    // 識(shí)別package.json中的sideEffects以剔除無(wú)用的模塊,用來(lái)做tree-shake
    // 依賴于optimization.providedExports和optimization.usedExports
    sideEffects: true,
    // 取代 new webpack.optimize.ModuleConcatenationPlugin()
    concatenateModules: true,
    // 取代 new webpack.NoEmitOnErrorsPlugin(),編譯錯(cuò)誤時(shí)不打印輸出資源。
    noEmitOnErrors: true,
    splitChunks: {
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          chunks: 'initial',
          name: 'vendors',
        },
        'async-vendors': {
          test: /[\\/]node_modules[\\/]/,
          minChunks: 2,
          chunks: 'async',
          name: 'async-vendors'
        }
      }
    },
    runtimeChunk: { name: 'runtime' }
    },
  })
第二步

上面操作主要是廢除和遷移了一些插件,由于webpack4將其內(nèi)置,所以無(wú)需在plugins下進(jìn)行實(shí)例。
繼續(xù)build
出現(xiàn)報(bào)錯(cuò)
Error: Chunk.entrypoints: Use Chunks.groupsIterable and filter by instanceof Entrypoint insted
at .../node_module/extract-text-webpack-plugin/dist/index.js

09b24449a1acc0be03_pc.png

報(bào)錯(cuò)原因: extract-text-webpack-plugin:3.0.0并不兼容webpack4
解決方案:
官方推薦使用mini-css-extract-plugin來(lái)替換extract-text-webpack-plugin,另一種方法是升級(jí)extract-text-webpack-pluginxtract-text-webpack-plugin@4.0.0-beta.0
安裝依賴npm i -D mini-css-extract-plugin@0.8.0
安裝完成之后,打開(kāi)utils.jswebpack.prod.conf.js文件

  • utils.js
// const ExtractTextPlugin = require('extract-text-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

exports.cssLoaders = function (options) {
  ...
// Extract CSS when that option is specified
    // (which is the case during production build)
    // if (options.extract) {
    //   return ExtractTextPlugin.extract({
    //     use: loaders,
    //     fallback: 'vue-style-loader',
    //     publicPath: '../../', //注意: 此處根據(jù)路徑, 自動(dòng)更改,添加publicPath,可以在css中使用背景圖
    //   })
    // } else {
    //   return ['vue-style-loader'].concat(loaders)
    // }
 return [
      options.extract ? MiniCssExtractPlugin.loader : 'vue-style-loader',
    ].concat(loaders)
}
  • webpack.prod.conf.js
    添加mini-css-extract-plugin,移除ExtractTextPlugin
//const ExtractTextPlugin = require('extract-text-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const webpackConfig = merge(baseWebpackConfig, {
    ...,
    plugins: [
      new MiniCssExtractPlugin({
        filename: utils.assetsPath('css/[name].[contenthash].css'),
        allChunks: true,
      }),
        // new ExtractTextPlugin({
        //   filename: utils.assetsPath('css/[name].[contenthash].css'),
        // Setting the following option to `false` will not extract CSS from codesplit chunks.
        // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
       // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
       // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
      //  allChunks: true,
      // }),
    ]
})

build,終于看到可愛(ài)的 dist文件了,吃了dev的教訓(xùn),首先我們還是檢查下dist,是否正常的,要是隔壁老王打出了包豈不是一片青青草原。
打開(kāi)網(wǎng)頁(yè),發(fā)現(xiàn)所有的背景圖全部失效,找到utils.js下的配置看看有什么不對(duì)的地方。
發(fā)現(xiàn)webpack3中的設(shè)置

if (options.extract) {
      return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader',
        publicPath: '../../', //注意: 此處根據(jù)路徑, 自動(dòng)更改,添加publicPath,可以在css中使用背景圖
      })
    } else {
      return ['vue-style-loader'].concat(loaders)
    }

webpack4中的設(shè)置

   return [
      options.extract ? MiniCssExtractPlugin.loader : 'vue-style-loader',
    ].concat(loaders)

這明顯不對(duì),人家webpack3有配置背景圖路徑,咱webpack4可不能丟下,修改配置如下即可

 return [
      options.extract ? {loader:MiniCssExtractPlugin.loader, options: {publicPath: '../../'}} : 'vue-style-loader',
    ].concat(loaders)

如果有本文章沒(méi)有出現(xiàn)的報(bào)錯(cuò),請(qǐng)下面留言我會(huì)試著幫你解決?。?!
如果文章對(duì)你有所幫助,請(qǐng)留下你的小愛(ài)心
就這樣這次webpack3遷移到webpack4的踏坑之旅快走到尾聲了,不過(guò)這里還有一些bug不一定發(fā)生,我簡(jiǎn)單列出來(lái)。

拓展-(可能你的項(xiàng)目還有其他報(bào)錯(cuò))

  1. 如果你的項(xiàng)目中使用了web worker,那么你在升級(jí)到webpack4運(yùn)行dev的時(shí)候,會(huì)出現(xiàn)報(bào)錯(cuò),如下:
    Uncaught ReferenceError: window is not defined

    其實(shí)在worker-loader插件的issues已經(jīng)有了解決辦法,想要了解更多,請(qǐng)戳我
    解決辦法:webpack.base.conf.js文件下加入globalObject: 'this'
module.exports = {
...
 output: {
  path: config.build.assetsRoot,
    filename: '[name].js',
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath,
    globalObject: 'this'
}
}

2.如果你的項(xiàng)目出現(xiàn)這個(gè)錯(cuò)誤

Vue packages version mismatch:
- vue@2.5.21
- vue-template-compiler@2.5.16
This may cause things to work incorrectly. Make sure to use the same version for both.
If you are using vue-loader@>=10.0, simply update vue-template-compiler.
If you are using vue-loader@<10.0 or vueify, re-installing vue-loader/vueify should bump vue-template-compiler to the latest.
@ ./resources/assets/js/app.js 12:13-46
@ multi ./resources/assets/js/app.js ./resources/assets/sass/app.scss
ERROR in ./resources/assets/js/components/steps.vue
Module build failed (from ./node_modules/vue-loader/lib/index.js):
TypeError: Cannot read property 'parseComponent' of undefined

不要擔(dān)心,這只是你的vue版本和vue-template-compiler不一致導(dǎo)致的,
統(tǒng)一下兩個(gè)的版本就可以了

  1. 如果你的項(xiàng)目使用了eslint報(bào)錯(cuò)
TypeError: Cannot read property 'eslint' of undefined

請(qǐng)升級(jí)npm i -D eslint-loader@2.2.1

打包優(yōu)化

一.使用happypack多線程打包

1.安裝依賴npm i -D happypack@5.0.1
2.導(dǎo)入插件
webpack.base.conf.js

const HappyPack = require('happypack')
const os = require('os')
// 創(chuàng)建 happypack 共享進(jìn)程池
const happyThreadPool = HappyPack.ThreadPool({size: os.cpus().length})
module.exports = {
 ...
  module: {
   rules: [
    ...
     {
       test: /\.js$/,
       //loader: 'babel-loader',
       use: ['happypack/loader?id=babel'],
      // include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
       include: [resolve('src'),resolve('test')],
       exclude: /node_modules/
     }
   ]
 },
 plugins: [
    new HappyPack({
     /*
      * 必須配置項(xiàng)
      */
     // id 標(biāo)識(shí)符,要和 rules 中指定的 id 對(duì)應(yīng)起來(lái)
     id: 'babel',
     // 需要使用的 loader,用法和 rules 中 Loader 配置一樣
     // 可以直接是字符串,也可以是對(duì)象形式
     loaders: ['babel-loader?cacheDirectory'],
     // 使用共享進(jìn)程池中的進(jìn)程處理任務(wù)
     threadPool: happyThreadPool,
     verbose: true
   })
 ]
}

二.使用DllReferencePlugin打包公共方法

1.新建文件dll.jswebpack.dll.conf.js,兩個(gè)文件和webpack.base.conf.js同級(jí)。
2.在dll.js文件下添加以下代碼

const path  = require('path');
const webpack = require('webpack');
const dllConfig  = require('./webpack.dll.conf');
const chalk = require('chalk')
const rm  = require('rimraf')
const ora = require('ora')
const spinner = ora({
  color: 'green',
  text: '正為生產(chǎn)環(huán)境打包dll包中...'
})
spinner.start()
rm(path.resolve(__dirname, '../dll'),  err => {
  if (err) throw err
  webpack(dllConfig,function (err, stats) {
    spinner.stop()
    if (err) throw err
    process.stdout.write(stats.toString({
      colors: true,
      modules: false,
      children: false,
      chunks: false,
      chunkModules: false
    }) + '\n\n')
    console.log(chalk.cyan('  dll打包已完成啦!\n'))
  })
});

3.在webpack.dll.conf.js文件下添加以下代碼


const path = require('path')
const webpack = require('webpack')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const AssetsPlugin = require('assets-webpack-plugin')
module.exports = {
  mode: 'development',
  entry: {
    vendor: [ 
      'vue/dist/vue.esm.js'
    ]
  },
  output: {
    path: path.join(__dirname, '../static/js/'), // 生成的文件存放路徑
    filename: 'dll.[name].[chunkhash].js', // 生成的文件名字(默認(rèn)為dll.vendor.[hash].js)
    library: '[name]_[chunkhash]' // 生成文件的映射關(guān)系,與下面DllPlugin中配置對(duì)應(yīng)
  },
  plugins: [
    new CleanWebpackPlugin(['dll','../static/js']),
    new webpack.DllPlugin({
      // 會(huì)生成一個(gè)json文件,里面是關(guān)于dll.js的一些配置信息
      path:path.join(__dirname, '../dll/[name]-manifest.json'),
      name: '[name]_[chunkhash]', // 與上面output中配置對(duì)應(yīng)
      context: __dirname // 上下文環(huán)境路徑(必填,為了與DllReferencePlugin存在與同一上下文中)
    }),
    new AssetsPlugin({  //
      filename: 'bundle-conf.json',
      path: './dll'
    })
  ]
}

  1. 安裝依賴npm i -D clean-webpack-plugin@latest assets-webpack-plugin@latest
    5.在webpack.prod.conf.js文件下
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const boundConf = require('../dll/bundle-conf.json') //bundle-confi.json目錄結(jié)構(gòu)要正確
const webpackConfig = merge(baseWebpackConfig, {
...
 plugins: [
    new CleanWebpackPlugin(['dist']),
      new webpack.DllReferencePlugin({
        context: __dirname,
        manifest: require('../dll/vendor-manifest.json')
      }),
    //這個(gè)主要是將生成的vendor.dll.js文件加上hash值插入到頁(yè)面中。
    new AddAssetHtmlPlugin([{
      filepath: path.resolve(`./static/js/${boundConf.vendor.js}`),
      outputPath: utils.assetsPath('js'),
      publicPath: path.posix.join(config.build.assetsPublicPath, 'static/js'),
      includeSourcemap: false,
      hash: true,
    }]),
  ]
})
  1. package.sjon下添加配置"build:dll": "node build/dll.js",
"scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "build": "node build/build.js",
    "build:dll": "node build/dll.js"
  },

6 只需第一次執(zhí)行npm run build:dll,打包生成文件之后,以后就不需要在npm run build之前再npm run build:dll

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容