diff --git a/docs/generated/packages/angular.json b/docs/generated/packages/angular.json index f121457093ce2..229f008f0cb94 100644 --- a/docs/generated/packages/angular.json +++ b/docs/generated/packages/angular.json @@ -360,7 +360,7 @@ }, "export": { "type": "boolean", - "description": "Specifies if the component should be exported in the declaring `NgModule`. Additionally, if the project is a library, the component will be exported from the project's entry point (normally `index.ts`).", + "description": "Specifies if the component should be exported in the declaring `NgModule`. Additionally, if the project is a library, the component will be exported from the project's entry point (normally `index.ts`) if the module it belongs to is also exported.", "default": false } }, diff --git a/packages/angular/src/generators/component/__snapshots__/component.spec.ts.snap b/packages/angular/src/generators/component/__snapshots__/component.spec.ts.snap index ff824e58db2e4..9a1d4f259b5ad 100644 --- a/packages/angular/src/generators/component/__snapshots__/component.spec.ts.snap +++ b/packages/angular/src/generators/component/__snapshots__/component.spec.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`component Generator --flat should create the component correctly and export it 1`] = ` +exports[`component Generator --flat should create the component correctly and export it in the entry point 1`] = ` "import { Component, OnInit } from '@angular/core'; @Component({ @@ -19,7 +19,7 @@ export class ExampleComponent implements OnInit { " `; -exports[`component Generator --flat should create the component correctly and not export it 1`] = ` +exports[`component Generator --flat should create the component correctly and not export it when "export=false" 1`] = ` "import { Component, OnInit } from '@angular/core'; @Component({ @@ -38,7 +38,7 @@ export class ExampleComponent implements OnInit { " `; -exports[`component Generator --path should create the component correctly and export it 1`] = ` +exports[`component Generator --path should create the component correctly and export it in the entry point 1`] = ` "import { Component, OnInit } from '@angular/core'; @Component({ @@ -57,7 +57,7 @@ export class ExampleComponent implements OnInit { " `; -exports[`component Generator should create the component correctly and export it 1`] = ` +exports[`component Generator secondary entry points should create the component correctly and export it in the entry point 1`] = ` "import { Component, OnInit } from '@angular/core'; @Component({ @@ -76,12 +76,12 @@ export class ExampleComponent implements OnInit { " `; -exports[`component Generator should create the component correctly and export it 2`] = ` -" - export * from \\"./lib/example/example.component\\";" +exports[`component Generator secondary entry points should create the component correctly and export it in the entry point 2`] = ` +"export * from \\"./lib/secondary.module\\"; +export * from \\"./lib/example/example.component\\";" `; -exports[`component Generator should create the component correctly and not export it 1`] = ` +exports[`component Generator should create the component correctly and export it in the entry point 1`] = ` "import { Component, OnInit } from '@angular/core'; @Component({ @@ -100,6 +100,11 @@ export class ExampleComponent implements OnInit { " `; +exports[`component Generator should create the component correctly and export it in the entry point 2`] = ` +"export * from \\"./lib/lib.module\\"; +export * from \\"./lib/example/example.component\\";" +`; + exports[`component Generator should create the component correctly and not export it when "--skip-import=true" 1`] = ` "import { Component, OnInit } from '@angular/core'; @@ -119,7 +124,26 @@ export class ExampleComponent implements OnInit { " `; -exports[`component Generator should create the component correctly but not export it when no entry point exists 1`] = ` +exports[`component Generator should create the component correctly and not export it when "export=false" 1`] = ` +"import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'example', + templateUrl: './example.component.html', + styleUrls: ['./example.component.css'] +}) +export class ExampleComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} +" +`; + +exports[`component Generator should create the component correctly but not export it in the entry point when it does not exist 1`] = ` "import { Component, OnInit } from '@angular/core'; @Component({ diff --git a/packages/angular/src/generators/component/component.spec.ts b/packages/angular/src/generators/component/component.spec.ts index a92ba9d814489..6a8b3ac1b4f19 100644 --- a/packages/angular/src/generators/component/component.spec.ts +++ b/packages/angular/src/generators/component/component.spec.ts @@ -1,9 +1,9 @@ -import { addProjectConfiguration } from '@nrwl/devkit'; +import { addProjectConfiguration, writeJson } from '@nrwl/devkit'; import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; import componentGenerator from './component'; describe('component Generator', () => { - it('should create the component correctly and export it', async () => { + it('should create the component correctly and export it in the entry point', async () => { // ARRANGE const tree = createTreeWithEmptyWorkspace(2); addProjectConfiguration(tree, 'lib1', { @@ -22,7 +22,7 @@ describe('component Generator', () => { }) export class LibModule {}` ); - tree.write('libs/lib1/src/index.ts', ''); + tree.write('libs/lib1/src/index.ts', 'export * from "./lib/lib.module";'); // ACT await componentGenerator(tree, { @@ -42,7 +42,7 @@ describe('component Generator', () => { expect(indexSource).toMatchSnapshot(); }); - it('should create the component correctly and not export it', async () => { + it('should create the component correctly and not export it when "export=false"', async () => { // ARRANGE const tree = createTreeWithEmptyWorkspace(2); addProjectConfiguration(tree, 'lib1', { @@ -61,7 +61,7 @@ describe('component Generator', () => { }) export class LibModule {}` ); - tree.write('libs/lib1/src/index.ts', ''); + tree.write('libs/lib1/src/index.ts', 'export * from "./lib/lib.module";'); // ACT await componentGenerator(tree, { @@ -79,7 +79,7 @@ describe('component Generator', () => { const indexSource = tree.read('libs/lib1/src/index.ts', 'utf-8'); expect(indexSource).not.toContain( - `export * from "./lib/example/example.component"` + `export * from "./lib/example/example.component";` ); }); @@ -102,7 +102,7 @@ describe('component Generator', () => { }) export class LibModule {}` ); - tree.write('libs/lib1/src/index.ts', ''); + tree.write('libs/lib1/src/index.ts', 'export * from "./lib/lib.module";'); // ACT await componentGenerator(tree, { @@ -120,11 +120,11 @@ describe('component Generator', () => { const indexSource = tree.read('libs/lib1/src/index.ts', 'utf-8'); expect(indexSource).not.toContain( - `export * from "./lib/example/example.component"` + `export * from "./lib/example/example.component";` ); }); - it('should create the component correctly but not export it when no entry point exists', async () => { + it('should create the component correctly but not export it in the entry point when it does not exist', async () => { // ARRANGE const tree = createTreeWithEmptyWorkspace(2); addProjectConfiguration(tree, 'lib1', { @@ -162,8 +162,41 @@ describe('component Generator', () => { expect(indexExists).toBeFalsy(); }); + it('should not export the component in the entry point when the module it belongs to is not exported', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(2); + addProjectConfiguration(tree, 'lib1', { + projectType: 'library', + sourceRoot: 'libs/lib1/src', + root: 'libs/lib1', + }); + tree.write( + 'libs/lib1/src/lib/lib.module.ts', + ` + import { NgModule } from '@angular/core'; + + @NgModule({ + declarations: [], + exports: [] + }) + export class LibModule {}` + ); + tree.write('libs/lib1/src/index.ts', ''); + + // ACT + await componentGenerator(tree, { + name: 'example', + project: 'lib1', + export: true, + }); + + // ASSERT + const indexSource = tree.read('libs/lib1/src/index.ts', 'utf-8'); + expect(indexSource).toBe(''); + }); + describe('--flat', () => { - it('should create the component correctly and export it', async () => { + it('should create the component correctly and export it in the entry point', async () => { // ARRANGE const tree = createTreeWithEmptyWorkspace(2); addProjectConfiguration(tree, 'lib1', { @@ -182,7 +215,7 @@ describe('component Generator', () => { }) export class LibModule {}` ); - tree.write('libs/lib1/src/index.ts', ''); + tree.write('libs/lib1/src/index.ts', 'export * from "./lib/lib.module";'); // ACT await componentGenerator(tree, { @@ -200,10 +233,10 @@ describe('component Generator', () => { expect(componentSource).toMatchSnapshot(); const indexSource = tree.read('libs/lib1/src/index.ts', 'utf-8'); - expect(indexSource).toContain(`export * from "./lib/example.component"`); + expect(indexSource).toContain(`export * from "./lib/example.component";`); }); - it('should create the component correctly and not export it', async () => { + it('should create the component correctly and not export it when "export=false"', async () => { // ARRANGE const tree = createTreeWithEmptyWorkspace(2); addProjectConfiguration(tree, 'lib1', { @@ -222,7 +255,7 @@ describe('component Generator', () => { }) export class LibModule {}` ); - tree.write('libs/lib1/src/index.ts', ''); + tree.write('libs/lib1/src/index.ts', 'export * from "./lib/lib.module";'); // ACT await componentGenerator(tree, { @@ -241,13 +274,13 @@ describe('component Generator', () => { const indexSource = tree.read('libs/lib1/src/index.ts', 'utf-8'); expect(indexSource).not.toContain( - `export * from "./lib/example.component"` + `export * from "./lib/example.component";` ); }); }); describe('--path', () => { - it('should create the component correctly and export it', async () => { + it('should create the component correctly and export it in the entry point', async () => { // ARRANGE const tree = createTreeWithEmptyWorkspace(2); addProjectConfiguration(tree, 'lib1', { @@ -266,7 +299,7 @@ describe('component Generator', () => { }) export class LibModule {}` ); - tree.write('libs/lib1/src/index.ts', ''); + tree.write('libs/lib1/src/index.ts', 'export * from "./lib/lib.module";'); // ACT await componentGenerator(tree, { @@ -285,7 +318,7 @@ describe('component Generator', () => { const indexSource = tree.read('libs/lib1/src/index.ts', 'utf-8'); expect(indexSource).toContain( - `export * from "./lib/mycomp/example/example.component"` + `export * from "./lib/mycomp/example/example.component";` ); }); @@ -308,7 +341,7 @@ describe('component Generator', () => { }) export class LibModule {}` ); - tree.write('libs/lib1/src/index.ts', ''); + tree.write('libs/lib1/src/index.ts', 'export * from "./lib/lib.module";'); // ACT & ASSERT await expect( @@ -321,4 +354,220 @@ describe('component Generator', () => { ).rejects.toThrow(); }); }); + + describe('--module', () => { + it.each([ + './lib.module.ts', + 'lib.module.ts', + './lib.module', + 'lib.module', + './lib', + 'lib', + ])( + 'should export it in the entry point when "--module" is set to "%s"', + async (module) => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(2); + addProjectConfiguration(tree, 'lib1', { + projectType: 'library', + sourceRoot: 'libs/lib1/src', + root: 'libs/lib1', + }); + tree.write( + 'libs/lib1/src/lib/lib.module.ts', + ` + import { NgModule } from '@angular/core'; + + @NgModule({ + declarations: [], + exports: [] + }) + export class LibModule {}` + ); + tree.write( + 'libs/lib1/src/index.ts', + 'export * from "./lib/lib.module";' + ); + + // ACT + await componentGenerator(tree, { + name: 'example', + project: 'lib1', + module, + export: true, + }); + + // ASSERT + const indexSource = tree.read('libs/lib1/src/index.ts', 'utf-8'); + expect(indexSource).toContain( + `export * from "./lib/example/example.component";` + ); + } + ); + + it('should not export it in the entry point when the module it belong to is not exported', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(2); + addProjectConfiguration(tree, 'lib1', { + projectType: 'library', + sourceRoot: 'libs/lib1/src', + root: 'libs/lib1', + }); + tree.write( + 'libs/lib1/src/lib/lib.module.ts', + ` + import { NgModule } from '@angular/core'; + + @NgModule({ + declarations: [], + exports: [] + }) + export class LibModule {}` + ); + tree.write( + 'libs/lib1/src/lib/not-exported.module.ts', + ` + import { NgModule } from '@angular/core'; + + @NgModule({ + declarations: [], + exports: [] + }) + export class NotExportedModule {}` + ); + tree.write('libs/lib1/src/index.ts', 'export * from "./lib/lib.module";'); + + // ACT + await componentGenerator(tree, { + name: 'example', + project: 'lib1', + module: 'not-exported', + export: true, + }); + + // ASSERT + const indexSource = tree.read('libs/lib1/src/index.ts', 'utf-8'); + expect(indexSource).toMatchInlineSnapshot( + `"export * from \\"./lib/lib.module\\";"` + ); + }); + }); + + describe('secondary entry points', () => { + it('should create the component correctly and export it in the entry point', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(2); + addProjectConfiguration(tree, 'lib1', { + projectType: 'library', + sourceRoot: 'libs/lib1/src', + root: 'libs/lib1', + }); + tree.write( + 'libs/lib1/src/lib/lib.module.ts', + ` + import { NgModule } from '@angular/core'; + + @NgModule({ + declarations: [], + exports: [] + }) + export class LibModule {}` + ); + tree.write('libs/lib1/src/index.ts', 'export * from "./lib/lib.module";'); + + // secondary entry point + writeJson(tree, 'libs/lib1/secondary/ng-package.json', { + lib: { entryFile: './src/index.ts' }, + }); + tree.write( + 'libs/lib1/secondary/src/index.ts', + 'export * from "./lib/secondary.module";' + ); + tree.write( + 'libs/lib1/secondary/src/lib/secondary.module.ts', + ` + import { NgModule } from '@angular/core'; + + @NgModule({ + declarations: [], + exports: [] + }) + export class SecondaryModule {}` + ); + + // ACT + await componentGenerator(tree, { + name: 'example', + project: 'lib1', + path: 'libs/lib1/secondary/src/lib', + export: true, + }); + + // ASSERT + const componentSource = tree.read( + 'libs/lib1/secondary/src/lib/example/example.component.ts', + 'utf-8' + ); + expect(componentSource).toMatchSnapshot(); + + const secondaryIndexSource = tree.read( + 'libs/lib1/secondary/src/index.ts', + 'utf-8' + ); + expect(secondaryIndexSource).toMatchSnapshot(); + }); + + it('should not export the component in the entry point when the module it belongs to is not exported', async () => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(2); + addProjectConfiguration(tree, 'lib1', { + projectType: 'library', + sourceRoot: 'libs/lib1/src', + root: 'libs/lib1', + }); + tree.write( + 'libs/lib1/src/lib/lib.module.ts', + ` + import { NgModule } from '@angular/core'; + + @NgModule({ + declarations: [], + exports: [] + }) + export class LibModule {}` + ); + tree.write('libs/lib1/src/index.ts', 'export * from "./lib/lib.module";'); + + // secondary entry point + writeJson(tree, 'libs/lib1/secondary/ng-package.json', { + lib: { entryFile: './src/index.ts' }, + }); + tree.write('libs/lib1/secondary/src/index.ts', ''); + tree.write( + 'libs/lib1/secondary/src/lib/secondary.module.ts', + ` + import { NgModule } from '@angular/core'; + + @NgModule({ + declarations: [], + exports: [] + }) + export class SecondaryModule {}` + ); + + // ACT + await componentGenerator(tree, { + name: 'example', + project: 'lib1', + export: true, + }); + + // ASSERT + const indexSource = tree.read( + 'libs/lib1/secondary/src/index.ts', + 'utf-8' + ); + expect(indexSource).toBe(''); + }); + }); }); diff --git a/packages/angular/src/generators/component/component.ts b/packages/angular/src/generators/component/component.ts index 92a3fdfd97a74..f21bd5dfe852b 100644 --- a/packages/angular/src/generators/component/component.ts +++ b/packages/angular/src/generators/component/component.ts @@ -1,13 +1,23 @@ -import { joinPathFragments, logger, names, readJson, Tree } from '@nrwl/devkit'; -import type { Schema } from './schema'; -import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter'; +import type { Tree } from '@nrwl/devkit'; import { formatFiles, - readWorkspaceConfiguration, - readProjectConfiguration, + joinPathFragments, + logger, + names, normalizePath, + readProjectConfiguration, + readWorkspaceConfiguration, + stripIndents, } from '@nrwl/devkit'; +import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter'; import { pathStartsWith } from '../utils/path'; +import { + findModuleFromOptions, + getRelativeImportToFile, + locateLibraryEntryPointFromDirectory, + shouldExportInEntryPoint, +} from './lib'; +import type { Schema } from './schema'; export async function componentGenerator(tree: Tree, schema: Schema) { checkPathUnderProjectRoot(tree, schema); @@ -16,16 +26,14 @@ export async function componentGenerator(tree: Tree, schema: Schema) { '@schematics/angular', 'component' ); - await angularComponentSchematic(tree, { - ...schema, - }); + await angularComponentSchematic(tree, { ...schema }); - exportComponent(tree, schema); + exportComponentInEntryPoint(tree, schema); await formatFiles(tree); } -function checkPathUnderProjectRoot(tree: Tree, schema: Partial) { +function checkPathUnderProjectRoot(tree: Tree, schema: Schema): void { if (!schema.path) { return; } @@ -41,12 +49,13 @@ function checkPathUnderProjectRoot(tree: Tree, schema: Partial) { if (!pathStartsWith(pathToComponent, root)) { throw new Error( - `The path provided for the component (${schema.path}) does not exist under the project root (${root}).` + `The path provided for the component (${schema.path}) does not exist under the project root (${root}). ` + + `Please make sure to provide a path that exists under the project root.` ); } } -function exportComponent(tree: Tree, schema: Schema) { +function exportComponentInEntryPoint(tree: Tree, schema: Schema): void { if (!schema.export || schema.skipImport) { return; } @@ -66,52 +75,51 @@ function exportComponent(tree: Tree, schema: Schema) { const componentNames = names(schema.name); const componentFileName = `${componentNames.fileName}.${ - schema.type ?? 'component' + schema.type ? names(schema.type).fileName : 'component' }`; - let componentDirectory = schema.flat - ? joinPathFragments(sourceRoot, 'lib') - : joinPathFragments(sourceRoot, 'lib', componentNames.fileName); - - if (schema.path) { - componentDirectory = schema.flat - ? normalizePath(schema.path) - : joinPathFragments(schema.path, componentNames.fileName); - } + const projectSourceRoot = sourceRoot ?? joinPathFragments(root, 'src'); + schema.path ??= joinPathFragments(projectSourceRoot, 'lib'); + const componentDirectory = schema.flat + ? normalizePath(schema.path) + : joinPathFragments(schema.path, componentNames.fileName); const componentFilePath = joinPathFragments( componentDirectory, `${componentFileName}.ts` ); - const ngPackageJsonPath = joinPathFragments(root, 'ng-package.json'); - const ngPackageEntryPoint = tree.exists(ngPackageJsonPath) - ? readJson(tree, ngPackageJsonPath).lib?.entryFile - : undefined; - - const projectEntryPoint = ngPackageEntryPoint - ? joinPathFragments(root, ngPackageEntryPoint) - : joinPathFragments(sourceRoot, `index.ts`); - - if (!tree.exists(projectEntryPoint)) { - // Let's not error, simply warn the user - // It's not too much effort to manually do this - // It would be more frustrating to have to find the correct path and re-run the command + const entryPointPath = locateLibraryEntryPointFromDirectory( + tree, + componentDirectory, + root, + projectSourceRoot + ); + if (!entryPointPath) { logger.warn( - `Could not export the component in the library's entry point. Unable to determine project's entry point. The path ${projectEntryPoint} does not exist. The component has still been created.` + `Unable to determine whether the component should be exported in the library entry point file. ` + + `The library's entry point file could not be found. Skipping exporting the component in the entry point file.` ); return; } - const relativePathFromEntryPoint = `.${componentFilePath - .split(sourceRoot)[1] - .replace('.ts', '')}`; + const modulePath = findModuleFromOptions(tree, schema, root); + if (!shouldExportInEntryPoint(tree, entryPointPath, modulePath)) { + return; + } - const updateEntryPointContent = `${tree.read(projectEntryPoint, 'utf-8')} + const relativePathFromEntryPoint = getRelativeImportToFile( + entryPointPath, + componentFilePath + ); + const updateEntryPointContent = stripIndents`${tree.read( + entryPointPath, + 'utf-8' + )} export * from "${relativePathFromEntryPoint}";`; - tree.write(projectEntryPoint, updateEntryPointContent); + tree.write(entryPointPath, updateEntryPointContent); } export default componentGenerator; diff --git a/packages/angular/src/generators/component/lib/entry-point.ts b/packages/angular/src/generators/component/lib/entry-point.ts new file mode 100644 index 0000000000000..ae01aefa54734 --- /dev/null +++ b/packages/angular/src/generators/component/lib/entry-point.ts @@ -0,0 +1,56 @@ +import type { Tree } from '@nrwl/devkit'; +import { joinPathFragments, readJson } from '@nrwl/devkit'; +import { tsquery } from '@phenomnomnominal/tsquery'; +import { dirname } from 'path'; +import type { StringLiteral } from 'typescript'; +import { getRelativeImportToFile } from './path'; + +export function locateLibraryEntryPointFromDirectory( + tree: Tree, + directory: string, + projectRoot: string, + projectSourceRoot: string +): string | null { + const ngPackageJsonPath = joinPathFragments(directory, 'ng-package.json'); + let entryPointFile = tree.exists(ngPackageJsonPath) + ? readJson(tree, ngPackageJsonPath).lib?.entryFile + : null; + + if (entryPointFile) { + return joinPathFragments(directory, entryPointFile); + } + + if (directory === projectRoot) { + const indexFile = joinPathFragments(projectSourceRoot, 'index.ts'); + + return tree.exists(indexFile) ? indexFile : null; + } + + return locateLibraryEntryPointFromDirectory( + tree, + dirname(directory), + projectRoot, + projectSourceRoot + ); +} + +export function shouldExportInEntryPoint( + tree: Tree, + entryPoint: string, + modulePath: string +): boolean { + if (!modulePath) { + return false; + } + + const moduleImportPath = getRelativeImportToFile(entryPoint, modulePath); + const entryPointContent = tree.read(entryPoint, 'utf-8'); + const entryPointAst = tsquery.ast(entryPointContent); + const moduleExport = tsquery( + entryPointAst, + `ExportDeclaration StringLiteral[value='${moduleImportPath}']`, + { visitAllChildren: true } + )[0] as StringLiteral; + + return Boolean(moduleExport); +} diff --git a/packages/angular/src/generators/component/lib/index.ts b/packages/angular/src/generators/component/lib/index.ts new file mode 100644 index 0000000000000..4717f631bdc4e --- /dev/null +++ b/packages/angular/src/generators/component/lib/index.ts @@ -0,0 +1,3 @@ +export * from './entry-point'; +export * from './module'; +export * from './path'; diff --git a/packages/angular/src/generators/component/lib/module.ts b/packages/angular/src/generators/component/lib/module.ts new file mode 100644 index 0000000000000..cb6edc69b6c73 --- /dev/null +++ b/packages/angular/src/generators/component/lib/module.ts @@ -0,0 +1,84 @@ +import type { Tree } from '@nrwl/devkit'; +import { joinPathFragments, normalizePath } from '@nrwl/devkit'; +import { basename, dirname } from 'path'; +import type { Schema } from '../schema'; + +// Adapted from https://github.com/angular/angular-cli/blob/main/packages/schematics/angular/utility/find-module.ts#L29 +// to match the logic in the component schematic. It doesn't throw if it can't +// find a module since the schematic would have thrown before getting here. +const moduleExt = '.module.ts'; +const routingModuleExt = '-routing.module.ts'; + +export function findModuleFromOptions( + tree: Tree, + options: Schema, + projectRoot: string +): string | null { + if (!options.module) { + const pathToCheck = joinPathFragments(options.path, options.name); + + return normalizePath(findModule(tree, pathToCheck, projectRoot)); + } else { + const modulePath = joinPathFragments(options.path, options.module); + const componentPath = joinPathFragments(options.path, options.name); + const moduleBaseName = basename(modulePath); + + const candidateSet = new Set([options.path]); + + const projectRootParent = dirname(projectRoot); + for (let dir = modulePath; dir !== projectRootParent; dir = dirname(dir)) { + candidateSet.add(dir); + } + for (let dir = componentPath; dir !== projectRoot; dir = dirname(dir)) { + candidateSet.add(dir); + } + + const candidatesDirs = [...candidateSet].sort( + (a, b) => b.length - a.length + ); + for (const c of candidatesDirs) { + const candidateFiles = [ + '', + `${moduleBaseName}.ts`, + `${moduleBaseName}${moduleExt}`, + ].map((x) => joinPathFragments(c, x)); + + for (const sc of candidateFiles) { + if (tree.isFile(sc)) { + return normalizePath(sc); + } + } + } + + return null; + } +} + +function findModule( + tree: Tree, + generateDir: string, + projectRoot: string +): string | null { + let dir = generateDir; + const projectRootParent = dirname(projectRoot); + + while (dir !== projectRootParent) { + const allMatches = tree + .children(dir) + .map((path) => joinPathFragments(dir, path)) + .filter((path) => tree.isFile(path) && path.endsWith(moduleExt)); + const filteredMatches = allMatches.filter( + (path) => !path.endsWith(routingModuleExt) + ); + + if (filteredMatches.length == 1) { + return filteredMatches[0]; + } else if (filteredMatches.length > 1) { + return null; + } + + dir = dirname(dir); + } + + return null; +} diff --git a/packages/angular/src/generators/component/lib/path.ts b/packages/angular/src/generators/component/lib/path.ts new file mode 100644 index 0000000000000..6569436e2298b --- /dev/null +++ b/packages/angular/src/generators/component/lib/path.ts @@ -0,0 +1,17 @@ +import { joinPathFragments } from '@nrwl/devkit'; +import { basename, dirname, relative } from 'path'; + +export function getRelativeImportToFile( + sourceFilePath: string, + targetFilePath: string +): string { + const relativeDirToTarget = relative( + dirname(sourceFilePath), + dirname(targetFilePath) + ); + + return `./${joinPathFragments( + relativeDirToTarget, + basename(targetFilePath, '.ts') + )}`; +} diff --git a/packages/angular/src/generators/component/schema.json b/packages/angular/src/generators/component/schema.json index 17903c6b894e5..6da473646ac1c 100644 --- a/packages/angular/src/generators/component/schema.json +++ b/packages/angular/src/generators/component/schema.json @@ -108,7 +108,7 @@ }, "export": { "type": "boolean", - "description": "Specifies if the component should be exported in the declaring `NgModule`. Additionally, if the project is a library, the component will be exported from the project's entry point (normally `index.ts`).", + "description": "Specifies if the component should be exported in the declaring `NgModule`. Additionally, if the project is a library, the component will be exported from the project's entry point (normally `index.ts`) if the module it belongs to is also exported.", "default": false } },