mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-30 08:04:18 +00:00
feat(shadcn): resolve imports from anywhere (#7220)
* feat(shadcn): resolve imports from anywhere * fix: type errors * fix: add debug * feat: handle root paths * fix: src prefix * fix: tests * chore: changeset
This commit is contained in:
5
.changeset/funny-coins-remember.md
Normal file
5
.changeset/funny-coins-remember.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
resolve imports from anywhere
|
||||
@@ -1,4 +1,5 @@
|
||||
import { existsSync, promises as fs } from "fs"
|
||||
import { tmpdir } from "os"
|
||||
import path, { basename } from "path"
|
||||
import { getRegistryBaseColor } from "@/src/registry/api"
|
||||
import { RegistryItem, registryItemFileSchema } from "@/src/registry/schema"
|
||||
@@ -6,6 +7,7 @@ import { Config } from "@/src/utils/get-config"
|
||||
import { ProjectInfo, getProjectInfo } from "@/src/utils/get-project-info"
|
||||
import { highlighter } from "@/src/utils/highlighter"
|
||||
import { logger } from "@/src/utils/logger"
|
||||
import { resolveImport } from "@/src/utils/resolve-import"
|
||||
import { spinner } from "@/src/utils/spinner"
|
||||
import { transform } from "@/src/utils/transformers"
|
||||
import { transformCssVars } from "@/src/utils/transformers/transform-css-vars"
|
||||
@@ -14,6 +16,8 @@ import { transformImport } from "@/src/utils/transformers/transform-import"
|
||||
import { transformRsc } from "@/src/utils/transformers/transform-rsc"
|
||||
import { transformTwPrefixes } from "@/src/utils/transformers/transform-tw-prefix"
|
||||
import prompts from "prompts"
|
||||
import { Project, ScriptKind } from "ts-morph"
|
||||
import { loadConfig } from "tsconfig-paths"
|
||||
import { z } from "zod"
|
||||
|
||||
export async function updateFiles(
|
||||
@@ -50,9 +54,9 @@ export async function updateFiles(
|
||||
getRegistryBaseColor(config.tailwind.baseColor),
|
||||
])
|
||||
|
||||
const filesCreated = []
|
||||
const filesUpdated = []
|
||||
const filesSkipped = []
|
||||
let filesCreated: string[] = []
|
||||
let filesUpdated: string[] = []
|
||||
let filesSkipped: string[] = []
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.content) {
|
||||
@@ -153,11 +157,25 @@ export async function updateFiles(
|
||||
: filesCreated.push(path.relative(config.resolvedPaths.cwd, filePath))
|
||||
}
|
||||
|
||||
const allFiles = [...filesCreated, ...filesUpdated, ...filesSkipped]
|
||||
const updatedFiles = await resolveImports(allFiles, config)
|
||||
|
||||
// Let's update filesUpdated with the updated files.
|
||||
filesUpdated.push(...updatedFiles)
|
||||
|
||||
// If a file is in filesCreated and filesUpdated, we should remove it from filesUpdated.
|
||||
filesUpdated = filesUpdated.filter((file) => !filesCreated.includes(file))
|
||||
|
||||
const hasUpdatedFiles = filesCreated.length || filesUpdated.length
|
||||
if (!hasUpdatedFiles && !filesSkipped.length) {
|
||||
filesCreatedSpinner?.info("No files updated.")
|
||||
}
|
||||
|
||||
// Remove duplicates.
|
||||
filesCreated = Array.from(new Set(filesCreated))
|
||||
filesUpdated = Array.from(new Set(filesUpdated))
|
||||
filesSkipped = Array.from(new Set(filesSkipped))
|
||||
|
||||
if (filesCreated.length) {
|
||||
filesCreatedSpinner?.succeed(
|
||||
`Created ${filesCreated.length} ${
|
||||
@@ -371,3 +389,227 @@ export function resolvePageTarget(
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
async function resolveImports(filePaths: string[], config: Config) {
|
||||
const project = new Project({
|
||||
compilerOptions: {},
|
||||
})
|
||||
const projectInfo = await getProjectInfo(config.resolvedPaths.cwd)
|
||||
const tsConfig = await loadConfig(config.resolvedPaths.cwd)
|
||||
const updatedFiles = []
|
||||
|
||||
if (!projectInfo || tsConfig.resultType === "failed") {
|
||||
return []
|
||||
}
|
||||
|
||||
for (const filepath of filePaths) {
|
||||
const resolvedPath = path.resolve(config.resolvedPaths.cwd, filepath)
|
||||
|
||||
// Check if the file exists.
|
||||
if (!existsSync(resolvedPath)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const content = await fs.readFile(resolvedPath, "utf-8")
|
||||
|
||||
const dir = await fs.mkdtemp(path.join(tmpdir(), "shadcn-"))
|
||||
const sourceFile = project.createSourceFile(
|
||||
path.join(dir, basename(resolvedPath)),
|
||||
content,
|
||||
{
|
||||
scriptKind: ScriptKind.TSX,
|
||||
}
|
||||
)
|
||||
|
||||
const importDeclarations = sourceFile.getImportDeclarations()
|
||||
for (const importDeclaration of importDeclarations) {
|
||||
const moduleSpecifier = importDeclaration.getModuleSpecifierValue()
|
||||
|
||||
// Filter out non-local imports.
|
||||
if (
|
||||
projectInfo?.aliasPrefix &&
|
||||
!moduleSpecifier.startsWith(`${projectInfo.aliasPrefix}/`)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the probable import file path.
|
||||
// This is where we expect to find the file on disk.
|
||||
const probableImportFilePath = await resolveImport(
|
||||
moduleSpecifier,
|
||||
tsConfig
|
||||
)
|
||||
|
||||
if (!probableImportFilePath) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the actual import file path.
|
||||
// This is the path where the file has been installed.
|
||||
const resolvedImportFilePath = resolveModuleByProbablePath(
|
||||
probableImportFilePath,
|
||||
filePaths,
|
||||
config
|
||||
)
|
||||
|
||||
if (!resolvedImportFilePath) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert the resolved import file path to an aliased import.
|
||||
const newImport = toAliasedImport(
|
||||
resolvedImportFilePath,
|
||||
config,
|
||||
projectInfo
|
||||
)
|
||||
|
||||
if (!newImport || newImport === moduleSpecifier) {
|
||||
continue
|
||||
}
|
||||
|
||||
importDeclaration.setModuleSpecifier(newImport)
|
||||
|
||||
// Write the updated content to the file.
|
||||
await fs.writeFile(resolvedPath, sourceFile.getFullText(), "utf-8")
|
||||
|
||||
// Track the updated file.
|
||||
updatedFiles.push(filepath)
|
||||
}
|
||||
}
|
||||
|
||||
return updatedFiles
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an absolute "probable" import path (no ext),
|
||||
* plus an array of absolute file paths you already know about,
|
||||
* return 0–N matches (best match first), and also check disk for any missing ones.
|
||||
*/
|
||||
export function resolveModuleByProbablePath(
|
||||
probableImportFilePath: string,
|
||||
files: string[],
|
||||
config: Config,
|
||||
extensions: string[] = [".tsx", ".ts", ".js", ".jsx", ".css"]
|
||||
) {
|
||||
const cwd = path.normalize(config.resolvedPaths.cwd)
|
||||
|
||||
// 1) Build a set of POSIX-normalized, project-relative files
|
||||
const relativeFiles = files.map((f) => f.split(path.sep).join(path.posix.sep))
|
||||
const fileSet = new Set(relativeFiles)
|
||||
|
||||
// 2) Strip any existing extension off the absolute base path
|
||||
const extInPath = path.extname(probableImportFilePath)
|
||||
const hasExt = extInPath !== ""
|
||||
const absBase = hasExt
|
||||
? probableImportFilePath.slice(0, -extInPath.length)
|
||||
: probableImportFilePath
|
||||
|
||||
// 3) Compute the project-relative "base" directory for strong matching
|
||||
const relBaseRaw = path.relative(cwd, absBase)
|
||||
const relBase = relBaseRaw.split(path.sep).join(path.posix.sep)
|
||||
|
||||
// 4) Decide which extensions to try
|
||||
const tryExts = hasExt ? [extInPath] : extensions
|
||||
|
||||
// 5) Collect candidates
|
||||
const candidates = new Set<string>()
|
||||
|
||||
// 5a) Fast‑path: [base + ext] and [base/index + ext]
|
||||
for (const e of tryExts) {
|
||||
const absCand = absBase + e
|
||||
const relCand = path.posix.normalize(path.relative(cwd, absCand))
|
||||
if (fileSet.has(relCand) || existsSync(absCand)) {
|
||||
candidates.add(relCand)
|
||||
}
|
||||
|
||||
const absIdx = path.join(absBase, `index${e}`)
|
||||
const relIdx = path.posix.normalize(path.relative(cwd, absIdx))
|
||||
if (fileSet.has(relIdx) || existsSync(absIdx)) {
|
||||
candidates.add(relIdx)
|
||||
}
|
||||
}
|
||||
|
||||
// 5b) Fallback: scan known files by basename
|
||||
const name = path.basename(absBase)
|
||||
for (const f of relativeFiles) {
|
||||
if (tryExts.some((e) => f.endsWith(`/${name}${e}`))) {
|
||||
candidates.add(f)
|
||||
}
|
||||
}
|
||||
|
||||
// 6) If no matches, bail
|
||||
if (candidates.size === 0) return null
|
||||
|
||||
// 7) Sort by (1) extension priority, then (2) "strong" base match
|
||||
const sorted = Array.from(candidates).sort((a, b) => {
|
||||
// a) extension order
|
||||
const aExt = path.posix.extname(a)
|
||||
const bExt = path.posix.extname(b)
|
||||
const ord = tryExts.indexOf(aExt) - tryExts.indexOf(bExt)
|
||||
if (ord !== 0) return ord
|
||||
// b) strong match if path starts with relBase
|
||||
const aStrong = relBase && a.startsWith(relBase) ? -1 : 1
|
||||
const bStrong = relBase && b.startsWith(relBase) ? -1 : 1
|
||||
return aStrong - bStrong
|
||||
})
|
||||
|
||||
// 8) Return the first (best) candidate
|
||||
return sorted[0]
|
||||
}
|
||||
|
||||
export function toAliasedImport(
|
||||
filePath: string,
|
||||
config: Config,
|
||||
projectInfo: ProjectInfo
|
||||
): string | null {
|
||||
const abs = path.normalize(path.join(config.resolvedPaths.cwd, filePath))
|
||||
|
||||
// 1️⃣ Find the longest matching alias root in resolvedPaths
|
||||
// e.g. key="ui", root="/…/components/ui" beats key="components"
|
||||
const matches = Object.entries(config.resolvedPaths)
|
||||
.filter(
|
||||
([, root]) => root && abs.startsWith(path.normalize(root + path.sep))
|
||||
)
|
||||
.sort((a, b) => b[1].length - a[1].length)
|
||||
|
||||
if (matches.length === 0) {
|
||||
return null
|
||||
}
|
||||
const [aliasKey, rootDir] = matches[0]
|
||||
|
||||
// 2️⃣ Compute the path UNDER that root
|
||||
let rel = path.relative(rootDir, abs)
|
||||
// force POSIX-style separators
|
||||
rel = rel.split(path.sep).join("/") // e.g. "button/index.tsx"
|
||||
|
||||
// 3️⃣ Strip code-file extensions, keep others (css, json, etc.)
|
||||
const ext = path.posix.extname(rel)
|
||||
const codeExts = [".ts", ".tsx", ".js", ".jsx"]
|
||||
const keepExt = codeExts.includes(ext) ? "" : ext
|
||||
let noExt = rel.slice(0, rel.length - ext.length)
|
||||
|
||||
// 4️⃣ Collapse "/index" to its directory
|
||||
if (noExt.endsWith("/index")) {
|
||||
noExt = noExt.slice(0, -"/index".length)
|
||||
}
|
||||
|
||||
// 5️⃣ Build the aliased path
|
||||
// config.aliases[aliasKey] is e.g. "@/components/ui"
|
||||
const aliasBase =
|
||||
aliasKey === "cwd"
|
||||
? projectInfo.aliasPrefix
|
||||
: config.aliases[aliasKey as keyof typeof config.aliases]
|
||||
if (!aliasBase) {
|
||||
return null
|
||||
}
|
||||
// if noExt is empty (i.e. file was exactly at the root), we import the root
|
||||
let suffix = noExt === "" ? "" : `/${noExt}`
|
||||
|
||||
// Rremove /src from suffix.
|
||||
// Alias will handle this.
|
||||
suffix = suffix.replace("/src", "")
|
||||
|
||||
// 6️⃣ Prepend the prefix from projectInfo (e.g. "@") if needed
|
||||
// but usually config.aliases already include it.
|
||||
return `${aliasBase}${suffix}${keepExt}`
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { existsSync } from "fs"
|
||||
import path from "path"
|
||||
import { afterAll, afterEach, describe, expect, test, vi } from "vitest"
|
||||
|
||||
@@ -5,7 +6,9 @@ import { getConfig } from "../../../src/utils/get-config"
|
||||
import {
|
||||
findCommonRoot,
|
||||
resolveFilePath,
|
||||
resolveModuleByProbablePath,
|
||||
resolveNestedFilePath,
|
||||
toAliasedImport,
|
||||
updateFiles,
|
||||
} from "../../../src/utils/updaters/update-files"
|
||||
|
||||
@@ -809,3 +812,340 @@ return <div>Hello World</div>
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("resolveModuleByProbablePath", () => {
|
||||
test("should resolve exact file match in provided files list", () => {
|
||||
const files = [
|
||||
"components/button.tsx",
|
||||
"components/card.tsx",
|
||||
"lib/utils.ts",
|
||||
]
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
},
|
||||
}
|
||||
expect(
|
||||
resolveModuleByProbablePath("/foo/bar/components/button", files, config)
|
||||
).toBe("components/button.tsx")
|
||||
})
|
||||
|
||||
test("should resolve index file", () => {
|
||||
const files = ["components/button/index.tsx", "components/card.tsx"]
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
},
|
||||
}
|
||||
expect(
|
||||
resolveModuleByProbablePath("/foo/bar/components/button", files, config)
|
||||
).toBe("components/button/index.tsx")
|
||||
})
|
||||
|
||||
test("should try different extensions", () => {
|
||||
const files = ["components/button.jsx", "components/card.tsx"]
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
},
|
||||
}
|
||||
expect(
|
||||
resolveModuleByProbablePath("/foo/bar/components/button", files, config)
|
||||
).toBe("components/button.jsx")
|
||||
})
|
||||
|
||||
test("should fallback to basename matching", () => {
|
||||
const files = ["components/ui/button.tsx", "components/card.tsx"]
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
},
|
||||
}
|
||||
expect(
|
||||
resolveModuleByProbablePath("/foo/bar/components/button", files, config)
|
||||
).toBe("components/ui/button.tsx")
|
||||
})
|
||||
|
||||
test("should return null when file not found", () => {
|
||||
const files = ["components/card.tsx", "lib/utils.ts"]
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
},
|
||||
}
|
||||
expect(
|
||||
resolveModuleByProbablePath("/foo/bar/components/button", files, config)
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
test("should sort by extension priority", () => {
|
||||
const files = [
|
||||
"components/button.jsx",
|
||||
"components/button.tsx",
|
||||
"components/button.js",
|
||||
]
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
},
|
||||
}
|
||||
expect(
|
||||
resolveModuleByProbablePath("/foo/bar/components/button", files, config, [
|
||||
".tsx",
|
||||
".jsx",
|
||||
".js",
|
||||
])
|
||||
).toBe("components/button.tsx")
|
||||
})
|
||||
|
||||
test("should preserve extension if specified in path", () => {
|
||||
const files = ["components/button.tsx", "components/button.css"]
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
},
|
||||
}
|
||||
expect(
|
||||
resolveModuleByProbablePath(
|
||||
"/foo/bar/components/button.css",
|
||||
files,
|
||||
config
|
||||
)
|
||||
).toBe("components/button.css")
|
||||
})
|
||||
})
|
||||
|
||||
describe("toAliasedImport", () => {
|
||||
test("should convert components path to aliased import", () => {
|
||||
const filePath = "components/button.tsx"
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
},
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
ui: "@/components/ui",
|
||||
lib: "@/lib",
|
||||
},
|
||||
}
|
||||
const projectInfo = {
|
||||
aliasPrefix: "@",
|
||||
}
|
||||
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
|
||||
"@/components/button"
|
||||
)
|
||||
})
|
||||
|
||||
test("should convert ui path to aliased import", () => {
|
||||
const filePath = "components/ui/button.tsx"
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
},
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
ui: "@/components/ui",
|
||||
lib: "@/lib",
|
||||
},
|
||||
}
|
||||
const projectInfo = {
|
||||
aliasPrefix: "@",
|
||||
}
|
||||
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
|
||||
"@/components/ui/button"
|
||||
)
|
||||
})
|
||||
|
||||
test("should collapse index files", () => {
|
||||
const filePath = "components/ui/button/index.tsx"
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
},
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
ui: "@/components/ui",
|
||||
lib: "@/lib",
|
||||
},
|
||||
}
|
||||
const projectInfo = {
|
||||
aliasPrefix: "@",
|
||||
}
|
||||
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
|
||||
"@/components/ui/button"
|
||||
)
|
||||
})
|
||||
|
||||
test("should return null when no matching alias found", () => {
|
||||
const filePath = "src/pages/index.tsx"
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
},
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
ui: "@/components/ui",
|
||||
lib: "@/lib",
|
||||
},
|
||||
}
|
||||
const projectInfo = {
|
||||
aliasPrefix: "@",
|
||||
}
|
||||
expect(toAliasedImport(filePath, config, projectInfo)).toBe("@/pages")
|
||||
})
|
||||
|
||||
test("should handle nested directories", () => {
|
||||
const filePath = "components/forms/inputs/text-input.tsx"
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
},
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
ui: "@/components/ui",
|
||||
lib: "@/lib",
|
||||
},
|
||||
}
|
||||
const projectInfo = {
|
||||
aliasPrefix: "@",
|
||||
}
|
||||
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
|
||||
"@/components/forms/inputs/text-input"
|
||||
)
|
||||
})
|
||||
|
||||
test("should keep non-code file extensions", () => {
|
||||
const filePath = "components/styles/theme.css"
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
},
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
ui: "@/components/ui",
|
||||
lib: "@/lib",
|
||||
},
|
||||
}
|
||||
const projectInfo = {
|
||||
aliasPrefix: "@",
|
||||
}
|
||||
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
|
||||
"@/components/styles/theme.css"
|
||||
)
|
||||
})
|
||||
|
||||
test("should prefer longer matching paths", () => {
|
||||
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: "@/ui",
|
||||
},
|
||||
}
|
||||
const projectInfo = {
|
||||
aliasPrefix: "@",
|
||||
}
|
||||
expect(toAliasedImport(filePath, config, projectInfo)).toBe("@/ui/button")
|
||||
})
|
||||
|
||||
test("should support tilde (~) alias prefix", () => {
|
||||
const filePath = "components/button.tsx"
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
},
|
||||
aliases: {
|
||||
components: "~components",
|
||||
},
|
||||
}
|
||||
const projectInfo = {
|
||||
aliasPrefix: "~",
|
||||
}
|
||||
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
|
||||
"~components/button"
|
||||
)
|
||||
})
|
||||
|
||||
test("should support @shadcn alias prefix", () => {
|
||||
const filePath = "components/ui/button.tsx"
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
},
|
||||
aliases: {
|
||||
components: "@shadcn/components",
|
||||
ui: "@shadcn/ui",
|
||||
},
|
||||
}
|
||||
const projectInfo = {
|
||||
aliasPrefix: "@shadcn",
|
||||
}
|
||||
expect(toAliasedImport(filePath, config, projectInfo)).toBe(
|
||||
"@shadcn/ui/button"
|
||||
)
|
||||
})
|
||||
|
||||
test("should support ~cn alias prefix", () => {
|
||||
const filePath = "lib/utils/index.tsx"
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
lib: "/foo/bar/lib",
|
||||
},
|
||||
aliases: {
|
||||
lib: "~cn/lib",
|
||||
},
|
||||
}
|
||||
const projectInfo = {
|
||||
aliasPrefix: "~cn",
|
||||
}
|
||||
expect(toAliasedImport(filePath, config, projectInfo)).toBe("~cn/lib/utils")
|
||||
})
|
||||
|
||||
test("should use project alias prefix when aliasKey is cwd", () => {
|
||||
const filePath = "src/pages/home.tsx"
|
||||
const config = {
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
},
|
||||
aliases: {
|
||||
components: "@/components",
|
||||
ui: "@/components/ui",
|
||||
lib: "@/lib",
|
||||
},
|
||||
}
|
||||
const projectInfo = {
|
||||
aliasPrefix: "@",
|
||||
}
|
||||
expect(toAliasedImport(filePath, config, projectInfo)).toBe("@/pages/home")
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user