nx/packages/vite/plugins/nx-tsconfig-paths.plugin.ts
Matthias Stemmler b4f6e425c4
fix(vite): get tsconfig from new path including target (#22775)
## Current Behavior
Since v18.2.3 (e4c4697f69cf29c50aced1997715a7da5f680e23) the
`tsconfig.generated.json` file generated if `"buildLibsFromSource":
false` is placed under a path containing the current build target. The
`nx-tsconfig-paths` plugin of `@nx/vite` was not updated accordingly, so
now it doesn't find `tsconfig.generated.json`, effectively causing
`@nx/vite` to ignore the `"buildLibsFromSource": false` setting.

## Expected Behavior
With this PR, `nx-tsconfig-paths` finds `tsconfig.generated.json` at the
correct path, so `"buildLibsFromSource": false` works as it did before
v18.2.3.
2024-05-08 12:30:07 +01:00

185 lines
5.3 KiB
TypeScript

import { joinPathFragments, stripIndents, workspaceRoot } from '@nx/devkit';
import { existsSync } from 'node:fs';
import { relative, join, resolve } from 'node:path';
import {
loadConfig,
createMatchPath,
MatchPath,
ConfigLoaderSuccessResult,
} from 'tsconfig-paths';
export interface nxViteTsPathsOptions {
/**
* Enable debug logging
* @default false
**/
debug?: boolean;
/**
* export fields in package.json to use for resolving
* @default [['exports', '.', 'import'], 'module', 'main']
*
* fallback resolution will use ['main', 'module']
**/
mainFields?: (string | string[])[];
/**
* extensions to check when resolving files when package.json resolution fails
* @default ['.ts', '.tsx', '.js', '.jsx', '.json', '.mjs', '.cjs']
**/
extensions?: string[];
}
export function nxViteTsPaths(options: nxViteTsPathsOptions = {}) {
let matchTsPathEsm: MatchPath;
let matchTsPathFallback: MatchPath | undefined;
let tsConfigPathsEsm: ConfigLoaderSuccessResult;
let tsConfigPathsFallback: ConfigLoaderSuccessResult;
options.extensions ??= [
'.ts',
'.tsx',
'.js',
'.jsx',
'.json',
'.mjs',
'.cjs',
];
options.mainFields ??= [['exports', '.', 'import'], 'module', 'main'];
return {
name: 'nx-vite-ts-paths',
configResolved(config: any) {
const projectRoot = config.root;
const projectRootFromWorkspaceRoot = relative(workspaceRoot, projectRoot);
const foundTsConfigPath = getTsConfig(
join(
workspaceRoot,
'tmp',
projectRootFromWorkspaceRoot,
process.env.NX_TASK_TARGET_TARGET ?? 'build',
'tsconfig.generated.json'
)
);
if (!foundTsConfigPath) {
throw new Error(stripIndents`Unable to find a tsconfig in the workspace!
There should at least be a tsconfig.base.json or tsconfig.json in the root of the workspace ${workspaceRoot}`);
}
const parsed = loadConfig(foundTsConfigPath);
logIt('first parsed tsconfig: ', parsed);
if (parsed.resultType === 'failed') {
throw new Error(`Failed loading tsonfig at ${foundTsConfigPath}`);
}
tsConfigPathsEsm = parsed;
matchTsPathEsm = createMatchPath(
parsed.absoluteBaseUrl,
parsed.paths,
options.mainFields
);
const rootLevelTsConfig = getTsConfig(
join(workspaceRoot, 'tsconfig.base.json')
);
const rootLevelParsed = loadConfig(rootLevelTsConfig);
logIt('fallback parsed tsconfig: ', rootLevelParsed);
if (rootLevelParsed.resultType === 'success') {
tsConfigPathsFallback = rootLevelParsed;
matchTsPathFallback = createMatchPath(
rootLevelParsed.absoluteBaseUrl,
rootLevelParsed.paths,
['main', 'module']
);
}
},
resolveId(importPath: string) {
let resolvedFile: string;
try {
resolvedFile = matchTsPathEsm(importPath);
} catch (e) {
logIt('Using fallback path matching.');
resolvedFile = matchTsPathFallback?.(importPath);
}
if (!resolvedFile) {
if (tsConfigPathsEsm || tsConfigPathsFallback) {
logIt(
`Unable to resolve ${importPath} with tsconfig paths. Using fallback file matching.`
);
resolvedFile =
loadFileFromPaths(tsConfigPathsEsm, importPath) ||
loadFileFromPaths(tsConfigPathsFallback, importPath);
} else {
logIt(`Unable to resolve ${importPath} with tsconfig paths`);
}
}
logIt(`Resolved ${importPath} to ${resolvedFile}`);
// Returning null defers to other resolveId functions and eventually the default resolution behavior
// https://rollupjs.org/plugin-development/#resolveid
return resolvedFile || null;
},
};
function getTsConfig(preferredTsConfigPath: string): string {
return [
resolve(preferredTsConfigPath),
resolve(join(workspaceRoot, 'tsconfig.base.json')),
resolve(join(workspaceRoot, 'tsconfig.json')),
].find((tsPath) => {
if (existsSync(tsPath)) {
logIt('Found tsconfig at', tsPath);
return tsPath;
}
});
}
function logIt(...msg: any[]) {
if (process.env.NX_VERBOSE_LOGGING === 'true' || options?.debug) {
console.debug('\n[Nx Vite TsPaths]', ...msg);
}
}
function loadFileFromPaths(
tsconfig: ConfigLoaderSuccessResult,
importPath: string
) {
logIt(
`Trying to resolve file from config in ${tsconfig.configFileAbsolutePath}`
);
let resolvedFile: string;
for (const alias in tsconfig.paths) {
const paths = tsconfig.paths[alias];
const normalizedImport = alias.replace(/\/\*$/, '');
if (importPath.startsWith(normalizedImport)) {
const joinedPath = joinPathFragments(
tsconfig.absoluteBaseUrl,
paths[0].replace(/\/\*$/, '')
);
resolvedFile = findFile(
importPath.replace(normalizedImport, joinedPath)
);
}
}
return resolvedFile;
}
function findFile(path: string): string {
for (const ext of options.extensions) {
const resolvedPath = resolve(path + ext);
if (existsSync(resolvedPath)) {
return resolvedPath;
}
const resolvedIndexPath = resolve(path, `index${ext}`);
if (existsSync(resolvedIndexPath)) {
return resolvedIndexPath;
}
}
}
}