Skip to content

Commit

Permalink
feat(angular): add backwards support to component (#13583)
Browse files Browse the repository at this point in the history
  • Loading branch information
Coly010 committed Dec 2, 2022
1 parent 1d4900d commit 0187000
Show file tree
Hide file tree
Showing 9 changed files with 1,262 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`component Generator support angular 14 --flat should create the component correctly and export it in the entry point 1`] = `
"import { Component } from '@angular/core';
@Component({
selector: 'example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent {
}
"
`;

exports[`component Generator support angular 14 --flat should create the component correctly and not export it when "export=false" 1`] = `
"import { Component } from '@angular/core';
@Component({
selector: 'example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent {
}
"
`;

exports[`component Generator support angular 14 --path should create the component correctly and export it in the entry point 1`] = `
"import { Component } from '@angular/core';
@Component({
selector: 'example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent {
}
"
`;

exports[`component Generator support angular 14 secondary entry points should create the component correctly and export it in the entry point 1`] = `
"import { Component } from '@angular/core';
@Component({
selector: 'example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent {
}
"
`;

exports[`component Generator support angular 14 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 support angular 14 should create the component correctly and export it in the entry point when "export=true" 1`] = `
"import { Component } from '@angular/core';
@Component({
selector: 'example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent {
}
"
`;

exports[`component Generator support angular 14 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 support angular 14 should create the component correctly and export it in the entry point when is standalone and "export=true" 1`] = `
"import { Component } 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 {
}
"
`;

exports[`component Generator support angular 14 should create the component correctly and not export it in the entry point when "export=false" 1`] = `
"import { Component } from '@angular/core';
@Component({
selector: 'example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent {
}
"
`;

exports[`component Generator support angular 14 should create the component correctly and not export it in the entry point when is standalone and "export=false" 1`] = `
"import { Component } 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 {
}
"
`;

exports[`component Generator support angular 14 should create the component correctly and not export it when "--skip-import=true" 1`] = `
"import { Component } from '@angular/core';
@Component({
selector: 'example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent {
}
"
`;

exports[`component Generator support angular 14 should create the component correctly but not export it in the entry point when it does not exist 1`] = `
"import { Component } from '@angular/core';
@Component({
selector: 'example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent {
}
"
`;
53 changes: 53 additions & 0 deletions packages/angular/src/generators/component/angular-v14/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { Tree } from '@nrwl/devkit';
import {
formatFiles,
normalizePath,
readProjectConfiguration,
readWorkspaceConfiguration,
} from '@nrwl/devkit';
import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter';
import { pathStartsWith } from '../../utils/path';
import { exportComponentInEntryPoint } from './lib/component';
import { normalizeOptions } from './lib/normalize-options';
import type { NormalizedSchema, Schema } from './schema';

export async function componentGenerator(tree: Tree, rawOptions: Schema) {
const options = await normalizeOptions(tree, rawOptions);
const { projectSourceRoot, ...schematicOptions } = options;

checkPathUnderProjectRoot(tree, options);

const angularComponentSchematic = wrapAngularDevkitSchematic(
'@schematics/angular',
'component'
);
await angularComponentSchematic(tree, schematicOptions);

exportComponentInEntryPoint(tree, options);

await formatFiles(tree);
}

function checkPathUnderProjectRoot(tree: Tree, schema: NormalizedSchema): void {
if (!schema.path) {
return;
}

const project =
schema.project ?? readWorkspaceConfiguration(tree).defaultProject;
const { root } = readProjectConfiguration(tree, project);

let pathToComponent = normalizePath(schema.path);
pathToComponent = pathToComponent.startsWith('/')
? pathToComponent.slice(1)
: pathToComponent;

if (!pathStartsWith(pathToComponent, root)) {
throw new Error(
`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.`
);
}
}

export default componentGenerator;
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { Tree } from '@nrwl/devkit';
import { logger, readProjectConfiguration, stripIndents } from '@nrwl/devkit';
import { getComponentFileInfo } from '../../../utils/file-info';
import { locateLibraryEntryPointFromDirectory } from '../../../utils/entry-point';
import { getRelativeImportToFile } from '../../../utils/path';
import type { NormalizedSchema } from '../schema';
import { shouldExportInEntryPoint } from './entry-point';
import { findModuleFromOptions } from './module';

export function exportComponentInEntryPoint(
tree: Tree,
schema: NormalizedSchema
): void {
if (!schema.export || (schema.skipImport && !schema.standalone)) {
return;
}

const { root, projectType } = readProjectConfiguration(tree, schema.project);

if (projectType === 'application') {
return;
}

const { directory, filePath } = getComponentFileInfo(tree, schema);

const entryPointPath = locateLibraryEntryPointFromDirectory(
tree,
directory,
root,
schema.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,
filePath
);
const updateEntryPointContent = stripIndents`${tree.read(
entryPointPath,
'utf-8'
)}
export * from "${relativePathFromEntryPoint}";`;

tree.write(entryPointPath, updateEntryPointContent);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Tree } from '@nrwl/devkit';
import { tsquery } from '@phenomnomnominal/tsquery';
import type { StringLiteral } from 'typescript';
import { getRelativeImportToFile } from '../../../utils/path';

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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type { Tree } from '@nrwl/devkit';
import { joinPathFragments, normalizePath } from '@nrwl/devkit';
import { basename, dirname } from 'path';
import type { NormalizedSchema } 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: NormalizedSchema,
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<string>([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;
}

0 comments on commit 0187000

Please sign in to comment.