Skip to content

Commit

Permalink
refactor: finish splitting ElectronTypes to main process (#1442)
Browse files Browse the repository at this point in the history
* refactor: finish splitting ElectronTypes to main process

* refactor: move file watcher to separate function, catch errors, and only read file once
  • Loading branch information
dsanders11 authored Aug 21, 2023
1 parent 0ca6863 commit 05f5d24
Show file tree
Hide file tree
Showing 13 changed files with 431 additions and 207 deletions.
8 changes: 8 additions & 0 deletions src/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
OutputEntry,
PMOperationOptions,
RunResult,
RunnableVersion,
SelectedLocalVersion,
TestRequest,
Version,
Expand Down Expand Up @@ -72,6 +73,10 @@ declare global {
type: 'toggle-monaco-option',
listener: (path: string) => void,
): void;
addEventListener(
type: 'electron-types-changed',
listener: (types: string, version: string) => void,
): void;
addModules(
{ dir, packageManager }: PMOperationOptions,
...names: Array<string>
Expand All @@ -87,6 +92,7 @@ declare global {
): Promise<LoadedFiddleTheme>;
fetchVersions(): Promise<Version[]>;
getAvailableThemes(): Promise<Array<LoadedFiddleTheme>>;
getElectronTypes(ver: RunnableVersion): Promise<string | undefined>;
getIsPackageManagerInstalled(
packageManager: IPackageManager,
ignoreCache?: boolean,
Expand Down Expand Up @@ -128,6 +134,8 @@ declare global {
showWindow(): void;
taskDone(result: RunResult): void;
themePath: string;
uncacheTypes(ver: RunnableVersion): Promise<void>;
unwatchElectronTypes(): Promise<void>;
};
}
}
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export const SENTRY_DSN =
'https://[email protected]/1882540';

export const ELECTRON_DTS = 'electron.d.ts';
1 change: 1 addition & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export type FiddleEvent =
| 'before-quit'
| 'bisect-task'
| 'clear-console'
| 'electron-types-changed'
| 'execute-monaco-command'
| 'load-example'
| 'load-gist'
Expand Down
6 changes: 6 additions & 0 deletions src/ipc-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export enum IpcEvents {
GET_PROJECT_NAME = 'GET_PROJECT_NAME',
GET_USERNAME = 'GET_USERNAME',
PATH_EXISTS = 'PATH_EXISTS',
ELECTRON_TYPES_CHANGED = 'ELECTRON_TYPES_CHANGED',
UNCACHE_TYPES = 'UNCACHE_TYPES',
GET_ELECTRON_TYPES = 'GET_ELECTRON_TYPES',
UNWATCH_ELECTRON_TYPES = 'UNWATCH_ELECTRON_TYPES',
GET_NODE_TYPES = 'GET_NODE_TYPES',
}

Expand Down Expand Up @@ -97,6 +101,8 @@ export const ipcMainEvents = [
IpcEvents.GET_PROJECT_NAME,
IpcEvents.GET_USERNAME,
IpcEvents.PATH_EXISTS,
IpcEvents.GET_ELECTRON_TYPES,
IpcEvents.UNWATCH_ELECTRON_TYPES,
IpcEvents.GET_NODE_TYPES,
];

Expand Down
154 changes: 151 additions & 3 deletions src/main/electron-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,97 @@ import * as path from 'node:path';

import { ElectronVersions } from '@electron/fiddle-core';
import fetch from 'cross-fetch';
import { IpcMainEvent, app } from 'electron';
import { BrowserWindow, IpcMainEvent, app } from 'electron';
import * as fs from 'fs-extra';
import watch from 'node-watch';
import packageJson from 'package-json';
import readdir from 'recursive-readdir';
import semver from 'semver';

import { ipcMainManager } from './ipc';
import { NodeTypes } from '../interfaces';
import { ELECTRON_DTS } from '../constants';
import { NodeTypes, RunnableVersion, VersionSource } from '../interfaces';
import { IpcEvents } from '../ipc-events';

let electronTypes: ElectronTypes;

export class ElectronTypes {
private localPaths: Map<BrowserWindow, string>;
private watchers: Map<string, fs.FSWatcher>;

constructor(
private readonly knownVersions: ElectronVersions,
private readonly electronCacheDir: string,
private readonly nodeCacheDir: string,
) {}
) {
this.localPaths = new Map();
this.watchers = new Map();
}

private getWindowsForLocalPath(localPath: string): BrowserWindow[] {
return Array.from(this.localPaths.entries())
.filter(([, path]) => path === localPath)
.map(([window]) => window);
}

private notifyElectronTypesChanged(
dir: string,
file: string,
version: string,
) {
try {
const content = fs.readFileSync(file, 'utf8');

// Notify all windows watching that path
for (const window of this.getWindowsForLocalPath(dir)) {
ipcMainManager.send(
IpcEvents.ELECTRON_TYPES_CHANGED,
[content, version],
window.webContents,
);
}
} catch (err) {
console.debug(`Unable to read types from "${file}": ${err.message}`);
}
}

public async getElectronTypes(
window: BrowserWindow,
ver: RunnableVersion,
): Promise<string | undefined> {
const { localPath: dir, source, version } = ver;
let content: string | undefined;

// If it's a local development version, pull Electron types from out directory.
if (dir) {
const file = path.join(dir, 'gen/electron/tsc/typings', ELECTRON_DTS);
content = this.getTypesFromFile(file);
try {
this.unwatch(window);
this.localPaths.set(window, dir);

// If no watcher for that path yet, create it
if (!this.watchers.has(dir)) {
const watcher = watch(file, () =>
this.notifyElectronTypesChanged(dir, file, version),
);
this.watchers.set(dir, watcher);
}
window.once('close', () => this.unwatch(window));
} catch (err) {
console.debug(`Unable to watch "${file}" for changes: ${err}`);
}
}

// If it's a published version, pull from cached file.
else if (source === VersionSource.remote) {
const file = this.getCacheFile(version);
await this.ensureElectronVersionIsCachedAt(version, file);
content = this.getTypesFromFile(file);
}

return content;
}

public async getNodeTypes(
version: string,
Expand All @@ -41,6 +115,11 @@ export class ElectronTypes {
}
}

public uncache(ver: RunnableVersion) {
if (ver.source === VersionSource.remote)
fs.removeSync(this.getCacheFile(ver.version));
}

private async getTypesFromDir(dir: string): Promise<NodeTypes> {
const types: NodeTypes = {};

Expand All @@ -60,10 +139,42 @@ export class ElectronTypes {
return types;
}

private getTypesFromFile(file: string): string | undefined {
try {
return fs.readFileSync(file, 'utf8');
} catch (err) {
console.debug(`Unable to read types from "${file}": ${err.message}`);
return undefined;
}
}

private getCacheFile(version: string) {
return path.join(this.electronCacheDir, version, ELECTRON_DTS);
}

private getCacheDir(version: string) {
return path.join(this.nodeCacheDir, version);
}

public unwatch(window: BrowserWindow) {
const localPath = this.localPaths.get(window);

if (localPath) {
this.localPaths.delete(window);

const windows = this.getWindowsForLocalPath(localPath);

// If it's the last window watching that path, close the watcher
if (!windows.length) {
const watcher = this.watchers.get(localPath);
if (watcher) {
watcher.close();
}
this.watchers.delete(localPath);
}
}
}

/**
* This function ensures that the Node.js version for a given version of
* Electron is downloaded and cached. It can be the case that DefinitelyTyped
Expand Down Expand Up @@ -123,20 +234,57 @@ export class ElectronTypes {

return downloadVersion;
}

private async ensureElectronVersionIsCachedAt(version: string, file: string) {
if (fs.existsSync(file)) return;

const name = version.includes('nightly') ? 'electron-nightly' : 'electron';
const url = `https://unpkg.com/${name}@${version}/${ELECTRON_DTS}`;
try {
const response = await fetch(url);
const text = await response.text();
if (text.includes('Cannot find package')) throw new Error(text);
fs.outputFileSync(file, text);
} catch (err) {
console.warn(`Error saving "${url}" to "${file}": ${err}`);
}
}
}

export async function setupTypes(knownVersions: ElectronVersions) {
const userDataPath = app.getPath('userData');

electronTypes = new ElectronTypes(
knownVersions,
path.join(userDataPath, 'electron-typedef'),
path.join(userDataPath, 'nodejs-typedef'),
);

ipcMainManager.handle(
IpcEvents.GET_ELECTRON_TYPES,
(event: IpcMainEvent, ver: RunnableVersion) => {
return electronTypes.getElectronTypes(
BrowserWindow.fromWebContents(event.sender)!,
ver,
);
},
);
ipcMainManager.handle(
IpcEvents.GET_NODE_TYPES,
(_: IpcMainEvent, version: string) => {
return electronTypes.getNodeTypes(version);
},
);
ipcMainManager.handle(
IpcEvents.UNCACHE_TYPES,
(_: IpcMainEvent, ver: RunnableVersion) => {
electronTypes.uncache(ver);
},
);
ipcMainManager.handle(
IpcEvents.UNWATCH_ELECTRON_TYPES,
(event: IpcMainEvent) => {
electronTypes.unwatch(BrowserWindow.fromWebContents(event.sender)!);
},
);
}
12 changes: 12 additions & 0 deletions src/preload/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const channelMapping: Record<FiddleEvent, IpcEvents> = {
'before-quit': IpcEvents.BEFORE_QUIT,
'bisect-task': IpcEvents.TASK_BISECT,
'clear-console': IpcEvents.CLEAR_CONSOLE,
'electron-types-changed': IpcEvents.ELECTRON_TYPES_CHANGED,
'execute-monaco-command': IpcEvents.MONACO_EXECUTE_COMMAND,
'load-example': IpcEvents.LOAD_ELECTRON_EXAMPLE_REQUEST,
'load-gist': IpcEvents.LOAD_GIST_REQUEST,
Expand Down Expand Up @@ -79,6 +80,10 @@ export async function setupFiddleGlobal() {
fetchVersions() {
return ipcRenderer.invoke(IpcEvents.FETCH_VERSIONS);
},
getElectronTypes(ver) {
// Destructure ver into a copy, as the object sometimes can't be cloned
return ipcRenderer.invoke(IpcEvents.GET_ELECTRON_TYPES, { ...ver });
},
getLatestStable() {
return ipcRenderer.sendSync(IpcEvents.GET_LATEST_STABLE);
},
Expand Down Expand Up @@ -181,6 +186,13 @@ export async function setupFiddleGlobal() {
ipcRenderer.send(IpcEvents.TASK_DONE, result);
},
themePath: await ipcRenderer.sendSync(IpcEvents.GET_THEME_PATH),
async uncacheTypes(ver) {
// Destructure ver into a copy, as the object sometimes can't be cloned
await ipcRenderer.invoke(IpcEvents.UNCACHE_TYPES, { ...ver });
},
async unwatchElectronTypes() {
await ipcRenderer.invoke(IpcEvents.UNWATCH_ELECTRON_TYPES);
},
};
}

Expand Down
8 changes: 1 addition & 7 deletions src/renderer/app.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import * as path from 'node:path';

import { autorun, reaction, when } from 'mobx';

import { USER_DATA_PATH } from './constants';
import { ElectronTypes } from './electron-types';
import { FileManager } from './file-manager';
import { RemoteLoader } from './remote-loader';
Expand Down Expand Up @@ -42,10 +39,7 @@ export class App {

this.taskRunner = new TaskRunner(this);

this.electronTypes = new ElectronTypes(
window.ElectronFiddle.monaco,
path.join(USER_DATA_PATH, 'electron-typedef'),
);
this.electronTypes = new ElectronTypes(window.ElectronFiddle.monaco);
}

private confirmReplaceUnsaved(): Promise<boolean> {
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as path from 'node:path';

export const USER_DATA_PATH = window.ElectronFiddle.appPaths.userData;
const USER_DATA_PATH = window.ElectronFiddle.appPaths.userData;
export const ELECTRON_DOWNLOAD_PATH = path.join(USER_DATA_PATH, 'electron-bin');
export const ELECTRON_INSTALL_PATH = path.join(
ELECTRON_DOWNLOAD_PATH,
Expand Down
Loading

0 comments on commit 05f5d24

Please sign in to comment.