javascript
js后退页面不重新加载_快应用:支持加载单独JS文件的规范思考
本文討論的主要是:webpack打包時(shí),能夠?qū)⒁蕾嘕S不合入到頁面JS中,而是在rpk的ZIP壓縮文件中單獨(dú)存在,然后運(yùn)行時(shí),頁面在需要的時(shí)候加載該JS文件;
當(dāng)前如果每個(gè)頁面都引入一個(gè)共同的JS文件,如:foo.js;那么toolkit工具打包時(shí),會將每個(gè)foo.js都打包到頁面JS代碼中;
這樣帶來以下幾個(gè)問題,造成頁面渲染變慢:
1) 每個(gè)頁面JS都包含了foo.js,使得JS代碼重復(fù),導(dǎo)致rpk包體積增大,從而運(yùn)行時(shí)下載rpk時(shí)間變長;
2) 每個(gè)頁面JS都包含各自的foo.js,運(yùn)行時(shí)JS引擎編譯代碼字符串為AST和JS對象,用時(shí)變長;
3) 由于JS代碼存在多個(gè)地方,導(dǎo)致每個(gè)頁面引入的foo都是獨(dú)立的JS對象,而不是:同一個(gè)JS內(nèi)存對象;
面對上面的問題,我們提供一種新的規(guī)范來解決:編譯時(shí)支持JS單獨(dú)構(gòu)建,并在運(yùn)行時(shí)調(diào)用加載;
2. 規(guī)范的方案拆解
針對上面所述,規(guī)范實(shí)現(xiàn)的工作主要拆解到兩大模塊中:
1) 編譯時(shí):頁面中用到的公共JS文件;在構(gòu)建時(shí),生成單獨(dú)的JS文件;保持rpk中只有一份該JS文件的代碼;
2) 運(yùn)行時(shí):提供動態(tài)加載JS文件的API:頁面中首次加載時(shí),則從內(nèi)存/磁盤中同步讀取并執(zhí)行;第二次加載時(shí),選擇復(fù)用內(nèi)存中之前的JS對象或者重新執(zhí)行;
上面劃分看上去實(shí)現(xiàn)簡單,但圍繞一些拆解出來的子任務(wù),其實(shí)思路很多,需要權(quán)衡利弊:
1) 編譯時(shí)的單獨(dú)JS構(gòu)建方案;
2) 當(dāng)前編譯時(shí)用的toolkit使用的是webpack4;如果使用其它工具(如:rollupjs),如何持續(xù)兼容;
3) 動態(tài)加載JS文件時(shí),需要考慮:模塊化,緩存,頁面/APP上下文,時(shí)間成本,向后兼容,可擴(kuò)展能力等的問題;
比如:模塊化是否要放在運(yùn)行時(shí);當(dāng)頁面銷毀后如果保證頁面里的接口調(diào)用也跟著銷毀;
接下來,圍繞各項(xiàng)子任務(wù)分別進(jìn)行考慮;
3. 任務(wù)1:編譯時(shí)的單獨(dú)JS構(gòu)建方案
目前市面上主流的構(gòu)建方案分為兩類:語法型,配置型;其中webpack4中的splitChunkPlugin應(yīng)用就屬于配置型;
3.1 語法型
這種指的是,快應(yīng)用平臺運(yùn)行時(shí)直接給開發(fā)者暴露一個(gè) $app_evaluate$ 的函數(shù),開發(fā)者在自己的ux文件或者js文件中,通過`const foo = $app_evaluate$('./foo.js')`方式引入JS依賴;
當(dāng)工具toolkit編譯時(shí),檢測到這樣的函數(shù)API的JS,就不再打包到頁面JS中;
優(yōu)點(diǎn):
1) 開發(fā)者可以靈活改寫,讓某個(gè)JS既可以打包到某個(gè)頁面中,又可以獨(dú)立存在;
2) toolkit編譯時(shí)無需開發(fā)者配置,簡單場景下方便易用;
缺點(diǎn):
1) 這種方式對單個(gè)JS引用,很容易替換不完全,造成重復(fù)打包;
2) 編譯工具需要自行構(gòu)建依賴分析的能力,將相對路徑的引入轉(zhuǎn)換為絕對路徑;不僅無法復(fù)用webpack本身的依賴分析,而且需要增加依賴分析的邏輯和管理;實(shí)現(xiàn)起來增加工作量和難度;
3) 對開發(fā)者來說,有認(rèn)知成本,與現(xiàn)有的require, import語法相比,不夠貼切自然;
3.2 配置型
這種指的是,開發(fā)者通過給某個(gè)JS文件增加標(biāo)識,標(biāo)識為獨(dú)立打包;接著在ux與js中,如果通過require或者import引入的,那么toolkit在編譯時(shí),根據(jù)標(biāo)識,就不再打包到頁面JS的代碼中,而是單獨(dú)創(chuàng)建一個(gè)JS文件;
比如:page1,page2等都引用了 foo.js文件,foo.js中聲明:`/** quickapp:standalone */` 標(biāo)識為該文件不打包到頁面JS中;
優(yōu)點(diǎn):
1) 開發(fā)者只需要在JS文件中聲明一下即可,或者采用其它配置形式(如:在manifest.json或者quickapp.config.js的配置文件中聲明);
缺點(diǎn):
1) 如果JS文件很多,可能每個(gè)希望獨(dú)立打包的JS都需要聲明;
2) 編譯時(shí)webpack分析出依賴關(guān)系后,需要替換代碼中的`require`關(guān)鍵字為`$app_evaluate$`,避免被webpack引入;
當(dāng)然,配置型的表現(xiàn)形式也有很多:
1) 與文件所在路徑放在一起,同時(shí)更新:
在JS文件中,使用標(biāo)記 `/** quickapp:standalone */` 類似語法,配置每個(gè)JS文件單獨(dú)打包;
2) 在manifest.json中聲明:
在manifest.json文件中,配置JS文件單獨(dú)打包,通過resource屬性去聲明單獨(dú)打包的JS文件的路徑;
3) 在quickapp.config.js中聲明:
`quickapp.config.js`是快應(yīng)用項(xiàng)目下的一個(gè)配置文件,代表項(xiàng)目將如何構(gòu)建;通過在配置文件中聲明,以確定哪些文件單獨(dú)創(chuàng)建;
由于JS單獨(dú)打包的功能僅僅只是一個(gè)編譯時(shí)的工作,不應(yīng)該與manifest這種運(yùn)行時(shí)檢查的文件混合在一起,因此優(yōu)于上一條;
以上三種類似Java中的Annotation與XML聲明,各有優(yōu)缺:
前者直接分散,和代碼一起,不必?fù)?dān)心JS文件路徑變化時(shí),引起后者配置路徑無效的問題;
后者直觀方便,統(tǒng)一性強(qiáng),但是與JS文件代碼分離較遠(yuǎn),容易發(fā)生路徑找不到;
3.3 總結(jié)
其實(shí)對開發(fā)者來說,他關(guān)注的是:如何以最小的認(rèn)知成本,來達(dá)到rpk最小的體積與最快的頁面加載;
所以,最佳的方式是:什么配置都不需要就能實(shí)現(xiàn),對開發(fā)者無感知;
其次是簡單的一條選擇項(xiàng)或一條配置就能完成;
語法型有較大的缺陷、難度、認(rèn)知,就不再考慮了;
上面說的語法型、配置型是站在開發(fā)者的角度上說的;如果站在內(nèi)部實(shí)現(xiàn)的角度來說,可能部分屬于語法型了,因?yàn)樯婕暗綄?#96;require('foo.js')`替換為`$app_evaluate$('foo.js')`的更改;4. 任務(wù)2:各頁面加載同一個(gè)JS文件時(shí),其JS對象是否應(yīng)該共用
下文的`evaluate`指的是:編譯JS文件的字符串代碼 轉(zhuǎn)換為 JS對象;(通過:eval,new Function等形式)共用指的是:各頁面都依賴同一個(gè)JS文件時(shí),那么這個(gè)JS文件在evaluate后對應(yīng)的JS對象,是否應(yīng)該被各頁面共用?
如果共用,意味著:各頁面之間訪問的是:同一個(gè)JS對象;頁面之間對該模塊可以一起更新并取值;
如果不共用,意味著:各頁面之間訪問的是:不同的JS對象;頁面之間的該模塊操作互不干擾影響;
4.1 總結(jié)
經(jīng)過討論,我們認(rèn)為:各頁面之間不要共用,利大于弊;
因?yàn)榭鞈?yīng)用是一個(gè)APP的概念,頁面與頁面之間應(yīng)該保持很強(qiáng)的獨(dú)立性,頁面之間的數(shù)據(jù)與狀態(tài)更新應(yīng)該通過專有的固定通道來通信;
否則的話,開發(fā)者容易濫用,帶來頁面之間公用模塊的互相操作影響,造成:耦合性強(qiáng),狀態(tài)不穩(wěn)定,通信機(jī)制泛濫,監(jiān)控不到位;
與瀏覽器中運(yùn)行的SPA模式、NodeJS中模塊共用的方式相比,快應(yīng)用選擇了不一樣的設(shè)計(jì)思路,最主要的目的就是:保證頁面的強(qiáng)獨(dú)立性;
5. 任務(wù)3:如果JS模塊不共用,那么頁面之間隔離的方式怎么實(shí)現(xiàn)?
5.1 方式1. JS文件只會evaluate一次;
雖然JS文件僅evaluate一次,但是利用編譯時(shí)webpack的能力,它將每個(gè)JS文件封裝為模塊化代碼(`function (module, exports, __webpack_require__) { ...code... }`);
當(dāng)每個(gè)頁面重新加載該JS時(shí),其實(shí)是填充到了各自頁面級別的module緩存`installedModules`中;
這樣,當(dāng)每次頁面引入該JS時(shí),得到的就是,該頁面下的該模塊的JS對象;
優(yōu)點(diǎn):JS的evaluate只會一次,但頁面對應(yīng)的該JS模塊會重新填充一次;
缺點(diǎn):JS中模塊化代碼之外的代碼只會執(zhí)行一次,但是模塊內(nèi)的代碼會執(zhí)行N次(N個(gè)頁面加載);
5.2 方式2. 每次頁面加載JS文件時(shí),都重新evaluate對應(yīng)的JS代碼,得到不同的JS對象;
優(yōu)點(diǎn):做到了運(yùn)行時(shí)的天然隔離每個(gè)JS模塊,即使以后更換為沒有模塊化的打包工具時(shí),仍然能夠無縫隔離;
缺點(diǎn):每次新頁面加載,都重新evaluate,引起同樣的JS代碼執(zhí)行多次;
5.3 總結(jié)
綜上所述,我們認(rèn)為:頁面中的JS模塊獨(dú)立性是一項(xiàng)運(yùn)行時(shí)的能力;
這種能力不應(yīng)該依賴于編譯時(shí)的保證,以后即使不使用webpack編譯工具時(shí),仍然能夠保證隔離,而且還提高了頁面的安全性;
所以,選擇方式2,頁面每次加載都重新evaluate對應(yīng)的JS代碼;
6. 任務(wù)4. 單獨(dú)JS文件的模塊化管理
既然要支持單獨(dú)的JS文件加載,那么就需要對所有的單獨(dú)JS文件做模塊化管理;
需要考慮幾個(gè)方面:實(shí)現(xiàn)位置,遞歸依賴,路徑轉(zhuǎn)換;
實(shí)現(xiàn)位置指的是:模塊化的功能在運(yùn)行時(shí)實(shí)現(xiàn),還是編譯時(shí)實(shí)現(xiàn);遞歸依賴的問題,可以參考NodeJS中的模塊化實(shí)現(xiàn),是在首次加載JS文件前,先定義模塊為空對象,然后執(zhí)行時(shí)包裹一段模塊化代碼:`function (exports, require, module, __filename, __dirname) { ...code... }`;當(dāng)發(fā)生遞歸式依賴時(shí),傳遞之前已經(jīng)定義的模塊對象;
由此來看,模塊化無論發(fā)生在編譯時(shí)還是運(yùn)行時(shí),都需要解決遞歸依賴;
6.1 實(shí)現(xiàn)位置:編譯時(shí)
在webpack構(gòu)建中,如果是僅生成一個(gè)JS文件,是通過`installedModules`內(nèi)部緩存了各個(gè)模塊;如果要生成多個(gè)JS文件時(shí),是通過`webpackJsonp`完成的,因此只需要對這塊針對快應(yīng)用添加適配,提供同步讀取rpk中JS文件的能力,即可完成編譯時(shí)模塊化的能力;
編譯時(shí)同時(shí)也需要完成源代碼中相對路徑到絕對路徑的轉(zhuǎn)換,這塊都可以使用webpack現(xiàn)有的實(shí)現(xiàn)方式;
6.2 實(shí)現(xiàn)位置:運(yùn)行時(shí)
運(yùn)行時(shí)模塊化增加這塊機(jī)制的好處在于:可以擺脫對webpack編譯工具的模塊管理依賴,以后如果有其它的編譯工具,那么也能夠無縫支持;
6.3 總結(jié)
考慮到規(guī)范發(fā)版,開發(fā)的時(shí)間成本,本次先利用成熟的webpack工具,即:編譯時(shí)能力解決,待以后再增加對應(yīng)的運(yùn)行時(shí)實(shí)現(xiàn);
關(guān)于模塊化中的`動態(tài)加載`模塊的能力(`const variable1 = 'test'; require(variable1);`),以及異步加載的能力,由于需求不太緊迫,因此本次規(guī)范暫不考慮;7. 任務(wù)5:流式加載
當(dāng)前快應(yīng)用項(xiàng)目構(gòu)建的RPK文件是一個(gè)ZIP文件,里面根據(jù)運(yùn)行時(shí)加載JS文件的順序,相應(yīng)的安排了每個(gè)文件在ZIP索引中的順序;比如根據(jù)順序,先后主要為:`manifest.json`,`app.js`,每個(gè)`page.js`;
這項(xiàng)任務(wù)指的是:如何安排獨(dú)立JS文件的位置(哪些在頁面JS之前與之后),讓頁面首屏渲染需要的文件能夠保證內(nèi)存級別的同步讀取耗時(shí)最少(避免磁盤讀引起的時(shí)間損耗);
也就是說,這項(xiàng)任務(wù)的核心是:是否有辦法確定哪些JS是屬于頁面首屏渲染必須的?
經(jīng)過分析之后,目前沒有直接的辦法確定,通過讓開發(fā)者加標(biāo)記FLAG也并不是一件好辦法;
但可以換個(gè)思路考慮問題:假設(shè)所有的獨(dú)立JS放在頁面JS之前,因?yàn)樗⒉粫涣⒓磮?zhí)行為為JS對象,所以放在頁面之前也僅僅只是增加了RPK下載與ZIP解壓縮這些獨(dú)立JS的時(shí)間;目前看,這些時(shí)間相對較小;
所以實(shí)現(xiàn)思路上,可以將:單獨(dú)JS全部放在app.js與頁面JS之前,因?yàn)閍pp.js也可能會用到獨(dú)立JS;
8. 任務(wù)6:分包支持
當(dāng)前快應(yīng)用支持分包能力,非獨(dú)立包又分為:頁面模塊包與基礎(chǔ)包;
為了管理上的方便,建議:將所有的獨(dú)立JS放置在一個(gè)統(tǒng)一的固定目錄下,如:(`%PROJECT%/build/chunks/foo.js`);
定義一個(gè)`chunks`的文件夾,將獨(dú)立JS在里面,當(dāng)然這個(gè)目錄下可能還存在子目錄的路徑;
8.1 非獨(dú)立包
如果是非獨(dú)立包的話,可以將所有的獨(dú)立JS也就是對應(yīng)的目錄,移動到:`基礎(chǔ)包`中;
8.2 獨(dú)立包
如果是獨(dú)立包的話,則將自己用到的獨(dú)立JS放在該模塊下;
9. 文章總結(jié)
經(jīng)過上面幾個(gè)任務(wù)的分析,為了使得v1070版本提供的規(guī)范盡快發(fā)版,將使用編譯時(shí)的模塊化能力,運(yùn)行時(shí)僅提供文件的同步讀取API,以支持:加載單獨(dú)JS文件的規(guī)范;
針對于運(yùn)行時(shí)模塊化(即:動態(tài)加載)、異步加載的其它需求,將在后面收到開發(fā)者需求后再制定快應(yīng)用聯(lián)盟規(guī)范。
總結(jié)
以上是生活随笔為你收集整理的js后退页面不重新加载_快应用:支持加载单独JS文件的规范思考的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 最高响应比优先算法(HRRF)及例题详解
- 下一篇: 永宏plc和台达vfd-m变頻器modb