Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: spin up a separate Node process for each preview server #2126

Merged
merged 25 commits into from
Oct 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion chromeless/src/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export async function startPreview({
port?: number;
}) {
const preview = await workspace.startServer({ port });
await page.goto(preview.url());
await page.goto(`http://localhost:${preview.port}`);

// This callback will be invoked each time a previewable is done rendering.
let onRenderingDone = () => {
Expand Down
4 changes: 2 additions & 2 deletions core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export async function createWorkspace({
await previewer.start();
activePreviewers.add(previewer);
return {
url: () => `http://localhost:${port}`,
port,
stop: async () => {
activePreviewers.delete(previewer);
await previewer.stop();
Expand Down Expand Up @@ -245,6 +245,6 @@ export interface Workspace {
}

export interface PreviewServer {
url(): string;
port: number;
stop(): Promise<void>;
}
28 changes: 20 additions & 8 deletions daemon/patch-build.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
// For some reason, index.mjs ends up with an incorrect import
// For some reason, we end up with an incorrect import
// that looks like `import ... from 'process/'`.
//
// This script removes the unwanted slash.

import fs from "fs";
import path from "path";

const indexFilePath = "dist/index.mjs";
const indexContent = fs.readFileSync(indexFilePath, "utf8");
fs.writeFileSync(
indexFilePath,
indexContent.replace(`'process/'`, `'process'`),
"utf8"
);
visit("./dist");

function visit(dirPath: string) {
for (const f of fs.readdirSync(dirPath)) {
const filePath = path.join(dirPath, f);
const stat = fs.statSync(filePath);
if (stat.isFile()) {
const content = fs.readFileSync(filePath, "utf8");
fs.writeFileSync(
filePath,
content.replace(`'process/'`, `'process'`),
"utf8"
);
} else if (stat.isDirectory()) {
visit(filePath);
}
}
}
5 changes: 4 additions & 1 deletion daemon/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,19 +108,22 @@ if (logFilePath) {

export interface DaemonStartOptions {
loaderInstallDir: string;
loaderWorkerPath: string;
onServerStartModuleName: string;
versionCode: string;
port: number;
}

export async function startDaemon({
loaderInstallDir,
loaderWorkerPath,
onServerStartModuleName,
versionCode,
port,
}: DaemonStartOptions) {
const previewjs = await load({
installDir: loaderInstallDir,
workerFilePath: loaderWorkerPath,
onServerStartModuleName,
});
const logger = previewjs.logger;
Expand Down Expand Up @@ -357,7 +360,7 @@ export async function startDaemon({
previewServers[req.workspaceId] || (await workspace.startServer());
previewServers[req.workspaceId] = previewServer;
return {
url: previewServer.url(),
url: `http://localhost:${previewServer.port}`,
};
}
);
Expand Down
8 changes: 7 additions & 1 deletion daemon/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@ const port = parseInt(process.env.PORT || "9100");

const onServerStartModuleName =
process.env.PREVIEWJS_PACKAGE_NAME || "@previewjs/pro";
const loaderInstallDir = process.env.PREVIEWJS_LOADER_INSTALL_DIR!;

const loaderInstallDir = process.env.PREVIEWJS_LOADER_INSTALL_DIR!;
if (!loaderInstallDir) {
throw new Error(`Missing environment variable: PREVIEWJS_LOADER_INSTALL_DIR`);
}

const loaderWorkerPath = process.env.PREVIEWJS_LOADER_WORKER_PATH!;
if (!loaderWorkerPath) {
throw new Error(`Missing environment variable: PREVIEWJS_LOADER_WORKER_PATH`);
}

const versionCode = process.env.PREVIEWJS_VERSION_CODE!;
if (!versionCode) {
throw new Error(`Missing environment variable: PREVIEWJS_VERSION_CODE`);
}

startDaemon({
loaderInstallDir,
loaderWorkerPath,
onServerStartModuleName,
versionCode,
port,
Expand Down
4 changes: 2 additions & 2 deletions integrations/cli/esbuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import url from "url";
try {
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
await build({
entryPoints: ["./src/main.ts"],
entryPoints: ["./src/main.ts", "./src/worker.ts"],
minify: false,
bundle: true,
format: "esm",
outfile: "./dist/main.js",
outdir: "./dist",
platform: "node",
target: "es2020",
banner: {
Expand Down
2 changes: 2 additions & 0 deletions integrations/cli/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { load } from "@previewjs/loader";
import chalk from "chalk";
import { program } from "commander";
import { readFileSync } from "fs";
import path from "path";
import url from "url";

const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
Expand All @@ -30,6 +31,7 @@ program
const onServerStartModuleName = process.env.PREVIEWJS_PACKAGE_NAME;
const previewjs = await load({
installDir: process.env.PREVIEWJS_MODULES_DIR || __dirname,
workerFilePath: path.join(__dirname, "worker.js"),
onServerStartModuleName,
});
const workspace = await previewjs.getWorkspace({
Expand Down
1 change: 1 addition & 0 deletions integrations/cli/src/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "@previewjs/loader/worker";
4 changes: 2 additions & 2 deletions integrations/intellij/daemon/esbuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ try {
}

await build({
entryPoints: ["./src/main.ts"],
entryPoints: ["./src/main.ts", "./src/worker.ts"],
minify: false,
bundle: true,
format: "esm",
outfile: "./dist/main.js",
outdir: "./dist",
platform: "node",
target: "es2020",
define: {
Expand Down
2 changes: 2 additions & 0 deletions integrations/intellij/daemon/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { startDaemon } from "@previewjs/daemon";
import path from "path";
import url from "url";

const port = parseInt(process.argv[2] || "0", 10);
Expand All @@ -19,6 +20,7 @@ if (!version) {
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
startDaemon({
loaderInstallDir: __dirname,
loaderWorkerPath: path.join(__dirname, "worker.js"),
onServerStartModuleName,
versionCode: `intellij-${version}`,
port,
Expand Down
1 change: 1 addition & 0 deletions integrations/intellij/daemon/src/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "@previewjs/loader/worker";
2 changes: 1 addition & 1 deletion integrations/vscode/esbuild.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import url from "url";
try {
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
await build({
entryPoints: ["./src/index.ts", "./src/daemon.ts"],
entryPoints: ["./src/index.ts", "./src/daemon.ts", "./src/worker.ts"],
minify: false,
bundle: true,
format: "cjs", // VS Code does not support ESM extensions
Expand Down
2 changes: 2 additions & 0 deletions integrations/vscode/src/daemon.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { startDaemon } from "@previewjs/daemon";
import { readFileSync } from "fs";
import { join } from "path";

const { version } = JSON.parse(
readFileSync(`${__dirname}/../package.json`, "utf8")
Expand All @@ -17,6 +18,7 @@ if (!port) {

startDaemon({
loaderInstallDir: process.env.PREVIEWJS_MODULES_DIR || __dirname,
loaderWorkerPath: join(__dirname, "worker.js"),
onServerStartModuleName,
versionCode: `vscode-${version}`,
port,
Expand Down
3 changes: 3 additions & 0 deletions integrations/vscode/src/start-daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ async function startDaemon(outputChannel: OutputChannel): Promise<{
});
}
daemonProcess.unref();
daemonProcess.on("error", (error) => {
outputChannel.append(`${error}`);
});
return { daemonProcess };
}

Expand Down
1 change: 1 addition & 0 deletions integrations/vscode/src/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "@previewjs/loader/worker";
10 changes: 9 additions & 1 deletion loader/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
"./runner": {
"default": "./dist/runner.js"
},
"./worker": {
"default": "./dist/worker.js"
},
"./setup": {
"default": "./dist/setup.js"
}
Expand All @@ -33,6 +36,9 @@
"runner": [
"./dist/runner.d.ts"
],
"worker": [
"./dist/worker.d.ts"
],
"setup": [
"./dist/setup.d.ts"
]
Expand All @@ -44,7 +50,9 @@
"devDependencies": {
"@previewjs/core": "workspace:*",
"@previewjs/vfs": "workspace:*",
"@types/fs-extra": "^11.0.3",
"@types/fs-extra": "^11.0.2",
"@types/proper-lockfile": "^4.1.2",
"assert-never": "^1.2.1",
"exclusive-promises": "^1.0.3",
"execa": "^8.0.1",
"fs-extra": "^11.1.1",
Expand Down
3 changes: 2 additions & 1 deletion loader/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export type LogLevel = "silent" | "error" | "warn" | "info";
export type LogLevel = "silent" | "error" | "warn" | "info" | "debug";

export { load } from "./runner.js";
export type { ServerWorker as WorkspaceWorker } from "./runner.js";
21 changes: 12 additions & 9 deletions loader/src/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,7 @@ import path from "path";
import type { Logger } from "pino";
import url from "url";

export async function loadModules({
logger,
installDir,
onServerStartModuleName,
}: {
logger: Logger;
installDir: string;
onServerStartModuleName?: string;
}) {
export async function installDependenciesIfRequired(installDir: string) {
if (
fs.existsSync(path.join(installDir, "pnpm")) &&
!fs.existsSync(path.join(installDir, "node_modules"))
Expand All @@ -36,6 +28,17 @@ export async function loadModules({
// Note: The bracketed tag is required for VS Code and IntelliJ to detect end of installation.
process.stdout.write("[install:end] Done.\n");
}
}

export async function loadModules({
logger,
installDir,
onServerStartModuleName,
}: {
logger: Logger;
installDir: string;
onServerStartModuleName?: string;
}) {
const coreModule: typeof core = await importModule("@previewjs/core");
const vfsModule: typeof vfs = await importModule("@previewjs/vfs");
const frameworkPlugins: core.FrameworkPluginFactory[] = [
Expand Down
22 changes: 22 additions & 0 deletions loader/src/resolvable-promise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export type ResolvablePromise<T> = Promise<T> & {
resolved: T | null;
};

export function resolvablePromise<T>(promise: Promise<T>) {
let onResolve: (value: T) => void;
let onReject: (error: any) => void;
const resolvablePromise = new Promise<T>((resolve, reject) => {
onResolve = resolve;
onReject = reject;
}) as ResolvablePromise<T>;
resolvablePromise.resolved = null;
promise
.then((value) => {
resolvablePromise.resolved = value;
onResolve(value);
})
.catch((e) => {
onReject(e);
});
return resolvablePromise;
}
Loading