From 5970246b51c17428b8d9f04123b06f780ac824bc Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Tue, 10 Jan 2023 16:20:26 +0000 Subject: [PATCH] feat(angular): add backwards compat support for library (#14249) --- .../src/generators/add-linting/add-linting.ts | 2 +- .../src/generators/application/application.ts | 2 +- packages/angular/src/generators/init/init.ts | 2 +- .../angular/src/generators/karma/karma.ts | 2 +- .../__snapshots__/library.spec.ts.snap | 46 +++++++++ .../src/generators/library/library.spec.ts | 97 +++++++++++++++++++ .../angular/src/generators/library/library.ts | 24 ++++- .../src/generators/utils/create-ts-config.ts | 6 +- ...generator-directory-for-ng-version.spec.ts | 50 ---------- .../get-generator-directory-for-ng-version.ts | 27 ------ .../user-installed-angular-versions.spec.ts | 96 ++++++++++++++++++ .../utils/user-installed-angular-versions.ts | 44 +++++++++ 12 files changed, 314 insertions(+), 84 deletions(-) delete mode 100644 packages/angular/src/utils/get-generator-directory-for-ng-version.spec.ts delete mode 100644 packages/angular/src/utils/get-generator-directory-for-ng-version.ts create mode 100644 packages/angular/src/utils/user-installed-angular-versions.spec.ts create mode 100644 packages/angular/src/utils/user-installed-angular-versions.ts diff --git a/packages/angular/src/generators/add-linting/add-linting.ts b/packages/angular/src/generators/add-linting/add-linting.ts index 74ed82ae8123c..799a73cfe5414 100755 --- a/packages/angular/src/generators/add-linting/add-linting.ts +++ b/packages/angular/src/generators/add-linting/add-linting.ts @@ -10,7 +10,7 @@ import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-ser import { addAngularEsLintDependencies } from './lib/add-angular-eslint-dependencies'; import { extendAngularEslintJson } from './lib/create-eslint-configuration'; import type { AddLintingGeneratorSchema } from './schema'; -import { getGeneratorDirectoryForInstalledAngularVersion } from '../../utils/get-generator-directory-for-ng-version'; +import { getGeneratorDirectoryForInstalledAngularVersion } from '../../utils/user-installed-angular-versions'; import { join } from 'path'; export async function addLintingGenerator( diff --git a/packages/angular/src/generators/application/application.ts b/packages/angular/src/generators/application/application.ts index 67d3ea900fd71..df3fa53478dc2 100644 --- a/packages/angular/src/generators/application/application.ts +++ b/packages/angular/src/generators/application/application.ts @@ -29,7 +29,7 @@ import { updateNxComponentTemplate, } from './lib'; import type { Schema } from './schema'; -import { getGeneratorDirectoryForInstalledAngularVersion } from '../../utils/get-generator-directory-for-ng-version'; +import { getGeneratorDirectoryForInstalledAngularVersion } from '../../utils/user-installed-angular-versions'; import { join } from 'path'; export async function applicationGenerator( diff --git a/packages/angular/src/generators/init/init.ts b/packages/angular/src/generators/init/init.ts index 819f5db4d4389..3cb471d69a675 100755 --- a/packages/angular/src/generators/init/init.ts +++ b/packages/angular/src/generators/init/init.ts @@ -28,7 +28,7 @@ import { } from '../../utils/versions'; import { karmaGenerator } from '../karma/karma'; import { Schema } from './schema'; -import { getGeneratorDirectoryForInstalledAngularVersion } from '../../utils/get-generator-directory-for-ng-version'; +import { getGeneratorDirectoryForInstalledAngularVersion } from '../../utils/user-installed-angular-versions'; import { join } from 'path'; export async function angularInitGenerator( diff --git a/packages/angular/src/generators/karma/karma.ts b/packages/angular/src/generators/karma/karma.ts index b742445cf7f73..2799a5f33d1f5 100644 --- a/packages/angular/src/generators/karma/karma.ts +++ b/packages/angular/src/generators/karma/karma.ts @@ -19,7 +19,7 @@ import { typesNodeVersion, } from '../../utils/versions'; import { GeneratorOptions } from './schema'; -import { getGeneratorDirectoryForInstalledAngularVersion } from '../../utils/get-generator-directory-for-ng-version'; +import { getGeneratorDirectoryForInstalledAngularVersion } from '../../utils/user-installed-angular-versions'; import { join } from 'path'; function addTestInputs(tree: Tree) { diff --git a/packages/angular/src/generators/library/__snapshots__/library.spec.ts.snap b/packages/angular/src/generators/library/__snapshots__/library.spec.ts.snap index deb7e5bcf8862..c3684d7d6f22b 100644 --- a/packages/angular/src/generators/library/__snapshots__/library.spec.ts.snap +++ b/packages/angular/src/generators/library/__snapshots__/library.spec.ts.snap @@ -1,5 +1,51 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`lib --angular-14 should generate a library with a standalone component as entry point with angular 14.1.0 1`] = `"export * from \\"./lib/my-lib/my-lib.component\\";"`; + +exports[`lib --angular-14 should generate a library with a standalone component as entry point with angular 14.1.0 2`] = ` +"import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'proj-my-lib', + standalone: true, + imports: [CommonModule], + templateUrl: './my-lib.component.html', + styleUrls: ['./my-lib.component.css'] +}) +export class MyLibComponent { + +} +" +`; + +exports[`lib --angular-14 should generate a library with a standalone component as entry point with angular 14.1.0 3`] = ` +"import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MyLibComponent } from './my-lib.component'; + +describe('MyLibComponent', () => { + let component: MyLibComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ MyLibComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(MyLibComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); +" +`; + exports[`lib --standalone should generate a library with a standalone component and have it flat 1`] = `"export * from \\"./lib/my-lib.component\\";"`; exports[`lib --standalone should generate a library with a standalone component and have it flat 2`] = ` diff --git a/packages/angular/src/generators/library/library.spec.ts b/packages/angular/src/generators/library/library.spec.ts index 20e51e0b30d26..5be9c7c0b298a 100644 --- a/packages/angular/src/generators/library/library.spec.ts +++ b/packages/angular/src/generators/library/library.spec.ts @@ -20,6 +20,7 @@ import { autoprefixerVersion, postcssVersion, tailwindVersion, + versions, } from '../../utils/versions'; import libraryGenerator from './library'; import { Schema } from './schema'; @@ -1736,4 +1737,100 @@ describe('lib', () => { ).toMatchSnapshot(); }); }); + + describe('--angular-14', () => { + beforeEach(() => { + tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); + updateJson(tree, 'package.json', (json) => ({ + ...json, + dependencies: { + ...json.dependencies, + '@angular/core': '14.1.0', + }, + })); + }); + + it('should create a local tsconfig.json', async () => { + // ACT + await runLibraryGeneratorWithOpts(); + + // ASSERT + const tsconfigJson = readJson(tree, 'libs/my-lib/tsconfig.json'); + expect(tsconfigJson).toEqual({ + extends: '../../tsconfig.base.json', + angularCompilerOptions: { + enableI18nLegacyMessageIdFormat: false, + strictInjectionParameters: true, + strictInputAccessModifiers: true, + strictTemplates: true, + }, + compilerOptions: { + forceConsistentCasingInFileNames: true, + noFallthroughCasesInSwitch: true, + noPropertyAccessFromIndexSignature: true, + noImplicitOverride: true, + noImplicitReturns: true, + strict: true, + target: 'es2020', + useDefineForClassFields: false, + }, + files: [], + include: [], + references: [ + { + path: './tsconfig.lib.json', + }, + { + path: './tsconfig.spec.json', + }, + ], + }); + }); + + it('should generate a library with a standalone component as entry point with angular 14.1.0', async () => { + await runLibraryGeneratorWithOpts({ standalone: true }); + + expect(tree.read('libs/my-lib/src/index.ts', 'utf-8')).toMatchSnapshot(); + expect( + tree.read('libs/my-lib/src/lib/my-lib/my-lib.component.ts', 'utf-8') + ).toMatchSnapshot(); + expect( + tree.read( + 'libs/my-lib/src/lib/my-lib/my-lib.component.spec.ts', + 'utf-8' + ) + ).toMatchSnapshot(); + }); + + it('should throw an error when trying to generate a library with a standalone component as entry point when angular version is < 14.1.0', async () => { + updateJson(tree, 'package.json', (json) => ({ + ...json, + dependencies: { + ...json.dependencies, + '@angular/core': '14.0.0', + }, + })); + + await expect( + runLibraryGeneratorWithOpts({ standalone: true }) + ).rejects.toThrow( + `The \"--standalone\" option is not supported in Angular versions < 14.1.0.` + ); + }); + + it('should update package.json with correct versions when buildable', async () => { + // ACT + await runLibraryGeneratorWithOpts({ buildable: true }); + + // ASSERT + const packageJson = readJson(tree, '/package.json'); + expect(packageJson.devDependencies['ng-packagr']).toEqual( + versions.angularV14.ngPackagrVersion + ); + expect(packageJson.devDependencies['postcss']).toBeDefined(); + expect(packageJson.devDependencies['postcss-import']).toBeDefined(); + expect(packageJson.devDependencies['postcss-preset-env']).toBeDefined(); + expect(packageJson.devDependencies['postcss-url']).toBeDefined(); + }); + }); }); diff --git a/packages/angular/src/generators/library/library.ts b/packages/angular/src/generators/library/library.ts index 611e346354d8e..06e705f8e5cd8 100644 --- a/packages/angular/src/generators/library/library.ts +++ b/packages/angular/src/generators/library/library.ts @@ -12,7 +12,11 @@ import { Linter } from '@nrwl/linter'; import { convertToNxProjectGenerator } from '@nrwl/workspace/generators'; import init from '../../generators/init/init'; import { E2eTestRunner } from '../../utils/test-runners'; -import { ngPackagrVersion } from '../../utils/versions'; +import { + angularVersion, + ngPackagrVersion, + versions, +} from '../../utils/versions'; import addLintingGenerator from '../add-linting/add-linting'; import karmaProjectGenerator from '../karma-project/karma-project'; import setupTailwindGenerator from '../setup-tailwind/setup-tailwind'; @@ -29,6 +33,8 @@ import { updateProject } from './lib/update-project'; import { updateTsConfig } from './lib/update-tsconfig'; import { addStandaloneComponent } from './lib/add-standalone-component'; import { Schema } from './schema'; +import { getUserInstalledAngularVersionInfo } from '../../utils/user-installed-angular-versions'; +import { coerce, lt, major } from 'semver'; export async function libraryGenerator(tree: Tree, schema: Schema) { // Do some validation checks @@ -48,6 +54,16 @@ export async function libraryGenerator(tree: Tree, schema: Schema) { ); } + const userInstalledAngularVersion = getUserInstalledAngularVersionInfo(tree); + if ( + lt(userInstalledAngularVersion.cleanedVersion, '14.1.0') && + schema.standalone + ) { + throw new Error( + `The "--standalone" option is not supported in Angular versions < 14.1.0.` + ); + } + const options = normalizeOptions(tree, schema); const { libraryOptions, componentOptions } = options; @@ -105,7 +121,11 @@ export async function libraryGenerator(tree: Tree, schema: Schema) { tree, {}, { - 'ng-packagr': ngPackagrVersion, + 'ng-packagr': + userInstalledAngularVersion.major < major(coerce(angularVersion)) + ? versions[`angularV${userInstalledAngularVersion.major}`] + ?.ngPackagrVersion ?? ngPackagrVersion + : ngPackagrVersion, } ); addBuildableLibrariesPostCssDependencies(tree); diff --git a/packages/angular/src/generators/utils/create-ts-config.ts b/packages/angular/src/generators/utils/create-ts-config.ts index 1fc2d88cbb9d6..4334bf0ad72d6 100644 --- a/packages/angular/src/generators/utils/create-ts-config.ts +++ b/packages/angular/src/generators/utils/create-ts-config.ts @@ -1,6 +1,8 @@ import { Tree } from 'nx/src/generators/tree'; import { tsConfigBaseOptions } from '@nrwl/workspace/src/utils/create-ts-config'; import { writeJson } from 'nx/src/generators/utils/json'; +import { getUserInstalledAngularMajorVersion } from '../../utils/user-installed-angular-versions'; + export { extractTsConfigBase } from '@nrwl/workspace/src/utils/create-ts-config'; export function createTsConfig( @@ -15,9 +17,11 @@ export function createTsConfig( }, relativePathToRootTsConfig: string ) { + let majorAngularVersion = getUserInstalledAngularMajorVersion(host); + const json = { compilerOptions: { - target: 'es2022', + target: majorAngularVersion === 14 ? 'es2020' : 'es2022', useDefineForClassFields: false, }, files: [], diff --git a/packages/angular/src/utils/get-generator-directory-for-ng-version.spec.ts b/packages/angular/src/utils/get-generator-directory-for-ng-version.spec.ts deleted file mode 100644 index 1cff4a56a7d3d..0000000000000 --- a/packages/angular/src/utils/get-generator-directory-for-ng-version.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; -import { updateJson } from '@nrwl/devkit'; -import { getGeneratorDirectoryForInstalledAngularVersion } from './get-generator-directory-for-ng-version'; - -describe('getGeneratorDirectoryForAngularVersion', () => { - test.each(['14.0.0', '~14.1.0', '^14.2.0', '~14.3.0-beta.0'])( - 'should return correct directory name for v14', - (ngVersion) => { - // ARRANGE - const tree = createTreeWithEmptyWorkspace(); - updateJson(tree, 'package.json', (json) => ({ - ...json, - dependencies: { - '@angular/core': ngVersion, - }, - })); - - // ACT - const directoryName = - getGeneratorDirectoryForInstalledAngularVersion(tree); - - // ASSERT - expect(directoryName).toEqual('angular-v14'); - } - ); - - test.each([ - '15.0.0', - '~15.1.0', - '^13.2.0', - '~15.3.0-beta.0', - 'latest', - 'next', - ])('should return null for anything other than v14', (ngVersion) => { - // ARRANGE - const tree = createTreeWithEmptyWorkspace(); - updateJson(tree, 'package.json', (json) => ({ - ...json, - dependencies: { - '@angular/core': ngVersion, - }, - })); - - // ACT - const directoryName = getGeneratorDirectoryForInstalledAngularVersion(tree); - - // ASSERT - expect(directoryName).toBe(null); - }); -}); diff --git a/packages/angular/src/utils/get-generator-directory-for-ng-version.ts b/packages/angular/src/utils/get-generator-directory-for-ng-version.ts deleted file mode 100644 index f83492d66f43b..0000000000000 --- a/packages/angular/src/utils/get-generator-directory-for-ng-version.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Tree } from '@nrwl/devkit'; -import { readJson } from '@nrwl/devkit'; -import { coerce, clean, major } from 'semver'; - -export function getGeneratorDirectoryForInstalledAngularVersion(tree: Tree) { - const pkgJson = readJson(tree, 'package.json'); - const angularVersion = - pkgJson.dependencies && pkgJson.dependencies['@angular/core']; - - if ( - !angularVersion || - angularVersion === 'latest' || - angularVersion === 'next' - ) { - return null; - } - - const majorAngularVersion = major( - clean(angularVersion) ?? coerce(angularVersion) - ); - - const directoryDictionary = { - 14: 'angular-v14', - }; - - return directoryDictionary[majorAngularVersion] ?? null; -} diff --git a/packages/angular/src/utils/user-installed-angular-versions.spec.ts b/packages/angular/src/utils/user-installed-angular-versions.spec.ts new file mode 100644 index 0000000000000..97f4414bece21 --- /dev/null +++ b/packages/angular/src/utils/user-installed-angular-versions.spec.ts @@ -0,0 +1,96 @@ +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { updateJson } from '@nrwl/devkit'; +import { + getGeneratorDirectoryForInstalledAngularVersion, + getUserInstalledAngularMajorVersion, + getUserInstalledAngularVersion, +} from './user-installed-angular-versions'; + +describe('userInstalledAngularVersions', () => { + test.each(['14.0.0', '~14.1.0', '^14.2.0', '~14.3.0-beta.0'])( + 'should return correct directory name for v14', + (ngVersion) => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'package.json', (json) => ({ + ...json, + dependencies: { + '@angular/core': ngVersion, + }, + })); + + // ACT + const directoryName = + getGeneratorDirectoryForInstalledAngularVersion(tree); + + // ASSERT + expect(directoryName).toEqual('angular-v14'); + } + ); + + test.each(['14.0.0', '~14.1.0', '^14.2.0', '~14.3.0-beta.0'])( + 'should return correct major version', + (ngVersion) => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'package.json', (json) => ({ + ...json, + dependencies: { + '@angular/core': ngVersion, + }, + })); + + // ACT + const angularVersion = getUserInstalledAngularMajorVersion(tree); + + // ASSERT + expect(angularVersion).toBe(14); + } + ); + + test.each([ + ['14.0.0', '14.0.0'], + ['~14.1.0', '14.1.0'], + ['^14.2.0', '14.2.0'], + ['~14.3.0-beta.0', '14.3.0'], + ])('should return correct major version', (ngVersion, expectedVersion) => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'package.json', (json) => ({ + ...json, + dependencies: { + '@angular/core': ngVersion, + }, + })); + + // ACT + const angularVersion = getUserInstalledAngularVersion(tree); + + // ASSERT + expect(angularVersion).toEqual(expectedVersion); + }); + + test.each([ + '15.0.0', + '~15.1.0', + '^13.2.0', + '~15.3.0-beta.0', + 'latest', + 'next', + ])('should return null for anything other than v14', (ngVersion) => { + // ARRANGE + const tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'package.json', (json) => ({ + ...json, + dependencies: { + '@angular/core': ngVersion, + }, + })); + + // ACT + const directoryName = getGeneratorDirectoryForInstalledAngularVersion(tree); + + // ASSERT + expect(directoryName).toBe(null); + }); +}); diff --git a/packages/angular/src/utils/user-installed-angular-versions.ts b/packages/angular/src/utils/user-installed-angular-versions.ts new file mode 100644 index 0000000000000..8a282f6502009 --- /dev/null +++ b/packages/angular/src/utils/user-installed-angular-versions.ts @@ -0,0 +1,44 @@ +import type { Tree } from '@nrwl/devkit'; +import { readJson } from '@nrwl/devkit'; +import { clean, coerce, major } from 'semver'; +import { angularVersion } from './versions'; + +export function getGeneratorDirectoryForInstalledAngularVersion(tree: Tree) { + const majorAngularVersion = getUserInstalledAngularMajorVersion(tree); + + const directoryDictionary = { + 14: 'angular-v14', + }; + + return directoryDictionary[majorAngularVersion] ?? null; +} + +export function getUserInstalledAngularVersion(tree: Tree) { + const pkgJson = readJson(tree, 'package.json'); + const installedAngularVersion = + pkgJson.dependencies && pkgJson.dependencies['@angular/core']; + + if ( + !installedAngularVersion || + installedAngularVersion === 'latest' || + installedAngularVersion === 'next' + ) { + return clean(angularVersion) ?? coerce(angularVersion).version; + } + + return ( + clean(installedAngularVersion) ?? coerce(installedAngularVersion).version + ); +} + +export function getUserInstalledAngularMajorVersion(tree: Tree): number { + return major(getUserInstalledAngularVersion(tree)); +} + +export function getUserInstalledAngularVersionInfo(tree: Tree) { + const installedVersion = getUserInstalledAngularVersion(tree); + return { + cleanedVersion: installedVersion, + major: major(installedVersion), + }; +}