From 588823b437840db6ae6c8ee2c3e3ffcc9a9552ef Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Mon, 11 Nov 2019 09:27:36 +0100 Subject: [PATCH] refactor(elements): ng-add schematics (#33723) This PR brings a couple of changes; - Removes undeed dependencies in bazel targets such as `//packages/common` & `//packages/core` - Removes RxJs usage - Adds `document-register-element` to architect test targets - Use @schematics/angular helpers - Uses the standard `$source": "projectName"` to get the projectName, which is defined in the `schema.json` - Use workspace writer to update the workspace config PR Close #33723 --- packages/elements/schematics/collection.json | 3 +- .../elements/schematics/ng-add/BUILD.bazel | 12 +-- packages/elements/schematics/ng-add/index.ts | 100 +++++++++--------- .../elements/schematics/ng-add/index_spec.ts | 36 ++++--- .../elements/schematics/ng-add/schema.json | 22 ++++ 5 files changed, 100 insertions(+), 73 deletions(-) create mode 100644 packages/elements/schematics/ng-add/schema.json diff --git a/packages/elements/schematics/collection.json b/packages/elements/schematics/collection.json index a5d825684d..66437dc93b 100644 --- a/packages/elements/schematics/collection.json +++ b/packages/elements/schematics/collection.json @@ -3,7 +3,8 @@ "schematics": { "ng-add": { "description": "Adds the document-register-element polyfill.", - "factory": "./ng-add" + "factory": "./ng-add", + "schema": "ng-add/schema.json" } } } diff --git a/packages/elements/schematics/ng-add/BUILD.bazel b/packages/elements/schematics/ng-add/BUILD.bazel index bc961159c8..24f3639225 100644 --- a/packages/elements/schematics/ng-add/BUILD.bazel +++ b/packages/elements/schematics/ng-add/BUILD.bazel @@ -10,10 +10,8 @@ ts_library( "schema.ts", ], deps = [ - "//packages/common", - "//packages/core", "@npm//@angular-devkit/schematics", - "@npm//rxjs", + "@npm//@schematics/angular", ], ) @@ -23,12 +21,13 @@ ts_library( srcs = [ "index_spec.ts", ], + data = [ + "schema.json", + ], deps = [ ":ng-add", - "//packages/common", - "//packages/core", "@npm//@angular-devkit/schematics", - "@npm//rxjs", + "@npm//@schematics/angular", ], ) @@ -37,6 +36,5 @@ jasmine_node_test( deps = [ ":test_lib", "//packages/elements/schematics:collection", - "@npm//@schematics/angular", ], ) diff --git a/packages/elements/schematics/ng-add/index.ts b/packages/elements/schematics/ng-add/index.ts index cfae88cc3a..ad8c0afcd1 100644 --- a/packages/elements/schematics/ng-add/index.ts +++ b/packages/elements/schematics/ng-add/index.ts @@ -5,72 +5,74 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {Rule, SchematicContext, Tree, chain, noop} from '@angular-devkit/schematics'; +import {Rule, SchematicContext, SchematicsException, Tree, chain, noop} from '@angular-devkit/schematics'; import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks'; +import {NodeDependencyType, addPackageJsonDependency} from '@schematics/angular/utility/dependencies'; +import {getWorkspace} from '@schematics/angular/utility/workspace'; + import {Schema} from './schema'; export default function(options: Schema): Rule { return chain([ - options && options.skipPackageJson ? noop() : addPackageJsonDependency(), addScript(options) + options && options.skipPackageJson ? noop() : addPolyfillDependency(), + addPolyfill(options), ]); } /** Adds a package.json dependency for document-register-element */ -function addPackageJsonDependency() { +function addPolyfillDependency(): Rule { return (host: Tree, context: SchematicContext) => { + addPackageJsonDependency(host, { + type: NodeDependencyType.Default, + name: 'document-register-element', + version: '^1.7.2', + }); + context.logger.info('Added "document-register-element" as a dependency.'); - if (host.exists('package.json')) { - const jsonStr = host.read('package.json') !.toString('utf-8'); - const json = JSON.parse(jsonStr); - - // If there are no dependencies, create an entry for dependencies. - const type = 'dependencies'; - if (!json[type]) { - json[type] = {}; - } - - // If not already present, add the dependency. - const pkg = 'document-register-element'; - const version = '^1.7.2'; - if (!json[type][pkg]) { - json[type][pkg] = version; - } - - // Write the JSON back to package.json - host.overwrite('package.json', JSON.stringify(json, null, 2)); - context.logger.log('info', 'Added `document-register-element` as a dependency.'); - - // Install the dependency - context.addTask(new NodePackageInstallTask()); - } - - return host; + // Install the dependency + context.addTask(new NodePackageInstallTask()); }; } -/** Adds the document-register-element.js script to the angular CLI json. */ -function addScript(options: Schema) { - return (host: Tree, context: SchematicContext) => { - const script = 'node_modules/document-register-element/build/document-register-element.js'; +/** Adds the document-register-element.js to the polyfills file. */ +function addPolyfill(options: Schema): Rule { + return async(host: Tree, context: SchematicContext) => { + const projectName = options.project; - - try { - // Handle the new json - angular.json - const angularJsonFile = host.read('angular.json'); - if (angularJsonFile) { - const json = JSON.parse(angularJsonFile.toString('utf-8')); - const project = Object.keys(json['projects'])[0] || options.project; - const scripts = json['projects'][project]['architect']['build']['options']['scripts']; - scripts.push({input: script}); - host.overwrite('angular.json', JSON.stringify(json, null, 2)); - } - } catch { - context.logger.log( - 'warn', 'Failed to add the polyfill document-register-element.js to scripts'); + if (!projectName) { + throw new SchematicsException('Option "project" is required.'); } - context.logger.log('info', 'Added document-register-element.js polyfill to scripts'); + const workspace = await getWorkspace(host); + const project = workspace.projects.get(projectName); - return host; + if (!project) { + throw new SchematicsException(`Project ${projectName} is not defined in this workspace.`); + } + + if (project.extensions['projectType'] !== 'application') { + throw new SchematicsException( + `@angular/elements requires a project type of "application" but ${projectName} isn't.`); + } + + const buildTarget = project.targets.get('build'); + if (!buildTarget || !buildTarget.options) { + throw new SchematicsException(`Cannot find 'options' for ${projectName} build target.`); + } + + const {polyfills} = buildTarget.options; + if (typeof polyfills !== 'string') { + throw new SchematicsException(`polyfills for ${projectName} build target is not a string.`); + } + + const content = host.read(polyfills).toString(); + if (!content.includes('document-register-element')) { + // Add string at the end of the file. + const recorder = host.beginUpdate(polyfills); + recorder.insertRight(content.length, `import 'document-register-element';\n`); + host.commitUpdate(recorder); + } + + context.logger.info('Added "document-register-element" to polyfills.'); }; } diff --git a/packages/elements/schematics/ng-add/index_spec.ts b/packages/elements/schematics/ng-add/index_spec.ts index 674a4584c2..4e4d8737f2 100644 --- a/packages/elements/schematics/ng-add/index_spec.ts +++ b/packages/elements/schematics/ng-add/index_spec.ts @@ -7,19 +7,14 @@ */ import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing'; import * as path from 'path'; -import {Observable} from 'rxjs'; -import {concatMap} from 'rxjs/operators'; import {Schema as ElementsOptions} from './schema'; - -const polyfillPath = 'node_modules/document-register-element/build/document-register-element.js'; - // tslint:disable:max-line-length describe('Elements Schematics', () => { const schematicRunner = new SchematicTestRunner( '@angular/elements', path.join(__dirname, '../test-collection.json'), ); - const defaultOptions: ElementsOptions = {project: 'bar', skipPackageJson: false}; + const defaultOptions: ElementsOptions = {project: 'elements', skipPackageJson: false}; let appTree: UnitTestTree; @@ -40,20 +35,29 @@ describe('Elements Schematics', () => { skipTests: false, }; - beforeEach((done) => { - schematicRunner.runExternalSchematicAsync('@schematics/angular', 'workspace', workspaceOptions) - .pipe(concatMap( - (tree) => schematicRunner.runExternalSchematicAsync( - '@schematics/angular', 'application', appOptions, tree))) - .subscribe((tree: UnitTestTree) => appTree = tree, done.fail, done); + beforeEach(async() => { + appTree = await schematicRunner + .runExternalSchematicAsync('@schematics/angular', 'workspace', workspaceOptions) + .toPromise(); + appTree = + await schematicRunner + .runExternalSchematicAsync('@schematics/angular', 'application', appOptions, appTree) + .toPromise(); }); it('should run the ng-add schematic', async() => { const tree = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, appTree).toPromise(); - const configText = tree.readContent('/angular.json'); - const config = JSON.parse(configText); - const scripts = config.projects.elements.architect.build.options.scripts; - expect(scripts[0].input).toEqual(polyfillPath); + expect(tree.readContent('/projects/elements/src/polyfills.ts')) + .toContain(`import 'document-register-element';`); + }); + + it('should add polyfill as a dependency in package.json', async() => { + const tree = + await schematicRunner.runSchematicAsync('ng-add', defaultOptions, appTree).toPromise(); + const pkgJsonText = tree.readContent('/package.json'); + const pkgJson = JSON.parse(pkgJsonText); + const {dependencies} = pkgJson; + expect(dependencies['document-register-element']).toBeDefined(); }); }); diff --git a/packages/elements/schematics/ng-add/schema.json b/packages/elements/schematics/ng-add/schema.json new file mode 100644 index 0000000000..42d57ecf1d --- /dev/null +++ b/packages/elements/schematics/ng-add/schema.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "SchematicsAngularElementsNgAdd", + "title": "Angular Elements Ng Add Schema", + "type": "object", + "properties": { + "project": { + "type": "string", + "description": "The name of the project.", + "$default": { + "$source": "projectName" + } + }, + "skipPackageJson": { + "description": "When true, does not add dependencies to the \"package.json\" file.", + "type": "boolean", + "default": false + } + }, + "required": [ + ] +}