nx/packages/js/src/utils/compiler-helper-dependency.ts
Chau Tran 5daeaf8e8f
fix(js): return empty array if no helper dependencies found (#10582)
* fix(js): return empty array if no helper dependencies found

* chore(js): add test for generate package json with helper dependency
2022-06-03 23:05:38 -05:00

172 lines
5.3 KiB
TypeScript

import {
logger,
ProjectGraphDependency,
readCachedProjectGraph,
readJsonFile,
} from '@nrwl/devkit';
import { DependentBuildableProjectNode } from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript';
import { join } from 'path';
import { ExecutorOptions, SwcExecutorOptions } from './schema';
import { getSwcrcPath } from './swc/get-swcrc-path';
export enum HelperDependency {
tsc = 'npm:tslib',
swc = 'npm:@swc/helpers',
}
const jsExecutors = {
'@nrwl/js:tsc': {
helperDependency: HelperDependency.tsc,
getConfigPath: (options: ExecutorOptions, contextRoot: string, _: string) =>
join(contextRoot, options.tsConfig),
} as const,
'@nrwl/js:swc': {
helperDependency: HelperDependency.swc,
getConfigPath: (
options: SwcExecutorOptions,
contextRoot: string,
projectRoot: string
) => getSwcrcPath(options, contextRoot, projectRoot),
} as const,
} as const;
/**
* Check and return a DependencyNode for the compiler's external helpers npm package. Return "null"
* if it doesn't need it or it cannot be found in the Project Graph
*
* @param {HelperDependency} helperDependency
* @param {string} configPath
* @param {DependentBuildableProjectNode[]} dependencies
* @param {boolean=false} returnDependencyIfFound
*/
export function getHelperDependency(
helperDependency: HelperDependency,
configPath: string,
dependencies: DependentBuildableProjectNode[],
returnDependencyIfFound = false
): DependentBuildableProjectNode | null {
const dependency = dependencies.find((dep) => dep.name === helperDependency);
if (!!dependency) {
// if a helperDependency is found, we either return null or the found dependency
// We return the found return dependency for the cases where it is a part of a
// project's dependency's dependency instead
// eg: app-a -> lib-a (helperDependency is on lib-a instead of app-a)
// When building app-a, we'd want to know about the found helper dependency still
return returnDependencyIfFound ? dependency : null;
}
let isHelperNeeded = false;
switch (helperDependency) {
case HelperDependency.tsc:
isHelperNeeded = !!readTsConfig(configPath).options['importHelpers'];
break;
case HelperDependency.swc:
isHelperNeeded = !!readJsonFile(configPath)['jsc']['externalHelpers'];
break;
}
if (!isHelperNeeded) return null;
const projectGraph = readCachedProjectGraph();
const libNode = projectGraph.externalNodes[helperDependency];
if (!libNode) {
logger.warn(
`Your library compilation option specifies that the compiler external helper (${
helperDependency.split(':')[1]
}) is needed but it is not installed.`
);
return null;
}
return {
name: helperDependency,
outputs: [],
node: libNode,
};
}
export function getHelperDependenciesFromProjectGraph(
contextRoot: string,
sourceProject: string
): ProjectGraphDependency[] {
const projectGraph = readCachedProjectGraph();
// if the source project isn't part of the projectGraph nodes; skip
if (!projectGraph.nodes[sourceProject]) return [];
// if the source project does not have any dependencies; skip
if (
!projectGraph.dependencies[sourceProject] ||
!projectGraph.dependencies[sourceProject].length
)
return [];
const sourceDependencies = projectGraph.dependencies[sourceProject];
const internalDependencies = sourceDependencies.reduce(
(result, dependency) => {
// we check if a dependency is part of the workspace and if it's a library
// because we wouldn't want to include external dependencies (npm packages)
if (
!dependency.target.startsWith('npm:') &&
!!projectGraph.nodes[dependency.target] &&
projectGraph.nodes[dependency.target].type === 'lib'
) {
const targetData = projectGraph.nodes[dependency.target].data;
// check if the dependency has a buildable target with one of the jsExecutors
const targetExecutor = Object.values(targetData.targets).find(
({ executor }) => !!jsExecutors[executor]
);
if (targetExecutor) {
const jsExecutor = jsExecutors[targetExecutor['executor']];
const { root: projectRoot } = targetData;
const configPath = jsExecutor.getConfigPath(
targetExecutor['options'],
contextRoot,
projectRoot
);
// construct the correct helperDependency configurations
// so we can compute the ProjectGraphDependency later
result.push({
helperDependency:
jsExecutors[targetExecutor['executor']].helperDependency,
dependencies: projectGraph.dependencies[dependency.target],
configPath,
});
}
}
return result;
},
[]
);
return internalDependencies.reduce(
(result, { helperDependency, configPath, dependencies }) => {
const dependency = getHelperDependency(
helperDependency,
configPath,
dependencies,
true
);
if (dependency) {
result.push({
type: 'static',
source: sourceProject,
target: dependency.name,
} as ProjectGraphDependency);
}
return result;
},
[] as ProjectGraphDependency[]
);
}