From 2151137d6f6f078b5a651e102bd80543985314d6 Mon Sep 17 00:00:00 2001 From: Shevchik Igor Date: Mon, 29 Jun 2026 13:57:54 +0000 Subject: [PATCH] perf(types): import cross-component types from source, not the barrel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port of upstream nuxt/ui 7eab4bc (#6646). Replace `import type { … } from '../types'` barrel imports with per-source imports (the owning component `.vue`, e.g. `ButtonProps` from `./Button.vue`, or a specific `../types/*` module, e.g. `Messages` from `../types/locale`) across 119 consumer files under `src/runtime/`. The barrel re-exports every component, so a single barrel import eagerly loads the whole library into a consumer's type graph. Adds an eslint `no-restricted-imports` guard on `../types` / `../../types` for `components/**/*.vue` and `composables/**/*.ts`, with `useComponentProps.ts` exempted (it legitimately needs the whole barrel via `import type * as ComponentTypes`). Type→source mapping resolved against b24ui's own exports (e.g. `IconComponent` → `../types/icons`, since b24ui has no `Icon.vue`). Types-only change; no runtime or snapshot changes. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01JS8ypVfQSFzYVZzkTHhURb --- ...eab4bcb78f17b9eed365d617aa3ef3874c21bac.md | 59 +++++++++++++++++++ .sync/nuxt-ui.json | 10 +++- eslint.config.mjs | 15 +++++ src/runtime/components/Accordion.vue | 2 +- src/runtime/components/Advice.vue | 2 +- src/runtime/components/Alert.vue | 5 +- src/runtime/components/App.vue | 3 +- src/runtime/components/Avatar.vue | 3 +- src/runtime/components/Badge.vue | 2 +- src/runtime/components/Banner.vue | 4 +- src/runtime/components/Breadcrumb.vue | 4 +- src/runtime/components/Button.vue | 3 +- src/runtime/components/Calendar.vue | 4 +- src/runtime/components/ChatMessage.vue | 4 +- src/runtime/components/ChatMessages.vue | 5 +- src/runtime/components/ChatPrompt.vue | 2 +- src/runtime/components/ChatPromptSubmit.vue | 4 +- src/runtime/components/ChatReasoning.vue | 2 +- src/runtime/components/ChatTool.vue | 2 +- src/runtime/components/CheckboxGroup.vue | 2 +- src/runtime/components/Chip.vue | 2 +- src/runtime/components/CommandPalette.vue | 8 ++- src/runtime/components/ContextMenu.vue | 5 +- src/runtime/components/ContextMenuContent.vue | 5 +- src/runtime/components/Countdown.vue | 2 +- src/runtime/components/DashboardNavbar.vue | 4 +- src/runtime/components/DashboardSearch.vue | 6 +- .../components/DashboardSearchButton.vue | 6 +- src/runtime/components/DashboardSidebar.vue | 6 +- .../components/DashboardSidebarCollapse.vue | 3 +- .../components/DashboardSidebarToggle.vue | 3 +- src/runtime/components/DescriptionList.vue | 4 +- src/runtime/components/DropdownMenu.vue | 6 +- .../components/DropdownMenuContent.vue | 6 +- src/runtime/components/EditorDragHandle.vue | 4 +- src/runtime/components/EditorMentionMenu.vue | 3 +- .../components/EditorSuggestionMenu.vue | 2 +- src/runtime/components/EditorToolbar.vue | 5 +- src/runtime/components/Empty.vue | 3 +- src/runtime/components/Error.vue | 4 +- src/runtime/components/FileUpload.vue | 4 +- src/runtime/components/FooterColumns.vue | 3 +- src/runtime/components/Header.vue | 6 +- src/runtime/components/Input.vue | 3 +- src/runtime/components/InputDate.vue | 4 +- src/runtime/components/InputMenu.vue | 8 ++- src/runtime/components/InputNumber.vue | 5 +- src/runtime/components/InputTags.vue | 4 +- src/runtime/components/InputTime.vue | 4 +- src/runtime/components/LinkBase.vue | 2 +- src/runtime/components/Modal.vue | 4 +- src/runtime/components/NavigationMenu.vue | 8 ++- src/runtime/components/PageCard.vue | 4 +- src/runtime/components/PageCardGroup.vue | 5 +- src/runtime/components/PageFeature.vue | 3 +- src/runtime/components/PageHeader.vue | 2 +- src/runtime/components/PageLinks.vue | 3 +- src/runtime/components/PageSection.vue | 4 +- src/runtime/components/Pagination.vue | 3 +- src/runtime/components/Range.vue | 2 +- src/runtime/components/Select.vue | 6 +- src/runtime/components/SelectMenu.vue | 8 ++- src/runtime/components/Separator.vue | 3 +- src/runtime/components/Sidebar.vue | 7 ++- src/runtime/components/Slideover.vue | 4 +- src/runtime/components/Stepper.vue | 2 +- src/runtime/components/Switch.vue | 2 +- src/runtime/components/Tabs.vue | 4 +- src/runtime/components/Textarea.vue | 3 +- src/runtime/components/Timeline.vue | 3 +- src/runtime/components/Toast.vue | 6 +- src/runtime/components/Tooltip.vue | 2 +- src/runtime/components/User.vue | 4 +- .../components/color-mode/ColorModeAvatar.vue | 2 +- .../components/color-mode/ColorModeButton.vue | 3 +- .../components/color-mode/ColorModeSelect.vue | 2 +- .../components/color-mode/ColorModeSwitch.vue | 2 +- .../components/content/ContentSearch.vue | 7 ++- .../content/ContentSearchButton.vue | 6 +- .../components/content/ContentSurround.vue | 2 +- src/runtime/components/content/ContentToc.vue | 2 +- src/runtime/components/prose/Accordion.vue | 3 +- src/runtime/components/prose/Callout.vue | 3 +- src/runtime/components/prose/Card.vue | 3 +- src/runtime/components/prose/CodeCollapse.vue | 2 +- src/runtime/components/prose/CodeGroup.vue | 2 +- src/runtime/components/prose/CodeIcon.vue | 2 +- src/runtime/components/prose/Collapsible.vue | 3 +- src/runtime/components/prose/Pre.vue | 2 +- src/runtime/components/prose/Prompt.vue | 2 +- src/runtime/components/prose/Tabs.vue | 3 +- src/runtime/composables/useAvatarGroup.ts | 2 +- src/runtime/composables/useComponentIcons.ts | 3 +- src/runtime/composables/useContentSearch.ts | 2 +- src/runtime/composables/useFormField.ts | 2 +- src/runtime/composables/useToast.ts | 2 +- src/runtime/dictionary/icons.ts | 2 +- src/runtime/locale/ar.ts | 2 +- src/runtime/locale/br.ts | 2 +- src/runtime/locale/de.ts | 2 +- src/runtime/locale/en.ts | 2 +- src/runtime/locale/fr.ts | 2 +- src/runtime/locale/id.ts | 2 +- src/runtime/locale/in.ts | 2 +- src/runtime/locale/it.ts | 2 +- src/runtime/locale/ja.ts | 2 +- src/runtime/locale/kz.ts | 2 +- src/runtime/locale/la.ts | 2 +- src/runtime/locale/ms.ts | 2 +- src/runtime/locale/pl.ts | 2 +- src/runtime/locale/ru.ts | 2 +- src/runtime/locale/sc.ts | 2 +- src/runtime/locale/tc.ts | 2 +- src/runtime/locale/th.ts | 2 +- src/runtime/locale/tr.ts | 2 +- src/runtime/locale/ua.ts | 2 +- src/runtime/locale/vn.ts | 2 +- src/runtime/utils/index.ts | 2 +- src/runtime/utils/link.ts | 2 +- .../components/color-mode/ColorModeSelect.vue | 2 +- .../components/color-mode/ColorModeSwitch.vue | 2 +- .../vue/overrides/inertia/LinkBase.vue | 2 +- 122 files changed, 351 insertions(+), 122 deletions(-) create mode 100644 .sync/log/7eab4bcb78f17b9eed365d617aa3ef3874c21bac.md diff --git a/.sync/log/7eab4bcb78f17b9eed365d617aa3ef3874c21bac.md b/.sync/log/7eab4bcb78f17b9eed365d617aa3ef3874c21bac.md new file mode 100644 index 000000000..6ec7a5574 --- /dev/null +++ b/.sync/log/7eab4bcb78f17b9eed365d617aa3ef3874c21bac.md @@ -0,0 +1,59 @@ +# Port: perf(types): import cross-component types from source, not the barrel (#6646) + +**Upstream:** `7eab4bcb78f17b9eed365d617aa3ef3874c21bac` (nuxt/ui) +**Decision:** port + +## Upstream change +A perf refactor: stop importing cross-component types from the `../types` +**barrel** (`src/runtime/types/index.ts`, which `export *`s every component + +every `types/*` submodule). One barrel import eagerly pulls the whole library +into a consumer's type graph. Instead, import each type directly from its +**source** — the owning component `.vue` (e.g. `ButtonProps` from +`./Button.vue`) or a specific `../types/*` module (e.g. `Messages` from +`../types/locale`). Adds an eslint `no-restricted-imports` guard so the barrel +can't creep back in, with `useComponentProps.ts` exempted (it legitimately +needs the whole barrel via `import type * as ComponentTypes`). + +## b24ui port — adapted, not 1:1 +b24ui's `types/index.ts` barrel has the **same** shape (re-exports every +`components/*.vue` + `types/{content,editor,form,input,icons,locale,prose,color-mode,utils}`), +so the same refactor applies — but the per-type → source mapping had to be +resolved against **b24ui's own** exports because the component/type set +diverges from upstream: + +- b24ui has **no `Icon.vue`**: upstream maps `IconProps` → `./Icon.vue`; b24ui + components use `IconComponent`, which resolves to `../types/icons`. +- b24ui has its own component set (no `AuthForm`/`BlogPost`/…, plus extras like + `Advice`, `Countdown`, the `Sidebar*`/`Navbar*` families). +- b24ui maintains a **curated, smaller locale set** (§2) — 20 locale files + touched here vs upstream's 62. Existing locales are edited normally + (`Messages` import retargeted to `../types/locale`); no new locales added. + +Rather than copy upstream's per-file import lines, I built a name→source index +from b24ui's barrel sources and rewrote every consumer's +`import type { … } from '<../types barrel>'` into per-source grouped imports +with the correct relative specifier for the consumer's depth. Verified: **0** +unresolved type names, **0** ambiguous names (no type exported by >1 source). + +### Scope +- **119** consumer files rewritten (`src/runtime/{components,composables,utils, + dictionary,locale,vue/...}`), `+`eslint.config.mjs. `120` files total, + +284/−120 lines (single barrel lines split into per-source lines). +- Only remaining barrel import: `composables/useComponentProps.ts` + (`import type * as ComponentTypes from '../types'`) — intentional, and the + eslint rule turns `no-restricted-imports` **off** for that file, mirroring + upstream. +- eslint rule appended via the existing `.append(...)` chain (b24ui uses the + `bitrix24-ui/` plugin namespace, not upstream's `nuxt-ui/`). Rule `files` + globs and restricted paths (`../types`, `../../types`) mirror upstream. + b24ui's only depth-3 barrel importers live under `src/runtime/vue/…` (not + `components/**`/`composables/**`), so — exactly as upstream — they are + rewritten but not eslint-guarded. + +## Tests +Types-only change, no runtime/markup change → no snapshot churn. Full suite +unchanged (225 files, 5143 passed / 6 skipped). + +## Verify (CI=true) +`dev:prepare` · `lint` (new rule active, green) · `typecheck` (all 119 rewrites +resolve) · `test` · `build` — all green. diff --git a/.sync/nuxt-ui.json b/.sync/nuxt-ui.json index d2975ac45..f1f46b8c2 100644 --- a/.sync/nuxt-ui.json +++ b/.sync/nuxt-ui.json @@ -2,7 +2,7 @@ "upstream": "nuxt/ui", "branch": "v4", "sync_enabled": false, - "cursor": "88baabcf42b115b7da1a65e5f71092a37b5c9bf3", + "cursor": "7eab4bcb78f17b9eed365d617aa3ef3874c21bac", "_cursor_note": "cursor = last upstream commit ported into b24ui (oldest-first, manual cadence). sync_enabled stays false until Phase 2 (porter workflow #67 + CLAUDE_CODE_OAUTH_TOKEN) is wired and trusted. `processed` is maintained per port from now on (backfilled #68-#72 on 2026-06-09).", "stats": { "queue_depth": 0, @@ -773,10 +773,16 @@ "summary": "fix(ContentNavigation): key items by identity to prevent leading icon flash (#6640) — NO-OP: upstream keys the navigation v-for by `${index}-${link.path}` in src/runtime/components/content/ContentNavigation.vue (+spec). b24ui has no ContentNavigation component (git ls-files grep contentnavigation => none). Nothing to port. Bookkeeping only" }, "88baabcf42b115b7da1a65e5f71092a37b5c9bf3": { + "pr": 225, + "b24ui_sha": "007602bf", + "decision": "port", + "summary": "fix(Separator): forward fall-through attributes to root (#6641) — Separator.vue: add defineOptions({ inheritAttrs: false }) + change root binding v-bind=rootProps -> v-bind={...rootProps, ...$attrs} so fall-through attrs land on the actual root. Direct 1:1 (b24ui matched pre-change; useForwardProps rootProps + reka root unchanged; no ui/icon/color renames). +attr-forwarding spec mirroring #218 (d3b5f1d3) (targets [data-slot=root] since Separator is multi-root). 2 snapshots updated (Separator + -vue): the existing 'with color success' renderEach case passes `color` which is NOT a SeparatorProps member = a fall-through attr; pre-fix multi-root dropped it, post-fix $attrs forwards it to root -> snapshot now carries color=air-primary-success (the fix demonstrating itself)" + }, + "7eab4bcb78f17b9eed365d617aa3ef3874c21bac": { "pr": null, "b24ui_sha": "pending-merge", "decision": "port", - "summary": "fix(Separator): forward fall-through attributes to root (#6641) — Separator.vue: add defineOptions({ inheritAttrs: false }) + change root binding v-bind=rootProps -> v-bind={...rootProps, ...$attrs} so fall-through attrs land on the actual root. Direct 1:1 (b24ui matched pre-change; useForwardProps rootProps + reka root unchanged; no ui/icon/color renames). +attr-forwarding spec mirroring #218 (d3b5f1d3) (targets [data-slot=root] since Separator is multi-root). 2 snapshots updated (Separator + -vue): the existing 'with color success' renderEach case passes `color` which is NOT a SeparatorProps member = a fall-through attr; pre-fix multi-root dropped it, post-fix $attrs forwards it to root -> snapshot now carries color=air-primary-success (the fix demonstrating itself)" + "summary": "perf(types): import cross-component types from source, not the barrel (#6646) — replace `import type {...} from '../types'` barrel imports with per-source imports (owning component .vue, e.g. ButtonProps from ./Button.vue; or specific ../types/* module, e.g. Messages from ../types/locale) across 119 consumer files in src/runtime/{components,composables,utils,dictionary,locale,vue/...}; +eslint no-restricted-imports guard on ../types & ../../types for components/**/*.vue + composables/**/*.ts, with useComponentProps.ts exempted (keeps `import type * as ComponentTypes from '../types'`). Adapted, not 1:1: resolved each type->source against b24ui's OWN exports (IconComponent->../types/icons since b24ui has no Icon.vue; own component set; curated 20 locale files vs upstream 62 per §2). 0 unresolved / 0 ambiguous names. Rule appended via existing .append chain (bitrix24-ui/ namespace). Types-only, no snapshot churn. 120 files, +284/-120" } } } diff --git a/eslint.config.mjs b/eslint.config.mjs index ef17e8dc8..000034c63 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -180,4 +180,19 @@ export default createConfigForNuxt({ rules: { 'bitrix24-ui/no-bare-prop-refs': 'error' } +}).append({ + files: ['src/runtime/components/**/*.vue', 'src/runtime/composables/**/*.ts'], + rules: { + 'no-restricted-imports': ['error', { + paths: [ + { name: '../types', message: 'Import cross-component types from their source file (e.g. \'./Button.vue\') or a specific \'../types/*\' module, not the \'../types\' barrel: it re-exports every component, so one import eagerly loads the whole library into a consumer\'s type graph.' }, + { name: '../../types', message: 'Import cross-component types from their source file (e.g. \'./Button.vue\') or a specific \'../../types/*\' module, not the \'../../types\' barrel: it re-exports every component, so one import eagerly loads the whole library into a consumer\'s type graph.' } + ] + }] + } +}, { + files: ['src/runtime/composables/useComponentProps.ts'], + rules: { + 'no-restricted-imports': 'off' + } }) diff --git a/src/runtime/components/Accordion.vue b/src/runtime/components/Accordion.vue index 828f0aa98..23998a28d 100644 --- a/src/runtime/components/Accordion.vue +++ b/src/runtime/components/Accordion.vue @@ -4,7 +4,7 @@ import type { AccordionRootProps, AccordionRootEmits } from 'reka-ui' import type { VNode } from 'vue' import type { AppConfig } from '@nuxt/schema' import theme from '#build/b24ui/accordion' -import type { IconComponent } from '../types' +import type { IconComponent } from '../types/icons' import type { DynamicSlots, GetItemKeys } from '../types/utils' import type { ComponentConfig } from '../types/tv' diff --git a/src/runtime/components/Advice.vue b/src/runtime/components/Advice.vue index 95915b80c..59f411a7f 100644 --- a/src/runtime/components/Advice.vue +++ b/src/runtime/components/Advice.vue @@ -3,7 +3,7 @@ import type { VNode } from 'vue' import type { AppConfig } from '@nuxt/schema' import theme from '#build/b24ui/advice' import type { UseComponentIconsProps } from '../composables/useComponentIcons' -import type { AvatarProps } from '../types' +import type { AvatarProps } from './Avatar.vue' import type { ComponentConfig } from '../types/tv' type Advice = ComponentConfig diff --git a/src/runtime/components/Alert.vue b/src/runtime/components/Alert.vue index 8e98ac64e..901714255 100644 --- a/src/runtime/components/Alert.vue +++ b/src/runtime/components/Alert.vue @@ -2,7 +2,10 @@ import type { VNode } from 'vue' import type { AppConfig } from '@nuxt/schema' import theme from '#build/b24ui/alert' -import type { AvatarProps, ButtonProps, IconComponent, LinkPropsKeys } from '../types' +import type { IconComponent } from '../types/icons' +import type { AvatarProps } from './Avatar.vue' +import type { ButtonProps } from './Button.vue' +import type { LinkPropsKeys } from './Link.vue' import type { ComponentConfig } from '../types/tv' type Alert = ComponentConfig diff --git a/src/runtime/components/App.vue b/src/runtime/components/App.vue index f065eb3b1..516229d0e 100644 --- a/src/runtime/components/App.vue +++ b/src/runtime/components/App.vue @@ -1,7 +1,8 @@