From 3cf60a862c29d2a466f004ee266426a07277a4da Mon Sep 17 00:00:00 2001 From: Jonathan Jayet Date: Tue, 20 Sep 2016 19:28:53 +0200 Subject: [PATCH 01/35] Fixed the ssl command not working anymore --- packages/angular-cli/custom-typings.d.ts | 3 +++ packages/angular-cli/tasks/serve-webpack.ts | 26 ++++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/angular-cli/custom-typings.d.ts b/packages/angular-cli/custom-typings.d.ts index 1fbb264de74a..d82b7503e7dc 100644 --- a/packages/angular-cli/custom-typings.d.ts +++ b/packages/angular-cli/custom-typings.d.ts @@ -17,6 +17,9 @@ interface IWebpackDevServerConfigurationOptions { headers?: { [key: string]: string }; stats?: { [key: string]: boolean }; inline: boolean; + https?:boolean; + key?: string; + cert?: string; } interface WebpackProgressPluginOutputOptions { diff --git a/packages/angular-cli/tasks/serve-webpack.ts b/packages/angular-cli/tasks/serve-webpack.ts index 4dfb1dffff1f..5a1bae5ab1b2 100644 --- a/packages/angular-cli/tasks/serve-webpack.ts +++ b/packages/angular-cli/tasks/serve-webpack.ts @@ -13,7 +13,7 @@ import { CliConfig } from '../models/config'; import { oneLine } from 'common-tags'; export default Task.extend({ - run: function(commandOptions: ServeTaskOptions) { + run: function (commandOptions: ServeTaskOptions) { const ui = this.ui; let webpackCompiler: any; @@ -46,6 +46,20 @@ export default Task.extend({ } } + let sslKey: string = null; + let sslCert: string = null; + + if (commandOptions.ssl) { + const keyPath = path.resolve(this.project.root, commandOptions.sslKey); + if (fs.existsSync(keyPath)) { + sslKey = fs.readFileSync(keyPath, 'utf-8'); + } + const certPath = path.resolve(this.project.root, commandOptions.sslCert); + if (fs.existsSync(certPath)) { + sslCert = fs.readFileSync(certPath, 'utf-8'); + } + } + const webpackDevServerConfiguration: IWebpackDevServerConfigurationOptions = { contentBase: path.resolve( this.project.root, @@ -54,19 +68,25 @@ export default Task.extend({ historyApiFallback: true, stats: webpackDevServerOutputOptions, inline: true, + https: commandOptions.ssl, proxy: proxyConfig }; + if (sslKey != null && sslCert != null) { + webpackDevServerConfiguration.key = sslKey; + webpackDevServerConfiguration.cert = sslCert; + } + ui.writeLine(chalk.green(oneLine` ** NG Live Development Server is running on - http://${commandOptions.host}:${commandOptions.port}. + http${commandOptions.ssl ? 's' : ''}://${commandOptions.host}:${commandOptions.port}. ** `)); const server = new WebpackDevServer(webpackCompiler, webpackDevServerConfiguration); return new Promise((resolve, reject) => { - server.listen(commandOptions.port, `${commandOptions.host}`, function(err: any, stats: any) { + server.listen(commandOptions.port, `${commandOptions.host}`, function (err: any, stats: any) { if (err) { console.error(err.stack || err); if (err.details) { console.error(err.details); } From 7fb5fb1d4f16bda312e7a9ec5e1f0a6f30263a1c Mon Sep 17 00:00:00 2001 From: Jonathan Jayet Date: Tue, 20 Sep 2016 19:41:09 +0200 Subject: [PATCH 02/35] Fixes lint --- packages/angular-cli/custom-typings.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/angular-cli/custom-typings.d.ts b/packages/angular-cli/custom-typings.d.ts index d82b7503e7dc..08a6737a9f19 100644 --- a/packages/angular-cli/custom-typings.d.ts +++ b/packages/angular-cli/custom-typings.d.ts @@ -3,7 +3,7 @@ interface IWebpackDevServerConfigurationOptions { hot?: boolean; historyApiFallback?: boolean; compress?: boolean; - proxy?: {[key: string]: string}; + proxy?: { [key: string]: string }; staticOptions?: any; quiet?: boolean; noInfo?: boolean; @@ -17,7 +17,7 @@ interface IWebpackDevServerConfigurationOptions { headers?: { [key: string]: string }; stats?: { [key: string]: boolean }; inline: boolean; - https?:boolean; + https?: boolean; key?: string; cert?: string; } From 745be76d71d988caae498404d9a20e567a201af1 Mon Sep 17 00:00:00 2001 From: Mike Cebrian Date: Tue, 20 Sep 2016 21:50:24 -0400 Subject: [PATCH 03/35] fix(karma): set defaults for karma.conf.js (#1837) The karma plugin is currently overwriting configuration set in karma.conf.js instead of merging existing values. This fix will set defaults but not overwrite existing configuration. --- packages/angular-cli/plugins/karma.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/angular-cli/plugins/karma.js b/packages/angular-cli/plugins/karma.js index cfd414212116..1c17e28e3771 100644 --- a/packages/angular-cli/plugins/karma.js +++ b/packages/angular-cli/plugins/karma.js @@ -12,8 +12,8 @@ const init = (config) => { const environment = config.angularCli.environment || 'dev'; // add webpack config - config.webpack = getWebpackTestConfig(config.basePath, environment, appConfig); - config.webpackMiddleware = { + const webpackConfig = getWebpackTestConfig(config.basePath, environment, appConfig); + const webpackMiddlewareConfig = { noInfo: true, // Hide webpack output because its noisy. stats: { // Also prevent chunk and module display output, cleaner look. Only emit errors. assets: false, @@ -25,6 +25,8 @@ const init = (config) => { chunkModules: false } }; + config.webpack = Object.assign(webpackConfig, config.webpack); + config.webpackMiddleware = Object.assign(webpackMiddlewareConfig, config.webpackMiddleware); // replace the angular-cli preprocessor with webpack+sourcemap Object.keys(config.preprocessors) From 04afb8f34793b98a920e04299cdaa5c4f8b89353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Passos?= Date: Wed, 21 Sep 2016 22:35:23 -0300 Subject: [PATCH 04/35] fix(generate): Update directive.spec.ts blueprint to fix incorret import (#1940) --- .../directive/files/__path__/__name__.directive.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/angular-cli/blueprints/directive/files/__path__/__name__.directive.spec.ts b/packages/angular-cli/blueprints/directive/files/__path__/__name__.directive.spec.ts index 481baeaa967c..ffbcffab88c6 100644 --- a/packages/angular-cli/blueprints/directive/files/__path__/__name__.directive.spec.ts +++ b/packages/angular-cli/blueprints/directive/files/__path__/__name__.directive.spec.ts @@ -1,7 +1,7 @@ /* tslint:disable:no-unused-variable */ import { TestBed, async } from '@angular/core/testing'; -import { <%= classifiedModuleName %> } from './<%= dasherizedModuleName %>.directive'; +import { <%= classifiedModuleName %>Directive } from './<%= dasherizedModuleName %>.directive'; describe('Directive: <%= classifiedModuleName %>', () => { it('should create an instance', () => { From 7e9e5191d28106c79ab58c5c6dd39cc6598b4ecc Mon Sep 17 00:00:00 2001 From: Catalin Zalog Date: Mon, 26 Sep 2016 02:40:27 +0300 Subject: [PATCH 05/35] Trim trailing whitespace (#2345) --- packages/angular-cli/blueprints/ng2/files/__path__/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/angular-cli/blueprints/ng2/files/__path__/index.html b/packages/angular-cli/blueprints/ng2/files/__path__/index.html index 250c1d86e223..1f8c4ad70d2d 100644 --- a/packages/angular-cli/blueprints/ng2/files/__path__/index.html +++ b/packages/angular-cli/blueprints/ng2/files/__path__/index.html @@ -22,7 +22,6 @@ <% } %> - <<%= prefix %>-root>Loading...-root> From 015dbbb1ac72e98c9a5250ce58b5e20ae42e5568 Mon Sep 17 00:00:00 2001 From: Mike Cebrian Date: Mon, 26 Sep 2016 15:35:30 -0400 Subject: [PATCH 06/35] feat(webpackDevServer): Add watchOptions for webpackDevServer (#1814) Add watchOptions to webpackDevServerConfiguration to conditionally enable polling option in watchpack Remove additional blank lines from end of serve-watchpack.ts so that only one is remaining --- packages/angular-cli/lib/config/schema.d.ts | 1 + packages/angular-cli/lib/config/schema.json | 5 ++++- packages/angular-cli/tasks/serve-webpack.ts | 5 ++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/angular-cli/lib/config/schema.d.ts b/packages/angular-cli/lib/config/schema.d.ts index f660f183f221..bb47f47212a8 100644 --- a/packages/angular-cli/lib/config/schema.d.ts +++ b/packages/angular-cli/lib/config/schema.d.ts @@ -59,5 +59,6 @@ export interface CliConfig { defaults?: { styleExt?: string; prefixInterfaces?: boolean; + poll?: number; }; } diff --git a/packages/angular-cli/lib/config/schema.json b/packages/angular-cli/lib/config/schema.json index af5d76267e71..5077846501e6 100644 --- a/packages/angular-cli/lib/config/schema.json +++ b/packages/angular-cli/lib/config/schema.json @@ -135,10 +135,13 @@ }, "prefixInterfaces": { "type": "boolean" + }, + "poll": { + "type": "number" } }, "additionalProperties": false } }, "additionalProperties": false -} \ No newline at end of file +} diff --git a/packages/angular-cli/tasks/serve-webpack.ts b/packages/angular-cli/tasks/serve-webpack.ts index 5a1bae5ab1b2..c87960bf831a 100644 --- a/packages/angular-cli/tasks/serve-webpack.ts +++ b/packages/angular-cli/tasks/serve-webpack.ts @@ -69,7 +69,10 @@ export default Task.extend({ stats: webpackDevServerOutputOptions, inline: true, https: commandOptions.ssl, - proxy: proxyConfig + proxy: proxyConfig, + watchOptions: { + poll: CliConfig.fromProject().config.defaults.poll + } }; if (sslKey != null && sslCert != null) { From 8e5918f69bfb871b24ceb1e05897d970d7936436 Mon Sep 17 00:00:00 2001 From: Leon Radley Date: Tue, 30 Aug 2016 15:37:53 +0200 Subject: [PATCH 07/35] chore(docs): proxy-config documentation Close #1896 --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 41284f42db97..5d34a7060d14 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ The generated project has dependencies that require **Node 4.x.x and NPM 3.x.x** * [Adding extra files to the build](#adding-extra-files-to-the-build) * [Running Unit Tests](#running-unit-tests) * [Running End-to-End Tests](#running-end-to-end-tests) +* [Proxy To Backend](#proxy-to-backend) * [Deploying the App via GitHub Pages](#deploying-the-app-via-github-pages) * [Linting and formatting code](#linting-and-formatting-code) * [Support for offline applications](#support-for-offline-applications) @@ -192,6 +193,33 @@ Before running the tests make sure you are serving the app via `ng serve`. End-to-end tests are run via [Protractor](https://angular.github.io/protractor/). +### Proxy To Backend +Using the proxying support in webpack's dev server we can highjack certain urls and send them to a backend server. +We do this by passing a file to `--proxy-config` + +Say we have a server running on `http://localhost:3000/api` and we want all calls th `http://localhost:4200/api` to go to that server. + +We create a file next to projects `package.json` called `proxy.conf.json` +with the content + +``` +{ + "/api": { + "target": "http://localhost:3000", + "secure": false + } +} +``` + +You can read more about what options are available here [webpack-dev-server proxy settings](https://webpack.github.io/docs/webpack-dev-server.html#proxy) + +and then we edit the `package.json` file's start script to be + +``` +"start": "ng serve --proxy-config proxy.conf.json", +``` + +now run it with `npm start` ### Deploying the app via GitHub Pages From 25fc7bbc5b2a985387278aa2a0bedbdbcc6fb479 Mon Sep 17 00:00:00 2001 From: Brian Troncone Date: Sat, 17 Sep 2016 05:04:39 -0400 Subject: [PATCH 08/35] chore(docs): fixed typo Fixed typo (removed extra 'to') and added colon to match other steps in global installation instructions. Close #2190 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d34a7060d14..163a838c612f 100644 --- a/README.md +++ b/README.md @@ -357,7 +357,7 @@ First install Bootstrap from `npm`: npm install bootstrap@next ``` -Then add the needed script files to to `apps[0].scripts`. +Then add the needed script files to `apps[0].scripts`: ``` "scripts": [ From acc8a6d9dd7a23c31de06a8c165344846b0758e4 Mon Sep 17 00:00:00 2001 From: Peter Blazejewicz Date: Sat, 17 Sep 2016 22:22:06 +0200 Subject: [PATCH 09/35] chore(readme): update Bootstrap CSS inclusion example This commit fixes README example of including Bootstrap dependency in generated project in that way that Bootstrap CSS dependency is moved up - to be the first in build bundle. The reason behind this change is that Bootstrap comes with reset CSS code and globals that are intended to be global unless reset and changed by cascading file - here style.css. Thanks! Close #2197 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 163a838c612f..22d011faed01 100644 --- a/README.md +++ b/README.md @@ -370,8 +370,8 @@ Then add the needed script files to `apps[0].scripts`: Finally add the Bootstrap CSS to the `apps[0].styles` array: ``` "styles": [ - "styles.css", - "../node_modules/bootstrap/dist/css/bootstrap.css" + "../node_modules/bootstrap/dist/css/bootstrap.css", + "styles.css" ], ``` From fb9e311e0748ddfd4638941e6295d478495ad8a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rokas=20Brazd=C5=BEionis?= Date: Sun, 18 Sep 2016 14:01:56 +0300 Subject: [PATCH 10/35] chore(readme): fix webpack update version typo Close #2200 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 22d011faed01..d96b119f8625 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@ If you wish to collaborate while the project is still young, check out [our issu ## Webpack update -We changed the build system between beta.10 and beta.12, from SystemJS to Webpack. +We changed the build system between beta.10 and beta.14, from SystemJS to Webpack. And with it comes a lot of benefits. To take advantage of these, your app built with the old beta will need to migrate. -You can update your `beta.10` projects to `beta.12` by following [these instructions](https://github.com/angular/angular-cli/wiki/Upgrading-from-Beta.10-to-Beta.14). +You can update your `beta.10` projects to `beta.14` by following [these instructions](https://github.com/angular/angular-cli/wiki/Upgrading-from-Beta.10-to-Beta.14). ## Prerequisites From e8af222fb94b4fa1dabfbbec6f94bfbd518eedcf Mon Sep 17 00:00:00 2001 From: Mike Brocchi Date: Fri, 23 Sep 2016 23:06:11 -0400 Subject: [PATCH 11/35] bug(generate): directive selectors are now camelCase Fixes #1216 Close #2323 --- .../directive/files/__path__/__name__.directive.ts | 2 +- packages/angular-cli/blueprints/directive/index.js | 6 +++--- tests/acceptance/generate-directive.spec.js | 11 +++++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/angular-cli/blueprints/directive/files/__path__/__name__.directive.ts b/packages/angular-cli/blueprints/directive/files/__path__/__name__.directive.ts index b7d242e80b5a..ee8bd3254f68 100644 --- a/packages/angular-cli/blueprints/directive/files/__path__/__name__.directive.ts +++ b/packages/angular-cli/blueprints/directive/files/__path__/__name__.directive.ts @@ -1,7 +1,7 @@ import { Directive } from '@angular/core'; @Directive({ - selector: '[<%= rawEntityName %>]' + selector: '[<%= selector %>]' }) export class <%= classifiedModuleName %>Directive { diff --git a/packages/angular-cli/blueprints/directive/index.js b/packages/angular-cli/blueprints/directive/index.js index db0858d2fcc9..db8adc6487bc 100644 --- a/packages/angular-cli/blueprints/directive/index.js +++ b/packages/angular-cli/blueprints/directive/index.js @@ -31,9 +31,9 @@ module.exports = { this.project.ngConfig.apps[0].prefix) { defaultPrefix = this.project.ngConfig.apps[0].prefix; } - var prefix = this.options.prefix ? defaultPrefix : ''; + var prefix = this.options.prefix ? `${defaultPrefix}-` : ''; - this.rawEntityName = prefix + parsedPath.name; + this.selector = stringUtils.camelize(prefix + parsedPath.name); return parsedPath.name; }, @@ -41,7 +41,7 @@ module.exports = { return { dynamicPath: this.dynamicPath.dir, flat: options.flat, - rawEntityName: this.rawEntityName + selector: this.selector }; }, diff --git a/tests/acceptance/generate-directive.spec.js b/tests/acceptance/generate-directive.spec.js index 6a2b93266d06..93398ee218ea 100644 --- a/tests/acceptance/generate-directive.spec.js +++ b/tests/acceptance/generate-directive.spec.js @@ -156,4 +156,15 @@ describe('Acceptance: ng generate directive', function () { expect(err).to.equal(`Invalid path: "..${path.sep}my-dir" cannot be above the "src${path.sep}app" directory`); }); }); + + it('converts dash-cased-name to a camelCasedSelector', () => { + const appRoot = path.join(root, 'tmp/foo'); + const directivePath = path.join(appRoot, 'src/app/my-dir.directive.ts'); + return ng(['generate', 'directive', 'my-dir']) + .then(() => readFile(directivePath, 'utf-8')) + .then(content => { + // expect(content).matches(/selector: [app-my-dir]/m); + expect(content).matches(/selector: '\[appMyDir\]'/); + }); + }); }); From ef4d1ea0053049a5c7b8152c1694e9920fae1270 Mon Sep 17 00:00:00 2001 From: cexbrayat Date: Sat, 24 Sep 2016 16:40:06 +0200 Subject: [PATCH 12/35] chore(e2e): bump to protractor 4.0.9 Close #2330 --- packages/angular-cli/blueprints/ng2/files/e2e/app.po.ts | 2 +- packages/angular-cli/blueprints/ng2/files/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/angular-cli/blueprints/ng2/files/e2e/app.po.ts b/packages/angular-cli/blueprints/ng2/files/e2e/app.po.ts index 07330f209b56..9008b7bec9b0 100644 --- a/packages/angular-cli/blueprints/ng2/files/e2e/app.po.ts +++ b/packages/angular-cli/blueprints/ng2/files/e2e/app.po.ts @@ -1,4 +1,4 @@ -import { browser, element, by } from 'protractor/globals'; +import { browser, element, by } from 'protractor'; export class <%= jsComponentName %>Page { navigateTo() { diff --git a/packages/angular-cli/blueprints/ng2/files/package.json b/packages/angular-cli/blueprints/ng2/files/package.json index 776c8819baa3..dd92fc612e45 100644 --- a/packages/angular-cli/blueprints/ng2/files/package.json +++ b/packages/angular-cli/blueprints/ng2/files/package.json @@ -43,7 +43,7 @@ "karma-cli": "^1.0.1", "karma-jasmine": "^1.0.2", "karma-remap-istanbul": "^0.2.1", - "protractor": "4.0.5", + "protractor": "4.0.9", "ts-node": "1.2.1", "tslint": "3.13.0", "typescript": "2.0.2" From 26f916a6700ba2281fbeb7113abdf3948c9e965f Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Tue, 27 Sep 2016 00:20:35 +0100 Subject: [PATCH 13/35] fix(build): fail ng build on error (#2360) Fix #2014 --- packages/angular-cli/tasks/build-webpack.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/angular-cli/tasks/build-webpack.ts b/packages/angular-cli/tasks/build-webpack.ts index 49abc0687b57..47491c209722 100644 --- a/packages/angular-cli/tasks/build-webpack.ts +++ b/packages/angular-cli/tasks/build-webpack.ts @@ -24,6 +24,9 @@ export default Task.extend({ runTaskOptions.baseHref ).config; + // fail on build error + config.bail = true; + const webpackCompiler: any = webpack(config); const ProgressPlugin = require('webpack/lib/ProgressPlugin'); From 32cd7a2da121d3cd9bf4d4a077d662a2723d4d96 Mon Sep 17 00:00:00 2001 From: Mike Brocchi Date: Tue, 27 Sep 2016 07:29:29 -0400 Subject: [PATCH 14/35] chore(generate): update routing files & add --routing to ng new (#2361) --- README.md | 33 ++++++++++--------- .../files/__path__/__name__-routing.module.ts | 11 +++++++ .../module/files/__path__/__name__.module.ts | 4 +-- .../module/files/__path__/__name__.routing.ts | 6 ---- .../angular-cli/blueprints/module/index.js | 2 +- .../files/__path__/app/app-routing.module.ts | 11 +++++++ .../ng2/files/__path__/app/app.module.ts | 6 ++-- packages/angular-cli/blueprints/ng2/index.js | 9 +++-- packages/angular-cli/commands/init.ts | 6 ++-- packages/angular-cli/commands/new.ts | 3 +- .../utilities/find-parent-module.ts | 5 ++- .../tests/generate/module/module-routing.ts | 2 +- 12 files changed, 62 insertions(+), 36 deletions(-) create mode 100644 packages/angular-cli/blueprints/module/files/__path__/__name__-routing.module.ts delete mode 100644 packages/angular-cli/blueprints/module/files/__path__/__name__.routing.ts create mode 100644 packages/angular-cli/blueprints/ng2/files/__path__/app/app-routing.module.ts diff --git a/README.md b/README.md index d96b119f8625..3e2f109ff60a 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,13 @@ Prototype of a CLI for Angular 2 applications based on the [ember-cli](http://ww This project is very much still a work in progress. -The CLI is now in beta. +The CLI is now in beta. If you wish to collaborate while the project is still young, check out [our issue list](https://github.com/angular/angular-cli/issues). ## Webpack update -We changed the build system between beta.10 and beta.14, from SystemJS to Webpack. -And with it comes a lot of benefits. +We changed the build system between beta.10 and beta.14, from SystemJS to Webpack. +And with it comes a lot of benefits. To take advantage of these, your app built with the old beta will need to migrate. You can update your `beta.10` projects to `beta.14` by following [these instructions](https://github.com/angular/angular-cli/wiki/Upgrading-from-Beta.10-to-Beta.14). @@ -109,6 +109,7 @@ Service | `ng g service my-new-service` Class | `ng g class my-new-class` Interface | `ng g interface my-new-interface` Enum | `ng g enum my-new-enum` +Module | `ng g module my-module` ### Generating a route @@ -126,8 +127,8 @@ The build artifacts will be stored in the `dist/` directory. ### Build Targets and Environment Files -`ng build` can specify both a build target (`--target=production` or `--target=development`) and an -environment file to be used with that build (`--environment=dev` or `--environment=prod`). +`ng build` can specify both a build target (`--target=production` or `--target=development`) and an +environment file to be used with that build (`--environment=dev` or `--environment=prod`). By default, the development build target and environment are used. The mapping used to determine which environment file is used can be found in `angular-cli.json`: @@ -157,7 +158,7 @@ ng build You can also add your own env files other than `dev` and `prod` by doing the following: - create a `src/environments/environment.NAME.ts` -- add `{ "NAME": 'src/environments/environment.NAME.ts' }` to the the `apps[0].environments` object in `angular-cli.json` +- add `{ "NAME": 'src/environments/environment.NAME.ts' }` to the the `apps[0].environments` object in `angular-cli.json` - use them via the `--env=NAME` flag on the build/serve commands. ### Base tag handling in index.html @@ -172,7 +173,7 @@ ng build --bh /myUrl/ ### Bundling -All builds make use of bundling, and using the `--prod` flag in `ng build --prod` +All builds make use of bundling, and using the `--prod` flag in `ng build --prod` or `ng serve --prod` will also make use of uglifying and tree-shaking functionality. ### Running unit tests @@ -290,11 +291,11 @@ source ~/.bash_profile ### Global styles -The `styles.css` file allows users to add global styles and supports -[CSS imports](https://developer.mozilla.org/en/docs/Web/CSS/@import). +The `styles.css` file allows users to add global styles and supports +[CSS imports](https://developer.mozilla.org/en/docs/Web/CSS/@import). -If the project is created with the `--style=sass` option, this will be a `.sass` -file instead, and the same applies to `scss/less/styl`. +If the project is created with the `--style=sass` option, this will be a `.sass` +file instead, and the same applies to `scss/less/styl`. You can add more global styles via the `apps[0].styles` property in `angular-cli.json`. @@ -344,11 +345,11 @@ npm install @types/d3 --save-dev ### Global Library Installation -Some javascript libraries need to be added to the global scope, and loaded as if -they were in a script tag. We can do this using the `apps[0].scripts` and +Some javascript libraries need to be added to the global scope, and loaded as if +they were in a script tag. We can do this using the `apps[0].scripts` and `apps[0].styles` properties of `angular-cli.json`. -As an example, to use [Boostrap 4](http://v4-alpha.getbootstrap.com/) this is +As an example, to use [Boostrap 4](http://v4-alpha.getbootstrap.com/) this is what you need to do: First install Bootstrap from `npm`: @@ -375,7 +376,7 @@ Finally add the Bootstrap CSS to the `apps[0].styles` array: ], ``` -Restart `ng serve` if you're running it, and Bootstrap 4 should be working on +Restart `ng serve` if you're running it, and Bootstrap 4 should be working on your app. ### Updating angular-cli @@ -400,7 +401,7 @@ Running `ng init` will check for changes in all the auto-generated files created Carefully read the diffs for each code file, and either accept the changes or incorporate them manually after `ng init` finishes. -**The main cause of errors after an update is failing to incorporate these updates into your code**. +**The main cause of errors after an update is failing to incorporate these updates into your code**. You can find more details about changes between versions in [CHANGELOG.md](https://github.com/angular/angular-cli/blob/master/CHANGELOG.md). diff --git a/packages/angular-cli/blueprints/module/files/__path__/__name__-routing.module.ts b/packages/angular-cli/blueprints/module/files/__path__/__name__-routing.module.ts new file mode 100644 index 000000000000..2d4459211035 --- /dev/null +++ b/packages/angular-cli/blueprints/module/files/__path__/__name__-routing.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +const routes: Routes = []; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [] +}) +export class <%= classifiedModuleName %>RoutingModule { } diff --git a/packages/angular-cli/blueprints/module/files/__path__/__name__.module.ts b/packages/angular-cli/blueprints/module/files/__path__/__name__.module.ts index 67f4f4a7a854..2505c216531f 100644 --- a/packages/angular-cli/blueprints/module/files/__path__/__name__.module.ts +++ b/packages/angular-cli/blueprints/module/files/__path__/__name__.module.ts @@ -1,11 +1,11 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common';<% if (routing) { %> -import { <%= camelizedModuleName %>Routing } from './<%= dasherizedModuleName %>.routing';<% } %> +import { <%= classifiedModuleName %>RoutingModule } from './<%= dasherizedModuleName %>-routing.module';<% } %> @NgModule({ imports: [ CommonModule<% if (routing) { %>, - <%= camelizedModuleName %>Routing<% } %> + <%= classifiedModuleName %>RoutingModule<% } %> ], declarations: [] }) diff --git a/packages/angular-cli/blueprints/module/files/__path__/__name__.routing.ts b/packages/angular-cli/blueprints/module/files/__path__/__name__.routing.ts deleted file mode 100644 index f59bd42e3fdc..000000000000 --- a/packages/angular-cli/blueprints/module/files/__path__/__name__.routing.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Routes, RouterModule } from '@angular/router'; - -export const <%= camelizedModuleName %>Routes: Routes = []; - -export const <%= camelizedModuleName %>Routing = RouterModule.forChild(<%= camelizedModuleName %>Routes); - diff --git a/packages/angular-cli/blueprints/module/index.js b/packages/angular-cli/blueprints/module/index.js index 7b2f01ff0e04..7153c6650034 100644 --- a/packages/angular-cli/blueprints/module/index.js +++ b/packages/angular-cli/blueprints/module/index.js @@ -34,7 +34,7 @@ module.exports = { fileList = fileList.filter(p => p.indexOf('__name__.module.spec.ts') < 0); } if (this.options && !this.options.routing) { - fileList = fileList.filter(p => p.indexOf('__name__.routing.ts') < 0); + fileList = fileList.filter(p => p.indexOf('__name__-routing.module.ts') < 0); } return fileList; diff --git a/packages/angular-cli/blueprints/ng2/files/__path__/app/app-routing.module.ts b/packages/angular-cli/blueprints/ng2/files/__path__/app/app-routing.module.ts new file mode 100644 index 000000000000..405cb10d7d3d --- /dev/null +++ b/packages/angular-cli/blueprints/ng2/files/__path__/app/app-routing.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +const routes: Routes = []; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], + providers: [] +}) +export class <%= classifiedModuleName %>RoutingModule { } diff --git a/packages/angular-cli/blueprints/ng2/files/__path__/app/app.module.ts b/packages/angular-cli/blueprints/ng2/files/__path__/app/app.module.ts index 67ae49119baa..a4d339d7b9dc 100644 --- a/packages/angular-cli/blueprints/ng2/files/__path__/app/app.module.ts +++ b/packages/angular-cli/blueprints/ng2/files/__path__/app/app.module.ts @@ -1,7 +1,8 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { HttpModule } from '@angular/http'; +import { HttpModule } from '@angular/http';<% if (routing) { %> +import { <%= classifiedModuleName %>RoutingModule } from './<%= dasherizedModuleName %>-routing.module';<% } %> import { AppComponent } from './app.component'; @@ -12,7 +13,8 @@ import { AppComponent } from './app.component'; imports: [ BrowserModule, FormsModule, - HttpModule + HttpModule<% if (routing) { %>, + <%= classifiedModuleName %>RoutingModule<% } %> ], providers: [], bootstrap: [AppComponent] diff --git a/packages/angular-cli/blueprints/ng2/index.js b/packages/angular-cli/blueprints/ng2/index.js index 81d14a41d762..a73844862a67 100644 --- a/packages/angular-cli/blueprints/ng2/index.js +++ b/packages/angular-cli/blueprints/ng2/index.js @@ -10,7 +10,8 @@ module.exports = { { name: 'source-dir', type: String, default: 'src', aliases: ['sd'] }, { name: 'prefix', type: String, default: 'app', aliases: ['p'] }, { name: 'style', type: String, default: 'css' }, - { name: 'mobile', type: Boolean, default: false } + { name: 'mobile', type: Boolean, default: false }, + { name: 'routing', type: Boolean, default: false } ], afterInstall: function (options) { @@ -38,7 +39,8 @@ module.exports = { prefix: options.prefix, styleExt: this.styleExt, relativeRootPath: relativeRootPath, - isMobile: options.mobile + isMobile: options.mobile, + routing: options.routing }; }, @@ -48,6 +50,9 @@ module.exports = { fileList = fileList.filter(p => p.indexOf('__name__.component.html') < 0); fileList = fileList.filter(p => p.indexOf('__name__.component.__styleext__') < 0); } + if (this.options && !this.options.routing) { + fileList = fileList.filter(p => p.indexOf('__name__-routing.module.ts') < 0); + } return fileList; }, diff --git a/packages/angular-cli/commands/init.ts b/packages/angular-cli/commands/init.ts index f8f082214c76..51e76ebcbe88 100644 --- a/packages/angular-cli/commands/init.ts +++ b/packages/angular-cli/commands/init.ts @@ -26,7 +26,8 @@ const InitCommand: any = Command.extend({ { name: 'source-dir', type: String, default: 'src', aliases: ['sd'] }, { name: 'style', type: String, default: 'css' }, { name: 'prefix', type: String, default: 'app', aliases: ['p'] }, - { name: 'mobile', type: Boolean, default: false } + { name: 'mobile', type: Boolean, default: false }, + { name: 'routing', type: Boolean, default: false } ], anonymousOptions: [''], @@ -104,7 +105,8 @@ const InitCommand: any = Command.extend({ sourceDir: commandOptions.sourceDir, style: commandOptions.style, prefix: commandOptions.prefix, - mobile: commandOptions.mobile + mobile: commandOptions.mobile, + routing: commandOptions.routing }; if (!validProjectName(packageName)) { diff --git a/packages/angular-cli/commands/new.ts b/packages/angular-cli/commands/new.ts index 0c0d518744c7..a23886e60c9e 100644 --- a/packages/angular-cli/commands/new.ts +++ b/packages/angular-cli/commands/new.ts @@ -24,7 +24,8 @@ const NewCommand = Command.extend({ { name: 'source-dir', type: String, default: 'src', aliases: ['sd'] }, { name: 'style', type: String, default: 'css' }, { name: 'prefix', type: String, default: 'app', aliases: ['p'] }, - { name: 'mobile', type: Boolean, default: false } + { name: 'mobile', type: Boolean, default: false }, + { name: 'routing', type: Boolean, default: false } ], run: function (commandOptions: any, rawArgs: string[]) { diff --git a/packages/angular-cli/utilities/find-parent-module.ts b/packages/angular-cli/utilities/find-parent-module.ts index 7648560ad5ca..7a2273e19068 100644 --- a/packages/angular-cli/utilities/find-parent-module.ts +++ b/packages/angular-cli/utilities/find-parent-module.ts @@ -11,10 +11,9 @@ export default function findParentModule(project: any, currentDir: string): stri let pathToCheck = path.join(sourceRoot, currentDir); while (pathToCheck.length >= sourceRoot.length) { - // let files: string[] = fs.readdirSync(pathToCheck); - - // files = files.filter(file => file.indexOf('.module.ts') > 0); + // TODO: refactor to not be based upon file name const files = fs.readdirSync(pathToCheck) + .filter(fileName => !fileName.endsWith('routing.module.ts')) .filter(fileName => fileName.endsWith('.module.ts')) .filter(fileName => fs.statSync(path.join(pathToCheck, fileName)).isFile()); diff --git a/tests/e2e/tests/generate/module/module-routing.ts b/tests/e2e/tests/generate/module/module-routing.ts index d339ebf01740..96ab4d7f64dc 100644 --- a/tests/e2e/tests/generate/module/module-routing.ts +++ b/tests/e2e/tests/generate/module/module-routing.ts @@ -9,7 +9,7 @@ export default function() { return ng('generate', 'module', 'test-module', '--routing') .then(() => expectFileToExist(moduleDir)) .then(() => expectFileToExist(join(moduleDir, 'test-module.module.ts'))) - .then(() => expectFileToExist(join(moduleDir, 'test-module.routing.ts'))) + .then(() => expectFileToExist(join(moduleDir, 'test-module-routing.module.ts'))) .then(() => expectFileToExist(join(moduleDir, 'test-module.component.ts'))) .then(() => expectFileToExist(join(moduleDir, 'test-module.component.spec.ts'))) .then(() => expectFileToExist(join(moduleDir, 'test-module.component.html'))) From e8a51851d3b5d7cca45685e244c07780bcdfae6e Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Tue, 27 Sep 2016 16:48:21 +0100 Subject: [PATCH 15/35] chore(build): update node-zopfli (#2365) --- package.json | 2 +- packages/angular-cli/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0f061d2b7463..d76964ba35ce 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "awesome-typescript-loader": "^2.2.3", "chalk": "^1.1.3", "common-tags": "^1.3.1", - "compression-webpack-plugin": "^0.3.1", + "compression-webpack-plugin": "github:webpack/compression-webpack-plugin#7e55907cd54a2e91b96d25a660acc6a2a6453f54", "copy-webpack-plugin": "^3.0.1", "core-js": "^2.4.0", "css-loader": "^0.23.1", diff --git a/packages/angular-cli/package.json b/packages/angular-cli/package.json index 3ce25ae89fa4..b3ff8a171626 100644 --- a/packages/angular-cli/package.json +++ b/packages/angular-cli/package.json @@ -37,7 +37,7 @@ "awesome-typescript-loader": "^2.2.3", "chalk": "^1.1.3", "common-tags": "^1.3.1", - "compression-webpack-plugin": "^0.3.1", + "compression-webpack-plugin": "github:webpack/compression-webpack-plugin#7e55907cd54a2e91b96d25a660acc6a2a6453f54", "copy-webpack-plugin": "^3.0.1", "core-js": "^2.4.0", "css-loader": "^0.23.1", From 32839bba02333ca6cfc9c4cdb8ea8f157684047f Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Tue, 27 Sep 2016 17:23:29 +0100 Subject: [PATCH 16/35] fix(build): use config output path as default (#2158) --- packages/angular-cli/commands/build.ts | 4 ++-- packages/angular-cli/commands/serve.ts | 1 - packages/angular-cli/tasks/build-webpack.ts | 8 +++++--- tests/e2e/tests/build/output-dir.ts | 9 +++++++++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/angular-cli/commands/build.ts b/packages/angular-cli/commands/build.ts index 1072ed950d9e..b58acf7e8bf7 100644 --- a/packages/angular-cli/commands/build.ts +++ b/packages/angular-cli/commands/build.ts @@ -25,8 +25,8 @@ const BuildCommand = Command.extend({ aliases: ['t', { 'dev': 'development' }, { 'prod': 'production' }] }, { name: 'environment', type: String, default: '', aliases: ['e'] }, - { name: 'output-path', type: 'Path', default: 'dist/', aliases: ['o'] }, - { name: 'watch', type: Boolean, default: false, aliases: ['w'] }, + { name: 'output-path', type: 'Path', default: null, aliases: ['o'] }, + { name: 'watch', type: Boolean, default: false, aliases: ['w'] }, { name: 'watcher', type: String }, { name: 'suppress-sizes', type: Boolean, default: false }, { name: 'base-href', type: String, default: null, aliases: ['bh'] }, diff --git a/packages/angular-cli/commands/serve.ts b/packages/angular-cli/commands/serve.ts index c820102a2eb3..da67a503fb3a 100644 --- a/packages/angular-cli/commands/serve.ts +++ b/packages/angular-cli/commands/serve.ts @@ -22,7 +22,6 @@ export interface ServeTaskOptions { liveReloadLiveCss?: boolean; target?: string; environment?: string; - outputPath?: string; ssl?: boolean; sslKey?: string; sslCert?: string; diff --git a/packages/angular-cli/tasks/build-webpack.ts b/packages/angular-cli/tasks/build-webpack.ts index 47491c209722..148ee583dd5d 100644 --- a/packages/angular-cli/tasks/build-webpack.ts +++ b/packages/angular-cli/tasks/build-webpack.ts @@ -5,22 +5,24 @@ import * as webpack from 'webpack'; import { BuildOptions } from '../commands/build'; import { NgCliWebpackConfig } from '../models/webpack-config'; import { webpackOutputOptions } from '../models/'; +import { CliConfig } from '../models/config'; // Configure build and output; let lastHash: any = null; export default Task.extend({ - // Options: String outputPath run: function (runTaskOptions: BuildOptions) { const project = this.cliProject; - rimraf.sync(path.resolve(project.root, runTaskOptions.outputPath)); + const outputDir = runTaskOptions.outputPath || CliConfig.fromProject().config.apps[0].outDir; + rimraf.sync(path.resolve(project.root, outputDir)); + const config = new NgCliWebpackConfig( project, runTaskOptions.target, runTaskOptions.environment, - runTaskOptions.outputPath, + outputDir, runTaskOptions.baseHref ).config; diff --git a/tests/e2e/tests/build/output-dir.ts b/tests/e2e/tests/build/output-dir.ts index 837ee7f49ed0..0e34c8450fb9 100644 --- a/tests/e2e/tests/build/output-dir.ts +++ b/tests/e2e/tests/build/output-dir.ts @@ -2,11 +2,20 @@ import {ng} from '../../utils/process'; import {expectFileToExist} from '../../utils/fs'; import {expectToFail} from '../../utils/utils'; import {expectGitToBeClean} from '../../utils/git'; +import {updateJsonFile} from '../../utils/project'; export default function() { return ng('build', '-o', './build-output') .then(() => expectFileToExist('./build-output/index.html')) .then(() => expectFileToExist('./build-output/main.bundle.js')) + .then(() => expectToFail(expectGitToBeClean)) + .then(() => updateJsonFile('angular-cli.json', configJson => { + const app = configJson['apps'][0]; + app['outDir'] = 'config-build-output'; + })) + .then(() => ng('build')) + .then(() => expectFileToExist('./config-build-output/index.html')) + .then(() => expectFileToExist('./config-build-output/main.bundle.js')) .then(() => expectToFail(expectGitToBeClean)); } From b610c8e3fd6217dae8263131b3ce0f235f32b8fe Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Tue, 27 Sep 2016 18:21:05 +0100 Subject: [PATCH 17/35] chore(test): add failing build test (#2366) Add test for https://github.com/angular/angular-cli/pull/2360 --- tests/e2e/tests/build/fail-build.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/e2e/tests/build/fail-build.ts diff --git a/tests/e2e/tests/build/fail-build.ts b/tests/e2e/tests/build/fail-build.ts new file mode 100644 index 000000000000..3bbc8eff3158 --- /dev/null +++ b/tests/e2e/tests/build/fail-build.ts @@ -0,0 +1,9 @@ +import {ng} from '../../utils/process'; +import {expectToFail} from '../../utils/utils'; +import {deleteFile} from '../../utils/fs'; + +export default function() { + return deleteFile('src/app/app.component.ts') + // This is supposed to fail since there's a missing file + .then(() => expectToFail(() => ng('build'))); +} From 3aff7ad432dfeedd96c45fef19b26c4dbe922b12 Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Tue, 27 Sep 2016 19:39:09 +0100 Subject: [PATCH 18/35] fix(test): correctly report packages spec failures (#2238) --- package.json | 2 +- packages/ast-tools/src/ast-utils.ts | 8 ++++---- packages/ast-tools/src/route-utils.spec.ts | 2 +- packages/ast-tools/src/route-utils.ts | 4 ++-- scripts/run-packages-spec.js | 2 ++ tests/acceptance/generate-class.spec.js | 3 +-- tests/acceptance/generate-component.spec.js | 2 +- tests/acceptance/generate-directive.spec.js | 2 +- tests/acceptance/generate-pipe.spec.js | 2 +- 9 files changed, 14 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index d76964ba35ce..d9cf4d873e74 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "build": "node ./scripts/publish/build.js", "build:patch": "node ./scripts/patch.js", "build:packages": "for PKG in packages/*; do echo Building $PKG...; tsc -p $PKG; done", - "test": "npm run test:packages && npm run test:cli", + "test": "npm-run-all -c test:packages test:cli", "e2e": "npm run test:e2e", "e2e:nightly": "node tests/e2e_runner.js --nightly", "mobile_test": "mocha tests/e2e/e2e_workflow.spec.js", diff --git a/packages/ast-tools/src/ast-utils.ts b/packages/ast-tools/src/ast-utils.ts index 41fd8664c764..8d0980102116 100644 --- a/packages/ast-tools/src/ast-utils.ts +++ b/packages/ast-tools/src/ast-utils.ts @@ -222,8 +222,8 @@ function _addSymbolToNgModuleMetadata(ngModulePath: string, metadataField: strin position = node.getEnd(); // Get the indentation of the last element, if any. const text = node.getFullText(source); - if (text.startsWith('\n')) { - toInsert = `,${text.match(/^\n(\r?)\s+/)[0]}${metadataField}: [${symbolName}]`; + if (text.match('^\r?\r?\n')) { + toInsert = `,${text.match(/^\r?\n\s+/)[0]}${metadataField}: [${symbolName}]`; } else { toInsert = `, ${metadataField}: [${symbolName}]`; } @@ -235,8 +235,8 @@ function _addSymbolToNgModuleMetadata(ngModulePath: string, metadataField: strin } else { // Get the indentation of the last element, if any. const text = node.getFullText(source); - if (text.startsWith('\n')) { - toInsert = `,${text.match(/^\n(\r?)\s+/)[0]}${symbolName}`; + if (text.match(/^\r?\n/)) { + toInsert = `,${text.match(/^\r?\n(\r?)\s+/)[0]}${symbolName}`; } else { toInsert = `, ${symbolName}`; } diff --git a/packages/ast-tools/src/route-utils.spec.ts b/packages/ast-tools/src/route-utils.spec.ts index fb221f20013c..f1af932454e6 100644 --- a/packages/ast-tools/src/route-utils.spec.ts +++ b/packages/ast-tools/src/route-utils.spec.ts @@ -293,7 +293,7 @@ export default [\n { path: 'new-route', component: NewRouteComponent }\n];`); `\nexport default [\n` + ` { path: 'home', component: HomeComponent,\n` + ` children: [\n` + - ` { path: 'about/:id', component: AboutComponent } ` + + ` { path: 'about/:id', component: AboutComponent }` + `\n ]\n }\n];`); }); }); diff --git a/packages/ast-tools/src/route-utils.ts b/packages/ast-tools/src/route-utils.ts index db40e0a41766..3fd166b0edf8 100644 --- a/packages/ast-tools/src/route-utils.ts +++ b/packages/ast-tools/src/route-utils.ts @@ -416,11 +416,11 @@ function addChildPath (parentObject: ts.Node, pathOptions: any, route: string) { if (childrenNode.length !== 0) { // add to beginning of children array pos = childrenNode[0].getChildAt(2).getChildAt(1).pos; // open bracket - newContent = `\n${spaces}${content}, `; + newContent = `\n${spaces}${content},`; } else { // no children array, add one pos = parentObject.getChildAt(2).pos; // close brace - newContent = `,\n${spaces.substring(2)}children: [\n${spaces}${content} ` + + newContent = `,\n${spaces.substring(2)}children: [\n${spaces}${content}` + `\n${spaces.substring(2)}]\n${spaces.substring(5)}`; } return {newContent: newContent, pos: pos}; diff --git a/scripts/run-packages-spec.js b/scripts/run-packages-spec.js index 33b924b6ea81..987b3ea3d87e 100644 --- a/scripts/run-packages-spec.js +++ b/scripts/run-packages-spec.js @@ -14,6 +14,8 @@ const projectBaseDir = path.join(__dirname, '../packages'); const jasmine = new Jasmine({ projectBaseDir: projectBaseDir }); jasmine.loadConfig({}); jasmine.addReporter(new JasmineSpecReporter()); +// Manually set exit code (needed with custom reporters) +jasmine.onComplete((success) => process.exitCode = !success); // Run the tests. const allTests = diff --git a/tests/acceptance/generate-class.spec.js b/tests/acceptance/generate-class.spec.js index 9215bd32cfc9..d0c38f964135 100644 --- a/tests/acceptance/generate-class.spec.js +++ b/tests/acceptance/generate-class.spec.js @@ -17,6 +17,7 @@ describe('Acceptance: ng generate class', function () { after(conf.restore); beforeEach(function () { + this.timeout(10000); return tmp.setup('./tmp').then(function () { process.chdir('./tmp'); }).then(function () { @@ -25,8 +26,6 @@ describe('Acceptance: ng generate class', function () { }); afterEach(function () { - this.timeout(10000); - return tmp.teardown('./tmp'); }); diff --git a/tests/acceptance/generate-component.spec.js b/tests/acceptance/generate-component.spec.js index c5abd319faf7..6416ef2fff76 100644 --- a/tests/acceptance/generate-component.spec.js +++ b/tests/acceptance/generate-component.spec.js @@ -43,7 +43,7 @@ describe('Acceptance: ng generate component', function () { .then(content => { // Expect that the app.module contains a reference to my-comp and its import. expect(content).matches(/import.*MyCompComponent.*from '.\/my-comp\/my-comp.component';/); - expect(content).matches(/declarations:\s*\[[^\]]+?,\n\s+MyCompComponent\n/m); + expect(content).matches(/declarations:\s*\[[^\]]+?,\r?\n\s+MyCompComponent\r?\n/m); }); }); diff --git a/tests/acceptance/generate-directive.spec.js b/tests/acceptance/generate-directive.spec.js index 93398ee218ea..3620720b2018 100644 --- a/tests/acceptance/generate-directive.spec.js +++ b/tests/acceptance/generate-directive.spec.js @@ -51,7 +51,7 @@ describe('Acceptance: ng generate directive', function () { .then(() => readFile(appModulePath, 'utf-8')) .then(content => { expect(content).matches(/import.*\bMyDirDirective\b.*from '.\/my-dir\/my-dir.directive';/); - expect(content).matches(/declarations:\s*\[[^\]]+?,\n\s+MyDirDirective\n/m); + expect(content).matches(/declarations:\s*\[[^\]]+?,\r?\n\s+MyDirDirective\r?\n/m); }); }); diff --git a/tests/acceptance/generate-pipe.spec.js b/tests/acceptance/generate-pipe.spec.js index c6463697e2b0..2d98f8605895 100644 --- a/tests/acceptance/generate-pipe.spec.js +++ b/tests/acceptance/generate-pipe.spec.js @@ -44,7 +44,7 @@ describe('Acceptance: ng generate pipe', function () { .then(() => readFile(appModulePath, 'utf-8')) .then(content => { expect(content).matches(/import.*\bMyPipePipe\b.*from '.\/my-pipe.pipe';/); - expect(content).matches(/declarations:\s*\[[^\]]+?,\n\s+MyPipePipe\n/m); + expect(content).matches(/declarations:\s*\[[^\]]+?,\r?\n\s+MyPipePipe\r?\n/m); }); }); From 4d3be08a67185a1465cf3b426d23651db8a10562 Mon Sep 17 00:00:00 2001 From: Hans Date: Tue, 27 Sep 2016 18:59:33 -0700 Subject: [PATCH 19/35] feature(compiler): add support for AoT to the CLI. (#2333) Also adding a new package, webpack, which is a plugin and loader for webpack that adds support for AoT. It is behind a `--aot` flag in the CLI that is supported by build and serve. --- lib/bootstrap-local.js | 1 + lib/packages.js | 5 +- package.json | 1 + .../angular-cli/blueprints/component/index.js | 13 +- .../angular-cli/blueprints/directive/index.js | 3 +- packages/angular-cli/blueprints/pipe/index.js | 3 +- packages/angular-cli/commands/build.ts | 2 + packages/angular-cli/commands/serve.ts | 4 +- .../models/webpack-build-common.ts | 29 +-- .../models/webpack-build-typescript.ts | 63 +++++ packages/angular-cli/models/webpack-config.ts | 38 +-- packages/angular-cli/package.json | 2 + .../angular-cli/tasks/build-webpack-watch.ts | 3 +- packages/angular-cli/tasks/build-webpack.ts | 4 +- packages/angular-cli/tasks/serve-webpack.ts | 8 +- packages/angular-cli/tsconfig.json | 14 +- .../angular-cli/utilities/module-resolver.ts | 8 +- packages/ast-tools/src/ast-utils.spec.ts | 34 +-- packages/ast-tools/src/change.spec.ts | 29 ++- packages/ast-tools/src/change.ts | 44 ++-- packages/ast-tools/src/route-utils.spec.ts | 76 +++--- packages/ast-tools/src/route-utils.ts | 6 +- packages/webpack/package.json | 15 ++ packages/webpack/src/compiler.ts | 16 ++ packages/webpack/src/index.ts | 4 + packages/webpack/src/loader.ts | 149 ++++++++++++ packages/webpack/src/plugin.ts | 225 ++++++++++++++++++ packages/webpack/src/reflector_host.ts | 26 ++ packages/webpack/src/resource_loader.ts | 116 +++++++++ packages/webpack/src/utils.ts | 16 ++ packages/webpack/tsconfig.json | 28 +++ scripts/publish/build.js | 12 + .../webpack/test-app/app/app.component.html | 5 + .../webpack/test-app/app/app.component.scss | 3 + .../webpack/test-app/app/app.component.ts | 10 + .../assets/webpack/test-app/app/app.module.ts | 28 +++ .../test-app/app/feature/feature.module.ts | 20 ++ .../webpack/test-app/app/lazy.module.ts | 25 ++ .../assets/webpack/test-app/app/main.aot.ts | 5 + .../assets/webpack/test-app/app/main.jit.ts | 5 + tests/e2e/assets/webpack/test-app/index.html | 12 + .../e2e/assets/webpack/test-app/package.json | 26 ++ .../e2e/assets/webpack/test-app/tsconfig.json | 23 ++ .../assets/webpack/test-app/webpack.config.js | 31 +++ tests/e2e/setup/100-npm-link.ts | 23 +- tests/e2e/setup/200-create-tmp-dir.ts | 5 +- tests/e2e/setup/300-log-environment.ts | 21 ++ tests/e2e/setup/500-create-project.ts | 9 +- tests/e2e/tests/build/styles/less.ts | 2 +- tests/e2e/tests/build/styles/scss.ts | 4 +- tests/e2e/tests/packages/webpack/test.ts | 29 +++ tests/e2e/utils/assets.ts | 29 +++ tests/e2e/utils/ast.ts | 7 +- tests/e2e/utils/env.ts | 13 + tests/e2e/utils/fs.ts | 47 +++- tests/e2e/utils/process.ts | 29 ++- tests/e2e_runner.js | 19 +- tsconfig.json | 3 +- 58 files changed, 1258 insertions(+), 172 deletions(-) create mode 100644 packages/angular-cli/models/webpack-build-typescript.ts create mode 100644 packages/webpack/package.json create mode 100644 packages/webpack/src/compiler.ts create mode 100644 packages/webpack/src/index.ts create mode 100644 packages/webpack/src/loader.ts create mode 100644 packages/webpack/src/plugin.ts create mode 100644 packages/webpack/src/reflector_host.ts create mode 100644 packages/webpack/src/resource_loader.ts create mode 100644 packages/webpack/src/utils.ts create mode 100644 packages/webpack/tsconfig.json create mode 100644 tests/e2e/assets/webpack/test-app/app/app.component.html create mode 100644 tests/e2e/assets/webpack/test-app/app/app.component.scss create mode 100644 tests/e2e/assets/webpack/test-app/app/app.component.ts create mode 100644 tests/e2e/assets/webpack/test-app/app/app.module.ts create mode 100644 tests/e2e/assets/webpack/test-app/app/feature/feature.module.ts create mode 100644 tests/e2e/assets/webpack/test-app/app/lazy.module.ts create mode 100644 tests/e2e/assets/webpack/test-app/app/main.aot.ts create mode 100644 tests/e2e/assets/webpack/test-app/app/main.jit.ts create mode 100644 tests/e2e/assets/webpack/test-app/index.html create mode 100644 tests/e2e/assets/webpack/test-app/package.json create mode 100644 tests/e2e/assets/webpack/test-app/tsconfig.json create mode 100644 tests/e2e/assets/webpack/test-app/webpack.config.js create mode 100644 tests/e2e/setup/300-log-environment.ts create mode 100644 tests/e2e/tests/packages/webpack/test.ts create mode 100644 tests/e2e/utils/assets.ts create mode 100644 tests/e2e/utils/env.ts diff --git a/lib/bootstrap-local.js b/lib/bootstrap-local.js index 1712a756116c..34b7a28c7a47 100644 --- a/lib/bootstrap-local.js +++ b/lib/bootstrap-local.js @@ -7,6 +7,7 @@ const ts = require('typescript'); global.angularCliIsLocal = true; +global.angularCliPackages = require('./packages'); const compilerOptions = JSON.parse(fs.readFileSync(path.join(__dirname, '../tsconfig.json'))); diff --git a/lib/packages.js b/lib/packages.js index ccb0e67215d8..204f71dab448 100644 --- a/lib/packages.js +++ b/lib/packages.js @@ -11,8 +11,11 @@ const packages = fs.readdirSync(packageRoot) .map(pkgName => ({ name: pkgName, root: path.join(packageRoot, pkgName) })) .filter(pkg => fs.statSync(pkg.root).isDirectory()) .reduce((packages, pkg) => { - let name = pkg == 'angular-cli' ? 'angular-cli' : `@angular-cli/${pkg.name}`; + let pkgJson = JSON.parse(fs.readFileSync(path.join(pkg.root, 'package.json'), 'utf8')); + let name = pkgJson['name']; packages[name] = { + dist: path.join(__dirname, '../dist', pkg.name), + packageJson: path.join(pkg.root, 'package.json'), root: pkg.root, main: path.resolve(pkg.root, 'src/index.ts') }; diff --git a/package.json b/package.json index d9cf4d873e74..8175a2c0bd5d 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "fs.realpath": "^1.0.0", "glob": "^7.0.3", "handlebars": "^4.0.5", + "html-loader": "^0.4.4", "html-webpack-plugin": "^2.19.0", "istanbul-instrumenter-loader": "^0.2.0", "json-loader": "^0.5.4", diff --git a/packages/angular-cli/blueprints/component/index.js b/packages/angular-cli/blueprints/component/index.js index 3f671370397e..e037aa260839 100644 --- a/packages/angular-cli/blueprints/component/index.js +++ b/packages/angular-cli/blueprints/component/index.js @@ -1,11 +1,12 @@ -var path = require('path'); -var chalk = require('chalk'); -var Blueprint = require('ember-cli/lib/models/blueprint'); -var dynamicPathParser = require('../../utilities/dynamic-path-parser'); +const path = require('path'); +const chalk = require('chalk'); +const Blueprint = require('ember-cli/lib/models/blueprint'); +const dynamicPathParser = require('../../utilities/dynamic-path-parser'); const findParentModule = require('../../utilities/find-parent-module').default; -var getFiles = Blueprint.prototype.files; +const getFiles = Blueprint.prototype.files; const stringUtils = require('ember-cli-string-utils'); const astUtils = require('../../utilities/ast-utils'); +const NodeHost = require('@angular-cli/ast-tools').NodeHost; module.exports = { description: '', @@ -117,7 +118,7 @@ module.exports = { if (!options['skip-import']) { returns.push( astUtils.addDeclarationToModule(this.pathToModule, className, importPath) - .then(change => change.apply())); + .then(change => change.apply(NodeHost))); } return Promise.all(returns); diff --git a/packages/angular-cli/blueprints/directive/index.js b/packages/angular-cli/blueprints/directive/index.js index db8adc6487bc..60d029aabbab 100644 --- a/packages/angular-cli/blueprints/directive/index.js +++ b/packages/angular-cli/blueprints/directive/index.js @@ -3,6 +3,7 @@ var dynamicPathParser = require('../../utilities/dynamic-path-parser'); const stringUtils = require('ember-cli-string-utils'); const astUtils = require('../../utilities/ast-utils'); const findParentModule = require('../../utilities/find-parent-module').default; +const NodeHost = require('@angular-cli/ast-tools').NodeHost; module.exports = { description: '', @@ -73,7 +74,7 @@ module.exports = { if (!options['skip-import']) { returns.push( astUtils.addDeclarationToModule(this.pathToModule, className, importPath) - .then(change => change.apply())); + .then(change => change.apply(NodeHost))); } return Promise.all(returns); diff --git a/packages/angular-cli/blueprints/pipe/index.js b/packages/angular-cli/blueprints/pipe/index.js index 13f4d0902891..bc4b0710ca60 100644 --- a/packages/angular-cli/blueprints/pipe/index.js +++ b/packages/angular-cli/blueprints/pipe/index.js @@ -3,6 +3,7 @@ var dynamicPathParser = require('../../utilities/dynamic-path-parser'); const stringUtils = require('ember-cli-string-utils'); const astUtils = require('../../utilities/ast-utils'); const findParentModule = require('../../utilities/find-parent-module').default; +const NodeHost = require('@angular-cli/ast-tools').NodeHost; module.exports = { description: '', @@ -61,7 +62,7 @@ module.exports = { if (!options['skip-import']) { returns.push( astUtils.addDeclarationToModule(this.pathToModule, className, importPath) - .then(change => change.apply())); + .then(change => change.apply(NodeHost))); } return Promise.all(returns); diff --git a/packages/angular-cli/commands/build.ts b/packages/angular-cli/commands/build.ts index b58acf7e8bf7..744b405fe567 100644 --- a/packages/angular-cli/commands/build.ts +++ b/packages/angular-cli/commands/build.ts @@ -10,6 +10,7 @@ export interface BuildOptions { watcher?: string; supressSizes: boolean; baseHref?: string; + aot?: boolean; } const BuildCommand = Command.extend({ @@ -30,6 +31,7 @@ const BuildCommand = Command.extend({ { name: 'watcher', type: String }, { name: 'suppress-sizes', type: Boolean, default: false }, { name: 'base-href', type: String, default: null, aliases: ['bh'] }, + { name: 'aot', type: Boolean, default: false } ], run: function (commandOptions: BuildOptions) { diff --git a/packages/angular-cli/commands/serve.ts b/packages/angular-cli/commands/serve.ts index da67a503fb3a..936a341b4cfc 100644 --- a/packages/angular-cli/commands/serve.ts +++ b/packages/angular-cli/commands/serve.ts @@ -25,6 +25,7 @@ export interface ServeTaskOptions { ssl?: boolean; sslKey?: string; sslCert?: string; + aot?: boolean; } const ServeCommand = Command.extend({ @@ -77,7 +78,8 @@ const ServeCommand = Command.extend({ { name: 'environment', type: String, default: '', aliases: ['e'] }, { name: 'ssl', type: Boolean, default: false }, { name: 'ssl-key', type: String, default: 'ssl/server.key' }, - { name: 'ssl-cert', type: String, default: 'ssl/server.crt' } + { name: 'ssl-cert', type: String, default: 'ssl/server.crt' }, + { name: 'aot', type: Boolean, default: false } ], run: function(commandOptions: ServeTaskOptions) { diff --git a/packages/angular-cli/models/webpack-build-common.ts b/packages/angular-cli/models/webpack-build-common.ts index 6de3b9d3f6c0..a11514fcd1db 100644 --- a/packages/angular-cli/models/webpack-build-common.ts +++ b/packages/angular-cli/models/webpack-build-common.ts @@ -1,11 +1,9 @@ +import * as webpack from 'webpack'; import * as path from 'path'; +import {BaseHrefWebpackPlugin} from '@angular-cli/base-href-webpack'; + const CopyWebpackPlugin = require('copy-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); -import * as webpack from 'webpack'; -const atl = require('awesome-typescript-loader'); - -import { BaseHrefWebpackPlugin } from '@angular-cli/base-href-webpack'; -import { findLazyModules } from './find-lazy-modules'; export function getWebpackCommonConfig( @@ -23,7 +21,6 @@ export function getWebpackCommonConfig( const scripts = appConfig.scripts ? appConfig.scripts.map((script: string) => path.resolve(appRoot, script)) : []; - const lazyModules = findLazyModules(appRoot); let entry: { [key: string]: string[] } = { main: [appMain] @@ -56,21 +53,7 @@ export function getWebpackCommonConfig( } ], loaders: [ - { - test: /\.ts$/, - loaders: [ - { - loader: 'awesome-typescript-loader', - query: { - useForkChecker: true, - tsconfig: path.resolve(appRoot, appConfig.tsconfig) - } - }, { - loader: 'angular2-template-loader' - } - ], - exclude: [/\.(spec|e2e)\.ts$/] - }, + // TypeScript loaders are separated into webpack-build-typescript. // in main, load css as raw text        { @@ -115,7 +98,7 @@ export function getWebpackCommonConfig(        { test: /\.json$/, loader: 'json-loader' },        { test: /\.(jpg|png|gif)$/, loader: 'url-loader?limit=10000' }, -        { test: /\.html$/, loader: 'raw-loader' }, +        { test: /\.html$/, loader: 'html-loader' }, { test: /\.(otf|woff|ttf|svg)$/, loader: 'url?limit=10000' }, { test: /\.woff2$/, loader: 'url?limit=10000&mimetype=font/woff2' }, @@ -123,8 +106,6 @@ export function getWebpackCommonConfig( ] }, plugins: [ - new webpack.ContextReplacementPlugin(/.*/, appRoot, lazyModules), - new atl.ForkCheckerPlugin(), new HtmlWebpackPlugin({ template: path.resolve(appRoot, appConfig.index), chunksSortMode: 'dependency' diff --git a/packages/angular-cli/models/webpack-build-typescript.ts b/packages/angular-cli/models/webpack-build-typescript.ts new file mode 100644 index 000000000000..9ed49ecfc9ab --- /dev/null +++ b/packages/angular-cli/models/webpack-build-typescript.ts @@ -0,0 +1,63 @@ +import * as path from 'path'; +import * as webpack from 'webpack'; +import {findLazyModules} from './find-lazy-modules'; +import {NgcWebpackPlugin} from '@ngtools/webpack'; + +const atl = require('awesome-typescript-loader'); + +const g: any = global; +const webpackLoader: string = g['angularCliIsLocal'] + ? g.angularCliPackages['@ngtools/webpack'].main + : '@ngtools/webpack'; + + +export const getWebpackNonAotConfigPartial = function(projectRoot: string, appConfig: any) { + const appRoot = path.resolve(projectRoot, appConfig.root); + const lazyModules = findLazyModules(appRoot); + + return { + module: { + loaders: [ + { + test: /\.ts$/, + loaders: [{ + loader: 'awesome-typescript-loader', + query: { + useForkChecker: true, + tsconfig: path.resolve(appRoot, appConfig.tsconfig) + } + }, { + loader: 'angular2-template-loader' + }], + exclude: [/\.(spec|e2e)\.ts$/] + } + ], + }, + plugins: [ + new webpack.ContextReplacementPlugin(/.*/, appRoot, lazyModules), + new atl.ForkCheckerPlugin(), + ] + }; +}; + +export const getWebpackAotConfigPartial = function(projectRoot: string, appConfig: any) { + return { + module: { + loaders: [ + { + test: /\.ts$/, + loader: webpackLoader, + exclude: [/\.(spec|e2e)\.ts$/] + } + ] + }, + plugins: [ + new NgcWebpackPlugin({ + project: path.resolve(projectRoot, appConfig.root, appConfig.tsconfig), + baseDir: path.resolve(projectRoot, ''), + entryModule: path.join(projectRoot, appConfig.root, 'app/app.module#AppModule'), + genDir: path.join(projectRoot, appConfig.outDir, 'ngfactory') + }), + ] + }; +}; diff --git a/packages/angular-cli/models/webpack-config.ts b/packages/angular-cli/models/webpack-config.ts index 85c8c90fea54..6317fef937a3 100644 --- a/packages/angular-cli/models/webpack-config.ts +++ b/packages/angular-cli/models/webpack-config.ts @@ -1,3 +1,7 @@ +import { + getWebpackAotConfigPartial, + getWebpackNonAotConfigPartial +} from './webpack-build-typescript'; const webpackMerge = require('webpack-merge'); import { CliConfig } from './config'; import { @@ -12,50 +16,54 @@ export class NgCliWebpackConfig { // TODO: When webpack2 types are finished lets replace all these any types // so this is more maintainable in the future for devs public config: any; - private devConfigPartial: any; - private prodConfigPartial: any; - private baseConfig: any; constructor( public ngCliProject: any, public target: string, public environment: string, outputDir?: string, - baseHref?: string + baseHref?: string, + isAoT = false ) { const config: CliConfig = CliConfig.fromProject(); const appConfig = config.config.apps[0]; appConfig.outDir = outputDir || appConfig.outDir; - this.baseConfig = getWebpackCommonConfig( + let baseConfig = getWebpackCommonConfig( this.ngCliProject.root, environment, appConfig, baseHref ); - this.devConfigPartial = getWebpackDevConfigPartial(this.ngCliProject.root, appConfig); - this.prodConfigPartial = getWebpackProdConfigPartial(this.ngCliProject.root, appConfig); + let targetConfigPartial = this.getTargetConfig(this.ngCliProject.root, appConfig); + const typescriptConfigPartial = isAoT + ? getWebpackAotConfigPartial(this.ngCliProject.root, appConfig) + : getWebpackNonAotConfigPartial(this.ngCliProject.root, appConfig); if (appConfig.mobile) { let mobileConfigPartial = getWebpackMobileConfigPartial(this.ngCliProject.root, appConfig); let mobileProdConfigPartial = getWebpackMobileProdConfigPartial(this.ngCliProject.root, appConfig); - this.baseConfig = webpackMerge(this.baseConfig, mobileConfigPartial); - this.prodConfigPartial = webpackMerge(this.prodConfigPartial, mobileProdConfigPartial); + baseConfig = webpackMerge(baseConfig, mobileConfigPartial); + if (this.target == 'production') { + targetConfigPartial = webpackMerge(targetConfigPartial, mobileProdConfigPartial); + } } - this.generateConfig(); + this.config = webpackMerge( + baseConfig, + targetConfigPartial, + typescriptConfigPartial + ); } - generateConfig(): void { + getTargetConfig(projectRoot: string, appConfig: any): any { switch (this.target) { case 'development': - this.config = webpackMerge(this.baseConfig, this.devConfigPartial); - break; + return getWebpackDevConfigPartial(projectRoot, appConfig); case 'production': - this.config = webpackMerge(this.baseConfig, this.prodConfigPartial); - break; + return getWebpackProdConfigPartial(projectRoot, appConfig); default: throw new Error("Invalid build target. Only 'development' and 'production' are available."); } diff --git a/packages/angular-cli/package.json b/packages/angular-cli/package.json index b3ff8a171626..55752eadc976 100644 --- a/packages/angular-cli/package.json +++ b/packages/angular-cli/package.json @@ -33,6 +33,7 @@ "@angular/platform-browser": "^2.0.0", "@angular/platform-server": "^2.0.0", "@angular/tsc-wrapped": "^0.3.0", + "@ngtools/webpack": "latest", "angular2-template-loader": "^0.5.0", "awesome-typescript-loader": "^2.2.3", "chalk": "^1.1.3", @@ -53,6 +54,7 @@ "fs.realpath": "^1.0.0", "glob": "^7.0.3", "handlebars": "^4.0.5", + "html-loader": "^0.4.4", "html-webpack-plugin": "^2.19.0", "istanbul-instrumenter-loader": "^0.2.0", "json-loader": "^0.5.4", diff --git a/packages/angular-cli/tasks/build-webpack-watch.ts b/packages/angular-cli/tasks/build-webpack-watch.ts index a85d4f1112c5..008168dac399 100644 --- a/packages/angular-cli/tasks/build-webpack-watch.ts +++ b/packages/angular-cli/tasks/build-webpack-watch.ts @@ -21,7 +21,8 @@ export default Task.extend({ runTaskOptions.target, runTaskOptions.environment, runTaskOptions.outputPath, - runTaskOptions.baseHref + runTaskOptions.baseHref, + runTaskOptions.aot ).config; const webpackCompiler: any = webpack(config); diff --git a/packages/angular-cli/tasks/build-webpack.ts b/packages/angular-cli/tasks/build-webpack.ts index 148ee583dd5d..88941955247b 100644 --- a/packages/angular-cli/tasks/build-webpack.ts +++ b/packages/angular-cli/tasks/build-webpack.ts @@ -17,13 +17,13 @@ export default Task.extend({ const outputDir = runTaskOptions.outputPath || CliConfig.fromProject().config.apps[0].outDir; rimraf.sync(path.resolve(project.root, outputDir)); - const config = new NgCliWebpackConfig( project, runTaskOptions.target, runTaskOptions.environment, outputDir, - runTaskOptions.baseHref + runTaskOptions.baseHref, + runTaskOptions.aot ).config; // fail on build error diff --git a/packages/angular-cli/tasks/serve-webpack.ts b/packages/angular-cli/tasks/serve-webpack.ts index c87960bf831a..4166f25486b2 100644 --- a/packages/angular-cli/tasks/serve-webpack.ts +++ b/packages/angular-cli/tasks/serve-webpack.ts @@ -19,8 +19,12 @@ export default Task.extend({ let webpackCompiler: any; let config = new NgCliWebpackConfig( - this.project, commandOptions.target, - commandOptions.environment + this.project, + commandOptions.target, + commandOptions.environment, + undefined, + undefined, + commandOptions.aot ).config; // This allows for live reload of page when changes are made to repo. diff --git a/packages/angular-cli/tsconfig.json b/packages/angular-cli/tsconfig.json index 0e08554fd4c8..9623f183e150 100644 --- a/packages/angular-cli/tsconfig.json +++ b/packages/angular-cli/tsconfig.json @@ -8,22 +8,26 @@ "moduleResolution": "node", "noEmitOnError": true, "noImplicitAny": true, - "outDir": "../../dist/", - "rootDir": "..", + "outDir": "../../dist/angular-cli", + "rootDir": ".", "sourceMap": true, "sourceRoot": "/", "target": "es5", "lib": ["es6"], + "skipLibCheck": true, "typeRoots": [ "../../node_modules/@types" ], "baseUrl": "", "paths": { - "@angular-cli/ast-tools": [ "../../packages/ast-tools/src" ], - "@angular-cli/base-href-webpack": [ "../../packages/base-href-webpack/src" ], - "@angular-cli/webpack": [ "../../packages/webpack/src" ] + "@angular-cli/ast-tools": [ "../../dist/ast-tools/src" ], + "@angular-cli/base-href-webpack": [ "../../dist/base-href-webpack/src" ], + "@ngtools/webpack": [ "../../dist/webpack/src" ] } }, + "include": [ + "**/*" + ], "exclude": [ "blueprints/*/files/**/*" ] diff --git a/packages/angular-cli/utilities/module-resolver.ts b/packages/angular-cli/utilities/module-resolver.ts index 3b889116b53d..020b25a11389 100644 --- a/packages/angular-cli/utilities/module-resolver.ts +++ b/packages/angular-cli/utilities/module-resolver.ts @@ -5,7 +5,8 @@ import * as ts from 'typescript'; import * as dependentFilesUtils from './get-dependent-files'; -import { Change, ReplaceChange } from './change'; +import {Change, ReplaceChange} from './change'; +import {NodeHost, Host} from '@angular-cli/ast-tools'; /** * Rewrites import module of dependent files when the file is moved. @@ -21,13 +22,14 @@ export class ModuleResolver { * then apply() method is called sequentially. * * @param changes {Change []} + * @param host {Host} * @return Promise after all apply() method of Change class is called * to all Change instances sequentially. */ - applySortedChangePromise(changes: Change[]): Promise { + applySortedChangePromise(changes: Change[], host: Host = NodeHost): Promise { return changes .sort((currentChange, nextChange) => nextChange.order - currentChange.order) - .reduce((newChange, change) => newChange.then(() => change.apply()), Promise.resolve()); + .reduce((newChange, change) => newChange.then(() => change.apply(host)), Promise.resolve()); } /** diff --git a/packages/ast-tools/src/ast-utils.spec.ts b/packages/ast-tools/src/ast-utils.spec.ts index 001ff9a9beab..17c0444e417b 100644 --- a/packages/ast-tools/src/ast-utils.spec.ts +++ b/packages/ast-tools/src/ast-utils.spec.ts @@ -3,7 +3,7 @@ import mockFs = require('mock-fs'); import ts = require('typescript'); import fs = require('fs'); -import {InsertChange, RemoveChange} from './change'; +import {InsertChange, NodeHost, RemoveChange} from './change'; import {insertAfterLastOccurrence, addDeclarationToModule} from './ast-utils'; import {findNodes} from './node'; import {it} from './spec-utils'; @@ -31,7 +31,7 @@ describe('ast-utils: findNodes', () => { it('finds no imports', () => { let editedFile = new RemoveChange(sourceFile, 0, `import * as myTest from 'tests' \n`); return editedFile - .apply() + .apply(NodeHost) .then(() => { let rootNode = getRootNode(sourceFile); let nodes = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration); @@ -47,10 +47,10 @@ describe('ast-utils: findNodes', () => { // remove new line and add an inline import let editedFile = new RemoveChange(sourceFile, 32, '\n'); return editedFile - .apply() + .apply(NodeHost) .then(() => { let insert = new InsertChange(sourceFile, 32, `import {Routes} from '@angular/routes'`); - return insert.apply(); + return insert.apply(NodeHost); }) .then(() => { let rootNode = getRootNode(sourceFile); @@ -61,7 +61,7 @@ describe('ast-utils: findNodes', () => { it('finds two imports from new line separated declarations', () => { let editedFile = new InsertChange(sourceFile, 33, `import {Routes} from '@angular/routes'`); return editedFile - .apply() + .apply(NodeHost) .then(() => { let rootNode = getRootNode(sourceFile); let nodes = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration); @@ -89,7 +89,7 @@ describe('ast-utils: insertAfterLastOccurrence', () => { let imports = getNodesOfKind(ts.SyntaxKind.ImportDeclaration, sourceFile); return insertAfterLastOccurrence(imports, `\nimport { Router } from '@angular/router';`, sourceFile, 0) - .apply() + .apply(NodeHost) .then(() => { return readFile(sourceFile, 'utf8'); }).then((content) => { @@ -106,12 +106,12 @@ describe('ast-utils: insertAfterLastOccurrence', () => { let content = `import { foo, bar } from 'fizz';`; let editedFile = new InsertChange(sourceFile, 0, content); return editedFile - .apply() + .apply(NodeHost) .then(() => { let imports = getNodesOfKind(ts.SyntaxKind.ImportDeclaration, sourceFile); return insertAfterLastOccurrence(imports, ', baz', sourceFile, 0, ts.SyntaxKind.Identifier) - .apply(); + .apply(NodeHost); }) .then(() => { return readFile(sourceFile, 'utf8'); @@ -122,12 +122,12 @@ describe('ast-utils: insertAfterLastOccurrence', () => { let content = `import * from 'foo' \n import { bar } from 'baz'`; let editedFile = new InsertChange(sourceFile, 0, content); return editedFile - .apply() + .apply(NodeHost) .then(() => { let imports = getNodesOfKind(ts.SyntaxKind.ImportDeclaration, sourceFile); return insertAfterLastOccurrence(imports, `\nimport Router from '@angular/router'`, sourceFile) - .apply(); + .apply(NodeHost); }) .then(() => { return readFile(sourceFile, 'utf8'); @@ -142,12 +142,12 @@ describe('ast-utils: insertAfterLastOccurrence', () => { let content = `import {} from 'foo'`; let editedFile = new InsertChange(sourceFile, 0, content); return editedFile - .apply() + .apply(NodeHost) .then(() => { let imports = getNodesOfKind(ts.SyntaxKind.ImportDeclaration, sourceFile); return insertAfterLastOccurrence(imports, ', bar', sourceFile, undefined, ts.SyntaxKind.Identifier) - .apply(); + .apply(NodeHost); }) .catch(() => { return readFile(sourceFile, 'utf8'); @@ -160,7 +160,7 @@ describe('ast-utils: insertAfterLastOccurrence', () => { ts.SyntaxKind.CloseBraceToken).pop().pos; return insertAfterLastOccurrence(imports, ' bar ', sourceFile, pos, ts.SyntaxKind.Identifier) - .apply(); + .apply(NodeHost); }) .then(() => { return readFile(sourceFile, 'utf8'); @@ -211,7 +211,7 @@ class Module {}` it('works with empty array', () => { return addDeclarationToModule('1.ts', 'MyClass', 'MyImportPath') - .then(change => change.apply()) + .then(change => change.apply(NodeHost)) .then(() => readFile('1.ts', 'utf-8')) .then(content => { expect(content).toEqual( @@ -229,7 +229,7 @@ class Module {}` it('works with array with declarations', () => { return addDeclarationToModule('2.ts', 'MyClass', 'MyImportPath') - .then(change => change.apply()) + .then(change => change.apply(NodeHost)) .then(() => readFile('2.ts', 'utf-8')) .then(content => { expect(content).toEqual( @@ -250,7 +250,7 @@ class Module {}` it('works without any declarations', () => { return addDeclarationToModule('3.ts', 'MyClass', 'MyImportPath') - .then(change => change.apply()) + .then(change => change.apply(NodeHost)) .then(() => readFile('3.ts', 'utf-8')) .then(content => { expect(content).toEqual( @@ -268,7 +268,7 @@ class Module {}` it('works without a declaration field', () => { return addDeclarationToModule('4.ts', 'MyClass', 'MyImportPath') - .then(change => change.apply()) + .then(change => change.apply(NodeHost)) .then(() => readFile('4.ts', 'utf-8')) .then(content => { expect(content).toEqual( diff --git a/packages/ast-tools/src/change.spec.ts b/packages/ast-tools/src/change.spec.ts index d4abb85d5aad..0bebb3230169 100644 --- a/packages/ast-tools/src/change.spec.ts +++ b/packages/ast-tools/src/change.spec.ts @@ -4,7 +4,7 @@ let mockFs = require('mock-fs'); import {it} from './spec-utils'; -import {InsertChange, RemoveChange, ReplaceChange} from './change'; +import {InsertChange, NodeHost, RemoveChange, ReplaceChange} from './change'; import fs = require('fs'); let path = require('path'); @@ -35,7 +35,7 @@ describe('Change', () => { it('adds text to the source code', () => { let changeInstance = new InsertChange(sourceFile, 6, ' world!'); return changeInstance - .apply() + .apply(NodeHost) .then(() => readFile(sourceFile, 'utf8')) .then(contents => { expect(contents).toEqual('hello world!'); @@ -47,7 +47,7 @@ describe('Change', () => { it('adds nothing in the source code if empty string is inserted', () => { let changeInstance = new InsertChange(sourceFile, 6, ''); return changeInstance - .apply() + .apply(NodeHost) .then(() => readFile(sourceFile, 'utf8')) .then(contents => { expect(contents).toEqual('hello'); @@ -61,7 +61,7 @@ describe('Change', () => { it('removes given text from the source code', () => { let changeInstance = new RemoveChange(sourceFile, 9, 'as foo'); return changeInstance - .apply() + .apply(NodeHost) .then(() => readFile(sourceFile, 'utf8')) .then(contents => { expect(contents).toEqual('import * from "./bar"'); @@ -73,7 +73,7 @@ describe('Change', () => { it('does not change the file if told to remove empty string', () => { let changeInstance = new RemoveChange(sourceFile, 9, ''); return changeInstance - .apply() + .apply(NodeHost) .then(() => readFile(sourceFile, 'utf8')) .then(contents => { expect(contents).toEqual('import * as foo from "./bar"'); @@ -86,7 +86,7 @@ describe('Change', () => { let sourceFile = path.join(sourcePath, 'remove-replace-file.txt'); let changeInstance = new ReplaceChange(sourceFile, 7, '* as foo', '{ fooComponent }'); return changeInstance - .apply() + .apply(NodeHost) .then(() => readFile(sourceFile, 'utf8')) .then(contents => { expect(contents).toEqual('import { fooComponent } from "./bar"'); @@ -96,11 +96,22 @@ describe('Change', () => { let sourceFile = path.join(sourcePath, 'remove-replace-file.txt'); expect(() => new ReplaceChange(sourceFile, -6, 'hello', ' world!')).toThrow(); }); + it('fails for invalid replacement', () => { + let sourceFile = path.join(sourcePath, 'replace-file.txt'); + let changeInstance = new ReplaceChange(sourceFile, 0, 'foobar', ''); + return changeInstance + .apply(NodeHost) + .then(() => expect(false).toBe(true), err => { + // Check that the message contains the string to replace and the string from the file. + expect(err.message).toContain('foobar'); + expect(err.message).toContain('import'); + }); + }); it('adds string to the position of an empty string', () => { let sourceFile = path.join(sourcePath, 'replace-file.txt'); let changeInstance = new ReplaceChange(sourceFile, 9, '', 'BarComponent, '); return changeInstance - .apply() + .apply(NodeHost) .then(() => readFile(sourceFile, 'utf8')) .then(contents => { expect(contents).toEqual('import { BarComponent, FooComponent } from "./baz"'); @@ -108,9 +119,9 @@ describe('Change', () => { }); it('removes the given string only if an empty string to add is given', () => { let sourceFile = path.join(sourcePath, 'remove-replace-file.txt'); - let changeInstance = new ReplaceChange(sourceFile, 9, ' as foo', ''); + let changeInstance = new ReplaceChange(sourceFile, 8, ' as foo', ''); return changeInstance - .apply() + .apply(NodeHost) .then(() => readFile(sourceFile, 'utf8')) .then(contents => { expect(contents).toEqual('import * from "./bar"'); diff --git a/packages/ast-tools/src/change.ts b/packages/ast-tools/src/change.ts index dde0bc5523a4..14e537575b3c 100644 --- a/packages/ast-tools/src/change.ts +++ b/packages/ast-tools/src/change.ts @@ -4,8 +4,19 @@ import denodeify = require('denodeify'); const readFile = (denodeify(fs.readFile) as (...args: any[]) => Promise); const writeFile = (denodeify(fs.writeFile) as (...args: any[]) => Promise); +export interface Host { + write(path: string, content: string): Promise; + read(path: string): Promise; +} + +export const NodeHost: Host = { + write: (path: string, content: string) => writeFile(path, content, 'utf8'), + read: (path: string) => readFile(path, 'utf8') +}; + + export interface Change { - apply(): Promise; + apply(host: Host): Promise; // The file this change should be applied to. Some changes might not apply to // a file (maybe the config). @@ -61,11 +72,11 @@ export class MultiChange implements Change { get order() { return Math.max(...this._changes.map(c => c.order)); } get path() { return this._path; } - apply() { + apply(host: Host) { return this._changes .sort((a: Change, b: Change) => b.order - a.order) .reduce((promise, change) => { - return promise.then(() => change.apply()); + return promise.then(() => change.apply(host)); }, Promise.resolve()); } } @@ -90,11 +101,11 @@ export class InsertChange implements Change { /** * This method does not insert spaces if there is none in the original string. */ - apply(): Promise { - return readFile(this.path, 'utf8').then(content => { + apply(host: Host): Promise { + return host.read(this.path).then(content => { let prefix = content.substring(0, this.pos); let suffix = content.substring(this.pos); - return writeFile(this.path, `${prefix}${this.toAdd}${suffix}`); + return host.write(this.path, `${prefix}${this.toAdd}${suffix}`); }); } } @@ -115,12 +126,12 @@ export class RemoveChange implements Change { this.order = pos; } - apply(): Promise { - return readFile(this.path, 'utf8').then(content => { + apply(host: Host): Promise { + return host.read(this.path).then(content => { let prefix = content.substring(0, this.pos); let suffix = content.substring(this.pos + this.toRemove.length); // TODO: throw error if toRemove doesn't match removed string. - return writeFile(this.path, `${prefix}${suffix}`); + return host.write(this.path, `${prefix}${suffix}`); }); } } @@ -141,12 +152,17 @@ export class ReplaceChange implements Change { this.order = pos; } - apply(): Promise { - return readFile(this.path, 'utf8').then(content => { - let prefix = content.substring(0, this.pos); - let suffix = content.substring(this.pos + this.oldText.length); + apply(host: Host): Promise { + return host.read(this.path).then(content => { + const prefix = content.substring(0, this.pos); + const suffix = content.substring(this.pos + this.oldText.length); + const text = content.substring(this.pos, this.pos + this.oldText.length); + + if (text !== this.oldText) { + return Promise.reject(new Error(`Invalid replace: "${text}" != "${this.oldText}".`)); + } // TODO: throw error if oldText doesn't match removed string. - return writeFile(this.path, `${prefix}${this.newText}${suffix}`); + return host.write(this.path, `${prefix}${this.newText}${suffix}`); }); } } diff --git a/packages/ast-tools/src/route-utils.spec.ts b/packages/ast-tools/src/route-utils.spec.ts index f1af932454e6..4405742a35f4 100644 --- a/packages/ast-tools/src/route-utils.spec.ts +++ b/packages/ast-tools/src/route-utils.spec.ts @@ -2,7 +2,7 @@ import * as mockFs from 'mock-fs'; import * as fs from 'fs'; import * as nru from './route-utils'; import * as path from 'path'; -import { InsertChange, RemoveChange } from './change'; +import { NodeHost, InsertChange, RemoveChange } from './change'; import denodeify = require('denodeify'); import * as _ from 'lodash'; import {it} from './spec-utils'; @@ -29,8 +29,8 @@ describe('route utils', () => { it('inserts as last import if not present', () => { let content = `'use strict'\n import {foo} from 'bar'\n import * as fz from 'fizz';`; let editedFile = new InsertChange(sourceFile, 0, content); - return editedFile.apply() - .then(() => nru.insertImport(sourceFile, 'Router', '@angular/router').apply()) + return editedFile.apply(NodeHost) + .then(() => nru.insertImport(sourceFile, 'Router', '@angular/router').apply(NodeHost)) .then(() => readFile(sourceFile, 'utf8')) .then(newContent => { expect(newContent).toEqual(content + `\nimport { Router } from '@angular/router';`); @@ -39,7 +39,7 @@ describe('route utils', () => { it('does not insert if present', () => { let content = `'use strict'\n import {Router} from '@angular/router'`; let editedFile = new InsertChange(sourceFile, 0, content); - return editedFile.apply() + return editedFile.apply(NodeHost) .then(() => nru.insertImport(sourceFile, 'Router', '@angular/router')) .then(() => readFile(sourceFile, 'utf8')) .then(newContent => { @@ -49,8 +49,8 @@ describe('route utils', () => { it('inserts into existing import clause if import file is already cited', () => { let content = `'use strict'\n import { foo, bar } from 'fizz'`; let editedFile = new InsertChange(sourceFile, 0, content); - return editedFile.apply() - .then(() => nru.insertImport(sourceFile, 'baz', 'fizz').apply()) + return editedFile.apply(NodeHost) + .then(() => nru.insertImport(sourceFile, 'baz', 'fizz').apply(NodeHost)) .then(() => readFile(sourceFile, 'utf8')) .then(newContent => { expect(newContent).toEqual(`'use strict'\n import { foo, bar, baz } from 'fizz'`); @@ -59,7 +59,7 @@ describe('route utils', () => { it('understands * imports', () => { let content = `\nimport * as myTest from 'tests' \n`; let editedFile = new InsertChange(sourceFile, 0, content); - return editedFile.apply() + return editedFile.apply(NodeHost) .then(() => nru.insertImport(sourceFile, 'Test', 'tests')) .then(() => readFile(sourceFile, 'utf8')) .then(newContent => { @@ -69,8 +69,8 @@ describe('route utils', () => { it('inserts after use-strict', () => { let content = `'use strict';\n hello`; let editedFile = new InsertChange(sourceFile, 0, content); - return editedFile.apply() - .then(() => nru.insertImport(sourceFile, 'Router', '@angular/router').apply()) + return editedFile.apply(NodeHost) + .then(() => nru.insertImport(sourceFile, 'Router', '@angular/router').apply(NodeHost)) .then(() => readFile(sourceFile, 'utf8')) .then(newContent => { expect(newContent).toEqual( @@ -78,7 +78,7 @@ describe('route utils', () => { }); }); it('inserts inserts at beginning of file if no imports exist', () => { - return nru.insertImport(sourceFile, 'Router', '@angular/router').apply() + return nru.insertImport(sourceFile, 'Router', '@angular/router').apply(NodeHost) .then(() => readFile(sourceFile, 'utf8')) .then(newContent => { expect(newContent).toEqual(`import { Router } from '@angular/router';\n`); @@ -86,7 +86,7 @@ describe('route utils', () => { }); it('inserts subcomponent in win32 environment', () => { let content = './level1\\level2/level2.component'; - return nru.insertImport(sourceFile, 'level2', content).apply() + return nru.insertImport(sourceFile, 'level2', content).apply(NodeHost) .then(() => readFile(sourceFile, 'utf8')) .then(newContent => { if (process.platform.startsWith('win')) { @@ -132,7 +132,7 @@ describe('route utils', () => { }); }); xit('does not add a provideRouter import if it exits already', () => { - return nru.insertImport(mainFile, 'provideRouter', '@angular/router').apply() + return nru.insertImport(mainFile, 'provideRouter', '@angular/router').apply(NodeHost) .then(() => nru.applyChanges(nru.bootstrapItem(mainFile, routes, toBootstrap))) .then(() => readFile(mainFile, 'utf8')) .then(content => { @@ -145,7 +145,7 @@ describe('route utils', () => { xit('does not duplicate import to route.ts ', () => { let editedFile = new InsertChange(mainFile, 100, `\nimport routes from './routes';`); return editedFile - .apply() + .apply(NodeHost) .then(() => nru.applyChanges(nru.bootstrapItem(mainFile, routes, toBootstrap))) .then(() => readFile(mainFile, 'utf8')) .then(content => { @@ -163,7 +163,7 @@ describe('route utils', () => { }); it('adds provideRouter to bootstrap if absent and empty providers array', () => { let editFile = new InsertChange(mainFile, 124, ', []'); - return editFile.apply() + return editFile.apply(NodeHost) .then(() => nru.applyChanges(nru.bootstrapItem(mainFile, routes, toBootstrap))) .then(() => readFile(mainFile, 'utf8')) .then(content => { @@ -173,7 +173,7 @@ describe('route utils', () => { }); it('adds provideRouter to bootstrap if absent and non-empty providers array', () => { let editedFile = new InsertChange(mainFile, 124, ', [ HTTP_PROVIDERS ]'); - return editedFile.apply() + return editedFile.apply(NodeHost) .then(() => nru.applyChanges(nru.bootstrapItem(mainFile, routes, toBootstrap))) .then(() => readFile(mainFile, 'utf8')) .then(content => { @@ -185,7 +185,7 @@ describe('route utils', () => { let editedFile = new InsertChange(mainFile, 124, ', [ HTTP_PROVIDERS, provideRouter(routes) ]'); - return editedFile.apply() + return editedFile.apply(NodeHost) .then(() => nru.applyChanges(nru.bootstrapItem(mainFile, routes, toBootstrap))) .then(() => readFile(mainFile, 'utf8')) .then(content => { @@ -195,7 +195,7 @@ describe('route utils', () => { }); it('inserts into the correct array', () => { let editedFile = new InsertChange(mainFile, 124, ', [ HTTP_PROVIDERS, {provide: [BAR]}]'); - return editedFile.apply() + return editedFile.apply(NodeHost) .then(() => nru.applyChanges(nru.bootstrapItem(mainFile, routes, toBootstrap))) .then(() => readFile(mainFile, 'utf8')) .then(content => { @@ -205,7 +205,7 @@ describe('route utils', () => { }); it('throws an error if there is no or multiple bootstrap expressions', () => { let editedFile = new InsertChange(mainFile, 126, '\n bootstrap(moreStuff);'); - return editedFile.apply() + return editedFile.apply(NodeHost) .then(() => nru.bootstrapItem(mainFile, routes, toBootstrap)) .catch(e => expect(e.message).toEqual('Did not bootstrap provideRouter in' + @@ -214,7 +214,7 @@ describe('route utils', () => { }); it('configures correctly if bootstrap or provide router is not at top level', () => { let editedFile = new InsertChange(mainFile, 126, '\n if(e){bootstrap, provideRouter});'); - return editedFile.apply() + return editedFile.apply(NodeHost) .then(() => nru.applyChanges(nru.bootstrapItem(mainFile, routes, toBootstrap))) .then(() => readFile(mainFile, 'utf8')) .then(content => { @@ -262,7 +262,7 @@ export default [\n { path: 'new-route', component: NewRouteComponent }\n];`); }); it('throws error if multiple export defaults exist', () => { let editedFile = new InsertChange(routesFile, 20, 'export default {}'); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { return nru.addPathToRoutes(routesFile, _.merge({route: 'new-route'}, options)); }).catch(e => { expect(e.message).toEqual('Did not insert path in routes.ts because ' @@ -271,7 +271,7 @@ export default [\n { path: 'new-route', component: NewRouteComponent }\n];`); }); it('throws error if no export defaults exists', () => { let editedFile = new RemoveChange(routesFile, 0, 'export default []'); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { return nru.addPathToRoutes(routesFile, _.merge({route: 'new-route'}, options)); }).catch(e => { expect(e.message).toEqual('Did not insert path in routes.ts because ' @@ -281,7 +281,7 @@ export default [\n { path: 'new-route', component: NewRouteComponent }\n];`); it('treats positional params correctly', () => { let editedFile = new InsertChange(routesFile, 16, `\n { path: 'home', component: HomeComponent }\n`); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { options.dasherizedName = 'about'; options.component = 'AboutComponent'; return nru.applyChanges( @@ -299,7 +299,7 @@ export default [\n { path: 'new-route', component: NewRouteComponent }\n];`); }); it('inserts under parent, mid', () => { let editedFile = new InsertChange(routesFile, 16, nestedRoutes); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { options.dasherizedName = 'details'; options.component = 'DetailsComponent'; return nru.applyChanges( @@ -324,7 +324,7 @@ export default [ }); it('inserts under parent, deep', () => { let editedFile = new InsertChange(routesFile, 16, nestedRoutes); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { options.dasherizedName = 'sections'; options.component = 'SectionsComponent'; return nru.applyChanges( @@ -360,7 +360,7 @@ export default [ ] }\n`; let editedFile = new InsertChange(routesFile, 16, paths); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { options.dasherizedName = 'about'; options.component = 'AboutComponent_1'; return nru.applyChanges( @@ -383,7 +383,7 @@ export default [ }); it('throws error if repeating child, shallow', () => { let editedFile = new InsertChange(routesFile, 16, nestedRoutes); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { options.dasherizedName = 'home'; options.component = 'HomeComponent'; return nru.addPathToRoutes(routesFile, _.merge({route: '/home'}, options)); @@ -393,7 +393,7 @@ export default [ }); it('throws error if repeating child, mid', () => { let editedFile = new InsertChange(routesFile, 16, nestedRoutes); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { options.dasherizedName = 'about'; options.component = 'AboutComponent'; return nru.addPathToRoutes(routesFile, _.merge({route: 'home/about/'}, options)); @@ -403,7 +403,7 @@ export default [ }); it('throws error if repeating child, deep', () => { let editedFile = new InsertChange(routesFile, 16, nestedRoutes); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { options.dasherizedName = 'more'; options.component = 'MoreComponent'; return nru.addPathToRoutes(routesFile, _.merge({route: 'home/about/more'}, options)); @@ -413,7 +413,7 @@ export default [ }); it('does not report false repeat', () => { let editedFile = new InsertChange(routesFile, 16, nestedRoutes); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { options.dasherizedName = 'more'; options.component = 'MoreComponent'; return nru.applyChanges(nru.addPathToRoutes(routesFile, _.merge({route: 'more'}, options))); @@ -448,7 +448,7 @@ export default [ },\n { path: 'trap-queen', component: TrapQueenComponent}\n`; let editedFile = new InsertChange(routesFile, 16, routes); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { options.dasherizedName = 'trap-queen'; options.component = 'TrapQueenComponent'; return nru.applyChanges( @@ -475,10 +475,10 @@ export default [ it('resolves imports correctly', () => { let editedFile = new InsertChange(routesFile, 16, `\n { path: 'home', component: HomeComponent }\n`); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { let editedFile = new InsertChange(routesFile, 0, `import { HomeComponent } from './app/home/home.component';\n`); - return editedFile.apply(); + return editedFile.apply(NodeHost); }) .then(() => { options.dasherizedName = 'home'; @@ -507,12 +507,12 @@ export default [ { path: 'details', component: DetailsComponent } ] }`); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { let editedFile = new InsertChange(routesFile, 0, `import { AboutComponent } from './app/about/about.component'; import { DetailsComponent } from './app/about/details/details.component'; import { DetailsComponent as DetailsComponent_1 } from './app/about/description/details.component;\n`); // tslint:disable-line - return editedFile.apply(); + return editedFile.apply(NodeHost); }).then(() => { options.dasherizedName = 'details'; options.component = 'DetailsComponent'; @@ -524,7 +524,7 @@ import { DetailsComponent as DetailsComponent_1 } from './app/about/description/ it('adds guard to parent route: addItemsToRouteProperties', () => { let path = `\n { path: 'home', component: HomeComponent }\n`; let editedFile = new InsertChange(routesFile, 16, path); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { let toInsert = {'home': ['canActivate', '[ MyGuard ]'] }; return nru.applyChanges(nru.addItemsToRouteProperties(routesFile, toInsert)); }) @@ -539,7 +539,7 @@ import { DetailsComponent as DetailsComponent_1 } from './app/about/description/ it('adds guard to child route: addItemsToRouteProperties', () => { let path = `\n { path: 'home', component: HomeComponent }\n`; let editedFile = new InsertChange(routesFile, 16, path); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { options.dasherizedName = 'more'; options.component = 'MoreComponent'; return nru.applyChanges( @@ -609,14 +609,14 @@ export default [ }); it('finds component in the presence of decorators: confirmComponentExport', () => { let editedFile = new InsertChange(componentFile, 0, '@Component{}\n'); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { let exportExists = nru.confirmComponentExport(componentFile, 'AboutComponent'); expect(exportExists).toBeTruthy(); }); }); it('report absence of component name: confirmComponentExport', () => { let editedFile = new RemoveChange(componentFile, 21, 'onent'); - return editedFile.apply().then(() => { + return editedFile.apply(NodeHost).then(() => { let exportExists = nru.confirmComponentExport(componentFile, 'AboutComponent'); expect(exportExists).not.toBeTruthy(); }); diff --git a/packages/ast-tools/src/route-utils.ts b/packages/ast-tools/src/route-utils.ts index 3fd166b0edf8..4455bafdc814 100644 --- a/packages/ast-tools/src/route-utils.ts +++ b/packages/ast-tools/src/route-utils.ts @@ -4,6 +4,7 @@ import * as path from 'path'; import {Change, InsertChange, NoopChange} from './change'; import {findNodes} from './node'; import {insertAfterLastOccurrence} from './ast-utils'; +import {NodeHost, Host} from './change'; /** * Adds imports to mainFile and adds toBootstrap to the array of providers @@ -368,13 +369,14 @@ export function resolveComponentPath(projectRoot: string, currentDir: string, fi /** * Sort changes in decreasing order and apply them. * @param changes + * @param host * @return Promise */ -export function applyChanges(changes: Change[]): Promise { +export function applyChanges(changes: Change[], host: Host = NodeHost): Promise { return changes .filter(change => !!change) .sort((curr, next) => next.order - curr.order) - .reduce((newChange, change) => newChange.then(() => change.apply()), Promise.resolve()); + .reduce((newChange, change) => newChange.then(() => change.apply(host)), Promise.resolve()); } /** * Helper for addPathToRoutes. Adds child array to the appropriate position in the routes.ts file diff --git a/packages/webpack/package.json b/packages/webpack/package.json new file mode 100644 index 000000000000..90218f2ab6e0 --- /dev/null +++ b/packages/webpack/package.json @@ -0,0 +1,15 @@ +{ + "name": "@ngtools/webpack", + "version": "1.0.0", + "description": "", + "main": "./src/index.js", + "license": "MIT", + "dependencies": { + "@angular-cli/ast-tools": "^1.0.0" + }, + "peerDependencies": { + "typescript": "2.0.2", + "@angular/compiler-cli": "^0.6.0", + "@angular/core": "^2.0.0" + } +} diff --git a/packages/webpack/src/compiler.ts b/packages/webpack/src/compiler.ts new file mode 100644 index 000000000000..87dd33414619 --- /dev/null +++ b/packages/webpack/src/compiler.ts @@ -0,0 +1,16 @@ +import * as tscWrapped from '@angular/tsc-wrapped/src/compiler_host'; +import * as ts from 'typescript'; + + +export class NgcWebpackCompilerHost extends tscWrapped.DelegatingHost { + fileCache = new Map(); + + constructor(delegate: ts.CompilerHost) { + super(delegate); + } +} + +export function createCompilerHost(tsConfig: any) { + const delegateHost = ts.createCompilerHost(tsConfig['compilerOptions']); + return new NgcWebpackCompilerHost(delegateHost); +} diff --git a/packages/webpack/src/index.ts b/packages/webpack/src/index.ts new file mode 100644 index 000000000000..8fd3bad21818 --- /dev/null +++ b/packages/webpack/src/index.ts @@ -0,0 +1,4 @@ +import 'reflect-metadata'; + +export * from './plugin' +export {ngcLoader as default} from './loader' diff --git a/packages/webpack/src/loader.ts b/packages/webpack/src/loader.ts new file mode 100644 index 000000000000..ba162a5d18fd --- /dev/null +++ b/packages/webpack/src/loader.ts @@ -0,0 +1,149 @@ +import * as path from 'path'; +import * as ts from 'typescript'; +import {NgcWebpackPlugin} from './plugin'; +import {MultiChange, ReplaceChange, insertImport} from '@angular-cli/ast-tools'; + +// TODO: move all this to ast-tools. +function _findNodes(sourceFile: ts.SourceFile, node: ts.Node, kind: ts.SyntaxKind, + keepGoing = false): ts.Node[] { + if (node.kind == kind && !keepGoing) { + return [node]; + } + + return node.getChildren(sourceFile).reduce((result, n) => { + return result.concat(_findNodes(sourceFile, n, kind, keepGoing)); + }, node.kind == kind ? [node] : []); +} + +function _removeDecorators(fileName: string, source: string): string { + const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest); + // Find all decorators. + const decorators = _findNodes(sourceFile, sourceFile, ts.SyntaxKind.Decorator); + decorators.sort((a, b) => b.pos - a.pos); + + decorators.forEach(d => { + source = source.slice(0, d.pos) + source.slice(d.end); + }); + + return source; +} + + +function _replaceBootstrap(fileName: string, + source: string, + plugin: NgcWebpackPlugin): Promise { + // If bootstrapModule can't be found, bail out early. + if (!source.match(/\bbootstrapModule\b/)) { + return Promise.resolve(source); + } + + let changes = new MultiChange(); + + // Calculate the base path. + const basePath = path.normalize(plugin.angularCompilerOptions.basePath); + const genDir = path.normalize(plugin.genDir); + const dirName = path.normalize(path.dirname(fileName)); + const [entryModulePath, entryModuleName] = plugin.entryModule.split('#'); + const entryModuleFileName = path.normalize(entryModulePath + '.ngfactory'); + const relativeEntryModulePath = path.relative(basePath, entryModuleFileName); + const fullEntryModulePath = path.resolve(genDir, relativeEntryModulePath); + const relativeNgFactoryPath = path.relative(dirName, fullEntryModulePath); + const ngFactoryPath = './' + relativeNgFactoryPath.replace(/\\/g, '/'); + + const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest); + + const allCalls = _findNodes( + sourceFile, sourceFile, ts.SyntaxKind.CallExpression, true) as ts.CallExpression[]; + + const bootstraps = allCalls + .filter(call => call.expression.kind == ts.SyntaxKind.PropertyAccessExpression) + .map(call => call.expression as ts.PropertyAccessExpression) + .filter(access => { + return access.name.kind == ts.SyntaxKind.Identifier + && access.name.text == 'bootstrapModule'; + }); + + const calls: ts.Node[] = bootstraps + .reduce((previous, access) => { + return previous.concat(_findNodes(sourceFile, access, ts.SyntaxKind.CallExpression, true)); + }, []) + .filter(call => { + return call.expression.kind == ts.SyntaxKind.Identifier + && call.expression.text == 'platformBrowserDynamic'; + }); + + if (calls.length == 0) { + // Didn't find any dynamic bootstrapping going on. + return Promise.resolve(source); + } + + // Create the changes we need. + allCalls + .filter(call => bootstraps.some(bs => bs == call.expression)) + .forEach((call: ts.CallExpression) => { + changes.appendChange(new ReplaceChange(fileName, call.arguments[0].getStart(sourceFile), + entryModuleName, entryModuleName + 'NgFactory')); + }); + + calls + .forEach(call => { + changes.appendChange(new ReplaceChange(fileName, call.getStart(sourceFile), + 'platformBrowserDynamic', 'platformBrowser')); + }); + + bootstraps + .forEach((bs: ts.PropertyAccessExpression) => { + // This changes the call. + changes.appendChange(new ReplaceChange(fileName, bs.name.getStart(sourceFile), + 'bootstrapModule', 'bootstrapModuleFactory')); + }); + changes.appendChange(insertImport(fileName, 'platformBrowser', '@angular/platform-browser')); + changes.appendChange(insertImport(fileName, entryModuleName + 'NgFactory', ngFactoryPath)); + + let sourceText = source; + return changes.apply({ + read: (path: string) => Promise.resolve(sourceText), + write: (path: string, content: string) => Promise.resolve(sourceText = content) + }).then(() => sourceText); +} + + +// Super simple TS transpiler loader for testing / isolated usage. does not type check! +export function ngcLoader(source: string) { + this.cacheable(); + + const plugin = this._compilation._ngToolsWebpackPluginInstance as NgcWebpackPlugin; + if (plugin && plugin instanceof NgcWebpackPlugin) { + const cb: any = this.async(); + + plugin.done + .then(() => _removeDecorators(this.resource, source)) + .then(sourceText => _replaceBootstrap(this.resource, sourceText, plugin)) + .then(sourceText => { + const result = ts.transpileModule(sourceText, { + compilerOptions: { + target: ts.ScriptTarget.ES5, + module: ts.ModuleKind.ES2015, + } + }); + + if (result.diagnostics && result.diagnostics.length) { + let message = ''; + result.diagnostics.forEach(d => { + message += d.messageText + '\n'; + }); + cb(new Error(message)); + } + + cb(null, result.outputText, result.sourceMapText ? JSON.parse(result.sourceMapText) : null); + }) + .catch(err => cb(err)); + } else { + return ts.transpileModule(source, { + compilerOptions: { + target: ts.ScriptTarget.ES5, + module: ts.ModuleKind.ES2015, + } + }).outputText; + } +} diff --git a/packages/webpack/src/plugin.ts b/packages/webpack/src/plugin.ts new file mode 100644 index 000000000000..707c069c9ffe --- /dev/null +++ b/packages/webpack/src/plugin.ts @@ -0,0 +1,225 @@ +import * as ts from 'typescript'; +import * as path from 'path'; + +import {NgModule} from '@angular/core'; +import * as ngCompiler from '@angular/compiler-cli'; +import {tsc} from '@angular/tsc-wrapped/src/tsc'; + +import {patchReflectorHost} from './reflector_host'; +import {WebpackResourceLoader} from './resource_loader'; +import {createResolveDependenciesFromContextMap} from './utils'; +import { AngularCompilerOptions } from '@angular/tsc-wrapped'; + + +/** + * Option Constants + */ +export interface AngularWebpackPluginOptions { + tsconfigPath?: string; + providers?: any[]; + entryModule?: string; + project: string; + baseDir: string; + basePath?: string; + genDir?: string; +} + + +export class NgcWebpackPlugin { + projectPath: string; + rootModule: string; + rootModuleName: string; + reflector: ngCompiler.StaticReflector; + reflectorHost: ngCompiler.ReflectorHost; + program: ts.Program; + compilerHost: ts.CompilerHost; + compilerOptions: ts.CompilerOptions; + angularCompilerOptions: AngularCompilerOptions; + files: any[]; + lazyRoutes: any; + loader: any; + genDir: string; + entryModule: string; + + done: Promise; + + nmf: any = null; + cmf: any = null; + compiler: any = null; + compilation: any = null; + + constructor(public options: AngularWebpackPluginOptions) { + const tsConfig = tsc.readConfiguration(options.project, options.baseDir); + this.compilerOptions = tsConfig.parsed.options; + this.files = tsConfig.parsed.fileNames; + this.angularCompilerOptions = Object.assign({}, tsConfig.ngOptions, options); + + this.angularCompilerOptions.basePath = options.baseDir || process.cwd(); + this.genDir = this.options.genDir + || path.resolve(process.cwd(), this.angularCompilerOptions.genDir + '/app'); + this.entryModule = options.entryModule || (this.angularCompilerOptions as any).entryModule; + + const entryModule = this.entryModule; + const [rootModule, rootNgModule] = entryModule.split('#'); + this.projectPath = options.project; + this.rootModule = rootModule; + this.rootModuleName = rootNgModule; + this.compilerHost = ts.createCompilerHost(this.compilerOptions, true); + this.program = ts.createProgram(this.files, this.compilerOptions, this.compilerHost); + this.reflectorHost = new ngCompiler.ReflectorHost( + this.program, this.compilerHost, this.angularCompilerOptions); + this.reflector = new ngCompiler.StaticReflector(this.reflectorHost); + } + + // registration hook for webpack plugin + apply(compiler: any) { + this.compiler = compiler; + compiler.plugin('normal-module-factory', (nmf: any) => this.nmf = nmf); + compiler.plugin('context-module-factory', (cmf: any) => { + this.cmf = cmf; + cmf.plugin('before-resolve', (request: any, callback: (err?: any, request?: any) => void) => { + if (!request) { + return callback(); + } + + request.request = this.genDir; + request.recursive = true; + request.dependencies.forEach((d: any) => d.critical = false); + return callback(null, request); + }); + cmf.plugin('after-resolve', (result: any, callback: (err?: any, request?: any) => void) => { + if (!result) { + return callback(); + } + + result.resource = this.genDir; + result.recursive = true; + result.dependencies.forEach((d: any) => d.critical = false); + result.resolveDependencies = createResolveDependenciesFromContextMap((_: any, cb: any) => { + return cb(null, this.lazyRoutes); + }); + + return callback(null, result); + }); + }); + + compiler.plugin('make', (compilation: any, cb: any) => this._make(compilation, cb)); + compiler.plugin('after-emit', (compilation: any, cb: any) => { + this.done = null; + this.compilation = null; + compilation._ngToolsWebpackPluginInstance = null; + cb(); + }); + } + + private _make(compilation: any, cb: (err?: any, request?: any) => void) { + const rootModulePath = path.normalize(this.rootModule + '.ts'); + const rootModuleName = this.rootModuleName; + this.compilation = compilation; + + if (this.compilation._ngToolsWebpackPluginInstance) { + cb(new Error('A ngtools/webpack plugin already exist for this compilation.')); + } + this.compilation._ngToolsWebpackPluginInstance = this; + + this.loader = new WebpackResourceLoader(compilation); + + const i18nOptions: any = { + i18nFile: undefined, + i18nFormat: undefined, + locale: undefined, + basePath: this.options.baseDir + }; + + // Create the Code Generator. + const codeGenerator = ngCompiler.CodeGenerator.create( + this.angularCompilerOptions, + i18nOptions, + this.program, + this.compilerHost, + new ngCompiler.NodeReflectorHostContext(this.compilerHost), + this.loader + ); + + // We need to temporarily patch the CodeGenerator until either it's patched or allows us + // to pass in our own ReflectorHost. + patchReflectorHost(codeGenerator); + this.done = codeGenerator.codegen() + .then(() => { + // process the lazy routes + const lazyModules = this._processNgModule(rootModulePath, rootModuleName, rootModulePath) + .map(moduleKey => moduleKey.split('#')[0]); + this.lazyRoutes = lazyModules.reduce((lazyRoutes: any, lazyModule: any) => { + const genDir = this.genDir; + lazyRoutes[`${lazyModule}.ngfactory`] = path.join(genDir, lazyModule + '.ngfactory.ts'); + return lazyRoutes; + }, {}); + }) + .then(() => cb(), (err) => cb(err)); + } + + private _processNgModule(mod: string, ngModuleName: string, containingFile: string): string[] { + const staticSymbol = this.reflectorHost.findDeclaration(mod, ngModuleName, containingFile); + const entryNgModuleMetadata = this.getNgModuleMetadata(staticSymbol); + const loadChildren = this.extractLoadChildren(entryNgModuleMetadata); + + return loadChildren.reduce((res, lc) => { + const [childModule, childNgModule] = lc.split('#'); + + // TODO calculate a different containingFile for relative paths + + const children = this._processNgModule(childModule, childNgModule, containingFile); + return res.concat(children); + }, loadChildren); + } + + private getNgModuleMetadata(staticSymbol: ngCompiler.StaticSymbol) { + const ngModules = this.reflector.annotations(staticSymbol).filter(s => s instanceof NgModule); + if (ngModules.length === 0) { + throw new Error(`${staticSymbol.name} is not an NgModule`); + } + return ngModules[0]; + } + + private extractLoadChildren(ngModuleDecorator: any): any[] { + const routes = ngModuleDecorator.imports.reduce((mem: any[], m: any) => { + return mem.concat(this.collectRoutes(m.providers)); + }, this.collectRoutes(ngModuleDecorator.providers)); + return this.collectLoadChildren(routes); + } + + private collectRoutes(providers: any[]): any[] { + if (!providers) { + return []; + } + const ROUTES = this.reflectorHost.findDeclaration( + '@angular/router/src/router_config_loader', 'ROUTES', undefined); + + return providers.reduce((m, p) => { + if (p.provide === ROUTES) { + return m.concat(p.useValue); + } else if (Array.isArray(p)) { + return m.concat(this.collectRoutes(p)); + } else { + return m; + } + }, []); + } + + private collectLoadChildren(routes: any[]): any[] { + if (!routes) { + return []; + } + return routes.reduce((m, r) => { + if (r.loadChildren) { + return m.concat(r.loadChildren); + } else if (Array.isArray(r)) { + return m.concat(this.collectLoadChildren(r)); + } else if (r.children) { + return m.concat(this.collectLoadChildren(r.children)); + } else { + return m; + } + }, []); + } +} diff --git a/packages/webpack/src/reflector_host.ts b/packages/webpack/src/reflector_host.ts new file mode 100644 index 000000000000..0e995c2ee007 --- /dev/null +++ b/packages/webpack/src/reflector_host.ts @@ -0,0 +1,26 @@ +import {CodeGenerator} from '@angular/compiler-cli'; + + +/** + * Patch the CodeGenerator instance to use a custom reflector host. + */ +export function patchReflectorHost(codeGenerator: CodeGenerator) { + const reflectorHost = (codeGenerator as any).reflectorHost; + const oldGIP = reflectorHost.getImportPath; + + reflectorHost.getImportPath = function(containingFile: string, importedFile: string): string { + // Hack together SCSS and LESS files URLs so that they match what the default ReflectorHost + // is expected. We only do that for shimmed styles. + const m = importedFile.match(/(.*)(\..+)(\.shim)(\..+)/); + if (!m) { + return oldGIP.call(this, containingFile, importedFile); + } + + // We call the original, with `css` in its name instead of the extension, and replace the + // extension from the result. + const [, baseDirAndName, styleExt, shim, ext] = m; + const result = oldGIP.call(this, containingFile, baseDirAndName + '.css' + shim + ext); + + return result.replace(/\.css\./, styleExt + '.'); + }; +} diff --git a/packages/webpack/src/resource_loader.ts b/packages/webpack/src/resource_loader.ts new file mode 100644 index 000000000000..5b1d069b9967 --- /dev/null +++ b/packages/webpack/src/resource_loader.ts @@ -0,0 +1,116 @@ +import {ResourceLoader} from '@angular/compiler'; +import {readFileSync} from 'fs'; +import * as vm from 'vm'; +import * as path from 'path'; + +const NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin'); +const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin'); +const LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin'); +const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin'); + + + +export class WebpackResourceLoader implements ResourceLoader { + private _context: string; + private _uniqueId = 0; + + constructor(private _compilation: any) { + this._context = _compilation.context; + } + + private _compile(filePath: string, content: string): Promise { + const compilerName = `compiler(${this._uniqueId++})`; + const outputOptions = { filename: filePath }; + const relativePath = path.relative(this._context || '', filePath); + const childCompiler = this._compilation.createChildCompiler(relativePath, outputOptions); + childCompiler.context = this._context; + childCompiler.apply( + new NodeTemplatePlugin(outputOptions), + new NodeTargetPlugin(), + new SingleEntryPlugin(this._context, filePath, content), + new LoaderTargetPlugin('node') + ); + + // Store the result of the parent compilation before we start the child compilation + let assetsBeforeCompilation = Object.assign( + {}, + this._compilation.assets[outputOptions.filename] + ); + + // Fix for "Uncaught TypeError: __webpack_require__(...) is not a function" + // Hot module replacement requires that every child compiler has its own + // cache. @see https://github.com/ampedandwired/html-webpack-plugin/pull/179 + childCompiler.plugin('compilation', function (compilation: any) { + if (compilation.cache) { + if (!compilation.cache[compilerName]) { + compilation.cache[compilerName] = {}; + } + compilation.cache = compilation.cache[compilerName]; + } + }); + + // Compile and return a promise + return new Promise((resolve, reject) => { + childCompiler.runAsChild((err: Error, entries: any[], childCompilation: any) => { + // Resolve / reject the promise + if (childCompilation && childCompilation.errors && childCompilation.errors.length) { + const errorDetails = childCompilation.errors.map(function (error: any) { + return error.message + (error.error ? ':\n' + error.error : ''); + }).join('\n'); + reject(new Error('Child compilation failed:\n' + errorDetails)); + } else if (err) { + reject(err); + } else { + // Replace [hash] placeholders in filename + const outputName = this._compilation.mainTemplate.applyPluginsWaterfall( + 'asset-path', outputOptions.filename, { + hash: childCompilation.hash, + chunk: entries[0] + }); + + // Restore the parent compilation to the state like it was before the child compilation. + this._compilation.assets[outputName] = assetsBeforeCompilation[outputName]; + if (assetsBeforeCompilation[outputName] === undefined) { + // If it wasn't there - delete it. + delete this._compilation.assets[outputName]; + } + + resolve({ + // Hash of the template entry point. + hash: entries[0].hash, + // Output name. + outputName: outputName, + // Compiled code. + content: childCompilation.assets[outputName].source() + }); + } + }); + }); + } + + private _evaluate(fileName: string, source: string): Promise { + const vmContext = vm.createContext(Object.assign({ require: require }, global)); + const vmScript = new vm.Script(source, { filename: fileName }); + + // Evaluate code and cast to string + let newSource: string; + try { + newSource = vmScript.runInContext(vmContext).toString(); + } catch (e) { + return Promise.reject(e); + } + + if (typeof newSource == 'string') { + return Promise.resolve(newSource); + } else if (typeof newSource == 'function') { + return Promise.resolve(newSource()); + } + + return Promise.reject('The loader "' + fileName + '" didn\'t return a string.'); + } + + get(filePath: string): Promise { + return this._compile(filePath, readFileSync(filePath, 'utf8')) + .then((result: any) => this._evaluate(result.outputName, result.content)); + } +} diff --git a/packages/webpack/src/utils.ts b/packages/webpack/src/utils.ts new file mode 100644 index 000000000000..954cc206ad9b --- /dev/null +++ b/packages/webpack/src/utils.ts @@ -0,0 +1,16 @@ +const ContextElementDependency = require('webpack/lib/dependencies/ContextElementDependency'); + +export function createResolveDependenciesFromContextMap(createContextMap: Function) { + return (fs: any, resource: any, recursive: any, regExp: RegExp, callback: any) => { + createContextMap(fs, function(err: Error, map: any) { + if (err) { + return callback(err); + } + + const dependencies = Object.keys(map) + .map((key) => new ContextElementDependency(map[key], key)); + + callback(null, dependencies); + }); + }; +} diff --git a/packages/webpack/tsconfig.json b/packages/webpack/tsconfig.json new file mode 100644 index 000000000000..68fa995ac3a7 --- /dev/null +++ b/packages/webpack/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "declaration": true, + "experimentalDecorators": true, + "mapRoot": "", + "module": "commonjs", + "moduleResolution": "node", + "noEmitOnError": true, + "noImplicitAny": true, + "outDir": "../../dist/webpack", + "rootDir": ".", + "lib": ["es2015", "es6", "dom"], + "target": "es5", + "sourceMap": true, + "sourceRoot": "/", + "baseUrl": ".", + "paths": { + "@angular-cli/ast-tools": [ "../../dist/ast-tools/src" ] + }, + "typeRoots": [ + "../../node_modules/@types" + ], + "types": [ + "jasmine", + "node" + ] + } +} diff --git a/scripts/publish/build.js b/scripts/publish/build.js index b5bda9d3a951..642a353f6a1a 100755 --- a/scripts/publish/build.js +++ b/scripts/publish/build.js @@ -40,6 +40,18 @@ Promise.resolve() .then(() => { const packages = require('../../lib/packages'); return Object.keys(packages) + // Order packages in order of dependency. + .sort((a, b) => { + const aPackageJson = require(packages[a].packageJson); + const bPackageJson = require(packages[b].packageJson); + if (Object.keys(aPackageJson['dependencies'] || {}).indexOf(b) == -1) { + return 0; + } else if (Object.keys(bPackageJson['dependencies'] || {}).indexOf(a) == -1) { + return 1; + } else { + return -1; + } + }) .reduce((promise, packageName) => { const pkg = packages[packageName]; const name = path.relative(packagesRoot, pkg.root); diff --git a/tests/e2e/assets/webpack/test-app/app/app.component.html b/tests/e2e/assets/webpack/test-app/app/app.component.html new file mode 100644 index 000000000000..5a532db9308f --- /dev/null +++ b/tests/e2e/assets/webpack/test-app/app/app.component.html @@ -0,0 +1,5 @@ +
+

hello world

+ lazy + +
diff --git a/tests/e2e/assets/webpack/test-app/app/app.component.scss b/tests/e2e/assets/webpack/test-app/app/app.component.scss new file mode 100644 index 000000000000..5cde7b922336 --- /dev/null +++ b/tests/e2e/assets/webpack/test-app/app/app.component.scss @@ -0,0 +1,3 @@ +:host { + background-color: blue; +} diff --git a/tests/e2e/assets/webpack/test-app/app/app.component.ts b/tests/e2e/assets/webpack/test-app/app/app.component.ts new file mode 100644 index 000000000000..2fc4df46be32 --- /dev/null +++ b/tests/e2e/assets/webpack/test-app/app/app.component.ts @@ -0,0 +1,10 @@ +// Code generated by angular2-stress-test + +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +export class AppComponent { } diff --git a/tests/e2e/assets/webpack/test-app/app/app.module.ts b/tests/e2e/assets/webpack/test-app/app/app.module.ts new file mode 100644 index 000000000000..79e133f3eeeb --- /dev/null +++ b/tests/e2e/assets/webpack/test-app/app/app.module.ts @@ -0,0 +1,28 @@ +// Code generated by angular2-stress-test +import { NgModule, Component } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; +import { AppComponent } from './app.component'; + +@Component({ + selector: 'home-view', + template: 'home!' +}) +export class HomeView {} + + +@NgModule({ + declarations: [ + AppComponent, + HomeView + ], + imports: [ + BrowserModule, + RouterModule.forRoot([ + {path: 'lazy', loadChildren: './lazy.module#LazyModule'}, + {path: '', component: HomeView} + ]) + ], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/tests/e2e/assets/webpack/test-app/app/feature/feature.module.ts b/tests/e2e/assets/webpack/test-app/app/feature/feature.module.ts new file mode 100644 index 000000000000..f464ca028b05 --- /dev/null +++ b/tests/e2e/assets/webpack/test-app/app/feature/feature.module.ts @@ -0,0 +1,20 @@ +import {NgModule, Component} from '@angular/core'; +import {RouterModule} from '@angular/router'; + +@Component({ + selector: 'feature-component', + template: 'foo.html' +}) +export class FeatureComponent {} + +@NgModule({ + declarations: [ + FeatureComponent + ], + imports: [ + RouterModule.forChild([ + { path: '', component: FeatureComponent} + ]) + ] +}) +export class FeatureModule {} diff --git a/tests/e2e/assets/webpack/test-app/app/lazy.module.ts b/tests/e2e/assets/webpack/test-app/app/lazy.module.ts new file mode 100644 index 000000000000..29dab5d11d35 --- /dev/null +++ b/tests/e2e/assets/webpack/test-app/app/lazy.module.ts @@ -0,0 +1,25 @@ +import {NgModule, Component} from '@angular/core'; +import {RouterModule} from '@angular/router'; +import {HttpModule, Http} from '@angular/http'; + +@Component({ + selector: 'lazy-comp', + template: 'lazy!' +}) +export class LazyComponent {} + +@NgModule({ + imports: [ + RouterModule.forChild([ + {path: '', component: LazyComponent, pathMatch: 'full'}, + {path: 'feature', loadChildren: './feature/feature.module#FeatureModule'} + ]), + HttpModule + ], + declarations: [LazyComponent] +}) +export class LazyModule { + constructor(http: Http) {} +} + +export class SecondModule {} diff --git a/tests/e2e/assets/webpack/test-app/app/main.aot.ts b/tests/e2e/assets/webpack/test-app/app/main.aot.ts new file mode 100644 index 000000000000..1b4503a81e31 --- /dev/null +++ b/tests/e2e/assets/webpack/test-app/app/main.aot.ts @@ -0,0 +1,5 @@ +import 'core-js/es7/reflect'; +import {platformBrowser} from '@angular/platform-browser'; +import {AppModuleNgFactory} from './ngfactory/app/app.module.ngfactory'; + +platformBrowser().bootstrapModuleFactory(AppModuleNgFactory); diff --git a/tests/e2e/assets/webpack/test-app/app/main.jit.ts b/tests/e2e/assets/webpack/test-app/app/main.jit.ts new file mode 100644 index 000000000000..4f083729991e --- /dev/null +++ b/tests/e2e/assets/webpack/test-app/app/main.jit.ts @@ -0,0 +1,5 @@ +import 'reflect-metadata'; +import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; +import {AppModule} from './app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/tests/e2e/assets/webpack/test-app/index.html b/tests/e2e/assets/webpack/test-app/index.html new file mode 100644 index 000000000000..89fb0893c35d --- /dev/null +++ b/tests/e2e/assets/webpack/test-app/index.html @@ -0,0 +1,12 @@ + + + + Document + + + + + + + + diff --git a/tests/e2e/assets/webpack/test-app/package.json b/tests/e2e/assets/webpack/test-app/package.json new file mode 100644 index 000000000000..a82238a71050 --- /dev/null +++ b/tests/e2e/assets/webpack/test-app/package.json @@ -0,0 +1,26 @@ +{ + "name": "test", + "license": "MIT", + "dependencies": { + "@angular/common": "^2.0.0", + "@angular/compiler": "^2.0.0", + "@angular/compiler-cli": "0.6.2", + "@angular/core": "^2.0.0", + "@angular/http": "^2.0.0", + "@angular/platform-browser": "^2.0.0", + "@angular/platform-browser-dynamic": "^2.0.0", + "@angular/platform-server": "^2.0.0", + "@angular/router": "^3.0.0", + "core-js": "^2.4.1", + "rxjs": "^5.0.0-beta.12", + "zone.js": "^0.6.21" + }, + "devDependencies": { + "node-sass": "^3.7.0", + "performance-now": "^0.2.0", + "raw-loader": "^0.5.1", + "sass-loader": "^3.2.0", + "typescript": "2.0.2", + "webpack": "2.1.0-beta.22" + } +} diff --git a/tests/e2e/assets/webpack/test-app/tsconfig.json b/tests/e2e/assets/webpack/test-app/tsconfig.json new file mode 100644 index 000000000000..585586e38d21 --- /dev/null +++ b/tests/e2e/assets/webpack/test-app/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "es2015", + "moduleResolution": "node", + "target": "es5", + "noImplicitAny": false, + "sourceMap": true, + "mapRoot": "", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "es2015", + "dom" + ], + "outDir": "lib", + "skipLibCheck": true, + "rootDir": "." + }, + "angularCompilerOptions": { + "genDir": "./app/ngfactory", + "entryModule": "app/app.module#AppModule" + } +} diff --git a/tests/e2e/assets/webpack/test-app/webpack.config.js b/tests/e2e/assets/webpack/test-app/webpack.config.js new file mode 100644 index 000000000000..cfb6ea75f284 --- /dev/null +++ b/tests/e2e/assets/webpack/test-app/webpack.config.js @@ -0,0 +1,31 @@ +const NgcWebpackPlugin = require('@ngtools/webpack').NgcWebpackPlugin; +const path = require('path'); + +module.exports = { + resolve: { + extensions: ['.ts', '.js'] + }, + entry: './app/main.aot.ts', + output: { + path: './dist', + publicPath: 'dist/', + filename: 'app.main.js' + }, + plugins: [ + new NgcWebpackPlugin({ + project: './tsconfig.json', + baseDir: path.resolve(__dirname, '') + }) + ], + module: { + loaders: [ + { test: /\.scss$/, loaders: ['raw-loader', 'sass-loader'] }, + { test: /\.css$/, loader: 'raw-loader' }, + { test: /\.html$/, loader: 'raw-loader' }, + { test: /\.ts$/, loader: '@ngtools/webpack' } + ] + }, + devServer: { + historyApiFallback: true + } +}; diff --git a/tests/e2e/setup/100-npm-link.ts b/tests/e2e/setup/100-npm-link.ts index fbe66d23b70c..396b974d94ec 100644 --- a/tests/e2e/setup/100-npm-link.ts +++ b/tests/e2e/setup/100-npm-link.ts @@ -1,5 +1,8 @@ import {join} from 'path'; import {npm, exec} from '../utils/process'; +import {updateJsonFile} from '../utils/project'; + +const packages = require('../../../lib/packages'); export default function (argv: any) { @@ -12,8 +15,24 @@ export default function (argv: any) { const distAngularCli = join(__dirname, '../../../dist/angular-cli'); const oldCwd = process.cwd(); process.chdir(distAngularCli); - return npm('link') - .then(() => process.chdir(oldCwd)); + + // Update the package.json of each packages. + return Promise.all(Object.keys(packages).map(pkgName => { + const p = packages[pkgName]; + + return updateJsonFile(join(p.dist, 'package.json'), json => { + for (const pkgName of Object.keys(packages)) { + if (json['dependencies'] && json['dependencies'][pkgName]) { + json['dependencies'][pkgName] = packages[pkgName].dist; + } + if (json['devDependencies'] && json['devDependencies'][pkgName]) { + json['devDependencies'][pkgName] = packages[pkgName].dist; + } + } + }); + })) + .then(() => npm('link')) + .then(() => process.chdir(oldCwd)); }) .then(() => exec(process.platform.startsWith('win') ? 'where' : 'which', 'ng')); } diff --git a/tests/e2e/setup/200-create-tmp-dir.ts b/tests/e2e/setup/200-create-tmp-dir.ts index e1a6dbfffc91..8cd88ec70a11 100644 --- a/tests/e2e/setup/200-create-tmp-dir.ts +++ b/tests/e2e/setup/200-create-tmp-dir.ts @@ -1,9 +1,12 @@ -const temp = require('temp'); +import {setGlobalVariable} from '../utils/env'; + +const temp = require('temp'); export default function(argv: any) { // Get to a temporary directory. let tempRoot = argv.reuse || temp.mkdirSync('angular-cli-e2e-'); console.log(` Using "${tempRoot}" as temporary directory for a new project.`); + setGlobalVariable('tmp-root', tempRoot); process.chdir(tempRoot); } diff --git a/tests/e2e/setup/300-log-environment.ts b/tests/e2e/setup/300-log-environment.ts new file mode 100644 index 000000000000..de8823beee8a --- /dev/null +++ b/tests/e2e/setup/300-log-environment.ts @@ -0,0 +1,21 @@ +import {node, ng, npm} from '../utils/process'; + +const packages = require('../../../lib/packages'); + + +export default function() { + return Promise.resolve() + .then(() => console.log('Environment:')) + .then(() => { + Object.keys(process.env).forEach(envName => { + console.log(` ${envName}: ${process.env[envName].replace(/[\n\r]+/g, '\n ')}`); + }); + }) + .then(() => { + console.log('Packages:'); + console.log(JSON.stringify(packages, null, 2)); + }) + .then(() => node('--version')) + .then(() => npm('--version')) + .then(() => ng('version')); +} diff --git a/tests/e2e/setup/500-create-project.ts b/tests/e2e/setup/500-create-project.ts index dd7fb0481a26..e2ff78992b76 100644 --- a/tests/e2e/setup/500-create-project.ts +++ b/tests/e2e/setup/500-create-project.ts @@ -9,8 +9,11 @@ import {gitClean, gitCommit} from '../utils/git'; export default function(argv: any) { let createProject = null; - // If we're set to reuse an existing project, just chdir to it and clean it. - if (argv.reuse) { + // This is a dangerous flag, but is useful for testing packages only. + if (argv.noproject) { + return Promise.resolve(); + } else if (argv.reuse) { + // If we're set to reuse an existing project, just chdir to it and clean it. createProject = Promise.resolve() .then(() => process.chdir(argv.reuse)) .then(() => gitClean()); @@ -29,6 +32,7 @@ export default function(argv: any) { json['devDependencies']['angular-cli'] = join(dist, 'angular-cli'); json['devDependencies']['@angular-cli/ast-tools'] = join(dist, 'ast-tools'); json['devDependencies']['@angular-cli/base-href-webpack'] = join(dist, 'base-href-webpack'); + json['devDependencies']['@ngtools/webpack'] = join(dist, 'webpack'); })) .then(() => { if (argv.nightly) { @@ -57,5 +61,6 @@ export default function(argv: any) { })) .then(() => git('config', 'user.email', 'angular-core+e2e@google.com')) .then(() => git('config', 'user.name', 'Angular CLI E2e')) + .then(() => git('config', 'commit.gpgSign', 'false')) .then(() => gitCommit('tsconfig-e2e-update')); } diff --git a/tests/e2e/tests/build/styles/less.ts b/tests/e2e/tests/build/styles/less.ts index 840d0bde2483..279742be2ceb 100644 --- a/tests/e2e/tests/build/styles/less.ts +++ b/tests/e2e/tests/build/styles/less.ts @@ -28,6 +28,6 @@ export default function() { .then(() => replaceInFile('src/app/app.component.ts', './app.component.css', './app.component.less')) .then(() => ng('build')) - .then(() => expectFileToMatch('dist/main.bundle.js', '.outer .inner')) + .then(() => expectFileToMatch('dist/main.bundle.js', /.outer.*.inner.*background:\s*#[fF]+/)) .then(() => moveFile('src/app/app.component.less', 'src/app/app.component.css')); } diff --git a/tests/e2e/tests/build/styles/scss.ts b/tests/e2e/tests/build/styles/scss.ts index 48b4792312a4..52d7d46fbd42 100644 --- a/tests/e2e/tests/build/styles/scss.ts +++ b/tests/e2e/tests/build/styles/scss.ts @@ -35,7 +35,7 @@ export default function() { .then(() => replaceInFile('src/app/app.component.ts', './app.component.css', './app.component.scss')) .then(() => ng('build')) - .then(() => expectFileToMatch('dist/main.bundle.js', '.outer .inner')) - .then(() => expectFileToMatch('dist/main.bundle.js', '.partial .inner')) + .then(() => expectFileToMatch('dist/main.bundle.js', /\.outer.*\.inner.*background.*#def/)) + .then(() => expectFileToMatch('dist/main.bundle.js', /\.partial.*\.inner.*background.*#def/)) .then(() => moveFile('src/app/app.component.scss', 'src/app/app.component.css')); } diff --git a/tests/e2e/tests/packages/webpack/test.ts b/tests/e2e/tests/packages/webpack/test.ts new file mode 100644 index 000000000000..26c848e97636 --- /dev/null +++ b/tests/e2e/tests/packages/webpack/test.ts @@ -0,0 +1,29 @@ +import {copyAssets} from '../../../utils/assets'; +import {exec, silentNpm} from '../../../utils/process'; +import {updateJsonFile} from '../../../utils/project'; +import {join} from 'path'; +import {expectFileSizeToBeUnder} from '../../../utils/fs'; + + +export default function(argv: any, skipCleaning: () => void) { + const currentDir = process.cwd(); + + if (process.platform.startsWith('win')) { + // Disable the test on Windows. + return Promise.resolve(); + } + + return Promise.resolve() + .then(() => copyAssets('webpack/test-app')) + .then(dir => process.chdir(dir)) + .then(() => updateJsonFile('package.json', json => { + const dist = '../../../../../dist/'; + json['dependencies']['@ngtools/webpack'] = join(__dirname, dist, 'webpack'); + })) + .then(() => silentNpm('install')) + .then(() => exec('node_modules/.bin/webpack', '-p')) + .then(() => expectFileSizeToBeUnder('dist/app.main.js', 400000)) + .then(() => expectFileSizeToBeUnder('dist/0.app.main.js', 40000)) + .then(() => process.chdir(currentDir)) + .then(() => skipCleaning()); +} diff --git a/tests/e2e/utils/assets.ts b/tests/e2e/utils/assets.ts new file mode 100644 index 000000000000..309d25702638 --- /dev/null +++ b/tests/e2e/utils/assets.ts @@ -0,0 +1,29 @@ +import {join} from 'path'; +import * as glob from 'glob'; +import {getGlobalVariable} from './env'; +import {relative} from 'path'; +import {copyFile} from './fs'; + + +export function assetDir(assetName: string) { + return join(__dirname, '../assets', assetName); +} + + +export function copyAssets(assetName: string) { + const tempRoot = join(getGlobalVariable('tmp-root'), 'assets', assetName); + const root = assetDir(assetName); + + return Promise.resolve() + .then(() => { + const allFiles = glob.sync(join(root, '**/*'), { dot: true, nodir: true }); + + return allFiles.reduce((promise, filePath) => { + const relPath = relative(root, filePath); + const toPath = join(tempRoot, relPath); + + return promise.then(() => copyFile(filePath, toPath)); + }, Promise.resolve()); + }) + .then(() => tempRoot); +} diff --git a/tests/e2e/utils/ast.ts b/tests/e2e/utils/ast.ts index d5a5376f9996..b23be570a8fd 100644 --- a/tests/e2e/utils/ast.ts +++ b/tests/e2e/utils/ast.ts @@ -1,15 +1,16 @@ import { insertImport as _insertImport, - addImportToModule as _addImportToModule + addImportToModule as _addImportToModule, + NodeHost } from '@angular-cli/ast-tools'; export function insertImport(file: string, symbol: string, module: string) { return _insertImport(file, symbol, module) - .then(change => change.apply()); + .then(change => change.apply(NodeHost)); } export function addImportToModule(file: string, symbol: string, module: string) { return _addImportToModule(file, symbol, module) - .then(change => change.apply()); + .then(change => change.apply(NodeHost)); } diff --git a/tests/e2e/utils/env.ts b/tests/e2e/utils/env.ts new file mode 100644 index 000000000000..dd80596f0698 --- /dev/null +++ b/tests/e2e/utils/env.ts @@ -0,0 +1,13 @@ +const global: {[name: string]: any} = Object.create(null); + + +export function setGlobalVariable(name: string, value: any) { + global[name] = value; +} + +export function getGlobalVariable(name: string): any { + if (!(name in global)) { + throw new Error(`Trying to access variable "${name}" but it's not defined.`); + } + return global[name]; +} diff --git a/tests/e2e/utils/fs.ts b/tests/e2e/utils/fs.ts index 5f42c98f1b93..f6a56ee76d43 100644 --- a/tests/e2e/utils/fs.ts +++ b/tests/e2e/utils/fs.ts @@ -1,4 +1,6 @@ import * as fs from 'fs'; +import {dirname} from 'path'; +import {stripIndents} from 'common-tags'; export function readFile(fileName: string) { @@ -52,6 +54,30 @@ export function moveFile(from: string, to: string) { } +function _recursiveMkDir(path: string) { + if (fs.existsSync(path)) { + return Promise.resolve(); + } else { + return _recursiveMkDir(dirname(path)) + .then(() => fs.mkdirSync(path)); + } +} + +export function copyFile(from: string, to: string) { + return _recursiveMkDir(dirname(to)) + .then(() => new Promise((resolve, reject) => { + const rd = fs.createReadStream(from); + rd.on('error', (err) => reject(err)); + + const wr = fs.createWriteStream(to); + wr.on('error', (err) => reject(err)); + wr.on('close', (ex) => resolve()); + + rd.pipe(wr); + })); +} + + export function writeMultipleFiles(fs: any) { return Object.keys(fs) .reduce((previous, curr) => { @@ -85,12 +111,29 @@ export function expectFileToMatch(fileName: string, regEx: RegExp | string) { .then(content => { if (typeof regEx == 'string') { if (content.indexOf(regEx) == -1) { - throw new Error(`File "${fileName}" did not contain "${regEx}"...`); + throw new Error(stripIndents`File "${fileName}" did not contain "${regEx}"... + Content: + ${content} + ------ + `); } } else { if (!content.match(regEx)) { - throw new Error(`File "${fileName}" did not match regex ${regEx}...`); + throw new Error(stripIndents`File "${fileName}" did not contain "${regEx}"... + Content: + ${content} + ------ + `); } } }); } + +export function expectFileSizeToBeUnder(fileName: string, sizeInBytes: number) { + return readFile(fileName) + .then(content => { + if (content.length > sizeInBytes) { + throw new Error(`File "${fileName}" exceeded file size of "${sizeInBytes}".`); + } + }); +} diff --git a/tests/e2e/utils/process.ts b/tests/e2e/utils/process.ts index d8d9a69be1d8..4746453ec424 100644 --- a/tests/e2e/utils/process.ts +++ b/tests/e2e/utils/process.ts @@ -20,8 +20,15 @@ function _exec(options: ExecOptions, cmd: string, args: string[]): Promise x !== undefined); - - console.log(blue(` Running \`${cmd} ${args.map(x => `"${x}"`).join(' ')}\`...`)); + const flags = [ + options.silent && 'silent', + options.waitForMatch && `matching(${options.waitForMatch})` + ] + .filter(x => !!x) // Remove false and undefined. + .join(', ') + .replace(/^(.+)$/, ' [$1]'); // Proper formatting. + + console.log(blue(` Running \`${cmd} ${args.map(x => `"${x}"`).join(' ')}\`${flags}...`)); console.log(blue(` CWD: ${cwd}`)); const spawnOptions: any = {cwd}; @@ -31,8 +38,8 @@ function _exec(options: ExecOptions, cmd: string, args: string[]): Promise { + const childProcess = child_process.spawn(cmd, args, spawnOptions); + childProcess.stdout.on('data', (data: Buffer) => { stdout += data.toString('utf-8'); if (options.silent) { return; @@ -42,7 +49,7 @@ function _exec(options: ExecOptions, cmd: string, args: string[]): Promise line !== '') .forEach(line => console.log(' ' + line)); }); - npmProcess.stderr.on('data', (data: Buffer) => { + childProcess.stderr.on('data', (data: Buffer) => { stderr += data.toString('utf-8'); data.toString('utf-8') .split(/[\n\r]+/) @@ -50,13 +57,13 @@ function _exec(options: ExecOptions, cmd: string, args: string[]): Promise console.error(yellow(' ' + line))); }); - _processes.push(npmProcess); + _processes.push(childProcess); // Create the error here so the stack shows who called this function. const err = new Error(`Running "${cmd} ${args.join(' ')}" returned error code `); return new Promise((resolve, reject) => { - npmProcess.on('exit', (error: any) => { - _processes = _processes.filter(p => p !== npmProcess); + childProcess.on('exit', (error: any) => { + _processes = _processes.filter(p => p !== childProcess); if (!error) { resolve(stdout); @@ -67,7 +74,7 @@ function _exec(options: ExecOptions, cmd: string, args: string[]): Promise { + childProcess.stdout.on('data', (data: Buffer) => { if (data.toString().match(options.waitForMatch)) { resolve(stdout); } @@ -113,6 +120,10 @@ export function npm(...args: string[]) { return _exec({}, 'npm', args); } +export function node(...args: string[]) { + return _exec({}, 'node', args); +} + export function git(...args: string[]) { return _exec({}, 'git', args); } diff --git a/tests/e2e_runner.js b/tests/e2e_runner.js index d173b9080ad9..bc96d6f0f535 100644 --- a/tests/e2e_runner.js +++ b/tests/e2e_runner.js @@ -2,6 +2,8 @@ 'use strict'; require('../lib/bootstrap-local'); +Error.stackTraceLimit = Infinity; + /** * This file is ran using the command line, not using Jasmine / Mocha. */ @@ -20,6 +22,7 @@ const white = chalk.white; /** * Here's a short description of those flags: * --debug If a test fails, block the thread so the temporary directory isn't deleted. + * --noproject Skip creating a project or using one. * --nolink Skip linking your local angular-cli directory. Can save a few seconds. * --nightly Install angular nightly builds over the test project. * --reuse=/path Use a path instead of create a new project. That project should have been @@ -28,7 +31,7 @@ const white = chalk.white; * If unnamed flags are passed in, the list of tests will be filtered to include only those passed. */ const argv = minimist(process.argv.slice(2), { - 'boolean': ['debug', 'nolink', 'nightly'], + 'boolean': ['debug', 'nolink', 'nightly', 'noproject'], 'string': ['reuse'] }); @@ -53,9 +56,9 @@ const testsToRun = allSetups } return argv._.some(argName => { - return path.join(process.cwd(), argName) == path.join(__dirname, name) - || argName == name - || argName == name.replace(/\.ts$/, ''); + return path.join(process.cwd(), argName) == path.join(__dirname, 'e2e', name) + || argName == name + || argName == name.replace(/\.ts$/, ''); }); })); @@ -88,12 +91,14 @@ testsToRun.reduce((previous, relativeName) => { throw new Error('Invalid test module.'); }; + let clean = true; return Promise.resolve() .then(() => printHeader(currentFileName)) - .then(() => fn(argv)) + .then(() => fn(argv, () => clean = false)) .then(() => { - // Only clean after a real test, not a setup step. - if (allSetups.indexOf(relativeName) == -1) { + // Only clean after a real test, not a setup step. Also skip cleaning if the test + // requested an exception. + if (allSetups.indexOf(relativeName) == -1 && clean) { return gitClean(); } }) diff --git a/tsconfig.json b/tsconfig.json index 8a6e1ba0aad8..1055b93bdb0f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "rootDir": ".", "sourceMap": true, "sourceRoot": "", + "inlineSourceMap": true, "target": "es5", "lib": ["es6"], "baseUrl": "", @@ -24,7 +25,7 @@ "angular-cli/*": [ "./packages/angular-cli/*" ], "@angular-cli/ast-tools": [ "./packages/ast-tools/src" ], "@angular-cli/base-href-webpack": [ "./packages/base-href-webpack/src" ], - "@angular-cli/webpack": [ "./packages/webpack/src" ] + "@ngtools/webpack": [ "./packages/webpack/src" ] } }, "exclude": [ From 82589b5c25523bbd6910bc3853aa90dbfd2ce6df Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Tue, 27 Sep 2016 19:47:22 -0700 Subject: [PATCH 20/35] 1.0.0-beta.16 --- CHANGELOG.md | 19 +++++++++++++++++++ package.json | 2 +- packages/angular-cli/package.json | 2 +- packages/ast-tools/package.json | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9e221aca32..0025ae6129cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ + +# [1.0.0-beta.16](https://github.com/angular/angular-cli/compare/v1.0.0-beta.15...v1.0.0-beta.16) (2016-09-28) + + +### Bug Fixes + +* **build:** fail ng build on error ([#2360](https://github.com/angular/angular-cli/issues/2360)) ([aa48c30](https://github.com/angular/angular-cli/commit/aa48c30)), closes [#2014](https://github.com/angular/angular-cli/issues/2014) +* **build:** use config output path as default ([#2158](https://github.com/angular/angular-cli/issues/2158)) ([49a120b](https://github.com/angular/angular-cli/commit/49a120b)) +* **generate:** Update directive.spec.ts blueprint to fix incorret import ([#1940](https://github.com/angular/angular-cli/issues/1940)) ([93da512](https://github.com/angular/angular-cli/commit/93da512)) +* **karma:** set defaults for karma.conf.js ([#1837](https://github.com/angular/angular-cli/issues/1837)) ([e2e94a5](https://github.com/angular/angular-cli/commit/e2e94a5)) +* **test:** correctly report packages spec failures ([#2238](https://github.com/angular/angular-cli/issues/2238)) ([3102453](https://github.com/angular/angular-cli/commit/3102453)) + + +### Features + +* **webpackDevServer:** Add watchOptions for webpackDevServer ([#1814](https://github.com/angular/angular-cli/issues/1814)) ([ce03088](https://github.com/angular/angular-cli/commit/ce03088)) + + + # [1.0.0-beta.15](https://github.com/angular/angular-cli/compare/v1.0.0-beta.14...v1.0.0-beta.15) (2016-09-20) diff --git a/package.json b/package.json index 8175a2c0bd5d..b35d63c57401 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-cli", - "version": "1.0.0-beta.15", + "version": "1.0.0-beta.16", "description": "CLI tool for Angular", "main": "packages/angular-cli/lib/cli/index.js", "trackingCode": "UA-8594346-19", diff --git a/packages/angular-cli/package.json b/packages/angular-cli/package.json index 55752eadc976..0f665d849538 100644 --- a/packages/angular-cli/package.json +++ b/packages/angular-cli/package.json @@ -1,6 +1,6 @@ { "name": "angular-cli", - "version": "1.0.0-beta.15", + "version": "1.0.0-beta.16", "description": "CLI tool for Angular", "main": "lib/cli/index.js", "trackingCode": "UA-8594346-19", diff --git a/packages/ast-tools/package.json b/packages/ast-tools/package.json index cb551bfe1586..6d7cec56b0f3 100644 --- a/packages/ast-tools/package.json +++ b/packages/ast-tools/package.json @@ -1,6 +1,6 @@ { "name": "@angular-cli/ast-tools", - "version": "1.0.1", + "version": "1.0.2", "description": "CLI tool for Angular", "main": "./src/index.js", "keywords": [ From a8a07ee97a806b75096c4abac9157b6663de0017 Mon Sep 17 00:00:00 2001 From: Sean Larkin Date: Wed, 28 Sep 2016 03:10:49 -0500 Subject: [PATCH 21/35] chore(npm): updated webpack version and config cleanup (#2237) * chore(npm): updated webpack version and reworked configs for breaking changes * fix: swapped enforce rules by accident, fix this * fix: fix version, as well as convert loaders to rules for future api changes * chore: update webpack to beta.24 * chore(npm): updated webpack to beta.25 * chore(lint): remove rogue double quote * Added augmented module typings for webpack v2 supporting webpack.LoaderOptionsPlugin * chore(lint): linting chores spaces, return lines, semis * chore(npm): updated both package.jsons cause who forgets to do that --- package.json | 2 +- .../models/webpack-build-common.ts | 14 ++---- .../models/webpack-build-development.ts | 30 +++++++++--- .../models/webpack-build-production.ts | 39 ++++++++------- .../angular-cli/models/webpack-build-test.js | 48 ++++++++++--------- .../models/webpack-build-typescript.ts | 4 +- packages/angular-cli/package.json | 2 +- 7 files changed, 79 insertions(+), 60 deletions(-) diff --git a/package.json b/package.json index b35d63c57401..4fea554a5c1c 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "typedoc": "^0.4.2", "typescript": "2.0.2", "url-loader": "^0.5.7", - "webpack": "2.1.0-beta.22", + "webpack": "2.1.0-beta.25", "webpack-dev-server": "2.1.0-beta.3", "webpack-md5-hash": "0.0.5", "webpack-merge": "^0.14.0", diff --git a/packages/angular-cli/models/webpack-build-common.ts b/packages/angular-cli/models/webpack-build-common.ts index a11514fcd1db..ec755c36979a 100644 --- a/packages/angular-cli/models/webpack-build-common.ts +++ b/packages/angular-cli/models/webpack-build-common.ts @@ -33,8 +33,7 @@ export function getWebpackCommonConfig( return { devtool: 'source-map', resolve: { - extensions: ['', '.ts', '.js'], - root: appRoot + extensions: ['.ts', '.js'] }, context: path.resolve(__dirname, './'), entry: entry, @@ -43,18 +42,15 @@ export function getWebpackCommonConfig( filename: '[name].bundle.js' }, module: { - preLoaders: [ + rules: [ { + enforce: 'pre', test: /\.js$/, loader: 'source-map-loader', exclude: [ /node_modules/ ] - } - ], - loaders: [ - // TypeScript loaders are separated into webpack-build-typescript. - + }, // in main, load css as raw text        { exclude: styles, @@ -140,7 +136,7 @@ export function getWebpackCommonConfig( ], node: { fs: 'empty', - global: 'window', + global: true, crypto: 'empty', module: false, clearImmediate: false, diff --git a/packages/angular-cli/models/webpack-build-development.ts b/packages/angular-cli/models/webpack-build-development.ts index 50c5d9a55efc..5fb423d6edb2 100644 --- a/packages/angular-cli/models/webpack-build-development.ts +++ b/packages/angular-cli/models/webpack-build-development.ts @@ -1,5 +1,17 @@ const path = require('path'); +import * as webpack from 'webpack'; + +declare module 'webpack' { + export interface LoaderOptionsPlugin {} + export interface LoaderOptionsPluginStatic { + new (optionsObject: any): LoaderOptionsPlugin; + } + interface Webpack { + LoaderOptionsPlugin: LoaderOptionsPluginStatic; + } +}; + export const getWebpackDevConfigPartial = function(projectRoot: string, appConfig: any) { return { devtool: 'source-map', @@ -9,14 +21,20 @@ export const getWebpackDevConfigPartial = function(projectRoot: string, appConfi sourceMapFilename: '[name].map', chunkFilename: '[id].chunk.js' }, - tslint: { - emitErrors: false, - failOnHint: false, - resourcePath: path.resolve(projectRoot, appConfig.root) - }, + plugins: [ + new webpack.LoaderOptionsPlugin({ + options: { + tslint: { + emitErrors: false, + failOnHint: false, + resourcePath: path.resolve(projectRoot, appConfig.root) + }, + } + }) + ], node: { fs: 'empty', - global: 'window', + global: true, crypto: 'empty', process: true, module: false, diff --git a/packages/angular-cli/models/webpack-build-production.ts b/packages/angular-cli/models/webpack-build-production.ts index da7c9b636281..d1c7b24b1a45 100644 --- a/packages/angular-cli/models/webpack-build-production.ts +++ b/packages/angular-cli/models/webpack-build-production.ts @@ -5,7 +5,6 @@ import * as webpack from 'webpack'; export const getWebpackProdConfigPartial = function(projectRoot: string, appConfig: any) { return { - debug: false, devtool: 'source-map', output: { path: path.resolve(projectRoot, appConfig.outDir), @@ -25,27 +24,31 @@ export const getWebpackProdConfigPartial = function(projectRoot: string, appConf test: /\.js$|\.html$/, threshold: 10240, minRatio: 0.8 + }), + new webpack.LoaderOptionsPlugin({ + options: { + tslint: { + emitErrors: true, + failOnHint: true, + resourcePath: path.resolve(projectRoot, appConfig.root) + }, + htmlLoader: { + minimize: true, + removeAttributeQuotes: false, + caseSensitive: true, + customAttrSurround: [ + [/#/, /(?:)/], + [/\*/, /(?:)/], + [/\[?\(?/, /(?:)/] + ], + customAttrAssign: [/\)?\]?=/] + } + } }) ], - tslint: { - emitErrors: true, - failOnHint: true, - resourcePath: path.resolve(projectRoot, appConfig.root) - }, - htmlLoader: { - minimize: true, - removeAttributeQuotes: false, - caseSensitive: true, - customAttrSurround: [ - [/#/, /(?:)/], - [/\*/, /(?:)/], - [/\[?\(?/, /(?:)/] - ], - customAttrAssign: [/\)?\]?=/] - }, node: { fs: 'empty', - global: 'window', + global: true, crypto: 'empty', process: true, module: false, diff --git a/packages/angular-cli/models/webpack-build-test.js b/packages/angular-cli/models/webpack-build-test.js index dfb6deba34ff..403ed91fdeec 100644 --- a/packages/angular-cli/models/webpack-build-test.js +++ b/packages/angular-cli/models/webpack-build-test.js @@ -11,8 +11,7 @@ const getWebpackTestConfig = function (projectRoot, environment, appConfig) { devtool: 'inline-source-map', context: path.resolve(__dirname, './'), resolve: { - extensions: ['', '.ts', '.js'], - root: appRoot + extensions: ['.ts', '.js'] }, entry: { test: path.resolve(appRoot, appConfig.test) @@ -22,9 +21,10 @@ const getWebpackTestConfig = function (projectRoot, environment, appConfig) { filename: '[name].bundle.js' }, module: { - preLoaders: [ + rules: [ { test: /\.ts$/, + enforce: 'pre', loader: 'tslint-loader', exclude: [ path.resolve(projectRoot, 'node_modules') @@ -32,14 +32,13 @@ const getWebpackTestConfig = function (projectRoot, environment, appConfig) { }, { test: /\.js$/, + enforce: 'pre', loader: 'source-map-loader', exclude: [ path.resolve(projectRoot, 'node_modules/rxjs'), path.resolve(projectRoot, 'node_modules/@angular') ] - } - ], - loaders: [ + }, { test: /\.ts$/, loaders: [ @@ -58,23 +57,22 @@ const getWebpackTestConfig = function (projectRoot, environment, appConfig) { ], exclude: [/\.e2e\.ts$/] }, - { test: /\.json$/, loader: 'json-loader' }, - { test: /\.css$/, loaders: ['raw-loader', 'postcss-loader'] }, - { test: /\.styl$/, loaders: ['raw-loader', 'postcss-loader', 'stylus-loader'] }, - { test: /\.less$/, loaders: ['raw-loader', 'postcss-loader', 'less-loader'] }, - { test: /\.scss$|\.sass$/, loaders: ['raw-loader', 'postcss-loader', 'sass-loader'] }, - { test: /\.(jpg|png)$/, loader: 'url-loader?limit=128000' }, - { test: /\.html$/, loader: 'raw-loader', exclude: [path.resolve(appRoot, appConfig.index)] } - ], - postLoaders: [ { test: /\.(js|ts)$/, loader: 'sourcemap-istanbul-instrumenter-loader', + enforce: 'post', exclude: [ /\.(e2e|spec)\.ts$/, /node_modules/ ], query: { 'force-sourcemap': true } - } + }, + { test: /\.json$/, loader: 'json-loader' }, + { test: /\.css$/, loaders: ['raw-loader', 'postcss-loader'] }, + { test: /\.styl$/, loaders: ['raw-loader', 'postcss-loader', 'stylus-loader'] }, + { test: /\.less$/, loaders: ['raw-loader', 'postcss-loader', 'less-loader'] }, + { test: /\.scss$|\.sass$/, loaders: ['raw-loader', 'postcss-loader', 'sass-loader'] }, + { test: /\.(jpg|png)$/, loader: 'url-loader?limit=128000' }, + { test: /\.html$/, loader: 'raw-loader', exclude: [path.resolve(appRoot, appConfig.index)] } ] }, plugins: [ @@ -89,16 +87,20 @@ const getWebpackTestConfig = function (projectRoot, environment, appConfig) { new RegExp(path.resolve(appRoot, appConfig.environments['source']) .replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&')), path.resolve(appRoot, appConfig.environments[environment]) - ) + ), + new webpack.LoaderOptionsPlugin({ + options: { + tslint: { + emitErrors: false, + failOnHint: false, + resourcePath: `./${appConfig.root}` + } + } + }) ], - tslint: { - emitErrors: false, - failOnHint: false, - resourcePath: `./${appConfig.root}` - }, node: { fs: 'empty', - global: 'window', + global: true, process: false, crypto: 'empty', module: false, diff --git a/packages/angular-cli/models/webpack-build-typescript.ts b/packages/angular-cli/models/webpack-build-typescript.ts index 9ed49ecfc9ab..339b03c604f6 100644 --- a/packages/angular-cli/models/webpack-build-typescript.ts +++ b/packages/angular-cli/models/webpack-build-typescript.ts @@ -17,7 +17,7 @@ export const getWebpackNonAotConfigPartial = function(projectRoot: string, appCo return { module: { - loaders: [ + rules: [ { test: /\.ts$/, loaders: [{ @@ -43,7 +43,7 @@ export const getWebpackNonAotConfigPartial = function(projectRoot: string, appCo export const getWebpackAotConfigPartial = function(projectRoot: string, appConfig: any) { return { module: { - loaders: [ + rules: [ { test: /\.ts$/, loader: webpackLoader, diff --git a/packages/angular-cli/package.json b/packages/angular-cli/package.json index 0f665d849538..15f55fd89a29 100644 --- a/packages/angular-cli/package.json +++ b/packages/angular-cli/package.json @@ -93,7 +93,7 @@ "typedoc": "^0.4.2", "typescript": "2.0.2", "url-loader": "^0.5.7", - "webpack": "2.1.0-beta.22", + "webpack": "2.1.0-beta.25", "webpack-dev-server": "2.1.0-beta.3", "webpack-md5-hash": "0.0.5", "webpack-merge": "^0.14.0", From 73c9b7c2219647cfacb122537256cd51ab6266c0 Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Wed, 28 Sep 2016 18:33:42 +0100 Subject: [PATCH 22/35] chore(new): use tilde versions for @angular (#2387) --- .../blueprints/ng2/files/package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/angular-cli/blueprints/ng2/files/package.json b/packages/angular-cli/blueprints/ng2/files/package.json index dd92fc612e45..ff5f4fae3c83 100644 --- a/packages/angular-cli/blueprints/ng2/files/package.json +++ b/packages/angular-cli/blueprints/ng2/files/package.json @@ -12,21 +12,21 @@ }, "private": true, "dependencies": { - "@angular/common": "2.0.0", - "@angular/compiler": "2.0.0", - "@angular/core": "2.0.0", - "@angular/forms": "2.0.0", - "@angular/http": "2.0.0", - "@angular/platform-browser": "2.0.0", - "@angular/platform-browser-dynamic": "2.0.0", - "@angular/router": "3.0.0", + "@angular/common": "~2.0.0", + "@angular/compiler": "~2.0.0", + "@angular/core": "~2.0.0", + "@angular/forms": "~2.0.0", + "@angular/http": "~2.0.0", + "@angular/platform-browser": "~2.0.0", + "@angular/platform-browser-dynamic": "~2.0.0", + "@angular/router": "~3.0.0", "core-js": "^2.4.1", "rxjs": "5.0.0-beta.12", "ts-helpers": "^1.1.1", "zone.js": "^0.6.23" }, "devDependencies": {<% if(isMobile) { %> - "@angular/platform-server": "2.0.0", + "@angular/platform-server": "~2.0.0", "@angular/service-worker": "0.2.0", "@angular/app-shell": "0.0.0", "angular2-universal":"0.104.5", From e10ca5316a72adb8942aa8ae21daf8d5e00daf56 Mon Sep 17 00:00:00 2001 From: Clemens Tolboom Date: Wed, 28 Sep 2016 20:31:56 +0200 Subject: [PATCH 23/35] feat(template): issue template look better (#2384) The current template produces disjunct list items so the numbering contains only 1's. Let's make this a H3 --- .github/ISSUE_TEMPLATE.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 92f0cfde8292..fa7c45bbb104 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,22 +1,23 @@ > Please provide us with the following information: > --------------------------------------------------------------- -1. OS? Windows 7, 8 or 10. Linux (which distribution). Mac OSX (Yosemite? El Capitan?) +### OS? +> Windows 7, 8 or 10. Linux (which distribution). Mac OSX (Yosemite? El Capitan?) -2. Versions. Please run `ng --version`. If there's nothing outputted, please run -in a Terminal: `node --version` and paste the result here: +### Versions. +> Please run `ng --version`. If there's nothing outputted, please run in a Terminal: `node --version` and paste the result here: -3. Repro steps. Was this an app that wasn't created using the CLI? What change did you - do on your code? etc. +### Repro steps. +> Was this an app that wasn't created using the CLI? What change did you do on your code? etc. -4. The log given by the failure. Normally this include a stack trace and some - more information. +### The log given by the failure. +> Normally this include a stack trace and some more information. -5. Mention any other details that might be useful. +### Mention any other details that might be useful. > --------------------------------------------------------------- > Thanks! We'll be in touch soon. From 5aa9cf0c9dd81307af9c60a86d203002c3d65a7c Mon Sep 17 00:00:00 2001 From: Suhas Date: Wed, 28 Sep 2016 14:32:20 -0400 Subject: [PATCH 24/35] fix(typo): fixed typo in README (#2383) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e2f109ff60a..29eb76208584 100644 --- a/README.md +++ b/README.md @@ -319,7 +319,7 @@ export class AppComponent { } ``` -When generating a new project you can also define which extention you want for +When generating a new project you can also define which extension you want for style files: ```bash From 8b0f4d48bd544f430688065cea0387d440a217e5 Mon Sep 17 00:00:00 2001 From: Grzegorz Rozdzialik Date: Wed, 28 Sep 2016 20:33:19 +0200 Subject: [PATCH 25/35] fix(generate): use prefix when initializing app (#2046) (#2367) E2E test would fail when using a prefix to generate the app --- .../blueprints/ng2/files/__path__/app/app.component.spec.ts | 4 ++-- .../blueprints/ng2/files/__path__/app/app.component.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/angular-cli/blueprints/ng2/files/__path__/app/app.component.spec.ts b/packages/angular-cli/blueprints/ng2/files/__path__/app/app.component.spec.ts index dfdebd7b9d19..7c6fe5eb99e3 100644 --- a/packages/angular-cli/blueprints/ng2/files/__path__/app/app.component.spec.ts +++ b/packages/angular-cli/blueprints/ng2/files/__path__/app/app.component.spec.ts @@ -21,13 +21,13 @@ describe('App: <%= jsComponentName %>', () => { it(`should have as title 'app works!'`, async(() => { let fixture = TestBed.createComponent(AppComponent); let app = fixture.debugElement.componentInstance; - expect(app.title).toEqual('app works!'); + expect(app.title).toEqual('<%= prefix %> works!'); })); it('should render title in a h1 tag', async(() => { let fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); let compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('h1').textContent).toContain('app works!'); + expect(compiled.querySelector('h1').textContent).toContain('<%= prefix %> works!'); })); }); diff --git a/packages/angular-cli/blueprints/ng2/files/__path__/app/app.component.ts b/packages/angular-cli/blueprints/ng2/files/__path__/app/app.component.ts index 0d430aed8ce3..f8348b911a4a 100644 --- a/packages/angular-cli/blueprints/ng2/files/__path__/app/app.component.ts +++ b/packages/angular-cli/blueprints/ng2/files/__path__/app/app.component.ts @@ -13,5 +13,5 @@ import { APP_SHELL_DIRECTIVES } from '@angular/app-shell';<% } %> styleUrls: ['./app.component.<%= styleExt %>']<% } %> }) export class AppComponent { - title = 'app works!'; + title = '<%= prefix %> works!'; } From 24a791d2828af04689dd439726189b84f81eaf08 Mon Sep 17 00:00:00 2001 From: Mike Brocchi Date: Fri, 30 Sep 2016 10:40:29 -0400 Subject: [PATCH 26/35] feature(route): allow lazy route reference by relative paths (#2434) --- packages/angular-cli/models/find-lazy-modules.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/angular-cli/models/find-lazy-modules.ts b/packages/angular-cli/models/find-lazy-modules.ts index 34d3ba74b4f5..cb838b6e6f83 100644 --- a/packages/angular-cli/models/find-lazy-modules.ts +++ b/packages/angular-cli/models/find-lazy-modules.ts @@ -55,7 +55,12 @@ export function findLazyModules(projectRoot: any): {[key: string]: string} { glob.sync(path.join(projectRoot, '/**/*.ts')) .forEach(tsPath => { findLoadChildren(tsPath).forEach(moduleName => { - const fileName = path.resolve(projectRoot, moduleName) + '.ts'; + let fileName = path.resolve(projectRoot, moduleName) + '.ts'; + // If path is a relative path + if (moduleName.startsWith('.')) { + const tsPathInfo = path.parse(tsPath); + fileName = path.resolve(tsPathInfo.dir, moduleName) + '.ts'; + } if (fs.existsSync(fileName)) { // Put the moduleName as relative to the main.ts. result[moduleName] = fileName; From 402798053883d8c4d1d6275d82be9edf3b22cc59 Mon Sep 17 00:00:00 2001 From: Mike Brocchi Date: Fri, 30 Sep 2016 11:31:46 -0400 Subject: [PATCH 27/35] bug(route): fix the class names for --routing in ng new (#2435) Fixes #2433 --- .../blueprints/ng2/files/__path__/app/app-routing.module.ts | 2 +- .../blueprints/ng2/files/__path__/app/app.module.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/angular-cli/blueprints/ng2/files/__path__/app/app-routing.module.ts b/packages/angular-cli/blueprints/ng2/files/__path__/app/app-routing.module.ts index 405cb10d7d3d..9e01aadffa7e 100644 --- a/packages/angular-cli/blueprints/ng2/files/__path__/app/app-routing.module.ts +++ b/packages/angular-cli/blueprints/ng2/files/__path__/app/app-routing.module.ts @@ -8,4 +8,4 @@ const routes: Routes = []; exports: [RouterModule], providers: [] }) -export class <%= classifiedModuleName %>RoutingModule { } +export class AppRoutingModule { } diff --git a/packages/angular-cli/blueprints/ng2/files/__path__/app/app.module.ts b/packages/angular-cli/blueprints/ng2/files/__path__/app/app.module.ts index a4d339d7b9dc..51485d6538e3 100644 --- a/packages/angular-cli/blueprints/ng2/files/__path__/app/app.module.ts +++ b/packages/angular-cli/blueprints/ng2/files/__path__/app/app.module.ts @@ -2,7 +2,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http';<% if (routing) { %> -import { <%= classifiedModuleName %>RoutingModule } from './<%= dasherizedModuleName %>-routing.module';<% } %> +import { AppRoutingModule } from './app-routing.module';<% } %> import { AppComponent } from './app.component'; @@ -14,7 +14,7 @@ import { AppComponent } from './app.component'; BrowserModule, FormsModule, HttpModule<% if (routing) { %>, - <%= classifiedModuleName %>RoutingModule<% } %> + AppRoutingModule<% } %> ], providers: [], bootstrap: [AppComponent] From 61f266a91892ef1c306dea6b6d3a735804d3cd6b Mon Sep 17 00:00:00 2001 From: Mike Brocchi Date: Sat, 1 Oct 2016 15:20:25 -0400 Subject: [PATCH 28/35] feature(new): allow specification of --inline-style & --inline-template for new apps (#2455) Fixes #2446 --- .../ng2/files/__path__/app/app.component.ts | 10 +++--- packages/angular-cli/blueprints/ng2/index.js | 20 +++++++++--- packages/angular-cli/commands/init.ts | 8 +++-- packages/angular-cli/commands/new.ts | 4 ++- tests/acceptance/init.spec.js | 32 ++++++++++++++++--- tests/acceptance/new.spec.js | 16 ++++++++++ 6 files changed, 72 insertions(+), 18 deletions(-) diff --git a/packages/angular-cli/blueprints/ng2/files/__path__/app/app.component.ts b/packages/angular-cli/blueprints/ng2/files/__path__/app/app.component.ts index f8348b911a4a..a7d81f09063c 100644 --- a/packages/angular-cli/blueprints/ng2/files/__path__/app/app.component.ts +++ b/packages/angular-cli/blueprints/ng2/files/__path__/app/app.component.ts @@ -2,14 +2,14 @@ import { Component } from '@angular/core';<% if (isMobile) { %> import { APP_SHELL_DIRECTIVES } from '@angular/app-shell';<% } %> @Component({ - selector: '<%= prefix %>-root', - <% if (isMobile) { %>template: ` + selector: '<%= prefix %>-root',<% if (isMobile || inlineTemplate) { %> + template: `

{{title}}

- `, - styles: [], - directives: [APP_SHELL_DIRECTIVES]<% } else { %>templateUrl: './app.component.html', + `,<% } else { %> + templateUrl: './app.component.html',<% } %><% if (isMobile || inlineStyle) { %> + styles: []<% } else { %> styleUrls: ['./app.component.<%= styleExt %>']<% } %> }) export class AppComponent { diff --git a/packages/angular-cli/blueprints/ng2/index.js b/packages/angular-cli/blueprints/ng2/index.js index a73844862a67..70a9f35d31ab 100644 --- a/packages/angular-cli/blueprints/ng2/index.js +++ b/packages/angular-cli/blueprints/ng2/index.js @@ -11,7 +11,9 @@ module.exports = { { name: 'prefix', type: String, default: 'app', aliases: ['p'] }, { name: 'style', type: String, default: 'css' }, { name: 'mobile', type: Boolean, default: false }, - { name: 'routing', type: Boolean, default: false } + { name: 'routing', type: Boolean, default: false }, + { name: 'inline-style', type: Boolean, default: false, aliases: ['is'] }, + { name: 'inline-template', type: Boolean, default: false, aliases: ['it'] } ], afterInstall: function (options) { @@ -40,18 +42,26 @@ module.exports = { styleExt: this.styleExt, relativeRootPath: relativeRootPath, isMobile: options.mobile, - routing: options.routing + routing: options.routing, + inlineStyle: options.inlineStyle, + inlineTemplate: options.inlineTemplate }; }, files: function() { var fileList = getFiles.call(this); if (this.options && this.options.mobile) { - fileList = fileList.filter(p => p.indexOf('__name__.component.html') < 0); - fileList = fileList.filter(p => p.indexOf('__name__.component.__styleext__') < 0); + fileList = fileList.filter(p => p.indexOf('app.component.html') < 0); + fileList = fileList.filter(p => p.indexOf('app.component.__styleext__') < 0); } if (this.options && !this.options.routing) { - fileList = fileList.filter(p => p.indexOf('__name__-routing.module.ts') < 0); + fileList = fileList.filter(p => p.indexOf('app-routing.module.ts') < 0); + } + if (this.options && this.options.inlineTemplate) { + fileList = fileList.filter(p => p.indexOf('app.component.html') < 0); + } + if (this.options && this.options.inlineStyle) { + fileList = fileList.filter(p => p.indexOf('app.component.__styleext__') < 0); } return fileList; diff --git a/packages/angular-cli/commands/init.ts b/packages/angular-cli/commands/init.ts index 51e76ebcbe88..491192f90614 100644 --- a/packages/angular-cli/commands/init.ts +++ b/packages/angular-cli/commands/init.ts @@ -27,7 +27,9 @@ const InitCommand: any = Command.extend({ { name: 'style', type: String, default: 'css' }, { name: 'prefix', type: String, default: 'app', aliases: ['p'] }, { name: 'mobile', type: Boolean, default: false }, - { name: 'routing', type: Boolean, default: false } + { name: 'routing', type: Boolean, default: false }, + { name: 'inline-style', type: Boolean, default: false, aliases: ['is'] }, + { name: 'inline-template', type: Boolean, default: false, aliases: ['it'] } ], anonymousOptions: [''], @@ -106,7 +108,9 @@ const InitCommand: any = Command.extend({ style: commandOptions.style, prefix: commandOptions.prefix, mobile: commandOptions.mobile, - routing: commandOptions.routing + routing: commandOptions.routing, + inlineStyle: commandOptions.inlineStyle, + inlineTemplate: commandOptions.inlineTemplate }; if (!validProjectName(packageName)) { diff --git a/packages/angular-cli/commands/new.ts b/packages/angular-cli/commands/new.ts index a23886e60c9e..27fbbb9acfbc 100644 --- a/packages/angular-cli/commands/new.ts +++ b/packages/angular-cli/commands/new.ts @@ -25,7 +25,9 @@ const NewCommand = Command.extend({ { name: 'style', type: String, default: 'css' }, { name: 'prefix', type: String, default: 'app', aliases: ['p'] }, { name: 'mobile', type: Boolean, default: false }, - { name: 'routing', type: Boolean, default: false } + { name: 'routing', type: Boolean, default: false }, + { name: 'inline-style', type: Boolean, default: false, aliases: ['is'] }, + { name: 'inline-template', type: Boolean, default: false, aliases: ['it'] } ], run: function (commandOptions: any, rawArgs: string[]) { diff --git a/tests/acceptance/init.spec.js b/tests/acceptance/init.spec.js index 964397d8db78..6be6287337c2 100644 --- a/tests/acceptance/init.spec.js +++ b/tests/acceptance/init.spec.js @@ -17,6 +17,7 @@ var unique = require('lodash/uniq'); var forEach = require('lodash/forEach'); var any = require('lodash/some'); var EOL = require('os').EOL; +var existsSync = require('exists-sync'); var defaultIgnoredFiles = Blueprint.ignoredFiles; @@ -44,7 +45,8 @@ describe('Acceptance: ng init', function () { return tmp.teardown('./tmp'); }); - function confirmBlueprinted(isMobile) { + function confirmBlueprinted(isMobile, routing) { + routing = !!routing; var blueprintPath = path.join(root, 'blueprints', 'ng2', 'files'); var mobileBlueprintPath = path.join(root, 'blueprints', 'mobile', 'files'); var expected = unique(walkSync(blueprintPath).concat(isMobile ? walkSync(mobileBlueprintPath) : []).sort()); @@ -55,14 +57,18 @@ describe('Acceptance: ng init', function () { }); expected.forEach(function (file, index) { - expected[index] = file.replace(/__name__/g, 'tmp'); + expected[index] = file.replace(/__name__/g, 'app'); expected[index] = expected[index].replace(/__styleext__/g, 'css'); expected[index] = expected[index].replace(/__path__/g, 'src'); }); - + if (isMobile) { - expected = expected.filter(p => p.indexOf('tmp.component.html') < 0); - expected = expected.filter(p => p.indexOf('tmp.component.css') < 0); + expected = expected.filter(p => p.indexOf('app.component.html') < 0); + expected = expected.filter(p => p.indexOf('app.component.css') < 0); + } + + if (!routing) { + expected = expected.filter(p => p.indexOf('app-routing.module.ts') < 0); } removeIgnored(expected); @@ -200,4 +206,20 @@ describe('Acceptance: ng init', function () { }) .then(confirmBlueprinted); }); + + it('ng init --inline-template does not generate a template file', () => { + return ng(['init', '--skip-npm', '--skip-git', '--inline-template']) + .then(() => { + const templateFile = path.join('src', 'app', 'app.component.html'); + expect(existsSync(templateFile)).to.equal(false); + }); + }); + + it('ng init --inline-style does not gener a style file', () => { + return ng(['init', '--skip-npm', '--skip-git', '--inline-style']) + .then(() => { + const styleFile = path.join('src', 'app', 'app.component.css'); + expect(existsSync(styleFile)).to.equal(false); + }); + }); }); diff --git a/tests/acceptance/new.spec.js b/tests/acceptance/new.spec.js index cb3449f6d83b..533f2893d97d 100644 --- a/tests/acceptance/new.spec.js +++ b/tests/acceptance/new.spec.js @@ -168,4 +168,20 @@ describe('Acceptance: ng new', function () { expect(pkgJson.name).to.equal('foo', 'uses app name for package name'); }); }); + + it('ng new --inline-template does not generate a template file', () => { + return ng(['new', 'foo', '--skip-npm', '--skip-git', '--inline-template']) + .then(() => { + const templateFile = path.join('src', 'app', 'app.component.html'); + expect(existsSync(templateFile)).to.equal(false); + }); + }); + + it('ng new --inline-style does not gener a style file', () => { + return ng(['new', 'foo', '--skip-npm', '--skip-git', '--inline-style']) + .then(() => { + const styleFile = path.join('src', 'app', 'app.component.css'); + expect(existsSync(styleFile)).to.equal(false); + }); + }); }); From 2a562b6617abdd1ad51beb3d8ab2e0b7a6bf1b6f Mon Sep 17 00:00:00 2001 From: Mike Brocchi Date: Sat, 1 Oct 2016 17:27:48 -0400 Subject: [PATCH 29/35] feature(config): config inline template & style in angular-cli.json (#2457) Fixes #2447 --- package.json | 2 +- packages/angular-cli/addon/index.js | 3 ++- packages/angular-cli/blueprints/component/index.js | 12 ++++++++++-- packages/angular-cli/lib/config/schema.d.ts | 4 ++++ packages/angular-cli/lib/config/schema.json | 13 +++++++++++++ .../angular-cli/models/json-schema/schema-tree.ts | 2 +- 6 files changed, 31 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4fea554a5c1c..8a11064fddad 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "test:cli": "node tests/runner", "test:inspect": "node --inspect --debug-brk tests/runner", "test:packages": "node scripts/run-packages-spec.js", - "build-config-interface": "dtsgen lib/config/schema.json --out lib/config/schema.d.ts", + "build-config-interface": "dtsgen packages/angular-cli/lib/config/schema.json --out packages/angular-cli/lib/config/schema.d.ts", "eslint": "eslint .", "tslint": "tslint \"**/*.ts\" -c tslint.json -e \"**/blueprints/*/files/**/*.ts\" -e \"node_modules/**\" -e \"tmp/**\" -e \"dist/**\"", "lint": "npm-run-all -c eslint tslint" diff --git a/packages/angular-cli/addon/index.js b/packages/angular-cli/addon/index.js index 13056bc09538..3a2d7abb1850 100644 --- a/packages/angular-cli/addon/index.js +++ b/packages/angular-cli/addon/index.js @@ -8,7 +8,8 @@ module.exports = { name: 'ng2', config: function () { - this.project.ngConfig = this.project.ngConfig || config.CliConfig.fromProject().config; + this.project.ngConfigObj = this.project.ngConfigObj || config.CliConfig.fromProject(); + this.project.ngConfig = this.project.ngConfig || this.project.ngConfigObj.config; }, blueprintsPath: function () { diff --git a/packages/angular-cli/blueprints/component/index.js b/packages/angular-cli/blueprints/component/index.js index e037aa260839..39243e1eb508 100644 --- a/packages/angular-cli/blueprints/component/index.js +++ b/packages/angular-cli/blueprints/component/index.js @@ -13,8 +13,8 @@ module.exports = { availableOptions: [ { name: 'flat', type: Boolean, default: false }, - { name: 'inline-template', type: Boolean, default: false, aliases: ['it'] }, - { name: 'inline-style', type: Boolean, default: false, aliases: ['is'] }, + { name: 'inline-template', type: Boolean, aliases: ['it'] }, + { name: 'inline-style', type: Boolean, aliases: ['is'] }, { name: 'prefix', type: Boolean, default: true }, { name: 'spec', type: Boolean, default: true } ], @@ -56,6 +56,14 @@ module.exports = { this.styleExt = this.project.ngConfig.defaults.styleExt; } + options.inlineStyle = options.inlineStyle !== undefined ? + options.inlineStyle : + this.project.ngConfigObj.get('defaults.inline.style'); + + options.inlineTemplate = options.inlineTemplate !== undefined ? + options.inlineTemplate : + this.project.ngConfigObj.get('defaults.inline.template'); + return { dynamicPath: this.dynamicPath.dir.replace(this.dynamicPath.appRoot, ''), flat: options.flat, diff --git a/packages/angular-cli/lib/config/schema.d.ts b/packages/angular-cli/lib/config/schema.d.ts index bb47f47212a8..2c09a9471ca3 100644 --- a/packages/angular-cli/lib/config/schema.d.ts +++ b/packages/angular-cli/lib/config/schema.d.ts @@ -60,5 +60,9 @@ export interface CliConfig { styleExt?: string; prefixInterfaces?: boolean; poll?: number; + inline?: { + style?: boolean; + template?: boolean; + }; }; } diff --git a/packages/angular-cli/lib/config/schema.json b/packages/angular-cli/lib/config/schema.json index 5077846501e6..8abcc7870466 100644 --- a/packages/angular-cli/lib/config/schema.json +++ b/packages/angular-cli/lib/config/schema.json @@ -138,6 +138,19 @@ }, "poll": { "type": "number" + }, + "inline": { + "type": "object", + "properties": { + "style": { + "type": "boolean", + "default": false + }, + "template": { + "type": "boolean", + "default": false + } + } } }, "additionalProperties": false diff --git a/packages/angular-cli/models/json-schema/schema-tree.ts b/packages/angular-cli/models/json-schema/schema-tree.ts index 68a46dee9394..51c46b7f013b 100644 --- a/packages/angular-cli/models/json-schema/schema-tree.ts +++ b/packages/angular-cli/models/json-schema/schema-tree.ts @@ -281,7 +281,7 @@ export abstract class LeafSchemaTreeNode extends SchemaTreeNode { constructor(metaData: TreeNodeConstructorArgument) { super(metaData); - this._defined = metaData.value !== undefined; + this._defined = !(metaData.value === undefined || metaData.value === null); if ('default' in metaData.schema) { this._default = metaData.schema['default']; } From 556fe5696af52053970639c5a81f9c6bdcb83f49 Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Sun, 2 Oct 2016 17:03:10 +0100 Subject: [PATCH 30/35] chore(new): remove blueprint option (#2390) --- packages/angular-cli/commands/init.ts | 7 +----- packages/angular-cli/commands/new.ts | 7 ++---- tests/acceptance/new.spec.js | 36 --------------------------- 3 files changed, 3 insertions(+), 47 deletions(-) diff --git a/packages/angular-cli/commands/init.ts b/packages/angular-cli/commands/init.ts index 491192f90614..1b300e43b588 100644 --- a/packages/angular-cli/commands/init.ts +++ b/packages/angular-cli/commands/init.ts @@ -18,7 +18,6 @@ const InitCommand: any = Command.extend({ availableOptions: [ { name: 'dry-run', type: Boolean, default: false, aliases: ['d'] }, { name: 'verbose', type: Boolean, default: false, aliases: ['v'] }, - { name: 'blueprint', type: String, aliases: ['b'] }, { name: 'link-cli', type: Boolean, default: false, aliases: ['lc'] }, { name: 'skip-npm', type: Boolean, default: false, aliases: ['sn'] }, { name: 'skip-bower', type: Boolean, default: true, aliases: ['sb'] }, @@ -34,10 +33,6 @@ const InitCommand: any = Command.extend({ anonymousOptions: [''], - _defaultBlueprint: function () { - return 'ng2'; - }, - run: function (commandOptions: any, rawArgs: string[]) { if (commandOptions.dryRun) { commandOptions.skipNpm = true; @@ -100,7 +95,7 @@ const InitCommand: any = Command.extend({ const blueprintOpts = { dryRun: commandOptions.dryRun, - blueprint: commandOptions.blueprint || this._defaultBlueprint(), + blueprint: 'ng2', rawName: packageName, targetFiles: rawArgs || '', rawArgs: rawArgs.toString(), diff --git a/packages/angular-cli/commands/new.ts b/packages/angular-cli/commands/new.ts index 27fbbb9acfbc..5737c0afaee8 100644 --- a/packages/angular-cli/commands/new.ts +++ b/packages/angular-cli/commands/new.ts @@ -1,11 +1,11 @@ import * as chalk from 'chalk'; +import InitCommand from './init'; + const Command = require('ember-cli/lib/models/command'); const Project = require('ember-cli/lib/models/project'); const SilentError = require('silent-error'); const validProjectName = require('ember-cli/lib/utilities/valid-project-name'); -const normalizeBlueprint = require('ember-cli/lib/utilities/normalize-blueprint-option'); -import InitCommand from './init'; const NewCommand = Command.extend({ name: 'new', @@ -15,7 +15,6 @@ const NewCommand = Command.extend({ availableOptions: [ { name: 'dry-run', type: Boolean, default: false, aliases: ['d'] }, { name: 'verbose', type: Boolean, default: false, aliases: ['v'] }, - { name: 'blueprint', type: String, default: 'ng2', aliases: ['b'] }, { name: 'link-cli', type: Boolean, default: false, aliases: ['lc'] }, { name: 'skip-npm', type: Boolean, default: false, aliases: ['sn'] }, { name: 'skip-bower', type: Boolean, default: true, aliases: ['sb'] }, @@ -62,8 +61,6 @@ const NewCommand = Command.extend({ )); } - commandOptions.blueprint = normalizeBlueprint(commandOptions.blueprint); - if (!commandOptions.directory) { commandOptions.directory = packageName; } diff --git a/tests/acceptance/new.spec.js b/tests/acceptance/new.spec.js index 533f2893d97d..1cdcc640b214 100644 --- a/tests/acceptance/new.spec.js +++ b/tests/acceptance/new.spec.js @@ -103,42 +103,6 @@ describe('Acceptance: ng new', function () { .then(confirmBlueprinted); }); - it('ng new with blueprint uses the specified blueprint directory with a relative path', - function () { - return tmp.setup('./tmp/my_blueprint') - .then(function () { - return tmp.setup('./tmp/my_blueprint/files'); - }) - .then(function () { - fs.writeFileSync('./tmp/my_blueprint/files/gitignore'); - process.chdir('./tmp'); - - return ng([ - 'new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', - '--blueprint=./my_blueprint' - ]); - }) - .then(confirmBlueprintedForDir('tmp/my_blueprint')); - }); - - it('ng new with blueprint uses the specified blueprint directory with an absolute path', - function () { - return tmp.setup('./tmp/my_blueprint') - .then(function () { - return tmp.setup('./tmp/my_blueprint/files'); - }) - .then(function () { - fs.writeFileSync('./tmp/my_blueprint/files/gitignore'); - process.chdir('./tmp'); - - return ng([ - 'new', 'foo', '--skip-npm', '--skip-bower', '--skip-git', - '--blueprint=' + path.resolve(process.cwd(), './my_blueprint') - ]); - }) - .then(confirmBlueprintedForDir('tmp/my_blueprint')); - }); - it('ng new without skip-git flag creates .git dir', function () { return ng(['new', 'foo', '--skip-npm', '--skip-bower']).then(function () { expect(existsSync('.git')); From 43f1fda9658ba410cdc257e4938cb2dc80a6f8be Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Sun, 2 Oct 2016 17:06:07 +0100 Subject: [PATCH 31/35] fix(build): use baseUrl and paths from tsconfig (#2470) --- .../angular-cli/models/webpack-build-test.js | 8 ++++- .../models/webpack-build-typescript.ts | 7 ++++ packages/angular-cli/tasks/build-webpack.ts | 7 ++-- tests/e2e/tests/build/ts-paths.ts | 35 +++++++++++++++++++ tests/e2e/utils/fs.ts | 5 +++ 5 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 tests/e2e/tests/build/ts-paths.ts diff --git a/packages/angular-cli/models/webpack-build-test.js b/packages/angular-cli/models/webpack-build-test.js index 403ed91fdeec..e1d45510ec70 100644 --- a/packages/angular-cli/models/webpack-build-test.js +++ b/packages/angular-cli/models/webpack-build-test.js @@ -2,6 +2,7 @@ const path = require('path'); const webpack = require('webpack'); +const atl = require('awesome-typescript-loader'); const getWebpackTestConfig = function (projectRoot, environment, appConfig) { @@ -11,7 +12,12 @@ const getWebpackTestConfig = function (projectRoot, environment, appConfig) { devtool: 'inline-source-map', context: path.resolve(__dirname, './'), resolve: { - extensions: ['.ts', '.js'] + extensions: ['.ts', '.js'], + plugins: [ + new atl.TsConfigPathsPlugin({ + tsconfig: path.resolve(appRoot, appConfig.tsconfig) + }) + ] }, entry: { test: path.resolve(appRoot, appConfig.test) diff --git a/packages/angular-cli/models/webpack-build-typescript.ts b/packages/angular-cli/models/webpack-build-typescript.ts index 339b03c604f6..09a5f3e10d1d 100644 --- a/packages/angular-cli/models/webpack-build-typescript.ts +++ b/packages/angular-cli/models/webpack-build-typescript.ts @@ -16,6 +16,13 @@ export const getWebpackNonAotConfigPartial = function(projectRoot: string, appCo const lazyModules = findLazyModules(appRoot); return { + resolve: { + plugins: [ + new atl.TsConfigPathsPlugin({ + tsconfig: path.resolve(appRoot, appConfig.tsconfig) + }) + ] + }, module: { rules: [ { diff --git a/packages/angular-cli/tasks/build-webpack.ts b/packages/angular-cli/tasks/build-webpack.ts index 88941955247b..80fc0b9e62a5 100644 --- a/packages/angular-cli/tasks/build-webpack.ts +++ b/packages/angular-cli/tasks/build-webpack.ts @@ -45,11 +45,8 @@ export default Task.extend({ if (err) { lastHash = null; - console.error(err.stack || err); - if (err.details) { - console.error(err.details); - } - reject(err.details); + console.error(err.details || err); + reject(err.details || err); } if (stats.hash !== lastHash) { diff --git a/tests/e2e/tests/build/ts-paths.ts b/tests/e2e/tests/build/ts-paths.ts new file mode 100644 index 000000000000..f5e5b125854b --- /dev/null +++ b/tests/e2e/tests/build/ts-paths.ts @@ -0,0 +1,35 @@ +import {updateTsConfig} from '../../utils/project'; +import {writeMultipleFiles, appendToFile} from '../../utils/fs'; +import {ng} from '../../utils/process'; +import {stripIndents} from 'common-tags'; + + +export default function() { + return updateTsConfig(json => { + json['compilerOptions']['baseUrl'] = '.'; + json['compilerOptions']['paths'] = { + '@shared': [ + 'app/shared' + ], + '@shared/*': [ + 'app/shared/*' + ] + }; + }) + .then(() => writeMultipleFiles({ + 'src/app/shared/meaning.ts': 'export var meaning = 42;', + 'src/app/shared/index.ts': `export * from './meaning'` + })) + .then(() => appendToFile('src/app/app.component.ts', stripIndents` + import { meaning } from 'app/shared/meaning'; + import { meaning as meaning2 } from '@shared'; + import { meaning as meaning3 } from '@shared/meaning'; + + // need to use imports otherwise they are ignored and + // no error is outputted, even if baseUrl/paths don't work + console.log(meaning) + console.log(meaning2) + console.log(meaning3) + `)) + .then(() => ng('build')); +} diff --git a/tests/e2e/utils/fs.ts b/tests/e2e/utils/fs.ts index f6a56ee76d43..5ff15116456e 100644 --- a/tests/e2e/utils/fs.ts +++ b/tests/e2e/utils/fs.ts @@ -93,6 +93,11 @@ export function replaceInFile(filePath: string, match: RegExp, replacement: stri } +export function appendToFile(filePath: string, text: string) { + return readFile(filePath) + .then((content: string) => writeFile(filePath, content.concat(text))); +} + export function expectFileToExist(fileName: string) { return new Promise((resolve, reject) => { From 6d8a7e242eb877fe4a704e70ded6a8378d3dc3d7 Mon Sep 17 00:00:00 2001 From: Carlo Dapor Date: Sun, 2 Oct 2016 23:01:59 +0200 Subject: [PATCH 32/35] chore(completion): ng completion update for command options (#2379) * Updated ng command options. - "build" now additionally supports --base-hre / -bh, -w - "generate" got cleaned up - "github-pages:deploy" / "gh-pages:deploy" now is supported, too - "init" now knows --routing and --source-dir - "new" also accepts --routing - "serve" dumped --insecure-proxy, --inspr, --output-path, -op, -out and -pxy - "test" now honours --build and --reporters, dumped --config-file, --environment, --filter, --host, --launch, --module, path, --query, reporter, --server, --silent, --test-page and --test-port * Added --aot for "build" and "serve". * Recent the commands "init" and "new" received two more options: "--inline-stile" (alias -is) and "inline-template" (alias -it). Support the, both. * Recently the commands "init" and "new" dropped --blueprint (alias -b). --- packages/angular-cli/utilities/completion.sh | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/angular-cli/utilities/completion.sh b/packages/angular-cli/utilities/completion.sh index bcb16f28b879..81ff665a793b 100644 --- a/packages/angular-cli/utilities/completion.sh +++ b/packages/angular-cli/utilities/completion.sh @@ -7,15 +7,15 @@ ng_opts='b build completion doc e2e g generate get github-pages:deploy gh-pages:deploy h help i init install lint make-this-awesome new s serve server set t test v version -h --help' -build_opts='--environment --output-path --suppress-sizes --target --watch --watcher -dev -e -prod' -generate_opts='class component directive enum module pipe route service --generate -d --dry-run --verbose -v --pod -p --classic -c --dummy -dum -id --in-repo --in-repo-addon -ir' -github_pages_deploy_opts='--environment --gh-token --gh-username --skip-build --user-page --message' +build_opts='--aot --base-href --environment --output-path --suppress-sizes --target --watch --watcher -bh -dev -e -o -prod -t -w' +generate_opts='class component directive enum module pipe route service c cl d e m p r s --help' +github_pages_deploy_opts='--base-href --environment --gh-token --gh-username --message --skip-build --target --user-page -bh -e -t' help_opts='--json --verbose -v' -init_opts='--blueprint --dry-run --link-cli --mobile --name --prefix --skip-bower --skip-npm --source-dir --style --verbose -b -d -lc -n -p -sb -sd -sn -v' -new_opts='--blueprint --directory --dry-run --link-cli --mobile --prefix --skip-bower --skip-git --skip-npm --source-dir --style --verbose -b -d -dir -lc -p -sb -sd -sg -sn -v' -serve_opts='--environment --host --insecure-proxy --inspr --live-reload --live-reload-base-url --live-reload-host --live-reload-live-css --live-reload-port --output-path --port --proxy --ssl --ssl-cert --ssl-key --target --watcher -H -dev -e -lr -lrbu -lrh -lrp -op -out -p -pr -prod -pxy -t -w' +init_opts='--dry-run inline-style inline-template --link-cli --mobile --name --prefix --routing --skip-bower --skip-npm --source-dir --style --verbose -d -is -it -lc -n -p -sb -sd -sn -v' +new_opts='--directory --dry-run inline-style inline-template --link-cli --mobile --prefix --routing --skip-bower --skip-git --skip-npm --source-dir --style --verbose -d -dir -is -it -lc -p -sb -sd -sg -sn -v' +serve_opts='--aot --environment --host --live-reload --live-reload-base-url --live-reload-host --live-reload-live-css --live-reload-port --port --proxy-config --ssl --ssl-cert --ssl-key --target --watcher -H -e -lr -lrbu -lrh -lrp -p -pc -t -w' set_opts='--global -g' -test_opts='--browsers --colors --config-file --environment --filter --host --launch --log-level --module --path --port --query --reporter --server --silent --test-page --test-port --watch -H -c -cf -e -f -m -r -s -tp -w' +test_opts='--browsers --build --colors --log-level --port --reporters --watch -w' version_opts='--verbose' if type complete &>/dev/null; then @@ -30,6 +30,7 @@ if type complete &>/dev/null; then ng) opts=$ng_opts ;; b|build) opts=$build_opts ;; g|generate) opts=$generate_opts ;; + gh-pages:deploy|github-pages:deploy) opts=$github_pages_deploy_opts h|help|-h|--help) opts=$help_opts ;; init) opts=$init_opts ;; new) opts=$new_opts ;; @@ -57,6 +58,7 @@ elif type compctl &>/dev/null; then ng) opts=$ng_opts ;; b|build) opts=$build_opts ;; g|generate) opts=$generate_opts ;; + gh-pages:deploy|github-pages:deploy) opts=$github_pages_deploy_opts h|help|-h|--help) opts=$help_opts ;; init) opts=$init_opts ;; new) opts=$new_opts ;; @@ -75,4 +77,4 @@ elif type compctl &>/dev/null; then compctl -K _ng_completion ng fi -###-end-ng-completion### \ No newline at end of file +###-end-ng-completion### From 986f89f0a59c439e80ff25b74e360cc2f768ef34 Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Sun, 2 Oct 2016 22:02:38 +0100 Subject: [PATCH 33/35] chore(ci): use node 4 instead of 5 (#2469) --- .appveyor.yml | 3 ++- .travis.yml | 42 +++++++++++++++++++++++++----------------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index d9cd073ebc96..6d3c82f53721 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,6 +1,6 @@ environment: matrix: - - nodejs_version: "5.0" + - nodejs_version: "4.0" - nodejs_version: "6.0" matrix: @@ -8,6 +8,7 @@ matrix: install: - ps: Install-Product node $env:nodejs_version + - npm install -g npm - npm install test_script: diff --git a/.travis.yml b/.travis.yml index cd2b7f8c4631..f821a853b7c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,9 @@ dist: trusty sudo: required language: node_js node_js: - - "5" - - "6" + - "4" os: - linux - - osx env: global: - DBUS_SESSION_BUS_ADDRESS=/dev/null @@ -20,25 +18,34 @@ env: matrix: fast_finish: true allow_failures: + - node_js: "5" - os: osx - env: NODE_SCRIPT="tests/e2e_runner.js --nightly" - exclude: + include: + - node_js: "5" + os: linux + env: SCRIPT=test + - node_js: "5" + os: linux + env: NODE_SCRIPT=tests/e2e_runner.js - node_js: "6" - env: SCRIPT=lint - - os: osx - env: NODE_SCRIPT="tests/e2e_runner.js --nightly" + os: linux + env: SCRIPT=test - node_js: "6" - env: NODE_SCRIPT="tests/e2e_runner.js --nightly" - - os: osx - node_js: "5" - env: SCRIPT=lint + os: linux + env: NODE_SCRIPT=tests/e2e_runner.js + - node_js: "4" + os: osx + env: SCRIPT=test + - node_js: "4" + os: osx + env: NODE_SCRIPT=tests/e2e_runner.js - node_js: "6" - env: SCRIPT=build - - os: osx - node_js: "5" - env: SCRIPT=build - - os: osx - env: TARGET=mobile SCRIPT=mobile_test + os: osx + env: SCRIPT=test + - node_js: "6" + os: osx + env: NODE_SCRIPT=tests/e2e_runner.js before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi @@ -50,6 +57,7 @@ before_install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CHROME_BIN=chromium-browser; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo "--no-sandbox" > ~/.config/chromium-flags.conf; fi - if [[ "$TARGET" == "mobile" ]]; then export MOBILE_TEST=true; fi + - npm install -g npm - npm config set spin false - npm config set progress false From f8ef300401deb554608ae8a6c4b7a27c154466ce Mon Sep 17 00:00:00 2001 From: Hans Date: Sun, 2 Oct 2016 15:27:56 -0700 Subject: [PATCH 34/35] feat(aot): creating files in a virtual fs. (#2464) In addition, reading and using AST on main.ts to figure out the entry module, if not specified. --- .../models/webpack-build-typescript.ts | 4 +- packages/webpack/src/compiler_host.ts | 195 ++++++++++++++++++ packages/webpack/src/entry_resolver.ts | 178 ++++++++++++++++ packages/webpack/src/plugin.ts | 19 +- packages/webpack/src/resource_loader.ts | 42 ++-- tests/e2e/tests/build/aot.ts | 8 + tests/e2e/tests/misc/lazy-module.ts | 14 +- 7 files changed, 430 insertions(+), 30 deletions(-) create mode 100644 packages/webpack/src/compiler_host.ts create mode 100644 packages/webpack/src/entry_resolver.ts create mode 100644 tests/e2e/tests/build/aot.ts diff --git a/packages/angular-cli/models/webpack-build-typescript.ts b/packages/angular-cli/models/webpack-build-typescript.ts index 09a5f3e10d1d..d23ad7ab074c 100644 --- a/packages/angular-cli/models/webpack-build-typescript.ts +++ b/packages/angular-cli/models/webpack-build-typescript.ts @@ -62,8 +62,8 @@ export const getWebpackAotConfigPartial = function(projectRoot: string, appConfi new NgcWebpackPlugin({ project: path.resolve(projectRoot, appConfig.root, appConfig.tsconfig), baseDir: path.resolve(projectRoot, ''), - entryModule: path.join(projectRoot, appConfig.root, 'app/app.module#AppModule'), - genDir: path.join(projectRoot, appConfig.outDir, 'ngfactory') + main: path.join(projectRoot, appConfig.root, appConfig.main), + genDir: path.resolve(projectRoot, '') }), ] }; diff --git a/packages/webpack/src/compiler_host.ts b/packages/webpack/src/compiler_host.ts new file mode 100644 index 000000000000..a65c46053896 --- /dev/null +++ b/packages/webpack/src/compiler_host.ts @@ -0,0 +1,195 @@ +import * as ts from 'typescript'; +import {basename, dirname} from 'path'; +import * as fs from 'fs'; + + +export interface OnErrorFn { + (message: string): void; +} + + +const dev = Math.floor(Math.random() * 10000); + + +export class VirtualStats implements fs.Stats { + protected _ctime = new Date(); + protected _mtime = new Date(); + protected _atime = new Date(); + protected _btime = new Date(); + protected _dev = dev; + protected _ino = Math.floor(Math.random() * 100000); + protected _mode = parseInt('777', 8); // RWX for everyone. + protected _uid = process.env['UID'] || 0; + protected _gid = process.env['GID'] || 0; + + constructor(protected _path: string) {} + + isFile() { return false; } + isDirectory() { return false; } + isBlockDevice() { return false; } + isCharacterDevice() { return false; } + isSymbolicLink() { return false; } + isFIFO() { return false; } + isSocket() { return false; } + + get dev() { return this._dev; } + get ino() { return this._ino; } + get mode() { return this._mode; } + get nlink() { return 1; } // Default to 1 hard link. + get uid() { return this._uid; } + get gid() { return this._gid; } + get rdev() { return 0; } + get size() { return 0; } + get blksize() { return 512; } + get blocks() { return Math.ceil(this.size / this.blksize); } + get atime() { return this._atime; } + get mtime() { return this._mtime; } + get ctime() { return this._ctime; } + get birthtime() { return this._btime; } +} + +export class VirtualDirStats extends VirtualStats { + constructor(_fileName: string) { + super(_fileName); + } + + isDirectory() { return true; } + + get size() { return 1024; } +} + +export class VirtualFileStats extends VirtualStats { + private _sourceFile: ts.SourceFile; + constructor(_fileName: string, private _content: string) { + super(_fileName); + } + + get content() { return this._content; } + set content(v: string) { + this._content = v; + this._mtime = new Date(); + } + getSourceFile(languageVersion: ts.ScriptTarget, setParentNodes: boolean) { + if (!this._sourceFile) { + this._sourceFile = ts.createSourceFile( + this._path, + this._content, + languageVersion, + setParentNodes); + } + + return this._sourceFile; + } + + isFile() { return true; } + + get size() { return this._content.length; } +} + + +export class WebpackCompilerHost implements ts.CompilerHost { + private _delegate: ts.CompilerHost; + private _files: {[path: string]: VirtualFileStats} = Object.create(null); + private _directories: {[path: string]: VirtualDirStats} = Object.create(null); + + constructor(private _options: ts.CompilerOptions, private _setParentNodes = true) { + this._delegate = ts.createCompilerHost(this._options, this._setParentNodes); + } + + private _setFileContent(fileName: string, content: string) { + this._files[fileName] = new VirtualFileStats(fileName, content); + + let p = dirname(fileName); + while (p && !this._directories[p]) { + this._directories[p] = new VirtualDirStats(p); + p = dirname(p); + } + } + + populateWebpackResolver(resolver: any) { + const fs = resolver.fileSystem; + + for (const fileName of Object.keys(this._files)) { + const stats = this._files[fileName]; + fs._statStorage.data[fileName] = [null, stats]; + fs._readFileStorage.data[fileName] = [null, stats.content]; + } + for (const path of Object.keys(this._directories)) { + const stats = this._directories[path]; + const dirs = this.getDirectories(path); + const files = this.getFiles(path); + fs._statStorage.data[path] = [null, stats]; + fs._readdirStorage.data[path] = [null, files.concat(dirs)]; + } + } + + fileExists(fileName: string): boolean { + return fileName in this._files || this._delegate.fileExists(fileName); + } + + readFile(fileName: string): string { + return (fileName in this._files) + ? this._files[fileName].content + : this._delegate.readFile(fileName); + } + + directoryExists(directoryName: string): boolean { + return (directoryName in this._directories) || this._delegate.directoryExists(directoryName); + } + + getFiles(path: string): string[] { + return Object.keys(this._files) + .filter(fileName => dirname(fileName) == path) + .map(path => basename(path)); + } + + getDirectories(path: string): string[] { + const subdirs = Object.keys(this._directories) + .filter(fileName => dirname(fileName) == path) + .map(path => basename(path)); + + let delegated: string[]; + try { + delegated = this._delegate.getDirectories(path); + } catch (e) { + delegated = []; + } + return delegated.concat(subdirs); + } + + getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: OnErrorFn) { + if (!(fileName in this._files)) { + return this._delegate.getSourceFile(fileName, languageVersion, onError); + } + + return this._files[fileName].getSourceFile(languageVersion, this._setParentNodes); + } + + getCancellationToken() { + return this._delegate.getCancellationToken(); + } + + getDefaultLibFileName(options: ts.CompilerOptions) { + return this._delegate.getDefaultLibFileName(options); + } + + writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: OnErrorFn) { + this._setFileContent(fileName, data); + } + + getCurrentDirectory(): string { + return this._delegate.getCurrentDirectory(); + } + + getCanonicalFileName(fileName: string): string { + return this._delegate.getCanonicalFileName(fileName); + } + + useCaseSensitiveFileNames(): boolean { + return this._delegate.useCaseSensitiveFileNames(); + } + + getNewLine(): string { + return this._delegate.getNewLine(); + } +} diff --git a/packages/webpack/src/entry_resolver.ts b/packages/webpack/src/entry_resolver.ts new file mode 100644 index 000000000000..a96ab266269f --- /dev/null +++ b/packages/webpack/src/entry_resolver.ts @@ -0,0 +1,178 @@ +import * as fs from 'fs'; +import {dirname, join, resolve} from 'path'; +import * as ts from 'typescript'; + + +function _createSource(path: string): ts.SourceFile { + return ts.createSourceFile(path, fs.readFileSync(path, 'utf-8'), ts.ScriptTarget.Latest); +} + +function _findNodes(sourceFile: ts.SourceFile, node: ts.Node, kind: ts.SyntaxKind, + keepGoing = false): ts.Node[] { + if (node.kind == kind && !keepGoing) { + return [node]; + } + + return node.getChildren(sourceFile).reduce((result, n) => { + return result.concat(_findNodes(sourceFile, n, kind, keepGoing)); + }, node.kind == kind ? [node] : []); +} + +function _recursiveSymbolExportLookup(sourcePath: string, + sourceFile: ts.SourceFile, + symbolName: string): string | null { + // Check this file. + const hasSymbol = _findNodes(sourceFile, sourceFile, ts.SyntaxKind.ClassDeclaration) + .some((cd: ts.ClassDeclaration) => { + return cd.name && cd.name.text == symbolName; + }); + if (hasSymbol) { + return sourcePath; + } + + // We found the bootstrap variable, now we just need to get where it's imported. + const exports = _findNodes(sourceFile, sourceFile, ts.SyntaxKind.ExportDeclaration, false) + .map(node => node as ts.ExportDeclaration); + + for (const decl of exports) { + if (!decl.moduleSpecifier || decl.moduleSpecifier.kind !== ts.SyntaxKind.StringLiteral) { + continue; + } + + const module = resolve(dirname(sourcePath), (decl.moduleSpecifier as ts.StringLiteral).text); + if (!decl.exportClause) { + const moduleTs = module + '.ts'; + if (fs.existsSync(moduleTs)) { + const moduleSource = _createSource(moduleTs); + const maybeModule = _recursiveSymbolExportLookup(module, moduleSource, symbolName); + if (maybeModule) { + return maybeModule; + } + } + continue; + } + + const binding = decl.exportClause as ts.NamedExports; + for (const specifier of binding.elements) { + if (specifier.name.text == symbolName) { + // If it's a directory, load its index and recursively lookup. + if (fs.statSync(module).isDirectory()) { + const indexModule = join(module, 'index.ts'); + if (fs.existsSync(indexModule)) { + const maybeModule = _recursiveSymbolExportLookup( + indexModule, _createSource(indexModule), symbolName); + if (maybeModule) { + return maybeModule; + } + } + } + + // Create the source and verify that the symbol is at least a class. + const source = _createSource(module); + const hasSymbol = _findNodes(source, source, ts.SyntaxKind.ClassDeclaration) + .some((cd: ts.ClassDeclaration) => { + return cd.name && cd.name.text == symbolName; + }); + + if (hasSymbol) { + return module; + } else { + return null; + } + } + } + } + + return null; +} + +function _symbolImportLookup(sourcePath: string, + sourceFile: ts.SourceFile, + symbolName: string): string | null { + // We found the bootstrap variable, now we just need to get where it's imported. + const imports = _findNodes(sourceFile, sourceFile, ts.SyntaxKind.ImportDeclaration, false) + .map(node => node as ts.ImportDeclaration); + + for (const decl of imports) { + if (!decl.importClause || !decl.moduleSpecifier) { + continue; + } + if (decl.moduleSpecifier.kind !== ts.SyntaxKind.StringLiteral) { + continue; + } + + const module = resolve(dirname(sourcePath), (decl.moduleSpecifier as ts.StringLiteral).text); + + if (decl.importClause.namedBindings.kind == ts.SyntaxKind.NamespaceImport) { + const binding = decl.importClause.namedBindings as ts.NamespaceImport; + if (binding.name.text == symbolName) { + // This is a default export. + return module; + } + } else if (decl.importClause.namedBindings.kind == ts.SyntaxKind.NamedImports) { + const binding = decl.importClause.namedBindings as ts.NamedImports; + for (const specifier of binding.elements) { + if (specifier.name.text == symbolName) { + // If it's a directory, load its index and recursively lookup. + if (fs.statSync(module).isDirectory()) { + const indexModule = join(module, 'index.ts'); + if (fs.existsSync(indexModule)) { + const maybeModule = _recursiveSymbolExportLookup( + indexModule, _createSource(indexModule), symbolName); + if (maybeModule) { + return maybeModule; + } + } + } + + // Create the source and verify that the symbol is at least a class. + const source = _createSource(module); + const hasSymbol = _findNodes(source, source, ts.SyntaxKind.ClassDeclaration) + .some((cd: ts.ClassDeclaration) => { + return cd.name && cd.name.text == symbolName; + }); + + if (hasSymbol) { + return module; + } else { + return null; + } + } + } + } + } + return null; +} + + +export function resolveEntryModuleFromMain(mainPath: string) { + const source = _createSource(mainPath); + + const bootstrap = _findNodes(source, source, ts.SyntaxKind.CallExpression, false) + .map(node => node as ts.CallExpression) + .filter(call => { + const access = call.expression as ts.PropertyAccessExpression; + return access.kind == ts.SyntaxKind.PropertyAccessExpression + && access.name.kind == ts.SyntaxKind.Identifier + && (access.name.text == 'bootstrapModule' + || access.name.text == 'bootstrapModuleFactory'); + }); + + if (bootstrap.length != 1 + || bootstrap[0].arguments[0].kind !== ts.SyntaxKind.Identifier) { + throw new Error('Tried to find bootstrap code, but could not. Specify either ' + + 'statically analyzable bootstrap code or pass in an entryModule ' + + 'to the plugins options.'); + } + + const bootstrapSymbolName = (bootstrap[0].arguments[0] as ts.Identifier).text; + const module = _symbolImportLookup(mainPath, source, bootstrapSymbolName); + if (module) { + return `${resolve(dirname(mainPath), module)}#${bootstrapSymbolName}`; + } + + // shrug... something bad happened and we couldn't find the import statement. + throw new Error('Tried to find bootstrap code, but could not. Specify either ' + + 'statically analyzable bootstrap code or pass in an entryModule ' + + 'to the plugins options.'); +} diff --git a/packages/webpack/src/plugin.ts b/packages/webpack/src/plugin.ts index 707c069c9ffe..92201ad1c9d8 100644 --- a/packages/webpack/src/plugin.ts +++ b/packages/webpack/src/plugin.ts @@ -9,6 +9,8 @@ import {patchReflectorHost} from './reflector_host'; import {WebpackResourceLoader} from './resource_loader'; import {createResolveDependenciesFromContextMap} from './utils'; import { AngularCompilerOptions } from '@angular/tsc-wrapped'; +import {WebpackCompilerHost} from './compiler_host'; +import {resolveEntryModuleFromMain} from './entry_resolver'; /** @@ -22,6 +24,7 @@ export interface AngularWebpackPluginOptions { baseDir: string; basePath?: string; genDir?: string; + main?: string; } @@ -32,7 +35,7 @@ export class NgcWebpackPlugin { reflector: ngCompiler.StaticReflector; reflectorHost: ngCompiler.ReflectorHost; program: ts.Program; - compilerHost: ts.CompilerHost; + compilerHost: WebpackCompilerHost; compilerOptions: ts.CompilerOptions; angularCompilerOptions: AngularCompilerOptions; files: any[]; @@ -58,13 +61,16 @@ export class NgcWebpackPlugin { this.genDir = this.options.genDir || path.resolve(process.cwd(), this.angularCompilerOptions.genDir + '/app'); this.entryModule = options.entryModule || (this.angularCompilerOptions as any).entryModule; + if (!options.entryModule && options.main) { + this.entryModule = resolveEntryModuleFromMain(options.main); + } const entryModule = this.entryModule; const [rootModule, rootNgModule] = entryModule.split('#'); this.projectPath = options.project; this.rootModule = rootModule; this.rootModuleName = rootNgModule; - this.compilerHost = ts.createCompilerHost(this.compilerOptions, true); + this.compilerHost = new WebpackCompilerHost(this.compilerOptions); this.program = ts.createProgram(this.files, this.compilerOptions, this.compilerHost); this.reflectorHost = new ngCompiler.ReflectorHost( this.program, this.compilerHost, this.angularCompilerOptions); @@ -110,6 +116,15 @@ export class NgcWebpackPlugin { compilation._ngToolsWebpackPluginInstance = null; cb(); }); + + // Virtual file system. + compiler.resolvers.normal.plugin('resolve', (request: any, cb?: () => void) => { + // populate the file system cache with the virtual module + this.compilerHost.populateWebpackResolver(compiler.resolvers.normal); + if (cb) { + cb(); + } + }); } private _make(compilation: any, cb: (err?: any, request?: any) => void) { diff --git a/packages/webpack/src/resource_loader.ts b/packages/webpack/src/resource_loader.ts index 5b1d069b9967..4b146ea1e310 100644 --- a/packages/webpack/src/resource_loader.ts +++ b/packages/webpack/src/resource_loader.ts @@ -14,27 +14,27 @@ export class WebpackResourceLoader implements ResourceLoader { private _context: string; private _uniqueId = 0; - constructor(private _compilation: any) { - this._context = _compilation.context; + constructor(private _parentCompilation: any) { + this._context = _parentCompilation.context; } private _compile(filePath: string, content: string): Promise { const compilerName = `compiler(${this._uniqueId++})`; const outputOptions = { filename: filePath }; const relativePath = path.relative(this._context || '', filePath); - const childCompiler = this._compilation.createChildCompiler(relativePath, outputOptions); + const childCompiler = this._parentCompilation.createChildCompiler(relativePath, outputOptions); childCompiler.context = this._context; childCompiler.apply( new NodeTemplatePlugin(outputOptions), new NodeTargetPlugin(), - new SingleEntryPlugin(this._context, filePath, content), + new SingleEntryPlugin(this._context, filePath), new LoaderTargetPlugin('node') ); // Store the result of the parent compilation before we start the child compilation let assetsBeforeCompilation = Object.assign( {}, - this._compilation.assets[outputOptions.filename] + this._parentCompilation.assets[outputOptions.filename] ); // Fix for "Uncaught TypeError: __webpack_require__(...) is not a function" @@ -62,17 +62,17 @@ export class WebpackResourceLoader implements ResourceLoader { reject(err); } else { // Replace [hash] placeholders in filename - const outputName = this._compilation.mainTemplate.applyPluginsWaterfall( + const outputName = this._parentCompilation.mainTemplate.applyPluginsWaterfall( 'asset-path', outputOptions.filename, { hash: childCompilation.hash, chunk: entries[0] }); // Restore the parent compilation to the state like it was before the child compilation. - this._compilation.assets[outputName] = assetsBeforeCompilation[outputName]; + this._parentCompilation.assets[outputName] = assetsBeforeCompilation[outputName]; if (assetsBeforeCompilation[outputName] === undefined) { // If it wasn't there - delete it. - delete this._compilation.assets[outputName]; + delete this._parentCompilation.assets[outputName]; } resolve({ @@ -89,24 +89,22 @@ export class WebpackResourceLoader implements ResourceLoader { } private _evaluate(fileName: string, source: string): Promise { - const vmContext = vm.createContext(Object.assign({ require: require }, global)); - const vmScript = new vm.Script(source, { filename: fileName }); - - // Evaluate code and cast to string - let newSource: string; try { - newSource = vmScript.runInContext(vmContext).toString(); + const vmContext = vm.createContext(Object.assign({require: require}, global)); + const vmScript = new vm.Script(source, {filename: fileName}); + + // Evaluate code and cast to string + let newSource: string; + newSource = vmScript.runInContext(vmContext); + + if (typeof newSource == 'string') { + return Promise.resolve(newSource); + } + + return Promise.reject('The loader "' + fileName + '" didn\'t return a string.'); } catch (e) { return Promise.reject(e); } - - if (typeof newSource == 'string') { - return Promise.resolve(newSource); - } else if (typeof newSource == 'function') { - return Promise.resolve(newSource()); - } - - return Promise.reject('The loader "' + fileName + '" didn\'t return a string.'); } get(filePath: string): Promise { diff --git a/tests/e2e/tests/build/aot.ts b/tests/e2e/tests/build/aot.ts new file mode 100644 index 000000000000..0cfe62bc7364 --- /dev/null +++ b/tests/e2e/tests/build/aot.ts @@ -0,0 +1,8 @@ +import {ng} from '../../utils/process'; +import {expectFileToMatch} from '../../utils/fs'; + +export default function() { + return ng('build', '--aot') + .then(() => expectFileToMatch('dist/main.bundle.js', + /bootstrapModuleFactory.*\/\* AppModuleNgFactory \*\//)); +} diff --git a/tests/e2e/tests/misc/lazy-module.ts b/tests/e2e/tests/misc/lazy-module.ts index 9f25a42e663f..ad7b4cfab6ac 100644 --- a/tests/e2e/tests/misc/lazy-module.ts +++ b/tests/e2e/tests/misc/lazy-module.ts @@ -12,8 +12,6 @@ export default function(argv: any) { } let oldNumberOfFiles = 0; - let currentNumberOfDistFiles = 0; - return Promise.resolve() .then(() => ng('build')) .then(() => oldNumberOfFiles = readdirSync('dist').length) @@ -22,8 +20,16 @@ export default function(argv: any) { RouterModule.forRoot([{ path: "lazy", loadChildren: "app/lazy/lazy.module#LazyModule" }]) `, '@angular/router')) .then(() => ng('build')) - .then(() => currentNumberOfDistFiles = readdirSync('dist').length) - .then(() => { + .then(() => readdirSync('dist').length) + .then(currentNumberOfDistFiles => { + if (oldNumberOfFiles >= currentNumberOfDistFiles) { + throw new Error('A bundle for the lazy module was not created.'); + } + }) + // Check for AoT and lazy routes. + .then(() => ng('build', '--aot')) + .then(() => readdirSync('dist').length) + .then(currentNumberOfDistFiles => { if (oldNumberOfFiles >= currentNumberOfDistFiles) { throw new Error('A bundle for the lazy module was not created.'); } From 237dc1e7167b660fc3c507180cd7ab89b08296df Mon Sep 17 00:00:00 2001 From: Jonathan Jayet Date: Tue, 20 Sep 2016 19:28:53 +0200 Subject: [PATCH 35/35] Fixed the ssl command not working anymore --- packages/angular-cli/custom-typings.d.ts | 4 ++++ packages/angular-cli/tasks/serve-webpack.ts | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/angular-cli/custom-typings.d.ts b/packages/angular-cli/custom-typings.d.ts index 08a6737a9f19..6b4552345a83 100644 --- a/packages/angular-cli/custom-typings.d.ts +++ b/packages/angular-cli/custom-typings.d.ts @@ -17,7 +17,11 @@ interface IWebpackDevServerConfigurationOptions { headers?: { [key: string]: string }; stats?: { [key: string]: boolean }; inline: boolean; +<<<<<<< HEAD https?: boolean; +======= + https?:boolean; +>>>>>>> Fixed the ssl command not working anymore key?: string; cert?: string; } diff --git a/packages/angular-cli/tasks/serve-webpack.ts b/packages/angular-cli/tasks/serve-webpack.ts index 4166f25486b2..1bb0906ad2df 100644 --- a/packages/angular-cli/tasks/serve-webpack.ts +++ b/packages/angular-cli/tasks/serve-webpack.ts @@ -24,7 +24,8 @@ export default Task.extend({ commandOptions.environment, undefined, undefined, - commandOptions.aot + commandOptions.aot, + ).config; // This allows for live reload of page when changes are made to repo.