Skip to content

Commit

Permalink
feat!: migrate to Vite 6 environment API (#634)
Browse files Browse the repository at this point in the history
  • Loading branch information
hi-ogawa authored Jan 24, 2025
1 parent 0ccd8c7 commit 8d3e10c
Show file tree
Hide file tree
Showing 14 changed files with 356 additions and 333 deletions.
14 changes: 8 additions & 6 deletions packages/react-server-next/src/vite/adapters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ export type AdapterType = "node" | "vercel" | "vercel-edge" | "cloudflare";
export function adapterPlugin(options: {
adapter: AdapterType;
outDir: string;
}): { server?: Plugin[]; client?: Plugin[] } {
}): Plugin[] {
const adapter = options.adapter ?? autoSelectAdapter();
if (adapter === "node") {
return {};
return [];
}

const buildPlugin: Plugin = {
Expand Down Expand Up @@ -102,10 +102,12 @@ export function adapterPlugin(options: {
},
};

return {
server: [aliasPlatformPlugin],
client: [registerHooksPlugin, buildPlugin, devPlatformPlugin],
};
return [
registerHooksPlugin,
buildPlugin,
devPlatformPlugin,
aliasPlatformPlugin,
];
}

// cf. https://github.com/sveltejs/kit/blob/52e5461b055a104694f276859a7104f58452fab0/packages/adapter-auto/adapters.js
Expand Down
78 changes: 36 additions & 42 deletions packages/react-server-next/src/vite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path from "node:path";
import {
type ReactServerPluginOptions,
vitePluginReactServer,
wrapServerPlugin,
} from "@hiogawa/react-server/plugin";
import {
vitePluginFetchUrlImportMetaUrl,
Expand Down Expand Up @@ -32,10 +33,6 @@ export default function vitePluginReactServerNext(
): PluginOption {
const outDir = options?.outDir ?? "dist";
const adapter = options?.adapter ?? autoSelectAdapter();
const adapterPlugins = adapterPlugin({
adapter,
outDir,
});

return [
react(),
Expand All @@ -45,34 +42,33 @@ export default function vitePluginReactServerNext(
vitePluginReactServer({
...options,
routeDir: options?.routeDir ?? "app",
plugins: [
nextJsxPlugin(),
tsconfigPaths(),
nextOgPlugin(),
vitePluginWasmModule({
buildMode:
adapter === "cloudflare" || adapter === "vercel-edge"
? "import"
: "fs",
}),
vitePluginFetchUrlImportMetaUrl({
buildMode:
adapter === "cloudflare"
? "import"
: adapter === "vercel-edge"
? "inline"
: "fs",
}),
adapterPlugins.server,
options?.plugins,
],
}),
nextOgPlugin(),
wrapServerPlugin([
vitePluginWasmModule({
buildMode:
adapter === "cloudflare" || adapter === "vercel-edge"
? "import"
: "fs",
}),
vitePluginFetchUrlImportMetaUrl({
buildMode:
adapter === "cloudflare"
? "import"
: adapter === "vercel-edge"
? "inline"
: "fs",
}),
]),
vitePluginLogger(),
vitePluginSsrMiddleware({
entry: "next/vite/entry-ssr",
preview: path.resolve(outDir, "server", "index.js"),
}),
adapterPlugins.client,
adapterPlugin({
adapter,
outDir,
}),
appFaviconPlugin(),
{
name: "next-exclude-optimize",
Expand Down Expand Up @@ -115,10 +111,19 @@ function nextOgPlugin(): Plugin[] {
];
}

// workaround https://github.com/vitejs/vite/issues/17689
(globalThis as any).__next_vite_last_env__ ??= [];
declare let __next_vite_last_env__: string[];

function nextConfigPlugin(): Plugin {
return {
name: nextConfigPlugin.name,
config() {
// remove last loaded env so that Vite reloads a new value
for (const key of __next_vite_last_env__) {
delete process.env[key];
}

// TODO
// this is only for import.meta.env.NEXT_PUBLIC_xxx replacement.
// we might want to define process.env.NEXT_PUBLIC_xxx for better compatibility.
Expand All @@ -128,26 +133,15 @@ function nextConfigPlugin(): Plugin {
};
},
configResolved(config) {
updateEnv(() => loadEnv(config.mode, config.envDir, ""));
const loadedEnv = loadEnv(config.mode, config.envDir, "");
__next_vite_last_env__ = Object.keys(loadedEnv).filter(
(key) => !(key in process.env),
);
Object.assign(process.env, loadedEnv);
},
};
}

// workaround https://github.com/vitejs/vite/issues/17689
(globalThis as any).__next_vite_last_env__ ??= [];
declare let __next_vite_last_env__: string[];

function updateEnv(loadEnv: () => Record<string, string>) {
for (const key of __next_vite_last_env__) {
delete process.env[key];
}
const loadedEnv = loadEnv();
__next_vite_last_env__ = Object.keys(loadedEnv).filter(
(key) => !(key in process.env),
);
Object.assign(process.env, loadedEnv);
}

function nextJsxPlugin(): Plugin {
return {
name: nextJsxPlugin.name,
Expand Down
1 change: 1 addition & 0 deletions packages/react-server/examples/basic/e2e/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export async function inspectDevModules<T extends string>(
}

export const testNoJs = test.extend({
// @ts-ignore
javaScriptEnabled: ({}, use) => use(false),
});

Expand Down
44 changes: 19 additions & 25 deletions packages/react-server/examples/basic/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import path from "node:path";
import { vitePluginReactServer } from "@hiogawa/react-server/plugin";
import {
vitePluginReactServer,
wrapClientPlugin,
wrapServerPlugin,
} from "@hiogawa/react-server/plugin";
import { vitePluginErrorOverlay } from "@hiogawa/vite-plugin-error-overlay";
import { vitePluginWasmModule } from "@hiogawa/vite-plugin-server-asset";
import {
Expand All @@ -14,40 +18,21 @@ import { type Plugin, defineConfig } from "vite";
export default defineConfig({
clearScreen: false,
plugins: [
// TODO: for now mdx is server only.
// see https://mdxjs.com/docs/getting-started/#vite for how to setup client hmr.
mdx(),
process.env["USE_SWC"]
? (await import("@vitejs/plugin-react-swc".slice())).default()
: react(),
unocss(),
// TODO: remove from ssr build
wrapClientPlugin(unocss()),
!process.env["E2E"] &&
vitePluginErrorOverlay({
patchConsoleError: true,
}),
vitePluginReactServer({
entryBrowser: "/src/entry-browser",
entryServer: "/src/entry-server",
plugins: [
// TODO: for now mdx is server only.
// see https://mdxjs.com/docs/getting-started/#vite for how to setup client hmr.
mdx(),
testVitePluginVirtual(),
vitePluginWasmModule({
buildMode:
process.env.VERCEL || process.env.CF_PAGES ? "import" : "fs",
}),
{
name: "cusotm-react-server-config",
config() {
return {
build: {
assetsInlineLimit(filePath) {
// test non-inlined server asset
return !filePath.includes("/test/assets/");
},
},
};
},
},
],
}),
vitePluginLogger(),
vitePluginSsrMiddleware({
Expand All @@ -66,9 +51,18 @@ export default defineConfig({
},
},
testVitePluginVirtual(),
wrapServerPlugin([
vitePluginWasmModule({
buildMode: process.env.VERCEL || process.env.CF_PAGES ? "import" : "fs",
}),
]),
],
build: {
ssrEmitAssets: true,
assetsInlineLimit(filePath) {
// test non-inlined server asset
return !filePath.includes("/test/assets/");
},
},
ssr: {
noExternal: [
Expand Down
27 changes: 15 additions & 12 deletions packages/react-server/src/entry/ssr.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createDebug, tinyassert } from "@hiogawa/utils";
import { createMemoryHistory } from "@tanstack/history";
import ReactDOMServer from "react-dom/server.edge";
import type { ModuleNode, ViteDevServer } from "vite";
import type { DevEnvironment, EnvironmentModuleNode } from "vite";
import type { SsrAssetsType } from "../features/assets/plugin";
import { DEV_SSR_CSS, SERVER_CSS_PROXY } from "../features/assets/shared";
import {
Expand Down Expand Up @@ -79,7 +79,7 @@ export async function prerender(request: Request) {

export async function importReactServer(): Promise<typeof import("./server")> {
if (import.meta.env.DEV) {
return $__global.dev.reactServer.ssrLoadModule(ENTRY_SERVER_WRAPPER) as any;
return $__global.dev.reactServerRunner.import(ENTRY_SERVER_WRAPPER);
} else {
return import("virtual:react-server-build" as string);
}
Expand Down Expand Up @@ -264,21 +264,24 @@ async function devInspectHandler(request: Request) {
tinyassert(request.method === "POST");
const data = await request.json();
if (data.type === "module") {
let mod: ModuleNode | undefined;
let mod: EnvironmentModuleNode | undefined;
if (data.environment === "ssr") {
mod = await getModuleNode($__global.dev.server, data.url, true);
mod = await getModuleNode(
$__global.dev.server.environments.ssr,
data.url,
);
}
if (data.environment === "react-server") {
mod = await getModuleNode($__global.dev.reactServer, data.url, true);
if (data.environment === "rsc") {
mod = await getModuleNode(
$__global.dev.server.environments["rsc"]!,
data.url,
);
}
const result = mod && {
id: mod.id,
lastInvalidationTimestamp: mod.lastInvalidationTimestamp,
importers: [...(mod.importers ?? [])].map((m) => m.id),
ssrImportedModules: [...(mod.ssrImportedModules ?? [])].map((m) => m.id),
clientImportedModules: [...(mod.clientImportedModules ?? [])].map(
(m) => m.id,
),
importedModules: [...(mod.importedModules ?? [])].map((m) => m.id),
};
return new Response(JSON.stringify(result || false, null, 2), {
headers: { "content-type": "application/json" },
Expand All @@ -287,8 +290,8 @@ async function devInspectHandler(request: Request) {
tinyassert(false);
}

async function getModuleNode(server: ViteDevServer, url: string, ssr: boolean) {
const resolved = await server.moduleGraph.resolveUrl(url, ssr);
async function getModuleNode(server: DevEnvironment, url: string) {
const resolved = await server.moduleGraph.resolveUrl(url);
return server.moduleGraph.getModuleById(resolved[1]);
}

Expand Down
34 changes: 15 additions & 19 deletions packages/react-server/src/features/assets/css.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import type { ViteDevServer } from "vite";
import {
type DevEnvironment,
type EnvironmentModuleNode,
isCSSRequest,
} from "vite";

// cf
// https://github.com/hi-ogawa/vite-plugins/blob/3c496fa1bb5ac66d2880986877a37ed262f1d2a6/packages/vite-glob-routes/examples/demo/vite-plugin-ssr-css.ts
// https://github.com/remix-run/remix/blob/dev/packages/remix-dev/vite/styles.ts

export async function collectStyle(
server: ViteDevServer,
options: { entries: string[]; ssr: boolean },
export async function transformStyleUrls(
server: DevEnvironment,
urls: string[],
) {
const urls = await collectStyleUrls(server, options);
const styles = await Promise.all(
urls.map(async (url) => {
const res = await server.transformRequest(url + "?direct");
Expand All @@ -19,35 +22,28 @@ export async function collectStyle(
}

export async function collectStyleUrls(
server: ViteDevServer,
{ entries, ssr }: { entries: string[]; ssr: boolean },
server: DevEnvironment,
{ entries }: { entries: string[] },
) {
const visited = new Set<string>();
const visited = new Set<EnvironmentModuleNode>();

async function traverse(url: string) {
const [, id] = await server.moduleGraph.resolveUrl(url);
if (visited.has(id)) {
return;
}
visited.add(id);
const mod = server.moduleGraph.getModuleById(id);
if (!mod) {
if (!mod || visited.has(mod)) {
return;
}
visited.add(mod);
await Promise.all(
[...mod.importedModules].map((childMod) => traverse(childMod.url)),
);
}

// ensure import analysis is ready for top entries
await Promise.all(entries.map((e) => server.transformRequest(e, { ssr })));
await Promise.all(entries.map((e) => server.transformRequest(e)));

// traverse
await Promise.all(entries.map((url) => traverse(url)));

return [...visited].filter((url) => url.match(CSS_LANGS_RE));
return [...visited].map((mod) => mod.url).filter((url) => isCSSRequest(url));
}

// cf. https://github.com/vitejs/vite/blob/d6bde8b03d433778aaed62afc2be0630c8131908/packages/vite/src/node/constants.ts#L49C23-L50
export const CSS_LANGS_RE =
/\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
Loading

0 comments on commit 8d3e10c

Please sign in to comment.