Webpack
構建的基於zepto
的多頁應用腳手架,本文聊聊本次項目中Webpack
構建多頁應用的一些心得體會。javascript
因爲公司舊版的腳手架是基於Gulp
構建的zepto
多頁應用(有興趣能夠看看web-mobile-cli),有着很多的痛點。例如:css
promise
,不能使用await
、generator
等。(由於babel-runtime
須要模塊化);Gulp
插件相對Webpack
少且久遠,維護成本高等等。此次升級有幾個地方須要注意和改進:html
Github倉庫:前端
Webpack
的多頁應用經過多入口entry
和多實例html-webpack-plugin
配合來構建,html-webpack-plugin
的chunk
屬性傳入對應entry
的key
就能夠作到關聯,例如:java
module.exports = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
},
plugins: [
new HtmlWebpackPlugin({
filename: `pageOne.html`,
template: `./src/pageOne.html`,
chunks: ['pageOne']
}),
new HtmlWebpackPlugin({
filename: `pageTwo.html`,
template: `./src/pageTwo.html`,
chunks: ['pageTwo']
}),
new HtmlWebpackPlugin({
filename: `pageTwo.html`,
template: `./src/pageTwo.html`,
chunks: ['pageTwo']
})
]
}
複製代碼
那麼問題來了,開發新的頁面每次都得添加豈不是很麻煩。這裏推薦神器glob根據正則規則匹配。node
const glob = require('glob')
module.exports = {
entry: glob.sync('./src/js/*.js').reduce((pre, filepath) => {
const tempList = filepath.split('src/')[1].split(/js\//)
const filename = `${tempList[0]}${tempList[1].replace(/\.js/g, '')}`
return Object.assign(pre, {[filename]: filepath})
}, {}),
plugins: [
...glob.sync('./src/html/*.ejs').map((filepath, i) => {
const tempList = filepath.split('src/')[1].split(/html\//)
const fileName = tempList[1].split('.')[0].split(/[\/|\/\/|\\|\\\\]/g).pop()
const fileChunk = `${tempList[0]}${fileName}`
return new HtmlWebpackPlugin({
filename: `${fileChunk}.html`,
template: filepath,
chunks: [fileChunk]
})
})
]
}
複製代碼
項目沒有直接使用html
,而是使用了ejs
做爲模板,這裏有至少兩個好處:webpack
// header.ejs
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%= title %></title>
</head>
// index.ejs
<!DOCTYPE html>
<html lang="en">
<% include ./header.ejs %>
<body>
<!-- page -->
</body>
<script src="<%= publicPath %>lib/zepto.js"></script>
</html>
複製代碼
<% include ./header.ejs %>
就是引用了header.ejs
文件,<%= title %>
和<%= publicPath %>
是我在配置文件定義的兩個變量,publicPath
是爲了統一cdn
緩存服務器的域名,很是有用。git
項目中使用了zepto
,因此須要墊片,所謂墊片就是shim 預置依賴,即全局依賴。github
webpack compiler 可以識別遵循 ES2015 模塊語法、CommonJS 或 AMD 規範編寫的模塊。然而,一些 third party(第三方庫) 可能會引用一些全局依賴(例如 jQuery 中的 $)。所以這些 library 也可能會建立一些須要導出的全局變量。這些 "broken modules(不符合規範的模塊)" 就是 shim(預置依賴) 發揮做用的地方。web
墊片有兩種方式:
html
文件中,全部引用的js
文件的最前面引用的文件(例如zepto
);Webpack
配置shim預置依賴
。最終我選擇了Webpack
配置shim預置依賴
這種方式,由於:
ejs
能夠抽離出來成爲公共模塊,但仍是須要每一個頁面手動引入公共模塊);Webpack
能夠把全部第三方插件的代碼都拆分打包成爲一個獨立的chunk
,只需一個請求。module.exports = {
entry: {...},
module: {
rules: [
{
test: require.resolve('zepto'),
use: 'imports-loader?this=>window'
}
]
},
plugins: [
new webpack.ProvidePlugin({$: 'zepto'})
]
}
複製代碼
通常來說Webpack
的配置entry
中每一個key
就對應輸出一個chunk
,那麼該項目中會提取這幾類chunk
:
entry
)對應的chunk
;common
:屢次引用的公共文件;vender
:第三方依賴;manifest
:Webpack
運行時(runtime
)代碼,它存儲着Webpack
對module
和chunk
的信息。module.exports = {
entry: {...},
module: {...},
plugins: [],
optimization: {
runtimeChunk: {
name: 'manifest'
},
splitChunks: {
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
chunks: 'all',
name: 'vendors',
filename: 'js/vendors.[contenthash:8].js',
priority: 2,
reuseExistingChunk: true
},
common: {
test: /\.m?js$/,
chunks: 'all',
name: 'common',
filename: 'js/common.[contenthash:8].js',
minSize: 0,
minChunks: 2,
priority: 1,
reuseExistingChunk: true
}
}
}
}
}
複製代碼
這裏注意的有兩點:
priority
比common
代碼的priority
大;common
代碼:minChunks
爲引用次數,我設置爲引用2次即提取爲公共代碼。minSize
爲最小字節,設置爲0。緩存的目的是爲了提升加載速度,Webpack
在緩存方面已是老生常談的了,每一個文件賦予惟一的hash值,只有更新過的文件,hash值才改變,以達到總體項目最少文件改動。
Webpack
中有三種hash
值:
hash
:所有文件同一hash
,一旦某個文件改變,所有文件的hash都將改變(同一hash
不知足需求);chunkhash
:根據不一樣的入口文件(Entry
)進行依賴文件解析、構建對應的chunk,生成對應的哈希值(問題是css
做爲模塊import
到JavaScript
文件中的,它們的chunkhash
是一致的,一旦改變js
文件,即便import
的css
文件內容沒有改變,其chunkhash
值也會一同改變,不知足需求);contexthash
:只有模塊的內容變了,那麼hash值才改變(採用)。module.exports = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
},
output: {
path: 'src',
chunkFilename: 'j[name].[contenthash:8].js',
filename: '[name].[contenthash:8].js'
},
plugins: [
new HtmlWebpackPlugin({
filename: `pageOne.html`,
template: `./src/pageOne.html`,
chunks: ['pageOne']
}),
new HtmlWebpackPlugin({
filename: `pageTwo.html`,
template: `./src/pageTwo.html`,
chunks: ['pageTwo']
}),
new HtmlWebpackPlugin({
filename: `pageTwo.html`,
template: `./src/pageTwo.html`,
chunks: ['pageTwo']
})
]
}
複製代碼
僅僅使用contexthash
還不足夠,每當import
的資源文件順序改變時,chunk
依然會改變,目的沒有達成。要解決這個問題首先要理解module
和chunk
分別是什麼,簡單理解:
module
:一個import
對應一個module
(例如:import zepto from 'zepto'
中的zepto
就是一個module
);chunk
:根據配置文件打包出來的包,就是chunk
。(例如多頁應用中每一個entry
的key
值對應的文件)。由於Webpack
內部維護了一個自增的id
,依照順序賦予給每一個module
,每當新增或者刪減致使module
的順序改變時,受影響的chunk
的hash
值也會改變。解決辦法就是使用惟一的hash
值替代自增的id
。
module.exports = {
entry: {...},
module: {...},
plugins: [],
optimization: {
moduleIds: 'hashed'
}
}
複製代碼
優化的目的是提升執行和打包的速度。
告訴Webpack
解析模塊時應該搜索的目錄,縮小編譯範圍,減小沒必要要的編譯工做。
const {resolve} = require('path')
module.exports = {
entry: {...},
module: {...},
plugins: [],
optimization: {...},
resolve: {
alias: {
'@': resolve(__dirname, '../src'),
},
modules: [
resolve('src'),
resolve('node_modules'),
]
}
}
複製代碼
指定loader
的include
目錄,做用是縮小編譯範圍。
const {resolve} = require('path')
module.exports = {
entry: {...},
module: {
rules: [
{
test: /\.css$/,
include: [
resolve("src"),
],
use: ['style-loader', 'css-loader']
}
]
},
plugins: [],
optimization: {...},
resolve: {...}
}
複製代碼
babel
緩存目錄babel-loader
開始緩存目錄cacheDirectory
。
const {resolve} = require('path')
module.exports = {
entry: {...},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
include: [
resolve("src"),
],
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-runtime']
}
}
}
]
},
plugins: [],
optimization: {...},
resolve: {...}
}
複製代碼
TerserJSPlugin
TerserJSPlugin
插件的做用是壓縮JavaScript
,優化的地方是開啓緩存目錄和開啓多線程。
const {resolve} = require('path')
module.exports = {
entry: {...},
module: {...},
plugins: [],
optimization: {
minimizer: [
new TerserJSPlugin({
parallel: true,
cache: true,
})
]
},
resolve: {...}
}
複製代碼
經過此次學習Webpack
到升級腳手架,對前端工程化有了進一步的瞭解,也感覺到了Webpack4
帶來的開箱即用,挺方便的。
參考文章:
Webpack官方文檔
【實戰】webpack4 + ejs + express 帶你擼一個多頁應用項目架構
基於 webpack 的持久化緩存方案