你好,我的名字是Lucas Wasilewski,就像自從我開始使用NodeJS 編程(2021 年初)以來我在github 上添加項目描述一樣,我一直想寫一些看起來像工具的東西,在我觀看有關該項目的紀錄片後,這種情況才有所增加,我驚訝於開源世界如何能夠有一些曲折,並且在需要時非常受歡迎。經過一週的大量頭痛之後,我決定寫這篇文章,以便未來想要這個挑戰的瘋狂程式設計師不會犯我犯過的同樣的錯誤。
這個術語很容易誤導那些不太了解主題的人,因此需要一個好的定義:
Javascript 執行時期是一種允許您在瀏覽器之外運行語言的工具
現在有3 個流行的運行時:NodeJS、Deno (Node Killer) 和Bun (Deno Killer),但它們基本上做同樣的事情:它們允許您在瀏覽器之外使用javascript 並使用其他庫來創建新功能,這非常好,因為您可以使用它們中的任何一個來建立伺服器、創建庫甚至移動或終端應用程式。
Node 和Deno 都是由同一個人創建的:Ryan Dahl,早在2009 年,他就創建了該工具,使開發人員能夠創建“異步IO”應用程序,也就是說,不會阻塞主線程,但仍會繼續回應請求,考慮到這一點,他創建了Libuv,一個專門做這件事的庫。在那之前,這個計畫只是一大堆C,如果他想讓更多人使用這個工具,他需要一些更容易理解和使用的語言,巧合的是,谷歌同時推出了V8,總的來說,這是一個超快速的javascript 編譯器,這使他將兩者結合起來,從而創建了Node.
一段時間後(更具體地說是9 年),Ryan 離開了該專案並開始從事其他他認為更有趣的事情,這讓他意識到一些可以在Node 中修復的錯誤,但社區已經非常大了退一步是不可能的,所以,決心做得更好,他創建了Deno,另一個承諾比Node 優越得多的IO 運行時,從今天(2024 年)開始,Deno 是2.0 版本,非常好用。對於專案和社區來說都是穩定的。
這整個故事讓更多的人加入了運行時社區,這也導致我們創建了 Bun,更好的是,我的和你的運行時!現在讓我們開始正題吧。
如前所述,V8 是 Node 引擎,因此我們必須實際下載它並手動編譯它才能存取其庫和標頭。由於它是一個 Google 項目,他們有自己的下載和編譯方法,因此為此我們必須遵循他們的手冊:鏈接,只需複製和貼上即可到達最終命令。
但是,我在這裡犯了一個錯誤,花了三天時間我才意識到我所做的一切都是錯的。使用以下命令產生建置設定檔後:
tools/dev/v8gen.py x64.release
您需要非常小心out.gn/x64.release/資料夾中的args.gn文件,因為它包含ninja(編譯工具)將用來產生庫文件的構建配置,一些舊教程使用v8_monolithic = true 參數,但在最近的版本中不再使用。根據這個 StackOverflow 評論,我們現在需要使用 is_component_build = true 參數來產生正確的檔案並在編譯檔案時修改標誌,這是一個非常愚蠢的事情,如果您不注意的話可能會浪費寶貴的時間。
正確放置其餘標誌後,我們只需要執行指令來編譯專案
ninja -C out.gn/x64.release
同時,去吃點東西吧,因為 V8 是一個非常廣泛的項目,有無數的測試,根據您的機器,這個過程很容易需要 1 小時或更長時間,所以讓它運行並繼續閱讀。
編譯完成後,您可以查看 v8/samples/hello-world.cc 並開始了解如何編譯 javascript,但具體是以下幾行:
v8::Local<v8::String> source = v8::String::NewFromUtf8Literal(isolate, "'Hello' + ', World!'"); // Compile the source code. v8::Local<v8::Script> script = v8::Script::Compile(context, source).ToLocalChecked();
繼續使用包含「Hello World」的字串,建立函數、循環、條件,當您意識到如果包含經典的console.log() 時,您將收到一個未定義的訊息,這讓您感到很困惑對我來說,我一直認為console 物件是V8 本身的一部分,但事實上Node 本身包含它,並且瀏覽器將它作為DOM 的一部分包含(2012 年的帖子說瀏覽器可能不支援console.log ),這意味著我們必須自己創建它。
為了能夠創建我們自己的函數,我們首先需要了解V8 適用於多個範圍,其中之一是上下文,通過它,運行時知道在其中的位置以及如何單獨執行腳本可能有一個全局對像在所有其他物件之間共享,我們將在其中插入自訂函數。
tools/dev/v8gen.py x64.release
透過這些行,我們能夠建立一個名為 global 的對象,我們插入了一個「列印」函數模板,該模板在執行時呼叫 Print 函數。
v8::Local<v8::String> source = v8::String::NewFromUtf8Literal(isolate, "'Hello' + ', World!'"); // Compile the source code. v8::Local<v8::Script> script = v8::Script::Compile(context, source).ToLocalChecked();
Print 函數接收這個瘋狂的參數,其中包含有關javascript 中函數調用的信息,透過它,我們迭代其中的所有項目,將它們轉換為C 字串並將它們打印在屏幕上,非常直接,非常簡單,它完成了它的作用,這足以將它放入一個文件中,讀取它並在V8 上播放它(我把那個留在你手中)。
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(GetIsolate()); global->Set(GetIsolate(), "print", v8::FunctionTemplate::New(GetIsolate(), this->Print)); v8::Local<v8::Context> context = v8::Context::New(GetIsolate(), nullptr, global);
好吧,我希望到目前為止您已經能夠跟上,甚至已經停止閱讀來為您的自製Node 進行一些獨特的實現,但是V8 只會帶我們到目前為止,以便我們能夠更接近專業運行時我們需要讓javascript 能夠執行更多操作,為此我們將使用Libuv,它正是為此而創建的。
您可以在這裡找到安裝和編譯的教學。這裡需要注意的重要一點是,它使我們可以自由地執行非同步操作,即不阻塞主線程,從而允許程式在執行較繁重的工作(例如打開檔案或等待伺服器中的請求)時繼續執行插座)。
它本身已經內建了創建 http 伺服器的功能,因此我們只需將其與 V8 呼叫同步即可。毫無疑問,這不是一件容易的事,因為這兩個庫的介面有很大不同,因此很難將兩者連接起來,但總有一種方法,並且節點原始碼是開放的,因此請務必從其中偷取一些想法那裡
我們已經到了另一篇文章的結尾,我們透過它來看看我在實現過程中註意到的一些細節。首先肯定是複雜性,當然,這不是一個簡單的項目,但是一旦你了解瞭如何與 V8 介面交互,事情就會進展得很快。
這個專案也讓我更了解 Node。事實上,運行時只是庫通訊的集合體,這使得理解更複雜的事物(如「事件循環」)如何運作變得非常容易。
如果你想看看我做對了什麼,或者可能做錯了什麼,請看一下 github 上的項目:done
空談很便宜,給我看程式碼 - Linus Torvalds
## 參考文獻
https://github.com/libuv/libuv
https://v8.dev/docs
https://stackoverflow.com/questions/71213580/cant-get-v8-monolith-to-genorate
https://github.com/ErickWendel/myownnode
https://github.com/WasixXD/done
以上是建立運行時的詳細內容。更多資訊請關注PHP中文網其他相關文章!