rwsdk
Version:
Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime
379 lines (378 loc) • 14.2 kB
JavaScript
import { version as viteVersionString } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
const translatedOptimizeDepsPlugins = new WeakMap();
export function compatTransform(plugins, options) {
const viteMajor = options?.viteVersion ?? parseInt(viteVersionString.split(".")[0], 10);
if (viteMajor >= 8) {
return plugins;
}
return [...plugins, tsconfigPaths(), createVite7CompatPlugin()];
}
function createVite7CompatPlugin() {
return {
name: "rwsdk:vite7-compat",
enforce: "post",
config(config) {
translateRootConfig(config);
},
configEnvironment(_env, config) {
translateEnvironmentConfig(config);
},
configResolved(config) {
// context(justinvdm, 2026-06-24): Some plugins add optimizeDeps
// Rolldown plugins in configResolved after configEnvironment has run.
// Vite 7 still needs those late plugins translated before the esbuild
// optimizer runs, especially the dev vendor-barrel redirect plugin.
translateResolvedConfig(config);
installRolldownProxies(config);
},
};
}
function translateRootConfig(config) {
if (config.optimizeDeps) {
translateOptimizeDeps(config.optimizeDeps);
}
if (config.build) {
translateBuildOptions(config.build);
}
}
function translateEnvironmentConfig(config) {
if (!config) {
return;
}
if (config.optimizeDeps) {
translateOptimizeDeps(config.optimizeDeps);
}
if (config.build) {
translateBuildOptions(config.build);
}
}
function translateResolvedConfig(config) {
translateRootConfig(config);
if (!config.environments) {
return;
}
for (const env of Object.values(config.environments)) {
translateEnvironmentConfig(env);
translateEnvironmentConfig(env.config);
}
}
function translateBuildOptions(build) {
if (build.rolldownOptions == null) {
return;
}
const rolldownOptions = build.rolldownOptions;
// context(justinvdm, 2026-06-22): Start from a fresh rollupOptions object.
// Vite 7's environment config merge can share nested output objects across
// environments, so inheriting an existing rollupOptions.output would cause
// SSR's entryFileNames to leak into the client build.
const rollupOptions = { ...build.rollupOptions };
for (const key of Object.keys(rolldownOptions)) {
if (key === "output") {
rollupOptions.output = mirrorOutputOptions(rolldownOptions.output);
}
else {
rollupOptions[key] = rolldownOptions[key];
}
}
build.rollupOptions = rollupOptions;
build.rolldownOptions = createRolldownOptionsProxy(rolldownOptions, rollupOptions);
translateLibOptions(build);
}
function translateLibOptions(build) {
if (build.lib == null || build.lib === false) {
return;
}
build.rollupOptions ??= {};
const hasInput = build.rollupOptions.input != null &&
(Array.isArray(build.rollupOptions.input)
? build.rollupOptions.input.length > 0
: Object.keys(build.rollupOptions.input).length > 0);
if (hasInput) {
return;
}
build.rollupOptions.input = build.lib.entry;
if (build.lib.fileName) {
// context(justinvdm, 2026-06-22): Vite 7's environment config merge can
// share the same output object across environments. Create a new object
// so SSR's entryFileNames do not leak into the client build.
build.rollupOptions.output = {
...(build.rollupOptions.output ?? {}),
};
const fileName = typeof build.lib.fileName === "function"
? build.lib.fileName()
: build.lib.fileName;
build.rollupOptions.output.entryFileNames = fileName;
build.rollupOptions.output.chunkFileNames = fileName;
}
}
function mirrorOutputOptions(output) {
if (output == null) {
return output;
}
if (Array.isArray(output)) {
return output.map(mirrorSingleOutputOptions);
}
return mirrorSingleOutputOptions(output);
}
function mirrorSingleOutputOptions(output) {
if (output == null || typeof output !== "object") {
return output;
}
const result = {};
for (const [key, value] of Object.entries(output)) {
if (key === "codeSplitting") {
result.inlineDynamicImports = value === false;
}
else {
result[key] = value;
}
}
return result;
}
function translateOptimizeDeps(optimizeDeps) {
if (optimizeDeps.rolldownOptions == null) {
return;
}
optimizeDeps.esbuildOptions ??= {};
const rolldownOptions = optimizeDeps.rolldownOptions;
const esbuildOptions = optimizeDeps.esbuildOptions;
if (rolldownOptions.transform) {
if (rolldownOptions.transform.jsx === "react-jsx") {
esbuildOptions.jsx = "automatic";
esbuildOptions.jsxImportSource = "react";
}
else if (rolldownOptions.transform.jsx != null) {
esbuildOptions.jsx = rolldownOptions.transform.jsx;
}
if (rolldownOptions.transform.define) {
esbuildOptions.define ??= {};
Object.assign(esbuildOptions.define, rolldownOptions.transform.define);
}
}
if (Array.isArray(rolldownOptions.plugins)) {
esbuildOptions.plugins ??= [];
let translatedPlugins = translatedOptimizeDepsPlugins.get(optimizeDeps);
if (!translatedPlugins) {
translatedPlugins = new WeakSet();
translatedOptimizeDepsPlugins.set(optimizeDeps, translatedPlugins);
}
for (const plugin of rolldownOptions.plugins) {
if (translatedPlugins.has(plugin)) {
continue;
}
esbuildOptions.plugins.push(toEsbuildPlugin(plugin));
translatedPlugins.add(plugin);
}
}
}
function toEsbuildPlugin(rolldownPlugin) {
return {
name: rolldownPlugin.name,
setup(build) {
if (rolldownPlugin.resolveId) {
build.onResolve({ filter: /.*/ }, async (args) => {
const result = await rolldownPlugin.resolveId(args.path, args.importer, { kind: args.kind });
if (result == null) {
return undefined;
}
const id = typeof result === "string" ? result : result.id ?? args.path;
const hasNullPrefix = id.startsWith("\0");
const namespace = hasNullPrefix ? rolldownPlugin.name : undefined;
const path = hasNullPrefix ? id.slice(1) : id;
return {
path,
namespace,
external: typeof result === "string" ? undefined : result.external,
};
});
}
if (rolldownPlugin.load) {
const loadCallback = async (args) => {
const id = args.namespace === rolldownPlugin.name
? `\0${args.path}`
: args.path;
const result = await rolldownPlugin.load(id);
if (result == null) {
return undefined;
}
if (typeof result === "string") {
return { contents: result };
}
return {
contents: result.code,
loader: moduleTypeToLoader(result.moduleType),
};
};
build.onLoad({ filter: /.*/, namespace: rolldownPlugin.name }, loadCallback);
build.onLoad({ filter: /.*/ }, loadCallback);
}
},
};
}
function moduleTypeToLoader(moduleType) {
if (moduleType == null) {
return undefined;
}
switch (moduleType) {
case "js":
case "ts":
case "jsx":
case "tsx":
return moduleType;
default:
return undefined;
}
}
function installRolldownProxies(config) {
if (config.build?.rolldownOptions != null) {
config.build.rolldownOptions = createRolldownOptionsProxy(config.build.rolldownOptions, config.build.rollupOptions);
}
if (config.environments) {
for (const env of Object.values(config.environments)) {
const envBuild = env.build ?? env.config?.build;
if (envBuild?.rolldownOptions != null) {
envBuild.rolldownOptions = createRolldownOptionsProxy(envBuild.rolldownOptions, envBuild.rollupOptions);
}
}
}
// context(justinvdm, 2026-06-22): Vite 7 creates separate environment
// config objects at build time, so the proxies installed above on the
// resolved config do not reach the builder.environments[*].config objects
// that buildApp uses. Wrap builder.buildApp to install proxies on the live
// builder environments right before our build orchestration runs.
if (config.builder?.buildApp) {
const originalBuildApp = config.builder.buildApp;
config.builder.buildApp = async (builder) => {
installRolldownProxiesOnBuilder(builder);
return originalBuildApp(builder);
};
}
}
function installRolldownProxiesOnBuilder(builder) {
if (builder.config?.build?.rolldownOptions == null) {
builder.config.build.rolldownOptions = createRolldownOptionsProxy({}, builder.config.build.rollupOptions);
}
if (builder.environments) {
for (const env of Object.values(builder.environments)) {
const envBuild = env.config?.build;
if (envBuild?.rolldownOptions == null) {
envBuild.rolldownOptions = createRolldownOptionsProxy({}, envBuild.rollupOptions);
}
}
}
}
function createRolldownOptionsProxy(rolldownOptions, rollupOptions) {
rollupOptions ??= {};
return new Proxy(rolldownOptions, {
get(_target, prop, _receiver) {
if (prop === "output") {
if (rollupOptions.output == null) {
rollupOptions.output = {};
}
return createOutputProxy(rolldownOptions.output, rollupOptions.output);
}
const value = Reflect.get(rollupOptions, prop, rollupOptions);
if (typeof value === "function") {
return value.bind(rollupOptions);
}
return value;
},
set(_target, prop, value, _receiver) {
if (prop === "output") {
rollupOptions.output = mirrorOutputOptions(value);
return true;
}
return Reflect.set(rollupOptions, prop, value, rollupOptions);
},
has(_target, prop) {
return Reflect.has(rollupOptions, prop);
},
ownKeys(_target) {
return Reflect.ownKeys(rollupOptions);
},
getOwnPropertyDescriptor(_target, prop) {
return Reflect.getOwnPropertyDescriptor(rollupOptions, prop);
},
});
}
function createOutputProxy(rolldownOutput, rollupOutput) {
if (Array.isArray(rolldownOutput)) {
rollupOutput = ensureArray(rollupOutput);
return new Proxy(rolldownOutput, {
get(_target, prop) {
if (prop === "length") {
return rollupOutput.length;
}
const index = Number(prop);
if (!Number.isNaN(index)) {
return createSingleOutputProxy(rolldownOutput[index], rollupOutput[index]);
}
const value = Reflect.get(rollupOutput, prop, rollupOutput);
if (typeof value === "function") {
return value.bind(rollupOutput);
}
return value;
},
set(_target, prop, value) {
if (prop === "length") {
rollupOutput.length = value;
return true;
}
const index = Number(prop);
if (!Number.isNaN(index)) {
rollupOutput[index] = mirrorSingleOutputOptions(value);
return true;
}
return Reflect.set(rollupOutput, prop, value, rollupOutput);
},
has(_target, prop) {
return Reflect.has(rollupOutput, prop);
},
ownKeys(_target) {
return Reflect.ownKeys(rollupOutput);
},
getOwnPropertyDescriptor(_target, prop) {
return Reflect.getOwnPropertyDescriptor(rollupOutput, prop);
},
});
}
return createSingleOutputProxy(rolldownOutput ?? {}, rollupOutput ?? {});
}
function ensureArray(value) {
if (value == null) {
return [];
}
return Array.isArray(value) ? value : [value];
}
function createSingleOutputProxy(rolldownSingle, rollupSingle) {
rollupSingle ??= {};
return new Proxy(rolldownSingle, {
get(_target, prop) {
if (prop === "codeSplitting") {
return rollupSingle.inlineDynamicImports === true ? false : true;
}
const value = Reflect.get(rollupSingle, prop, rollupSingle);
if (typeof value === "function") {
return value.bind(rollupSingle);
}
return value;
},
set(_target, prop, value) {
if (prop === "codeSplitting") {
rollupSingle.inlineDynamicImports = value === false;
return true;
}
return Reflect.set(rollupSingle, prop, value, rollupSingle);
},
has(_target, prop) {
return Reflect.has(rollupSingle, prop);
},
ownKeys(_target) {
return Reflect.ownKeys(rollupSingle);
},
getOwnPropertyDescriptor(_target, prop) {
return Reflect.getOwnPropertyDescriptor(rollupSingle, prop);
},
});
}