Hey, 看看小程式的page-frame.html把~

DearVikki發表於2018-12-08

嘿,各位作為小程式的開發者,有認真看過小程式的page-frame.html嗎!想要告訴大家,很有必要的喔!通過這段html,管中窺豹,可以領悟到小程式程式碼的載入流程,還蠻有收穫的,故在此記錄一番~

在此之前,先強烈安利有贊團隊的從原始碼看微信小程式啟動過程 ,超良心超有料,我看了三遍都麼有完全領悟得道(¯∀¯٥)…有想深入瞭解的同學可以研讀一下這篇文章~

page-frame.html

其實我覺得從模組引用和頁面展示來說,小程式開發頗像開發單頁應用。雖然開發小程式時會分不同的頁面來編寫,但實際呈現出來的是一張大頁面,也就是page-frame.html。

在開發者工具的控制檯輸入document,即可以清晰得到整段html。如下圖:

Hey, 看看小程式的page-frame.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:

Hey, 看看小程式的page-frame.html把~

程式碼表明,所有js都以路徑-模組的鍵值對的方式存在了某全域性變數c裡。模組有兩個屬性,status和factory。其中status暫為1,代表模組尚未載入;factory即被閉包在函式中的模組內容。

require:

Hey, 看看小程式的page-frame.html把~

可以看到require可分為三步:

  1. 從全域性變數c中讀出define裡定義的模組物件(即當前的t)。

  2. 如status為1,則初始化模組;否則直接返回t.exports。

  3. 載入模組

    ①將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~

Hey, 看看小程式的page-frame.html把~

不難發現小程式通過script標籤引入所有js,並按照其他js > 頁面js >app.js的順序。之後會立即執行require('app.js')和require('各頁面.js')來註冊app和頁面。

不過,大家有好奇過這幾塊script標籤是如何生成的嗎?它們是一開始就存在嗎?只要我們接著往下看,注意之後的script標籤,可以留意到這樣一段程式碼:

image-20181208010815326

重要的部分我大概圈了出來~從中發現,其實上段程式碼中小程式引入的所有js script,都是通過這段終極script製造出來的喔。它的行動過程大概分為兩步:

  1. 通過document.head.appendChild()動態append所有需要載入的js,如<script src="pages/home/index"></script>`(小小留意一下順序問題,app.js最後才被add script)
  2. 待所有需載入js的script新增完畢後,會在最後append進require('app.js')和require('各頁面.js')的程式碼塊來執行對應板塊的程式碼!~

所以這就是為什麼app.js和各page.js在引入完成後,就會自動執行的原因~

元件載入

元件是小程式晚些時候提供的能力,我們也可以將其視為一種特殊的頁面。如下圖,可見元件載入方式也是與頁面一樣一樣的~(當然實現不一樣)。

Hey, 看看小程式的page-frame.html把~

好的,目前為止,普通頁面的載入過程應該已一目瞭然~那自然引入下一個問題,小程式是如何處理分包程式碼載入的呢,大概率也是在前往分包頁面後,將分包程式碼append script的把?

分包載入

那麼頁面結構保持不變,不過在app.json中,將pages/pageA劃入subPackages的分包範疇。再次重新整理程式碼:

在home頁時,可以看到document的結構中確實只有app.js和home.js,沒有引入pageA.js:

Hey, 看看小程式的page-frame.html把~

然後navigateTo pageA頁,再次檢閱document結構,發現符合猜想:

Hey, 看看小程式的page-frame.html把~

原始碼的基礎上多了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!

相關文章