嘿,各位作為小程式的開發者,有認真看過小程式的page-frame.html嗎!想要告訴大家,很有必要的喔!通過這段html,管中窺豹,可以領悟到小程式程式碼的載入流程,還蠻有收穫的,故在此記錄一番~
在此之前,先強烈安利有贊團隊的從原始碼看微信小程式啟動過程 ,超良心超有料,我看了三遍都麼有完全領悟得道(¯∀¯٥)…有想深入瞭解的同學可以研讀一下這篇文章~
page-frame.html
其實我覺得從模組引用和頁面展示來說,小程式開發頗像開發單頁應用。雖然開發小程式時會分不同的頁面來編寫,但實際呈現出來的是一張大頁面,也就是page-frame.html。
在開發者工具的控制檯輸入document,即可以清晰得到整段html。如下圖:
整份html的各個部分在這裡就不再贅言啦,基本是初始化全域性變數>>載入框架WAService.js>>載入業務程式碼的過程。而我最感興趣的是業務程式碼的載入。
顯而易見,每一份js程式碼實際通過script的方式載入。那浮現在我腦中的第一個問題自然是,小程式是如何實現js模組的引用的呢?
小程式的js模組載入機制
以一個簡單的demo為例,pages/home/index.js與pages/home/common.js,前者引入後者。
pages/home/index.js:
import boom from './common';
Page({});
複製程式碼
pages/home/common.js:
export default 'boom';
複製程式碼
經小程式處理後,結果如下>>>>
pages/home/index.js:
define("pages/home/index.js", function(require, module, exports, window,document,frames,self,location,navigator,localStorage,history,Caches,screen,alert,confirm,prompt,fetch,XMLHttpRequest,WebSocket,webkit,WeixinJSCore,Reporter,print,URL,DOMParser,upload,preview,build,showDecryptedInfo,syncMessage,checkProxy,showSystemInfo,openVendor,openToolsLog,showRequestInfo,help,showDebugInfoTable,closeDebug,showDebugInfo,__global,WeixinJSBridge){ 'use strict';
var _common = require('./common');
var _common2 = _interopRequireDefault(_common);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
Page({});
});
複製程式碼
pages/home/common.js:
define("pages/home/common.js", function(require, module, exports, window,document,frames,self,location,navigator,localStorage,history,Caches,screen,alert,confirm,prompt,fetch,XMLHttpRequest,WebSocket,webkit,WeixinJSCore,Reporter,print,URL,DOMParser,upload,preview,build,showDecryptedInfo,syncMessage,checkProxy,showSystemInfo,openVendor,openToolsLog,showRequestInfo,help,showDebugInfoTable,closeDebug,showDebugInfo,__global,WeixinJSBridge){ 'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = 'boom';
});
複製程式碼
可見小程式對這份程式碼做了es6>es5的轉換(當然前提是要在專案設定中開啟允許),並將其處理成類amd的模式。那麼小程式是如何處理define及require的呢?
因為程式碼在執行時全域性變數define和局域變數require已存在,所以我們可以在程式碼中列印它們出來,並順勢找到這兩段程式碼定義的位置。它們都位於WAService.js。
define:
程式碼表明,所有js都以路徑-模組的鍵值對的方式存在了某全域性變數c裡。模組有兩個屬性,status和factory。其中status暫為1,代表模組尚未載入;factory即被閉包在函式中的模組內容。
require:
可以看到require可分為三步:
-
從全域性變數c中讀出define裡定義的模組物件(即當前的t)。
-
如status為1,則初始化模組;否則直接返回t.exports。
-
載入模組
①將status的值置為2,表示該模組已被初始化。
②呼叫t.factory(即r(...)這一步)載入模組,並將結果賦給t.exports。
仔細看看r()這一步,有傳入三個入參,分別對應define中的require, module及exports。第一個入參,i(e),它到底執行了什麼呢?答案就在require的上面一個程式碼塊中(line 37005)。它對模組的存在性做了校驗,若讀不到模組,會丟擲cant find module xxx的異常。它的返回值就是require函式。
③當一段js模組載入完成後,其模組物件會變成
{ status: 2, factory: t, exports: t裡匯出的值 } 複製程式碼
所以,這其實就是典型的模組載入思路,我覺得也與webpack打包模組的處理方式十分相似,保證每份js模組只在被第一次引用時被初始化一次,並將結果快取全域性以供其他模組日後使用。
js引入流程
通用載入
瞭解完小程式的js模組載入機制,再將目光移回page-frame.html~
不難發現小程式通過script標籤引入所有js,並按照其他js > 頁面js >app.js的順序。之後會立即執行require('app.js')和require('各頁面.js')來註冊app和頁面。
不過,大家有好奇過這幾塊script標籤是如何生成的嗎?它們是一開始就存在嗎?只要我們接著往下看,注意之後的script標籤,可以留意到這樣一段程式碼:
重要的部分我大概圈了出來~從中發現,其實上段程式碼中小程式引入的所有js script,都是通過這段終極script製造出來的喔。它的行動過程大概分為兩步:
- 通過document.head.appendChild()動態append所有需要載入的js,如
<script src="pages/home/index"></script>
`(小小留意一下順序問題,app.js最後才被add script) - 待所有需載入js的script新增完畢後,會在最後append進
require('app.js')和require('各頁面.js')
的程式碼塊來執行對應板塊的程式碼!~
所以這就是為什麼app.js和各page.js在引入完成後,就會自動執行的原因~
元件載入
元件是小程式晚些時候提供的能力,我們也可以將其視為一種特殊的頁面。如下圖,可見元件載入方式也是與頁面一樣一樣的~(當然實現不一樣)。
好的,目前為止,普通頁面的載入過程應該已一目瞭然~那自然引入下一個問題,小程式是如何處理分包程式碼載入的呢,大概率也是在前往分包頁面後,將分包程式碼append script的把?
分包載入
那麼頁面結構保持不變,不過在app.json中,將pages/pageA劃入subPackages的分包範疇。再次重新整理程式碼:
在home頁時,可以看到document的結構中確實只有app.js和home.js,沒有引入pageA.js:
然後navigateTo pageA頁,再次檢閱document結構,發現符合猜想:
原始碼的基礎上多了pageA的script,以及又一大坨程式碼段,粗略看起來是處理wxml的nodes外加與處理載入相關的其他程式碼…不過之前我以為也會顯性通過appendrequire('pages/pagesA/index.js')
程式碼塊的方式來執行pageA.js頁面,現在看來並沒有。
總結
①page-frame.html體現了小程式處理頁面的顯性流程。
②各js模組通過script標籤引入,通過類AMD方式進行載入執行。
③app.js和page.js以及component.js會在引入後立即執行。
emmm…雖然知道了這些我很開心 但是貌似對開發也麼有什麼太大的幫助>.< 不過總比兩眼一抹黑的狀態要好啦,並且想做小程式打包時,知道這些會有些許裨益~!Happy wxa coding!