feat(vue): support experimental.componentDetection - #396
Merged
Conversation
Sync with nuxt/ui 3a568b5 (#6731). Component detection narrows the generated theme CSS to the components an app actually uses; it only ever worked under Nuxt. The mechanism has to differ between the integrations. Nuxt narrows the `@source` list to the detected components' template files. The Vue plugin can't: its templates live inside node_modules, where Tailwind widens a file `@source` to a scan of the whole parent directory. It sources the directory and blanks the theme of undetected components at write time instead — the files still have to exist for the `#build` aliases and the type imports. Scanner fixes that came with it: kebab-case tags, which are mandatory for in-DOM templates and were never matched; prose components overwriting the dependency set of the eight regular components they share a basename with; graph edges and detections to names that are not components; `components.d.ts`, which declares every component ever rendered; `.mts`/`.mjs`/`.cjs`; and scanning `scanPackages` packages and `components.dirs` outside the root. Two defects this surfaced here: `playgrounds/vue/tsconfig.app.json` mapped `#build/b24ui` with a wildcard on the value but none on the key, so `#build/b24ui/button` never resolved through it; and both installation guides named a `Dropdown` component that does not exist (it is `DropdownMenu`) — now a build-time warning rather than nothing. The scanned-extension list is shared between the glob and the dev watcher rather than spelled twice. An extension in one and not the other means a file that changes the answer never triggers a refresh, with no error and no signal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JS8ypVfQSFzYVZzkTHhURb
IgorShevchik
force-pushed
the
sync/nuxt-3a568b5
branch
from
August 13, 2026 15:18
5764246 to
ae4ee7b
Compare
4 tasks
IgorShevchik
added a commit
that referenced
this pull request
Aug 22, 2026
Ports three upstream commits, contiguous in `v4` (§6 4b): `d6c3802a` (nuxt/ui #6848) · `f62aa143` (no-op) · `aa5f4af0`. **`d6c3802a`, against `Range`.** Upstream's `Slider` is this fork's `Range`; the reka primitives keep their own names, so only the wrapper differs. This is the second commit in that family — `f3c2ac21` (#431) already moved `useFormField`'s derived attributes onto the thumb. What still did not get there was **the caller's own** `aria-*`: `v-bind="rootProps"` put them on `SliderRoot`, which is not the element with `role="slider"`. A screen reader announcing the thumb read Reka UI's placeholder name `Thumb` while the author's `aria-label` sat on a wrapper that announces nothing. The fix stops inheriting attributes and routes six of them by hand. With one thumb they go to the thumb and everything else stays on the root; the `'Thumb'` fallback applies only when the caller gave neither a label nor a `labelledby`. With several thumbs they stay on the root, because Reka UI already names each thumb by position (`Minimum`/`Maximum`, `Value n of m`) and one repeated label would make them indistinguishable — the root takes `role="group"` instead, unless the caller supplied a `role`, which wins. Our `Range.vue` matched upstream's pre-image on both thumb lines and on the `SliderRoot` binding, so the diff applies unchanged apart from `ui`→`b24ui` and `UTooltip`→`B24Tooltip`. `pick`/`omit` already exist in `../utils`. **`f62aa143`** is a no-op: upstream's release bookkeeping. This fork has its own release train and its `CHANGELOG.md` is generated from our commits. **`aa5f4af0`** turns upstream's "coming" badges into what shipped. The shape transfers; every value is fork-specific, and each was checked against this repo's tags with `git tag --contains` rather than assumed to match upstream's timing: - `experimental.componentDetection`: `Soon` → **`2.12+`**, not their `4.11+` — `530b9616` (#396) is in no tag before `v2.12.0` - `input-rating.md`: `New` dropped — `d08b5830` (#283) shipped in `v2.10.0`, two releases back, so the badge was stale here too - `splitter.md`, `progress-group.md`: badge **added**, not edited — both first appear in `v2.12.0` and our pages never carried one - the `::note` badges upstream removes were never added here: `07f3fe8d` ported those notes without them, precisely because this commit deletes them Left alone: `calendar.md`, `drawer.md`, `modal.md`, `slideover.md` and the two `With external scroll element` badges — this fork's own markers on sections upstream's commit does not mention. Verified by mutation, not by a passing suite. Upstream's 14 `aria` cases and 2 render cases are ported; with `Range.vue` reverted and the tests kept, **11 of the 14 fail** plus all 3 affected render cases. The 3 that pass either way pin behaviour that was already right — the `'Thumb'` fallback, not grouping an unlabelled range, leaving non-`aria` attributes on the root — and are kept as regression pins rather than counted as evidence. Badges checked in the built navigation data: `splitter` and `progress-group` carry `New`, `input-rating` no longer does, `scroll-area` is untouched, and `2.12+` renders on the Vue installation page. Gate with `CI=true`: `dev:prepare` · `lint` · `typecheck` · `test` (7016 passed, 6 skipped, 308 files) · `build` · `docs:generate`. Ledger: cursor → `aa5f4af0` (upstream HEAD), three entries, parity snapshot refreshed (one line, zero package differences). Co-authored-by: Shevchik Igor <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.
Sync with
nuxt/ui— ports3a568b5(#6731).experimental.componentDetectionnarrows the generated theme CSS to the components an app actually uses, plus their dependency closure. It only ever worked under Nuxt; this makes it work in the Vue (unplugin) integration, and fixes several things in the scanner along the way.The mechanism differs between the two integrations
Worth stating, because it looks like an inconsistency and isn't. Nuxt narrows the
@sourcelist to the detected components' template files. The Vue plugin cannot: its templates live insidenode_modules, where Tailwind widens a file@sourceto a scan of the whole parent directory — a narrowed list wouldn't narrow the CSS. So it sources the whole directory and instead blanks the theme of undetected components at write time, the same transformationtheme.unstyledperforms. The files still exist, because the#build/*aliases and the type imports need them to.Scanner fixes that came with it
<b24-button>is mandatory for in-DOM templates and was never matched. The kebab alternative only matches as a tag; bare kebab identifiers in scripts and prose would match far too much ordinary text.prose/twin (Accordion,Badge,Card,Collapsible,FieldGroup,Kbd,Table,Tabs). Letting them into the graph overwrote the regular component's dependency set.includeComponentsnames too, and unknown ones warn.components.d.tsdeclares every component ever rendered, so scanning it kept anything used once detected forever..mts/.mjs/.cjsin the scan glob and the dev watcher.resolveExtraScanDirs—scanPackagespackages live innode_modules(which the root scan skips) andcomponents.dirscan point outside the root. Build-output ignores are skipped for package dirs, since published packages ship their code indist/.Fork adaptations
All plumbing, no logic:
getTemplatestakesvueas its fourth argument,TemplatePlugintakescomponentDiras its second — this fork dropped upstream's unuseduiConfig/appConfigthreading in earlier ports.Bitrix24UIOptionsstops omittingexperimental, which is what made the option unreachable from the Vue plugin in the first place.consolarather than@nuxt/kit'sloggerinutils/components.tsand the Vue plugin — both run outside Nuxt, and that file must not drag@nuxt/kitinto a Vue build.templates.tskeepslogger; it is Nuxt-only there.'B24'at both call sites (this fork has no configurable component prefix).kebabCase('B24')is'b24', so<b24-button>and<lazy-b24-tooltip>match — verified, not assumed.PageCTAacronym case is n/a: no component here has an acronym in its name.Two defects this surfaced
playgrounds/vue/tsconfig.app.jsonhad a broken alias. It mapped"#build/b24ui"to"./node_modules/.b24ui-nuxt/b24ui/*"— a wildcard on the value with none on the key, plus a trailing comma. TypeScript treats a pattern without*as an exact match, so#build/b24ui/buttonnever resolved through it. Upstream's commit adds the same alias correctly; taken as the fix.The docs named a component that does not exist. Both installation guides showed
componentDetection: ['Modal', 'Dropdown', 'Popover']. There is noDropdownin either project — it isDropdownMenu. That used to be harmless; it isn't any more, now that unknown names warn.One deliberate divergence
Upstream keeps the scanned-extension list as a literal in two places — the scan glob and the
builder:watchfilter — and had to edit both in this very commit when.mts/.mjs/.cjswere added. An extension in one and not the other means a file that changes the answer never triggers a refresh: no error, no signal. Exported asCOMPONENT_DETECTION_EXTENSIONSand used by both. This fork had already splitCOMPONENT_DETECTION_WATCH_REout for the same reason.Tests
test/utils/components.spec.ts(new) — upstream's spec, adapted. Real temporary directories rather than a mockedfs, because half of what is checked is glob and module-resolution behaviour, which a mock would define away.Two tests are this fork's rather than upstream's:
does not build graph edges to names that are not components— upstream's spec does not cover that filter. Verified by deleting it and watching all 38 tests still pass. It needs a stand-in component directory: the real tree has no component that mentions aB24-prefixed non-component, so no fixture undersrc/can show the difference.componentDetection examples in the docs name components that exist— theDropdowndefect, now guarded.Upstream's junk-identifier fixture (
URL→RL,UUID→UID) doesn't apply with aB24prefix and is replaced withB24Config/B24Endpoint. The first attempt usedB24Theme— which turns out to be a real component here, so the test failed and said so.test/utils/templates.spec.ts— the extension test now iterates the shared constant instead of a five-item literal, and a second test holds the glob to the same constant so the two can't drift back apart with every test green.Mutations tried, each red: drop the
prose/**ignore; drop the graph-edge filter; drop thevalidComponentsfilter; stop ignoring*.d.ts; drop the kebab alternative; make the graph follow the scan prefix; apply build-output ignores to package dirs; point the watcher at a literal; point the glob at a literal; putDropdownback in the docs.End-to-end check
Built
playgrounds/vuewithcomponentDetection: true— a real exercise ofresolveExtraScanDirs, since that playground'scomponents.dirspoints at../nuxt/app/components, outside the Vite root:node_modules/.b24ui-nuxt/b24ui/color-picker.tscomes out with every slot blanked;button.tsdoes not.The CSS saving there is modest, and worth stating plainly rather than rounding up: 599,544 → 596,929 bytes, about 0.4%. That app uses 43 components, and most of the bundle is shared utilities and design tokens rather than per-component classes. The feature does what it says; the headline number depends entirely on how much of the library an app leaves unused.
Verify (
CI=true)lint·typecheck·test·build— all green. Tests 6410 passed | 6 skipped across 280 files.Ledger: cursor →
3a568b5, 241 entries; the9b08a84andedf73d3entries are reconciled to PR #395 /c4d291c.Generated by Claude Code