fix(angular): improve prefix handling across generators (#16913)

This commit is contained in:
Leosvel Pérez Espinosa 2023-05-10 16:18:20 +01:00 committed by GitHub
parent 20f25bfe1c
commit 284fedab46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 72 additions and 39 deletions

View File

@ -11,7 +11,7 @@ import { E2eTestRunner, UnitTestRunner } from '../../../utils/test-runners';
import { Linter } from '@nx/linter'; import { Linter } from '@nx/linter';
import { import {
normalizeDirectory, normalizeDirectory,
normalizePrefix, normalizeNewProjectPrefix,
normalizeProjectName, normalizeProjectName,
} from '../../utils/project'; } from '../../utils/project';
@ -45,7 +45,7 @@ export function normalizeOptions(
? options.tags.split(',').map((s) => s.trim()) ? options.tags.split(',').map((s) => s.trim())
: []; : [];
const prefix = normalizePrefix(options.prefix, npmScope); const prefix = normalizeNewProjectPrefix(options.prefix, npmScope, 'app');
options.standaloneConfig = options.standaloneConfig ?? standaloneAsDefault; options.standaloneConfig = options.standaloneConfig ?? standaloneAsDefault;

View File

@ -64,6 +64,7 @@ export async function host(tree: Tree, options: Schema) {
skipFormat: true, skipFormat: true,
skipE2E, skipE2E,
e2eProjectName: skipE2E ? undefined : `${appName}-e2e`, e2eProjectName: skipE2E ? undefined : `${appName}-e2e`,
prefix: options.prefix,
}); });
let installTasks = [appInstallTask]; let installTasks = [appInstallTask];

View File

@ -10,7 +10,7 @@ import { Schema } from '../schema';
import { NormalizedSchema } from './normalized-schema'; import { NormalizedSchema } from './normalized-schema';
import { Linter } from '@nx/linter'; import { Linter } from '@nx/linter';
import { UnitTestRunner } from '../../../utils/test-runners'; import { UnitTestRunner } from '../../../utils/test-runners';
import { normalizePrefix } from '../../utils/project'; import { normalizeNewProjectPrefix } from '../../utils/project';
export function normalizeOptions(host: Tree, schema: Schema): NormalizedSchema { export function normalizeOptions(host: Tree, schema: Schema): NormalizedSchema {
// Create a schema with populated default values // Create a schema with populated default values
@ -55,7 +55,7 @@ export function normalizeOptions(host: Tree, schema: Schema): NormalizedSchema {
? options.tags.split(',').map((s) => s.trim()) ? options.tags.split(',').map((s) => s.trim())
: []; : [];
const modulePath = `${projectRoot}/src/lib/${fileName}.module.ts`; const modulePath = `${projectRoot}/src/lib/${fileName}.module.ts`;
const prefix = normalizePrefix(options.prefix, npmScope); const prefix = normalizeNewProjectPrefix(options.prefix, npmScope, 'lib');
options.standaloneConfig = options.standaloneConfig ?? standaloneAsDefault; options.standaloneConfig = options.standaloneConfig ?? standaloneAsDefault;

View File

@ -57,6 +57,7 @@ export async function remote(tree: Tree, options: Schema) {
skipE2E, skipE2E,
e2eProjectName: skipE2E ? undefined : `${appName}-e2e`, e2eProjectName: skipE2E ? undefined : `${appName}-e2e`,
standalone: options.standalone, standalone: options.standalone,
prefix: options.prefix,
}); });
let installTasks = [appInstallTask]; let installTasks = [appInstallTask];

View File

@ -1,12 +1,6 @@
import type { Tree } from '@nx/devkit'; import type { Tree } from '@nx/devkit';
import { import { generateFiles, joinPathFragments } from '@nx/devkit';
generateFiles,
joinPathFragments,
readNxJson,
readProjectConfiguration,
} from '@nx/devkit';
import { addRoute } from '../../../utils/nx-devkit/route-utils'; import { addRoute } from '../../../utils/nx-devkit/route-utils';
import type { AngularProjectConfiguration } from '../../../utils/types';
import type { Schema } from '../schema'; import type { Schema } from '../schema';
export function addRemoteEntry( export function addRemoteEntry(
@ -14,11 +8,6 @@ export function addRemoteEntry(
{ appName, routing, prefix, standalone }: Schema, { appName, routing, prefix, standalone }: Schema,
appRoot: string appRoot: string
) { ) {
prefix =
prefix ??
(readProjectConfiguration(tree, appName) as AngularProjectConfiguration)
?.prefix ??
readNxJson(tree).npmScope;
generateFiles( generateFiles(
tree, tree,
standalone standalone

View File

@ -5,6 +5,7 @@ export * from './change-build-target';
export * from './fix-bootstrap'; export * from './fix-bootstrap';
export * from './generate-config'; export * from './generate-config';
export * from './get-remotes-with-ports'; export * from './get-remotes-with-ports';
export * from './normalize-options';
export * from './set-tsconfig-target'; export * from './set-tsconfig-target';
export * from './setup-host-if-dynamic'; export * from './setup-host-if-dynamic';
export * from './setup-serve-target'; export * from './setup-serve-target';

View File

@ -0,0 +1,14 @@
import type { Tree } from '@nx/devkit';
import { getProjectPrefix } from '../../utils/project';
import type { NormalizedOptions, Schema } from '../schema';
export function normalizeOptions(
tree: Tree,
options: Schema
): NormalizedOptions {
return {
...options,
federationType: options.federationType ?? 'static',
prefix: options.prefix ?? getProjectPrefix(tree, options.appName),
};
}

View File

@ -1,8 +1,6 @@
import { Tree } from 'nx/src/generators/tree'; import type { Tree } from '@nx/devkit';
import { joinPathFragments, readProjectConfiguration } from '@nx/devkit';
import { Schema } from '../schema'; import { Schema } from '../schema';
import { readProjectConfiguration } from 'nx/src/generators/utils/project-configuration';
import { joinPathFragments } from 'nx/src/utils/path';
import { readNxJson } from '@nx/devkit';
export function removeDeadCodeFromRemote(tree: Tree, options: Schema) { export function removeDeadCodeFromRemote(tree: Tree, options: Schema) {
const projectName = options.appName; const projectName = options.appName;
@ -73,13 +71,15 @@ export class AppModule {}`
} else { } else {
tree.delete(pathToAppComponent); tree.delete(pathToAppComponent);
const prefix = options.prefix ?? readNxJson(tree).npmScope;
const remoteEntrySelector = `${prefix}-${projectName}-entry`;
const pathToIndexHtml = project.targets.build.options.index; const pathToIndexHtml = project.targets.build.options.index;
const indexContents = tree.read(pathToIndexHtml, 'utf-8'); const indexContents = tree.read(pathToIndexHtml, 'utf-8');
const rootSelectorRegex = new RegExp(
const rootSelectorRegex = new RegExp(`${prefix}-root`, 'ig'); `${options.prefix || 'app'}-root`,
'ig'
);
const remoteEntrySelector = `${
options.prefix || 'app'
}-${projectName}-entry`;
const newIndexContents = indexContents.replace( const newIndexContents = indexContents.replace(
rootSelectorRegex, rootSelectorRegex,
remoteEntrySelector remoteEntrySelector

View File

@ -1,10 +1,12 @@
type FederationType = 'static' | 'dynamic';
export interface Schema { export interface Schema {
appName: string; appName: string;
mfType: 'host' | 'remote'; mfType: 'host' | 'remote';
port?: number; port?: number;
remotes?: string[]; remotes?: string[];
host?: string; host?: string;
federationType?: 'static' | 'dynamic'; federationType?: FederationType;
routing?: boolean; routing?: boolean;
skipFormat?: boolean; skipFormat?: boolean;
skipPackageJson?: boolean; skipPackageJson?: boolean;
@ -13,3 +15,8 @@ export interface Schema {
standalone?: boolean; standalone?: boolean;
skipE2E?: boolean; skipE2E?: boolean;
} }
export interface NormalizedOptions extends Schema {
federationType: FederationType;
prefix: string | undefined;
}

View File

@ -14,6 +14,7 @@ import {
fixBootstrap, fixBootstrap,
generateWebpackConfig, generateWebpackConfig,
getRemotesWithPorts, getRemotesWithPorts,
normalizeOptions,
removeDeadCodeFromRemote, removeDeadCodeFromRemote,
setupHostIfDynamic, setupHostIfDynamic,
setupServeTarget, setupServeTarget,
@ -24,17 +25,16 @@ import { getInstalledAngularVersionInfo } from '../utils/version-utils';
import { nxVersion } from '../../utils/versions'; import { nxVersion } from '../../utils/versions';
import { lt } from 'semver'; import { lt } from 'semver';
export async function setupMf(tree: Tree, options: Schema) { export async function setupMf(tree: Tree, rawOptions: Schema) {
const installedAngularInfo = getInstalledAngularVersionInfo(tree); const installedAngularInfo = getInstalledAngularVersionInfo(tree);
if (lt(installedAngularInfo.version, '14.1.0') && rawOptions.standalone) {
if (lt(installedAngularInfo.version, '14.1.0') && options.standalone) {
throw new Error( throw new Error(
`The --standalone flag is not supported in your current version of Angular (${installedAngularInfo.version}). Please update to a version of Angular that supports Standalone Components (>= 14.1.0).` `The --standalone flag is not supported in your current version of Angular (${installedAngularInfo.version}). Please update to a version of Angular that supports Standalone Components (>= 14.1.0).`
); );
} }
const projectConfig = readProjectConfiguration(tree, options.appName);
options.federationType = options.federationType ?? 'static'; const options = normalizeOptions(tree, rawOptions);
const projectConfig = readProjectConfiguration(tree, options.appName);
if (options.mfType === 'host') { if (options.mfType === 'host') {
setupHostIfDynamic(tree, options); setupHostIfDynamic(tree, options);

View File

@ -1,4 +1,6 @@
import { names } from '@nx/devkit'; import type { Tree } from '@nx/devkit';
import { names, readNxJson, readProjectConfiguration } from '@nx/devkit';
import type { AngularProjectConfiguration } from '../../utils/types';
export function normalizeDirectory( export function normalizeDirectory(
appName: string, appName: string,
@ -16,19 +18,27 @@ export function normalizeProjectName(
return normalizeDirectory(appName, directoryName).replace(/\//g, '-'); return normalizeDirectory(appName, directoryName).replace(/\//g, '-');
} }
export function normalizePrefix( export function normalizeNewProjectPrefix(
prefix: string | undefined, prefix: string | undefined,
npmScope: string | undefined npmScope: string | undefined,
fallbackPrefix: string
): string { ): string {
if (prefix) {
return prefix;
}
// Prefix needs to be a valid html selector, if npmScope it's not valid, we don't default // Prefix needs to be a valid html selector, if npmScope it's not valid, we don't default
// to it and let it fall through to the Angular schematic to handle it // to it and let it fall through to the Angular schematic to handle it
// https://github.com/angular/angular-cli/blob/1c634cd327e5a850553b258aa2d5e6a6b2c75c65/packages/schematics/angular/component/index.ts#L130 // https://github.com/angular/angular-cli/blob/1c634cd327e5a850553b258aa2d5e6a6b2c75c65/packages/schematics/angular/component/index.ts#L130
const htmlSelectorRegex = const htmlSelectorRegex =
/^[a-zA-Z][.0-9a-zA-Z]*(:?-[a-zA-Z][.0-9a-zA-Z]*)*$/; /^[a-zA-Z][.0-9a-zA-Z]*(:?-[a-zA-Z][.0-9a-zA-Z]*)*$/;
if (prefix) {
if (!htmlSelectorRegex.test(prefix)) {
throw new Error(
'The provided "prefix" is invalid. The prefix must start with a letter, and must contain only alphanumeric characters or dashes. When adding a dash the segment after the dash must also start with a letter.'
);
}
return prefix;
}
if (npmScope && !htmlSelectorRegex.test(npmScope)) { if (npmScope && !htmlSelectorRegex.test(npmScope)) {
throw new Error(`The "--prefix" option was not provided, therefore attempted to use the "npmScope" defined in "nx.json" to set the application's selector prefix, but it is invalid. throw new Error(`The "--prefix" option was not provided, therefore attempted to use the "npmScope" defined in "nx.json" to set the application's selector prefix, but it is invalid.
@ -41,5 +51,15 @@ If you encountered this error when creating a new Nx Workspace, the workspace na
Valid selector prefixes must start with a letter, and must contain only alphanumeric characters or dashes. When adding a dash the segment after the dash must also start with a letter.`); Valid selector prefixes must start with a letter, and must contain only alphanumeric characters or dashes. When adding a dash the segment after the dash must also start with a letter.`);
} }
return npmScope || 'app'; return npmScope || fallbackPrefix;
}
export function getProjectPrefix(
tree: Tree,
project: string
): string | undefined {
return (
(readProjectConfiguration(tree, project) as AngularProjectConfiguration)
.prefix ?? readNxJson(tree).npmScope
);
} }