從 webpack v1 遷移到 webpack v2 新特性

pingfan發表於2019-02-16

從 webpack v1 遷移到 webpack v2 新特性

歡迎小夥伴們為 前端導航平臺 點star
github倉庫: https://github.com/pfan123/fr…
訪問 前端導航平臺

Tree Shaking

Tree shaking 是一個術語,通常用來描述移除 JavaScript 上下文中無用程式碼這個過程,或者更準確的說是按需引用程式碼,它依賴於 ES2015 模組系統中 import/export 的靜態結構特性。這個術語和概念實際上是興起於 ES2015 模組打包工具 rollup

webpack 2 原生支援ES6模組 (別名 harmony modules) ,並能檢測出未使用的模組輸出。

示例:舉一個 maths.js 庫例子,它輸出兩個方法 square 和 cube:

// 這個函式沒有被其他地方引用過
export function square(x) {
    return x * x;
}

// 這個函式被引用了
export function cube(x) {
    return x * x * x;
}

在 main.js 中我們只引用 cube 方法:

import {cube} from `./maths.js`;
console.log(cube(5)); // 125

執行 node_modules/.bin/webpack main.js dist.js 並檢查 dist.js 可發現 square 沒有被輸出:

/* ... webpackBootstrap ... */
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* unused harmony export square */
/* harmony export (immutable) */ __webpack_exports__["a"] = cube;
// 這個函式沒有被其他地方引用過
function square(x) {
  return x * x;
}

// 這個函式被引用了
function cube(x) {
  return x * x * x;
}

/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__maths_js__ = __webpack_require__(0);

console.log(__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__maths_js__["a" /* cube */])(5)); // 125

/***/ })

resolve.root, resolve.fallback, resolve.modulesDirectories

上述三個選項將被合併為一個標準配置項:resolve.modules. 更多關於resolve的資訊資訊可查閱 resolving.

  resolve: {
-   root: path.join(__dirname, "src")
+   modules: [
+     path.join(__dirname, "src"),
+     "node_modules"
+   ]
  }

resolve.extensions

該配置項將不再要求強制轉入一個空字串,而被改動到了resolve.enforceExtension下, 更多關於resolve的資訊資訊可查閱 resolving.

resolve.*

更多相關改動和一些不常用的配置項在此不一一列舉,大家如果在實際專案中用到可以到resolving)中進行檢視.

module.loaders 將變為 module.rules

舊版本中loaders配置項將被功能更為強大的rules取代,同時考慮到新舊版本的相容,之前舊版本的module.loaders的相關寫法仍舊有效,loaders中的相關配置項也依舊可以被識別。

新的loader配置規則會變得更加通俗易用,因此官方也非常推薦使用者能及時按module.rules中的相關配置進行調整升級。

  module: {
-   loaders: [
+   rules: [
      {
        test: /.css$/,
-       loaders: [
+       use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
-           query: {
+           options: {
              modules: true
            }
          }
        ]
      },
      {
        test: /.jsx$/,
        loader: "babel-loader", // Do not use "use" here
        options: {
          // ...
        }
      }
    ]
  }

鏈式loaders

同webpack1.X中類似,loaders繼續支援鏈式寫法,可將相關正則匹配到的檔案資源資料在幾個loader之間進行共享傳遞,詳細使用說明可見 rule.use

在wepback2中,使用者可通過use項來指定需要用到的loaders列表(官方推薦),而在weback1中,如果需要配置多個loaders則需要依靠簡單的 !符來切分,這種語法出於新舊相容的考慮,只會在module.loaders中生效。

  module: {
-   loaders: {
+   rules: {
      test: /.less$/,
-     loader: "style-loader!css-loader!less-loader"
+     use: [
+       "style-loader",
+       "css-loader",
+       "less-loader"
+     ]
    }
  }

module名稱後自動自動補全 -loader的功能將被移除

在配置loader時,官方不再允許省略-loader副檔名,loader的配置寫法上將逐步趨於嚴謹。

  module: {
    rules: [
      {
        use: [
-         "style",
+         "style-loader",
-         "css",
+         "css-loader",
-         "less",
+         "less-loader",
        ]
      }
    ]
  }

當然,如果你想繼續保持之前的省略寫法,你寫可以在resolveLoader.moduleExtensions中開啟預設副檔名配置,不過這種做法並不被推薦。

+ resolveLoader: {
+   moduleExtensions: ["-loader"]
+ }

可以從這裡檢視 #2986此次變更的原因;

json-loader無需要獨立安裝

當我們需要讀取json格式檔案時,我們不再需要安裝任何loader,webpack2中將會內建 json-loader,自動支援json格式的讀取(喜大普奔啊)。

  module: {
    rules: [
-     {
-       test: /.json/,
-       loader: "json-loader"
-     }
    ]
  }

為何需要預設支援json格式官方的解釋是為了在webpack, node.js and browserify三種構建環境下提供無差異的開發體驗。

loader配置項將預設從context中讀取

在webpack 1中的一些特殊的loader在讀取對應資源時,需要通過require.resolve指定後才能指定生效。從webpack 2後,配置loader在直接從context中進行讀取,這就解決了一些在使用“npm連結”或引用模組之外的context造成的模組重複匯入的問題。

配置中可以刪除如下程式碼:

  module: {
    rules: [
      {
        // ...
-       loader: require.resolve("my-loader")
+       loader: "my-loader"
      }
    ]
  },
  resolveLoader: {
-   root: path.resolve(__dirname, "node_modules")
  }

module.preLoadersmodule.postLoaders 將被移除

  module: {
-   preLoaders: [
+   rules: [
      {
        test: /.js$/,
+       enforce: "pre",
        loader: "eslint-loader"
      }
    ]
  }

之前需要用到preLoader的地方可以改到rules的enfore中進行配置。

UglifyJsPlugin中的 sourceMap配置項將預設關閉

UglifyJsPlugin中的sourceMap 預設項將從 true變為 false

這就意味著當你的js編譯壓縮後,需要繼續讀取原始指令碼資訊的行數,位置,警告等有效除錯資訊時,你需要手動開啟UglifyJsPlugin 的配置項:sourceMap: true

  devtool: "source-map",
  plugins: [
    new UglifyJsPlugin({
+     sourceMap: true
    })
  ]

UglifyJsPlugin 的警告配置將預設關閉

UglifyJsPlugin中的 compress.warnings 預設項將從 true變為 false

這就意味著當你想在編譯壓縮的時候檢視一部分js的警告資訊時,你需要將compress.warnings 手動設定為 true

  devtool: "source-map",
  plugins: [
    new UglifyJsPlugin({
+     compress: {
+       warnings: true
+     }
    })
  ]

UglifyJsPlugin 不再支援讓 Loaders 最小化檔案的模式了

UglifyJsPlugin 將不再支援讓 Loaders 最小化檔案的模式。debug 選項已經被移除。Loaders 不能從 webpack 的配置中讀取到他們的配置項。

loade的最小化檔案模式將會在webpack 3或者後續版本中被徹底取消掉.

為了相容部分舊式loader,你可以通過 LoaderOptionsPlugin 的配置項來提供這些功能。

  plugins: [
+   new webpack.LoaderOptionsPlugin({
+     minimize: true
+   })
  ]

DedupePlugin 已經被移除

webpack.optimize.DedupePlugin 不再需要. 從你以前的配置移除這個配置選項.

BannerPlugin 配置項將有所改變

BannerPlugin 將不再允許接受兩個引數,而是隻提供一個物件配置項.

  plugins: [
-    new webpack.BannerPlugin(`Banner`, {raw: true, entryOnly: true});
+    new webpack.BannerPlugin({banner: `Banner`, raw: true, entryOnly: true});
  ]

OccurrenceOrderPlugin 將被內建加入

不需要再針對OccurrenceOrderPlugin進行配置

  plugins: [
-   new webpack.optimize.OccurrenceOrderPlugin()
  ]

ExtractTextWebpackPlugin配置項將有所改變

ExtractTextPlugin ] 1.0.0 在webpack v2將無法使用,你需要重新指定安裝ExtractTextPlugin 的webpack2的適配版本.

npm install --save-dev extract-text-webpack-plugin@beta

更新後的ExtractTextPlugin版本會針對wepback2進行相應的調整。

ExtractTextPlugin.extract的配置書寫方式將調整

module: {
  rules: [
    test: /.css$/,
-    loader: ExtractTextPlugin.extract("style-loader", "css-loader", { publicPath: "/dist" })
+    loader: ExtractTextPlugin.extract({
+      fallbackLoader: "style-loader",
+      loader: "css-loader",
+      publicPath: "/dist"
+    })
  ]
}

new ExtractTextPlugin({options})的配置書寫方式將調整

plugins: [
-  new ExtractTextPlugin("bundle.css", { allChunks: true, disable: false })
+  new ExtractTextPlugin({
+    filename: "bundle.css",
+    disable: false,
+    allChunks: true
+  })
]

全量動態載入資源將預設失效

只有使用一個表示式的資源依賴引用(i. e. require(expr)),現在將建立一個空的context,而不是一個context的完整目錄。

當在es2015的模組化中無法工作時,請最好重構這部分的程式碼,如果無法進行修改這部分程式碼,你可以在ContextReplacementPlugin中來提示編譯器做出正確處理。

Cli使用自定義引數作為配置項傳入方式將做調整

如果你隨意將自定義引數通過cli傳入到配置項中,如:

webpack --custom-stuff

// webpack.config.js
var customStuff = process.argv.indexOf("--custom-stuff") >= 0;
/* ... */
module.exports = config;

你會發現這將不會被允許,cli的執行將會遵循更為嚴格的標準。

取而代之的是用一個介面來做傳遞引數配置。這應該是新的代替方案,未來的工具開發也可能依賴於此。

webpack --env.customStuff

module.exports = function(env) {
  var customStuff = env.customStuff;
  /* ... */
  return config;
};

檢視更多介紹 CLI.

require.ensureAMD require將採用非同步式呼叫

require.ensureamd require將預設採用非同步的載入方式來呼叫,而非之前的當模組請求載入完成後再在回撥函式中同步觸發。

require.ensure將基於原生的Promise物件重新實現,當你在使用 require.ensure 時請確保你的執行環境預設支援Promise物件,如果缺少則推薦使用安裝polyfill.

Loader的配置項將通過options來設定

webpack.config.js中將不再允許使用自定義屬性來配置loder,這直接帶來的一個影響是:在ts配置項中的自定義屬性將無法在被在webpack2中正確使用:

module.exports = {
  ...
  module: {
    rules: [{
      test: /.tsx?$/,
      loader: `ts-loader`
    }]
  },
  // does not work with webpack 2
  ts: { transpileOnly: false }
}

什麼是 options?

這是一個非常好的提問,嚴格意義上來說,custom propertyoptions均是用於webpack loader的配置方式,從更通俗的說法上看,options應該被稱作query,作為一種類似字串的形式被追加到每一個loader的命名後面,非常類似我們用於url中的查詢字串,但在實際應用中功能要更為強大:

module.exports = {
  ...
  module: {
    rules: [{
      test: /.tsx?$/,
      loader: `ts-loader?` + JSON.stringify({ transpileOnly: false })
    }]
  }
}

options也可作為一個獨立的字面物件量,在loader的配置中搭配使用。

module.exports = {
  ...
  module: {
    rules: [{
      test: /.tsx?$/,
      loader: `ts-loader`,
      options:  { transpileOnly: false }
    }]
  }
}

LoaderOptionsPlugin context

部分loader需要配置context資訊, 並且支援從配置檔案中讀取。這需要loader通過用長選項傳遞進來,更多loader的明細配置項可以查閱相關文件。

為了相容部分舊式的loader配置,也可以採用如下外掛的形式來進行配置:

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

debug

debug作為loader中的一個除錯模式選項,可以在webpack1的配置中靈活切換。在webpack2中,則需要loader通過用長選項傳遞進來,更多loader的明細配置項可以查閱相關文件。

loder的debug模式在webpack3.0或者後續版本中將會被移除。

為了相容部分舊式的loader配置,也可以採用如下外掛的形式來進行配置:

- debug: true,
  plugins: [
+   new webpack.LoaderOptionsPlugin({
+     debug: true
+   })
  ]

Code Splitting with ES2015

在webpack1中,你需要使用require.ensure實現chunks的懶載入,如:

require.ensure([], function(require) {
  var foo = require("./module");
});

在es2015的 loader中通過定義import()作為資源載入方法,當讀取到符合ES2015規範的模組時,可實現模組中的內容在執行時動態載入。

webpack在處理import()時可以實現按需提取開發中所用到的模組資源,再寫入到各個獨立的chunk中。webpack2已經支援原生的 ES6 的模組載入器了,這意味著 webpack 2 能夠理解和處理 importexport了。

import()支援將模組名作為引數出入並且返回一個Promise物件。

function onClick() {
  import("./module").then(module => {
    return module.default;
  }).catch(err => {
    console.log("Chunk loading failed");
  });
}

這樣做的還有一個額外的好處就是當我們的模組載入失敗時也可以被捕獲到了,因為這些都會遵循Promise的標準來實現。

值得注意的地方:require.ensure的第三個引數選項允許使用簡單的chunk命名方式,但是import API中將不被支援,如果你希望繼續採用函式式的寫法,你可以繼續使用require.ensure

require.ensure([], function(require) {
  var foo = require("./module");
}, "custom-chunk-name");

(注: System.import將會被棄用,webpack中將不再推薦使用 System.import,官方也推薦使用import進行替換,詳見 v2.1.0-beta.28)

如果想要繼續使用Babel中提供的import,你需要獨立安裝 dynamic-import 外掛並且選擇babel的Stage 3來捕獲時的錯誤, 當然這也可以根據實際情況來操作而不做強制約束。

Dynamic expressions動態表示式

現在import()中的傳參可支援部分表示式的寫法了,如果之前有接觸過CommonJS中require()表示式寫法,應該不會對此感到陌生,(它的操作其實和 CommonJS 是類似的,給所有可能的檔案建立一個環境,當你傳遞那部分程式碼的模組還不確定的時候,webpack 會自動生成所有可能的模組,然後根據需求載入。這個特性在前端路由的時候很有用,可以實現按需載入資源)

import() 會針對每一個讀取到的module建立獨立的separte chunk

function route(path, query) {
  return import(`./routes/${path}/route`)
    .then(route => new route.Route(query));
}
// This creates a separate chunk for each possible route

可以混用 ES2015 和 AMD 和 CommonJS

在 AMD 和 CommonJS 模組載入器中,你可以混合使用所有(三種)的模組型別(即使是在同一個檔案裡面)。

// CommonJS consuming ES2015 Module
var book = require("./book");

book.currentPage;
book.readPage();
book.default === "This is a book";
// ES2015 Module consuming CommonJS
import fs from "fs"; // module.exports map to default
import { readFileSync } from "fs"; // named exports are read from returned object+

typeof fs.readFileSync === "function";
typeof readFileSync === "function";

注:es2015 balel 的預設預處理會把 ES6 模組載入器轉化成 CommonJS 模組載入。要是想使用 webpack 新增的對原生 ES6 模組載入器的支援,你需要使用 es2015-webpack 來代替,另外如果你希望繼續使用babel,則需要通過配置babel項,使其不會強制解析這部分的module symbols以便webpack能正確使用它們,babel的配置如下:

.babelrc

{
  "presets": [
    ["es2015", { "modules": false }]
  ]
}

Hints

No need to change something, but opportunities

Template strings模板字串

webpack中的資源引數已經開始支援模板字串了,這意味著你可以使用如下的配置寫法:

- require("./templates/" + name);
+ require(`./templates/${name}`);

配置支援項支援Promise

webpack現在在配置檔案項中返回Promise了,這就允許你在配置中可以進行一些非同步的寫法了,如下所示:

webpack.config.js

module.exports = function() {
  return fetchLangs().then(lang => ({
    entry: "...",
    // ...
    plugins: [
      new DefinePlugin({ LANGUAGE: lang })
    ]
  }));
};

Loader匹配支援更多的高階寫法

webpack中的loader配置支援如下寫法:

module: {
  rules: [
    {
      resource: /filename/, // matches "/path/filename.js"
      resourceQuery: /querystring/, // matches "/filename.js?querystring"
      issuer: /filename/, // matches "/path/something.js" if requested from "/path/filename.js"
    }
  ]
}

更多的CLI引數項

如下有更多的CLI 引數項可用:

--define process.env.NODE_ENV="production" 支援直接配置DefinePlugin.

--display-depth 能顯示每個entry中的module的資源深度

--display-used-exports 能顯示每個module中依賴使用了哪些資源.

--display-max-modules能限制顯示output中引用到的資源數量 (預設顯示15個).

-p 指定當前的編譯環境為生產環境,即修改:process.env.NODE_ENV"production"

Cacheable快取項

Loaders 現在預設可被快取。Loaders 如果不想被快取,需要選擇不被快取。

  // Cacheable loader
  module.exports = function(source) {
-   this.cacheable();
    return source;
  }
  // Not cacheable loader
  module.exports = function(source) {
+   this.cacheable(false);
    return source;
  }

Complex options複合引數項寫法

webpack v1 只支援能夠「可 JSON.stringify的物件」作為 loader 的 options。

webpack2中的loader引數項中已經可以支援任意的JS物件的寫法了。

使用複合選項時會有一個限制,你需要配置一個ident作為項來保證能正確引用到其他的loader,這意味著通過配置我們可以在內聯寫法中去呼叫對應依賴的載入器,如下:

require("some-loader??by-ident!resource")

{
  test: /.../,
  loader: "...",
  options: {
    ident: "by-ident",
    magic: () => return Math.random()
  }
}

v2.2.1之前(即從 v2.0.0 到 v2.2.0),使用 Complex options,需要在 options 物件上新增 ident,允許它能夠被其他 loader 引用。這在 v2.2.1 中被刪除,因此目前的遷移不再需要使用 ident 鍵。

{
  test: /.ext/
  use: {
    loader: `...`,
    options: {
-     ident: `id`,
      fn: () => require(`./foo.js`)
    }
  }
}

參考資料:
webpack2.0
webpack guide

相關文章