一.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 의 프로젝트 구조도 최소한 이 두 부분을 포함한다
메인 프로세스
백그라운드 서비스에 해당하며, 다음과 같이 사용:
-
다중 윈도우 관리 (생성/전환)
-
애플리케이션 수명 주기 관리
-
프로세스 통신 기지국으로 (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 라고 함
각 층은 타겟 실행 환경에 따라 세분화·조직화:
-
common: 환경을 넘어 재사용 가능 -
browser: 브라우저 API 에 의존. 예를 들어 DOM 조작 -
node: Node API 에 의존 -
electron-browser: electron renderer-process API 에 의존 -
electron-main: electron main-process API 에 의존
三.시작 플로우
시작 플로우 관련 파일의 단계 관계는 다음과 같다:
기능 엔트리
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());
그 중CodeApplication은vs/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. 즉, 먼저window를new하고, 즉시 수동으로 그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
});
}
중요한CodeWindow는src/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.ts의startup()에 도달:
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 레이아웃
WorkbenchShell은src/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는 두 종류로 나뉜다. 의존관계가 있는 것과 없는 것:
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 를 로드하여 렌더러프로세스 초기화 프로세스 시작. 두 갈래로 나뉨:
-
기능エリア 인터페이스 조립
-
기능エリア 인터페이스에 대응하는 기능 서비스 생성
-
참고 자료
-
vscode 소스코드剖析: 항상 같은 일을 하는 사람이 있는 법, 다행히 거인의 어깨는 점점 높아지고 있다
-
Hacking Node require:
runInThisContext hack require관련 혼란의 답
아직 댓글이 없습니다