Skip to content

Commit

Permalink
fix(bundling): fallback to manual file resolution if tsconfig-paths f…
Browse files Browse the repository at this point in the history
…ails (#18477)
  • Loading branch information
barbados-clemens authored Aug 25, 2023
1 parent cf1175f commit e3b513b
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 35 deletions.
34 changes: 26 additions & 8 deletions e2e/vite/src/vite.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
promisifiedTreeKill,
readFile,
readJson,
removeFile,
rmDist,
runCLI,
runCLIAsync,
Expand Down Expand Up @@ -267,22 +268,21 @@ describe('Vite Plugin', () => {
const buildableJsLibFn = names(`${lib}-js`).propertyName;

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 NxWelcome from './nx-welcome';
import { ${buildableLibCmp} } from '@acme/buildable';
import { ${buildableJsLibFn} } from '@acme/js-lib';
import { ${nonBuildableLibCmp} } from '@acme/non-buildable';
export function App() {
return (
<div>
<${buildableLibCmp} />
<${nonBuildableLibCmp} />
<p>{${buildableJsLibFn}()}</p>
<NxWelcome title="${app}" />
</div>
<div>
<${buildableLibCmp} />
<${nonBuildableLibCmp} />
<p>{${buildableJsLibFn}()}</p>
<NxWelcome title="${app}" />
</div>
);
}
export default App;
Expand All @@ -307,6 +307,24 @@ export default App;
// this should be less modules than building from source
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', () => {
Expand Down
137 changes: 110 additions & 27 deletions packages/vite/plugins/nx-tsconfig-paths.plugin.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,49 @@
import { stripIndents, workspaceRoot } from '@nx/devkit';
import { existsSync } from 'node:fs';
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 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',
Expand All @@ -31,59 +69,104 @@ There should at least be a tsconfig.base.json or tsconfig.json in the root of th
if (parsed.resultType === 'failed') {
throw new Error(`Failed loading tsonfig at ${foundTsConfigPath}`);
}
tsConfigPathsEsm = parsed;

matchTsPathEsm = createMatchPath(parsed.absoluteBaseUrl, parsed.paths, [
['exports', '.', 'import'],
'module',
'main',
]);
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(source: string) {
resolveId(importPath: string) {
let resolvedFile: string;
try {
resolvedFile = matchTsPathEsm(source);
resolvedFile = matchTsPathEsm(importPath);
} catch (e) {
logIt('Using fallback path matching.');
resolvedFile = matchTsPathFallback?.(source);
resolvedFile = matchTsPathFallback?.(importPath);
}

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 {
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 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 logIt(...msg: any[]) {
if (process.env.NX_VERBOSE_LOGGING === 'true') {
console.debug('[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 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;
}
}
}
}

0 comments on commit e3b513b

Please sign in to comment.