php與vite結(jié)合使用案例解析
一、背景
前段日子公司里準(zhǔn)備要重構(gòu)一個(gè)擁有10年高齡的網(wǎng)站,當(dāng)時(shí)聽到這個(gè)消息心里無比激動(dòng),因?yàn)槲椰F(xiàn)在就是這個(gè)網(wǎng)站的維護(hù)人員??????,在現(xiàn)代這個(gè)前端技術(shù)快速發(fā)展的年代很難想象我盡然還在寫JQ+php模板??????,這都不是最令我不爽的,最不爽的還是我改一段js代碼,還要去后端項(xiàng)目去更改時(shí)間戳,才能保證他更新,這樣換來的結(jié)果就是我要同時(shí)去更改兩個(gè)項(xiàng)目,哪怕我只改了一點(diǎn),css也是一樣。
這個(gè)項(xiàng)目前端代碼和后端代碼是分開的,后端主要是php模板渲染html,前端使用的是sea.js模塊化加載插件以及JQ
二、技術(shù)調(diào)研
由于是一個(gè)10年高齡的網(wǎng)站了,其中了業(yè)務(wù)邏輯盤根交錯(cuò)、代碼分布錯(cuò)綜復(fù)雜,想要一把梭哈的話我估計(jì)我就離失業(yè)不遠(yuǎn)了??,只能說是一個(gè)頁面一個(gè)頁面慢慢重構(gòu)然后上線觀察,基于這種形勢(shì)我采用了vue3+vite的技術(shù)方向,你要問我為啥不選webpack作為打包工具,我只能說當(dāng)你開過法拉利跑車后你還會(huì)去開拖拉機(jī)嗎?再者vite作為vue3的親兒子肯定選他啊。
三、搭建基礎(chǔ)框架
一開始本來是想直接按照vite文檔上與后端集成的那種方式去加載js和css文件的但是,我TM突然想到前端資源和后端模板壓根不在一個(gè)項(xiàng)目啊,咋辦去拉著臉皮求后端幫我們?nèi)?dòng)態(tài)拉取打包號(hào)的manifest資源清單?
manifest.json是一個(gè)vite打包之后產(chǎn)出的文件資源清單里面記錄了當(dāng)前這個(gè)入口依賴了那些js和css,大致就是下面這個(gè)結(jié)構(gòu)。

作為一個(gè)有理想和抱負(fù)的前端自己選的,在困難也要完成,隨即我發(fā)動(dòng)聰明的大腦瞬間一個(gè)想法誕生,如果我寫一個(gè)加載器,根據(jù)入口文件名去manifest資源清單里面去尋找,入口當(dāng)前依賴了那些文件并把它全部加載到頁面上不就可以了?說干就干。
四、Vite加載器編寫
首先我們先編寫一個(gè)vite 的class類里面有一個(gè)use方法用來加載入口名對(duì)應(yīng)的文件依賴,方法里面還要區(qū)分是否為dev環(huán)境,如果為dev環(huán)境就不需要加載資源清單了直接調(diào)用getAssetSource方法拿到文件路徑去請(qǐng)求入口文件,入口文件里面的依賴會(huì)根據(jù)esModule的特性瀏覽器自動(dòng)發(fā)送請(qǐng)求獲取。
class Vite{
private static config: ViteConfig;
private static manifestJson: ManifestJson
private static root: string = 'vite/src/'
private static is_dev: boolean = window.location.origin.indexOf('dev') > -1;
private static support_module: boolean = window.support_module;
private static manifest_loading: boolean = false;
static async use(src: string) {
//開發(fā)環(huán)境
if (this.is_dev) {
src = this.getAssetSource(src, environment.dev);
this.loadScript(src, 'module')
this.loadScript(`${this.config.base}@vite/client`, 'module')
return
}
//頁面多個(gè)vite.use時(shí),防重複加載衝突
if(this.manifest_loading){
setTimeout(() => {
this.use(src)
}, 1000);
return
}
//加載清單文件
if (!this.manifestJson) {
await this.getManifest()
}
//加載清單源文件
let src_item = this.getAssetSource(src, environment.production);
this.importCSS(src_item);
return this.importSrc(src_item.file)
}
}
現(xiàn)在讓我們來看看getAssetSource方法做了啥
//獲取manifest對(duì)應(yīng)的源字段值
static getAssetSource(src: string, ev: environment.dev): string;
static getAssetSource(src: string, ev: environment.debug | environment.production ): ManifestItem;
static getAssetSource(src: string, ev: environment): ManifestItem | string {
if (ev == 'dev') {
return this.config.base + this.root + src
}
//不支持module 則調(diào)用legacy
if(!this.support_module){
let file_suffix = src.includes('.ts') ? '-legacy.ts' : '-legacy.js';
src = src.replace(/\.js|\.ts/g, '') + file_suffix;
}
//如果是從清單文件中獲取的路徑則不需要拼接root路徑
if (src.indexOf(this.root) == -1) {
src = this.root + src;
}
return this.manifestJson[src];
}
在dev環(huán)境下主要是用來拼接文件完整路徑的,在production環(huán)境下還要區(qū)分瀏覽器是否支持esModule,不支持的話則需要更改文件名,用來加載@vitejs/plugin-legacy插件打包出來的代碼做兼容舊版瀏覽器的操作,最終返回入口文件名依賴的文件對(duì)象
這個(gè)就是資源清單里兼容的舊版瀏覽器的對(duì)象名

這個(gè)是支持esModel的對(duì)象名

dev環(huán)境下獲得完整文件路徑后則調(diào)用loadScript去加載文件
//插入script標(biāo)籤加載js
static async loadScript(src: string, type?: string) {
return new Promise((resolve, reject) => {
let script = document.createElement('script');
if (type) script.type = type;
script.src = src;
document.head.appendChild(script);
script.addEventListener('load', ev => {
resolve(ev);
})
script.addEventListener('error', ev => {
reject(ev)
})
})
}
還要去加載vite的熱更新包 this.loadScript(${this.config.base}@vite/client, 'module'),這個(gè)樣子dev環(huán)境就能愉快的使用vue3和熱更新做開發(fā)了爽的一批。
現(xiàn)在我們來處理一下production環(huán)境下的文件加載,首先我們先加載打包好的資源清單文件,然后調(diào)用getAssetSource方法如果是production環(huán)境他會(huì)根據(jù)你傳入的文件名去資源清單里面找出當(dāng)前這個(gè)入口文件名所依賴的文件對(duì)象,比如你在項(xiàng)目里面創(chuàng)建了一個(gè)warePay.ts的文件里面初始化了vue,然后你吧這個(gè)文件當(dāng)做一個(gè)打包入口傳入給vite這個(gè)時(shí)候產(chǎn)出的資源清單manifest里面就會(huì)有一個(gè)根據(jù)你文件根路徑+文件名當(dāng)做key的一個(gè)對(duì)象這個(gè)對(duì)象的value就是你這個(gè)文件所依賴的所有資源。
以下是加載資源清單的getManifest方法讓我們看看
//獲取編譯後清單文件
private static async getManifest() {
this.manifest_loading = true;
let manifestSrc = this.config.base + `manifest.json?t=${new Date().getTime()}`
await $.get(manifestSrc, res => {
this.manifestJson = res;
}, 'json');
// 加載兼容墊片
if(!this.support_module){
this.manifestJson['vite/src/legacy-polyfills'] = this.manifestJson['\x00vite/legacy-polyfills']
let legacy_src = this.config.base + this.manifestJson['vite/src/legacy-polyfills'].file
await this.loadScript(legacy_src)
}
this.manifest_loading = false;
}
方法里面首先去加載資源清單文件并加上時(shí)間戳這樣只要修改代碼,打包后就能拿到最新的資源清單文件加載最新的代碼就不需要去更改php模板了美滋滋,如果不支持esModule的話還要加載兼容墊片,這里有個(gè)小坑資源清單里面的兼容墊片的key前面會(huì)有一串Unicode編碼的空格。

所以我們要轉(zhuǎn)換一下。
拿到依賴對(duì)象后就可以加載css和js文件了,首先我們先調(diào)用importCSS來加載css文件,如果當(dāng)前這個(gè)對(duì)象還有子依賴的css的話就遞歸去加載其子依賴的css。
//生產(chǎn)環(huán)境引入css
static importCSS(item: ManifestItem) {
if (this.support_module && item.css) {
for(let v of item.css){
let id = v.split('/')[1].replace(/\./g,'-');
if(!document.querySelector(`#${id}`)){
let css = document.createElement("link");
css.setAttribute('rel', 'stylesheet');
css.setAttribute('href', this.config.base + v);
css.setAttribute('id', id);
document.head.appendChild(css)
}
}
}
//檢測該文件引入的其他模塊是否包含css文件有的話導(dǎo)入
if (item.imports && item.imports.length > 0) {
for (let css of item.imports) {
let src_item = this.manifestJson[css]
if(src_item && src_item.css && src_item.css.length>0) this.importCSS(src_item)
}
}
}
隨后再去調(diào)用importSrc去加載js
//生產(chǎn)環(huán)境引入js
static importSrc(url: string) {
url = this.config.base + url;
if(this.support_module){
this.loadScript(url, 'module')
return;
}
if (window.System) {
return window.System.import(url)
}
}
同樣也要區(qū)分是否支持esModule如果支持就直接加載主入口文件就可以了,隨后瀏覽器后自動(dòng)去請(qǐng)求其子依賴, 如果不支持的話,我們就要調(diào)用 window.System.import方法去加載入口文件和其子依賴。
window.System對(duì)象是我們?cè)趃etManifest方法里面加載的兼容墊片js里的對(duì)象他可以根據(jù)主入口去加載其子依賴,因?yàn)樗淖右蕾嚶窂饺吭谌肟谖募锩?,大概就是這個(gè)樣子。
import("data:text/javascript,") } import { J as t, E as e } from "./JqExtension.ae8d9012.js"; import { L as a } from "./LotteryTicketModel.3322a10b.js"; import { R as s } from "./RegistrationModel.d9d02bf8.js"; import { W as i } from "./WinningModel.6aaaa2e1.js"; import "./BaseModel.69145e29.js";
到此為止加載器基本完成,在php模板里面就可以通過Vite.use('xxx/xxx/xxx/warePay.ts')去加載文件愉快的使用vue開發(fā)了。
這里是全部代碼
interface ViteConfig {
base: string,
}
interface ManifestItem{
file: string;
src?: string,
isEntry?: boolean,
css?: string;
imports?:Array<string>
}
interface ManifestJson{
[fileKey:string]: ManifestItem
}
enum environment{
dev = 'dev',
debug = 'debug',
production = 'production'
}
class Vite {
private static config: ViteConfig;
private static manifestJson: ManifestJson
private static root: string = 'vite/src/'
private static is_dev: boolean = window.location.origin.indexOf('dev') > -1;
private static support_module: boolean = window.support_module;
private static manifest_loading: boolean = false;
//設(shè)置基本屬性
static setConfig(config: ViteConfig) {
this.config = config;
this.config.base = this.config.base + 'vite_dist/';
if (this.is_dev) this.config.base = `${location.protocol}//www.dev.8591.com.tw/v31/`;
}
//插入script標(biāo)籤加載js
static async loadScript(src: string, type?: string) {
return new Promise((resolve, reject) => {
let script = document.createElement('script');
if (type) script.type = type;
script.src = src;
document.head.appendChild(script);
script.addEventListener('load', ev => {
resolve(ev);
})
script.addEventListener('error', ev => {
reject(ev)
})
})
}
//獲取編譯後清單文件
private static async getManifest() {
this.manifest_loading = true;
let manifestSrc = this.config.base + `manifest.json?t=${new Date().getTime()}`
await $.get(manifestSrc, res => {
this.manifestJson = res;
}, 'json');
// 加載兼容墊片
if(!this.support_module){
this.manifestJson['vite/src/legacy-polyfills'] = this.manifestJson['\x00vite/legacy-polyfills']
let legacy_src = this.config.base + this.manifestJson['vite/src/legacy-polyfills'].file
await this.loadScript(legacy_src)
}
this.manifest_loading = false;
}
//獲取manifest對(duì)應(yīng)的源字段值
static getAssetSource(src: string, ev: environment.dev): string;
static getAssetSource(src: string, ev: environment.debug | environment.production ): ManifestItem;
static getAssetSource(src: string, ev: environment): ManifestItem | string {
if (ev == 'dev') {
return this.config.base + this.root + src
}
//不支持module 則調(diào)用legacy
if(!this.support_module){
let file_suffix = src.includes('.ts') ? '-legacy.ts' : '-legacy.js';
src = src.replace(/\.js|\.ts/g, '') + file_suffix;
}
//如果是從清單文件中獲取的路徑則不需要拼接root路徑
if (src.indexOf(this.root) == -1) {
src = this.root + src;
}
return this.manifestJson[src];
}
//生產(chǎn)環(huán)境引入js
static importSrc(url: string) {
url = this.config.base + url;
if(this.support_module){
this.loadScript(url, 'module')
return;
}
if (window.System) {
return window.System.import(url)
}
}
//生產(chǎn)環(huán)境引入css
static importCSS(item: ManifestItem) {
if (this.support_module && item.css) {
for(let v of item.css){
let id = v.split('/')[1].replace(/\./g,'-');
if(!document.querySelector(`#${id}`)){
let css = document.createElement("link");
css.setAttribute('rel', 'stylesheet');
css.setAttribute('href', this.config.base + v);
css.setAttribute('id', id);
document.head.appendChild(css)
}
}
}
//檢測該文件引入的其他模塊是否包含css文件有的話導(dǎo)入
if (item.imports && item.imports.length > 0) {
for (let css of item.imports) {
let src_item = this.manifestJson[css]
if(src_item && src_item.css && src_item.css.length>0) this.importCSS(src_item)
}
}
}
//主加載器
static async use(src: string) {
//開發(fā)環(huán)境
if (this.is_dev) {
src = this.getAssetSource(src, environment.dev);
this.loadScript(src, 'module')
this.loadScript(`${this.config.base}@vite/client`, 'module')
return
}
//頁面多個(gè)vite.use時(shí),防重複加載衝突
if(this.manifest_loading){
setTimeout(() => {
this.use(src)
}, 1000);
return
}
//加載清單文件
if (!this.manifestJson) {
await this.getManifest()
}
//加載清單源文件
let src_item = this.getAssetSource(src, environment.production);
this.importCSS(src_item);
return this.importSrc(src_item.file)
}
}
如何去判斷瀏覽器是否支持esMule了,我這邊使用了一個(gè)比較笨的方法,在全局模板里面加入這段代碼,如果家人們有更好的方案可以留言告知謝謝。
<script nomodule>window.support_module = false;</script>
至此還有一個(gè)小坑要說說,那就是熱更新的域名因?yàn)槲疫@邊是前端和后端是不同的域名,在后端php模板里面加載熱更新插件的時(shí)候,他默認(rèn)會(huì)取當(dāng)前的域名作為連接socket的域名導(dǎo)致,一直連接不上熱更新服務(wù)器,這個(gè)時(shí)候我們就要修改vite的配置文件指定域名
server: {
host: "0.0.0.0",
port: 3001,
hmr: {
host: 'localhost',
protocol: 'ws'
}
},
當(dāng)我們指定域名后熱更新js就要按照我們指定的域名去連接socket。
經(jīng)此一役我感覺我變得更強(qiáng)了
但是頭發(fā)也變少了
五、未來的暢想
在用vite+vue3遷移老頁面達(dá)到一定的規(guī)模后,就可以把他在遷移到nuxt上面求這樣就能擁有服務(wù)端渲染的能力,才能脫離后端自己定義路由和數(shù)據(jù),實(shí)現(xiàn)真正的前后端分離的目標(biāo)。
如果想考慮SEO的話建議使用無頭瀏覽器,去把js渲染成html返回給爬蟲。
以上就是php與vite結(jié)合使用案例解析的詳細(xì)內(nèi)容,更多關(guān)于php結(jié)合vite使用案例的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信公眾平臺(tái)開發(fā)教程③ PHP實(shí)現(xiàn)微信公眾號(hào)支付功能圖文詳解
這篇文章主要介紹了微信公眾平臺(tái)開發(fā)PHP實(shí)現(xiàn)微信公眾號(hào)支付功能,結(jié)合圖文形式詳細(xì)分析了基于php的微信公眾號(hào)支付功能開發(fā)流程、原理及相關(guān)操作技巧,需要的朋友可以參考下2019-04-04
PHP中spl_autoload_register函數(shù)的用法總結(jié)
本文是對(duì)PHP中spl_autoload_register函數(shù)的用法進(jìn)行了詳細(xì)的總結(jié)介紹,需要的朋友可以過來參考下,希望對(duì)大家有所幫助2013-11-11
PHP連接MySQL數(shù)據(jù)庫并以json格式輸出
PHP連接數(shù)據(jù)庫有多種方法,現(xiàn)介紹常用的MySQL數(shù)據(jù)庫連接方法,PHP連接MySQL也有兩種方式,一是面向?qū)ο?,二是面向過程方式,兩種方法稍有區(qū)別。下面通過代碼介紹兩種方法連接MySQL并以json格式輸出2018-05-05
gearman管理工具GearmanManager的安裝與php使用方法示例
這篇文章主要介紹了gearman管理工具GearmanManager的安裝與php使用方法,結(jié)合實(shí)例形式詳細(xì)分析了gearman管理工具GearmanManager的安裝及php使用GearmanManager相關(guān)配置與操作注意事項(xiàng),需要的朋友可以參考下2020-02-02
PHP簡易延時(shí)隊(duì)列的實(shí)現(xiàn)流程詳解
普通的隊(duì)列是先進(jìn)先出,但是延時(shí)隊(duì)列并不是,而是加上了時(shí)間這一權(quán)重。希望到達(dá)時(shí)間點(diǎn)的先執(zhí)行。從某種意義上來講,延遲隊(duì)列的結(jié)構(gòu)并不像一個(gè)隊(duì)列,而更像是一種以時(shí)間為權(quán)重的有序堆結(jié)構(gòu)2022-11-11

