Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(angular): support exporting standalone component in library's entry point file #10544

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/generated/packages/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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({
Expand All @@ -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({
Expand Down
88 changes: 86 additions & 2 deletions packages/angular/src/generators/component/component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', {
Expand Down Expand Up @@ -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', {
Expand All @@ -67,6 +150,7 @@ describe('component Generator', () => {
await componentGenerator(tree, {
name: 'example',
project: 'lib1',
standalone: true,
export: false,
});

Expand Down
78 changes: 1 addition & 77 deletions packages/angular/src/generators/component/component.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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;
86 changes: 86 additions & 0 deletions packages/angular/src/generators/component/lib/component.ts
Original file line number Diff line number Diff line change
@@ -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);
}
3 changes: 0 additions & 3 deletions packages/angular/src/generators/component/lib/index.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/angular/src/generators/component/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
},
Expand Down