架構(gòu)概述
本節(jié)描述了Theia的整體架構(gòu)。
Theia被設(shè)計(jì)為一個(gè)可以在本地運(yùn)行的桌面應(yīng)用程序,也可以在瀏覽器和遠(yuǎn)程服務(wù)器之間工作。為了支持這兩種工作方式,Theia運(yùn)行在兩個(gè)獨(dú)立的進(jìn)程中,它們被稱之為前端和后端,相互之間通過(guò)WebSockets上的JSON-RPC消息或HTTP上的REST APIs來(lái)通信。對(duì)于Electron而言,前端和后端都在本地運(yùn)行,而在遠(yuǎn)程上下文中,后端運(yùn)行在遠(yuǎn)程服務(wù)器上。
前端和后端進(jìn)行都有它們各自的依賴注入(DI)容器(詳見下文),以方便開發(fā)者進(jìn)行擴(kuò)展。
前端
前端部分負(fù)責(zé)客戶端的UI呈現(xiàn)。在瀏覽器中,它只是簡(jiǎn)單地在渲染循環(huán)中運(yùn)行。而在Electron中,它運(yùn)行在Electron窗口中,這是一個(gè)包含Electron和Node.js APIs的瀏覽器窗口。因此,任何前端代碼都可以把瀏覽器而不是Node.js作為一個(gè)運(yùn)行平臺(tái)。
啟動(dòng)前端進(jìn)程將首先加載所有擴(kuò)展包的DI模塊,然后獲取一個(gè)FrontendApplication的實(shí)例并在上面調(diào)用start()。
后端
后端進(jìn)程運(yùn)行在Node.js上。我們使用express作為HTTP服務(wù)器,它可以不使用任何需要瀏覽器平臺(tái)的代碼(DOM API)。
啟動(dòng)后端應(yīng)用程序?qū)⑹紫燃虞d所有擴(kuò)展包的DI模塊,然后獲取一個(gè)BackendApplication的實(shí)例并在上面調(diào)用start(portNumber)。
默認(rèn)情況下后端的express服務(wù)器也為前端提供代碼。
按平臺(tái)進(jìn)行區(qū)分
在擴(kuò)展包的根目錄下,包含如下子目錄層級(jí),按不同的平臺(tái)進(jìn)行區(qū)分:
common目錄下包含的代碼不依賴于任何運(yùn)行時(shí)。
browser目錄下包含的代碼需要運(yùn)行在現(xiàn)代瀏覽器平臺(tái)上(DOM API)。
electron-browser目錄下包含了需要DOM API及Electron渲染進(jìn)程特定的APIs的前端代碼。
node目錄包含了需要運(yùn)行在Node.js下的后端代碼。
node-electron目錄包含了Electron特定的后端代碼。
參見
可以查看這篇文章了解有關(guān)Theia架構(gòu)的簡(jiǎn)要概述:
利用JS實(shí)現(xiàn)多語(yǔ)言IDE——目標(biāo)和架構(gòu)(Multi_Language IDE Implemented in JS - Scope and Architecture)
擴(kuò)展包
Theia由擴(kuò)展包構(gòu)成。一個(gè)擴(kuò)展包就是一個(gè)npm包,在這個(gè)npm包中公開了用于創(chuàng)建DI容器的多個(gè)DI模塊(ContainerModule)。
通過(guò)在應(yīng)用程序的package.json中添加npm包的依賴項(xiàng)來(lái)使用擴(kuò)展包。擴(kuò)展包能夠在運(yùn)行時(shí)安裝和卸載,這將觸發(fā)重新編譯和重啟。
通過(guò)DI模塊,擴(kuò)展包能提供從類型到具體實(shí)現(xiàn)的綁定,即提供服務(wù)和功能。
Services和Contributions
本節(jié)我們將描述一個(gè)擴(kuò)展包如何使用另一個(gè)擴(kuò)展包中的服務(wù),以及它們?nèi)绾谓oTheia提供功能。
依賴注入(DI)
Theia使用DI框架Inversify.js來(lái)連接不同的組件。
DI在創(chuàng)建時(shí)注入組件(作為構(gòu)造函數(shù)的參數(shù)),從而將組件從依賴項(xiàng)中徹底解耦出來(lái)。DI容器根據(jù)你在啟動(dòng)時(shí)通過(guò)所謂的容器模塊提供的配置項(xiàng)來(lái)進(jìn)行創(chuàng)建。
例如,Navigator小部件需要訪問(wèn)FileSystem用來(lái)在樹形結(jié)構(gòu)中顯示文件夾和文件,但是FileSystem接口的實(shí)現(xiàn)對(duì)Navigator來(lái)說(shuō)并不重要,它可以大膽地假設(shè)與FileSystem接口一致的對(duì)象已經(jīng)準(zhǔn)備好并可以使用了。在Theia中,F(xiàn)ileSystem的實(shí)現(xiàn)僅僅是一個(gè)發(fā)送JSON-RPC消息到后端的代理,它需要一個(gè)特殊的配置和處理程序。Navigator不需要關(guān)心這些細(xì)節(jié),因?yàn)樗鼘@取一個(gè)被注入的FileSystem的實(shí)例。
此外,這種結(jié)構(gòu)的解耦和使用,允許擴(kuò)展包在需要時(shí)能提供非常具體的功能實(shí)現(xiàn),例如這里提到的FileSystem,而不需要接觸到FileSystem接口的任何實(shí)現(xiàn)。
DI在Theia中是一個(gè)非常重要的部分,因此,我們強(qiáng)烈建議先學(xué)習(xí)Inversify.js的基礎(chǔ)知識(shí)。
Services
Service只是一個(gè)提供給其它組件使用的綁定。例如,一個(gè)擴(kuò)展包可以公開SelectionService,這樣其它擴(kuò)展包就可以獲得一個(gè)注入的實(shí)例并使用它。
Contribution-Points
如果一個(gè)擴(kuò)展包想要提供一個(gè)鉤子,由其它擴(kuò)展包來(lái)實(shí)現(xiàn)其中的功能,那么它應(yīng)該定義一個(gè)contribution-point。一個(gè)contribution-point就是一個(gè)可以被其它擴(kuò)展包實(shí)現(xiàn)的接口。擴(kuò)展包可以在需要時(shí)將它委托給其它部分。
例如,OpenerService定義了一個(gè)contribution point,允許其它擴(kuò)展包注冊(cè)O(shè)penHandler。你可以查看這里的代碼。
Theia已經(jīng)提供了大量的contribution points列表,查看已存在的contribution points的一個(gè)好方法是查找bindContributionProvider的引用。
Contribution Providers
一個(gè)contribution provider基本上是contributions的容器,其中的contributions是綁定類型的實(shí)例。
這是非常通用的。
要將類型綁定到contribution provider,你可以這樣做:
(來(lái)自messageing-module.ts)
export const messagingModule = new ContainerModule(bind => {
bind(BackendApplicationContribution).to(MessagingContribution);
bindContributionProvider(bind, ConnectionHandler)
});
最后一行將一個(gè)ContributionProvider綁定到一個(gè)包含所有ConnectionHandler綁定實(shí)例的對(duì)象上。
像這樣來(lái)使用:
(來(lái)自messageing-module.ts)
constructor( @inject(ContributionProvider) @named(ConnectionHandler) protected readonly handlers: ContributionProvider) {
}
這里我們注入了一個(gè)ContributionProvider,它的name值是ConnectionHandler,這個(gè)值之前是由bindContributionProvider綁定的。
這使得任何人都可以綁定ConnectionHandler,現(xiàn)在,當(dāng)messageingModule啟動(dòng)時(shí),所有的ConnectionHandlers都將被初始化。