Skip to content

Commit 963316e

Browse files
branchseerclaude
andauthored
feat(config): static vite config extraction for run config (#679)
Add new `vite_static_config` crate that uses `oxc_parser` to statically extract JSON-serializable fields from `vite.config.*` files without executing JavaScript ## Performance Measured using Chrome trace instrumentation (PR #663). On a cache hit, `vp run` loads the config, validates the fingerprint, and streams cached output — no build work. Before this PR, config loading required a NAPI round-trip to evaluate the config file. With static extraction that cost drops to ~0ms. ### `vp run` end-to-end time (cache hit) | Project | Before | After | Reduction | |---------|--------|-------|-----------| | vue-mini | ~217ms | **23ms** | ~89% | | oxlint-plugin-complexity | ~220ms | **17ms** | ~92% | | vitepress | ~282ms | **32ms** | ~89% | | vite-vue-vercel | ~326ms | **28ms** | ~91% | | rollipop | ~670ms | **16–53ms** | ~92–98% | | frm-stack (10 packages) | ~895ms | **34–69ms** | ~92–96% | | tanstack-start-helloworld | ~1,383ms | ~1,394ms | — (indirect export, not yet handled) | ## Summary - `VitePlusConfigLoader` now tries static extraction first for the `run` config, falling back to NAPI-based resolution only when the config cannot be statically extracted - Static extraction supports all common patterns: - `export default { ... }` — bare object literal - `export default defineConfig({ ... })` — direct call - `export default defineConfig(() => ({ ... }))` — concise arrow body - `export default defineConfig(fn)` — block body with single return - `module.exports = ...` — CJS equivalents - When no `vite.config.*` file exists, returns an empty map immediately (skips NAPI entirely) - Spread and computed-key properties correctly invalidate previously-seen fields ## Test plan - [x] 50 unit tests in `vite_static_config` pass - [x] All existing `vite-plus-cli` tests pass - [x] `cargo fmt --check` passes - [x] `cargo shear` reports no unused dependencies - [x] `cargo clippy` clean (no new warnings) - [ ] CI passes on all platforms 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 72ff497 commit 963316e

File tree

7 files changed

+1181
-0
lines changed

7 files changed

+1181
-0
lines changed

Cargo.lock

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ vite_glob = { git = "ssh://git@github.com/voidzero-dev/vite-task.git", rev = "6f
187187
vite_install = { path = "crates/vite_install" }
188188
vite_migration = { path = "crates/vite_migration" }
189189
vite_shared = { path = "crates/vite_shared" }
190+
vite_static_config = { path = "crates/vite_static_config" }
190191
vite_path = { git = "ssh://git@github.com/voidzero-dev/vite-task.git", rev = "6fdc4f106563491be4fb36381b84c5937d74fe9c" }
191192
vite_str = { git = "ssh://git@github.com/voidzero-dev/vite-task.git", rev = "6fdc4f106563491be4fb36381b84c5937d74fe9c" }
192193
vite_task = { git = "ssh://git@github.com/voidzero-dev/vite-task.git", rev = "6fdc4f106563491be4fb36381b84c5937d74fe9c" }
@@ -211,7 +212,10 @@ oxc = { version = "0.115.0", features = [
211212
"cfg",
212213
] }
213214
oxc_allocator = { version = "0.115.0", features = ["pool"] }
215+
oxc_ast = "0.115.0"
214216
oxc_ecmascript = "0.115.0"
217+
oxc_parser = "0.115.0"
218+
oxc_span = "0.115.0"
215219
oxc_napi = "0.115.0"
216220
oxc_minify_napi = "0.115.0"
217221
oxc_parser_napi = "0.115.0"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "vite_static_config"
3+
version = "0.0.0"
4+
authors.workspace = true
5+
edition.workspace = true
6+
homepage.workspace = true
7+
license.workspace = true
8+
repository.workspace = true
9+
10+
[dependencies]
11+
oxc_allocator = { workspace = true }
12+
oxc_ast = { workspace = true }
13+
oxc_parser = { workspace = true }
14+
oxc_span = { workspace = true }
15+
rustc-hash = { workspace = true }
16+
serde_json = { workspace = true }
17+
vite_path = { workspace = true }
18+
19+
[dev-dependencies]
20+
tempfile = { workspace = true }
21+
22+
[lints]
23+
workspace = true
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# vite_static_config
2+
3+
Statically extracts configuration from `vite.config.*` files without executing JavaScript.
4+
5+
## What it does
6+
7+
Parses vite config files using [oxc_parser](https://crates.io/crates/oxc_parser) and extracts
8+
top-level fields whose values are pure JSON literals. This allows reading config like `run`
9+
without needing a Node.js runtime (NAPI).
10+
11+
## Supported patterns
12+
13+
**ESM:**
14+
15+
```js
16+
export default { run: { tasks: { build: { command: "echo build" } } } }
17+
export default defineConfig({ run: { cacheScripts: true } })
18+
```
19+
20+
**CJS:**
21+
22+
```js
23+
module.exports = { run: { tasks: { build: { command: 'echo build' } } } };
24+
module.exports = defineConfig({ run: { cacheScripts: true } });
25+
```
26+
27+
## Config file resolution
28+
29+
Searches for config files in the same order as Vite's
30+
[`DEFAULT_CONFIG_FILES`](https://github.com/vitejs/vite/blob/25227bbdc7de0ed07cf7bdc9a1a733e3a9a132bc/packages/vite/src/node/constants.ts#L98-L105):
31+
32+
1. `vite.config.js`
33+
2. `vite.config.mjs`
34+
3. `vite.config.ts`
35+
4. `vite.config.cjs`
36+
5. `vite.config.mts`
37+
6. `vite.config.cts`
38+
39+
## Return type
40+
41+
`resolve_static_config` returns `Option<FxHashMap<Box<str>, FieldValue>>`:
42+
43+
- **`None`** — config is not statically analyzable (no config file, parse error, no
44+
`export default`/`module.exports`, or the exported value is not an object literal).
45+
Caller should fall back to runtime evaluation (e.g. NAPI).
46+
- **`Some(map)`** — config object was successfully located:
47+
- `FieldValue::Json(value)` — field value extracted as pure JSON
48+
- `FieldValue::NonStatic` — field exists but contains non-JSON expressions
49+
(function calls, variables, template literals with interpolation, etc.)
50+
- Key absent — field does not exist in the config object
51+
52+
## Limitations
53+
54+
- Only extracts values that are pure JSON literals (strings, numbers, booleans, null,
55+
arrays, and objects composed of these)
56+
- Fields with dynamic values (function calls, variable references, spread operators,
57+
computed properties, template literals with expressions) are reported as `NonStatic`
58+
- Does not follow imports or evaluate expressions

0 commit comments

Comments
 (0)