從 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.preLoaders
和 module.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.ensure
和 AMD require
將採用非同步式呼叫
require.ensure
和amd 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 property
和options
均是用於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 能夠理解和處理 import
和export
了。
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`)
}
}
}