摘要:最后,我們?cè)诳刂婆_(tái)中打印這個(gè)新數(shù)組。也可以借助簡(jiǎn)單的將其跑在瀏覽器上,之后可在控制臺(tái)中看到同樣的運(yùn)行結(jié)果。使用配置文件雖然會(huì)更占位置,但與此同時(shí)增加了可讀性,因?yàn)樗怯蓪?xiě)成的。例如,規(guī)定后綴的文件要先通過(guò)檢查,再通過(guò)把語(yǔ)法轉(zhuǎn)換為語(yǔ)法。
譯者:小 boy (滬江前端開(kāi)發(fā)工程師)
本文原創(chuàng),轉(zhuǎn)載請(qǐng)注明作者及出處。
原文地址:https://www.smashingmagazine....
JavaSript 模塊化打包已混跡江湖許久。2009年,RequireJS 就提交了它的第一個(gè)版本,Browserify 接踵而至,隨后其他打包工具也開(kāi)始大行其道。最終,Webpack 從其中脫穎而出。如果你對(duì)它不甚了解,希望我的文章能讓你上手這件強(qiáng)力打包工具。
什么是模塊化打包工具?在大多數(shù)語(yǔ)言(JS 的最新版本 ECMAScript 2015+ 也支持,但并非支持所有瀏覽器)中,你可以將代碼拆分至多個(gè)文件,并且通過(guò)在業(yè)務(wù)代碼中引用這些文件來(lái)使用它們包含的方法??上У氖菫g覽器并不擁有這個(gè)能力。因此,模塊化打包工具應(yīng)運(yùn)而生,它以?xún)煞N形式為瀏覽器提供這個(gè)能力:1.異步加載模塊,并且在加載結(jié)束后運(yùn)行它們。2.將需要用到的文件拼湊成單一 JS 文件,最終在 HTML 中使用 標(biāo)簽加載該 JS 文件。
如果沒(méi)有模塊化加載及打包工具的話(huà),你就得手動(dòng)拼湊文件或者在 HTML 中加載無(wú)數(shù)的 標(biāo)簽了,而且這樣干有一些不好的地方:
你需要關(guān)心文件的加載順序,包括哪些文件依賴(lài)于其他文件;還需要確認(rèn)是否引入了不需要的文件。
多個(gè) 標(biāo)簽意味著用多個(gè)網(wǎng)絡(luò)請(qǐng)求來(lái)加載代碼,同時(shí)也意味著更差的性能。
顯然,其中有很多本可以交給計(jì)算機(jī)完成的手工活。
大多數(shù)模塊化打包工具直接跟 npm 或者 Bower(譯者注:兩者都是包管理工具)整合,這樣可以讓你更容易地在業(yè)務(wù)代碼中添加第三方依賴(lài)包(dependencies)。你僅需要安裝一下,然后寫(xiě)一行代碼引入它們,接著運(yùn)行模塊化打包工具,這樣就已經(jīng)將第三方代碼整合進(jìn)自己的業(yè)務(wù)代碼了。或者,如若配置正確,你可以將所有要用的三方代碼整合進(jìn)一個(gè)分開(kāi)的文件,這樣一來(lái),當(dāng)你更新業(yè)務(wù)代碼,用戶(hù)需要更新緩存的時(shí)候,他們就無(wú)需重新下載公共庫(kù)代碼(vendor code)了。
為什么選擇 Webpack ?至此,你已對(duì) Webpack 的愿景有了基礎(chǔ)的認(rèn)知,然而為什么在各路豪杰中選擇 Webpack 呢?在我看來(lái)有這樣一些理由:
小鮮肉的特性助了它一臂之力,借此特點(diǎn)它可以繞開(kāi)或者避免前輩們遇到的問(wèn)題。
上手簡(jiǎn)單。如果只是想打包一些JS文件,而沒(méi)有其他需求的話(huà),你甚至都不需要一份配置文件。
它的插件體系使其能做更多事情,從而十分強(qiáng)大,所以它可能是你需要的唯一構(gòu)建工具。
據(jù)我所知,少有其他的模塊打包和構(gòu)建工具也能做到這些。但 Webpack 仍勝一籌:當(dāng)你踩坑的時(shí)候有龐大的社區(qū)支持。
Browserify 的社區(qū)可能只是大,如果它不大的話(huà),就會(huì)缺少一些 Webpack 的潛在必要特性。說(shuō)了這么多 Webpack 的優(yōu)點(diǎn),估計(jì)你就等上代碼了吧?那么我們開(kāi)始。
在使用 Webpack 前,我們首先要先把它安裝好。為此我們需要 Node.js 和 npm ,我就假設(shè)你已經(jīng)安裝過(guò)它們了,實(shí)在沒(méi)有的話(huà),請(qǐng)從Node.js 官網(wǎng)開(kāi)始吧。
有兩種方式安裝 webpack (或著是其他 CLI 包):全局安裝(globally)或者本地安裝(locally)。對(duì)于全局安裝,雖然你可以在任意目錄下使用它,但是它不會(huì)包括在項(xiàng)目的依賴(lài)模塊列表(dependencies)中。此外,你也不能在兩個(gè)不同的項(xiàng)目(有些項(xiàng)目可能需要投入更多工作量才能更新到最新版本,所以這些項(xiàng)目還需要維持老版本)中切換不同版本的 Webpack 。所以我更愿意本地安裝 CLI 包,并且用相對(duì)路徑抑或是 npm 腳本來(lái)運(yùn)行它。如果你不習(xí)慣本地安裝 CLI 包,可以看一下我之前寫(xiě)的關(guān)于擺脫全局安裝 npm 包的博文。
不管怎樣,在示例項(xiàng)目中,我們就使用 npm 腳本。接下來(lái),先本地安裝示例項(xiàng)目。首先:創(chuàng)建一個(gè)用來(lái)實(shí)驗(yàn)和學(xué)習(xí) Webpack 的目錄。 我在 GitHub 上有一個(gè)倉(cāng)庫(kù),你可以將它 clone 到本地,然后在分支間切換來(lái)進(jìn)行下面的學(xué)習(xí),或者從零開(kāi)始創(chuàng)建一個(gè)新項(xiàng)目,此后可以與我的倉(cāng)庫(kù)代碼進(jìn)行對(duì)照。
經(jīng)過(guò)命令行選擇,一進(jìn)到項(xiàng)目目錄,你將用 npm init 命令來(lái)初始化項(xiàng)目。接下來(lái)要填的信息一點(diǎn)都不重要(譯者注:一路回車(chē)即可),除非你想把項(xiàng)目發(fā)布到 npm 上。
至此 package.json 文件準(zhǔn)備就緒(它是通過(guò) npm init 命令創(chuàng)建的),在此文件中,你可以保存依賴(lài)包信息。我們通過(guò) npm install webpack -D (-D 是 --save-dev 命令的簡(jiǎn)寫(xiě),它的作用是將 npm 包作為開(kāi)發(fā)環(huán)境的依賴(lài)包安裝,并將依賴(lài)信息保存到 package.json 文件中)命令將 Webpack 作為依賴(lài)包安裝。
我們需要一個(gè)簡(jiǎn)單的應(yīng)用來(lái)開(kāi)啟運(yùn)用 Webpack 之旅。所謂的簡(jiǎn)單就是:首先執(zhí)行 npm install lodash -S(-S == --save) 安裝 Lodash,如此一來(lái)我們的簡(jiǎn)單應(yīng)用就有一個(gè)依賴(lài)包可以用來(lái)加載了。接著我們創(chuàng)建一個(gè) 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));
很簡(jiǎn)單對(duì)吧?我們僅僅創(chuàng)建了一個(gè)包含整數(shù)1至6的小數(shù)組,然后用 Loadash 庫(kù)中的 map 函數(shù)創(chuàng)建了一個(gè)新數(shù)組,這個(gè)新數(shù)組中的數(shù)字是原數(shù)組中數(shù)字的平方。最后,我們?cè)诳刂婆_(tái)中打印這個(gè)新數(shù)組。運(yùn)行命令 node src/main.js 就能看到結(jié)果:[1, 4, 9, 16, 25, 36]。你瞧,其實(shí) Node.js 都能運(yùn)行這個(gè)文件。
但如果我們想打包這個(gè)小腳本,其中還包括我們能跑在瀏覽器的 Lodash 代碼,使用 Webpack 應(yīng)該從哪入手?如何做到?
Webpack 命令行若不想在配置文件上浪費(fèi)時(shí)間,使用 Webpack 命令行是最容易的上手方式。如果不啟用配置文件的話(huà),最簡(jiǎn)潔的命令需要包含輸入文件(input file)路徑和輸出文件(output file)路徑。Webpack 會(huì)讀取輸入文件,追蹤它的依賴(lài)關(guān)系樹(shù),并將所有依賴(lài)文件打包進(jìn)一個(gè)文件,最終在你指定的輸出路徑下輸出該文件。在本例中,輸入路徑是 src/main.js ,我們要將打包后的文件輸出到 dist/bundle.js 下。為此,我們先添加 npm 腳本(我們并沒(méi)有全局安裝 Webpack ,所以不能直接在命令行中運(yùn)行)。編輯 package.json 文件的 "scripts" 部分如下:
"scripts": { "build": "webpack src/main.js dist/bundle.js", }
現(xiàn)在,執(zhí)行 npm run build 命令,Webpack 就會(huì)運(yùn)行了。很快,運(yùn)行完畢的時(shí)候會(huì)生成 dist/bundle.js 文件。然后你便可以用 Node.js (通過(guò) node dist/bundle.js 命令)運(yùn)行該文件了。也可以借助簡(jiǎn)單的 HTML 將其跑在瀏覽器上,之后可在控制臺(tái)中看到同樣的運(yùn)行結(jié)果。
在繼續(xù)探索 Webpack 前,我們先把構(gòu)建腳本調(diào)整得更專(zhuān)業(yè)一點(diǎn):在重新構(gòu)建(rebuilding)前刪除 dist 目錄及其內(nèi)容,此外,我們?cè)偬砑右恍┯糜谥苯訄?zhí)行 bundle 文件的腳本。首先,安裝 del-cli 工具,這樣就不用在刪除目錄的時(shí)候顧慮操作系統(tǒng)的區(qū)別了(見(jiàn)諒,因?yàn)槲矣玫氖?Windows)。運(yùn)行 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í)行的命令會(huì)在每次 "build" 命令執(zhí)行之前運(yùn)行。同時(shí)增加的還有 "execute" 配置:使用 Node.js 執(zhí)行已經(jīng)打包好的腳本。此外,使用 "start" 配置可以通過(guò)一條命令執(zhí)行以上所有命令(-s 的作用僅僅是不讓 npm 腳本在控制臺(tái)打印一些沒(méi)用的東西)。執(zhí)行 npm start 命令,就可以在控制臺(tái)里看到 Webpack 的輸出信息,緊接著打印的是平方后的數(shù)組。
恭喜!你剛剛完成了 example1 分支里所有的事情。這個(gè)分支就在我之前提到的倉(cāng)庫(kù)中。
Webpack 配置文件跟使用 Webpack 命令行上手一樣有趣的是,一旦開(kāi)始使用更多 Webpack 的功能, 你就會(huì)想要放棄通過(guò)命令行傳遞 Webpack 配置參數(shù),轉(zhuǎn)而投入配置文件的懷抱。使用配置文件雖然會(huì)更占位置,但與此同時(shí)增加了可讀性,因?yàn)樗怯?JS 寫(xiě)成的。
那我們就來(lái)創(chuàng)建配置文件吧。在根目錄下創(chuàng)建一個(gè)新文件 webpack.config.js。Webpack 默認(rèn)尋找該文件,但如果想給配置文件取別的名字或者將配置文件放在其他目錄,你可以通過(guò)傳遞 --config [filename] 參數(shù)來(lái)做到。
在本教程中,我們使用默認(rèn)文件名?,F(xiàn)在,我們?cè)囍屌渲梦募鹱饔?,達(dá)到與僅使用命令行同樣的效果。為此,我們需要在配置文件中添置如下代碼:
module.exports = { entry: "./src/main.js", output: { path: "./dist", filename: "bundle.js" } };
如此前一樣,我們規(guī)定輸入和輸出文件。因?yàn)檫@不是 JSON 文件而是 JS 文件,所以我們需要把配置對(duì)象(configuration object )導(dǎo)出,故使用 module.exports。雖然現(xiàn)在還看不出寫(xiě)這些配置會(huì)比用命令好多少,但文章結(jié)尾你肯定會(huì)愛(ài)上這里的一切。
接下來(lái),移除 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 命令,運(yùn)行結(jié)果是不是似曾相識(shí)呢?以上就是分支 example2 中需要做的事情。
Webpack 加載器(Loaders)我們主要通過(guò)兩種方式增強(qiáng) Webpack: 加載器(loaders)和插件(plugins)。我們先講加載器,插件稍后再議。加載器用以轉(zhuǎn)換或操作特定類(lèi)型的文件,你可以將多個(gè)加載器串聯(lián)在一起來(lái)處理一種類(lèi)型的文件。例如,規(guī)定 .js 后綴的文件要先通過(guò) ESLint 檢查,再通過(guò) Babel 把 ES2015 語(yǔ)法轉(zhuǎn)換為 ES5 語(yǔ)法。ESLint 發(fā)出的警報(bào)將會(huì)在控制臺(tái)打印出來(lái),而遇到語(yǔ)法錯(cuò)誤的時(shí)候則會(huì)阻止 Webpack 繼續(xù)打包。
我們這里就不設(shè)置語(yǔ)法檢查了,但要通過(guò)設(shè)置 Babel 來(lái)把代碼轉(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í)際上這段代碼和之前做的事情一樣,但有兩點(diǎn):其一,使用箭頭函數(shù)替代了之前定義的 square 函數(shù)。其二,使用了 ES2015 中的 import 語(yǔ)法加載 lodash 庫(kù)中的 map 函數(shù),但這將會(huì)把整個(gè) Lodash 庫(kù)的代碼打包到我們的輸出文件中,而不是引入僅僅包含 map 函數(shù)相關(guān)代碼的 "lodash/map" 庫(kù)。如果樂(lè)意的話(huà),你也可以把第一行改成 import map from "lodash/map"; 但我寫(xiě)成這樣有我的理由:
在更具規(guī)模的應(yīng)用里,你可能要用到 Lodash 庫(kù)的很多部分,所以你最好全加載進(jìn)來(lái)。
如果你正在用 Backbone.js 框架,會(huì)發(fā)現(xiàn)僅打包你需要的函數(shù)是很困難的,因?yàn)楦揪蜎](méi)有文檔告訴你函數(shù)依賴(lài)哪些函數(shù)。
在 Webpack 的下一個(gè)大版本中,開(kāi)發(fā)者打算加入一個(gè)叫 tree-shaking 的東西,tree-shaking 會(huì)排除掉引入模塊中沒(méi)有用到的部分。所以那也是一種辦法。
這樣寫(xiě)是為了舉一個(gè)例子,好讓你理解我之前提到的要點(diǎn)。
(注:Lodash 這兩種加載方式都可以用,因?yàn)樗拈_(kāi)發(fā)者明確規(guī)定可以這么做,而不是所有的庫(kù)都可以通過(guò)這種加載方式工作。)
無(wú)論如何,ES2015 代碼現(xiàn)已在手,我們要把它轉(zhuǎn)化成 ES5 代碼,這樣它們就能在老式瀏覽器(事實(shí)上,在新版瀏覽器里 ES2015 的支持度還不錯(cuò))里跑起來(lái)了。因此,我們需要 Babel 及在 Webpack 中運(yùn)行 Babel 的配套設(shè)施。至少要有 babel-core(Babel 的核心功能庫(kù)),babel-loader(babel-core 的 Webpack 加載器接口),babel-preset-es2015(里面有 ES2015 到 ES5 的轉(zhuǎn)化規(guī)則,這是 Babel 需要得知的)。同時(shí)我們引進(jìn) babel-plugin-transform-runtime 和 babel-polyfill ,盡管它們實(shí)現(xiàn)方式有點(diǎn)不同,但都用于改變 Babel 添加語(yǔ)法填充(polyfills)和輔助函數(shù)(helper functions)的方式。正是因此,它們適應(yīng)于不同種類(lèi)的項(xiàng)目。你可能不想把它們倆都引入,二者擇一即可,但我在這把它倆都引入,這樣無(wú)論你選擇哪個(gè),都能知道引入的方式。想知道更多的話(huà),請(qǐng)?jiān)L問(wèn) polyfill 和 runtime transform 的官方文檔吧。
不管怎樣,先安裝它們:npm i -D babel-core babel-loader babel-preset-es2015 babel-plugin-transform-runtime babel-polyfill。再為它們配置 Webpack。首先,添加一個(gè)部分用于增添加載器。更新 webpack.config.js 如下:
module.exports = { entry: "./src/main.js", output: { path: "./dist", filename: "bundle.js" }, module: { rules: [ … ] } };
我們?cè)黾恿艘粋€(gè) module 屬性,其中包含了 rules 屬性。rules 是一個(gè)數(shù)組,這個(gè)數(shù)組囊括每個(gè)加載器的配置。我們將把 babel-loader 相關(guān)配置加到這里。對(duì)于每一個(gè)加載器,我們都要配置至少兩個(gè)參數(shù):test 和 loader。test 通常是一個(gè)正則表達(dá)式,它用以驗(yàn)證(test)每個(gè)文件的絕對(duì)路徑。我們一般只驗(yàn)證文件后綴,例如:/.js$/驗(yàn)證所有以 .js 結(jié)尾的文件。在這里,我們把這個(gè)參數(shù)設(shè)為 /.jsx?$/ 這樣可以匹配到 .js 文件和 .jsx 文件,以便使用 React。接下來(lái)配置 loader 參數(shù),它描述了在相應(yīng)的 test 參數(shù)下,應(yīng)該使用哪一個(gè)加載器處理文件。
將加載器的名字所拼成的字符串傳入該參數(shù)即可奏效,其中,名字用感嘆號(hào)隔開(kāi),例如 "babel-loader!eslint-loader"。eslint-loader 會(huì)比 babel-loader 先運(yùn)行,因?yàn)?Webpack 的讀取順序是從右到左。如果某個(gè)加載器有特殊參數(shù)配置,你可以使用 query string 語(yǔ)法。比如,要給 Babel 配置一個(gè) fakeoption 參數(shù)為 true,我們得把前面的例子改為 "babel-loader?fakeoption=true!eslint-loader"。如果你覺(jué)得更易閱讀和維護(hù)的話(huà),也可以使用 use 替代 loader 配置,這樣可以傳入一個(gè)數(shù)組替代此前的字符串。把之前的例子改為:use: ["babel-loader?fakeoption=true", "eslint-loader"],更有甚者,你可以把它們寫(xiě)成多行以提高可讀性。
目前我們只用 Babel loader ,所以我們的配置文件看起來(lái)像下面這個(gè)樣子:
… rules: [ { test: /.jsx?$/, loader: "babel-loader" } ] …
如果只用一個(gè)加載器,我們還可以這樣配置來(lái)替代 query string 的寫(xiě)法:使用 options 配置對(duì)象,它就是一個(gè)鍵值對(duì) map。因此,對(duì)于 fakeoption 的例子,我們的配置文件可以寫(xiě)成這樣:
… rules: [ { test: /.jsx?$/, loader: "babel-loader", options: { fakeoption: true } } ] …
用上面這種方式來(lái)配置我們的 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 文件獨(dú)立配置這些參數(shù),但那樣不利于演示 Webpack。一般我推薦使用 .babelrc 文件,但在這里我們還是保持不變。
萬(wàn)事俱備,只欠東風(fēng)。我們需要告知 Babel 跳過(guò)處理 node_modules 中的文件,這樣可以提高我們的構(gòu)建速度。添置 exclude 屬性以告知加載器忽略目標(biāo)目錄下的文件,它的值是一個(gè)正則表達(dá)式,因此我們這樣寫(xiě):/node_modules/。
… rules: [ { test: /.jsx?$/, loader: "babel-loader", exclude: /node_modules/, options: { plugins: ["transform-runtime"], presets: ["es2015"] } } ] …
此外,我們本應(yīng)使用 include 屬性來(lái)描述我們僅讀 src 目錄,但我覺(jué)得應(yīng)該保持原樣。于是,你應(yīng)該可以再次執(zhí)行 npm start 命令,然后獲取為瀏覽器準(zhǔn)備的 ES5 代碼了。若想使用 polyfill 替代 transform-runtime 插件,你需要做一兩處改動(dòng)。首先刪除 plugins: ["transform-runtime], 這行(如果不打算再用了,你也可以直接用 npm 卸載該插件)。接下來(lái),編輯 Webpack 配置文件的 entry 部分如下:
entry: [ "babel-polyfill", "./src/main.js" ],
我們把描述單一入口的字符串替換成了描述多入口的數(shù)組,新添的入口乃 語(yǔ)法填充(polyfill)。我們將其置于首位,這樣語(yǔ)法填充將會(huì)率先出現(xiàn)在打包后的文件里,因?yàn)槲覀冊(cè)诖a里使用語(yǔ)法填充前,要確保它們已經(jīng)存在。
除了借助 Webpack 配置文件,我們本可以通過(guò)在 src/main.js 的首行加上 import "babel-polyfill; 來(lái)達(dá)到相同的目的。而我們卻使用了配置文件,除了用于服務(wù)本例,更是為了用作一個(gè)演示多入口打包至單一文件的范例。好吧,那便是倉(cāng)庫(kù)里的 example3 分支。容我再說(shuō)一遍,你可以運(yùn)行 npm start 命令來(lái)確認(rèn)項(xiàng)目正常運(yùn)行。
另一個(gè)例子:Handlebars 加載器我們?cè)贋轫?xiàng)目添置一個(gè)加載器:Handlebars。Handlebars 加載器用以將 Handlebars 模版編譯成函數(shù),當(dāng)你在 JS 中引入(import)一個(gè) Handlebars 文件時(shí),該文件編譯成的函數(shù)就會(huì)被引入 JS 文件。這便是我喜歡 Webpack 加載器的地方:即便引入非 JS 文件,該文件也會(huì)在打包時(shí)被轉(zhuǎn)化為 JS 里可用的東西。接下來(lái)的例子將會(huì)使用另一個(gè)加載器:允許引入圖片文件并將圖片文件轉(zhuǎn)化成 base64 編碼的 URL 字符串,該字符串可被用于在 JS 中為頁(yè)面添加內(nèi)聯(lián)圖片。這也意味著,如果你串聯(lián)多個(gè)加載器,其中一個(gè)甚至能優(yōu)化把圖片的文件大小。
同樣,我們首先安裝這個(gè)加載器:執(zhí)行 npm install -D handlebars-loader 命令。當(dāng)你用的時(shí)候會(huì)發(fā)現(xiàn) Handlebars 本身也是不可或缺的:執(zhí)行 npm install -D handlebars 命令。這樣你就可以在不更新加載器版本的情況下控制 Handlebars 的版本,它們可以分別獨(dú)立迭代。
二者現(xiàn)已安裝完畢,我們弄一個(gè) Handlebars 模板來(lái)用。在 src 目錄下創(chuàng)建一個(gè) numberlist.hbs 文件,其內(nèi)容如下:
該模板描繪了一個(gè)數(shù)組(變量名為 numbers ,也可以是別的變量名),創(chuàng)建了一個(gè)無(wú)序列表。
接下來(lái),我們調(diào)整此前的 JS 文件來(lái)使用模板輸出一個(gè)列表,不再止步于打印數(shù)組本身。main.js 看起來(lái)會(huì)像下面一樣:
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 ,因?yàn)樗⒎?JS 文件。我們可以在 import 的路徑前加點(diǎn)東西通知 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}));
通過(guò)給路徑增添加載器名字,并將名字和路徑以感嘆號(hào)隔開(kāi)的前綴,我們告知 Webpack 那個(gè)文件應(yīng)該使用那個(gè)加載器。這樣,我們不必在配置文件里添置任何東西。然而,在頗有規(guī)模的項(xiàng)目里,你極有可能加載不止一個(gè)模板,所以,在配置文件里告知 Webpack 我們使用 Handlebars ,以免去引入模板時(shí)在路徑前添加前綴,這樣做會(huì)更有意義。那我們就更新一下配置文件:
… rules: [ {/* babel loader config… */}, { test: /.hbs$/, loader: "handlebars-loader" } ] …
這部分相當(dāng)簡(jiǎn)單。我們所需要做的就是指定用 handlebars-loader 去處理以 .hbs 結(jié)尾的文件,僅此而已。我們搞定了 Handlebars 同時(shí)也搞定了 example4 分支。現(xiàn)在,一旦運(yùn)行 npm start ,你會(huì)看到 Webpack 打包輸出如下內(nèi)容:
插件是另一種用來(lái)自定義 Webpack 功能的方式。你可以更自由地把它們添加到 Webpack 工作流(workflow)中,因?yàn)椋虞d特殊文件類(lèi)型之外,它們幾乎不受限制。它們可被植入到任何地方,正因如此,他們更加強(qiáng)勁。我很難定義 Webpack 插件到底能做多少事情,因此我僅給出一個(gè) npm 上的搜索結(jié)果列表 npm packages that have “webpack-plugin”,那應(yīng)該不失為一個(gè)好的答案。
本教程中我們只接觸兩個(gè)插件(其中一個(gè)馬上揭曉)。行文已至此你也知道我的風(fēng)格,過(guò)多的例子我們就不需要了。我們首先上 HTML Webpack Plugin ,它的作用很純粹:生成 HTML 文件 —— 終于可以開(kāi)始進(jìn)軍瀏覽器了!
在使用該插件之前,我們首先更新 npm 腳本來(lái)運(yùn)行一個(gè)能夠測(cè)試示例應(yīng)用的簡(jiǎn)單服務(wù)器。先安裝一個(gè)服務(wù)器:運(yùn)行 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 會(huì)同時(shí)啟動(dòng)一個(gè) web 服務(wù)器,將瀏覽器跳轉(zhuǎn)到 localhost:8080 可以訪(fǎng)問(wèn)到你的頁(yè)面。自然,我們?nèi)匀恍枰坎寮?lái)創(chuàng)建該頁(yè)面,所以接下來(lái),我們需要安裝插件: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() ] };
我們有作兩處改動(dòng):其一在文件頂部引入新安裝的插件,其二在配置對(duì)象尾部添置了一個(gè) plugins 部分,并在此處傳入了插件的實(shí)例對(duì)象。
目前我們并沒(méi)有為該插件實(shí)例傳入配置對(duì)象,默認(rèn)使用它的基礎(chǔ)模板,除了我們打包好的腳本文件以外,該基礎(chǔ)模版并沒(méi)有包含很多東西。在運(yùn)行 npm start 后在瀏覽器訪(fǎng)問(wèn)相應(yīng) URL ,你會(huì)看到一空白頁(yè),但若在開(kāi)發(fā)者工具中打開(kāi)控制臺(tái),應(yīng)該會(huì)看到里面打印出了 HTML。
我們可能要獲得模板并將 HTML 吐(spit out)到頁(yè)面上而不是控制臺(tái)里,這樣一個(gè)“正常人”就能真正從頁(yè)面上得到信息了。我們先在 src 目錄下創(chuàng)建 index.html 文件,這樣就能定義自己的模板了。默認(rèn)情況下,該插件用的是 EJS 模板語(yǔ)法,不過(guò),你也可以配置該插件使其使用其它受到支持的模板語(yǔ)言。在這里我們就用 EJS 因?yàn)橛檬裁凑Z(yǔ)法都沒(méi)有實(shí)質(zhì)區(qū)別,index.html 的內(nèi)容如下:
<%= htmlWebpackPlugin.options.title %> This is my Index.html Template
請(qǐng)注意幾點(diǎn):
我們將為插件傳入一個(gè)配置對(duì)象來(lái)定義標(biāo)題(僅僅因?yàn)槲覀兡茏龅剑?/p>
沒(méi)有具體指定該在哪里插入我們的腳本文件,因?yàn)樵摬寮J(rèn)會(huì)在 body 元素結(jié)尾前添加腳本。
這里 div 的 id 并非特定,我們?cè)谶@里隨便取了一個(gè)。
現(xiàn)在我們得到了想要的模板,最終不會(huì)只是一個(gè)空白頁(yè)了。接下來(lái)更新 main.js ,把 HTML 結(jié)構(gòu)加入那個(gè) div 里以替代此前打印在控制臺(tái)里。為此,我們僅需更新 main.js 的最后一行:document.getElementById("app-container").innerHTML = template({numbers});
同時(shí),我們也需要更新 Webpack 配置文件,為插件傳入兩個(gè)參數(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 配置被傳入了模板?,F(xiàn)在,運(yùn)行 npm start,你將會(huì)在瀏覽器里看到下面的內(nèi)容:
假如你一直跟著做的話(huà),example5 分支便在此結(jié)束。不同插件傳入的參數(shù)或者配置項(xiàng)也大異其趣,其原因在于插件種類(lèi)繁多且涵蓋范圍廣闊,但殊途同歸的是,他們最終都會(huì)被添加到 webpack.config.js 的 plugins 數(shù)組中。同樣,也有其他方式可以處理 HTML 頁(yè)面的生成和文件名填充,一旦你開(kāi)始為打包后的文件添加清緩存哈希值(cache-busting hashes)后綴,這些事情就會(huì)變得非常簡(jiǎn)單。
觀察示例倉(cāng)庫(kù),你會(huì)發(fā)現(xiàn)有一個(gè) example6 分支,在該分支里我通過(guò)添加插件實(shí)現(xiàn)了 JS 代碼壓縮,但這不是必須的,除非你想改動(dòng) UglifyJS 配置。如果你不爽 UglifyJS 的默認(rèn)配置,可將倉(cāng)庫(kù)切換 (check out)至該分支下(只需要查看 webpack.config.js )去找到如何使用該插件并加以配置。但如果默認(rèn)配置正合你意,你只需要在命令行運(yùn)行 webpack 時(shí)傳入 -p 參數(shù)。該參數(shù)是 production 的簡(jiǎn)寫(xiě),與使用 --optimize-minimize 和 --optimize-occurence-order 參數(shù)的效果一樣,前者用以壓縮 JS 代碼,后者用以?xún)?yōu)化已引入模塊的順序,著眼于稍小的文件尺寸和稍快的執(zhí)行速度。在示例倉(cāng)庫(kù)完成一段時(shí)間后我才知道 -p 這個(gè)參數(shù),所以我決定保存該插件示例,可以用來(lái)提醒你還有更簡(jiǎn)單的方法(除了添加插件之外)。另一可供使用的快捷命令參數(shù)是 -d ,-d 會(huì)展示更多 Webpack 打印出的信息,并且可不借助其他參數(shù)生成資料圖(source map)。還有很多其他命令行快捷參數(shù)可供使用。
懶加載數(shù)據(jù)塊懶加載(lazy-loading)模塊是我在 RequireJS 中用得舒適但在 Browserify 中難以工作的模塊。一個(gè)頗具規(guī)模的 JS 文件固然可以從減少網(wǎng)絡(luò)請(qǐng)求中受益,但也幾乎坐實(shí)了在一次會(huì)話(huà)中,某些用戶(hù)不必用到的代碼會(huì)被下載下來(lái)。
Webpack 可以將打包文件拆分成可被懶加載的若干塊(chunks),而且還不需要任何配置。你僅需要從兩種書(shū)寫(xiě)方式中挑一種來(lái)書(shū)寫(xiě)代碼,剩下的則交給 Webpack。這兩種方式其一基于 CommonJS ,其二則基于 AMD。如果使用前者懶加載,需要這樣寫(xiě):
require.ensure(["module-a", "module-b"], function(require) { var a = require("module-a"); var b = require("module-b"); // … });
require.ensure 需要確保模塊是可用的(但并非運(yùn)行模塊),然后傳入一個(gè)由模塊名構(gòu)成的數(shù)組,接著傳入一個(gè)回調(diào)函數(shù)(callback)。真正想要在回調(diào)函數(shù)里使用模塊,你需要顯式 require 數(shù)組里傳入的相應(yīng)模塊。
私以為這種方式相麻煩,所以,我們來(lái)看 AMD 的寫(xiě)法。
require(["module-a", "module-b"], function(a, b) { // … });
AMD 模式下,使用 require 函數(shù),傳入包含依賴(lài)模塊名的數(shù)組,接著再傳入回調(diào)函數(shù)。該回調(diào)函數(shù)的參數(shù)就是依賴(lài)模塊的引用,它們的排列順序與依賴(lài)模塊在數(shù)組中的排列順序相同。
Webpack 2 同時(shí)也支持 System.import,其借助于 promises 而非回調(diào)函數(shù)。盡管將回調(diào)內(nèi)容包裹在 promise 下并非難事,但我仍以為該提升非常有用。不過(guò)需要注意的是, System.import 現(xiàn)已過(guò)時(shí),較新的規(guī)范推薦使用 import()。不過(guò),這里告誡一下, Babel (以及 TypeScript)會(huì)在你使用System.import的時(shí)候拋出語(yǔ)法異常。你可以借助于 babel-plugin-dynamic-import-webpack 插件,但該插件將會(huì)將其轉(zhuǎn)化為 require.ensure,而不是讓 Babel 合法處理新 import 或者任之由 Webpack 處置。我認(rèn)為 AMD 或 require.ensure 在很久之后才會(huì)被棄置,且 Webpack 直到第三個(gè)版本才會(huì)支持 System.import ,那還遠(yuǎn)著呢,所以用你順眼的那個(gè)就好了。
擴(kuò)充我們的代碼,令其停滯兩秒,然后再將 Handlebars 模板懶加載進(jìn)來(lái)并輸出到屏幕上。為此,我們移除頂部 import 模板的語(yǔ)句,然后將最后一行包裹到 setTimeout 和 AMD 模式的 require 中引入模板。
運(yùn)行 npm start ,你會(huì)發(fā)現(xiàn)生成了另外一個(gè)名為 1.bundle.js 的資源文件(asset)。在瀏覽器打開(kāi)該頁(yè)面,然后在開(kāi)發(fā)者工具中監(jiān)聽(tīng)網(wǎng)絡(luò)流量,2秒之后你會(huì)發(fā)現(xiàn)新的資源文件最終被加載并且運(yùn)行了。以上這些實(shí)現(xiàn)起來(lái)并不困難,但提升用戶(hù)體驗(yàn)可不止一點(diǎn)。
注意,這些二級(jí)打包文件(sub-bundles)或曰數(shù)據(jù)塊(chunks),內(nèi)部囊括了他們的所有依賴(lài)模塊(dependencies),但不包含其主數(shù)據(jù)塊(parent chunks)已引入的依賴(lài)模塊。(你可以有多個(gè)入口文件,每個(gè)都懶加載一個(gè)數(shù)據(jù)塊,因此該數(shù)據(jù)塊在其主數(shù)據(jù)塊中加載的依賴(lài)模塊也會(huì)不同。)
創(chuàng)建公共庫(kù)數(shù)據(jù)塊 (Vendor Chunk)我們?cè)僬f(shuō)一個(gè)優(yōu)化的點(diǎn):公共庫(kù)數(shù)據(jù)塊。你可以定義一個(gè)多帶帶用以打包的 bundle,該 bundle 中存放不常改動(dòng)的 “common” 庫(kù)或第三方代碼。該策略可使用戶(hù)獨(dú)立緩存你的公共庫(kù)文件,以區(qū)別于業(yè)務(wù)代碼,以便在你迭代應(yīng)用時(shí)讓用戶(hù)無(wú)需重新下載該庫(kù)文件。
為此,我們使用 Webpack 官方插件:CommonsChunkPlugin。它已附帶在 Webpack 中,所以我們無(wú)需安裝。僅對(duì) 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" }) ] };
我們?cè)诘谌幸朐摬寮?。此后,?entry 部分修改配置,將其換成了一個(gè)對(duì)象字面量(literal),用以指定多入口。vendor 入口記錄了會(huì)在公共庫(kù)數(shù)據(jù)塊中——這里包含了 polyfill 和 Lodash ——被引入的庫(kù)并將我們的主要入口放置在 main 入口里。接著,我們僅需將 CommonsChunkPlugin 添加到 plugins 部分,指定 “vendor” 數(shù)據(jù)塊作為該插件生成數(shù)據(jù)塊的索引,同時(shí)指定 vendor.bundle.js 文件用以存放公共庫(kù)代碼(譯者注:這里插件配置中的 name: "vendor" 對(duì)應(yīng) entry 中的 vendor 入口,入口數(shù)組中指定的依賴(lài)模塊即最終存放于 vendor.bundle.js 文件中的依賴(lài)模塊)。
通過(guò)指定 “vendor” 數(shù)據(jù)塊,該插件將拉取此數(shù)據(jù)塊所有的依賴(lài)模塊,并將其存放于公共庫(kù)數(shù)據(jù)塊內(nèi),這些依賴(lài)模塊在一個(gè)多帶帶入口文件里被指定。如果不在入口對(duì)象字面量中指定數(shù)據(jù)塊名,插件會(huì)基于多入口文件之間公用的依賴(lài)模塊來(lái)生成獨(dú)立文件。
運(yùn)行 Webpack ,你將看到3份 JS 文件:bundle.js, 1.bundle.js 和 vendor.bundle.js。如果愿意的話(huà)也可以運(yùn)行 npm start 命令來(lái)在瀏覽器中查看結(jié)果。看起來(lái) Webpack 甚至?xí)炎陨砑虞d不同模塊的主要代碼放進(jìn)公共庫(kù)數(shù)據(jù)塊,此舉極為實(shí)用。
至此我們結(jié)束了 example8 分支之旅,同時(shí)本篇教程也接近尾聲。我所談?lì)H多,但僅讓你對(duì) Webpack 的能力淺嘗輒止。Webpack 實(shí)現(xiàn)了更簡(jiǎn)便的 CSS module、清緩存、圖片優(yōu)化等等很多事情——多到即便書(shū)巨著一本,我也無(wú)法說(shuō)窮道盡,且在我成書(shū)之前,大多數(shù)已寫(xiě)的內(nèi)容也將被更新替代。So,嘗試一下 Webpack 吧,且告訴我它有沒(méi)有提升工作流。祝吾主保佑,編程愉快!
iKcamp原創(chuàng)新書(shū)《移動(dòng)Web前端高效開(kāi)發(fā)實(shí)戰(zhàn)》已在亞馬遜、京東、當(dāng)當(dāng)開(kāi)售。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/85056.html
摘要:如果遇到非常的復(fù)雜的匹配,正則表達(dá)式的優(yōu)勢(shì)就更加明顯了。關(guān)于正則表達(dá)式書(shū)寫(xiě)規(guī)則,可查看,上面說(shuō)的很清楚了,我就不貼出來(lái)了。替換與正則表達(dá)式匹配的子串,并返回替換后的字符串。結(jié)語(yǔ)正則表達(dá)式并不難,懂了其中的套路之后,一切都變得簡(jiǎn)單了。 前言 在正文開(kāi)始前,先說(shuō)說(shuō)正則表達(dá)式是什么,為什么要用正則表達(dá)式?正則表達(dá)式在我個(gè)人看來(lái)就是一個(gè)瀏覽器可以識(shí)別的規(guī)則,有了這個(gè)規(guī)則,瀏覽器就可以幫我們判斷...
摘要:前端日?qǐng)?bào)精選從化的探討體會(huì)團(tuán)隊(duì)設(shè)計(jì)思想導(dǎo)致的跨站漏洞在餓了么前端的實(shí)踐還是你應(yīng)該選擇哪一個(gè)前端框架上手這篇就夠了中文網(wǎng)格布局入門(mén)上最流行的項(xiàng)目眾成翻譯的入門(mén)教程眾成翻譯開(kāi)發(fā),在中配置眾成翻譯組件間的樣式污染掘金核心模塊之 2017-08-31 前端日?qǐng)?bào) 精選 從 setState promise 化的探討 體會(huì) React 團(tuán)隊(duì)設(shè)計(jì)思想jQuery導(dǎo)致的XSS跨站漏洞Weex 在餓了么...
摘要:哈哈,我理解,架構(gòu)就是骨架,如下圖所示譯年月個(gè)有趣的和庫(kù)前端掘金我們創(chuàng)辦的使命是讓你及時(shí)的了解開(kāi)發(fā)中最新最酷的趨勢(shì)。 翻譯 | 上手 Webpack ? 這篇就夠了! - 掘金譯者:小 boy (滬江前端開(kāi)發(fā)工程師) 本文原創(chuàng),轉(zhuǎn)載請(qǐng)注明作者及出處。 原文地址:https://www.smashingmagazine.... JavaSrip... 讀 Zepto 源碼之代碼結(jié)構(gòu) - ...
摘要:注解在類(lèi)上為類(lèi)提供一個(gè)全參的構(gòu)造方法,加了這個(gè)注解后,類(lèi)中不提供默認(rèn)構(gòu)造方法了。這個(gè)注解用在類(lèi)上,使用類(lèi)中所有帶有注解的或者帶有修飾的成員變量生成對(duì)應(yīng)的構(gòu)造方法。 轉(zhuǎn)載請(qǐng)注明原創(chuàng)地址:http://www.54tianzhisheng.cn/2018/01/07/lombok/ showImg(http://ohfk1r827.bkt.clouddn.com/blog/180107/7...
閱讀 1255·2021-09-01 10:30
閱讀 2133·2021-07-23 10:38
閱讀 907·2019-08-29 15:06
閱讀 3161·2019-08-29 13:53
閱讀 3284·2019-08-26 11:54
閱讀 1837·2019-08-26 11:38
閱讀 2379·2019-08-26 10:29
閱讀 3134·2019-08-23 18:15