225 lines
7.8 KiB
JavaScript
225 lines
7.8 KiB
JavaScript
import process from "node:process";
|
|
import net from "node:net";
|
|
import { Buffer } from "node:buffer";
|
|
import { isTest } from "std-env";
|
|
function getViteNodeOptionsEnvVar() {
|
|
const envVar = process.env.NUXT_VITE_NODE_OPTIONS;
|
|
try {
|
|
return JSON.parse(envVar || "{}");
|
|
} catch (e) {
|
|
console.error("vite-node-shared: Failed to parse NUXT_VITE_NODE_OPTIONS environment variable.", e);
|
|
return {};
|
|
}
|
|
}
|
|
const viteNodeOptions = getViteNodeOptionsEnvVar();
|
|
const pendingRequests = /* @__PURE__ */ new Map();
|
|
let requestIdCounter = 0;
|
|
let clientSocket;
|
|
let currentConnectPromise;
|
|
const MAX_RETRY_ATTEMPTS = viteNodeOptions.maxRetryAttempts ?? 5;
|
|
const BASE_RETRY_DELAY_MS = viteNodeOptions.baseRetryDelay ?? 100;
|
|
const MAX_RETRY_DELAY_MS = viteNodeOptions.maxRetryDelay ?? 2e3;
|
|
const REQUEST_TIMEOUT_MS = viteNodeOptions.requestTimeout ?? 6e4;
|
|
function calculateRetryDelay(attempt) {
|
|
const exponentialDelay = BASE_RETRY_DELAY_MS * Math.pow(2, attempt);
|
|
const jitter = Math.random() * .1 * exponentialDelay;
|
|
return Math.min(exponentialDelay + jitter, MAX_RETRY_DELAY_MS);
|
|
}
|
|
function connectSocket() {
|
|
if (clientSocket && !clientSocket.destroyed) return Promise.resolve(clientSocket);
|
|
if (currentConnectPromise) return currentConnectPromise;
|
|
const thisPromise = new Promise((resolve, reject) => {
|
|
if (!viteNodeOptions.socketPath) {
|
|
console.error("vite-node-shared: NUXT_VITE_NODE_OPTIONS.socketPath is not defined.");
|
|
return reject(/* @__PURE__ */ new Error("Vite Node IPC socket path not configured."));
|
|
}
|
|
const attemptConnection = (attempt = 0) => {
|
|
const socket = net.createConnection(viteNodeOptions.socketPath);
|
|
const INITIAL_BUFFER_SIZE = 64 * 1024;
|
|
const MAX_BUFFER_SIZE = 1024 * 1024 * 1024;
|
|
let buffer = Buffer.alloc(INITIAL_BUFFER_SIZE);
|
|
let writeOffset = 0;
|
|
let readOffset = 0;
|
|
socket.setNoDelay(true);
|
|
socket.setKeepAlive(true, 3e4);
|
|
const cleanup = () => {
|
|
socket.off("connect", onConnect);
|
|
socket.off("data", onData);
|
|
socket.off("error", onError);
|
|
socket.off("close", onClose);
|
|
};
|
|
const resetBuffer = () => {
|
|
writeOffset = 0;
|
|
readOffset = 0;
|
|
};
|
|
const compactBuffer = () => {
|
|
if (readOffset > 0) {
|
|
const remainingData = writeOffset - readOffset;
|
|
if (remainingData > 0) buffer.copy(buffer, 0, readOffset, writeOffset);
|
|
writeOffset = remainingData;
|
|
readOffset = 0;
|
|
}
|
|
};
|
|
const ensureBufferCapacity = (additionalBytes) => {
|
|
const requiredSize = writeOffset + additionalBytes;
|
|
if (requiredSize > MAX_BUFFER_SIZE) throw new Error(`Buffer size limit exceeded: ${requiredSize} > ${MAX_BUFFER_SIZE}`);
|
|
if (requiredSize > buffer.length) {
|
|
compactBuffer();
|
|
if (writeOffset + additionalBytes > buffer.length) {
|
|
const newSize = Math.min(Math.max(buffer.length * 2, requiredSize), MAX_BUFFER_SIZE);
|
|
const newBuffer = Buffer.alloc(newSize);
|
|
buffer.copy(newBuffer, 0, 0, writeOffset);
|
|
buffer = newBuffer;
|
|
}
|
|
}
|
|
};
|
|
const onConnect = () => {
|
|
clientSocket = socket;
|
|
resolve(socket);
|
|
};
|
|
const onData = (data) => {
|
|
try {
|
|
ensureBufferCapacity(data.length);
|
|
data.copy(buffer, writeOffset);
|
|
writeOffset += data.length;
|
|
while (writeOffset - readOffset >= 4) {
|
|
const messageLength = buffer.readUInt32BE(readOffset);
|
|
if (writeOffset - readOffset < 4 + messageLength) return;
|
|
const message = buffer.subarray(readOffset + 4, readOffset + 4 + messageLength).toString("utf-8");
|
|
readOffset += 4 + messageLength;
|
|
try {
|
|
const response = JSON.parse(message);
|
|
const requestHandlers = pendingRequests.get(response.id);
|
|
if (requestHandlers) {
|
|
const { resolve: resolveRequest, reject: rejectRequest } = requestHandlers;
|
|
if (response.type === "error") {
|
|
const err = new Error(response.error.message);
|
|
err.stack = response.error.stack;
|
|
err.data = response.error.data;
|
|
err.statusCode = err.status = response.error.status || response.error.statusCode;
|
|
rejectRequest(err);
|
|
} else resolveRequest(response.data);
|
|
pendingRequests.delete(response.id);
|
|
}
|
|
} catch (parseError) {
|
|
console.warn("vite-node-shared: Failed to parse IPC response:", parseError);
|
|
}
|
|
}
|
|
if (readOffset > buffer.length / 2) compactBuffer();
|
|
} catch (error) {
|
|
socket.destroy(error instanceof Error ? error : /* @__PURE__ */ new Error("Buffer management error"));
|
|
}
|
|
};
|
|
const onError = (err) => {
|
|
cleanup();
|
|
resetBuffer();
|
|
if (attempt < MAX_RETRY_ATTEMPTS) {
|
|
const delay = calculateRetryDelay(attempt);
|
|
setTimeout(() => attemptConnection(attempt + 1), delay);
|
|
} else {
|
|
if (currentConnectPromise === thisPromise) reject(err);
|
|
for (const { reject: rejectRequest } of pendingRequests.values()) rejectRequest(err);
|
|
pendingRequests.clear();
|
|
if (clientSocket === socket) clientSocket = void 0;
|
|
if (currentConnectPromise === thisPromise) currentConnectPromise = void 0;
|
|
}
|
|
};
|
|
const onClose = () => {
|
|
cleanup();
|
|
resetBuffer();
|
|
for (const { reject: rejectRequest } of pendingRequests.values()) rejectRequest(/* @__PURE__ */ new Error("IPC connection closed"));
|
|
pendingRequests.clear();
|
|
if (clientSocket === socket) clientSocket = void 0;
|
|
if (currentConnectPromise === thisPromise) currentConnectPromise = void 0;
|
|
};
|
|
socket.on("connect", onConnect);
|
|
socket.on("data", onData);
|
|
socket.on("error", onError);
|
|
socket.on("close", onClose);
|
|
};
|
|
attemptConnection();
|
|
});
|
|
currentConnectPromise = thisPromise;
|
|
return currentConnectPromise;
|
|
}
|
|
async function sendRequest(type, payload) {
|
|
const requestId = requestIdCounter++;
|
|
let lastError;
|
|
for (let requestAttempt = 0; requestAttempt <= MAX_RETRY_ATTEMPTS; requestAttempt++) try {
|
|
const socket = await connectSocket();
|
|
return new Promise((resolve, reject) => {
|
|
const timeoutId = setTimeout(() => {
|
|
pendingRequests.delete(requestId);
|
|
reject(/* @__PURE__ */ new Error(`Request timeout after ${REQUEST_TIMEOUT_MS}ms for type: ${type}`));
|
|
}, REQUEST_TIMEOUT_MS);
|
|
pendingRequests.set(requestId, {
|
|
resolve: (value) => {
|
|
clearTimeout(timeoutId);
|
|
resolve(value);
|
|
},
|
|
reject: (reason) => {
|
|
clearTimeout(timeoutId);
|
|
reject(reason);
|
|
}
|
|
});
|
|
const message = JSON.stringify({
|
|
id: requestId,
|
|
type,
|
|
payload
|
|
});
|
|
const messageBuffer = Buffer.from(message, "utf-8");
|
|
const messageLength = messageBuffer.length;
|
|
const fullMessage = Buffer.alloc(4 + messageLength);
|
|
fullMessage.writeUInt32BE(messageLength, 0);
|
|
messageBuffer.copy(fullMessage, 4);
|
|
try {
|
|
socket.write(fullMessage);
|
|
} catch (error) {
|
|
clearTimeout(timeoutId);
|
|
pendingRequests.delete(requestId);
|
|
reject(error);
|
|
}
|
|
});
|
|
} catch (error) {
|
|
lastError = error;
|
|
if (requestAttempt < MAX_RETRY_ATTEMPTS) {
|
|
const delay = calculateRetryDelay(requestAttempt);
|
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
if (clientSocket) {
|
|
clientSocket.destroy();
|
|
clientSocket = void 0;
|
|
}
|
|
currentConnectPromise = void 0;
|
|
}
|
|
}
|
|
throw lastError || /* @__PURE__ */ new Error("Request failed after all retry attempts");
|
|
}
|
|
const viteNodeFetch = {
|
|
getManifest() {
|
|
return sendRequest("manifest", void 0);
|
|
},
|
|
getInvalidates() {
|
|
return sendRequest("invalidates", void 0);
|
|
},
|
|
resolveId(id, importer) {
|
|
return sendRequest("resolve", {
|
|
id,
|
|
importer
|
|
});
|
|
},
|
|
fetchModule(moduleId) {
|
|
return sendRequest("module", { moduleId });
|
|
},
|
|
ensureConnected() {
|
|
return connectSocket();
|
|
}
|
|
};
|
|
let preConnectAttempted = false;
|
|
function preConnect() {
|
|
if (preConnectAttempted || !viteNodeOptions.socketPath) return;
|
|
preConnectAttempted = true;
|
|
return connectSocket().catch(() => {});
|
|
}
|
|
if (typeof process !== "undefined" && !isTest) setTimeout(preConnect, 100);
|
|
export { viteNodeFetch, viteNodeOptions };
|