mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-29 23:55:02 +00:00
Fix: Preserve 'use client' directive in universal registry items (#8798)
* fix: preserve 'use client' directive in universal registry items Universal items (registry:file and registry:item) are framework-agnostic components that can be installed without shadcn project initialization. However, the RSC transformer was incorrectly removing 'use client' directives from these files when config.rsc was false/undefined, breaking client-side functionality. This fix ensures transformers are skipped for universal items, preserving their original content including 'use client' directives, while regular shadcn components continue to have transformers applied as expected. Changes: - Skip all transformers for registry:file and registry:item types - Add tests to verify 'use client' preservation in universal items - Ensure regular components still have transformers applied Fixes issue where universal items would lose 'use client' directives when copied without a full shadcn project setup. * chore: changeset --------- Co-authored-by: shadcn <m@shadcn.com>
This commit is contained in:
5
.changeset/kind-candies-float.md
Normal file
5
.changeset/kind-candies-float.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
Fix: skip all transforms for universal registry items
|
||||
@@ -124,30 +124,35 @@ export async function updateFiles(
|
||||
|
||||
// Run our transformers.
|
||||
// Skip transformers for .env files to preserve exact content
|
||||
const content = isEnvFile(filePath)
|
||||
? file.content
|
||||
: await transform(
|
||||
{
|
||||
filename: file.path,
|
||||
raw: file.content,
|
||||
config,
|
||||
baseColor,
|
||||
transformJsx: !config.tsx,
|
||||
isRemote: options.isRemote,
|
||||
},
|
||||
[
|
||||
transformImport,
|
||||
transformRsc,
|
||||
transformCssVars,
|
||||
transformTwPrefixes,
|
||||
transformIcons,
|
||||
transformMenu,
|
||||
transformAsChild,
|
||||
...(_isNext16Middleware(filePath, projectInfo, config)
|
||||
? [transformNext]
|
||||
: []),
|
||||
]
|
||||
)
|
||||
// Skip transformers for universal item files (registry:file and registry:item)
|
||||
// to preserve their original content as they're meant to be framework-agnostic
|
||||
const isUniversalItemFile =
|
||||
file.type === "registry:file" || file.type === "registry:item"
|
||||
const content =
|
||||
isEnvFile(filePath) || isUniversalItemFile
|
||||
? file.content
|
||||
: await transform(
|
||||
{
|
||||
filename: file.path,
|
||||
raw: file.content,
|
||||
config,
|
||||
baseColor,
|
||||
transformJsx: !config.tsx,
|
||||
isRemote: options.isRemote,
|
||||
},
|
||||
[
|
||||
transformImport,
|
||||
transformRsc,
|
||||
transformCssVars,
|
||||
transformTwPrefixes,
|
||||
transformIcons,
|
||||
transformMenu,
|
||||
transformAsChild,
|
||||
...(_isNext16Middleware(filePath, projectInfo, config)
|
||||
? [transformNext]
|
||||
: []),
|
||||
]
|
||||
)
|
||||
|
||||
// Skip the file if it already exists and the content is the same.
|
||||
// Exception: Don't skip .env files as we merge content instead of replacing
|
||||
|
||||
@@ -1526,6 +1526,111 @@ DATABASE_URL=postgres://localhost:5432/mydb`,
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test("should preserve 'use client' directive for universal item files (registry:file)", async () => {
|
||||
const config = await getConfig(
|
||||
path.resolve(__dirname, "../../fixtures/vite-with-tailwind")
|
||||
)
|
||||
const result = await updateFiles(
|
||||
[
|
||||
{
|
||||
path: "custom-component.tsx",
|
||||
type: "registry:file",
|
||||
target: "~/custom-component.tsx",
|
||||
content: `"use client"
|
||||
|
||||
export function CustomComponent() {
|
||||
return <div>Custom Component</div>
|
||||
}`,
|
||||
},
|
||||
],
|
||||
config,
|
||||
{
|
||||
overwrite: true,
|
||||
silent: true,
|
||||
}
|
||||
)
|
||||
|
||||
// Verify that the file was created
|
||||
expect(result.filesCreated).toContain("custom-component.tsx")
|
||||
|
||||
// Read the written file and check if 'use client' is preserved
|
||||
const writtenContent = (fs.writeFile as any).mock.calls.find((call: any) =>
|
||||
call[0].endsWith("custom-component.tsx")
|
||||
)?.[1]
|
||||
|
||||
expect(writtenContent).toContain('"use client"')
|
||||
})
|
||||
|
||||
test("should preserve 'use client' directive for universal item files (registry:item)", async () => {
|
||||
const config = await getConfig(
|
||||
path.resolve(__dirname, "../../fixtures/vite-with-tailwind")
|
||||
)
|
||||
const result = await updateFiles(
|
||||
[
|
||||
{
|
||||
path: "universal-widget.tsx",
|
||||
type: "registry:item",
|
||||
target: "~/universal-widget.tsx",
|
||||
content: `'use client'
|
||||
|
||||
export function UniversalWidget() {
|
||||
return <div>Universal Widget</div>
|
||||
}`,
|
||||
},
|
||||
],
|
||||
config,
|
||||
{
|
||||
overwrite: true,
|
||||
silent: true,
|
||||
}
|
||||
)
|
||||
|
||||
// Verify that the file was created
|
||||
expect(result.filesCreated).toContain("universal-widget.tsx")
|
||||
|
||||
// Read the written file and check if 'use client' is preserved
|
||||
const writtenContent = (fs.writeFile as any).mock.calls.find((call: any) =>
|
||||
call[0].endsWith("universal-widget.tsx")
|
||||
)?.[1]
|
||||
|
||||
expect(writtenContent).toContain("'use client'")
|
||||
})
|
||||
|
||||
test("should remove 'use client' directive for non-universal item files when rsc is false", async () => {
|
||||
const config = await getConfig(
|
||||
path.resolve(__dirname, "../../fixtures/vite-with-tailwind")
|
||||
)
|
||||
const result = await updateFiles(
|
||||
[
|
||||
{
|
||||
path: "registry/default/ui/regular-component.tsx",
|
||||
type: "registry:ui",
|
||||
content: `"use client"
|
||||
|
||||
export function RegularComponent() {
|
||||
return <div>Regular Component</div>
|
||||
}`,
|
||||
},
|
||||
],
|
||||
config,
|
||||
{
|
||||
overwrite: true,
|
||||
silent: true,
|
||||
}
|
||||
)
|
||||
|
||||
// Verify that the file was created (filesCreated contains relative paths)
|
||||
expect(result.filesCreated.length).toBeGreaterThan(0)
|
||||
|
||||
// Read the written file and check if 'use client' was removed
|
||||
const writtenContent = (fs.writeFile as any).mock.calls.find((call: any) =>
|
||||
call[0].endsWith("regular-component.tsx")
|
||||
)?.[1]
|
||||
|
||||
// The 'use client' should be removed by the RSC transformer
|
||||
expect(writtenContent).not.toContain('"use client"')
|
||||
})
|
||||
})
|
||||
|
||||
describe("resolveModuleByProbablePath", () => {
|
||||
|
||||
Reference in New Issue
Block a user