摘要:構建的基于的多頁應用腳手架,本文聊聊本次項目中構建多頁應用的一些心得體會。倉庫構建的舊版多頁應用構建的多頁應用。例如多頁應用中每個的值對應的文件。
Webpack構建的基于zepto的多頁應用腳手架,本文聊聊本次項目中Webpack構建多頁應用的一些心得體會。
1.前言由于公司舊版的腳手架是基于Gulp構建的zepto多頁應用(有興趣可以看看web-mobile-cli),有著不少的痛點。例如:
需要兼容低版本瀏覽器,只能采用promise,不能使用await、generator等。(因為babel-runtime需要模塊化);
瀏覽器緩存不友好(只能全緩存而不是使用資源文件的后綴哈希值來達到局部緩存的效果);
項目的結構不友好(可以更好的結構化);
開發環境下的構建速度(內存);
Gulp插件相對Webpack少且久遠,維護成本高等等。
這次升級有幾個地方需要注意和改進:
項目舊代碼盡量做到無縫轉移;
資源文件的緩存;
組件式的組織目錄結構。
Github倉庫:
Gulp構建的舊版多頁應用web-mobile-cli;
Webpack構建的多頁應用web-mobile-webpack-cli。
Webpack的多頁應用通過多入口entry和多實例html-webpack-plugin配合來構建,html-webpack-plugin的chunk屬性傳入對應entry的key就可以做到關聯,例如:
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根據正則規則匹配。
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]
})
})
]
}
3.模板
項目沒有直接使用html,而是使用了ejs作為模板,這里有至少兩個好處:
把公共的代碼抽離出來;
傳入公共的變量。
// 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
<html lang="en">
<% include ./header.ejs %>
<body>
body>
<script src="<%= publicPath %>lib/zepto.js">script>
html>
<% include ./header.ejs %>就是引用了header.ejs文件,<%= title %>和<%= publicPath %>是我在配置文件定義的兩個變量,publicPath是為了統一cdn緩存服務器的域名,非常有用。
4.墊片項目中使用了zepto,所以需要墊片,所謂墊片就是shim 預置依賴,即全局依賴。
webpack compiler 能夠識別遵循 ES2015 模塊語法、CommonJS 或 AMD 規范編寫的模塊。然而,一些 third party(第三方庫) 可能會引用一些全局依賴(例如 jQuery 中的 $)。因此這些 library 也可能會創建一些需要導出的全局變量。這些 "broken modules(不符合規范的模塊)" 就是 shim(預置依賴) 發揮作用的地方。
墊片有兩種方式:
傳統方式的墊片就是在html文件中,所有引用的js文件的最前面引用的文件(例如zepto);
Webpack配置shim預置依賴。
最終我選擇了Webpack配置shim預置依賴這種方式,因為:
傳統的方式需要每個頁面都手動引入(雖說搭配ejs可以抽離出來成為公共模塊,但還是需要每個頁面手動引入公共模塊);
傳統的方式需要多發一次請求去請求墊片;
Webpack可以把所有第三方插件的代碼都拆分打包成為一個獨立的chunk,只需一個請求。
module.exports = {
entry: {...},
module: {
rules: [
{
test: require.resolve("zepto"),
use: "imports-loader");
}
]
},
plugins: [
new webpack.ProvidePlugin({$: "zepto"})
]
}
5.拆分
一般來講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");,
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值才改變,以達到整體項目最少文件改動。
6.1 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"]
})
]
}
6.2 module id
僅僅使用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"
}
}
7.優化
優化的目的是提高執行和打包的速度。
7.1 查找路徑告訴Webpack解析模塊時應該搜索的目錄,縮小編譯范圍,減少不必要的編譯工作。
const {resolve} = require("path")
module.exports = {
entry: {...},
module: {...},
plugins: [],
optimization: {...},
resolve: {
alias: {
"@": resolve(__dirname, "../src"),
},
modules: [
resolve("src"),
resolve("node_modules"),
]
}
}
7.2 指定目錄
指定loader的include目錄,作用是縮小編譯范圍。
const {resolve} = require("path")
module.exports = {
entry: {...},
module: {
rules: [
{
test: /.css$/,
include: [
resolve("src"),
],
use: ["style-loader", "css-loader"]
}
]
},
plugins: [],
optimization: {...},
resolve: {...}
}
7.3 babel緩存目錄
babel-loader開始緩存目錄cacheDirectory。
const {resolve} = require("path")
module.exports = {
entry: {...},
module: {
rules: [
{
test: /.m");,
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: {...}
}
7.4 插件TerserJSPlugin
TerserJSPlugin插件的作用是壓縮JavaScript,優化的地方是開啟緩存目錄和開啟多線程。
const {resolve} = require("path")
module.exports = {
entry: {...},
module: {...},
plugins: [],
optimization: {
minimizer: [
new TerserJSPlugin({
parallel: true,
cache: true,
})
]
},
resolve: {...}
}
8.總結
通過這次學習Webpack到升級腳手架,對前端工程化有了進一步的了解,也感受到了Webpack4帶來的開箱即用,挺方便的。
參考文章:
Webpack官方文檔
【實戰】webpack4 + ejs + express 帶你擼一個多頁應用項目架構
基于 webpack 的持久化緩存方案
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/6847.html
摘要:然而在某些特殊的應用場景之中,則需要使用到傳統的多頁應用。在使用進行項目工程化構建時,也需要對應到調整。配置入口設置多頁應用的打包會對應多個入口文件,以及多個模版文件。方法一使用的文件系統。組合如下完整可查看多頁應用 背景 隨著react, vue, angular 三大前端框架在前端領域地位的穩固,SPA應用正在被應用到越來越多的項目之中。然而在某些特殊的應用場景之中,則需要使用到傳...
摘要:原文地址如果您對本系列文章感興趣,歡迎關注訂閱這里前言上文多頁應用架構系列十二利用生成普通網頁頁面模板我們基本上已經搞清楚如何利用來生成普通網頁頁面模板,本文將以我的腳手架項目介紹如何在這基礎上搭建一套簡單的模板布局系統。 本文首發于Array_Huang的技術博客——實用至上,非經作者同意,請勿轉載。原文地址:https://segmentfault.com/a/1190000007...
摘要:回到純靜態頁面開發階段,讓頁面不需要后端渲染也能跑起來。改造開始本文著重介紹如何將靜態頁面改造成后端渲染需要的模板。總結在后端渲染的項目里使用多頁應用架構是絕對可行的,可不要給老頑固們嚇唬得又回到傳統前端架構了。 本文首發于Array_Huang的技術博客——實用至上,非經作者同意,請勿轉載。原文地址:https://segmentfault.com/a/119000000820338...
摘要:回到純靜態頁面開發階段,讓頁面不需要后端渲染也能跑起來。改造開始本文著重介紹如何將靜態頁面改造成后端渲染需要的模板。總結在后端渲染的項目里使用多頁應用架構是絕對可行的,可不要給老頑固們嚇唬得又回到傳統前端架構了。 本文首發于Array_Huang的技術博客——實用至上,非經作者同意,請勿轉載。原文地址:https://segmentfault.com/a/119000000820338...
摘要:本文首發于的技術博客實用至上,非經作者同意,請勿轉載。原文地址如果您對本系列文章感興趣,歡迎關注訂閱這里這系列文章講什么本系列文章主要介紹如何用這一當前流行的構建工具來設計一個多頁應用的架構。 本文首發于Array_Huang的技術博客——實用至上,非經作者同意,請勿轉載。原文地址:https://segmentfault.com/a/1190000006843916如果您對本系列文章...
閱讀 2252·2021-11-22 09:34
閱讀 2021·2021-09-22 15:22
閱讀 2022·2019-08-29 15:05
閱讀 2110·2019-08-26 10:43
閱讀 3411·2019-08-26 10:26
閱讀 886·2019-08-23 18:29
閱讀 3522·2019-08-23 16:42
閱讀 2001·2019-08-23 14:46