摘要:和類似的預(yù)處理器還有等。的用處非常多,包括給自動加前綴使用下一代語法等,目前越來越多的人開始用它,它很可能會成為預(yù)處理器的最終贏家。
webpack實戰(zhàn)
查看所有文檔頁面:全棧開發(fā),獲取更多信息。快馬加鞭,加班加點,終于把這個文檔整理出來了,順便深入地學(xué)習(xí)一番,鞏固知識,就是太累人,影響睡眠時間和質(zhì)量。極客就是想要把事情做到極致,開始了就必須到達終點。
原文鏈接:webpack實戰(zhàn),原文廣告模態(tài)框遮擋,閱讀體驗不好,所以整理成本文,方便查找。
本章教你如何用 Webpack 去解決實際項目中常見的場景。
按照不同場景劃分成以下幾類:
使用新語言來開發(fā)項目:
使用 ES6 語言
使用 TypeScript 語言
使用 Flow 檢查器
使用 SCSS 語言
使用 PostCSS
使用新框架來開發(fā)項目:
使用 React 框架
使用 Vue 框架
使用 Angular2 框架
用 Webpack 構(gòu)建單頁應(yīng)用:
為單頁應(yīng)用生成 HTML
管理多個單頁應(yīng)用
用 Webpack 構(gòu)建不同運行環(huán)境的項目:
構(gòu)建同構(gòu)應(yīng)用
構(gòu)建 Electron 應(yīng)用
構(gòu)建 Npm 模塊
構(gòu)建離線應(yīng)用
Webpack 結(jié)合其它工具搭配使用,各取所長:
搭配 Npm Script
檢查代碼
通過 Node.js API 啟動 Webpack
使用 Webpack Dev Middleware
用 Webpack 加載特殊類型的資源:
加載圖片
加載SVG
加載 Source Map
使用 TypeScript 語言由于本文不推薦使用TypeScript,ES6就足夠完成大部分任務(wù)。原文鏈接:使用 TypeScript 語言
使用 Angular2 框架Angular2不在我的技術(shù)棧范圍,所以這一章不加入,有興趣的查看原文:使用 Angular2 框架
使用ES6語言通常我們需要把采用 ES6 編寫的代碼轉(zhuǎn)換成目前已經(jīng)支持良好的 ES5 代碼,這包含2件事:
把新的 ES6 語法用 ES5 實現(xiàn),例如 ES6 的 class 語法用 ES5 的 prototype 實現(xiàn)。
給新的 API 注入 polyfill ,例如使用新的 fetch API 時注入對應(yīng)的 polyfill 后才能讓低端瀏覽器正常運行。
BabelBabel 可以方便的完成以上2件事。
Babel 是一個 JavaScript 編譯器,能將 ES6 代碼轉(zhuǎn)為 ES5 代碼,讓你使用最新的語言特性而不用擔(dān)心兼容性問題,并且可以通過插件機制根據(jù)需求靈活的擴展。
在 Babel 執(zhí)行編譯的過程中,會從項目根目錄下的 .babelrc 文件讀取配置。.babelrc 是一個 JSON 格式的文件,內(nèi)容大致如下:
{ "plugins": [ [ "transform-runtime", { "polyfill": false } ] ], "presets": [ [ "es2015", { "modules": false } ], "stage-2", "react" ] }Plugins
plugins 屬性告訴 Babel 要使用哪些插件,插件可以控制如何轉(zhuǎn)換代碼。
以上配置文件里的 transform-runtime 對應(yīng)的插件全名叫做 babel-plugin-transform-runtime,即在前面加上了 babel-plugin-,要讓 Babel 正常運行我們必須先安裝它:
npm i -D babel-plugin-transform-runtime
babel-plugin-transform-runtime 是 Babel 官方提供的一個插件,作用是減少冗余代碼。
Babel 在把 ES6 代碼轉(zhuǎn)換成 ES5 代碼時通常需要一些 ES5 寫的輔助函數(shù)來完成新語法的實現(xiàn),例如在轉(zhuǎn)換 class extent 語法時會在轉(zhuǎn)換后的 ES5 代碼里注入 _extent 輔助函數(shù)用于實現(xiàn)繼承:
function _extent(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }
這會導(dǎo)致每個使用了 class extent 語法的文件都被注入重復(fù)的 _extent 輔助函數(shù)代碼,babel-plugin-transform-runtime 的作用在于不把輔助函數(shù)內(nèi)容注入到文件里,而是注入一條導(dǎo)入語句:
var _extent = require("babel-runtime/helpers/_extent");
這樣能減小 Babel 編譯出來的代碼的文件大小。
同時需要注意的是由于 babel-plugin-transform-runtime 注入了 require("babel-runtime/helpers/_extent") 語句到編譯后的代碼里,需要安裝 babel-runtime 依賴到你的項目后,代碼才能正常運行。 也就是說 babel-plugin-transform-runtime 和 babel-runtime 需要配套使用,使用了 babel-plugin-transform-runtime 后一定需要 babel-runtime。
Presetspresets 屬性告訴 Babel 要轉(zhuǎn)換的源碼使用了哪些新的語法特性,一個 Presets 對一組新語法特性提供支持,多個 Presets 可以疊加。
Presets 其實是一組 Plugins 的集合,每一個 Plugin 完成一個新語法的轉(zhuǎn)換工作。Presets 是按照 ECMAScript 草案來組織的,通常可以分為以下三大類:
已經(jīng)被寫入 ECMAScript 標準里的特性,由于之前每年都有新特性被加入到標準里;
env 包含當(dāng)前所有 ECMAScript 標準里的最新特性。
被社區(qū)提出來的但還未被寫入 ECMAScript 標準里特性,這其中又分為以下四種:
stage0 只是一個美好激進的想法,有 Babel 插件實現(xiàn)了對這些特性的支持,但是不確定是否會被定為標準;
stage1 值得被納入標準的特性;
stage2 該特性規(guī)范已經(jīng)被起草,將會被納入標準里;
stage3 該特性規(guī)范已經(jīng)定稿,各大瀏覽器廠商和 Node.js 社區(qū)開始著手實現(xiàn);
stage4 在接下來的一年將會加入到標準里去。
為了支持一些特定應(yīng)用場景下的語法,和 ECMAScript 標準沒有關(guān)系,例如 babel-preset-react 是為了支持 React 開發(fā)中的 JSX 語法。
在實際應(yīng)用中,你需要根據(jù)項目源碼所使用的語法去安裝對應(yīng)的 Plugins 或 Presets。
接入 Babel由于 Babel 所做的事情是轉(zhuǎn)換代碼,所以應(yīng)該通過 Loader 去接入 Babel,Webpack 配置如下:
module.exports = { module: { rules: [ { test: /.js$/, use: ["babel-loader"], }, ] }, // 輸出 source-map 方便直接調(diào)試 ES6 源碼 devtool: "source-map" };
配置命中了項目目錄下所有的 JavaScript 文件,通過 babel-loader 去調(diào)用 Babel 完成轉(zhuǎn)換工作。 在重新執(zhí)行構(gòu)建前,需要先安裝新引入的依賴:
# Webpack 接入 Babel 必須依賴的模塊 npm i -D babel-core babel-loader # 根據(jù)你的需求選擇不同的 Plugins 或 Presets npm i -D babel-preset-env使用SCSS語言
SCSS 可以讓你用更靈活的方式寫 CSS。 它是一種 CSS 預(yù)處理器,語法和 CSS 相似,但加入了變量、邏輯等編程元素,代碼類似這樣:
$blue: #1875e7; div { color: $blue; }
SCSS 又叫 SASS,區(qū)別在于 SASS 語法類似 Ruby,而 SCSS 語法類似 CSS,對于熟悉 CSS 的前端工程師來說會更喜歡 SCSS。
采用 SCSS 去寫 CSS 的好處在于可以方便地管理代碼,抽離公共的部分,通過邏輯寫出更靈活的代碼。 和 SCSS 類似的 CSS 預(yù)處理器還有 LESS 等。
使用 SCSS 可以提升編碼效率,但是必須把 SCSS 源代碼編譯成可以直接在瀏覽器環(huán)境下運行的 CSS 代碼。
node-sass 核心模塊是由 C++ 編寫,再用 Node.js 封裝了一層,以供給其它 Node.js 調(diào)用。 node-sass 還支持通過命令行調(diào)用,先安裝它到全局:
npm i -g node-sass
再執(zhí)行編譯命令:
# 把 main.scss 源文件編譯成 main.css node-sass main.scss main.css
你就能在源碼同目錄下看到編譯后的 main.css 文件。
接入 WebpackWebpack 接入 sass-loader 相關(guān)配置如下:
module.exports = { module: { rules: [ { // 增加對 SCSS 文件的支持 test: /.scss/, // SCSS 文件的處理順序為先 sass-loader 再 css-loader 再 style-loader use: ["style-loader", "css-loader", "sass-loader"], }, ] }, };
以上配置通過正則 /.scss/ 匹配所有以 .scss 為后綴的 SCSS 文件,再分別使用3個 Loader 去處理。具體處理流程如下:
通過 sass-loader 把 SCSS 源碼轉(zhuǎn)換為 CSS 代碼,再把 CSS 代碼交給 css-loader 去處理。
css-loader 會找出 CSS 代碼中的 @import 和 url() 這樣的導(dǎo)入語句,告訴 Webpack 依賴這些資源。同時還支持 CSS Modules、壓縮 CSS 等功能。處理完后再把結(jié)果交給 style-loader 去處理。
style-loader 會把 CSS 代碼轉(zhuǎn)換成字符串后,注入到 JavaScript 代碼中去,通過 JavaScript 去給 DOM 增加樣式。如果你想把 CSS 代碼提取到一個多帶帶的文件而不是和 JavaScript 混在一起,可以使用1-5 使用Plugin 中介紹過的 ExtractTextPlugin。
由于接入 sass-loader,項目需要安裝這些新的依賴:
# 安裝 Webpack Loader 依賴 npm i -D sass-loader css-loader style-loader # sass-loader 依賴 node-sass npm i -D node-sass使用Flow檢查器
Flow 是一個 Facebook 開源的 JavaScript 靜態(tài)類型檢測器,它是 JavaScript 語言的超集。
你所需要做的就是在需要的地方加上類型檢查,例如在兩個由不同人開發(fā)的模塊對接的接口出加上靜態(tài)類型檢查,能在編譯階段就指出部分模塊使用不當(dāng)?shù)膯栴}。 同時 Flow 也能通過類型推斷檢查出 JavaScript 代碼中潛在的 Bug。
Flow 使用效果如下:
// @flow // 靜態(tài)類型檢查 function square1(n: number): number { return n * n; } square1("2"); // Error: square1 需要傳入 number 作為參數(shù) // 類型推斷檢查 function square2(n) { return n * n; // Error: 傳入的 string 類型不能做乘法運算 } square2("2");
需要注意的時代碼中的第一行 // @flow 告訴 Flow 檢查器這個文件需要被檢查。使用 Flow
Flow 檢測器由高性能跨平臺的 OCaml 語言編寫,它的可執(zhí)行文件可以通過:
npm i -D flow-bin
安裝,安裝完成后通過先配置 Npm Script:
"scripts": { "flow": "flow" }
再通過 npm run flow 去調(diào)用 Flow 執(zhí)行代碼檢查。
除此之外你還可以通過:
npm i -g flow-bin
把 Flow 安裝到全局后,再直接通過 flow 命令去執(zhí)行代碼檢查。
安裝成功后,在項目根目錄下執(zhí)行 Flow 后,F(xiàn)low 會遍歷出所有需要檢查的文件并對其進行檢查,輸出錯誤結(jié)果到控制臺。
采用了 Flow 靜態(tài)類型語法的 JavaScript 是無法直接在目前已有的 JavaScript 引擎中運行的,要讓代碼可以運行需要把這些靜態(tài)類型語法去掉。
// 采用 Flow 的源代碼 function foo(one: any, two: number, three?): string {} // 去掉靜態(tài)類型語法后輸出代碼 function foo(one, two, three) {}
有兩種方式可以做到這點:
flow-remove-types 可多帶帶使用,速度快。
babel-preset-flow 與 Babel 集成。
集成 Webpack由于使用了 Flow 項目一般都會使用 ES6 語法,所以把 Flow 集成到使用 Webpack 構(gòu)建的項目里最方便的方法是借助 Babel。
安裝 npm i -D babel-preset-flow 依賴到項目。
修改 .babelrc 配置文件,加入 Flow Preset:
"presets": [ ...[], "flow" ]
往源碼里加入靜態(tài)類型后重新構(gòu)建項目,你會發(fā)現(xiàn)采用了 Flow 的源碼還是能正常在瀏覽器中運行。
要明確構(gòu)建的目的只是為了去除源碼中的 Flow 靜態(tài)類型語法,而代碼檢查和構(gòu)建無關(guān)。 許多編輯器已經(jīng)整合 Flow,可以實時在代碼中高亮指出 Flow 檢查出的問題。使用PostCSS
PostCSS 是一個 CSS 處理工具,和 SCSS 不同的地方在于它通過插件機制可以靈活的擴展其支持的特性,而不是像 SCSS 那樣語法是固定的。 PostCSS 的用處非常多,包括給 CSS 自動加前綴、使用下一代 CSS 語法等,目前越來越多的人開始用它,它很可能會成為 CSS 預(yù)處理器的最終贏家。
PostCSS 和 CSS 的關(guān)系就像 Babel 和 JavaScript 的關(guān)系,它們解除了語法上的禁錮,通過插件機制來擴展語言本身,用工程化手段給語言帶來了更多的可能性。PostCSS 和 SCSS 的關(guān)系就像 Babel 和 TypeScript 的關(guān)系,PostCSS 更加靈活、可擴張性強,而 SCSS 內(nèi)置了大量功能而不能擴展。
給 CSS 自動加前綴,增加各瀏覽器的兼容性:
/*輸入*/ h1 { display: flex; } /*輸出*/ h1 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; }
使用下一代 CSS 語法:
/*輸入*/ :root { --red: #d33; } h1 { color: var(--red); } /*輸出*/ h1 { color: #d33; }
PostCSS 全部采用 JavaScript 編寫,運行在 Node.js 之上,即提供了給 JavaScript 代碼調(diào)用的模塊,也提供了可執(zhí)行的文件。
在 PostCSS 啟動時,會從目錄下的 postcss.config.js 文件中讀取所需配置,所以需要新建該文件,文件內(nèi)容大致如下:
module.exports = { plugins: [ // 需要使用的插件列表 require("postcss-cssnext") ] }
其中的 postcss-cssnext 插件可以讓你使用下一代 CSS 語法編寫代碼,再通過 PostCSS 轉(zhuǎn)換成目前的瀏覽器可識別的 CSS,并且該插件還包含給 CSS 自動加前綴的功能。
目前 Chrome 等現(xiàn)代瀏覽器已經(jīng)能完全支持 cssnext 中的所有語法,也就是說按照 cssnext 語法寫的 CSS 在不經(jīng)過轉(zhuǎn)換的情況下也能在瀏覽器中直接運行。接入 Webpack
雖然使用 PostCSS 后文件后綴還是 .css 但這些文件必須先交給 postcss-loader 處理一遍后再交給 css-loader。
接入 PostCSS 相關(guān)的 Webpack 配置如下:
module.exports = { module: { rules: [ { // 使用 PostCSS 處理 CSS 文件 test: /.css/, use: ["style-loader", "css-loader", "postcss-loader"], }, ] }, };
接入 PostCSS 給項目帶來了新的依賴需要安裝,如下:
# 安裝 Webpack Loader 依賴 npm i -D postcss-loader css-loader style-loader # 根據(jù)你使用的特性安裝對應(yīng)的 PostCSS 插件依賴 npm i -D postcss-cssnext使用React框架 React 語法特征
使用了 React 項目的代碼特征有 JSX 和 Class 語法,例如:
class Button extends Component { render() { returnHello,Webpack
} }
在使用了 React 的項目里 JSX 和 Class 語法并不是必須的,但使用新語法寫出的代碼看上去更優(yōu)雅。
其中 JSX 語法是無法在任何現(xiàn)有的 JavaScript 引擎中運行的,所以在構(gòu)建過程中需要把源碼轉(zhuǎn)換成可以運行的代碼,例如:
// 原 JSX 語法代碼 returnReact 與 BabelHello,Webpack
// 被轉(zhuǎn)換成正常的 JavaScript 代碼 return React.createElement("h1", null, "Hello,Webpack")
要在使用 Babel 的項目中接入 React 框架是很簡單的,只需要加入 React 所依賴的 Presets babel-preset-react。
通過以下命令:
# 安裝 React 基礎(chǔ)依賴 npm i -D react react-dom # 安裝 babel 完成語法轉(zhuǎn)換所需依賴 npm i -D babel-preset-react
安裝新的依賴后,再修改 .babelrc 配置文件加入 React Presets
"presets": [ "react" ],
就完成了一切準備工作。
再修改 main.js 文件如下:
import * as React from "react"; import { Component } from "react"; import { render } from "react-dom"; class Button extends Component { render() { returnHello,Webpack
} } render(, window.document.getElementById("app"));
重新執(zhí)行構(gòu)建打開網(wǎng)頁你將會發(fā)現(xiàn)由 React 渲染出來的 Hello,Webpack。
React 與 TypeScriptTypeScript 相比于 Babel 的優(yōu)點在于它原生支持 JSX 語法,你不需要重新安裝新的依賴,只需修改一行配置。 但 TypeScript 的不同在于:
使用了 JSX 語法的文件后綴必須是 tsx。
由于 React 不是采用 TypeScript 編寫的,需要安裝 react 和 react-dom 對應(yīng)的 TypeScript 接口描述模塊 @types/react 和 @types/react-dom 后才能通過編譯。
修改 TypeScript 編譯器配置文件 tsconfig.json 增加對 JSX 語法的支持,如下:
{ "compilerOptions": { "jsx": "react" // 開啟 jsx ,支持 React } }
由于 main.js 文件中存在 JSX 語法,再把 main.js 文件重命名為 main.tsx,同時修改文件內(nèi)容為在上面 React 與 Babel 里所采用的 React 代碼。 同時為了讓 Webpack 對項目里的 ts 與 tsx 原文件都采用 awesome-typescript-loader 去轉(zhuǎn)換, 需要注意的是 Webpack Loader 配置的 test 選項需要匹配到 tsx 類型的文件,并且 extensions 中也要加上 .tsx,配置如下:
module.exports = { // TS 執(zhí)行入口文件 entry: "./main", output: { filename: "bundle.js", path: path.resolve(__dirname, "./dist"), }, resolve: { // 先嘗試 ts,tsx 后綴的 TypeScript 源碼文件 extensions: [".ts", ".tsx", ".js",] }, module: { rules: [ { // 同時匹配 ts,tsx 后綴的 TypeScript 源碼文件 test: /.tsx?$/, loader: "awesome-typescript-loader" } ] }, devtool: "source-map",// 輸出 Source Map 方便在瀏覽器里調(diào)試 TypeScript 代碼 };
通過npm i react react-dom @types/react @types/react-dom安裝新的依賴后重啟構(gòu)建,重新打開網(wǎng)頁你將會發(fā)現(xiàn)由 React 渲染出來的 Hello,Webpack。
使用Vue框架Vue是一個漸進式的 MVVM 框架,相比于 React、Angular 它更靈活輕量。 它不會強制性地內(nèi)置一些功能和語法,你可以根據(jù)自己的需要一點點地添加功能。 雖然采用 Vue 的項目能用可直接運行在瀏覽器環(huán)境里的代碼編寫,但為了方便編碼大多數(shù)項目都會采用 Vue 官方的單文件組件的寫法去編寫項目。
Vue 的單文件組件通過一個類似 HTML 文件的 .vue 文件就能描述清楚一個組件所需的模版、樣式、邏輯。
main.js 入口文件:
import Vue from "vue" import App from "./App.vue" new Vue({ el: "#app", render: h => h(App) });
入口文件創(chuàng)建一個 Vue 的根實例,在 ID 為 app 的 DOM 節(jié)點上渲染出上面定義的 App 組件。
接入 Webpack目前最成熟和流行的開發(fā) Vue 項目的方式是采用 ES6 加 Babel 轉(zhuǎn)換,這和基本的采用 ES6 開發(fā)的項目很相似,差別在于要解析 .vue 格式的單文件組件。 好在 Vue 官方提供了對應(yīng)的 vue-loader 可以非常方便的完成單文件組件的轉(zhuǎn)換。
修改 Webpack 相關(guān)配置如下:
module: { rules: [ { test: /.vue$/, use: ["vue-loader"], }, ] }
安裝新引入的依賴:
# Vue 框架運行需要的庫 npm i -S vue # 構(gòu)建所需的依賴 npm i -D vue-loader css-loader vue-template-compiler
在這些依賴中,它們的作用分別是:
vue-loader:解析和轉(zhuǎn)換 .vue 文件,提取出其中的邏輯代碼 script、樣式代碼 style、以及 HTML 模版 template,再分別把它們交給對應(yīng)的 Loader 去處理。
css-loader:加載由 vue-loader 提取出的 CSS 代碼。
vue-template-compiler:把 vue-loader 提取出的 HTML 模版編譯成對應(yīng)的可執(zhí)行的 JavaScript 代碼,這和 React 中的 JSX 語法被編譯成 JavaScript 代碼類似。預(yù)先編譯好 HTML 模版相對于在瀏覽器中再去編譯 HTML 模版的好處在于性能更好。
使用 TypeScript 編寫 Vue 應(yīng)用從 Vue 2.5.0+ 版本開始,提供了對 TypeScript 的良好支持,使用 TypeScript 編寫 Vue 是一個很好的選擇,因為 TypeScript 能檢查出一些潛在的錯誤。
新增 tsconfig.json 配置文件,內(nèi)容如下:
{ "compilerOptions": { // 構(gòu)建出 ES5 版本的 JavaScript,與 Vue 的瀏覽器支持保持一致 "target": "es5", // 開啟嚴格模式,這可以對 `this` 上的數(shù)據(jù)屬性進行更嚴格的推斷 "strict": true, // TypeScript 編譯器輸出的 JavaScript 采用 es2015 模塊化,使 Tree Shaking 生效 "module": "es2015", "moduleResolution": "node" } }
修改 App.vue 腳本部分內(nèi)容如下:
注意 script 標簽中的 lang="ts" 是為了指明代碼的語法是 TypeScript。
修改 main.ts 執(zhí)行入口文件為如下:
import Vue from "vue" import App from "./App.vue" new Vue({ el: "#app", render: h => h(App) });
由于 TypeScript 不認識 .vue 結(jié)尾的文件,為了讓其支持 import App from "./App.vue" 導(dǎo)入語句,還需要以下文件 vue-shims.d.ts 去定義 .vue 的類型:
// 告訴 TypeScript 編譯器 .vue 文件其實是一個 Vue declare module "*.vue" { import Vue from "vue"; export default Vue; }
Webpack 配置需要修改兩個地方,如下:
const path = require("path"); module.exports = { resolve: { // 增加對 TypeScript 的 .ts 和 .vue 文件的支持 extensions: [".ts", ".js", ".vue", ".json"], }, module: { rules: [ // 加載 .ts 文件 { test: /.ts$/, loader: "ts-loader", exclude: /node_modules/, options: { // 讓 tsc 把 vue 文件當(dāng)成一個 TypeScript 模塊去處理,以解決 moudle not found 的問題,tsc 本身不會處理 .vue 結(jié)尾的文件 appendTsSuffixTo: [/.vue$/], } }, ] }, };
除此之外還需要安裝新引入的依賴:npm i -D ts-loader typescript
為單頁應(yīng)用生成HTML 引入問題在使用 React 框架中,是用最簡單的 Hello,Webpack 作為例子讓大家理解, 這個例子里因為只輸出了一個 bundle.js 文件,所以手寫了一個 index.html 文件去引入這個 bundle.js,才能讓應(yīng)用在瀏覽器中運行起來。
在實際項目中遠比這復(fù)雜,一個頁面常常有很多資源要加載。接下來舉一個實戰(zhàn)中的例子,要求如下:
項目采用 ES6 語言加 React 框架。
給頁面加入 Google Analytics,這部分代碼需要內(nèi)嵌進 HEAD 標簽里去。
給頁面加入 Disqus 用戶評論,這部分代碼需要異步加載以提升首屏加載速度。
壓縮和分離 JavaScript 和 CSS 代碼,提升加載速度。
在開始前先來看看該應(yīng)用最終發(fā)布到線上的代碼。
可以看到部分代碼被內(nèi)嵌進了 HTML 的 HEAD 標簽中,部分文件的文件名稱被打上根據(jù)文件內(nèi)容算出的 Hash 值,并且加載這些文件的 URL 地址也被正常的注入到了 HTML 中。
解決方案推薦一個用于方便地解決以上問題的 Webpack 插件 web-webpack-plugin。 該插件已經(jīng)被社區(qū)上許多人使用和驗證,解決了大家的痛點獲得了很多好評,下面具體介紹如何用它來解決上面的問題。
首先,修改 Webpack 配置。
以上配置中,大多數(shù)都是按照前面已經(jīng)講過的內(nèi)容增加的配置,例如:
增加對 CSS 文件的支持,提取出 Chunk 中的 CSS 代碼到多帶帶的文件中,壓縮 CSS 文件;
定義 NODE_ENV 環(huán)境變量為 production,以去除源碼中只有開發(fā)時才需要的部分;
給輸出的文件名稱加上 Hash 值;
壓縮輸出的 JavaScript 代碼。
但最核心的部分在于 plugins 里的:
new WebPlugin({ template: "./template.html", // HTML 模版文件所在的文件路徑 filename: "index.html" // 輸出的 HTML 的文件名稱 })
其中 template: "./template.html" 所指的模版文件 template.html 的內(nèi)容是:
該文件描述了哪些資源需要被以何種方式加入到輸出的 HTML 文件中。
以 為例,按照正常引入 CSS 文件一樣的語法來引入 Webpack 生產(chǎn)的代碼。href 屬性中的 app?_inline 可以分為兩部分,前面的 app 表示 CSS 代碼來自名叫 app 的 Chunk 中,后面的 _inline 表示這些代碼需要被內(nèi)嵌到這個標簽所在的位置。
同樣的 表示 JavaScript 代碼來自相對于當(dāng)前模版文件 template.html 的本地文件 ./google_analytics.js, 而且文件中的 JavaScript 代碼也需要被內(nèi)嵌到這個標簽所在的位置。
也就是說資源鏈接 URL 字符串里問號前面的部分表示資源內(nèi)容來自哪里,后面的 querystring 表示這些資源注入的方式。
除了 _inline 表示內(nèi)嵌外,還支持以下屬性:
_dist 只有在生產(chǎn)環(huán)境下才引入該資源;
_dev 只有在開發(fā)環(huán)境下才引入該資源;
_ie 只有IE瀏覽器才需要引入的資源,通過 [if IE]>resource 注釋實現(xiàn)。
這些屬性之間可以搭配使用,互不沖突。例如 app?_inline&_dist 表示只在生產(chǎn)環(huán)境下才引入該資源,并且需要內(nèi)嵌到 HTML 里去。
WebPlugin 插件還支持一些其它更高級的用法,詳情可以訪問該項目主頁閱讀文檔。
管理多個單頁應(yīng)用 引入問題在開始前先來看看該應(yīng)用最終發(fā)布到線上的代碼。
構(gòu)建出的目錄結(jié)構(gòu)為:
dist ├── common_029086ff.js ├── common_7cc98ad0.css ├── index.html ├── index_04c08fbf.css ├── index_b3d3761c.js ├── login.html ├── login_0a3feca9.js └── login_e31e214b.css
如果按照上節(jié)的思路,可能需要為每個單頁應(yīng)用配置一段如下代碼:
new WebPlugin({ template: "./template.html", // HTML 模版文件所在的文件路徑 filename: "login.html" // 輸出的 HTML 的文件名稱 })
并且把頁面對應(yīng)的入口加入到 enrty 配置項中,就像這樣:
entry: { index: "./pages/index/index.js",// 頁面 index.html 的入口文件 login: "./pages/login/index.js",// 頁面 login.html 的入口文件 }
當(dāng)有新頁面加入時就需要修改 Webpack 配置文件,新插入一段以上代碼,這會導(dǎo)致構(gòu)建代碼難以維護而且易錯。
解決方案項目源碼目錄結(jié)構(gòu)如下:
├── pages │ ├── index │ │ ├── index.css // 該頁面多帶帶需要的 CSS 樣式 │ │ └── index.js // 該頁面的入口文件 │ └── login │ ├── index.css │ └── index.js ├── common.css // 所有頁面都需要的公共 CSS 樣式 ├── google_analytics.js ├── template.html └── webpack.config.js
從目錄結(jié)構(gòu)中可以看成出下幾點要求:
所有單頁應(yīng)用的代碼都需要放到一個目錄下,例如都放在 pages 目錄下;
一個單頁應(yīng)用一個多帶帶的文件夾,例如最后生成的 index.html 相關(guān)的代碼都在 index 目錄下,login.html 同理;
每個單頁應(yīng)用的目錄下都有一個 index.js 文件作為入口執(zhí)行文件。
雖然 AutoWebPlugin 強制性的規(guī)定了項目部分的目錄結(jié)構(gòu),但從實戰(zhàn)經(jīng)驗來看這是一種優(yōu)雅的目錄規(guī)范,合理的拆分了代碼,又能讓新人快速的看懂項目結(jié)構(gòu),也方便日后的維護。
Webpack 配置文件修改如下:
See the Pen webpack管理多個單頁應(yīng)用 by whjin (@whjin) on CodePen.