Vite Config
Every Tempest frontend app starts with the same vite.config.ts: the React
plugin, the @ → src alias, and the dev-server defaults. Copying that block
from app to app is tedious, easy to get wrong, and drifts over time. The
createViteConfig helper kills that boilerplate: you call one function, get a
ready-made config object back, and assign it straight to export default.
// vite.config.ts
import { createViteConfig } from "tempest-react-sdk/vite";
export default createViteConfig({
proxy: { "/api": "http://127.0.0.1:8000" },
});
That's it. React plugin wired, @ resolving to src, dev server listening on
127.0.0.1:5173, and /api calls forwarded to your local backend — all in four
lines.
The tempest-react-sdk/vite subpath
Look at the import: it comes from tempest-react-sdk/vite, not the main
barrel tempest-react-sdk.
import { createViteConfig } from "tempest-react-sdk/vite";
Why a separate subpath?
The main barrel runs in the browser (React components, hooks). But
createViteConfig runs in Node, inside vite.config.ts, during the
build. Different environments — so the helper lives in a dedicated subpath, to
never drag config code into your production bundle.
Peer deps
vite and @vitejs/plugin-react are optional peer dependencies of the SDK.
Since every Vite app already has both in its devDependencies, there's nothing
extra to install — the helper just reuses them.
Only createViteConfig needs @vitejs/plugin-react, and it only loads it when
the config is built. The other helpers of this subpath (tempestVitest,
tempestCsp, tempestStyles, …) work without it — in a library, or in an app on
@vitejs/plugin-react-swc. Up to 0.73.0, importing any of them required the
React plugin to be installed (ERR_MODULE_NOT_FOUND,
#401).
The minimal example
With no options at all, you still get the full defaults:
// vite.config.ts
import { createViteConfig } from "tempest-react-sdk/vite";
export default createViteConfig();
That gives you:
| Default | Value |
|---|---|
| React plugin | enabled |
tempestVitest() |
enabled |
@ alias |
→ src |
server.port |
5173 |
server.host |
"127.0.0.1" |
server.open |
false |
The @ alias (and the tsconfig that must follow)
The most useful default is the @ alias: it points at your source directory, so
@/components/Button resolves to <root>/src/components/Button. No more
../../../components/Button.
// src/main.tsx
import { Button } from "@/components/Button";
import { formatBRL } from "@/utils/money";
The source path is resolved against process.cwd() (the project root, where Vite
runs from). Want to alias a different folder? Use srcDir:
// vite.config.ts
import { createViteConfig } from "tempest-react-sdk/vite";
export default createViteConfig({
srcDir: "app", // now @ → <root>/app
});
The @ alias must ALSO live in tsconfig.json
Vite and TypeScript resolve paths independently. createViteConfig teaches
Vite to resolve @, but the type-checker knows nothing until you declare the
same alias in compilerOptions.paths. Without it, tsc complains "Cannot find
module '@/...'" even though the app runs fine.
// tsconfig.json — keep the @ alias in sync for the type-checker
{
"compilerOptions": {
"paths": { "@/*": ["./src/*"] }
}
}
If you change srcDir, change paths too (e.g. "@/*": ["./app/*"]).
Proxy: string shorthand vs. object
The proxy option accepts two shapes. String values are the common shorthand:
you pass just the target URL and the helper auto-expands it to
{ target, changeOrigin: true }.
// vite.config.ts
import { createViteConfig } from "tempest-react-sdk/vite";
export default createViteConfig({
proxy: { "/api": "http://127.0.0.1:8000" },
});
// becomes { "/api": { target: "http://127.0.0.1:8000", changeOrigin: true } }
Need fine control (path rewrite, websocket, headers)? Pass an object — it
passes through raw as Vite's ProxyOptions, with no magic:
// vite.config.ts
import { createViteConfig } from "tempest-react-sdk/vite";
export default createViteConfig({
proxy: {
"/api": "http://127.0.0.1:8000", // string shorthand
"/ws": {
target: "ws://127.0.0.1:8000", // raw object
ws: true,
changeOrigin: true,
},
},
});
Mix the two freely
Each proxy key is handled independently: strings are expanded, objects pass
through. Use the shorthand where you can and the object where you must.
The escape hatch: overrides
The defaults cover most apps, but no helper anticipates everything. The
overrides option takes an arbitrary Vite UserConfig and deep-merges it
last, after everything the helper built.
// vite.config.ts
import { createViteConfig } from "tempest-react-sdk/vite";
export default createViteConfig({
srcDir: "app",
port: 3000,
alias: { "~": "/lib" },
overrides: { build: { target: "es2020" } },
});
Merged, not replaced
In overrides, the keys plugins, resolve, and server are merged with
what the helper already configured — not overwritten. So an overrides.server
with a new key coexists with the port/host you passed as top-level options,
and overrides.plugins are appended to yours. The rest of the UserConfig
(build, define, etc.) applies normally.
Testing with Vitest
Every SDK component imports its own stylesheet. In the app, Vite resolves that
import. In a Vitest suite, it does not: Vitest hands every ESM package from
node_modules to Node, and Node cannot load .css. Any test that imports the
SDK dies before running:
TypeError: Unknown file extension ".css" for …/node_modules/tempest-react-sdk/dist/styles/component/Accordion.css
If your vite.config.ts is createViteConfig, there is nothing to do: it
already includes tempestVitest(), which tells Vitest to transform the SDK through
Vite's pipeline, as the app does. Outside Vitest the plugin is inert — Vite ignores
the test key.
With a vitest.config.ts of your own, add the plugin:
// vitest.config.ts
import { defineConfig } from "vitest/config";
import { tempestVitest } from "tempest-react-sdk/vite";
export default defineConfig({
plugins: [tempestVitest()],
test: { environment: "jsdom" },
});
Measured with tempest-react-sdk@0.70.0 and a one-line test importing Button
(the repro of #397):
| Vitest | default config | with tempestVitest() |
|---|---|---|
| 2.1.9 | ❌ Unknown file extension ".css" |
✅ 1 passed |
| 3.2.7 | ❌ Unknown file extension ".css" |
✅ 1 passed |
| 4.1.11 | ❌ Unknown file extension ".css" |
✅ 1 passed |
What the plugin does underneath
It adds "tempest-react-sdk" to test.server.deps.inline, on top of whatever
list you already have. If your config already uses inline: true (inline
everything), the plugin leaves it alone — merging the two would crash Vitest 4
with ex.test is not a function.
Options reference
All options are optional.
| Option | Type | Default | What it does |
|---|---|---|---|
srcDir |
string |
"src" |
Directory aliased to @, resolved against process.cwd(). |
port |
number |
5173 |
Dev-server port. |
host |
string \| boolean |
"127.0.0.1" |
Dev-server host. |
open |
boolean |
false |
Open the browser on dev start. |
proxy |
Record<string, string \| object> |
— | Dev proxy. Strings become { target, changeOrigin: true }; objects pass through raw. |
alias |
Record<string, string> |
— | Extra aliases merged on top of the default @. |
plugins |
unknown[] |
— | Vite plugins appended after the React plugin. |
overrides |
Record<string, unknown> |
— | Arbitrary UserConfig deep-merged last. |
Content-Security-Policy
tempestCsp() lives in the same subpath and derives connect-src from the
VITE_* variables the app reads at runtime. See Content-Security-Policy.
Recap
- Import
createViteConfigfromtempest-react-sdk/vite— the dedicated Node subpath, separate from the browser barrel. - Call and assign:
export default createViteConfig({ ... }). With no options you already get the React plugin, the@→srcalias, and a dev server on127.0.0.1:5173. - Also declare the
@alias intsconfig.json("paths": { "@/*": ["./src/*"] }) — Vite and TS resolve paths independently. - In
proxy, strings are shorthand ({ target, changeOrigin: true }); objects pass through raw asProxyOptions. - In a Vitest suite,
tempestVitest()(already included) lets the SDK import withoutUnknown file extension ".css"; with avitest.config.tsof your own, add it. overridesis the escape hatch: deep-merged last, mergingplugins/resolve/serverinstead of replacing them.