Skip to content

Commit 8a0f515

Browse files
authored
refactor: use pnpm to load dependencies on install (#1213)
1 parent 9433d10 commit 8a0f515

File tree

22 files changed

+3927
-8443
lines changed

22 files changed

+3927
-8443
lines changed

integrations/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
},
2020
"scripts": {
2121
"prepublish": "cd .. && pnpm turbo run build --scope=@previewjs/cli --no-deps --include-dependencies",
22-
"build": "rimraf dist && tsc && node esbuild.js && shx cp -rL ../../loader/src/release/node_modules dist/node_modules",
22+
"build": "rimraf dist && tsc && node esbuild.js && shx cp -rL ../../node_modules/pnpm dist/pnpm && shx cp -rL ../../loader/src/release/* dist/",
2323
"dev": "cross-env PREVIEWJS_MODULES_DIR=$INIT_CWD/../../dev-workspace PREVIEWJS_PACKAGE_NAME=@previewjs/app ts-node-dev --respawn src/main.ts",
2424
"dev:pro": "cross-env PREVIEWJS_MODULES_DIR=$INIT_CWD/../../dev-workspace PREVIEWJS_PACKAGE_NAME=@previewjs/pro ts-node-dev --respawn src/main.ts"
2525
},

integrations/intellij/controller/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
},
1515
"homepage": "https://previewjs.com",
1616
"scripts": {
17-
"build": "rimraf dist && tsc && node esbuild.js && shx cp -rL ../../../loader/src/release/node_modules dist/node_modules"
17+
"build": "rimraf dist && tsc && node esbuild.js && shx cp -rL ../../../node_modules/pnpm dist/pnpm && shx cp -rL ../../../loader/src/release/* dist/"
1818
},
1919
"devDependencies": {
2020
"@previewjs/server": "workspace:*",

integrations/intellij/controller/src/main.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ensureServerRunning } from "@previewjs/server";
1+
import { startServer } from "@previewjs/server";
22

33
const port = parseInt(process.argv[2] || "0", 10);
44
if (!port) {
@@ -15,7 +15,7 @@ if (!version) {
1515
throw new Error(`Missing environment variable: PREVIEWJS_INTELLIJ_VERSION`);
1616
}
1717

18-
ensureServerRunning({
18+
startServer({
1919
loaderInstallDir: __dirname,
2020
packageName,
2121
versionCode: `intellij-${version}`,

integrations/vscode/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
"main": "./dist/index.js",
8989
"scripts": {
9090
"vscode:prepublish": "pnpm build",
91-
"build": "rimraf dist previewjs-1.16.1.vsix && tsc && node esbuild.js && shx cp -rL ../../loader/src/release/node_modules dist/node_modules",
91+
"build": "rimraf dist previewjs-1.16.1.vsix && tsc && node esbuild.js && shx cp -rL ../../node_modules/pnpm dist/pnpm && shx cp -rL ../../loader/src/release/* dist/",
9292
"prod": "cross-env PREVIEWJS_DEV=0 pnpm vsce package --no-dependencies && pnpm dev:install",
9393
"dev": "cross-env PREVIEWJS_PACKAGE_NAME=@previewjs/app pnpm dev:build",
9494
"dev:build": "cross-env PREVIEWJS_DEV=1 pnpm vsce package --no-dependencies && pnpm dev:install",

integrations/vscode/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import vscode from "vscode";
44
import { clientId } from "./client-id";
55
import { closePreviewPanel, updatePreviewPanel } from "./preview-panel";
66
import { ensurePreviewServerStarted } from "./preview-server";
7-
import { startPreviewJsServer } from "./start-server";
7+
import { ensureServerRunning } from "./start-server";
88
import { openUsageOnFirstTimeStart } from "./welcome";
99

1010
const codeLensLanguages = [
@@ -39,7 +39,7 @@ let dispose = async () => {
3939
export async function activate(context: vscode.ExtensionContext) {
4040
const outputChannel = vscode.window.createOutputChannel("Preview.js");
4141

42-
const previewjsInitPromise = startPreviewJsServer(outputChannel)
42+
const previewjsInitPromise = ensureServerRunning(outputChannel)
4343
.catch((e) => {
4444
outputChannel.appendLine(e.stack);
4545
return null;

integrations/vscode/src/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ensureServerRunning } from "@previewjs/server";
1+
import { startServer } from "@previewjs/server";
22
import { readFileSync } from "fs";
33
import { SERVER_PORT } from "./port";
44

@@ -11,7 +11,7 @@ if (!packageName) {
1111
throw new Error(`Missing environment variable: PREVIEWJS_PACKAGE_NAME`);
1212
}
1313

14-
ensureServerRunning({
14+
startServer({
1515
loaderInstallDir: process.env.PREVIEWJS_MODULES_DIR || __dirname,
1616
packageName,
1717
versionCode: `vscode-${version}`,

integrations/vscode/src/start-server.ts

Lines changed: 66 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,29 @@
11
import { Client, createClient } from "@previewjs/server/client";
22
import execa from "execa";
3-
import { openSync, readFileSync } from "fs";
3+
import { closeSync, openSync, readFileSync, utimesSync, watch } from "fs";
44
import path from "path";
55
import type { OutputChannel } from "vscode";
66
import { clientId } from "./client-id";
77
import { SERVER_PORT } from "./port";
88

9-
export async function startPreviewJsServer(
9+
const logsPath = path.join(__dirname, "server.log");
10+
const serverLockFilePath = path.join(__dirname, "process.lock");
11+
12+
export async function ensureServerRunning(
1013
outputChannel: OutputChannel
1114
): Promise<Client | null> {
15+
const client = createClient(`http://localhost:${SERVER_PORT}`);
16+
await startServer(outputChannel);
17+
const ready = streamServerLogs(outputChannel);
18+
await ready;
19+
await client.updateClientStatus({
20+
clientId,
21+
alive: true,
22+
});
23+
return client;
24+
}
25+
26+
async function startServer(outputChannel: OutputChannel): Promise<boolean> {
1227
const isWindows = process.platform === "win32";
1328
let useWsl = false;
1429
const nodeVersionCommand = "node --version";
@@ -39,7 +54,7 @@ export async function startPreviewJsServer(
3954
invalidNode: if (checkNodeVersion.kind === "invalid") {
4055
outputChannel.appendLine(checkNodeVersion.message);
4156
if (!isWindows) {
42-
return null;
57+
return false;
4358
}
4459
// On Windows, try WSL as well.
4560
outputChannel.appendLine(`Attempting again with WSL...`);
@@ -59,22 +74,21 @@ export async function startPreviewJsServer(
5974
break invalidNode;
6075
}
6176
outputChannel.appendLine(checkNodeVersionWsl.message);
62-
return null;
77+
return false;
6378
}
64-
const logsPath = path.join(__dirname, "server.log");
65-
const logs = openSync(logsPath, "w");
6679
outputChannel.appendLine(
6780
`🚀 Starting Preview.js server${useWsl ? " from WSL" : ""}...`
6881
);
6982
outputChannel.appendLine(`Streaming server logs to: ${logsPath}`);
70-
outputChannel.appendLine(
71-
`If you experience any issues, please include this log file in bug reports.`
72-
);
7383
const nodeServerCommand = "node --trace-warnings server.js";
74-
const serverOptions = {
84+
const serverOptions: execa.Options = {
7585
cwd: __dirname,
76-
stdio: ["ignore", logs, logs],
77-
} as const;
86+
stdio: "inherit",
87+
env: {
88+
PREVIEWJS_LOCK_FILE: serverLockFilePath,
89+
PREVIEWJS_LOG_FILE: logsPath,
90+
},
91+
};
7892
let serverProcess: execa.ExecaChildProcess<string>;
7993
if (useWsl) {
8094
serverProcess = execa(
@@ -90,53 +104,51 @@ export async function startPreviewJsServer(
90104
detached: true,
91105
});
92106
}
107+
serverProcess.unref();
108+
return true;
109+
}
93110

94-
const client = createClient(`http://localhost:${SERVER_PORT}`);
95-
try {
96-
const startTime = Date.now();
97-
const timeoutMillis = 30000;
98-
loop: while (true) {
99-
try {
100-
await client.info();
101-
break loop;
102-
} catch (e) {
103-
if (serverProcess.exitCode) {
104-
// Important: an exit code of 0 may be correct, especially if:
105-
// 1. Another server is already running.
106-
// 2. WSL is used, so the process exits immediately because it spans another one.
107-
outputChannel.append(readFileSync(logsPath, "utf8"));
108-
throw new Error(
109-
`Preview.js server exited with code ${serverProcess.exitCode}`
110-
);
111-
}
112-
if (Date.now() - startTime > timeoutMillis) {
113-
throw new Error(
114-
`Connection timed out after ${timeoutMillis}ms: ${e}`
115-
);
111+
function streamServerLogs(outputChannel: OutputChannel) {
112+
const ready = new Promise<void>((resolve) => {
113+
let lastKnownLogsLength = 0;
114+
let resolved = false;
115+
// Ensure file exists before watching.
116+
// Source: https://remarkablemark.org/blog/2017/12/17/touch-file-nodejs/
117+
try {
118+
const time = Date.now();
119+
utimesSync(logsPath, time, time);
120+
} catch (e) {
121+
let fd = openSync(logsPath, "a");
122+
closeSync(fd);
123+
}
124+
watch(
125+
logsPath,
126+
{
127+
persistent: false,
128+
},
129+
() => {
130+
try {
131+
const logsContent = ignoreBellPrefix(readFileSync(logsPath, "utf8"));
132+
const newLogsLength = logsContent.length;
133+
if (newLogsLength < lastKnownLogsLength) {
134+
// Log file has been rewritten.
135+
outputChannel.append("\n⚠️ Preview.js server was restarted ⚠️\n\n");
136+
lastKnownLogsLength = 0;
137+
}
138+
outputChannel.append(logsContent.slice(lastKnownLogsLength));
139+
lastKnownLogsLength = newLogsLength;
140+
// Note: " running on " also appears in "Preview.js daemon server is already running on port 9315".
141+
if (!resolved && logsContent.includes(" running on ")) {
142+
resolve();
143+
resolved = true;
144+
}
145+
} catch (e: any) {
146+
// Fine, ignore. It just means log streaming is broken.
116147
}
117-
// Ignore the error and retry after a short delay.
118-
await new Promise<void>((resolve) => setTimeout(resolve, 100));
119148
}
120-
}
121-
client.info();
122-
await client.waitForReady();
123-
outputChannel.appendLine(`✅ Preview.js server ready.`);
124-
serverProcess.unref();
125-
} catch (e: any) {
126-
if (e.stack) {
127-
outputChannel.appendLine(e.stack);
128-
}
129-
outputChannel.appendLine(
130-
`❌ Preview.js server failed to start. Please check logs above and report the issue: https://github.com/fwouts/previewjs/issues`
131149
);
132-
await serverProcess;
133-
throw e;
134-
}
135-
await client.updateClientStatus({
136-
clientId,
137-
alive: true,
138150
});
139-
return client;
151+
return ready;
140152
}
141153

142154
function checkNodeVersionResult(result: execa.ExecaReturnValue<string>):

loader/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
},
3535
"scripts": {
3636
"prepublish": "cd .. && pnpm turbo run build --scope=@previewjs/loader --no-deps --include-dependencies",
37-
"build": "rimraf dist && tsc && cd src/release && pnpm npm install --ignore-scripts -f"
37+
"build": "rimraf dist && tsc"
3838
},
3939
"devDependencies": {
4040
"@previewjs/core": "workspace:*",

loader/src/modules.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
11
import type * as core from "@previewjs/core";
22
import type * as vfs from "@previewjs/vfs";
3-
import { chmodSync, constants, existsSync, lstatSync, readdirSync } from "fs";
3+
import execa from "execa";
4+
import fs from "fs";
45
import path from "path";
56

6-
export function loadModules({
7+
export async function loadModules({
78
installDir,
89
packageName,
910
}: {
1011
installDir: string;
1112
packageName: string;
1213
}) {
14+
if (
15+
fs.existsSync(path.join(installDir, "pnpm")) &&
16+
!fs.existsSync(path.join(installDir, "node_modules"))
17+
) {
18+
const pnpmProcess = execa.command(
19+
`cd "${installDir}" && node pnpm/bin/pnpm.cjs install --frozen-lockfile`,
20+
{
21+
shell: true,
22+
}
23+
);
24+
pnpmProcess.stdout?.on("data", (chunk) => process.stdout.write(chunk));
25+
pnpmProcess.stderr?.on("data", (chunk) => process.stderr.write(chunk));
26+
await pnpmProcess;
27+
}
1328
const coreModule = requireModule("@previewjs/core") as typeof core;
1429
const vfsModule = requireModule("@previewjs/vfs") as typeof vfs;
1530
const setupEnvironment: core.SetupPreviewEnvironment =
@@ -33,18 +48,6 @@ export function loadModules({
3348
}
3449
}
3550

36-
for (const f of readdirSync(path.join(installDir, "node_modules"))) {
37-
if (f.startsWith("esbuild-")) {
38-
const binPath = path.join(__dirname, "node_modules", f, "bin", "esbuild");
39-
if (
40-
existsSync(binPath) &&
41-
!(lstatSync(binPath).mode & constants.S_IXUSR)
42-
) {
43-
chmodSync(binPath, "555");
44-
}
45-
}
46-
}
47-
4851
return {
4952
core: coreModule,
5053
vfs: vfsModule,

0 commit comments

Comments
 (0)