github:github.com/fenivana/we…javascript
webpack 更新到了 4.0,官網尚未更新文檔。所以把教程更新一下,方便你們用起 webpack 4。php
先說說爲何要寫這篇文章,最初的緣由是組裏的小朋友們看了 webpack 文檔後,表情都是這樣的:摘自 webpack 一篇文檔的評論區)css
和這樣的:html
是的,即便是外國佬也在吐槽這文檔不是人能看的。回想起當年本身啃 webpack 文檔的血與淚的往事,以爲有必要整一個教程,可讓你們看完後愉悅地搭建起一個 webpack 打包方案的項目。前端
官網新的 webpack 文檔如今寫的很詳細了,能看英文的小夥伴能夠直接去看官網。vue
可能會有人問 webpack 到底有什麼用,你不能上來就糊我一臉代碼讓我立刻搞,我照着搞了一遍結果根本沒什麼用,都是騙人的。因此,在說 webpack 以前,我想先談一下前端打包方案這幾年的演進歷程,在什麼場景下,咱們遇到了什麼問題,催生出了應對這些問題的工具。瞭解了需求和目的以後,你就知道何時 webpack 能夠幫到你。我但願我用完以後很爽,大家用完以後也是。java
在很長的一段前端歷史裏,是不存在打包這個說法的。那個時候頁面基本是純靜態的或者服務端輸出的,沒有 AJAX,也沒有 jQuery。那個時候的 JavaScript 就像個玩具,用處大概就是在側欄弄個時鐘,用 media player 放個 mp3 之類的腳本,代碼量不是不少,直接放在 <script>
標籤裏或者弄個 js 文件引一下就行,日子過得很輕鬆愉快。node
隨後的幾年,人們開始嘗試在一個頁面裏作更多的事情。容器的顯示,隱藏,切換。用 css 寫的彈層,圖片輪播等等。但若是一個頁面內不能向服務器請求數據,能作的事情畢竟有限的,代碼的量也能維持在頁面交互邏輯範圍內。這時候不少人開始突破一個頁面能作的事情的範圍,使用隱藏的 iframe 和 flash 等做爲和服務器通訊的橋樑,新世界的大門慢慢地被打開,在一個頁面內和服務器進行數據交互,意味着之前須要跳轉多個頁面的事情如今能夠用一個頁面搞定。但因爲 iframe 和 flash 技術過於 tricky 和複雜,並沒能獲得普遍的推廣。python
直到 Google 推出 Gmail 的時候(2004 年),人們意識到了一個被忽略的接口,XMLHttpRequest, 也就是咱們俗稱的 AJAX, 這是一個使用方便的,兼容性良好的服務器通訊接口。今後開始,咱們的頁面開始玩出各類花來了,前端一會兒出現了各類各樣的庫,Prototype、Dojo、MooTools、Ext JS、jQuery…… 咱們開始往頁面裏插入各類庫和插件,咱們的 js 文件也就爆炸了。react
隨着 js 能作的事情愈來愈多,引用愈來愈多,文件愈來愈大,加上當時大約只有 2Mbps 左右的網速,下載速度還不如 3G 網絡,對 js 文件的壓縮和合並的需求愈來愈強烈,固然這裏面也有把代碼混淆了不容易被盜用等其餘因素在裏面。JSMin、YUI Compressor、Closure Compiler、UglifyJS 等 js 文件壓縮合並工具陸陸續續誕生了。壓縮工具是有了,但咱們得要執行它,最簡單的辦法呢,就是 windows 上搞個 bat 腳本,mac / linux 上搞個 bash 腳本,哪幾個文件要合併在一塊的,哪幾個要壓縮的,發佈的時候運行一下腳本,生成壓縮後的文件。
基於合併壓縮技術,項目越作越大,問題也愈來愈多,大概就是如下這些問題:
剛好就在這個時候(2009 年),隨着後端 JavaScript 技術的發展,人們提出了 CommonJS 的模塊化規範,大概的語法是: 若是 a.js
依賴 b.js
和 c.js
, 那麼就在 a.js
的頭部,引入這些依賴文件:
var b = require('./b')
var c = require('./c')
複製代碼
那麼變量 b
和 c
會是什麼呢?那就是 b.js 和 c.js 導出的東西,好比 b.js 能夠這樣導出:
exports.square = function(num) {
return num * num
}
複製代碼
而後就能夠在 a.js 使用這個 square
方法:
var n = b.square(2)
複製代碼
若是 c.js 依賴 d.js, 導出的是一個 Number
, 那麼能夠這樣寫:
var d = require('./d')
module.exports = d.PI // 假設 d.PI 的值是 3.14159
複製代碼
那麼 a.js 中的變量 c
就是數字 3.14159
,具體的語法規範能夠查看 Node.js 的 文檔。
可是 CommonJS 在瀏覽器內並不適用。由於 require()
的返回是同步的,意味着有多個依賴的話須要一個一個依次下載,堵塞了 js 腳本的執行。因此人們就在 CommonJS 的基礎上定義了 Asynchronous Module Definition (AMD) 規範(2011 年),使用了異步回調的語法來並行下載多個依賴項,好比做爲入口的 a.js 能夠這樣寫:
require(['./b', './c'], function(b, c) {
var n = b.square(2)
console.log(c)
})
複製代碼
相應的導出語法也是異步回調方式,好比 c.js
依賴 d.js
, 就寫成這樣:
define(['./d'], function(d) {
return d.PI
})
複製代碼
能夠看到,定義一個模塊是使用 define()
函數,define()
和 require()
的區別是,define()
必需要在回調函數中返回一個值做爲導出的東西,require()
不須要導出東西,所以回調函數中不須要返回值,也沒法做爲被依賴項被其餘文件導入,所以通常用於入口文件,好比頁面中這樣加載 a.js
:
<script src="js/require.js" data-main="js/a"></script>
複製代碼
以上是 AMD 規範的基本用法,更詳細的就很少說了(反正也淘汰了~),有興趣的能夠看 這裏。
js 模塊化問題基本解決了,css 和 html 也沒閒着。什麼 less,sass,stylus 的 css 預處理器橫空出世,說能幫咱們簡化 css 的寫法,自動給你加 vendor prefix。html 在這期間也出現了一堆模板語言,什麼 handlebars,ejs,jade,能夠把 ajax 拿到的數據插入到模板中,而後用 innerHTML 顯示到頁面上。
託 AMD 和 CSS 預處理和模板語言的福,咱們的編譯腳本也洋洋灑灑寫了百來行。命令行腳本有個很差的地方,就是 windows 和 mac/linux 是不通用的,若是有跨平臺需求的話,windows 要裝個能夠執行 bash 腳本的命令行工具,好比 msys(目前最新的是 msys2),或者使用 php 或 python 等其餘語言的腳原本編寫,對於非全棧型的前端程序員來講,寫 bash / php / python 仍是很生澀的。所以咱們須要一個簡單的打包工具,能夠利用各類編譯工具,編譯 / 壓縮 js、css、html、圖片等資源。而後 Grunt 產生了(2012 年),配置文件格式是咱們最愛的 js,寫法也很簡單,社區有很是多的插件支持各類編譯、lint、測試工具。一年多後另外一個打包工具 gulp 誕生了,擴展性更強,採用流式處理效率更高。
依託 AMD 模塊化編程,SPA(Single-page application) 的實現方式更爲簡單清晰,一個網頁再也不是傳統的相似 word 文檔的頁面,而是一個完整的應用程序。SPA 應用有一個總的入口頁面,咱們一般把它命名爲 index.html、app.html、main.html,這個 html 的 <body>
通常是空的,或者只有總的佈局(layout),好比下圖:
佈局會把 header、nav、footer 的內容填上,但 main 區域是個空的容器。這個做爲入口的 html 最主要的工做是加載啓動 SPA 的 js 文件,而後由 js 驅動,根據當前瀏覽器地址進行路由分發,加載對應的 AMD 模塊,而後該 AMD 模塊執行,渲染對應的 html 到頁面指定的容器內(好比圖中的 main)。在點擊連接等交互時,頁面不會跳轉,而是由 js 路由加載對應的 AMD 模塊,而後該 AMD 模塊渲染對應的 html 到容器內。
雖然 AMD 模塊讓 SPA 更容易地實現,但小問題仍是不少的:
shim
,很麻煩。<img>
的路徑是個問題,須要使用絕對路徑而且保持打包後的圖片路徑和打包前的路徑不變,或者使用 html 模板語言把 src
寫成變量,在運行時生成。到了這裏,咱們的主角 webpack 登場了(2012 年)(此處應有掌聲)。
和 webpack 差很少同期登場的還有 Browserify。這裏簡單介紹一下 Browserify。Browserify 的目的是讓前端也能用 CommonJS 的語法 require('module')
來加載 js。它會從入口 js 文件開始,把全部的 require()
調用的文件打包合併到一個文件,這樣就解決了異步加載的問題。那麼 Browserify 有什麼不足之處致使我不推薦使用它呢? 主要緣由有下面幾點:
require()
js 模塊的問題,其餘文件不是它關心的部分。好比 html 文件裏的 img 標籤,它只能轉成 Data URI 的形式,而不能替換爲打包後的路徑。基於以上幾點,Browserify 並非一個理想的選擇。那麼 webpack 是否解決了以上的幾個問題呢? 廢話,否則介紹它幹嗎。那麼下面章節咱們用實戰的方式來講明 webpack 是怎麼解決上述的問題的。
一上來步子太大容易扯到蛋,讓咱們先弄個最簡單的 webpack 配置來熱一下身。
webpack 是基於我大 Node.js 的打包工具,上來第一件事天然是先安裝 Node.js 了,傳送門 ->。
咱們先隨便找個地方,建一個文件夾叫 simple
, 而後在這裏面搭項目。完成品在 examples/simple 目錄,你們搞的時候能夠參照一下。咱們先看一下目錄結構:
├── dist 打包輸出目錄,只需部署這個目錄到生產環境
├── package.json 項目配置信息
├── node_modules npm 安裝的依賴包都在這裏面
├── src 咱們的源代碼
│ ├── components 能夠複用的模塊放在這裏面
│ ├── index.html 入口 html
│ ├── index.js 入口 js
│ ├── shared 公共函數庫
│ └── views 頁面放這裏
└── webpack.config.js webpack 配置文件
複製代碼
打開命令行窗口,cd
到剛纔建的 simple 目錄。而後執行這個命令初始化項目:
npm init
複製代碼
命令行會要你輸入一些配置信息,咱們這裏一路按回車下去,生成一個默認的項目配置文件 package.json
。
咱們安裝 eslint, 用來檢查語法報錯,當咱們書寫 js 時,有錯誤的地方會出現提示。
npm install eslint eslint-config-enough eslint-loader --save-dev
複製代碼
npm install
能夠一條命令同時安裝多個包,包之間用空格分隔。包會被安裝進 node_modules
目錄中。
--save-dev
會把安裝的包和版本號記錄到 package.json
中的 devDependencies
對象中,還有一個 --save
, 會記錄到 dependencies
對象中,它們的區別,咱們能夠先簡單的理解爲打包工具和測試工具用到的包使用 --save-dev
存到 devDependencies
, 好比 eslint、webpack。瀏覽器中執行的 js 用到的包存到 dependencies
, 好比 jQuery 等。那麼它們用來幹嗎的?
由於有些 npm 包安裝是須要編譯的,那麼致使 windows / mac /linux 上編譯出的可執行文件是不一樣的,也就是沒法通用,所以咱們在提交代碼到 git 上去的時候,通常都會在 .gitignore
裏指定忽略 node_modules 目錄和裏面的文件,這樣其餘人從 git 上拉下來的項目是沒有 node_modules 目錄的,這時咱們須要運行
npm install
複製代碼
它會讀取 package.json
中的 devDependencies
和 dependencies
字段,把記錄的包的相應版本下載下來。
這裏 eslint-config-enough 是配置文件,它規定了代碼規範,要使它生效,咱們要在 package.json
中添加內容:
{
"eslintConfig": {
"extends": "enough",
"env": {
"browser": true,
"node": true
}
}
}
複製代碼
業界最有名的語法規範是 airbnb 出品的,但它規定的太死板了,好比不容許使用 for-of
和 for-in
等。感興趣的同窗能夠參照 這裏 安裝使用。
eslint-loader 用於在 webpack 編譯的時候檢查代碼,若是有錯誤,webpack 會報錯。
項目裏安裝了 eslint 還沒用,咱們的 IDE 和編輯器也得要裝 eslint 插件支持它。
Visual Studio Code 須要安裝 ESLint 擴展
atom 須要安裝 linter 和 linter-eslint 這兩個插件,裝好後重啓生效。
WebStorm 須要在設置中打開 eslint 開關:
咱們寫一個最簡單的 SPA 應用來介紹 SPA 應用的內部工做原理。首先,創建 src/index.html 文件,內容以下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
</body>
</html>
複製代碼
它是一個空白頁面,注意這裏咱們不須要本身寫 <script src="index.js"></script>
, 由於打包後的文件名和路徑可能會變,因此咱們用 webpack 插件幫咱們自動加上。
src/index.js:
// 引入 router
import router from './router'
// 啓動 router
router.start()
複製代碼
src/router.js:
// 引入頁面文件
import foo from './views/foo'
import bar from './views/bar'
const routes = {
'/foo': foo,
'/bar': bar
}
// Router 類,用來控制頁面根據當前 URL 切換
class Router {
start() {
// 點擊瀏覽器後退 / 前進按鈕時會觸發 window.onpopstate 事件,咱們在這時切換到相應頁面
// https://developer.mozilla.org/en-US/docs/Web/Events/popstate
window.addEventListener('popstate', () => {
this.load(location.pathname)
})
// 打開頁面時加載當前頁面
this.load(location.pathname)
}
// 前往 path,變動地址欄 URL,並加載相應頁面
go(path) {
// 變動地址欄 URL
history.pushState({}, '', path)
// 加載頁面
this.load(path)
}
// 加載 path 路徑的頁面
load(path) {
// 首頁
if (path === '/') path = '/foo'
// 建立頁面實例
const view = new routes[path]()
// 調用頁面方法,把頁面加載到 document.body 中
view.mount(document.body)
}
}
// 導出 router 實例
export default new Router()
複製代碼
src/views/foo/index.js:
// 引入 router
import router from '../../router'
// 引入 html 模板,會被做爲字符串引入
import template from './index.html'
// 引入 css, 會生成 <style> 塊插入到 <head> 頭中
import './style.css'
// 導出類
export default class {
mount(container) {
document.title = 'foo'
container.innerHTML = template
container.querySelector('.foo__gobar').addEventListener('click', () => {
// 調用 router.go 方法加載 /bar 頁面
router.go('/bar')
})
}
}
複製代碼
src/views/bar/index.js:
// 引入 router
import router from '../../router'
// 引入 html 模板,會被做爲字符串引入
import template from './index.html'
// 引入 css, 會生成 <style> 塊插入到 <head> 頭中
import './style.css'
// 導出類
export default class {
mount(container) {
document.title = 'bar'
container.innerHTML = template
container.querySelector('.bar__gofoo').addEventListener('click', () => {
// 調用 router.go 方法加載 /foo 頁面
router.go('/foo')
})
}
}
複製代碼
藉助 webpack 插件,咱們能夠 import
html, css 等其餘格式的文件,文本類的文件會被儲存爲變量打包進 js 文件,其餘二進制類的文件,好比圖片,能夠本身配置,小圖片做爲 Data URI 打包進 js 文件,大文件打包爲單獨文件,咱們稍後再講這塊。
其餘的 src 目錄下的文件你們本身瀏覽,拷貝一份到本身的工做目錄,等會打包時會用到。
頁面代碼這樣就差很少搞定了,接下來咱們進入 webpack 的安裝和配置階段。如今咱們尚未講 webpack 配置因此頁面還沒法訪問,等會弄好 webpack 配置後再看頁面實際效果。
咱們把 webpack 和它的插件安裝到項目:
npm install webpack webpack-cli webpack-serve html-webpack-plugin html-loader css-loader style-loader file-loader url-loader --save-dev
複製代碼
webpack 即 webpack 核心庫。它提供了不少 API, 經過 Node.js 腳本中 require('webpack')
的方式來使用 webpack。
webpack-cli 是 webpack 的命令行工具。讓咱們能夠不用寫打包腳本,只需配置打包配置文件,而後在命令行輸入 webpack-cli --config webpack.config.js
來使用 webpack, 簡單不少。webpack 4 以前命令行工具是集成在 webpack 包中的,4.0 開始 webpack 包自己再也不集成 cli。
webpack-serve 是 webpack 提供的用來開發調試的服務器,讓你能夠用 http://127.0.0.1:8080/ 這樣的 url 打開頁面來調試,有了它就不用配置 nginx 了,方便不少。
html-webpack-plugin, html-loader, css-loader, style-loader 等看名字就知道是打包 html 文件,css 文件的插件,你們在這裏可能會有疑問,html-webpack-plugin
和 html-loader
有什麼區別,css-loader
和 style-loader
有什麼區別,咱們等會看配置文件的時候再講。
file-loader 和 url-loader 是打包二進制文件的插件,具體也在配置文件章節講解。
接下來,爲了能讓不支持 ES6 的瀏覽器 (好比 IE) 也能照常運行,咱們須要安裝 babel, 它會把咱們寫的 ES6 源代碼轉化成 ES5,這樣咱們源代碼寫 ES6,打包時生成 ES5。
npm install babel-core babel-preset-env babel-loader --save-dev
複製代碼
這裏 babel-core
顧名思義是 babel 的核心編譯器。babel-preset-env 是一個配置文件,咱們可使用這個配置文件轉換 ES2015/ES2016/ES2017 到 ES5,是的,不僅 ES6 哦。babel 還有 其餘配置文件。
光安裝了 babel-preset-env
,在打包時是不會生效的,須要在 package.json
加入 babel
配置:
{
"babel": {
"presets": ["env"]
}
}
複製代碼
打包時 babel 會讀取 package.json
中 babel
字段的內容,而後執行相應的轉換。
babel-loader 是 webpack 的插件,咱們下面章節再說。
包都裝好了,接下來總算能夠進入正題了。咱們來建立 webpack 配置文件 webpack.config.js
,注意這個文件是在 node.js 中運行的,所以不支持 ES6 的 import
語法。咱們來看文件內容:
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const history = require('connect-history-api-fallback')
const convert = require('koa-connect')
// 使用 WEBPACK_SERVE 環境變量檢測當前是不是在 webpack-server 啓動的開發環境中
const dev = Boolean(process.env.WEBPACK_SERVE)
module.exports = {
/* webpack 執行模式 development:開發環境,它會在配置文件中插入調試相關的選項,好比 moduleId 使用文件路徑方便調試 production:生產環境,webpack 會將代碼作壓縮等優化 */
mode: dev ? 'development' : 'production',
/* 配置 source map 開發模式下使用 cheap-module-eval-source-map, 生成的 source map 能和源碼每行對應,方便打斷點調試 生產模式下使用 hidden-source-map, 生成獨立的 source map 文件,而且不在 js 文件中插入 source map 路徑,用於在 error report 工具中查看 (好比 Sentry) */
devtool: dev ? 'cheap-module-eval-source-map' : 'hidden-source-map',
// 配置頁面入口 js 文件
entry: './src/index.js',
// 配置打包輸出相關
output: {
// 打包輸出目錄
path: resolve(__dirname, 'dist'),
// 入口 js 的打包輸出文件名
filename: 'index.js'
},
module: {
/* 配置各類類型文件的加載器,稱之爲 loader webpack 當遇到 import ... 時,會調用這裏配置的 loader 對引用的文件進行編譯 */
rules: [
{
/* 使用 babel 編譯 ES6 / ES7 / ES8 爲 ES5 代碼 使用正則表達式匹配後綴名爲 .js 的文件 */
test: /\.js$/,
// 排除 node_modules 目錄下的文件,npm 安裝的包不須要編譯
exclude: /node_modules/,
/* use 指定該文件的 loader, 值能夠是字符串或者數組。 這裏先使用 eslint-loader 處理,返回的結果交給 babel-loader 處理。loader 的處理順序是從最後一個到第一個。 eslint-loader 用來檢查代碼,若是有錯誤,編譯的時候會報錯。 babel-loader 用來編譯 js 文件。 */
use: ['babel-loader', 'eslint-loader']
},
{
// 匹配 html 文件
test: /\.html$/,
/* 使用 html-loader, 將 html 內容存爲 js 字符串,好比當遇到 import htmlString from './template.html'; template.html 的文件內容會被轉成一個 js 字符串,合併到 js 文件裏。 */
use: 'html-loader'
},
{
// 匹配 css 文件
test: /\.css$/,
/* 先使用 css-loader 處理,返回的結果交給 style-loader 處理。 css-loader 將 css 內容存爲 js 字符串,而且會把 background, @font-face 等引用的圖片, 字體文件交給指定的 loader 打包,相似上面的 html-loader, 用什麼 loader 一樣在 loaders 對象中定義,等會下面就會看到。 */
use: ['style-loader', 'css-loader']
},
{
/* 匹配各類格式的圖片和字體文件 上面 html-loader 會把 html 中 <img> 標籤的圖片解析出來,文件名匹配到這裏的 test 的正則表達式, css-loader 引用的圖片和字體一樣會匹配到這裏的 test 條件 */
test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
/* 使用 url-loader, 它接受一個 limit 參數,單位爲字節(byte) 當文件體積小於 limit 時,url-loader 把文件轉爲 Data URI 的格式內聯到引用的地方 當文件大於 limit 時,url-loader 會調用 file-loader, 把文件儲存到輸出目錄,並把引用的文件路徑改寫成輸出後的路徑 好比 views/foo/index.html 中 <img src="smallpic.png"> 會被編譯成 <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAA..."> 而 <img src="largepic.png"> 會被編譯成 <img src="/f78661bef717cf2cc2c2e5158f196384.png"> */
use: [
{
loader: 'url-loader',
options: {
limit: 10000
}
}
]
}
]
},
/* 配置 webpack 插件 plugin 和 loader 的區別是,loader 是在 import 時根據不一樣的文件名,匹配不一樣的 loader 對這個文件作處理, 而 plugin, 關注的不是文件的格式,而是在編譯的各個階段,會觸發不一樣的事件,讓你能夠干預每一個編譯階段。 */
plugins: [
/* html-webpack-plugin 用來打包入口 html 文件 entry 配置的入口是 js 文件,webpack 以 js 文件爲入口,遇到 import, 用配置的 loader 加載引入文件 但做爲瀏覽器打開的入口 html, 是引用入口 js 的文件,它在整個編譯過程的外面, 因此,咱們須要 html-webpack-plugin 來打包做爲入口的 html 文件 */
new HtmlWebpackPlugin({
/* template 參數指定入口 html 文件路徑,插件會把這個文件交給 webpack 去編譯, webpack 按照正常流程,找到 loaders 中 test 條件匹配的 loader 來編譯,那麼這裏 html-loader 就是匹配的 loader html-loader 編譯後產生的字符串,會由 html-webpack-plugin 儲存爲 html 文件到輸出目錄,默認文件名爲 index.html 能夠經過 filename 參數指定輸出的文件名 html-webpack-plugin 也能夠不指定 template 參數,它會使用默認的 html 模板。 */
template: './src/index.html',
/* 由於和 webpack 4 的兼容性問題,chunksSortMode 參數須要設置爲 none https://github.com/jantimon/html-webpack-plugin/issues/870 */
chunksSortMode: 'none'
})
]
}
/* 配置開發時用的服務器,讓你能夠用 http://127.0.0.1:8080/ 這樣的 url 打開頁面來調試 而且帶有熱更新的功能,打代碼時保存一下文件,瀏覽器會自動刷新。比 nginx 方便不少 若是是修改 css, 甚至不須要刷新頁面,直接生效。這讓像彈框這種須要點擊交互後纔會出來的東西調試起來方便不少。 由於 webpack-cli 沒法正確識別 serve 選項,使用 webpack-cli 執行打包時會報錯。 所以咱們在這裏判斷一下,僅當使用 webpack-serve 時插入 serve 選項。 issue:https://github.com/webpack-contrib/webpack-serve/issues/19 */
if (dev) {
module.exports.serve = {
// 配置監聽端口,默認值 8080
port: 8080,
// add: 用來給服務器的 koa 實例注入 middleware 增長功能
add: app => {
/* 配置 SPA 入口 SPA 的入口是一個統一的 html 文件,好比 http://localhost:8080/foo 咱們要返回給它 http://localhost:8080/index.html 這個文件 */
app.use(convert(history()))
}
}
}
複製代碼
配置 OK 了,接下來咱們就運行一下吧。咱們先試一下開發環境用的 webpack-serve
:
./node_modules/.bin/webpack-serve webpack.config.js
複製代碼
執行時須要指定配置文件。
上面的命令適用於 Mac / Linux 等 * nix 系統,也適用於 Windows 上的 PowerShell 和 bash/zsh 環境(Windows Subsystem for Linux, Git Bash、Babun、MSYS2 等)。安利一下 Windows 同窗使用 Ubuntu on Windows,能夠避免不少跨平臺的問題,好比設置環境變量。
若是使用 Windows 的 cmd.exe,請執行:
node_modules\.bin\webpack-serve webpack.config.js
複製代碼
npm 會把包的可執行文件安裝到 ./node_modules/.bin/
目錄下,因此咱們要在這個目錄下執行命令。
命令執行後,控制檯顯示:
「wdm」: Compiled successfully。
複製代碼
這就表明編譯成功了,咱們能夠在瀏覽器打開 http://localhost:8080/
看看效果。若是有報錯,那多是什麼地方沒弄對?請本身仔細檢查一下~
咱們能夠隨意更改一下 src 目錄下的源代碼,保存後,瀏覽器裏的頁面應該很快會有相應變化。
要退出編譯,按 ctrl+c
。
開發環境編譯試過以後,咱們試試看編譯生產環境的代碼,命令是:
./node_modules/.bin/webpack-cli
複製代碼
不須要指定配置文件,默認讀取 webpack.config.js
執行腳本的命令有點麻煩,所以,咱們能夠利用 npm,把命令寫在 package.json
中:
{
"scripts": {
"dev": "webpack-serve webpack.config.js",
"build": "webpack-cli"
}
}
複製代碼
package.json
中的 scripts
對象,能夠用來寫一些腳本命令,命令不須要前綴目錄 ./node_modules/.bin/
,npm 會自動尋找該目錄下的命令。咱們能夠執行:
npm run dev
複製代碼
來啓動開發環境。
執行
npm run build
複製代碼
來打包生產環境的代碼。
上面的項目雖然能夠跑起來了,但有幾個點咱們尚未考慮到:
那麼,讓咱們在上面的配置的基礎上繼續完善,下面的代碼咱們只寫出改變的部分。代碼在 examples/advanced 目錄。
如今咱們的資源文件的 url 直接在根目錄,好比 http://127.0.0.1:8080/index.js
, 這樣作緩存控制和 CDN 不是很方便,所以咱們給資源文件的 url 加一個前綴,好比 http://127.0.0.1:8080/assets/index.js
. 咱們來修改一下 webpack 配置:
{
output: {
publicPath: '/assets/'
}
}
複製代碼
webpack-serve
也須要修改:
if (dev) {
module.exports.serve = {
port: 8080,
host: '0.0.0.0',
dev: {
/* 指定 webpack-dev-middleware 的 publicpath 通常狀況下與 output.publicPath 保持一致(除非 output.publicPath 使用的是相對路徑) https://github.com/webpack/webpack-dev-middleware#publicpath */
publicPath: '/assets/'
},
add: app => {
app.use(convert(history({
index: '/assets/' // index.html 文件在 /assets/ 路徑下
})))
}
}
}
複製代碼
這樣瀏覽器只需加載當前頁面所需的代碼。
webpack 可使用異步加載文件的方式引用模塊,咱們使用 async/ await 和 dynamic import 來實現:
src/router.js:
// 將 async/await 轉換成 ES5 代碼後須要這個運行時庫來支持
import 'regenerator-runtime/runtime'
const routes = {
// import() 返回 promise
'/foo': () => import('./views/foo'),
'/bar.do': () => import('./views/bar.do')
}
class Router {
// ...
// 加載 path 路徑的頁面
// 使用 async/await 語法
async load(path) {
// 首頁
if (path === '/') path = '/foo'
// 動態加載頁面
const View = (await routes[path]()).default
// 建立頁面實例
const view = new View()
// 調用頁面方法,把頁面加載到 document.body 中
view.mount(document.body)
}
}
複製代碼
這樣咱們就不須要在開頭把全部頁面文件都 import 進來了。
由於 import()
尚未正式進入標準,須要使用 babel-preset-stage-2 來支持:
npm install babel-preset-stage-2 --save-dev
複製代碼
package.json
改一下:
{
"babel": {
"presets": [
"env",
"stage-2"
]
}
}
複製代碼
而後修改 webpack 配置:
{
output: {
/* 代碼中引用的文件(js、css、圖片等)會根據配置合併爲一個或多個包,咱們稱一個包爲 chunk。 每一個 chunk 包含多個 modules。不管是不是 js,webpack 都將引入的文件視爲一個 module。 chunkFilename 用來配置這個 chunk 輸出的文件名。 [chunkhash]:這個 chunk 的 hash 值,文件發生變化時該值也會變。使用 [chunkhash] 做爲文件名能夠防止瀏覽器讀取舊的緩存文件。 還有一個佔位符 [id],編譯時每一個 chunk 會有一個id。 咱們在這裏不使用它,由於這個 id 是個遞增的數字,增長或減小一個chunk,均可能致使其餘 chunk 的 id 發生改變,致使緩存失效。 */
chunkFilename: '[chunkhash].js',
}
}
複製代碼
這樣更新業務代碼時能夠藉助瀏覽器緩存,用戶不須要從新下載沒有發生變化的第三方庫。 Webpack 4 最大的改進即是自動拆分 chunk, 若是同時知足下列條件,chunk 就會被拆分:
通常狀況只需配置這幾個參數便可:
{
plugins: [
// ...
/* 使用文件路徑的 hash 做爲 moduleId。 雖然咱們使用 [chunkhash] 做爲 chunk 的輸出名,但仍然不夠。 由於 chunk 內部的每一個 module 都有一個 id,webpack 默認使用遞增的數字做爲 moduleId。 若是引入了一個新文件或刪掉一個文件,可能會致使其餘文件的 moduleId 也發生改變, 那麼受影響的 module 所在的 chunk 的 [chunkhash] 就會發生改變,致使緩存失效。 所以使用文件路徑的 hash 做爲 moduleId 來避免這個問題。 */
new webpack.HashedModuleIdsPlugin()
],
optimization: {
/* 上面提到 chunkFilename 指定了 chunk 打包輸出的名字,那麼文件名存在哪裏了呢? 它就存在引用它的文件中。這意味着一個 chunk 文件名發生改變,會致使引用這個 chunk 文件也發生改變。 runtimeChunk 設置爲 true, webpack 就會把 chunk 文件名所有存到一個單獨的 chunk 中, 這樣更新一個文件只會影響到它所在的 chunk 和 runtimeChunk,避免了引用這個 chunk 的文件也發生改變。 */
runtimeChunk: true,
splitChunks: {
/* 默認 entry 的 chunk 不會被拆分 由於咱們使用了 html-webpack-plugin 來動態插入 <script> 標籤,entry 被拆成多個 chunk 也能自動被插入到 html 中, 因此咱們能夠配置成 all, 把 entry chunk 也拆分了 */
chunks: 'all'
}
}
}
複製代碼
webpack 4 支持更多的手動優化,詳見: https://gist.github.com/sokra/1522d586b8e5c0f5072d7565c2bee693
但正如 webpack 文檔中所說,默認配置已經足夠優化,在沒有測試的狀況下不要盲目手動優化。
上面咱們提到了 chunkFilename
使用 [chunkhash]
防止瀏覽器讀取錯誤緩存,那麼 entry 一樣須要加上 hash。 但使用 webpack-serve
啓動開發環境時,entry 文件是沒有 [chunkhash]
的,用了會報錯。 所以咱們只在執行 webpack-cli
時使用 [chunkhash]
。
{
output: {
filename: dev ? '[name].js' : '[chunkhash].js'
}
}
複製代碼
這裏咱們使用了 [name]
佔位符。解釋它以前咱們先了解一下 entry
的完整定義:
{
entry: {
NAME: [FILE1, FILE2, ...]
}
}
複製代碼
咱們能夠定義多個 entry 文件,好比你的項目有多個 html 入口文件,每一個 html 對應一個或多個 entry 文件。 而後每一個 entry 能夠定義由多個 module 組成,這些 module 會依次執行。 在 webpack 4 以前,這是頗有用的功能,好比以前提到的第三方庫和業務代碼分開打包,在之前,咱們須要這麼配置:
{
entry {
main: './src/index.js',
vendor: ['jquery', 'lodash']
}
}
複製代碼
entry 引用文件的規則和 import
是同樣的,會尋找 node_modules
裏的包。而後結合 CommonsChunkPlugin
把 vendor 定義的 module 從業務代碼分離出來打包成一個單獨的 chunk。 若是 entry 是一個 module,咱們能夠不使用數組的形式。
在 simple 項目中,咱們配置了 entry: './src/index.js'
,這是最簡單的形式,轉換成完整的寫法就是:
{
entry: {
main: ['./src/index.js']
}
}
複製代碼
webpack 會給這個 entry 指定名字爲 main
。
看到這應該知道 [name]
的意思了吧?它就是 entry 的名字。
有人可能注意到官網文檔中還有一個 [hash]
佔位符,這個 hash 是整個編譯過程產生的一個總的 hash 值,而不是單個文件的 hash 值,項目中任何一個文件的改動,都會形成這個 hash 值的改變。[hash]
佔位符是始終存在的,但咱們不但願修改一個文件致使全部輸出的文件 hash 都改變,這樣就沒法利用瀏覽器緩存了。所以這個 [hash]
意義不大。
咱們注意到運行開發環境是命令行會報一段 warning:
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (250 kB).
This can impact web performance.
複製代碼
這是說建議每一個輸出的 js 文件的大小不要超過 250k。但開發環境由於包含了 sourcemap 而且代碼未壓縮因此通常都會超過這個大小,因此咱們能夠在開發環境把這個 warning 關閉。
webpack 配置中加入:
{
performance: {
hints: dev ? false : 'warning'
}
}
複製代碼
在 src 目錄中放一張 favicon.png,而後 src/index.html
的 <head>
中插入:
<link rel="icon" type="image/png" href="favicon.png">
複製代碼
修改 webpack 配置:
{
module: {
rules: [
{
test: /\.html$/,
use: [
{
loader: 'html-loader',
options: {
/* html-loader 接受 attrs 參數,表示什麼標籤的什麼屬性須要調用 webpack 的 loader 進行打包。 好比 <img> 標籤的 src 屬性,webpack 會把 <img> 引用的圖片打包,而後 src 的屬性值替換爲打包後的路徑。 使用什麼 loader 代碼,一樣是在 module.rules 定義中使用匹配的規則。 若是 html-loader 不指定 attrs 參數,默認值是 img:src, 意味着會默認打包 <img> 標籤的圖片。 這裏咱們加上 <link> 標籤的 href 屬性,用來打包入口 index.html 引入的 favicon.png 文件。 */
attrs: ['img:src', 'link:href']
}
}
]
},
{
/* 匹配 favicon.png 上面的 html-loader 會把入口 index.html 引用的 favicon.png 圖標文件解析出來進行打包 打包規則就按照這裏指定的 loader 執行 */
test: /favicon\.png$/,
use: [
{
// 使用 file-loader
loader: 'file-loader',
options: {
/* name:指定文件輸出名 [hash] 爲源文件的hash值,[ext] 爲後綴。 */
name: '[hash].[ext]'
}
}
]
},
// 圖片文件的加載配置增長一個 exclude 參數
{
test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
// 排除 favicon.png, 由於它已經由上面的 loader 處理了。若是不排除掉,它會被這個 loader 再處理一遍
exclude: /favicon\.png$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000
}
}
]
}
]
}
}
複製代碼
其實 html-webpack-plugin 接受一個 favicon
參數,能夠指定 favicon 文件路徑,會自動打包插入到 html 文件中。但它有個 bug,打包後的文件名路徑不帶 hash,就算有 hash,它也是 [hash],而不是 [chunkhash]。致使修改代碼也會改變 favicon 打包輸出的文件名。issue 中提到的 favicons-webpack-plugin 卻是能夠用,但它依賴 PhantomJS, 很是大。
const internalIp = require('internal-ip')
module.exports.serve = {
host: '0.0.0.0',
hot: {
host: {
client: internalIp.v4.sync(),
server: '0.0.0.0'
}
},
// ...
}
複製代碼
在多人開發時,每一個人可能須要有本身的配置,好比說 webpack-serve 監聽的端口號,若是寫死在 webpack 配置裏,而那個端口號在某個同窗的電腦上被其餘進程佔用了,簡單粗暴的修改 webpack.config.js
會致使提交代碼後其餘同窗的端口也被改掉。
還有一點就是開發環境、測試環境、生產環境的部分 webpack 配置是不一樣的,好比 publicPath
在生產環境可能要配置一個 CDN 地址。
咱們在根目錄創建一個文件夾 config
,裏面建立 3 個配置文件:
module.exports = {
publicPath: 'http://cdn.example.com/assets/'
}
複製代碼
module.exports = {
publicPath: '/assets/',
serve: {
port: 8090
}
}
複製代碼
const config = require('./dev')
config.serve.port = 8070
module.exports = config
複製代碼
package.json
修改 scripts
:
{
"scripts": {
"local": "npm run webpack-serve --config=local",
"dev": "npm run webpack-serve --config=dev",
"webpack-serve": "webpack-serve webpack.config.js",
"build": "webpack-cli"
}
}
複製代碼
webpack 配置修改:
// ...
const url = require('url')
const config = require('./config/' + (process.env.npm_config_config || 'default'))
module.exports = {
// ...
output: {
// ...
publicPath: config.publicPath
}
// ...
}
if (dev) {
module.exports.serve = {
host: '0.0.0.0',
port: config.serve.port,
dev: {
publicPath: config.publicPath
},
add: app => {
app.use(convert(history({
index: url.parse(config.publicPath).pathname
})))
}
}
}
複製代碼
這裏的關鍵是 npm run
傳進來的自定義參數能夠經過 process.env.npm_config_*
得到。參數中若是有 -
會被轉成 _
。
還有一點,咱們不須要把本身我的用的配置文件提交到 git,因此咱們在 .gitignore
中加入:
config/*
!config/default.js
!config/dev.js
複製代碼
把 config
目錄排除掉,可是保留生產環境和 dev 默認配置文件。
可能有同窗注意到了 webpack-cli
能夠經過 --env 的方式從命令行傳參給腳本,遺憾的是 webpack-cli
不支持。
當處理帶後綴名的請求時,好比 http://localhost:8080/bar.do ,connect-history-api-fallback
會認爲它應該是一個實際存在的文件,就算找不到該文件,也不會 fallback 到 index.html,而是返回 404。但在 SPA 應用中這不是咱們但願的。
幸虧有一個配置選項 disableDotRule: true
能夠禁用這個規則,使帶後綴的文件當不存在時也能 fallback 到 index.html
module.exports.serve = {
// ...
add: app => {
app.use(convert(history({
// ...
disableDotRule: true,
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'] // 須要配合 disableDotRule 一塊兒使用
})))
}
}
複製代碼
在業務代碼中,有些變量在開發環境和生產環境是不一樣的,好比域名、後臺 API 地址等。還有開發環境可能須要打印調試信息等。
咱們可使用 DefinePlugin 插件在打包時往代碼中插入須要的環境變量。
// ...
const pkgInfo = require('./package.json')
module.exports = {
// ...
plugins: [
new webpack.DefinePlugin({
DEBUG: dev,
VERSION: JSON.stringify(pkgInfo.version),
CONFIG: JSON.stringify(config.runtimeConfig)
}),
// ...
]
}
複製代碼
DefinePlugin 插件的原理很簡單,若是咱們在代碼中寫:
console.log(DEBUG)
複製代碼
它會作相似這樣的處理:
'console.log(DEBUG)'.replace('DEBUG', true)
複製代碼
最後生成:
console.log(true)
複製代碼
這裏有一點須要注意,像這裏的 VERSION
, 若是咱們不對 pkgInfo.version
作 JSON.stringify()
,
console.log(VERSION)
複製代碼
而後作替換操做:
'console.log(VERSION)'.replace('VERSION', '1.0.0')
複製代碼
最後生成:
console.log(1.0.0)
複製代碼
這樣語法就錯誤了。因此,咱們須要 JSON.stringify(pkgInfo.version)
轉一下變成 '"1.0.0"'
,替換的時候纔會帶引號。
還有一點,webpack 打包壓縮的時候,會把代碼進行優化,好比:
if (DEBUG) {
console.log('debug mode')
} else {
console.log('production mode')
}
複製代碼
會被編譯成:
if (false) {
console.log('debug mode')
} else {
console.log('production mode')
}
複製代碼
而後壓縮優化爲:
console.log('production mode')
複製代碼
文件 a 引入文件 b 時,b 的路徑是相對於 a 文件所在目錄的。若是 a 和 b 在不一樣的目錄,藏得又深,寫起來就會很麻煩:
import b from '../../../components/b'
複製代碼
爲了方便,咱們能夠定義一個路徑別名(alias):
resolve: {
alias: {
'~': resolve(__dirname, 'src')
}
}
複製代碼
這樣,咱們能夠以 src
目錄爲基礎路徑來 import
文件:
import b from '~/components/b'
複製代碼
html 中的 <img>
標籤無法使用這個別名功能,但 html-loader
有一個 root
參數,可使 /
開頭的文件相對於 root
目錄解析。
{
test: /\.html$/,
use: [
{
loader: 'html-loader',
options: {
root: resolve(__dirname, 'src'),
attrs: ['img:src', 'link:href']
}
}
]
}
複製代碼
那麼,<img src="/favicon.png">
就能順利指向到 src 目錄下的 favicon.png 文件,不須要關心當前文件和目標文件的相對路徑。
PS: 在調試 <img>
標籤的時候遇到一個坑,html-loader
會解析 <!-- -->
註釋中的內容,以前在註釋中寫的
<!-- 大於 10kb 的圖片,圖片會被儲存到輸出目錄,src 會被替換爲打包後的路徑 <img src="/assets/f78661bef717cf2cc2c2e5158f196384.png"> -->
複製代碼
以前由於沒有加 root
參數,因此 /
開頭的文件名不會被解析,加了 root
致使編譯時報錯,找不到該文件。你們記住這一點。
babel 編譯後的代碼通常會形成性能損失,babel 提供了一個 loose 選項,使編譯後的代碼不須要徹底遵循 ES6 規定,簡化編譯後的代碼,提升代碼執行效率:
package.json:
{
"babel": {
"presets": [
[
"env",
{
"loose": true
}
],
"stage-2"
]
}
}
複製代碼
但這麼作會有兼容性的風險,可能會致使 ES6 源碼理應的執行結果和編譯後的 ES5 代碼的實際結果並不一致。若是代碼沒有遇到實際的效率瓶頸,官方 不建議 使用 loose
模式。
咱們目前的配置,babel 會把 ES6 模塊定義轉爲 CommonJS 定義,但 webpack 本身能夠處理 import
和 export
, 並且 webpack 處理 import
時會作代碼優化,把沒用到的部分代碼刪除掉。所以咱們經過 babel 提供的 modules: false
選項把 ES6 模塊轉爲 CommonJS 模塊的功能給關閉掉。
package.json:
{
"babel": {
"presets": [
[
"env",
{
"loose": true,
"modules": false
}
],
"stage-2"
]
}
}
複製代碼
css 有一個很麻煩的問題就是比較新的 css 屬性在各個瀏覽器裏是要加前綴的,咱們可使用 autoprefixer 工具自動建立這些瀏覽器規則,那麼咱們的 css 中只須要寫:
:fullscreen a {
display: flex
}
複製代碼
autoprefixer 會編譯成:
:-webkit-full-screen a {
display: -webkit-box;
display: flex
}
:-moz-full-screen a {
display: flex
}
:-ms-fullscreen a {
display: -ms-flexbox;
display: flex
}
:fullscreen a {
display: -webkit-box;
display: -ms-flexbox;
display: flex
}
複製代碼
首先,咱們用 npm 安裝它:
npm install postcss-loader autoprefixer --save-dev
複製代碼
autoprefixer 是 postcss 的一個插件,因此咱們也要安裝 postcss 的 webpack loader。
修改一下 webpack 的 css rule:
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
}
複製代碼
而後建立文件 postcss.config.js
:
module.exports = {
plugins: [
require('autoprefixer')()
]
}
複製代碼
多頁面網站一樣能夠用 webpack 來打包,以便使用 npm 包,import()
,code splitting
等好處。
MPA 意味着並沒不是一個單一的 html 入口和 js 入口,而是每一個頁面對應一個 html 和多個 js。那麼咱們能夠把項目結構設計爲:
├── dist
├── package.json
├── node_modules
├── src
│ ├── components
│ ├── shared
| ├── favicon.png
│ └── pages 頁面放這裏
| ├── foo 編譯後生成 http://localhost:8080/foo.html
| | ├── index.html
| | ├── index.js
| | ├── style.css
| | └── pic.png
| └── bar http://localhost:8080/bar.html
| ├── index.html
| ├── index.js
| ├── style.css
| └── baz http://localhost:8080/bar/baz.html
| ├── index.html
| ├── index.js
| └── style.css
└── webpack.config.js
複製代碼
這裏每一個頁面的 index.html
是個完整的從 <!DOCTYPE html>
開頭到 </html>
結束的頁面,這些文件都要用 html-webpack-plugin
處理。index.js
是每一個頁面的業務邏輯,做爲每一個頁面的入口 js 配置到 entry
中。這裏咱們須要用 glob
庫來把這些文件都篩選出來批量操做。爲了使用 webpack 4 的 optimization.splitChunks
和 optimization.runtimeChunk
功能,我寫了 html-webpack-include-sibling-chunks-plugin 插件來配合使用。還要裝幾個插件把 css 壓縮並放到 <head>
中。
npm install glob html-webpack-include-sibling-chunks-plugin uglifyjs-webpack-plugin mini-css-extract-plugin optimize-css-assets-webpack-plugin --save-dev
複製代碼
webpack.config.js
修改的地方:
// ...
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const HtmlWebpackIncludeSiblingChunksPlugin = require('html-webpack-include-sibling-chunks-plugin')
const glob = require('glob')
const dev = Boolean(process.env.WEBPACK_SERVE)
const config = require('./config/' + (process.env.npm_config_config || 'default'))
const entries = glob.sync('./src/**/index.js')
const entry = {}
const htmlPlugins = []
for (const path of entries) {
const template = path.replace('index.js', 'index.html')
const chunkName = path.slice('./src/pages/'.length, -'/index.js'.length)
entry[chunkName] = dev ? [path, template] : path
htmlPlugins.push(new HtmlWebpackPlugin({
template,
filename: chunkName + '.html',
chunksSortMode: 'none',
chunks: [chunkName]
}))
}
module.exports = {
entry,
output: {
path: resolve(__dirname, 'dist'),
// 咱們不定義 publicPath,不然訪問 html 時須要帶上 publicPath 前綴
filename: dev ? '[name].js' : '[chunkhash].js',
chunkFilename: '[chunkhash].js'
},
optimization: {
runtimeChunk: true,
splitChunks: {
chunks: 'all'
},
minimizer: dev ? [] : [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: true
}),
new OptimizeCSSAssetsPlugin()
]
},
module: {
rules: [
// ...
{
test: /\.css$/,
use: [dev ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
},
// ...
]
},
plugins: [
// ...
/* 這裏不使用 [chunkhash] 由於從同一個 chunk 抽離出來的 css 共享同一個 [chunkhash] [contenthash] 你能夠簡單理解爲 moduleId + content 生成的 hash 所以一個 chunk 中的多個 module 有本身的 [contenthash] */
new MiniCssExtractPlugin({
filename: '[contenthash].css',
chunkFilename: '[contenthash].css'
}),
// 必須放在html-webpack-plugin前面
new HtmlWebpackIncludeSiblingChunksPlugin(),
...htmlPlugins
],
// ...
}
複製代碼
entry
和 htmlPlugins
會經過遍歷 pages 目錄生成,好比:
entry:
{
'bar/baz': './src/pages/bar/baz/index.js',
bar: './src/pages/bar/index.js',
foo: './src/pages/foo/index.js'
}
複製代碼
在開發環境中,爲了可以修改 html 文件後網頁可以自動刷新,咱們還須要把 html 文件也加入 entry 中,好比:
{
foo: ['./src/pages/foo/index.js', './src/pages/foo/index.html']
}
複製代碼
這樣,當 foo 頁面的 index.js 或 index.html 文件改動時,都會觸發瀏覽器刷新該頁面。雖然把 html 加入 entry 很奇怪,但放心,不會致使錯誤。記得不要在生產環境這麼作,否則致使 chunk 文件包含了無用的 html 片斷。
htmlPlugins:
[
new HtmlWebpackPlugin({
template: './src/pages/bar/baz/index.html',
filename: 'bar/baz.html',
chunksSortMode: 'none',
chunks: ['bar/baz']
},
new HtmlWebpackPlugin({
template: './src/pages/bar/index.html',
filename: 'bar.html',
chunksSortMode: 'none',
chunks: ['bar']
},
new HtmlWebpackPlugin({
template: './src/pages/foo/index.html',
filename: 'foo.html',
chunksSortMode: 'none',
chunks: ['foo']
}
]
複製代碼
代碼在 examples/mpa 目錄。
經過這篇文章,我想你們應該學會了 webpack 的正確打開姿式。雖然我沒有說起如何用 webpack 來編譯 React 和 vue.js, 但你們能夠想到,無非是安裝一些 loader 和 plugin 來處理 jsx 和 vue 格式的文件,那時難度就不在於 webpack 了,而是代碼架構組織的問題了。具體的你們本身去摸索一下。
本做品採用 知識共享署名 - 非商業性使用 4.0 國際許可協議 進行許可。