feat: add Chrome tracing for performance measurement#663
Draft
branchseer wants to merge 38 commits intomainfrom
Draft
feat: add Chrome tracing for performance measurement#663branchseer wants to merge 38 commits intomainfrom
branchseer wants to merge 38 commits intomainfrom
Conversation
…n` config Add a new `vite_static_config` crate that uses oxc_parser to statically extract JSON-serializable fields from vite.config.* files without needing a Node.js runtime. The `VitePlusConfigLoader` now tries static extraction first for the `run` config and falls back to NAPI only when needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The config file resolution order was .ts first, but Vite resolves .js, .mjs, .ts, .cjs, .mts, .cts — matching that order now. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…olver Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
resolve_static_config now returns Option<FxHashMap<..., StaticFieldValue>>: - None: config is not analyzable (no file, parse error, no export default, or exported value is not an object literal) — caller should fall back to NAPI - Some(map): config was successfully analyzed - Json(value): field extracted as pure JSON - NonStatic: field exists but value is not a JSON literal - key absent: field does not exist in the config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tching - PropertyKey::static_name() replaces property_key_to_string and property_key_to_json_key - TemplateLiteral::single_quasi() replaces manual quasis/expressions check - Expression::is_specific_id() replaces is_define_config_call helper - ArrayExpressionElement::is_elision()/is_spread() replaces variant matching - ObjectPropertyKind::is_spread() replaces variant matching Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add support for CommonJS config files:
- module.exports = { ... }
- module.exports = defineConfig({ ... })
Refactored shared config extraction into extract_config_from_expr,
used by both export default and module.exports paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace umbrella `oxc` dependency with `oxc_ast`, `oxc_parser`, and `oxc_span` for more precise dependency tracking. Add README documenting supported patterns, config resolution order, return type semantics, and limitations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Value Rewrite f64_to_json_number to follow JSON.stringify semantics using serde_json's From<f64> for the NaN/Infinity→null fallback, and i64::try_from for safe integer conversion. Rename StaticFieldValue to FieldValue for brevity. Add tests for overflow-to-infinity and -0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2036b4f to
087e05d
Compare
031f50a to
7251d04
Compare
❌ Deploy Preview for viteplus-staging failed.
|
…onfig file Two improvements to static config extraction: 1. When no vite.config.* file exists in a workspace package, resolve_static_config now returns an empty map (instead of None). The caller sees no `run` field and returns immediately, skipping the NAPI/JS callback. This eliminates ~165ms cold Node.js init + ~3ms/pkg warm overhead for monorepo packages without config files. 2. Support defineConfig(fn) where fn is an arrow function or function expression. The extractor locates the return expression inside the function body and extracts fields from it. Functions with multiple return statements are rejected as not statically analyzable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
474fef7 to
ecbafe2
Compare
7251d04 to
4be1fe4
Compare
…r lookup
Handles the common pattern where the config object is assigned to a variable
before being exported or returned:
const config = defineConfig({ ... });
export default config; // ESM indirect export
module.exports = config; // CJS indirect export
return config; // inside defineConfig(fn) callback
Resolution scans top-level VariableDeclarator nodes by name (simple binding
identifiers only; destructured patterns are skipped). One level of indirection
is supported — chained references (const a = b; export default a) are not
resolved and fall back to NAPI as before.
Fixes tanstack-start-helloworld's ~1.3s config load time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4be1fe4 to
c7e8d4c
Compare
…dentifier lookup" This reverts commit ecc3fc2.
c7e8d4c to
b6af764
Compare
`{ a: 1, ...x, b: 2 }` — the spread may override `a`, so `a` is now
marked NonStatic. Fields declared after the spread (`b`) are unaffected
since they win over any spread key. Unknown keys from the spread are
still not added to the map.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
b6af764 to
6f2ca96
Compare
`{ a: 1, [key]: 2, b: 3 }` — `[key]` could resolve to `'a'` and
override it, so `a` is now marked NonStatic. Same logic as spreads.
Fields after the computed key are unaffected (they explicitly win).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Chrome DevTools timeline tracing output via `VITE_LOG_OUTPUT=chrome-json` - Update `init_tracing()` to support three output modes: chrome-json, readable, stdout - Add `VITE_LOG_OUTPUT` env var constant - Add `tracing` and `tracing-chrome` dependencies to vite_shared - Keep tracing guard alive in main() for Chrome trace file flushing - Update vite-task dependency to include tracing instrumentation - Enable tracing in e2e-test workflow and upload trace artifacts - Run e2e commands twice to measure caching performance Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The tracing crate is not directly used in vite_shared - only tracing-chrome and tracing-subscriber are needed. Fixes cargo-shear. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instrument vite-plus callback implementations (handle_command, load_user_config_file, resolve), NAPI bridge (run, JS resolvers, vite config resolver), and JS startup timing for performance measurement. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use tracing::debug! events instead of spans in Send-required contexts (NAPI run function and vite config resolver) - Fix cargo fmt formatting in tracing.rs - Add process.uptime() to JS startup timing for absolute timeline Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update lock file to match vite-task rev 9ed02855, which adds tracing dependencies and updates wax to 0.7.0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The NAPI init() function was dropping the chrome-json tracing guard immediately, resulting in empty trace files from the Node.js process. Store it in a static OnceLock so it lives until process exit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OnceLock requires its inner type to be Sync for static storage, but Box<dyn Any + Send> is not Sync. Wrap in Mutex to satisfy the bound. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rust statics stored in OnceLock are never dropped on process exit, so the ChromeLayer FlushGuard never flushes trace data to disk. Add an explicit shutdown_tracing() NAPI function that drops the guard, and call it from bin.ts before process.exit(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Includes detailed timing analysis from Chrome tracing (tracing-chrome) across global CLI, NAPI startup, vite-task session, and task execution phases. Key finding: vite.config.ts loading via JS callbacks accounts for 99% of the overhead before task execution (~930ms for 10-package monorepo). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The new shutdownTracing() NAPI function needs its export registered in the auto-generated binding files for the snapshot test to pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Align markdown table columns per oxfmt formatting rules. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
NAPI-RS generates type declarations alphabetically, placing shutdownTracing (s) after runCommand (r) and its interfaces. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tters
Trace files (trace-*.json) written to the project cwd were picked up by
formatters during E2E tests, causing failures due to truncated JSON.
Add VITE_LOG_OUTPUT_DIR env var to write trace files to a dedicated
directory. Set it in the E2E workflow to ${{ runner.temp }}/trace-artifacts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Expands from 2-project analysis to full 9-project ecosystem-ci coverage (73 trace files). Adds cross-project comparison table, per-command breakdown for frm-stack, config loading pattern analysis, and first-run vs cache-run statistics. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Enable `cacheScripts: true` in patch-project.ts after migration so `vp run` commands exercise the task cache hit/miss code paths. - Add a third "cache miss" run to the e2e workflow that modifies package.json between runs to trigger PostRunFingerprintMismatch. The e2e flow is now: first run (cache miss NotFound) → second run (cache hit) → invalidate → third run (cache miss FingerprintMismatch). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migration preserves the existing config extension. Some projects use vite.config.js, so we need to check both filenames. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Some projects (vue-mini, vitepress, frm-stack, dify) have no lint/fmt/pack config to merge, so migration doesn't create a vite config. Create a minimal one with cacheScripts enabled. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Projects like vue-mini run `vp run format` which checks Prettier formatting. The injected cacheScripts config must be formatted. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Projects like vue-mini have strict TypeScript-ESLint configs that reject .ts files not included in tsconfig.json. Using .js avoids this issue. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge architecture overview/phase breakdowns with cache-enabled trace analysis (run #22558467033). Remove turbo references and optimization suggestions. Add cache hit savings, operation overhead, execution timeline, and miss root cause data. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6f2ca96 to
0f9cfd8
Compare
6 tasks
45787e4 to
9cce0f4
Compare
branchseer
added a commit
that referenced
this pull request
Mar 12, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
VITE_LOG=debug VITE_LOG_OUTPUT=chrome-jsonChanges
vite_shared tracing
init_tracing()to support three output modes:chrome-json,readable,stdoutVITE_LOG_OUTPUTenvironment variabletracingandtracing-chromedependenciesinit_tracing()to ensure Chrome trace files are flushede2e-test.yml
VITE_LOG=debugandVITE_LOG_OUTPUT=chrome-jsonin e2e test stepsvite-task dependency
#[tracing::instrument]to 20+ key functionsTest plan
🤖 Generated with Claude Code