feat(core): improve project graph creation not to invalidate nxdeps when global config change
This commit is contained in:
parent
f26eb1cffc
commit
f86a07367a
@ -163,11 +163,6 @@ describe('project graph', () => {
|
|||||||
type: 'app',
|
type: 'app',
|
||||||
data: expect.anything(),
|
data: expect.anything(),
|
||||||
},
|
},
|
||||||
'demo-e2e': {
|
|
||||||
name: 'demo-e2e',
|
|
||||||
type: 'e2e',
|
|
||||||
data: expect.anything(),
|
|
||||||
},
|
|
||||||
ui: {
|
ui: {
|
||||||
name: 'ui',
|
name: 'ui',
|
||||||
type: 'lib',
|
type: 'lib',
|
||||||
@ -175,13 +170,6 @@ describe('project graph', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependencies: {
|
dependencies: {
|
||||||
'demo-e2e': [
|
|
||||||
{
|
|
||||||
type: 'implicit',
|
|
||||||
source: 'demo-e2e',
|
|
||||||
target: 'demo',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
demo: [
|
demo: [
|
||||||
{
|
{
|
||||||
type: 'static',
|
type: 'static',
|
||||||
@ -240,21 +228,9 @@ describe('project graph', () => {
|
|||||||
type: 'app',
|
type: 'app',
|
||||||
data: expect.anything(),
|
data: expect.anything(),
|
||||||
},
|
},
|
||||||
'demo-e2e': {
|
|
||||||
name: 'demo-e2e',
|
|
||||||
type: 'e2e',
|
|
||||||
data: expect.anything(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
dependencies: {
|
dependencies: {
|
||||||
'npm:happy-nrwl': [],
|
'npm:happy-nrwl': [],
|
||||||
'demo-e2e': [
|
|
||||||
{
|
|
||||||
type: 'implicit',
|
|
||||||
source: 'demo-e2e',
|
|
||||||
target: 'demo',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
demo: [
|
demo: [
|
||||||
{
|
{
|
||||||
type: 'static',
|
type: 'static',
|
||||||
@ -286,7 +262,7 @@ describe('project graph', () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(Object.keys(affected.nodes)).toEqual(['demo', 'demo-e2e', 'api']);
|
expect(Object.keys(affected.nodes)).toEqual(['demo', 'api']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support implicit JSON file dependencies (all projects)', async () => {
|
it('should support implicit JSON file dependencies (all projects)', async () => {
|
||||||
|
|||||||
358
packages/workspace/src/core/nx-deps/nx-deps-cache.spec.ts
Normal file
358
packages/workspace/src/core/nx-deps/nx-deps-cache.spec.ts
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
import { NxJsonConfiguration, WorkspaceJsonConfiguration } from '@nrwl/devkit';
|
||||||
|
import {
|
||||||
|
extractCachedPartOfProjectGraph,
|
||||||
|
ProjectGraphCache,
|
||||||
|
shouldRecomputeWholeGraph,
|
||||||
|
} from './nx-deps-cache';
|
||||||
|
|
||||||
|
describe('nx deps utils', () => {
|
||||||
|
describe('shouldRecomputeWholeGraph', () => {
|
||||||
|
it('should be false when nothing changes', () => {
|
||||||
|
expect(
|
||||||
|
shouldRecomputeWholeGraph(
|
||||||
|
createCache({}),
|
||||||
|
createPackageJsonDeps({}),
|
||||||
|
createWorkspaceJson({}),
|
||||||
|
createNxJson({}),
|
||||||
|
createTsConfigJson()
|
||||||
|
)
|
||||||
|
).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be true when version of nrwl/workspace changes', () => {
|
||||||
|
expect(
|
||||||
|
shouldRecomputeWholeGraph(
|
||||||
|
createCache({
|
||||||
|
deps: {
|
||||||
|
'@nrwl/workspace': '12.0.1',
|
||||||
|
plugin: '1.0.0',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
createPackageJsonDeps({}),
|
||||||
|
createWorkspaceJson({}),
|
||||||
|
createNxJson({}),
|
||||||
|
createTsConfigJson()
|
||||||
|
)
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be true when a cached project is missing', () => {
|
||||||
|
expect(
|
||||||
|
shouldRecomputeWholeGraph(
|
||||||
|
createCache({
|
||||||
|
nodes: {
|
||||||
|
'renamed-mylib': {} as any,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
createPackageJsonDeps({}),
|
||||||
|
createWorkspaceJson({}),
|
||||||
|
createNxJson({}),
|
||||||
|
createTsConfigJson()
|
||||||
|
)
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be true when a path mapping changes', () => {
|
||||||
|
expect(
|
||||||
|
shouldRecomputeWholeGraph(
|
||||||
|
createCache({}),
|
||||||
|
createPackageJsonDeps({}),
|
||||||
|
createWorkspaceJson({}),
|
||||||
|
createNxJson({}),
|
||||||
|
createTsConfigJson({ mylib: ['libs/mylib/changed.ts'] })
|
||||||
|
)
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be true when number of plugins changed', () => {
|
||||||
|
expect(
|
||||||
|
shouldRecomputeWholeGraph(
|
||||||
|
createCache({}),
|
||||||
|
createPackageJsonDeps({}),
|
||||||
|
createWorkspaceJson({}),
|
||||||
|
createNxJson({
|
||||||
|
plugins: ['plugin', 'plugin2'],
|
||||||
|
}),
|
||||||
|
createTsConfigJson()
|
||||||
|
)
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be true when plugin version changed', () => {
|
||||||
|
expect(
|
||||||
|
shouldRecomputeWholeGraph(
|
||||||
|
createCache({}),
|
||||||
|
createPackageJsonDeps({ plugin: '2.0.0' }),
|
||||||
|
createWorkspaceJson({}),
|
||||||
|
createNxJson({}),
|
||||||
|
createTsConfigJson()
|
||||||
|
)
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('extractCachedPartOfProjectGraph', () => {
|
||||||
|
it('should return the cache project graph when nothing has changed', () => {
|
||||||
|
const cached = {
|
||||||
|
nodes: {
|
||||||
|
mylib: {
|
||||||
|
name: 'mylib',
|
||||||
|
type: 'lib',
|
||||||
|
data: {
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
file: 'index.ts',
|
||||||
|
ext: 'ts',
|
||||||
|
hash: 'hash1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: { mylib: [] },
|
||||||
|
} as any;
|
||||||
|
const r = extractCachedPartOfProjectGraph(
|
||||||
|
{
|
||||||
|
mylib: [
|
||||||
|
{
|
||||||
|
file: 'index.ts',
|
||||||
|
ext: 'ts',
|
||||||
|
hash: 'hash1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
createNxJson({}),
|
||||||
|
createCache({
|
||||||
|
nodes: { ...cached.nodes },
|
||||||
|
dependencies: { ...cached.dependencies },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(r.filesDifferentFromCache).toEqual({});
|
||||||
|
expect(r.cachedPartOfProjectGraph).toEqual(cached);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle cases when no projects are added', () => {
|
||||||
|
const cached = {
|
||||||
|
nodes: {
|
||||||
|
mylib: {
|
||||||
|
name: 'mylib',
|
||||||
|
type: 'lib',
|
||||||
|
data: {
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
file: 'index.ts',
|
||||||
|
ext: 'ts',
|
||||||
|
hash: 'hash1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: { mylib: [] },
|
||||||
|
} as any;
|
||||||
|
const r = extractCachedPartOfProjectGraph(
|
||||||
|
{
|
||||||
|
mylib: [
|
||||||
|
{
|
||||||
|
file: 'index.ts',
|
||||||
|
ext: 'ts',
|
||||||
|
hash: 'hash1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
secondlib: [
|
||||||
|
{
|
||||||
|
file: 'index.ts',
|
||||||
|
ext: 'ts',
|
||||||
|
hash: 'hash2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
createNxJson({}),
|
||||||
|
createCache({
|
||||||
|
nodes: { ...cached.nodes },
|
||||||
|
dependencies: { ...cached.dependencies },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(r.filesDifferentFromCache).toEqual({
|
||||||
|
secondlib: [
|
||||||
|
{
|
||||||
|
file: 'index.ts',
|
||||||
|
ext: 'ts',
|
||||||
|
hash: 'hash2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(r.cachedPartOfProjectGraph).toEqual(cached);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle cases when files change', () => {
|
||||||
|
const cached = {
|
||||||
|
nodes: {
|
||||||
|
mylib: {
|
||||||
|
name: 'mylib',
|
||||||
|
type: 'lib',
|
||||||
|
data: {
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
file: 'index.ts',
|
||||||
|
ext: 'ts',
|
||||||
|
hash: 'hash1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: { mylib: [] },
|
||||||
|
} as any;
|
||||||
|
const r = extractCachedPartOfProjectGraph(
|
||||||
|
{
|
||||||
|
mylib: [
|
||||||
|
{
|
||||||
|
file: 'index.ts',
|
||||||
|
ext: 'ts',
|
||||||
|
hash: 'hash2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
createNxJson({}),
|
||||||
|
createCache({
|
||||||
|
nodes: { ...cached.nodes },
|
||||||
|
dependencies: { ...cached.dependencies },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(r.filesDifferentFromCache).toEqual({
|
||||||
|
mylib: [
|
||||||
|
{
|
||||||
|
file: 'index.ts',
|
||||||
|
ext: 'ts',
|
||||||
|
hash: 'hash2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(r.cachedPartOfProjectGraph).toEqual({
|
||||||
|
nodes: {},
|
||||||
|
dependencies: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle cases when implicits change', () => {
|
||||||
|
const cached = {
|
||||||
|
nodes: {
|
||||||
|
mylib: {
|
||||||
|
name: 'mylib',
|
||||||
|
type: 'lib',
|
||||||
|
data: {
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
file: 'index.ts',
|
||||||
|
ext: 'ts',
|
||||||
|
hash: 'hash1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
implicitDependencies: ['otherlib'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
mylib: [{ type: 'static', source: 'mylib', target: 'otherlib' }],
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
const r = extractCachedPartOfProjectGraph(
|
||||||
|
{
|
||||||
|
mylib: [
|
||||||
|
{
|
||||||
|
file: 'index.ts',
|
||||||
|
ext: 'ts',
|
||||||
|
hash: 'hash1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
createNxJson({
|
||||||
|
projects: {
|
||||||
|
mylib: {
|
||||||
|
implicitDependencies: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
createCache({
|
||||||
|
nodes: { ...cached.nodes },
|
||||||
|
dependencies: { ...cached.dependencies },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(r.filesDifferentFromCache).toEqual({
|
||||||
|
mylib: [
|
||||||
|
{
|
||||||
|
file: 'index.ts',
|
||||||
|
ext: 'ts',
|
||||||
|
hash: 'hash1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(r.cachedPartOfProjectGraph).toEqual({
|
||||||
|
nodes: {},
|
||||||
|
dependencies: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createCache(p: Partial<ProjectGraphCache>): ProjectGraphCache {
|
||||||
|
const defaults: ProjectGraphCache = {
|
||||||
|
version: '3.0',
|
||||||
|
deps: {
|
||||||
|
'@nrwl/workspace': '12.0.0',
|
||||||
|
plugin: '1.0.0',
|
||||||
|
},
|
||||||
|
pathMappings: {
|
||||||
|
mylib: ['libs/mylib/index.ts'],
|
||||||
|
},
|
||||||
|
nxJsonPlugins: [{ name: 'plugin', version: '1.0.0' }],
|
||||||
|
nodes: {
|
||||||
|
mylib: {} as any,
|
||||||
|
},
|
||||||
|
dependencies: { mylib: [] },
|
||||||
|
};
|
||||||
|
return { ...defaults, ...p };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPackageJsonDeps(
|
||||||
|
p: Record<string, string>
|
||||||
|
): Record<string, string> {
|
||||||
|
const defaults = {
|
||||||
|
'@nrwl/workspace': '12.0.0',
|
||||||
|
plugin: '1.0.0',
|
||||||
|
};
|
||||||
|
return { ...defaults, ...p };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWorkspaceJson(p: any): WorkspaceJsonConfiguration {
|
||||||
|
const defaults = {
|
||||||
|
projects: { mylib: {} },
|
||||||
|
} as any;
|
||||||
|
return { ...defaults, ...p };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNxJson(p: Partial<NxJsonConfiguration>): NxJsonConfiguration {
|
||||||
|
const defaults: NxJsonConfiguration = {
|
||||||
|
npmScope: '',
|
||||||
|
projects: { mylib: {} },
|
||||||
|
workspaceLayout: {} as any,
|
||||||
|
targetDependencies: {},
|
||||||
|
plugins: ['plugin'],
|
||||||
|
};
|
||||||
|
return { ...defaults, ...p };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTsConfigJson(paths?: { [k: string]: any }): any {
|
||||||
|
const r = {
|
||||||
|
compilerOptions: {
|
||||||
|
paths: {
|
||||||
|
mylib: ['libs/mylib/index.ts'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
if (paths) {
|
||||||
|
r.compilerOptions.paths = paths;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,8 +1,10 @@
|
|||||||
import { FileData, filesChanged } from '../file-utils';
|
import { FileData, filesChanged } from '../file-utils';
|
||||||
import type {
|
import type {
|
||||||
|
NxJsonConfiguration,
|
||||||
ProjectGraph,
|
ProjectGraph,
|
||||||
ProjectGraphDependency,
|
ProjectGraphDependency,
|
||||||
ProjectGraphNode,
|
ProjectGraphNode,
|
||||||
|
WorkspaceJsonConfiguration,
|
||||||
} from '@nrwl/devkit';
|
} from '@nrwl/devkit';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { appRootPath } from '@nrwl/tao/src/utils/app-root';
|
import { appRootPath } from '@nrwl/tao/src/utils/app-root';
|
||||||
@ -23,7 +25,9 @@ import {
|
|||||||
|
|
||||||
export interface ProjectGraphCache {
|
export interface ProjectGraphCache {
|
||||||
version: string;
|
version: string;
|
||||||
rootFiles: FileData[];
|
deps: Record<string, string>;
|
||||||
|
pathMappings: Record<string, any>;
|
||||||
|
nxJsonPlugins: { name: string; version: string }[];
|
||||||
nodes: Record<string, ProjectGraphNode>;
|
nodes: Record<string, ProjectGraphNode>;
|
||||||
dependencies: Record<string, ProjectGraphDependency[]>;
|
dependencies: Record<string, ProjectGraphDependency[]>;
|
||||||
}
|
}
|
||||||
@ -74,68 +78,125 @@ export function readCache(): false | ProjectGraphCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function writeCache(
|
export function writeCache(
|
||||||
rootFiles: FileData[],
|
packageJsonDeps: Record<string, string>,
|
||||||
|
nxJson: NxJsonConfiguration,
|
||||||
|
tsConfig: { compilerOptions: { paths: { [k: string]: any } } },
|
||||||
projectGraph: ProjectGraph
|
projectGraph: ProjectGraph
|
||||||
): void {
|
): void {
|
||||||
performance.mark('write cache:start');
|
performance.mark('write cache:start');
|
||||||
writeJsonFile(nxDepsPath, {
|
const nxJsonPlugins = (nxJson.plugins || []).map((p) => ({
|
||||||
version: '2.0',
|
name: p,
|
||||||
rootFiles,
|
version: packageJsonDeps[p],
|
||||||
|
}));
|
||||||
|
const newValue: ProjectGraphCache = {
|
||||||
|
version: '3.0',
|
||||||
|
deps: packageJsonDeps,
|
||||||
|
pathMappings: tsConfig.compilerOptions.paths,
|
||||||
|
nxJsonPlugins,
|
||||||
nodes: projectGraph.nodes,
|
nodes: projectGraph.nodes,
|
||||||
dependencies: projectGraph.dependencies,
|
dependencies: projectGraph.dependencies,
|
||||||
});
|
};
|
||||||
|
writeJsonFile(nxDepsPath, newValue);
|
||||||
performance.mark('write cache:end');
|
performance.mark('write cache:end');
|
||||||
performance.measure('write cache', 'write cache:start', 'write cache:end');
|
performance.measure('write cache', 'write cache:start', 'write cache:end');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function differentFromCache(
|
export function shouldRecomputeWholeGraph(
|
||||||
|
cache: ProjectGraphCache,
|
||||||
|
packageJsonDeps: Record<string, string>,
|
||||||
|
workspaceJson: WorkspaceJsonConfiguration,
|
||||||
|
nxJson: NxJsonConfiguration,
|
||||||
|
tsConfig: { compilerOptions: { paths: { [k: string]: any } } }
|
||||||
|
): boolean {
|
||||||
|
if (cache.deps['@nrwl/workspace'] !== packageJsonDeps['@nrwl/workspace']) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have a cached project that is no longer present
|
||||||
|
if (
|
||||||
|
Object.keys(cache.nodes).some(
|
||||||
|
(p) =>
|
||||||
|
cache.nodes[p].type != 'app' &&
|
||||||
|
cache.nodes[p].type != 'lib' &&
|
||||||
|
!workspaceJson.projects[p]
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a path mapping for an existing project has changed
|
||||||
|
if (
|
||||||
|
Object.keys(cache.pathMappings).some(
|
||||||
|
(t) =>
|
||||||
|
JSON.stringify(cache.pathMappings[t]) !=
|
||||||
|
JSON.stringify(tsConfig.compilerOptions.paths[t])
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a new plugin has been added
|
||||||
|
if ((nxJson.plugins || []).length !== cache.nxJsonPlugins.length) return true;
|
||||||
|
|
||||||
|
// a plugin has changed
|
||||||
|
if (
|
||||||
|
(nxJson.plugins || []).some((t) => {
|
||||||
|
const matchingPlugin = cache.nxJsonPlugins.find((p) => p.name === t);
|
||||||
|
if (!matchingPlugin) return true;
|
||||||
|
return matchingPlugin.version !== packageJsonDeps[t];
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This can only be invoked when the list of projects is either the same
|
||||||
|
or new projects have been added, so every project in the cache has a corresponding
|
||||||
|
project in fileMap
|
||||||
|
*/
|
||||||
|
export function extractCachedPartOfProjectGraph(
|
||||||
fileMap: ProjectFileMap,
|
fileMap: ProjectFileMap,
|
||||||
|
nxJson: NxJsonConfiguration,
|
||||||
c: ProjectGraphCache
|
c: ProjectGraphCache
|
||||||
): {
|
): {
|
||||||
noDifference: boolean;
|
|
||||||
filesDifferentFromCache: ProjectFileMap;
|
filesDifferentFromCache: ProjectFileMap;
|
||||||
partiallyConstructedProjectGraph?: ProjectGraph;
|
cachedPartOfProjectGraph: ProjectGraph;
|
||||||
} {
|
} {
|
||||||
const currentProjects = Object.keys(fileMap)
|
const currentProjects = Object.keys(fileMap).filter(
|
||||||
.sort()
|
(name) => fileMap[name].length > 0
|
||||||
.filter((name) => fileMap[name].length > 0);
|
);
|
||||||
const previousProjects = Object.keys(c.nodes)
|
|
||||||
.sort()
|
|
||||||
.filter((name) => c.nodes[name].data.files.length > 0);
|
|
||||||
|
|
||||||
// Projects changed -> compute entire graph
|
|
||||||
if (
|
|
||||||
currentProjects.length !== previousProjects.length ||
|
|
||||||
currentProjects.some((val, idx) => val !== previousProjects[idx])
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
filesDifferentFromCache: fileMap,
|
|
||||||
partiallyConstructedProjectGraph: null,
|
|
||||||
noDifference: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Projects are same -> compute projects with file changes
|
|
||||||
const filesDifferentFromCache: ProjectFileMap = {};
|
const filesDifferentFromCache: ProjectFileMap = {};
|
||||||
|
// Re-compute nodes and dependencies for projects whose files changed
|
||||||
currentProjects.forEach((p) => {
|
currentProjects.forEach((p) => {
|
||||||
if (filesChanged(c.nodes[p].data.files, fileMap[p])) {
|
if (!c.nodes[p] || filesChanged(c.nodes[p].data.files, fileMap[p])) {
|
||||||
filesDifferentFromCache[p] = fileMap[p];
|
filesDifferentFromCache[p] = fileMap[p];
|
||||||
|
delete c.dependencies[p];
|
||||||
|
delete c.nodes[p];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Re-compute nodes and dependencies for each project in file map.
|
// Re-compute nodes and dependencies for projects whose implicit deps changed
|
||||||
Object.keys(filesDifferentFromCache).forEach((key) => {
|
Object.keys(nxJson.projects || {}).forEach((p) => {
|
||||||
delete c.dependencies[key];
|
if (
|
||||||
|
nxJson.projects[p]?.implicitDependencies &&
|
||||||
|
JSON.stringify(c.nodes[p].data.implicitDependencies) !==
|
||||||
|
JSON.stringify(nxJson.projects[p].implicitDependencies)
|
||||||
|
) {
|
||||||
|
filesDifferentFromCache[p] = fileMap[p];
|
||||||
|
delete c.dependencies[p];
|
||||||
|
delete c.nodes[p];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const partiallyConstructedProjectGraph = {
|
|
||||||
nodes: c.nodes,
|
|
||||||
dependencies: c.dependencies,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filesDifferentFromCache,
|
filesDifferentFromCache,
|
||||||
partiallyConstructedProjectGraph,
|
cachedPartOfProjectGraph: {
|
||||||
noDifference: Object.keys(filesDifferentFromCache).length === 0,
|
nodes: c.nodes,
|
||||||
|
dependencies: c.dependencies,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,14 +17,5 @@ export function buildImplicitProjectDependencies(
|
|||||||
addDependency(DependencyType.implicit, source, target);
|
addDependency(DependencyType.implicit, source, target);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(v10): remove this because implicit dependencies are generated now..
|
|
||||||
if (source.endsWith('-e2e')) {
|
|
||||||
const target = source.replace(/-e2e$/, '');
|
|
||||||
// Only add if expected source actually exists, otherwise this will error out.
|
|
||||||
if (nodes[target]) {
|
|
||||||
addDependency(DependencyType.implicit, source, target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,28 +11,16 @@ export {
|
|||||||
DependencyType,
|
DependencyType,
|
||||||
} from '@nrwl/devkit';
|
} from '@nrwl/devkit';
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
export type ProjectGraphNodeRecords = Record<string, ProjectGraphNode>;
|
export type ProjectGraphNodeRecords = Record<string, ProjectGraphNode>;
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
export type AddProjectNode = (node: ProjectGraphNode) => void;
|
export type AddProjectNode = (node: ProjectGraphNode) => void;
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
export type AddProjectDependency = (
|
export type AddProjectDependency = (
|
||||||
type: DependencyType | string,
|
type: DependencyType | string,
|
||||||
source: string,
|
source: string,
|
||||||
target: string
|
target: string
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
export interface ProjectGraphContext {
|
export interface ProjectGraphContext {
|
||||||
workspaceJson: any;
|
workspaceJson: any;
|
||||||
nxJson: NxJsonConfiguration;
|
nxJson: NxJsonConfiguration;
|
||||||
|
|||||||
@ -162,9 +162,7 @@ describe('project graph', () => {
|
|||||||
api: [
|
api: [
|
||||||
{ type: DependencyType.static, source: 'api', target: 'npm:express' },
|
{ type: DependencyType.static, source: 'api', target: 'npm:express' },
|
||||||
],
|
],
|
||||||
'demo-e2e': [
|
'demo-e2e': [],
|
||||||
{ type: DependencyType.implicit, source: 'demo-e2e', target: 'demo' },
|
|
||||||
],
|
|
||||||
demo: [
|
demo: [
|
||||||
{ type: DependencyType.static, source: 'demo', target: 'ui' },
|
{ type: DependencyType.static, source: 'demo', target: 'ui' },
|
||||||
{
|
{
|
||||||
@ -193,21 +191,19 @@ describe('project graph', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update the graph if the workspace file changes ', async () => {
|
it('should update the graph if a project got renamed', async () => {
|
||||||
let graph = await createProjectGraphAsync();
|
let graph = await createProjectGraphAsync();
|
||||||
expect(graph.nodes).toMatchObject({
|
expect(graph.nodes).toMatchObject({
|
||||||
demo: { name: 'demo', type: 'app' },
|
demo: { name: 'demo', type: 'app' },
|
||||||
});
|
});
|
||||||
workspaceJson.projects.demo.projectType = 'library';
|
workspaceJson.projects.renamed = workspaceJson.projects.demo;
|
||||||
//wait a tick to ensure the modified time of workspace.json will be after the creation of the project graph file
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
||||||
fs.writeFileSync('/root/workspace.json', JSON.stringify(workspaceJson));
|
fs.writeFileSync('/root/workspace.json', JSON.stringify(workspaceJson));
|
||||||
|
|
||||||
defaultFileHasher.init();
|
defaultFileHasher.init();
|
||||||
|
|
||||||
graph = await createProjectGraphAsync();
|
graph = await createProjectGraphAsync();
|
||||||
expect(graph.nodes).toMatchObject({
|
expect(graph.nodes).toMatchObject({
|
||||||
demo: { name: 'demo', type: 'lib' },
|
renamed: { name: 'renamed', type: 'app' },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -2,27 +2,31 @@ import type {
|
|||||||
FileData,
|
FileData,
|
||||||
NxJsonConfiguration,
|
NxJsonConfiguration,
|
||||||
NxJsonProjectConfiguration,
|
NxJsonProjectConfiguration,
|
||||||
|
NxPlugin,
|
||||||
ProjectConfiguration,
|
ProjectConfiguration,
|
||||||
ProjectFileMap,
|
ProjectFileMap,
|
||||||
WorkspaceJsonConfiguration,
|
|
||||||
ProjectGraphProcessorContext,
|
|
||||||
NxPlugin,
|
|
||||||
ProjectGraph,
|
ProjectGraph,
|
||||||
|
ProjectGraphProcessorContext,
|
||||||
|
WorkspaceJsonConfiguration,
|
||||||
} from '@nrwl/devkit';
|
} from '@nrwl/devkit';
|
||||||
|
import { ProjectGraphBuilder, readJsonFile } from '@nrwl/devkit';
|
||||||
import { ProjectGraphBuilder } from '@nrwl/devkit';
|
|
||||||
|
|
||||||
import { appRootPath } from '@nrwl/tao/src/utils/app-root';
|
import { appRootPath } from '@nrwl/tao/src/utils/app-root';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { performance } from 'perf_hooks';
|
||||||
import { assertWorkspaceValidity } from '../assert-workspace-validity';
|
import { assertWorkspaceValidity } from '../assert-workspace-validity';
|
||||||
import { createProjectFileMap } from '../file-graph';
|
import { createProjectFileMap } from '../file-graph';
|
||||||
import {
|
import {
|
||||||
filesChanged,
|
|
||||||
readNxJson,
|
readNxJson,
|
||||||
readWorkspaceFiles,
|
readWorkspaceFiles,
|
||||||
readWorkspaceJson,
|
readWorkspaceJson,
|
||||||
rootWorkspaceFileData,
|
|
||||||
} from '../file-utils';
|
} from '../file-utils';
|
||||||
import { normalizeNxJson } from '../normalize-nx-json';
|
import { normalizeNxJson } from '../normalize-nx-json';
|
||||||
|
import {
|
||||||
|
extractCachedPartOfProjectGraph,
|
||||||
|
readCache,
|
||||||
|
shouldRecomputeWholeGraph,
|
||||||
|
writeCache,
|
||||||
|
} from '../nx-deps/nx-deps-cache';
|
||||||
import {
|
import {
|
||||||
BuildDependencies,
|
BuildDependencies,
|
||||||
buildExplicitPackageJsonDependencies,
|
buildExplicitPackageJsonDependencies,
|
||||||
@ -34,17 +38,16 @@ import {
|
|||||||
buildNpmPackageNodes,
|
buildNpmPackageNodes,
|
||||||
buildWorkspaceProjectNodes,
|
buildWorkspaceProjectNodes,
|
||||||
} from './build-nodes';
|
} from './build-nodes';
|
||||||
import {
|
|
||||||
differentFromCache,
|
|
||||||
readCache,
|
|
||||||
writeCache,
|
|
||||||
} from '../nx-deps/nx-deps-cache';
|
|
||||||
import { performance } from 'perf_hooks';
|
|
||||||
|
|
||||||
export async function createProjectGraphAsync(): Promise<ProjectGraph> {
|
export async function createProjectGraphAsync(): Promise<ProjectGraph> {
|
||||||
return createProjectGraph();
|
return createProjectGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readCombinedDeps() {
|
||||||
|
const json = readJsonFile(join(appRootPath, 'package.json'));
|
||||||
|
return { ...json.dependencies, ...json.devDependencies };
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(v13): remove this deprecated function
|
// TODO(v13): remove this deprecated function
|
||||||
/**
|
/**
|
||||||
* @deprecated This function is deprecated in favor of the new asynchronous version {@link createProjectGraphAsync}
|
* @deprecated This function is deprecated in favor of the new asynchronous version {@link createProjectGraphAsync}
|
||||||
@ -58,30 +61,29 @@ export function createProjectGraph(
|
|||||||
let cache = cacheEnabled ? readCache() : false;
|
let cache = cacheEnabled ? readCache() : false;
|
||||||
assertWorkspaceValidity(workspaceJson, nxJson);
|
assertWorkspaceValidity(workspaceJson, nxJson);
|
||||||
const normalizedNxJson = normalizeNxJson(nxJson);
|
const normalizedNxJson = normalizeNxJson(nxJson);
|
||||||
|
|
||||||
const rootFiles = rootWorkspaceFileData();
|
|
||||||
const projectFileMap = createProjectFileMap(workspaceJson, workspaceFiles);
|
const projectFileMap = createProjectFileMap(workspaceJson, workspaceFiles);
|
||||||
|
const packageJsonDeps = readCombinedDeps();
|
||||||
if (cache && !filesChanged(rootFiles, cache.rootFiles)) {
|
const rootTsConfig = readRootTsConfig();
|
||||||
const diff = differentFromCache(projectFileMap, cache);
|
if (
|
||||||
if (diff.noDifference) {
|
cache &&
|
||||||
return addWorkspaceFiles(
|
cache.version === '3.0' &&
|
||||||
diff.partiallyConstructedProjectGraph,
|
!shouldRecomputeWholeGraph(
|
||||||
workspaceFiles
|
cache,
|
||||||
);
|
packageJsonDeps,
|
||||||
}
|
workspaceJson,
|
||||||
|
normalizedNxJson,
|
||||||
|
rootTsConfig
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const diff = extractCachedPartOfProjectGraph(projectFileMap, nxJson, cache);
|
||||||
const ctx = {
|
const ctx = {
|
||||||
workspaceJson,
|
workspaceJson,
|
||||||
nxJson: normalizedNxJson,
|
nxJson: normalizedNxJson,
|
||||||
fileMap: diff.filesDifferentFromCache,
|
fileMap: diff.filesDifferentFromCache,
|
||||||
};
|
};
|
||||||
const projectGraph = buildProjectGraph(
|
const projectGraph = buildProjectGraph(ctx, diff.cachedPartOfProjectGraph);
|
||||||
ctx,
|
|
||||||
diff.partiallyConstructedProjectGraph
|
|
||||||
);
|
|
||||||
if (cacheEnabled) {
|
if (cacheEnabled) {
|
||||||
writeCache(rootFiles, projectGraph);
|
writeCache(packageJsonDeps, nxJson, rootTsConfig, projectGraph);
|
||||||
}
|
}
|
||||||
return addWorkspaceFiles(projectGraph, workspaceFiles);
|
return addWorkspaceFiles(projectGraph, workspaceFiles);
|
||||||
} else {
|
} else {
|
||||||
@ -92,7 +94,7 @@ export function createProjectGraph(
|
|||||||
};
|
};
|
||||||
const projectGraph = buildProjectGraph(ctx, null);
|
const projectGraph = buildProjectGraph(ctx, null);
|
||||||
if (cacheEnabled) {
|
if (cacheEnabled) {
|
||||||
writeCache(rootFiles, projectGraph);
|
writeCache(packageJsonDeps, nxJson, rootTsConfig, projectGraph);
|
||||||
}
|
}
|
||||||
return addWorkspaceFiles(projectGraph, workspaceFiles);
|
return addWorkspaceFiles(projectGraph, workspaceFiles);
|
||||||
}
|
}
|
||||||
@ -110,31 +112,41 @@ function addWorkspaceFiles(
|
|||||||
return { ...projectGraph, allWorkspaceFiles };
|
return { ...projectGraph, allWorkspaceFiles };
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildProjectGraph(
|
type BuilderContext = {
|
||||||
ctx: {
|
|
||||||
nxJson: NxJsonConfiguration<string[]>;
|
nxJson: NxJsonConfiguration<string[]>;
|
||||||
workspaceJson: WorkspaceJsonConfiguration;
|
workspaceJson: WorkspaceJsonConfiguration;
|
||||||
fileMap: ProjectFileMap;
|
fileMap: ProjectFileMap;
|
||||||
},
|
};
|
||||||
projectGraph: ProjectGraph
|
|
||||||
) {
|
|
||||||
performance.mark('build project graph:start');
|
|
||||||
const builder = new ProjectGraphBuilder(projectGraph);
|
|
||||||
const buildNodesFns: BuildNodes[] = [
|
|
||||||
buildWorkspaceProjectNodes,
|
|
||||||
buildNpmPackageNodes,
|
|
||||||
];
|
|
||||||
const buildDependenciesFns: BuildDependencies[] = [
|
|
||||||
buildExplicitTypeScriptDependencies,
|
|
||||||
buildImplicitProjectDependencies,
|
|
||||||
buildExplicitPackageJsonDependencies,
|
|
||||||
];
|
|
||||||
buildNodesFns.forEach((f) => f(ctx, builder.addNode.bind(builder)));
|
|
||||||
buildDependenciesFns.forEach((f) =>
|
|
||||||
f(ctx, builder.nodes, builder.addDependency.bind(builder))
|
|
||||||
);
|
|
||||||
const r = builder.getProjectGraph();
|
|
||||||
|
|
||||||
|
function buildProjectGraph(ctx: BuilderContext, projectGraph: ProjectGraph) {
|
||||||
|
performance.mark('build project graph:start');
|
||||||
|
|
||||||
|
const builder = new ProjectGraphBuilder(projectGraph);
|
||||||
|
const addNode = builder.addNode.bind(builder);
|
||||||
|
const addDependency = builder.addDependency.bind(builder);
|
||||||
|
buildWorkspaceProjectNodes(ctx, addNode);
|
||||||
|
buildNpmPackageNodes(ctx, addNode);
|
||||||
|
buildExplicitTypeScriptDependencies(ctx, builder.nodes, addDependency);
|
||||||
|
buildExplicitPackageJsonDependencies(ctx, builder.nodes, addDependency);
|
||||||
|
buildImplicitProjectDependencies(ctx, builder.nodes, addDependency);
|
||||||
|
const initProjectGraph = builder.getProjectGraph();
|
||||||
|
|
||||||
|
const r = updateProjectGraphWithPlugins(ctx, initProjectGraph);
|
||||||
|
|
||||||
|
performance.mark('build project graph:end');
|
||||||
|
performance.measure(
|
||||||
|
'build project graph',
|
||||||
|
'build project graph:start',
|
||||||
|
'build project graph:end'
|
||||||
|
);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProjectGraphWithPlugins(
|
||||||
|
ctx: BuilderContext,
|
||||||
|
initProjectGraph: ProjectGraph
|
||||||
|
) {
|
||||||
const plugins = (ctx.nxJson.plugins || []).map((path) => {
|
const plugins = (ctx.nxJson.plugins || []).map((path) => {
|
||||||
const pluginPath = require.resolve(path, {
|
const pluginPath = require.resolve(path, {
|
||||||
paths: [appRootPath],
|
paths: [appRootPath],
|
||||||
@ -161,19 +173,18 @@ function buildProjectGraph(
|
|||||||
fileMap: ctx.fileMap,
|
fileMap: ctx.fileMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = plugins.reduce((graph, plugin) => {
|
return plugins.reduce((graph, plugin) => {
|
||||||
if (!plugin.processProjectGraph) {
|
if (!plugin.processProjectGraph) {
|
||||||
return graph;
|
return graph;
|
||||||
}
|
}
|
||||||
|
|
||||||
return plugin.processProjectGraph(graph, context);
|
return plugin.processProjectGraph(graph, context);
|
||||||
}, r);
|
}, initProjectGraph);
|
||||||
|
}
|
||||||
performance.mark('build project graph:end');
|
|
||||||
performance.measure(
|
function readRootTsConfig() {
|
||||||
'build project graph',
|
try {
|
||||||
'build project graph:start',
|
return readJsonFile(join(appRootPath, 'tsconfig.base.json'));
|
||||||
'build project graph:end'
|
} catch (e) {
|
||||||
);
|
return readJsonFile(join(appRootPath, 'tsconfig.json'));
|
||||||
return result;
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user