Skip to content

Commit

Permalink
feat(angular): support exporting standalone component in library's en…
Browse files Browse the repository at this point in the history
…try point file (#10544)
  • Loading branch information
leosvelperez committed Jun 1, 2022
1 parent 10e9992 commit 98014dd
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 88 deletions.
2 changes: 1 addition & 1 deletion docs/generated/packages/angular.json
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
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
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
@@ -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
@@ -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
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

1 comment on commit 98014dd

@vercel
Copy link

@vercel vercel bot commented on 98014dd Jun 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx-dev-nrwl.vercel.app
nx.dev
nx-dev-git-master-nrwl.vercel.app
nx-five.vercel.app

Please sign in to comment.