從 VSCode 看大型 IDE 技術架構

前端深閱讀 發佈 2019-12-30T10:52:34+00:00

作者: paranoidjk@alipay零、前言為什麼要去看 VSCode?

作者: paranoidjk@alipay

零、前言

為什麼要去看 VSCode?

因為我們團隊在做中後台 Web 編輯器 是一款類似 Web IDE 形態的產品:



而談起 Web IDE,沒人能繞開 VSCode,它非常流行,同時又完全開源,總共 350000 行 TypeScript 代碼的巨大工程,使用了 142 個開源庫。市面上選擇基於 VSCode 去修改定製的 IDE 比比皆是:Weex Studio、白鷺Egret Wing、快應用IDE...


我希望從 VSCode 身上看到什麼?

  • 大型複雜 GUI 軟體(如 IDE 類)如何組織功能模塊代碼
  • 如何使用 Electron 技術將 Web 軟體桌面化
  • 如何在打造插件化開放生態的同時保證軟體整體質量與性能
  • 如何打造一款好用的、流行的工具軟體


一、VSCode 是什麼

官方定義

https://code.visualstudio.com/



關鍵詞:

  • 代碼編輯(工具屬性)
  • 跨平台運行、開源
  • 核心功能:
  • IntelliSense(代碼提示)、 debugging(代碼調試)、 git(代碼管理) 都是圍繞代碼編輯的核心鏈路
  • extensions (插件)則肩負著打造開放生態的責任



點評:

對於工具軟體而言,需要內心能想清楚邊界。哪些是自己應該專注去做的,哪些可以外溢到交給第三方擴展來滿足。


發展歷程

  • 2015-04 (4年前) 發布
  • 2015-11 (發布之後半年)開源
  • 2019-05 發布 VSCode Remote Development


團隊負責人:Erich Gamma . JUnit 作者之一,《設計模式》作者之一, Eclipse 架構師。2011 加入微軟,在瑞士蘇黎世組建團隊開發基於 web 技術的編輯器,也就是後來的 monaco-editor。VSCode 開發團隊從 10 來個人開始,早期成員大多有 Eclipse 開發團隊的背景。

Visual Studio Code有哪些工程方面的亮點 維護一個大型開源項目是怎樣的體驗?「Shape Up」 適合中小團隊的一種工作方式


Erich Gamma 在 GOTO 2016 發表了主題為 《The journey of visual studio code: Building an App Using JS/TypeScript, Node, Electron & 100 OSS Components》的演講,詳細講解了這個項目的發展歷程:

沒時間觀看視頻的可以下載完整 PDF

PPT 的第一頁,就是 Erich Gamma 截取自己正式加入微軟之後收到的工作內容描述的郵件:

」探索一種全新的和桌面 IDE 一樣成功的在線開發工具模式「


整個團隊從大致 10 個人開始,混合老中新三代不同水平的程式設計師,在微軟這個巨無霸的商業公司裡面想要落地這樣一個宏大的願景是不容易的,團隊一開始定下的思路就是像 start up 一樣工作,每月每年都要 ship 東西。


同時他也提出早期會瘋狂的在公司內部尋找落地場景,比如 Visual Studio Online 的在線 Code DIff 頁面,TypeScript 的官網的 Playground 編輯器,OneDrive 代碼文件,Edge Dev Tool 的代碼瀏覽等。


一個重要轉折點是微軟本身發生的巨大變化:


伴隨微軟整個的開放開源跨平颱風潮,Erich Gamma 敏銳的決定將產品從 Browser Based IDE 轉向跨平台的 Desktop IDE,但仍然使用 Web 技術,於是 electron 粉墨登場,VSCode 開源這些事情接連發生,VSCode 團隊花了六個月使用 Electron 將 Web 編輯器桌面化,又花了六個月將整個 IDE 插件化。



產品定位

Erich Gamma 在 2017 SpringOne Platform 上有一個 關於 VSCode 的分享,講解了在他開發 Eclipse 的過往經驗基礎上,對 VSCode 進行頂層設計時的諸多思路與決策,其中提到過對於 VSCode 的產品定位:



從圖中可以看出 VSCode 定位是處於編輯器和 IDE 的中間並且偏向輕量編輯器一側的。

VSCode 的核心是「編輯器 + 代碼理解 + 調試「,圍繞這個關鍵路徑做深做透,其他東西非常克制,產品保持輕量與高性能。


點評:

生產力工具類的軟體一定要守住主線,否則很可能會變成不收門票的遊樂園牛逼的產品背後一定有牛逼的團隊,比如微軟挖到 Anders Hejlsberg,接連創造了 C# 和 TypeScript,挖到 Erich Gamma,接連誕生了 monaco 和 vscode 這些明珠


二、Electron 是什麼

上文提到 VSCode 有一個特性是跨平台,它的跨平台實質是通過 electron 實現的。所以我們需要先簡單了解下 electron


官方定義



核心技術

  • 使用 Web 技術來編寫 UI,用 chrome 瀏覽器內核來運行
  • 使用 NodeJS 來操作文件系統和發起網絡請求
  • 使用 NodeJS C++ Addon 去調用作業系統的 native API


應用架構

https://electronjs.org/docs/tutorial/application-architecture

  • 1 個主進程:一個 Electron App 只會啟動一個主進程,它會運行 package.json 的 main 欄位指定的腳本
  • N 個渲染進程:主進程代碼可以調用 Chromium API 創建任意多個 web 頁面,而 Chromium 本身是多進程架構,每個 web 頁面都運行在屬於它自己的渲染進程中


進程間通訊:

  • Render 進程之間的通訊本質上和多個 Web 頁面之間通訊沒有差別,可以使用各種瀏覽器能力如 localStorage
  • Render 進程與 Main 進程之間也可以通過 API 互相通訊 (ipcRenderer/ipcMain)


點評:

普通 web 頁面無法調用 native api,因此缺少一些能力electron 的 web 頁面所處的 Render 進程可以將任務轉發至運行在 NodeJS 環境的 Main 進程,從而實現 native API這套架構大大擴展了 electron app 相比 web app 的能力豐富度,但同時又保留了 web 快捷流暢的開發體驗,再加上 web 本身的跨平台優勢,結合起來讓 electron 成為性價比非常高的方案


三、VSCode 技術架構


多進程架構

  • 主進程:VSCode 的入口進程,負責一些類似窗口管理、進程間通信、自動更新等全局任務
  • 渲染進程:負責一個 Web 頁面的渲染
  • 插件宿主進程:每個插件的代碼都會運行在一個獨屬於自己的 NodeJS 環境的宿主進程中,插件不允許訪問 UI
  • Debug 進程:Debugger 相比普通插件做了特殊化
  • Search 進程:搜索是一類計算密集型的任務,單開進程保證軟體整體體驗與性能


開發流程

  • 開發文檔:https://github.com/Microsoft/vscode/wiki/How-to-Contribute
  • 主倉庫:https://github.com/microsoft/vscode
  • 其它關聯項目:https://github.com/Microsoft/vscode/wiki/Related-Projects


# 檢出代碼
git clone git@github.com:microsoft/vscode.git
cd vscode

# 安裝依賴
yarn

# 啟動 web 版
yarn watch && yarn web

# 啟動 桌面 版
yarn watch && ./scripts/code.sh

# 打包
yarn run gulp vscode-[platform]
yarn run gulp vscode-[platform]-min
# platforms: win32-ia32 | win32-x64 | darwin | linux-ia32 | linux-x64 | linux-arm


源碼組織

https://github.com/microsoft/vscode/wiki/Source-Code-Organization

下面是整個 VSCode project 的一些頂級的重點文件夾,後文會重點關注 src 與 extensions:

├── build         # 構建腳本
├── extensions    # 內置插件
├── scripts       # 工具腳本
├── out           # 產物目錄
├── src           # 源碼目錄
├── test          # 測試代碼


VSCode 的代碼架構也是隨著產品的階段演進演進不斷更迭的:


下文會分享一些整個 VScode 源碼組織的一些亮點與特色:

1. 隔離內核 (src) 與插件 (extensions),內核分層模塊化

  • /src/vs:分層和模塊化的 core
  • /src/vs/base: 通用的公共方法和公共視圖組件
  • /src/vs/code: VSCode 應用主入口
  • /src/vs/platform:可被依賴注入的各種純服務
  • /src/vs/editor: 文本編輯器
  • /src/vs/workbench:整體視圖框架
  • /src/typings: 公共基礎類型
  • /extensions:內置插件


2. 每層按環境隔離

內核裡面每一層代碼都會遵守 electron 規範,按不同環境細分文件夾:

  • common: 公共的 js 方法,在哪裡都可以運行的
  • browser: 只使用瀏覽器 API 的代碼,可以調用 common
  • node: 只使用 NodeJS API 的代碼,可以調用 common
  • electron-browser: 使用 electron 渲染線程和瀏覽器 API 的代碼,可以調用 common,browser,node
  • electron-main: 使用 electron 主線程和 NodeJS API 的代碼,可以調用 common, node
  • test: 測試代碼


點評:

實際開發中也遇到了類似問題,作為一個 低代碼 + 可視化 的研發平台,許多功能模塊的實現都需要橫跨編輯態和運行態,如果代碼不加以限制和區分,很容易導致錯誤的依賴關係和預期之外的 bug,因此最終也決定採用 (editor/runtime/common) 類似的隔離架構


3. 內核代碼本身也採用擴展機制: Contrib

可以看到 /src/vs/workbench/contrib 這個目錄下存放著非常多的 VSCode 的小的功能單元:

├── backup
├── callHierarchy
├── cli
├── codeActions
├── codeEditor
├── comments
├── configExporter
├── customEditor
├── debug
├── emmet
├──....中間省略無數....
├── watermark
├── webview
└── welcome


Contrib 有一些特點:

  • Contrib 目錄下的所有代碼不允許依賴任何本文件夾之外的文件
  • Contrib 主要是使用 Core 暴露的一些擴展點來做事情
  • 每一個 Contrib 如果要對外暴露,將API 在一個出口文件裡面導出 eg: contrib/search/common/search.ts
  • 一個 Contrib 如果要和另一個 Contrib 發生調用,不允許使用除了出口 API 文件之外的其它文件
  • 接上一條,即使 Contrib 可以調用另一個 Contrib 的出口 API,也要審慎的考慮,應儘量避免兩個 Contrib 互相依賴


VSCode 開發團隊做這個設計的目的我猜大概是因為重型的工具軟體功能點實在太多,如果這些功能代碼直接採用原始的模塊引用的方式聚合瓶裝起來,是一個自頂向下的架構,對維護性的挑戰比較大。


而採用暴露擴展點的方式,可以將依賴關係反轉,依附於擴展點協議,將核心功能和非核心功能分割開,獨立的小功能的代碼實現可以單獨聚合,降低信息密度與提升維護性。


但是 VSCode Contrib 的具體業務代碼組織其實看起來沒有太多範式,而且這個內核代碼的擴展機制 Contrib 和 VSCode 開放給外界的插件化機制 extension 是有差異的,讀起來十分頭疼。


通過和 CloudIDE 開發同學木農的交流,我得到兩條主要差異性:

  • extension 每一個都是運行在歸宿於自己的獨立宿主進程,而 contrib 的功能基本是要運行在主進程的
  • extension 只能依附於 core 開放的擴展點而活,但是 contrib 可以通過依賴注入拿到所有 core 內部實現的 class (雖然官方不推薦)


4. 依賴注入

上一小節提到了 VSCode 的代碼大量使用了依賴注入,這項技術的具體實現細節本文不會展開細講,感興趣的可以閱讀一些好的實現:

  • 團隊揚遠同學寫的 power-di
  • 社區開源的強大的 http://inversify.io


TS 依賴注入常見的實現原理是使用 reflect-metadata 設置與獲取元信息,從而可以實現在運行時拿到本來屬於編輯態的 TypeScript 類型相關元信息,具體來說就是下面這些 API:

  • Reflect.getMetadata("design:type", , target, key): 獲取 class 屬性類型元信息
  • Reflect.getMetadata("design:paramtypes", target, key): 獲取 class 方法參數元信息
  • Reflect.getMetadata("design:returntype", target, key):獲取 class 方法返回值元信息
  • Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey):設置元信息
  • Reflect.getMetadata(metadataKey, target, propertyKey): 獲取元信息


不過具體到 VSCode 的依賴注入,它沒有使用 reflect-metadata 這一套,而是基於 decorator 去標註元信息,整個實現了一套自己的依賴注入方式,具體可以參考vscode 源碼解析-依賴注入 這篇文章,大致包含如下幾類角色:

  • Service:服務的實現邏輯
  • Interface:服務的接口描述
  • Client:服務使用方
  • Manger:服務管理器


舉個例子來看,在 /src/core/platform 裡面定義了大量 service,其他地方消費者 Client 都可以用依賴注入的方式使用到,偽代碼如下:

class Client {
  // 構造函數參數注入(依賴注入方式的一種)
  constructor(
    // 必選
    @IModelService modelService: IModelService, 
    // 可選
    @optional(IEditorService) editorService: IEditorService
  ) {
    // use services
  }
}

// 實例化
instantiationService.createInstance(Client);


5. 絕對路徑 import


點評:絕對路徑 import 是一個非常值得學習的技巧,具體的方式是配置 TypeScript compilerOptions.paths

相對路徑 import 對閱讀者的大腦負擔高,依賴當前文件位置上下文信息才能理解重構代碼的時候移動文件位置,相對路徑需要修改本文件的所有 import,絕對路徑不需要


6. 命令系統

VSCode 和 monaco-editor 都有自己的命令系統,螞蟻 CloudIDE 團隊的同學也曾經對命令系統的優勢做過總結:


傳統的模塊調用是個網狀,不太好找到一個切面來理解或治理:



而命令系統是中心化的,各功能末端變成了扁平化的結構:



7. TypeScript


啟動流程 (TLDR)

上文初步了解了 vscode 的技術架構與源碼組織,手癢的同學估計有點等不及嘗試走一遍 vscode 的啟動流程了。


然後在正式發車之前,我需要給大家一點友情提醒,如果你沒耐心看完下面的 VSCode 的啟動流程,應該知道,人生得過且過 o(╥﹏╥)o


總體來看,VSCode 的啟動代碼真正 show 給我們看了一個複雜的客戶端軟體的代碼會工程化到什麼地步,這其中摻雜了大量的基於 TypeScript 的 OOP 式的代碼組織,各種對邊界,宿主環境,上下文的處理,本來簡單的啟動 APP 渲染一個頁面流程變得極其複雜。


下面精簡抽取核心啟動鏈路的文件和方法看一看:

  • 入口:package.json { "main": "./out/main" } : electron 的標準啟動入口
  • '/out/main.js': 是構建產物的入口文件,它對應源碼 '/src/main.js'


// /src/main.js 的精簡核心鏈路
const { app, protocol } = require('electron');

app.once('ready', function () {
  // electron 啟動好之後,調用 vscode 的入口
    onReady();
});

async function onReady() {
    // 獲取緩存文件目錄地址和語言配置,執行啟動
        const [cachedDataDir, nlsConfig] = await Promise.all([nodeCachedDataDir.ensureExists(), resolveNlsConfiguration()]);
        startup(cachedDataDir, nlsConfig);
}

function startup(cachedDataDir, nlsConfig) {
  // 先加載 vscode 自己開源的 AMD Loader https://github.com/Microsoft/vscode-loader/
  // 再使用這個 loader 去加載 VSCode 的主入口文件
    require('./bootstrap-amd').load('vs/code/electron-main/main');
}


// /src/vs/code/electron-main/main.ts 精簡核心鏈路

// 初始化主類
const code = new CodeMain();
// 執行主入口函數
code.main();

class CodeMain {
  main() {
    // vscode 的 class public 入口一般只是空殼,真正的都在 private 邏輯裡面
    this.startUp();
  }
  private async startup() {
    // 先創建依賴的初始化 service
        const [instantiationService, instanceEnvironment] = this.createServices();
    // 創建編輯器實例並調用 startUp 方法
    return instantiationService.createInstance(CodeApplication).startup();
  }
}


// /src/vs/code/electron-main/app.ts

export class CodeApplication extends Disposable {
    async startup(): Promise<void> {
        // IPC Server
        const electronIpcServer = new ElectronIPCServer();
        // SharedProcess
        const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
        // 創建一大堆依賴的 service
        // IUpdateService IWindowsMainService IDialogMainService IMenubarService IStorageMainService......
        const appInstantiationService = await this.createServices(machineId, trueMachineId, sharedProcess, sharedProcessClient);
        // 打開一個窗口
            const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient));
    }
      private openFirstWindow() {
      const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
            return windowsMainService.open();
    }
}


// /src/vs/platform/windows/electron-main/windowsMainService.ts
export class WindowsMainService extends Disposable implements IWindowsMainService {
  open() {
    // 執行 open
    this.doOpen();
  }
  private doOpen() {
    // 打開瀏覽器窗口
    this.openInBrowserWindow();
  }
  private openInBrowserWindow() {
            // 創建窗口
            const createdWindow = window = this.instantiationService.createInstance(CodeWindow, {
                state,
                extensionDevelopmentPath: configuration.extensionDevelopmentPath,
                isExtensionTestHost: !!configuration.extensionTestsPath
            });
  }
    private doOpenInBrowserWindow() {
        // 加載頁面
    window.load(configuration);
  }
}


// /src/vs/code/electron-main/window.ts
export class CodeWindow extends Disposable implements ICodeWindow {
  load() {
        // 調用 electron 的 api 加載一個 url 的 html 頁面
        this._win.loadURL(this.getUrl(configuration));
  }
    private getUrl() {
    // 獲取要打開的 url
    let configUrl = this.doGetUrl(config);
    return configUrl;
  }
    private doGetUrl(config: object): string {
    // 終於看到 html 了!!淚流滿面〒▽〒
    // 打開 VSCode 的工作檯,也就是 workbench 
        return `${require.toUrl('vs/code/electron-browser/workbench/workbench.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
    }
}


代碼編輯器技術

因為本文關注的重點並不在真正的代碼編輯器技術而是在調研一下大型軟體的工程化,因此本文只會簡要介紹一下代碼編輯相關的的一些核心技術:

  • monaco-editor 文本編輯器,非常精深的領域,不展開聊,可以看一些有意思的細節:
  • Text Buffer 性能優化
  • MVVM 架構
  • language server protocol 語言提示, 也是 vscode 的一大創舉:
  • 不再關注 AST 和 Parser,轉而關注 Document 和 Position,從而實現語言無關。
  • 將語言提示變成 CS 架構,核心抽象成當點擊了文檔的第幾行第幾列位置需要 server 作出什麼響應的一個簡單模型,基於 JSON RPC 協議傳輸,每個語言都可以基於協議實現通用後端
  • Debug Adaptor Prototal: 調試協議



點評:

monaco-editor 可以看出專家級人物的領域積累,屬於 VSCode 的核心競爭力language server protocol 和 Debug Adaptor Prototal 的設計就屬於高屋建瓴,可以看出思想層次,把自己的東西做成標準,做出生態,開放共贏


四、VSCode 插件系統


理念差異

對比幾大 IDE:

  • Visual Studio / IntelliJ:不需要插件,all in one (不夠開放)
  • Eclipse: 一切皆插件 (臃腫、慢、不穩定、體驗差)
  • VSCode:中庸之道


VSCode 插件的強隔離


  • 獨立進程:VSCode plugin 代碼運行在只屬於自己的獨立 Extension Host 宿主進程里
  • 邏輯與視圖隔離:插件完全無法訪問 DOM 以及操作 UI,插件只能響應 VSCode Core 暴露的擴展點
  • 視圖擴展能力非常弱:VSCode 有非常穩定的交互與視覺設計,提供給插件的 UI 上的洞(component slot)非常少且穩定
  • 只能使用限制的組件來擴展:VSCode 對視圖擴展的能力限制非常強,洞裡面的 UI 是並不能隨意繪製的,只能使用一些官方提供的內置組件,比如 TreeView 之類


vscode 有哪些擴展能力?https://code.visualstudio.com/api/extension-capabilities/overview

點評:

視圖擴展的克制帶來統一的視覺與交互風格,帶來好的用戶體驗,便於建立穩定的用戶心智插件獨立進程,與視圖隔離,保證整體軟體的質量、性能、安全性


Workbench 視圖結構

https://code.visualstudio.com/docs/getstarted/userinterface

  • 標題欄: Title Bar
  • 活動欄: Activity Bar
  • 側邊欄: Side Bar
  • 面板: Panal
  • 編輯器: Editor
  • 狀態欄: Status Bar


在這個視圖結構裡面有哪些可擴展呢?詳見 extending workbench:



插件 API 注入

插件開發者調用 core 能力時需要引入名為 vscode 的 npm 模塊

import * as vscode from 'vscode';


而實際上這只是一個 vscode.d.ts 類型聲明文件,它聲明了所有插件可用的 API 類型。

這些 API 的具體實現在 src/vs/workbench/api/common/extHost.api.impl.ts createApiFactoryAndRegisterActors


那麼具體這些 API 在 plugin 執行上下文是何時注入的呢?其實是在插件 import 語句執行的時候動了手腳。

// /src/vs/workbench/api/common/extHostRequireInterceptor.ts

class VSCodeNodeModuleFactory implements INodeModuleFactory {
    public load(_request: string, parent: URI): any {

        // get extension id from filename and api for extension
        const ext = this._extensionPaths.findSubstr(parent.fsPath);
        if (ext) {
            let apiImpl = this._extApiImpl.get(ExtensionIdentifier.toKey(ext.identifier));
            if (!apiImpl) {
                apiImpl = this._apiFactory(ext, this._extensionRegistry, this._configProvider);
                this._extApiImpl.set(ExtensionIdentifier.toKey(ext.identifier), apiImpl);
            }
            return apiImpl;
        }

        // fall back to a default implementation
        if (!this._defaultApiImpl) {
            let extensionPathsPretty = '';
            this._extensionPaths.forEach((value, index) => extensionPathsPretty += `\t${index} -> ${value.identifier.value}\n`);
            this._logService.warn(`Could not identify extension for 'vscode' require call from ${parent.fsPath}. These are the extension path mappings: \n${extensionPathsPretty}`);
            this._defaultApiImpl = this._apiFactory(nullExtensionDescription, this._extensionRegistry, this._configProvider);
        }
        return this._defaultApiImpl;
    }
}


vscode plugin 的 require 全部被 Microsoft/vscode-loader 劫持了,通過對 require 的 hack 將插件 API 注入到了運行環境。


插件開發與配置

腳手架: https://github.com/Microsoft/vscode-generator-code

官方 demo: https://github.com/Microsoft/vscode-eslint


npm install -g yo generator-code
yo code


一個插件核心就是一個配置文件:Extension Manifest JSON (package.json 裡面的一個欄位)

https://code.visualstudio.com/api/references/extension-manifest


一些關鍵配置如下:

main:主文件入口,比如導出一個 activate 方法,可以接受 ctx 做一些事情


"main": "./src/extension.js",


// extension.js
const vscode = require("vscode");

function activate(context) {
  console.log('Congratulations, your extension "helloworld" is now active!');

  let disposable = vscode.commands.registerCommand(
    "extension.helloWorld",
    function() {
      vscode.window.showInformationMessage("Hello World!");
    }
  );

  context.subscriptions.push(disposable);
}

exports.activate = activate;


Activation Events 激活時機
  • onLanguage:包含該語言類型的文件被打開
  • onLanguage:json
  • onCommand:某個命令
  • onCommand:extension.sayHello
  • onDebug:開始調試
  • onDebugInitialConfigurations
  • onDebugResolve
  • workspaceContains:有匹配規則的文件被打開
  • workspaceContains:**/.editorconfig
  • onFileSystem:打開某個特殊協議的文件
  • onFileSystem:sftp
  • onView:某個 id 的視圖被顯示
  • onView:nodeDependencies
  • onUri:向作業系統註冊的 schema
  • vscode://vscode.git/init
  • onWebviewPanel:某種 viewType 的 webview 打開時
  • onWebviewPanel:catCoding
  • *:啟動就立即打開


Contribution Points 擴展點
  • contributes.configuration:本插件有哪些可供用戶配置的選項
  • contributes.configurationDefaults:覆蓋 vscode 默認的一些編輯器配置
  • contributes.commands:向 vscode 的命令系統註冊一些可供用戶調用的命令
  • contributes.menus:擴展菜單
  • ...


五、感想

Eric Raymond 有一本非常知名的著作 《大教堂與集市》,其中提到過一些有意思的觀點:

  • 健壯的結構遠比精巧的設計來得重要。換句話說,結構是第一位的,功能是第二位的。
  • 保持項目的簡單性。設計達到完美的時候,不是無法再增加東西了,而是無法再減少東西了。


VSCode 的一些工程上的優秀設計,比如依賴注入、絕對路徑引用、命令系統對於實際項目來說是可以馬上學以致用的,而 contrib 與 extension 的擴展系統,則非一日之功,也並不宜盲目下手。


而事實上在嘗試打造每一個開發者都夢想的萬物皆 plugin 式的工具軟體之前,有一些通用的問題需要先冷靜下來思考:

  • 用戶核心在操作的資源是什麼?
  • 用戶的關鍵路徑是什麼?
  • 這個軟體的整體功能形態,交互與視覺設計已經穩定了嗎?
  • 內核功能區和第三方擴展的功能域之間的界限在哪裡?
  • 哪些環節可能會出現外溢需求需要第三方擴展才能被滿足,不適宜官方動手做嗎?


對 VSCode 而言:

  • 核心操作的資源是文件
  • 關鍵路徑是:打開文件 - 編輯文件 - 保存文件
  • 整體功能設計,交互與視覺設計非常穩定
  • 內核是文件管理與代碼編輯,多樣性的程式語言生態,CICD 等衍生研發鏈路等可能會出現擴展需求


六、相關資料

  • https://github.com/JChehe/blog/issues/5
  • https://electronjs.org/docs/tutorial/application-architecture
  • https://zhuanlan.zhihu.com/p/54289476
  • http://phosphorjs.github.io/
  • http://gitlab.alipay-inc.com/bigfish/bigfish-vscode-plugin/tree/master
  • https://developer.egret.com/cn/docs/page/778
  • https://developer.chrome.com/extensions/getstarted
  • https://developer.alipay.com/article/8708
  • https://zhaomenghuan.js.org/blog/vscode-custom-development-basic-preparation.html

原文連結

https://www.yuque.com/paranoidjk/blog/vuuz30#YT3Xh

由於平台推廣規則限制,去除了營銷性質的相關內容。

關鍵字: