diff --git a/.monorepo.json b/.monorepo.json index 509606bc5535..1fe5ae22a69a 100644 --- a/.monorepo.json +++ b/.monorepo.json @@ -42,16 +42,16 @@ }, "packages": { "@_/benchmark": { - "version": "0.13.0-rc.0", - "hash": "4eecb4a76317039defc5c2b4bd31e4f9" + "version": "0.13.6", + "hash": "f828d4a3fa042a7feac4a48c86893375" }, "@_/builders": { - "version": "0.13.0-rc.0", - "hash": "796f00a3b325334632fd8bb9d1b46b02" + "version": "0.13.6", + "hash": "6983d21ece5e42aa536b50adfdceb56c" }, "devkit": { - "version": "0.13.0-rc.0", - "hash": "10155f9a3d1491f01029f3e1f17e53c5" + "version": "0.13.6", + "hash": "aaee71d2039099677b168f9deb0a0cac" }, "@angular/cli": { "name": "Angular CLI", @@ -62,15 +62,15 @@ "url": "/packages/angular/cli/README.md" } ], - "version": "7.3.0-rc.0", + "version": "7.3.6", "snapshotRepo": "angular/cli-builds", - "hash": "98ba66c65bd76e6416c395168ea36942" + "hash": "d6fa81e906638abb269a66f95725a9cc" }, "@angular/pwa": { "name": "Angular PWA Schematics", "section": "Schematics", - "version": "0.13.0-rc.0", - "hash": "d083cd4c874ab24a9e64a688a46819d5", + "version": "0.13.6", + "hash": "ad7671c7158d5913f0f5a41d9d89373c", "snapshotRepo": "angular/angular-pwa-builds" }, "@angular-devkit/architect": { @@ -81,21 +81,21 @@ "url": "/packages/angular_devkit/architect/README.md" } ], - "version": "0.13.0-rc.0", - "hash": "cad5e9583409b4aaa4265c9058570e1c", + "version": "0.13.6", + "hash": "c8f98e1162c129b33d1b6ddcdd29d18f", "snapshotRepo": "angular/angular-devkit-architect-builds" }, "@angular-devkit/architect-cli": { "name": "Architect CLI", - "version": "0.13.0-rc.0", - "hash": "79975f9eddbd7c9da181621757242159", + "version": "0.13.6", + "hash": "3dce11eb931afe7b8376f3f057393887", "snapshotRepo": "angular/angular-devkit-architect-cli-builds" }, "@angular-devkit/benchmark": { "name": "Benchmark", "section": "Tooling", - "version": "1.2.0-rc.0", - "hash": "2fd80ee178831cf05f721dc60d427045" + "version": "1.2.6", + "hash": "8574149d93ee40b6fe54c4269986b9eb" }, "@angular-devkit/build-optimizer": { "name": "Build Optimizer", @@ -105,8 +105,8 @@ "url": "/packages/angular_devkit/build_optimizer/README.md" } ], - "version": "0.13.0-rc.0", - "hash": "963623296eed8991da173b36ae133422", + "version": "0.13.6", + "hash": "a6681417acd58d6575e33e314bdede68", "snapshotRepo": "angular/angular-devkit-build-optimizer-builds" }, "@angular-devkit/build-ng-packagr": { @@ -117,8 +117,8 @@ "url": "/packages/angular_devkit/build_ng_packagr/README.md" } ], - "version": "0.13.0-rc.0", - "hash": "204d6049be4dc38231ad340f6647234e", + "version": "0.13.6", + "hash": "2fb405a473f1c3cafbbdb065ae3423b9", "snapshotRepo": "angular/angular-devkit-build-ng-packagr-builds" }, "@angular-devkit/build-angular": { @@ -129,8 +129,8 @@ "url": "/packages/angular_devkit/build_angular/README.md" } ], - "version": "0.13.0-rc.0", - "hash": "62c5c47288d1e6253aee2b87f58d23ea", + "version": "0.13.6", + "hash": "98f0cdeab6069febe297b41ab6d3a01d", "snapshotRepo": "angular/angular-devkit-build-angular-builds" }, "@angular-devkit/build-webpack": { @@ -141,9 +141,9 @@ "url": "/packages/angular_devkit/build_webpack/README.md" } ], - "version": "0.13.0-rc.0", + "version": "0.13.6", "snapshotRepo": "angular/angular-devkit-build-webpack-builds", - "hash": "f969c837647b6e7b2252e8043d5f51a4" + "hash": "a2787aa9f4059b2b9617bf9f542426af" }, "@angular-devkit/core": { "name": "Core", @@ -153,8 +153,8 @@ "url": "/packages/angular_devkit/core/README.md" } ], - "version": "7.3.0-rc.0", - "hash": "3b41fea05a2407f68376577c7e187125", + "version": "7.3.6", + "hash": "395406a65e0ae33d02fa49f032059493", "snapshotRepo": "angular/angular-devkit-core-builds" }, "@angular-devkit/schematics": { @@ -165,43 +165,43 @@ "url": "/packages/angular_devkit/schematics/README.md" } ], - "version": "7.3.0-rc.0", - "hash": "1c02c44f881d843eb073203f3dc6d595", + "version": "7.3.6", + "hash": "d0884821f686dab6b9cadfbb64068a68", "snapshotRepo": "angular/angular-devkit-schematics-builds" }, "@angular-devkit/schematics-cli": { "name": "Schematics CLI", "section": "Tooling", - "version": "0.13.0-rc.0", - "hash": "e7387a3eacefb5cfbcacd7c652a4cce4", + "version": "0.13.6", + "hash": "39c636aa4c6ada56f73a57f0a80becdc", "snapshotRepo": "angular/angular-devkit-schematics-cli-builds" }, "@ngtools/webpack": { "name": "Webpack Angular Plugin", - "version": "7.3.0-rc.0", + "version": "7.3.6", "section": "Misc", - "hash": "29ac1ec6a3c2011594995d1cec89cab4", + "hash": "e7503bbc3740595bab82540051f24186", "snapshotRepo": "angular/ngtools-webpack-builds" }, "@schematics/angular": { "name": "Angular Schematics", "section": "Schematics", - "version": "7.3.0-rc.0", - "hash": "fcadd5d025797bdb162ce731e1515b99", + "version": "7.3.6", + "hash": "a7098857c57f12029fe4c55e9f33ab3d", "snapshotRepo": "angular/schematics-angular-builds" }, "@schematics/schematics": { "name": "Schematics Schematics", - "version": "0.13.0-rc.0", + "version": "0.13.6", "section": "Schematics", - "hash": "edbe388e57e8805d49bdd41410b147bc", + "hash": "0f8df00686de8567a33cda26baf22732", "snapshotRepo": "angular/schematics-schematics-builds" }, "@schematics/update": { "name": "Package Update Schematics", - "version": "0.13.0-rc.0", + "version": "0.13.6", "section": "Schematics", - "hash": "128b864b9f5d8d844cee04103a639d5c", + "hash": "e10ff5ce6776cf8f64bd194115cd6ed1", "snapshotRepo": "angular/schematics-update-builds" } } diff --git a/package.json b/package.json index bcb8584a444d..7f0ca5864e7b 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "@types/webpack-dev-server": "^3.1.0", "@types/webpack-sources": "^0.1.5", "@yarnpkg/lockfile": "1.1.0", - "ajv": "6.7.0", + "ajv": "6.9.1", "common-tags": "^1.8.0", "conventional-changelog": "^1.1.0", "conventional-commits-parser": "^3.0.0", diff --git a/packages/angular/cli/commands/add-impl.ts b/packages/angular/cli/commands/add-impl.ts index 1e915ec4460b..34c9b75b1fbc 100644 --- a/packages/angular/cli/commands/add-impl.ts +++ b/packages/angular/cli/commands/add-impl.ts @@ -140,7 +140,11 @@ export class AddCommand extends SchematicCommand { private isPackageInstalled(name: string): boolean { try { - resolve(name, { checkLocal: true, basedir: this.workspace.root }); + resolve(name, { + checkLocal: true, + basedir: this.workspace.root, + resolvePackageJson: true, + }); return true; } catch (e) { diff --git a/packages/angular/cli/commands/add.json b/packages/angular/cli/commands/add.json index 9dabd06b8ef0..97d7400dd739 100644 --- a/packages/angular/cli/commands/add.json +++ b/packages/angular/cli/commands/add.json @@ -24,7 +24,7 @@ ] }, { - "$ref": "./definitions.json#/definitions/schematic" + "$ref": "./definitions.json#/definitions/interactive" }, { "$ref": "./definitions.json#/definitions/base" diff --git a/packages/angular/cli/commands/definitions.json b/packages/angular/cli/commands/definitions.json index 2adccc4a0693..6c6f6f0b90a6 100644 --- a/packages/angular/cli/commands/definitions.json +++ b/packages/angular/cli/commands/definitions.json @@ -49,7 +49,11 @@ "default": false, "aliases": [ "f" ], "description": "When true, forces overwriting of existing files." - }, + } + } + }, + "interactive": { + "properties": { "interactive": { "type": "boolean", "default": "true", diff --git a/packages/angular/cli/commands/doc-impl.ts b/packages/angular/cli/commands/doc-impl.ts index 6a0e7a265ecf..b93a82333ceb 100644 --- a/packages/angular/cli/commands/doc-impl.ts +++ b/packages/angular/cli/commands/doc-impl.ts @@ -14,13 +14,21 @@ const opn = require('opn'); export class DocCommand extends Command { public async run(options: DocCommandSchema & Arguments) { + if (!options.keyword) { + this.logger.error('You should specify a keyword, for instance, `ng doc ActivatedRoute`.'); + + return 0; + } let searchUrl = `https://angular.io/api?query=${options.keyword}`; if (options.search) { searchUrl = `https://www.google.com/search?q=site%3Aangular.io+${options.keyword}`; } - return opn(searchUrl, { - wait: false, + // We should wrap `opn` in a new Promise because `opn` is already resolved + await new Promise(() => { + opn(searchUrl, { + wait: false, + }); }); } } diff --git a/packages/angular/cli/commands/generate.json b/packages/angular/cli/commands/generate.json index f8668058cc9f..be287dd307c8 100644 --- a/packages/angular/cli/commands/generate.json +++ b/packages/angular/cli/commands/generate.json @@ -26,6 +26,7 @@ ] }, { "$ref": "./definitions.json#/definitions/base" }, - { "$ref": "./definitions.json#/definitions/schematic" } + { "$ref": "./definitions.json#/definitions/schematic" }, + { "$ref": "./definitions.json#/definitions/interactive" } ] } diff --git a/packages/angular/cli/commands/new.json b/packages/angular/cli/commands/new.json index aeffbfe5255f..89cfbda500dd 100644 --- a/packages/angular/cli/commands/new.json +++ b/packages/angular/cli/commands/new.json @@ -28,6 +28,7 @@ "required": [] }, { "$ref": "./definitions.json#/definitions/base" }, - { "$ref": "./definitions.json#/definitions/schematic" } + { "$ref": "./definitions.json#/definitions/schematic" }, + { "$ref": "./definitions.json#/definitions/interactive" } ] } diff --git a/packages/angular/cli/lib/config/schema.json b/packages/angular/cli/lib/config/schema.json index 50b56f08650e..28a2e5a6af0f 100644 --- a/packages/angular/cli/lib/config/schema.json +++ b/packages/angular/cli/lib/config/schema.json @@ -847,7 +847,7 @@ }, "statsJson": { "type": "boolean", - "description": "Generates a 'stats.json' file which can be analyzed using tools such as: #webpack-bundle-analyzer' or https://webpack.github.io/analyse .", + "description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.", "default": false }, "forkTypeChecker": { @@ -1753,7 +1753,7 @@ }, "statsJson": { "type": "boolean", - "description": "Generates a 'stats.json' file which can be analyzed using tools such as: #webpack-bundle-analyzer' or https://webpack.github.io/analyse .", + "description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.", "default": false }, "forkTypeChecker": { diff --git a/packages/angular_devkit/build_angular/package.json b/packages/angular_devkit/build_angular/package.json index 0176aa0db1ab..6beef2d3b4af 100644 --- a/packages/angular_devkit/build_angular/package.json +++ b/packages/angular_devkit/build_angular/package.json @@ -11,14 +11,13 @@ "@angular-devkit/build-webpack": "0.0.0", "@angular-devkit/core": "0.0.0", "@ngtools/webpack": "0.0.0", - "ajv": "6.7.0", + "ajv": "6.9.1", "autoprefixer": "9.4.6", "circular-dependency-plugin": "5.0.2", "clean-css": "4.2.1", "copy-webpack-plugin": "4.6.0", "file-loader": "3.0.1", "glob": "7.1.3", - "istanbul": "0.4.5", "istanbul-instrumenter-loader": "3.0.1", "karma-source-map-support": "1.3.0", "less": "3.9.0", @@ -44,7 +43,7 @@ "stylus": "0.54.5", "stylus-loader": "3.0.2", "tree-kill": "1.2.1", - "terser-webpack-plugin": "1.2.1", + "terser-webpack-plugin": "1.2.2", "webpack": "4.29.0", "webpack-dev-middleware": "3.5.1", "webpack-dev-server": "3.1.14", diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts index 372a5e05e330..53db8b5761cf 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts @@ -5,6 +5,7 @@ * 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 { tags } from '@angular-devkit/core'; import * as CopyWebpackPlugin from 'copy-webpack-plugin'; import * as path from 'path'; import { HashedModuleIdsPlugin, debug } from 'webpack'; @@ -12,8 +13,7 @@ import { AssetPatternObject } from '../../../browser/schema'; import { BundleBudgetPlugin } from '../../plugins/bundle-budget'; import { CleanCssWebpackPlugin } from '../../plugins/cleancss-webpack-plugin'; import { ScriptsWebpackPlugin } from '../../plugins/scripts-webpack-plugin'; -import { findUp } from '../../utilities/find-up'; -import { isDirectory } from '../../utilities/is-directory'; +import { findAllNodeModules, findUp } from '../../utilities/find-up'; import { requireProjectModule } from '../../utilities/require-project-module'; import { BuildOptions, WebpackConfigOptions } from '../build-options'; import { getOutputHashFormat, normalizeExtraEntryPoints } from './utils'; @@ -200,15 +200,8 @@ export function getCommonConfig(wco: WebpackConfigOptions) { // Allow loaders to be in a node_modules nested inside the devkit/build-angular package. // This is important in case loaders do not get hoisted. // If this file moves to another location, alter potentialNodeModules as well. - const loaderNodeModules = ['node_modules']; - const buildAngularNodeModules = findUp('node_modules', __dirname); - if (buildAngularNodeModules - && isDirectory(buildAngularNodeModules) - && buildAngularNodeModules !== nodeModules - && buildAngularNodeModules.startsWith(nodeModules) - ) { - loaderNodeModules.push(buildAngularNodeModules); - } + const loaderNodeModules = findAllNodeModules(__dirname, projectRoot); + loaderNodeModules.unshift('node_modules'); // Load rxjs path aliases. // https://github.com/ReactiveX/rxjs/blob/master/doc/lettable-operators.md#build-and-treeshaking @@ -272,6 +265,14 @@ export function getCommonConfig(wco: WebpackConfigOptions) { ); } + if (wco.tsConfig.options.target === 4) { + wco.logger.warn(tags.stripIndent` + WARNING: Zone.js does not support native async/await in ES2017. + These blocks are not intercepted by zone.js and will not triggering change detection. + See: https://github.com/angular/zone.js/pull/1140 for more information. + `); + } + return { mode: scriptsOptimization || stylesOptimization ? 'production' diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/server.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/server.ts index 12bf23f720df..370eed31757e 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/server.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/server.ts @@ -5,34 +5,26 @@ * 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 { isAbsolute } from 'path'; import { Configuration } from 'webpack'; import { WebpackConfigOptions } from '../build-options'; import { getSourceMapDevTool } from './utils'; - /** * Returns a partial specific to creating a bundle for node * @param wco Options which are include the build options and app config */ export function getServerConfig(wco: WebpackConfigOptions) { - const extraPlugins = []; if (wco.buildOptions.sourceMap) { const { scripts, styles, hidden } = wco.buildOptions.sourceMap; - extraPlugins.push(getSourceMapDevTool( - scripts, - styles, - hidden, - )); + extraPlugins.push(getSourceMapDevTool(scripts || false, styles || false, hidden || false)); } const config: Configuration = { resolve: { - mainFields: [ - ...(wco.supportES2015 ? ['es2015'] : []), - 'main', 'module', - ], + mainFields: [...(wco.supportES2015 ? ['es2015'] : []), 'main', 'module'], }, target: 'node', output: { @@ -45,23 +37,15 @@ export function getServerConfig(wco: WebpackConfigOptions) { if (wco.buildOptions.bundleDependencies == 'none') { config.externals = [ /^@angular/, - // tslint:disable-next-line:no-any - (_: any, request: any, callback: (error?: any, result?: any) => void) => { + (context: string, request: string, callback: (error?: null, result?: string) => void) => { // Absolute & Relative paths are not externals - if (request.match(/^\.{0,2}\//)) { + if (/^\.{0,2}\//.test(request) || isAbsolute(request)) { return callback(); } try { - // Attempt to resolve the module via Node - const e = require.resolve(request); - if (/node_modules/.test(e)) { - // It's a node_module - callback(null, request); - } else { - // It's a system thing (.ie util, fs...) - callback(); - } + require.resolve(request); + callback(null, request); } catch { // Node couldn't find it, so it must be user-aliased callback(); diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/test.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/test.ts index a61ada1d7a5e..2ea075e9c397 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/test.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/test.ts @@ -60,8 +60,8 @@ export function getTestConfig( const { styles, scripts } = wco.buildOptions.sourceMap; extraPlugins.push(getSourceMapDevTool( - styles, - scripts, + scripts || false, + styles || false, false, true, )); diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/typescript.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/typescript.ts index 1c5539c6ee77..3971f9621ccc 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/typescript.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/typescript.ts @@ -13,18 +13,13 @@ import * as path from 'path'; import { AngularCompilerPlugin, AngularCompilerPluginOptions, + NgToolsLoader, PLATFORM } from '@ngtools/webpack'; import { buildOptimizerLoader } from './common'; import { WebpackConfigOptions } from '../build-options'; -const g: any = typeof global !== 'undefined' ? global : {}; -const webpackLoader: string = g['_DevKitIsLocal'] - ? require.resolve('@ngtools/webpack') - : '@ngtools/webpack'; - - function _createAotPlugin( wco: WebpackConfigOptions, options: any, @@ -92,7 +87,7 @@ export function getNonAotConfig(wco: WebpackConfigOptions, host: virtualFs.Host< const { tsConfigPath } = wco; return { - module: { rules: [{ test: /\.tsx?$/, loader: webpackLoader }] }, + module: { rules: [{ test: /\.tsx?$/, loader: NgToolsLoader }] }, plugins: [_createAotPlugin(wco, { tsConfigPath, skipCodeGeneration: true }, host)] }; } @@ -104,7 +99,7 @@ export function getAotConfig( ) { const { tsConfigPath, buildOptions } = wco; - const loaders: any[] = [webpackLoader]; + const loaders: any[] = [NgToolsLoader]; if (buildOptions.buildOptimizer) { loaders.unshift({ loader: buildOptimizerLoader, @@ -124,7 +119,7 @@ export function getNonAotTestConfig(wco: WebpackConfigOptions, host: virtualFs.H const { tsConfigPath } = wco; return { - module: { rules: [{ test: /\.tsx?$/, loader: webpackLoader }] }, + module: { rules: [{ test: /\.tsx?$/, loader: NgToolsLoader }] }, plugins: [_createAotPlugin(wco, { tsConfigPath, skipCodeGeneration: true }, host, false)] }; } diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/index-html-webpack-plugin.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/index-html-webpack-plugin.ts index a06ed3964bd5..d9b357152eaa 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/index-html-webpack-plugin.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/index-html-webpack-plugin.ts @@ -200,7 +200,7 @@ export class IndexHtmlWebpackPlugin { treeAdapter.appendChild(baseFragment, baseElement); indexSource.insert( - headElement.__location.startTag.endOffset + 1, + headElement.__location.startTag.endOffset, parse5.serialize(baseFragment, { treeAdapter }), ); } else { diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/karma.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/karma.ts index bdc4e2a04744..a009ab2abab5 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/karma.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/karma.ts @@ -18,6 +18,7 @@ import { statsErrorsToString } from '../utilities/stats'; import { getWebpackStatsConfig } from '../models/webpack-configs/stats'; import { createConsoleLogger } from '@angular-devkit/core/node'; import { logging } from '@angular-devkit/core'; +import { WebpackTestOptions } from '../models/build-options'; /** * Enumerate needed (but not require/imported) dependencies from this file @@ -63,7 +64,7 @@ const init: any = (config: any, emitter: any, customFileHandlers: any) => { ` be used from within Angular CLI and will not work correctly outside of it.` ) } - const options = config.buildWebpack.options; + const options = config.buildWebpack.options as WebpackTestOptions; const logger: logging.Logger = config.buildWebpack.logger || createConsoleLogger(); successCb = config.buildWebpack.successCb; failureCb = config.buildWebpack.failureCb; @@ -77,7 +78,7 @@ const init: any = (config: any, emitter: any, customFileHandlers: any) => { } // Add a reporter that fixes sourcemap urls. - if (options.sourceMap) { + if (options.sourceMap.scripts) { config.reporters.unshift('@angular-devkit/build-angular--sourcemap-reporter'); // Code taken from https://github.com/tschaub/karma-source-map-support. @@ -285,7 +286,7 @@ const sourceMapReporter: any = function (this: any, baseReporterDecorator: any, muteDuplicateReporterLogging(this, config); - const urlRegexp = /\(http:\/\/localhost:\d+\/_karma_webpack_\/webpack:\//gi; + const urlRegexp = /http:\/\/localhost:\d+\/_karma_webpack_\/webpack:\//gi; this.onSpecComplete = function (_browser: any, result: any) { if (!result.success && result.log.length > 0) { diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts index ca20d6fb386c..62c9fb652e69 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts @@ -154,7 +154,7 @@ export function calculateBytes( let value = Number(matches[1]); switch (matches[2] && matches[2].toLowerCase()) { case '%': - value = baselineBytes * value / 100 * factor; + value = baselineBytes * value / 100; break; case 'kb': value *= 1024; @@ -167,5 +167,9 @@ export function calculateBytes( break; } - return value + baselineBytes; + if (baselineBytes === 0) { + return value; + } + + return baselineBytes + value * factor; } diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator_spec.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator_spec.ts index c610706ebe0c..c2d3774b7ea3 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator_spec.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator_spec.ts @@ -67,6 +67,10 @@ describe('bundle-calculator', () => { expect(calculateBytes('25.0gB')).toBe(25 * 1024 * 1024 * 1024); }); + it ('converts a decimal with mb and baseline', () => { + expect(calculateBytes('3mb', '5mb', -1)).toBe(2 * 1024 * 1024); + }); + it ('converts a percentage with baseline', () => { expect(calculateBytes('20%', '1mb')).toBe(1024 * 1024 * 1.2); expect(calculateBytes('20%', '1mb', -1)).toBe(1024 * 1024 * 0.8); diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/find-up.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/find-up.ts index a57d2b64fccc..037e99235956 100644 --- a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/find-up.ts +++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/find-up.ts @@ -5,11 +5,9 @@ * 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 */ -// tslint:disable -// TODO: cleanup this file, it's copied as is from Angular CLI. - -import * as path from 'path'; import { existsSync } from 'fs'; +import * as path from 'path'; +import { isDirectory } from './is-directory'; export function findUp(names: string | string[], from: string, stopOnNodeModules = false) { if (!Array.isArray(names)) { @@ -38,3 +36,23 @@ export function findUp(names: string | string[], from: string, stopOnNodeModules return null; } + +export function findAllNodeModules(from: string, root?: string) { + const nodeModules: string[] = []; + + let current = from; + while (current && current !== root) { + const potential = path.join(current, 'node_modules'); + if (existsSync(potential) && isDirectory(potential)) { + nodeModules.push(potential); + } + + const next = path.dirname(current); + if (next === current) { + break; + } + current = next; + } + + return nodeModules; +} diff --git a/packages/angular_devkit/build_angular/src/browser/index.ts b/packages/angular_devkit/build_angular/src/browser/index.ts index b13b08eb1823..d81bd9748147 100644 --- a/packages/angular_devkit/build_angular/src/browser/index.ts +++ b/packages/angular_devkit/build_angular/src/browser/index.ts @@ -113,7 +113,7 @@ export class BrowserBuilder implements Builder { ) { // Ensure Build Optimizer is only used with AOT. if (options.buildOptimizer && !options.aot) { - throw new Error('The `--build-optimizer` option cannot be used without `--aot`.'); + throw new Error(`The 'buildOptimizer' option cannot be used without 'aot'.`); } let wco: WebpackConfigOptions; diff --git a/packages/angular_devkit/build_angular/src/browser/schema.json b/packages/angular_devkit/build_angular/src/browser/schema.json index 9fff2eea5f6d..ee7ea8a26e00 100644 --- a/packages/angular_devkit/build_angular/src/browser/schema.json +++ b/packages/angular_devkit/build_angular/src/browser/schema.json @@ -271,7 +271,7 @@ }, "statsJson": { "type": "boolean", - "description": "Generates a 'stats.json' file which can be analyzed using tools such as: 'webpack-bundle-analyzer' or https://webpack.github.io/analyse.", + "description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.", "default": false }, "forkTypeChecker": { diff --git a/packages/angular_devkit/build_angular/src/protractor/index.ts b/packages/angular_devkit/build_angular/src/protractor/index.ts index a6effc5032f8..8e37fc796ddb 100644 --- a/packages/angular_devkit/build_angular/src/protractor/index.ts +++ b/packages/angular_devkit/build_angular/src/protractor/index.ts @@ -44,7 +44,14 @@ export class ProtractorBuilder implements Builder { const options = builderConfig.options; const root = this.context.workspace.root; const projectRoot = resolve(root, builderConfig.root); - // const projectSystemRoot = getSystemPath(projectRoot); + + // ensure that either one of this option is used + if (options.devServerTarget && options.baseUrl) { + throw new Error(tags.stripIndents` + The 'baseUrl' option cannot be used with 'devServerTarget'. + When present, 'devServerTarget' will be used to automatically setup 'baseUrl' for Protractor. + `); + } // TODO: verify using of(null) to kickstart things is a pattern. return of(null).pipe( @@ -81,7 +88,7 @@ export class ProtractorBuilder implements Builder { } // Compute baseUrl from devServerOptions. - if (options.devServerTarget && builderConfig.options.publicHost) { + if (builderConfig.options.publicHost) { let publicHost = builderConfig.options.publicHost; if (!/^\w+:\/\//.test(publicHost)) { publicHost = `${builderConfig.options.ssl @@ -90,7 +97,7 @@ export class ProtractorBuilder implements Builder { } const clientUrl = url.parse(publicHost); baseUrl = url.format(clientUrl); - } else if (options.devServerTarget) { + } else { const result: DevServerResult | undefined = buildEvent.result; baseUrl = url.format({ diff --git a/packages/angular_devkit/build_angular/src/protractor/schema.json b/packages/angular_devkit/build_angular/src/protractor/schema.json index f01e86bc81e6..6e9998bc6dea 100644 --- a/packages/angular_devkit/build_angular/src/protractor/schema.json +++ b/packages/angular_devkit/build_angular/src/protractor/schema.json @@ -45,7 +45,8 @@ }, "baseUrl": { "type": "string", - "description": "Base URL for protractor to connect to." + "description": "Base URL for protractor to connect to.", + "x-deprecated": "Use \"baseUrl\" in the Protractor config file instead." } }, "additionalProperties": false, diff --git a/packages/angular_devkit/build_angular/src/server/schema.json b/packages/angular_devkit/build_angular/src/server/schema.json index 338933858d3a..82aa03077af3 100644 --- a/packages/angular_devkit/build_angular/src/server/schema.json +++ b/packages/angular_devkit/build_angular/src/server/schema.json @@ -201,7 +201,7 @@ }, "statsJson": { "type": "boolean", - "description": "Generates a 'stats.json' file which can be analyzed using tools such as: 'webpack-bundle-analyzer' or https://webpack.github.io/analyse.", + "description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.", "default": false }, "forkTypeChecker": { diff --git a/packages/angular_devkit/build_angular/test/browser/base-href_spec_large.ts b/packages/angular_devkit/build_angular/test/browser/base-href_spec_large.ts index c70912e862dc..9d323aa59820 100644 --- a/packages/angular_devkit/build_angular/test/browser/base-href_spec_large.ts +++ b/packages/angular_devkit/build_angular/test/browser/base-href_spec_large.ts @@ -7,7 +7,7 @@ */ import { runTargetSpec } from '@angular-devkit/architect/testing'; -import { join, normalize, virtualFs } from '@angular-devkit/core'; +import { join, normalize, tags, virtualFs } from '@angular-devkit/core'; import { tap } from 'rxjs/operators'; import { browserTargetSpec, host } from '../utils'; @@ -35,4 +35,24 @@ describe('Browser Builder base href', () => { }), ).toPromise().then(done, done.fail); }); + + it('should insert base href in the the correct position', (done) => { + host.writeMultipleFiles({ + 'src/index.html': tags.oneLine` + + + `, + }); + + const overrides = { baseHref: '/myUrl' }; + + runTargetSpec(host, browserTargetSpec, overrides).pipe( + tap((buildEvent) => expect(buildEvent.success).toBe(true)), + tap(() => { + const fileName = join(outputPath, 'index.html'); + const content = virtualFs.fileBufferToString(host.scopedSync().read(fileName)); + expect(content).toContain(''); + }), + ).toPromise().then(done, done.fail); + }); }); diff --git a/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes.ts b/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes.ts index 0ade653c54f8..8637c66baa71 100644 --- a/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes.ts +++ b/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes.ts @@ -30,7 +30,7 @@ export function testPrefixClasses(content: string) { exportVarSetter, multiLineComment, /\(/, multiLineComment, /\s*function \(_super\) {/, newLine, - /\w*\.?__extends\(\w+, _super\);/, + /\S*\.?__extends\(\S+, _super\);/, ], ].map(arr => new RegExp(arr.map(x => x.source).join(''), 'm')); diff --git a/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes_spec.ts b/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes_spec.ts index d8b92f70ee77..21149b28bf00 100644 --- a/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes_spec.ts +++ b/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes_spec.ts @@ -193,6 +193,40 @@ describe('prefix-classes', () => { expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); + it('prefix TS 2.5 - 2.6 renamed downlevel class with extends', () => { + const input = tags.stripIndent` + var NgModuleFactory$1 = /** @class */ (function (_super) { + __extends(NgModuleFactory$$1, _super); + function NgModuleFactory$$1(moduleType) { + var _this = _super.call(this) || this; + _this.moduleType = moduleType; + return _this; + } + NgModuleFactory$$1.prototype.create = function (parentInjector) { + return new NgModuleRef$1(this.moduleType, parentInjector); + }; + return NgModuleFactory$$1; + }(NgModuleFactory)); + `; + const output = tags.stripIndent` + var NgModuleFactory$1 = /** @class */ /*@__PURE__*/ (function (_super) { + __extends(NgModuleFactory$$1, _super); + function NgModuleFactory$$1(moduleType) { + var _this = _super.call(this) || this; + _this.moduleType = moduleType; + return _this; + } + NgModuleFactory$$1.prototype.create = function (parentInjector) { + return new NgModuleRef$1(this.moduleType, parentInjector); + }; + return NgModuleFactory$$1; + }(NgModuleFactory)); + `; + + expect(testPrefixClasses(input)).toBeTruthy(); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); + }); + it('prefix TS 2.5 - 2.6 downlevel class with static variable', () => { const input = tags.stripIndent` var StaticTestCase = /** @class */ (function () { diff --git a/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions.ts b/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions.ts index 05ac4f0bac23..d2414bb98970 100644 --- a/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions.ts +++ b/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions.ts @@ -64,6 +64,14 @@ export function findTopLevelFunctions(parentNode: ts.Node): Set { return; } + if ((ts.isFunctionExpression(innerNode) || ts.isArrowFunction(innerNode)) + && ts.isParenthesizedExpression(node)) { + // pure functions can be wrapped in parentizes + // we should not add pure comments to this sort of syntax. + // example var foo = (() => x) + return; + } + if (noPureComment) { if (ts.isNewExpression(innerNode)) { topLevelFunctions.add(node); diff --git a/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions_spec.ts b/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions_spec.ts index 459eb69fda8c..d8b55c65a5d4 100644 --- a/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions_spec.ts +++ b/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions_spec.ts @@ -141,4 +141,30 @@ describe('prefix-functions', () => { expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); }); + + it('doesn\'t add comment to downlevel arrow function', () => { + const input = tags.stripIndent` + var populate = (function (props, rawData, entity) { + props.forEach(function (prop) { }); + }); + `; + const output = tags.stripIndent` + ${input} + `; + + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); + }); + + it('doesn\'t add comment inside arrow function', () => { + const input = tags.stripIndent` + const populate = ((props, rawData, entity) => { + props.forEach(x => x); + }); + `; + const output = tags.stripIndent` + ${input} + `; + + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); + }); }); diff --git a/packages/angular_devkit/core/package.json b/packages/angular_devkit/core/package.json index 8895ba09e704..c40953c07d11 100644 --- a/packages/angular_devkit/core/package.json +++ b/packages/angular_devkit/core/package.json @@ -8,7 +8,7 @@ "core" ], "dependencies": { - "ajv": "6.7.0", + "ajv": "6.9.1", "chokidar": "2.0.4", "fast-json-stable-stringify": "2.0.0", "rxjs": "6.3.3", diff --git a/packages/angular_devkit/schematics/collection-schema.json b/packages/angular_devkit/schematics/collection-schema.json index 83ad73562af6..497504442015 100644 --- a/packages/angular_devkit/schematics/collection-schema.json +++ b/packages/angular_devkit/schematics/collection-schema.json @@ -26,6 +26,12 @@ "additionalProperties": { "type": "object", "properties": { + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, "factory": { "type": "string", "description": "A folder or file path to the schematic factory" diff --git a/packages/angular_devkit/schematics/src/rules/move.ts b/packages/angular_devkit/schematics/src/rules/move.ts index b11e9de5f0d8..211d00f01837 100644 --- a/packages/angular_devkit/schematics/src/rules/move.ts +++ b/packages/angular_devkit/schematics/src/rules/move.ts @@ -5,7 +5,7 @@ * 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 { normalize } from '@angular-devkit/core'; +import { join, normalize } from '@angular-devkit/core'; import { Rule } from '../engine/interface'; import { noop } from './base'; @@ -30,7 +30,7 @@ export function move(from: string, to?: string): Rule { } else { // fromPath is a directory tree.getDir(fromPath).visit(path => { - tree.rename(path, toPath + '/' + path.substr(fromPath.length)); + tree.rename(path, join(toPath, path.substr(fromPath.length))); }); } diff --git a/packages/ngtools/webpack/src/angular_compiler_plugin.ts b/packages/ngtools/webpack/src/angular_compiler_plugin.ts index 67a26cf8d66d..46c864f86c2d 100644 --- a/packages/ngtools/webpack/src/angular_compiler_plugin.ts +++ b/packages/ngtools/webpack/src/angular_compiler_plugin.ts @@ -89,6 +89,7 @@ export interface AngularCompilerPluginOptions { tsConfigPath: string; basePath?: string; entryModule?: string; + entryModules?: string[]; mainPath?: string; skipCodeGeneration?: boolean; hostReplacementPaths?: { [path: string]: string } | ((path: string) => string); @@ -138,6 +139,7 @@ export class AngularCompilerPlugin { private _lazyRoutes: LazyRouteMap = {}; private _tsConfigPath: string; private _entryModule: string | null; + private _entryModules: string[] | null; private _mainPath: string | undefined; private _basePath: string; private _transformers: ts.TransformerFactory[] = []; @@ -170,15 +172,18 @@ export class AngularCompilerPlugin { get options() { return this._options; } get done() { return this._donePromise; } - get entryModule() { - if (!this._entryModule) { + get entryModules() { + if (!this._entryModules) { return null; } - const splitted = this._entryModule.split(/(#[a-zA-Z_]([\w]+))$/); - const path = splitted[0]; - const className = !!splitted[1] ? splitted[1].substring(1) : 'default'; - return { path, className }; + return this._entryModules.map((entryModule) => { + const splitted = entryModule.split(/(#[a-zA-Z_]([\w]+))$/); + const path = splitted[0]; + const className = !!splitted[1] ? splitted[1].substring(1) : 'default'; + + return { path, className }; + }); } get typeChecker(): ts.TypeChecker | null { @@ -301,13 +306,13 @@ export class AngularCompilerPlugin { this._contextElementDependencyConstructor = options.contextElementDependencyConstructor || require('webpack/lib/dependencies/ContextElementDependency'); - // Use entryModule if available in options, otherwise resolve it from mainPath after program + // Use entryModules if available in options, otherwise resolve it from mainPath after program // creation. - if (this._options.entryModule) { - this._entryModule = this._options.entryModule; + if (this._options.entryModules) { + this._entryModules = this._options.entryModules || [this._options.entryModule]; } else if (this._compilerOptions.entryModule) { - this._entryModule = path.resolve(this._basePath, - this._compilerOptions.entryModule as string); // temporary cast for type issue + this._entryModules = [path.resolve(this._basePath, + this._compilerOptions.entryModule as string)]; // temporary cast for type issue } // Set platform. @@ -411,7 +416,7 @@ export class AngularCompilerPlugin { this._entryModule = resolveEntryModuleFromMain( this._mainPath, this._compilerHost, this._getTsProgram() as ts.Program); - if (!this.entryModule && !this._compilerOptions.enableIvy) { + if (!this.entryModules && !this._compilerOptions.enableIvy) { this._warnings.push('Lazy routes discovery is not enabled. ' + 'Because there is neither an entryModule nor a ' + 'statically analyzable bootstrap code in the main file.', @@ -442,7 +447,7 @@ export class AngularCompilerPlugin { let ngProgram: Program; if (this._JitMode) { - if (!this.entryModule) { + if (!this.entryModules) { return {}; } @@ -454,7 +459,8 @@ export class AngularCompilerPlugin { }); timeEnd('AngularCompilerPlugin._listLazyRoutesFromProgram.createProgram'); - entryRoute = this.entryModule.path + '#' + this.entryModule.className; + const entryModule = this.entryModules[0]; + entryRoute = entryModule.path + '#' + entryModule.className; } else { ngProgram = this._program as Program; } @@ -851,9 +857,12 @@ export class AngularCompilerPlugin { const isMainPath = (fileName: string) => fileName === ( this._mainPath ? workaroundResolve(this._mainPath) : this._mainPath ); - const getEntryModule = () => this.entryModule - ? { path: workaroundResolve(this.entryModule.path), className: this.entryModule.className } - : this.entryModule; + // TODO: fix fn usage + const getEntryModules = () => this.entryModules + ? this.entryModules.map((entryModule) => { + return { path: workaroundResolve(entryModule.path), className: entryModule.className }; + }) + : this.entryModules; const getLazyRoutes = () => this._lazyRoutes; const getTypeChecker = () => (this._getTsProgram() as ts.Program).getTypeChecker(); @@ -873,7 +882,7 @@ export class AngularCompilerPlugin { // This transform must go before replaceBootstrap because it looks for the entry module // import, which will be replaced. if (this._normalizedLocale) { - this._transformers.push(registerLocaleData(isAppPath, getEntryModule, + this._transformers.push(registerLocaleData(isAppPath, getEntryModules, this._normalizedLocale)); } @@ -881,7 +890,7 @@ export class AngularCompilerPlugin { // Replace bootstrap in browser AOT. this._transformers.push(replaceBootstrap( isAppPath, - getEntryModule, + getEntryModules, getTypeChecker, !!this._compilerOptions.enableIvy, )); @@ -890,8 +899,8 @@ export class AngularCompilerPlugin { this._transformers.push(exportLazyModuleMap(isMainPath, getLazyRoutes)); if (!this._JitMode) { this._transformers.push( - exportNgFactory(isMainPath, getEntryModule), - replaceServerBootstrap(isMainPath, getEntryModule, getTypeChecker)); + exportNgFactory(isMainPath, getEntryModules), + replaceServerBootstrap(isMainPath, getEntryModules, getTypeChecker)); } } } diff --git a/packages/ngtools/webpack/src/index.ts b/packages/ngtools/webpack/src/index.ts index 0ac24959c8f6..68ee8c1ecb39 100644 --- a/packages/ngtools/webpack/src/index.ts +++ b/packages/ngtools/webpack/src/index.ts @@ -8,3 +8,5 @@ export * from './angular_compiler_plugin'; export { ngcLoader as default } from './loader'; + +export const NgToolsLoader = __filename; diff --git a/packages/ngtools/webpack/src/transformers/export_ngfactory.ts b/packages/ngtools/webpack/src/transformers/export_ngfactory.ts index 04a8afd1bcce..c09e086993e1 100644 --- a/packages/ngtools/webpack/src/transformers/export_ngfactory.ts +++ b/packages/ngtools/webpack/src/transformers/export_ngfactory.ts @@ -13,18 +13,29 @@ import { makeTransform } from './make_transform'; export function exportNgFactory( shouldTransform: (fileName: string) => boolean, - getEntryModule: () => { path: string, className: string } | null, + getEntryModules: () => { path: string, className: string }[] | null, ): ts.TransformerFactory { const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) { + const ops: TransformOperation[] = []; - const entryModule = getEntryModule(); + const entryModules = getEntryModules(); - if (!shouldTransform(sourceFile.fileName) || !entryModule) { + if (!shouldTransform(sourceFile.fileName) || !entryModules) { return ops; } + return entryModules.reduce((ops, entryModule) => { + return ops.concat(standardTransformHelper(sourceFile, entryModule)); + }, ops); + }; + + const standardTransformHelper = function ( + sourceFile: ts.SourceFile, + entryModule: { path: string, className: string }) { + const ops: TransformOperation[] = []; + // Find all identifiers using the entry module class name. const entryModuleIdentifiers = collectDeepNodes(sourceFile, ts.SyntaxKind.Identifier) diff --git a/packages/ngtools/webpack/src/transformers/export_ngfactory_spec.ts b/packages/ngtools/webpack/src/transformers/export_ngfactory_spec.ts index 52dc70c098dc..9217b8582891 100644 --- a/packages/ngtools/webpack/src/transformers/export_ngfactory_spec.ts +++ b/packages/ngtools/webpack/src/transformers/export_ngfactory_spec.ts @@ -22,7 +22,7 @@ describe('@ngtools/webpack transformers', () => { const transformer = exportNgFactory( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), ); const result = transformTypescript(input, [transformer]); @@ -40,7 +40,7 @@ describe('@ngtools/webpack transformers', () => { const transformer = exportNgFactory( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), ); const result = transformTypescript(input, [transformer]); @@ -54,7 +54,7 @@ describe('@ngtools/webpack transformers', () => { const transformer = exportNgFactory( () => false, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), ); const result = transformTypescript(input, [transformer]); diff --git a/packages/ngtools/webpack/src/transformers/multiple_transformers_spec.ts b/packages/ngtools/webpack/src/transformers/multiple_transformers_spec.ts index 0726efb20dad..32e26fddd00b 100644 --- a/packages/ngtools/webpack/src/transformers/multiple_transformers_spec.ts +++ b/packages/ngtools/webpack/src/transformers/multiple_transformers_spec.ts @@ -67,14 +67,14 @@ describe('@ngtools/webpack transformers', () => { const { program, compilerHost } = createTypescriptContext(input); const shouldTransform = () => true; - const getEntryModule = () => - ({ path: '/project/src/app/app.module', className: 'AppModule' }); + const getEntryModules = () => + ([{ path: '/project/src/app/app.module', className: 'AppModule' }]); const getTypeChecker = () => program.getTypeChecker(); const transformers = [ - replaceBootstrap(shouldTransform, getEntryModule, getTypeChecker), - exportNgFactory(shouldTransform, getEntryModule), + replaceBootstrap(shouldTransform, getEntryModules, getTypeChecker), + exportNgFactory(shouldTransform, getEntryModules), exportLazyModuleMap(shouldTransform, () => ({ './lazy/lazy.module.ngfactory#LazyModuleNgFactory': diff --git a/packages/ngtools/webpack/src/transformers/register_locale_data.ts b/packages/ngtools/webpack/src/transformers/register_locale_data.ts index fd2aa2056603..7a5bec27741c 100644 --- a/packages/ngtools/webpack/src/transformers/register_locale_data.ts +++ b/packages/ngtools/webpack/src/transformers/register_locale_data.ts @@ -14,19 +14,30 @@ import { makeTransform } from './make_transform'; export function registerLocaleData( shouldTransform: (fileName: string) => boolean, - getEntryModule: () => { path: string, className: string } | null, + getEntryModules: () => { path: string, className: string }[] | null, locale: string, ): ts.TransformerFactory { const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) { + const ops: TransformOperation[] = []; - const entryModule = getEntryModule(); + const entryModules = getEntryModules(); - if (!shouldTransform(sourceFile.fileName) || !entryModule || !locale) { + if (!shouldTransform(sourceFile.fileName) || !entryModules || !locale) { return ops; } + return entryModules.reduce((ops, entryModule) => { + return ops.concat(standardTransformHelper(sourceFile, entryModule)); + }, ops); + }; + + const standardTransformHelper = function ( + sourceFile: ts.SourceFile, + entryModule: { path: string, className: string }) { + const ops: TransformOperation[] = []; + // Find all identifiers using the entry module class name. const entryModuleIdentifiers = collectDeepNodes(sourceFile, ts.SyntaxKind.Identifier) diff --git a/packages/ngtools/webpack/src/transformers/register_locale_data_spec.ts b/packages/ngtools/webpack/src/transformers/register_locale_data_spec.ts index 1310bfce3e77..717dcfdb045b 100644 --- a/packages/ngtools/webpack/src/transformers/register_locale_data_spec.ts +++ b/packages/ngtools/webpack/src/transformers/register_locale_data_spec.ts @@ -45,7 +45,7 @@ describe('@ngtools/webpack transformers', () => { const transformer = registerLocaleData( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), 'fr', ); const result = transformTypescript(input, [transformer]); diff --git a/packages/ngtools/webpack/src/transformers/replace_bootstrap.ts b/packages/ngtools/webpack/src/transformers/replace_bootstrap.ts index 064e8acedd67..5ce1a708e480 100644 --- a/packages/ngtools/webpack/src/transformers/replace_bootstrap.ts +++ b/packages/ngtools/webpack/src/transformers/replace_bootstrap.ts @@ -15,15 +15,31 @@ import { makeTransform } from './make_transform'; export function replaceBootstrap( shouldTransform: (fileName: string) => boolean, - getEntryModule: () => { path: string, className: string } | null, + getEntryModules: () => { path: string, className: string }[] | null, getTypeChecker: () => ts.TypeChecker, enableIvy?: boolean, ): ts.TransformerFactory { + const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) { + const ops: TransformOperation[] = []; - const entryModule = getEntryModule(); + const entryModules = getEntryModules(); + + if (!shouldTransform(sourceFile.fileName) || !entryModules) { + return ops; + } + + return entryModules.reduce((ops, entryModule) => { + return ops.concat(standardTransformHelper(sourceFile, entryModule)); + }, ops); + }; + + const standardTransformHelper = function ( + sourceFile: ts.SourceFile, + entryModule: { path: string, className: string }) { + const ops: TransformOperation[] = []; if (!shouldTransform(sourceFile.fileName) || !entryModule) { return ops; diff --git a/packages/ngtools/webpack/src/transformers/replace_bootstrap_spec.ts b/packages/ngtools/webpack/src/transformers/replace_bootstrap_spec.ts index 31358da6e4dd..4f97e0d9fdc8 100644 --- a/packages/ngtools/webpack/src/transformers/replace_bootstrap_spec.ts +++ b/packages/ngtools/webpack/src/transformers/replace_bootstrap_spec.ts @@ -44,7 +44,7 @@ describe('@ngtools/webpack transformers', () => { const { program, compilerHost } = createTypescriptContext(input); const transformer = replaceBootstrap( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), () => program.getTypeChecker(), ); const result = transformTypescript(undefined, [transformer], program, compilerHost); @@ -127,7 +127,7 @@ describe('@ngtools/webpack transformers', () => { const { program, compilerHost } = createTypescriptContext(input); const transformer = replaceBootstrap( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), () => program.getTypeChecker(), ); const result = transformTypescript(undefined, [transformer], program, compilerHost); diff --git a/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts b/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts index 483080bf4a6b..41c7c06a6061 100644 --- a/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts +++ b/packages/ngtools/webpack/src/transformers/replace_server_bootstrap.ts @@ -14,19 +14,31 @@ import { makeTransform } from './make_transform'; export function replaceServerBootstrap( shouldTransform: (fileName: string) => boolean, - getEntryModule: () => { path: string, className: string } | null, + getEntryModules: () => { path: string, className: string }[] | null, getTypeChecker: () => ts.TypeChecker, ): ts.TransformerFactory { const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) { + const ops: TransformOperation[] = []; - const entryModule = getEntryModule(); + const entryModules = getEntryModules(); - if (!shouldTransform(sourceFile.fileName) || !entryModule) { + if (!shouldTransform(sourceFile.fileName) || !entryModules) { return ops; } + return entryModules.reduce((ops, entryModule) => { + return ops.concat(standardTransformHelper(sourceFile, entryModule)); + }, ops); + }; + + const standardTransformHelper = function ( + sourceFile: ts.SourceFile, + entryModule: { path: string, className: string }) { + + const ops: TransformOperation[] = []; + // Find all identifiers. const entryModuleIdentifiers = collectDeepNodes(sourceFile, ts.SyntaxKind.Identifier) diff --git a/packages/ngtools/webpack/src/transformers/replace_server_bootstrap_spec.ts b/packages/ngtools/webpack/src/transformers/replace_server_bootstrap_spec.ts index bd73a70eb5cf..27ddac74ec70 100644 --- a/packages/ngtools/webpack/src/transformers/replace_server_bootstrap_spec.ts +++ b/packages/ngtools/webpack/src/transformers/replace_server_bootstrap_spec.ts @@ -45,7 +45,7 @@ describe('@ngtools/webpack transformers', () => { const { program, compilerHost } = createTypescriptContext(input); const transformer = replaceServerBootstrap( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), () => program.getTypeChecker(), ); const result = transformTypescript(undefined, [transformer], program, compilerHost); @@ -92,7 +92,7 @@ describe('@ngtools/webpack transformers', () => { const { program, compilerHost } = createTypescriptContext(input); const transformer = replaceServerBootstrap( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), () => program.getTypeChecker(), ); const result = transformTypescript(undefined, [transformer], program, compilerHost); @@ -145,7 +145,7 @@ describe('@ngtools/webpack transformers', () => { const { program, compilerHost } = createTypescriptContext(input); const transformer = replaceServerBootstrap( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), () => program.getTypeChecker(), ); const result = transformTypescript(undefined, [transformer], program, compilerHost); @@ -186,7 +186,7 @@ describe('@ngtools/webpack transformers', () => { const { program, compilerHost } = createTypescriptContext(input); const transformer = replaceServerBootstrap( () => true, - () => ({ path: '/project/src/app/app.module', className: 'AppModule' }), + () => ([{ path: '/project/src/app/app.module', className: 'AppModule' }]), () => program.getTypeChecker(), ); const result = transformTypescript(undefined, [transformer], program, compilerHost); diff --git a/packages/schematics/angular/application/files/lint/__tsLintRoot__/tslint.json.template b/packages/schematics/angular/application/files/lint/__tsLintRoot__/tslint.json.template index 20341240eecf..1ebe192c95f2 100644 --- a/packages/schematics/angular/application/files/lint/__tsLintRoot__/tslint.json.template +++ b/packages/schematics/angular/application/files/lint/__tsLintRoot__/tslint.json.template @@ -1,17 +1,17 @@ { - "extends": "<%= relativePathToWorkspaceRoot %>/tslint.json", - "rules": { - "directive-selector": [ - true, - "attribute", - "<%= prefix %>", - "camelCase" - ], - "component-selector": [ - true, - "element", - "<%= prefix %>", - "kebab-case" - ] - } + "extends": "<%= relativePathToWorkspaceRoot %>/tslint.json", + "rules": { + "directive-selector": [ + true, + "attribute", + "<%= utils.camelize(prefix) %>", + "camelCase" + ], + "component-selector": [ + true, + "element", + "<%= utils.dasherize(prefix) %>", + "kebab-case" + ] + } } diff --git a/packages/schematics/angular/application/files/root/karma.conf.js.template b/packages/schematics/angular/application/files/root/karma.conf.js.template index a4d11df3425d..88505e166a08 100644 --- a/packages/schematics/angular/application/files/root/karma.conf.js.template +++ b/packages/schematics/angular/application/files/root/karma.conf.js.template @@ -26,6 +26,7 @@ module.exports = function (config) { logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], - singleRun: false + singleRun: false, + restartOnFileChange: true }); }; diff --git a/packages/schematics/angular/application/files/src/main.ts.template b/packages/schematics/angular/application/files/src/main.ts.template index c7b673cf44b3..3d492bb9626b 100644 --- a/packages/schematics/angular/application/files/src/main.ts.template +++ b/packages/schematics/angular/application/files/src/main.ts.template @@ -1,4 +1,4 @@ -import { enableProdMode } from '@angular/core'; +import { enableProdMode<% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%> } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; @@ -7,6 +7,12 @@ import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } - +<% if(!!viewEncapsulation) { %> +platformBrowserDynamic().bootstrapModule(AppModule, { + defaultEncapsulation: ViewEncapsulation.<%= viewEncapsulation %> +}) + .catch(err => console.error(err)); +<% } else { %> platformBrowserDynamic().bootstrapModule(AppModule) .catch(err => console.error(err)); +<% } %> \ No newline at end of file diff --git a/packages/schematics/angular/application/files/src/styles.__styleExt__.template b/packages/schematics/angular/application/files/src/styles.__style__.template similarity index 100% rename from packages/schematics/angular/application/files/src/styles.__styleExt__.template rename to packages/schematics/angular/application/files/src/styles.__style__.template diff --git a/packages/schematics/angular/application/index.ts b/packages/schematics/angular/application/index.ts index 946a43970c7e..3f326b65c4c8 100644 --- a/packages/schematics/angular/application/index.ts +++ b/packages/schematics/angular/application/index.ts @@ -31,7 +31,6 @@ import { url, } from '@angular-devkit/schematics'; import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; -import { styleToFileExtention } from '../component/index'; import { Schema as ComponentOptions } from '../component/schema'; import { Schema as E2eOptions } from '../e2e/schema'; import { @@ -151,7 +150,7 @@ function addAppToWorkspaceFile(options: ApplicationOptions, workspace: Workspace // } let projectRoot = options.projectRoot !== undefined ? options.projectRoot - : `${workspace.newProjectRoot}/${options.name}`; + : `${workspace.newProjectRoot || ''}/${options.name}`; if (projectRoot !== '' && !projectRoot.endsWith('/')) { projectRoot += '/'; } @@ -164,16 +163,18 @@ function addAppToWorkspaceFile(options: ApplicationOptions, workspace: Workspace if (options.inlineTemplate === true || options.inlineStyle === true || options.style !== Style.Css) { - schematics['@schematics/angular:component'] = {}; + const componentSchematicsOptions: JsonObject = {}; if (options.inlineTemplate === true) { - (schematics['@schematics/angular:component'] as JsonObject).inlineTemplate = true; + componentSchematicsOptions.inlineTemplate = true; } if (options.inlineStyle === true) { - (schematics['@schematics/angular:component'] as JsonObject).inlineStyle = true; + componentSchematicsOptions.inlineStyle = true; } if (options.style && options.style !== Style.Css) { - (schematics['@schematics/angular:component'] as JsonObject).style = options.style; + componentSchematicsOptions.style = options.style; } + + schematics['@schematics/angular:component'] = componentSchematicsOptions; } if (options.skipTests === true) { @@ -322,7 +323,7 @@ export default function (options: ApplicationOptions): Rule { }; const workspace = getWorkspace(host); - let newProjectRoot = workspace.newProjectRoot; + let newProjectRoot = workspace.newProjectRoot || ''; let appDir = `${newProjectRoot}/${options.name}`; let sourceRoot = `${appDir}/src`; let sourceDir = `${sourceRoot}/app`; @@ -347,8 +348,6 @@ export default function (options: ApplicationOptions): Rule { projectRoot: newProjectRoot ? `${newProjectRoot}/${options.name}-e2e` : 'e2e', }; - const styleExt = styleToFileExtention(options.style); - return chain([ addAppToWorkspaceFile(options, workspace), mergeWith( @@ -359,7 +358,6 @@ export default function (options: ApplicationOptions): Rule { ...options, 'dot': '.', relativePathToWorkspaceRoot, - styleExt, }), move(sourceRoot), ])), @@ -421,7 +419,6 @@ export default function (options: ApplicationOptions): Rule { ...options as any, // tslint:disable-line:no-any selector: appRootSelector, ...componentOptions, - styleExt, }), move(sourceDir), ]), MergeStrategy.Overwrite), diff --git a/packages/schematics/angular/application/index_spec.ts b/packages/schematics/angular/application/index_spec.ts index 290f7978e26e..a99934e1cb6e 100644 --- a/packages/schematics/angular/application/index_spec.ts +++ b/packages/schematics/angular/application/index_spec.ts @@ -10,7 +10,7 @@ import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/te import { latestVersions } from '../utility/latest-versions'; import { getFileContent } from '../utility/test'; import { Schema as WorkspaceOptions } from '../workspace/schema'; -import { Schema as ApplicationOptions } from './schema'; +import { Schema as ApplicationOptions, Style, ViewEncapsulation } from './schema'; // tslint:disable:max-line-length describe('Application Schematic', () => { @@ -116,6 +116,17 @@ describe('Application Schematic', () => { expect(content).toMatch(/import { AppComponent } from \'\.\/app\.component\';/); }); + it(`should set 'defaultEncapsulation' in main.ts when 'ViewEncapsulation' is provided`, () => { + const tree = schematicRunner.runSchematic('application', { + ...defaultOptions, + viewEncapsulation: ViewEncapsulation.ShadowDom, + }, workspaceTree); + const path = '/projects/foo/src/main.ts'; + const content = tree.readContent(path); + expect(content).toContain('defaultEncapsulation: ViewEncapsulation.ShadowDom'); + expect(content).toContain(`import { enableProdMode, ViewEncapsulation } from '@angular/core'`); + }); + it('should set the right paths in the tsconfig files', () => { const tree = schematicRunner.runSchematic('application', defaultOptions, workspaceTree); let path = '/projects/foo/tsconfig.app.json'; @@ -137,6 +148,15 @@ describe('Application Schematic', () => { expect(content.rules['component-selector'][2]).toMatch('app'); }); + it('should set the right prefix in the tslint file when provided is kebabed', () => { + const options: ApplicationOptions = { ...defaultOptions, prefix: 'foo-bar' }; + const tree = schematicRunner.runSchematic('application', options, workspaceTree); + const path = '/projects/foo/tslint.json'; + const content = JSON.parse(tree.readContent(path)); + expect(content.rules['directive-selector'][2]).toMatch('fooBar'); + expect(content.rules['component-selector'][2]).toMatch('foo-bar'); + }); + it('should set the right coverage folder in the karma.json file', () => { const tree = schematicRunner.runSchematic('application', defaultOptions, workspaceTree); const karmaConf = getFileContent(tree, '/projects/foo/karma.conf.js'); @@ -275,9 +295,24 @@ describe('Application Schematic', () => { ]); }); + it('should set values in angular.json correctly when using a style preprocessor', () => { + const options = { ...defaultOptions, projectRoot: '', style: Style.Sass }; + const tree = schematicRunner.runSchematic('application', options, workspaceTree); + const config = JSON.parse(tree.readContent('/angular.json')); + const prj = config.projects.foo; + const buildOpt = prj.architect.build.options; + expect(buildOpt.styles).toEqual([ + 'src/styles.sass', + ]); + const testOpt = prj.architect.test.options; + expect(testOpt.styles).toEqual([ + 'src/styles.sass', + ]); + expect(tree.exists('src/styles.sass')).toBe(true); + }); + it('should set the relative tsconfig paths', () => { const options = { ...defaultOptions, projectRoot: '' }; - const tree = schematicRunner.runSchematic('application', options, workspaceTree); const appTsConfig = JSON.parse(tree.readContent('/src/tsconfig.app.json')); expect(appTsConfig.extends).toEqual('../tsconfig.json'); diff --git a/packages/schematics/angular/application/other-files/app.component.ts.template b/packages/schematics/angular/application/other-files/app.component.ts.template index 91aad371b12d..36845433365f 100644 --- a/packages/schematics/angular/application/other-files/app.component.ts.template +++ b/packages/schematics/angular/application/other-files/app.component.ts.template @@ -28,7 +28,7 @@ import { Component } from '@angular/core'; `,<% } else { %> templateUrl: './app.component.html',<% } if(inlineStyle) { %> styles: []<% } else { %> - styleUrls: ['./app.component.<%= styleExt %>']<% } %> + styleUrls: ['./app.component.<%= style %>']<% } %> }) export class AppComponent { title = '<%= name %>'; diff --git a/packages/schematics/angular/class/index.ts b/packages/schematics/angular/class/index.ts index b008d1d443c8..ff1a68906ef8 100644 --- a/packages/schematics/angular/class/index.ts +++ b/packages/schematics/angular/class/index.ts @@ -13,7 +13,6 @@ import { Tree, apply, applyTemplates, - branchAndMerge, chain, filter, mergeWith, @@ -57,7 +56,7 @@ export default function (options: ClassOptions): Rule { ]); return chain([ - branchAndMerge(mergeWith(templateSource)), + mergeWith(templateSource), options.lintFix ? applyLintFix(options.path) : noop(), ]); }; diff --git a/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.component.__styleExt__.template b/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.component.__style__.template similarity index 100% rename from packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.component.__styleExt__.template rename to packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.component.__style__.template diff --git a/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.component.ts.template b/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.component.ts.template index b78148e8ed09..05940077a7fb 100644 --- a/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.component.ts.template +++ b/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.component.ts.template @@ -9,7 +9,7 @@ import { Component, OnInit<% if(!!viewEncapsulation) { %>, ViewEncapsulation<% } `,<% } else { %> templateUrl: './<%= dasherize(name) %>.component.html',<% } if(inlineStyle) { %> styles: []<% } else { %> - styleUrls: ['./<%= dasherize(name) %>.component.<%= styleExt %>']<% } %><% if(!!viewEncapsulation) { %>, + styleUrls: ['./<%= dasherize(name) %>.component.<%= style %>']<% } %><% if(!!viewEncapsulation) { %>, encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>, changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %> }) diff --git a/packages/schematics/angular/component/index.ts b/packages/schematics/angular/component/index.ts index fac77746a981..48288a20f719 100644 --- a/packages/schematics/angular/component/index.ts +++ b/packages/schematics/angular/component/index.ts @@ -12,7 +12,6 @@ import { Tree, apply, applyTemplates, - branchAndMerge, chain, filter, mergeWith, @@ -155,32 +154,20 @@ export default function (options: ComponentOptions): Rule { const templateSource = apply(url('./files'), [ options.skipTests ? filter(path => !path.endsWith('.spec.ts.template')) : noop(), - options.inlineStyle ? filter(path => !path.endsWith('.__styleExt__.template')) : noop(), + options.inlineStyle ? filter(path => !path.endsWith('.__style__.template')) : noop(), options.inlineTemplate ? filter(path => !path.endsWith('.html.template')) : noop(), applyTemplates({ ...strings, 'if-flat': (s: string) => options.flat ? '' : s, ...options, - styleExt: styleToFileExtention(options.style), }), move(parsedPath.path), ]); return chain([ - branchAndMerge(chain([ - addDeclarationToNgModule(options), - mergeWith(templateSource), - ])), + addDeclarationToNgModule(options), + mergeWith(templateSource), options.lintFix ? applyLintFix(options.path) : noop(), ]); }; } - -export function styleToFileExtention(style: Style | undefined): string { - switch (style) { - case Style.Sass: - return 'scss'; - default: - return style || 'css'; - } -} diff --git a/packages/schematics/angular/component/index_spec.ts b/packages/schematics/angular/component/index_spec.ts index 516f126c5005..e79895f49ccc 100644 --- a/packages/schematics/angular/component/index_spec.ts +++ b/packages/schematics/angular/component/index_spec.ts @@ -250,20 +250,11 @@ describe('Component Schematic', () => { }); it('should respect the style option', () => { - const options = { ...defaultOptions, style: Style.Scss }; - const tree = schematicRunner.runSchematic('component', options, appTree); - const content = tree.readContent('/projects/bar/src/app/foo/foo.component.ts'); - expect(content).toMatch(/styleUrls: \['.\/foo.component.scss/); - expect(tree.files).toContain('/projects/bar/src/app/foo/foo.component.scss'); - expect(tree.files).not.toContain('/projects/bar/src/app/foo/foo.component.css'); - }); - - it('should respect the style preprocessor option', () => { const options = { ...defaultOptions, style: Style.Sass }; const tree = schematicRunner.runSchematic('component', options, appTree); const content = tree.readContent('/projects/bar/src/app/foo/foo.component.ts'); - expect(content).toMatch(/styleUrls: \['.\/foo.component.scss/); - expect(tree.files).toContain('/projects/bar/src/app/foo/foo.component.scss'); + expect(content).toMatch(/styleUrls: \['.\/foo.component.sass/); + expect(tree.files).toContain('/projects/bar/src/app/foo/foo.component.sass'); expect(tree.files).not.toContain('/projects/bar/src/app/foo/foo.component.css'); }); diff --git a/packages/schematics/angular/directive/index.ts b/packages/schematics/angular/directive/index.ts index 0b1fa6d076a0..1630dde98c6f 100644 --- a/packages/schematics/angular/directive/index.ts +++ b/packages/schematics/angular/directive/index.ts @@ -12,7 +12,6 @@ import { Tree, apply, applyTemplates, - branchAndMerge, chain, filter, mergeWith, @@ -135,10 +134,8 @@ export default function (options: DirectiveOptions): Rule { ]); return chain([ - branchAndMerge(chain([ - addDeclarationToNgModule(options), - mergeWith(templateSource), - ])), + addDeclarationToNgModule(options), + mergeWith(templateSource), options.lintFix ? applyLintFix(options.path) : noop(), ]); }; diff --git a/packages/schematics/angular/e2e/files/src/app.e2e-spec.ts.template b/packages/schematics/angular/e2e/files/src/app.e2e-spec.ts.template index fb169a4ded40..1ff95e3adebe 100644 --- a/packages/schematics/angular/e2e/files/src/app.e2e-spec.ts.template +++ b/packages/schematics/angular/e2e/files/src/app.e2e-spec.ts.template @@ -18,6 +18,6 @@ describe('workspace-project App', () => { const logs = await browser.manage().logs().get(logging.Type.BROWSER); expect(logs).not.toContain(jasmine.objectContaining({ level: logging.Level.SEVERE, - })); + } as logging.Entry)); }); }); diff --git a/packages/schematics/angular/e2e/files/src/app.po.ts.template b/packages/schematics/angular/e2e/files/src/app.po.ts.template index cb95601b81e3..4e1cf115d60f 100644 --- a/packages/schematics/angular/e2e/files/src/app.po.ts.template +++ b/packages/schematics/angular/e2e/files/src/app.po.ts.template @@ -2,7 +2,7 @@ import { browser, by, element } from 'protractor'; export class AppPage { navigateTo() { - return browser.get('/') as Promise; + return browser.get(browser.baseUrl) as Promise; } getTitleText() { diff --git a/packages/schematics/angular/enum/index.ts b/packages/schematics/angular/enum/index.ts index 214c969168f4..5c2d489f784f 100644 --- a/packages/schematics/angular/enum/index.ts +++ b/packages/schematics/angular/enum/index.ts @@ -13,7 +13,6 @@ import { Tree, apply, applyTemplates, - branchAndMerge, chain, mergeWith, move, @@ -50,9 +49,7 @@ export default function (options: EnumOptions): Rule { ]); return chain([ - branchAndMerge(chain([ - mergeWith(templateSource), - ])), + mergeWith(templateSource), options.lintFix ? applyLintFix(options.path) : noop(), ]); }; diff --git a/packages/schematics/angular/guard/index.ts b/packages/schematics/angular/guard/index.ts index 3c166049e1fa..95666fea1583 100644 --- a/packages/schematics/angular/guard/index.ts +++ b/packages/schematics/angular/guard/index.ts @@ -12,7 +12,6 @@ import { Tree, apply, applyTemplates, - branchAndMerge, chain, filter, mergeWith, @@ -66,13 +65,11 @@ export default function (options: GuardOptions): Rule { ...strings, ...options, }), - move(parsedPath.path), + move(parsedPath.path + (options.flat ? '' : '/' + strings.dasherize(options.name))), ]); return chain([ - branchAndMerge(chain([ - mergeWith(templateSource), - ])), + mergeWith(templateSource), options.lintFix ? applyLintFix(options.path) : noop(), ]); }; diff --git a/packages/schematics/angular/guard/index_spec.ts b/packages/schematics/angular/guard/index_spec.ts index 0c8c413ab970..a98bf7f148c0 100644 --- a/packages/schematics/angular/guard/index_spec.ts +++ b/packages/schematics/angular/guard/index_spec.ts @@ -56,6 +56,15 @@ describe('Guard Schematic', () => { expect(files).toContain('/projects/bar/src/app/foo.guard.ts'); }); + it('should respect the flat flag', () => { + const options = { ...defaultOptions, flat: false }; + + const tree = schematicRunner.runSchematic('guard', options, appTree); + const files = tree.files; + expect(files).toContain('/projects/bar/src/app/foo/foo.guard.spec.ts'); + expect(files).toContain('/projects/bar/src/app/foo/foo.guard.ts'); + }); + it('should respect the sourceRoot value', () => { const config = JSON.parse(appTree.readContent('/angular.json')); config.projects.bar.sourceRoot = 'projects/bar/custom'; diff --git a/packages/schematics/angular/interface/index.ts b/packages/schematics/angular/interface/index.ts index d1d7e77aad62..a008e6a3ec37 100644 --- a/packages/schematics/angular/interface/index.ts +++ b/packages/schematics/angular/interface/index.ts @@ -12,7 +12,6 @@ import { Tree, apply, applyTemplates, - branchAndMerge, chain, mergeWith, move, @@ -52,9 +51,7 @@ export default function (options: InterfaceOptions): Rule { ]); return chain([ - branchAndMerge(chain([ - mergeWith(templateSource), - ])), + mergeWith(templateSource), options.lintFix ? applyLintFix(options.path) : noop(), ]); }; diff --git a/packages/schematics/angular/library/files/__projectRoot__/README.md.template b/packages/schematics/angular/library/files/README.md.template similarity index 100% rename from packages/schematics/angular/library/files/__projectRoot__/README.md.template rename to packages/schematics/angular/library/files/README.md.template diff --git a/packages/schematics/angular/library/files/__projectRoot__/karma.conf.js.template b/packages/schematics/angular/library/files/karma.conf.js.template similarity index 95% rename from packages/schematics/angular/library/files/__projectRoot__/karma.conf.js.template rename to packages/schematics/angular/library/files/karma.conf.js.template index 04af0229f689..87b2dd32d60d 100644 --- a/packages/schematics/angular/library/files/__projectRoot__/karma.conf.js.template +++ b/packages/schematics/angular/library/files/karma.conf.js.template @@ -26,6 +26,7 @@ module.exports = function (config) { logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], - singleRun: false + singleRun: false, + restartOnFileChange: true }); }; diff --git a/packages/schematics/angular/library/files/__projectRoot__/ng-package.json.template b/packages/schematics/angular/library/files/ng-package.json.template similarity index 100% rename from packages/schematics/angular/library/files/__projectRoot__/ng-package.json.template rename to packages/schematics/angular/library/files/ng-package.json.template diff --git a/packages/schematics/angular/library/files/__projectRoot__/package.json.template b/packages/schematics/angular/library/files/package.json.template similarity index 100% rename from packages/schematics/angular/library/files/__projectRoot__/package.json.template rename to packages/schematics/angular/library/files/package.json.template diff --git a/packages/schematics/angular/library/files/__projectRoot__/src/__entryFile__.ts.template b/packages/schematics/angular/library/files/src/__entryFile__.ts.template similarity index 100% rename from packages/schematics/angular/library/files/__projectRoot__/src/__entryFile__.ts.template rename to packages/schematics/angular/library/files/src/__entryFile__.ts.template diff --git a/packages/schematics/angular/library/files/__projectRoot__/src/test.ts.template b/packages/schematics/angular/library/files/src/test.ts.template similarity index 100% rename from packages/schematics/angular/library/files/__projectRoot__/src/test.ts.template rename to packages/schematics/angular/library/files/src/test.ts.template diff --git a/packages/schematics/angular/library/files/__projectRoot__/tsconfig.lib.json.template b/packages/schematics/angular/library/files/tsconfig.lib.json.template similarity index 100% rename from packages/schematics/angular/library/files/__projectRoot__/tsconfig.lib.json.template rename to packages/schematics/angular/library/files/tsconfig.lib.json.template diff --git a/packages/schematics/angular/library/files/__projectRoot__/tsconfig.spec.json.template b/packages/schematics/angular/library/files/tsconfig.spec.json.template similarity index 100% rename from packages/schematics/angular/library/files/__projectRoot__/tsconfig.spec.json.template rename to packages/schematics/angular/library/files/tsconfig.spec.json.template diff --git a/packages/schematics/angular/library/files/__projectRoot__/tslint.json.template b/packages/schematics/angular/library/files/tslint.json.template similarity index 78% rename from packages/schematics/angular/library/files/__projectRoot__/tslint.json.template rename to packages/schematics/angular/library/files/tslint.json.template index ad2f4a64bf8b..33ecd2e67fbf 100644 --- a/packages/schematics/angular/library/files/__projectRoot__/tslint.json.template +++ b/packages/schematics/angular/library/files/tslint.json.template @@ -4,13 +4,13 @@ "directive-selector": [ true, "attribute", - "<%= prefix %>", + "<%= camelize(prefix) %>", "camelCase" ], "component-selector": [ true, "element", - "<%= prefix %>", + "<%= dasherize(prefix) %>", "kebab-case" ] } diff --git a/packages/schematics/angular/library/index.ts b/packages/schematics/angular/library/index.ts index 57b9e55fb551..02fc671be66f 100644 --- a/packages/schematics/angular/library/index.ts +++ b/packages/schematics/angular/library/index.ts @@ -13,9 +13,9 @@ import { Tree, apply, applyTemplates, - branchAndMerge, chain, mergeWith, + move, noop, schematic, url, @@ -193,7 +193,7 @@ export default function (options: LibraryOptions): Rule { } const workspace = getWorkspace(host); - const newProjectRoot = workspace.newProjectRoot; + const newProjectRoot = workspace.newProjectRoot || ''; const scopeFolder = scopeName ? strings.dasherize(scopeName) + '/' : ''; const folderName = `${scopeFolder}${strings.dasherize(options.name)}`; @@ -215,13 +215,11 @@ export default function (options: LibraryOptions): Rule { angularLatestVersion: latestVersions.Angular.replace('~', '').replace('^', ''), folderName, }), - // TODO: Moving inside `branchAndMerge` should work but is bugged right now. - // The __projectRoot__ is being used meanwhile. - // move(projectRoot), + move(projectRoot), ]); return chain([ - branchAndMerge(mergeWith(templateSource)), + mergeWith(templateSource), addAppToWorkspaceFile(options, workspace, projectRoot, projectName), options.skipPackageJson ? noop() : addDependenciesToPackageJson(), options.skipTsConfig ? noop() : updateTsConfig(packageName, distRoot), diff --git a/packages/schematics/angular/library/index_spec.ts b/packages/schematics/angular/library/index_spec.ts index a0d6c7acec5d..6220a07fa23c 100644 --- a/packages/schematics/angular/library/index_spec.ts +++ b/packages/schematics/angular/library/index_spec.ts @@ -25,7 +25,7 @@ describe('Library Schematic', () => { ); const defaultOptions: GenerateLibrarySchema = { name: 'foo', - entryFile: 'my_index', + entryFile: 'my-index', skipPackageJson: false, skipTsConfig: false, skipInstall: false, @@ -51,7 +51,7 @@ describe('Library Schematic', () => { '/projects/foo/README.md', '/projects/foo/tslint.json', '/projects/foo/src/test.ts', - '/projects/foo/src/my_index.ts', + '/projects/foo/src/my-index.ts', '/projects/foo/src/lib/foo.module.ts', '/projects/foo/src/lib/foo.component.spec.ts', '/projects/foo/src/lib/foo.component.ts', @@ -89,7 +89,7 @@ describe('Library Schematic', () => { const tree = schematicRunner.runSchematic('library', defaultOptions, workspaceTree); const fileContent = getJsonFileContent(tree, '/projects/foo/ng-package.json'); expect(fileContent.lib).toBeDefined(); - expect(fileContent.lib.entryFile).toEqual('src/my_index.ts'); + expect(fileContent.lib.entryFile).toEqual('src/my-index.ts'); expect(fileContent.dest).toEqual('../../dist/foo'); }); @@ -97,7 +97,7 @@ describe('Library Schematic', () => { const tree = schematicRunner.runSchematic('library', { name: 'foobar', }, workspaceTree); - expect(tree.files).toContain('/projects/foobar/src/public_api.ts'); + expect(tree.files).toContain('/projects/foobar/src/public-api.ts'); }); it(`should add library to workspace`, () => { @@ -123,6 +123,15 @@ describe('Library Schematic', () => { expect(workspace.projects.foo.prefix).toEqual('pre'); }); + it('should set the right prefix in the tslint file when provided is kebabed', () => { + const options: GenerateLibrarySchema = { ...defaultOptions, prefix: 'foo-bar' }; + const tree = schematicRunner.runSchematic('library', options, workspaceTree); + const path = '/projects/foo/tslint.json'; + const content = JSON.parse(tree.readContent(path)); + expect(content.rules['directive-selector'][2]).toMatch('fooBar'); + expect(content.rules['component-selector'][2]).toMatch('foo-bar'); + }); + it('should handle a pascalCasedName', () => { const options = {...defaultOptions, name: 'pascalCasedName'}; const tree = schematicRunner.runSchematic('library', options, workspaceTree); diff --git a/packages/schematics/angular/library/schema.json b/packages/schematics/angular/library/schema.json index 2739b644b174..d199c76eb893 100644 --- a/packages/schematics/angular/library/schema.json +++ b/packages/schematics/angular/library/schema.json @@ -19,7 +19,7 @@ "type": "string", "format": "path", "description": "The path at which to create the library's public API file, relative to the workspace root.", - "default": "public_api" + "default": "public-api" }, "prefix": { "type": "string", diff --git a/packages/schematics/angular/migrations/update-7/polyfill-metadata.ts b/packages/schematics/angular/migrations/update-7/polyfill-metadata.ts index cbc2fb4ac8b9..04bfa1bc69e4 100644 --- a/packages/schematics/angular/migrations/update-7/polyfill-metadata.ts +++ b/packages/schematics/angular/migrations/update-7/polyfill-metadata.ts @@ -29,18 +29,15 @@ function _removeReflectFromPolyfills(tree: Tree, path: string) { const recorder = tree.beginUpdate(path); const sourceFile = ts.createSourceFile(path, source.toString(), ts.ScriptTarget.Latest); - const imports = ( - sourceFile.statements - .filter(s => s.kind === ts.SyntaxKind.ImportDeclaration) as ts.ImportDeclaration[] - ); + const imports = sourceFile.statements + .filter(s => s.kind === ts.SyntaxKind.ImportDeclaration) as ts.ImportDeclaration[]; for (const i of imports) { - const module = i.moduleSpecifier.kind == ts.SyntaxKind.StringLiteral - && (i.moduleSpecifier as ts.StringLiteral).text; + const module = ts.isStringLiteral(i.moduleSpecifier) && i.moduleSpecifier.text; switch (module) { case 'core-js/es7/reflect': - recorder.remove(i.getStart(sourceFile), i.getWidth(sourceFile)); + recorder.remove(i.getFullStart(), i.getFullWidth()); break; } } diff --git a/packages/schematics/angular/migrations/update-7/polyfill-metadata_spec.ts b/packages/schematics/angular/migrations/update-7/polyfill-metadata_spec.ts index a696e8ac9bc4..bf3c8ae80e55 100644 --- a/packages/schematics/angular/migrations/update-7/polyfill-metadata_spec.ts +++ b/packages/schematics/angular/migrations/update-7/polyfill-metadata_spec.ts @@ -42,6 +42,34 @@ import 'web-animations-js'; // Run \`npm install --save web-animations-js\`. import 'zone.js/dist/zone'; // Included with Angular CLI. `; +const newPolyfills = ` +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +// import 'core-js/es6/symbol'; +// import 'core-js/es6/object'; +import 'core-js/es6/function'; +import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; + +/** IE10 and IE11 requires the following for the Reflect API. */ +import 'core-js/es6/reflect'; + +import 'web-animations-js'; // Run \`npm install --save web-animations-js\`. + + (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. +`; + describe('polyfillMetadataRule', () => { const schematicRunner = new SchematicTestRunner( @@ -84,7 +112,7 @@ describe('polyfillMetadataRule', () => { const tree2 = await schematicRunner.runSchematicAsync('migration-03', {}, tree.branch()) .toPromise(); - expect(tree2.readContent(polyfillPath)).not.toMatch(/import .*es7.*reflect.*;/); + expect(tree2.readContent(polyfillPath)).toBe(newPolyfills); }); it('should work as expected for a project with a root', async () => { @@ -96,6 +124,6 @@ describe('polyfillMetadataRule', () => { const tree2 = await schematicRunner.runSchematicAsync('migration-03', {}, tree.branch()) .toPromise(); - expect(tree2.readContent(polyfillPath)).not.toMatch(/import .*es7.*reflect.*;/); + expect(tree2.readContent(polyfillPath)).toBe(newPolyfills); }); }); diff --git a/packages/schematics/angular/module/index.ts b/packages/schematics/angular/module/index.ts index 31ac6fc6bd98..041eb005bcf3 100644 --- a/packages/schematics/angular/module/index.ts +++ b/packages/schematics/angular/module/index.ts @@ -12,7 +12,6 @@ import { Tree, apply, applyTemplates, - branchAndMerge, chain, filter, mergeWith, @@ -99,10 +98,8 @@ export default function (options: ModuleOptions): Rule { ]); return chain([ - branchAndMerge(chain([ - addDeclarationToNgModule(options), - mergeWith(templateSource), - ])), + addDeclarationToNgModule(options), + mergeWith(templateSource), options.lintFix ? applyLintFix(options.path) : noop(), ]); }; diff --git a/packages/schematics/angular/ng-new/schema.json b/packages/schematics/angular/ng-new/schema.json index e8dda08442cc..20565beb38d2 100644 --- a/packages/schematics/angular/ng-new/schema.json +++ b/packages/schematics/angular/ng-new/schema.json @@ -125,10 +125,11 @@ "message": "Which stylesheet format would you like to use?", "type": "list", "items": [ - { "value": "css", "label": "CSS" }, - { "value": "sass", "label": "Sass [ http://sass-lang.com ]" }, - { "value": "less", "label": "Less [ http://lesscss.org ]" }, - { "value": "styl", "label": "Stylus [ http://stylus-lang.com ]" } + { "value": "css", "label": "CSS" }, + { "value": "scss", "label": "SCSS [ http://sass-lang.com/documentation/file.SASS_REFERENCE.html#syntax ]" }, + { "value": "sass", "label": "Sass [ http://sass-lang.com/documentation/file.INDENTED_SYNTAX.html ]" }, + { "value": "less", "label": "Less [ http://lesscss.org ]" }, + { "value": "styl", "label": "Stylus [ http://stylus-lang.com ] " } ] } }, diff --git a/packages/schematics/angular/package.json b/packages/schematics/angular/package.json index 00ab86792635..4fee8391d0b7 100644 --- a/packages/schematics/angular/package.json +++ b/packages/schematics/angular/package.json @@ -11,6 +11,6 @@ "dependencies": { "@angular-devkit/core": "0.0.0", "@angular-devkit/schematics": "0.0.0", - "typescript": "3.2.2" + "typescript": "3.2.4" } } diff --git a/packages/schematics/angular/pipe/index.ts b/packages/schematics/angular/pipe/index.ts index 919704c07db0..5954738dcfd3 100644 --- a/packages/schematics/angular/pipe/index.ts +++ b/packages/schematics/angular/pipe/index.ts @@ -12,7 +12,6 @@ import { Tree, apply, applyTemplates, - branchAndMerge, chain, filter, mergeWith, @@ -115,12 +114,10 @@ export default function (options: PipeOptions): Rule { move(parsedPath.path), ]); - return branchAndMerge( - chain([ - addDeclarationToNgModule(options), - mergeWith(templateSource), - options.lintFix ? applyLintFix(options.path) : noop(), - ]), - ); + return chain([ + addDeclarationToNgModule(options), + mergeWith(templateSource), + options.lintFix ? applyLintFix(options.path) : noop(), + ]); }; } diff --git a/packages/schematics/angular/utility/find-module.ts b/packages/schematics/angular/utility/find-module.ts index 4d439a99aebf..7ff53bf7ca14 100644 --- a/packages/schematics/angular/utility/find-module.ts +++ b/packages/schematics/angular/utility/find-module.ts @@ -12,7 +12,6 @@ import { join, normalize, relative, - strings, } from '@angular-devkit/core'; import { DirEntry, Tree } from '@angular-devkit/schematics'; @@ -25,7 +24,6 @@ export interface ModuleOptions { skipImport?: boolean; moduleExt?: string; routingModuleExt?: string; - nameFormatter?: (str: string) => string; } const MODULE_EXT = '.module.ts'; @@ -43,8 +41,7 @@ export function findModuleFromOptions(host: Tree, options: ModuleOptions): Path const routingModuleExt = options.routingModuleExt || ROUTING_MODULE_EXT; if (!options.module) { - options.nameFormatter = options.nameFormatter || strings.dasherize; - const pathToCheck = (options.path || '') + '/' + options.nameFormatter(options.name); + const pathToCheck = (options.path || '') + '/' + options.name; return normalize(findModule(host, pathToCheck, moduleExt, routingModuleExt)); } else { diff --git a/packages/schematics/angular/utility/find-module_spec.ts b/packages/schematics/angular/utility/find-module_spec.ts index 643eda8324de..b47faf1db2e6 100644 --- a/packages/schematics/angular/utility/find-module_spec.ts +++ b/packages/schematics/angular/utility/find-module_spec.ts @@ -5,7 +5,7 @@ * 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 { Path, strings } from '@angular-devkit/core'; +import { Path } from '@angular-devkit/core'; import { EmptyTree, Tree } from '@angular-devkit/schematics'; import { ModuleOptions, findModule, findModuleFromOptions } from './find-module'; @@ -111,12 +111,20 @@ describe('find-module', () => { expect(modPath).toEqual('/projects/my-proj/src/app.module.ts' as Path); }); - it('should find a module if nameFormatter is provided', () => { - tree.create('/projects/my-proj/src/app_test.module.ts', ''); + it('should find a module when name has underscore', () => { + tree.create('/projects/my-proj/src/feature_module/app_test.module.ts', ''); options.path = '/projects/my-proj/src'; - options.nameFormatter = strings.underscore; + options.name = 'feature_module/new_component'; const modPath = findModuleFromOptions(tree, options); - expect(modPath).toEqual('/projects/my-proj/src/app_test.module.ts' as Path); + expect(modPath).toEqual('/projects/my-proj/src/feature_module/app_test.module.ts' as Path); + }); + + it('should find a module when name has uppercase', () => { + tree.create('/projects/my-proj/src/featureModule/appTest.module.ts', ''); + options.path = '/projects/my-proj/src'; + options.name = 'featureModule/newComponent'; + const modPath = findModuleFromOptions(tree, options); + expect(modPath).toEqual('/projects/my-proj/src/featureModule/appTest.module.ts' as Path); }); it('should find a module if flat is true', () => { diff --git a/packages/schematics/angular/utility/json-utils.ts b/packages/schematics/angular/utility/json-utils.ts index 6a509ca7cd4c..c42171214788 100644 --- a/packages/schematics/angular/utility/json-utils.ts +++ b/packages/schematics/angular/utility/json-utils.ts @@ -22,18 +22,22 @@ export function appendPropertyInAstObject( indent: number, ) { const indentStr = _buildIndent(indent); - + let index = node.start.offset + 1; if (node.properties.length > 0) { // Insert comma. const last = node.properties[node.properties.length - 1]; - recorder.insertRight(last.start.offset + last.text.replace(/\s+$/, '').length, ','); + const {text, end} = last; + const commaIndex = text.endsWith('\n') ? end.offset - 1 : end.offset; + recorder.insertRight(commaIndex, ','); + index = end.offset; } - - recorder.insertLeft( - node.end.offset - 1, - ' ' - + `"${propertyName}": ${JSON.stringify(value, null, 2).replace(/\n/g, indentStr)}` - + indentStr.slice(0, -2), + const content = JSON.stringify(value, null, indent).replace(/\n/g, indentStr); + recorder.insertRight( + index, + (node.properties.length === 0 && indent ? '\n' : '') + + ' '.repeat(indent) + + `"${propertyName}":${indent ? ' ' : ''}${content}` + + indentStr.slice(0, -indent), ); } @@ -77,15 +81,14 @@ export function insertPropertyInAstObjectInOrder( } const indentStr = _buildIndent(indent); - const insertIndex = insertAfterProp === null ? node.start.offset + 1 : insertAfterProp.end.offset + 1; - + const content = JSON.stringify(value, null, indent).replace(/\n/g, indentStr); recorder.insertRight( insertIndex, indentStr - + `"${propertyName}": ${JSON.stringify(value, null, 2).replace(/\n/g, indentStr)}` + + `"${propertyName}":${indent ? ' ' : ''}${content}` + ',', ); } @@ -98,18 +101,20 @@ export function appendValueInAstArray( indent = 4, ) { const indentStr = _buildIndent(indent); - + let index = node.start.offset + 1; if (node.elements.length > 0) { // Insert comma. const last = node.elements[node.elements.length - 1]; - recorder.insertRight(last.start.offset + last.text.replace(/\s+$/, '').length, ','); + recorder.insertRight(last.end.offset, ','); + index = indent ? last.end.offset + 1 : last.end.offset; } - recorder.insertLeft( - node.end.offset - 1, - ' ' - + JSON.stringify(value, null, 2).replace(/\n/g, indentStr) - + indentStr.slice(0, -2), + recorder.insertRight( + index, + (node.elements.length === 0 && indent ? '\n' : '') + + ' '.repeat(indent) + + JSON.stringify(value, null, indent).replace(/\n/g, indentStr) + + indentStr.slice(0, -indent), ); } @@ -129,5 +134,5 @@ export function findPropertyInAstObject( } function _buildIndent(count: number): string { - return '\n' + new Array(count + 1).join(' '); + return count ? '\n' + ' '.repeat(count) : ''; } diff --git a/packages/schematics/angular/utility/json-utils_spec.ts b/packages/schematics/angular/utility/json-utils_spec.ts index 98ab24c1bc90..14cea6c8c06f 100644 --- a/packages/schematics/angular/utility/json-utils_spec.ts +++ b/packages/schematics/angular/utility/json-utils_spec.ts @@ -5,77 +5,157 @@ * 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 { parseJsonAst } from '@angular-devkit/core'; -import { HostTree } from '@angular-devkit/schematics'; +import { JsonAstArray, JsonAstObject, JsonValue, parseJsonAst } from '@angular-devkit/core'; +import { HostTree, UpdateRecorder } from '@angular-devkit/schematics'; import { UnitTestTree } from '@angular-devkit/schematics/testing'; -import { insertPropertyInAstObjectInOrder } from './json-utils'; - -type Pojso = { - [key: string]: string; -}; +import { + appendPropertyInAstObject, + appendValueInAstArray, + insertPropertyInAstObjectInOrder, +} from './json-utils'; describe('json-utils', () => { + const a = 'a'; + const b = 'b'; + const m = 'm'; + const z = 'z'; const filePath = '/temp'; let tree: UnitTestTree; + + function runTest(testFn: Function, obj: JsonValue, indent: number): string { + const content = JSON.stringify(obj, null, indent); + tree.create(filePath, content); + const ast = parseJsonAst(content); + const rec = tree.beginUpdate(filePath); + testFn(rec, ast); + tree.commitUpdate(rec); + + const result = tree.readContent(filePath); + // Clean up the tree by deleting the file. + tree.delete(filePath); + + return result; + } + beforeEach(() => { tree = new UnitTestTree(new HostTree()); }); - describe('insertPropertyInAstObjectInOrder', () => { - function runTest(obj: Pojso, prop: string, val: string): Pojso { - const content = JSON.stringify(obj, null, 2); - tree.create(filePath, content); - const ast = parseJsonAst(content); - const rec = tree.beginUpdate(filePath); - if (ast.kind === 'object') { - insertPropertyInAstObjectInOrder(rec, ast, prop, val, 2); + describe('appendPropertyInAstObject', () => { + it('should insert multiple props with different indentations', () => { + const cases: Array<[{}, string, {}, number]> = [ + // initial | prop | want | indent + [{}, z, {z}, 0], + [{z}, m, {z, m}, 0], + [{m, z}, a, {m, z, a}, 0], + [{a, m, z}, b, {a, m, z, b}, 0], + [{}, z, {z}, 2], + [{z}, m, {z, m}, 2], + [{m, z}, a, {m, z, a}, 2], + [{a, m, z}, b, {a, m, z, b}, 2], + [{}, z, {z}, 4], + [{z}, m, {z, m}, 4], + [{m, z}, a, {m, z, a}, 4], + [{a, m, z}, b, {a, m, z, b}, 4], + ]; + for (const c of cases) { + const [initial, prop, want, indent] = c; + const got = runTest((rec: UpdateRecorder, ast: JsonAstObject) => { + expect(ast.kind).toBe('object'); + appendPropertyInAstObject(rec, ast, prop, prop, indent); + }, initial, indent); + expect(got).toBe(JSON.stringify(want, null, indent)); + expect(JSON.parse(got)).toEqual(want); } - tree.commitUpdate(rec); - - const result = JSON.parse(tree.readContent(filePath)); - // Clean up the tree by deleting the file. - tree.delete(filePath); - - return result; - } + }); + }); + describe('insertPropertyInAstObjectInOrder', () => { it('should insert a first prop', () => { - const obj = { - m: 'm', - z: 'z', - }; - const result = runTest(obj, 'a', 'val'); - expect(Object.keys(result)).toEqual(['a', 'm', 'z']); + const indent = 2; + const result = runTest((rec: UpdateRecorder, ast: JsonAstObject) => { + expect(ast.kind).toBe('object'); + insertPropertyInAstObjectInOrder(rec, ast, a, a, indent); + }, {m, z}, indent); + expect(result).toBe(JSON.stringify({a, m, z}, null, indent)); + expect(JSON.parse(result)).toEqual({a, m, z}); }); it('should insert a middle prop', () => { - const obj = { - a: 'a', - z: 'z', - }; - const result = runTest(obj, 'm', 'val'); - expect(Object.keys(result)).toEqual(['a', 'm', 'z']); + const indent = 2; + const result = runTest((rec: UpdateRecorder, ast: JsonAstObject) => { + expect(ast.kind).toBe('object'); + insertPropertyInAstObjectInOrder(rec, ast, m, m, indent); + }, {a, z}, indent); + expect(result).toBe(JSON.stringify({a, m, z}, null, indent)); + expect(JSON.parse(result)).toEqual({a, m, z}); }); it('should insert a last prop', () => { - const obj = { - a: 'a', - m: 'm', - }; - const result = runTest(obj, 'z', 'val'); - expect(Object.keys(result)).toEqual(['a', 'm', 'z']); + const indent = 2; + const result = runTest((rec: UpdateRecorder, ast: JsonAstObject) => { + expect(ast.kind).toBe('object'); + insertPropertyInAstObjectInOrder(rec, ast, z, z, indent); + }, {a, m}, indent); + expect(result).toBe(JSON.stringify({a, m, z}, null, indent)); + expect(JSON.parse(result)).toEqual({a, m, z}); + }); + + it('should insert multiple props with different indentations', () => { + const cases: Array<[{}, string, {}, number]> = [ + // initial | prop | want | indent + [{}, z, {z}, 0], + [{z}, m, {m, z}, 0], + [{m, z}, a, {a, m, z}, 0], + [{a, m, z}, b, {a, b, m, z}, 0], + [{}, z, {z}, 2], + [{z}, m, {m, z}, 2], + [{m, z}, a, {a, m, z}, 2], + [{a, m, z}, b, {a, b, m, z}, 2], + [{}, z, {z}, 4], + [{z}, m, {m, z}, 4], + [{m, z}, a, {a, m, z}, 4], + [{a, m, z}, b, {a, b, m, z}, 4], + ]; + for (const c of cases) { + const [initial, prop, want, indent] = c; + const got = runTest((rec: UpdateRecorder, ast: JsonAstObject) => { + expect(ast.kind).toBe('object'); + insertPropertyInAstObjectInOrder(rec, ast, prop, prop, indent); + }, initial, indent); + expect(got).toBe(JSON.stringify(want, null, indent)); + expect(JSON.parse(got)).toEqual(want); + } }); + }); - it('should insert multiple props', () => { - let obj = {}; - obj = runTest(obj, 'z', 'val'); - expect(Object.keys(obj)).toEqual(['z']); - obj = runTest(obj, 'm', 'val'); - expect(Object.keys(obj)).toEqual(['m', 'z']); - obj = runTest(obj, 'a', 'val'); - expect(Object.keys(obj)).toEqual(['a', 'm', 'z']); - obj = runTest(obj, 'b', 'val'); - expect(Object.keys(obj)).toEqual(['a', 'b', 'm', 'z']); + describe('appendValueInAstArray', () => { + it('should insert multiple props with different indentations', () => { + const cases: Array<[string[], string, {}, number]> = [ + // initial | value | want | indent + [[], z, [z], 0], + [[z], m, [z, m], 0], + [[m, z], a, [m, z, a], 0], + [[a, m, z], b, [a, m, z, b], 0], + [[], z, [z], 2], + [[z], m, [z, m], 2], + [[m, z], a, [m, z, a], 2], + [[a, m, z], b, [a, m, z, b], 2], + [[], z, [z], 4], + [[z], m, [z, m], 4], + [[m, z], a, [m, z, a], 4], + [[a, m, z], b, [a, m, z, b], 4], + ]; + for (const c of cases) { + const [initial, value, want, indent] = c; + const got = runTest((rec: UpdateRecorder, ast: JsonAstArray) => { + expect(ast.kind).toBe('array'); + appendValueInAstArray(rec, ast, value, indent); + }, initial, indent); + expect(got).toBe(JSON.stringify(want, null, indent)); + expect(JSON.parse(got)).toEqual(want); + } }); }); + }); diff --git a/packages/schematics/angular/utility/latest-versions.ts b/packages/schematics/angular/utility/latest-versions.ts index bcc335b079f3..476e218a8dcf 100644 --- a/packages/schematics/angular/utility/latest-versions.ts +++ b/packages/schematics/angular/utility/latest-versions.ts @@ -8,12 +8,12 @@ export const latestVersions = { // These versions should be kept up to date with latest Angular peer dependencies. - Angular: '~7.2.0-rc.0', + Angular: '~7.2.0', RxJs: '~6.3.3', ZoneJs: '~0.8.26', TypeScript: '~3.2.2', TsLib: '^1.9.0', // The versions below must be manually updated when making a new devkit release. - DevkitBuildAngular: '~0.13.0-beta.0', - DevkitBuildNgPackagr: '~0.13.0-beta.0', + DevkitBuildAngular: '~0.13.0', + DevkitBuildNgPackagr: '~0.13.0', }; diff --git a/packages/schematics/angular/workspace/files/__dot__gitignore.template b/packages/schematics/angular/workspace/files/__dot__gitignore.template index 47765ada9d86..f4f46a5feeb9 100644 --- a/packages/schematics/angular/workspace/files/__dot__gitignore.template +++ b/packages/schematics/angular/workspace/files/__dot__gitignore.template @@ -4,6 +4,8 @@ /dist /tmp /out-tsc +# Only exists if Bazel was run +/bazel-out # dependencies /node_modules diff --git a/packages/schematics/angular/workspace/files/package.json.template b/packages/schematics/angular/workspace/files/package.json.template index 4c6f3a0b0a71..dba404bdf044 100644 --- a/packages/schematics/angular/workspace/files/package.json.template +++ b/packages/schematics/angular/workspace/files/package.json.template @@ -34,7 +34,7 @@ "codelyzer": "~4.5.0", "jasmine-core": "~2.99.1", "jasmine-spec-reporter": "~4.2.1", - "karma": "~3.1.1", + "karma": "~4.0.0", "karma-chrome-launcher": "~2.2.0", "karma-coverage-istanbul-reporter": "~2.0.1", "karma-jasmine": "~1.1.2", diff --git a/packages/schematics/schematics/schematic/files/src/collection.json b/packages/schematics/schematics/schematic/files/src/collection.json index e9802c819a23..b5cebc776eec 100644 --- a/packages/schematics/schematics/schematic/files/src/collection.json +++ b/packages/schematics/schematics/schematic/files/src/collection.json @@ -5,7 +5,7 @@ // We read package.json using a require() call, which is standard JSON. { // This is just to indicate to your IDE that there is a schema for collection.json. - "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", + "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", // Schematics are listed as a map of schematicName => schematicDescription. // Each description contains a description field which is required, a factory reference, diff --git a/tests/angular_devkit/build_angular/hello-world-app/e2e/app.po.ts b/tests/angular_devkit/build_angular/hello-world-app/e2e/app.po.ts index 04d4aa2b9143..ba8e25c4823e 100644 --- a/tests/angular_devkit/build_angular/hello-world-app/e2e/app.po.ts +++ b/tests/angular_devkit/build_angular/hello-world-app/e2e/app.po.ts @@ -9,7 +9,7 @@ import { browser, by, element } from 'protractor'; export class AppPage { navigateTo() { - return browser.get('/'); + return browser.get(browser.baseUrl); } getTitleText() { diff --git a/tests/angular_devkit/build_ng_packagr/ng-packaged/projects/lib/ng-package.json b/tests/angular_devkit/build_ng_packagr/ng-packaged/projects/lib/ng-package.json index 36fcf49d9c1b..cce46c5a8b7b 100644 --- a/tests/angular_devkit/build_ng_packagr/ng-packaged/projects/lib/ng-package.json +++ b/tests/angular_devkit/build_ng_packagr/ng-packaged/projects/lib/ng-package.json @@ -2,6 +2,6 @@ "$schema": "../../../../../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../dist/lib", "lib": { - "entryFile": "src/public_api.ts" + "entryFile": "src/public-api.ts" } } \ No newline at end of file diff --git a/tests/angular_devkit/build_ng_packagr/ng-packaged/projects/lib/src/public_api.ts b/tests/angular_devkit/build_ng_packagr/ng-packaged/projects/lib/src/public-api.ts similarity index 100% rename from tests/angular_devkit/build_ng_packagr/ng-packaged/projects/lib/src/public_api.ts rename to tests/angular_devkit/build_ng_packagr/ng-packaged/projects/lib/src/public-api.ts diff --git a/tests/legacy-cli/e2e/tests/commands/add/add-material.ts b/tests/legacy-cli/e2e/tests/commands/add/add-material.ts index e9249a37f596..44b075a20a0a 100644 --- a/tests/legacy-cli/e2e/tests/commands/add/add-material.ts +++ b/tests/legacy-cli/e2e/tests/commands/add/add-material.ts @@ -1,8 +1,11 @@ -import { expectFileToMatch } from '../../../utils/fs'; +import { expectFileToMatch, rimraf } from '../../../utils/fs'; import { ng } from '../../../utils/process'; export default async function () { + // forcibly remove in case another test doesn't clean itself up + await rimraf('node_modules/@angular/material'); + await ng('add', '@angular/material'); await expectFileToMatch('package.json', /@angular\/material/); } diff --git a/tests/legacy-cli/e2e/tests/misc/universal-bundle-dependencies.ts b/tests/legacy-cli/e2e/tests/misc/universal-bundle-dependencies.ts new file mode 100644 index 000000000000..5c34a295cc27 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/misc/universal-bundle-dependencies.ts @@ -0,0 +1,66 @@ +import * as path from 'path'; +import { + createDir, + expectFileToMatch, + rimraf, + symlinkFile, + writeMultipleFiles, +} from '../../utils/fs'; +import { ng } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; + +export default async function() { + await updateJsonFile('angular.json', workspaceJson => { + const appArchitect = workspaceJson.projects['test-project'].architect; + appArchitect['server'] = { + builder: '@angular-devkit/build-angular:server', + options: { + bundleDependencies: 'none', + outputPath: 'dist/test-project-server', + main: 'src/main.server.ts', + tsConfig: 'tsconfig.server.json', + }, + }; + }); + + await createDir('./dummy-lib'); + + await writeMultipleFiles({ + './tsconfig.server.json': ` + { + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../dist-server", + "baseUrl": "./", + "module": "commonjs", + "types": [] + }, + "include": [ + "src/main.server.ts" + ] + } + `, + './src/main.server.ts': ` + import { dummyVersion } from 'dummy-lib'; + console.log(dummyVersion); + `, + // create a dummy library + './dummy-lib/package.json': `{ + "name": "dummy-lib", + "version": "0.0.0", + "typings": "./main.d.ts", + "main": "./main.js" + }`, + './dummy-lib/main.js': 'export const dummyVersion = 1', + './dummy-lib/main.d.ts': 'export declare const dummyVersion = 1', + }); + + await symlinkFile(path.resolve('./dummy-lib'), path.resolve('./node_modules/dummy-lib'), 'dir'); + + await ng('run', 'test-project:server'); + // when preserve symlinks is true, it should not included node_modules in the bundle + await expectFileToMatch('dist/test-project-server/main.js', 'require("dummy-lib")'); + + // cleanup the package + await rimraf('node_modules/dummy-lib'); +} diff --git a/tests/legacy-cli/e2e/tests/test/test-sourcemap.ts b/tests/legacy-cli/e2e/tests/test/test-sourcemap.ts new file mode 100644 index 000000000000..1963cfbd7bef --- /dev/null +++ b/tests/legacy-cli/e2e/tests/test/test-sourcemap.ts @@ -0,0 +1,29 @@ +import { writeFile } from '../../utils/fs'; +import { execAndWaitForOutputToMatch, killAllProcesses } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; + +export default async function () { + await updateJsonFile('angular.json', configJson => { + const appArchitect = configJson.projects['test-project'].architect; + appArchitect.test.options.sourceMap = { + scripts: true, + }; + }); + + await writeFile('src/app/app.component.spec.ts', ` + it('show fail', () => { + expect(undefined).toBeTruthy(); + }); + `); + + // when sourcemaps are not working the stacktrace won't point to the spec.ts file. + try { + await execAndWaitForOutputToMatch( + 'ng', + ['test', '--watch', 'false'], + /app\.component\.spec\.ts/, + ); + } finally { + killAllProcesses(); + } +} diff --git a/yarn.lock b/yarn.lock index 59d204cb7460..4e69d6a5ca36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -837,10 +837,10 @@ ajv-keywords@^3.1.0: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= -ajv@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.7.0.tgz#e3ce7bb372d6577bb1839f1dfdfcbf5ad2948d96" - integrity sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg== +ajv@6.9.1: + version "6.9.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.1.tgz#a4d3683d74abc5670e75f0b16520f70a20ea8dc1" + integrity sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA== dependencies: fast-deep-equal "^2.0.1" fast-json-stable-stringify "^2.0.0" @@ -5334,7 +5334,7 @@ istanbul-reports@^2.0.1: dependencies: handlebars "^4.0.11" -istanbul@0.4.5, istanbul@^0.4.5: +istanbul@^0.4.5: version "0.4.5" resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" integrity sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs= @@ -9139,7 +9139,7 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@0.5.10: +source-map-support@0.5.10, source-map-support@~0.5.9: version "0.5.10" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c" integrity sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ== @@ -9730,17 +9730,17 @@ term-size@^1.2.0: dependencies: execa "^0.7.0" -terser-webpack-plugin@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.1.tgz#7545da9ae5f4f9ae6a0ac961eb46f5e7c845cc26" - integrity sha512-GGSt+gbT0oKcMDmPx4SRSfJPE1XaN3kQRWG4ghxKQw9cn5G9x6aCKSsgYdvyM0na9NJ4Drv0RG6jbBByZ5CMjw== +terser-webpack-plugin@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.2.tgz#9bff3a891ad614855a7dde0d707f7db5a927e3d9" + integrity sha512-1DMkTk286BzmfylAvLXwpJrI7dWa5BnFmscV/2dCr8+c56egFcbaeFAl7+sujAjdmpLam21XRdhA4oifLyiWWg== dependencies: cacache "^11.0.2" find-cache-dir "^2.0.0" schema-utils "^1.0.0" serialize-javascript "^1.4.0" source-map "^0.6.1" - terser "^3.8.1" + terser "^3.16.1" webpack-sources "^1.1.0" worker-farm "^1.5.2" @@ -9758,6 +9758,15 @@ terser-webpack-plugin@^1.1.0: webpack-sources "^1.1.0" worker-farm "^1.5.2" +terser@^3.16.1: + version "3.16.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-3.16.1.tgz#5b0dd4fa1ffd0b0b43c2493b2c364fd179160493" + integrity sha512-JDJjgleBROeek2iBcSNzOHLKsB/MdDf+E/BOAJ0Tk9r7p9/fVobfv7LMJ/g/k3v9SXdmjZnIlFd5nfn/Rt0Xow== + dependencies: + commander "~2.17.1" + source-map "~0.6.1" + source-map-support "~0.5.9" + terser@^3.8.1: version "3.10.8" resolved "https://registry.yarnpkg.com/terser/-/terser-3.10.8.tgz#2fe3967396a10cdc3d575074fe857efd30a2895a"