Skip to content

Commit

Permalink
refactor: move code
Browse files Browse the repository at this point in the history
  • Loading branch information
hi-ogawa committed Oct 6, 2024
1 parent 1d0929f commit 8b86b7c
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 162 deletions.
9 changes: 3 additions & 6 deletions examples/child-process/src/entry-ssr.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import assert from "node:assert";
import React from "react";
import ReactDomServer from "react-dom/server.edge";
import ReactClient from "react-server-dom-webpack/client.edge";
import type { ViteDevServer } from "vite";
import type { ChildProcessFetchDevEnvironment } from "../vite.config";
import type { StreamData } from "./entry-rsc";
import type { ChildProcessFetchDevEnvironment } from "./lib/vite/environment";

export default async function handler(request: Request): Promise<Response> {
const url = new URL(request.url);
Expand Down Expand Up @@ -49,10 +48,8 @@ export default async function handler(request: Request): Promise<Response> {
return new Response(ssrStream, { headers: { "content-type": "text/html" } });
}

declare const __vite_server__: ViteDevServer;
declare const __vite_environment_rsc__: ChildProcessFetchDevEnvironment;

async function handleRsc(request: Request): Promise<Response> {
return (
__vite_server__.environments["rsc"] as ChildProcessFetchDevEnvironment
).dispatchFetch("/src/entry-rsc.tsx", request);
return __vite_environment_rsc__.dispatchFetch("/src/entry-rsc.tsx", request);
}
142 changes: 142 additions & 0 deletions examples/child-process/src/lib/vite/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import assert from "node:assert";
import childProcess from "node:child_process";
import http from "node:http";
import { join } from "node:path";
import { webToNodeHandler } from "@hiogawa/utils-node";
import { DevEnvironment, type DevEnvironmentOptions } from "vite";

// TODO
// can we abstract away child process? FetchBridgeDevEnvironment?
// multiple children per env (like Vitest)? need different API?
export class ChildProcessFetchDevEnvironment extends DevEnvironment {
public bridge!: http.Server;
public bridgeUrl!: string;
public child!: childProcess.ChildProcess;
public childUrl!: string;
public childUrlPromise!: PromiseWithResolvers<string>;

static createFactory(options: {
runtime: "node" | "bun";
conditions?: string[];
}): NonNullable<DevEnvironmentOptions["createEnvironment"]> {
return (name, config) => {
const command = [
options.runtime === "node" ? ["node"] : [],
options.runtime === "bun" ? ["bun", "run"] : [],
options.conditions ? ["--conditions", ...options.conditions] : [],
join(import.meta.dirname, `./runtime/${options.runtime}.js`),
].flat();
return new ChildProcessFetchDevEnvironment({ command }, name, config, {
// TODO
hot: false,
});
};
}

constructor(
public extraOptions: { command: string[] },
...args: ConstructorParameters<typeof DevEnvironment>
) {
super(...args);
}

override init: DevEnvironment["init"] = async (...args) => {
await super.init(...args);

const listener = webToNodeHandler(async (request) => {
const url = new URL(request.url);
// TODO: other than json?
if (url.pathname === "/rpc") {
const { method, args } = await request.json();
assert(method in this);
const result = await (this as any)[method]!(...args);
return Response.json(result);
}
return undefined;
});

const bridge = http.createServer((req, res) => {
listener(req, res, (e) => {
console.error(e);
res.statusCode = 500;
res.end("Internal server error");
});
});
this.bridge = bridge;

await new Promise<void>((resolve, reject) => {
bridge.listen(() => {
const address = bridge.address();
assert(address && typeof address !== "string");
this.bridgeUrl = `http://localhost:${address.port}`;
resolve();
});
bridge.on("error", (e) => {
console.error(e);
reject(e);
});
});

// TODO: separate child process concern?
this.childUrlPromise = PromiseWithReoslvers();
const command = this.extraOptions.command;
const child = childProcess.spawn(
command[0]!,
[
...command.slice(1),
JSON.stringify({
bridgeUrl: this.bridgeUrl,
root: this.config.root,
}),
],
{
stdio: ["ignore", "inherit", "inherit"],
},
);
this.child = child;
await new Promise<void>((resolve, reject) => {
child.on("spawn", () => {
resolve();
});
child.on("error", (e) => {
reject(e);
});
});
this.childUrl = await this.childUrlPromise.promise;
console.log("[environment.init]", {
bridgeUrl: this.bridgeUrl,
childUrl: this.childUrl,
});
};

override close: DevEnvironment["close"] = async (...args) => {
await super.close(...args);
this.child?.kill();
this.bridge?.close();
};

async dispatchFetch(entry: string, request: Request): Promise<Response> {
const headers = new Headers(request.headers);
headers.set("x-vite-meta", JSON.stringify({ entry, url: request.url }));
const url = new URL(request.url);
const childUrl = new URL(this.childUrl);
url.host = childUrl.host;
return fetch(new Request(url, { ...request, headers }));
}

/** @internal rpc for runner */
async register(childUrl: string) {
this.childUrlPromise.resolve(childUrl);
return true;
}
}

function PromiseWithReoslvers<T>(): PromiseWithResolvers<T> {
let resolve: any;
let reject: any;
const promise = new Promise<any>((resolve_, reject_) => {
resolve = resolve_;
reject = reject_;
});
return { promise, resolve, reject };
}
162 changes: 6 additions & 156 deletions examples/child-process/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import assert from "node:assert";
import childProcess from "node:child_process";
import http from "node:http";
import { join } from "node:path";
import { webToNodeHandler } from "@hiogawa/utils-node";
import react from "@vitejs/plugin-react";
import {
DevEnvironment,
type DevEnvironmentOptions,
defineConfig,
isRunnableDevEnvironment,
} from "vite";
import { defineConfig, isRunnableDevEnvironment } from "vite";
import { ChildProcessFetchDevEnvironment } from "./src/lib/vite/environment";

export default defineConfig((_env) => ({
clearScreen: false,
Expand All @@ -19,17 +12,13 @@ export default defineConfig((_env) => ({
{
name: "app",
configureServer(server) {
Object.assign(globalThis, { __vite_server__: server });
Object.assign(globalThis, {
__vite_server__: server,
__vite_environment_rsc__: server.environments["rsc"],
});
return () => {
server.middlewares.use(
webToNodeHandler(async (request) => {
if (0) {
// debug
return (server.environments["rsc"] as any).dispatchFetch(
"/src/entry-rsc.tsx",
request,
);
}
const ssrEnv = server.environments.ssr;
assert(isRunnableDevEnvironment(ssrEnv));
const mod = await ssrEnv.runner.import("/src/entry-ssr.tsx");
Expand Down Expand Up @@ -57,142 +46,3 @@ export default defineConfig((_env) => ({
},
},
}));

// TODO
// can we abstract away child process? FetchBridgeDevEnvironment?
// multiple children per env (like Vitest)? need different API?
export class ChildProcessFetchDevEnvironment extends DevEnvironment {
public bridge!: http.Server;
public bridgeUrl!: string;
public child!: childProcess.ChildProcess;
public childUrl!: string;
public childUrlPromise!: PromiseWithResolvers<string>;

static createFactory(options: {
runtime: "node" | "bun";
conditions?: string[];
}): NonNullable<DevEnvironmentOptions["createEnvironment"]> {
return (name, config) => {
const command = [
options.runtime === "node" ? ["node"] : [],
options.runtime === "bun" ? ["bun", "run"] : [],
options.conditions ? ["--conditions", ...options.conditions] : [],
join(
import.meta.dirname,
`./src/lib/vite/runtime/${options.runtime}.js`,
),
].flat();
return new ChildProcessFetchDevEnvironment({ command }, name, config, {
// TODO
hot: false,
});
};
}

constructor(
public extraOptions: { command: string[] },
...args: ConstructorParameters<typeof DevEnvironment>
) {
super(...args);
}

override init: DevEnvironment["init"] = async (...args) => {
await super.init(...args);

const listener = webToNodeHandler(async (request) => {
const url = new URL(request.url);
// TODO: other than json?
if (url.pathname === "/rpc") {
const { method, args } = await request.json();
assert(method in this);
const result = await (this as any)[method]!(...args);
return Response.json(result);
}
return undefined;
});

const bridge = http.createServer((req, res) => {
listener(req, res, (e) => {
console.error(e);
res.statusCode = 500;
res.end("Internal server error");
});
});
this.bridge = bridge;

await new Promise<void>((resolve, reject) => {
bridge.listen(() => {
const address = bridge.address();
assert(address && typeof address !== "string");
this.bridgeUrl = `http://localhost:${address.port}`;
resolve();
});
bridge.on("error", (e) => {
console.error(e);
reject(e);
});
});

// TODO: separate child process concern?
this.childUrlPromise = PromiseWithReoslvers();
const command = this.extraOptions.command;
const child = childProcess.spawn(
command[0]!,
[
...command.slice(1),
JSON.stringify({
bridgeUrl: this.bridgeUrl,
root: this.config.root,
}),
],
{
stdio: ["ignore", "inherit", "inherit"],
},
);
this.child = child;
await new Promise<void>((resolve, reject) => {
child.on("spawn", () => {
resolve();
});
child.on("error", (e) => {
reject(e);
});
});
this.childUrl = await this.childUrlPromise.promise;
console.log("[environment.init]", {
bridgeUrl: this.bridgeUrl,
childUrl: this.childUrl,
});
};

override close: DevEnvironment["close"] = async (...args) => {
await super.close(...args);
this.child?.kill();
this.bridge?.close();
};

async dispatchFetch(entry: string, request: Request): Promise<Response> {
const headers = new Headers(request.headers);
headers.set("x-vite-meta", JSON.stringify({ entry, url: request.url }));
const url = new URL(request.url);
const childUrl = new URL(this.childUrl);
url.host = childUrl.host;
return fetch(new Request(url, { ...request, headers }));
}

/** @internal rpc for runner */
async register(childUrl: string) {
this.childUrlPromise.resolve(childUrl);
return true;
}
}

function PromiseWithReoslvers<T>(): PromiseWithResolvers<T> {
let resolve: any;
let reject: any;
const promise = new Promise<any>((resolve_, reject_) => {
resolve = resolve_;
reject = reject_;
});
return { promise, resolve, reject };
}

0 comments on commit 8b86b7c

Please sign in to comment.