UNPKG

@feature-sliced/filesystem

Version:

A set of utilities for locating and working with FSD roots in the file system.

109 lines (104 loc) 2.93 kB
import { sep } from "node:path"; import ts from "typescript"; import type { CompilerOptions } from "typescript"; /** * Given a file name, an imported path, and a TSConfig object, produce a path to the imported file, relative to TypeScript's `baseUrl`. * * The resulting path uses OS-appropriate path separators. * * @example * ```tsx * // ./src/pages/home/ui/HomePage.tsx * import { Button } from "~/shared/ui"; * ``` * * ```json * // ./tsconfig.json * { * "compilerOptions": { * "moduleResolution": "Bundler", * "baseUrl": ".", * "paths": { * "~/*": ["./src/*"], * }, * }, * } * ``` * * ```tsx * resolveImport( * "~/shared/ui", * "./src/pages/home/ui/HomePage.tsx", * { moduleResolution: "Bundler", baseUrl: ".", paths: { "~/*": ["./src/*"] } }, * fs.existsSync * ); * ``` * Expected output: `src/shared/ui/index.ts` */ export function resolveImport( importedPath: string, importerPath: string, tsCompilerOptions: ImperfectCompilerOptions, fileExists: (path: string) => boolean, directoryExists?: (path: string) => boolean, ): string | null { return ( ts .resolveModuleName( importedPath, importerPath, normalizeCompilerOptions(tsCompilerOptions), { ...ts.sys, fileExists, directoryExists, }, ) .resolvedModule?.resolvedFileName?.replaceAll("/", sep) ?? null ); } const imperfectKeys = { module: ts.ModuleKind, moduleResolution: { ...ts.ModuleResolutionKind, node: ts.ModuleResolutionKind.Node10, }, moduleDetection: ts.ModuleDetectionKind, newLine: ts.NewLineKind, target: ts.ScriptTarget, }; /** TypeScript has a few fields which have values from an internal enum, and refuses to take the literal values from the tsconfig.json. */ function normalizeCompilerOptions( compilerOptions: ImperfectCompilerOptions, ): CompilerOptions { return Object.fromEntries( Object.entries(compilerOptions).map(([key, value]) => { if ( Object.keys(imperfectKeys).includes(key) && typeof value === "string" ) { for (const [enumKey, enumValue] of Object.entries( imperfectKeys[key as keyof typeof imperfectKeys], )) { if (enumKey.toLowerCase() === value.toLowerCase()) { return [key, enumValue]; } } } return [key, value]; }), ) as CompilerOptions; } export interface ImperfectCompilerOptions extends Omit<CompilerOptions, keyof typeof imperfectKeys> { module?: ts.ModuleKind | keyof typeof ts.ModuleKind; moduleResolution?: | ts.ModuleResolutionKind | keyof typeof ts.ModuleResolutionKind | "node"; moduleDetection?: | ts.ModuleDetectionKind | keyof typeof ts.ModuleDetectionKind; newLine?: ts.NewLineKind | keyof typeof ts.NewLineKind; target?: ts.ScriptTarget | keyof typeof ts.ScriptTarget; }