翻譯 | 上手 Webpack ? 這篇就夠了待榔!

作者:小 boy (滬江前端開發(fā)工程師)
本文原創(chuàng)劲蜻,轉(zhuǎn)載請注明作者及出處毛俏。
原文地址:https://www.smashingmagazine.com/2017/02/a-detailed-introduction-to-webpack

JavaSript 模塊化打包已混跡江湖許久。2009年杏死,RequireJS 就提交了它的第一個版本泵肄,Browserify 接踵而至,隨后其他打包工具也開始大行其道淑翼。最終腐巢,Webpack 從其中脫穎而出。如果你對它不甚了解玄括,希望我的文章能讓你上手這件強力打包工具冯丙。

什么是模塊化打包工具?

在大多數(shù)語言(JS 的最新版本 ECMAScript 2015+ 也支持惠豺,但并非支持所有瀏覽器)中银还,你可以將代碼拆分至多個文件,并且通過在業(yè)務(wù)代碼中引用這些文件來使用它們包含的方法洁墙∮挤瑁可惜的是瀏覽器并不擁有這個能力。因此热监,模塊化打包工具應(yīng)運而生捺弦,它以兩種形式為瀏覽器提供這個能力:1.異步加載模塊,并且在加載結(jié)束后運行它們。2.將需要用到的文件拼湊成單一 JS 文件列吼,最終在 HTML 中使用 <script> 標(biāo)簽加載該 JS 文件幽崩。

如果沒有模塊化加載及打包工具的話,你就得手動拼湊文件或者在 HTML 中加載無數(shù)的 <script> 標(biāo)簽了寞钥,而且這樣干有一些不好的地方:

  • 你需要關(guān)心文件的加載順序慌申,包括哪些文件依賴于其他文件;還需要確認是否引入了不需要的文件理郑。
  • 多個 <script> 標(biāo)簽意味著用多個網(wǎng)絡(luò)請求來加載代碼蹄溉,同時也意味著更差的性能。
  • 顯然您炉,其中有很多本可以交給計算機完成的手工活柒爵。

大多數(shù)模塊化打包工具直接跟 npm 或者 Bower(譯者注:兩者都是包管理工具)整合,這樣可以讓你更容易地在業(yè)務(wù)代碼中添加第三方依賴包(dependencies)赚爵。你僅需要安裝一下棉胀,然后寫一行代碼引入它們,接著運行模塊化打包工具冀膝,這樣就已經(jīng)將第三方代碼整合進自己的業(yè)務(wù)代碼了唁奢。或者窝剖,如若配置正確驮瞧,你可以將所有要用的三方代碼整合進一個分開的文件,這樣一來枯芬,當(dāng)你更新業(yè)務(wù)代碼,用戶需要更新緩存的時候采郎,他們就無需重新下載公共庫代碼(vendor code)了千所。

為什么選擇 Webpack ?

至此,你已對 Webpack 的愿景有了基礎(chǔ)的認知蒜埋,然而為什么在各路豪杰中選擇 Webpack 呢淫痰?在我看來有這樣一些理由:

  • 小鮮肉的特性助了它一臂之力,借此特點它可以繞開或者避免前輩們遇到的問題整份。
  • 上手簡單待错。如果只是想打包一些JS文件,而沒有其他需求的話烈评,你甚至都不需要一份配置文件火俄。
  • 它的插件體系使其能做更多事情,從而十分強大讲冠,所以它可能是你需要的唯一構(gòu)建工具瓜客。

據(jù)我所知,少有其他的模塊打包和構(gòu)建工具也能做到這些。但 Webpack 仍勝一籌:當(dāng)你踩坑的時候有龐大的社區(qū)支持谱仪。
Browserify 的社區(qū)可能只是大玻熙,如果它不大的話,就會缺少一些 Webpack 的潛在必要特性疯攒。說了這么多 Webpack 的優(yōu)點嗦随,估計你就等上代碼了吧?那么我們開始敬尺。

安裝 Webpack

在使用 Webpack 前枚尼,我們首先要先把它安裝好。為此我們需要 Node.js 和 npm 筷转,我就假設(shè)你已經(jīng)安裝過它們了姑原,實在沒有的話,請從Node.js 官網(wǎng)開始吧呜舒。

有兩種方式安裝 webpack (或著是其他 CLI 包):全局安裝(globally)或者本地安裝(locally)锭汛。對于全局安裝,雖然你可以在任意目錄下使用它袭蝗,但是它不會包括在項目的依賴模塊列表(dependencies)中唤殴。此外,你也不能在兩個不同的項目(有些項目可能需要投入更多工作量才能更新到最新版本,所以這些項目還需要維持老版本)中切換不同版本的 Webpack 哪轿。所以我更愿意本地安裝 CLI 包稚虎,并且用相對路徑抑或是 npm 腳本來運行它。如果你不習(xí)慣本地安裝 CLI 包配名,可以看一下我之前寫的關(guān)于擺脫全局安裝 npm 包的博文。

不管怎樣晋辆,在示例項目中渠脉,我們就使用 npm 腳本。接下來瓶佳,先本地安裝示例項目芋膘。首先:創(chuàng)建一個用來實驗和學(xué)習(xí) Webpack 的目錄。 我在 GitHub 上有一個倉庫霸饲,你可以將它 clone 到本地为朋,然后在分支間切換來進行下面的學(xué)習(xí),或者從零開始創(chuàng)建一個新項目厚脉,此后可以與我的倉庫代碼進行對照习寸。

經(jīng)過命令行選擇,一進到項目目錄傻工,你將用 npm init 命令來初始化項目融涣。接下來要填的信息一點都不重要(譯者注:一路回車即可)童番,除非你想把項目發(fā)布到 npm 上。

至此 package.json 文件準(zhǔn)備就緒(它是通過 npm init 命令創(chuàng)建的)威鹿,在此文件中剃斧,你可以保存依賴包信息。我們通過 npm install webpack -D-D--save-dev 命令的簡寫忽你,它的作用是將 npm 包作為開發(fā)環(huán)境的依賴包安裝幼东,并將依賴信息保存到 package.json 文件中)命令將 Webpack 作為依賴包安裝。

我們需要一個簡單的應(yīng)用來開啟運用 Webpack 之旅科雳。所謂的簡單就是:首先執(zhí)行 npm install lodash -S-S == --save) 安裝 Lodash根蟹,如此一來我們的簡單應(yīng)用就有一個依賴包可以用來加載了。接著我們創(chuàng)建一個 src 目錄糟秘,再于該目錄中創(chuàng)建名為 main.js 的文件简逮,其內(nèi)容如下:

var map = require('lodash/map');

function square(n) {
    return n*n;
}

console.log(map([1,2,3,4,5,6], square));

很簡單對吧?我們僅僅創(chuàng)建了一個包含整數(shù)1至6的小數(shù)組尿赚,然后用 Loadash 庫中的 map 函數(shù)創(chuàng)建了一個新數(shù)組散庶,這個新數(shù)組中的數(shù)字是原數(shù)組中數(shù)字的平方。最后凌净,我們在控制臺中打印這個新數(shù)組悲龟。運行命令 node src/main.js 就能看到結(jié)果:[1, 4, 9, 16, 25, 36]。你瞧冰寻,其實 Node.js 都能運行這個文件须教。

但如果我們想打包這個小腳本,其中還包括我們能跑在瀏覽器的 Lodash 代碼斩芭,使用 Webpack 應(yīng)該從哪入手轻腺?如何做到?

Webpack 命令行

若不想在配置文件上浪費時間划乖,使用 Webpack 命令行是最容易的上手方式约计。如果不啟用配置文件的話,最簡潔的命令需要包含輸入文件(input file)路徑和輸出文件(output file)路徑迁筛。Webpack 會讀取輸入文件,追蹤它的依賴關(guān)系樹耕挨,并將所有依賴文件打包進一個文件细卧,最終在你指定的輸出路徑下輸出該文件。在本例中筒占,輸入路徑是 src/main.js 贪庙,我們要將打包后的文件輸出到 dist/bundle.js 下。為此翰苫,我們先添加 npm 腳本(我們并沒有全局安裝 Webpack 止邮,所以不能直接在命令行中運行)这橙。編輯 package.json 文件的 "scripts" 部分如下:

"scripts": {
    "build": "webpack src/main.js dist/bundle.js",
}

現(xiàn)在,執(zhí)行 npm run build 命令导披,Webpack 就會運行了屈扎。很快,運行完畢的時候會生成 dist/bundle.js 文件撩匕。然后你便可以用 Node.js (通過 node dist/bundle.js 命令)運行該文件了鹰晨。也可以借助簡單的 HTML 將其跑在瀏覽器上,之后可在控制臺中看到同樣的運行結(jié)果止毕。

在繼續(xù)探索 Webpack 前模蜡,我們先把構(gòu)建腳本調(diào)整得更專業(yè)一點:在重新構(gòu)建(rebuilding)前刪除 dist 目錄及其內(nèi)容,此外扁凛,我們再添加一些用于直接執(zhí)行 bundle 文件的腳本忍疾。首先,安裝 del-cli 工具谨朝,這樣就不用在刪除目錄的時候顧慮操作系統(tǒng)的區(qū)別了(見諒卤妒,因為我用的是 Windows)。運行 npm install del-cli -D 命令即可叠必。接著更新 npm 腳本如下:

"scripts": {
    "prebuild": "del-cli dist -f",
    "build": "webpack src/main.js dist/bundle.js",
    "execute": "node dist/bundle.js",
    "start": "npm run build -s && npm run execute -s"
  }

我們保持 "build" 配置同之前一樣荚孵,但增加了 "prebuild" 配置用以清除目錄,這條配置所執(zhí)行的命令會在每次 "build" 命令執(zhí)行之前運行纬朝。同時增加的還有 "execute" 配置:使用 Node.js 執(zhí)行已經(jīng)打包好的腳本收叶。此外,使用 "start" 配置可以通過一條命令執(zhí)行以上所有命令(-s 的作用僅僅是不讓 npm 腳本在控制臺打印一些沒用的東西)共苛。執(zhí)行 npm start 命令判没,就可以在控制臺里看到 Webpack 的輸出信息,緊接著打印的是平方后的數(shù)組隅茎。

恭喜澄峰!你剛剛完成了 example1 分支里所有的事情。這個分支就在我之前提到的倉庫中辟犀。

Webpack 配置文件

跟使用 Webpack 命令行上手一樣有趣的是俏竞,一旦開始使用更多 Webpack 的功能, 你就會想要放棄通過命令行傳遞 Webpack 配置參數(shù)堂竟,轉(zhuǎn)而投入配置文件的懷抱魂毁。使用配置文件雖然會更占位置,但與此同時增加了可讀性出嘹,因為它是由 JS 寫成的席楚。

那我們就來創(chuàng)建配置文件吧。在根目錄下創(chuàng)建一個新文件 webpack.config.js税稼。Webpack 默認尋找該文件烦秩,但如果想給配置文件取別的名字或者將配置文件放在其他目錄垮斯,你可以通過傳遞 --config [filename] 參數(shù)來做到。

在本教程中只祠,我們使用默認文件名《等洌現(xiàn)在,我們試著讓配置文件起作用铆农,達到與僅使用命令行同樣的效果牺氨。為此,我們需要在配置文件中添置如下代碼:

module.exports = {
    entry: './src/main.js',
    output: {
        path: './dist',
        filename: 'bundle.js'
    }
};

如此前一樣墩剖,我們規(guī)定輸入和輸出文件猴凹。因為這不是 JSON 文件而是 JS 文件,所以我們需要把配置對象(configuration object )導(dǎo)出岭皂,故使用 module.exports郊霎。雖然現(xiàn)在還看不出寫這些配置會比用命令好多少,但文章結(jié)尾你肯定會愛上這里的一切爷绘。

接下來书劝,移除 package.json 文件中給 Webpack 傳的配置,像這樣:

"scripts": {
    "prebuild": "del-cli dist -f",
    "build": "webpack",
    "execute": "node dist/bundle.js",
    "start": "npm run build -s && npm run execute -s"
}

像之前一樣執(zhí)行 npm start 命令土至,運行結(jié)果是不是似曾相識呢购对?以上就是分支 example2 中需要做的事情。

Webpack 加載器(Loaders)

我們主要通過兩種方式增強 Webpack: 加載器(loaders)和插件(plugins)陶因。我們先講加載器骡苞,插件稍后再議。加載器用以轉(zhuǎn)換或操作特定類型的文件楷扬,你可以將多個加載器串聯(lián)在一起來處理一種類型的文件解幽。例如,規(guī)定 .js 后綴的文件要先通過 ESLint 檢查烘苹,再通過 Babel 把 ES2015 語法轉(zhuǎn)換為 ES5 語法躲株。ESLint 發(fā)出的警報將會在控制臺打印出來,而遇到語法錯誤的時候則會阻止 Webpack 繼續(xù)打包镣衡。

我們這里就不設(shè)置語法檢查了霜定,但要通過設(shè)置 Babel 來把代碼轉(zhuǎn)化成 ES5。當(dāng)然我們得先有些 ES2015 代碼吧廊鸥?把 main.js 文件的代碼改成下面的樣子:

import { map } from 'lodash';

console.log(map([1,2,3,4,5,6], n => n*n));

實際上這段代碼和之前做的事情一樣望浩,但有兩點:其一,使用箭頭函數(shù)替代了之前定義的 square 函數(shù)黍图。其二,使用了 ES2015 中的 import 語法加載 lodash 庫中的 map 函數(shù)奴烙,但這將會把整個 Lodash 庫的代碼打包到我們的輸出文件中助被,而不是引入僅僅包含 map 函數(shù)相關(guān)代碼的 'lodash/map' 庫剖张。如果樂意的話,你也可以把第一行改成 import map from 'lodash/map'; 但我寫成這樣有我的理由:

  • 在更具規(guī)模的應(yīng)用里揩环,你可能要用到 Lodash 庫的很多部分搔弄,所以你最好全加載進來。
  • 如果你正在用 Backbone.js 框架丰滑,會發(fā)現(xiàn)僅打包你需要的函數(shù)是很困難的顾犹,因為根本就沒有文檔告訴你函數(shù)依賴哪些函數(shù)。
  • 在 Webpack 的下一個大版本中褒墨,開發(fā)者打算加入一個叫 tree-shaking 的東西炫刷,tree-shaking 會排除掉引入模塊中沒有用到的部分。所以那也是一種辦法郁妈。
  • 這樣寫是為了舉一個例子浑玛,好讓你理解我之前提到的要點。

(注:Lodash 這兩種加載方式都可以用噩咪,因為它的開發(fā)者明確規(guī)定可以這么做顾彰,而不是所有的庫都可以通過這種加載方式工作。)

無論如何胃碾,ES2015 代碼現(xiàn)已在手涨享,我們要把它轉(zhuǎn)化成 ES5 代碼,這樣它們就能在老式瀏覽器(事實上仆百,在新版瀏覽器里 ES2015 的支持度還不錯)里跑起來了厕隧。因此,我們需要 Babel 及在 Webpack 中運行 Babel 的配套設(shè)施儒旬。至少要有 babel-core(Babel 的核心功能庫)栏账,babel-loader(babel-core 的 Webpack 加載器接口),babel-preset-es2015(里面有 ES2015 到 ES5 的轉(zhuǎn)化規(guī)則栈源,這是 Babel 需要得知的)挡爵。同時我們引進 babel-plugin-transform-runtimebabel-polyfill ,盡管它們實現(xiàn)方式有點不同甚垦,但都用于改變 Babel 添加語法填充(polyfills)和輔助函數(shù)(helper functions)的方式茶鹃。正是因此,它們適應(yīng)于不同種類的項目艰亮。你可能不想把它們倆都引入闭翩,二者擇一即可,但我在這把它倆都引入迄埃,這樣無論你選擇哪個疗韵,都能知道引入的方式。想知道更多的話侄非,請訪問 polyfill 和 runtime transform 的官方文檔吧蕉汪。

不管怎樣流译,先安裝它們:npm i -D babel-core babel-loader babel-preset-es2015 babel-plugin-transform-runtime babel-polyfill。再為它們配置 Webpack者疤。首先福澡,添加一個部分用于增添加載器。更新 webpack.config.js 如下:

module.exports = {
    entry: './src/main.js',
    output: {
        path: './dist',
        filename: 'bundle.js'
    },
    module: {
        rules: [
            …
        ]
    }
};

我們增加了一個 module 屬性驹马,其中包含了 rules 屬性革砸。rules 是一個數(shù)組,這個數(shù)組囊括每個加載器的配置糯累。我們將把 babel-loader 相關(guān)配置加到這里算利。對于每一個加載器,我們都要配置至少兩個參數(shù):testloader寇蚊。test 通常是一個正則表達式笔时,它用以驗證(test)每個文件的絕對路徑。我們一般只驗證文件后綴仗岸,例如:/\.js$/驗證所有以 .js 結(jié)尾的文件允耿。在這里,我們把這個參數(shù)設(shè)為 /\.jsx?$/ 這樣可以匹配到 .js 文件和 .jsx 文件扒怖,以便使用 React较锡。接下來配置 loader 參數(shù),它描述了在相應(yīng)的 test 參數(shù)下盗痒,應(yīng)該使用哪一個加載器處理文件蚂蕴。

將加載器的名字所拼成的字符串傳入該參數(shù)即可奏效,其中俯邓,名字用感嘆號隔開骡楼,例如 'babel-loader!eslint-loader'eslint-loader 會比 babel-loader 先運行稽鞭,因為 Webpack 的讀取順序是從右到左鸟整。如果某個加載器有特殊參數(shù)配置,你可以使用 query string 語法朦蕴。比如篮条,要給 Babel 配置一個 fakeoption 參數(shù)為 true,我們得把前面的例子改為 'babel-loader?fakeoption=true!eslint-loader'吩抓。如果你覺得更易閱讀和維護的話涉茧,也可以使用 use 替代 loader 配置,這樣可以傳入一個數(shù)組替代此前的字符串疹娶。把之前的例子改為:use: ['babel-loader?fakeoption=true', 'eslint-loader']伴栓,更有甚者,你可以把它們寫成多行以提高可讀性。

目前我們只用 Babel loader 钳垮,所以我們的配置文件看起來像下面這個樣子:

…
rules: [
    { test: /\.jsx?$/, loader: 'babel-loader' }
]
…

如果只用一個加載器除师,我們還可以這樣配置來替代 query string 的寫法:使用 options 配置對象,它就是一個鍵值對 map扔枫。因此,對于 fakeoption 的例子锹安,我們的配置文件可以寫成這樣:

…
rules: [
    {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        options: {
            fakeoption: true
        }
    }
]
…

用上面這種方式來配置我們的 Babel 加載器:

…
rules: [
    {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        options: {
            plugins: ['transform-runtime'],
            presets: ['es2015']
        }
    }
]
…

預(yù)設(shè)(presets)用于把 ES2015 特性轉(zhuǎn)成 ES5短荐,我們也給 Babel 設(shè)置了已經(jīng)安裝的 transform-runtime 插件。如此前所言叹哭,該插件并非必要忍宋,這里是為了演示。我們也可以另建 .babelrc 文件獨立配置這些參數(shù)风罩,但那樣不利于演示 Webpack糠排。一般我推薦使用 .babelrc 文件,但在這里我們還是保持不變超升。

萬事俱備入宦,只欠東風(fēng)。我們需要告知 Babel 跳過處理 node_modules 中的文件室琢,這樣可以提高我們的構(gòu)建速度乾闰。添置 exclude 屬性以告知加載器忽略目標(biāo)目錄下的文件,它的值是一個正則表達式盈滴,因此我們這樣寫:/node_modules/涯肩。

…
rules: [
    {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
            plugins: ['transform-runtime'],
            presets: ['es2015']
        }
    }
]
…

此外,我們本應(yīng)使用 include 屬性來描述我們僅讀 src 目錄巢钓,但我覺得應(yīng)該保持原樣病苗。于是,你應(yīng)該可以再次執(zhí)行 npm start 命令症汹,然后獲取為瀏覽器準(zhǔn)備的 ES5 代碼了硫朦。若想使用 polyfill 替代 transform-runtime 插件,你需要做一兩處改動烈菌。首先刪除 plugins: ['transform-runtime], 這行(如果不打算再用了阵幸,你也可以直接用 npm 卸載該插件)。接下來芽世,編輯 Webpack 配置文件的 entry 部分如下:

entry: [
    'babel-polyfill',
    './src/main.js'
],

我們把描述單一入口的字符串替換成了描述多入口的數(shù)組挚赊,新添的入口乃 語法填充(polyfill)。我們將其置于首位济瓢,這樣語法填充將會率先出現(xiàn)在打包后的文件里荠割,因為我們在代碼里使用語法填充前,要確保它們已經(jīng)存在。

除了借助 Webpack 配置文件蔑鹦,我們本可以通過在 src/main.js 的首行加上 import 'babel-polyfill; 來達到相同的目的夺克。而我們卻使用了配置文件,除了用于服務(wù)本例嚎朽,更是為了用作一個演示多入口打包至單一文件的范例铺纽。好吧,那便是倉庫里的 example3 分支哟忍。容我再說一遍狡门,你可以運行 npm start 命令來確認項目正常運行。

另一個例子:Handlebars 加載器

我們再為項目添置一個加載器:Handlebars锅很。Handlebars 加載器用以將 Handlebars 模版編譯成函數(shù)其馏,當(dāng)你在 JS 中引入(import)一個 Handlebars 文件時,該文件編譯成的函數(shù)就會被引入 JS 文件爆安。這便是我喜歡 Webpack 加載器的地方:即便引入非 JS 文件叛复,該文件也會在打包時被轉(zhuǎn)化為 JS 里可用的東西。接下來的例子將會使用另一個加載器:允許引入圖片文件并將圖片文件轉(zhuǎn)化成 base64 編碼的 URL 字符串扔仓,該字符串可被用于在 JS 中為頁面添加內(nèi)聯(lián)圖片褐奥。這也意味著,如果你串聯(lián)多個加載器翘簇,其中一個甚至能優(yōu)化把圖片的文件大小抖僵。

同樣,我們首先安裝這個加載器:執(zhí)行 npm install -D handlebars-loader 命令缘揪。當(dāng)你用的時候會發(fā)現(xiàn) Handlebars 本身也是不可或缺的:執(zhí)行 npm install -D handlebars 命令耍群。這樣你就可以在不更新加載器版本的情況下控制 Handlebars 的版本,它們可以分別獨立迭代找筝。

二者現(xiàn)已安裝完畢蹈垢,我們弄一個 Handlebars 模板來用。在 src 目錄下創(chuàng)建一個 numberlist.hbs 文件袖裕,其內(nèi)容如下:

<ul>
  {{#each numbers as |number i|}}
    <li>{{number}}</li>
  {{/each}}
</ul>

該模板描繪了一個數(shù)組(變量名為 numbers 曹抬,也可以是別的變量名),創(chuàng)建了一個無序列表急鳄。

接下來谤民,我們調(diào)整此前的 JS 文件來使用模板輸出一個列表,不再止步于打印數(shù)組本身疾宏。main.js 看起來會像下面一樣:

import { map } from 'lodash';
import template from './numberlist.hbs';

let numbers = map([1,2,3,4,5,6], n => n*n);

console.log(template({numbers}));

可惜目前為止 Webpack 并不知道如何引入 numberlist.hbs 张足,因為它并非 JS 文件。我們可以在 import 的路徑前加點東西通知 Webpack 要使用 Handlebars 加載器:

import { map } from 'lodash';
import template from 'handlebars-loader!./numberlist.hbs';

let numbers = map([1,2,3,4,5,6], n => n*n);

console.log(template({numbers}));

通過給路徑增添加載器名字坎藐,并將名字和路徑以感嘆號隔開的前綴为牍,我們告知 Webpack 那個文件應(yīng)該使用那個加載器哼绑。這樣,我們不必在配置文件里添置任何東西碉咆。然而抖韩,在頗有規(guī)模的項目里,你極有可能加載不止一個模板疫铜,所以茂浮,在配置文件里告知 Webpack 我們使用 Handlebars ,以免去引入模板時在路徑前添加前綴壳咕,這樣做會更有意義励稳。那我們就更新一下配置文件:

…
rules: [
    {/* babel loader config… */},
    { test: /\.hbs$/, loader: 'handlebars-loader' }
]
…

這部分相當(dāng)簡單。我們所需要做的就是指定用 handlebars-loader 去處理以 .hbs 結(jié)尾的文件囱井,僅此而已。我們搞定了 Handlebars 同時也搞定了 example4 分支∪け埽現(xiàn)在庞呕,一旦運行 npm start ,你會看到 Webpack 打包輸出如下內(nèi)容:

<ul>
<li>1</li>
<li>4</li>
<li>9</li>
<li>16</li>
<li>25</li>
<li>36</li>
</ul>

Webpack 插件

插件是另一種用來自定義 Webpack 功能的方式程帕。你可以更自由地把它們添加到 Webpack 工作流(workflow)中住练,因為,除加載特殊文件類型之外愁拭,它們幾乎不受限制讲逛。它們可被植入到任何地方,正因如此岭埠,他們更加強勁盏混。我很難定義 Webpack 插件到底能做多少事情,因此我僅給出一個 npm 上的搜索結(jié)果列表 npm packages that have “webpack-plugin”惜论,那應(yīng)該不失為一個好的答案许赃。

本教程中我們只接觸兩個插件(其中一個馬上揭曉)。行文已至此你也知道我的風(fēng)格馆类,過多的例子我們就不需要了混聊。我們首先上 HTML Webpack Plugin ,它的作用很純粹:生成 HTML 文件 —— 終于可以開始進軍瀏覽器了乾巧!

在使用該插件之前句喜,我們首先更新 npm 腳本來運行一個能夠測試示例應(yīng)用的簡單服務(wù)器。先安裝一個服務(wù)器:運行 npm i -D http-server 命令沟于。接著咳胃,仿照下面的代碼將此前的 execute 腳本改成 server 腳本。

…
"scripts": {
  "prebuild": "del-cli dist -f",
  "build": "webpack",
  "server": "http-server ./dist",
  "start": "npm run build -s && npm run server -s"
},
…

Webpack 完成構(gòu)建后旷太,npm start 會同時啟動一個 web 服務(wù)器拙绊,將瀏覽器跳轉(zhuǎn)到 localhost:8080 可以訪問到你的頁面。自然,我們?nèi)匀恍枰坎寮韯?chuàng)建該頁面标沪,所以接下來榄攀,我們需要安裝插件:npm i -D html-webpack-plugin

安裝完畢以后金句,我們移步 webpack.config.js 并作如下修改:

var HtmlwebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: [
        'babel-polyfill',
        './src/main.js'
    ],
    output: {
        path: './dist',
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/,
                options: { plugins: ['transform-runtime'], presets: ['es2015'] }
            },
            { test: /\.hbs$/, loader: 'handlebars-loader' }
        ]
    },
    plugins: [
        new HtmlwebpackPlugin()
    ]
};

我們有作兩處改動:其一在文件頂部引入新安裝的插件檩赢,其二在配置對象尾部添置了一個 plugins 部分,并在此處傳入了插件的實例對象违寞。

目前我們并沒有為該插件實例傳入配置對象贞瞒,默認使用它的基礎(chǔ)模板,除了我們打包好的腳本文件以外趁曼,該基礎(chǔ)模版并沒有包含很多東西军浆。在運行 npm start 后在瀏覽器訪問相應(yīng) URL ,你會看到一空白頁挡闰,但若在開發(fā)者工具中打開控制臺乒融,應(yīng)該會看到里面打印出了 HTML。

我們可能要獲得模板并將 HTML 吐(spit out)到頁面上而不是控制臺里摄悯,這樣一個“正常人”就能真正從頁面上得到信息了赞季。我們先在 src 目錄下創(chuàng)建 index.html 文件,這樣就能定義自己的模板了奢驯。默認情況下申钩,該插件用的是 EJS 模板語法,不過瘪阁,你也可以配置該插件使其使用其它受到支持的模板語言撒遣。在這里我們就用 EJS 因為用什么語法都沒有實質(zhì)區(qū)別,index.html 的內(nèi)容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
    <h2>This is my Index.html Template</h2>
    <div id="app-container"></div>
</body>
</html>

請注意幾點:

  • 我們將為插件傳入一個配置對象來定義標(biāo)題(僅僅因為我們能做到)管跺。
  • 沒有具體指定該在哪里插入我們的腳本文件愉舔,因為該插件默認會在 body 元素結(jié)尾前添加腳本。
  • 這里 div 的 id 并非特定伙菜,我們在這里隨便取了一個轩缤。

現(xiàn)在我們得到了想要的模板,最終不會只是一個空白頁了贩绕。接下來更新 main.js 火的,把 HTML 結(jié)構(gòu)加入那個 div 里以替代此前打印在控制臺里。為此淑倾,我們僅需更新 main.js 的最后一行:document.getElementById("app-container").innerHTML = template({numbers});

同時馏鹤,我們也需要更新 Webpack 配置文件,為插件傳入兩個參數(shù)娇哆。配置文件現(xiàn)在應(yīng)改成這樣:

var HtmlwebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: [
        'babel-polyfill',
        './src/main.js'
    ],
    output: {
        path: './dist',
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/,
                options: { plugins: ['transform-runtime'], presets: ['es2015'] }
            },
            { test: /\.hbs$/, loader: 'handlebars-loader' }
        ]
    },
    plugins: [
        new HtmlwebpackPlugin({
            title: 'Intro to webpack',
            template: 'src/index.html'
        })
    ]
};

template 配置指定了模板文件的位置湃累,title 配置被傳入了模板〔龋現(xiàn)在,運行 npm start治力,你將會在瀏覽器里看到下面的內(nèi)容:

假如你一直跟著做的話蒙秒,example5 分支便在此結(jié)束。不同插件傳入的參數(shù)或者配置項也大異其趣宵统,其原因在于插件種類繁多且涵蓋范圍廣闊晕讲,但殊途同歸的是,他們最終都會被添加到 webpack.config.jsplugins 數(shù)組中马澈。同樣瓢省,也有其他方式可以處理 HTML 頁面的生成和文件名填充,一旦你開始為打包后的文件添加清緩存哈希值(cache-busting hashes)后綴痊班,這些事情就會變得非常簡單勤婚。

觀察示例倉庫,你會發(fā)現(xiàn)有一個 example6 分支涤伐,在該分支里我通過添加插件實現(xiàn)了 JS 代碼壓縮馒胆,但這不是必須的,除非你想改動 UglifyJS 配置废亭。如果你不爽 UglifyJS 的默認配置,可將倉庫切換 (check out)至該分支下(只需要查看 webpack.config.js )去找到如何使用該插件并加以配置具钥。但如果默認配置正合你意豆村,你只需要在命令行運行 webpack 時傳入 -p 參數(shù)。該參數(shù)是 production 的簡寫骂删,與使用 --optimize-minimize--optimize-occurence-order 參數(shù)的效果一樣掌动,前者用以壓縮 JS 代碼,后者用以優(yōu)化已引入模塊的順序宁玫,著眼于稍小的文件尺寸和稍快的執(zhí)行速度粗恢。在示例倉庫完成一段時間后我才知道 -p 這個參數(shù),所以我決定保存該插件示例欧瘪,可以用來提醒你還有更簡單的方法(除了添加插件之外)眷射。另一可供使用的快捷命令參數(shù)是 -d-d 會展示更多 Webpack 打印出的信息佛掖,并且可不借助其他參數(shù)生成資料圖(source map)妖碉。還有很多其他命令行快捷參數(shù)可供使用。

懶加載數(shù)據(jù)塊

懶加載(lazy-loading)模塊是我在 RequireJS 中用得舒適但在 Browserify 中難以工作的模塊芥被。一個頗具規(guī)模的 JS 文件固然可以從減少網(wǎng)絡(luò)請求中受益欧宜,但也幾乎坐實了在一次會話中,某些用戶不必用到的代碼會被下載下來拴魄。

Webpack 可以將打包文件拆分成可被懶加載的若干塊(chunks)冗茸,而且還不需要任何配置席镀。你僅需要從兩種書寫方式中挑一種來書寫代碼,剩下的則交給 Webpack夏漱。這兩種方式其一基于 CommonJS 豪诲,其二則基于 AMD。如果使用前者懶加載麻蹋,需要這樣寫:

require.ensure(["module-a", "module-b"], function(require) {
    var a = require("module-a");
    var b = require("module-b");
    // …
});

require.ensure 需要確保模塊是可用的(但并非運行模塊)跛溉,然后傳入一個由模塊名構(gòu)成的數(shù)組,接著傳入一個回調(diào)函數(shù)(callback)扮授。真正想要在回調(diào)函數(shù)里使用模塊芳室,你需要顯式 require 數(shù)組里傳入的相應(yīng)模塊。

私以為這種方式相麻煩刹勃,所以堪侯,我們來看 AMD 的寫法。

require(["module-a", "module-b"], function(a, b) {
    // …
});

AMD 模式下荔仁,使用 require 函數(shù)伍宦,傳入包含依賴模塊名的數(shù)組,接著再傳入回調(diào)函數(shù)乏梁。該回調(diào)函數(shù)的參數(shù)就是依賴模塊的引用次洼,它們的排列順序與依賴模塊在數(shù)組中的排列順序相同。

Webpack 2 同時也支持 System.import遇骑,其借助于 promises 而非回調(diào)函數(shù)卖毁。盡管將回調(diào)內(nèi)容包裹在 promise 下并非難事,但我仍以為該提升非常有用落萎。不過需要注意的是亥啦, System.import 現(xiàn)已過時,較新的規(guī)范推薦使用 import()练链。不過翔脱,這里告誡一下, Babel (以及 TypeScript)會在你使用System.import的時候拋出語法異常媒鼓。你可以借助于 babel-plugin-dynamic-import-webpack 插件届吁,但該插件將會將其轉(zhuǎn)化為 require.ensure,而不是讓 Babel 合法處理新 import 或者任之由 Webpack 處置绿鸣。我認為 AMD 或 require.ensure 在很久之后才會被棄置瓷产,且 Webpack 直到第三個版本才會支持 System.import ,那還遠著呢枚驻,所以用你順眼的那個就好了濒旦。

擴充我們的代碼,令其停滯兩秒再登,然后再將 Handlebars 模板懶加載進來并輸出到屏幕上尔邓。為此晾剖,我們移除頂部 import 模板的語句,然后將最后一行包裹到 setTimeout 和 AMD 模式的 require 中引入模板梯嗽。

運行 npm start 齿尽,你會發(fā)現(xiàn)生成了另外一個名為 1.bundle.js 的資源文件(asset)。在瀏覽器打開該頁面灯节,然后在開發(fā)者工具中監(jiān)聽網(wǎng)絡(luò)流量循头,2秒之后你會發(fā)現(xiàn)新的資源文件最終被加載并且運行了。以上這些實現(xiàn)起來并不困難炎疆,但提升用戶體驗可不止一點卡骂。

注意,這些二級打包文件(sub-bundles)或曰數(shù)據(jù)塊(chunks)形入,內(nèi)部囊括了他們的所有依賴模塊(dependencies)全跨,但不包含其主數(shù)據(jù)塊(parent chunks)已引入的依賴模塊。(你可以有多個入口文件亿遂,每個都懶加載一個數(shù)據(jù)塊浓若,因此該數(shù)據(jù)塊在其主數(shù)據(jù)塊中加載的依賴模塊也會不同。)

創(chuàng)建公共庫數(shù)據(jù)塊 (Vendor Chunk)

我們再說一個優(yōu)化的點:公共庫數(shù)據(jù)塊蛇数。你可以定義一個單獨用以打包的 bundle挪钓,該 bundle 中存放不常改動的 “common” 庫或第三方代碼。該策略可使用戶獨立緩存你的公共庫文件耳舅,以區(qū)別于業(yè)務(wù)代碼碌上,以便在你迭代應(yīng)用時讓用戶無需重新下載該庫文件。

為此挽放,我們使用 Webpack 官方插件:CommonsChunkPlugin绍赛。它已附帶在 Webpack 中蔓纠,所以我們無需安裝辑畦。僅對 webpack.config.js 稍作修改即可:

var HtmlwebpackPlugin = require('html-webpack-plugin');
var UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');

module.exports = {
    entry: {
        vendor: ['babel-polyfill', 'lodash'],
        main: './src/main.js'
    },
    output: {
        path: './dist',
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/,
                options: { plugins: ['transform-runtime'], presets: ['es2015'] }
            },
            { test: /\.hbs$/, loader: 'handlebars-loader' }
        ]
    },
    plugins: [
        new HtmlwebpackPlugin({
            title: 'Intro to webpack',
            template: 'src/index.html'
        }),
        new UglifyJsPlugin({
            beautify: false,
            mangle: { screw_ie8 : true },
            compress: { screw_ie8: true, warnings: false },
            comments: false
        }),
        new CommonsChunkPlugin({
            name: "vendor",
            filename: "vendor.bundle.js"
        })
    ]
};

我們在第三行引入該插件。此后腿倚,在 entry 部分修改配置纯出,將其換成了一個對象字面量(literal),用以指定多入口敷燎。vendor 入口記錄了會在公共庫數(shù)據(jù)塊中——這里包含了 polyfill 和 Lodash ——被引入的庫并將我們的主要入口放置在 main 入口里暂筝。接著,我們僅需將 CommonsChunkPlugin 添加到 plugins 部分硬贯,指定 “vendor” 數(shù)據(jù)塊作為該插件生成數(shù)據(jù)塊的索引焕襟,同時指定 vendor.bundle.js 文件用以存放公共庫代碼(譯者注:這里插件配置中的 name: "vendor" 對應(yīng) entry 中的 vendor 入口,入口數(shù)組中指定的依賴模塊即最終存放于 vendor.bundle.js 文件中的依賴模塊)饭豹。

通過指定 “vendor” 數(shù)據(jù)塊鸵赖,該插件將拉取此數(shù)據(jù)塊所有的依賴模塊燎字,并將其存放于公共庫數(shù)據(jù)塊內(nèi)扼脐,這些依賴模塊在一個單獨入口文件里被指定。如果不在入口對象字面量中指定數(shù)據(jù)塊名,插件會基于多入口文件之間公用的依賴模塊來生成獨立文件惋戏。

運行 Webpack ,你將看到3份 JS 文件:bundle.js, 1.bundle.jsvendor.bundle.js混萝。如果愿意的話也可以運行 npm start 命令來在瀏覽器中查看結(jié)果拜秧。看起來 Webpack 甚至?xí)炎陨砑虞d不同模塊的主要代碼放進公共庫數(shù)據(jù)塊老赤,此舉極為實用轮洋。

至此我們結(jié)束了 example8 分支之旅,同時本篇教程也接近尾聲诗越。我所談頗多砖瞧,但僅讓你對 Webpack 的能力淺嘗輒止。Webpack 實現(xiàn)了更簡便的 CSS module嚷狞、清緩存块促、圖片優(yōu)化等等很多事情——多到即便書巨著一本,我也無法說窮道盡床未,且在我成書之前竭翠,大多數(shù)已寫的內(nèi)容也將被更新替代。So薇搁,嘗試一下 Webpack 吧斋扰,且告訴我它有沒有提升工作流。祝吾主保佑啃洋,編程愉快传货!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市宏娄,隨后出現(xiàn)的幾起案子问裕,更是在濱河造成了極大的恐慌,老刑警劉巖孵坚,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粮宛,死亡現(xiàn)場離奇詭異,居然都是意外死亡卖宠,警方通過查閱死者的電腦和手機巍杈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扛伍,“玉大人筷畦,你說我怎么就攤上這事〈倘鳎” “怎么了鳖宾?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵亚斋,是天一觀的道長。 經(jīng)常有香客問我攘滩,道長帅刊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任漂问,我火速辦了婚禮赖瞒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蚤假。我一直安慰自己栏饮,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布磷仰。 她就那樣靜靜地躺著袍嬉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪灶平。 梳的紋絲不亂的頭發(fā)上伺通,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音逢享,去河邊找鬼罐监。 笑死,一個胖子當(dāng)著我的面吹牛瞒爬,可吹牛的內(nèi)容都是我干的弓柱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼侧但,長吁一口氣:“原來是場噩夢啊……” “哼矢空!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起禀横,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤屁药,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后燕侠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體者祖,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡立莉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年绢彤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜓耻。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡茫舶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出刹淌,到底是詐尸還是另有隱情饶氏,我是刑警寧澤讥耗,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站疹启,受9級特大地震影響古程,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜喊崖,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一挣磨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荤懂,春花似錦茁裙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至廊宪,卻和暖如春矾瘾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背箭启。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工霜威, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人册烈。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓戈泼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親赏僧。 傳聞我的和親對象是個殘疾皇子大猛,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容