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/.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.
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
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/README.md b/README.md
index 41284f42db97..29eb76208584 100644
--- a/README.md
+++ b/README.md
@@ -13,16 +13,16 @@ 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.12, 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.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
@@ -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)
@@ -108,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
@@ -125,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`:
@@ -156,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
@@ -171,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
@@ -192,6 +194,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
@@ -262,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`.
@@ -290,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
@@ -316,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`:
@@ -329,7 +358,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": [
@@ -342,12 +371,12 @@ Then add the needed script files to 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"
],
```
-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
@@ -372,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/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 0f061d2b7463..8a11064fddad 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",
@@ -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",
@@ -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"
@@ -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",
@@ -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",
@@ -109,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/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 3f671370397e..39243e1eb508 100644
--- a/packages/angular-cli/blueprints/component/index.js
+++ b/packages/angular-cli/blueprints/component/index.js
@@ -1,19 +1,20 @@
-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: '',
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 }
],
@@ -55,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,
@@ -117,7 +126,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/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', () => {
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..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: '',
@@ -31,9 +32,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 +42,7 @@ module.exports = {
return {
dynamicPath: this.dynamicPath.dir,
flat: options.flat,
- rawEntityName: this.rawEntityName
+ selector: this.selector
};
},
@@ -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/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..9e01aadffa7e
--- /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 AppRoutingModule { }
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..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,16 +2,16 @@ 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 {
- title = 'app works!';
+ title = '<%= prefix %> works!';
}
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..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
@@ -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 { AppRoutingModule } from './app-routing.module';<% } %>
import { AppComponent } from './app.component';
@@ -12,7 +13,8 @@ import { AppComponent } from './app.component';
imports: [
BrowserModule,
FormsModule,
- HttpModule
+ HttpModule<% if (routing) { %>,
+ AppRoutingModule<% } %>
],
providers: [],
bootstrap: [AppComponent]
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...<%= prefix %>-root>
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..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",
@@ -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"
diff --git a/packages/angular-cli/blueprints/ng2/index.js b/packages/angular-cli/blueprints/ng2/index.js
index 81d14a41d762..70a9f35d31ab 100644
--- a/packages/angular-cli/blueprints/ng2/index.js
+++ b/packages/angular-cli/blueprints/ng2/index.js
@@ -10,7 +10,10 @@ 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 },
+ { name: 'inline-style', type: Boolean, default: false, aliases: ['is'] },
+ { name: 'inline-template', type: Boolean, default: false, aliases: ['it'] }
],
afterInstall: function (options) {
@@ -38,15 +41,27 @@ module.exports = {
prefix: options.prefix,
styleExt: this.styleExt,
relativeRootPath: relativeRootPath,
- isMobile: options.mobile
+ isMobile: options.mobile,
+ 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('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/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 1072ed950d9e..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({
@@ -25,11 +26,12 @@ 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'] },
+ { name: 'aot', type: Boolean, default: false }
],
run: function (commandOptions: BuildOptions) {
diff --git a/packages/angular-cli/commands/init.ts b/packages/angular-cli/commands/init.ts
index f8f082214c76..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'] },
@@ -26,15 +25,14 @@ 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 },
+ { name: 'inline-style', type: Boolean, default: false, aliases: ['is'] },
+ { name: 'inline-template', type: Boolean, default: false, aliases: ['it'] }
],
anonymousOptions: [''],
- _defaultBlueprint: function () {
- return 'ng2';
- },
-
run: function (commandOptions: any, rawArgs: string[]) {
if (commandOptions.dryRun) {
commandOptions.skipNpm = true;
@@ -97,14 +95,17 @@ const InitCommand: any = Command.extend({
const blueprintOpts = {
dryRun: commandOptions.dryRun,
- blueprint: commandOptions.blueprint || this._defaultBlueprint(),
+ blueprint: 'ng2',
rawName: packageName,
targetFiles: rawArgs || '',
rawArgs: rawArgs.toString(),
sourceDir: commandOptions.sourceDir,
style: commandOptions.style,
prefix: commandOptions.prefix,
- mobile: commandOptions.mobile
+ mobile: commandOptions.mobile,
+ 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 0c0d518744c7..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'] },
@@ -24,7 +23,10 @@ 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 },
+ { name: 'inline-style', type: Boolean, default: false, aliases: ['is'] },
+ { name: 'inline-template', type: Boolean, default: false, aliases: ['it'] }
],
run: function (commandOptions: any, rawArgs: string[]) {
@@ -59,8 +61,6 @@ const NewCommand = Command.extend({
));
}
- commandOptions.blueprint = normalizeBlueprint(commandOptions.blueprint);
-
if (!commandOptions.directory) {
commandOptions.directory = packageName;
}
diff --git a/packages/angular-cli/commands/serve.ts b/packages/angular-cli/commands/serve.ts
index c820102a2eb3..936a341b4cfc 100644
--- a/packages/angular-cli/commands/serve.ts
+++ b/packages/angular-cli/commands/serve.ts
@@ -22,10 +22,10 @@ export interface ServeTaskOptions {
liveReloadLiveCss?: boolean;
target?: string;
environment?: string;
- outputPath?: string;
ssl?: boolean;
sslKey?: string;
sslCert?: string;
+ aot?: boolean;
}
const ServeCommand = Command.extend({
@@ -78,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/custom-typings.d.ts b/packages/angular-cli/custom-typings.d.ts
index 1fbb264de74a..6b4552345a83 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,6 +17,13 @@ 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;
}
interface WebpackProgressPluginOutputOptions {
diff --git a/packages/angular-cli/lib/config/schema.d.ts b/packages/angular-cli/lib/config/schema.d.ts
index f660f183f221..2c09a9471ca3 100644
--- a/packages/angular-cli/lib/config/schema.d.ts
+++ b/packages/angular-cli/lib/config/schema.d.ts
@@ -59,5 +59,10 @@ export interface CliConfig {
defaults?: {
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 af5d76267e71..8abcc7870466 100644
--- a/packages/angular-cli/lib/config/schema.json
+++ b/packages/angular-cli/lib/config/schema.json
@@ -135,10 +135,26 @@
},
"prefixInterfaces": {
"type": "boolean"
+ },
+ "poll": {
+ "type": "number"
+ },
+ "inline": {
+ "type": "object",
+ "properties": {
+ "style": {
+ "type": "boolean",
+ "default": false
+ },
+ "template": {
+ "type": "boolean",
+ "default": false
+ }
+ }
}
},
"additionalProperties": false
}
},
"additionalProperties": false
-}
\ No newline at end of file
+}
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;
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'];
}
diff --git a/packages/angular-cli/models/webpack-build-common.ts b/packages/angular-cli/models/webpack-build-common.ts
index 6de3b9d3f6c0..ec755c36979a 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]
@@ -36,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,
@@ -46,32 +42,15 @@ export function getWebpackCommonConfig(
filename: '[name].bundle.js'
},
module: {
- preLoaders: [
+ rules: [
{
+ enforce: 'pre',
test: /\.js$/,
loader: 'source-map-loader',
exclude: [
/node_modules/
]
- }
- ],
- 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$/]
},
-
// in main, load css as raw text
{
exclude: styles,
@@ -115,7 +94,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 +102,6 @@ export function getWebpackCommonConfig(
]
},
plugins: [
- new webpack.ContextReplacementPlugin(/.*/, appRoot, lazyModules),
- new atl.ForkCheckerPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(appRoot, appConfig.index),
chunksSortMode: 'dependency'
@@ -159,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..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,8 +12,12 @@ const getWebpackTestConfig = function (projectRoot, environment, appConfig) {
devtool: 'inline-source-map',
context: path.resolve(__dirname, './'),
resolve: {
- extensions: ['', '.ts', '.js'],
- root: appRoot
+ extensions: ['.ts', '.js'],
+ plugins: [
+ new atl.TsConfigPathsPlugin({
+ tsconfig: path.resolve(appRoot, appConfig.tsconfig)
+ })
+ ]
},
entry: {
test: path.resolve(appRoot, appConfig.test)
@@ -22,9 +27,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 +38,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 +63,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 +93,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
new file mode 100644
index 000000000000..d23ad7ab074c
--- /dev/null
+++ b/packages/angular-cli/models/webpack-build-typescript.ts
@@ -0,0 +1,70 @@
+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 {
+ resolve: {
+ plugins: [
+ new atl.TsConfigPathsPlugin({
+ tsconfig: path.resolve(appRoot, appConfig.tsconfig)
+ })
+ ]
+ },
+ module: {
+ rules: [
+ {
+ 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: {
+ rules: [
+ {
+ test: /\.ts$/,
+ loader: webpackLoader,
+ exclude: [/\.(spec|e2e)\.ts$/]
+ }
+ ]
+ },
+ plugins: [
+ new NgcWebpackPlugin({
+ project: path.resolve(projectRoot, appConfig.root, appConfig.tsconfig),
+ baseDir: path.resolve(projectRoot, ''),
+ main: path.join(projectRoot, appConfig.root, appConfig.main),
+ genDir: path.resolve(projectRoot, '')
+ }),
+ ]
+ };
+};
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 3ce25ae89fa4..15f55fd89a29 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",
@@ -33,11 +33,12 @@
"@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",
"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",
@@ -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",
@@ -91,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",
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)
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 49abc0687b57..80fc0b9e62a5 100644
--- a/packages/angular-cli/tasks/build-webpack.ts
+++ b/packages/angular-cli/tasks/build-webpack.ts
@@ -5,25 +5,30 @@ 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,
- runTaskOptions.baseHref
+ outputDir,
+ runTaskOptions.baseHref,
+ runTaskOptions.aot
).config;
+ // fail on build error
+ config.bail = true;
+
const webpackCompiler: any = webpack(config);
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
@@ -40,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/packages/angular-cli/tasks/serve-webpack.ts b/packages/angular-cli/tasks/serve-webpack.ts
index 4dfb1dffff1f..1bb0906ad2df 100644
--- a/packages/angular-cli/tasks/serve-webpack.ts
+++ b/packages/angular-cli/tasks/serve-webpack.ts
@@ -13,14 +13,19 @@ 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;
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.
@@ -46,6 +51,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 +73,28 @@ export default Task.extend({
historyApiFallback: true,
stats: webpackDevServerOutputOptions,
inline: true,
- proxy: proxyConfig
+ https: commandOptions.ssl,
+ proxy: proxyConfig,
+ watchOptions: {
+ poll: CliConfig.fromProject().config.defaults.poll
+ }
};
+ 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); }
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/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###
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/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/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": [
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/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/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 fb221f20013c..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(
@@ -293,13 +293,13 @@ 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];`);
});
});
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 db40e0a41766..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
@@ -416,11 +418,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/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/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/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..92201ad1c9d8
--- /dev/null
+++ b/packages/webpack/src/plugin.ts
@@ -0,0 +1,240 @@
+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';
+import {WebpackCompilerHost} from './compiler_host';
+import {resolveEntryModuleFromMain} from './entry_resolver';
+
+
+/**
+ * Option Constants
+ */
+export interface AngularWebpackPluginOptions {
+ tsconfigPath?: string;
+ providers?: any[];
+ entryModule?: string;
+ project: string;
+ baseDir: string;
+ basePath?: string;
+ genDir?: string;
+ main?: string;
+}
+
+
+export class NgcWebpackPlugin {
+ projectPath: string;
+ rootModule: string;
+ rootModuleName: string;
+ reflector: ngCompiler.StaticReflector;
+ reflectorHost: ngCompiler.ReflectorHost;
+ program: ts.Program;
+ compilerHost: WebpackCompilerHost;
+ 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;
+ 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 = 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);
+ 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();
+ });
+
+ // 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) {
+ 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..4b146ea1e310
--- /dev/null
+++ b/packages/webpack/src/resource_loader.ts
@@ -0,0 +1,114 @@
+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 _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._parentCompilation.createChildCompiler(relativePath, outputOptions);
+ childCompiler.context = this._context;
+ childCompiler.apply(
+ new NodeTemplatePlugin(outputOptions),
+ new NodeTargetPlugin(),
+ 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._parentCompilation.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._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._parentCompilation.assets[outputName] = assetsBeforeCompilation[outputName];
+ if (assetsBeforeCompilation[outputName] === undefined) {
+ // If it wasn't there - delete it.
+ delete this._parentCompilation.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 {
+ try {
+ 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);
+ }
+ }
+
+ 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/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 6a2b93266d06..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);
});
});
@@ -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\]'/);
+ });
+ });
});
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);
});
});
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..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'));
@@ -168,4 +132,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);
+ });
+ });
});
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 @@
+
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/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/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')));
+}
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));
}
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/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/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')))
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.');
}
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..5ff15116456e 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) => {
@@ -67,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) => {
@@ -85,12 +116,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": [