mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-07-01 00:24:20 +00:00
Compare commits
6 Commits
@shadcn/re
...
shadcn/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ef12b0e09 | ||
|
|
801e7d6b3d | ||
|
|
5fad0832c6 | ||
|
|
1c73a831ae | ||
|
|
2b62c90dae | ||
|
|
c753e32471 |
@@ -157,7 +157,7 @@ Here are some guidelines to follow when building components for a registry.
|
|||||||
- It is recommended to add a proper name and description to your registry item. This helps LLMs understand the component and its purpose.
|
- It is recommended to add a proper name and description to your registry item. This helps LLMs understand the component and its purpose.
|
||||||
- Make sure to list all registry dependencies in `registryDependencies`. A registry dependency is the name of the component in the registry eg. `input`, `button`, `card`, etc or a URL to a registry item eg. `http://localhost:3000/r/editor.json`.
|
- Make sure to list all registry dependencies in `registryDependencies`. A registry dependency is the name of the component in the registry eg. `input`, `button`, `card`, etc or a URL to a registry item eg. `http://localhost:3000/r/editor.json`.
|
||||||
- Make sure to list all dependencies in `dependencies`. A dependency is the name of the package in the registry eg. `zod`, `sonner`, etc. To set a version, you can use the `name@version` format eg. `zod@^3.20.0`.
|
- Make sure to list all dependencies in `dependencies`. A dependency is the name of the package in the registry eg. `zod`, `sonner`, etc. To set a version, you can use the `name@version` format eg. `zod@^3.20.0`.
|
||||||
- **Imports should always use the `@/registry` path.** eg. `import { HelloWorld } from "@/registry/new-york/hello-world/hello-world"`
|
- **Imports should always use the `@/registry` or `#registry` path.** eg. `import { HelloWorld } from "@/registry/new-york/hello-world/hello-world"` or `import { HelloWorld } from "#registry/new-york/hello-world/hello-world.ts"`
|
||||||
- Ideally, place your files within a registry item in `components`, `hooks`, `lib` directories.
|
- Ideally, place your files within a registry item in `components`, `hooks`, `lib` directories.
|
||||||
|
|
||||||
## Install using the CLI
|
## Install using the CLI
|
||||||
|
|||||||
@@ -209,7 +209,6 @@ function TeamSwitcher({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarGroup>
|
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -269,7 +268,6 @@ function TeamSwitcher({
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroup>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,7 +393,6 @@ function NavUser({
|
|||||||
const { isMobile } = useSidebar()
|
const { isMobile } = useSidebar()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarGroup>
|
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -470,7 +467,6 @@ function NavUser({
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroup>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -326,11 +326,10 @@ export default function SidebarIconExample() {
|
|||||||
key={item.title}
|
key={item.title}
|
||||||
defaultOpen={item.isActive}
|
defaultOpen={item.isActive}
|
||||||
className="group/collapsible"
|
className="group/collapsible"
|
||||||
render={<SidebarMenuItem />}
|
|
||||||
>
|
>
|
||||||
<SidebarMenuButton
|
<SidebarMenuItem>
|
||||||
tooltip={item.title}
|
<CollapsibleTrigger
|
||||||
render={<CollapsibleTrigger />}
|
render={<SidebarMenuButton tooltip={item.title} />}
|
||||||
>
|
>
|
||||||
{item.icon}
|
{item.icon}
|
||||||
<span>{item.title}</span>
|
<span>{item.title}</span>
|
||||||
@@ -342,7 +341,7 @@ export default function SidebarIconExample() {
|
|||||||
remixicon="RiArrowRightSLine"
|
remixicon="RiArrowRightSLine"
|
||||||
className="ml-auto transition-transform duration-100 group-data-open/collapsible:rotate-90"
|
className="ml-auto transition-transform duration-100 group-data-open/collapsible:rotate-90"
|
||||||
/>
|
/>
|
||||||
</SidebarMenuButton>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<SidebarMenuSub>
|
<SidebarMenuSub>
|
||||||
{item.items?.map((subItem) => (
|
{item.items?.map((subItem) => (
|
||||||
@@ -356,6 +355,7 @@ export default function SidebarIconExample() {
|
|||||||
))}
|
))}
|
||||||
</SidebarMenuSub>
|
</SidebarMenuSub>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
|
</SidebarMenuItem>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
))}
|
))}
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
|
|||||||
@@ -119,9 +119,9 @@ export async function recursivelyResolveFileImports(
|
|||||||
const moduleSpecifier = importStatement.getModuleSpecifierValue()
|
const moduleSpecifier = importStatement.getModuleSpecifierValue()
|
||||||
|
|
||||||
const isRelativeImport = moduleSpecifier.startsWith(".")
|
const isRelativeImport = moduleSpecifier.startsWith(".")
|
||||||
const isAliasImport = moduleSpecifier.startsWith(
|
const isAliasImport =
|
||||||
`${projectInfo.aliasPrefix}/`
|
moduleSpecifier.startsWith(`${projectInfo.aliasPrefix}/`) ||
|
||||||
)
|
moduleSpecifier.startsWith("#")
|
||||||
|
|
||||||
// If not a local import, add to the dependencies array.
|
// If not a local import, add to the dependencies array.
|
||||||
if (!isAliasImport && !isRelativeImport) {
|
if (!isAliasImport && !isRelativeImport) {
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
|
import { readFileSync } from "fs"
|
||||||
|
import path from "path"
|
||||||
import { createMatchPath, type ConfigLoaderSuccessResult } from "tsconfig-paths"
|
import { createMatchPath, type ConfigLoaderSuccessResult } from "tsconfig-paths"
|
||||||
|
|
||||||
export async function resolveImport(
|
export async function resolveImport(
|
||||||
importPath: string,
|
importPath: string,
|
||||||
config: Pick<ConfigLoaderSuccessResult, "absoluteBaseUrl" | "paths">
|
config: Pick<ConfigLoaderSuccessResult, "absoluteBaseUrl" | "paths">
|
||||||
) {
|
) {
|
||||||
|
// Handle subpath imports (#) by resolving from package.json imports field.
|
||||||
|
if (importPath.startsWith("#")) {
|
||||||
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
const resolved = import.meta.resolve(importPath, import.meta.url)
|
||||||
|
if (resolved) return resolved
|
||||||
|
} catch {
|
||||||
|
// If native resolution fails, fallback to manual resolution.
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveSubpathImport(importPath, config.absoluteBaseUrl)
|
||||||
|
}
|
||||||
|
|
||||||
return createMatchPath(config.absoluteBaseUrl, config.paths)(
|
return createMatchPath(config.absoluteBaseUrl, config.paths)(
|
||||||
importPath,
|
importPath,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -11,3 +26,80 @@ export async function resolveImport(
|
|||||||
[".ts", ".tsx", ".jsx", ".js", ".css"]
|
[".ts", ".tsx", ".jsx", ".js", ".css"]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveSubpathImport(
|
||||||
|
importPath: string,
|
||||||
|
baseDir: string
|
||||||
|
): string | null {
|
||||||
|
let packageJson: { imports?: Record<string, unknown> }
|
||||||
|
try {
|
||||||
|
packageJson = JSON.parse(
|
||||||
|
readFileSync(path.resolve(baseDir, "package.json"), "utf-8")
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const imports = packageJson.imports
|
||||||
|
if (!imports || typeof imports !== "object") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try exact match first.
|
||||||
|
if (importPath in imports) {
|
||||||
|
const resolved = resolveImportTarget(imports[importPath])
|
||||||
|
if (resolved) {
|
||||||
|
return path.resolve(baseDir, resolved)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try wildcard patterns (most specific / longest pattern first).
|
||||||
|
const patterns = Object.keys(imports)
|
||||||
|
.filter((p) => p.includes("*"))
|
||||||
|
.sort((a, b) => b.length - a.length)
|
||||||
|
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
const [prefix, suffix] = pattern.split("*")
|
||||||
|
if (
|
||||||
|
importPath.startsWith(prefix) &&
|
||||||
|
(suffix === "" || importPath.endsWith(suffix))
|
||||||
|
) {
|
||||||
|
const wildcard = suffix
|
||||||
|
? importPath.slice(prefix.length, -suffix.length)
|
||||||
|
: importPath.slice(prefix.length)
|
||||||
|
const target = resolveImportTarget(imports[pattern])
|
||||||
|
if (target) {
|
||||||
|
return path.resolve(baseDir, target.replace(/\*/g, wildcard))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve a conditional import target to the first local path.
|
||||||
|
function resolveImportTarget(target: unknown): string | null {
|
||||||
|
if (typeof target === "string") {
|
||||||
|
return target.startsWith(".") ? target : null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(target)) {
|
||||||
|
for (const item of target) {
|
||||||
|
const resolved = resolveImportTarget(item)
|
||||||
|
if (resolved) return resolved
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target && typeof target === "object") {
|
||||||
|
// Iterate conditions in order, return the first local path.
|
||||||
|
for (const value of Object.values(target as Record<string, unknown>)) {
|
||||||
|
const resolved = resolveImportTarget(value)
|
||||||
|
if (resolved) return resolved
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { describe, expect, test } from "vitest"
|
||||||
|
|
||||||
|
import { transform } from "."
|
||||||
|
import { createConfig } from "../get-config"
|
||||||
|
import { transformImport } from "./transform-import"
|
||||||
|
|
||||||
|
describe("transformImport", () => {
|
||||||
|
describe("subpath imports in source files", () => {
|
||||||
|
const testConfig = createConfig({
|
||||||
|
aliases: {
|
||||||
|
components: "@/components",
|
||||||
|
ui: "@/components/ui",
|
||||||
|
utils: "@/lib/utils",
|
||||||
|
lib: "@/lib",
|
||||||
|
hooks: "@/hooks",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
["#/registry/new-york/ui/button", "@/components/ui/button"],
|
||||||
|
["#registry/new-york/ui/button", "@/components/ui/button"],
|
||||||
|
["#/registry/new-york/hooks/use-hook", "@/hooks/use-hook"],
|
||||||
|
["#/registry/new-york/lib/helper", "@/lib/helper"],
|
||||||
|
["#/registry/new-york/components/card", "@/components/card"],
|
||||||
|
["#/lib/foo", "@/lib/foo"],
|
||||||
|
["#/lib/utils", "@/lib/utils"],
|
||||||
|
["#lib/utils", "@/lib/utils"],
|
||||||
|
["#lib/foo", "@/lib/foo"],
|
||||||
|
["#hooks/use-hook", "@/hooks/use-hook"],
|
||||||
|
["react", "react"],
|
||||||
|
])("%s → %s", async (input, expected) => {
|
||||||
|
const result = await transform(
|
||||||
|
{
|
||||||
|
filename: "test.tsx",
|
||||||
|
raw: `import x from "${input}"`,
|
||||||
|
config: testConfig,
|
||||||
|
},
|
||||||
|
[transformImport]
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toContain(`"${expected}"`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("user config with # aliases", () => {
|
||||||
|
const hashConfig = createConfig({
|
||||||
|
aliases: {
|
||||||
|
components: "#/src/components",
|
||||||
|
ui: "#/src/components/ui",
|
||||||
|
utils: "#/src/utils",
|
||||||
|
lib: "#/src/lib",
|
||||||
|
hooks: "#/src/hooks",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
["@/registry/new-york/ui/button", "#/src/components/ui/button"],
|
||||||
|
["@/registry/new-york/hooks/use-hook", "#/src/hooks/use-hook"],
|
||||||
|
["@/registry/new-york/components/card", "#/src/components/card"],
|
||||||
|
["@/config/foo", "#/config/foo"],
|
||||||
|
["#/registry/new-york/ui/button", "#/src/components/ui/button"],
|
||||||
|
["#registry/new-york/ui/button", "#/src/components/ui/button"],
|
||||||
|
])("%s → %s", async (input, expected) => {
|
||||||
|
const result = await transform(
|
||||||
|
{
|
||||||
|
filename: "test.tsx",
|
||||||
|
raw: `import x from "${input}"`,
|
||||||
|
config: hashConfig,
|
||||||
|
},
|
||||||
|
[transformImport]
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toContain(`"${expected}"`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// cn import requires named import to trigger utils alias rewrite.
|
||||||
|
test("@/lib/utils (cn) → #/src/utils", async () => {
|
||||||
|
const result = await transform(
|
||||||
|
{
|
||||||
|
filename: "test.tsx",
|
||||||
|
raw: `import { cn } from "@/lib/utils"`,
|
||||||
|
config: hashConfig,
|
||||||
|
},
|
||||||
|
[transformImport]
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toContain(`"#/src/utils"`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -55,6 +55,13 @@ function updateImportAliases(
|
|||||||
config: Config,
|
config: Config,
|
||||||
isRemote: boolean = false
|
isRemote: boolean = false
|
||||||
) {
|
) {
|
||||||
|
// Normalize subpath imports (#) to @/ prefix.
|
||||||
|
if (moduleSpecifier.startsWith("#/")) {
|
||||||
|
moduleSpecifier = moduleSpecifier.replace(/^#\//, "@/")
|
||||||
|
} else if (moduleSpecifier.startsWith("#")) {
|
||||||
|
moduleSpecifier = moduleSpecifier.replace(/^#/, "@/")
|
||||||
|
}
|
||||||
|
|
||||||
// Not a local import.
|
// Not a local import.
|
||||||
if (!moduleSpecifier.startsWith("@/") && !isRemote) {
|
if (!moduleSpecifier.startsWith("@/") && !isRemote) {
|
||||||
return moduleSpecifier
|
return moduleSpecifier
|
||||||
|
|||||||
@@ -550,9 +550,11 @@ async function resolveImports(filePaths: string[], config: Config) {
|
|||||||
const moduleSpecifier = importDeclaration.getModuleSpecifierValue()
|
const moduleSpecifier = importDeclaration.getModuleSpecifierValue()
|
||||||
|
|
||||||
// Filter out non-local imports.
|
// Filter out non-local imports.
|
||||||
|
// Also accept subpath imports (#) as local.
|
||||||
if (
|
if (
|
||||||
projectInfo?.aliasPrefix &&
|
projectInfo?.aliasPrefix &&
|
||||||
!moduleSpecifier.startsWith(`${projectInfo.aliasPrefix}/`)
|
!moduleSpecifier.startsWith(`${projectInfo.aliasPrefix}/`) &&
|
||||||
|
!moduleSpecifier.startsWith("#")
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -707,9 +709,14 @@ export function toAliasedImport(
|
|||||||
rel = rel.split(path.sep).join("/") // e.g. "button/index.tsx"
|
rel = rel.split(path.sep).join("/") // e.g. "button/index.tsx"
|
||||||
|
|
||||||
// 3️⃣ Strip code-file extensions, keep others (css, json, etc.)
|
// 3️⃣ Strip code-file extensions, keep others (css, json, etc.)
|
||||||
|
// Subpath imports (#) require explicit file extensions.
|
||||||
const ext = path.posix.extname(rel)
|
const ext = path.posix.extname(rel)
|
||||||
const codeExts = [".ts", ".tsx", ".js", ".jsx"]
|
const codeExts = [".ts", ".tsx", ".js", ".jsx"]
|
||||||
const keepExt = codeExts.includes(ext) ? "" : ext
|
const isSubpathImport =
|
||||||
|
aliasKey === "cwd"
|
||||||
|
? projectInfo.aliasPrefix?.startsWith("#")
|
||||||
|
: config.aliases[aliasKey as keyof typeof config.aliases]?.startsWith("#")
|
||||||
|
const keepExt = codeExts.includes(ext) && !isSubpathImport ? "" : ext
|
||||||
let noExt = rel.slice(0, rel.length - ext.length)
|
let noExt = rel.slice(0, rel.length - ext.length)
|
||||||
|
|
||||||
// 4️⃣ Collapse "/index" to its directory
|
// 4️⃣ Collapse "/index" to its directory
|
||||||
|
|||||||
13
packages/shadcn/test/fixtures/with-subpath-imports/package.json
vendored
Normal file
13
packages/shadcn/test/fixtures/with-subpath-imports/package.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "test-subpath-imports",
|
||||||
|
"type": "module",
|
||||||
|
"imports": {
|
||||||
|
"#src/*": "./src/*",
|
||||||
|
"#components/*": "./src/components/*",
|
||||||
|
"#hooks": "./src/hooks/index.ts",
|
||||||
|
"#dep": {
|
||||||
|
"node": "dep-node-native",
|
||||||
|
"default": "./dep-polyfill.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import { loadConfig, type ConfigLoaderSuccessResult } from "tsconfig-paths"
|
import { loadConfig, type ConfigLoaderSuccessResult } from "tsconfig-paths"
|
||||||
import { expect, test } from "vitest"
|
import { describe, expect, test } from "vitest"
|
||||||
|
|
||||||
import { resolveImport } from "../../src/utils/resolve-import"
|
import { resolveImport } from "../../src/utils/resolve-import"
|
||||||
|
|
||||||
@@ -79,3 +79,41 @@ test("resolve import without base url", async () => {
|
|||||||
path.resolve(cwd, "foo/bar")
|
path.resolve(cwd, "foo/bar")
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("resolve subpath imports", () => {
|
||||||
|
const cwd = path.resolve(__dirname, "../fixtures/with-subpath-imports")
|
||||||
|
const config = {
|
||||||
|
absoluteBaseUrl: cwd,
|
||||||
|
paths: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should resolve wildcard subpath import", async () => {
|
||||||
|
expect(await resolveImport("#src/components/ui", config)).toEqual(
|
||||||
|
path.resolve(cwd, "src/components/ui")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should resolve more specific wildcard pattern", async () => {
|
||||||
|
expect(await resolveImport("#components/button", config)).toEqual(
|
||||||
|
path.resolve(cwd, "src/components/button")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should resolve exact match subpath import", async () => {
|
||||||
|
expect(await resolveImport("#hooks", config)).toEqual(
|
||||||
|
path.resolve(cwd, "src/hooks/index.ts")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should resolve conditional subpath import to first local path", async () => {
|
||||||
|
// #dep has { "node": "dep-node-native", "default": "./dep-polyfill.js" }
|
||||||
|
// Should skip "dep-node-native" (not local) and pick "./dep-polyfill.js"
|
||||||
|
expect(await resolveImport("#dep", config)).toEqual(
|
||||||
|
path.resolve(cwd, "dep-polyfill.js")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should return null for unmatched subpath import", async () => {
|
||||||
|
expect(await resolveImport("#nonexistent/foo", config)).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -1968,4 +1968,84 @@ describe("toAliasedImport", () => {
|
|||||||
}
|
}
|
||||||
expect(toAliasedImport(filePath, config, projectInfo)).toBe("@/pages/home")
|
expect(toAliasedImport(filePath, config, projectInfo)).toBe("@/pages/home")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("should keep code extension for # subpath import alias", () => {
|
||||||
|
const filePath = "components/ui/button.tsx"
|
||||||
|
const config = {
|
||||||
|
resolvedPaths: {
|
||||||
|
cwd: "/foo/bar",
|
||||||
|
components: "/foo/bar/components",
|
||||||
|
ui: "/foo/bar/components/ui",
|
||||||
|
},
|
||||||
|
aliases: {
|
||||||
|
components: "#/src/components",
|
||||||
|
ui: "#/src/components/ui",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const projectInfo = {
|
||||||
|
aliasPrefix: "#",
|
||||||
|
}
|
||||||
|
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
|
||||||
|
"#/src/components/ui/button.tsx"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should keep .ts extension for # subpath import alias", () => {
|
||||||
|
const filePath = "lib/utils.ts"
|
||||||
|
const config = {
|
||||||
|
resolvedPaths: {
|
||||||
|
cwd: "/foo/bar",
|
||||||
|
lib: "/foo/bar/lib",
|
||||||
|
},
|
||||||
|
aliases: {
|
||||||
|
lib: "#/src/lib",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const projectInfo = {
|
||||||
|
aliasPrefix: "#",
|
||||||
|
}
|
||||||
|
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
|
||||||
|
"#/src/lib/utils.ts"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should still strip extension for @ alias (existing behavior)", () => {
|
||||||
|
const filePath = "components/ui/button.tsx"
|
||||||
|
const config = {
|
||||||
|
resolvedPaths: {
|
||||||
|
cwd: "/foo/bar",
|
||||||
|
components: "/foo/bar/components",
|
||||||
|
ui: "/foo/bar/components/ui",
|
||||||
|
},
|
||||||
|
aliases: {
|
||||||
|
components: "@/components",
|
||||||
|
ui: "@/components/ui",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const projectInfo = {
|
||||||
|
aliasPrefix: "@",
|
||||||
|
}
|
||||||
|
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
|
||||||
|
"@/components/ui/button"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should keep css extension for # alias (non-code ext always kept)", () => {
|
||||||
|
const filePath = "components/styles/theme.css"
|
||||||
|
const config = {
|
||||||
|
resolvedPaths: {
|
||||||
|
cwd: "/foo/bar",
|
||||||
|
components: "/foo/bar/components",
|
||||||
|
},
|
||||||
|
aliases: {
|
||||||
|
components: "#/src/components",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const projectInfo = {
|
||||||
|
aliasPrefix: "#",
|
||||||
|
}
|
||||||
|
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
|
||||||
|
"#/src/components/styles/theme.css"
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user