Files
stream.ui/plugins/ssrPlugin.ts
2026-01-05 19:13:03 +07:00

156 lines
5.1 KiB
TypeScript

import { readFileSync } from "node:fs";
import path from "node:path";
import type { Plugin } from "vite";
export function createVirtualPlugin(name: string, load: Plugin["load"]) {
name = "virtual:" + name;
return {
name,
resolveId(source, _importer, _options) {
if (source === name || source.startsWith(`${name}?`)) {
return `\0${source}`;
}
return;
},
load(id, options) {
if (id === `\0${name}` || id.startsWith(`\0${name}?`)) {
return (load as any).apply(this, [id, options]);
}
},
} satisfies Plugin;
}
export function clientFirstBuild(): Plugin {
return {
name: "client-first-build",
config(config) {
config.builder ??= {};
config.builder.buildApp = async (builder) => {
const clientEnvironment = builder.environments.client;
const workerEnvironments = Object.keys(builder.environments)
.filter((name) => name !== "client")
.map((name) => builder.environments[name]);
// console.log('Client First Build Plugin: Starting builds...', workerEnvironments)
// Client build first
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (clientEnvironment) {
// clientEnvironment.config.build.outDir = "dist/client";
// console.log("Client First Build Plugin: Building client...", Object.keys());
await builder.build(clientEnvironment);
}
// console.log("Client First Build Plugin: Client build complete.", workerEnvironments);
// Then worker builds
for (const workerEnv of workerEnvironments) {
await builder.build(workerEnv);
}
};
},
};
}
export function injectManifest(): Plugin {
let clientOutDir = "dist/client";
return {
name: "inject-manifest",
config(config) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const viteConfig = config as any;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
clientOutDir =
viteConfig.environments?.client?.build?.outDir ?? "dist/client";
},
async transform(code, id, options) {
// Only transform in SSR environment (non-client)
if (!options?.ssr) {
return;
}
// Only transform files that contain the placeholder
if (!code.includes("__VITE_MANIFEST_CONTENT__")) {
return;
}
// Read manifest from client build output
const manifestPath = path.resolve(
process.cwd(),
clientOutDir,
".vite/manifest.json"
);
let manifestContent: string | undefined;
try {
manifestContent = await this.fs
.readFile(manifestPath)
.then((data) => data.toString());
} catch {
// Manifest not found
}
if (!manifestContent) return;
// Replace placeholder string with actual manifest data
// Format: { "__manifest__": { default: <manifest> } } to match the Object.entries loop
const newCode = code.replace(
/"__VITE_MANIFEST_CONTENT__"/g,
`{ "__manifest__": { default: ${manifestContent} } }`
);
if (newCode !== code) {
return { code: newCode, map: null };
}
},
};
}
export default function ssrPlugin(): Plugin[] {
// const { hotReload: hotReloadOption = true, entry: entryOption = {} } = options
const plugins: Plugin[] = [];
plugins.push(clientFirstBuild());
plugins.push({
name: "ssr-auto-entry",
config(config) {
config.define = config.define || {};
},
resolveId(id, importer, options) {
if (!id.startsWith('@httpClientAdapter')) return
const pwd = process.cwd()
console.log('Resolving httpClientAdapter in', pwd, 'for', {id, importer, options})
return path.resolve(
__dirname,
options?.ssr
? pwd+"/src/api/httpClientAdapter.server.ts"
: pwd+"/src/api/httpClientAdapter.client.ts"
);
},
async configResolved(config) {
const viteConfig = config as any;
if (!viteConfig.environments) {
viteConfig.environments = {};
}
if (!viteConfig.environments.client) {
viteConfig.environments.client = {};
}
if (!viteConfig.environments.client.build) {
viteConfig.environments.client.build = {};
}
const clientBuild = viteConfig.environments.client.build;
clientBuild.manifest = true;
clientBuild.rollupOptions = clientBuild.rollupOptions || {};
clientBuild.rollupOptions.input = "src/client.ts";
if (!viteConfig.environments.ssr) {
const manifestPath = path.join(clientBuild.outDir as string, '.vite/manifest.json')
try {
const resolvedPath = path.resolve(process.cwd(), manifestPath)
const manifestContent = readFileSync(resolvedPath, 'utf-8')
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
config.define['import.meta.env.VITE_MANIFEST_CONTENT'] = JSON.stringify(manifestContent)
} catch {}
}
},
});
plugins.push(injectManifest());
return plugins;
}