mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-02-09 02:49:29 +08:00
fix(shadcn): fix async imports not being transformed (#8036)
* fix(shadcn): fix async imports not being transformed when installing components * fix(shadcn): improve performance * test(shadcn): add tests for transform import * test: update timeout --------- Co-authored-by: shadcn <m@shadcn.com>
This commit is contained in:
5
.changeset/tame-schools-flow.md
Normal file
5
.changeset/tame-schools-flow.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": patch
|
||||
---
|
||||
|
||||
fix async imports not being transformed when installing components
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Config } from "@/src/utils/get-config"
|
||||
import { Transformer } from "@/src/utils/transformers"
|
||||
import { SyntaxKind } from "ts-morph"
|
||||
|
||||
export const transformImport: Transformer = async ({
|
||||
sourceFile,
|
||||
@@ -9,32 +10,34 @@ export const transformImport: Transformer = async ({
|
||||
const workspaceAlias = config.aliases?.utils?.split("/")[0]?.slice(1)
|
||||
const utilsImport = `@${workspaceAlias}/lib/utils`
|
||||
|
||||
const importDeclarations = sourceFile.getImportDeclarations()
|
||||
|
||||
if (![".tsx", ".ts", ".jsx", ".js"].includes(sourceFile.getExtension())) {
|
||||
return sourceFile
|
||||
}
|
||||
|
||||
for (const importDeclaration of importDeclarations) {
|
||||
const moduleSpecifier = updateImportAliases(
|
||||
importDeclaration.getModuleSpecifierValue(),
|
||||
for (const specifier of sourceFile.getImportStringLiterals()) {
|
||||
const updated = updateImportAliases(
|
||||
specifier.getLiteralValue(),
|
||||
config,
|
||||
isRemote
|
||||
)
|
||||
|
||||
importDeclaration.setModuleSpecifier(moduleSpecifier)
|
||||
specifier.setLiteralValue(updated)
|
||||
|
||||
// Replace `import { cn } from "@/lib/utils"`
|
||||
if (utilsImport === moduleSpecifier || moduleSpecifier === "@/lib/utils") {
|
||||
const namedImports = importDeclaration.getNamedImports()
|
||||
const cnImport = namedImports.find((i) => i.getName() === "cn")
|
||||
if (cnImport) {
|
||||
importDeclaration.setModuleSpecifier(
|
||||
utilsImport === moduleSpecifier
|
||||
? moduleSpecifier.replace(utilsImport, config.aliases.utils)
|
||||
: config.aliases.utils
|
||||
)
|
||||
}
|
||||
if (utilsImport === updated || updated === "@/lib/utils") {
|
||||
const importDeclaration = specifier.getFirstAncestorByKind(
|
||||
SyntaxKind.ImportDeclaration
|
||||
)
|
||||
const isCnImport = importDeclaration
|
||||
?.getNamedImports()
|
||||
.some((namedImport) => namedImport.getName() === "cn")
|
||||
|
||||
if (!isCnImport) continue
|
||||
|
||||
specifier.setLiteralValue(
|
||||
utilsImport === updated
|
||||
? updated.replace(utilsImport, config.aliases.utils)
|
||||
: config.aliases.utils
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,57 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`transform async/dynamic imports 1`] = `
|
||||
"import * as React from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
async function loadComponent() {
|
||||
const { cn } = await import("@/lib/utils")
|
||||
const module = await import("@/components/ui/card")
|
||||
return module
|
||||
}
|
||||
|
||||
function lazyLoad() {
|
||||
return import("@/components/ui/dialog").then(module => module)
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform async/dynamic imports 2`] = `
|
||||
"import { Button } from "~/components/ui/button"
|
||||
|
||||
async function loadUtils() {
|
||||
const utils = await import("~/lib/utils")
|
||||
const { cn } = await import("~/lib/utils")
|
||||
return { utils, cn }
|
||||
}
|
||||
|
||||
const dialogPromise = import("~/components/ui/dialog")
|
||||
const cardModule = import("~/components/ui/card")
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform dynamic imports with cn utility 1`] = `
|
||||
"async function loadCn() {
|
||||
const { cn } = await import("@/lib/utils")
|
||||
return cn
|
||||
}
|
||||
|
||||
async function loadMultiple() {
|
||||
const utils1 = await import("@/lib/utils")
|
||||
const { cn, twMerge } = await import("@/lib/utils")
|
||||
const other = await import("@/lib/other")
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform dynamic imports with cn utility 2`] = `
|
||||
"async function loadWorkspaceCn() {
|
||||
const { cn } = await import("@workspace/lib/utils")
|
||||
return cn
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform import 1`] = `
|
||||
"import * as React from "react"
|
||||
import { Foo } from "bar"
|
||||
@@ -91,3 +143,14 @@ import { Foo } from "bar"
|
||||
import { cn } from "@repo/ui/lib/utils"
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`transform re-exports with dynamic imports 1`] = `
|
||||
"export { cn } from "@/lib/utils"
|
||||
export { Button } from "@/components/ui/button"
|
||||
|
||||
async function load() {
|
||||
const module = await import("@/components/ui/card")
|
||||
return module
|
||||
}
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -144,7 +144,6 @@ import { Foo } from "bar"
|
||||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
|
||||
test("transform import for monorepo", async () => {
|
||||
expect(
|
||||
await transform({
|
||||
@@ -196,3 +195,122 @@ import { Foo } from "bar"
|
||||
})
|
||||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test("transform async/dynamic imports", async () => {
|
||||
expect(
|
||||
await transform({
|
||||
filename: "test.ts",
|
||||
raw: `import * as React from "react"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
async function loadComponent() {
|
||||
const { cn } = await import("@/lib/utils")
|
||||
const module = await import("@/registry/new-york/ui/card")
|
||||
return module
|
||||
}
|
||||
|
||||
function lazyLoad() {
|
||||
return import("@/registry/new-york/ui/dialog").then(module => module)
|
||||
}
|
||||
`,
|
||||
config: {
|
||||
tsx: true,
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
utils: "@/lib/utils",
|
||||
},
|
||||
},
|
||||
})
|
||||
).toMatchSnapshot()
|
||||
|
||||
expect(
|
||||
await transform({
|
||||
filename: "test.ts",
|
||||
raw: `import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
async function loadUtils() {
|
||||
const utils = await import("@/lib/utils")
|
||||
const { cn } = await import("@/lib/utils")
|
||||
return { utils, cn }
|
||||
}
|
||||
|
||||
const dialogPromise = import("@/registry/new-york/ui/dialog")
|
||||
const cardModule = import("@/registry/new-york/ui/card")
|
||||
`,
|
||||
config: {
|
||||
tsx: true,
|
||||
aliases: {
|
||||
components: "~/components",
|
||||
utils: "~/lib/utils",
|
||||
},
|
||||
},
|
||||
})
|
||||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test("transform dynamic imports with cn utility", async () => {
|
||||
expect(
|
||||
await transform({
|
||||
filename: "test.ts",
|
||||
raw: `async function loadCn() {
|
||||
const { cn } = await import("@/lib/utils")
|
||||
return cn
|
||||
}
|
||||
|
||||
async function loadMultiple() {
|
||||
const utils1 = await import("@/lib/utils")
|
||||
const { cn, twMerge } = await import("@/lib/utils")
|
||||
const other = await import("@/lib/other")
|
||||
}
|
||||
`,
|
||||
config: {
|
||||
tsx: true,
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
utils: "@/lib/utils",
|
||||
},
|
||||
},
|
||||
})
|
||||
).toMatchSnapshot()
|
||||
|
||||
expect(
|
||||
await transform({
|
||||
filename: "test.ts",
|
||||
raw: `async function loadWorkspaceCn() {
|
||||
const { cn } = await import("@/lib/utils")
|
||||
return cn
|
||||
}
|
||||
`,
|
||||
config: {
|
||||
tsx: true,
|
||||
aliases: {
|
||||
components: "@workspace/ui/components",
|
||||
utils: "@workspace/ui/lib/utils",
|
||||
},
|
||||
},
|
||||
})
|
||||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test("transform re-exports with dynamic imports", async () => {
|
||||
expect(
|
||||
await transform({
|
||||
filename: "test.ts",
|
||||
raw: `export { cn } from "@/lib/utils"
|
||||
export { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
async function load() {
|
||||
const module = await import("@/registry/new-york/ui/card")
|
||||
return module
|
||||
}
|
||||
`,
|
||||
config: {
|
||||
tsx: true,
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
utils: "@/lib/utils",
|
||||
},
|
||||
},
|
||||
})
|
||||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
@@ -9,6 +9,7 @@ export default defineConfig({
|
||||
"**/fixtures/**",
|
||||
"**/templates/**",
|
||||
],
|
||||
testTimeout: 8000,
|
||||
},
|
||||
plugins: [
|
||||
tsconfigPaths({
|
||||
|
||||
Reference in New Issue
Block a user