[Refactor/Chore] Improve vp check performance
- Dominant language
- TypeScript
- Stars
- 156k
- Forks
- 24.6k
- Avg merge
- 22h 9m
- Merged PRs (30d)
- 610
Description
### Self Checks
- [X] I have read the [Contributing Guide]() and [Language Policy]().
- [X] This is only for refactors or chores; if you would like to ask a question, please head to [Discussions]().
- [X] I have searched for existing issues [search for existing issues](), including closed ones.
- [X] I confirm that I am using English to submit this report, otherwise it will be closed.
- [X] 【中文用户 & Non English User】请使用英语提交,否则会被关闭 :)
- [X] Please do not modify this template :) and fill in all the required fields.
### Description
Improve the end-to-end performance of the repository's Vite+ static check while keeping each lint-coverage trade-off independently reviewable.
This is a follow-up to the Vite+ migration in langgenius/dify#38833. Profiling the migrated configuration shows that `vp check` has two material cost centers: Oxlint's JavaScript compatibility plugins and the type-aware TypeScript program used by tsgolint/type checking. Native Oxlint rules are comparatively inexpensive.
#### Current profile
| Scope | Wall time | Maximum RSS | Interpretation |
| -- | -- | -- | -- |
| Full configured lint | about 28.5 s | about 6.0-6.6 GB | JavaScript plugins, native rules, and type-aware work |
| JavaScript plugins with type-aware lint disabled | 20.93 s | 2.41 GB | Dominant wall-time layer |
| Type-aware tsgolint phase | 6.6-7.0 s | 1.42 GB live heap | Most of the remaining runtime |
| Native rules with type-aware lint disabled | 0.16 s | 97 MB | Not the bottleneck |
These phase measurements use different entry points and may overlap in setup work, so they are directional and must not be added as exact accounting.
Oxlint 1.72 does not attribute JavaScript plugin time to individual rules. Temporary configurations were therefore used to keep the same files and rule baseline while removing one external plugin at a time:
| Removed plugin | JavaScript-plugin-only wall-time delta |
| -- | -- |
| `eslint-react` | -5.93 s |
| `eslint-plugin-better-tailwindcss` | -5.45 s |
| `eslint-plugin-jsdoc` | -3.00 s |
| `eslint-plugin-regexp` | -2.77 s |
| `@tanstack/eslint-plugin-query` | -0.54 s |
| `eslint-plugin-perfectionist` | -0.37 s |
Other measured plugins were within noise. The four high-delta plugins reproduced 19.45 seconds, or about 93% of the JavaScript-plugin-only wall time, when run together. Individual deltas are ranking signals rather than additive accounting because each run repeats AST conversion, plugin initialization, and garbage collection.
The first implementation candidate, direct removal of `eslint-plugin-better-tailwindcss`, was also measured against the complete repository configuration in the same benchmark window:
| Configuration | Runs | Median wall time | Median user CPU | Registered rules | Visible warnings |
| -- | -- | -- | -- | -- | -- |
| Original configuration | 27.54 s, 28.30 s, 28.84 s | 28.30 s | 61.34 s | 401 | 2,599 |
| Tailwind lint plugin removed | 22.75 s, 22.69 s, 22.95 s | 22.75 s | 54.65 s | 399 | 2,439 |
This reduced median wall time by 5.55 seconds (19.6%) and median user CPU by 6.69 CPU-seconds (10.9%). Memory was inconclusive because the run ranges overlapped and the median did not improve.
#### Type-aware profile
The type-aware backend assigned 6,909 selected files to 8 TypeScript programs. Approximately 6.07 of 6.99 seconds (87%) were spent on `web/tsconfig.json`, whose program contained 11,924 source files: 6,597 under `web`, 5,160 under pnpm dependencies, and 167 under workspace packages.
A tsgolint CPU profile recorded 32.33 CPU-seconds over 6.61 wall-seconds. Runtime memory clearing, scanning, syscalls, and garbage collection dominated the flat profile. The completion heap retained 1.42 GB after 7.76 GB of total allocations. This points to TypeScript program construction/checking and dependency-surface memory churn rather than one slow native lint rule.
#### Proposed sequence
- [ ] Remove `eslint-plugin-better-tailwindcss`, its two configured rules, dependency records, and obsolete inline suppressions.
- [ ] Evaluate native Oxlint and React Compiler replacements before reducing the external React rule set.
- [ ] Evaluate the coverage trade-offs of removing or replacing the JSDoc and RegExp JavaScript plugins.
- [ ] Audit `web/tsconfig.json` and its dependency surface to reduce type-aware program construction and memory churn.
- [ ] Evaluate an Oxlint upgrade that exposes type-aware per-rule timings, then repeat the profile.
- [ ] Re-run same-window end-to-end benchmarks after each independently reviewable change and record lint coverage changes.
Each plugin removal must explicitly document which checks are lost or replaced. The Tailwind step removes `tailwindcss/no-duplicate-classes` and `tailwindcss/no-unknown-classes` but does not change the application's Tailwind CSS build dependencies or runtime behavior.
### Motivation
Static checks sit directly in the local feedback loop and CI path. The current lint phase takes roughly 28 seconds and can approach 6-7 GB maximum RSS on the measured repository state. The profile shows several concrete opportunities for large wall-time improvements, but they have different correctness and coverage trade-offs.
Tracking the work under one performance issue preserves the overall objective and benchmark context, while separate pull requests allow reviewers to accept, reject, or replace each lint rule group independently.
### Additional Context
Measurements were collected on 2026-07-13 on an Apple M4 Pro Mac with 12 logical CPUs and 24 GiB RAM, using Node.js 22.22.3, pnpm 11.10.0, Vite+ 0.2.4, Oxlint 1.72.0, and oxlint-tsgolint 0.24.0. The full lint selected approximately 6,900 files. These are repeated engineering measurements rather than a statistically controlled benchmark; sub-second deltas should be treated as noise.
The direct end-to-end lint benchmark used:
```sh
/usr/bin/time -lp pnpm lint:oxlint --format default --silent
```
Native timing inspection used Oxlint's `--debug timings`; deeper tsgolint CPU and heap profiles used the official `OXLINT_TSGOLINT_CPU` and `OXLINT_TSGOLINT_HEAP` hooks. Oxlint 1.72's timing table covers native rules but not JavaScript plugin rules, which is why controlled plugin bisection was necessary.
Relevant upstream references:
* [Oxlint usage guide]()
* [Oxlint rule catalog]()
* [React Compiler rule implementation]()
* [Bulk suppression support]()
Contributor guide
Assessment
This issue has not been assessed yet.