Skip to content

Commit 086fd85

Browse files
fix: normalize generate-types specifiers via tsconfig context. (#28)
1 parent a79242d commit 086fd85

5 files changed

Lines changed: 754 additions & 9 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/css/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@knighted/css",
3-
"version": "1.0.0-rc.9",
3+
"version": "1.0.0-rc.10",
44
"description": "A build-time utility that traverses JavaScript/TypeScript module dependency graphs to extract, compile, and optimize all imported CSS into a single, in-memory string.",
55
"type": "module",
66
"main": "./dist/css.js",

packages/css/src/generateTypes.ts

Lines changed: 143 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import { fileURLToPath, pathToFileURL } from 'node:url'
77
import { init, parse } from 'es-module-lexer'
88
import { moduleType } from 'node-module-type'
99

10+
import { getTsconfig, type TsConfigResult } from 'get-tsconfig'
11+
import { createMatchPath, type MatchPath } from 'tsconfig-paths'
12+
1013
import { cssWithMeta } from './css.js'
1114
import {
1215
determineSelectorVariant,
@@ -34,12 +37,22 @@ interface DeclarationRecord {
3437
filePath: string
3538
}
3639

40+
interface TsconfigResolutionContext {
41+
absoluteBaseUrl?: string
42+
matchPath?: MatchPath
43+
}
44+
45+
type CssWithMetaFn = typeof cssWithMeta
46+
47+
let activeCssWithMeta: CssWithMetaFn = cssWithMeta
48+
3749
interface GenerateTypesInternalOptions {
3850
rootDir: string
3951
include: string[]
4052
outDir: string
4153
typesRoot: string
4254
stableNamespace?: string
55+
tsconfig?: TsconfigResolutionContext
4356
}
4457

4558
export interface GenerateTypesResult {
@@ -84,12 +97,17 @@ const SUPPORTED_EXTENSIONS = new Set([
8497
'.cjs',
8598
])
8699

100+
type ModuleTypeDetector = () => ReturnType<typeof moduleType>
101+
102+
let moduleTypeDetector: ModuleTypeDetector = moduleType
103+
let importMetaUrlProvider: () => string | undefined = getImportMetaUrl
104+
87105
function resolvePackageRoot(): string {
88-
const detectedType = moduleType()
106+
const detectedType = moduleTypeDetector()
89107
if (detectedType === 'commonjs' && typeof __dirname === 'string') {
90108
return path.resolve(__dirname, '..')
91109
}
92-
const moduleUrl = getImportMetaUrl()
110+
const moduleUrl = importMetaUrlProvider()
93111
if (moduleUrl) {
94112
return path.resolve(path.dirname(fileURLToPath(moduleUrl)), '..')
95113
}
@@ -115,6 +133,7 @@ export async function generateTypes(
115133
const include = normalizeIncludeOptions(options.include, rootDir)
116134
const outDir = path.resolve(options.outDir ?? DEFAULT_OUT_DIR)
117135
const typesRoot = path.resolve(options.typesRoot ?? DEFAULT_TYPES_ROOT)
136+
const tsconfig = loadTsconfigResolutionContext(rootDir)
118137
await init
119138
await fs.mkdir(outDir, { recursive: true })
120139
await fs.mkdir(typesRoot, { recursive: true })
@@ -125,6 +144,7 @@ export async function generateTypes(
125144
outDir,
126145
typesRoot,
127146
stableNamespace: options.stableNamespace,
147+
tsconfig,
128148
}
129149

130150
return generateDeclarations(internalOptions)
@@ -162,6 +182,7 @@ async function generateDeclarations(
162182
resource,
163183
match.importer,
164184
options.rootDir,
185+
options.tsconfig,
165186
)
166187
if (!resolvedPath) {
167188
warnings.push(
@@ -174,7 +195,7 @@ async function generateDeclarations(
174195
let selectorMap = selectorCache.get(cacheKey)
175196
if (!selectorMap) {
176197
try {
177-
const { css } = await cssWithMeta(resolvedPath, {
198+
const { css } = await activeCssWithMeta(resolvedPath, {
178199
cwd: options.rootDir,
179200
peerResolver,
180201
})
@@ -342,6 +363,7 @@ async function resolveImportPath(
342363
resourceSpecifier: string,
343364
importerPath: string,
344365
rootDir: string,
366+
tsconfig?: TsconfigResolutionContext,
345367
): Promise<string | undefined> {
346368
if (!resourceSpecifier) return undefined
347369
if (resourceSpecifier.startsWith('.')) {
@@ -350,6 +372,10 @@ async function resolveImportPath(
350372
if (resourceSpecifier.startsWith('/')) {
351373
return path.resolve(rootDir, resourceSpecifier.slice(1))
352374
}
375+
const tsconfigResolved = await resolveWithTsconfigPaths(resourceSpecifier, tsconfig)
376+
if (tsconfigResolved) {
377+
return tsconfigResolved
378+
}
353379
const requireFromRoot = getProjectRequire(rootDir)
354380
try {
355381
return requireFromRoot.resolve(resourceSpecifier)
@@ -491,6 +517,93 @@ async function fileExists(target: string): Promise<boolean> {
491517
}
492518
}
493519

520+
async function resolveWithTsconfigPaths(
521+
specifier: string,
522+
tsconfig?: TsconfigResolutionContext,
523+
): Promise<string | undefined> {
524+
if (!tsconfig) {
525+
return undefined
526+
}
527+
if (tsconfig.matchPath) {
528+
const matched = tsconfig.matchPath(specifier)
529+
if (matched && (await fileExists(matched))) {
530+
return matched
531+
}
532+
}
533+
if (tsconfig.absoluteBaseUrl && isNonRelativeSpecifier(specifier)) {
534+
const candidate = path.join(
535+
tsconfig.absoluteBaseUrl,
536+
specifier.split('/').join(path.sep),
537+
)
538+
if (await fileExists(candidate)) {
539+
return candidate
540+
}
541+
}
542+
return undefined
543+
}
544+
545+
function loadTsconfigResolutionContext(
546+
rootDir: string,
547+
loader: typeof getTsconfig = getTsconfig,
548+
): TsconfigResolutionContext | undefined {
549+
let result: TsConfigResult | null
550+
try {
551+
result = loader(rootDir) as TsConfigResult | null
552+
} catch {
553+
return undefined
554+
}
555+
if (!result) {
556+
return undefined
557+
}
558+
const compilerOptions = result.config.compilerOptions ?? {}
559+
const configDir = path.dirname(result.path)
560+
const absoluteBaseUrl = compilerOptions.baseUrl
561+
? path.resolve(configDir, compilerOptions.baseUrl)
562+
: undefined
563+
const normalizedPaths = normalizeTsconfigPaths(compilerOptions.paths)
564+
const matchPath =
565+
absoluteBaseUrl && normalizedPaths
566+
? createMatchPath(absoluteBaseUrl, normalizedPaths)
567+
: undefined
568+
if (!absoluteBaseUrl && !matchPath) {
569+
return undefined
570+
}
571+
return { absoluteBaseUrl, matchPath }
572+
}
573+
574+
function normalizeTsconfigPaths(
575+
paths: Record<string, string[] | string> | undefined,
576+
): Record<string, string[]> | undefined {
577+
if (!paths) {
578+
return undefined
579+
}
580+
const normalized: Record<string, string[]> = {}
581+
for (const [pattern, replacements] of Object.entries(paths)) {
582+
if (!replacements) {
583+
continue
584+
}
585+
const values = Array.isArray(replacements) ? replacements : [replacements]
586+
if (values.length === 0) {
587+
continue
588+
}
589+
normalized[pattern] = values
590+
}
591+
return Object.keys(normalized).length > 0 ? normalized : undefined
592+
}
593+
594+
function isNonRelativeSpecifier(specifier: string): boolean {
595+
if (!specifier) {
596+
return false
597+
}
598+
if (specifier.startsWith('.') || specifier.startsWith('/')) {
599+
return false
600+
}
601+
if (/^[a-z][\w+.-]*:/i.test(specifier)) {
602+
return false
603+
}
604+
return true
605+
}
606+
494607
function createProjectPeerResolver(rootDir: string) {
495608
const resolver = getProjectRequire(rootDir)
496609
return async (name: string) => {
@@ -643,13 +756,40 @@ function reportCliResult(result: GenerateTypesResult): void {
643756
}
644757
}
645758

759+
function setCssWithMetaImplementation(impl?: CssWithMetaFn): void {
760+
activeCssWithMeta = impl ?? cssWithMeta
761+
}
762+
763+
function setModuleTypeDetector(detector?: ModuleTypeDetector): void {
764+
moduleTypeDetector = detector ?? moduleType
765+
}
766+
767+
function setImportMetaUrlProvider(provider?: () => string | undefined): void {
768+
importMetaUrlProvider = provider ?? getImportMetaUrl
769+
}
770+
646771
export const __generateTypesInternals = {
772+
writeTypesIndex,
647773
stripInlineLoader,
648774
splitResourceAndQuery,
775+
findSpecifierImports,
776+
resolveImportPath,
777+
resolvePackageRoot,
649778
buildDeclarationFileName,
650779
formatModuleDeclaration,
651780
formatSelectorType,
781+
relativeToRoot,
782+
collectCandidateFiles,
652783
normalizeIncludeOptions,
784+
normalizeTsconfigPaths,
785+
setCssWithMetaImplementation,
786+
setModuleTypeDetector,
787+
setImportMetaUrlProvider,
788+
isNonRelativeSpecifier,
789+
createProjectPeerResolver,
790+
getProjectRequire,
791+
loadTsconfigResolutionContext,
792+
resolveWithTsconfigPaths,
653793
parseCliArgs,
654794
printHelp,
655795
reportCliResult,

0 commit comments

Comments
 (0)