From 7ab1891976375bc34f9ace513207981ae60d87d9 Mon Sep 17 00:00:00 2001 From: Derek Wene Date: Tue, 27 Oct 2026 13:06:28 +0000 Subject: [PATCH 1/3] Fix: emit one module per file so subpath imports stay small ui/checkbox, ui/dialog and ui/tabs imported `cn` from the `ui` barrel, which formed a cycle through data-table-filter. Rollup resolved it by merging them into a shared filter-selector chunk, so importing a checkbox loaded the calendar, react-day-picker, date-fns and input-otp. - Move `cn` into ui/utils/cn and import it directly everywhere, so leaf components no longer pull zod, react-router or filter sync helpers. - Build with preserveModules (no hashed shared chunks) and externalize every declared dependency/peer instead of a hand-maintained list. Undeclared bundled code goes to dist/_vendor, since npm strips nested node_modules from tarballs. - Declare "sideEffects": false so barrel imports tree-shake. Co-Authored-By: Claude Opus 5.5 --- .changeset/per-module-build.md | 5 ++ packages/components/package.json | 1 + packages/components/src/ui/badge.tsx | 2 +- packages/components/src/ui/button.tsx | 2 +- packages/components/src/ui/calendar.tsx | 2 +- packages/components/src/ui/checkbox-field.tsx | 2 +- packages/components/src/ui/checkbox.tsx | 2 +- packages/components/src/ui/command.tsx | 2 +- .../components/filter-actions.tsx | 2 +- .../components/filter-selector.tsx | 2 +- .../components/filter-value.tsx | 2 +- .../data-table/data-table-faceted-filter.tsx | 2 +- .../src/ui/data-table/data-table.tsx | 2 +- .../components/src/ui/date-picker-field.tsx | 2 +- packages/components/src/ui/dialog.tsx | 2 +- packages/components/src/ui/dropdown-menu.tsx | 2 +- packages/components/src/ui/form.tsx | 2 +- packages/components/src/ui/label.tsx | 2 +- .../components/src/ui/otp-input-field.tsx | 2 +- packages/components/src/ui/otp-input.tsx | 2 +- packages/components/src/ui/password-field.tsx | 2 +- .../components/src/ui/phone-input-field.tsx | 2 +- packages/components/src/ui/phone-input.tsx | 2 +- packages/components/src/ui/popover.tsx | 2 +- .../components/src/ui/radio-group-field.tsx | 2 +- .../src/ui/radio-group-item-field.tsx | 2 +- packages/components/src/ui/radio-group.tsx | 2 +- packages/components/src/ui/select.tsx | 2 +- packages/components/src/ui/separator.tsx | 2 +- packages/components/src/ui/slider.tsx | 2 +- packages/components/src/ui/switch-field.tsx | 2 +- packages/components/src/ui/switch.tsx | 2 +- packages/components/src/ui/table.tsx | 2 +- packages/components/src/ui/tabs.tsx | 2 +- packages/components/src/ui/text-field.tsx | 2 +- packages/components/src/ui/text-input.tsx | 2 +- packages/components/src/ui/textarea.tsx | 2 +- packages/components/src/ui/utils/cn.ts | 6 +++ packages/components/src/ui/utils/index.ts | 8 +-- packages/components/vite.config.ts | 54 ++++++++----------- 40 files changed, 70 insertions(+), 74 deletions(-) create mode 100644 .changeset/per-module-build.md create mode 100644 packages/components/src/ui/utils/cn.ts diff --git a/.changeset/per-module-build.md b/.changeset/per-module-build.md new file mode 100644 index 00000000..14cac166 --- /dev/null +++ b/.changeset/per-module-build.md @@ -0,0 +1,5 @@ +--- +"@lambdacurry/forms": patch +--- + +Publish one file per source module so subpath imports stay small. `ui/checkbox`, `ui/dialog` and `ui/tabs` no longer load the shared `filter-selector-.js` chunk with the data-table filter, calendar, `react-day-picker`, `date-fns` and `input-otp`; each now loads only its own Radix primitive and `cn`. The package also declares `"sideEffects": false` so barrel imports tree-shake, and every declared dependency is left external instead of being partially inlined (previously `cmdk`, several Radix packages and a copy of `react-dom` ended up in the bundle). Public exports and subpath imports are unchanged. diff --git a/packages/components/package.json b/packages/components/package.json index 78748981..3830e3c0 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -33,6 +33,7 @@ "files": [ "dist" ], + "sideEffects": false, "scripts": { "prepublishOnly": "yarn run build", "build": "vite build", diff --git a/packages/components/src/ui/badge.tsx b/packages/components/src/ui/badge.tsx index 42dc12a3..3c9488c8 100644 --- a/packages/components/src/ui/badge.tsx +++ b/packages/components/src/ui/badge.tsx @@ -1,7 +1,7 @@ import { cva, type VariantProps } from 'class-variance-authority'; import type * as React from 'react'; -import { cn } from './utils'; +import { cn } from './utils/cn'; const badgeVariants = cva( 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', diff --git a/packages/components/src/ui/button.tsx b/packages/components/src/ui/button.tsx index 4780b5a5..fce33cad 100644 --- a/packages/components/src/ui/button.tsx +++ b/packages/components/src/ui/button.tsx @@ -2,7 +2,7 @@ import { Slot } from '@radix-ui/react-slot'; import { cva, type VariantProps } from 'class-variance-authority'; import type * as React from 'react'; import type { ButtonHTMLAttributes } from 'react'; -import { cn } from './utils'; +import { cn } from './utils/cn'; const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", diff --git a/packages/components/src/ui/calendar.tsx b/packages/components/src/ui/calendar.tsx index f960b2d1..1de74fb4 100644 --- a/packages/components/src/ui/calendar.tsx +++ b/packages/components/src/ui/calendar.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { type DayButton, DayPicker } from 'react-day-picker'; import { Button, buttonVariants } from './button'; -import { cn } from './utils'; +import { cn } from './utils/cn'; function Calendar({ className, diff --git a/packages/components/src/ui/checkbox-field.tsx b/packages/components/src/ui/checkbox-field.tsx index a25f322a..96e2753d 100644 --- a/packages/components/src/ui/checkbox-field.tsx +++ b/packages/components/src/ui/checkbox-field.tsx @@ -12,7 +12,7 @@ import { FormLabel, FormMessage, } from './form'; -import { cn } from './utils'; +import { cn } from './utils/cn'; export interface CheckboxFieldComponents extends FieldComponents { Checkbox?: React.ComponentType>; diff --git a/packages/components/src/ui/checkbox.tsx b/packages/components/src/ui/checkbox.tsx index 3872cb09..054f20f0 100644 --- a/packages/components/src/ui/checkbox.tsx +++ b/packages/components/src/ui/checkbox.tsx @@ -2,7 +2,7 @@ import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; import { CheckIcon } from 'lucide-react'; import type * as React from 'react'; -import { cn } from '../ui'; +import { cn } from './utils/cn'; function Checkbox({ className, ...props }: React.ComponentProps) { return ( diff --git a/packages/components/src/ui/command.tsx b/packages/components/src/ui/command.tsx index 1cee6ee0..ce0af7de 100644 --- a/packages/components/src/ui/command.tsx +++ b/packages/components/src/ui/command.tsx @@ -3,7 +3,7 @@ import { Command as CommandPrimitive } from 'cmdk'; import type * as React from 'react'; import { forwardRef } from 'react'; -import { cn } from './utils'; +import { cn } from './utils/cn'; const Command = ({ className, ...props }: React.ComponentPropsWithoutRef) => ( { column?: Column; diff --git a/packages/components/src/ui/data-table/data-table.tsx b/packages/components/src/ui/data-table/data-table.tsx index 6727bf71..d4f29a89 100644 --- a/packages/components/src/ui/data-table/data-table.tsx +++ b/packages/components/src/ui/data-table/data-table.tsx @@ -1,6 +1,6 @@ import { flexRender, type Table as TableType } from '@tanstack/react-table'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../table'; -import { cn } from '../utils'; +import { cn } from '../utils/cn'; import { DataTablePagination } from './data-table-pagination'; interface DataTableProps { diff --git a/packages/components/src/ui/date-picker-field.tsx b/packages/components/src/ui/date-picker-field.tsx index 7d2c09c7..378740f2 100644 --- a/packages/components/src/ui/date-picker-field.tsx +++ b/packages/components/src/ui/date-picker-field.tsx @@ -13,7 +13,7 @@ import { FormMessage, } from './form'; import { Popover, PopoverContent, PopoverTrigger } from './popover'; -import { cn } from './utils'; +import { cn } from './utils/cn'; export interface DatePickerFieldProps< TFieldValues extends FieldValues = FieldValues, diff --git a/packages/components/src/ui/dialog.tsx b/packages/components/src/ui/dialog.tsx index d89678fd..ffcefd2e 100644 --- a/packages/components/src/ui/dialog.tsx +++ b/packages/components/src/ui/dialog.tsx @@ -2,7 +2,7 @@ import * as DialogPrimitive from '@radix-ui/react-dialog'; import { XIcon } from 'lucide-react'; import type * as React from 'react'; -import { cn } from '../ui'; +import { cn } from './utils/cn'; function Dialog({ ...props }: React.ComponentProps) { return ; diff --git a/packages/components/src/ui/dropdown-menu.tsx b/packages/components/src/ui/dropdown-menu.tsx index 1c82e3c4..2a108282 100644 --- a/packages/components/src/ui/dropdown-menu.tsx +++ b/packages/components/src/ui/dropdown-menu.tsx @@ -1,7 +1,7 @@ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; import { Check, ChevronRight, Circle } from 'lucide-react'; import type * as React from 'react'; -import { cn } from './utils'; +import { cn } from './utils/cn'; const DropdownMenu = DropdownMenuPrimitive.Root; diff --git a/packages/components/src/ui/form.tsx b/packages/components/src/ui/form.tsx index 0fda05ee..8e713515 100644 --- a/packages/components/src/ui/form.tsx +++ b/packages/components/src/ui/form.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { Controller, type ControllerProps, type FieldPath, type FieldValues } from 'react-hook-form'; import { Label } from './label'; import type { InputProps } from './text-input'; -import { cn } from './utils'; +import { cn } from './utils/cn'; export interface FieldComponents { FormControl: React.ComponentType; diff --git a/packages/components/src/ui/label.tsx b/packages/components/src/ui/label.tsx index 55e28b0a..81d78347 100644 --- a/packages/components/src/ui/label.tsx +++ b/packages/components/src/ui/label.tsx @@ -1,7 +1,7 @@ import * as LabelPrimitive from '@radix-ui/react-label'; import { cva, type VariantProps } from 'class-variance-authority'; import type * as React from 'react'; -import { cn } from './utils'; +import { cn } from './utils/cn'; const labelVariants = cva('text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'); diff --git a/packages/components/src/ui/otp-input-field.tsx b/packages/components/src/ui/otp-input-field.tsx index ac74defe..71b961a6 100644 --- a/packages/components/src/ui/otp-input-field.tsx +++ b/packages/components/src/ui/otp-input-field.tsx @@ -13,7 +13,7 @@ import { FormMessage, } from './form'; import { OTPInput } from './otp-input'; -import { cn } from './utils'; +import { cn } from './utils/cn'; export type { OTPInputProps }; diff --git a/packages/components/src/ui/otp-input.tsx b/packages/components/src/ui/otp-input.tsx index fe9bb410..29761b74 100644 --- a/packages/components/src/ui/otp-input.tsx +++ b/packages/components/src/ui/otp-input.tsx @@ -1,5 +1,5 @@ import { OTPInput as OTPInputPrimitive, type OTPInputProps } from 'input-otp'; -import { cn } from './utils'; +import { cn } from './utils/cn'; export type { OTPInputProps }; diff --git a/packages/components/src/ui/password-field.tsx b/packages/components/src/ui/password-field.tsx index 650e8c37..eafbcfff 100644 --- a/packages/components/src/ui/password-field.tsx +++ b/packages/components/src/ui/password-field.tsx @@ -12,7 +12,7 @@ import { FormMessage, } from './form'; import { type InputProps, TextInput } from './text-input'; -import { cn } from './utils'; +import { cn } from './utils/cn'; export interface PasswordToggleButtonProps { isVisible: boolean; diff --git a/packages/components/src/ui/phone-input-field.tsx b/packages/components/src/ui/phone-input-field.tsx index 05b16e29..be29f756 100644 --- a/packages/components/src/ui/phone-input-field.tsx +++ b/packages/components/src/ui/phone-input-field.tsx @@ -10,7 +10,7 @@ import { FormMessage, } from './form'; import { type PhoneInputProps, PhoneNumberInput } from './phone-input'; -import { cn } from './utils'; +import { cn } from './utils/cn'; export interface PhoneInputFieldProps extends Omit { control?: Control; diff --git a/packages/components/src/ui/phone-input.tsx b/packages/components/src/ui/phone-input.tsx index 3cca2267..fbaf38e7 100644 --- a/packages/components/src/ui/phone-input.tsx +++ b/packages/components/src/ui/phone-input.tsx @@ -2,7 +2,7 @@ import { AsYouType } from 'libphonenumber-js'; import type { ChangeEvent, InputHTMLAttributes, KeyboardEvent, Ref } from 'react'; import { useCallback, useEffect, useRef } from 'react'; -import { cn } from './utils'; +import { cn } from './utils/cn'; // Constants const US_PHONE_LENGTH = 10; diff --git a/packages/components/src/ui/popover.tsx b/packages/components/src/ui/popover.tsx index c514166d..d840acb4 100644 --- a/packages/components/src/ui/popover.tsx +++ b/packages/components/src/ui/popover.tsx @@ -1,6 +1,6 @@ import * as PopoverPrimitive from '@radix-ui/react-popover'; import type * as React from 'react'; -import { cn } from './utils'; +import { cn } from './utils/cn'; const Popover = PopoverPrimitive.Root; diff --git a/packages/components/src/ui/radio-group-field.tsx b/packages/components/src/ui/radio-group-field.tsx index 7c3134a8..ab967daf 100644 --- a/packages/components/src/ui/radio-group-field.tsx +++ b/packages/components/src/ui/radio-group-field.tsx @@ -11,7 +11,7 @@ import { FormMessage, } from './form'; import { RadioGroup } from './radio-group'; -import { cn } from './utils'; +import { cn } from './utils/cn'; export interface RadioGroupFieldComponents extends FieldComponents { RadioGroup?: React.ComponentType>; diff --git a/packages/components/src/ui/radio-group-item-field.tsx b/packages/components/src/ui/radio-group-item-field.tsx index 65b6ec8b..fbb84941 100644 --- a/packages/components/src/ui/radio-group-item-field.tsx +++ b/packages/components/src/ui/radio-group-item-field.tsx @@ -1,7 +1,7 @@ import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'; import type * as React from 'react'; import { Label } from './label'; -import { cn } from './utils'; +import { cn } from './utils/cn'; export interface RadioGroupItemFieldComponents { RadioGroupItem?: React.ComponentType>; diff --git a/packages/components/src/ui/radio-group.tsx b/packages/components/src/ui/radio-group.tsx index 7ce51594..33ff59b4 100644 --- a/packages/components/src/ui/radio-group.tsx +++ b/packages/components/src/ui/radio-group.tsx @@ -1,7 +1,7 @@ import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'; import { Circle } from 'lucide-react'; import type * as React from 'react'; -import { cn } from './utils'; +import { cn } from './utils/cn'; export interface RadioGroupItemComponents { RadioGroupItem?: React.ComponentType< diff --git a/packages/components/src/ui/select.tsx b/packages/components/src/ui/select.tsx index d569e0b2..93a1a597 100644 --- a/packages/components/src/ui/select.tsx +++ b/packages/components/src/ui/select.tsx @@ -3,7 +3,7 @@ import { Popover } from '@radix-ui/react-popover'; import { Check as DefaultCheckIcon, ChevronDown as DefaultChevronIcon } from 'lucide-react'; import { useOverlayTriggerState } from 'react-stately'; import { PopoverTrigger } from './popover'; -import { cn } from './utils'; +import { cn } from './utils/cn'; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from './command'; import { forwardRef, diff --git a/packages/components/src/ui/separator.tsx b/packages/components/src/ui/separator.tsx index 569ebc49..73d17235 100644 --- a/packages/components/src/ui/separator.tsx +++ b/packages/components/src/ui/separator.tsx @@ -1,7 +1,7 @@ import * as SeparatorPrimitive from '@radix-ui/react-separator'; import * as React from 'react'; -import { cn } from './utils'; +import { cn } from './utils/cn'; const Separator = React.forwardRef< React.ElementRef, diff --git a/packages/components/src/ui/slider.tsx b/packages/components/src/ui/slider.tsx index 1e01328a..395e5348 100644 --- a/packages/components/src/ui/slider.tsx +++ b/packages/components/src/ui/slider.tsx @@ -3,7 +3,7 @@ import * as SliderPrimitive from '@radix-ui/react-slider'; import * as React from 'react'; -import { cn } from '../ui/'; +import { cn } from './utils/cn'; function Slider({ className, diff --git a/packages/components/src/ui/switch-field.tsx b/packages/components/src/ui/switch-field.tsx index f29d10d9..0fdb5739 100644 --- a/packages/components/src/ui/switch-field.tsx +++ b/packages/components/src/ui/switch-field.tsx @@ -2,7 +2,7 @@ import * as SwitchPrimitives from '@radix-ui/react-switch'; import type * as React from 'react'; import type { Control, FieldPath, FieldValues } from 'react-hook-form'; import { type FieldComponents, FormControl, FormDescription, FormField, FormItem, FormLabel } from './form'; -import { cn } from './utils'; +import { cn } from './utils/cn'; // Default styled Switch component const DefaultSwitchPrimitive = (props: React.ComponentPropsWithoutRef) => ( diff --git a/packages/components/src/ui/switch.tsx b/packages/components/src/ui/switch.tsx index 5744a37b..58717346 100644 --- a/packages/components/src/ui/switch.tsx +++ b/packages/components/src/ui/switch.tsx @@ -1,6 +1,6 @@ import * as SwitchPrimitives from '@radix-ui/react-switch'; import type * as React from 'react'; -import { cn } from './utils'; +import { cn } from './utils/cn'; function Switch({ className, ...props }: React.ComponentProps) { return ( diff --git a/packages/components/src/ui/table.tsx b/packages/components/src/ui/table.tsx index 0cefdc5f..0569d3f9 100644 --- a/packages/components/src/ui/table.tsx +++ b/packages/components/src/ui/table.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { cn } from './utils'; +import { cn } from './utils/cn'; const Table = React.forwardRef>( ({ className, ...props }, ref) => ( diff --git a/packages/components/src/ui/tabs.tsx b/packages/components/src/ui/tabs.tsx index a41d20dd..352be5c2 100644 --- a/packages/components/src/ui/tabs.tsx +++ b/packages/components/src/ui/tabs.tsx @@ -3,7 +3,7 @@ import * as TabsPrimitive from '@radix-ui/react-tabs'; import type * as React from 'react'; -import { cn } from '../ui'; +import { cn } from './utils/cn'; function Tabs({ className, ...props }: React.ComponentProps) { return ; diff --git a/packages/components/src/ui/text-field.tsx b/packages/components/src/ui/text-field.tsx index efcc3ba0..93425bf0 100644 --- a/packages/components/src/ui/text-field.tsx +++ b/packages/components/src/ui/text-field.tsx @@ -10,7 +10,7 @@ import { FormMessage, } from './form'; import { type InputProps, TextInput } from './text-input'; -import { cn } from './utils'; +import { cn } from './utils/cn'; export const FieldPrefix = ({ children, className }: { children: React.ReactNode; className?: string }) => { return ( diff --git a/packages/components/src/ui/text-input.tsx b/packages/components/src/ui/text-input.tsx index 909b4b91..e601139b 100644 --- a/packages/components/src/ui/text-input.tsx +++ b/packages/components/src/ui/text-input.tsx @@ -1,5 +1,5 @@ import type * as React from 'react'; -import { cn } from './utils'; +import { cn } from './utils/cn'; export interface InputProps extends React.InputHTMLAttributes { ref?: React.Ref; diff --git a/packages/components/src/ui/textarea.tsx b/packages/components/src/ui/textarea.tsx index 77c13b5f..055502a1 100644 --- a/packages/components/src/ui/textarea.tsx +++ b/packages/components/src/ui/textarea.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { cn } from './utils'; +import { cn } from './utils/cn'; export interface TextareaProps extends React.TextareaHTMLAttributes { CustomTextarea?: React.ForwardRefExoticComponent< diff --git a/packages/components/src/ui/utils/cn.ts b/packages/components/src/ui/utils/cn.ts new file mode 100644 index 00000000..9ad0df42 --- /dev/null +++ b/packages/components/src/ui/utils/cn.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/packages/components/src/ui/utils/index.ts b/packages/components/src/ui/utils/index.ts index 0c8b6801..6e4c16d4 100644 --- a/packages/components/src/ui/utils/index.ts +++ b/packages/components/src/ui/utils/index.ts @@ -1,10 +1,4 @@ -import { type ClassValue, clsx } from 'clsx'; -import { twMerge } from 'tailwind-merge'; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} - +export * from './cn'; export * from './debounce'; export * from './filters'; export * from './use-filter-sync'; diff --git a/packages/components/vite.config.ts b/packages/components/vite.config.ts index 9b15422c..f0930d3f 100644 --- a/packages/components/vite.config.ts +++ b/packages/components/vite.config.ts @@ -4,6 +4,14 @@ import react from '@vitejs/plugin-react'; import { glob } from 'glob'; import { defineConfig } from 'vite'; import dts from 'vite-plugin-dts'; +import pkg from './package.json' with { type: 'json' }; + +const externalPackages = [ + ...Object.keys(pkg.dependencies), + ...Object.keys(pkg.peerDependencies), + 'react-dom', + '@react-router/node', +]; export default defineConfig({ plugins: [ @@ -45,38 +53,20 @@ export default defineConfig({ fileURLToPath(new URL(file, import.meta.url)), ]), ), - external: [ - 'react', - 'react/jsx-runtime', - '@radix-ui/react-alert-dialog', - '@radix-ui/react-avatar', - '@radix-ui/react-checkbox', - '@radix-ui/react-dialog', - '@radix-ui/react-dropdown-menu', - '@radix-ui/react-icons', - '@radix-ui/react-label', - '@radix-ui/react-popover', - '@radix-ui/react-radio-group', - '@radix-ui/react-scroll-area', - '@radix-ui/react-slot', - '@radix-ui/react-switch', - '@radix-ui/react-tooltip', - 'react-router', - '@react-router/node', - 'class-variance-authority', - 'clsx', - 'date-fns', - 'input-otp', - 'lucide-react', - 'next-themes', - 'react-day-picker', - 'react-hook-form', - 'remix-hook-form', - 'sonner', - 'tailwind-merge', - 'tailwindcss-animate', - 'zod', - ], + // Externalize every declared dependency and peer dependency (including subpaths such as + // `react/jsx-runtime`) so consumers dedupe them instead of getting inlined copies. + external: (id) => externalPackages.some((name) => id === name || id.startsWith(`${name}/`)), + output: { + // Emit one file per source module so each subpath (e.g. `ui/checkbox.js`) only pulls in + // the modules it actually imports, instead of hash-named shared chunks that mix unrelated code. + preserveModules: true, + preserveModulesRoot: 'src', + // Keep each file's imports limited to its direct dependencies (no hoisted `import 'x'` lines). + hoistTransitiveImports: false, + // Undeclared third-party code that still gets bundled (e.g. libphonenumber-js) would land in + // `dist/node_modules`, which npm strips from published tarballs, so emit it under `dist/_vendor`. + entryFileNames: (chunk) => `${chunk.name.replace(/(^|\/)node_modules\//g, '$1_vendor/')}.js`, + }, }, }, resolve: { From 8bef5c7d56620da62a54ff68b4d06cb4f242a9c8 Mon Sep 17 00:00:00 2001 From: Derek Wene Date: Tue, 27 Oct 2026 13:17:33 +0000 Subject: [PATCH 2/3] Chore: bump Playwright to 1.64.1 for Ubuntu 26.04 runners ubuntu-latest now resolves to 26.04, where Playwright 1.55 refuses to install Chromium, so the test job failed before running any tests. Playwright 1.61+ supports 26.04. @storybook/test-runner accepts playwright ^1.14.0, so this is a root devDependency + lockfile bump. Co-Authored-By: Claude Opus 5.5 --- package.json | 2 +- yarn.lock | 53 +++++++++++++++------------------------------------- 2 files changed, 16 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index f38ad857..23645bad 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "devDependencies": { "@biomejs/biome": "2.2.4", - "@playwright/test": "^1.54.2", + "@playwright/test": "^1.64.1", "@types/react-dom": "^19", "turbo": "^2.3.3" }, diff --git a/yarn.lock b/yarn.lock index 14e8b0df..59b7f91e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1977,14 +1977,14 @@ __metadata: languageName: node linkType: hard -"@playwright/test@npm:^1.54.2": - version: 1.55.0 - resolution: "@playwright/test@npm:1.55.0" +"@playwright/test@npm:^1.64.1": + version: 1.64.1 + resolution: "@playwright/test@npm:1.64.1" dependencies: - playwright: "npm:1.55.0" + playwright: "npm:1.64.1" bin: playwright: cli.js - checksum: 10c0/e68b59cd8271f1b57c0649fc0562ab2d5f6bba8c3653dd7bd52ca1338dc380fde34588d0254e3cd3f0f2b20af04a80dfb080419ceb7475990bb2fc4d8c474984 + checksum: 10c0/0a1a853d76d6c532cafaf0366a5448584b766737ebd39bbd23dc0580f3d3b6ef4c833082af47a792e7fc2501f444debdfaad37a87a76cc5b195f3803fc80ea3c languageName: node linkType: hard @@ -7078,7 +7078,7 @@ __metadata: dependencies: "@biomejs/biome": "npm:2.2.4" "@changesets/cli": "npm:^2.27.11" - "@playwright/test": "npm:^1.54.2" + "@playwright/test": "npm:^1.64.1" "@remix-run/react": "npm:^2.17.0" "@types/react-dom": "npm:^19" react-dom: "npm:^19.1.1" @@ -7166,16 +7166,6 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:2.3.2": - version: 2.3.2 - resolution: "fsevents@npm:2.3.2" - dependencies: - node-gyp: "npm:latest" - checksum: 10c0/be78a3efa3e181cda3cf7a4637cb527bcebb0bd0ea0440105a3bb45b86f9245b307dc10a2507e8f4498a7d4ec349d1910f4d73e4d4495b16103106e07eee735b - conditions: os=darwin - languageName: node - linkType: hard - "fsevents@npm:^2.3.2, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" @@ -7186,15 +7176,6 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin": - version: 2.3.2 - resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1" - dependencies: - node-gyp: "npm:latest" - conditions: os=darwin - languageName: node - linkType: hard - "fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" @@ -10023,27 +10004,23 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.55.0, playwright-core@npm:>=1.2.0": - version: 1.55.0 - resolution: "playwright-core@npm:1.55.0" +"playwright-core@npm:1.64.1, playwright-core@npm:>=1.2.0": + version: 1.64.1 + resolution: "playwright-core@npm:1.64.1" bin: playwright-core: cli.js - checksum: 10c0/c39d6aa30e7a4e73965942ca5e13405ae05c9cb49f755a35f04248c864c0b24cf662d9767f1797b3ec48d1cf4e54774dce4a19c16534bd5cfd2aa3da81c9dc3a + checksum: 10c0/91c4f5ed1cff4b0e18b6ac62f66770f5f1b6c204d4aebd6a321c07466dca18a70822ac692597c6059705a703f9768097ac551df179fb49294d639509148e04f3 languageName: node linkType: hard -"playwright@npm:1.55.0, playwright@npm:^1.14.0": - version: 1.55.0 - resolution: "playwright@npm:1.55.0" +"playwright@npm:1.64.1, playwright@npm:^1.14.0": + version: 1.64.1 + resolution: "playwright@npm:1.64.1" dependencies: - fsevents: "npm:2.3.2" - playwright-core: "npm:1.55.0" - dependenciesMeta: - fsevents: - optional: true + playwright-core: "npm:1.64.1" bin: playwright: cli.js - checksum: 10c0/51605b7e57a5650e57972c5fdfc09d7a9934cca1cbee5beacca716fa801e25cb5bb7c1663de90c22b300fde884e5545a2b13a0505a93270b660687791c478304 + checksum: 10c0/edf52d0a79feca37bf383e841a467aa68750684128218eac560063991c9e4b615a94901f580cd8a088bb05fd53008ab4a0e5e8d976067698b60ae5d1de2459f3 languageName: node linkType: hard From b05bedbfccbc140fa5d3b779233dca35a68f70eb Mon Sep 17 00:00:00 2001 From: Derek Wene Date: Tue, 27 Oct 2026 21:08:05 +0000 Subject: [PATCH 3/3] Chore: release per-module build as a minor The dist layout changes and several dependencies move from inlined to external. In 0.x, ^0.23.0 consumers would pick up a patch automatically, so ship it as 0.24.0 to make the upgrade opt-in. Co-Authored-By: Claude Opus 5.5 --- .changeset/per-module-build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/per-module-build.md b/.changeset/per-module-build.md index 14cac166..f0f83bc8 100644 --- a/.changeset/per-module-build.md +++ b/.changeset/per-module-build.md @@ -1,5 +1,5 @@ --- -"@lambdacurry/forms": patch +"@lambdacurry/forms": minor --- Publish one file per source module so subpath imports stay small. `ui/checkbox`, `ui/dialog` and `ui/tabs` no longer load the shared `filter-selector-.js` chunk with the data-table filter, calendar, `react-day-picker`, `date-fns` and `input-otp`; each now loads only its own Radix primitive and `cn`. The package also declares `"sideEffects": false` so barrel imports tree-shake, and every declared dependency is left external instead of being partially inlined (previously `cmdk`, several Radix packages and a copy of `react-dom` ended up in the bundle). Public exports and subpath imports are unchanged.