Skip to content

Commit b13f263

Browse files
Add @arkenv/rsbuild-plugin for TanStack Start + Rsbuild server/client env (#1802)
Fixes #1794 Ships `@arkenv/rsbuild-plugin` on the v1 branch, giving TanStack Start + Rsbuild the same server/client env path that `@arkenv/vite-plugin` provides for Vite: - **Client rewrite**: in `web` / `web-worker` environments the `env.ts` module is replaced via `api.transform` with a scrubbed client module — `PUBLIC_` keys inlined as coerced literals, server-only keys replaced with throwing getters (`Do not access server-only key '…' on the client…`), validator imports stripped. - **Server passthrough**: `node` environments keep the real `env.ts` so boot-time validation runs against the deployment environment. - **Build-time validation**: `loadValidatedEnv` runs in `onBeforeEnvironmentCompile` and fails the build before assets are emitted. - **Dev reload**: schema + `.env*` files are registered as build dependencies via `addDependency` / `addMissingDependency`, so edits re-validate and refresh inlined values during `rsbuild dev`. - **`/standard` entry** mirroring `@arkenv/vite-plugin/standard`. Shared logic (`generateClientEnvModule`, `classifyEnvKeys`, `loadValidatedEnv`, schema discovery) comes from `@arkenv/build` — no duplicated validate/filter/inject logic. Tests mirror the vite-plugin transform/scrub cases plus dual-environment isolation: a real `rsbuild.build()` with `web` + `node` environments asserts the client bundle inlines public values and contains zero server secrets, while the node bundle preserves `env.ts` untouched; a missing required variable fails the build with no emitted assets. Out of scope (per the issue): docs claiming Rsbuild parity, an example app, and the CLI init scaffolder. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 5336d5a commit b13f263

23 files changed

Lines changed: 1544 additions & 202 deletions

.changeset/rsbuild-plugin.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
"@arkenv/rsbuild-plugin": minor
3+
---
4+
5+
#### Add `@arkenv/rsbuild-plugin` for Rsbuild and TanStack Start
6+
7+
The new `@arkenv/rsbuild-plugin` brings the same server/client env path as `@arkenv/vite-plugin` to Rsbuild projects, including TanStack Start apps built with Rsbuild:
8+
9+
- **Client rewrite**: in `web` and `web-worker` environments, the `env.ts` module is replaced with a scrubbed client module — public/shared keys (default prefix `PUBLIC_`) are inlined as coerced literals and server-only keys become throwing getters, so secrets never reach the browser bundle.
10+
- **Server passthrough**: in `node` environments the real `env.ts` runs unchanged against the deployment environment.
11+
- **Build-time validation**: the schema is validated via `@arkenv/build` before each environment compiles; missing or invalid required variables fail the build before assets are emitted.
12+
- **Dev reload**: the schema and `.env*` files are registered as build dependencies, so edits re-validate and refresh inlined values during `rsbuild dev`.
13+
- **Standard Schema**: an `@arkenv/rsbuild-plugin/standard` entry mirrors the ArkType-free path of `@arkenv/vite-plugin/standard`.
14+
15+
Usage:
16+
17+
```ts
18+
// rsbuild.config.ts
19+
import { defineConfig } from "@rsbuild/core";
20+
import { arkenvPlugin } from "@arkenv/rsbuild-plugin";
21+
22+
export default defineConfig({
23+
plugins: [arkenvPlugin({ schemaPath: "src/env.ts" })],
24+
});
25+
```
26+
27+
Install with `npm install @arkenv/rsbuild-plugin arktype`.

packages/rsbuild-plugin/.gitignore

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# dependencies (pnpm install)
2+
node_modules
3+
4+
# output
5+
out
6+
dist
7+
*.tgz
8+
9+
# code coverage
10+
coverage
11+
*.lcov
12+
13+
# logs
14+
logs
15+
*.log
16+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
17+
18+
# dotenv environment variable files
19+
.env
20+
.env.development.local
21+
.env.test.local
22+
.env.production.local
23+
.env.local
24+
25+
# caches
26+
.eslintcache
27+
.cache
28+
*.tsbuildinfo
29+
30+
# IntelliJ based IDEs
31+
.idea
32+
33+
# Finder (MacOS) folder config
34+
.DS_Store

packages/rsbuild-plugin/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# `@arkenv/rsbuild-plugin`
2+
3+
[Rsbuild](https://rsbuild.dev/) plugin to validate environment variables at build-time with ArkEnv.
4+
5+
<br />
6+
7+
<br />
8+
9+
<br />
10+
11+
## [Read the docs →](https://arkenv.js.org/docs)
12+
13+
<br />
14+
15+
```sh
16+
npm install @arkenv/rsbuild-plugin arktype
17+
```
18+
19+
## Related
20+
21+
- [ArkEnv](https://arkenv.js.org) - Core library and docs
22+
- [ArkType](https://arktype.io/) - Underlying validator / type system
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
{
2+
"name": "@arkenv/rsbuild-plugin",
3+
"version": "1.0.0-alpha.0",
4+
"author": "Yam Borodetsky <yam@yam.codes>",
5+
"repository": {
6+
"type": "git",
7+
"url": "git+https://github.com/yamcodes/arkenv.git"
8+
},
9+
"main": "./dist/index.js",
10+
"module": "./dist/index.js",
11+
"dependencies": {
12+
"@arkenv/build": "workspace:*",
13+
"jiti": "catalog:"
14+
},
15+
"devDependencies": {
16+
"@arkenv/core": "workspace:*",
17+
"@arkenv/standard": "workspace:*",
18+
"@repo/log": "workspace:*",
19+
"@repo/scope": "workspace:*",
20+
"@repo/types": "workspace:*",
21+
"@repo/utils": "workspace:*",
22+
"@rsbuild/core": "2.2.3",
23+
"@size-limit/preset-small-lib": "catalog:",
24+
"arktype": "catalog:",
25+
"rimraf": "catalog:",
26+
"size-limit": "catalog:",
27+
"tsdown": "catalog:",
28+
"typescript": "catalog:",
29+
"vitest": "catalog:"
30+
},
31+
"peerDependencies": {
32+
"@arkenv/core": "^1.0.0",
33+
"@arkenv/standard": "^1.0.0",
34+
"@rsbuild/core": "^1.0.0 || ^2.0.0",
35+
"arktype": "^2.1.22"
36+
},
37+
"peerDependenciesMeta": {
38+
"@arkenv/core": {
39+
"optional": true
40+
},
41+
"@arkenv/standard": {
42+
"optional": true
43+
},
44+
"arktype": {
45+
"optional": true
46+
}
47+
},
48+
"exports": {
49+
".": {
50+
"types": "./dist/index.d.ts",
51+
"import": "./dist/index.js",
52+
"default": "./dist/index.js"
53+
},
54+
"./standard": {
55+
"types": "./dist/standard.d.ts",
56+
"import": "./dist/standard.js",
57+
"default": "./dist/standard.js"
58+
}
59+
},
60+
"bugs": "https://github.com/yamcodes/arkenv/labels/%40arkenv%2Frsbuild-plugin",
61+
"description": "ArkEnv plugin for Rsbuild",
62+
"files": [
63+
"dist"
64+
],
65+
"homepage": "https://arkenv.js.org",
66+
"keywords": [
67+
"arkenv",
68+
"arktype",
69+
"env",
70+
"environment-variables",
71+
"dotenv",
72+
"config",
73+
"validation",
74+
"typesafe",
75+
"standard-schema",
76+
"rsbuild",
77+
"plugin",
78+
"rsbuild-plugin"
79+
],
80+
"license": "MIT",
81+
"scripts": {
82+
"build": "tsdown",
83+
"typecheck": "tsc --noEmit",
84+
"clean": "rimraf dist node_modules",
85+
"test": "vitest",
86+
"fix": "pnpm -w run fix",
87+
"changeset": "pnpm -w run changeset",
88+
"size": "size-limit --json > .size-limit.json"
89+
},
90+
"type": "module",
91+
"types": "./dist/index.d.ts",
92+
"size-limit": [
93+
{
94+
"path": "dist/index.js",
95+
"limit": "8 kB",
96+
"import": "*",
97+
"ignore": [
98+
"@rsbuild/core",
99+
"arktype",
100+
"jiti",
101+
"@arkenv/build"
102+
]
103+
}
104+
]
105+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import arkenv from "@arkenv/core";
2+
3+
export const env = arkenv({
4+
SECRET_TOKEN: "string",
5+
});
6+
7+
export default env;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { env } from "./env";
2+
3+
export function readSecretToken() {
4+
return env.SECRET_TOKEN;
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
PUBLIC_API_URL=https://fixture.example.com
2+
PUBLIC_DEBUG=true
3+
PUBLIC_PORT=8080
4+
DATABASE_URL=postgres://fixture:5432/db
5+
NODE_ENV=test
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import arkenv from "@arkenv/core";
2+
3+
export const env = arkenv({
4+
DATABASE_URL: "string = 'postgres://localhost:5432/mydb'",
5+
PUBLIC_API_URL: "string = 'https://api.example.com'",
6+
PUBLIC_DEBUG: "boolean = false",
7+
PUBLIC_PORT: "number = 3000",
8+
NODE_ENV: "'development' | 'production' | 'test' = 'test'",
9+
});
10+
11+
export default env;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { env } from "./env";
2+
3+
export const config = {
4+
apiUrl: env.PUBLIC_API_URL,
5+
debug: env.PUBLIC_DEBUG,
6+
port: env.PUBLIC_PORT,
7+
nodeEnv: env.NODE_ENV,
8+
};
9+
10+
export function readServerSecret() {
11+
return env.DATABASE_URL;
12+
}

0 commit comments

Comments
 (0)