fix(bundling): fallback to manual file resolution if tsconfig-paths fails (#18477)
This commit is contained in:
parent
cf1175f2ae
commit
e3b513b6c0
@ -11,6 +11,7 @@ import {
|
|||||||
promisifiedTreeKill,
|
promisifiedTreeKill,
|
||||||
readFile,
|
readFile,
|
||||||
readJson,
|
readJson,
|
||||||
|
removeFile,
|
||||||
rmDist,
|
rmDist,
|
||||||
runCLI,
|
runCLI,
|
||||||
runCLIAsync,
|
runCLIAsync,
|
||||||
@ -267,9 +268,8 @@ describe('Vite Plugin', () => {
|
|||||||
const buildableJsLibFn = names(`${lib}-js`).propertyName;
|
const buildableJsLibFn = names(`${lib}-js`).propertyName;
|
||||||
|
|
||||||
updateFile(`apps/${app}/src/app/app.tsx`, () => {
|
updateFile(`apps/${app}/src/app/app.tsx`, () => {
|
||||||
return `// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
return `
|
||||||
import styles from './app.module.css';
|
import styles from './app.module.css';
|
||||||
|
|
||||||
import NxWelcome from './nx-welcome';
|
import NxWelcome from './nx-welcome';
|
||||||
import { ${buildableLibCmp} } from '@acme/buildable';
|
import { ${buildableLibCmp} } from '@acme/buildable';
|
||||||
import { ${buildableJsLibFn} } from '@acme/js-lib';
|
import { ${buildableJsLibFn} } from '@acme/js-lib';
|
||||||
@ -277,12 +277,12 @@ import { ${nonBuildableLibCmp} } from '@acme/non-buildable';
|
|||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<${buildableLibCmp} />
|
<${buildableLibCmp} />
|
||||||
<${nonBuildableLibCmp} />
|
<${nonBuildableLibCmp} />
|
||||||
<p>{${buildableJsLibFn}()}</p>
|
<p>{${buildableJsLibFn}()}</p>
|
||||||
<NxWelcome title="${app}" />
|
<NxWelcome title="${app}" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default App;
|
export default App;
|
||||||
@ -307,6 +307,24 @@ export default App;
|
|||||||
// this should be less modules than building from source
|
// this should be less modules than building from source
|
||||||
expect(results).toContain('38 modules transformed');
|
expect(results).toContain('38 modules transformed');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should build app from libs without package.json in lib', () => {
|
||||||
|
removeFile(`libs/${lib}-buildable/package.json`);
|
||||||
|
|
||||||
|
const buildFromSourceResults = runCLI(
|
||||||
|
`build ${app} --buildLibsFromSource=true`
|
||||||
|
);
|
||||||
|
expect(buildFromSourceResults).toContain(
|
||||||
|
'Successfully ran target build for project'
|
||||||
|
);
|
||||||
|
|
||||||
|
const noBuildFromSourceResults = runCLI(
|
||||||
|
`build ${app} --buildLibsFromSource=false`
|
||||||
|
);
|
||||||
|
expect(noBuildFromSourceResults).toContain(
|
||||||
|
'Successfully ran target build for project'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('should be able to create libs that use vitest', () => {
|
describe('should be able to create libs that use vitest', () => {
|
||||||
|
|||||||
@ -1,11 +1,49 @@
|
|||||||
import { stripIndents, workspaceRoot } from '@nx/devkit';
|
import { stripIndents, workspaceRoot } from '@nx/devkit';
|
||||||
import { existsSync } from 'node:fs';
|
import { existsSync } from 'node:fs';
|
||||||
import { relative, join, resolve } from 'node:path';
|
import { relative, join, resolve } from 'node:path';
|
||||||
import { loadConfig, createMatchPath, MatchPath } from 'tsconfig-paths';
|
import {
|
||||||
|
loadConfig,
|
||||||
|
createMatchPath,
|
||||||
|
MatchPath,
|
||||||
|
ConfigLoaderSuccessResult,
|
||||||
|
} from 'tsconfig-paths';
|
||||||
|
|
||||||
export function nxViteTsPaths() {
|
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 matchTsPathEsm: MatchPath;
|
||||||
let matchTsPathFallback: MatchPath | undefined;
|
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 {
|
return {
|
||||||
name: 'nx-vite-ts-paths',
|
name: 'nx-vite-ts-paths',
|
||||||
@ -31,12 +69,13 @@ There should at least be a tsconfig.base.json or tsconfig.json in the root of th
|
|||||||
if (parsed.resultType === 'failed') {
|
if (parsed.resultType === 'failed') {
|
||||||
throw new Error(`Failed loading tsonfig at ${foundTsConfigPath}`);
|
throw new Error(`Failed loading tsonfig at ${foundTsConfigPath}`);
|
||||||
}
|
}
|
||||||
|
tsConfigPathsEsm = parsed;
|
||||||
|
|
||||||
matchTsPathEsm = createMatchPath(parsed.absoluteBaseUrl, parsed.paths, [
|
matchTsPathEsm = createMatchPath(
|
||||||
['exports', '.', 'import'],
|
parsed.absoluteBaseUrl,
|
||||||
'module',
|
parsed.paths,
|
||||||
'main',
|
options.mainFields
|
||||||
]);
|
);
|
||||||
|
|
||||||
const rootLevelTsConfig = getTsConfig(
|
const rootLevelTsConfig = getTsConfig(
|
||||||
join(workspaceRoot, 'tsconfig.base.json')
|
join(workspaceRoot, 'tsconfig.base.json')
|
||||||
@ -44,6 +83,7 @@ There should at least be a tsconfig.base.json or tsconfig.json in the root of th
|
|||||||
const rootLevelParsed = loadConfig(rootLevelTsConfig);
|
const rootLevelParsed = loadConfig(rootLevelTsConfig);
|
||||||
logIt('fallback parsed tsconfig: ', rootLevelParsed);
|
logIt('fallback parsed tsconfig: ', rootLevelParsed);
|
||||||
if (rootLevelParsed.resultType === 'success') {
|
if (rootLevelParsed.resultType === 'success') {
|
||||||
|
tsConfigPathsFallback = rootLevelParsed;
|
||||||
matchTsPathFallback = createMatchPath(
|
matchTsPathFallback = createMatchPath(
|
||||||
rootLevelParsed.absoluteBaseUrl,
|
rootLevelParsed.absoluteBaseUrl,
|
||||||
rootLevelParsed.paths,
|
rootLevelParsed.paths,
|
||||||
@ -51,39 +91,82 @@ There should at least be a tsconfig.base.json or tsconfig.json in the root of th
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resolveId(source: string) {
|
resolveId(importPath: string) {
|
||||||
let resolvedFile: string;
|
let resolvedFile: string;
|
||||||
try {
|
try {
|
||||||
resolvedFile = matchTsPathEsm(source);
|
resolvedFile = matchTsPathEsm(importPath);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logIt('Using fallback path matching.');
|
logIt('Using fallback path matching.');
|
||||||
resolvedFile = matchTsPathFallback?.(source);
|
resolvedFile = matchTsPathFallback?.(importPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!resolvedFile) {
|
if (!resolvedFile) {
|
||||||
logIt(`Unable to resolve ${source} with tsconfig paths`);
|
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`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolvedFile;
|
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 {
|
function getTsConfig(preferredTsConfigPath: string): string {
|
||||||
return [
|
return [
|
||||||
resolve(preferredTsConfigPath),
|
resolve(preferredTsConfigPath),
|
||||||
resolve(join(workspaceRoot, 'tsconfig.base.json')),
|
resolve(join(workspaceRoot, 'tsconfig.base.json')),
|
||||||
resolve(join(workspaceRoot, 'tsconfig.json')),
|
resolve(join(workspaceRoot, 'tsconfig.json')),
|
||||||
].find((tsPath) => {
|
].find((tsPath) => {
|
||||||
if (existsSync(tsPath)) {
|
if (existsSync(tsPath)) {
|
||||||
logIt('Found tsconfig at', tsPath);
|
logIt('Found tsconfig at', tsPath);
|
||||||
return tsPath;
|
return tsPath;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function logIt(...msg: any[]) {
|
||||||
|
if (process.env.NX_VERBOSE_LOGGING === 'true' || options?.debug) {
|
||||||
|
console.debug('\n[Nx Vite TsPaths]', ...msg);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function logIt(...msg: any[]) {
|
function loadFileFromPaths(
|
||||||
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
tsconfig: ConfigLoaderSuccessResult,
|
||||||
console.debug('[Nx Vite TsPaths]', ...msg);
|
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 path = (tsconfig.absoluteBaseUrl, paths[0].replace(/\/\*$/, ''));
|
||||||
|
resolvedFile = findFile(importPath.replace(normalizedImport, path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findFile(path: string): string {
|
||||||
|
for (const ext of options.extensions) {
|
||||||
|
const r = resolve(path + ext);
|
||||||
|
if (existsSync(r)) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user