譯者:小 boy (滬江前端開發工程師)
本文原創,轉載請註明做者及出處。
原文地址:https://www.smashingmagazine....javascript
JavaSript 模塊化打包已混跡江湖許久。2009年,RequireJS 就提交了它的第一個版本,Browserify 接踵而至,隨後其餘打包工具也開始大行其道。最終,Webpack 從其中脫穎而出。若是你對它不甚瞭解,但願個人文章能讓你上手這件強力打包工具。html
在大多數語言(JS 的最新版本 ECMAScript 2015+ 也支持,但並不是支持全部瀏覽器)中,你能夠將代碼拆分至多個文件,而且經過在業務代碼中引用這些文件來使用它們包含的方法。惋惜的是瀏覽器並不擁有這個能力。所以,模塊化打包工具應運而生,它以兩種形式爲瀏覽器提供這個能力:1.異步加載模塊,而且在加載結束後運行它們。2.將須要用到的文件拼湊成單一 JS 文件,最終在 HTML 中使用 <script>
標籤加載該 JS 文件。前端
若是沒有模塊化加載及打包工具的話,你就得手動拼湊文件或者在 HTML 中加載無數的 <script>
標籤了,並且這樣幹有一些很差的地方:java
<script>
標籤意味着用多個網絡請求來加載代碼,同時也意味着更差的性能。大多數模塊化打包工具直接跟 npm 或者 Bower(譯者注:二者都是包管理工具)整合,這樣可讓你更容易地在業務代碼中添加第三方依賴包(dependencies)。你僅須要安裝一下,而後寫一行代碼引入它們,接着運行模塊化打包工具,這樣就已經將第三方代碼整合進本身的業務代碼了。或者,如若配置正確,你能夠將全部要用的三方代碼整合進一個分開的文件,這樣一來,當你更新業務代碼,用戶須要更新緩存的時候,他們就無需從新下載公共庫代碼(vendor code)了。node
至此,你已對 Webpack 的願景有了基礎的認知,然而爲何在各路豪傑中選擇 Webpack 呢?在我看來有這樣一些理由:webpack
據我所知,少有其餘的模塊打包和構建工具也能作到這些。但 Webpack 仍勝一籌:當你踩坑的時候有龐大的社區支持。
Browserify 的社區可能只是大,若是它不大的話,就會缺乏一些 Webpack 的潛在必要特性。說了這麼多 Webpack 的優勢,估計你就等上代碼了吧?那麼咱們開始。git
在使用 Webpack 前,咱們首先要先把它安裝好。爲此咱們須要 Node.js 和 npm ,我就假設你已經安裝過它們了,實在沒有的話,請從Node.js 官網開始吧。github
有兩種方式安裝 webpack (或着是其餘 CLI 包):全局安裝(globally)或者本地安裝(locally)。對於全局安裝,雖然你能夠在任意目錄下使用它,可是它不會包括在項目的依賴模塊列表(dependencies)中。此外,你也不能在兩個不一樣的項目(有些項目可能須要投入更多工做量才能更新到最新版本,因此這些項目還須要維持老版本)中切換不一樣版本的 Webpack 。因此我更願意本地安裝 CLI 包,而且用相對路徑抑或是 npm 腳原本運行它。若是你不習慣本地安裝 CLI 包,能夠看一下我以前寫的關於擺脫全局安裝 npm 包的博文。web
無論怎樣,在示例項目中,咱們就使用 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 命令行是最容易的上手方式。若是不啓用配置文件的話,最簡潔的命令須要包含輸入文件(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 配置參數,轉而投入配置文件的懷抱。使用配置文件雖然會更佔位置,但與此同時增長了可讀性,由於它是由 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)和插件(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 這兩種加載方式均可以用,由於它的開發者明確規定能夠這麼作,而不是全部的庫均可以經過這種加載方式工做。)
不管如何,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 模版編譯成函數,當你在 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
文件,其內容以下:
<ul> {{#each numbers as |number i|}} <li>{{number}}</li> {{/each}} </ul>
該模板描繪了一個數組(變量名爲 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 打包輸出以下內容:
<ul> <li>1</li> <li>4</li> <li>9</li> <li>16</li> <li>25</li> <li>36</li> </ul>
插件是另外一種用來自定義 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
的內容以下:
<!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>
請注意幾點:
body
元素結尾前添加腳本。如今咱們獲得了想要的模板,最終不會只是一個空白頁了。接下來更新 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)已引入的依賴模塊。(你能夠有多個入口文件,每一個都懶加載一個數據塊,所以該數據塊在其主數據塊中加載的依賴模塊也會不一樣。)
咱們再說一個優化的點:公共庫數據塊。你能夠定義一個單獨用以打包的 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前端高效開發實戰》已在亞馬遜、京東、噹噹開售。