From 6b6cf450982e917298d3dee4de0c269feba9e59d Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Thu, 20 Sep 2018 19:39:31 +0200 Subject: [PATCH] refactor(schematics): foundation for cdk schematics (#13213) * Moves the utility functions which will be needed for building CDK schematics to the CDK (`cdk/schematics`). * Updates package tools to properly build schematics for CDK and Material (shared logic for building schematics; Material schematics depend on CDK schematics) * Update to `ng-add`: The Angular CLI only installs `@angular/material` if someone runs `ng add @angular/material`. But since we need access to the utility functions from the CDK, we need to install the dependencies first, and then setup the project. --- .github/CODEOWNERS | 3 +- src/cdk/package.json | 5 + src/cdk/schematics/BUILD.bazel | 41 ++++++ src/cdk/schematics/README.md | 1 + src/cdk/schematics/collection.json | 4 + src/cdk/schematics/index.ts | 9 ++ src/cdk/schematics/migration.json | 4 + src/cdk/schematics/tsconfig.json | 24 ++++ src/{lib => cdk}/schematics/utils/ast.ts | 0 .../schematics/utils/build-component.ts | 0 .../schematics/utils/get-project.ts | 0 src/cdk/schematics/utils/index.ts | 18 +++ .../schematics/utils/package-json.ts | 0 .../schematics/utils/parse5-element.ts | 0 .../schematics/utils/project-main-file.ts | 0 .../schematics/utils/project-style-file.ts | 0 .../schematics/utils/project-targets.ts | 0 .../schematics/utils/schematic-options.ts | 0 .../utils/version-agnostic-typescript.ts | 2 +- src/cdk/tsconfig-tests.json | 3 + src/lib/schematics/BUILD.bazel | 3 +- src/lib/schematics/address-form/index.ts | 9 +- src/lib/schematics/collection.json | 6 + src/lib/schematics/dashboard/index.ts | 7 +- src/lib/schematics/nav/index.ts | 9 +- .../schematics/ng-add/fonts/head-element.ts | 2 +- .../schematics/ng-add/fonts/material-fonts.ts | 2 +- .../ng-add/fonts/project-index-html.ts | 2 +- .../ng-add/gestures/hammerjs-import.ts | 3 +- src/lib/schematics/ng-add/index.spec.ts | 39 ++++-- src/lib/schematics/ng-add/index.ts | 129 +++++------------- src/lib/schematics/ng-add/package-json.ts | 61 +++++++++ src/lib/schematics/ng-add/setup-project.ts | 87 ++++++++++++ src/lib/schematics/ng-add/theming/theming.ts | 8 +- src/lib/schematics/table/index.ts | 9 +- src/lib/schematics/tree/index.ts | 7 +- src/lib/schematics/tsconfig.json | 8 +- tools/gulp/packages.ts | 4 + tools/gulp/tasks/material-release.ts | 31 +---- tools/package-tools/build-package.ts | 3 + tools/package-tools/build-release.ts | 4 + tools/package-tools/compile-entry-point.ts | 4 +- tools/package-tools/gulp/build-tasks-gulp.ts | 30 +++- .../{ngc-compile.ts => ts-compile.ts} | 11 +- 44 files changed, 421 insertions(+), 171 deletions(-) create mode 100644 src/cdk/schematics/BUILD.bazel create mode 100644 src/cdk/schematics/README.md create mode 100644 src/cdk/schematics/collection.json create mode 100644 src/cdk/schematics/index.ts create mode 100644 src/cdk/schematics/migration.json create mode 100644 src/cdk/schematics/tsconfig.json rename src/{lib => cdk}/schematics/utils/ast.ts (100%) rename src/{lib => cdk}/schematics/utils/build-component.ts (100%) rename src/{lib => cdk}/schematics/utils/get-project.ts (100%) create mode 100644 src/cdk/schematics/utils/index.ts rename src/{lib => cdk}/schematics/utils/package-json.ts (100%) rename src/{lib => cdk}/schematics/utils/parse5-element.ts (100%) rename src/{lib => cdk}/schematics/utils/project-main-file.ts (100%) rename src/{lib => cdk}/schematics/utils/project-style-file.ts (100%) rename src/{lib => cdk}/schematics/utils/project-targets.ts (100%) rename src/{lib => cdk}/schematics/utils/schematic-options.ts (100%) rename src/{lib => cdk}/schematics/utils/version-agnostic-typescript.ts (92%) create mode 100644 src/lib/schematics/ng-add/package-json.ts create mode 100644 src/lib/schematics/ng-add/setup-project.ts rename tools/package-tools/{ngc-compile.ts => ts-compile.ts} (57%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index eb3fde5f180..4900b2de03f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -23,7 +23,7 @@ /src/lib/progress-bar/** @jelbourn @crisbeto @josephperrott /src/lib/progress-spinner/** @jelbourn @crisbeto @josephperrott /src/lib/radio/** @jelbourn @devversion -/src/lib/schematics/** @amcdnl +/src/lib/schematics/** @devversion @amcdnl /src/lib/select/** @crisbeto /src/lib/sidenav/** @mmalerba /src/lib/slide-toggle/** @devversion @@ -71,6 +71,7 @@ /src/cdk/overlay/** @jelbourn @crisbeto /src/cdk/platform/** @jelbourn @devversion /src/cdk/portal/** @jelbourn +/src/cdk/schematics/** @devversion @jelbourn /src/cdk/scrolling/** @andrewseguin @crisbeto /src/cdk/stepper/** @mmalerba /src/cdk/table/** @andrewseguin diff --git a/src/cdk/package.json b/src/cdk/package.json index 19a59ed0589..60587c5ca1e 100644 --- a/src/cdk/package.json +++ b/src/cdk/package.json @@ -29,7 +29,12 @@ "dependencies": { "tslib": "^1.7.1" }, + "optionalDependencies": { + "parse5": "^5.0.0" + }, + "schematics": "./schematics/collection.json", "ng-update": { + "migrations": "./schematics/migration.json", "packageGroup": [ "@angular/material", "@angular/cdk", diff --git a/src/cdk/schematics/BUILD.bazel b/src/cdk/schematics/BUILD.bazel new file mode 100644 index 00000000000..86cd587cfdd --- /dev/null +++ b/src/cdk/schematics/BUILD.bazel @@ -0,0 +1,41 @@ +package(default_visibility = ["//visibility:public"]) + +load("@build_bazel_rules_typescript//:defs.bzl", "ts_library") +load("@build_bazel_rules_nodejs//:defs.bzl", "npm_package", "jasmine_node_test") +load("//:packages.bzl", "VERSION_PLACEHOLDER_REPLACEMENTS") + +filegroup( + name = "schematics_assets", + srcs = ["README.md"], +) + +ts_library( + name = "schematics", + module_name = "@angular/cdk/schematics", + srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]), + tsconfig = ":tsconfig.json", +) + +# This package is intended to be combined into the main @angular/cdk package as a dep. +npm_package( + name = "npm_package", + srcs = [":schematics_assets"], + deps = [":schematics"], + replacements = VERSION_PLACEHOLDER_REPLACEMENTS, +) + +### Testing rules + +jasmine_node_test( + name = "unit_tests", + srcs = [":schematics_test_sources"], + data = [":schematics_assets"] +) + +ts_library( + name = "schematics_test_sources", + srcs = glob(["**/*.spec.ts"]), + deps = [":schematics"], + tsconfig = ":tsconfig.json", + testonly = True, +) diff --git a/src/cdk/schematics/README.md b/src/cdk/schematics/README.md new file mode 100644 index 00000000000..a14de5ce3a5 --- /dev/null +++ b/src/cdk/schematics/README.md @@ -0,0 +1 @@ +## Cdk schematics diff --git a/src/cdk/schematics/collection.json b/src/cdk/schematics/collection.json new file mode 100644 index 00000000000..ff6a1e9fcba --- /dev/null +++ b/src/cdk/schematics/collection.json @@ -0,0 +1,4 @@ +{ + "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", + "schematics": {} +} diff --git a/src/cdk/schematics/index.ts b/src/cdk/schematics/index.ts new file mode 100644 index 00000000000..16474ab6022 --- /dev/null +++ b/src/cdk/schematics/index.ts @@ -0,0 +1,9 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * 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 + */ + +export * from './utils'; diff --git a/src/cdk/schematics/migration.json b/src/cdk/schematics/migration.json new file mode 100644 index 00000000000..ff6a1e9fcba --- /dev/null +++ b/src/cdk/schematics/migration.json @@ -0,0 +1,4 @@ +{ + "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", + "schematics": {} +} diff --git a/src/cdk/schematics/tsconfig.json b/src/cdk/schematics/tsconfig.json new file mode 100644 index 00000000000..fbc1d702b78 --- /dev/null +++ b/src/cdk/schematics/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "lib": ["es2017"], + "module": "commonjs", + "moduleResolution": "node", + "outDir": "../../../dist/packages/cdk/schematics", + "noEmitOnError": false, + "strictNullChecks": true, + "skipDefaultLibCheck": true, + "skipLibCheck": true, + "sourceMap": true, + "target": "es2015", + "types": [ + "jasmine", + "node" + ] + }, + "exclude": [ + "**/*.spec.ts" + ], + "bazelOptions": { + "suppressTsconfigOverrideWarnings": true + } +} diff --git a/src/lib/schematics/utils/ast.ts b/src/cdk/schematics/utils/ast.ts similarity index 100% rename from src/lib/schematics/utils/ast.ts rename to src/cdk/schematics/utils/ast.ts diff --git a/src/lib/schematics/utils/build-component.ts b/src/cdk/schematics/utils/build-component.ts similarity index 100% rename from src/lib/schematics/utils/build-component.ts rename to src/cdk/schematics/utils/build-component.ts diff --git a/src/lib/schematics/utils/get-project.ts b/src/cdk/schematics/utils/get-project.ts similarity index 100% rename from src/lib/schematics/utils/get-project.ts rename to src/cdk/schematics/utils/get-project.ts diff --git a/src/cdk/schematics/utils/index.ts b/src/cdk/schematics/utils/index.ts new file mode 100644 index 00000000000..3924774a2c2 --- /dev/null +++ b/src/cdk/schematics/utils/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * 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 + */ + +export * from './ast'; +export * from './build-component'; +export * from './get-project'; +export * from './package-json'; +export * from './parse5-element'; +export * from './project-main-file'; +export * from './project-style-file'; +export * from './project-targets'; +export * from './schematic-options'; +export * from './version-agnostic-typescript'; diff --git a/src/lib/schematics/utils/package-json.ts b/src/cdk/schematics/utils/package-json.ts similarity index 100% rename from src/lib/schematics/utils/package-json.ts rename to src/cdk/schematics/utils/package-json.ts diff --git a/src/lib/schematics/utils/parse5-element.ts b/src/cdk/schematics/utils/parse5-element.ts similarity index 100% rename from src/lib/schematics/utils/parse5-element.ts rename to src/cdk/schematics/utils/parse5-element.ts diff --git a/src/lib/schematics/utils/project-main-file.ts b/src/cdk/schematics/utils/project-main-file.ts similarity index 100% rename from src/lib/schematics/utils/project-main-file.ts rename to src/cdk/schematics/utils/project-main-file.ts diff --git a/src/lib/schematics/utils/project-style-file.ts b/src/cdk/schematics/utils/project-style-file.ts similarity index 100% rename from src/lib/schematics/utils/project-style-file.ts rename to src/cdk/schematics/utils/project-style-file.ts diff --git a/src/lib/schematics/utils/project-targets.ts b/src/cdk/schematics/utils/project-targets.ts similarity index 100% rename from src/lib/schematics/utils/project-targets.ts rename to src/cdk/schematics/utils/project-targets.ts diff --git a/src/lib/schematics/utils/schematic-options.ts b/src/cdk/schematics/utils/schematic-options.ts similarity index 100% rename from src/lib/schematics/utils/schematic-options.ts rename to src/cdk/schematics/utils/schematic-options.ts diff --git a/src/lib/schematics/utils/version-agnostic-typescript.ts b/src/cdk/schematics/utils/version-agnostic-typescript.ts similarity index 92% rename from src/lib/schematics/utils/version-agnostic-typescript.ts rename to src/cdk/schematics/utils/version-agnostic-typescript.ts index 79c9ed95d59..66cb5d0bd0a 100644 --- a/src/lib/schematics/utils/version-agnostic-typescript.ts +++ b/src/cdk/schematics/utils/version-agnostic-typescript.ts @@ -30,7 +30,7 @@ try { try { ts = require('typescript'); } catch { - throw new Error('Error: Could not find TypeScript for the Angular Material schematics. ' + + throw new Error('Error: Could not find a TypeScript version for the schematics. ' + 'Please report an issue on the Angular Material repository.'); } } diff --git a/src/cdk/tsconfig-tests.json b/src/cdk/tsconfig-tests.json index 5ee1e08a64e..279182fd009 100644 --- a/src/cdk/tsconfig-tests.json +++ b/src/cdk/tsconfig-tests.json @@ -27,5 +27,8 @@ // Include the index.ts for each secondary entry-point "./*/index.ts", "**/*.spec.ts" + ], + "exclude": [ + "**/schematics/**/*.ts" ] } diff --git a/src/lib/schematics/BUILD.bazel b/src/lib/schematics/BUILD.bazel index edd1e78e35f..bcc44af804a 100644 --- a/src/lib/schematics/BUILD.bazel +++ b/src/lib/schematics/BUILD.bazel @@ -18,6 +18,7 @@ ts_library( "test-setup/**/*", "update/test-cases/**/*", ]), + deps = ["//src/cdk/schematics"], tsconfig = ":tsconfig.json", ) @@ -40,7 +41,7 @@ jasmine_node_test( ts_library( name = "schematics_test_sources", srcs = glob(["**/*.spec.ts", "test-setup/**/*.ts"], exclude=["**/files/**/*"]), - deps = [":schematics"], + deps = ["//src/cdk/schematics", ":schematics"], tsconfig = ":tsconfig.json", testonly = True, ) diff --git a/src/lib/schematics/address-form/index.ts b/src/lib/schematics/address-form/index.ts index 7d411ba2aab..50fe77f728f 100644 --- a/src/lib/schematics/address-form/index.ts +++ b/src/lib/schematics/address-form/index.ts @@ -6,10 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import {chain, Rule, noop, Tree} from '@angular-devkit/schematics'; -import {buildComponent} from '../utils/build-component'; +import {chain, noop, Rule, Tree} from '@angular-devkit/schematics'; +import { + addModuleImportToModule, + buildComponent, + findModuleFromOptions, +} from '@angular/cdk/schematics'; import {Schema} from './schema'; -import {addModuleImportToModule, findModuleFromOptions} from '../utils/ast'; /** * Scaffolds a new table component. diff --git a/src/lib/schematics/collection.json b/src/lib/schematics/collection.json index e12c574a049..8bcff88a152 100644 --- a/src/lib/schematics/collection.json +++ b/src/lib/schematics/collection.json @@ -9,6 +9,12 @@ "schema": "./ng-add/schema.json", "aliases": ["material-shell", "install"] }, + "ng-add-setup-project": { + "description": "Sets up the specified project after the ng-add dependencies have been installed.", + "private": true, + "factory": "./ng-add/setup-project", + "schema": "./ng-add/schema.json" + }, // Create a dashboard component "dashboard": { "description": "Create a card-based dashboard component", diff --git a/src/lib/schematics/dashboard/index.ts b/src/lib/schematics/dashboard/index.ts index 57146d15cb9..6b7d9bc6fd6 100644 --- a/src/lib/schematics/dashboard/index.ts +++ b/src/lib/schematics/dashboard/index.ts @@ -7,8 +7,11 @@ */ import {chain, noop, Rule, Tree} from '@angular-devkit/schematics'; -import {addModuleImportToModule, findModuleFromOptions} from '../utils/ast'; -import {buildComponent} from '../utils/build-component'; +import { + addModuleImportToModule, + buildComponent, + findModuleFromOptions, +} from '@angular/cdk/schematics'; import {Schema} from './schema'; /** diff --git a/src/lib/schematics/nav/index.ts b/src/lib/schematics/nav/index.ts index dcfb28c7192..81f2463a2d1 100644 --- a/src/lib/schematics/nav/index.ts +++ b/src/lib/schematics/nav/index.ts @@ -6,10 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import {chain, Rule, noop, Tree} from '@angular-devkit/schematics'; -import {buildComponent} from '../utils/build-component'; +import {chain, noop, Rule, Tree} from '@angular-devkit/schematics'; +import { + addModuleImportToModule, + buildComponent, + findModuleFromOptions, +} from '@angular/cdk/schematics'; import {Schema} from './schema'; -import {addModuleImportToModule, findModuleFromOptions} from '../utils/ast'; /** * Scaffolds a new navigation component. diff --git a/src/lib/schematics/ng-add/fonts/head-element.ts b/src/lib/schematics/ng-add/fonts/head-element.ts index 1a97c3f7f67..0e31051ef30 100644 --- a/src/lib/schematics/ng-add/fonts/head-element.ts +++ b/src/lib/schematics/ng-add/fonts/head-element.ts @@ -7,9 +7,9 @@ */ import {SchematicsException, Tree} from '@angular-devkit/schematics'; +import {getChildElementIndentation} from '@angular/cdk/schematics'; import {WorkspaceProject} from '@schematics/angular/utility/config'; import {DefaultTreeDocument, DefaultTreeElement, parse as parseHtml} from 'parse5'; -import {getChildElementIndentation} from '../../utils/parse5-element'; import {getIndexHtmlPath} from './project-index-html'; /** Appends the given element HTML fragment to the index.html head tag. */ diff --git a/src/lib/schematics/ng-add/fonts/material-fonts.ts b/src/lib/schematics/ng-add/fonts/material-fonts.ts index 346e05a00e6..d05b06ba304 100644 --- a/src/lib/schematics/ng-add/fonts/material-fonts.ts +++ b/src/lib/schematics/ng-add/fonts/material-fonts.ts @@ -7,8 +7,8 @@ */ import {Tree} from '@angular-devkit/schematics'; +import {getProjectFromWorkspace} from '@angular/cdk/schematics'; import {getWorkspace} from '@schematics/angular/utility/config'; -import {getProjectFromWorkspace} from '../../utils/get-project'; import {Schema} from '../schema'; import {appendElementToHead} from './head-element'; diff --git a/src/lib/schematics/ng-add/fonts/project-index-html.ts b/src/lib/schematics/ng-add/fonts/project-index-html.ts index b79aea32d5d..94689cfc61b 100644 --- a/src/lib/schematics/ng-add/fonts/project-index-html.ts +++ b/src/lib/schematics/ng-add/fonts/project-index-html.ts @@ -7,8 +7,8 @@ */ import {SchematicsException} from '@angular-devkit/schematics'; +import {getProjectTargetOptions} from '@angular/cdk/schematics'; import {WorkspaceProject} from '@schematics/angular/utility/config'; -import {getProjectTargetOptions} from '../../utils/project-targets'; /** Looks for the index HTML file in the given project and returns its path. */ export function getIndexHtmlPath(project: WorkspaceProject): string { diff --git a/src/lib/schematics/ng-add/gestures/hammerjs-import.ts b/src/lib/schematics/ng-add/gestures/hammerjs-import.ts index 83c6382ea3e..6cf795980bd 100644 --- a/src/lib/schematics/ng-add/gestures/hammerjs-import.ts +++ b/src/lib/schematics/ng-add/gestures/hammerjs-import.ts @@ -7,10 +7,9 @@ */ import {Rule, Tree} from '@angular-devkit/schematics'; +import {getProjectFromWorkspace, getProjectMainFile} from '@angular/cdk/schematics'; import {getWorkspace} from '@schematics/angular/utility/config'; -import {getProjectFromWorkspace} from '../../utils/get-project'; import {Schema} from '../schema'; -import {getProjectMainFile} from '../../utils/project-main-file'; const hammerjsImportStatement = `import 'hammerjs';`; diff --git a/src/lib/schematics/ng-add/index.spec.ts b/src/lib/schematics/ng-add/index.spec.ts index 2330a901eaa..0242b53b7cd 100644 --- a/src/lib/schematics/ng-add/index.spec.ts +++ b/src/lib/schematics/ng-add/index.spec.ts @@ -1,13 +1,15 @@ +import {normalize} from '@angular-devkit/core'; import {Tree} from '@angular-devkit/schematics'; import {SchematicTestRunner} from '@angular-devkit/schematics/testing'; -import {getProjectTargetOptions} from '@angular/material/schematics/utils/project-targets'; -import {getProjectStyleFile} from '../utils/project-style-file'; -import {getIndexHtmlPath} from './fonts/project-index-html'; -import {getProjectFromWorkspace} from '../utils/get-project'; +import { + getProjectFromWorkspace, + getProjectStyleFile, + getProjectTargetOptions, +} from '@angular/cdk/schematics'; +import {getWorkspace, WorkspaceProject} from '@schematics/angular/utility/config'; import {getFileContent} from '@schematics/angular/utility/test'; import {collectionPath, createTestApp} from '../test-setup/test-app'; -import {getWorkspace, WorkspaceProject} from '@schematics/angular/utility/config'; -import {normalize} from '@angular-devkit/core'; +import {getIndexHtmlPath} from './fonts/project-index-html'; describe('ng-add schematic', () => { let runner: SchematicTestRunner; @@ -38,10 +40,23 @@ describe('ng-add schematic', () => { expect(Object.keys(dependencies)).toEqual(Object.keys(dependencies).sort(), 'Expected the modified "dependencies" to be sorted alphabetically.'); + + expect(runner.tasks.some(task => task.name === 'run-schematic')).toBe(true); + }); + + it('should not set up dependencies if skipPackageJson is specified', () => { + const tree = runner.runSchematic('ng-add', {skipPackageJson: true}, appTree); + const packageJson = JSON.parse(getFileContent(tree, '/package.json')); + const dependencies = packageJson.dependencies; + + expect(dependencies['@angular/material']).toBeUndefined(); + expect(dependencies['@angular/cdk']).toBeUndefined(); + + expect(runner.tasks.some(task => task.name === 'run-schematic')).toBe(true); }); it('should add hammerjs import to project main file', () => { - const tree = runner.runSchematic('ng-add', {}, appTree); + const tree = runner.runSchematic('ng-add-setup-project', {}, appTree); const fileContent = getFileContent(tree, '/projects/material/src/main.ts'); expect(fileContent).toContain(`import 'hammerjs';`, @@ -49,7 +64,7 @@ describe('ng-add schematic', () => { }); it('should add default theme', () => { - const tree = runner.runSchematic('ng-add', {}, appTree); + const tree = runner.runSchematic('ng-add-setup-project', {}, appTree); const workspace = getWorkspace(tree); const project = getProjectFromWorkspace(workspace); @@ -61,7 +76,7 @@ describe('ng-add schematic', () => { it('should support adding a custom theme', () => { appTree = createTestApp({style: 'scss'}); - const tree = runner.runSchematic('ng-add', {theme: 'custom'}, appTree); + const tree = runner.runSchematic('ng-add-setup-project', {theme: 'custom'}, appTree); const workspace = getWorkspace(tree); const project = getProjectFromWorkspace(workspace); @@ -77,7 +92,7 @@ describe('ng-add schematic', () => { it('should create a custom theme file if no SCSS file could be found', () => { appTree = createTestApp({style: 'css'}); - const tree = runner.runSchematic('ng-add', {theme: 'custom'}, appTree); + const tree = runner.runSchematic('ng-add-setup-project', {theme: 'custom'}, appTree); const workspace = getWorkspace(tree); const project = getProjectFromWorkspace(workspace); const expectedStylesPath = normalize(`/${project.root}/src/custom-theme.scss`); @@ -87,7 +102,7 @@ describe('ng-add schematic', () => { }); it('should add font links', () => { - const tree = runner.runSchematic('ng-add', {}, appTree); + const tree = runner.runSchematic('ng-add-setup-project', {}, appTree); const workspace = getWorkspace(tree); const project = getProjectFromWorkspace(workspace); @@ -105,7 +120,7 @@ describe('ng-add schematic', () => { }); it('should add material app styles', () => { - const tree = runner.runSchematic('ng-add', {}, appTree); + const tree = runner.runSchematic('ng-add-setup-project', {}, appTree); const workspace = getWorkspace(tree); const project = getProjectFromWorkspace(workspace); diff --git a/src/lib/schematics/ng-add/index.ts b/src/lib/schematics/ng-add/index.ts index 3ca9ed2de52..ce56ff99511 100644 --- a/src/lib/schematics/ng-add/index.ts +++ b/src/lib/schematics/ng-add/index.ts @@ -6,114 +6,47 @@ * found in the LICENSE file at https://angular.io/license */ -import { - chain, - noop, - Rule, - SchematicContext, - SchematicsException, - Tree, -} from '@angular-devkit/schematics'; -import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks'; -import {getWorkspace} from '@schematics/angular/utility/config'; -import * as parse5 from 'parse5'; -import {addModuleImportToRootModule} from '../utils/ast'; -import {getProjectFromWorkspace} from '../utils/get-project'; -import {addPackageToPackageJson, getPackageVersionFromPackageJson} from '../utils/package-json'; -import {getProjectStyleFile} from '../utils/project-style-file'; -import {addFontsToIndex} from './fonts/material-fonts'; -import {addHammerJsToMain} from './gestures/hammerjs-import'; +import {Rule, Tree, SchematicContext, TaskId} from '@angular-devkit/schematics'; +import {NodePackageInstallTask, RunSchematicTask} from '@angular-devkit/schematics/tasks'; +import {addPackageToPackageJson, getPackageVersionFromPackageJson} from './package-json'; import {Schema} from './schema'; -import {addThemeToAppStyles} from './theming/theming'; import {hammerjsVersion, materialVersion, requiredAngularVersionRange} from './version-names'; /** - * Scaffolds the basics of a Angular Material application, this includes: - * - Add Packages to package.json - * - Adds pre-built themes to styles.ext - * - Adds Browser Animation to app.module + * Schematic factory entry-point for the `ng-add` schematic. The ng-add schematic will be + * automatically executed if developers run `ng add @angular/material`. + * + * Since the Angular Material schematics depend on the schematic utility functions from the CDK, + * we need to install the CDK before loading the schematic files that import from the CDK. */ export default function(options: Schema): Rule { - if (!parse5) { - throw new SchematicsException('Parse5 is required but could not be found! Please install ' + - '"parse5" manually in order to continue.'); - } - - return chain([ - options && options.skipPackageJson ? noop() : addMaterialToPackageJson(options), - options && options.gestures ? addHammerJsToMain(options) : noop(), - addThemeToAppStyles(options), - addAnimationRootConfig(options), - addFontsToIndex(options), - addMaterialAppStyles(options), - ]); -} - -/** Add material, cdk, animations to package.json if not already present. */ -function addMaterialToPackageJson(options: Schema) { return (host: Tree, context: SchematicContext) => { - // Version tag of the `@angular/core` dependency that has been loaded from the `package.json` - // of the CLI project. This tag should be preferred because all Angular dependencies should - // have the same version tag if possible. - const ngCoreVersionTag = getPackageVersionFromPackageJson(host, '@angular/core'); + // Since the Angular Material schematics depend on the schematic utility functions from the + // CDK, we need to install the CDK before loading the schematic files that import from the CDK. + let installTaskId: TaskId; - addPackageToPackageJson(host, '@angular/cdk', `^${materialVersion}`); - addPackageToPackageJson(host, '@angular/material', `^${materialVersion}`); - addPackageToPackageJson(host, '@angular/animations', - ngCoreVersionTag || requiredAngularVersionRange); + if (!options.skipPackageJson) { + // Version tag of the `@angular/core` dependency that has been loaded from the `package.json` + // of the CLI project. This tag should be preferred because all Angular dependencies should + // have the same version tag if possible. + const ngCoreVersionTag = getPackageVersionFromPackageJson(host, '@angular/core'); - if (options.gestures) { - addPackageToPackageJson(host, 'hammerjs', hammerjsVersion); + addPackageToPackageJson(host, '@angular/cdk', `^${materialVersion}`); + addPackageToPackageJson(host, '@angular/material', `^${materialVersion}`); + addPackageToPackageJson(host, '@angular/animations', + ngCoreVersionTag || requiredAngularVersionRange); + + if (options.gestures) { + addPackageToPackageJson(host, 'hammerjs', hammerjsVersion); + } + + installTaskId = context.addTask(new NodePackageInstallTask()); + } else { + installTaskId = context.addTask(new NodePackageInstallTask({ + packageName: `@angular/cdk@^${materialVersion}` + })); } - context.addTask(new NodePackageInstallTask()); - - return host; - }; -} - -/** Add browser animation module to the app module file. */ -function addAnimationRootConfig(options: Schema) { - return (host: Tree) => { - const workspace = getWorkspace(host); - const project = getProjectFromWorkspace(workspace, options.project); - - addModuleImportToRootModule(host, 'BrowserAnimationsModule', - '@angular/platform-browser/animations', project); - - return host; - }; -} - -/** - * Adds custom Material styles to the project style file. The custom CSS sets up the Roboto font - * and reset the default browser body margin. - */ -function addMaterialAppStyles(options: Schema) { - return (host: Tree) => { - const workspace = getWorkspace(host); - const project = getProjectFromWorkspace(workspace, options.project); - const styleFilePath = getProjectStyleFile(project); - const buffer = host.read(styleFilePath!); - - if (!styleFilePath || !buffer) { - return console.warn(`Could not find styles file: "${styleFilePath}". Skipping styles ` + - `generation. Please consider manually adding the "Roboto" font and resetting the ` + - `body margin.`); - } - - const htmlContent = buffer.toString(); - const insertion = '\n' + - `html, body { height: 100%; }\n` + - `body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }\n`; - - if (htmlContent.includes(insertion)) { - return; - } - - const recorder = host.beginUpdate(styleFilePath); - - recorder.insertLeft(htmlContent.length, insertion); - host.commitUpdate(recorder); + context.addTask(new RunSchematicTask('ng-add-setup-project', options), [installTaskId]); }; } diff --git a/src/lib/schematics/ng-add/package-json.ts b/src/lib/schematics/ng-add/package-json.ts new file mode 100644 index 00000000000..a8cf8d33817 --- /dev/null +++ b/src/lib/schematics/ng-add/package-json.ts @@ -0,0 +1,61 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * 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 {Tree} from '@angular-devkit/schematics'; + +/* + * This file is temporary and a duplicate version has been moved to `@angular/cdk/schematics`. + * + * We still need this file inside of the Material schematics because if people run `ng add` for + * Angular Material, the CDK is not present, and we cannot use the utility functions from there. + */ + +/** + * Sorts the keys of the given object. + * @returns A new object instance with sorted keys + */ +function sortObjectByKeys(obj: object) { + return Object.keys(obj).sort().reduce((result, key) => (result[key] = obj[key]) && result, {}); +} + +/** Adds a package to the package.json in the given host tree. */ +export function addPackageToPackageJson(host: Tree, pkg: string, version: string): Tree { + + if (host.exists('package.json')) { + const sourceText = host.read('package.json')!.toString('utf-8'); + const json = JSON.parse(sourceText); + + if (!json.dependencies) { + json.dependencies = {}; + } + + if (!json.dependencies[pkg]) { + json.dependencies[pkg] = version; + json.dependencies = sortObjectByKeys(json.dependencies); + } + + host.overwrite('package.json', JSON.stringify(json, null, 2)); + } + + return host; +} + +/** Gets the version of the specified package by looking at the package.json in the given tree. */ +export function getPackageVersionFromPackageJson(tree: Tree, name: string): string | null { + if (!tree.exists('package.json')) { + return null; + } + + const packageJson = JSON.parse(tree.read('package.json')!.toString('utf8')); + + if (packageJson.dependencies && packageJson.dependencies[name]) { + return packageJson.dependencies[name]; + } + + return null; +} diff --git a/src/lib/schematics/ng-add/setup-project.ts b/src/lib/schematics/ng-add/setup-project.ts new file mode 100644 index 00000000000..ce01c28332a --- /dev/null +++ b/src/lib/schematics/ng-add/setup-project.ts @@ -0,0 +1,87 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * 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 {chain, noop, Rule, SchematicsException, Tree} from '@angular-devkit/schematics'; +import { + addModuleImportToRootModule, + getProjectFromWorkspace, + getProjectStyleFile, +} from '@angular/cdk/schematics'; +import {getWorkspace} from '@schematics/angular/utility/config'; +import * as parse5 from 'parse5'; +import {addFontsToIndex} from './fonts/material-fonts'; +import {addHammerJsToMain} from './gestures/hammerjs-import'; +import {Schema} from './schema'; +import {addThemeToAppStyles} from './theming/theming'; + +/** + * Scaffolds the basics of a Angular Material application, this includes: + * - Add Packages to package.json + * - Adds pre-built themes to styles.ext + * - Adds Browser Animation to app.module + */ +export default function(options: Schema): Rule { + if (!parse5) { + throw new SchematicsException('Parse5 is required but could not be found! Please install ' + + '"parse5" manually in order to continue.'); + } + + return chain([ + options && options.gestures ? addHammerJsToMain(options) : noop(), + addThemeToAppStyles(options), + addAnimationRootConfig(options), + addFontsToIndex(options), + addMaterialAppStyles(options), + ]); +} + +/** Add browser animation module to the app module file. */ +function addAnimationRootConfig(options: Schema) { + return (host: Tree) => { + const workspace = getWorkspace(host); + const project = getProjectFromWorkspace(workspace, options.project); + + addModuleImportToRootModule(host, 'BrowserAnimationsModule', + '@angular/platform-browser/animations', project); + + return host; + }; +} + +/** + * Adds custom Material styles to the project style file. The custom CSS sets up the Roboto font + * and reset the default browser body margin. + */ +function addMaterialAppStyles(options: Schema) { + return (host: Tree) => { + const workspace = getWorkspace(host); + const project = getProjectFromWorkspace(workspace, options.project); + const styleFilePath = getProjectStyleFile(project); + const buffer = host.read(styleFilePath!); + + if (!styleFilePath || !buffer) { + return console.warn(`Could not find styles file: "${styleFilePath}". Skipping styles ` + + `generation. Please consider manually adding the "Roboto" font and resetting the ` + + `body margin.`); + } + + const htmlContent = buffer.toString(); + const insertion = '\n' + + `html, body { height: 100%; }\n` + + `body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }\n`; + + if (htmlContent.includes(insertion)) { + return; + } + + const recorder = host.beginUpdate(styleFilePath); + + recorder.insertLeft(htmlContent.length, insertion); + host.commitUpdate(recorder); + }; +} diff --git a/src/lib/schematics/ng-add/theming/theming.ts b/src/lib/schematics/ng-add/theming/theming.ts index aefa689c915..53b6c0e9964 100644 --- a/src/lib/schematics/ng-add/theming/theming.ts +++ b/src/lib/schematics/ng-add/theming/theming.ts @@ -8,12 +8,14 @@ import {normalize} from '@angular-devkit/core'; import {SchematicsException, Tree} from '@angular-devkit/schematics'; +import { + getProjectFromWorkspace, + getProjectStyleFile, + getProjectTargetOptions, +} from '@angular/cdk/schematics'; import {InsertChange} from '@schematics/angular/utility/change'; import {getWorkspace, WorkspaceProject, WorkspaceSchema} from '@schematics/angular/utility/config'; import {join} from 'path'; -import {getProjectFromWorkspace} from '../../utils/get-project'; -import {getProjectStyleFile} from '../../utils/project-style-file'; -import {getProjectTargetOptions} from '../../utils/project-targets'; import {Schema} from '../schema'; import {createCustomTheme} from './custom-theme'; diff --git a/src/lib/schematics/table/index.ts b/src/lib/schematics/table/index.ts index b7215bd2183..76ab3901972 100644 --- a/src/lib/schematics/table/index.ts +++ b/src/lib/schematics/table/index.ts @@ -6,10 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import {chain, Rule, noop, Tree} from '@angular-devkit/schematics'; -import {buildComponent} from '../utils/build-component'; +import {chain, noop, Rule, Tree} from '@angular-devkit/schematics'; +import { + addModuleImportToModule, + buildComponent, + findModuleFromOptions, +} from '@angular/cdk/schematics'; import {Schema} from './schema'; -import {addModuleImportToModule, findModuleFromOptions} from '../utils/ast'; /** * Scaffolds a new table component. diff --git a/src/lib/schematics/tree/index.ts b/src/lib/schematics/tree/index.ts index 06277598d66..8959726cdee 100644 --- a/src/lib/schematics/tree/index.ts +++ b/src/lib/schematics/tree/index.ts @@ -7,8 +7,11 @@ */ import {chain, noop, Rule, Tree} from '@angular-devkit/schematics'; -import {addModuleImportToModule, findModuleFromOptions} from '../utils/ast'; -import {buildComponent} from '../utils/build-component'; +import { + addModuleImportToModule, + buildComponent, + findModuleFromOptions, +} from '@angular/cdk/schematics'; import {Schema} from './schema'; /** diff --git a/src/lib/schematics/tsconfig.json b/src/lib/schematics/tsconfig.json index 1417e0cca2f..b14e0392072 100644 --- a/src/lib/schematics/tsconfig.json +++ b/src/lib/schematics/tsconfig.json @@ -3,7 +3,7 @@ "lib": ["es2017"], "module": "commonjs", "moduleResolution": "node", - "outDir": "../../../dist/schematics", + "outDir": "../../../dist/packages/material/schematics", "noEmitOnError": false, "strictNullChecks": true, "skipDefaultLibCheck": true, @@ -13,7 +13,11 @@ "types": [ "jasmine", "node" - ] + ], + "baseUrl": ".", + "paths": { + "@angular/cdk/schematics": ["../../../dist/packages/cdk/schematics/"] + } }, "exclude": [ "**/*.spec.ts", diff --git a/tools/gulp/packages.ts b/tools/gulp/packages.ts index 56e130d4a65..839c4fc8c98 100644 --- a/tools/gulp/packages.ts +++ b/tools/gulp/packages.ts @@ -20,3 +20,7 @@ materialPackage.sourceDir = join(buildConfig.packagesDir, 'lib'); // release output root. This is different in the Material package because here a full SCSS bundle // will be generated. cdkPackage.copySecondaryEntryPointStylesToRoot = true; + +// Build and copy the schematics of the CDK and Material package. +cdkPackage.hasSchematics = true; +materialPackage.hasSchematics = true; diff --git a/tools/gulp/tasks/material-release.ts b/tools/gulp/tasks/material-release.ts index c9c0771451b..95a8d7b6d29 100644 --- a/tools/gulp/tasks/material-release.ts +++ b/tools/gulp/tasks/material-release.ts @@ -1,18 +1,15 @@ -import {task, src, dest} from 'gulp'; +import {mkdirpSync, writeFileSync} from 'fs-extra'; +import {dest, src, task} from 'gulp'; +import {buildConfig, composeRelease, sequenceTask} from 'material2-build-tools'; import {join} from 'path'; -import {writeFileSync, mkdirpSync} from 'fs-extra'; import {Bundler} from 'scss-bundle'; -import {composeRelease, buildConfig, sequenceTask} from 'material2-build-tools'; import {materialPackage} from '../packages'; -import {tsBuildTask} from '../util/task_helpers'; - // There are no type definitions available for these imports. const gulpRename = require('gulp-rename'); const distDir = buildConfig.outputDir; const {sourceDir, outputDir} = materialPackage; -const schematicsDir = join(sourceDir, 'schematics'); /** Path to the directory where all releases are created. */ const releasesDir = join(distDir, 'releases'); @@ -33,40 +30,22 @@ const prebuiltThemeGlob = join(outputDir, '**/theming/prebuilt/*.css?(.map)'); // the bundle. It's used to identify Sass files that shouldn't be included multiple times. const allScssDedupeGlob = join(buildConfig.packagesDir, '**/*.scss'); -// Pattern matching schematics files to be copied into the @angular/material package. -const schematicsGlobs = [ - // File templates and schemas are copied as-is from source. - join(schematicsDir, '**/+(data|files)/**/*'), - join(schematicsDir, '**/+(schema|collection|migration).json'), - - // JavaScript files compiled from the TypeScript sources. - join(distDir, 'schematics', '**/*.js?(.map)'), -]; - /** * Overwrite the release task for the material package. The material release will include special * files, like a bundled theming SCSS file or all prebuilt themes. */ task('material:build-release', ['material:prepare-release'], () => composeRelease(materialPackage)); -/** Compile the schematics TypeScript to JavaScript */ -task('schematics:build', tsBuildTask(join(schematicsDir, 'tsconfig.json'))); - /** * Task that will build the material package. Special treatment for this package includes: * - Copying all prebuilt themes into the package * - Bundling theming scss into a single theming file - * - Copying schematics code into the package */ task('material:prepare-release', sequenceTask( - ['material:build', 'schematics:build'], - ['material:copy-prebuilt-themes', 'material:bundle-theming-scss', 'material:copy-schematics'], + ['material:build'], + ['material:copy-prebuilt-themes', 'material:bundle-theming-scss'], )); -task('material:copy-schematics', () => { - return src(schematicsGlobs).pipe(dest(join(releasePath, 'schematics'))); -}); - /** Copies all prebuilt themes into the release package under `prebuilt-themes/` */ task('material:copy-prebuilt-themes', () => { return src(prebuiltThemeGlob) diff --git a/tools/package-tools/build-package.ts b/tools/package-tools/build-package.ts index 5543f575b77..4979829dd68 100644 --- a/tools/package-tools/build-package.ts +++ b/tools/package-tools/build-package.ts @@ -32,6 +32,9 @@ export class BuildPackage { /** Whether the secondary entry-point styles should be copied to the release output. */ copySecondaryEntryPointStylesToRoot = false; + /** Whether the build package has schematics or not. */ + hasSchematics = false; + /** Path to the entry file of the package in the output directory. */ readonly entryFilePath: string; diff --git a/tools/package-tools/build-release.ts b/tools/package-tools/build-release.ts index 1f44b1f4b6b..238e1557365 100644 --- a/tools/package-tools/build-release.ts +++ b/tools/package-tools/build-release.ts @@ -54,6 +54,10 @@ export function composeRelease(buildPackage: BuildPackage) { createTypingsReexportFile(releasePath, './typings/index', name); createMetadataReexportFile(releasePath, './typings/index', name, importAsName); + if (buildPackage.hasSchematics) { + copyFiles(join(packageOut, 'schematics'), '**/*', join(releasePath, 'schematics')); + } + if (buildPackage.secondaryEntryPoints.length) { createFilesForSecondaryEntryPoint(buildPackage, releasePath); } diff --git a/tools/package-tools/compile-entry-point.ts b/tools/package-tools/compile-entry-point.ts index b558c258b7d..6b1a98d7f4f 100644 --- a/tools/package-tools/compile-entry-point.ts +++ b/tools/package-tools/compile-entry-point.ts @@ -3,7 +3,7 @@ import {writeFileSync, readFileSync} from 'fs'; import {sync as glob} from 'glob'; import {red} from 'chalk'; import {BuildPackage} from './build-package'; -import {ngcCompile} from './ngc-compile'; +import {tsCompile} from './ts-compile'; /** Incrementing ID counter. */ let nextId = 0; @@ -19,7 +19,7 @@ export async function compileEntryPoint(buildPackage: BuildPackage, tsconfigName ngcFlags.push('--outDir', es5OutputPath, '--target', 'ES5'); } - return ngcCompile(ngcFlags).catch(() => { + return tsCompile('ngc', ngcFlags).catch(() => { const error = red(`Failed to compile ${secondaryEntryPoint} using ${entryPointTsconfigPath}`); console.error(error); return Promise.reject(error); diff --git a/tools/package-tools/gulp/build-tasks-gulp.ts b/tools/package-tools/gulp/build-tasks-gulp.ts index 2a3fd7ac486..9c154e3983d 100644 --- a/tools/package-tools/gulp/build-tasks-gulp.ts +++ b/tools/package-tools/gulp/build-tasks-gulp.ts @@ -1,5 +1,6 @@ import {dest, src, task} from 'gulp'; import {join} from 'path'; +import {tsCompile} from '../ts-compile'; import {composeRelease} from '../build-release'; import {inlineResourcesForDirectory} from '../inline-resources'; import {buildScssPipeline} from './build-scss-pipeline'; @@ -38,6 +39,15 @@ export function createPackageBuildTasks(buildPackage: BuildPackage, preBuildTask // List of watch tasks that need run together with the watch task of the current package. const dependentWatchTasks = buildPackage.dependencies.map(p => `${p.name}:watch`); + // Path to the schematics output directory if the build package has schematics. + const schematicsDir = join(buildPackage.sourceDir, 'schematics'); + + // Pattern matching schematics files to be copied into the output directory. + const schematicsGlobs = [ + join(schematicsDir, '**/+(data|files)/**/*'), + join(schematicsDir, '**/+(schema|collection|migration).json'), + ]; + /** * Main tasks for the package building. Tasks execute the different sub-tasks in the correct * order. @@ -87,11 +97,19 @@ export function createPackageBuildTasks(buildPackage: BuildPackage, preBuildTask /** * Asset tasks. Building Sass files and inlining CSS, HTML files into the ESM output. */ - task(`${taskName}:assets`, [ + const assetTasks = [ `${taskName}:assets:scss`, `${taskName}:assets:copy-styles`, `${taskName}:assets:html` - ]); + ]; + + // In case the build package has schematics, we need to build them like assets because + // those are not intended to be entry-points. + if (buildPackage.hasSchematics) { + assetTasks.push(`${taskName}:assets:schematics`); + } + + task(`${taskName}:assets`, assetTasks); task(`${taskName}:assets:scss`, () => { buildScssPipeline(buildPackage.sourceDir, true) @@ -114,6 +132,14 @@ export function createPackageBuildTasks(buildPackage: BuildPackage, preBuildTask task(`${taskName}:assets:inline`, () => inlineResourcesForDirectory(buildPackage.outputDir)); + task(`${taskName}:assets:schematics-ts`, () => { + return tsCompile('tsc', ['-p', join(schematicsDir, 'tsconfig.json')]); + }); + + task(`${taskName}:assets:schematics`, [`${taskName}:assets:schematics-ts`], () => { + return src(schematicsGlobs).pipe(dest(join(buildPackage.outputDir, 'schematics'))); + }); + /** * Watch tasks, that will rebuild the package whenever TS, SCSS, or HTML files change. */ diff --git a/tools/package-tools/ngc-compile.ts b/tools/package-tools/ts-compile.ts similarity index 57% rename from tools/package-tools/ngc-compile.ts rename to tools/package-tools/ts-compile.ts index 77d0ae9f17c..3b856c1870e 100644 --- a/tools/package-tools/ngc-compile.ts +++ b/tools/package-tools/ts-compile.ts @@ -3,14 +3,15 @@ import {spawn} from 'child_process'; import {red} from 'chalk'; /** - * Spawns a child process that compiles using ngc. - * @param flags Command-line flags to be passed to ngc. + * Spawns a child process that compiles TypeScript using the specified compiler binary. + * @param binary Binary name that will be used for TS compilation. + * @param flags Command-line flags to be passed to binary. * @returns Promise that resolves/rejects when the child process exits. */ -export function ngcCompile(flags: string[]) { +export function tsCompile(binary: 'tsc' | 'ngc', flags: string[]) { return new Promise((resolve, reject) => { - const ngcPath = resolvePath('./node_modules/.bin/ngc'); - const childProcess = spawn(ngcPath, flags, {shell: true}); + const binaryPath = resolvePath(`./node_modules/.bin/${binary}`); + const childProcess = spawn(binaryPath, flags, {shell: true}); // Pipe stdout and stderr from the child process. childProcess.stdout.on('data', (data: string|Buffer) => console.log(`${data}`));