メインコンテンツへ移動

VS Code ソースコード簡易分析

無料2018-02-24#Node#VSCode源码剖析#VSCode source code analysis#VSCode实现原理#VSCode启动流程#VSCode二次开发

常に同じことをする人がいるもので、幸いにも巨人の肩はますます高くなっている

一.Electron 基本構造

VS Code は Electron の成功事例として、ソースコードに飛び込む前に、Electron の基本構造を簡単に整理する必要がある

実装から見ると:

Electron = Node.js + Chromium + Native API

つまり Electron は Node 実行環境を持ち、Chromium に依存して Web 技術(HTML、CSS、JS)に基づくインターフェースインタラクションサポートを提供し、さらにいくつかのプラットフォーム特性を持っている。例えばデスクトップ通知など

API 設計から見ると、Electron App は一般に 1 つの Main Process と複数の Renderer Process を持つ:

  • main process:メインプロセス環境下で Node 及び Native API にアクセス可能

  • renderer process:レンダラープロセス環境下で Browser API と Node API 及び一部の Native API にアクセス可能

API 設計がこのようであれば、Electron App のプロジェクト構造も少なくともこれら 2 つの部分を含む

メインプロセス

バックグラウンドサービスに相当し、以下によく使用:

  • 複数ウィンドウ管理(作成/切り替え)

  • アプリケーションライフサイクル管理

  • プロセス通信基地局として(IPC Server)

  • 自動更新

  • ツールバーメニュー登録

レンダラープロセス

インターフェースインタラクション関連の、具体的なビジネス機能はすべて renderer プロセスが担当する。3 つの基本原则:

  • できるだけ renderer で作業する。ネットワークリクエストを含む

  • 時間がかかりすぎるものは Worker で分離する

  • renderer 間で共有する必要があるものはサブプロセスで分離し、main に管理を任せる

You can use all packages that work with Node.js in the main process as well as in the renderer process if you have webPreferences.nodeIntegration set to true in the BrowserWindow options. This is the default.

It's actually recommended to do as much as possible in the renderer process.

P.S.main と renderer の分担に関する議論は、What is the best way to make Http requests using Electron? を参照

二.vscode ソースコード構造

以下の内容はソースコードバージョン v1.19.3 を参照

ディレクトリ構造

├── build       # gulp コンパイル構築スクリプト
├── extensions  # 内蔵プラグイン
├── gulpfile.js # gulp task
├── i18n        # 国際化翻訳パッケージ
├── out         # コンパイル出力ディレクトリ
├── product.json  # App メタ情報
├── resources     # プラットフォーム関連静的リソース、アイコンなど
├── scripts       # ツールスクリプト、開発/テスト
├── src           # ソースコードディレクトリ
└── test          # テストスイート

src 下の構造は以下の通り:

├── bootstrap-amd.js  # サブプロセス実際のエントリー
├── bootstrap.js  # サブプロセス環境初期化
├── buildfile.js  # 構築 config
├── cli.js        # CLI エントリー
├── main.js       # メインプロセスエントリー
├── paths.js      # AppDataPath と DefaultUserDataPath
├── typings
│?? └── xxx.d.ts  # ts タイプ宣言
└── vs
    ├── base      # 汎用ツール/プロトコルと UI ライブラリ
    │?? ├── browser # 基礎 UI コンポーネント、DOM 操作、インタラクションイベント、DnD など
    │?? ├── common  # diff 記述、markdown パーサー、worker プロトコル、各種ツール関数
    │?? ├── node    # Node ツール関数
    │?? ├── parts   # IPC プロトコル(Electron、Node)、quickopen、tree コンポーネント
    │?? ├── test    # base 単体テスト用例
    │?? └── worker  # Worker factory と main Worker(IDE Core:Monaco を実行)
    ├── buildunit.json
    ├── code        # VS Code メインウィンドウ関連
    ├── css.build.js  # プラグイン構築用 CSS loader
    ├── css.js        # CSS loader
    ├── editor        # IDE Core と連携(編集/インタラクション状態読み取り)、コマンド、コンテキストメニュー、hover、snippet などのサポートを提供
    ├── loader.js     # AMD loader(AMD モジュールを非同期ロードするため、require.js に類似)
    ├── nls.build.js  # プラグイン構築用 NLS loader
    ├── nls.js        # NLS(National Language Support)多言語 loader
    ├── platform      # サービス注入とプラットフォーム関連基礎サービス(ファイル、クリップボード、ウィンドウ、ステータスバー)をサポート
    └── workbench     # editor を調整し viewlets にフレームワークを提供。例えばディレクトリビューア、ステータスバーなど。グローバル検索、Git、Debug を統合

その中で最も重要な部分(ビジネス関連)は:

  • src/vs/code:メインウィンドウ、ツールバーメニュー作成

  • src/vs/editor:コードエディター、IDE コア関連

  • src/vs/workbench:UI レイアウト、機能サービス連携

P.S.IDE Core は独立して使用可能。Monaco と呼ばれる

各層はターゲット実行環境に応じて細分化・組織化:

三.起動��ロー

起動フロー関連ファイルの段階関係は以下の通り:

機能エントリー
src/main.js
  src/vs/code/electron-main/main.ts
    src/vs/code/electron-main/app.ts
      src/vs/code/electron-main/windows.ts
        src/vs/code/electron-main/window.ts
UI エントリー
src/vs/workbench/electron-browser/bootstrap/index.html
  src/vs/workbench/electron-browser/bootstrap/index.js
    src/vs/workbench/workbench.main js index ファイル
      src/vs/workbench/electron-browser/main.ts
        src/vs/workbench/electron-browser/shell.ts インターフェースと機能サービスの接入点
          src/vs/workbench/electron-browser/workbench.ts インターフェース作成
            src/vs/workbench/browser/layout.ts レイアウト計算、絶対位置指定

Electron CLI アプリケーション起動

起動ステップ:

# コンパイル構築(ts 変換、パッケージング)
npm run compile
# Electron でアプリケーションを起動
./scripts/code.sh

code.sh の役割は Electron Demo で一般的なこれに類似:

"name": "electron-quick-start",
"version": "1.0.0",
"description": "A minimal Electron application",
"main": "main.js",
"scripts": {
  "start": "electron ."
}

主要部分は以下の通り:

# Configuration
export NODE_ENV=development
export VSCODE_DEV=1
export VSCODE_CLI=1
export ELECTRON_ENABLE_LOGGING=1
export ELECTRON_ENABLE_STACK_DUMPING=1

# Launch Code
exec "$CODE" . "$ @"

dev 環境変数を設定し、最後にexecで実行:

./.build/electron/Code - OSS.app/Contents/MacOS/Electron .

Electron CLI はpkg.mainをエントリーファイルとしてロード実行:

"name": "code-oss-dev",
"version": "1.19.3",
"distro": "2751aca3e43316e3418502935939817889deb719",
"author": {
  "name": "Microsoft Corporation"
},
"main": "./out/main"

つまりエントリーファイルout/main.jsに転送。対応するソースコードはsrc/main.js。重要な部分は以下の通り:

// src/main.js
app.once('ready', function () {
  perf.mark('main:appReady');
  global.perfAppReady = Date.now();
  var nlsConfig = getNLSConfiguration();
  process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig);

  nodeCachedDataDir.then(function () {
    require('./bootstrap-amd').bootstrap('vs/code/electron-main/main');
  }, console.error);
});

cacheDataディレクトリ準備完了後、AMD loader を通じてメインプロセスエントリーファイルout/vs/code/electron-main/main.jsをロードし、メインプロセス初期化フローに入る

メインプロセス初期化

メインプロセスエントリーファイル対応ソースコードsrc/vs/code/electron-main/main.jsの主要部分は以下の通り:

// Startup
	return instantiationService.invokeFunction(a => createPaths(a.get(IEnvironmentService)))
		.then(() => instantiationService.invokeFunction(setupIPC))
		.then(mainIpcServer => instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnv).startup());

その中でCodeApplicationvs/code/electron-main/app.tsから。起動フロー関連部分は以下の通り:

// Open Windows
appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));
// Post Open Windows Tasks
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));

openFirstWindow()の主要内容は以下の通り:

this.windowsMainService = accessor.get(IWindowsMainService);

// Open our first window
	const args = this.environmentService.args;
	const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
	if (args['new-window'] && args._.length === 0) {
		this.windowsMainService.open({ context, cli: args, forceNewWindow: true, forceEmpty: true, initialStartup: true }); // new window if "-n" was used without paths
	} else if (global.macOpenFiles && global.macOpenFiles.length && (!args._ || !args._.length)) {
		this.windowsMainService.open({ context: OpenContext.DOCK, cli: args, pathsToOpen: global.macOpenFiles, initialStartup: true }); // mac: open-file event received on startup
	} else {
		this.windowsMainService.open({ context, cli: args, forceNewWindow: args['new-window'] || (!args._.length && args['unity-launch']), diffMode: args.diff, initialStartup: true }); // default: read paths from cli
	}

// Only load when the window has not vetoed this
	this.lifecycleService.unload(window, UnloadReason.LOAD).done(veto => {
  // Load it
  window.load(configuration);
}

注意、this.lifecycleService.unload(window, UnloadReason.LOAD)这句は非常に誤解を招くunloadをトリガーし、理由(UnloadReason)はLOAD。つまり、まずwindownewし、すぐに手動でそのunload()を呼び出し、その後手動でload()を呼び出してこのウィンドウをロードする……では、なぜまずunload()を呼び出すのか?

P.S.このload()は非常に重要。後でまた戻る

ここまではエントリー HTML が見えず、windowsMainService.open()がある。追跡する(src/vs/code/electron-main/windows.ts):

public open(openConfig: IOpenConfiguration): CodeWindow[] {
  // Open based on config
  const usedWindows = this.doOpen(openConfig, workspacesToOpen, workspacesToRestore, foldersToOpen, foldersToRestore, emptyToRestore, emptyToOpen, filesToOpen, filesToCreate, filesToDiff, filesToWait, foldersToAdd);
}

private doOpen() {
  // Handle empty to open (only if no other window opened)
  if (usedWindows.length === 0) {
    for (let i = 0; i < emptyToOpen; i++) {
      usedWindows.push(this.openInBrowserWindow({
        userEnv: openConfig.userEnv,
        cli: openConfig.cli,
        initialStartup: openConfig.initialStartup,
        forceNewWindow: openFolderInNewWindow
      }));

      openFolderInNewWindow = true; // any other window to open must open in new window then
    }
  }
}

private openInBrowserWindow() {
  window = this.instantiationService.createInstance(CodeWindow, {
			state,
			extensionDevelopmentPath: configuration.extensionDevelopmentPath,
			isExtensionTestHost: !!configuration.extensionTestsPath
		});
}

重要なCodeWindowsrc/vs/code/electron-main/window.tsで定義されている。つまり初期化プロセスは複数ウィンドウ管理クラス(windows.ts)が VS Code メインウィンドウ(window.ts)を呼び出す。したがってopen()は最終的にCodeWindowインスタンスを返す。簡略化:

// Open our first window
	window = new CodeWindow();

// Only load when the window has not vetoed this
window.load(configuration);

次にload()を見る。重要な部分は以下の通り:

public load(config: IWindowConfiguration, isReload?: boolean): void {
  // Load URL
  mark('main:loadWindow');
  this._win.loadURL(this.getUrl(config));
}

private getUrl(windowConfiguration: IWindowConfiguration): string {
  // Config (combination of process.argv and window configuration)
  const config = objects.assign(environment, windowConfiguration);

  return `${require.toUrl('vs/workbench/electron-browser/bootstrap/index.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
}

HTML が現れた。メインプロセスの使命は完了し、レンダラープロセスが登場

レンダラープロセス初期化

エントリー HTMLsrc/vs/workbench/electron-browser/bootstrap/index.htmlの主要内容は以下の通り:

<body class="monaco-shell vs-dark" aria-label="">
	  <script src="preload.js"></script>
  </body>

<!-- Startup via index.js -->
  <script src="index.js"></script>

2 つの JS を導入。preload.jsは URL からconfigパラメータを解析し、テーマ設定に基づいてbody背景色を設定。index.jsにはロードロジックが含まれる:

function main() {
  const webFrame = require('electron').webFrame;
  // URL パラメータから config を解析
  const args = parseURLQueryArgs();
  const configuration = JSON.parse(args['config'] || '{}') || {};

  // 传入された環境変数を回復
  // Correctly inherit the parent's environment
  assign(process.env, configuration.userEnv);
  perf.importEntries(configuration.perfEntries);

  // NLS 多言語設定を回復
  // Get the nls configuration into the process.env as early as possible.
  var nlsConfig = { availableLanguages: {} };
  const config = process.env['VSCODE_NLS_CONFIG'];
  if (config) {
    process.env['VSCODE_NLS_CONFIG'] = config;
    try {
      nlsConfig = JSON.parse(config);
    } catch (e) { /*noop*/ }
  }
  var locale = nlsConfig.availableLanguages['*'] || 'en';
  if (locale === 'zh-tw') {
    locale = 'zh-Hant';
  } else if (locale === 'zh-cn') {
    locale = 'zh-Hans';
  }
  window.document.documentElement.setAttribute('lang', locale);

  // DevTools を有効にするか
  const enableDeveloperTools = (process.env['VSCODE_DEV'] || !!configuration.extensionDevelopmentPath) && !configuration.extensionTestsPath;
  const unbind = registerListeners(enableDeveloperTools);

  // 拡大縮小設定
  // disable pinch zoom & apply zoom level early to avoid glitches
  const zoomLevel = configuration.zoomLevel;
  webFrame.setVisualZoomLevelLimits(1, 1);
  if (typeof zoomLevel === 'number' && zoomLevel !== 0) {
    webFrame.setZoomLevel(zoomLevel);
  }

  // loader を初期化
  // Load the loader and start loading the workbench
  const loaderFilename = configuration.appRoot + '/out/vs/loader.js';
  const loaderSource = require('fs').readFileSync(loaderFilename);
  //!!! node require を置き換え、define 関数を提供
  require('vm').runInThisContext(loaderSource, { filename: loaderFilename });

  window.nodeRequire = require.__$__nodeRequire;

  define('fs', ['original-fs'], function (originalFS) { return originalFS; }); // パッチされた electron fs をすべての AMD コードのために元の node fs に置き換え

  window.MonacoEnvironment = {};
  const onNodeCachedData = window.MonacoEnvironment.onNodeCachedData = [];
    
  // require 設定
  require.config({
    baseUrl: uriFromPath(configuration.appRoot) + '/out',
    'vs/nls': nlsConfig,
    recordStats: !!configuration.performance,
    nodeCachedDataDir: configuration.nodeCachedDataDir,
    onNodeCachedData: function () { onNodeCachedData.push(arguments); },
    nodeModules: [/*BUILD->INSERT_NODE_MODULES*/]
  });
  if (nlsConfig.pseudo) {
    require(['vs/nls'], function (nlsPlugin) {
      nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
    });
  }

  // パフォーマンス設定とタイムスタンプを取得
  // Perf Counters
  const timers = window.MonacoEnvironment.timers = {
    isInitialStartup: !!configuration.isInitialStartup,
    hasAccessibilitySupport: !!configuration.accessibilitySupport,
    start: configuration.perfStartTime,
    appReady: configuration.perfAppReady,
    windowLoad: configuration.perfWindowLoadTime,
    beforeLoadWorkbenchMain: Date.now()
  };

  const workbenchMainClock = perf.time('loadWorkbenchMain');
  // 機能モジュール JS をロード
  require([
    'vs/workbench/workbench.main',
    'vs/nls!vs/workbench/workbench.main',
    'vs/css!vs/workbench/workbench.main'
  ], function () {
    workbenchMainClock.stop();
    timers.afterLoadWorkbenchMain = Date.now();

    process.lazyEnv.then(function () {
      perf.mark('main/startup');
      // electron-browser/main をロードし、startup() を呼び出す
      require('vs/workbench/electron-browser/main')
        .startup(configuration)
        .done(function () {
          unbind(); // workbench が実行されているので、開発者関連リスナーをバインド解除し、workbench に処理を任せる
        }, function (error) {
          onError(error, enableDeveloperTools);
        });
    });
  });
}

その中で、loader の実際の役割はグローバルrequire()を置き換えdefine()を提供すること。以下の通り:

define = function () {
  DefineFunc.apply(null, arguments);
};
AMDLoader.global.require = RequireFunc;
AMDLoader.global.require.__$__nodeRequire = nodeRequire;

P.S.loader はrunInThisContext()を通じて解釈実行される。API ドキュメントは以下の通り:

vm.runInThisContext() compiles code, runs it within the context of the current global and returns the result. Running code does not have access to local scope, but does have access to the current global object.

現在のglobal環境で给定コードを実行し結果を返す。eval()に類似しているが、非global変数にはアクセスできない。例えば:

let i = 1;
const result = require('vm').runInThisContext(`
  // require を篡改
  global.require = function(...args) {
    console.log.apply(global, ['require called: '].concat(args));
  }
  // エラー、i is not defined
  // i++;
  2;
`);
require('my_module', { opts: 'opts' }); // require called:  my_module Object {opts: "opts"}
require(result); // require called:  2

P.S.注意。Electron renderer process 環境でのみ上記の結果が得られる。Node REPL 環境(コマンドライン)とモジュール環境では不可。renderer process 内のrequire === global.requireであるため。其它環境のはModule.prototype._compile()が注入するもので、ローカル変数形式(module wrapper パラメータ)で存在し、vm.runInThisContext()を通じて篡改できない(もちろん、evalで行うことは可能)

最後にsrc/vs/workbench/electron-browser/main.tsstartup()に到達:

export function startup(configuration: IWindowConfiguration): TPromise<void> {
  // Open workbench
  return openWorkbench(configuration);
}

function openWorkbench(configuration: IWindowConfiguration): TPromise<void> {
  // ...各種 service を作成

  return createAndInitializeWorkspaceService(configuration, environmentService).then(workspaceService => {
    return domContentLoaded().then(() => {
      // 各機能エリア UI を初期化
      // Open Shell
      const shell = new WorkbenchShell(document.body, {
        contextService: workspaceService,
        configurationService: workspaceService,
        environmentService,
        logService,
        timerService,
        storageService
      }, mainServices, configuration);
      shell.open();
    });
  });
}

WorkbenchShellの作成から正式に機能エリア UI レイアウトに入る。UI は Shell と呼ばれ、機能を担うコンテナ(「殻」)として使用される

UI レイアウト

WorkbenchShellsrc/vs/workbench/electron-browser/shell.tsから。そのopen()メソッドの主要内容は以下の通り:

public open(): void {
  // content コンテナを作成
  // Controls
  this.content = $('.monaco-shell-content').appendTo(this.container).getHTMLElement();

  // 内容を填充
  // Create Contents
  this.contentsContainer = this.createContents($(this.content));

  // レイアウトを計算
  // Layout
  this.layout();

  // Listeners
  this.registerListeners();
}

注意すべきはレイアウト計算部分(this.layout())。VS Code は Flex/Grid などの強力な CSS レイアウト方式を採用せず、統一して絶対レイアウト + 計算の方式で正確なピクセルレイアウトを実装:

// ref: src/vs/workbench/browser/layout.ts
public layout(options?: ILayoutOptions): void {
  // Workbench
  this.workbenchContainer
		  .position(0, 0, 0, 0, 'relative')
		  .size(this.workbenchSize.width, this.workbenchSize.height);
  // Title Part
  if (isTitlebarHidden) {
    this.titlebar.getContainer().hide();
  } else {
    this.titlebar.getContainer().show();
  }
  // Editor Part and Panel part
  this.editor.getContainer().size(editorSize.width, editorSize.height);
	  this.panel.getContainer().size(panelDimension.width, panelDimension.height);
  // Activity Bar Part
  this.activitybar.getContainer().size(null, activityBarSize.height);
  // Sidebar Part
  this.sidebar.getContainer().size(sidebarSize.width, sidebarSize.height);
  // Statusbar Part
  this.statusbar.getContainer().position(this.workbenchSize.height - this.statusbarHeight);
  // Quick open
  this.quickopen.layout(this.workbenchSize);
  // Sashes
  this.sashXOne.layout();

  // Part レイアウトに伝播
  this.titlebar.layout(new Dimension(this.workbenchSize.width, this.titlebarHeight));
  this.editor.layout(new Dimension(editorSize.width, editorSize.height));
  this.sidebar.layout(sidebarSize);
  this.panel.layout(panelDimension);
  this.activitybar.layout(activityBarSize);
  // Context View に伝播
  this.contextViewService.layout();
}

2 つのことを行う:

  • 各機能エリアの位置とサイズを計算(XXX Part)

  • 各機能エリアがさらに内容レイアウトを計算(Propagate to Part Layouts)

P.S.ほとんどのレイアウト計算は JS で完了し、一部はcalc()を使用

機能サービス連携

WorkbenchShell作成時に传入された各種 service は最後にworkbench作成に使用:

// ref: src/vs/workbench/electron-browser/shell.ts
private createContents(parent: Builder): Builder {
  // サービスを持つ Instantiation service
  const [instantiationService, serviceCollection] = this.initServiceCollection(parent.getHTMLElement());

  // workbench を作成
  // Workbench
  this.workbench = instantiationService.createInstance(Workbench, parent.getHTMLElement(), workbenchContainer.getHTMLElement(), this.configuration, serviceCollection, this.lifecycleService);
  try {
    this.workbench.startup().done(startupInfos => this.onWorkbenchStarted(startupInfos, instantiationService));
  } catch (error) {/*...*/}
}

各部分機能が依存するサポートサービスをworkbenchに渡し、その後startup()を呼び出す:

public startup(): TPromise<IWorkbenchStartedInfo> {
  // UI と連携する具体的な機能 service を作成し、serviceCollection に追加
  // Services
  this.initServices();

  // service 依存関係を注入
  // Contexts
  this.messagesVisibleContext = MessagesVisibleContext.bindTo(this.contextKeyService);
  this.editorsVisibleContext = EditorsVisibleContext.bindTo(this.contextKeyService);
  this.inZenMode = InZenModeContext.bindTo(this.contextKeyService);
  this.sideBarVisibleContext = SidebarVisibleContext.bindTo(this.contextKeyService);
}

作成するserviceは 2 種類に分かれる。依存関係があるものとないもの:

private initServices(): void {
  // 依存なし、直接 new して collection に追加
  // Services we contribute
  serviceCollection.set(IPartService, this);
  // Clipboard
  serviceCollection.set(IClipboardService, new ClipboardService());

  // 依存あり、instantiationService で依存関係を処理してから collection に追加
  // Status bar
  this.statusbarPart = this.instantiationService.createInstance(StatusbarPart, Identifiers.STATUSBAR_PART);
  serviceCollection.set(IStatusbarService, this.statusbarPart);
  // List
  serviceCollection.set(IListService, this.instantiationService.createInstance(ListService));
}

instantiationService.createInstanceは依存関係を自動処理できる。非常に興味深い

private _createInstance<T>(desc: SyncDescriptor<T>, args: any[]): T {
  // arguments defined by service decorators
  let serviceDependencies = _util.getServiceDependencies(desc.ctor).sort((a, b) => a.index - b.index);

  // now create the instance
  const argArray = [desc.ctor];
  argArray.push(...staticArgs);
  argArray.push(...serviceArgs);

  return <T>create.apply(null, argArray);
}

依存関係を自動処理できる秘密(_util.getServiceDependencies)はここ:

export const DI_DEPENDENCIES = '$di$dependencies';

export function getServiceDependencies(ctor: any): { id: ServiceIdentifier<any>, index: number, optional: boolean }[] {
  return ctor[DI_DEPENDENCIES] || [];
}

Classに静的変数$di$dependenciesを掛け、依存関係を格納。例えば:

// ref: src/vs/editor/browser/services/codeEditorService.ts
export const ICodeEditorService = createDecorator<ICodeEditorService>('codeEditorService');

createDecorator()service依存関係を宣言するためのツール関数:

export function createDecorator<T>(serviceId: string): { (...args: any[]): void; type: T; } {
  const id = <any>function (target: Function, key: string, index: number): any {
    if (arguments.length !== 3) {
      throw new Error(' @IServiceName-decorator can only be used to decorate a parameter');
    }
    storeServiceDependency(id, target, index, false);
  };

  return id;
}

function storeServiceDependency(id: Function, target: Function, index: number, optional: boolean): void {
  if (target[_util.DI_TARGET] === target) {
    target[_util.DI_DEPENDENCIES].push({ id, index, optional });
  } else {
    target[_util.DI_DEPENDENCIES] = [{ id, index, optional }];
    target[_util.DI_TARGET] = target;
  }
}

至此、全体の起動フローが明確になった

  • まず Electron CLI を通じてエントリー JS をロード

  • エントリー JS を実行してメインプロセス初期化プロセスに入り、最後に BrowserWindow を作成しエントリー HTML をロード

  • エントリー HTML が依存 JS をロードしてレンダラープロセス初期化プロセスを開始。2 つに分かれる:

    • 機能エリアインターフェースを組み立て

    • 機能エリアインターフェースに対応する機能サービスを作成

参考資料

コメント

コメントはまだありません

コメントを書く