From 98014dd6600e3f6aa059b5b40221efbfeece2163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Wed, 1 Jun 2022 17:00:26 +0100 Subject: [PATCH] feat(angular): support exporting standalone component in library's entry point file (#10544) --- docs/generated/packages/angular.json | 2 +- .../__snapshots__/component.spec.ts.snap | 52 ++++++++++- .../generators/component/component.spec.ts | 88 ++++++++++++++++++- .../src/generators/component/component.ts | 78 +--------------- .../src/generators/component/lib/component.ts | 86 ++++++++++++++++++ .../src/generators/component/lib/index.ts | 3 - .../src/generators/component/schema.json | 2 +- 7 files changed, 223 insertions(+), 88 deletions(-) create mode 100644 packages/angular/src/generators/component/lib/component.ts delete mode 100644 packages/angular/src/generators/component/lib/index.ts diff --git a/docs/generated/packages/angular.json b/docs/generated/packages/angular.json index 229f008f0cb94..882ac72acbef5 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`) if the module it belongs to is also exported.", + "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 or if the component is standalone.", "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 9a1d4f259b5ad..7533c309f944c 100644 --- a/packages/angular/src/generators/component/__snapshots__/component.spec.ts.snap +++ b/packages/angular/src/generators/component/__snapshots__/component.spec.ts.snap @@ -81,7 +81,7 @@ exports[`component Generator secondary entry points should create the component export * from \\"./lib/example/example.component\\";" `; -exports[`component Generator should create the component correctly and export it in the entry point 1`] = ` +exports[`component Generator should create the component correctly and export it in the entry point when "export=true" 1`] = ` "import { Component, OnInit } from '@angular/core'; @Component({ @@ -100,12 +100,34 @@ export class ExampleComponent implements OnInit { " `; -exports[`component Generator should create the component correctly and export it in the entry point 2`] = ` +exports[`component Generator should create the component correctly and export it in the entry point when "export=true" 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`] = ` +exports[`component Generator should create the component correctly and export it in the entry point when is standalone and "export=true" 1`] = ` +"import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'example', + standalone: true, + imports: [CommonModule], + templateUrl: './example.component.html', + styleUrls: ['./example.component.css'] +}) +export class ExampleComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} +" +`; + +exports[`component Generator should create the component correctly and not export it in the entry point when "export=false" 1`] = ` "import { Component, OnInit } from '@angular/core'; @Component({ @@ -124,7 +146,29 @@ export class ExampleComponent implements OnInit { " `; -exports[`component Generator should create the component correctly and not export it when "export=false" 1`] = ` +exports[`component Generator should create the component correctly and not export it in the entry point when is standalone and "export=false" 1`] = ` +"import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'example', + standalone: true, + imports: [CommonModule], + templateUrl: './example.component.html', + styleUrls: ['./example.component.css'] +}) +export class ExampleComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} +" +`; + +exports[`component Generator should create the component correctly and not export it when "--skip-import=true" 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 6a8b3ac1b4f19..09ba8b92e07e5 100644 --- a/packages/angular/src/generators/component/component.spec.ts +++ b/packages/angular/src/generators/component/component.spec.ts @@ -3,7 +3,7 @@ import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; import componentGenerator from './component'; describe('component Generator', () => { - it('should create the component correctly and export it in the entry point', async () => { + it('should create the component correctly and export it in the entry point when "export=true"', async () => { // ARRANGE const tree = createTreeWithEmptyWorkspace(2); addProjectConfiguration(tree, 'lib1', { @@ -42,7 +42,90 @@ describe('component Generator', () => { expect(indexSource).toMatchSnapshot(); }); - it('should create the component correctly and not export it when "export=false"', async () => { + it('should create the component correctly and export it in the entry point when is standalone and "export=true"', 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', + standalone: true, + export: true, + }); + + // ASSERT + const componentSource = tree.read( + 'libs/lib1/src/lib/example/example.component.ts', + 'utf-8' + ); + expect(componentSource).toMatchSnapshot(); + + const indexSource = tree.read('libs/lib1/src/index.ts', 'utf-8'); + expect(indexSource).toMatchInlineSnapshot( + `"export * from \\"./lib/example/example.component\\";"` + ); + }); + + it('should create the component correctly and not export it in the entry point when "export=false"', 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";'); + + // ACT + await componentGenerator(tree, { + name: 'example', + project: 'lib1', + export: false, + }); + + // ASSERT + const componentSource = tree.read( + 'libs/lib1/src/lib/example/example.component.ts', + 'utf-8' + ); + expect(componentSource).toMatchSnapshot(); + + const indexSource = tree.read('libs/lib1/src/index.ts', 'utf-8'); + expect(indexSource).not.toContain( + `export * from "./lib/example/example.component";` + ); + }); + + it('should create the component correctly and not export it in the entry point when is standalone and "export=false"', async () => { // ARRANGE const tree = createTreeWithEmptyWorkspace(2); addProjectConfiguration(tree, 'lib1', { @@ -67,6 +150,7 @@ describe('component Generator', () => { await componentGenerator(tree, { name: 'example', project: 'lib1', + standalone: true, export: false, }); diff --git a/packages/angular/src/generators/component/component.ts b/packages/angular/src/generators/component/component.ts index f21bd5dfe852b..5082ff974ac24 100644 --- a/packages/angular/src/generators/component/component.ts +++ b/packages/angular/src/generators/component/component.ts @@ -1,22 +1,13 @@ import type { Tree } from '@nrwl/devkit'; import { formatFiles, - 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 { exportComponentInEntryPoint } from './lib/component'; import type { Schema } from './schema'; export async function componentGenerator(tree: Tree, schema: Schema) { @@ -55,71 +46,4 @@ function checkPathUnderProjectRoot(tree: Tree, schema: Schema): void { } } -function exportComponentInEntryPoint(tree: Tree, schema: Schema): void { - if (!schema.export || schema.skipImport) { - return; - } - - const project = - schema.project ?? readWorkspaceConfiguration(tree).defaultProject; - - const { root, sourceRoot, projectType } = readProjectConfiguration( - tree, - project - ); - - if (projectType === 'application') { - return; - } - - const componentNames = names(schema.name); - - const componentFileName = `${componentNames.fileName}.${ - schema.type ? names(schema.type).fileName : 'component' - }`; - - 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 entryPointPath = locateLibraryEntryPointFromDirectory( - tree, - componentDirectory, - root, - projectSourceRoot - ); - if (!entryPointPath) { - logger.warn( - `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 modulePath = findModuleFromOptions(tree, schema, root); - if (!shouldExportInEntryPoint(tree, entryPointPath, modulePath)) { - return; - } - - const relativePathFromEntryPoint = getRelativeImportToFile( - entryPointPath, - componentFilePath - ); - const updateEntryPointContent = stripIndents`${tree.read( - entryPointPath, - 'utf-8' - )} - export * from "${relativePathFromEntryPoint}";`; - - tree.write(entryPointPath, updateEntryPointContent); -} - export default componentGenerator; diff --git a/packages/angular/src/generators/component/lib/component.ts b/packages/angular/src/generators/component/lib/component.ts new file mode 100644 index 0000000000000..0574cf9c85768 --- /dev/null +++ b/packages/angular/src/generators/component/lib/component.ts @@ -0,0 +1,86 @@ +import type { Tree } from '@nrwl/devkit'; +import { + joinPathFragments, + logger, + names, + normalizePath, + readProjectConfiguration, + readWorkspaceConfiguration, + stripIndents, +} from '@nrwl/devkit'; +import type { Schema } from '../schema'; +import { + locateLibraryEntryPointFromDirectory, + shouldExportInEntryPoint, +} from './entry-point'; +import { findModuleFromOptions } from './module'; +import { getRelativeImportToFile } from './path'; + +export function exportComponentInEntryPoint(tree: Tree, schema: Schema): void { + if (!schema.export || (schema.skipImport && !schema.standalone)) { + return; + } + + const project = + schema.project ?? readWorkspaceConfiguration(tree).defaultProject; + + const { root, sourceRoot, projectType } = readProjectConfiguration( + tree, + project + ); + + if (projectType === 'application') { + return; + } + + const componentNames = names(schema.name); + + const componentFileName = `${componentNames.fileName}.${ + schema.type ? names(schema.type).fileName : 'component' + }`; + + 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 entryPointPath = locateLibraryEntryPointFromDirectory( + tree, + componentDirectory, + root, + projectSourceRoot + ); + if (!entryPointPath) { + logger.warn( + `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; + } + + if (!schema.standalone) { + const modulePath = findModuleFromOptions(tree, schema, root); + if (!shouldExportInEntryPoint(tree, entryPointPath, modulePath)) { + return; + } + } + + const relativePathFromEntryPoint = getRelativeImportToFile( + entryPointPath, + componentFilePath + ); + const updateEntryPointContent = stripIndents`${tree.read( + entryPointPath, + 'utf-8' + )} + export * from "${relativePathFromEntryPoint}";`; + + tree.write(entryPointPath, updateEntryPointContent); +} diff --git a/packages/angular/src/generators/component/lib/index.ts b/packages/angular/src/generators/component/lib/index.ts deleted file mode 100644 index 4717f631bdc4e..0000000000000 --- a/packages/angular/src/generators/component/lib/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './entry-point'; -export * from './module'; -export * from './path'; diff --git a/packages/angular/src/generators/component/schema.json b/packages/angular/src/generators/component/schema.json index 6da473646ac1c..0f1ee6b914f27 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`) if the module it belongs to is also exported.", + "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 or if the component is standalone.", "default": false } },