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