Compare commits

...

49 Commits

Author SHA1 Message Date
f5b7f2b9a5 docs: add changelog for 5.0.0-rc.9 2017-10-30 21:09:14 -07:00
509f392ab0 release: cut the 5.0.0-rc.9 release 2017-10-30 21:07:37 -07:00
cf5fce8d5e build: update rollup lint rule from bad merge (#20047)
PR Close #20047
2017-10-30 23:45:06 -04:00
f1248b69e6 refactor: make all rollup config ES5 compatible (#20028)
So they can be required by other Node scripts.

PR Close #20028
2017-10-30 23:28:18 -04:00
4498dddbe3 build: add lint rule for global flags in rollup config (#20028)
We now verify that every imports is part of the globals defined in the files rollup.config.js.

PR Close #20028
2017-10-30 23:28:11 -04:00
812786f44e fix: add missing globals from each rollup configuration (#20028)
PR Close #20028
2017-10-30 23:11:42 -04:00
de24d54517 fix(compiler): report errors properly in G3 in certain conditions (#20041)
Condition: static analysis error, given:
- noResolve:true
- generateCodeForLibraries: false
- CompilerHost.getSourceFile throws on non existent files

All of these are true in G3.
PR Close #20041
2017-10-30 23:07:37 -04:00
c295aeeca2 build: add release as a valid commit message subject (#19955)
PR Close #19955
2017-10-30 21:23:48 -04:00
a8add78fe1 build: temporarily increase the commit msg limit to 120
Right now HEAD and 5.0.x have a branch deviation and therefore
all the commits between both branches are being compared. There
exists a problematic commit which has a commit message that is
longer than 100 commits. This patch will temporarily increase
the limit to 120 so that CI passes. Once master is resumed to
being the primary development branch (once 5.0.0 is out) then
the the msg limit will be set back to 100.
2017-10-30 21:23:43 -04:00
e3a16ed02d fix(compiler): reexport less symbols in .ngfactory.ts files (#19884)
* don't reexport symbols that the user already reexported
* never reexport symbols that are part of arguments of non simple function calls

Fixes #19883

PR Close #19884
2017-10-30 21:19:44 -04:00
fd37f3fbab fix(compiler): always use relative paths to refer to generated code
Previously we generated imports like `@angular/material/index.ngfactory`,
which doesn’t make sense as we don’t ship generated code on npm

Closes #20031
2017-10-30 20:00:48 -04:00
85e95cc32b docs: add changelog for 5.0.0-rc.8 2017-10-27 23:31:35 -07:00
de71ba74bb release: cut the 5.0.0-rc.8 release 2017-10-27 23:31:27 -07:00
a01c877534 docs: ensure examples get rxjs ^5.5.0 (#19985)
This change coincidentally updates other packages that were in `package.json`
because it regenerates `yarn.lock`. This too should be fine.

PR Close #19985
2017-10-27 22:28:21 -07:00
2d508a3ef0 fix(compiler-cli): avoid producing source mappings for host views (#19965)
The host view doesn't map back to user code so the template compiler
produces a blank `url` for them.

PR Close #19965
2017-10-27 22:28:14 -07:00
4285b6c3e3 fix(platform-server): add missing packages to the UMD global rollup config
This adds the proper bindings for calling angular packages from platform-server in the UMD.
This was not a problem for universal apps that dont use UMD.

Fixes 19899
2017-10-27 22:27:43 -07:00
5542517b9c docs: add changelog for 5.0.0-rc.7 2017-10-26 19:03:14 -07:00
fef3539608 release: cut the 5.0.0-rc.7 release 2017-10-26 19:01:34 -07:00
f4d5729cb3 fix(compiler): make watch mode work on windows (#19953)
Fixes #19951
PR Close #19953
2017-10-26 21:52:35 -04:00
d343bf7885 fix(compiler): recover from structural errors in watch mode (#19953)
This also changes the compiler so that we throw less often
on structural changes and produce a meaningful state
in the `ng.Program` in case of errors.

Related to #19951

PR Close #19953
2017-10-26 21:52:25 -04:00
9ce7f0e538 fix(compiler): translate emit diagnostics with noEmitOnError: true. (#19953)
This prevents errors reported against `.ngfactory.ts` files show up
as the result of running `ngc`.

Closes #19935
PR Close #19953
2017-10-26 21:52:16 -04:00
4a23df3909 fix(compiler): don’t store invalid state when using listLazyRoutes (#19953)
Previously, `listLazyRoute` would store invalid information in a compiler
internal cache, which lead to incorrect paths that were used during emit.
This commit fixes this.

PR Close #19953
2017-10-26 21:51:43 -04:00
14016c781f fix(service-worker): fix improper call of Observable.merge (#19962)
Observable.merge was called using .call() as if it were an operator
and not an Observable factory. This removes the .call() and uses
the factory properly.

PR Close #19962
2017-10-26 18:16:20 -04:00
47caebfe86 fix(service-worker): don't block initialization on registration (#19936)
Importing ServiceWorkerModule.register() will schedule registration of
the Service Worker inside an APP_INITIALIZER. Previously, the Promise
returned by navigator.serviceWorker.register() was returned from the
initializer function. This has the unwanted side effect of blocking
initialization until the SW is registered. Even worse, if the SW script
fails to load, this can cause the app initialization to fail.

The solution is to not return the registration promise from the
initializer function, essentially decoupling registration from the rest
of the initialization flow.

This change is not unit testable as there are no mocks/adapters yet for
navigator.serviceWorker. A future integration test should cover this case
with better fidelity.

PR Close #19936
2017-10-26 16:10:17 -04:00
5cfd9c6020 fix(service-worker): listen for messages on the right event source (#19954)
Currently, the SwUpdate service doesn't receive messages from the SW.
This is because it attempts to subscribe to the 'message' event on
ServiceWorkerRegistration, when really messages are emitted by the
ServiceWorkerContainer.

This change moves to listening on ServiceWorkerContainer and changes
the mocks to reflect the way the browser actually works.

PR Close #19954
2017-10-26 16:10:07 -04:00
47bc6f105d docs: add changelog for 5.0.0-rc.6 2017-10-25 14:34:42 -07:00
40fa2593a9 release: cut the 5.0.0-rc.6 release 2017-10-25 14:32:11 -07:00
680bcf7b8a build: update to rxjs@5.5.2 (#19931)
PR Close #19931
2017-10-25 15:32:01 -04:00
ef08330341 fix(compiler): automatically set emitDecoratorMetadata when "annotationsAs": "static fields” (#19927)
This is a workaround for https://github.com/angular/tsickle/issues/635.

Fixes #19916
PR Close #19927
2017-10-25 14:26:28 -04:00
6cc042e2ba fix(compiler-cli): produce correct paths for windows output (#19915)
The path mapping was broken for Windows by fc0b1d5b61.
Fixed the path mapping and put code in place to make such a problem
to sneek by again.

PR Close #19915
2017-10-24 19:21:18 -04:00
9b26455740 fix(compiler-cli): only use error collector when needed. (#19912)
The error collector changes behavior of the metadata resolver
in ways that haven't been fully hardened. This changes limits
its use to the lazy route detection and the language service.

Issue: #19906

PR Close #19912
2017-10-24 19:21:13 -04:00
18bce5987c fix(compiler): don’t type check templates with skipTemplateCodegen (#19909)
This change is needed to prevent users’ builds from breaking.

If a user sets `fullTemlateTypeCheck` to true, we will
continue to check the templates even when `skipTemplateCodegen` is true
as well.

Related to #19906

PR Close #19909
2017-10-24 19:21:03 -04:00
f1108fea76 docs: add changelog for 5.0.0-rc.5 2017-10-23 23:28:28 -07:00
64b3e3e41a release: cut the 5.0.0-rc.5 release 2017-10-23 23:27:15 -07:00
a82f863e24 fix(compiler-cli): report all diagnostic error messages (#19886)
This fixes a problem introduced in 8d45fefc31
which modified how diagnostic error messages are reported for structural
metadata errors causing some of the diagnostics to be lost.

PR Close #19886
2017-10-24 00:57:41 -04:00
bde57016c6 docs: add changelog for 5.0.0-rc.4 2017-10-23 17:35:17 -07:00
b16f4bce98 release: cut the 5.0.0-rc.4 release 2017-10-23 17:31:42 -07:00
6bed189e37 build(service-worker): set skipTemplateCodegen for build (#19875)
PR Close #19875
2017-10-23 19:31:42 -04:00
4abacb58f1 fix(aio): add service worker entrypoint to aio build (#19875)
Fixes #19838

PR Close #19875
2017-10-23 19:31:42 -04:00
04200150d5 docs: add service-worker to list of allowed scopes (#19875)
PR Close #19875
2017-10-23 19:31:42 -04:00
fc0b1d5b61 fix(compiler): correctly calculate the outDir if it repeats a parts of the rootDir. (#19836)
Fixes #19718

PR Close #19836
2017-10-23 18:46:04 -04:00
8d45fefc31 refactor(compiler): remove old ngtools api and add listLazyRoutes to new api (#19836)
Usages of `NgTools_InternalApi_NG_2` from `@angular/compiler-cli` will now
throw an error.

Adds `listLazyRoutes` to `@angular/compiler-cli/ngtools2.ts` for getting
the lazy routes of a `ng.Program`.
PR Close #19836
2017-10-23 18:46:04 -04:00
5da96c75a2 fix(bazel): don't console.error from the compile helper (#19879)
This lets other callers of compile() choose different formatting for the diagnostics

PR Close #19879
2017-10-23 16:56:57 -04:00
90d1423fb4 fix(service-worker): include versionedFiles in the manifest hashTable (#19837)
There is no difference in runtime (yet) between versioned and unversioned
files. Theoretically, the SW does not have to cache-bust versioned files,
but the SW doesn't cache bust files on the first request anyway, so in the
common case it doesn't matter. If the hash doesn't match, the SW will cache
bust the file to be sure, which is technically unnecessary, but since the
file itself is versioned, the likelihood of this happening is rare.

This fixes a critical bug where versioned files were erroneously not included
in the hashTable in the generated manifest. This could lead to applications
not updating if only versioned files changed in between versions.

PR Close #19837
2017-10-23 15:11:38 -04:00
910735d732 build: fix yarn install command (--freeze-lockfile --> --frozen-lockfile) 2017-10-20 10:14:30 -07:00
fc86352adf build(aio): use http push for navigation.json 2017-10-20 09:41:02 -07:00
441e01c568 fix(aio): simplify GaService to avoid e2e test failures
The GaService and the E2E specs were unnecessarily complicated and had
arbitrary async timeouts to ensure that the interplay between the GA
library code and the rest of the app worked correctly. This resulted
in potential flaky tests if the timeouts were not adequate; this was
experienced when Travis upgraded to Chrome 62.

The new approach is to block loading of the Analytics library altogether
if there is a `__e2e__` flag set in the `SessionStorage` of the browser.
It doesn't appear to be enough just to set the flag directly on the
window. I think this is because the window gets cleaned when navigation
occurs (but I am not certain).

The downside of this is that we had to add a small piece of extra logic
to the GA snippet in index.html; and we also had to switch from using
`<script async ...>` to a programmatic approach to loading the GA library
which prevents the browser from eagerly fetching the library. This may
be mitigated by adding it to the HTTP/2 push configuration of the Firebase
hosting.

Re-enables the test that was disabled in https://github.com/angular/angular/pull/19784

Closes #19785
2017-10-20 09:40:52 -07:00
14380ff086 build: add warning about changing ngtools_api2 2017-10-20 09:39:30 -07:00
820bb7bd8c revert: ci: use chrome stable (#18307)
This reverts commit 8bcb268140.
2017-10-20 09:38:59 -07:00
136 changed files with 2874 additions and 2554 deletions

View File

@ -29,7 +29,7 @@ jobs:
- restore_cache:
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
- run: yarn install --freeze-lockfile --non-interactive
- run: yarn install --frozen-lockfile --non-interactive
- run: ./node_modules/.bin/gulp lint
build:

View File

@ -7,9 +7,12 @@
#
# alexeagle - Alex Eagle
# alxhub - Alex Rickabaugh
# brocco - Mike Brocchi
# chuckjaz - Chuck Jazdzewski
# filipesilva - Filipe Silva
# Foxandxss - Jesús Rodríguez
# gkalpak - George Kalpakas
# hansl - Hans Larsen
# IgorMinar - Igor Minar
# jasonaden - Jason Aden
# juleskremer - Jules Kremer
@ -144,11 +147,23 @@ groups:
- mhevery
- IgorMinar #fallback
compiler-cli/ngtools:
conditions:
files:
- "packages/compiler-cli/src/ngtools*"
users:
- hansl
- filipesilva #fallback
- brocco #fallback
compiler-cli:
conditions:
files:
- "packages/compiler-cli/*"
- "packages/bazel/*"
include:
- "packages/compiler-cli/*"
- "packages/bazel/*"
exclude:
- "packages/compiler-cli/src/ngtools*"
users:
- alexeagle
- chuckjaz

View File

@ -1,12 +1,10 @@
language: node_js
sudo: false
# force trusty as Google Chrome addon is not supported on Precise
dist: trusty
node_js:
- '6.9.5'
addons:
chrome: stable
# firefox: "38.0"
apt:
sources:

View File

@ -1,3 +1,78 @@
<a name="5.0.0-rc.9"></a>
# [5.0.0-rc.9](https://github.com/angular/angular/compare/5.0.0-rc.8...5.0.0-rc.9) (2017-10-31)
### Bug Fixes
* **compiler:** always use relative paths to refer to generated code ([fd37f3f](https://github.com/angular/angular/commit/fd37f3f)), closes [#20031](https://github.com/angular/angular/issues/20031)
* **compiler:** re-export less symbols in `.ngfactory.ts` files ([#19884](https://github.com/angular/angular/issues/19884)) ([e3a16ed](https://github.com/angular/angular/commit/e3a16ed)), closes [#19883](https://github.com/angular/angular/issues/19883)
* **compiler:** report errors properly in G3 in certain conditions ([#20041](https://github.com/angular/angular/issues/20041)) ([de24d54](https://github.com/angular/angular/commit/de24d54))
* **core** add missing globals from each rollup configuration ([#20028](https://github.com/angular/angular/issues/20028)) ([812786f](https://github.com/angular/angular/commit/812786f))
<a name="5.0.0-rc.8"></a>
# [5.0.0-rc.8](https://github.com/angular/angular/compare/5.0.0-rc.7...5.0.0-rc.8) (2017-10-28)
### Bug Fixes
* **compiler-cli:** avoid producing source mappings for host views ([#19965](https://github.com/angular/angular/issues/19965)) ([2d508a3](https://github.com/angular/angular/commit/2d508a3))
* **platform-server:** add missing packages to the UMD global rollup config ([4285b6c](https://github.com/angular/angular/commit/4285b6c))
<a name="5.0.0-rc.7"></a>
# [5.0.0-rc.7](https://github.com/angular/angular/compare/5.0.0-rc.6...5.0.0-rc.7) (2017-10-27)
### Bug Fixes
* **compiler:** dont store invalid state when using `listLazyRoutes` ([#19953](https://github.com/angular/angular/issues/19953)) ([4a23df3](https://github.com/angular/angular/commit/4a23df3))
* **compiler:** make watch mode work on windows ([#19953](https://github.com/angular/angular/issues/19953)) ([f4d5729](https://github.com/angular/angular/commit/f4d5729)), closes [#19951](https://github.com/angular/angular/issues/19951)
* **compiler:** recover from structural errors in watch mode ([#19953](https://github.com/angular/angular/issues/19953)) ([d343bf7](https://github.com/angular/angular/commit/d343bf7))
* **compiler:** translate emit diagnostics with `noEmitOnError: true`. ([#19953](https://github.com/angular/angular/issues/19953)) ([9ce7f0e](https://github.com/angular/angular/commit/9ce7f0e)), closes [#19935](https://github.com/angular/angular/issues/19935)
* **service-worker:** don't block initialization on registration ([#19936](https://github.com/angular/angular/issues/19936)) ([47caebf](https://github.com/angular/angular/commit/47caebf))
* **service-worker:** fix improper call of Observable.merge ([#19962](https://github.com/angular/angular/issues/19962)) ([14016c7](https://github.com/angular/angular/commit/14016c7))
* **service-worker:** listen for messages on the right event source ([#19954](https://github.com/angular/angular/issues/19954)) ([5cfd9c6](https://github.com/angular/angular/commit/5cfd9c6))
<a name="5.0.0-rc.6"></a>
# [5.0.0-rc.6](https://github.com/angular/angular/compare/5.0.0-rc.5...5.0.0-rc.6) (2017-10-25)
### Bug Fixes
* **compiler:** automatically set `emitDecoratorMetadata` when `"annotationsAs": "static fields”` ([#19927](https://github.com/angular/angular/issues/19927)) ([ef08330](https://github.com/angular/angular/commit/ef08330)), closes [#19916](https://github.com/angular/angular/issues/19916)
* **compiler:** dont type check templates with `skipTemplateCodegen` ([#19909](https://github.com/angular/angular/issues/19909)) ([18bce59](https://github.com/angular/angular/commit/18bce59))
* **compiler-cli:** only use error collector when needed. ([#19912](https://github.com/angular/angular/issues/19912)) ([9b26455](https://github.com/angular/angular/commit/9b26455))
* **compiler-cli:** produce correct paths for windows output ([#19915](https://github.com/angular/angular/issues/19915)) ([6cc042e](https://github.com/angular/angular/commit/6cc042e))
<a name="5.0.0-rc.5"></a>
# [5.0.0-rc.5](https://github.com/angular/angular/compare/5.0.0-rc.4...5.0.0-rc.5) (2017-10-24)
### Bug Fixes
* **compiler-cli:** report all diagnostic error messages ([#19886](https://github.com/angular/angular/issues/19886)) ([a82f863](https://github.com/angular/angular/commit/a82f863))
<a name="5.0.0-rc.4"></a>
# [5.0.0-rc.4](https://github.com/angular/angular/compare/5.0.0-rc.3...5.0.0-rc.4) (2017-10-24)
### Bug Fixes
* **bazel:** don't console.error from the compile helper ([#19879](https://github.com/angular/angular/issues/19879)) ([5da96c7](https://github.com/angular/angular/commit/5da96c7))
* **compiler:** correctly calculate the outDir if it repeats a parts of the `rootDir`. ([#19836](https://github.com/angular/angular/issues/19836)) ([fc0b1d5](https://github.com/angular/angular/commit/fc0b1d5)), closes [#19718](https://github.com/angular/angular/issues/19718)
* **service-worker:** include versionedFiles in the manifest hashTable ([#19837](https://github.com/angular/angular/issues/19837)) ([90d1423](https://github.com/angular/angular/commit/90d1423))
<a name="5.0.0-rc.3"></a>
# [5.0.0-rc.3](https://github.com/angular/angular/compare/5.0.0-rc.2...5.0.0-rc.3) (2017-10-18)

View File

@ -220,6 +220,7 @@ The following is the list of supported scopes:
* **platform-webworker**
* **platform-webworker-dynamic**
* **router**
* **service-worker**
* **upgrade**
There are currently a few exceptions to the "use package name" rule:

View File

@ -156,7 +156,7 @@ RUN find $AIO_SCRIPTS_SH_DIR -maxdepth 1 -type f -printf "%P\n" \
# Set up the Node.js scripts
COPY scripts-js/ $AIO_SCRIPTS_JS_DIR/
WORKDIR $AIO_SCRIPTS_JS_DIR/
RUN yarn install --production --freeze-lockfile
RUN yarn install --production --frozen-lockfile
# Set up health check

View File

@ -9,7 +9,7 @@ readonly defaultImageNameAndTag="aio-builds:latest"
# (Necessary, because only `scripts-js/dist/` is copied to the docker image.)
(
cd "$SCRIPTS_JS_DIR"
yarn install --freeze-lockfile --non-interactive
yarn install --frozen-lockfile --non-interactive
yarn build
)

View File

@ -7,6 +7,6 @@ source "`dirname $0`/_env.sh"
# Test `scripts-js/`
(
cd "$SCRIPTS_JS_DIR"
yarn install --freeze-lockfile --non-interactive
yarn install --frozen-lockfile --non-interactive
yarn test
)

View File

@ -68,30 +68,33 @@ describe('site App', function() {
// TODO(https://github.com/angular/angular/issues/19785): Activate this again
// once it is no more flaky.
xdescribe('google analytics', () => {
beforeEach(done => page.gaReady.then(done));
it('should call ga', done => {
page.ga()
.then(calls => {
expect(calls.length).toBeGreaterThan(2, 'ga calls');
done();
});
});
describe('google analytics', () => {
it('should call ga with initial URL', done => {
let path: string;
page.navigateTo('api');
page.locationPath()
.then(p => path = p)
.then(() => page.ga().then(calls => {
expect(calls.length).toBeGreaterThan(2, 'ga calls');
expect(calls[1]).toEqual(['set', 'page', path]);
// The last call (length-1) will be the `send` command
// The second to last call (length-2) will be the command to `set` the page url
expect(calls[calls.length - 2]).toEqual(['set', 'page', path]);
done();
}));
});
// Todo: add test to confirm tracking URL when navigate.
it('should call ga with new URL on navigation', done => {
let path: string;
page.getLink('features').click();
page.locationPath()
.then(p => path = p)
.then(() => page.ga().then(calls => {
// The last call (length-1) will be the `send` command
// The second to last call (length-2) will be the command to `set` the page url
expect(calls[calls.length - 2]).toEqual(['set', 'page', path]);
done();
}));
});
});
describe('search', () => {

View File

@ -12,7 +12,6 @@ export class SitePage {
.all(by.css('a'))
.filter((a: ElementFinder) => a.getAttribute('href').then(href => githubRegex.test(href)))
.first();
gaReady: promise.Promise<any>;
static setWindowWidth(newWidth: number) {
const win = browser.driver.manage().window();
@ -25,11 +24,14 @@ export class SitePage {
.first();
}
getLink(path) { return element(by.css(`a[href="${path}"]`)); }
ga() { return browser.executeScript('return window["gaCalls"]') as promise.Promise<any[][]>; }
ga() { return browser.executeScript('return window["ga"].q') as promise.Promise<any[][]>; }
locationPath() { return browser.executeScript('return document.location.pathname') as promise.Promise<string>; }
navigateTo(pageUrl = '') {
return browser.get('/' + pageUrl).then(_ => this.replaceGa(_));
return browser.get('/' + pageUrl)
// We need to tell the index.html not to load the real analytics library
// See the GA snippet in index.html
.then(() => browser.driver.executeScript('sessionStorage.setItem("__e2e__", true);'));
}
getDocViewerText() {
@ -61,31 +63,4 @@ export class SitePage {
browser.wait(ExpectedConditions.presenceOf(results.first()), 8000);
return results;
}
/**
* Replace the ambient Google Analytics tracker with homebrew spy
* don't send commands to GA during e2e testing!
* @param _ - forward's anything passed in
*/
private replaceGa(_: any) {
this.gaReady = browser.driver.executeScript(() => {
// Give ga() a "ready" callback:
// https://developers.google.com/analytics/devguides/collection/analyticsjs/command-queue-reference
window['ga'](() => {
window['gaCalls'] = [];
window['ga'] = function() { window['gaCalls'].push(arguments); };
});
})
.then(() => {
// wait for GaService to start using window.ga after analytics lib loads.
const d = promise.defer();
setTimeout(() => d.fulfill(), 1000); // GaService.initializeDelay
return d.promise;
});
return _;
}
}

View File

@ -44,6 +44,17 @@
"source": "**/!(*.*)",
"destination": "/index.html"
}
],
"headers": [
{
"source": "/",
"headers": [
{
"key": "Link",
"value": "</generated/navigation.json>;rel=preload;as=fetch,</generated/docs/index.json>;rel=preload;as=fetch"
}
]
}
]
}
}

View File

@ -7,7 +7,7 @@
"license": "MIT",
"scripts": {
"aio-use-local": "node tools/ng-packages-installer overwrite . --debug --ignore-packages @angular/service-worker",
"aio-use-npm": "node tools/ng-packages-installer restore .",
"aio-use-npm": "node tools/ng-packages-installer restore . && yarn upgrade @angular/cli@1.3.0",
"aio-check-local": "node tools/ng-packages-installer check .",
"ng": "yarn check-env && ng",
"start": "yarn check-env && ng serve",
@ -19,11 +19,11 @@
"test": "yarn check-env && ng test",
"pree2e": "yarn check-env && yarn ~~update-webdriver",
"e2e": "ng e2e --no-webdriver-update",
"presetup": "yarn install --freeze-lockfile && yarn ~~check-env && yarn boilerplate:remove",
"presetup": "yarn install --frozen-lockfile && yarn ~~check-env && yarn boilerplate:remove",
"setup": "yarn aio-use-npm && yarn example-use-npm",
"postsetup": "yarn boilerplate:add && yarn build-ie-polyfills && yarn generate-plunkers && yarn generate-zips && yarn docs",
"presetup-local": "yarn presetup",
"setup-local": "yarn aio-use-local && yarn example-use-local",
"setup-local": "yarn aio-use-local && yarn upgrade @angular/cli@1.5.0-rc.2 && yarn example-use-local",
"postsetup-local": "yarn postsetup",
"pretest-pwa-score-localhost": "yarn build",
"test-pwa-score-localhost": "concurrently --kill-others --success first \"http-server dist -p 4200 --silent\" \"yarn test-pwa-score http://localhost:4200 90\"",

View File

@ -19,6 +19,11 @@ const config = require('lighthouse/lighthouse-core/config/default.js');
// Constants
const VIEWER_URL = 'https://googlechrome.github.io/lighthouse/viewer/';
// Specify the path to Chrome on Travis
if (process.env.TRAVIS) {
process.env.LIGHTHOUSE_CHROMIUM_PATH = process.env.CHROME_BIN;
}
// Run
_main(process.argv.slice(2));

View File

@ -45,6 +45,7 @@ import { ScrollService } from 'app/shared/scroll.service';
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
import { SearchBoxComponent } from './search/search-box/search-box.component';
import { TocService } from 'app/shared/toc.service';
import { WindowToken, windowProvider } from 'app/shared/window';
import { SharedModule } from 'app/shared/shared.module';
@ -113,7 +114,8 @@ export const svgIconProviders = [
ScrollSpyService,
SearchService,
svgIconProviders,
TocService
TocService,
{ provide: WindowToken, useFactory: windowProvider },
],
bootstrap: [AppComponent]
})

View File

@ -1,130 +1,87 @@
import { ReflectiveInjector } from '@angular/core';
import { fakeAsync, tick } from '@angular/core/testing';
import { GaService } from 'app/shared/ga.service';
import { Logger } from 'app/shared/logger.service';
import { WindowToken } from 'app/shared/window';
describe('GaService', () => {
let gaSpy: jasmine.Spy;
let gaService: GaService;
let injector: ReflectiveInjector;
// filter for 'send' which communicates with server
// returns the url of the 'send pageview'
function gaSpySendCalls() {
let lastUrl: string;
return gaSpy.calls.all()
.reduce((acc, c) => {
const args = c.args;
if (args[0] === 'set') {
lastUrl = args[2];
} else if (args[0] === 'send') {
acc.push(lastUrl);
}
return acc;
}, []);
}
let gaSpy: jasmine.Spy;
let mockWindow: any;
beforeEach(() => {
injector = ReflectiveInjector.resolveAndCreate([
GaService,
{ provide: Logger, useClass: TestLogger }
]);
gaSpy = jasmine.createSpy('ga');
mockWindow = { ga: gaSpy };
injector = ReflectiveInjector.resolveAndCreate([GaService, { provide: WindowToken, useFactory: () => mockWindow }]);
gaService = injector.get(GaService);
});
describe('with ambient GA', () => {
let gaService: GaService;
beforeEach(fakeAsync(() => {
this.winGa = window['ga']; // remember current GA tracker just in case
// Replace Google Analytics tracker with spy after calling "ga ready" callback
window['ga'] = (fn: Function) => {
window['ga'] = gaSpy = jasmine.createSpy('ga');
fn();
tick(GaService.initializeDelay); // see GaService#initializeGa
};
gaService = injector.get(GaService);
}));
afterEach(() => {
window['ga'] = this.winGa;
});
it('should initialize ga with "create" when constructed', () => {
const first = gaSpy.calls.first().args;
expect(first[0]).toBe('create');
});
describe('#locationChanged(url)', () => {
it('should send page to url w/ leading slash', () => {
gaService.locationChanged('testUrl');
let args = gaSpy.calls.all()[1].args;
expect(args).toEqual(['set', 'page', '/testUrl']);
args = gaSpy.calls.all()[2].args;
expect(args).toEqual(['send', 'pageview']);
});
});
describe('#sendPage(url)', () => {
it('should set page to url w/ leading slash', () => {
gaService.sendPage('testUrl');
const args = gaSpy.calls.all()[1].args;
expect(args).toEqual(['set', 'page', '/testUrl']);
});
it('should send "pageview" ', () => {
gaService.sendPage('testUrl');
const args = gaSpy.calls.all()[2].args;
expect(args).toEqual(['send', 'pageview']);
});
it('should not send twice with same URL, back-to-back', () => {
gaService.sendPage('testUrl');
gaService.sendPage('testUrl');
expect(gaSpySendCalls()).toEqual(['/testUrl']);
});
it('should send twice with same URL, back-to-back, even when the hash changes', () => {
// Therefore it is up to caller NOT to call it when hash changes if this is unwanted.
// See LocationService and its specs
gaService.sendPage('testUrl#one');
gaService.sendPage('testUrl#two');
expect(gaSpySendCalls()).toEqual([
'/testUrl#one',
'/testUrl#two'
]);
});
it('should send same URL twice when other intervening URL', () => {
gaService.sendPage('testUrl');
gaService.sendPage('testUrl2');
gaService.sendPage('testUrl');
expect(gaSpySendCalls()).toEqual([
'/testUrl',
'/testUrl2',
'/testUrl'
]);
});
});
it('should initialize ga with "create" when constructed', () => {
const first = gaSpy.calls.first().args;
expect(first[0]).toBe('create');
});
describe('when no ambient GA', () => {
let gaService: GaService;
let logger: TestLogger;
it('should log with "create" when constructed', () => {
gaService = injector.get(GaService);
logger = injector.get(Logger);
expect(logger.log.calls.count()).toBe(1, 'logger.log should be called');
const first = logger.log.calls.first().args;
expect(first[0]).toBe('ga:');
expect(first[1][0]).toBe('create'); // first[1] is the array of args to ga()
describe('#locationChanged(url)', () => {
it('should send page to url w/ leading slash', () => {
gaService.locationChanged('testUrl');
expect(gaSpy).toHaveBeenCalledWith('set', 'page', '/testUrl');
expect(gaSpy).toHaveBeenCalledWith('send', 'pageview');
});
});
describe('#sendPage(url)', () => {
it('should set page to url w/ leading slash', () => {
gaService.sendPage('testUrl');
expect(gaSpy).toHaveBeenCalledWith('set', 'page', '/testUrl');
});
it('should send "pageview" ', () => {
gaService.sendPage('testUrl');
expect(gaSpy).toHaveBeenCalledWith('send', 'pageview');
});
it('should not send twice with same URL, back-to-back', () => {
gaService.sendPage('testUrl');
gaSpy.calls.reset();
gaService.sendPage('testUrl');
expect(gaSpy).not.toHaveBeenCalled();
});
it('should send again even if only the hash changes', () => {
// Therefore it is up to caller NOT to call it when hash changes if this is unwanted.
// See LocationService and its specs
gaService.sendPage('testUrl#one');
expect(gaSpy).toHaveBeenCalledWith('set', 'page', '/testUrl#one');
expect(gaSpy).toHaveBeenCalledWith('send', 'pageview');
gaSpy.calls.reset();
gaService.sendPage('testUrl#two');
expect(gaSpy).toHaveBeenCalledWith('set', 'page', '/testUrl#two');
expect(gaSpy).toHaveBeenCalledWith('send', 'pageview');
});
it('should send same URL twice when other intervening URL', () => {
gaService.sendPage('testUrl');
expect(gaSpy).toHaveBeenCalledWith('set', 'page', '/testUrl');
expect(gaSpy).toHaveBeenCalledWith('send', 'pageview');
gaSpy.calls.reset();
gaService.sendPage('testUrl2');
expect(gaSpy).toHaveBeenCalledWith('set', 'page', '/testUrl2');
expect(gaSpy).toHaveBeenCalledWith('send', 'pageview');
gaSpy.calls.reset();
gaService.sendPage('testUrl');
expect(gaSpy).toHaveBeenCalledWith('set', 'page', '/testUrl');
expect(gaSpy).toHaveBeenCalledWith('send', 'pageview');
});
});
it('should support replacing the `window.ga` function', () => {
const gaSpy2 = jasmine.createSpy('new ga');
mockWindow.ga = gaSpy2;
gaSpy.calls.reset();
gaService.sendPage('testUrl');
expect(gaSpy).not.toHaveBeenCalled();
expect(gaSpy2).toHaveBeenCalledWith('set', 'page', '/testUrl');
expect(gaSpy2).toHaveBeenCalledWith('send', 'pageview');
});
});
class TestLogger {
log = jasmine.createSpy('log');
}

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { Inject, Injectable } from '@angular/core';
import { environment } from '../../environments/environment';
import { Logger } from 'app/shared/logger.service';
import { WindowToken } from 'app/shared/window';
@Injectable()
/**
@ -10,15 +10,10 @@ import { Logger } from 'app/shared/logger.service';
* Associates data with a GA "property" from the environment (`gaId`).
*/
export class GaService {
// ms to wait before acquiring window.ga after analytics library loads
// empirically determined to allow time for e2e test setup
static initializeDelay = 1000;
private previousUrl: string;
private ga: (...rest: any[]) => void;
constructor(private logger: Logger) {
this.initializeGa();
constructor(@Inject(WindowToken) private window: Window) {
this.ga('create', environment['gaId'] , 'auto');
}
@ -34,27 +29,7 @@ export class GaService {
this.ga('send', 'pageview');
}
// These gyrations are necessary to make the service e2e testable
// and to disable ga tracking during e2e tests.
private initializeGa() {
const ga = window['ga'];
if (ga) {
// Queue commands until GA analytics script has loaded.
const gaQueue: any[][] = [];
this.ga = (...rest: any[]) => { gaQueue.push(rest); };
// Then send queued commands to either real or e2e test ga();
// after waiting to allow possible e2e test to replace global ga function
ga(() => setTimeout(() => {
// this.logger.log('GA fn:', window['ga'].toString());
this.ga = window['ga'];
gaQueue.forEach((command) => this.ga.apply(null, command));
}, GaService.initializeDelay));
} else {
// delegate `ga` calls to the logger if no ga installed
this.ga = (...rest: any[]) => { this.logger.log('ga:', rest); };
}
ga(...args) {
this.window['ga'](...args);
}
}

View File

@ -0,0 +1,4 @@
import { InjectionToken } from '@angular/core';
export const WindowToken = new InjectionToken('Window');
export function windowProvider() { return window; }

View File

@ -34,9 +34,13 @@
<!-- Google Analytics -->
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
// Note this is a customised version of the GA tracking snippet to aid e2e testing
// See the bit between /**/.../**/
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;/**/i.sessionStorage.__e2e__||/**/m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<!-- End Google Analytics -->
<script>

View File

@ -28,7 +28,7 @@
"angular-in-memory-web-api": "~0.4.6",
"core-js": "^2.4.1",
"express": "^4.14.1",
"rxjs": "^5.1.0",
"rxjs": "^5.5.0",
"systemjs": "0.19.39",
"web-animations-js": "^2.3.1",
"zone.js": "^0.8.4"

View File

@ -20,7 +20,11 @@ exports.config = {
// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome'
'browserName': 'chrome',
// For Travis
chromeOptions: {
binary: process.env.CHROME_BIN
}
},
// Framework to use. Jasmine is recommended.

View File

@ -2,26 +2,26 @@
# yarn lockfile v1
"@angular-devkit/build-optimizer@~0.0.23":
version "0.0.24"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.0.24.tgz#f361f34dd9f2a6b5ba14ab32327697419cc0ef7e"
"@angular-devkit/build-optimizer@~0.0.28":
version "0.0.31"
resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.0.31.tgz#511bc00e18c787a6cd4efe37b8cdb9f1bc6d4472"
dependencies:
loader-utils "^1.1.0"
source-map "^0.5.6"
typescript "^2.3.3"
webpack-sources "^1.0.1"
"@angular-devkit/core@0.0.16":
version "0.0.16"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-0.0.16.tgz#498efc22948e7e17fe7e826a67b5c23a60c5e873"
"@angular-devkit/core@0.0.20":
version "0.0.20"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-0.0.20.tgz#2ad36dd210fccd0e156d01c6499082ad4cd8c2af"
dependencies:
source-map "^0.5.6"
"@angular-devkit/schematics@~0.0.25":
version "0.0.26"
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-0.0.26.tgz#58b9b004dbe6bfd125674b883a63180a521f2d2d"
"@angular-devkit/schematics@~0.0.34":
version "0.0.34"
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-0.0.34.tgz#c3ef61b0e49e585d9982f2828e9a67b3879a6b1b"
dependencies:
"@angular-devkit/core" "0.0.16"
"@angular-devkit/core" "0.0.20"
"@ngtools/json-schema" "^1.1.0"
minimist "^1.2.0"
rxjs "^5.4.2"
@ -33,19 +33,19 @@
tslib "^1.7.1"
"@angular/cli@^1.3.1":
version "1.4.5"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-1.4.5.tgz#b982e13799b19e13eb820c2e17ef2c62a3b10881"
version "1.4.9"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-1.4.9.tgz#cd5cd7a8f3bf3a1c28dfcdc523a011f0fcebfb96"
dependencies:
"@angular-devkit/build-optimizer" "~0.0.23"
"@angular-devkit/schematics" "~0.0.25"
"@angular-devkit/build-optimizer" "~0.0.28"
"@angular-devkit/schematics" "~0.0.34"
"@ngtools/json-schema" "1.1.0"
"@ngtools/webpack" "1.7.2"
"@schematics/angular" "~0.0.38"
"@ngtools/webpack" "1.7.4"
"@schematics/angular" "~0.0.46"
autoprefixer "^6.5.3"
chalk "^2.0.1"
circular-dependency-plugin "^3.0.0"
common-tags "^1.3.1"
copy-webpack-plugin "^4.0.1"
copy-webpack-plugin "^4.1.1"
core-object "^3.1.0"
css-loader "^0.28.1"
cssnano "^3.10.0"
@ -53,7 +53,7 @@
ember-cli-string-utils "^1.0.0"
exports-loader "^0.6.3"
extract-text-webpack-plugin "3.0.0"
file-loader "^0.10.0"
file-loader "^1.1.5"
fs-extra "^4.0.0"
glob "^7.0.3"
html-webpack-plugin "^2.29.0"
@ -82,12 +82,13 @@
stylus "^0.54.5"
stylus-loader "^3.0.1"
typescript ">=2.0.0 <2.6.0"
url-loader "^0.6.0"
webpack "~3.6.0"
url-loader "^0.6.2"
webpack "~3.7.1"
webpack-concat-plugin "1.4.0"
webpack-dev-middleware "~1.12.0"
webpack-dev-server "~2.7.1"
webpack-merge "^4.1.0"
webpack-sources "^1.0.0"
zone.js "^0.8.14"
optionalDependencies:
node-sass "^4.3.0"
@ -172,18 +173,20 @@
version "1.1.0"
resolved "https://registry.yarnpkg.com/@ngtools/json-schema/-/json-schema-1.1.0.tgz#c3a0c544d62392acc2813a42c8a0dc6f58f86922"
"@ngtools/webpack@1.7.2", "@ngtools/webpack@^1.2.11":
version "1.7.2"
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.7.2.tgz#3fc4de01786dcc2f50d8cbaaa117311e56799977"
"@ngtools/webpack@1.7.4", "@ngtools/webpack@^1.2.11":
version "1.7.4"
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.7.4.tgz#5015c47ebd339045dd89a1bef0497f4524d2c8ed"
dependencies:
enhanced-resolve "^3.1.0"
loader-utils "^1.0.2"
magic-string "^0.22.3"
source-map "^0.5.6"
"@schematics/angular@~0.0.38":
version "0.0.39"
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-0.0.39.tgz#6dc2b461e95e4f4756a48693f2f13a771f6d6cb5"
"@schematics/angular@~0.0.46":
version "0.0.48"
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-0.0.48.tgz#6fa1bfb2676ba34c243811d2083173821d3cf07c"
dependencies:
"@angular-devkit/core" "0.0.20"
"@types/angular-animate@^1.5.5":
version "1.5.8"
@ -222,25 +225,33 @@
"@types/angular" "*"
"@types/angular@*", "@types/angular@^1.5.16":
version "1.6.32"
resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.6.32.tgz#fc791aad038227d9413eb5e552993e1076f8a509"
version "1.6.36"
resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.6.36.tgz#15e73d632274b5655a391470844863548c7755f4"
"@types/body-parser@*":
version "1.16.7"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.16.7.tgz#455fc23fd0ddaaeda6cd6cbb653558276e5920fa"
dependencies:
"@types/express" "*"
"@types/node" "*"
"@types/express-serve-static-core@*":
version "4.0.53"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.0.53.tgz#1723a35d1447f2c55e13c8721eab3448e42f4d82"
version "4.0.55"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.0.55.tgz#f53868838a955f98b380819ec9134f5df7d9482f"
dependencies:
"@types/node" "*"
"@types/express@^4.0.35":
version "4.0.37"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.0.37.tgz#625ac3765169676e01897ca47011c26375784971"
"@types/express@*", "@types/express@^4.0.35":
version "4.0.39"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.0.39.tgz#1441f21d52b33be8d4fa8a865c15a6a91cd0fa09"
dependencies:
"@types/body-parser" "*"
"@types/express-serve-static-core" "*"
"@types/serve-static" "*"
"@types/jasmine@*":
version "2.6.0"
resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.6.0.tgz#997b41a27752b4850af2683bc4a8d8222c25bd02"
version "2.6.2"
resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.6.2.tgz#6e6d4cb183cd55c7a1ad6270bced10fdd5367a3c"
"@types/jasmine@~2.5.53":
version "2.5.54"
@ -257,12 +268,12 @@
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b"
"@types/node@*":
version "8.0.32"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.32.tgz#869a716538b6eec65ab3893f183d557be3cda206"
version "8.0.47"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.47.tgz#968e596f91acd59069054558a00708c445ca30c2"
"@types/node@^6.0.45", "@types/node@^6.0.46":
version "6.0.88"
resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.88.tgz#f618f11a944f6a18d92b5c472028728a3e3d4b66"
version "6.0.90"
resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.90.tgz#0ed74833fa1b73dcdb9409dcb1c97ec0a8b13b02"
"@types/q@^0.0.32":
version "0.0.32"
@ -273,8 +284,8 @@
resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-2.53.42.tgz#74cb77fb6052edaff2a8984ddafd88d419f25cac"
"@types/serve-static@*":
version "1.7.32"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.7.32.tgz#0f6732e4dab0813771dd8fc8fe14940f34728b4c"
version "1.13.0"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.0.tgz#2ef359d8d5982bc011a7a8fa36016e629e8b7635"
dependencies:
"@types/express-serve-static-core" "*"
"@types/mime" "*"
@ -307,7 +318,7 @@ acorn@^4.0.3:
version "4.0.13"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
acorn@^5.0.0, acorn@^5.1.1:
acorn@^5.0.0, acorn@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.2.tgz#911cb53e036807cf0fa778dc5d370fbd864246d7"
@ -346,13 +357,13 @@ ajv@^4.9.1:
json-stable-stringify "^1.0.1"
ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5:
version "5.2.3"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.3.tgz#c06f598778c44c6b161abafe3466b81ad1814ed2"
version "5.3.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.3.0.tgz#4414ff74a50879c208ee5fdc826e32c303549eda"
dependencies:
co "^4.6.0"
fast-deep-equal "^1.0.0"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.3.0"
json-stable-stringify "^1.0.1"
align-text@^0.1.1, align-text@^0.1.3:
version "0.1.4"
@ -530,11 +541,7 @@ async@1.5.2, async@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
async@^0.9.0:
version "0.9.2"
resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
async@^2.1.2, async@^2.1.5, async@^2.4.1:
async@^2.1.2, async@^2.1.5, async@^2.4.1, async@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d"
dependencies:
@ -851,8 +858,8 @@ browser-sync@^2.18.5:
yargs "6.4.0"
browserify-aes@^1.0.0, browserify-aes@^1.0.4:
version "1.0.8"
resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.8.tgz#c8fa3b1b7585bb7ba77c5560b60996ddec6d5309"
version "1.1.1"
resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.1.1.tgz#38b7ab55edb806ff2dcda1a7f1620773a477c49f"
dependencies:
buffer-xor "^1.0.3"
cipher-base "^1.0.0"
@ -989,8 +996,8 @@ caniuse-api@^1.5.2:
lodash.uniq "^4.5.0"
caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
version "1.0.30000744"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000744.tgz#00758ff7dd5f7138d34a15608dccf71a59656ffe"
version "1.0.30000752"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000752.tgz#daa129d54ce700e94ad14388bdef514ec1e2dd54"
canonical-path@0.0.2:
version "0.0.2"
@ -1027,17 +1034,9 @@ chalk@^1.1.1, chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
chalk@^2.0.0, chalk@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e"
dependencies:
ansi-styles "^3.1.0"
escape-string-regexp "^1.0.5"
supports-color "^4.0.0"
chalk@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.0.1.tgz#dbec49436d2ae15f536114e76d14656cdbc0f44d"
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba"
dependencies:
ansi-styles "^3.1.0"
escape-string-regexp "^1.0.5"
@ -1215,10 +1214,10 @@ component-inherit@0.0.3:
resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
compressible@~2.0.11:
version "2.0.11"
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.11.tgz#16718a75de283ed8e604041625a2064586797d8a"
version "2.0.12"
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66"
dependencies:
mime-db ">= 1.29.0 < 2"
mime-db ">= 1.30.0 < 2"
compression@^1.5.2:
version "1.7.1"
@ -1258,8 +1257,8 @@ concurrently@^3.0.0:
tree-kill "^1.1.0"
connect-history-api-fallback@^1.1.0, connect-history-api-fallback@^1.2.0, connect-history-api-fallback@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.3.0.tgz#e51d17f8f0ef0db90a64fdb47de3051556e9f169"
version "1.4.0"
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.4.0.tgz#3db24f973f4b923b0e82f619ce0df02411ca623d"
connect-logger@0.0.1:
version "0.0.1"
@ -1327,9 +1326,9 @@ cookie@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
copy-webpack-plugin@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.1.1.tgz#53ae69e04955ebfa9fda411f54cbb968531d71fd"
copy-webpack-plugin@^4.0.1, copy-webpack-plugin@^4.1.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.2.0.tgz#252bb94597f96399d23d7fad355f8d3a661ac096"
dependencies:
bluebird "^3.5.1"
fs-extra "^4.0.2"
@ -1579,8 +1578,8 @@ dashdash@^1.12.0:
assert-plus "^1.0.0"
date-fns@^1.23.0:
version "1.28.5"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.28.5.tgz#257cfc45d322df45ef5658665967ee841cd73faf"
version "1.29.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6"
date-now@^0.1.4:
version "0.1.4"
@ -1711,8 +1710,8 @@ diff@^2.2.1:
resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99"
diff@^3.1.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75"
version "3.4.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c"
diffie-hellman@^5.0.0:
version "5.0.2"
@ -1840,8 +1839,8 @@ ejs@^2.5.7:
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a"
electron-to-chromium@^1.2.7:
version "1.3.24"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.24.tgz#9b7b88bb05ceb9fa016a177833cc2dde388f21b6"
version "1.3.27"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d"
elliptic@^6.0.0:
version "6.4.0"
@ -1978,20 +1977,20 @@ error-ex@^1.2.0:
dependencies:
is-arrayish "^0.2.1"
es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14:
version "0.10.30"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.30.tgz#7141a16836697dbabfaaaeee41495ce29f52c939"
es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14:
version "0.10.35"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.35.tgz#18ee858ce6a3c45c7d79e91c15fcca9ec568494f"
dependencies:
es6-iterator "2"
es6-symbol "~3.1"
es6-iterator "~2.0.1"
es6-symbol "~3.1.1"
es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512"
es6-iterator@^2.0.1, es6-iterator@~2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
dependencies:
d "1"
es5-ext "^0.10.14"
es6-symbol "^3.1"
es5-ext "^0.10.35"
es6-symbol "^3.1.1"
es6-map@^0.1.3:
version "0.1.5"
@ -2018,7 +2017,7 @@ es6-set@~0.1.5:
es6-symbol "3.1.1"
event-emitter "~0.3.5"
es6-symbol@3.1.1, es6-symbol@^3.1, es6-symbol@^3.1.1, es6-symbol@~3.1, es6-symbol@~3.1.1:
es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
dependencies:
@ -2185,8 +2184,8 @@ express@2.5.x:
qs "0.4.x"
express@^4.13.3, express@^4.14.1:
version "4.16.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.16.1.tgz#6b33b560183c9b253b7b62144df33a4654ac9ed0"
version "4.16.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c"
dependencies:
accepts "~1.3.4"
array-flatten "1.1.1"
@ -2263,6 +2262,10 @@ fast-deep-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
fast-json-stable-stringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
fastparse@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
@ -2285,18 +2288,19 @@ fd-slicer@~1.0.1:
dependencies:
pend "~1.2.0"
file-loader@^0.10.0:
version "0.10.1"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-0.10.1.tgz#815034119891fc6441fb5a64c11bc93c22ddd842"
dependencies:
loader-utils "^1.0.2"
file-loader@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-0.9.0.tgz#1d2daddd424ce6d1b07cfe3f79731bed3617ab42"
dependencies:
loader-utils "~0.2.5"
file-loader@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-1.1.5.tgz#91c25b6b6fbe56dae99f10a425fd64933b5c9daa"
dependencies:
loader-utils "^1.0.2"
schema-utils "^0.3.0"
filename-regex@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
@ -2812,8 +2816,8 @@ html-loader@^0.4.3:
object-assign "^4.1.0"
html-minifier@^3.0.1, html-minifier@^3.2.3:
version "3.5.5"
resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.5.tgz#3bdc9427e638bbe3dbde96c0eb988b044f02739e"
version "3.5.6"
resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.6.tgz#7e4e661a09999599c7d8e8a2b8d7fb7430bb5c3e"
dependencies:
camel-case "3.0.x"
clean-css "4.1.x"
@ -3049,8 +3053,8 @@ is-binary-path@^1.0.0:
binary-extensions "^1.0.0"
is-buffer@^1.0.2, is-buffer@^1.1.5, is-buffer@~1.1.1:
version "1.1.5"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
is-builtin-module@^1.0.0:
version "1.0.0"
@ -3240,8 +3244,8 @@ istanbul-lib-coverage@^1.1.1:
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da"
istanbul-lib-instrument@^1.1.3:
version "1.8.0"
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.8.0.tgz#66f6c9421cc9ec4704f76f2db084ba9078a2b532"
version "1.9.1"
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e"
dependencies:
babel-generator "^6.18.0"
babel-template "^6.16.0"
@ -3499,8 +3503,8 @@ less-loader@^4.0.5:
pify "^2.3.0"
less@^2.7.2:
version "2.7.2"
resolved "https://registry.yarnpkg.com/less/-/less-2.7.2.tgz#368d6cc73e1fb03981183280918743c5dcf9b3df"
version "2.7.3"
resolved "https://registry.yarnpkg.com/less/-/less-2.7.3.tgz#cc1260f51c900a9ec0d91fb6998139e02507b63b"
optionalDependencies:
errno "^0.1.1"
graceful-fs "^4.1.2"
@ -3508,12 +3512,12 @@ less@^2.7.2:
mime "^1.2.11"
mkdirp "^0.5.0"
promise "^7.1.1"
request "^2.72.0"
request "2.81.0"
source-map "^0.5.3"
license-webpack-plugin@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-1.1.0.tgz#99117ae985fb6c2a70df301b8a1ae0268f17f433"
version "1.1.1"
resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-1.1.1.tgz#76b2cedccc78f139fd7877e576f756cfc141b8c2"
dependencies:
ejs "^2.5.7"
@ -3635,8 +3639,8 @@ log4js@^0.6.31:
semver "~4.3.3"
loglevel@^1.4.1:
version "1.5.0"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.5.0.tgz#3863984a2c326b986fbb965f378758a6dc8a4324"
version "1.5.1"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.5.1.tgz#189078c94ab9053ee215a0acdbf24244ea0f6502"
longest@^1.0.1:
version "1.0.1"
@ -3776,7 +3780,11 @@ miller-rabin@^4.0.0:
bn.js "^4.0.0"
brorand "^1.0.1"
"mime-db@>= 1.29.0 < 2", mime-db@~1.30.0:
"mime-db@>= 1.30.0 < 2":
version "1.31.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.31.0.tgz#a49cd8f3ebf3ed1a482b60561d9105ad40ca74cb"
mime-db@~1.30.0:
version "1.30.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
@ -3863,8 +3871,8 @@ mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkd
minimist "0.0.8"
moment@*:
version "2.18.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
version "2.19.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.1.tgz#56da1a2d1cbf01d38b7e1afc31c10bcfa1929167"
ms@0.7.1:
version "0.7.1"
@ -4732,8 +4740,8 @@ pretty-error@^2.0.2:
utila "~0.4"
private@~0.1.5:
version "0.1.7"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1"
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
process-nextick-args@~1.0.6:
version "1.0.7"
@ -4811,8 +4819,8 @@ q@1.4.1:
resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e"
q@^1.1.2, q@^1.4.1:
version "1.5.0"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1"
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
qjobs@^1.1.4:
version "1.1.5"
@ -4892,8 +4900,8 @@ raw-loader@^0.5.1:
resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa"
rc@^1.1.7:
version "1.2.1"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
version "1.2.2"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077"
dependencies:
deep-extend "~0.4.0"
ini "~1.3.0"
@ -5068,7 +5076,7 @@ request-progress@~2.0.1:
dependencies:
throttleit "^1.0.0"
request@2, request@^2.72.0, request@^2.78.0, request@^2.79.0:
request@2, request@^2.78.0, request@^2.79.0:
version "2.83.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
dependencies:
@ -5143,8 +5151,8 @@ resolve@1.1.7:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
resolve@^1.1.6, resolve@^1.1.7, resolve@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86"
version "1.5.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
dependencies:
path-parse "^1.0.5"
@ -5179,10 +5187,10 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
inherits "^2.0.1"
rollup-plugin-commonjs@^8.0.2:
version "8.2.1"
resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-8.2.1.tgz#5e40c78375eb163c14c76bce69da1750e5905a2e"
version "8.2.5"
resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-8.2.5.tgz#924421278a6f879fd976b2ef1a28391b1e4f2a6e"
dependencies:
acorn "^5.1.1"
acorn "^5.1.2"
estree-walker "^0.5.0"
magic-string "^0.22.4"
resolve "^1.4.0"
@ -5223,9 +5231,9 @@ rx@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
rxjs@^5.1.0, rxjs@^5.4.2:
version "5.4.3"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.3.tgz#0758cddee6033d68e0fd53676f0f3596ce3d483f"
rxjs@^5.4.2, rxjs@^5.5.0:
version "5.5.2"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.2.tgz#28d403f0071121967f18ad665563255d54236ac3"
dependencies:
symbol-observable "^1.0.1"
@ -5481,8 +5489,8 @@ sntp@1.x.x:
hoek "2.x.x"
sntp@2.x.x:
version "2.0.2"
resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.0.2.tgz#5064110f0af85f7cfdb7d6b67a40028ce52b4b2b"
version "2.1.0"
resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
dependencies:
hoek "4.x.x"
@ -5604,12 +5612,12 @@ source-map-explorer@^1.3.2:
underscore "^1.8.3"
source-map-loader@^0.2.0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.2.tgz#1249348ff6a66ea64a2957fc98f74cb6bba67505"
version "0.2.3"
resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.3.tgz#d4b0c8cd47d54edce3e6bfa0f523f452b5b0e521"
dependencies:
async "^0.9.0"
async "^2.5.0"
loader-utils "~0.2.2"
source-map "~0.1.33"
source-map "~0.6.1"
source-map-support@^0.4.0, source-map-support@^0.4.1, source-map-support@^0.4.2, source-map-support@~0.4.0:
version "0.4.18"
@ -5617,7 +5625,7 @@ source-map-support@^0.4.0, source-map-support@^0.4.1, source-map-support@^0.4.2,
dependencies:
source-map "^0.5.6"
source-map@0.1.x, source-map@~0.1.33, source-map@~0.1.7:
source-map@0.1.x, source-map@~0.1.7:
version "0.1.43"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346"
dependencies:
@ -5633,7 +5641,7 @@ source-map@^0.4.2:
dependencies:
amdefine ">=0.0.4"
source-map@^0.6.1:
source-map@^0.6.1, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
@ -5700,7 +5708,11 @@ sshpk@^1.7.0:
jsbn "~0.1.0"
tweetnacl "~0.14.0"
"statuses@>= 1.3.1 < 2", statuses@~1.3.0, statuses@~1.3.1:
"statuses@>= 1.3.1 < 2":
version "1.4.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
statuses@~1.3.0, statuses@~1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
@ -5849,8 +5861,8 @@ supports-color@^3.1.1, supports-color@^3.2.3:
has-flag "^1.0.0"
supports-color@^4.0.0, supports-color@^4.2.1, supports-color@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e"
version "4.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b"
dependencies:
has-flag "^2.0.0"
@ -5881,8 +5893,8 @@ tapable@^0.2.7:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22"
tar-pack@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984"
version "3.4.1"
resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f"
dependencies:
debug "^2.2.0"
fstream "^1.0.10"
@ -6025,8 +6037,8 @@ tsickle@^0.21.0:
source-map-support "^0.4.2"
tslib@^1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.7.1.tgz#bc8004164691923a79fe8378bbeb3da2017538ec"
version "1.8.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.0.tgz#dc604ebad64bcbf696d613da6c954aa0e7ea1eb6"
tslint@^3.15.1:
version "3.15.1"
@ -6078,11 +6090,11 @@ ua-parser-js@0.7.12:
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb"
uglify-js@3.1.x:
version "3.1.3"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.1.3.tgz#d61f0453b4718cab01581f3162aa90bab7520b42"
version "3.1.5"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.1.5.tgz#4c1a6d53b2fe77e4710dd94631853effd3ff5143"
dependencies:
commander "~2.11.0"
source-map "~0.5.1"
source-map "~0.6.1"
uglify-js@^2.6.1, uglify-js@^2.8.29:
version "2.8.29"
@ -6172,7 +6184,7 @@ url-join@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz#741c6c2f4596c4830d6718460920d0c92202dc78"
url-loader@^0.6.0:
url-loader@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.6.2.tgz#a007a7109620e9d988d14bce677a1decb9a993f7"
dependencies:
@ -6384,16 +6396,16 @@ webpack-sources@^0.1.0:
source-list-map "~0.1.7"
source-map "~0.5.3"
webpack-sources@^1.0.1:
webpack-sources@^1.0.0, webpack-sources@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.1.tgz#c7356436a4d13123be2e2426a05d1dad9cbe65cf"
dependencies:
source-list-map "^2.0.0"
source-map "~0.5.3"
webpack@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.6.0.tgz#a89a929fbee205d35a4fa2cc487be9cbec8898bc"
webpack@~3.7.1:
version "3.7.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.7.1.tgz#6046b5c415ff7df7a0dc54c5b6b86098e8b952da"
dependencies:
acorn "^5.0.0"
acorn-dynamic-import "^2.0.0"

View File

@ -129,7 +129,7 @@ class NgPackagesInstaller {
* Yarn will also delete the local marker file for us.
*/
restoreNpmDependencies() {
this._installDeps('--freeze-lockfile', '--check-files');
this._installDeps('--frozen-lockfile', '--check-files');
}
// Protected helpers

View File

@ -203,7 +203,7 @@ describe('NgPackagesInstaller', () => {
it('should run `yarn install` in the specified directory, with the correct options', () => {
spyOn(installer, '_installDeps');
installer.restoreNpmDependencies();
expect(installer._installDeps).toHaveBeenCalledWith('--freeze-lockfile', '--check-files');
expect(installer._installDeps).toHaveBeenCalledWith('--frozen-lockfile', '--check-files');
});
});

View File

@ -63,6 +63,7 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage])
'router/index.ts',
'router/testing/index.ts',
'router/upgrade/index.ts',
'service-worker/index.ts',
'upgrade/index.ts',
'upgrade/static/index.ts',
];

View File

@ -21,6 +21,7 @@ const packageMap = {
'platform-webworker': ['platform-webworker/index.ts'],
'platform-webworker-dynamic': 'platform-webworker-dynamic/index.ts',
router: ['router/index.ts', 'router/testing/index.ts', 'router/upgrade/index.ts'],
'service-worker': ['service-worker/index.ts'],
upgrade: ['upgrade/index.ts', 'upgrade/static/index.ts']
};

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "5.0.0-rc.3",
"version": "5.0.0-rc.9",
"private": true,
"branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps",
@ -24,7 +24,7 @@
"dependencies": {
"core-js": "^2.4.1",
"reflect-metadata": "^0.1.3",
"rxjs": "^5.5.0",
"rxjs": "^5.5.2",
"tslib": "^1.7.1",
"zone.js": "^0.8.12"
},

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/core': 'ng.core',
@ -15,7 +15,7 @@ const globals = {
'@angular/animations': 'ng.animations'
};
export default {
module.exports = {
entry: '../../../dist/packages-dist/animations/esm5/browser.js',
dest: '../../../dist/packages-dist/animations/bundles/animations-browser.umd.js',
format: 'umd',

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/core': 'ng.core',
@ -16,7 +16,7 @@ const globals = {
'rxjs/Subject': 'Rx',
};
export default {
module.exports = {
entry: '../../../../dist/packages-dist/animations/esm5/browser/testing.js',
dest: '../../../../dist/packages-dist/animations/bundles/animations-browser-testing.umd.js',
format: 'umd',

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/core': 'ng.core',
@ -16,7 +16,7 @@ const globals = {
'rxjs/Subject': 'Rx',
};
export default {
module.exports = {
entry: '../../dist/packages-dist/animations/esm5/animations.js',
dest: '../../dist/packages-dist/animations/bundles/animations.umd.js',
format: 'umd',

View File

@ -60,6 +60,9 @@ export function runOneBuild(args: string[], inputs?: {[path: string]: string}):
inputs,
expectedOuts
});
if (diagnostics.length) {
console.error(ng.formatDiagnostics(diagnostics));
}
return diagnostics.every(d => d.category !== ts.DiagnosticCategory.Error);
}
@ -204,9 +207,7 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
{rootNames: files, options: compilerOpts, host: ngHost, emitCallback, gatherDiagnostics});
const tsickleEmitResult = emitResult as tsickle.EmitResult;
let externs = '/** @externs */\n';
if (diagnostics.length) {
console.error(ng.formatDiagnostics(diagnostics));
} else {
if (!diagnostics.length) {
if (bazelOpts.tsickleGenerateExterns) {
externs += tsickle.getGeneratedExterns(tsickleEmitResult.externs);
}

View File

@ -11,6 +11,7 @@ const globals = {
'@angular/platform-browser': 'ng.platformBrowser',
'@angular/common': 'ng.common',
'rxjs/Observable': 'Rx',
'rxjs/Observer': 'Rx',
'rxjs/Subject': 'Rx',
'rxjs/observable/of': 'Rx.Observable.prototype',
@ -20,7 +21,7 @@ const globals = {
'rxjs/operator/map': 'Rx.Observable.prototype',
};
export default {
module.exports = {
entry: '../../../dist/packages-dist/common/esm5/http.js',
dest: '../../../dist/packages-dist/common/bundles/common-http.umd.js',
format: 'umd',

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/core': 'ng.core',
@ -15,11 +15,14 @@ const globals = {
'@angular/common': 'ng.common',
'@angular/common/http': 'ng.common.http',
'rxjs/Observable': 'Rx',
'rxjs/Observer': 'Rx',
'rxjs/ReplaySubject': 'Rx',
'rxjs/Subject': 'Rx',
'rxjs/operator/startWith': 'Rx.Observable.prototype',
};
export default {
module.exports = {
entry: '../../../../dist/packages-dist/common/esm5/http/testing.js',
dest: '../../../../dist/packages-dist/common/bundles/common-http-testing.umd.js',
format: 'umd',

View File

@ -6,16 +6,18 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/core': 'ng.core',
'rxjs/Observable': 'Rx',
'rxjs/Observer': 'Rx',
'rxjs/Subject': 'Rx',
'rxjs/Subscription': 'Rx',
};
export default {
module.exports = {
entry: '../../dist/packages-dist/common/esm5/common.js',
dest: '../../dist/packages-dist/common/bundles/common.umd.js',
format: 'umd',

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/core': 'ng.core',
@ -16,7 +16,7 @@ const globals = {
'rxjs/Subject': 'Rx'
};
export default {
module.exports = {
entry: '../../../dist/packages-dist/common/esm5/testing.js',
dest: '../../../dist/packages-dist/common/bundles/common-testing.umd.js',
format: 'umd',

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import commonjs from 'rollup-plugin-commonjs';
import * as path from 'path';
const commonjs = require('rollup-plugin-commonjs');
const path = require('path');
import 'reflect-metadata';
require('reflect-metadata');
var m = /^\@angular\/((\w|\-)+)(\/(\w|\d|\/|\-)+)?$/;
var location = normalize('../../dist/packages-dist') + '/';
@ -49,7 +49,7 @@ function resolve(id, from) {
// hack to get around issues with default exports
var banner = `ts['default'] = ts['default'] || ts; fs['default'] = fs['default'] || fs;`;
export default {
module.exports = {
entry: '../../dist/packages-dist/compiler-cli/src/ngc.js',
dest: './browser-bundle.umd.js',
format: 'umd',

View File

@ -6,13 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
export {AotCompilerHost, AotCompilerHost as StaticReflectorHost, StaticReflector, StaticSymbol} from '@angular/compiler';
export {CodeGenerator} from './src/codegen';
export {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter, NodeCompilerHostContext} from './src/compiler_host';
export {DiagnosticTemplateInfo, getExpressionScope, getTemplateExpressionDiagnostics} from './src/diagnostics/expression_diagnostics';
export {AstType, ExpressionDiagnosticsContext} from './src/diagnostics/expression_type';
export {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './src/diagnostics/symbols';
export {getClassMembersFromDeclaration, getPipesTable, getSymbolQuery} from './src/diagnostics/typescript_symbols';
export {Extractor} from './src/extractor';
export {VERSION} from './src/version';
export * from './src/metadata';
@ -21,8 +18,7 @@ export * from './src/transformers/entry_points';
export * from './src/perform_compile';
// TODO(tbosch): remove this once everyone is on transformers
// TODO(tbosch): remove this once cli 1.5 is fully released,
// and usages in G3 are changed to `CompilerOptions`.
export {CompilerOptions as AngularCompilerOptions} from './src/transformers/api';
// TODO(hansl): moving to Angular 4 need to update this API.
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api';

View File

@ -15,8 +15,6 @@ import * as ts from 'typescript';
import * as assert from 'assert';
import {__NGTOOLS_PRIVATE_API_2, readConfiguration} from '@angular/compiler-cli';
const glob = require('glob');
/* tslint:disable:no-console */
/**
* Main method.
@ -27,9 +25,6 @@ function main() {
console.log(`testing ngtools API...`);
Promise.resolve()
.then(() => codeGenTest())
.then(() => codeGenTest(true))
.then(() => i18nTest())
.then(() => lazyRoutesTest())
.then(() => {
console.log('All done!');
@ -42,152 +37,6 @@ function main() {
});
}
function codeGenTest(forceError = false) {
const basePath = path.join(__dirname, '../ngtools_src');
const srcPath = path.join(__dirname, '../src');
const project = path.join(basePath, 'tsconfig-build.json');
const readResources: string[] = [];
const wroteFiles: string[] = [];
const config = readConfiguration(project);
const delegateHost = ts.createCompilerHost(config.options, true);
const host: ts.CompilerHost = Object.assign(
{}, delegateHost,
{writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }});
const program = ts.createProgram(config.rootNames, config.options, host);
config.options.basePath = basePath;
console.log(`>>> running codegen for ${project}`);
if (forceError) {
console.log(`>>> asserting that missingTranslation param with error value throws`);
}
return __NGTOOLS_PRIVATE_API_2
.codeGen({
basePath,
compilerOptions: config.options, program, host,
angularCompilerOptions: config.options,
// i18n options.
i18nFormat: 'xlf',
i18nFile: path.join(srcPath, 'messages.fi.xlf'),
locale: 'fi',
missingTranslation: forceError ? 'error' : 'ignore',
readResource: (fileName: string) => {
readResources.push(fileName);
if (!host.fileExists(fileName)) {
throw new Error(`Compilation failed. Resource file not found: ${fileName}`);
}
return Promise.resolve(host.readFile(fileName));
}
})
.then(() => {
console.log(`>>> codegen done, asserting read and wrote files`);
// Assert for each file that it has been read and each `ts` has a written file associated.
const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true});
allFiles.forEach((fileName: string) => {
// Skip tsconfig.
if (fileName.match(/tsconfig-build.json$/)) {
return;
}
// Assert that file was read.
if (fileName.match(/\.module\.ts$/)) {
const factory = fileName.replace(/\.module\.ts$/, '.module.ngfactory.ts');
assert(wroteFiles.indexOf(factory) != -1, `Expected file "${factory}" to be written.`);
} else if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) {
assert(
readResources.indexOf(fileName) != -1,
`Expected resource "${fileName}" to be read.`);
}
});
console.log(`done, no errors.`);
})
.catch((e: Error) => {
if (forceError) {
assert(
e.message.match(`Missing translation for message`),
`Expected error message for missing translations`);
console.log(`done, error catched`);
} else {
console.error(e.stack);
console.error('Compilation failed');
throw e;
}
});
}
function i18nTest() {
const basePath = path.join(__dirname, '../ngtools_src');
const project = path.join(basePath, 'tsconfig-build.json');
const readResources: string[] = [];
const wroteFiles: string[] = [];
const config = readConfiguration(project);
const delegateHost = ts.createCompilerHost(config.options, true);
const host: ts.CompilerHost = Object.assign(
{}, delegateHost,
{writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }});
const program = ts.createProgram(config.rootNames, config.options, host);
config.options.basePath = basePath;
console.log(`>>> running i18n extraction for ${project}`);
return __NGTOOLS_PRIVATE_API_2
.extractI18n({
basePath,
compilerOptions: config.options, program, host,
angularCompilerOptions: config.options,
i18nFormat: 'xlf',
locale: undefined,
outFile: undefined,
readResource: (fileName: string) => {
readResources.push(fileName);
if (!host.fileExists(fileName)) {
throw new Error(`Compilation failed. Resource file not found: ${fileName}`);
}
return Promise.resolve(host.readFile(fileName));
},
})
.then(() => {
console.log(`>>> i18n extraction done, asserting read and wrote files`);
const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true});
assert(wroteFiles.length == 1, `Expected a single message bundle file.`);
assert(
wroteFiles[0].endsWith('/ngtools_src/messages.xlf'),
`Expected the bundle file to be "message.xlf".`);
allFiles.forEach((fileName: string) => {
// Skip tsconfig.
if (fileName.match(/tsconfig-build.json$/)) {
return;
}
// Assert that file was read.
if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) {
assert(
readResources.indexOf(fileName) != -1,
`Expected resource "${fileName}" to be read.`);
}
});
console.log(`done, no errors.`);
})
.catch((e: Error) => {
console.error(e.stack);
console.error('Extraction failed');
throw e;
});
}
function lazyRoutesTest() {
const basePath = path.join(__dirname, '../ngtools_src');
const project = path.join(basePath, 'tsconfig-build.json');

View File

@ -1,126 +0,0 @@
#!/usr/bin/env node
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
// Must be imported first, because Angular decorators throw on load.
import 'reflect-metadata';
import * as path from 'path';
import * as ts from 'typescript';
import * as assert from 'assert';
import {CompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext, readConfiguration} from '@angular/compiler-cli';
/* tslint:disable:no-console */
/**
* Main method.
* Standalone program that executes the real codegen and tests that
* ngsummary.json files are used for libraries.
*/
function main() {
console.log(`testing usage of ngsummary.json files in libraries...`);
const basePath = path.resolve(__dirname, '..');
const project = path.resolve(basePath, 'tsconfig-build.json');
const readFiles: string[] = [];
const writtenFiles: {fileName: string, content: string}[] = [];
class AssertingHostContext extends NodeCompilerHostContext {
readFile(fileName: string): string {
if (/.*\/node_modules\/.*/.test(fileName) && !/.*ngsummary\.json$/.test(fileName) &&
!/package\.json$/.test(fileName)) {
// Only allow to read summaries and package.json files from node_modules
// TODO (mhevery): Fix this. TypeScript.d.ts does not allow returning null.
return null !;
}
readFiles.push(path.relative(basePath, fileName));
return super.readFile(fileName);
}
readResource(s: string): Promise<string> {
readFiles.push(path.relative(basePath, s));
return super.readResource(s);
}
}
const config = readConfiguration(project);
config.options.basePath = basePath;
// This flag tells ngc do not recompile libraries.
config.options.generateCodeForLibraries = false;
console.log(`>>> running codegen for ${project}`);
codegen(
config,
(host) => {
host.writeFile = (fileName: string, content: string) => {
fileName = path.relative(basePath, fileName);
writtenFiles.push({fileName, content});
};
return new AssertingHostContext();
})
.then((exitCode: any) => {
console.log(`>>> codegen done, asserting read files`);
assertSomeFileMatch(readFiles, /^node_modules\/.*\.ngsummary\.json$/);
assertNoFileMatch(readFiles, /^node_modules\/.*\.metadata.json$/);
assertNoFileMatch(readFiles, /^node_modules\/.*\.html$/);
assertNoFileMatch(readFiles, /^node_modules\/.*\.css$/);
assertNoFileMatch(readFiles, /^src\/.*\.ngsummary\.json$/);
assertSomeFileMatch(readFiles, /^src\/.*\.html$/);
assertSomeFileMatch(readFiles, /^src\/.*\.css$/);
console.log(`>>> asserting written files`);
assertWrittenFile(writtenFiles, /^src\/module\.ngfactory\.ts$/, /class MainModuleInjector/);
console.log(`done, no errors.`);
process.exit(exitCode);
})
.catch((e: any) => {
console.error(e.stack);
console.error('Compilation failed');
process.exit(1);
});
}
/**
* Simple adaption of main to just run codegen with a CompilerHostContext
*/
function codegen(
config: {options: CompilerOptions, rootNames: string[]},
hostContextFactory: (host: ts.CompilerHost) => CompilerHostContext) {
const host = ts.createCompilerHost(config.options, true);
// HACK: patch the realpath to solve symlink issue here:
// https://github.com/Microsoft/TypeScript/issues/9552
// todo(misko): remove once facade symlinks are removed
host.realpath = (path) => path;
const program = ts.createProgram(config.rootNames, config.options, host);
return CodeGenerator.create(config.options, {
} as any, program, host, hostContextFactory(host)).codegen();
}
function assertSomeFileMatch(fileNames: string[], pattern: RegExp) {
assert(
fileNames.some(fileName => pattern.test(fileName)),
`Expected some read files match ${pattern}`);
}
function assertNoFileMatch(fileNames: string[], pattern: RegExp) {
const matches = fileNames.filter(fileName => pattern.test(fileName));
assert(
matches.length === 0,
`Expected no read files match ${pattern}, but found: \n${matches.join('\n')}`);
}
function assertWrittenFile(
files: {fileName: string, content: string}[], filePattern: RegExp, contentPattern: RegExp) {
assert(
files.some(file => filePattern.test(file.fileName) && contentPattern.test(file.content)),
`Expected some written files for ${filePattern} and content ${contentPattern}`);
}
main();

View File

@ -31,7 +31,6 @@
"src/bootstrap",
"test/all_spec",
"test/test_ngtools_api",
"test/test_summaries",
"benchmarks/src/tree/ng2/index_aot.ts",
"benchmarks/src/tree/ng2_switch/index_aot.ts",
"benchmarks/src/largetable/ng2/index_aot.ts",

View File

@ -26,7 +26,6 @@
"src/bootstrap",
"test/all_spec",
"test/test_ngtools_api",
"test/test_summaries",
"benchmarks/src/tree/ng2/index_aot.ts",
"benchmarks/src/tree/ng2_switch/index_aot.ts",
"benchmarks/src/largetable/ng2/index_aot.ts",

View File

@ -1,117 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* Transform template html and css into executable code.
* Intended to be used in a build step.
*/
import * as compiler from '@angular/compiler';
import {readFileSync} from 'fs';
import * as ts from 'typescript';
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
import {CompilerOptions} from './transformers/api';
const GENERATED_META_FILES = /\.json$/;
const PREAMBLE = `/**
* @fileoverview This file is generated by the Angular template compiler.
* Do not edit.
* @suppress {suspiciousCode,uselessCode,missingProperties,missingOverride}
*/
/* tslint:disable */
`;
export interface CodeGeneratorI18nOptions {
i18nFormat: string|null;
i18nFile: string|null;
locale: string|null;
missingTranslation: string|null;
}
// TODO(tbosch): remove this once G3 uses the transformer compiler!
export class CodeGenerator {
constructor(
private options: CompilerOptions, private program: ts.Program, public host: ts.CompilerHost,
private compiler: compiler.AotCompiler, private ngCompilerHost: CompilerHost) {}
codegen(): Promise<string[]> {
return this.compiler
.analyzeModulesAsync(this.program.getSourceFiles().map(
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)))
.then(analyzedModules => this.emit(analyzedModules));
}
codegenSync(): string[] {
const analyzed = this.compiler.analyzeModulesSync(this.program.getSourceFiles().map(
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)));
return this.emit(analyzed);
}
private emit(analyzedModules: compiler.NgAnalyzedModules) {
const generatedModules = this.compiler.emitAllImpls(analyzedModules);
return generatedModules.map(generatedModule => {
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl);
const source = generatedModule.source || compiler.toTypeScript(generatedModule, PREAMBLE);
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
return emitPath;
});
}
static create(
options: CompilerOptions, i18nOptions: CodeGeneratorI18nOptions, program: ts.Program,
tsCompilerHost: ts.CompilerHost, compilerHostContext?: CompilerHostContext,
ngCompilerHost?: CompilerHost): CodeGenerator {
if (!ngCompilerHost) {
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost);
ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) :
new CompilerHost(program, options, context);
}
let transContent: string = '';
if (i18nOptions.i18nFile) {
if (!i18nOptions.locale) {
throw new Error(
`The translation file (${i18nOptions.i18nFile}) locale must be provided. Use the --locale option.`);
}
transContent = readFileSync(i18nOptions.i18nFile, 'utf8');
}
let missingTranslation = compiler.core.MissingTranslationStrategy.Warning;
if (i18nOptions.missingTranslation) {
switch (i18nOptions.missingTranslation) {
case 'error':
missingTranslation = compiler.core.MissingTranslationStrategy.Error;
break;
case 'warning':
missingTranslation = compiler.core.MissingTranslationStrategy.Warning;
break;
case 'ignore':
missingTranslation = compiler.core.MissingTranslationStrategy.Ignore;
break;
default:
throw new Error(
`Unknown option for missingTranslation (${i18nOptions.missingTranslation}). Use either error, warning or ignore.`);
}
}
if (!transContent) {
missingTranslation = compiler.core.MissingTranslationStrategy.Ignore;
}
const {compiler: aotCompiler} = compiler.createAotCompiler(ngCompilerHost, {
translations: transContent,
i18nFormat: i18nOptions.i18nFormat || undefined,
locale: i18nOptions.locale || undefined, missingTranslation,
enableLegacyTemplate: options.enableLegacyTemplate === true,
enableSummariesForJit: options.enableSummariesForJit !== false,
preserveWhitespaces: options.preserveWhitespaces,
});
return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost);
}
}

View File

@ -1,494 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompilerHost, StaticSymbol, UrlResolver, createOfflineCompileUrlResolver, syntaxError} from '@angular/compiler';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {CollectorOptions, METADATA_VERSION, MetadataCollector, ModuleMetadata} from './metadata/index';
import {CompilerOptions} from './transformers/api';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/;
const NODE_MODULES = '/node_modules/';
const IS_GENERATED = /\.(ngfactory|ngstyle|ngsummary)$/;
const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$|\.ngsummary\.ts$/;
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$|\.ngsummary\.ts$/;
const SHALLOW_IMPORT = /^((\w|-)+|(@(\w|-)+(\/(\w|-)+)+))$/;
export interface BaseAotCompilerHostContext extends ts.ModuleResolutionHost {
readResource?(fileName: string): Promise<string>|string;
}
export abstract class BaseAotCompilerHost<C extends BaseAotCompilerHostContext> implements
AotCompilerHost {
private resolverCache = new Map<string, ModuleMetadata[]>();
private flatModuleIndexCache = new Map<string, boolean>();
private flatModuleIndexNames = new Set<string>();
private flatModuleIndexRedirectNames = new Set<string>();
constructor(protected options: CompilerOptions, protected context: C) {}
abstract moduleNameToFileName(m: string, containingFile: string): string|null;
abstract resourceNameToFileName(m: string, containingFile: string): string|null;
abstract fileNameToModuleName(importedFile: string, containingFile: string): string;
abstract toSummaryFileName(fileName: string, referringSrcFileName: string): string;
abstract fromSummaryFileName(fileName: string, referringLibFileName: string): string;
abstract getMetadataForSourceFile(filePath: string): ModuleMetadata|undefined;
getMetadataFor(filePath: string): ModuleMetadata[]|undefined {
if (!this.context.fileExists(filePath)) {
// If the file doesn't exists then we cannot return metadata for the file.
// This will occur if the user referenced a declared module for which no file
// exists for the module (i.e. jQuery or angularjs).
return;
}
if (DTS.test(filePath)) {
let metadatas = this.readMetadata(filePath);
if (!metadatas) {
// If there is a .d.ts file but no metadata file we need to produce a
// metadata from the .d.ts file as metadata files capture reexports
// (starting with v3).
metadatas = [this.upgradeMetadataWithDtsData(
{'__symbolic': 'module', 'version': 1, 'metadata': {}}, filePath)];
}
return metadatas;
}
// Attention: don't cache this, so that e.g. the LanguageService
// can read in changes from source files in the metadata!
const metadata = this.getMetadataForSourceFile(filePath);
return metadata ? [metadata] : [];
}
protected readMetadata(dtsFilePath: string): ModuleMetadata[]|undefined {
let metadatas = this.resolverCache.get(dtsFilePath);
if (metadatas) {
return metadatas;
}
const metadataPath = dtsFilePath.replace(DTS, '.metadata.json');
if (!this.context.fileExists(metadataPath)) {
return undefined;
}
try {
const metadataOrMetadatas = JSON.parse(this.context.readFile(metadataPath));
const metadatas: ModuleMetadata[] = metadataOrMetadatas ?
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
[];
if (metadatas.length) {
let maxMetadata = metadatas.reduce((p, c) => p.version > c.version ? p : c);
if (maxMetadata.version < METADATA_VERSION) {
metadatas.push(this.upgradeMetadataWithDtsData(maxMetadata, dtsFilePath));
}
}
this.resolverCache.set(dtsFilePath, metadatas);
return metadatas;
} catch (e) {
console.error(`Failed to read JSON file ${metadataPath}`);
throw e;
}
}
private upgradeMetadataWithDtsData(oldMetadata: ModuleMetadata, dtsFilePath: string):
ModuleMetadata {
// patch v1 to v3 by adding exports and the `extends` clause.
// patch v3 to v4 by adding `interface` symbols for TypeAlias
let newMetadata: ModuleMetadata = {
'__symbolic': 'module',
'version': METADATA_VERSION,
'metadata': {...oldMetadata.metadata},
};
if (oldMetadata.exports) {
newMetadata.exports = oldMetadata.exports;
}
if (oldMetadata.importAs) {
newMetadata.importAs = oldMetadata.importAs;
}
if (oldMetadata.origins) {
newMetadata.origins = oldMetadata.origins;
}
const dtsMetadata = this.getMetadataForSourceFile(dtsFilePath);
if (dtsMetadata) {
for (let prop in dtsMetadata.metadata) {
if (!newMetadata.metadata[prop]) {
newMetadata.metadata[prop] = dtsMetadata.metadata[prop];
}
}
// Only copy exports from exports from metadata prior to version 3.
// Starting with version 3 the collector began collecting exports and
// this should be redundant. Also, with bundler will rewrite the exports
// which will hoist the exports from modules referenced indirectly causing
// the imports to be different than the .d.ts files and using the .d.ts file
// exports would cause the StaticSymbolResolver to redirect symbols to the
// incorrect location.
if ((!oldMetadata.version || oldMetadata.version < 3) && dtsMetadata.exports) {
newMetadata.exports = dtsMetadata.exports;
}
}
return newMetadata;
}
loadResource(filePath: string): Promise<string>|string {
if (this.context.readResource) return this.context.readResource(filePath);
if (!this.context.fileExists(filePath)) {
throw syntaxError(`Error: Resource file not found: ${filePath}`);
}
return this.context.readFile(filePath);
}
loadSummary(filePath: string): string|null {
if (this.context.fileExists(filePath)) {
return this.context.readFile(filePath);
}
return null;
}
isSourceFile(filePath: string): boolean {
const excludeRegex =
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
if (excludeRegex.test(filePath)) {
return false;
}
if (DTS.test(filePath)) {
// Check for a bundle index.
if (this.hasBundleIndex(filePath)) {
const normalFilePath = path.normalize(filePath);
return this.flatModuleIndexNames.has(normalFilePath) ||
this.flatModuleIndexRedirectNames.has(normalFilePath);
}
}
return true;
}
private hasBundleIndex(filePath: string): boolean {
const checkBundleIndex = (directory: string): boolean => {
let result = this.flatModuleIndexCache.get(directory);
if (result == null) {
if (path.basename(directory) == 'node_module') {
// Don't look outside the node_modules this package is installed in.
result = false;
} else {
// A bundle index exists if the typings .d.ts file has a metadata.json that has an
// importAs.
try {
const packageFile = path.join(directory, 'package.json');
if (this.context.fileExists(packageFile)) {
// Once we see a package.json file, assume false until it we find the bundle index.
result = false;
const packageContent: any = JSON.parse(this.context.readFile(packageFile));
if (packageContent.typings) {
const typings = path.normalize(path.join(directory, packageContent.typings));
if (DTS.test(typings)) {
const metadataFile = typings.replace(DTS, '.metadata.json');
if (this.context.fileExists(metadataFile)) {
const metadata = JSON.parse(this.context.readFile(metadataFile));
if (metadata.flatModuleIndexRedirect) {
this.flatModuleIndexRedirectNames.add(typings);
// Note: don't set result = true,
// as this would mark this folder
// as having a bundleIndex too early without
// filling the bundleIndexNames.
} else if (metadata.importAs) {
this.flatModuleIndexNames.add(typings);
result = true;
}
}
}
}
} else {
const parent = path.dirname(directory);
if (parent != directory) {
// Try the parent directory.
result = checkBundleIndex(parent);
} else {
result = false;
}
}
} catch (e) {
// If we encounter any errors assume we this isn't a bundle index.
result = false;
}
}
this.flatModuleIndexCache.set(directory, result);
}
return result;
};
return checkBundleIndex(path.dirname(filePath));
}
}
export interface CompilerHostContext extends ts.ModuleResolutionHost {
readResource?(fileName: string): Promise<string>|string;
assumeFileExists(fileName: string): void;
}
// TODO(tbosch): remove this once G3 uses the transformer compiler!
export class CompilerHost extends BaseAotCompilerHost<CompilerHostContext> {
protected metadataProvider: MetadataCollector;
protected basePath: string;
private moduleFileNames = new Map<string, string|null>();
private isGenDirChildOfRootDir: boolean;
private genDir: string;
protected resolveModuleNameHost: CompilerHostContext;
private urlResolver: UrlResolver;
constructor(
protected program: ts.Program, options: CompilerOptions, context: CompilerHostContext,
collectorOptions?: CollectorOptions) {
super(options, context);
this.metadataProvider = new MetadataCollector(collectorOptions);
// normalize the path so that it never ends with '/'.
this.basePath = path.normalize(path.join(this.options.basePath !, '.')).replace(/\\/g, '/');
this.genDir = path.normalize(path.join(this.options.genDir !, '.')).replace(/\\/g, '/');
const genPath: string = path.relative(this.basePath, this.genDir);
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
this.resolveModuleNameHost = Object.create(this.context);
// When calling ts.resolveModuleName,
// additional allow checks for .d.ts files to be done based on
// checks for .ngsummary.json files,
// so that our codegen depends on fewer inputs and requires to be called
// less often.
// This is needed as we use ts.resolveModuleName in reflector_host
// and it should be able to resolve summary file names.
this.resolveModuleNameHost.fileExists = (fileName: string): boolean => {
if (this.context.fileExists(fileName)) {
return true;
}
if (DTS.test(fileName)) {
const base = fileName.substring(0, fileName.length - 5);
return this.context.fileExists(base + '.ngsummary.json');
}
return false;
};
this.urlResolver = createOfflineCompileUrlResolver();
}
protected getSourceFile(filePath: string): ts.SourceFile {
let sf = this.program.getSourceFile(filePath);
if (!sf) {
if (this.context.fileExists(filePath)) {
const sourceText = this.context.readFile(filePath);
sf = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true);
} else {
throw new Error(`Source file ${filePath} not present in program.`);
}
}
return sf;
}
getMetadataForSourceFile(filePath: string): ModuleMetadata|undefined {
return this.metadataProvider.getMetadata(this.getSourceFile(filePath));
}
toSummaryFileName(fileName: string, referringSrcFileName: string): string {
return fileName.replace(EXT, '') + '.d.ts';
}
fromSummaryFileName(fileName: string, referringLibFileName: string): string { return fileName; }
calculateEmitPath(filePath: string): string {
// Write codegen in a directory structure matching the sources.
let root = this.options.basePath !;
for (const eachRootDir of this.options.rootDirs || []) {
if (this.options.trace) {
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
}
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
root = eachRootDir;
}
}
// transplant the codegen path to be inside the `genDir`
let relativePath: string = path.relative(root, filePath);
while (relativePath.startsWith('..' + path.sep)) {
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
// into `genDir`.
relativePath = relativePath.substr(3);
}
return path.join(this.options.genDir !, relativePath);
}
// We use absolute paths on disk as canonical.
getCanonicalFileName(fileName: string): string { return fileName; }
moduleNameToFileName(m: string, containingFile: string): string|null {
const key = m + ':' + (containingFile || '');
let result: string|null = this.moduleFileNames.get(key) || null;
if (!result) {
if (!containingFile || !containingFile.length) {
if (m.indexOf('.') === 0) {
throw new Error('Resolution of relative paths requires a containing file.');
}
// Any containing file gives the same result for absolute imports
containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts'));
}
m = m.replace(EXT, '');
const resolved =
ts.resolveModuleName(
m, containingFile.replace(/\\/g, '/'), this.options, this.resolveModuleNameHost)
.resolvedModule;
result = resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null;
this.moduleFileNames.set(key, result);
}
return result;
}
/**
* We want a moduleId that will appear in import statements in the generated code.
* These need to be in a form that system.js can load, so absolute file paths don't work.
*
* The `containingFile` is always in the `genDir`, where as the `importedFile` can be in
* `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or
* existing file.
*
* | genDir | node_module | rootDir
* --------------+----------+-------------+----------
* generated | relative | relative | n/a
* existing file | n/a | absolute | relative(*)
*
* NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`.
*/
fileNameToModuleName(importedFile: string, containingFile: string): string {
// If a file does not yet exist (because we compile it later), we still need to
// assume it exists it so that the `resolve` method works!
if (importedFile !== containingFile && !this.context.fileExists(importedFile)) {
this.context.assumeFileExists(importedFile);
}
containingFile = this.rewriteGenDirPath(containingFile);
const containingDir = path.dirname(containingFile);
// drop extension
importedFile = importedFile.replace(EXT, '');
const nodeModulesIndex = importedFile.indexOf(NODE_MODULES);
const importModule = nodeModulesIndex === -1 ?
null :
importedFile.substring(nodeModulesIndex + NODE_MODULES.length);
const isGeneratedFile = IS_GENERATED.test(importedFile);
if (isGeneratedFile) {
// rewrite to genDir path
if (importModule) {
// it is generated, therefore we do a relative path to the factory
return this.dotRelative(containingDir, this.genDir + NODE_MODULES + importModule);
} else {
// assume that import is also in `genDir`
importedFile = this.rewriteGenDirPath(importedFile);
return this.dotRelative(containingDir, importedFile);
}
} else {
// user code import
if (importModule) {
return importModule;
} else {
if (!this.isGenDirChildOfRootDir) {
// assume that they are on top of each other.
importedFile = importedFile.replace(this.basePath, this.genDir);
}
if (SHALLOW_IMPORT.test(importedFile)) {
return importedFile;
}
return this.dotRelative(containingDir, importedFile);
}
}
}
resourceNameToFileName(m: string, containingFile: string): string {
return this.urlResolver.resolve(containingFile, m);
}
/**
* Moves the path into `genDir` folder while preserving the `node_modules` directory.
*/
private rewriteGenDirPath(filepath: string) {
const nodeModulesIndex = filepath.indexOf(NODE_MODULES);
if (nodeModulesIndex !== -1) {
// If we are in node_modules, transplant them into `genDir`.
return path.join(this.genDir, filepath.substring(nodeModulesIndex));
} else {
// pretend that containing file is on top of the `genDir` to normalize the paths.
// we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later.
return filepath.replace(this.basePath, this.genDir);
}
}
private dotRelative(from: string, to: string): string {
const rPath: string = path.relative(from, to).replace(/\\/g, '/');
return rPath.startsWith('.') ? rPath : './' + rPath;
}
}
export class CompilerHostContextAdapter {
protected assumedExists: {[fileName: string]: boolean} = {};
assumeFileExists(fileName: string): void { this.assumedExists[fileName] = true; }
}
export class ModuleResolutionHostAdapter extends CompilerHostContextAdapter implements
CompilerHostContext {
public directoryExists: ((directoryName: string) => boolean)|undefined;
constructor(private host: ts.ModuleResolutionHost) {
super();
if (host.directoryExists) {
this.directoryExists = (directoryName: string) => host.directoryExists !(directoryName);
}
}
fileExists(fileName: string): boolean {
return this.assumedExists[fileName] || this.host.fileExists(fileName);
}
readFile(fileName: string): string { return this.host.readFile(fileName); }
readResource(s: string) {
if (!this.host.fileExists(s)) {
// TODO: We should really have a test for error cases like this!
throw new Error(`Compilation failed. Resource file not found: ${s}`);
}
return Promise.resolve(this.host.readFile(s));
}
}
export class NodeCompilerHostContext extends CompilerHostContextAdapter implements
CompilerHostContext {
fileExists(fileName: string): boolean {
return this.assumedExists[fileName] || fs.existsSync(fileName);
}
directoryExists(directoryName: string): boolean {
try {
return fs.statSync(directoryName).isDirectory();
} catch (e) {
return false;
}
}
readFile(fileName: string): string { return fs.readFileSync(fileName, 'utf8'); }
readResource(s: string) {
if (!this.fileExists(s)) {
// TODO: We should really have a test for error cases like this!
throw new Error(`Compilation failed. Resource file not found: ${s}`);
}
return Promise.resolve(this.readFile(s));
}
}

View File

@ -38,10 +38,10 @@ export function translateDiagnostics(host: TypeCheckHost, untranslatedDiagnostic
source: SOURCE,
code: DEFAULT_ERROR_CODE
});
return;
}
} else {
ts.push(diagnostic);
}
ts.push(diagnostic);
});
return {ts, ng};
}

View File

@ -1,64 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* Extract i18n messages from source code
*/
// Must be imported first, because Angular decorators throw on load.
import 'reflect-metadata';
import * as compiler from '@angular/compiler';
import * as path from 'path';
import * as ts from 'typescript';
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
import {CompilerOptions} from './transformers/api';
import {i18nExtract, i18nGetExtension, i18nSerialize} from './transformers/program';
export class Extractor {
constructor(
private options: CompilerOptions, private ngExtractor: compiler.Extractor,
public host: ts.CompilerHost, private ngCompilerHost: CompilerHost,
private program: ts.Program) {}
extract(formatName: string, outFile: string|null): Promise<string[]> {
return this.extractBundle().then(
bundle => i18nExtract(formatName, outFile, this.host, this.options, bundle));
}
extractBundle(): Promise<compiler.MessageBundle> {
const files = this.program.getSourceFiles().map(
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName));
return this.ngExtractor.extract(files);
}
serialize(bundle: compiler.MessageBundle, formatName: string): string {
return i18nSerialize(bundle, formatName, this.options);
}
getExtension(formatName: string): string { return i18nGetExtension(formatName); }
static create(
options: CompilerOptions, program: ts.Program, tsCompilerHost: ts.CompilerHost,
locale?: string|null, compilerHostContext?: CompilerHostContext,
ngCompilerHost?: CompilerHost): Extractor {
if (!ngCompilerHost) {
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost);
ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) :
new CompilerHost(program, options, context);
}
const {extractor: ngExtractor} = compiler.Extractor.create(ngCompilerHost, locale || null);
return new Extractor(options, ngExtractor, tsCompilerHost, ngCompilerHost, program);
}
}

View File

@ -14,9 +14,10 @@ Angular modules and Typescript as this will indirectly add a dependency
to the language service.
*/
export {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter, NodeCompilerHostContext} from './compiler_host';
export {DiagnosticTemplateInfo, ExpressionDiagnostic, getExpressionDiagnostics, getExpressionScope, getTemplateExpressionDiagnostics} from './diagnostics/expression_diagnostics';
export {AstType, DiagnosticKind, ExpressionDiagnosticsContext, TypeDiagnostic} from './diagnostics/expression_type';
export {BuiltinType, DeclarationKind, Definition, Location, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './diagnostics/symbols';
export {getClassFromStaticSymbol, getClassMembers, getClassMembersFromDeclaration, getPipesTable, getSymbolQuery} from './diagnostics/typescript_symbols';
export {MetadataCollector, ModuleMetadata} from './metadata';
export {CompilerOptions} from './transformers/api';
export {MetadataReaderCache, MetadataReaderHost, createMetadataReaderCache, readMetadata} from './transformers/metadata_reader';

View File

@ -28,15 +28,15 @@ export function main(
let {project, rootNames, options, errors: configErrors, watch, emitFlags} =
config || readNgcCommandLineAndConfiguration(args);
if (configErrors.length) {
return reportErrorsAndExit(configErrors, consoleError);
return reportErrorsAndExit(configErrors, /*options*/ undefined, consoleError);
}
if (watch) {
const result = watchMode(project, options, consoleError);
return reportErrorsAndExit(result.firstCompileResult, consoleError);
return reportErrorsAndExit(result.firstCompileResult, options, consoleError);
}
const {diagnostics: compileDiags} = performCompilation(
{rootNames, options, emitFlags, emitCallback: createEmitCallback(options)});
return reportErrorsAndExit(compileDiags, consoleError);
return reportErrorsAndExit(compileDiags, options, consoleError);
}
function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|undefined {
@ -45,6 +45,12 @@ function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|un
if (!transformDecorators && !transformTypesToClosure) {
return undefined;
}
if (transformDecorators) {
// This is needed as a workaround for https://github.com/angular/tsickle/issues/635
// Otherwise tsickle might emit references to non imported values
// as TypeScript elided the import.
options.emitDecoratorMetadata = true;
}
const tsickleHost: tsickle.TsickleHost = {
shouldSkipTsickleProcessing: (fileName) =>
/\.d\.ts$/.test(fileName) || GENERATED_FILES.test(fileName),
@ -128,10 +134,17 @@ export function readCommandLineAndConfiguration(
}
function reportErrorsAndExit(
allDiagnostics: Diagnostics, consoleError: (s: string) => void = console.error): number {
allDiagnostics: Diagnostics, options?: api.CompilerOptions,
consoleError: (s: string) => void = console.error): number {
const errorsAndWarnings = filterErrorsAndWarnings(allDiagnostics);
if (errorsAndWarnings.length) {
consoleError(formatDiagnostics(errorsAndWarnings));
let currentDir = options ? options.basePath : undefined;
const formatHost: ts.FormatDiagnosticsHost = {
getCurrentDirectory: () => currentDir || ts.sys.getCurrentDirectory(),
getCanonicalFileName: fileName => fileName,
getNewLine: () => ts.sys.newLine
};
consoleError(formatDiagnostics(errorsAndWarnings, formatHost));
}
return exitCodeFromResult(allDiagnostics);
}

View File

@ -13,15 +13,17 @@
* something else.
*/
import {AotCompilerHost, AotSummaryResolver, StaticReflector, StaticSymbolCache, StaticSymbolResolver} from '@angular/compiler';
/**
*********************************************************************
* Changes to this file need to be approved by the Angular CLI team. *
*********************************************************************
*/
import * as ts from 'typescript';
import {CodeGenerator} from './codegen';
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {Extractor} from './extractor';
import {listLazyRoutesOfModule} from './ngtools_impl';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
import {CompilerOptions} from './transformers/api';
import {CompilerHost, CompilerOptions, LazyRoute} from './transformers/api';
import {getOriginalReferences} from './transformers/compiler_host';
import {createProgram} from './transformers/entry_points';
export interface NgTools_InternalApi_NG2_CodeGen_Options {
basePath: string;
@ -66,47 +68,16 @@ export interface NgTools_InternalApi_NG2_ExtractI18n_Options {
outFile?: string;
}
/**
* A ModuleResolutionHostAdapter that overrides the readResource() method with the one
* passed in the interface.
*/
class CustomLoaderModuleResolutionHostAdapter extends ModuleResolutionHostAdapter {
constructor(
private _readResource: (path: string) => Promise<string>, host: ts.ModuleResolutionHost) {
super(host);
}
readResource(path: string) { return this._readResource(path); }
}
/**
* @internal
* @deprecatd Use ngtools_api2 instead!
*/
export class NgTools_InternalApi_NG_2 {
/**
* @internal
*/
static codeGen(options: NgTools_InternalApi_NG2_CodeGen_Options): Promise<any> {
const hostContext: CompilerHostContext =
new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host);
const i18nOptions = {
i18nFormat: options.i18nFormat !,
i18nFile: options.i18nFile !,
locale: options.locale !,
missingTranslation: options.missingTranslation !,
};
const ngOptions = options.angularCompilerOptions;
if (ngOptions.enableSummariesForJit === undefined) {
// default to false
ngOptions.enableSummariesForJit = false;
}
// Create the Code Generator.
const codeGenerator =
CodeGenerator.create(ngOptions, i18nOptions, options.program, options.host, hostContext);
return codeGenerator.codegen();
throw throwNotSupportedError();
}
/**
@ -114,42 +85,49 @@ export class NgTools_InternalApi_NG_2 {
*/
static listLazyRoutes(options: NgTools_InternalApi_NG2_ListLazyRoutes_Options):
NgTools_InternalApi_NG_2_LazyRouteMap {
const angularCompilerOptions = options.angularCompilerOptions;
const program = options.program;
// TODO(tbosch): Also throwNotSupportedError once Angular CLI 1.5.1 ships,
// as we only needed this to support Angular CLI 1.5.0 rc.*
const ngProgram = createProgram({
rootNames: options.program.getRootFileNames(),
options: {...options.angularCompilerOptions, collectAllErrors: true},
host: options.host
});
const lazyRoutes = ngProgram.listLazyRoutes(options.entryModule);
const moduleResolutionHost = new ModuleResolutionHostAdapter(options.host);
const usePathMapping =
!!angularCompilerOptions.rootDirs && angularCompilerOptions.rootDirs.length > 0;
const ngCompilerHost: AotCompilerHost = usePathMapping ?
new PathMappedCompilerHost(program, angularCompilerOptions, moduleResolutionHost) :
new CompilerHost(program, angularCompilerOptions, moduleResolutionHost);
// reset the referencedFiles that the ng.Program added to the SourceFiles
// as the host might be caching the source files!
for (const sourceFile of options.program.getSourceFiles()) {
const originalReferences = getOriginalReferences(sourceFile);
if (originalReferences) {
sourceFile.referencedFiles = originalReferences;
}
}
const symbolCache = new StaticSymbolCache();
const summaryResolver = new AotSummaryResolver(ngCompilerHost, symbolCache);
const symbolResolver = new StaticSymbolResolver(ngCompilerHost, symbolCache, summaryResolver);
const staticReflector = new StaticReflector(summaryResolver, symbolResolver);
const routeMap = listLazyRoutesOfModule(options.entryModule, ngCompilerHost, staticReflector);
const result: NgTools_InternalApi_NG_2_LazyRouteMap = {};
lazyRoutes.forEach(lazyRoute => {
const route = lazyRoute.route;
const referencedFilePath = lazyRoute.referencedModule.filePath;
if (result[route] && result[route] != referencedFilePath) {
throw new Error(
`Duplicated path in loadChildren detected: "${route}" is used in 2 loadChildren, ` +
`but they point to different modules "(${result[route]} and ` +
`"${referencedFilePath}"). Webpack cannot distinguish on context and would fail to ` +
'load the proper one.');
}
result[route] = referencedFilePath;
});
return Object.keys(routeMap).reduce(
(acc: NgTools_InternalApi_NG_2_LazyRouteMap, route: string) => {
acc[route] = routeMap[route].absoluteFilePath;
return acc;
},
{});
return result;
}
/**
* @internal
*/
static extractI18n(options: NgTools_InternalApi_NG2_ExtractI18n_Options): Promise<any> {
const hostContext: CompilerHostContext =
new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host);
// Create the i18n extractor.
const locale = options.locale || null;
const extractor = Extractor.create(
options.angularCompilerOptions, options.program, options.host, locale, hostContext);
return extractor.extract(options.i18nFormat !, options.outFile || null);
throw throwNotSupportedError();
}
}
function throwNotSupportedError() {
throw new Error(`Please update @angular/cli. Angular 5+ requires at least Angular CLI 1.5+`);
}

View File

@ -15,6 +15,12 @@
* Once the ngc api is public and stable, this can be removed.
*/
/**
*********************************************************************
* Changes to this file need to be approved by the Angular CLI team. *
*********************************************************************
*/
import {ParseSourceSpan} from '@angular/compiler';
import * as ts from 'typescript';
@ -94,6 +100,12 @@ export interface TsEmitArguments {
export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; }
export interface LazyRoute {
module: {name: string, filePath: string};
route: string;
referencedModule: {name: string, filePath: string};
}
export interface Program {
getTsProgram(): ts.Program;
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken): ts.Diagnostic[];
@ -106,6 +118,7 @@ export interface Program {
getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken):
Diagnostic[];
loadNgStructureAsync(): Promise<void>;
listLazyRoutes(entryRoute?: string): LazyRoute[];
emit({emitFlags, cancellationToken, customTransformers, emitCallback}: {
emitFlags?: EmitFlags,
cancellationToken?: ts.CancellationToken,

View File

@ -1,215 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* This is a private API for the ngtools toolkit.
*
* This API should be stable for NG 2. It can be removed in NG 4..., but should be replaced by
* something else.
*/
import {AotCompilerHost, StaticReflector, StaticSymbol, core} from '@angular/compiler';
// We cannot depend directly to @angular/router.
type Route = any;
const ROUTER_MODULE_PATH = '@angular/router';
const ROUTER_ROUTES_SYMBOL_NAME = 'ROUTES';
// LazyRoute information between the extractors.
export interface LazyRoute {
routeDef: RouteDef;
absoluteFilePath: string;
}
export type LazyRouteMap = {
[route: string]: LazyRoute
};
// A route definition. Normally the short form 'path/to/module#ModuleClassName' is used by
// the user, and this is a helper class to extract information from it.
export class RouteDef {
private constructor(public readonly path: string, public readonly className: string|null = null) {
}
toString() {
return (this.className === null || this.className == 'default') ?
this.path :
`${this.path}#${this.className}`;
}
static fromString(entry: string): RouteDef {
const split = entry.split('#');
return new RouteDef(split[0], split[1] || null);
}
}
export function listLazyRoutesOfModule(
entryModule: string, host: AotCompilerHost, reflector: StaticReflector): LazyRouteMap {
const entryRouteDef = RouteDef.fromString(entryModule);
const containingFile = _resolveModule(entryRouteDef.path, entryRouteDef.path, host);
const modulePath = `./${containingFile.replace(/^(.*)\//, '')}`;
const className = entryRouteDef.className !;
// List loadChildren of this single module.
const appStaticSymbol = reflector.findDeclaration(modulePath, className, containingFile);
const ROUTES = reflector.findDeclaration(ROUTER_MODULE_PATH, ROUTER_ROUTES_SYMBOL_NAME);
const lazyRoutes: LazyRoute[] =
_extractLazyRoutesFromStaticModule(appStaticSymbol, reflector, host, ROUTES);
const allLazyRoutes = lazyRoutes.reduce(
function includeLazyRouteAndSubRoutes(allRoutes: LazyRouteMap, lazyRoute: LazyRoute):
LazyRouteMap {
const route: string = lazyRoute.routeDef.toString();
_assertRoute(allRoutes, lazyRoute);
allRoutes[route] = lazyRoute;
// StaticReflector does not support discovering annotations like `NgModule` on default
// exports
// Which means: if a default export NgModule was lazy-loaded, we can discover it, but,
// we cannot parse its routes to see if they have loadChildren or not.
if (!lazyRoute.routeDef.className) {
return allRoutes;
}
const lazyModuleSymbol = reflector.findDeclaration(
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
const subRoutes =
_extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
return subRoutes.reduce(includeLazyRouteAndSubRoutes, allRoutes);
},
{});
return allLazyRoutes;
}
/**
* Try to resolve a module, and returns its absolute path.
* @private
*/
function _resolveModule(modulePath: string, containingFile: string, host: AotCompilerHost) {
const result = host.moduleNameToFileName(modulePath, containingFile);
if (!result) {
throw new Error(`Could not resolve "${modulePath}" from "${containingFile}".`);
}
return result;
}
/**
* Throw an exception if a route is in a route map, but does not point to the same module.
*/
function _assertRoute(map: LazyRouteMap, route: LazyRoute) {
const r = route.routeDef.toString();
if (map[r] && map[r].absoluteFilePath != route.absoluteFilePath) {
throw new Error(
`Duplicated path in loadChildren detected: "${r}" is used in 2 loadChildren, ` +
`but they point to different modules "(${map[r].absoluteFilePath} and ` +
`"${route.absoluteFilePath}"). Webpack cannot distinguish on context and would fail to ` +
'load the proper one.');
}
}
export function flatten<T>(list: Array<T|T[]>): T[] {
return list.reduce((flat: any[], item: T | T[]): T[] => {
const flatItem = Array.isArray(item) ? flatten(item) : item;
return (<T[]>flat).concat(flatItem);
}, []);
}
/**
* Extract all the LazyRoutes from a module. This extracts all `loadChildren` keys from this
* module and all statically referred modules.
* @private
*/
function _extractLazyRoutesFromStaticModule(
staticSymbol: StaticSymbol, reflector: StaticReflector, host: AotCompilerHost,
ROUTES: StaticSymbol): LazyRoute[] {
const moduleMetadata = _getNgModuleMetadata(staticSymbol, reflector);
const imports = flatten(moduleMetadata.imports || []);
const allRoutes: any = imports.filter(i => 'providers' in i).reduce((mem: Route[], m: any) => {
return mem.concat(_collectRoutes(m.providers || [], reflector, ROUTES));
}, _collectRoutes(moduleMetadata.providers || [], reflector, ROUTES));
const lazyRoutes: LazyRoute[] =
_collectLoadChildren(allRoutes).reduce((acc: LazyRoute[], route: string) => {
const routeDef = RouteDef.fromString(route);
const absoluteFilePath = _resolveModule(routeDef.path, staticSymbol.filePath, host);
acc.push({routeDef, absoluteFilePath});
return acc;
}, []);
const importedSymbols =
(imports as any[])
.filter(i => i instanceof StaticSymbol || i.ngModule instanceof StaticSymbol)
.map(i => {
if (i instanceof StaticSymbol) return i;
return i.ngModule;
}) as StaticSymbol[];
return importedSymbols
.reduce(
(acc: LazyRoute[], i: StaticSymbol) => {
return acc.concat(_extractLazyRoutesFromStaticModule(i, reflector, host, ROUTES));
},
[])
.concat(lazyRoutes);
}
/**
* Get the NgModule Metadata of a symbol.
*/
function _getNgModuleMetadata(
staticSymbol: StaticSymbol, reflector: StaticReflector): core.NgModule {
const ngModules =
reflector.annotations(staticSymbol).filter((s: any) => core.createNgModule.isTypeOf(s));
if (ngModules.length === 0) {
throw new Error(`${staticSymbol.name} is not an NgModule`);
}
return ngModules[0];
}
/**
* Return the routes from the provider list.
* @private
*/
function _collectRoutes(
providers: any[], reflector: StaticReflector, ROUTES: StaticSymbol): Route[] {
return providers.reduce((routeList: Route[], p: any) => {
if (p.provide === ROUTES) {
return routeList.concat(p.useValue);
} else if (Array.isArray(p)) {
return routeList.concat(_collectRoutes(p, reflector, ROUTES));
} else {
return routeList;
}
}, []);
}
/**
* Return the loadChildren values of a list of Route.
*/
function _collectLoadChildren(routes: Route[]): string[] {
return routes.reduce((m, r) => {
if (r.loadChildren && typeof r.loadChildren === 'string') {
return m.concat(r.loadChildren);
} else if (Array.isArray(r)) {
return m.concat(_collectLoadChildren(r));
} else if (r.children) {
return m.concat(_collectLoadChildren(r.children));
} else {
return m;
}
}, []);
}

View File

@ -1,142 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {StaticSymbol} from '@angular/compiler';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {CompilerHost, CompilerHostContext} from './compiler_host';
import {ModuleMetadata} from './metadata/index';
import {CompilerOptions} from './transformers/api';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/;
/**
* This version of the AotCompilerHost expects that the program will be compiled
* and executed with a "path mapped" directory structure, where generated files
* are in a parallel tree with the sources, and imported using a `./` relative
* import. This requires using TS `rootDirs` option and also teaching the module
* loader what to do.
*/
export class PathMappedCompilerHost extends CompilerHost {
constructor(program: ts.Program, options: CompilerOptions, context: CompilerHostContext) {
super(program, options, context);
}
getCanonicalFileName(fileName: string): string {
if (!fileName) return fileName;
// NB: the rootDirs should have been sorted longest-first
for (const dir of this.options.rootDirs || []) {
if (fileName.indexOf(dir) === 0) {
fileName = fileName.substring(dir.length);
}
}
return fileName;
}
moduleNameToFileName(m: string, containingFile: string): string|null {
if (!containingFile || !containingFile.length) {
if (m.indexOf('.') === 0) {
throw new Error('Resolution of relative paths requires a containing file.');
}
// Any containing file gives the same result for absolute imports
containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts'));
}
for (const root of this.options.rootDirs || ['']) {
const rootedContainingFile = path.join(root, containingFile);
const resolved =
ts.resolveModuleName(m, rootedContainingFile, this.options, this.context).resolvedModule;
if (resolved) {
if (this.options.traceResolution) {
console.error('resolve', m, containingFile, '=>', resolved.resolvedFileName);
}
return this.getCanonicalFileName(resolved.resolvedFileName);
}
}
return null;
}
/**
* We want a moduleId that will appear in import statements in the generated code.
* These need to be in a form that system.js can load, so absolute file paths don't work.
* Relativize the paths by checking candidate prefixes of the absolute path, to see if
* they are resolvable by the moduleResolution strategy from the CompilerHost.
*/
fileNameToModuleName(importedFile: string, containingFile: string): string {
if (this.options.traceResolution) {
console.error(
'getImportPath from containingFile', containingFile, 'to importedFile', importedFile);
}
// If a file does not yet exist (because we compile it later), we still need to
// assume it exists so that the `resolve` method works!
if (!this.context.fileExists(importedFile)) {
if (this.options.rootDirs && this.options.rootDirs.length > 0) {
this.context.assumeFileExists(path.join(this.options.rootDirs[0], importedFile));
} else {
this.context.assumeFileExists(importedFile);
}
}
const resolvable = (candidate: string) => {
const resolved = this.moduleNameToFileName(candidate, importedFile);
return resolved && resolved.replace(EXT, '') === importedFile.replace(EXT, '');
};
const importModuleName = importedFile.replace(EXT, '');
const parts = importModuleName.split(path.sep).filter(p => !!p);
let foundRelativeImport: string = undefined !;
for (let index = parts.length - 1; index >= 0; index--) {
let candidate = parts.slice(index, parts.length).join(path.sep);
if (resolvable(candidate)) {
return candidate;
}
candidate = '.' + path.sep + candidate;
if (resolvable(candidate)) {
foundRelativeImport = candidate;
}
}
if (foundRelativeImport) return foundRelativeImport;
// Try a relative import
const candidate = path.relative(path.dirname(containingFile), importModuleName);
if (resolvable(candidate)) {
return candidate;
}
throw new Error(
`Unable to find any resolvable import for ${importedFile} relative to ${containingFile}`);
}
getMetadataFor(filePath: string): ModuleMetadata[] {
for (const root of this.options.rootDirs || []) {
const rootedPath = path.join(root, filePath);
if (!this.context.fileExists(rootedPath)) {
// If the file doesn't exists then we cannot return metadata for the file.
// This will occur if the user refernced a declared module for which no file
// exists for the module (i.e. jQuery or angularjs).
continue;
}
if (DTS.test(rootedPath)) {
const metadatas = this.readMetadata(rootedPath);
if (metadatas) {
return metadatas;
}
} else {
// Attention: don't cache this, so that e.g. the LanguageService
// can read in changes from source files in the metadata!
const metadata = this.getMetadataForSourceFile(rootedPath);
return metadata ? [metadata] : [];
}
}
return null !;
}
}

View File

@ -129,6 +129,7 @@ export function performWatchCompilation(host: PerformWatchHost):
return {close, ready: cb => readyPromise.then(cb), firstCompileResult};
function cacheEntry(fileName: string): CacheEntry {
fileName = path.normalize(fileName);
let entry = fileCache.get(fileName);
if (!entry) {
entry = {};
@ -191,6 +192,10 @@ export function performWatchCompilation(host: PerformWatchHost):
};
}
ingoreFilesForWatch.clear();
const oldProgram = cachedProgram;
// We clear out the `cachedProgram` here as a
// program can only be used as `oldProgram` 1x
cachedProgram = undefined;
const compileResult = performCompilation({
rootNames: cachedOptions.rootNames,
options: cachedOptions.options,
@ -245,7 +250,7 @@ export function performWatchCompilation(host: PerformWatchHost):
if (event === FileChangeEvent.CreateDeleteDir) {
fileCache.clear();
} else {
fileCache.delete(fileName);
fileCache.delete(path.normalize(fileName));
}
if (!ingoreFilesForWatch.has(path.normalize(fileName))) {

View File

@ -150,6 +150,9 @@ export interface CompilerOptions extends ts.CompilerOptions {
* in JIT mode. This is off by default.
*/
enableSummariesForJit?: boolean;
/** @internal */
collectAllErrors?: boolean;
}
export interface CompilerHost extends ts.CompilerHost {
@ -229,6 +232,12 @@ export interface LibrarySummary {
sourceFile?: ts.SourceFile;
}
export interface LazyRoute {
route: string;
module: {name: string, filePath: string};
referencedModule: {name: string, filePath: string};
}
export interface Program {
/**
* Retrieve the TypeScript program used to produce semantic diagnostics and emit the sources.
@ -293,6 +302,14 @@ export interface Program {
*/
loadNgStructureAsync(): Promise<void>;
/**
* Returns the lazy routes in the program.
* @param entryRoute A reference to an NgModule like `someModule#name`. If given,
* will recursively analyze routes starting from this symbol only.
* Otherwise will list all routes for all NgModules in the program/
*/
listLazyRoutes(entryRoute?: string): LazyRoute[];
/**
* Emit the files requested by emitFlags implied by the program.
*

View File

@ -6,19 +6,18 @@
* found in the LICENSE file at https://angular.io/license
*/
import {EmitterVisitorContext, ExternalReference, GeneratedFile, ParseSourceSpan, TypeScriptEmitter, collectExternalReferences, syntaxError} from '@angular/compiler';
import {AotCompilerHost, EmitterVisitorContext, ExternalReference, GeneratedFile, ParseSourceSpan, TypeScriptEmitter, collectExternalReferences, syntaxError} from '@angular/compiler';
import * as path from 'path';
import * as ts from 'typescript';
import {BaseAotCompilerHost} from '../compiler_host';
import {TypeCheckHost} from '../diagnostics/translate_diagnostics';
import {ModuleMetadata} from '../metadata/index';
import {METADATA_VERSION, ModuleMetadata} from '../metadata/index';
import {CompilerHost, CompilerOptions, LibrarySummary} from './api';
import {GENERATED_FILES} from './util';
import {MetadataReaderHost, createMetadataReaderCache, readMetadata} from './metadata_reader';
import {DTS, GENERATED_FILES, isInRootDir, relativeToRootDirs} from './util';
const NODE_MODULES_PACKAGE_NAME = /node_modules\/((\w|-)+|(@(\w|-)+\/(\w|-)+))/;
const DTS = /\.d\.ts$/;
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
export function createCompilerHost(
@ -48,9 +47,12 @@ export interface CodeGenerator {
* - AotCompilerHost for @angular/compiler
* - TypeCheckHost for mapping ts errors to ng errors (via translateDiagnostics)
*/
export class TsCompilerAotCompilerTypeCheckHostAdapter extends
BaseAotCompilerHost<CompilerHost> implements ts.CompilerHost,
export class TsCompilerAotCompilerTypeCheckHostAdapter implements ts.CompilerHost, AotCompilerHost,
TypeCheckHost {
private metadataReaderCache = createMetadataReaderCache();
private flatModuleIndexCache = new Map<string, boolean>();
private flatModuleIndexNames = new Set<string>();
private flatModuleIndexRedirectNames = new Set<string>();
private rootDirs: string[];
private moduleResolutionCache: ts.ModuleResolutionCache;
private originalSourceFiles = new Map<string, ts.SourceFile|undefined>();
@ -58,6 +60,8 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
private generatedSourceFiles = new Map<string, GenSourceFile>();
private generatedCodeFor = new Map<string, string[]>();
private emitter = new TypeScriptEmitter();
private metadataReaderHost: MetadataReaderHost;
getCancellationToken: () => ts.CancellationToken;
getDefaultLibLocation: () => string;
trace: (s: string) => void;
@ -65,10 +69,9 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
directoryExists?: (directoryName: string) => boolean;
constructor(
private rootFiles: string[], options: CompilerOptions, context: CompilerHost,
private rootFiles: string[], private options: CompilerOptions, private context: CompilerHost,
private metadataProvider: MetadataProvider, private codeGenerator: CodeGenerator,
private librarySummaries = new Map<string, LibrarySummary>()) {
super(options, context);
this.moduleResolutionCache = ts.createModuleResolutionCache(
this.context.getCurrentDirectory !(), this.context.getCanonicalFileName.bind(this.context));
const basePath = this.options.basePath !;
@ -103,6 +106,15 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
if (context.fromSummaryFileName) {
this.fromSummaryFileName = context.fromSummaryFileName.bind(context);
}
this.metadataReaderHost = {
cacheMetadata: () => true,
getSourceFileMetadata: (filePath) => {
const sf = this.getOriginalSourceFile(filePath);
return sf ? this.metadataProvider.getMetadata(sf) : undefined;
},
fileExists: (filePath) => this.originalFileExists(filePath),
readFile: (filePath) => this.context.readFile(filePath),
};
}
private resolveModuleName(moduleName: string, containingFile: string): ts.ResolvedModule
@ -180,7 +192,8 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
const containingFilePackageName = getPackageName(containingFile);
let moduleName: string;
if (importedFilePackagName === containingFilePackageName) {
if (importedFilePackagName === containingFilePackageName ||
GENERATED_FILES.test(originalImportedFile)) {
const rootedContainingFile = relativeToRootDirs(containingFile, this.rootDirs);
const rootedImportedFile = relativeToRootDirs(importedFile, this.rootDirs);
@ -251,14 +264,6 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
return sf;
}
getMetadataForSourceFile(filePath: string): ModuleMetadata|undefined {
const sf = this.getOriginalSourceFile(filePath);
if (!sf) {
return undefined;
}
return this.metadataProvider.getMetadata(sf);
}
updateGeneratedFile(genFile: GeneratedFile): ts.SourceFile {
if (!genFile.stmts) {
throw new Error(
@ -301,7 +306,7 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
shouldGenerateFile(fileName: string): {generate: boolean, baseFileName?: string} {
// TODO(tbosch): allow generating files that are not in the rootDir
// See https://github.com/angular/angular/issues/19337
if (this.options.rootDir && !pathStartsWithPrefix(this.options.rootDir, fileName)) {
if (!isInRootDir(fileName, this.options)) {
return {generate: false};
}
const genMatch = GENERATED_FILES.exec(fileName);
@ -335,7 +340,7 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
// TODO(tbosch): allow generating files that are not in the rootDir
// See https://github.com/angular/angular/issues/19337
return !GENERATED_FILES.test(fileName) && this.isSourceFile(fileName) &&
(!this.options.rootDir || pathStartsWithPrefix(this.options.rootDir, fileName));
isInRootDir(fileName, this.options);
}
getSourceFile(
@ -415,16 +420,39 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
if (summary) {
return summary.text;
}
return super.loadSummary(filePath);
if (this.originalFileExists(filePath)) {
return this.context.readFile(filePath);
}
return null;
}
isSourceFile(filePath: string): boolean {
// Don't generate any files nor typecheck them
// if skipTemplateCodegen is set and fullTemplateTypeCheck is not yet set,
// for backwards compatibility.
if (this.options.skipTemplateCodegen && !this.options.fullTemplateTypeCheck) {
return false;
}
// If we have a summary from a previous compilation,
// treat the file never as a source file.
if (this.librarySummaries.has(filePath)) {
return false;
}
return super.isSourceFile(filePath);
if (GENERATED_FILES.test(filePath)) {
return false;
}
if (this.options.generateCodeForLibraries === false && DTS.test(filePath)) {
return false;
}
if (DTS.test(filePath)) {
// Check for a bundle index.
if (this.hasBundleIndex(filePath)) {
const normalFilePath = path.normalize(filePath);
return this.flatModuleIndexNames.has(normalFilePath) ||
this.flatModuleIndexRedirectNames.has(normalFilePath);
}
}
return true;
}
readFile(fileName: string) {
@ -434,6 +462,76 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
}
return this.context.readFile(fileName);
}
getMetadataFor(filePath: string): ModuleMetadata[]|undefined {
return readMetadata(filePath, this.metadataReaderHost, this.metadataReaderCache);
}
loadResource(filePath: string): Promise<string>|string {
if (this.context.readResource) return this.context.readResource(filePath);
if (!this.originalFileExists(filePath)) {
throw syntaxError(`Error: Resource file not found: ${filePath}`);
}
return this.context.readFile(filePath);
}
private hasBundleIndex(filePath: string): boolean {
const checkBundleIndex = (directory: string): boolean => {
let result = this.flatModuleIndexCache.get(directory);
if (result == null) {
if (path.basename(directory) == 'node_module') {
// Don't look outside the node_modules this package is installed in.
result = false;
} else {
// A bundle index exists if the typings .d.ts file has a metadata.json that has an
// importAs.
try {
const packageFile = path.join(directory, 'package.json');
if (this.originalFileExists(packageFile)) {
// Once we see a package.json file, assume false until it we find the bundle index.
result = false;
const packageContent: any = JSON.parse(this.context.readFile(packageFile));
if (packageContent.typings) {
const typings = path.normalize(path.join(directory, packageContent.typings));
if (DTS.test(typings)) {
const metadataFile = typings.replace(DTS, '.metadata.json');
if (this.originalFileExists(metadataFile)) {
const metadata = JSON.parse(this.context.readFile(metadataFile));
if (metadata.flatModuleIndexRedirect) {
this.flatModuleIndexRedirectNames.add(typings);
// Note: don't set result = true,
// as this would mark this folder
// as having a bundleIndex too early without
// filling the bundleIndexNames.
} else if (metadata.importAs) {
this.flatModuleIndexNames.add(typings);
result = true;
}
}
}
}
} else {
const parent = path.dirname(directory);
if (parent != directory) {
// Try the parent directory.
result = checkBundleIndex(parent);
} else {
result = false;
}
}
} catch (e) {
// If we encounter any errors assume we this isn't a bundle index.
result = false;
}
}
this.flatModuleIndexCache.set(directory, result);
}
return result;
};
return checkBundleIndex(path.dirname(filePath));
}
getDefaultLibFileName = (options: ts.CompilerOptions) =>
this.context.getDefaultLibFileName(options)
getCurrentDirectory = () => this.context.getCurrentDirectory();
@ -482,22 +580,6 @@ function getPackageName(filePath: string): string|null {
return match ? match[1] : null;
}
export function relativeToRootDirs(filePath: string, rootDirs: string[]): string {
if (!filePath) return filePath;
for (const dir of rootDirs || []) {
const rel = pathStartsWithPrefix(dir, filePath);
if (rel) {
return rel;
}
}
return filePath;
}
function pathStartsWithPrefix(prefix: string, fullPath: string): string|null {
const rel = path.relative(prefix, fullPath);
return rel.startsWith('..') ? null : rel;
}
function stripNodeModulesPrefix(filePath: string): string {
return filePath.replace(/.*node_modules\//, '');
}

View File

@ -6,7 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {createLoweredSymbol, isLoweredSymbol} from '@angular/compiler';
import * as ts from 'typescript';
import {CollectorOptions, MetadataCollector, MetadataValue, ModuleMetadata, isMetadataGlobalReferenceExpression} from '../metadata/index';
export interface LoweringRequest {
@ -181,11 +183,13 @@ function createVariableStatementForDeclarations(declarations: Declaration[]): ts
/* modifiers */ undefined, ts.createVariableDeclarationList(varDecls, ts.NodeFlags.Const));
}
export function getExpressionLoweringTransformFactory(requestsMap: RequestsMap):
(context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile {
export function getExpressionLoweringTransformFactory(
requestsMap: RequestsMap, program: ts.Program): (context: ts.TransformationContext) =>
(sourceFile: ts.SourceFile) => ts.SourceFile {
// Return the factory
return (context: ts.TransformationContext) => (sourceFile: ts.SourceFile): ts.SourceFile => {
const requests = requestsMap.getRequests(sourceFile);
// We need to use the original SourceFile for reading metadata, and not the transformed one.
const requests = requestsMap.getRequests(program.getSourceFile(sourceFile.fileName));
if (requests && requests.size) {
return transformSourceFile(sourceFile, requests, context);
}
@ -223,14 +227,12 @@ function shouldLower(node: ts.Node | undefined): boolean {
return true;
}
const REWRITE_PREFIX = '\u0275';
function isPrimitive(value: any): boolean {
return Object(value) !== value;
}
function isRewritten(value: any): boolean {
return isMetadataGlobalReferenceExpression(value) && value.name.startsWith(REWRITE_PREFIX);
return isMetadataGlobalReferenceExpression(value) && isLoweredSymbol(value.name);
}
function isLiteralFieldNamed(node: ts.Node, names: Set<string>): boolean {
@ -274,7 +276,7 @@ export class LowerMetadataCache implements RequestsMap {
private getMetadataAndRequests(sourceFile: ts.SourceFile): MetadataAndLoweringRequests {
let identNumber = 0;
const freshIdent = () => REWRITE_PREFIX + identNumber++;
const freshIdent = () => createLoweredSymbol(identNumber++);
const requests = new Map<number, LoweringRequest>();
const isExportedSymbol = (() => {

View File

@ -0,0 +1,128 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {METADATA_VERSION, ModuleMetadata} from '../metadata';
import {DTS} from './util';
export interface MetadataReaderHost {
getSourceFileMetadata(filePath: string): ModuleMetadata|undefined;
cacheMetadata?(fileName: string): boolean;
fileExists(filePath: string): boolean;
readFile(filePath: string): string;
}
export interface MetadataReaderCache {
/**
* @internal
*/
data: Map<string, ModuleMetadata[]|undefined>;
}
export function createMetadataReaderCache(): MetadataReaderCache {
const data = new Map<string, ModuleMetadata[]|undefined>();
return {data};
}
export function readMetadata(
filePath: string, host: MetadataReaderHost, cache?: MetadataReaderCache): ModuleMetadata[]|
undefined {
let metadatas = cache && cache.data.get(filePath);
if (metadatas) {
return metadatas;
}
if (host.fileExists(filePath)) {
// If the file doesn't exists then we cannot return metadata for the file.
// This will occur if the user referenced a declared module for which no file
// exists for the module (i.e. jQuery or angularjs).
if (DTS.test(filePath)) {
metadatas = readMetadataFile(host, filePath);
if (!metadatas) {
// If there is a .d.ts file but no metadata file we need to produce a
// metadata from the .d.ts file as metadata files capture reexports
// (starting with v3).
metadatas = [upgradeMetadataWithDtsData(
host, {'__symbolic': 'module', 'version': 1, 'metadata': {}}, filePath)];
}
} else {
const metadata = host.getSourceFileMetadata(filePath);
metadatas = metadata ? [metadata] : [];
}
}
if (cache && (!host.cacheMetadata || host.cacheMetadata(filePath))) {
cache.data.set(filePath, metadatas);
}
return metadatas;
}
function readMetadataFile(host: MetadataReaderHost, dtsFilePath: string): ModuleMetadata[]|
undefined {
const metadataPath = dtsFilePath.replace(DTS, '.metadata.json');
if (!host.fileExists(metadataPath)) {
return undefined;
}
try {
const metadataOrMetadatas = JSON.parse(host.readFile(metadataPath));
const metadatas: ModuleMetadata[] = metadataOrMetadatas ?
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
[];
if (metadatas.length) {
let maxMetadata = metadatas.reduce((p, c) => p.version > c.version ? p : c);
if (maxMetadata.version < METADATA_VERSION) {
metadatas.push(upgradeMetadataWithDtsData(host, maxMetadata, dtsFilePath));
}
}
return metadatas;
} catch (e) {
console.error(`Failed to read JSON file ${metadataPath}`);
throw e;
}
}
function upgradeMetadataWithDtsData(
host: MetadataReaderHost, oldMetadata: ModuleMetadata, dtsFilePath: string): ModuleMetadata {
// patch v1 to v3 by adding exports and the `extends` clause.
// patch v3 to v4 by adding `interface` symbols for TypeAlias
let newMetadata: ModuleMetadata = {
'__symbolic': 'module',
'version': METADATA_VERSION,
'metadata': {...oldMetadata.metadata},
};
if (oldMetadata.exports) {
newMetadata.exports = oldMetadata.exports;
}
if (oldMetadata.importAs) {
newMetadata.importAs = oldMetadata.importAs;
}
if (oldMetadata.origins) {
newMetadata.origins = oldMetadata.origins;
}
const dtsMetadata = host.getSourceFileMetadata(dtsFilePath);
if (dtsMetadata) {
for (let prop in dtsMetadata.metadata) {
if (!newMetadata.metadata[prop]) {
newMetadata.metadata[prop] = dtsMetadata.metadata[prop];
}
}
// Only copy exports from exports from metadata prior to version 3.
// Starting with version 3 the collector began collecting exports and
// this should be redundant. Also, with bundler will rewrite the exports
// which will hoist the exports from modules referenced indirectly causing
// the imports to be different than the .d.ts files and using the .d.ts file
// exports would cause the StaticSymbolResolver to redirect symbols to the
// incorrect location.
if ((!oldMetadata.version || oldMetadata.version < 3) && dtsMetadata.exports) {
newMetadata.exports = dtsMetadata.exports;
}
}
return newMetadata;
}

View File

@ -144,12 +144,14 @@ class _NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
const span = node.sourceSpan;
if (span.start.file == span.end.file) {
const file = span.start.file;
let source = this._templateSources.get(file);
if (!source) {
source = ts.createSourceMapSource(file.url, file.content, pos => pos);
this._templateSources.set(file, source);
if (file.url) {
let source = this._templateSources.get(file);
if (!source) {
source = ts.createSourceMapSource(file.url, file.content, pos => pos);
this._templateSources.set(file, source);
}
return {pos: span.start.offset, end: span.end.offset, source};
}
return {pos: span.start.offset, end: span.end.offset, source};
}
}
return null;

View File

@ -14,11 +14,13 @@ import * as ts from 'typescript';
import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics';
import {ModuleMetadata, createBundleIndexHost} from '../metadata/index';
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, LazyRoute, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host';
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
import {GENERATED_FILES, StructureIsReused, createMessageDiagnostic, tsStructureIsReused} from './util';
import {GENERATED_FILES, StructureIsReused, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused} from './util';
/**
* Maximum number of files that are emitable via calling ts.Program.emit
@ -50,8 +52,8 @@ class AngularCompilerProgram implements Program {
private emittedSourceFiles: ts.SourceFile[]|undefined;
// Lazily initialized fields
private _typeCheckHost: TypeCheckHost;
private _compiler: AotCompiler;
private _hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter;
private _tsProgram: ts.Program;
private _analyzedModules: NgAnalyzedModules|undefined;
private _structuralDiagnostics: Diagnostic[]|undefined;
@ -147,11 +149,9 @@ class AngularCompilerProgram implements Program {
getTsSemanticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken):
ts.Diagnostic[] {
if (sourceFile) {
return this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken);
}
const sourceFiles = sourceFile ? [sourceFile] : this.tsProgram.getSourceFiles();
let diags: ts.Diagnostic[] = [];
this.tsProgram.getSourceFiles().forEach(sf => {
sourceFiles.forEach(sf => {
if (!GENERATED_FILES.test(sf.fileName)) {
diags.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
}
@ -167,7 +167,7 @@ class AngularCompilerProgram implements Program {
diags.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
}
});
const {ng} = translateDiagnostics(this.typeCheckHost, diags);
const {ng} = translateDiagnostics(this.hostAdapter, diags);
return ng;
}
@ -175,16 +175,23 @@ class AngularCompilerProgram implements Program {
if (this._analyzedModules) {
throw new Error('Angular structure already loaded');
}
const {tmpProgram, sourceFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs();
return this._compiler.loadFilesAsync(sourceFiles)
.catch(this.catchAnalysisError.bind(this))
.then(analyzedModules => {
if (this._analyzedModules) {
throw new Error('Angular structure loaded both synchronously and asynchronsly');
}
this._updateProgramWithTypeCheckStubs(
tmpProgram, analyzedModules, hostAdapter, rootNames);
});
return Promise.resolve()
.then(() => {
const {tmpProgram, sourceFiles, rootNames} = this._createProgramWithBasicStubs();
return this.compiler.loadFilesAsync(sourceFiles).then(analyzedModules => {
if (this._analyzedModules) {
throw new Error('Angular structure loaded both synchronously and asynchronsly');
}
this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, rootNames);
});
})
.catch(e => this._createProgramOnError(e));
}
listLazyRoutes(route?: string): LazyRoute[] {
// Note: Don't analyzedModules if a route is given
// to be fast enough.
return this.compiler.listLazyRoutes(route, route ? undefined : this.analyzedModules);
}
emit(
@ -293,6 +300,10 @@ class AngularCompilerProgram implements Program {
}
}
this.emittedSourceFiles = emittedSourceFiles;
// translate the diagnostics in the emitResult as well.
const translatedEmitDiags = translateDiagnostics(this.hostAdapter, emitResult.diagnostics);
emitResult.diagnostics = translatedEmitDiags.ts.concat(
this.structuralDiagnostics.concat(translatedEmitDiags.ng).map(ngToTsDiagnostic));
if (!outSrcMapping.length) {
// if no files were emitted by TypeScript, also don't emit .json files
@ -341,11 +352,18 @@ class AngularCompilerProgram implements Program {
// Private members
private get compiler(): AotCompiler {
if (!this._compiler) {
this.initSync();
this._createCompiler();
}
return this._compiler !;
}
private get hostAdapter(): TsCompilerAotCompilerTypeCheckHostAdapter {
if (!this._hostAdapter) {
this._createCompiler();
}
return this._hostAdapter !;
}
private get analyzedModules(): NgAnalyzedModules {
if (!this._analyzedModules) {
this.initSync();
@ -367,19 +385,12 @@ class AngularCompilerProgram implements Program {
return this._tsProgram !;
}
private get typeCheckHost(): TypeCheckHost {
if (!this._typeCheckHost) {
this.initSync();
}
return this._typeCheckHost !;
}
private calculateTransforms(
genFiles: Map<string, GeneratedFile>,
customTransformers?: CustomTransformers): ts.CustomTransformers {
const beforeTs: ts.TransformerFactory<ts.SourceFile>[] = [];
if (!this.options.disableExpressionLowering) {
beforeTs.push(getExpressionLoweringTransformFactory(this.metadataCache));
beforeTs.push(getExpressionLoweringTransformFactory(this.metadataCache, this.tsProgram));
}
beforeTs.push(getAngularEmitterTransformFactory(genFiles));
if (customTransformers && customTransformers.beforeTs) {
@ -393,19 +404,41 @@ class AngularCompilerProgram implements Program {
if (this._analyzedModules) {
return;
}
const {tmpProgram, sourceFiles, hostAdapter, rootNames} = this._createProgramWithBasicStubs();
let analyzedModules: NgAnalyzedModules|null;
try {
analyzedModules = this._compiler.loadFilesSync(sourceFiles);
const {tmpProgram, sourceFiles, rootNames} = this._createProgramWithBasicStubs();
const analyzedModules = this.compiler.loadFilesSync(sourceFiles);
this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, rootNames);
} catch (e) {
analyzedModules = this.catchAnalysisError(e);
this._createProgramOnError(e);
}
this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, hostAdapter, rootNames);
}
private _createCompiler() {
const codegen: CodeGenerator = {
generateFile: (genFileName, baseFileName) =>
this._compiler.emitBasicStub(genFileName, baseFileName),
findGeneratedFileNames: (fileName) => this._compiler.findGeneratedFileNames(fileName),
};
this._hostAdapter = new TsCompilerAotCompilerTypeCheckHostAdapter(
this.rootNames, this.options, this.host, this.metadataCache, codegen,
this.oldProgramLibrarySummaries);
const aotOptions = getAotCompilerOptions(this.options);
this._structuralDiagnostics = [];
const errorCollector =
(this.options.collectAllErrors || this.options.fullTemplateTypeCheck) ? (err: any) => {
this._structuralDiagnostics !.push({
messageText: err.toString(),
category: ts.DiagnosticCategory.Error,
source: SOURCE,
code: DEFAULT_ERROR_CODE
});
} : undefined;
this._compiler = createAotCompiler(this._hostAdapter, aotOptions, errorCollector).compiler;
}
private _createProgramWithBasicStubs(): {
tmpProgram: ts.Program,
hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter,
rootNames: string[],
sourceFiles: string[],
} {
@ -418,58 +451,54 @@ class AngularCompilerProgram implements Program {
const codegen: CodeGenerator = {
generateFile: (genFileName, baseFileName) =>
this._compiler.emitBasicStub(genFileName, baseFileName),
findGeneratedFileNames: (fileName) => this._compiler.findGeneratedFileNames(fileName),
this.compiler.emitBasicStub(genFileName, baseFileName),
findGeneratedFileNames: (fileName) => this.compiler.findGeneratedFileNames(fileName),
};
const hostAdapter = new TsCompilerAotCompilerTypeCheckHostAdapter(
this.rootNames, this.options, this.host, this.metadataCache, codegen,
this.oldProgramLibrarySummaries);
const aotOptions = getAotCompilerOptions(this.options);
this._compiler = createAotCompiler(hostAdapter, aotOptions).compiler;
this._typeCheckHost = hostAdapter;
this._structuralDiagnostics = [];
let rootNames =
this.rootNames.filter(fn => !GENERATED_FILES.test(fn) || !hostAdapter.isSourceFile(fn));
let rootNames = [...this.rootNames];
if (this.options.generateCodeForLibraries !== false) {
// if we should generateCodeForLibraries, never include
// generated files in the program as otherwise we will
// ovewrite them and typescript will report the error
// TS5055: Cannot write file ... because it would overwrite input file.
rootNames = rootNames.filter(fn => !GENERATED_FILES.test(fn));
}
if (this.options.noResolve) {
this.rootNames.forEach(rootName => {
if (hostAdapter.shouldGenerateFilesFor(rootName)) {
rootNames.push(...this._compiler.findGeneratedFileNames(rootName));
if (this.hostAdapter.shouldGenerateFilesFor(rootName)) {
rootNames.push(...this.compiler.findGeneratedFileNames(rootName));
}
});
}
const tmpProgram = ts.createProgram(rootNames, this.options, hostAdapter, oldTsProgram);
const tmpProgram = ts.createProgram(rootNames, this.options, this.hostAdapter, oldTsProgram);
const sourceFiles: string[] = [];
tmpProgram.getSourceFiles().forEach(sf => {
if (hostAdapter.isSourceFile(sf.fileName)) {
if (this.hostAdapter.isSourceFile(sf.fileName)) {
sourceFiles.push(sf.fileName);
}
});
return {tmpProgram, sourceFiles, hostAdapter, rootNames};
return {tmpProgram, sourceFiles, rootNames};
}
private _updateProgramWithTypeCheckStubs(
tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules|null,
hostAdapter: TsCompilerAotCompilerTypeCheckHostAdapter, rootNames: string[]) {
this._analyzedModules = analyzedModules || emptyModules;
if (analyzedModules) {
tmpProgram.getSourceFiles().forEach(sf => {
if (sf.fileName.endsWith('.ngfactory.ts')) {
const {generate, baseFileName} = hostAdapter.shouldGenerateFile(sf.fileName);
if (generate) {
// Note: ! is ok as hostAdapter.shouldGenerateFile will always return a basefileName
// for .ngfactory.ts files.
const genFile = this._compiler.emitTypeCheckStub(sf.fileName, baseFileName !);
if (genFile) {
hostAdapter.updateGeneratedFile(genFile);
}
tmpProgram: ts.Program, analyzedModules: NgAnalyzedModules, rootNames: string[]) {
this._analyzedModules = analyzedModules;
tmpProgram.getSourceFiles().forEach(sf => {
if (sf.fileName.endsWith('.ngfactory.ts')) {
const {generate, baseFileName} = this.hostAdapter.shouldGenerateFile(sf.fileName);
if (generate) {
// Note: ! is ok as hostAdapter.shouldGenerateFile will always return a basefileName
// for .ngfactory.ts files.
const genFile = this.compiler.emitTypeCheckStub(sf.fileName, baseFileName !);
if (genFile) {
this.hostAdapter.updateGeneratedFile(genFile);
}
}
});
}
this._tsProgram = ts.createProgram(rootNames, this.options, hostAdapter, tmpProgram);
}
});
this._tsProgram = ts.createProgram(rootNames, this.options, this.hostAdapter, tmpProgram);
// Note: the new ts program should be completely reusable by TypeScript as:
// - we cache all the files in the hostAdapter
// - new new stubs use the exactly same imports/exports as the old once (we assert that in
@ -479,27 +508,37 @@ class AngularCompilerProgram implements Program {
}
}
private catchAnalysisError(e: any): NgAnalyzedModules|null {
private _createProgramOnError(e: any) {
// Still fill the analyzedModules and the tsProgram
// so that we don't cause other errors for users who e.g. want to emit the ngProgram.
this._analyzedModules = emptyModules;
this.oldTsProgram = undefined;
this._hostAdapter.isSourceFile = () => false;
this._tsProgram = ts.createProgram(this.rootNames, this.options, this.hostAdapter);
if (isSyntaxError(e)) {
const parserErrors = getParseErrors(e);
if (parserErrors && parserErrors.length) {
this._structuralDiagnostics =
parserErrors.map<Diagnostic>(e => ({
messageText: e.contextualMessage(),
category: ts.DiagnosticCategory.Error,
span: e.span,
source: SOURCE,
code: DEFAULT_ERROR_CODE
}));
this._structuralDiagnostics = [
...(this._structuralDiagnostics || []),
...parserErrors.map<Diagnostic>(e => ({
messageText: e.contextualMessage(),
category: ts.DiagnosticCategory.Error,
span: e.span,
source: SOURCE,
code: DEFAULT_ERROR_CODE
}))
];
} else {
this._structuralDiagnostics = [{
messageText: e.message,
category: ts.DiagnosticCategory.Error,
source: SOURCE,
code: DEFAULT_ERROR_CODE
}];
this._structuralDiagnostics = [
...(this._structuralDiagnostics || []), {
messageText: e.message,
category: ts.DiagnosticCategory.Error,
source: SOURCE,
code: DEFAULT_ERROR_CODE
}
];
}
return null;
return;
}
throw e;
}
@ -512,7 +551,10 @@ class AngularCompilerProgram implements Program {
if (!(emitFlags & EmitFlags.Codegen)) {
return {genFiles: [], genDiags: []};
}
let genFiles = this.compiler.emitAllImpls(this.analyzedModules);
// TODO(tbosch): allow generating files that are not in the rootDir
// See https://github.com/angular/angular/issues/19337
let genFiles = this.compiler.emitAllImpls(this.analyzedModules)
.filter(genFile => isInRootDir(genFile.genFileUrl, this.options));
if (this.oldProgramEmittedGeneratedFiles) {
const oldProgramEmittedGeneratedFiles = this.oldProgramEmittedGeneratedFiles;
genFiles = genFiles.filter(genFile => {
@ -672,6 +714,10 @@ function getNgOptionDiagnostics(options: CompilerOptions): Diagnostic[] {
return [];
}
function normalizeSeparators(path: string): string {
return path.replace(/\\/g, '/');
}
/**
* Returns a function that can adjust a path from source path to out path,
* based on an existing mapping from source to out path.
@ -693,17 +739,19 @@ export function createSrcToOutPathMapper(
} = path): (srcFileName: string) => string {
let srcToOutPath: (srcFileName: string) => string;
if (outDir) {
let path: {} = {}; // Ensure we error if we use `path` instead of `host`.
if (sampleSrcFileName == null || sampleOutFileName == null) {
throw new Error(`Can't calculate the rootDir without a sample srcFileName / outFileName. `);
}
const srcFileDir = host.dirname(sampleSrcFileName).replace(/\\/g, '/');
const outFileDir = host.dirname(sampleOutFileName).replace(/\\/g, '/');
const srcFileDir = normalizeSeparators(host.dirname(sampleSrcFileName));
const outFileDir = normalizeSeparators(host.dirname(sampleOutFileName));
if (srcFileDir === outFileDir) {
return (srcFileName) => srcFileName;
}
// calculate the common suffix, stopping
// at `outDir`.
const srcDirParts = srcFileDir.split('/');
const outDirParts = outFileDir.split('/');
// calculate the common suffix
const outDirParts = normalizeSeparators(host.relative(outDir, outFileDir)).split('/');
let i = 0;
while (i < Math.min(srcDirParts.length, outDirParts.length) &&
srcDirParts[srcDirParts.length - 1 - i] === outDirParts[outDirParts.length - 1 - i])
@ -719,7 +767,7 @@ export function createSrcToOutPathMapper(
export function i18nExtract(
formatName: string | null, outFile: string | null, host: ts.CompilerHost,
options: CompilerOptions, bundle: MessageBundle): string[] {
formatName = formatName || 'null';
formatName = formatName || 'xlf';
// Checks the format and returns the extension
const ext = i18nGetExtension(formatName);
const content = i18nSerialize(bundle, formatName, options);
@ -753,7 +801,7 @@ export function i18nSerialize(
}
export function i18nGetExtension(formatName: string): string {
const format = (formatName || 'xlf').toLowerCase();
const format = formatName.toLowerCase();
switch (format) {
case 'xmb':

View File

@ -6,11 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as path from 'path';
import * as ts from 'typescript';
import {DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from './api';
import {CompilerOptions, DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from './api';
export const GENERATED_FILES = /(.*?)\.(ngfactory|shim\.ngstyle|ngstyle|ngsummary)\.(js|d\.ts|ts)$/;
export const DTS = /\.d\.ts$/;
export const enum StructureIsReused {Not = 0, SafeModules = 1, Completely = 2}
@ -29,3 +31,49 @@ export function createMessageDiagnostic(messageText: string): ts.Diagnostic&Diag
source: SOURCE,
};
}
export function isInRootDir(fileName: string, options: CompilerOptions) {
return !options.rootDir || pathStartsWithPrefix(options.rootDir, fileName);
}
export function relativeToRootDirs(filePath: string, rootDirs: string[]): string {
if (!filePath) return filePath;
for (const dir of rootDirs || []) {
const rel = pathStartsWithPrefix(dir, filePath);
if (rel) {
return rel;
}
}
return filePath;
}
function pathStartsWithPrefix(prefix: string, fullPath: string): string|null {
const rel = path.relative(prefix, fullPath);
return rel.startsWith('..') ? null : rel;
}
/**
* Converts a ng.Diagnostic into a ts.Diagnostic.
* This looses some information, and also uses an incomplete object as `file`.
*
* I.e. only use this where the API allows only a ts.Diagnostic.
*/
export function ngToTsDiagnostic(ng: Diagnostic): ts.Diagnostic {
let file: ts.SourceFile|undefined;
let start: number|undefined;
let length: number|undefined;
if (ng.span) {
// Note: We can't use a real ts.SourceFile,
// but we can at least mirror the properties `fileName` and `text`, which
// are mostly used for error reporting.
file = { fileName: ng.span.start.file.url, text: ng.span.start.file.content } as ts.SourceFile;
start = ng.span.start.offset;
length = ng.span.end.offset - start;
}
return {
file,
messageText: ng.messageText,
category: ng.category,
code: ng.code, start, length,
};
}

View File

@ -1,310 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {METADATA_VERSION, ModuleMetadata} from '@angular/compiler-cli';
import * as ts from 'typescript';
import {CompilerHost} from '../src/compiler_host';
import {Directory, Entry, MockAotContext, MockCompilerHost} from './mocks';
describe('CompilerHost', () => {
let context: MockAotContext;
let program: ts.Program;
let hostNestedGenDir: CompilerHost;
let hostSiblingGenDir: CompilerHost;
beforeEach(() => {
context = new MockAotContext('/tmp/src', clone(FILES));
const host = new MockCompilerHost(context);
program = ts.createProgram(
['main.ts'], {
module: ts.ModuleKind.CommonJS,
},
host);
// Force a typecheck
const errors = program.getSemanticDiagnostics();
if (errors && errors.length) {
throw new Error('Expected no errors');
}
hostNestedGenDir = new CompilerHost(
program, {
genDir: '/tmp/project/src/gen/',
basePath: '/tmp/project/src',
skipMetadataEmit: false,
strictMetadataEmit: false,
skipTemplateCodegen: false,
trace: false
},
context);
hostSiblingGenDir = new CompilerHost(
program, {
genDir: '/tmp/project/gen',
basePath: '/tmp/project/src/',
skipMetadataEmit: false,
strictMetadataEmit: false,
skipTemplateCodegen: false,
trace: false
},
context);
});
describe('nestedGenDir', () => {
it('should import node_module from factory', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/node_modules/@angular/core.d.ts',
'/tmp/project/src/gen/my.ngfactory.ts', ))
.toEqual('@angular/core');
});
it('should import factory from factory', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./my.other.ngfactory');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.css.ngstyle.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other.css.ngstyle');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.shim.ngstyle.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./a/my.other.shim.ngstyle');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.sass.ngstyle.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other.sass.ngstyle');
});
it('should import application from factory', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../my.other');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../../my.other');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../a/my.other');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.css.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../a/my.other.css');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../a/my.other.css.shim');
});
});
describe('siblingGenDir', () => {
it('should import node_module from factory', () => {
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/node_modules/@angular/core.d.ts',
'/tmp/project/src/gen/my.ngfactory.ts'))
.toEqual('@angular/core');
});
it('should import factory from factory', () => {
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./my.other.ngfactory');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.css.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other.css');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./a/my.other.css.shim');
});
it('should import application from factory', () => {
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./my.other');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./a/my.other');
});
});
it('should be able to produce an import from main @angular/core', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/node_modules/@angular/core.d.ts', '/tmp/project/src/main.ts'))
.toEqual('@angular/core');
});
it('should be able to produce an import to a shallow import', () => {
expect(hostNestedGenDir.fileNameToModuleName('@angular/core', '/tmp/project/src/main.ts'))
.toEqual('@angular/core');
expect(hostNestedGenDir.fileNameToModuleName(
'@angular/upgrade/static', '/tmp/project/src/main.ts'))
.toEqual('@angular/upgrade/static');
expect(hostNestedGenDir.fileNameToModuleName('myLibrary', '/tmp/project/src/main.ts'))
.toEqual('myLibrary');
expect(hostNestedGenDir.fileNameToModuleName('lib23-43', '/tmp/project/src/main.ts'))
.toEqual('lib23-43');
});
it('should be able to produce an import from main to a sub-directory', () => {
expect(hostNestedGenDir.fileNameToModuleName('lib/utils.ts', 'main.ts')).toEqual('./lib/utils');
});
it('should be able to produce an import from to a peer file', () => {
expect(hostNestedGenDir.fileNameToModuleName('lib/collections.ts', 'lib/utils.ts'))
.toEqual('./collections');
});
it('should be able to produce an import from to a sibling directory', () => {
expect(hostNestedGenDir.fileNameToModuleName('lib/utils.ts', 'lib2/utils2.ts'))
.toEqual('../lib/utils');
});
it('should be able to read a metadata file', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts')).toEqual([
{__symbolic: 'module', version: METADATA_VERSION, metadata: {foo: {__symbolic: 'class'}}}
]);
});
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')).toEqual([
dummyMetadata
]);
});
it('should be able to read empty metadata ', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/empty.d.ts')).toEqual([]);
});
it('should return undefined for missing modules', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts')).toBeUndefined();
});
it(`should add missing v${METADATA_VERSION} metadata from v1 metadata and .d.ts files`, () => {
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1.d.ts')).toEqual([
{__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, {
__symbolic: 'module',
version: METADATA_VERSION,
metadata: {
foo: {__symbolic: 'class'},
aType: {__symbolic: 'interface'},
Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}},
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}},
ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'},
},
exports: [{from: './lib/utils2', export: ['Export']}],
}
]);
});
it(`should upgrade a missing metadata file into v${METADATA_VERSION}`, () => {
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1_empty.d.ts')).toEqual([{
__symbolic: 'module',
version: METADATA_VERSION,
metadata: {},
exports: [{from: './lib/utils'}]
}]);
});
it(`should upgrade v3 metadata into v${METADATA_VERSION}`, () => {
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v3.d.ts')).toEqual([
{__symbolic: 'module', version: 3, metadata: {foo: {__symbolic: 'class'}}}, {
__symbolic: 'module',
version: METADATA_VERSION,
metadata: {
foo: {__symbolic: 'class'},
aType: {__symbolic: 'interface'},
Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}},
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}},
ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'},
}
// Note: exports is missing because it was elided in the original.
}
]);
});
});
const dummyModule = 'export let foo: any[];';
const dummyMetadata: ModuleMetadata = {
__symbolic: 'module',
version: METADATA_VERSION,
metadata:
{foo: {__symbolic: 'error', message: 'Variable not initialized', line: 0, character: 11}}
};
const FILES: Entry = {
'tmp': {
'src': {
'main.ts': `
import * as c from '@angular/core';
import * as r from '@angular/router';
import * as u from './lib/utils';
import * as cs from './lib/collections';
import * as u2 from './lib2/utils2';
`,
'lib': {
'utils.ts': dummyModule,
'collections.ts': dummyModule,
},
'lib2': {'utils2.ts': dummyModule},
'node_modules': {
'@angular': {
'core.d.ts': dummyModule,
'core.metadata.json':
`{"__symbolic":"module", "version": ${METADATA_VERSION}, "metadata": {"foo": {"__symbolic": "class"}}}`,
'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}},
'unused.d.ts': dummyModule,
'empty.d.ts': 'export declare var a: string;',
'empty.metadata.json': '[]',
}
},
'metadata_versions': {
'v1.d.ts': `
import {ReExport} from './lib/utils2';
export {ReExport};
export {Export} from './lib/utils2';
export type aType = number;
export declare class Bar {
ngOnInit() {}
}
export declare class BarChild extends Bar {}
`,
'v1.metadata.json':
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
'v1_empty.d.ts': `
export * from './lib/utils';
`,
'v3.d.ts': `
import {ReExport} from './lib/utils2';
export {ReExport};
export {Export} from './lib/utils2';
export type aType = number;
export declare class Bar {
ngOnInit() {}
}
export declare class BarChild extends Bar {}
`,
'v3.metadata.json':
`{"__symbolic":"module", "version": 3, "metadata": {"foo": {"__symbolic": "class"}}}`,
}
}
}
};
function clone(entry: Entry): Entry {
if (typeof entry === 'string') {
return entry;
} else {
const result: Directory = {};
for (const name in entry) {
result[name] = clone(entry[name]);
}
return result;
}
}

View File

@ -8,6 +8,7 @@
import {StaticSymbol} from '@angular/compiler';
import {CompilerHost} from '@angular/compiler-cli';
import {ReflectorHost} from '@angular/language-service/src/reflector_host';
import * as ts from 'typescript';
import {getExpressionDiagnostics, getTemplateExpressionDiagnostics} from '../../src/diagnostics/expression_diagnostics';
@ -21,7 +22,6 @@ describe('expression diagnostics', () => {
let host: MockLanguageServiceHost;
let service: ts.LanguageService;
let context: DiagnosticContext;
let aotHost: CompilerHost;
let type: StaticSymbol;
beforeAll(() => {
@ -33,8 +33,8 @@ describe('expression diagnostics', () => {
const options: CompilerOptions = Object.create(host.getCompilationSettings());
options.genDir = '/dist';
options.basePath = '/src';
aotHost = new CompilerHost(program, options, host, {verboseInvalidExpression: true});
context = new DiagnosticContext(service, program, checker, aotHost);
const symbolResolverHost = new ReflectorHost(() => program, host, options);
context = new DiagnosticContext(service, program, checker, symbolResolverHost);
type = context.getStaticSymbol('app/app.component.ts', 'AppComponent');
});

View File

@ -6,9 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompilerHost, AotSummaryResolver, CompileMetadataResolver, CompilerConfig, DEFAULT_INTERPOLATION_CONFIG, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, InterpolationConfig, JitSummaryResolver, Lexer, NgAnalyzedModules, NgModuleResolver, ParseTreeResult, Parser, PipeResolver, ResourceLoader, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, SummaryResolver, TemplateParser, analyzeNgModules, createOfflineCompileUrlResolver} from '@angular/compiler';
import {AotCompilerHost, AotSummaryResolver, CompileMetadataResolver, CompilerConfig, DEFAULT_INTERPOLATION_CONFIG, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, InterpolationConfig, JitSummaryResolver, Lexer, NgAnalyzedModules, NgModuleResolver, ParseTreeResult, Parser, PipeResolver, ResourceLoader, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost, SummaryResolver, TemplateParser, analyzeNgModules, createOfflineCompileUrlResolver} from '@angular/compiler';
import {ViewEncapsulation, ɵConsole as Console} from '@angular/core';
import {CompilerHostContext} from 'compiler-cli';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
@ -25,7 +24,7 @@ function calcRootPath() {
const realFiles = new Map<string, string>();
export class MockLanguageServiceHost implements ts.LanguageServiceHost, CompilerHostContext {
export class MockLanguageServiceHost implements ts.LanguageServiceHost {
private options: ts.CompilerOptions;
private context: MockAotContext;
private assumedExist = new Set<string>();
@ -122,7 +121,7 @@ export class DiagnosticContext {
constructor(
public service: ts.LanguageService, public program: ts.Program,
public checker: ts.TypeChecker, public host: AotCompilerHost) {}
public checker: ts.TypeChecker, public host: StaticSymbolResolverHost) {}
private collectError(e: any, path?: string) { this._errors.push({e, path}); }

View File

@ -9,6 +9,7 @@
import {StaticSymbol} from '@angular/compiler';
import {CompilerHost} from '@angular/compiler-cli';
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, arrayToMockDir, arrayToMockMap, isSource, settings, setup, toMockFileArray} from '@angular/compiler/test/aot/test_util';
import {ReflectorHost} from '@angular/language-service/src/reflector_host';
import * as ts from 'typescript';
import {Symbol, SymbolQuery, SymbolTable} from '../../src/diagnostics/symbols';
@ -44,8 +45,8 @@ describe('symbol query', () => {
const options: CompilerOptions = Object.create(host.getCompilationSettings());
options.genDir = '/dist';
options.basePath = '/quickstart';
const aotHost = new CompilerHost(program, options, host, {verboseInvalidExpression: true});
context = new DiagnosticContext(service, program, checker, aotHost);
const symbolResolverHost = new ReflectorHost(() => program, host, options);
context = new DiagnosticContext(service, program, checker, symbolResolverHost);
query = getSymbolQuery(program, checker, sourceFile, emptyPipes);
});

View File

@ -6,14 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CompilerHostContext} from '@angular/compiler-cli/src/compiler_host';
import * as ts from 'typescript';
export type Entry = string | Directory;
export interface Directory { [name: string]: Entry; }
export class MockAotContext implements CompilerHostContext {
export class MockAotContext {
constructor(public currentDirectory: string, private files: Entry) {}
fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; }

View File

@ -502,29 +502,69 @@ describe('ngc transformer command-line', () => {
it('should add metadata as decorators', () => {
writeConfig(`{
"extends": "./tsconfig-base.json",
"compilerOptions": {
"emitDecoratorMetadata": true
},
"angularCompilerOptions": {
"annotationsAs": "decorators"
},
"files": ["mymodule.ts"]
}`);
write('aclass.ts', `export class AClass {}`);
write('mymodule.ts', `
import {NgModule, Component} from '@angular/core';
import {NgModule} from '@angular/core';
import {AClass} from './aclass';
@Component({template: ''})
export class MyComp {
fn(p: any) {}
}
@NgModule({declarations: [MyComp]})
export class MyModule {}
`);
@NgModule({declarations: []})
export class MyModule {
constructor(importedClass: AClass) {}
}
`);
const exitCode = main(['-p', basePath], errorSpy);
expect(exitCode).toEqual(0);
const mymodulejs = path.resolve(outDir, 'mymodule.js');
const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8');
expect(mymoduleSource).toContain('MyComp = __decorate([');
expect(mymoduleSource).toContain('MyModule = __decorate([');
expect(mymoduleSource).toContain(`import { AClass } from './aclass';`);
expect(mymoduleSource).toContain(`__metadata("design:paramtypes", [AClass])`);
});
it('should add metadata as static fields', () => {
// Note: Don't specify emitDecoratorMetadata here on purpose,
// as regression test for https://github.com/angular/angular/issues/19916.
writeConfig(`{
"extends": "./tsconfig-base.json",
"compilerOptions": {
"emitDecoratorMetadata": false
},
"angularCompilerOptions": {
"annotationsAs": "static fields"
},
"files": ["mymodule.ts"]
}`);
write('aclass.ts', `export class AClass {}`);
write('mymodule.ts', `
import {NgModule} from '@angular/core';
import {AClass} from './aclass';
@NgModule({declarations: []})
export class MyModule {
constructor(importedClass: AClass) {}
}
`);
const exitCode = main(['-p', basePath], errorSpy);
expect(exitCode).toEqual(0);
const mymodulejs = path.resolve(outDir, 'mymodule.js');
const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8');
expect(mymoduleSource).not.toContain('__decorate');
expect(mymoduleSource).toContain('args: [{ declarations: [] },] }');
expect(mymoduleSource).not.toContain(`__metadata`);
expect(mymoduleSource).toContain(`import { AClass } from './aclass';`);
expect(mymoduleSource).toContain(`{ type: AClass, }`);
});
});
@ -1391,5 +1431,73 @@ describe('ngc transformer command-line', () => {
main(['-p', path.join(basePath, 'src/tsconfig.json')], message => messages.push(message));
expect(exitCode).toBe(0, 'Compile failed unexpectedly.\n ' + messages.join('\n '));
});
it('should emit all structural errors', () => {
write('src/tsconfig.json', `{
"extends": "../tsconfig-base.json",
"files": ["test-module.ts"]
}`);
write('src/lib/indirect2.ts', `
declare var f: any;
export const t2 = f\`<p>hello</p>\`;
`);
write('src/lib/indirect1.ts', `
import {t2} from './indirect2';
export const t1 = t2 + ' ';
`);
write('src/lib/test.component.ts', `
import {Component} from '@angular/core';
import {t1} from './indirect1';
@Component({
template: t1
})
export class TestComponent {}
`);
write('src/test-module.ts', `
import {NgModule} from '@angular/core';
import {TestComponent} from './lib/test.component';
@NgModule({declarations: [TestComponent]})
export class TestModule {}
`);
const messages: string[] = [];
const exitCode =
main(['-p', path.join(basePath, 'src/tsconfig.json')], message => messages.push(message));
expect(exitCode).toBe(1, 'Compile was expected to fail');
expect(messages[0]).toContain(['Tagged template expressions are not supported in metadata']);
});
it('should allow using 2 classes with the same name in declarations with noEmitOnError=true',
() => {
write('src/tsconfig.json', `{
"extends": "../tsconfig-base.json",
"compilerOptions": {
"noEmitOnError": true
},
"files": ["test-module.ts"]
}`);
function writeComp(fileName: string) {
write(fileName, `
import {Component} from '@angular/core';
@Component({selector: 'comp', template: ''})
export class TestComponent {}
`);
}
writeComp('src/comp1.ts');
writeComp('src/comp2.ts');
write('src/test-module.ts', `
import {NgModule} from '@angular/core';
import {TestComponent as Comp1} from './comp1';
import {TestComponent as Comp2} from './comp2';
@NgModule({
declarations: [Comp1, Comp2],
})
export class MyModule {}
`);
expect(main(['-p', path.join(basePath, 'src/tsconfig.json')])).toBe(0);
});
});
});

View File

@ -0,0 +1,90 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as path from 'path';
import * as ts from 'typescript';
import {NgTools_InternalApi_NG_2} from '../src/ngtools_api';
import {TestSupport, setup} from './test_support';
describe('ngtools_api (deprecated)', () => {
let testSupport: TestSupport;
beforeEach(() => { testSupport = setup(); });
function createProgram(rootNames: string[]) {
const options = testSupport.createCompilerOptions();
const host = ts.createCompilerHost(options, true);
const program =
ts.createProgram(rootNames.map(p => path.resolve(testSupport.basePath, p)), options, host);
return {program, host, options};
}
function writeSomeRoutes() {
testSupport.writeFiles({
'src/main.ts': `
import {NgModule, Component} from '@angular/core';
import {RouterModule} from '@angular/router';
// Component with metadata errors.
@Component(() => {if (1==1) return null as any;})
export class ErrorComp2 {}
@NgModule({
declarations: [ErrorComp2, NonExistentComp],
imports: [RouterModule.forRoot([{loadChildren: './child#ChildModule'}])]
})
export class MainModule {}
`,
'src/child.ts': `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@NgModule({
imports: [RouterModule.forChild([{loadChildren: './child2#ChildModule2'}])]
})
export class ChildModule {}
`,
'src/child2.ts': `
import {NgModule} from '@angular/core';
@NgModule()
export class ChildModule2 {}
`,
});
}
it('should list lazy routes recursively', () => {
writeSomeRoutes();
const {program, host, options} = createProgram(['src/main.ts']);
const routes = NgTools_InternalApi_NG_2.listLazyRoutes({
program,
host,
angularCompilerOptions: options,
entryModule: 'src/main#MainModule',
});
expect(routes).toEqual({
'./child#ChildModule': path.resolve(testSupport.basePath, 'src/child.ts'),
'./child2#ChildModule2': path.resolve(testSupport.basePath, 'src/child2.ts'),
});
});
it('should allow to emit the program after analyzing routes', () => {
writeSomeRoutes();
const {program, host, options} = createProgram(['src/main.ts']);
NgTools_InternalApi_NG_2.listLazyRoutes({
program,
host,
angularCompilerOptions: options,
entryModule: 'src/main#MainModule',
});
program.emit();
testSupport.shouldExist('built/src/main.js');
});
});

View File

@ -105,6 +105,47 @@ describe('perform watch', () => {
expect(getSourceFileSpy !).toHaveBeenCalledWith(mainTsPath, ts.ScriptTarget.ES5);
expect(getSourceFileSpy !).toHaveBeenCalledWith(utilTsPath, ts.ScriptTarget.ES5);
});
it('should recover from static analysis errors', () => {
const config = createConfig();
const host = new MockWatchHost(config);
const okFileContent = `
import {NgModule} from '@angular/core';
@NgModule()
export class MyModule {}
`;
const errorFileContent = `
import {NgModule} from '@angular/core';
@NgModule(() => (1===1 ? null as any : null as any))
export class MyModule {}
`;
const indexTsPath = path.resolve(testSupport.basePath, 'src', 'index.ts');
testSupport.write(indexTsPath, okFileContent);
performWatchCompilation(host);
expectNoDiagnostics(config.options, host.diagnostics);
// Do it multiple times as the watch mode switches internal modes.
// E.g. from regular compile to using summaries, ...
for (let i = 0; i < 3; i++) {
host.diagnostics = [];
testSupport.write(indexTsPath, okFileContent);
host.triggerFileChange(FileChangeEvent.Change, indexTsPath);
expectNoDiagnostics(config.options, host.diagnostics);
host.diagnostics = [];
testSupport.write(indexTsPath, errorFileContent);
host.triggerFileChange(FileChangeEvent.Change, indexTsPath);
const errDiags = host.diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error);
expect(errDiags.length).toBe(1);
expect(errDiags[0].messageText).toContain('Function calls are not supported.');
}
});
});
function createModuleAndCompSource(prefix: string, template: string = prefix + 'template') {
@ -122,7 +163,8 @@ function createModuleAndCompSource(prefix: string, template: string = prefix + '
}
class MockWatchHost {
timeoutListeners: Array<(() => void)|null> = [];
nextTimeoutListenerId = 1;
timeoutListeners: {[id: string]: (() => void)} = {};
fileChangeListeners: Array<((event: FileChangeEvent, fileName: string) => void)|null> = [];
diagnostics: ng.Diagnostics = [];
constructor(public config: ng.ParsedConfiguration) {}
@ -141,16 +183,16 @@ class MockWatchHost {
close: () => this.fileChangeListeners[id] = null,
};
}
setTimeout(callback: () => void, ms: number): any {
const id = this.timeoutListeners.length;
this.timeoutListeners.push(callback);
setTimeout(callback: () => void): any {
const id = this.nextTimeoutListenerId++;
this.timeoutListeners[id] = callback;
return id;
}
clearTimeout(timeoutId: any): void { this.timeoutListeners[timeoutId] = null; }
clearTimeout(timeoutId: any): void { delete this.timeoutListeners[timeoutId]; }
flushTimeouts() {
this.timeoutListeners.forEach(cb => {
if (cb) cb();
});
const listeners = this.timeoutListeners;
this.timeoutListeners = {};
Object.keys(listeners).forEach(id => listeners[id]());
}
triggerFileChange(event: FileChangeEvent, fileName: string) {
this.fileChangeListeners.forEach(listener => {

View File

@ -64,10 +64,10 @@ export function setup(): TestSupport {
function write(fileName: string, content: string) {
const dir = path.dirname(fileName);
if (dir != '.') {
const newDir = path.join(basePath, dir);
const newDir = path.resolve(basePath, dir);
if (!fs.existsSync(newDir)) fs.mkdirSync(newDir);
}
fs.writeFileSync(path.join(basePath, fileName), content, {encoding: 'utf-8'});
fs.writeFileSync(path.resolve(basePath, fileName), content, {encoding: 'utf-8'});
}
function writeFiles(...mockDirs: {[fileName: string]: string}[]) {

View File

@ -90,6 +90,14 @@ describe('NgCompilerHost', () => {
.toBe('./a/child');
});
it('should use a relative import when accessing generated files, even if crossing packages',
() => {
expect(host.fileNameToModuleName(
'/tmp/node_modules/mod2/b.ngfactory.d.ts',
'/tmp/node_modules/mod1/a.ngfactory.d.ts'))
.toBe('../mod2/b.ngfactory');
});
it('should support multiple rootDirs when accessing a source file form a source file', () => {
const hostWithMultipleRoots = createHost({
options: {

View File

@ -181,13 +181,15 @@ function convert(annotatedSource: string) {
[fileName], {module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2017}, host);
const moduleSourceFile = program.getSourceFile(fileName);
const transformers: ts.CustomTransformers = {
before: [getExpressionLoweringTransformFactory({
getRequests(sourceFile: ts.SourceFile): RequestLocationMap{
if (sourceFile.fileName == moduleSourceFile.fileName) {
return requests;
} else {return new Map();}
}
})]
before: [getExpressionLoweringTransformFactory(
{
getRequests(sourceFile: ts.SourceFile): RequestLocationMap{
if (sourceFile.fileName == moduleSourceFile.fileName) {
return requests;
} else {return new Map();}
}
},
program)]
};
let result: string = '';
const emitResult = program.emit(

View File

@ -0,0 +1,177 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {METADATA_VERSION, MetadataCollector, ModuleMetadata} from '../../src/metadata';
import {MetadataReaderHost, readMetadata} from '../../src/transformers/metadata_reader';
import {Directory, Entry, MockAotContext} from '../mocks';
describe('metadata reader', () => {
let host: MetadataReaderHost;
beforeEach(() => {
const context = new MockAotContext('/tmp/src', clone(FILES));
const metadataCollector = new MetadataCollector();
host = {
fileExists: (fileName) => context.fileExists(fileName),
readFile: (fileName) => context.readFile(fileName),
getSourceFileMetadata: (fileName) => {
const sourceText = context.readFile(fileName);
return sourceText != null ?
metadataCollector.getMetadata(
ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.Latest)) :
undefined;
},
};
});
it('should be able to read a metadata file', () => {
expect(readMetadata('node_modules/@angular/core.d.ts', host)).toEqual([
{__symbolic: 'module', version: METADATA_VERSION, metadata: {foo: {__symbolic: 'class'}}}
]);
});
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
expect(readMetadata('node_modules/@angular/unused.d.ts', host)).toEqual([dummyMetadata]);
});
it('should be able to read empty metadata ',
() => { expect(readMetadata('node_modules/@angular/empty.d.ts', host)).toEqual([]); });
it('should return undefined for missing modules',
() => { expect(readMetadata('node_modules/@angular/missing.d.ts', host)).toBeUndefined(); });
it(`should add missing v${METADATA_VERSION} metadata from v1 metadata and .d.ts files`, () => {
expect(readMetadata('metadata_versions/v1.d.ts', host)).toEqual([
{__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, {
__symbolic: 'module',
version: METADATA_VERSION,
metadata: {
foo: {__symbolic: 'class'},
aType: {__symbolic: 'interface'},
Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}},
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}},
ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'},
},
exports: [{from: './lib/utils2', export: ['Export']}],
}
]);
});
it(`should upgrade a missing metadata file into v${METADATA_VERSION}`, () => {
expect(readMetadata('metadata_versions/v1_empty.d.ts', host)).toEqual([{
__symbolic: 'module',
version: METADATA_VERSION,
metadata: {},
exports: [{from: './lib/utils'}]
}]);
});
it(`should upgrade v3 metadata into v${METADATA_VERSION}`, () => {
expect(readMetadata('metadata_versions/v3.d.ts', host)).toEqual([
{__symbolic: 'module', version: 3, metadata: {foo: {__symbolic: 'class'}}}, {
__symbolic: 'module',
version: METADATA_VERSION,
metadata: {
foo: {__symbolic: 'class'},
aType: {__symbolic: 'interface'},
Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}},
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}},
ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'},
}
// Note: exports is missing because it was elided in the original.
}
]);
});
});
const dummyModule = 'export let foo: any[];';
const dummyMetadata: ModuleMetadata = {
__symbolic: 'module',
version: METADATA_VERSION,
metadata:
{foo: {__symbolic: 'error', message: 'Variable not initialized', line: 0, character: 11}}
};
const FILES: Entry = {
'tmp': {
'src': {
'main.ts': `
import * as c from '@angular/core';
import * as r from '@angular/router';
import * as u from './lib/utils';
import * as cs from './lib/collections';
import * as u2 from './lib2/utils2';
`,
'lib': {
'utils.ts': dummyModule,
'collections.ts': dummyModule,
},
'lib2': {'utils2.ts': dummyModule},
'node_modules': {
'@angular': {
'core.d.ts': dummyModule,
'core.metadata.json':
`{"__symbolic":"module", "version": ${METADATA_VERSION}, "metadata": {"foo": {"__symbolic": "class"}}}`,
'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}},
'unused.d.ts': dummyModule,
'empty.d.ts': 'export declare var a: string;',
'empty.metadata.json': '[]',
}
},
'metadata_versions': {
'v1.d.ts': `
import {ReExport} from './lib/utils2';
export {ReExport};
export {Export} from './lib/utils2';
export type aType = number;
export declare class Bar {
ngOnInit() {}
}
export declare class BarChild extends Bar {}
`,
'v1.metadata.json':
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
'v1_empty.d.ts': `
export * from './lib/utils';
`,
'v3.d.ts': `
import {ReExport} from './lib/utils2';
export {ReExport};
export {Export} from './lib/utils2';
export type aType = number;
export declare class Bar {
ngOnInit() {}
}
export declare class BarChild extends Bar {}
`,
'v3.metadata.json':
`{"__symbolic":"module", "version": 3, "metadata": {"foo": {"__symbolic": "class"}}}`,
}
}
}
};
function clone(entry: Entry): Entry {
if (typeof entry === 'string') {
return entry;
} else {
const result: Directory = {};
for (const name in entry) {
result[name] = clone(entry[name]);
}
return result;
}
}

View File

@ -11,7 +11,8 @@ import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {CompilerHost} from '../../src/transformers/api';
import {formatDiagnostics} from '../../src/perform_compile';
import {CompilerHost, EmitFlags, LazyRoute} from '../../src/transformers/api';
import {createSrcToOutPathMapper} from '../../src/transformers/program';
import {GENERATED_FILES, StructureIsReused, tsStructureIsReused} from '../../src/transformers/util';
import {TestSupport, expectNoDiagnosticsInProgram, setup} from '../test_support';
@ -77,6 +78,15 @@ describe('ng program', () => {
return {emitResult, program};
}
function resolveFiles(rootNames: string[]) {
const preOptions = testSupport.createCompilerOptions();
const preHost = ts.createCompilerHost(preOptions);
// don't resolve symlinks
preHost.realpath = (f) => f;
const preProgram = ts.createProgram(rootNames, preOptions, preHost);
return preProgram.getSourceFiles().map(sf => sf.fileName);
}
describe('reuse of old program', () => {
it('should reuse generated code for libraries from old programs', () => {
compileLib('lib');
@ -309,11 +319,35 @@ describe('ng program', () => {
});
});
it('should typecheck templates even if skipTemplateCodegen is set', () => {
it('should not typecheck templates if skipTemplateCodegen is set but fullTemplateTypeCheck is not',
() => {
testSupport.writeFiles({
'src/main.ts': `
import {NgModule} from '@angular/core';
@NgModule(() => {if (1==1) return null as any;})
export class SomeClassWithInvalidMetadata {}
`,
});
const options = testSupport.createCompilerOptions({skipTemplateCodegen: true});
const host = ng.createCompilerHost({options});
const program = ng.createProgram(
{rootNames: [path.resolve(testSupport.basePath, 'src/main.ts')], options, host});
expectNoDiagnosticsInProgram(options, program);
const emitResult = program.emit({emitFlags: EmitFlags.All});
expect(emitResult.diagnostics.length).toBe(0);
testSupport.shouldExist('built/src/main.metadata.json');
});
it('should typecheck templates if skipTemplateCodegen and fullTemplateTypeCheck is set', () => {
testSupport.writeFiles({
'src/main.ts': createModuleAndCompSource('main', `{{nonExistent}}`),
});
const options = testSupport.createCompilerOptions({skipTemplateCodegen: true});
const options = testSupport.createCompilerOptions({
skipTemplateCodegen: true,
fullTemplateTypeCheck: true,
});
const host = ng.createCompilerHost({options});
const program = ng.createProgram(
{rootNames: [path.resolve(testSupport.basePath, 'src/main.ts')], options, host});
@ -348,13 +382,7 @@ describe('ng program', () => {
testSupport.writeFiles({
'src/main.ts': createModuleAndCompSource('main'),
});
const preOptions = testSupport.createCompilerOptions();
const preHost = ts.createCompilerHost(preOptions);
// don't resolve symlinks
preHost.realpath = (f) => f;
const preProgram =
ts.createProgram([path.resolve(testSupport.basePath, 'src/main.ts')], preOptions, preHost);
const allRootNames = preProgram.getSourceFiles().map(sf => sf.fileName);
const allRootNames = resolveFiles([path.resolve(testSupport.basePath, 'src/main.ts')]);
// now do the actual test with noResolve
const program = compile(undefined, {noResolve: true}, allRootNames);
@ -454,25 +482,33 @@ describe('ng program', () => {
});
it('should not emit generated files whose sources are outside of the rootDir', () => {
compileLib('lib');
testSupport.writeFiles({
'src/main.ts': createModuleAndCompSource('main'),
'src/index.ts': `
export * from './main';
export * from 'lib/index';
`
});
compile(undefined, {rootDir: path.resolve(testSupport.basePath, 'src')});
const options =
testSupport.createCompilerOptions({rootDir: path.resolve(testSupport.basePath, 'src')});
const host = ng.createCompilerHost({options});
const writtenFileNames: string[] = [];
const oldWriteFile = host.writeFile;
host.writeFile = (fileName, data, writeByteOrderMark) => {
writtenFileNames.push(fileName);
oldWriteFile(fileName, data, writeByteOrderMark);
};
compile(/*oldProgram*/ undefined, options, /*rootNames*/ undefined, host);
// no emit for files from node_modules as they are outside of rootDir
expect(writtenFileNames.some(f => /node_modules/.test(f))).toBe(false);
// emit all gen files for files under src/
testSupport.shouldExist('built/main.js');
testSupport.shouldExist('built/main.d.ts');
testSupport.shouldExist('built/main.ngfactory.js');
testSupport.shouldExist('built/main.ngfactory.d.ts');
testSupport.shouldExist('built/main.ngsummary.json');
testSupport.shouldNotExist('built/node_modules/lib/index.js');
testSupport.shouldNotExist('built/node_modules/lib/index.d.ts');
testSupport.shouldNotExist('built/node_modules/lib/index.ngfactory.js');
testSupport.shouldNotExist('built/node_modules/lib/index.ngfactory.d.ts');
testSupport.shouldNotExist('built/node_modules/lib/index.ngsummary.json');
});
describe('createSrcToOutPathMapper', () => {
@ -492,10 +528,17 @@ describe('ng program', () => {
});
it('should adjust the filename if the outDir is outside of the rootDir', () => {
const mapper = createSrcToOutPathMapper('/out', '/tmp/a/x.ts', '/a/x.js');
const mapper = createSrcToOutPathMapper('/out', '/tmp/a/x.ts', '/out/a/x.js');
expect(mapper('/tmp/b/y.js')).toBe('/out/b/y.js');
});
it('should adjust the filename if the common prefix of sampleSrc and sampleOut is outside of outDir',
() => {
const mapper =
createSrcToOutPathMapper('/dist/common', '/src/common/x.ts', '/dist/common/x.js');
expect(mapper('/src/common/y.js')).toBe('/dist/common/y.js');
});
it('should work on windows with normalized paths', () => {
const mapper =
createSrcToOutPathMapper('c:/tmp/out', 'c:/tmp/a/x.ts', 'c:/tmp/out/a/x.js', path.win32);
@ -508,4 +551,425 @@ describe('ng program', () => {
expect(mapper('c:\\tmp\\b\\y.js')).toBe('c:\\tmp\\out\\b\\y.js');
});
});
describe('listLazyRoutes', () => {
function writeSomeRoutes() {
testSupport.writeFiles({
'src/main.ts': `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@NgModule({
imports: [RouterModule.forRoot([{loadChildren: './child#ChildModule'}])]
})
export class MainModule {}
`,
'src/child.ts': `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@NgModule({
imports: [RouterModule.forChild([{loadChildren: './child2#ChildModule2'}])]
})
export class ChildModule {}
`,
'src/child2.ts': `
import {NgModule} from '@angular/core';
@NgModule()
export class ChildModule2 {}
`,
});
}
function createProgram(rootNames: string[], overrideOptions: ng.CompilerOptions = {}) {
const options = testSupport.createCompilerOptions(overrideOptions);
const host = ng.createCompilerHost({options});
const program = ng.createProgram(
{rootNames: rootNames.map(p => path.resolve(testSupport.basePath, p)), options, host});
return {program, options};
}
function normalizeRoutes(lazyRoutes: LazyRoute[]) {
return lazyRoutes.map(
r => ({
route: r.route,
module: {name: r.module.name, filePath: r.module.filePath},
referencedModule:
{name: r.referencedModule.name, filePath: r.referencedModule.filePath},
}));
}
it('should list all lazyRoutes', () => {
writeSomeRoutes();
const {program, options} = createProgram(['src/main.ts', 'src/child.ts', 'src/child2.ts']);
expectNoDiagnosticsInProgram(options, program);
expect(normalizeRoutes(program.listLazyRoutes())).toEqual([
{
module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
referencedModule:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
route: './child#ChildModule'
},
{
module:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
referencedModule:
{name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')},
route: './child2#ChildModule2'
},
]);
});
it('should emit correctly after listing lazyRoutes', () => {
testSupport.writeFiles({
'src/main.ts': `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@NgModule({
imports: [RouterModule.forRoot([{loadChildren: './lazy/lazy#LazyModule'}])]
})
export class MainModule {}
`,
'src/lazy/lazy.ts': `
import {NgModule} from '@angular/core';
@NgModule()
export class ChildModule {}
`,
});
const {program, options} = createProgram(['src/main.ts', 'src/lazy/lazy.ts']);
expectNoDiagnosticsInProgram(options, program);
program.listLazyRoutes();
program.emit();
const lazyNgFactory =
fs.readFileSync(path.resolve(testSupport.basePath, 'built/src/lazy/lazy.ngfactory.js'));
expect(lazyNgFactory).toContain('import * as i1 from "./lazy";');
});
it('should list lazyRoutes given an entryRoute recursively', () => {
writeSomeRoutes();
const {program, options} = createProgram(['src/main.ts']);
expectNoDiagnosticsInProgram(options, program);
expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([
{
module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
referencedModule:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
route: './child#ChildModule'
},
{
module:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
referencedModule:
{name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')},
route: './child2#ChildModule2'
},
]);
expect(normalizeRoutes(program.listLazyRoutes('src/child#ChildModule'))).toEqual([
{
module:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
referencedModule:
{name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')},
route: './child2#ChildModule2'
},
]);
});
it('should list lazyRoutes pointing to a default export', () => {
testSupport.writeFiles({
'src/main.ts': `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@NgModule({
imports: [RouterModule.forRoot([{loadChildren: './child'}])]
})
export class MainModule {}
`,
'src/child.ts': `
import {NgModule} from '@angular/core';
@NgModule()
export default class ChildModule {}
`,
});
const {program, options} = createProgram(['src/main.ts']);
expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([
{
module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
referencedModule:
{name: undefined, filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
route: './child'
},
]);
});
it('should list lazyRoutes from imported modules', () => {
testSupport.writeFiles({
'src/main.ts': `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
import {NestedMainModule} from './nested/main';
@NgModule({
imports: [
RouterModule.forRoot([{loadChildren: './child#ChildModule'}]),
NestedMainModule,
]
})
export class MainModule {}
`,
'src/child.ts': `
import {NgModule} from '@angular/core';
@NgModule()
export class ChildModule {}
`,
'src/nested/main.ts': `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@NgModule({
imports: [RouterModule.forChild([{loadChildren: './child#NestedChildModule'}])]
})
export class NestedMainModule {}
`,
'src/nested/child.ts': `
import {NgModule} from '@angular/core';
@NgModule()
export class NestedChildModule {}
`,
});
const {program, options} = createProgram(['src/main.ts']);
expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([
{
module: {
name: 'NestedMainModule',
filePath: path.resolve(testSupport.basePath, 'src/nested/main.ts')
},
referencedModule: {
name: 'NestedChildModule',
filePath: path.resolve(testSupport.basePath, 'src/nested/child.ts')
},
route: './child#NestedChildModule'
},
{
module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
referencedModule:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
route: './child#ChildModule'
},
]);
});
it('should dedupe lazyRoutes given an entryRoute', () => {
writeSomeRoutes();
testSupport.writeFiles({
'src/index.ts': `
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@NgModule({
imports: [
RouterModule.forRoot([{loadChildren: './main#MainModule'}]),
RouterModule.forRoot([{loadChildren: './child#ChildModule'}]),
]
})
export class MainModule {}
`,
});
const {program, options} = createProgram(['src/index.ts']);
expectNoDiagnosticsInProgram(options, program);
expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([
{
module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
referencedModule:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
route: './child#ChildModule'
},
{
module:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
referencedModule:
{name: 'ChildModule2', filePath: path.resolve(testSupport.basePath, 'src/child2.ts')},
route: './child2#ChildModule2'
},
]);
});
it('should list lazyRoutes given an entryRoute even with static errors', () => {
testSupport.writeFiles({
'src/main.ts': `
import {NgModule, Component} from '@angular/core';
import {RouterModule} from '@angular/router';
@Component({
selector: 'url-comp',
// Non existent external template
templateUrl: 'non-existent.html',
})
export class ErrorComp {}
@Component({
selector: 'err-comp',
// Error in template
template: '<input/>{{',
})
export class ErrorComp2 {}
// Component with metadata errors.
@Component(() => {if (1==1) return null as any;})
export class ErrorComp3 {}
// Unused component
@Component({
selector: 'unused-comp',
template: ''
})
export class UnusedComp {}
@NgModule({
declarations: [ErrorComp, ErrorComp2, ErrorComp3, NonExistentComp],
imports: [RouterModule.forRoot([{loadChildren: './child#ChildModule'}])]
})
export class MainModule {}
@NgModule({
// Component used in 2 NgModules
declarations: [ErrorComp],
})
export class Mod2 {}
`,
'src/child.ts': `
import {NgModule} from '@angular/core';
@NgModule()
export class ChildModule {}
`,
});
const program = createProgram(['src/main.ts'], {collectAllErrors: true}).program;
expect(normalizeRoutes(program.listLazyRoutes('src/main#MainModule'))).toEqual([{
module: {name: 'MainModule', filePath: path.resolve(testSupport.basePath, 'src/main.ts')},
referencedModule:
{name: 'ChildModule', filePath: path.resolve(testSupport.basePath, 'src/child.ts')},
route: './child#ChildModule'
}]);
});
});
it('should report errors for ts and ng errors on emit with noEmitOnError=true', () => {
testSupport.writeFiles({
'src/main.ts': `
import {Component, NgModule} from '@angular/core';
// Ts error
let x: string = 1;
// Ng error
@Component({selector: 'comp', templateUrl: './main.html'})
export class MyComp {}
@NgModule({declarations: [MyComp]})
export class MyModule {}
`,
'src/main.html': '{{nonExistent}}'
});
const options = testSupport.createCompilerOptions({noEmitOnError: true});
const host = ng.createCompilerHost({options});
const program1 = ng.createProgram(
{rootNames: [path.resolve(testSupport.basePath, 'src/main.ts')], options, host});
const errorDiags =
program1.emit().diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error);
expect(formatDiagnostics(errorDiags))
.toContain(`src/main.ts(5,13): error TS2322: Type '1' is not assignable to type 'string'.`);
expect(formatDiagnostics(errorDiags))
.toContain(
`src/main.html(1,1): error TS100: Property 'nonExistent' does not exist on type 'MyComp'.`);
});
describe('errors', () => {
const fileWithStructuralError = `
import {NgModule} from '@angular/core';
@NgModule(() => (1===1 ? null as any : null as any))
export class MyModule {}
`;
const fileWithGoodContent = `
import {NgModule} from '@angular/core';
@NgModule()
export class MyModule {}
`;
it('should not throw on structural errors but collect them', () => {
testSupport.write('src/index.ts', fileWithStructuralError);
const options = testSupport.createCompilerOptions();
const host = ng.createCompilerHost({options});
const program = ng.createProgram(
{rootNames: [path.resolve(testSupport.basePath, 'src/index.ts')], options, host});
const structuralErrors = program.getNgStructuralDiagnostics();
expect(structuralErrors.length).toBe(1);
expect(structuralErrors[0].messageText).toContain('Function calls are not supported.');
});
it('should not throw on structural errors but collect them (loadNgStructureAsync)', (done) => {
testSupport.write('src/index.ts', fileWithStructuralError);
const options = testSupport.createCompilerOptions();
const host = ng.createCompilerHost({options});
const program = ng.createProgram(
{rootNames: [path.resolve(testSupport.basePath, 'src/index.ts')], options, host});
program.loadNgStructureAsync().then(() => {
const structuralErrors = program.getNgStructuralDiagnostics();
expect(structuralErrors.length).toBe(1);
expect(structuralErrors[0].messageText).toContain('Function calls are not supported.');
done();
});
});
it('should be able report structural errors with noResolve:true and generateCodeForLibraries:false ' +
'even if getSourceFile throws for non existent files',
() => {
testSupport.write('src/index.ts', fileWithGoodContent);
// compile angular and produce .ngsummary.json / ngfactory.d.ts files
compile();
testSupport.write('src/ok.ts', fileWithGoodContent);
testSupport.write('src/error.ts', fileWithStructuralError);
// Make sure the ok.ts file is before the error.ts file,
// so we added a .ngfactory.ts file for it.
const allRootNames = resolveFiles(
['src/ok.ts', 'src/error.ts'].map(fn => path.resolve(testSupport.basePath, fn)));
const options = testSupport.createCompilerOptions({
noResolve: true,
generateCodeForLibraries: false,
});
const host = ng.createCompilerHost({options});
const originalGetSourceFile = host.getSourceFile;
host.getSourceFile =
(fileName: string, languageVersion: ts.ScriptTarget,
onError?: ((message: string) => void) | undefined): ts.SourceFile => {
// We should never try to load .ngfactory.ts files
if (fileName.match(/\.ngfactory\.ts$/)) {
throw new Error(`Non existent ngfactory file: ` + fileName);
}
return originalGetSourceFile.call(host, fileName, languageVersion, onError);
};
const program = ng.createProgram({rootNames: allRootNames, options, host});
const structuralErrors = program.getNgStructuralDiagnostics();
expect(structuralErrors.length).toBe(1);
expect(structuralErrors[0].messageText).toContain('Function calls are not supported.');
});
});
});

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/core': 'ng.core',
@ -15,7 +15,7 @@ const globals = {
'rxjs/Subject': 'Rx',
};
export default {
module.exports = {
entry: '../../dist/packages-dist/compiler/esm5/compiler.js',
dest: '../../dist/packages-dist/compiler/bundles/compiler.umd.js',
format: 'umd',

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl} from '../compile_metadata';
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl, tokenReference} from '../compile_metadata';
import {CompilerConfig} from '../config';
import {ViewEncapsulation} from '../core';
import {MessageBundle} from '../i18n/message_bundle';
@ -29,6 +29,7 @@ import {ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
import {AotCompilerHost} from './compiler_host';
import {AotCompilerOptions} from './compiler_options';
import {GeneratedFile} from './generated_file';
import {LazyRoute, listLazyRoutes, parseLazyRoute} from './lazy_routes';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
@ -192,6 +193,7 @@ export class AotCompiler {
private _createNgFactoryStub(
outputCtx: OutputContext, file: NgAnalyzedFile, emitFlags: StubEmitFlags) {
let componentId = 0;
file.ngModules.forEach((ngModuleMeta, ngModuleIndex) => {
// Note: the code below needs to executed for StubEmitFlags.Basic and StubEmitFlags.TypeCheck,
// so we don't change the .ngfactory file too much when adding the typecheck block.
@ -229,12 +231,14 @@ export class AotCompiler {
if (!compMeta.isComponent) {
return;
}
componentId++;
this._createTypeCheckBlock(
outputCtx, ngModuleMeta, this._metadataResolver.getHostComponentMetadata(compMeta),
[compMeta.type], externalReferenceVars);
this._createTypeCheckBlock(
outputCtx, ngModuleMeta, compMeta, ngModuleMeta.transitiveModule.directives,
outputCtx, `${compMeta.type.reference.name}_Host_${componentId}`, ngModuleMeta,
this._metadataResolver.getHostComponentMetadata(compMeta), [compMeta.type],
externalReferenceVars);
this._createTypeCheckBlock(
outputCtx, `${compMeta.type.reference.name}_${componentId}`, ngModuleMeta, compMeta,
ngModuleMeta.transitiveModule.directives, externalReferenceVars);
});
}
});
@ -245,12 +249,13 @@ export class AotCompiler {
}
private _createTypeCheckBlock(
ctx: OutputContext, moduleMeta: CompileNgModuleMetadata, compMeta: CompileDirectiveMetadata,
directives: CompileIdentifierMetadata[], externalReferenceVars: Map<any, string>) {
ctx: OutputContext, componentId: string, moduleMeta: CompileNgModuleMetadata,
compMeta: CompileDirectiveMetadata, directives: CompileIdentifierMetadata[],
externalReferenceVars: Map<any, string>) {
const {template: parsedTemplate, pipes: usedPipes} =
this._parseTemplate(compMeta, moduleMeta, directives);
ctx.statements.push(...this._typeCheckCompiler.compileComponent(
compMeta, parsedTemplate, usedPipes, externalReferenceVars));
componentId, compMeta, parsedTemplate, usedPipes, externalReferenceVars));
}
emitMessageBundle(analyzeResult: NgAnalyzedModules, locale: string|null): MessageBundle {
@ -500,13 +505,13 @@ export class AotCompiler {
}
const arity = this._symbolResolver.getTypeArity(symbol) || 0;
const {filePath, name, members} = this._symbolResolver.getImportAs(symbol) || symbol;
const importModule = this._symbolResolver.fileNameToModuleName(filePath, genFilePath);
const importModule = this._fileNameToModuleName(filePath, genFilePath);
// It should be good enough to compare filePath to genFilePath and if they are equal
// there is a self reference. However, ngfactory files generate to .ts but their
// symbols have .d.ts so a simple compare is insufficient. They should be canonical
// and is tracked by #17705.
const selfReference = this._symbolResolver.fileNameToModuleName(genFilePath, genFilePath);
const selfReference = this._fileNameToModuleName(genFilePath, genFilePath);
const moduleName = importModule === selfReference ? null : importModule;
// If we are in a type expression that refers to a generic type then supply
@ -527,6 +532,12 @@ export class AotCompiler {
return {statements: [], genFilePath, importExpr};
}
private _fileNameToModuleName(importedFilePath: string, containingFilePath: string): string {
return this._summaryResolver.getKnownModuleName(importedFilePath) ||
this._symbolResolver.getKnownModuleName(importedFilePath) ||
this._host.fileNameToModuleName(importedFilePath, containingFilePath);
}
private _codegenStyles(
srcFileUrl: string, compMeta: CompileDirectiveMetadata,
stylesheetMetadata: CompileStylesheetMetadata, isShimmed: boolean,
@ -542,6 +553,43 @@ export class AotCompiler {
private _codegenSourceModule(srcFileUrl: string, ctx: OutputContext): GeneratedFile {
return new GeneratedFile(srcFileUrl, ctx.genFilePath, ctx.statements);
}
listLazyRoutes(entryRoute?: string, analyzedModules?: NgAnalyzedModules): LazyRoute[] {
const self = this;
if (entryRoute) {
const symbol = parseLazyRoute(entryRoute, this._reflector).referencedModule;
return visitLazyRoute(symbol);
} else if (analyzedModules) {
const allLazyRoutes: LazyRoute[] = [];
for (const ngModule of analyzedModules.ngModules) {
const lazyRoutes = listLazyRoutes(ngModule, this._reflector);
for (const lazyRoute of lazyRoutes) {
allLazyRoutes.push(lazyRoute);
}
}
return allLazyRoutes;
} else {
throw new Error(`Either route or analyzedModules has to be specified!`);
}
function visitLazyRoute(
symbol: StaticSymbol, seenRoutes = new Set<StaticSymbol>(),
allLazyRoutes: LazyRoute[] = []): LazyRoute[] {
// Support pointing to default exports, but stop recursing there,
// as the StaticReflector does not yet support default exports.
if (seenRoutes.has(symbol) || !symbol.name) {
return allLazyRoutes;
}
seenRoutes.add(symbol);
const lazyRoutes = listLazyRoutes(
self._metadataResolver.getNgModuleMetadata(symbol, true) !, self._reflector);
for (const lazyRoute of lazyRoutes) {
allLazyRoutes.push(lazyRoute);
visitLazyRoute(lazyRoute.referencedModule, seenRoutes, allLazyRoutes);
}
return allLazyRoutes;
}
}
}
function _createEmptyStub(outputCtx: OutputContext) {

View File

@ -52,15 +52,18 @@ export function createAotUrlResolver(host: {
/**
* Creates a new AotCompiler based on options and a host.
*/
export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCompilerOptions):
{compiler: AotCompiler, reflector: StaticReflector} {
export function createAotCompiler(
compilerHost: AotCompilerHost, options: AotCompilerOptions,
errorCollector?: (error: any, type?: any) =>
void): {compiler: AotCompiler, reflector: StaticReflector} {
let translations: string = options.translations || '';
const urlResolver = createAotUrlResolver(compilerHost);
const symbolCache = new StaticSymbolCache();
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);
const staticReflector = new StaticReflector(summaryResolver, symbolResolver);
const staticReflector =
new StaticReflector(summaryResolver, symbolResolver, [], [], errorCollector);
const htmlParser = new I18NHtmlParser(
new HtmlParser(), translations, options.i18nFormat, options.missingTranslation, console);
const config = new CompilerConfig({
@ -80,7 +83,7 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
const resolver = new CompileMetadataResolver(
config, htmlParser, new NgModuleResolver(staticReflector),
new DirectiveResolver(staticReflector), new PipeResolver(staticReflector), summaryResolver,
elementSchemaRegistry, normalizer, console, symbolCache, staticReflector);
elementSchemaRegistry, normalizer, console, symbolCache, staticReflector, errorCollector);
// TODO(vicb): do not pass options.i18nFormat here
const viewCompiler = new ViewCompiler(staticReflector);
const typeCheckCompiler = new TypeCheckCompiler(options, staticReflector);

View File

@ -14,6 +14,13 @@ import {AotSummaryResolverHost} from './summary_resolver';
* services and from underlying file systems.
*/
export interface AotCompilerHost extends StaticSymbolResolverHost, AotSummaryResolverHost {
/**
* Converts a file path to a module name that can be used as an `import.
* I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`.
*
* See ImportResolver.
*/
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string;
/**
* Converts a path that refers to a resource into an absolute filePath
* that can be later on used for loading the resource via `loadResource.

View File

@ -0,0 +1,62 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CompileNgModuleMetadata, tokenReference} from '../compile_metadata';
import {Route} from '../core';
import {CompileMetadataResolver} from '../metadata_resolver';
import {AotCompilerHost} from './compiler_host';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
export interface LazyRoute {
module: StaticSymbol;
route: string;
referencedModule: StaticSymbol;
}
export function listLazyRoutes(
moduleMeta: CompileNgModuleMetadata, reflector: StaticReflector): LazyRoute[] {
const allLazyRoutes: LazyRoute[] = [];
for (const {provider, module} of moduleMeta.transitiveModule.providers) {
if (tokenReference(provider.token) === reflector.ROUTES) {
const loadChildren = _collectLoadChildren(provider.useValue);
for (const route of loadChildren) {
allLazyRoutes.push(parseLazyRoute(route, reflector, module.reference));
}
}
}
return allLazyRoutes;
}
function _collectLoadChildren(routes: string | Route | Route[], target: string[] = []): string[] {
if (typeof routes === 'string') {
target.push(routes);
} else if (Array.isArray(routes)) {
for (const route of routes) {
_collectLoadChildren(route, target);
}
} else if (routes.loadChildren) {
_collectLoadChildren(routes.loadChildren, target);
} else if (routes.children) {
_collectLoadChildren(routes.children, target);
}
return target;
}
export function parseLazyRoute(
route: string, reflector: StaticReflector, module?: StaticSymbol): LazyRoute {
const [routePath, routeName] = route.split('#');
const referencedModule = reflector.resolveExternalReference(
{
moduleName: routePath,
name: routeName,
},
module ? module.filePath : undefined);
return {route: route, module: module || referencedModule, referencedModule};
}

View File

@ -45,7 +45,7 @@ export class StaticReflector implements CompileReflector {
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
private injectionToken: StaticSymbol;
private opaqueToken: StaticSymbol;
private ROUTES: StaticSymbol;
ROUTES: StaticSymbol;
private ANALYZE_FOR_ENTRY_COMPONENTS: StaticSymbol;
private annotationForParentClassWithSummaryKind =
new Map<CompileSummaryKind, MetadataFactory<any>[]>();
@ -76,11 +76,14 @@ export class StaticReflector implements CompileReflector {
return this.symbolResolver.getResourcePath(staticSymbol);
}
resolveExternalReference(ref: o.ExternalReference): StaticSymbol {
const refSymbol = this.symbolResolver.getSymbolByModule(ref.moduleName !, ref.name !);
resolveExternalReference(ref: o.ExternalReference, containingFile?: string): StaticSymbol {
const refSymbol =
this.symbolResolver.getSymbolByModule(ref.moduleName !, ref.name !, containingFile);
const declarationSymbol = this.findSymbolDeclaration(refSymbol);
this.symbolResolver.recordModuleNameForFileName(refSymbol.filePath, ref.moduleName !);
this.symbolResolver.recordImportAs(declarationSymbol, refSymbol);
if (!containingFile) {
this.symbolResolver.recordModuleNameForFileName(refSymbol.filePath, ref.moduleName !);
this.symbolResolver.recordImportAs(declarationSymbol, refSymbol);
}
return declarationSymbol;
}
@ -749,7 +752,7 @@ class PopulatedScope extends BindingScope {
}
function positionalError(message: string, fileName: string, line: number, column: number): Error {
const result = new Error(message);
const result = syntaxError(message);
(result as any).fileName = fileName;
(result as any).line = line;
(result as any).column = column;

View File

@ -39,13 +39,6 @@ export interface StaticSymbolResolverHost {
* `path/to/containingFile.ts` containing `import {...} from 'module-name'`.
*/
moduleNameToFileName(moduleName: string, containingFile?: string): string|null;
/**
* Converts a file path to a module name that can be used as an `import.
* I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`.
*
* See ImportResolver.
*/
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string;
}
const SUPPORTED_SCHEMA_VERSION = 4;
@ -160,15 +153,6 @@ export class StaticSymbolResolver {
return (resolvedSymbol && resolvedSymbol.metadata && resolvedSymbol.metadata.arity) || null;
}
/**
* Converts a file path to a module name that can be used as an `import`.
*/
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string {
return this.summaryResolver.getKnownModuleName(importedFilePath) ||
this.knownFileNameToModuleNames.get(importedFilePath) ||
this.host.fileNameToModuleName(importedFilePath, containingFilePath);
}
getKnownModuleName(filePath: string): string|null {
return this.knownFileNameToModuleNames.get(filePath) || null;
}
@ -498,9 +482,8 @@ export class StaticSymbolResolver {
const filePath = this.resolveModule(module, containingFile);
if (!filePath) {
this.reportError(
new Error(`Could not resolve module ${module}${containingFile ? ` relative to $ {
containingFile
} `: ''}`));
new Error(`Could not resolve module ${module}${containingFile ? ' relative to ' +
containingFile : ''}`));
return this.getStaticSymbol(`ERROR:${module}`, symbolName);
}
return this.getStaticSymbol(filePath, symbolName);

View File

@ -10,7 +10,7 @@ import {Summary, SummaryResolver} from '../summary_resolver';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {deserializeSummaries} from './summary_serializer';
import {ngfactoryFilePath, stripGeneratedFileSuffix, summaryFileName} from './util';
import {stripGeneratedFileSuffix, summaryFileName} from './util';
export interface AotSummaryResolverHost {
/**
@ -119,11 +119,7 @@ export class AotSummaryResolver implements SummaryResolver<StaticSymbol> {
if (moduleName) {
this.knownFileNameToModuleNames.set(filePath, moduleName);
}
importAs.forEach((importAs) => {
this.importAs.set(
importAs.symbol,
this.staticSymbolCache.get(ngfactoryFilePath(filePath), importAs.importAs));
});
importAs.forEach((importAs) => { this.importAs.set(importAs.symbol, importAs.importAs); });
}
return hasSummary;
}

View File

@ -12,7 +12,7 @@ import {OutputContext, ValueTransformer, ValueVisitor, visitValue} from '../util
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
import {summaryForJitFileName, summaryForJitName} from './util';
import {isLoweredSymbol, ngfactoryFilePath, summaryForJitFileName, summaryForJitName} from './util';
export function serializeSummaries(
srcFileName: string, forJitCtx: OutputContext | null,
@ -38,7 +38,7 @@ export function serializeSummaries(
});
const {json, exportAs} = toJsonSerializer.serialize();
if (forJitCtx) {
const forJitSerializer = new ForJitSerializer(forJitCtx, symbolResolver);
const forJitSerializer = new ForJitSerializer(forJitCtx, symbolResolver, summaryResolver);
types.forEach(({summary, metadata}) => { forJitSerializer.addSourceType(summary, metadata); });
toJsonSerializer.unprocessedSymbolSummariesBySymbol.forEach((summary) => {
if (summaryResolver.isLibraryFile(summary.symbol.filePath) && summary.type) {
@ -55,7 +55,7 @@ export function deserializeSummaries(
libraryFileName: string, json: string): {
moduleName: string | null,
summaries: Summary<StaticSymbol>[],
importAs: {symbol: StaticSymbol, importAs: string}[]
importAs: {symbol: StaticSymbol, importAs: StaticSymbol}[]
} {
const deserializer = new FromJsonDeserializer(symbolCache, summaryResolver);
return deserializer.deserialize(libraryFileName, json);
@ -83,6 +83,7 @@ class ToJsonSerializer extends ValueTransformer {
// Note: This only contains symbols without members.
private symbols: StaticSymbol[] = [];
private indexBySymbol = new Map<StaticSymbol, number>();
private reexportedBy = new Map<StaticSymbol, StaticSymbol>();
// This now contains a `__symbol: number` in the place of
// StaticSymbols, but otherwise has the same shape as the original objects.
private processedSummaryBySymbol = new Map<StaticSymbol, any>();
@ -126,9 +127,32 @@ class ToJsonSerializer extends ValueTransformer {
}
});
metadata = clone;
} else if (isCall(metadata)) {
if (!isFunctionCall(metadata) && !isMethodCallOnVariable(metadata)) {
// Don't store complex calls as we won't be able to simplify them anyways later on.
metadata = {
__symbolic: 'error',
message: 'Complex function calls are not supported.',
};
}
}
// Note: We need to keep storing ctor calls for e.g.
// `export const x = new InjectionToken(...)`
unprocessedSummary.metadata = metadata;
processedSummary.metadata = this.processValue(metadata, SerializationFlags.ResolveValue);
if (metadata instanceof StaticSymbol &&
this.summaryResolver.isLibraryFile(metadata.filePath)) {
const declarationSymbol = this.symbols[this.indexBySymbol.get(metadata) !];
if (!isLoweredSymbol(declarationSymbol.name)) {
// Note: symbols that were introduced during codegen in the user file can have a reexport
// if a user used `export *`. However, we can't rely on this as tsickle will change
// `export *` into named exports, using only the information from the typechecker.
// As we introduce the new symbols after typecheck, Tsickle does not know about them,
// and omits them when expanding `export *`.
// So we have to keep reexporting these symbols manually via .ngfactory files.
this.reexportedBy.set(declarationSymbol, summary.symbol);
}
}
}
if (!unprocessedSummary.type && summary.type) {
unprocessedSummary.type = summary.type;
@ -161,12 +185,17 @@ class ToJsonSerializer extends ValueTransformer {
summaries: this.processedSummaries,
symbols: this.symbols.map((symbol, index) => {
symbol.assertNoMembers();
let importAs: string = undefined !;
let importAs: string|number = undefined !;
if (this.summaryResolver.isLibraryFile(symbol.filePath)) {
const summary = this.unprocessedSymbolSummariesBySymbol.get(symbol);
if (!summary || !summary.metadata || summary.metadata.__symbolic !== 'interface') {
importAs = `${symbol.name}_${index}`;
exportAs.push({symbol, exportAs: importAs});
const reexportSymbol = this.reexportedBy.get(symbol);
if (reexportSymbol) {
importAs = this.indexBySymbol.get(reexportSymbol) !;
} else {
const summary = this.unprocessedSymbolSummariesBySymbol.get(symbol);
if (!summary || !summary.metadata || summary.metadata.__symbolic !== 'interface') {
importAs = `${symbol.name}_${index}`;
exportAs.push({symbol, exportAs: importAs});
}
}
}
return {
@ -246,29 +275,35 @@ class ToJsonSerializer extends ValueTransformer {
}
class ForJitSerializer {
private data = new Map<StaticSymbol, {
private data: Array<{
summary: CompileTypeSummary,
metadata: CompileNgModuleMetadata|CompileDirectiveMetadata|CompilePipeMetadata|
CompileTypeMetadata|null,
isLibrary: boolean
}>();
}> = [];
constructor(private outputCtx: OutputContext, private symbolResolver: StaticSymbolResolver) {}
constructor(
private outputCtx: OutputContext, private symbolResolver: StaticSymbolResolver,
private summaryResolver: SummaryResolver<StaticSymbol>) {}
addSourceType(
summary: CompileTypeSummary, metadata: CompileNgModuleMetadata|CompileDirectiveMetadata|
CompilePipeMetadata|CompileTypeMetadata) {
this.data.set(summary.type.reference, {summary, metadata, isLibrary: false});
this.data.push({summary, metadata, isLibrary: false});
}
addLibType(summary: CompileTypeSummary) {
this.data.set(summary.type.reference, {summary, metadata: null, isLibrary: true});
this.data.push({summary, metadata: null, isLibrary: true});
}
serialize(exportAs: {symbol: StaticSymbol, exportAs: string}[]): void {
serialize(exportAsArr: {symbol: StaticSymbol, exportAs: string}[]): void {
const exportAsBySymbol = new Map<StaticSymbol, string>();
for (const {symbol, exportAs} of exportAsArr) {
exportAsBySymbol.set(symbol, exportAs);
}
const ngModuleSymbols = new Set<StaticSymbol>();
Array.from(this.data.values()).forEach(({summary, metadata, isLibrary}) => {
for (const {summary, metadata, isLibrary} of this.data) {
if (summary.summaryKind === CompileSummaryKind.NgModule) {
// collect the symbols that refer to NgModule classes.
// Note: we can't just rely on `summary.type.summaryKind` to determine this as
@ -276,7 +311,9 @@ class ForJitSerializer {
// See serializeSummaries for details.
ngModuleSymbols.add(summary.type.reference);
const modSummary = <CompileNgModuleSummary>summary;
modSummary.modules.forEach((mod) => { ngModuleSymbols.add(mod.reference); });
for (const mod of modSummary.modules) {
ngModuleSymbols.add(mod.reference);
}
}
if (!isLibrary) {
const fnName = summaryForJitName(summary.type.reference.name);
@ -284,16 +321,15 @@ class ForJitSerializer {
this.outputCtx, summary.type.reference,
this.serializeSummaryWithDeps(summary, metadata !));
}
});
}
exportAs.forEach((entry) => {
const symbol = entry.symbol;
if (ngModuleSymbols.has(symbol)) {
const jitExportAsName = summaryForJitName(entry.exportAs);
this.outputCtx.statements.push(
o.variable(jitExportAsName).set(this.serializeSummaryRef(symbol)).toDeclStmt(null, [
o.StmtModifier.Exported
]));
ngModuleSymbols.forEach((ngModuleSymbol) => {
if (this.summaryResolver.isLibraryFile(ngModuleSymbol.filePath)) {
let exportAs = exportAsBySymbol.get(ngModuleSymbol) || ngModuleSymbol.name;
const jitExportAsName = summaryForJitName(exportAs);
this.outputCtx.statements.push(o.variable(jitExportAsName)
.set(this.serializeSummaryRef(ngModuleSymbol))
.toDeclStmt(null, [o.StmtModifier.Exported]));
}
});
}
@ -378,22 +414,26 @@ class FromJsonDeserializer extends ValueTransformer {
deserialize(libraryFileName: string, json: string): {
moduleName: string | null,
summaries: Summary<StaticSymbol>[],
importAs: {symbol: StaticSymbol, importAs: string}[]
importAs: {symbol: StaticSymbol, importAs: StaticSymbol}[]
} {
const data: {moduleName: string | null, summaries: any[], symbols: any[]} = JSON.parse(json);
const importAs: {symbol: StaticSymbol, importAs: string}[] = [];
this.symbols = [];
data.symbols.forEach((serializedSymbol) => {
const symbol = this.symbolCache.get(
this.summaryResolver.fromSummaryFileName(serializedSymbol.filePath, libraryFileName),
serializedSymbol.name);
this.symbols.push(symbol);
if (serializedSymbol.importAs) {
importAs.push({symbol: symbol, importAs: serializedSymbol.importAs});
const allImportAs: {symbol: StaticSymbol, importAs: StaticSymbol}[] = [];
this.symbols = data.symbols.map(
(serializedSymbol) => this.symbolCache.get(
this.summaryResolver.fromSummaryFileName(serializedSymbol.filePath, libraryFileName),
serializedSymbol.name));
data.symbols.forEach((serializedSymbol, index) => {
const symbol = this.symbols[index];
const importAs = serializedSymbol.importAs;
if (typeof importAs === 'number') {
allImportAs.push({symbol, importAs: this.symbols[importAs]});
} else if (typeof importAs === 'string') {
allImportAs.push(
{symbol, importAs: this.symbolCache.get(ngfactoryFilePath(libraryFileName), importAs)});
}
});
const summaries = visitValue(data.summaries, this, null);
return {moduleName: data.moduleName, summaries, importAs};
const summaries = visitValue(data.summaries, this, null) as Summary<StaticSymbol>[];
return {moduleName: data.moduleName, summaries, importAs: allImportAs};
}
visitStringMap(map: {[key: string]: any}, context: any): any {
@ -407,3 +447,16 @@ class FromJsonDeserializer extends ValueTransformer {
}
}
}
function isCall(metadata: any): boolean {
return metadata && metadata.__symbolic === 'call';
}
function isFunctionCall(metadata: any): boolean {
return isCall(metadata) && metadata.expression instanceof StaticSymbol;
}
function isMethodCallOnVariable(metadata: any): boolean {
return isCall(metadata) && metadata.expression && metadata.expression.__symbolic === 'select' &&
metadata.expression.expression instanceof StaticSymbol;
}

View File

@ -58,4 +58,14 @@ export function summaryForJitName(symbolName: string): string {
export function stripSummaryForJitNameSuffix(symbolName: string): string {
return symbolName.replace(JIT_SUMMARY_NAME, '');
}
}
const LOWERED_SYMBOL = /\u0275\d+/;
export function isLoweredSymbol(name: string) {
return LOWERED_SYMBOL.test(name);
}
export function createLoweredSymbol(id: number): string {
return `\u0275${id}`;
}

View File

@ -39,6 +39,8 @@ export * from './aot/static_reflector';
export * from './aot/static_symbol';
export * from './aot/static_symbol_resolver';
export * from './aot/summary_resolver';
export {isLoweredSymbol, createLoweredSymbol} from './aot/util';
export {LazyRoute} from './aot/lazy_routes';
export * from './ast_path';
export * from './summary_resolver';
export {Identifiers} from './identifiers';

View File

@ -260,3 +260,8 @@ function makeMetadataFactory<T>(name: string, props?: (...args: any[]) => T): Me
factory.ngMetadataName = name;
return factory;
}
export interface Route {
children?: Route[];
loadChildren?: string|Type|any;
}

View File

@ -9,7 +9,7 @@
import {AotCompilerOptions} from '../aot/compiler_options';
import {StaticReflector} from '../aot/static_reflector';
import {StaticSymbol} from '../aot/static_symbol';
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompilePipeSummary, viewClassName} from '../compile_metadata';
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompilePipeSummary} from '../compile_metadata';
import {BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
import {Identifiers} from '../identifiers';
@ -33,7 +33,8 @@ export class TypeCheckCompiler {
* and also violate the point above.
*/
compileComponent(
component: CompileDirectiveMetadata, template: TemplateAst[], usedPipes: CompilePipeSummary[],
componentId: string, component: CompileDirectiveMetadata, template: TemplateAst[],
usedPipes: CompilePipeSummary[],
externalReferenceVars: Map<StaticSymbol, string>): o.Statement[] {
const pipes = new Map<string, StaticSymbol>();
usedPipes.forEach(p => pipes.set(p.name, p.type.reference));
@ -48,7 +49,7 @@ export class TypeCheckCompiler {
const visitor = viewBuilderFactory(null);
visitor.visitAll([], template);
return visitor.build();
return visitor.build(componentId);
}
}
@ -103,8 +104,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
templateVisitAll(this, astNodes);
}
build(targetStatements: o.Statement[] = []): o.Statement[] {
this.children.forEach((child) => child.build(targetStatements));
build(componentId: string, targetStatements: o.Statement[] = []): o.Statement[] {
this.children.forEach((child) => child.build(componentId, targetStatements));
const viewStmts: o.Statement[] =
[o.variable(DYNAMIC_VAR_NAME).set(o.NULL_EXPR).toDeclStmt(o.DYNAMIC_TYPE)];
let bindingCount = 0;
@ -128,7 +129,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
});
const viewName = `_View_${this.component.name}_${this.embeddedViewIndex}`;
const viewName = `_View_${componentId}_${this.embeddedViewIndex}`;
const viewFactory = new o.DeclareFunctionStmt(viewName, [], viewStmts);
targetStatements.push(viewFactory);
return targetStatements;

View File

@ -491,10 +491,11 @@ describe('compiler (unbundled Angular)', () => {
const libInput: MockDirectory = {
'lib': {
'base.ts': `
export class AValue {}
export type AType = {};
export class AClass {
constructor(a: AType) {}
constructor(a: AType, b: AValue) {}
}
`
}
@ -502,7 +503,7 @@ describe('compiler (unbundled Angular)', () => {
const appInput: MockDirectory = {
'app': {
'main.ts': `
export * from '../lib/base';
export {AClass} from '../lib/base';
`
}
};
@ -511,7 +512,105 @@ describe('compiler (unbundled Angular)', () => {
const {genFiles: appGenFiles} =
compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true});
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts');
expect(toTypeScript(appNgFactory)).not.toContain('AType');
const appNgFactoryTs = toTypeScript(appNgFactory);
expect(appNgFactoryTs).not.toContain('AType');
expect(appNgFactoryTs).toContain('AValue');
});
it('should not reexport complex function calls', () => {
const libInput: MockDirectory = {
'lib': {
'base.ts': `
export class AClass {
constructor(arg: any) {}
static create(arg: any = null): AClass { return new AClass(arg); }
call(arg: any) {}
}
export function simple(arg: any) { return [arg]; }
export const ctor_arg = {};
export const ctor_call = new AClass(ctor_arg);
export const static_arg = {};
export const static_call = AClass.create(static_arg);
export const complex_arg = {};
export const complex_call = AClass.create().call(complex_arg);
export const simple_arg = {};
export const simple_call = simple(simple_arg);
`
}
};
const appInput: MockDirectory = {
'app': {
'main.ts': `
import {ctor_call, static_call, complex_call, simple_call} from '../lib/base';
export const calls = [ctor_call, static_call, complex_call, simple_call];
`,
}
};
const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true});
const {genFiles: appGenFiles} =
compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true});
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts');
const appNgFactoryTs = toTypeScript(appNgFactory);
// metadata of ctor calls is preserved, so we reexport the argument
expect(appNgFactoryTs).toContain('ctor_arg');
expect(appNgFactoryTs).toContain('ctor_call');
// metadata of static calls is preserved, so we reexport the argument
expect(appNgFactoryTs).toContain('static_arg');
expect(appNgFactoryTs).toContain('AClass');
expect(appNgFactoryTs).toContain('static_call');
// metadata of complex calls is elided, so we don't reexport the argument
expect(appNgFactoryTs).not.toContain('complex_arg');
expect(appNgFactoryTs).toContain('complex_call');
// metadata of simple calls is preserved, so we reexport the argument
expect(appNgFactoryTs).toContain('simple_arg');
expect(appNgFactoryTs).toContain('simple_call');
});
it('should not reexport already exported symbols except for lowered symbols', () => {
const libInput: MockDirectory = {
'lib': {
'base.ts': `
export const exportedVar = 1;
// A symbol introduced by lowering expressions
export const ɵ1 = 'lowered symbol';
`
}
};
const appInput: MockDirectory = {
'app': {
'main.ts': `export * from '../lib/base';`,
}
};
const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true});
const {genFiles: appGenFiles} =
compile([appInput, libOutDir, angularSummaryFiles], {useSummaries: true});
const appNgFactory = appGenFiles.find((f) => f.genFileUrl === '/app/main.ngfactory.ts');
const appNgFactoryTs = toTypeScript(appNgFactory);
// we don't need to reexport exported symbols via the .ngfactory
// as we can refer to them via the reexport.
expect(appNgFactoryTs).not.toContain('exportedVar');
// although ɵ1 is reexported via `export *`, we still need to reexport it
// via the .ngfactory as tsickle expands `export *` into named exports,
// and doesn't know about our lowered symbols as we introduce them
// after the typecheck phase.
expect(appNgFactoryTs).toContain('ɵ1');
});
});

View File

@ -1062,6 +1062,22 @@ describe('StaticReflector', () => {
.useValue)
.toEqual({path: 'foo', data: {e: 1}});
});
describe('resolveExternalReference', () => {
it('should register modules names in the StaticSymbolResolver if no containingFile is given',
() => {
init({
'/tmp/root.ts': ``,
'/tmp/a.ts': `export const x = 1;`,
});
let symbol =
reflector.resolveExternalReference({moduleName: './a', name: 'x'}, '/tmp/root.ts');
expect(symbolResolver.getKnownModuleName(symbol.filePath)).toBeFalsy();
symbol = reflector.resolveExternalReference({moduleName: 'a', name: 'x'});
expect(symbolResolver.getKnownModuleName(symbol.filePath)).toBe('a');
});
});
});
const DEFAULT_TEST_DATA: {[key: string]: any} = {

View File

@ -465,10 +465,6 @@ export class MockStaticSymbolResolverHost implements StaticSymbolResolverHost {
return '/tmp/' + modulePath + '.d.ts';
}
fileNameToModuleName(filePath: string, containingFile: string) {
return filePath.replace(/(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/, '');
}
getMetadataFor(moduleId: string): any { return this._getMetadataFor(moduleId); }
private _getMetadataFor(filePath: string): any {

View File

@ -317,37 +317,96 @@ export function main() {
expect(summaries[1].metadata).toBe('someString');
});
it('should not create "importAs" names for reexported types in libraries', () => {
it('should not create "importAs" names for ctor arguments which are types of reexported classes in libraries',
() => {
init();
const externalSerialized = serializeSummaries(
'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver,
[
{
symbol: symbolCache.get('/tmp/external.ts', 'type'),
metadata: {__symbolic: 'interface'}
},
{
symbol: symbolCache.get('/tmp/external.ts', 'value'),
metadata: {__symbolic: 'class'}
},
{
symbol: symbolCache.get('/tmp/external.ts', 'reexportClass'),
metadata: {
__symbolic: 'class',
'members': {
'__ctor__': [{
'__symbolic': 'constructor',
'parameters': [
symbolCache.get('/tmp/external.ts', 'type'),
symbolCache.get('/tmp/external.ts', 'value'),
]
}]
}
}
},
],
[]);
expect(externalSerialized.exportAs).toEqual([]);
init({
'/tmp/external.ngsummary.json': externalSerialized.json,
});
const serialized = serializeSummaries(
'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [{
symbol: symbolCache.get('/tmp/test.ts', 'mainClass'),
metadata: symbolCache.get('/tmp/external.d.ts', 'reexportClass'),
}],
[]);
const importAs =
deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serialized.json)
.importAs;
expect(importAs).toEqual([
{
symbol: symbolCache.get('/tmp/external.d.ts', 'reexportClass'),
importAs: symbolCache.get('/tmp/test.d.ts', 'mainClass'),
},
{
symbol: symbolCache.get('/tmp/external.d.ts', 'value'),
importAs: symbolCache.get('someFile.ngfactory.d.ts', 'value_3'),
}
]);
});
it('should use existing reexports for "importAs" for symbols of libraries', () => {
init();
const externalSerialized = serializeSummaries(
'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver,
[
{symbol: symbolCache.get('/tmp/external.ts', 'value'), metadata: 'aValue'},
{
symbol: symbolCache.get('/tmp/external.ts', 'type'),
metadata: {__symbolic: 'interface'}
},
{
symbol: symbolCache.get('/tmp/external.ts', 'reexportType'),
metadata: symbolCache.get('/tmp/external.ts', 'type')
symbol: symbolCache.get('/tmp/external.ts', 'reexportValue'),
metadata: symbolCache.get('/tmp/external.ts', 'value')
},
],
[]);
expect(externalSerialized.exportAs).toEqual([]);
init({
'/tmp/external.ngsummary.json': externalSerialized.json,
});
const serialized = serializeSummaries(
'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [{
symbol: symbolCache.get('/tmp/test.ts', 'mainType'),
metadata: symbolCache.get('/tmp/external.d.ts', 'reexportType'),
symbol: symbolCache.get('/tmp/test.ts', 'mainValue'),
metadata: symbolCache.get('/tmp/external.d.ts', 'reexportValue'),
}],
[]);
expect(serialized.exportAs).toEqual([]);
const importAs =
deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serialized.json)
.importAs;
expect(importAs).toEqual([]);
expect(importAs).toEqual([{
symbol: symbolCache.get('/tmp/external.d.ts', 'value'),
importAs: symbolCache.get('/tmp/test.d.ts', 'mainValue'),
}]);
});
it('should create "importAs" names for non source symbols', () => {
it('should create reexports in the ngfactory for symbols of libraries', () => {
init();
const serialized = serializeSummaries(
'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [{
@ -366,9 +425,10 @@ export function main() {
const deserialized =
deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serialized.json);
// Note: no entry for the symbol with members!
expect(deserialized.importAs).toEqual([
{symbol: symbolCache.get('/tmp/external.d.ts', 'lib'), importAs: 'lib_1'}
]);
expect(deserialized.importAs).toEqual([{
symbol: symbolCache.get('/tmp/external.d.ts', 'lib'),
importAs: symbolCache.get('someFile.ngfactory.d.ts', 'lib_1')
}]);
});
});
}

View File

@ -652,7 +652,7 @@ export function compile(
const tsSettings = {...settings, ...tsOptions};
const program = ts.createProgram(host.scriptNames.slice(0), tsSettings, host);
preCompile(program);
const {compiler, reflector} = createAotCompiler(aotHost, options);
const {compiler, reflector} = createAotCompiler(aotHost, options, (err) => { throw err; });
const analyzedModules =
compiler.analyzeModulesSync(program.getSourceFiles().map(sf => sf.fileName));
const genFiles = compiler.emitAllImpls(analyzedModules);

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/core': 'ng.core',
@ -17,7 +17,7 @@ const globals = {
'rxjs/Subject': 'Rx'
};
export default {
module.exports = {
entry: '../../../dist/packages-dist/compiler/esm5/testing.js',
dest: '../../../dist/packages-dist/compiler/bundles/compiler-testing.umd.js',
format: 'umd',

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'rxjs/Observable': 'Rx',
@ -18,7 +18,7 @@ const globals = {
'rxjs/operator/share': 'Rx.Observable.prototype'
};
export default {
module.exports = {
entry: '../../dist/packages-dist/core/esm5/core.js',
dest: '../../dist/packages-dist/core/bundles/core.umd.js',
format: 'umd',

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/core': 'ng.core',
@ -15,7 +15,7 @@ const globals = {
'rxjs/Subject': 'Rx',
};
export default {
module.exports = {
entry: '../../../dist/packages-dist/core/esm5/testing.js',
dest: '../../../dist/packages-dist/core/bundles/core-testing.umd.js',
format: 'umd',

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/core': 'ng.core',
@ -21,7 +21,7 @@ const globals = {
'rxjs/operator/map': 'Rx.Observable.prototype'
};
export default {
module.exports = {
entry: '../../dist/packages-dist/forms/esm5/forms.js',
dest: '../../dist/packages-dist/forms/bundles/forms.umd.js',
format: 'umd',

View File

@ -6,18 +6,19 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/core': 'ng.core',
'@angular/compiler': 'ng.compiler',
'@angular/platform-browser': 'ng.platformBrowser',
'rxjs/Observable': 'Rx',
'rxjs/Observer': 'Rx',
'rxjs/Subject': 'Rx'
};
export default {
module.exports = {
entry: '../../dist/packages-dist/http/esm5/http.js',
dest: '../../dist/packages-dist/http/bundles/http.umd.js',
format: 'umd',

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/core': 'ng.core',
@ -20,7 +20,7 @@ const globals = {
'rxjs/operator/take': 'Rx.Observable.prototype'
};
export default {
module.exports = {
entry: '../../../dist/packages-dist/http/esm5/testing.js',
dest: '../../../dist/packages-dist/http/bundles/http-testing.umd.js',
format: 'umd',

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import commonjs from 'rollup-plugin-commonjs';
import sourcemaps from 'rollup-plugin-sourcemaps';
import * as path from 'path';
const commonjs = require('rollup-plugin-commonjs');
const sourcemaps = require('rollup-plugin-sourcemaps');
const path = require('path');
var m = /^\@angular\/((\w|\-)+)(\/(\w|\d|\/|\-)+)?$/;
var location = normalize('../../dist/packages-dist') + '/';
@ -60,7 +60,7 @@ module.exports = function(provided) {
}
`;
export default {
module.exports = {
entry: '../../dist/packages-dist/language-service/esm5/language-service.js',
dest: '../../dist/packages-dist/language-service/bundles/language-service.umd.js',
format: 'amd',

View File

@ -6,12 +6,17 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompilerHost} from '@angular/compiler';
import {CompilerHost, CompilerOptions, ModuleResolutionHostAdapter} from '@angular/compiler-cli/src/language_services';
import {StaticSymbolResolverHost} from '@angular/compiler';
import {CompilerOptions, MetadataCollector, MetadataReaderCache, MetadataReaderHost, createMetadataReaderCache, readMetadata} from '@angular/compiler-cli/src/language_services';
import * as path from 'path';
import * as ts from 'typescript';
class ReflectorModuleModuleResolutionHost implements ts.ModuleResolutionHost {
constructor(private host: ts.LanguageServiceHost) {
class ReflectorModuleModuleResolutionHost implements ts.ModuleResolutionHost, MetadataReaderHost {
// Note: verboseInvalidExpressions is important so that
// the collector will collect errors instead of throwing
private metadataCollector = new MetadataCollector({verboseInvalidExpression: true});
constructor(private host: ts.LanguageServiceHost, private getProgram: () => ts.Program) {
if (host.directoryExists)
this.directoryExists = directoryName => this.host.directoryExists !(directoryName);
}
@ -29,24 +34,46 @@ class ReflectorModuleModuleResolutionHost implements ts.ModuleResolutionHost {
}
directoryExists: (directoryName: string) => boolean;
getSourceFileMetadata(fileName: string) {
const sf = this.getProgram().getSourceFile(fileName);
return sf ? this.metadataCollector.getMetadata(sf) : undefined;
}
cacheMetadata(fileName: string) {
// Don't cache the metadata for .ts files as they might change in the editor!
return fileName.endsWith('.d.ts');
}
}
// This reflector host's purpose is to first set verboseInvalidExpressions to true so the
// reflector will collect errors instead of throwing, and second to all deferring the creation
// of the program until it is actually needed.
export class ReflectorHost extends CompilerHost {
export class ReflectorHost implements StaticSymbolResolverHost {
private moduleResolutionCache: ts.ModuleResolutionCache;
private hostAdapter: ReflectorModuleModuleResolutionHost;
private metadataReaderCache = createMetadataReaderCache();
constructor(
private getProgram: () => ts.Program, serviceHost: ts.LanguageServiceHost,
options: CompilerOptions) {
super(
// The ancestor value for program is overridden below so passing null here is safe.
/* program */ null !, options,
new ModuleResolutionHostAdapter(new ReflectorModuleModuleResolutionHost(serviceHost)),
{verboseInvalidExpression: true});
getProgram: () => ts.Program, serviceHost: ts.LanguageServiceHost,
private options: CompilerOptions) {
this.hostAdapter = new ReflectorModuleModuleResolutionHost(serviceHost, getProgram);
this.moduleResolutionCache =
ts.createModuleResolutionCache(serviceHost.getCurrentDirectory(), (s) => s);
}
protected get program() { return this.getProgram(); }
protected set program(value: ts.Program) {
// Discard the result set by ancestor constructor
getMetadataFor(modulePath: string): {[key: string]: any}[]|undefined {
return readMetadata(modulePath, this.hostAdapter, this.metadataReaderCache);
}
moduleNameToFileName(moduleName: string, containingFile?: string): string|null {
if (!containingFile) {
if (moduleName.indexOf('.') === 0) {
throw new Error('Resolution of relative paths requires a containing file.');
}
// Any containing file gives the same result for absolute imports
containingFile = path.join(this.options.basePath !, 'index.ts');
}
const resolved =
ts.resolveModuleName(moduleName, containingFile, this.options, this.hostAdapter)
.resolvedModule;
return resolved ? resolved.resolvedFileName : null;
}
}

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/core': 'ng.core',
@ -16,7 +16,7 @@ const globals = {
'@angular/platform-browser': 'ng.platformBrowser',
};
export default {
module.exports = {
entry: '../../dist/packages-dist/platform-browser-dynamic/esm5/platform-browser-dynamic.js',
dest: '../../dist/packages-dist/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
format: 'umd',

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/core': 'ng.core',
@ -20,7 +20,7 @@ const globals = {
'@angular/platform-browser-dynamic': 'ng.platformBrowserDynamic'
};
export default {
module.exports = {
entry: '../../../dist/packages-dist/platform-browser-dynamic/esm5/testing.js',
dest:
'../../../dist/packages-dist/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
const resolve = require('rollup-plugin-node-resolve');
const sourcemaps = require('rollup-plugin-sourcemaps');
const globals = {
'@angular/core': 'ng.core',
@ -17,7 +17,7 @@ const globals = {
'@angular/animations/browser': 'ng.animations.browser'
};
export default {
module.exports = {
entry: '../../../dist/packages-dist/platform-browser/esm5/animations.js',
dest: '../../../dist/packages-dist/platform-browser/bundles/platform-browser-animations.umd.js',
format: 'umd',

Some files were not shown because too many files have changed in this diff Show More