本節描述了Theia的總體架構。前端
Theia被設計爲一個能夠在本地運行的桌面應用程序,也能夠在瀏覽器和遠程服務器之間工做。爲了支持這兩種工做方式,Theia運行在兩個獨立的進程中,它們被稱之爲前端和後端,相互之間經過WebSockets上的JSON-RPC消息或HTTP上的REST APIs來通訊。對於Electron而言,前端和後端都在本地運行,而在遠程上下文中,後端運行在遠程服務器上。node
前端和後端進行都有它們各自的依賴注入(DI)容器(詳見下文),以方便開發者進行擴展。git
前端部分負責客戶端的UI呈現。在瀏覽器中,它只是簡單地在渲染循環中運行。而在Electron中,它運行在Electron窗口中,這是一個包含Electron和Node.js APIs的瀏覽器窗口。所以,任何前端代碼均可以把瀏覽器而不是Node.js做爲一個運行平臺。github
啓動前端進程將首先加載全部擴展包的DI模塊,而後獲取一個FrontendApplication的實例並在上面調用start()。express
後端進程運行在Node.js上。咱們使用express做爲HTTP服務器,它能夠不使用任何須要瀏覽器平臺的代碼(DOM API)。npm
啓動後端應用程序將首先加載全部擴展包的DI模塊,而後獲取一個BackendApplication的實例並在上面調用start(portNumber)。json
默認狀況下後端的express服務器也爲前端提供代碼。後端
在擴展包的根目錄下,包含以下子目錄層級,按不一樣的平臺進行區分:瀏覽器
能夠查看這篇文章瞭解有關Theia架構的簡要概述:服務器
利用JS實現多語言IDE——目標和架構(Multi_Language IDE Implemented in JS - Scope and Architecture)
Theia由擴展包構成。一個擴展包就是一個npm包,在這個npm包中公開了用於建立DI容器的多個DI模塊(ContainerModule)。
經過在應用程序的package.json中添加npm包的依賴項來使用擴展包。擴展包可以在運行時安裝和卸載,這將觸發從新編譯和重啓。
經過DI模塊,擴展包能提供從類型到具體實現的綁定,即提供服務和功能。
本節咱們將描述一個擴展包如何使用另外一個擴展包中的服務,以及它們如何給Theia提供功能。
Theia使用DI框架Inversify.js來鏈接不一樣的組件。
DI在建立時注入組件(做爲構造函數的參數),從而將組件從依賴項中完全解耦出來。DI容器根據你在啓動時經過所謂的容器模塊提供的配置項來進行建立。
例如,Navigator小部件須要訪問FileSystem用來在樹形結構中顯示文件夾和文件,可是FileSystem接口的實現對Navigator來講並不重要,它能夠大膽地假設與FileSystem接口一致的對象已經準備好並能夠使用了。在Theia中,FileSystem的實現僅僅是一個發送JSON-RPC消息到後端的代理,它須要一個特殊的配置和處理程序。Navigator不須要關心這些細節,由於它將獲取一個被注入的FileSystem的實例。
此外,這種結構的解耦和使用,容許擴展包在須要時能提供很是具體的功能實現,例如這裏提到的FileSystem,而不須要接觸到FileSystem接口的任何實現。
DI在Theia中是一個很是重要的部分,所以,咱們強烈建議先學習Inversify.js的基礎知識。
Service只是一個提供給其它組件使用的綁定。例如,一個擴展包能夠公開SelectionService,這樣其它擴展包就能夠得到一個注入的實例並使用它。
若是一個擴展包想要提供一個鉤子,由其它擴展包來實現其中的功能,那麼它應該定義一個_contribution-point_。一個_contribution-point_就是一個能夠被其它擴展包實現的接口。擴展包能夠在須要時將它委託給其它部分。
例如,OpenerService定義了一個contribution point,容許其它擴展包註冊OpenHandler。你能夠查看這裏的代碼。
Theia已經提供了大量的contribution points列表,查看已存在的contribution points的一個好方法是查找bindContributionProvider的引用。
一個contribution provider基本上是contributions的容器,其中的contributions是綁定類型的實例。
這是很是通用的。
要將類型綁定到contribution provider,你能夠這樣作:
(來自messageing-module.ts)
export const messagingModule = new ContainerModule(bind => {
bind<BackendApplicationContribution>(BackendApplicationContribution).to(MessagingContribution); bindContributionProvider(bind, ConnectionHandler)
});
最後一行將一個ContributionProvider綁定到一個包含全部ConnectionHandler綁定實例的對象上。
像這樣來使用:
(來自messageing-module.ts)
constructor( @inject(ContributionProvider) @named(ConnectionHandler) protected readonly handlers: ContributionProvider<ConnectionHandler>) {
}
這裏咱們注入了一個ContributionProvider,它的name值是ConnectionHandler,這個值以前是由bindContributionProvider綁定的。
這使得任何人均可以綁定ConnectionHandler,如今,當messageingModule啓動時,全部的ConnectionHandlers都將被初始化。