Generate preload cache from live app

This commit is contained in:
Fedor Indutny 2024-09-08 16:26:12 -07:00 committed by GitHub
parent 695f64a55a
commit 3dba3a07f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 57 additions and 69 deletions

View file

@ -38,6 +38,7 @@ if (getEnvironment() === Environment.PackagedApp) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '';
process.env.SIGNAL_ENABLE_HTTP = '';
process.env.SIGNAL_CI_CONFIG = '';
process.env.GENERATE_PRELOAD_CACHE = '';
}
// We load config after we've made our modifications to NODE_ENV

View file

@ -2567,6 +2567,9 @@ ipc.on('restart', () => {
app.quit();
});
ipc.on('shutdown', () => {
if (process.env.GENERATE_PRELOAD_CACHE) {
windowState.markReadyForShutdown();
}
app.quit();
});

View file

@ -85,7 +85,7 @@
"build:esbuild:prod": "node scripts/esbuild.js --prod",
"build:electron": "electron-builder --config.extraMetadata.environment=$SIGNAL_ENV",
"build:release": "cross-env SIGNAL_ENV=production npm run build:electron -- --config.directories.output=release",
"build:preload-cache": "electron --js-args=\"--predictable --random-seed 1\" ts/scripts/generate-preload-cache.js",
"build:preload-cache": "node ts/scripts/generate-preload-cache.js",
"verify": "run-p --print-label verify:*",
"verify:ts": "tsc --noEmit",
"electron:install-app-deps": "electron-builder install-app-deps"

View file

@ -5,9 +5,10 @@
// https://github.com/zertosh/v8-compile-cache/blob/b6bc035d337fbda0e6e3ec7936499048fc9deafc/v8-compile-cache.js
import { Module } from 'node:module';
import { readFileSync } from 'node:fs';
import { readFileSync, writeFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { Script } from 'node:vm';
import { ipcRenderer } from 'electron';
const srcPath = join(__dirname, 'preload.bundle.js');
const cachePath = join(__dirname, 'preload.bundle.cache');
@ -22,6 +23,8 @@ try {
}
}
let script: Script | undefined;
function compile(
filename: string,
content: string
@ -31,7 +34,7 @@ function compile(
// create wrapper function
const wrapper = Module.wrap(content);
const script = new Script(wrapper, {
script = new Script(wrapper, {
filename,
lineOffset: 0,
cachedData,
@ -124,3 +127,9 @@ ModuleInternals.prototype._compile = function _compile(
// eslint-disable-next-line import/no-dynamic-require
require(srcPath);
// See `ts/scripts/generate-preload-cache.ts`
if (script && process.env.GENERATE_PRELOAD_CACHE) {
writeFileSync(cachePath, script.createCachedData());
ipcRenderer.send('shutdown');
}

View file

@ -1,12 +0,0 @@
<!-- Copyright 2014 Signal Messenger, LLC -->
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
<!doctype html>
<html>
<head>
<title>Generating cache...</title>
</head>
<body>
Generating cache...
</body>
</html>

View file

@ -1,29 +0,0 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { Module } from 'node:module';
import { readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { Script } from 'node:vm';
import { ipcRenderer } from 'electron';
ipcRenderer.on('compile', async () => {
try {
const sourceFile = join(__dirname, '..', '..', 'preload.bundle.js');
const outFile = sourceFile.replace(/\.js$/, '');
const source = await readFile(sourceFile, 'utf8');
const script = new Script(Module.wrap(source), {
filename: 'preload.bundle.js',
produceCachedData: true,
});
if (!script.cachedDataProduced || !script.cachedData) {
throw new Error('Cached data not produced');
}
await writeFile(`${outFile}.cache`, script.cachedData);
await ipcRenderer.invoke('done');
} catch (error) {
await ipcRenderer.invoke('error', error);
}
});

View file

@ -1,35 +1,51 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { pathToFileURL } from 'node:url';
import { spawnSync } from 'node:child_process';
import { join } from 'node:path';
import { app, BrowserWindow, ipcMain } from 'electron';
import { tmpdir } from 'node:os';
import { mkdtemp, rm } from 'node:fs/promises';
app.on('ready', async () => {
ipcMain.handle('done', () => {
app.quit();
});
const ROOT_DIR = join(__dirname, '..', '..');
ipcMain.handle('error', (_event, err) => {
console.error(err);
process.exit(1);
});
const ELECTRON = join(
ROOT_DIR,
'node_modules',
'.bin',
process.platform === 'win32' ? 'electron.cmd' : 'electron'
);
const window = new BrowserWindow({
show: false,
webPreferences: {
devTools: true,
nodeIntegration: false,
sandbox: false,
contextIsolation: true,
preload: join(__dirname, 'generate-preload-cache.preload.js'),
},
});
async function main(): Promise<void> {
const storagePath = await mkdtemp(join(tmpdir(), 'signal-preload-cache-'));
await window.loadURL(
pathToFileURL(join(__dirname, 'generate-preload-cache.html')).toString()
);
let status: number | null;
try {
({ status } = spawnSync(
ELECTRON,
['--js-args="--predictable --random-seed 1"', 'ci.js'],
{
cwd: ROOT_DIR,
env: {
...process.env,
GENERATE_PRELOAD_CACHE: 'on',
SIGNAL_CI_CONFIG: JSON.stringify({
storagePath,
}),
},
// Since we run `.cmd` file on Windows - use shell
shell: process.platform === 'win32',
}
));
} finally {
await rm(storagePath, { recursive: true });
}
window.webContents.openDevTools();
window.webContents.send('compile', process.argv[2], process.argv[3]);
if (status !== 0) {
throw new Error(`Exit code: ${status}`);
}
}
main().catch(error => {
console.error(error);
process.exit(1);
});