{{={= =}=}}
///
import { type PluginOption } from "vite";
import { defaultExclude } from "vitest/config"
// Vite merges `userConfig` and our `waspConfig` returned from the plugin.
// In that merge, primitive values from waspConfig take precedence, and
// arrays are concatenated.
//
// This allows us to treat config values differently:
// - Forced: hardcoded in the return object so they always win. If the
// user set one of these in their vite.config.ts, we throw an error.
// - Overridable: we read the user's value and use it or fall back to
// our default.
// - Additive (arrays): we only return Wasp's entries; Vite's merge
// appends them to whatever the user already has.
const forcedOptions = {
'base': "{= baseDir =}",
'envPrefix': "REACT_APP_",
'build.outDir': "{= clientBuildDirPath =}",
} as const;
export function waspConfig(): PluginOption {
return {
name: "wasp:config",
enforce: 'pre',
config(config) {
throwIfOverridingForcedOptions(config);
// Returned config is merged with the user's config by Vite (mergeConfig).
return {
base: forcedOptions['base'],
optimizeDeps: {
exclude: {=& depsExcludedFromOptimization =}
},
server: {
port: useUserValue(config.server?.port, {= defaultClientPort =}),
host: useUserValue(config.server?.host, "0.0.0.0"),
},
envPrefix: forcedOptions['envPrefix'],
build: {
outDir: forcedOptions['build.outDir'],
},
resolve: {
// These packages rely on a single instance per page. Not deduping them
// causes runtime errors (e.g., hook rule violation in react, QueryClient
// instance error in react-query, Invariant Error in react-router).
dedupe: ["react", "react-dom", "@tanstack/react-query", "react-router"],
alias: [
{
// Vite doesn't look for `.prisma/client` imports in the `node_modules`
// folder. We point it to the correct place here.
// TODO: Check if we can remove when updating Prisma (#2504)
find: /^\.prisma\/client\/(.+)$/,
replacement: "node_modules/.prisma/client/$1.js",
},
{
// Handle bare .prisma/client import
find: /^\.prisma\/client$/,
replacement: "node_modules/.prisma/client",
},
],
},
test: {
globals: useUserValue(config.test?.globals, true),
environment: useUserValue(config.test?.environment, "jsdom"),
setupFiles: {=& vitest.setupFilesArray =},
exclude: [
...defaultExclude,
"{= vitest.excludeWaspArtefactsPattern =}",
]
},
};
}
};
}
function useUserValue(userValue: T | undefined, defaultValue: T): T {
return userValue ?? defaultValue;
}
function throwIfOverridingForcedOptions(config: Record): void {
const conflicts: string[] = [];
for (const [path, forcedValue] of Object.entries(forcedOptions)) {
const userValue = getByPath(config, path);
if (userValue !== undefined && userValue !== forcedValue) {
conflicts.push(
` - "${path}" is set to ${JSON.stringify(userValue)}, but Wasp requires ${JSON.stringify(forcedValue)}`
);
}
}
if (conflicts.length > 0) {
throw new Error(
`Your vite.config.ts sets options that Wasp controls:\n${conflicts.join('\n')}\n\nRemove these from your Vite config, Wasp sets them automatically.`
);
}
}
function getByPath(obj: Record, path: string): unknown {
return path.split('.').reduce((node, segment) => node?.[segment], obj);
}