php與vite結合使用案例解析
一、背景
前段日子公司里準備要重構一個擁有10年高齡的網站,當時聽到這個消息心里無比激動,因為我現在就是這個網站的維護人員??????,在現代這個前端技術快速發(fā)展的年代很難想象我盡然還在寫JQ+php模板??????,這都不是最令我不爽的,最不爽的還是我改一段js代碼,還要去后端項目去更改時間戳,才能保證他更新,這樣換來的結果就是我要同時去更改兩個項目,哪怕我只改了一點,css也是一樣。
這個項目前端代碼和后端代碼是分開的,后端主要是php模板渲染html,前端使用的是sea.js模塊化加載插件以及JQ
二、技術調研
由于是一個10年高齡的網站了,其中了業(yè)務邏輯盤根交錯、代碼分布錯綜復雜,想要一把梭哈的話我估計我就離失業(yè)不遠了??,只能說是一個頁面一個頁面慢慢重構然后上線觀察,基于這種形勢我采用了vue3+vite的技術方向,你要問我為啥不選webpack作為打包工具,我只能說當你開過法拉利跑車后你還會去開拖拉機嗎?再者vite作為vue3的親兒子肯定選他啊。
三、搭建基礎框架
一開始本來是想直接按照vite文檔上與后端集成的那種方式去加載js和css文件的但是,我TM突然想到前端資源和后端模板壓根不在一個項目啊,咋辦去拉著臉皮求后端幫我們去動態(tài)拉取打包號的manifest資源清單?
manifest.json是一個vite打包之后產出的文件資源清單里面記錄了當前這個入口依賴了那些js和css,大致就是下面這個結構。
作為一個有理想和抱負的前端自己選的,在困難也要完成,隨即我發(fā)動聰明的大腦瞬間一個想法誕生,如果我寫一個加載器,根據入口文件名去manifest資源清單里面去尋找,入口當前依賴了那些文件并把它全部加載到頁面上不就可以了?說干就干。
四、Vite加載器編寫
首先我們先編寫一個vite 的class類里面有一個use方法用來加載入口名對應的文件依賴,方法里面還要區(qū)分是否為dev環(huán)境,如果為dev環(huán)境就不需要加載資源清單了直接調用getAssetSource方法拿到文件路徑去請求入口文件,入口文件里面的依賴會根據esModule的特性瀏覽器自動發(fā)送請求獲取。
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 } //頁面多個vite.use時,防重複加載衝突 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) } }
現在讓我們來看看getAssetSource方法做了啥
//獲取manifest對應的源字段值 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 則調用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
插件打包出來的代碼做兼容舊版瀏覽器的操作,最終返回入口文件名依賴的文件對象
這個就是資源清單里兼容的舊版瀏覽器的對象名
這個是支持esModel的對象名
dev環(huán)境下獲得完整文件路徑后則調用loadScript去加載文件
//插入script標籤加載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')
,這個樣子dev環(huán)境就能愉快的使用vue3和熱更新做開發(fā)了爽的一批。
現在我們來處理一下production環(huán)境下的文件加載,首先我們先加載打包好的資源清單文件,然后調用getAssetSource方法如果是production環(huán)境他會根據你傳入的文件名去資源清單里面找出當前這個入口文件名所依賴的文件對象,比如你在項目里面創(chuàng)建了一個warePay.ts的文件里面初始化了vue,然后你吧這個文件當做一個打包入口傳入給vite這個時候產出的資源清單manifest里面就會有一個根據你文件根路徑+文件名當做key的一個對象這個對象的value就是你這個文件所依賴的所有資源。
以下是加載資源清單的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; }
方法里面首先去加載資源清單文件并加上時間戳這樣只要修改代碼,打包后就能拿到最新的資源清單文件加載最新的代碼就不需要去更改php模板了美滋滋,如果不支持esModule的話還要加載兼容墊片,這里有個小坑資源清單里面的兼容墊片的key前面會有一串Unicode編碼的空格。
所以我們要轉換一下。
拿到依賴對象后就可以加載css和js文件了,首先我們先調用importCSS來加載css文件,如果當前這個對象還有子依賴的css的話就遞歸去加載其子依賴的css。
//生產環(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文件有的話導入 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) } } }
隨后再去調用importSrc去加載js
//生產環(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如果支持就直接加載主入口文件就可以了,隨后瀏覽器后自動去請求其子依賴, 如果不支持的話,我們就要調用 window.System.import方法去加載入口文件和其子依賴。
window.System對象是我們在getManifest方法里面加載的兼容墊片js里的對象他可以根據主入口去加載其子依賴,因為他的子依賴路徑全部在入口文件里面,大概就是這個樣子。
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; //設置基本屬性 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標籤加載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對應的源字段值 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 則調用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]; } //生產環(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) } } //生產環(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文件有的話導入 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 } //頁面多個vite.use時,防重複加載衝突 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了,我這邊使用了一個比較笨的方法,在全局模板里面加入這段代碼,如果家人們有更好的方案可以留言告知謝謝。
<script nomodule>window.support_module = false;</script>
至此還有一個小坑要說說,那就是熱更新的域名因為我這邊是前端和后端是不同的域名,在后端php模板里面加載熱更新插件的時候,他默認會取當前的域名作為連接socket的域名導致,一直連接不上熱更新服務器,這個時候我們就要修改vite的配置文件指定域名
server: { host: "0.0.0.0", port: 3001, hmr: { host: 'localhost', protocol: 'ws' } },
當我們指定域名后熱更新js就要按照我們指定的域名去連接socket。
經此一役我感覺我變得更強了
但是頭發(fā)也變少了
五、未來的暢想
在用vite+vue3遷移老頁面達到一定的規(guī)模后,就可以把他在遷移到nuxt上面求這樣就能擁有服務端渲染的能力,才能脫離后端自己定義路由和數據,實現真正的前后端分離的目標。
如果想考慮SEO的話建議使用無頭瀏覽器,去把js渲染成html返回給爬蟲。
以上就是php與vite結合使用案例解析的詳細內容,更多關于php結合vite使用案例的資料請關注腳本之家其它相關文章!
相關文章
JoshChen_web格式編碼UTF8-無BOM的小細節(jié)分析
下面這張圖是用chrome瀏覽器打開一個2012年-2013學年第二學期的PHP課程的期末作業(yè),用的是PHP + smarty + mysql來實現的2013-08-08