webpack4升級指南

鑑於圖書項目編譯速度極慢的狀況(項目裏面module太多了,編譯慢很正常)且最近需求很少(不多出現的空擋期)。因此我以爲搞一波webpack升級,看看有沒有幫助。webpack於2018年2月25正式發佈v4.0.0版本,代號legato。名字是否是很大器,不明覺厲的樣子。廢話少說下面正式進入主題。(本文的升級配置主要針對vue項目)css

1、webpack4更新變化

先說一下webpack4有幾個比較重要的更新:webpack4更新日誌html

1.環境支持: 官方再也不支持Node 4,Node 6,最好使用v8.5.0以上穩定版本。支持93%的ES6語法。由於webpack4使用了不少JS新的語法,它們在新版本的 v8 裏通過了優化。vue

2.0配置: 受Parcel打包工具啓發,儘量的讓開發者運行項目的成本變低。webpack4再也不強制須要 webpack.config.js 做爲打包的入口配置文件了,它默認的入口爲'./src/'和默認出口'./dist',這對於小項目來講確實是一件不錯的事情。node

3.mode選項: 告知 webpack 使用相應模式的內置優化。可選 development 或 production,舉個栗子。webpack

module.exports = {
  mode: 'production'
};
複製代碼

或者從 CLI 參數中傳遞: webpack --mode = productiongit

選項 描述
development 會將 process.env.NODE_ENV 的值設爲 development。啓用 NamedChunksPlugin 和 NamedModulesPlugin。
production 會將 process.env.NODE_ENV 的值設爲 production。啓用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin.

==注意:只設置 NODE_ENV,則不會自動設置 mode==github

mode: developmentweb

// webpack.dev.config.js
module.exports = {
+ mode: 'development'
- plugins: [
-   new webpack.NamedModulesPlugin(),
-   new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
- ]
}
複製代碼

mode: productionvue-router

// 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()
-  ]
}
複製代碼

4.插件變化: webpack4刪除了CommonsChunkPlugin插件,它使用內置API optimization.splitChunks 和 optimization.runtimeChunk,即webpack會默認爲你生成共享的代碼塊。npm

5.Rule.loaders: 此選項已廢棄(Rule.loaders 是 Rule.use 的別名。可使用Rule.use)

6.WebAssembly: 開箱即用WebAssembly(這個沒用到,不知道是啥)

2、升級webpack4 loader及插件的配置修改

升級webpack4首先須要更新webpack到v4.0.0以上版,而後安裝webpack-cli,建議使用cnpm安裝,有時候npm安裝下載不下來。

npm install --save-dev webpack-cli
複製代碼

項目相關的loader和插件也是須要更新的,否則會報錯。接下來介紹一些須要額外配置的loader和插件。

1.vue-loader(更多細節)

Vue Loader v15 如今須要配合一個 webpack 插件才能正確使用:

// webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  // ...
  plugins: [
    new VueLoaderPlugin()
  ]
}
複製代碼

如今 Vue Loader v15 使用了一個不同的策略來推導語言塊使用的 loader。

拿 <style lang="less"> 舉例:在 v14 或更低版本中,它會嘗試使用 less-loader 加載這個塊,並在其後面隱式地鏈上 css-loader 和 vue-style-loader,這一切都使用內聯的 loader 字符串。

在 v15 中,<style lang="less"> 會完成把它看成一個真實的 *.less 文件來加載。所以,爲了這樣處理它,你須要在你的主 webpack 配置中顯式地提供一條規則:

{
  module: {
    rules: [
      // ... 其它規則
      {
        test: /\.less$/,
        use: [
          'vue-style-loader',
          'css-loader',
          'less-loader'
        ]
      }
    ]
  }
}
複製代碼

這樣作的好處是這條規則一樣應用在 JavaScript 裏普通的 *.less 導入中,而且你能夠爲這些 loader 配置任何你想要的選項。在 v14 或更低版本中,若是你想爲一個推導出來的 loader 定製選項,你不得不在 Vue Loader 本身的 loaders 選項中將它重複一遍。在 v15 中你再也沒有必要這麼作了。若是是用cli搭建的項目,升級webpack4時,別忘記把配置中的樣式規則刪掉,以下:

//webpack.dev.conf.js
const devWebpackConfig = merge(baseWebpackConfig, {
    mode: 'development',
-   module: {
-     rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
-   },
    //其餘配置...
})
複製代碼

webpack.prod.conf.js文件同上修改

鑑於 v15 中的推導變化,若是你導入一個 node_modules 內的 Vue 單文件組件,它的 <script> 部分在轉譯時將會被排除在外。爲了確保 JS 的轉譯應用到 node_modules 的 Vue 單文件組件,你須要經過使用一個排除函數將它們加入白名單:

{
  test: /\.js$/,
  loader: 'babel-loader',
  exclude: file => (
    /node_modules/.test(file) &&
    !/\.vue\.js/.test(file)
  )
}
複製代碼

Vue Loader v15 還廢棄了不少選項,它們應該使用普通的 webpack 模塊的規則來配置:

  • loader
  • preLoaders
  • postLoaders
  • postcss
  • cssSourceMap
  • buble
  • extractCSS
  • template

下列選項已經被廢棄了,它們應該使用新的 compilerOptions 選項來配置:

  • preserveWhitespace (使用 compilerOptions.preserveWhitespace)
  • compilerModules (使用 compilerOptions.modules)
  • compilerDirectives (使用 compilerOptions.directives)

下列選項已經被更名了:

  • transformToRequire (如今更名爲 transformAssetUrls)

2.CommonsChunkPlugin

前面說起到webpack4刪除了CommonsChunkPlugin插件,須要使用內置API optimization.splitChunks 和 optimization.runtimeChunk替代,具體替代配置以下:

//webpack.prod.conf.js
optimization: {
    //其餘配置
    runtimeChunk: {
      name: 'manifest'
    },
    splitChunks:{
      chunks: 'async',
      minSize: 30000,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      name: false,
      cacheGroups: {
        vendor: {
          name: 'vendor',
          chunks: 'initial',
          priority: -10,
          reuseExistingChunk: false,
          test: /node_modules\/(.*)\.js/
        },
        styles: {
          name: 'styles',
          test: /\.(scss|css)$/,
          chunks: 'all',
          minChunks: 1,
          reuseExistingChunk: true,
          enforce: true
        }
      }
    }
  },
複製代碼

因爲CommonsChunkPlugin已經廢棄,因此HtmlWebpackPlugin插件配置中的chunksSortMode也再也不須要。

plugins: [
    //...
    new HtmlWebpackPlugin({
      filename: process.env.NODE_ENV === 'testing'? 'index.html': config.build.index,
      template: 'index.html',
      inject: true,
      inlineSource:/(app\.(.+)?\.css|manifest\.(.+)?\.js)$/,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
        // more options: https://github.com/kangax/html-minifier#options-quick-reference
      },
-      chunksSortMode: 'dependency'
    }),
]
複製代碼

3.mini-css-extract-plugin(更多細節)(webpack4新插件)

介紹新插件以前先說一下extract-text-webpack-plugin,相信你們多少會據說過,它的做用是會將全部的入口 chunk(entry chunks)中引用的 *.css,移動到獨立分離的 CSS 文件。所以,你的樣式將再也不內嵌到 JS bundle 中,而是會放到一個單獨的 CSS 文件(即 styles.css)當中。 若是你的樣式文件大小較大,這會作更快提早加載,由於 CSS bundle 會跟 JS bundle 並行加載。不過在webpack4中推薦使用mini-css-extract-plugin這個插件。具體配置以下:

//webpack.prod.conf.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin")

plugins:[
    //其餘配置
    new MiniCssExtractPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css'),//"[name].css"
      chunkFilename: utils.assetsPath('css/[id].css'),//"[id].css"
    }),
]
複製代碼
//webpack.base.conf.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const devMode = process.env.NODE_ENV === 'production'

module: {
    rules: [
      //...
      {
        test: /\.css$/,
        use: [
          devMode ?  MiniCssExtractPlugin.loader : 'vue-style-loader',
          {
            loader: 'css-loader',
            options: { importLoaders: 1 }
          },
          'postcss-loader'
        ]
      },
    ]
}
複製代碼

再來壓縮一下css和js

//webpack.prod.conf.js
const UglifyJsPlugin = require("uglifyjs-webpack-plugin")
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin")

optimization: {
    minimizer: [
      new UglifyJsPlugin({
        cache: true,
        parallel: true,
        sourceMap: true // set to true if you want JS source maps
      }),
      new OptimizeCSSAssetsPlugin({})
    ],
}
複製代碼

我在升級過程當中大概升級了這些東西:

"devDependencies": {
    //...
    "webpack": "^4.27.1",
    "webpack-cli": "^3.1.2",
    "webpack-dev-server": "^3.1.10",
    "vue-loader": "^15.4.2",
    "vue-style-loader": "^4.1.2",
    "html-webpack-plugin": "^3.2.0",
    "html-webpack-inline-source-plugin": "0.0.10",
    "babel-loader": "^7.1.3",
    "file-loader": "^2.0.0",
    "mini-css-extract-plugin": "^0.5.0",
    "ts-loader": "^5.3.1",
    "url-loader": "^1.1.2",
    "vue-html-loader": "^1.2.4",
}
複製代碼

由於不一樣的項目相關配置不一樣,因此接下來就能夠npm run dev,看看有啥報錯,哪一個插件、loader有問題,升個級就行了。

3、項目運行起來了

image

咋回事樣式亂了,看一眼控制檯,發現:

image

組件的模板沒有識別,這是什麼毛病?在翻閱了大量資料後,得知:

在引入組件的時候,除了ES6的 import 以外,還能夠用 webpack 的 require,好比,在vue文件裏,就寫了大量的以下代碼:

const KingKong = require('../BookHomeCommon/KingKong.vue');
const BookList = require('../BookHomeCommon/BookList.vue');
const HomeAlert = require('../BookHomeCommon/HomeAlert.vue');
const DoubleElevenToast = require('../Activitys/DoudleEleven/DoubleElevenToast.vue');
const BrandVideo = require('./BrandVideo.vue');
複製代碼

在vue-loader v13.0.0的更新文檔中提到:

New

  • Now uses ES modules internally to take advantage of webpack 3 scope hoisting. This should result in smaller bundle sizes.
  • Now uses PostCSS@6.

Breaking Changes

  • The esModule option is now true by default, because this is necessary for ES-module-based scope hoisting to work. This means the export from a *.vue file is now an ES module by default, so async components via dynamic import like this will break:
const Foo = () => import('./Foo.vue')
複製代碼

Note: the above can continue to work with Vue 2.4 + vue-router 2.7, which will automatically resolve ES modules' default exports when dealing with async components. In earlier versions of Vue and vue-router you will have to do this:

const Foo = () => import('./Foo.vue').then(m => m.default)
複製代碼

Alternatively, you can turn off the new behavior by explicitly using esModule: false in vue-loader options.


Similarly, old CommonJS-style requires will also need to be updated:

// before
const Foo = require('./Foo.vue')

// after
const Foo = require('./Foo.vue').default
複製代碼
  • PostCSS 6 might break old PostCSS plugins that haven't been updated to work with it yet.

官方文檔說的很清楚:

  • 能夠在 vue-loader 的 options 裏經過 esModule: false 配置來關閉 ES 模塊
  • 同步引入組件,正經常使用 import,而原來使用 require 引入 ES6 語法的文件(例如:export default {...}),如今須要多加一個 default 屬性來引用。異步引入組件,須要用動態 import 語法
  1. 若是有經過 require 引入組件的話,所有改成 require(xxx).default
  2. 若是有異步引入組件的話,所有更新爲動態 import 方式,() => import(xxx)

可是在vue-loader v14.0.0中已經移除options裏的esModule配置。沒法關閉ES模塊。那咋整?項目中已經有大量的const Foo = require('./Foo.vue')的用法。一個一個修改,太費時間,這時自定義loader就是一個很好的解決方案。代碼以下:

//compatible-es-module.js
module.exports = function(content) {
  return content.replace(new RegExp(/require\('\.[/\w\.]+[^(\.png|\.gif|\.jpg)]'\)(\.default)?/,'g'),function(res) {
    return  /(\.default)$/.test(res)  ? res :res + '.default';
  });
};

//

module: {
    rules: [
      {
        test: /\.(vue)$/,
        loader: './compatible-es-module'
      }
    ]
}
複製代碼

4、總結

費這麼大勁升級,不能沒有效果吧,下面看看編譯時間對比,這是webpack3編譯時長截圖:

image

這是webpack4的:

image

51萬ms和21萬ms編譯速度足足提升了50%多。(不一樣項目狀況不一樣,請以實際狀況爲準)

webpack4還有不少新的特性,有待繼續學習。

人們老是對未知的東西,感到恐懼。直面本身的恐懼,學習理解它,你就會進步。(手動耶)

相關文章
相關標籤/搜索