摘要:目錄許多開發(fā)者會(huì)把的目錄命名為但這並不強(qiáng)迫。所有的檔案都會(huì)使用從被編譯成。同時(shí)有個(gè)小小的重點(diǎn)那就是我們可已觀察編譯後的檔案大小。在專案目錄下執(zhí)行可以觀察截至目前為止的結(jié)果。我們的目標(biāo)是要把編譯封裝到我們的中。
在今時(shí)今日,webpack 已經(jīng)成為前端開發(fā)非常重要的工具之一。本質(zhì)上它是一個(gè) Javascript 模組封裝工具,但透過 loaders 和 plugins 它也可以轉(zhuǎn)換封裝其他前端的資源檔像是 HTML,CSS,甚至是圖片等,讓我們能夠控制程式發(fā)出 HTTP 請(qǐng)求的數(shù)量(編譯結(jié)果的檔案數(shù)量)。我們可以使用偏好的方式去撰寫這些資源檔像是 Jade, Sass, ES6 等等。同時(shí)也讓我們能夠輕易的使用來自 npm 的套件。
這篇文章目標(biāo)讀者是那些剛接觸 webpack 的新手。內(nèi)容將會(huì)包含設(shè)定,模組的使用,loaders,plugins,code splitting(分拆程式碼),熱替換(Hot module replacement)。
每過一陣子,重新學(xué)習(xí) webpack 就會(huì)有些新的發(fā)現(xiàn) - 廢話。
為了完成文章中的練習(xí)您需要些前置作業(yè):安裝 Nodejs(譯者使用 v6.9.2),如果您還麼安裝那麼這邊有篇完整使用 nvm 安裝的教學(xué)。
設(shè)定讓我們開始來使用 npm 初始化專案與安裝 webpack。
$ mkdir webpack-demo $ cd webpack-demo $ npm init -y $ npm i webpack@2 -D $ npm view webpack version # 2.2.1 $ mkdir src $ touch index.html src/app.js webpack.config.js
上面這些檔案的概略說明:
index.html - 首頁(yè),載入使用編譯好的 Javascript 檔案。
src/ 目錄 - 許多開發(fā)者會(huì)把 Source Code 的目錄命名為 src 但這並不強(qiáng)迫。
webpack.config.js - 設(shè)定 webpack 行為的設(shè)定檔,這邊我們可以先概略了解一下 webpack 的行為可以靠設(shè)定檔或者指令的參數(shù)調(diào)整,而 webpack.config.js 是預(yù)設(shè)的設(shè)定檔名稱。
src/app.js - 這是我們程式的 Entry point。
接著編輯我們剛產(chǎn)出的這些檔案
index.html
Hello webpack 2
src/app.js
const root = document.querySelector("#root") root.innerHTML = `Hello webpack 2
`
webpack.config.js
const path = require("path") const config = { /** * webpack 執(zhí)行環(huán)境,即 webpack 載入檔案時(shí)相對(duì)路徑的根目錄環(huán)境 * 預(yù)設(shè)(沒有設(shè)定時(shí))為執(zhí)行指令(webpack)所在的那個(gè)目錄 * * 【其他】 * 假設(shè)自己新增一個(gè) build/build.js 檔案,使用載入 webpack 的作法 * * 例如範(fàn)例:https://github.com/andyyou/webpack-context-prove/blob/master/build/build.js#L14 * * 在不同目錄執(zhí)行: * > node build/build.js (in root/ folder) * > node build.js (in build/ folder) * 預(yù)設(shè) context 會(huì)分別為 root/ 和 root/build/ */ context: path.join(__dirname, "src"), /** * Entry point * 因?yàn)樵O(shè)定了 context 所以不需要加上 src/ 了 */ entry: "./app.js", /** * 輸出路徑與檔名 */ output: { path: path.join(__dirname, "dist"), filename: "bundle.js" }, /** * loaders 對(duì)應(yīng)使用規(guī)則 */ module: { rules: [ { test: /.js$/, exclude: /node_modules/, use: [ { /* webpack 2.x 移除了省略 -loader 的寫法 */ loader: "babel-loader", options: { presets: [ /* Loose mode and No native modules(Tree Shaking) */ ["es2015", { modules: false, loose: false }] ] } } ] } ] } } module.exports = config
上面這些是常見的基本設(shè)定,它告訴 webpack 如何編譯我們的原始碼,進(jìn)入點(diǎn)是 src/app.js 輸出的檔案則放在 dist/bundle.js。
所有的 .js 檔案都會(huì)使用 Bable 從 ES2015 被編譯成 ES5。
Babel 從 6.13.0 之後提供額外的參數(shù) loose 和 modules
loose: 提供 loose 編譯模式,該模式啟動(dòng)下 Babel 會(huì)盡可能產(chǎn)生較精簡(jiǎn)的 ES5 程式碼,預(yù)設(shè) false 會(huì)盡可能產(chǎn)出接近 ES2015 規(guī)範(fàn)的程式碼。
modules: 轉(zhuǎn)換 ES2015 module 的語(yǔ)法(import)為其它類型,預(yù)設(shè)為 true 轉(zhuǎn)換為 commonjs。
為了要能夠執(zhí)行我們需要安裝 babel-core,babel-loader 和 babel-preset-es2015。上面還有一個(gè)值得注意的地方就是 {modules: false} 關(guān)閉這個(gè)設(shè)定是為了提供 Tree Shaking 的特性 - 移除沒有使用到的 exports 來縮小編譯的檔案大小。
$ npm i babel-core babel-loader babel-preset-es2015 -D
最後我們?cè)?package.json 補(bǔ)上 scripts 的部分。
"scripts": { "start": "webpack --watch", "build": "webpack -p" }
到這步我們就可以使用 npm start 來執(zhí)行 webpack,加入?yún)?shù) --watch 會(huì)讓 webpack 進(jìn)入監(jiān)視模式,當(dāng)發(fā)現(xiàn)檔案有異動(dòng)時(shí)就會(huì)立即重新編譯我們的原始碼。在 console 畫面會(huì)輸出類似下面的訊息來告知我們 bundle 已經(jīng)被建立了。同時(shí)有個(gè)小小的重點(diǎn)那就是我們可已觀察編譯後的檔案大小。
Webpack is watching the files… Hash: f4fadf78c49f43d8a078 Version: webpack 2.2.1 Time: 1342ms Asset Size Chunks Chunk Names bundle.js 2.6 kB 0 [emitted] main [0] ./app.js 87 bytes {0} [built]
在專案目錄下執(zhí)行 open index.html 可以觀察截至目前為止的結(jié)果。
開啟 dist/bundle.js 看看 webpack 編譯的結(jié)果,上半部是 webpack 處理模組載入的程式碼,最下面則是我們的模組。
到這您可能不覺得有什麼特別的,不過一旦我們開始使用 ES2015 來開發(fā)並將程式模組化,那麼 webpack 就可以替我們處理後續(xù)的工作讓我們的原始碼編譯成可以在瀏覽器執(zhí)行的版本。
接著,我們可以透過 Ctrl + C 來停止 webpack,換成執(zhí)行 npm run build 可以編譯產(chǎn)品模式的 bundle(壓縮)。
您可以注意到檔案大小從 2.6kB 變成 587 bytes,再次觀察 dist/bundle.js 會(huì)發(fā)現(xiàn)程式碼已經(jīng)被 Uglify 了。
至此我們初始化了專案並對(duì) webpack 設(shè)定有了基本的了解。
模組webpack 本身知道該如何處理載入各種格式的 Javascript 模組,比較值得注意的有兩個(gè):
ES2015 import
CommonJS require()
我們使用 lodash 來試驗(yàn)看看
$ npm i lodash -S
src/app.js
import {groupBy} from "lodash/collection" const people = [{ manager: "Jen", name: "Bob" }, { manager: "Jen", name: "Sue" }, { manager: "Bob", name: "Shirley" }, { manager: "Bob", name: "Terrence" }] const managerGroups = groupBy(people, "manager") const root = document.querySelector("#root") root.innerHTML = `${JSON.stringify(managerGroups, null, 2)}`
執(zhí)行 npm start 然後在瀏覽器重新載入 index.html 應(yīng)該要看到我們透過 lodash 區(qū)分群組的結(jié)果。
接著讓我們把 people 的資料搬移到 src/people.js
src/people.js
const people = [{ manager: "Jen", name: "Bob" }, { manager: "Jen", name: "Sue" }, { manager: "Bob", name: "Shirley" }, { manager: "Bob", name: "Terrence" }] export default people
在 src/app.js 使用 import 載入 people.js
import {groupBy} from "lodash/collection" import people from "./people" const managerGroups = groupBy(people, "manager") const root = document.querySelector("#root") root.innerHTML = `${JSON.stringify(managerGroups, null, 2)}`
注意:不使用相對(duì)路徑的例如 lodash/collection 模組將會(huì)從 /node_modules 載入,我們自定義的模組通常使用相對(duì)路徑。
匯入模組時(shí)的 path 分成 函式庫(kù) node_modules 相對(duì)路徑 絕對(duì)路徑
函式庫(kù):什麼都不加,單純 library name
相對(duì)路徑:./ 開頭
絕對(duì)路徑:/ 開頭
第二小節(jié)我們示範(fàn)了模組的使用,也就是我們可以把程式模組化再使用 webpack 來處理匯入使用的部分。
Loaders上面我們已經(jīng)使用了 babel-loader,它是眾多 loader 中的一個(gè),功能就是把 ES2015 轉(zhuǎn)成 ES5。而 loaders 的功用就是告訴 webpack 該如何處理匯入的檔案,通常是 Javascript 但 webpack 不限於處理 Javascript,其他資源檔像是 Sass,圖片等也都可以處理,只要提供對(duì)應(yīng)的 loader。
同時(shí) loader 也可以串連使用,概念上類似於 Linux 中的 pipe,A Loader 處理完之後把結(jié)果交給 B Loader 繼續(xù)轉(zhuǎn)換,以此類推。
最好的示範(fàn)範(fàn)例就是匯入 Sass 的流程,下面就讓我們來看看。
我們的目標(biāo)是要把 Sass 編譯封裝到我們的 bundle 中(Javascript)。這個(gè)轉(zhuǎn)換過程需要一些 loaders 和函式庫(kù):
$ npm i css-loader style-loader sass-loader node-sass -D
為處理 .scss 類型的檔案加入新的編譯規(guī)則
module: { rules: [ // {...}, { test: /.scss$/, /** * use 屬性是用來套用,串接多個(gè) loaders。 * v2 為了相容的因素保留 loaders 屬性,loaders 為 use 的別名, * 盡可能的使用 use 代替 loaders */ use: [ "style-loader", "css-loader", "sass-loader" ] } // {...} ] }
每當(dāng)我們修改了 webpack.config.js 我們就必須重啓。 Ctrl + C -> npm start
設(shè)定 loaders 的陣列實(shí)際上會(huì)反過來逐一執(zhí)行:
sass-loader - 編譯 Sass 成為 CSS
css-loader - 解析 CSS 轉(zhuǎn)換成 Javascript 同時(shí)解析相依的資源
style-loader - 輸出 CSS 到 document 的 元素內(nèi)
我們可以想成像下面這樣調(diào)用 function
styleLoader(cssLoader(sassLoader("source")))
讓我們來加入 Sass
src/style.scss
$bg-color: #2B3A43; pre { padding: 15px; background: $bg-color; color: #DEDEDE; }
現(xiàn)在我們可以在 app.js 匯入 scss 了。
import "./style.scss"
重載 index.html 我們應(yīng)該可以看到樣式已經(jīng)套用了。
CSS in JS上一步我們完成了從 JS 中把 Sass 當(dāng)作一個(gè)模組載入。
打開 dist/bundle.js 搜尋 pre {,我們看到 Sass 已經(jīng)被編譯為字串並存為一個(gè)模組。當(dāng)我們匯入該模組時(shí) style-loader 會(huì)把該字串嵌入 標(biāo)籤中。
為什麼我們要這麼作呢?
這邊我們不想探討太多這個(gè)議題,不過的確有些理由讓我們這麼作:
回想我們?cè)谑褂?jQuery 套件或 Javascript 元件時(shí),假如這些元件包含些資源檔像是 HTML,CSS,圖片,SVG 等等,通常這時(shí)候我們就需要把這檔案搬到對(duì)應(yīng)的目錄下,又或者我們有潔癖希望圖檔等放在我們定義的目錄下,這時(shí)我們就需要去修改路徑。當(dāng)全部都封裝在 Javascript 時(shí)我們?cè)诮M織時(shí)就方便很多。
方便移除不需要的程式碼 - 當(dāng) Javascript 元件不在被使用時(shí), CSS 等資源檔內(nèi)容也會(huì)一併被移除。
CSS Module - 隨著 CSS 樣式越來越多,命名常常容易衝突,透過 CSS Module 的方式可以對(duì)特定元件或模組套用其專用(local)的樣式,只有該模組可以套用樣式,這樣一來也相對(duì)容易維護(hù)。
減少 HTTP request 的數(shù)量。
圖片最後我們要在介紹一個(gè)常遇到的需求 - 圖片。我們將要使用 url-loader 來處理圖片的載入。
在標(biāo)準(zhǔn)的 HTML 文件中圖片需要透過 標(biāo)籤或 CSS 的 background-image 來載入。使用 webpack 我們可以優(yōu)化圖片並根據(jù)檔案大小分別處理。像是把比較小的圖片轉(zhuǎn)成 base64 字串存在 Javascript 中。這麼作瀏覽器可以預(yù)先載入,而且不會(huì)發(fā)出額外的 HTTP 請(qǐng)求。
npm i file-loader url-loader -D
當(dāng)然我們記住了,每當(dāng)我們需要一個(gè) loader 來幫我們處理某類型的檔案時(shí),我們除了安裝該 loader 外,還需要設(shè)定 rules。
rules: [ // ... { test: /.(png|jpe?g|gif|svg)(?.*)?$/, use: [ { loader: "url-loader", options: { limit: 10000 /* 小於 10kB 的圖片轉(zhuǎn)成 base64 */ } } ] } ]
讓我們下載張圖片並將其放在 src/
$ curl http://i.imgur.com/5Hk42Ct.png --output src/code.png
接著在 app.js 中使用下面範(fàn)例載入
src/app.js
import imageURL from "./code.png" const img = document.createElement("img") img.src = imageURL img.style = "background: #2B3A4F; padding: 15px;" img.width = 32 document.body.appendChild(img)
檢查 HTML 該圖片元素,我們看到會(huì)如下圖片的來源被轉(zhuǎn)換成 base64 了。
不只是在 Javascript 載入的圖片,我們之前提到 css-loader 同時(shí)也會(huì)解析相依的資源,但是要處理圖片 css-loader 也是需要 url-loader 來幫忙。我們可以從 css-loader 的文件 options 一節(jié)得知。
src/style.scss
pre { background: $bg-color url("code.png") no-repeat center center; }
也會(huì)被編譯成
pre { background: $bg-color url("data:image/png;base64,iVBORw0KG...") no-repeat center center; }從模組到靜態(tài)資源
現(xiàn)在您應(yīng)該明白 loaders 是如何協(xié)助我們編譯各式各樣的檔案,這也是 webpack 2 官方文件首頁(yè) 出現(xiàn)圖片所要表達(dá)的。
透過 Javascript 的 Entry point 進(jìn)入點(diǎn),webpack 就會(huì)明白我們需要那些模組以及各種類型的檔案該如何處理。
Plugins心法:組織好專案架構(gòu)後,安裝 webpack,設(shè)定的基礎(chǔ)是 entry,output 以及想編譯哪些類型的檔案,對(duì)應(yīng)安裝相關(guān) loaders 接著設(shè)定 rules。
開始講 plugins 之前,其實(shí)我們已經(jīng)看過一個(gè) webpack 內(nèi)建的 plugin。它就是當(dāng)我們執(zhí)行 webpack -p 時(shí)使用的 UglifyJsPlugin。
簡(jiǎn)單說,loaders 的任務(wù)是針對(duì)單一檔案協(xié)助轉(zhuǎn)換,而 plugins 的任務(wù)則針對(duì)轉(zhuǎn)換後的程式碼片段作處理。
通用程式碼commons-chunk-plugin 是另一個(gè)內(nèi)建的核心套件,可以將多個(gè) Entry point 中共用模組的部分抽出來獨(dú)立成一個(gè)模組。
到這邊我們都只有單一個(gè) Entry point 然後只輸出一個(gè) bundle。在實(shí)務(wù)上您可能會(huì)需要多個(gè)進(jìn)入點(diǎn)來切割 bundle,例如:我們自己開發(fā)的部分歸納在 app.js,其他外部的函式庫(kù)歸納在 vendor.js。
又或者假設(shè)我們的網(wǎng)站有兩個(gè)功能上需要切割的部分 - 一般使用者 app.js 和管理者 admin.js。這麼一來我們就可以像下面這樣處理:
const path = require("path") const webpack = require("webpack") const extractCommons = new webpack.optimize.CommonsChunkPlugin({ name: "commons", filename: "commons.js" }) const config = { context: path.join(__dirname, "src"), entry: { app: "./app.js", admin: "./admin.js" }, output: { path: path.join(__dirname, "dist"), filename: "[name].bundle.js" }, // ... plugins: [ extractCommons ] } module.exports = config
注意到 output.filename 部分我們?cè)谇懊婕由狭?[name],同時(shí) entry 也從字串換成物件格式,物件 key 的名稱會(huì)對(duì)應(yīng)到 [name],也就是照上面的設(shè)定會(huì)產(chǎn)生 app.bundle.js 和 admin.bundle.js 兩隻檔案。
而 commons-chunk-plugin 則會(huì)把這兩個(gè) entry point 中共用的模組抽出來產(chǎn)生第三隻檔案 commons.js。
我們先來看看範(fàn)例:
src/app.js
import "./style.scss" import {groupBy} from "lodash/collection" import people from "./people" const managerGroups = groupBy(people, "manager") const root = document.querySelector("#root") root.innerHTML = `${JSON.stringify(managerGroups, null, 2)}`
src/admin.js
import people from "./people" const root = document.querySelector("#root") root.innerHTML = `There are ${people.length} people.
`
再次執(zhí)行 npm start 就會(huì)看到 webpack 產(chǎn)生了 3 隻檔案
app.bundle.js 包含了 style 和 lodash/collection
admin.bundle.js 沒有其他額外的模組
commons.js 包含 people 模組
然後調(diào)整 HTML 的部分:
index.html
Hello webpack 2
admin.html
Hello webpack 2 - Admin
當(dāng)我們分別造訪兩個(gè)頁(yè)面的時(shí)候,commons.js 也可以因?yàn)榍耙淮伪?cache 了而加快了網(wǎng)頁(yè)載入的速度。
前面我們把 CSS 也當(dāng)作模組匯入並打包進(jìn) Javascript,但實(shí)務(wù)上我們想要讓瀏覽器非同步載入和平行處理 CSS 所以我們需要輸出獨(dú)立的 CSS 檔案。
這時(shí)我們就要另外一個(gè)非常熱門的 plugin - extract-text-webpack-plugin 它可以將模組匯出成檔案。
下面我們將改寫 .scss rule 的部分,將編譯好的 CSS 匯出成檔案,而不是放在 Javascript。
# extract-text-webpack-plugin 2 正處?kù)?rc 階段 # 我們可以透過下面的指令查看所有的版本 $ npm show extract-text-webpack-plugin versions # 安裝 $ npm i [email protected] -D
接著修改 webpack.config.js
const path = require("path") const webpack = require("webpack") const extractCommons = new webpack.optimize.CommonsChunkPlugin({ name: "commons", filename: "commons.js" }) const ExtractTextPlugin = require("extract-text-webpack-plugin") const extractCSS = new ExtractTextPlugin("[name].bundle.css") const config = { context: path.join(__dirname, "src"), entry: { app: "./app.js", admin: "./admin.js" }, output: { path: path.join(__dirname, "dist"), filename: "[name].bundle.js" }, module: { rules: [ // ... { test: /.scss$/, loader: extractCSS.extract(["css-loader", "sass-loader"]) /* use: [ "style-loader", "css-loader", "sass-loader" ] */ } ] }, plugins: [ extractCommons, extractCSS ] } module.exports = config
重啓 webpack 您應(yīng)該可以看到 webpack 匯出了 app.bundle.css,於是我們就可以在 HTML 中補(bǔ)上連結(jié)。每一個(gè) entry 內(nèi)匯入的 Sass 都會(huì)被抽出來成一隻獨(dú)立的檔案。由於 admin 沒有匯入 Sass 所以沒有輸出。
分拆原始碼Hello webpack 2
我們已經(jīng)看了一些分拆程式碼的方式:
手動(dòng)建立多個(gè) Entry point
使用 commons-chunk-plugin 分拆出共用模組的部分
使用 extract-text-webpack-plugin
另外一個(gè)分拆的方式是使用 System.import 或 require.ensure。透過調(diào)用這些方法設(shè)定,我們可以劃分程式碼讓它們?cè)谛枰臅r(shí)候才在執(zhí)行時(shí)期(runtime)載入,而不是一口氣全部載入。這樣能夠有效的改善載入所造成的效能問題。System.import 使用 module 名稱作為參數(shù),接著回傳一個(gè) Promise。require.ensure 則是傳入相依套件的列表, callback,和一個(gè)可選的參數(shù)來設(shè)定該程式片段的名稱。
System.import 是 ES2015 模組載入的 API
如果您的程式中某部分具有大量相依函式庫(kù),且程式的其他地方不需要。這種情況正好就適合將其拆分出來??纯聪旅娴墓?fàn)例,我們的 dashboard.js 需要 d3,但可以見得的其它地方不需要 d3。
$ npm i d3 -S
src/dashboard.js
import * as d3 from "d3" console.log("Loaded", d3) export const draw = () => { console.log("Draw!") }
接著在 app.js 的最下方我們模擬晚一點(diǎn)載入(需要的時(shí)候才載入)
function gotoDashboard () { System.import("./dashboard") .then(function (dashboard) { dashboard.draw() }) .catch(function (err) { console.log("Chunk loading failed") }) } setTimeout(gotoDashboard, 5000)
因?yàn)槲覀兪褂玫氖?System.import("./dashboard") 這樣的相對(duì)路徑,在線上的情況下會(huì)變成 http://example.com/dashboard.js 這樣的路徑是錯(cuò)誤的。所以需要加上 output.publicPath 的設(shè)定,這樣才會(huì)是 http://example.com/dist/dashboard.js。
output: { path: path.join(__dirname, "dist"), publicPath: "/dist/", filename: "[name].bundle.js" }
重啓 webpack 會(huì)發(fā)現(xiàn) console 有一個(gè)奇怪的 0.bundle.js
Hash: e96d4e3ab03b79a320aa Version: webpack 2.2.1 Time: 4030ms Asset Size Chunks Chunk Names 0.bundle.js 452 kB 0 [emitted] [big] app.bundle.js 184 kB 1 [emitted] app admin.bundle.js 461 bytes 2 [emitted] admin commons.js 5.91 kB 3 [emitted] commons app.bundle.css 1.31 kB 1 [emitted] app
webpack 用了較為突出的顏色顯示 [big] 讓我們?nèi)プ⒁馑?/p>
這個(gè) 0.bundle.js 將會(huì)在需要的時(shí)候發(fā)出 JSONP 的請(qǐng)求,這個(gè)時(shí)候如果我們繼續(xù)直接讀取檔案的方式是拿不到資料的。
暫時(shí)我們可以在專案目錄下使用 python 提供的簡(jiǎn)易伺服器(因?yàn)?Linux,OSX 內(nèi)建都有 python 我們不需要在作其他安裝)。
$ python -m SimpleHTTPServer
瀏覽 http://localhost:8000,5 秒後我們應(yīng)該可以看到一個(gè) GET 請(qǐng)求 /dist/0.bundle.js 同時(shí) console 顯示 Loaded 載入完成。
Webpack Dev ServerLive reload 的出現(xiàn),大大的改善了我們的開發(fā)體驗(yàn)也替開發(fā)者們節(jié)省了許多的時(shí)間。簡(jiǎn)單的說就是當(dāng)檔案發(fā)生變動(dòng)時(shí)瀏覽器會(huì)自動(dòng)重新載入頁(yè)面。
只需要安裝並使用 webpack-dev-server 這個(gè)開發(fā)伺服器我們就可以輕鬆取得這個(gè)功能。
$ npm i webpack-dev-server@2 -D
接著我們需要修改 package.json scripts start 的部分:
"scripts": { "start": "webpack-dev-server --inline", "build": "webpack -p" },
執(zhí)行 npm start 就可以透過 http://localhost:8080 來瀏覽網(wǎng)頁(yè)。
現(xiàn)在,只要我們修改 src 目錄下的檔案,例如:people.js 或 style.scss 就可以看到瀏覽器馬上更新結(jié)果。
熱替換(Hot Module Replacement)如果您對(duì)於 Live reload 印象深刻的話,那麼 HMR 可能將令你感到驚訝。
假如您已經(jīng)在開發(fā) SPA (Signle Page Application),您應(yīng)該已經(jīng)遭遇過了這種惱人的情況 - 在你的開發(fā)過程常常因?yàn)橐獪y(cè)試某元件而反覆操作一些流程。什麼意思?假如我們正在開發(fā)付款流程的頁(yè)面,分別有 4 個(gè)步驟,我們的元件在第 3 步,於是每當(dāng)我們一修改,所有狀態(tài)因?yàn)橹剌d的關(guān)係回到預(yù)設(shè),然後我們就只好反覆執(zhí)行步驟 1, 2。
Hot Module Replacement 就是為了拯救我們脫離這個(gè)迴圈而出現(xiàn)了。
我們理想的開發(fā)流程應(yīng)該是:每當(dāng)我們修改我們的模組,然後應(yīng)該只要編譯該模組,在不刷新瀏覽器,不影響其他模組的情況下把新的程式碼換上去,當(dāng)我們需要 reset 狀態(tài)時(shí)在重載頁(yè)面。大致上這就是 HMR 的功能。
我們只需要加入一個(gè)參數(shù) --hot 就可以啟用這個(gè)功能:
"scripts": { "start": "webpack-dev-server --inline --hot", "build": "webpack -p" }
為了讓我們的模組也支援 HMR 我們需要在 app.js 的最上面補(bǔ)上下面這段程式碼,好讓 webpack 知道我們模組的邊界以及更新底下相依的元件。
if (module.hot) { module.hot.accept() }
注意:webpack-dev-server --hot 會(huì)把 module.hot 設(shè)為 true 而且只有在開發(fā)模式才支援。在 production 模式下 module.hot 會(huì)是 false,相關(guān)的程式碼不會(huì)出現(xiàn)在 bundle 中。
加入 NamedModulesPlugin 到 webpack.config.js 的 plugins 陣列,如此一來我們?cè)跒g覽器的 console 就可以看出是哪個(gè)檔案更新。
plugins: [ // ... new webpack.NamedModulesPlugin() ]
最後,我們加入 到 HTML ,重新執(zhí)行 npm start 並在頁(yè)面的輸入框輸入一些字,編輯 people.js 中的人名,存檔。我們可以觀察到 HMR 的行為,的確不是整個(gè)頁(yè)面刷新。
Hot Reloading CSS修改 style.scss 中 的背景色,我們注意到 HMR 並沒有對(duì)應(yīng)更新
pre { background: red; }
當(dāng)我們使用 style-loader 時(shí) HMR 是會(huì)更新的,我們不需要作其他設(shè)定。不過因?yàn)槲覀兪褂昧?extract-text-webpack-plugin 把 CSS 獨(dú)立出去成為檔案,所以也就不支援 HMR。
HTTP/2使用 webpack 這類的封裝工具一個(gè)主要的好處就是可以控制最終資源檔被請(qǐng)求的數(shù)量。在過去幾年這是最佳的實(shí)作方式,不過 HTTP/2 的出現(xiàn),整合成單檔的方式不再是唯一,分散成許多小檔案在 HTTP/2 中是相對(duì)好的作法。
不過 webpack 的作者 Tobias Koppers 寫了篇文章闡述即使在 HTTP/2 的情況下,封裝工具仍有其重要性。連結(jié)
資源A Beginner’s Guide to Webpack 2 and Module Bundling
Migrating from v1 to v2
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/81577.html
摘要:相對(duì)於座標(biāo)在可視區(qū)的最左上角。滑鼠座標(biāo)通常透過事件取得。再次強(qiáng)調(diào)不幸的是沒有屬性可以直接取得元素對(duì)應(yīng)的座標(biāo)。觸發(fā)事件的元素相對(duì)於父容器定位元素的座標(biāo),從開始計(jì)算。上個(gè)座標(biāo)與當(dāng)前的座標(biāo)移動(dòng)距離。 座標(biāo)系統(tǒng) 在瀏覽器中有兩種座標(biāo)系統(tǒng) & 滑鼠座標(biāo): 1. 相對(duì)於 `document` - 座標(biāo) (0, 0) 在整個(gè)頁(yè)面的最左上角。 2. 相對(duì)於 `window` - 座標(biāo) (0, 0) 在...
摘要:確切位置因平臺(tái)而異。如果以編程方式使用,這個(gè)頁(yè)面也是一個(gè)強(qiáng)大的調(diào)試工具,能看到所有原始的協(xié)議命令通過連線,於瀏覽器進(jìn)行通信。警告協(xié)議可以做很多有趣的事,但作為入門選項(xiàng)他令人沮喪。目前,提供了比協(xié)議高級(jí)別的。 本文翻譯自:Getting Started with Headless Chrome原文更新時(shí)間:July 28,2017作者:Eric Bidelman(Engineer @ G...
摘要:的架構(gòu)設(shè)計(jì)促使第三方開發(fā)者讓核心發(fā)揮出無限的潛力。當(dāng)然建置比起開發(fā)是較進(jìn)階的議題,因?yàn)槲覀儽仨氁斫鈨?nèi)部的一些事件。這個(gè)編譯結(jié)果包含的訊息包含模組的狀態(tài),編譯後的資源檔,發(fā)生異動(dòng)的檔案,被觀察的相依套件等。 本文將對(duì) webpack 周邊的 middleware 與 plugin 套件等作些介紹,若您對(duì)於 webpack 還不了解可以參考這篇彙整的翻譯。 webpack dev ser...
摘要:現(xiàn)在,我們可以開始探討介面的設(shè)計(jì)模式了。匯出命名空間一個(gè)簡(jiǎn)單且常用的設(shè)計(jì)模式就是匯出一個(gè)包含數(shù)個(gè)屬性的物件,這些屬性具體的內(nèi)容主要是函式,但並不限於函式。如此,我們就能夠透過匯入該模組來取得這個(gè)命名空間下一系列相關(guān)的功能。 前言 這篇文章試著要整理,翻譯Export This: Interface Design Patterns for Node.js Modules這篇非常值得一讀的...
摘要:我們得從原因理解起。這個(gè)編碼位置是唯一的。為了確保其組織性,把這個(gè)範(fàn)圍的編碼區(qū)分成個(gè)區(qū)段,各自由個(gè)編碼組成。由進(jìn)制格式的個(gè)位元組成代表一個(gè)編碼位置。跳脫序列可以被用來表示編碼位置從到。 為了理解 ES6 到底對(duì)於 Unicode 萬(wàn)國(guó)碼有哪些新的支援。我們得從原因理解起。 Javascript 在處理 Unicode 時(shí)很有多問題 關(guān)於 Javascript 處理 Unicode 的方...
閱讀 3491·2023-04-25 22:45
閱讀 1294·2021-11-11 16:54
閱讀 2801·2019-08-30 15:44
閱讀 3198·2019-08-30 15:44
閱讀 1654·2019-08-30 13:55
閱讀 948·2019-08-29 18:45
閱讀 1207·2019-08-29 17:25
閱讀 1017·2019-08-29 12:59