deno的執行非常簡單,使用
deno xx.ts
即可。 從deno hello.ts出發,探究一下deno的運行機制。
- 學習目標
- deno代碼的模塊
- 各模塊之間的交互模式
回顧
- 在上一節學習過程中,已經知道了執行
make
指令之后,會將go代碼、ts代碼、js代碼已經node_module代碼打包。可以大致猜測一下,在整個deno項目里面,go會占一部分,js也會占一部分。作為js和go的交互模塊,v8worker也是很重要的一部分。
v8worker
- v8worker本身是ry大神基于V8做的一個go和js調用的中間層。主體只有兩個功能:
- 接收消息
- golang接收來自js的消息
//worker.go
func New(cb ReceiveMessageCallback) *Worker {
//...
}
- js接收來自golang的消息
V8Worker2.recv((ab: ArrayBuffer) => {
//...
}
- 發送消息給js
- golang分送消息到js
//worker.go
func (w *Worker) SendBytes(msg []byte) error {
- js分送消息到golang
V8Worker2.send(msg)
在deno里面,作者對v8worker做了簡單的封裝,在go和js層面分別做了一個 分發器。
dispatch.go
和dispatch.ts
,分別提供了訂閱、發布功能。
dispatch.go
func Sub(channel string, cb Subscriber) {
subscribers, ok := channels[channel]
subscribers = append(subscribers, cb)
channels[channel] = subscribers
}
func Pub(channel string, payload []byte) {
wg.Add(1)
resChan <- &BaseMsg{
Channel: channel,
Payload: payload,
}
}
dispatch.js
export function sub(channel: string, cb: MessageCallback): void {
let subscribers = channels.get(channel);
subscribers.push(cb);
}
export function pub(channel: string, payload: Uint8Array): null | ArrayBuffer {
const msg = pb.BaseMsg.fromObject({ channel, payload });
// ...
return send(ab);
}
通過以上代碼,就可以實現一個訂閱者模式,從而實現數據交互。值得一提的是,數據交互格式為
protobuf
。這種方式相當安全、高效。
main.go
- 通過下面代碼可以知道,
deno
的主入口就是 ./cmd/main.go文件
deno: msg.pb.go $(GO_FILES)
go build -o deno ./cmd
- ./cmd/main.go
package main
import (
"github.com/ry/deno"
)
func main() {
//初始化deno配置,預加載js代碼。形成bridger
deno.Init()
//執行denoMain()方法
deno.Eval("deno_main.js", "denoMain()")
deno.Loop()
}
- 初始化 2. 執行js 里面的 denoMain方法 3.執行一個輪詢
main.go 。 ./cmd/main.go引用了 deno包。Init、Eval、Loop都位于main.go下
Init
//創建所需文件夾 在 ${home}/.deno 創建 src cache 兩個文件夾
createDirs()
//訂閱 os
InitOS()
//訂閱echox
InitEcho()
//訂閱timers
InitTimers()
//訂閱fetch
InitFetch()
//一個v8工作線程
worker = v8worker2.New(recv)
//加載main.js代碼.
//main.js就是Makefile里面通過
/**
#生成 dist/main.js 通過 ts文件 和 node_moduels
dist/main.js: $(TS_FILES) node_modules
./node_modules/.bin/tsc --noEmit # Only for type checking.
./node_modules/.bin/parcel build --out-dir=dist/ --log-level=1 --no-minify main.ts
打包生成的代碼。
*/
main_js = stringAsset("main.js")
//加載main.js
err := worker.Load("/main.js", main_js)
exitOnError(err)
//代碼的map,用于定位代碼.
main_map = stringAsset("main.map")
- Eval
// It's up to library users to call
// deno.Eval("deno_main.js", "denoMain()")
func Eval(filename string, code string) {
//執行代碼
err := worker.Load(filename, code)
exitOnError(err)
}
- Loop
func Loop() {
cwd, err := os.Getwd()
check(err)
/**
可以將v8worker2 理解為一個C/S的架構。
Client發送消息,Server端回復消息。
*/
PubMsg("start", &Msg{
Command: Msg_START,
StartCwd: cwd,
StartArgv: workerArgs,
StartDebugFlag: *flagDebug,
StartMainJs: main_js,
StartMainMap: main_map,
})
DispatchLoop()
}
執行流程
- 初始化。
- 下面四個方法,通過V8注冊方法,實現對js調用的訂閱。
InitOS()
//訂閱echox
InitEcho()
//訂閱timers
InitTimers()
//訂閱fetch
InitFetch()
os.go
//訂閱了os。提供了5種模式。
Sub("os", func(buf []byte) []byte {
switch msg.Command {
case Msg_CODE_FETCH:
case Msg_CODE_CACHE:
case Msg_EXIT:
case Msg_READ_FILE_SYNC:
//讀取文件
case Msg_WRITE_FILE_SYNC:
}
})
剩下三個大同小異,不做具體說明。
- 加載 js文件
//會加載 /dist/main.js 文件
main_js = stringAsset("main.js")
//加載main.js,編譯并執行。
err := worker.Load("/main.js", main_js)
- 執行denoMain方法。
- denoMain方法是 位于main.js 里面。實際上在打包之前,是位于main.ts下。
(window as any)["denoMain"] = () => {
initTimers(); //初始化timer
initFetch(); //初始化fetch
//訂閱start事件。
dispatch.sub("start", (payload: Uint8Array) => {
runtime.setup(mainJs, mainMap);
//就是文件路徑
const inputFn = argv[0];
//模塊加載
const mod = runtime.resolveModule(inputFn, `${cwd}/`);
//執行編譯和運行ts代碼
mod.compileAndRun();
});
這里訂閱了start事件,等待觸發。觸發的時候,就是對ts文件做編譯、執行的時候。
- 開始Loop
main.go
func Loop() {
cwd, err := os.Getwd()
/**
觸發start事件。
*/
PubMsg("start", &Msg{
Command: Msg_START,
StartCwd: cwd,
StartArgv: workerArgs,
StartDebugFlag: *flagDebug,
StartMainJs: main_js,
StartMainMap: main_map,
})
//對消息輪詢,實現數據交換。
DispatchLoop()
}
dispatch.go
func DispatchLoop() {
// 對協程做監控。
//實際上,Pub會新建一個協程消息,這里做監聽。有新信息就通過worker發送給js層。
for {
select {
case msg := <-resChan:
out, err := proto.Marshal(msg)
err = worker.SendBytes(out)
case <-doneChan:
return
}
}
image.png
總結
- 目前來看deno是一個很簡單的執行ts的程序。效率也不是很高。實際上對ts的解析和執行都是在runtime時期運行的,相對于以前對typescript先編譯成js再使用的方式,是一種全新的嘗試。