使用 WebAssembly 打造定製 JS Runtime
大 廠 技 術 堅 持 周 更 精 選 好 文
本文為來自 教育-成人與創新-前端團隊 成員的文章,已授權 ELab 釋出。
背景
這是一次簡短的整活與折騰,起因是在 lightdm-webkit2-greeter 這個 lightdm 外掛中看到了自定義 JS Runtime的魔力,它支援在顯示管理器中使用 web 技術去自定義登入介面,與作業系統的互動是通過 Runtime 中的一組 JS API來實現登入、關機、睡眠等功能。
http://doclets.io/Antergos/web-greeter/stable
把 webkit 搬過來渲染系統介面,然後通過定製的 JS Runtime 與作業系統互動,相當於對瀏覽器本身進行了改造,關鍵的實現點是把系統呼叫封裝成了Native函式,並在JS Runtime中進行繫結,以實現瀏覽器介面控制作業系統。
這種方式和 Electron 的本質區別在於,無需讓瀏覽器與另外一個程序通訊,它直接拓展了 JS 的執行時環境,與 Node 的做法十分相像,不過這次我們越過中間商賺差價,自己實現 Runtime ,可以做的更小巧和定製化。
思考
直接在瀏覽器上去定製 Runtime 這個想法確實很酷,但顯然難度屬於地獄級,這相當於我們直接去爆改 V8、JavaScriptCore 這種成熟穩定又複雜的JS引擎來是實現 JS API層面的嵌入和拓展,但 JS 引擎並不只是瀏覽器獨有,真要改的話,可以找一個輕量、好改、好移植的。
很好,但是OS binding怎麼辦?總不能直接把瀏覽器裡的JS引擎整個替換成這個不復雜,又好改,又好移植的吧?確實這裡是一個坎,卡在這,活就整下去了,暫且先不做 OS binding,改做 Web binding,讓Web Assembly來跑 Runtime,然後在 Runtime 裡再跑JS,有點套娃了,但它依舊有一些應用的場景。
DEMO
起一個 JS 引擎
-
要方便移植,要好改,方便我們快速的定製
-
Native 與 JS 的互動足夠簡單(包括資料型別的轉換,通訊的實現,事件迴圈等)
-
因為是編譯到 WebAssembly 在 Web上跑,所以傳輸體積越小越好,同時執行時記憶體佔用也最好不要太大。
這裡選擇了 Figma 曾經的方案 - Duktape
-
duktape.c duktape.h duk_config.h
-
完整的 ES5 支援
-
支援垃圾回收
-
位元組碼快取
-
支援除錯功能
簡單寫一個函式,來實現JS的執行
extern "C" char* runScript(char* script){
duk_context *ctx = duk_create_heap_default();
duk_eval_string(ctx, script);
duk_pop(ctx); /* pop eval result */
duk_destroy_heap(ctx);
return "ok";
}
拓展一些 Runtime API
-
IO 功能實現
/* Being an embeddable engine, Duktape doesn't provide I/O
* bindings by default. Here's a simple one argument print()
* function.
*/
static duk_ret_t native_print(duk_context *ctx) {
duk_push_string(ctx, " ");
duk_insert(ctx, 0);
duk_join(ctx, duk_get_top(ctx) - 1);
printf("%s\n", duk_safe_to_string(ctx, -1));
return 0;
}
-
繫結到 Runtime
duk_push_c_function(ctx, native_print, DUK_VARARGS);
duk_put_global_string(ctx, "print");
-
這裡涉及到一些堆疊的基本概念,本文不做贅述,它在 Duktape 中的實現模型如下圖所示
至此,我們實現了一個基本的JS引擎,它可以完成 ES5 程式碼的解析和執行,我們在全域性物件上注入了一個 print 方法,它是一個 Native的實現,通過引擎內部的堆疊與 JS 互動,最後 使用Duktape提供的註冊方式暴露到 JS Runtime中
編譯成 WASM
這裡編譯器的實現選用 emscripten,用它直接生成相應的 WebAssembly 檔案和相應的 JS 膠水程式碼。
-
把剛剛實現的 JS 執行函式暴露到 宿主環境中(另一個JS Runtime)
int main() {
EM_ASM("console.log('wasm js runtime is ready!')");
EM_ASM("window.runScript = Module.cwrap('runScript', 'string', ['string'])");
return 0;
}
-
在編譯的時候,指定匯出函式
CCOPTS += -s EXPORTED_FUNCTIONS=['_runScript','_main']
-
完整的Makefile 如下
DUKTAPE_SOURCES = ./engine/duktape.c
CC = emcc
CCOPTS = -s DISABLE_EXCEPTION_CATCHING=0 -s ALLOW_MEMORY_GROWTH=1 -O3 --bind
CCOPTS += -s EXPORTED_RUNTIME_METHODS=["cwrap"]
CCOPTS += -s EXPORTED_FUNCTIONS=['_runScript','_main']
CCOPTS += -I./engine # for combined sources
DEFINES =
BUILD = wasm/index.html
all: $(DUKTAPE_SOURCES) main.cpp
${CC} $(CFLAGS) $(CPPFLAGS) ${LDFLAGS} -o ${BUILD} ${DEFINES} ${CCOPTS} ${DUKTAPE_SOURCES} main.cpp ${CCLIBS}
run:
cd wasm && python3 -m http.server 8080
簡單測試
make
make run
看一下 WASM 體積,膠水程式碼+ WASM本體不 600KB 出頭,基本在一張大圖的範圍內,可以接受
藉助這兩個專案,至此我們完成了一整個 JS Runtime 定製的流程,目前看起來它完全是可用的:
-
它足夠小巧,隨取隨用
-
它與宿主 JS Runtime 完全隔離,足夠安全
-
WASM 實現相對來說在Web上是效能較好的,不會影響瀏覽器中JS執行緒
應用場景
-
JS 沙箱
-
打造外掛系統
-
把WASM 產物一移植到 WASI 以實現真正的 OS Binding
參考
-
Duktape [1]
-
Main — Emscripten 3.1.21-git (dev) documentation [2]
-
How to build a plugin system on the web and also sleep well at night [3]
參考資料
Duktape: http://duktape.org/index.html
Main — Emscripten 3.1.21-git (dev) documentation: http://emscripten.org/
How to build a plugin system on the web and also sleep well at night: http://www.figma.com/blog/how-we-built-the-figma-plugin-system/
- END -
:heart: 謝謝支援
以上便是本次分享的全部內容,希望對你有所幫助^_^
喜歡的話別忘了 分享、點贊、收藏 三連哦~。
歡迎關注公眾號 ELab團隊 收貨大廠一手好文章
位元組 跳 動 校 / 社 招 內 推 碼 : YCE7SSZ
投 遞 鏈 接 : http://job.toutiao.com/s/6QatD8H
- 使用 WebAssembly 打造定製 JS Runtime
- 前端也要懂演算法,不會演算法也能微調一個 NLP 預訓練模型
- 聯機遊戲原理入門即入土 -- 入門篇
- Plasmo Framework:次世代的瀏覽器外掛開發框架
- 深入理解 Mocha 測試框架:從零實現一個 Mocha
- Single Source of Truth:XCode SwiftUI 的介面編輯的設計理念
- 深入理解 D3.js 視覺化庫之力導向圖原理與實現
- 淺析神經網路 Neural Networks
- Cutter - Web視訊剪輯工具原理淺析
- 你可能需要一個四捨五入的工具函式
- 淺析eslint原理
- 最小編譯器the-super-tiny-compiler
- Git儲存原理及部分實現
- 淺談短鏈的設計
- Web元件構建庫-Lit
- 使用Svelte開發Chrome Extension
- Web3.0開發入門
- vscode外掛原理淺析與實戰
- 深入淺出 Web Audio API
- 探祕HTTPS