Skip to content

Commit e3b513b

Browse files
fix(bundling): fallback to manual file resolution if tsconfig-paths fails (#18477)
1 parent cf1175f commit e3b513b

File tree

2 files changed

+136
-35
lines changed

2 files changed

+136
-35
lines changed

e2e/vite/src/vite.test.ts

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
promisifiedTreeKill,
1212
readFile,
1313
readJson,
14+
removeFile,
1415
rmDist,
1516
runCLI,
1617
runCLIAsync,
@@ -267,22 +268,21 @@ describe('Vite Plugin', () => {
267268
const buildableJsLibFn = names(`${lib}-js`).propertyName;
268269

269270
updateFile(`apps/${app}/src/app/app.tsx`, () => {
270-
return `// eslint-disable-next-line @typescript-eslint/no-unused-vars
271+
return `
271272
import styles from './app.module.css';
272-
273273
import NxWelcome from './nx-welcome';
274274
import { ${buildableLibCmp} } from '@acme/buildable';
275275
import { ${buildableJsLibFn} } from '@acme/js-lib';
276276
import { ${nonBuildableLibCmp} } from '@acme/non-buildable';
277277
278278
export function App() {
279279
return (
280-
<div>
281-
<${buildableLibCmp} />
282-
<${nonBuildableLibCmp} />
283-
<p>{${buildableJsLibFn}()}</p>
284-
<NxWelcome title="${app}" />
285-
</div>
280+
<div>
281+
<${buildableLibCmp} />
282+
<${nonBuildableLibCmp} />
283+
<p>{${buildableJsLibFn}()}</p>
284+
<NxWelcome title="${app}" />
285+
</div>
286286
);
287287
}
288288
export default App;
@@ -307,6 +307,24 @@ export default App;
307307
// this should be less modules than building from source
308308
expect(results).toContain('38 modules transformed');
309309
});
310+
311+
it('should build app from libs without package.json in lib', () => {
312+
removeFile(`libs/${lib}-buildable/package.json`);
313+
314+
const buildFromSourceResults = runCLI(
315+
`build ${app} --buildLibsFromSource=true`
316+
);
317+
expect(buildFromSourceResults).toContain(
318+
'Successfully ran target build for project'
319+
);
320+
321+
const noBuildFromSourceResults = runCLI(
322+
`build ${app} --buildLibsFromSource=false`
323+
);
324+
expect(noBuildFromSourceResults).toContain(
325+
'Successfully ran target build for project'
326+
);
327+
});
310328
});
311329

312330
describe('should be able to create libs that use vitest', () => {
Lines changed: 110 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,49 @@
11
import { stripIndents, workspaceRoot } from '@nx/devkit';
22
import { existsSync } from 'node:fs';
33
import { relative, join, resolve } from 'node:path';
4-
import { loadConfig, createMatchPath, MatchPath } from 'tsconfig-paths';
4+
import {
5+
loadConfig,
6+
createMatchPath,
7+
MatchPath,
8+
ConfigLoaderSuccessResult,
9+
} from 'tsconfig-paths';
510

6-
export function nxViteTsPaths() {
11+
export interface nxViteTsPathsOptions {
12+
/**
13+
* Enable debug logging
14+
* @default false
15+
**/
16+
debug?: boolean;
17+
/**
18+
* export fields in package.json to use for resolving
19+
* @default [['exports', '.', 'import'], 'module', 'main']
20+
*
21+
* fallback resolution will use ['main', 'module']
22+
**/
23+
mainFields?: (string | string[])[];
24+
/**
25+
* extensions to check when resolving files when package.json resolution fails
26+
* @default ['.ts', '.tsx', '.js', '.jsx', '.json', '.mjs', '.cjs']
27+
**/
28+
extensions?: string[];
29+
}
30+
31+
export function nxViteTsPaths(options: nxViteTsPathsOptions = {}) {
732
let matchTsPathEsm: MatchPath;
833
let matchTsPathFallback: MatchPath | undefined;
34+
let tsConfigPathsEsm: ConfigLoaderSuccessResult;
35+
let tsConfigPathsFallback: ConfigLoaderSuccessResult;
36+
37+
options.extensions ??= [
38+
'.ts',
39+
'.tsx',
40+
'.js',
41+
'.jsx',
42+
'.json',
43+
'.mjs',
44+
'.cjs',
45+
];
46+
options.mainFields ??= [['exports', '.', 'import'], 'module', 'main'];
947

1048
return {
1149
name: 'nx-vite-ts-paths',
@@ -31,59 +69,104 @@ There should at least be a tsconfig.base.json or tsconfig.json in the root of th
3169
if (parsed.resultType === 'failed') {
3270
throw new Error(`Failed loading tsonfig at ${foundTsConfigPath}`);
3371
}
72+
tsConfigPathsEsm = parsed;
3473

35-
matchTsPathEsm = createMatchPath(parsed.absoluteBaseUrl, parsed.paths, [
36-
['exports', '.', 'import'],
37-
'module',
38-
'main',
39-
]);
74+
matchTsPathEsm = createMatchPath(
75+
parsed.absoluteBaseUrl,
76+
parsed.paths,
77+
options.mainFields
78+
);
4079

4180
const rootLevelTsConfig = getTsConfig(
4281
join(workspaceRoot, 'tsconfig.base.json')
4382
);
4483
const rootLevelParsed = loadConfig(rootLevelTsConfig);
4584
logIt('fallback parsed tsconfig: ', rootLevelParsed);
4685
if (rootLevelParsed.resultType === 'success') {
86+
tsConfigPathsFallback = rootLevelParsed;
4787
matchTsPathFallback = createMatchPath(
4888
rootLevelParsed.absoluteBaseUrl,
4989
rootLevelParsed.paths,
5090
['main', 'module']
5191
);
5292
}
5393
},
54-
resolveId(source: string) {
94+
resolveId(importPath: string) {
5595
let resolvedFile: string;
5696
try {
57-
resolvedFile = matchTsPathEsm(source);
97+
resolvedFile = matchTsPathEsm(importPath);
5898
} catch (e) {
5999
logIt('Using fallback path matching.');
60-
resolvedFile = matchTsPathFallback?.(source);
100+
resolvedFile = matchTsPathFallback?.(importPath);
61101
}
62102

63103
if (!resolvedFile) {
64-
logIt(`Unable to resolve ${source} with tsconfig paths`);
104+
if (tsConfigPathsEsm || tsConfigPathsFallback) {
105+
logIt(
106+
`Unable to resolve ${importPath} with tsconfig paths. Using fallback file matching.`
107+
);
108+
resolvedFile =
109+
loadFileFromPaths(tsConfigPathsEsm, importPath) ||
110+
loadFileFromPaths(tsConfigPathsFallback, importPath);
111+
} else {
112+
logIt(`Unable to resolve ${importPath} with tsconfig paths`);
113+
}
65114
}
66115

67-
return resolvedFile;
116+
logIt(`Resolved ${importPath} to ${resolvedFile}`);
117+
// Returning null defers to other resolveId functions and eventually the default resolution behavior
118+
// https://rollupjs.org/plugin-development/#resolveid
119+
return resolvedFile || null;
68120
},
69121
};
70-
}
71122

72-
function getTsConfig(preferredTsConfigPath: string): string {
73-
return [
74-
resolve(preferredTsConfigPath),
75-
resolve(join(workspaceRoot, 'tsconfig.base.json')),
76-
resolve(join(workspaceRoot, 'tsconfig.json')),
77-
].find((tsPath) => {
78-
if (existsSync(tsPath)) {
79-
logIt('Found tsconfig at', tsPath);
80-
return tsPath;
123+
function getTsConfig(preferredTsConfigPath: string): string {
124+
return [
125+
resolve(preferredTsConfigPath),
126+
resolve(join(workspaceRoot, 'tsconfig.base.json')),
127+
resolve(join(workspaceRoot, 'tsconfig.json')),
128+
].find((tsPath) => {
129+
if (existsSync(tsPath)) {
130+
logIt('Found tsconfig at', tsPath);
131+
return tsPath;
132+
}
133+
});
134+
}
135+
136+
function logIt(...msg: any[]) {
137+
if (process.env.NX_VERBOSE_LOGGING === 'true' || options?.debug) {
138+
console.debug('\n[Nx Vite TsPaths]', ...msg);
81139
}
82-
});
83-
}
140+
}
84141

85-
function logIt(...msg: any[]) {
86-
if (process.env.NX_VERBOSE_LOGGING === 'true') {
87-
console.debug('[Nx Vite TsPaths]', ...msg);
142+
function loadFileFromPaths(
143+
tsconfig: ConfigLoaderSuccessResult,
144+
importPath: string
145+
) {
146+
logIt(
147+
`Trying to resolve file from config in ${tsconfig.configFileAbsolutePath}`
148+
);
149+
let resolvedFile: string;
150+
for (const alias in tsconfig.paths) {
151+
const paths = tsconfig.paths[alias];
152+
153+
const normalizedImport = alias.replace(/\/\*$/, '');
154+
155+
if (importPath.startsWith(normalizedImport)) {
156+
const path = (tsconfig.absoluteBaseUrl, paths[0].replace(/\/\*$/, ''));
157+
resolvedFile = findFile(importPath.replace(normalizedImport, path));
158+
}
159+
}
160+
161+
return resolvedFile;
162+
}
163+
164+
function findFile(path: string): string {
165+
for (const ext of options.extensions) {
166+
const r = resolve(path + ext);
167+
if (existsSync(r)) {
168+
return r;
169+
}
170+
}
88171
}
89172
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy