Compare commits

..

91 Commits

Author SHA1 Message Date
bf71b107b3 release: cut the v7.1.0 release 2018-11-21 14:57:44 -08:00
0d9b27ff26 fix(ivy): let ngcc transform @angular/core typings with relative imports (#27055)
PR Close #27055
2018-11-21 09:20:11 -08:00
c8c8648abf fix(ivy): prevent ngcc from referencing missing ɵsetClassMetadata (#27055)
When ngtsc compiles @angular/core, it rewrites core imports to the
r3_symbols.ts file that exposes all internal symbols under their
external name. When creating the FESM bundle, the r3_symbols.ts file
causes the external symbol names to be rewritten to their internal name.

Under ngcc compilations of FESM bundles, the indirection of
r3_symbols.ts is no longer in place such that the external names are
retained in the bundle. Previously, the external name `ɵdefineNgModule`
was explicitly declared internally to resolve this issue, but the
recently added `setClassMetadata` was not declared as such, causing
runtime errors.

Instead of relying on the r3_symbols.ts file to perform the rewrite of
the external modules to their internal variants, the translation is
moved into the `ImportManager` during the compilation itself. This
avoids the need for providing the external name manually.

PR Close #27055
2018-11-21 09:20:11 -08:00
8ce59a583b test(ivy): run router tests with ivy on CI (#27195)
PR Close #27195
2018-11-21 09:19:40 -08:00
573fb783e1 docs: add formatting productivity hints to DEVELOPER.md (#27211)
PR Close #27211
2018-11-21 09:19:17 -08:00
d666370e16 test(core): add JIRA references for root-casuse ivy TestBed failures (#27196)
PR Close #27196
2018-11-21 09:18:54 -08:00
a72250bace build(bazel): Add Bazel builders (#27141)
PR Close #27141
2018-11-21 07:46:42 -08:00
026b7e34b3 build: update yarn version (#27193)
Some engineers were already on Yarn 0.10.x which was permitted by the range in our package.json#engines
However this introduced 'integrity sha512' lines into the yarn.lock files.
Then when engineers use yarn 0.9 (in particular, Bazel did this) then the lock files get tons of meaningless edits.
We could force everyone back to yarn 0.9 but this commit chooses to instead advance everyone past 0.10

PR Close #27193
2018-11-21 07:46:22 -08:00
4390e10dfd fix(ivy): don't update parent pointers in the ivy switch transform (#27170)
Now that the Ivy switch transform uses ts.getMutableClone() to copy
statements, there's no need to set .parent pointers on the resulting
updated nodes. Doing this was causing assertion failures deep in
TypeScript in some cases.

PR Close #27170
2018-11-20 12:03:39 -08:00
e56c8bf8d1 fix(ivy): align discovery methods for consistency (#27117)
PR Close #27117
2018-11-20 11:44:14 -08:00
ca40565f9a fix(ivy): hack implementation of host styles (#27180)
PR Close #27180
2018-11-20 11:43:29 -08:00
975c269752 docs(forms): language correction (#27177)
Word missing from docs
PR Close #27177
2018-11-20 10:46:34 -08:00
34306c356e docs(forms): close markdown backticks (#27189)
PR Close #27189
2018-11-20 10:45:16 -08:00
2a7210e382 build: release scripts should use bazel from yarn (#26899)
* With ec29fd3e7b, the prefix for the `bazel` command has been removed because it threw off the Bash variables. In order to fix this while still running Bazel from the `node_modules` (keeping the bazel versions consistent), we should run Bazel without Yarn (similar to how it's done for `build-packages-dist.sh`)

PR Close #26899
2018-11-20 10:44:34 -08:00
391767fb8f build: fix size artifacts not measured by github robot (#27042)
Currently the Github robot is not able to measure the artifacts of the `hello_world` and `todo` bundling tests because those will be only built with the `ivy-only` rule tag. This means that the current CircleCI job that is supposed to upload the size artifacts, does not even build those bundles.

PR Close #27042
2018-11-20 10:44:12 -08:00
bf3beb5959 fix(ivy): set ng-version attribute on root component (#27175)
PR Close #27175
2018-11-20 10:43:51 -08:00
01917733a1 fix(ivy): set encapsulation to None when there is no style (#27175)
PR Close #27175
2018-11-20 10:43:51 -08:00
859da3af50 docs: The note had an indentation and was missing line breaks (#27183)
Fixed the formatting and made Note bold.
PR Close #27183
2018-11-20 10:43:31 -08:00
a43998c089 test(ivy): add JIRA references for root-casuse TestBed failrues (#27188)
PR Close #27188
2018-11-20 10:43:11 -08:00
20729b3378 fix(ivy): deduplicate imported modules in r3_injector (#27102)
PR Close #27102
2018-11-19 09:20:39 -08:00
cf1ebdc079 test(ivy): enable platform-server tests for ivy (#27102)
PR Close #27102
2018-11-19 09:20:39 -08:00
893c1735dd build(docs-infra): upgrade cli command docs sources to a176d127a (#27168)
Relevant changes in [commit range](b50950b97...a176d127a):

**Modified**

- help/add.json

- help/build.json

- help/config.json

- help/e2e.json

- help/generate.json

- help/lint.json

- help/new.json

- help/run.json

- help/serve.json

- help/test.json

- help/update.json

- help/xi18n.json

Closes #27088

Closes #27110

Closes #27128

Closes #27144

PR Close #27168
2018-11-19 09:20:21 -08:00
81e975ad93 build: update to latest Bazel rules_typescript (#27138)
PR Close #27138
2018-11-17 12:19:32 -08:00
20ea5b5634 refactor(ivy): ensure directive host bindings use the styling algorithm (#27145)
PR Close #27145
2018-11-17 10:12:47 -08:00
4222b63639 Revert "refactor(ivy): ensure directive host bindings use the styling algorithm (#27134)"
This reverts commit b5dbf5154e.
2018-11-16 17:55:41 -08:00
b5dbf5154e refactor(ivy): ensure directive host bindings use the styling algorithm (#27134)
PR Close #27134
2018-11-16 16:10:45 -08:00
92e80af875 feat(ivy): ICU support for Ivy (#26794)
PR Close #26794
2018-11-16 16:09:30 -08:00
a4934a74b6 docs: fix code examples style (#26573)
PR Close #26573
2018-11-16 12:27:00 -08:00
91bffa9c53 fix(ivy): set encapsulation metadata (#27115)
PR Close #27115
2018-11-16 12:22:11 -08:00
60800da6c1 fix(ivy): export elementContainerStart/End instructions (#27053)
PR Close #27053
2018-11-16 12:19:18 -08:00
b07bd30b70 feat(bazel): Bazel workspace schematics (#26971)
This commit creates a schematics for Bazel workspace.

PR Close #26971
2018-11-16 12:18:06 -08:00
4f965ad2a1 test(docs-infra): remove reflect-metadata polyfill from aio tests (#27130)
v7 CLI includes the polyfill automatically whenever JIT compiler is used, so we don't need
to do it explicitly.

PR Close #27130
2018-11-16 12:17:44 -08:00
848f4148c0 fix(ivy): DI should work when no element injector on starting node (#27133)
PR Close #27133
2018-11-16 09:26:29 -08:00
65943b458f docs: add link to workspace config page (#26927)
PR Close #26927
2018-11-15 21:19:45 -08:00
6574e61062 test(ivy): skip useJit tests with Ivy (#27067)
Currently the `useJit` option from `TestBed.configureCompiler` isn't supported. These changes rework the existing test suites not to pass in `useJit` when running with Ivy.

PR Close #27067
2018-11-15 21:19:21 -08:00
ee12e725c0 fix(ivy): component ref injector should support change detector ref (#27107)
PR Close #27107
2018-11-15 21:18:24 -08:00
3ec7c5081d test(ivy): fix paths for http tests to work with ivy (#27121)
PR Close #27121
2018-11-15 21:18:00 -08:00
e22a302cad feat(ivy): support for i18n & ICU expressions (#27101)
PR Close #27101
2018-11-14 16:22:01 -08:00
92b05652b2 release: cut the v7.1.0-rc.0 release 2018-11-14 14:27:00 -08:00
4756324b5c docs: release notes for the v7.0.4 release 2018-11-14 14:14:45 -08:00
ce5242462b fix(ivy): implement rootNodes getter on ViewRef (#27095)
PR Close #27095
2018-11-14 12:28:03 -08:00
1c9e526a83 fix(ivy): use the root view injector when resolving dependencies (#27090)
PR Close #27090
2018-11-14 12:26:36 -08:00
8a626288a6 test(ivy): run more common tests with ivy on ci (#27089)
With https://github.com/angular/angular/pull/27068 merged we can activate
more ivy tests on CI.

PR Close #27089
2018-11-14 12:25:35 -08:00
bf6ac6cef8 feat(router): add pathParamsChange mode for runGuardsAndResolvers (#26861)
This option means guards and resolvers will ignore changes to optional
parameters such as query and matrix params. When the path or any path
params change, guards and resolvers will be run

Related to discussion in #18253
FW-560 #resolve

PR Close #26861
2018-11-14 12:24:43 -08:00
3da82338d1 build(bazel): turn on --nolegacy-external-runfiles (#26770)
PR Close #26770
2018-11-14 12:23:38 -08:00
f8f1168fa6 Revert "feat(ivy): support for i18n & ICU expressions (#26275)"
This reverts commit a63fd2d0f5.
2018-11-14 10:23:21 -08:00
a752971bca fix(router): add relativeLinkResolution to recognize operator (#26990)
Close #26983

PR Close #26990
2018-11-13 16:18:08 -08:00
f0c4f31771 fix(ivy): should export lifecycle hooks feature (#27082)
PR Close #27082
2018-11-13 16:17:30 -08:00
a63fd2d0f5 feat(ivy): support for i18n & ICU expressions (#26275)
PR Close #26275
2018-11-13 14:50:30 -08:00
d97994b27f fix(ivy): clone ts.SourceFile in ivy_switch when triggered (#27032)
Make a copy of the ts.SourceFile before modifying it in the ivy_switch
transform. It's suspected that the Bazel tsc_wrapped host's SourceFile
cache has issues when the ts.SourceFiles are mutated.

PR Close #27032
2018-11-13 14:00:20 -08:00
c31e78f670 fix(compiler-cli): add missing tslib dependency (#27063)
PR Close #27063
2018-11-13 13:59:41 -08:00
bc652a2943 fix(ivy): jit compilation should support content queries with type predicates (#27068)
PR Close #27068
2018-11-13 12:18:13 -08:00
e6e590479e fix(ivy): support forward refs in @Inject annotations (#27069)
PR Close #27069
2018-11-13 12:17:18 -08:00
123da1a8c2 test(ivy): split out provider tests (#27069)
PR Close #27069
2018-11-13 12:17:18 -08:00
7695dbd0bd test(ivy): run common tests with ivy on ci (#27071)
PR Close #27071
2018-11-13 10:59:55 -08:00
9741f5b8cf docs: new doc for core directives (#26923)
PR Close #26923
2018-11-13 10:57:56 -08:00
1b97971eb7 docs: fixed typo on Github issue template (#27070)
PR Close #27070
2018-11-13 10:52:09 -08:00
0ada23a5fb fix(compiler-cli): only pass canonical genfile paths to compiler host (#27062)
In a more specific scenario: Considering people use a custom TypeScript compiler host with `NGC`, they _could_ expect only posix paths in methods like `writeFile`. This at first glance sounds like a trivial issue that should be just fixed by the actual compiler host, but usually TypeScript internal API's just pass around posix normalized paths, and therefore it would be good to follow the same standards when passing JSON genfiles to the `CompilerHost`.

For normal TypeScript files (and TS genfiles), this is already consistent because those will be handled by the actual TypeScript `Program` (see `emitCallback`).

PR Close #27062
2018-11-13 10:51:19 -08:00
6b1780d77e build: avoid writing \r in windows (#26888)
In windows when using readFile `\n` are replaced with `\r\n` which causes issues when comparing golden files

PR Close #26888
2018-11-13 10:50:21 -08:00
7865abf667 build: fix api guardian path for windows (#26888)
At the moment, `path.posix.relative` will break paths in windows as it will return something like
```
Error: Source file "../C:/users/alag/_bazel_alag/3tbqurya/execroot/angular/bazel-out/x64_windows-fastbuild/bin/packages/core/core.d.ts" not found
```

PR Close #26888
2018-11-13 10:50:21 -08:00
efa443bba3 docs: fix typo (#26878)
PR Close #26878
2018-11-13 10:49:35 -08:00
f5a0ec0d7c fix(ivy): ngcc should not fail on invalid package.json (#26539)
Some package.json files may have invalid JSON, for example package.json blueprints from `@schematics/angular` (see https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/workspace/files/package.json).

This makes ngcc more resilient, by simpling logging a warning if an error is encountered, instead of failing as it does right now.

PR Close #26539
2018-11-13 10:48:31 -08:00
5247594e86 fix(ivy): add support for providers in TestBed.configureCompiler (#27066)
Adds support for the `providers` that are passed in through `TestBed.configureCompiler` and scopes the error only if the consumer has passed in `useJit`.

PR Close #27066
2018-11-12 15:33:56 -08:00
095b6e8113 refactor(ivy): abstract all styling-related compiler logic into a shared class (#27043)
PR Close #27043
2018-11-12 15:32:48 -08:00
8b9249a670 fix(ivy): host bindings should support content children and content hooks (#27065)
PR Close #27065
2018-11-12 14:26:06 -08:00
f80c6008af docs: Fix wrong quoting in SSR guide (#27057)
PR Close #27057
2018-11-12 12:52:32 -08:00
b278ea1f09 docs: fix typo in toh-pt0.md (#27044)
PR Close #27044
2018-11-12 12:51:53 -08:00
1810cdf2c3 fix(ivy): compiler should generate restoreView() for local refs in listeners (#27034)
PR Close #27034
2018-11-12 12:50:58 -08:00
97ef8ae9e7 fix(ivy): let ngcc not consider deep imports as missing dependencies (#27031)
This fixes an issue where packages would be skipped if they contained
e.g. RxJS 5 style imports such as
```
import { observeOn } from 'rxjs/operators/observeOn';
```

Given that no package.json file can be found at the imported path, the
dependency would be reported missing, causing the package to be skipped.

PR Close #27031
2018-11-12 12:50:06 -08:00
99c5db1fb1 docs(forms): update API reference for value accessors (#26946)
PR Close #26946
2018-11-12 12:48:33 -08:00
e5c9f7a507 fix(ivy): provide NgZone to ComponentRef (#26898)
Fixes the r3 `TestBed` not providing an `NgZone` to the `ComponentFixture`.

PR Close #26898
2018-11-12 12:47:07 -08:00
9729e8f4b9 docs: removed unused pull-left classes from hero form component (#26178)
PR Close #26178
2018-11-09 10:23:17 -08:00
78b6f88cb3 fix(ivy): TestBed should use an NgZone (#27015)
PR Close #27015
2018-11-09 09:56:36 -08:00
552836ebf0 fix(ivy): merged host bindings functions should take superclass hostVars into account (#27013)
PR Close #27013
2018-11-09 09:55:47 -08:00
2f36a9591d fix(ivy): providers should not be inherited (#27013)
PR Close #27013
2018-11-09 09:55:47 -08:00
83c9bff242 docs(core): cleanup todo notes in ContentChild documentation examples (#26543)
PR Close #26543
2018-11-09 09:47:26 -08:00
4efb460127 docs(router): add RxJS filter pipe operator to example (#25686)
This example for Angular 6 requires piping the RxJS filter operator in order to traverse the multitude of Router Events.

PR Close #25686
2018-11-09 09:46:11 -08:00
ac1988d8e6 docs: add missing instruction in HTTP section (#25715)
PR Close #25715
2018-11-09 09:44:11 -08:00
edb50a2f51 docs: override code formatting for CLI commands (#26619)
Closes 26614

PR Close #26619
2018-11-09 09:43:00 -08:00
c89045fb77 docs: update links to TC39 and observable proposal (#26715)
Before (404 Not Found):
http://www.ecma-international.org/memento/TC39.htm
After:
https://www.ecma-international.org/memento/tc39-m.htm

PR Close #26715
2018-11-09 09:41:57 -08:00
1258ec041d docs: Fix links (#27027)
Fix the links.
PR Close #27027
2018-11-09 09:40:44 -08:00
499e303ea3 test(ivy): add global utils to the public_api_guard test (#27008)
This API is part of our public api surface and needs to be monitored by the public_api_guard.

I also had to go back and mark all of the exported functions with @publicApi jsdoc tag.

PR Close #27008
2018-11-08 15:37:11 -08:00
e618032d53 feat(ivy): introduce getPlayers() to global utils (#27008)
PR Close #27008
2018-11-08 15:37:11 -08:00
b9eeb1c383 ci: exclude symbol golden files from build-and-ci pullapprove group (#27004)
these files are test data and note test infrastructure, so they don't belong to this group.

this means that the core group can now approve the golden symbol changes

PR Close #27004
2018-11-08 13:10:53 -08:00
1529ecd8fa docs: add Angular subreddit to resources.json (#26976)
Close #26941

PR Close #26976
2018-11-08 13:09:36 -08:00
51f26cb4e0 docs: add ngx-api-utils to resources (#26120)
PR Close #26120
2018-11-08 13:07:42 -08:00
0d972d9bbf docs: update hero search component to use input event (#26440)
PR Close #26440
2018-11-08 11:29:10 -08:00
19546c234e build: add 'yarn list-fixme-ivy-targets' to list all the fixme-ivy-* targest (#26834)
PR Close #26834
2018-11-08 11:09:00 -08:00
791d192e63 docs: correct https://angularconsole.com/ link (#26999)
PR Close #26999
2018-11-08 11:06:24 -08:00
15e671ec5a build(bazel): upgrade benchmarks to protractor_web_test_suite for CI without local chrome (#26908)
PR Close #26908
2018-11-07 16:47:39 -08:00
3567e81175 fix(ivy): restore missing match operation in View and Content Queries (#26847)
PR Close #26847
2018-11-07 16:29:19 -08:00
249 changed files with 17385 additions and 10902 deletions

View File

@ -24,6 +24,10 @@ build --symlink_prefix=/
# Performance: avoid stat'ing input files
build --watchfs
# Turn off legacy external runfiles
run --nolegacy_external_runfiles
test --nolegacy_external_runfiles
###############################
# Release support #
###############################

View File

@ -16,10 +16,6 @@ test --test_tag_filters=-manual
# (e.g. /etc/bazel.bazelrc vs. tools/bazel.rc)
build --announce_rc
# Create dist/bin symlink to $(bazel info bazel-bin)
# We use this when uploading artifacts after the build finishes
build --symlink_prefix=dist/
# Workaround https://github.com/bazelbuild/bazel/issues/3645
# Bazel doesn't calculate the memory ceiling correctly when running under Docker.
# Limit Bazel to consuming resources that fit in CircleCI "xlarge" class

View File

@ -90,25 +90,6 @@ jobs:
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
- run: yarn bazel test //... --build_tag_filters=-ivy-only,local --test_tag_filters=-ivy-only,local
# CircleCI will allow us to go back and view/download these artifacts from past builds.
# Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts.
# The destination keys need be format {projectName}/{context}/{fileName} so that the github-robot can process them for size calculations
# projectName should remain consistant to group files
# context and fileName can be almost anything (within usual URI rules)
# There should only be exactly 2 forward slashes in the path
# This is so they're backwards compatiable with the existing data we have on bundle sizes
- store_artifacts:
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js
destination: core/hello_world/bundle
- store_artifacts:
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js
destination: core/todo/bundle
- store_artifacts:
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js.br
destination: core/hello_world/bundle.br
- store_artifacts:
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js.br
destination: core/todo/bundle.br
- save_cache:
key: *cache_key
paths:
@ -129,7 +110,29 @@ jobs:
- *yarn_install
- *setup_bazel_remote_execution
- run: yarn test-ivy-aot //...
# We need to explicitly specify the --symlink_prefix option because otherwise we would
# not be able to easily find the output bin directory when uploading artifacts for size
# measurements.
- run: yarn test-ivy-aot //... --symlink_prefix=dist/
# Publish bundle artifacts which will be used to calculate the size change. **Note**: Make
# sure that the size plugin from the Angular robot fetches the artifacts from this CircleCI
# job (see .github/angular-robot.yml). Additionally any artifacts need to be stored with the
# following path format: "{projectName}/{context}/{fileName}". This format is necessary
# because otherwise the bot is not able to pick up the artifacts from CircleCI. See:
# https://github.com/angular/github-robot/blob/master/functions/src/plugins/size.ts#L392-L394
- store_artifacts:
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js
destination: core/hello_world/bundle
- store_artifacts:
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js
destination: core/todo/bundle
- store_artifacts:
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js.br
destination: core/hello_world/bundle.br
- store_artifacts:
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js.br
destination: core/todo/bundle.br
test_aio:
<<: *job_defaults

View File

@ -15,7 +15,7 @@ Existing issues often contain information about workarounds, resolution, or prog
# 🚀 feature request
### Releavant Package
### Relevant Package
<!-- Can you pin-point one or more @angular/* packages the are relevant for this feature request? -->
<!-- ✍edit: --> This feature request is for @angular/....

View File

@ -4,7 +4,7 @@
size:
disabled: false
maxSizeIncrease: 2000
circleCiStatusName: "ci/circleci: test"
circleCiStatusName: "ci/circleci: test_ivy_aot"
# options for the merge plugin
merge:

2
.gitignore vendored
View File

@ -1,7 +1,7 @@
.DS_STORE
/dist/
bazel-*
/bazel-*
e2e_test.*
node_modules
bower_components

View File

@ -108,8 +108,9 @@ groups:
- "*.lock"
- "tools/*"
exclude:
- "tools/public_api_guard/*"
- "aio/*"
- "packages/core/test/bundling/*"
- "tools/public_api_guard/*"
users:
- IgorMinar #primary
- alexeagle

View File

@ -1,3 +1,74 @@
<a name="7.1.0"></a>
# [7.1.0](https://github.com/angular/angular/compare/7.1.0-rc.0...7.1.0) (2018-11-21)
### Bug Fixes
* **core:** allow null value for renderer setElement(…) ([#17065](https://github.com/angular/angular/issues/17065)) ([ff15043](https://github.com/angular/angular/commit/ff15043)), closes [#13686](https://github.com/angular/angular/issues/13686)
* **router:** fix regression where navigateByUrl promise didn't resolve on CanLoad failure ([#26455](https://github.com/angular/angular/issues/26455)) ([1c9b065](https://github.com/angular/angular/commit/1c9b065)), closes [#26284](https://github.com/angular/angular/issues/26284)
* **service-worker:** clean up caches from old SW versions ([#26319](https://github.com/angular/angular/issues/26319)) ([2326b9c](https://github.com/angular/angular/commit/2326b9c))
* **upgrade:** properly destroy upgraded component elements and descendants ([#26209](https://github.com/angular/angular/issues/26209)) ([071934e](https://github.com/angular/angular/commit/071934e)), closes [#26208](https://github.com/angular/angular/issues/26208)
* **compiler:** generate inputs with aliases properly ([#26774](https://github.com/angular/angular/issues/26774)) ([19fcfc3](https://github.com/angular/angular/commit/19fcfc3))
* **compiler:** generate relative paths only in summary file errors ([#26759](https://github.com/angular/angular/issues/26759)) ([56f44be](https://github.com/angular/angular/commit/56f44be))
* **core:** ignore comment nodes under unsafe elements ([#25879](https://github.com/angular/angular/issues/25879)) ([d5cbcef](https://github.com/angular/angular/commit/d5cbcef))
* **core:** Remove static dependency from [@angular](https://github.com/angular)/core to [@angular](https://github.com/angular)/compiler ([#26734](https://github.com/angular/angular/issues/26734)) ([d042c4a](https://github.com/angular/angular/commit/d042c4a))
* **core:** support computed base class in metadata inheritance ([#24014](https://github.com/angular/angular/issues/24014)) ([95743e3](https://github.com/angular/angular/commit/95743e3))
* **bazel:** unknown replay compiler error in windows ([#26711](https://github.com/angular/angular/issues/26711)) ([aed95fd](https://github.com/angular/angular/commit/aed95fd))
* **core:** ensure that `ɵdefineNgModule` is available in flat-file formats ([#26403](https://github.com/angular/angular/issues/26403)) ([a64859b](https://github.com/angular/angular/commit/a64859b))
* **router:** remove type bludgeoning of context and outlet when running CanDeactivate ([#26496](https://github.com/angular/angular/issues/26496)) ([496372d](https://github.com/angular/angular/commit/496372d)), closes [#18253](https://github.com/angular/angular/issues/18253)
* **service-worker:** add typing to public api guard and fix lint errors ([#25860](https://github.com/angular/angular/issues/25860)) ([1061875](https://github.com/angular/angular/commit/1061875))
* **upgrade:** improve downgrading-related error messages ([#26217](https://github.com/angular/angular/issues/26217)) ([7dbc103](https://github.com/angular/angular/commit/7dbc103))
* **upgrade:** make typings compatible with older AngularJS typings ([#26880](https://github.com/angular/angular/issues/26880)) ([64647af](https://github.com/angular/angular/commit/64647af)), closes [#26420](https://github.com/angular/angular/issues/26420)
* **compiler-cli:** add missing tslib dependency ([#27063](https://github.com/angular/angular/issues/27063)) ([c31e78f](https://github.com/angular/angular/commit/c31e78f))
* **compiler-cli:** only pass canonical genfile paths to compiler host ([#27062](https://github.com/angular/angular/issues/27062)) ([0ada23a](https://github.com/angular/angular/commit/0ada23a))
* **router:** add `relativeLinkResolution` to `recognize` operator ([#26990](https://github.com/angular/angular/issues/26990)) ([a752971](https://github.com/angular/angular/commit/a752971)), closes [#26983](https://github.com/angular/angular/issues/26983)
### Features
* **bazel:** Bazel workspace schematics ([#26971](https://github.com/angular/angular/issues/26971)) ([b07bd30](https://github.com/angular/angular/commit/b07bd30))
* **router:** add prioritizedGuardValue operator optimization and allowing UrlTree return from guard ([#26478](https://github.com/angular/angular/issues/26478)) ([fdfedce](https://github.com/angular/angular/commit/fdfedce))
* **compiler:** ability to mark an InvokeFunctionExpr as pure ([#26860](https://github.com/angular/angular/issues/26860)) ([4dfa71f](https://github.com/angular/angular/commit/4dfa71f))
* **forms:** add updateOn option to FormBuilder ([#24599](https://github.com/angular/angular/issues/24599)) ([e9e804f](https://github.com/angular/angular/commit/e9e804f))
* **router:** allow guards to return UrlTree as well as boolean ([#26521](https://github.com/angular/angular/issues/26521)) ([081f95c](https://github.com/angular/angular/commit/081f95c))
* **router:** allow redirect from guards by returning UrlTree ([#26521](https://github.com/angular/angular/issues/26521)) ([152ca66](https://github.com/angular/angular/commit/152ca66))
* **router:** guard returning UrlTree cancels current navigation and redirects ([#26521](https://github.com/angular/angular/issues/26521)) ([4e9f2e5](https://github.com/angular/angular/commit/4e9f2e5)), closes [#24618](https://github.com/angular/angular/issues/24618)
* **service-worker:** add typing for messagesClicked in SwPush service ([#25860](https://github.com/angular/angular/issues/25860)) ([c78c221](https://github.com/angular/angular/commit/c78c221))
* **service-worker:** close notifications and focus window on click ([#25860](https://github.com/angular/angular/issues/25860)) ([f5d5a3d](https://github.com/angular/angular/commit/f5d5a3d))
* **service-worker:** handle 'notificationclick' events ([#25860](https://github.com/angular/angular/issues/25860)) ([cf6ea28](https://github.com/angular/angular/commit/cf6ea28)), closes [#20956](https://github.com/angular/angular/issues/20956) [#22311](https://github.com/angular/angular/issues/22311)
* **upgrade:** support downgrading multiple modules ([#26217](https://github.com/angular/angular/issues/26217)) ([93837e9](https://github.com/angular/angular/commit/93837e9)), closes [#26062](https://github.com/angular/angular/issues/26062)
* **router:** add pathParamsChange mode for runGuardsAndResolvers ([#26861](https://github.com/angular/angular/issues/26861)) ([bf6ac6c](https://github.com/angular/angular/commit/bf6ac6c)), closes [#18253](https://github.com/angular/angular/issues/18253)
<a name="7.1.0-rc.0"></a>
# [7.1.0-rc.0](https://github.com/angular/angular/compare/7.1.0-beta.2...7.1.0-rc.0) (2018-11-14)
### Bug Fixes
* **compiler-cli:** add missing tslib dependency ([#27063](https://github.com/angular/angular/issues/27063)) ([c31e78f](https://github.com/angular/angular/commit/c31e78f))
* **compiler-cli:** only pass canonical genfile paths to compiler host ([#27062](https://github.com/angular/angular/issues/27062)) ([0ada23a](https://github.com/angular/angular/commit/0ada23a))
* **router:** add `relativeLinkResolution` to `recognize` operator ([#26990](https://github.com/angular/angular/issues/26990)) ([a752971](https://github.com/angular/angular/commit/a752971)), closes [#26983](https://github.com/angular/angular/issues/26983)
### Features
* **router:** add pathParamsChange mode for runGuardsAndResolvers ([#26861](https://github.com/angular/angular/issues/26861)) ([bf6ac6c](https://github.com/angular/angular/commit/bf6ac6c)), closes [#18253](https://github.com/angular/angular/issues/18253)
<a name="7.0.4"></a>
## [7.0.4](https://github.com/angular/angular/compare/7.0.3...7.0.4) (2018-11-14)
### Bug Fixes
* **compiler-cli:** add missing tslib dependency ([#27063](https://github.com/angular/angular/issues/27063)) ([4348c47](https://github.com/angular/angular/commit/4348c47))
* **compiler-cli:** only pass canonical genfile paths to compiler host ([#27062](https://github.com/angular/angular/issues/27062)) ([188e9ce](https://github.com/angular/angular/commit/188e9ce))
* **router:** add `relativeLinkResolution` to `recognize` operator ([#26990](https://github.com/angular/angular/issues/26990)) ([d304427](https://github.com/angular/angular/commit/d304427)), closes [#26983](https://github.com/angular/angular/issues/26983)
<a name="7.1.0-beta.2"></a>
# [7.1.0-beta.2](https://github.com/angular/angular/compare/7.1.0-beta.1...7.1.0-beta.2) (2018-11-07)

View File

@ -53,7 +53,7 @@ node_repositories(
node_version = "10.9.0",
package_json = ["//:package.json"],
preserve_symlinks = True,
yarn_version = "1.9.2",
yarn_version = "1.12.1",
)
yarn_install(

View File

@ -11,13 +11,13 @@ Install the CLI using the `npm` package manager:
npm install -g @angular/cli
</code-example>
For details about changes between versions, and information about updating from previous releases,
For details about changes between versions, and information about updating from previous releases,
see the Releases tab on GitHub: https://github.com/angular/angular-cli/releases
## Basic workflow
Invoke the tool on the command line through the `ng` executable.
Online help is available on the command line.
Invoke the tool on the command line through the `ng` executable.
Online help is available on the command line.
Enter the following to list commands or options for a given command (such as [generate](cli/generate)) with a short description.
<code-example format="." language="bash">
@ -33,36 +33,35 @@ cd my-first-project
ng serve
</code-example>
In your browser, open http://localhost:4200/ to see the new app run.
In your browser, open http://localhost:4200/ to see the new app run.
When you use the [ng serve](cli/serve) command to build an app and serve it locally, the server automatically rebuilds the app and reloads the page when you change any of the source files.
## Workspaces and project files
The [ng new](cli/new) command creates an *Angular workspace* folder and generates a new app skeleton.
The [ng new](cli/new) command creates an *Angular workspace* folder and generates a new app skeleton.
A workspace can contain multiple apps and libraries.
The initial app created by the [ng new](cli/new) command is at the top level of the workspace.
The initial app created by the [ng new](cli/new) command is at the top level of the workspace.
When you generate an additional app or library in a workspace, it goes into a `projects/` subfolder.
A newly generated app contains the source files for a root module, with a root component and template.
A newly generated app contains the source files for a root module, with a root component and template.
Each app has a `src` folder that contains the logic, data, and assets.
You can edit the generated files directly, or add to and modify them using CLI commands.
Use the [ng generate](cli/generate) command to add new files for additional components and services, and code for new pipes, directives, and so on.
You can edit the generated files directly, or add to and modify them using CLI commands.
Use the [ng generate](cli/generate) command to add new files for additional components and services, and code for new pipes, directives, and so on.
Commands such as [add](cli/add) and [generate](cli/generate), which create or operate on apps and libraries, must be executed from within a workspace or project folder.
* See more about the [Workspace file structure](guide/file-structure).
### Workspace and project configuration
A single workspace configuration file, `angular.json`, is created at the top level of the workspace.
A single workspace configuration file, `angular.json`, is created at the top level of the workspace.
This is where you can set per-project defaults for CLI command options, and specify configurations to use when the CLI builds a project for different targets.
The [ng config](cli/config) command lets you set and retrieve configuration values from the command line, or you can edit the `angular.json` file directly.
Note that option names in the configuration file must use [camelCase](guide/glossary#case-types), while option names supplied to commands can use either camelCase or dash-case.
The [ng config](cli/config) command lets you set and retrieve configuration values from the command line, or you can edit the `angular.json` file directly.
Note that option names in the configuration file must use [camelCase](guide/glossary#case-types), while option names supplied to commands can use either camelCase or dash-case.
* See more about [Workspace Configuration](guide/workspace-config).
* See the [complete schema](https://github.com/angular/angular-cli/wiki/angular-workspace) for `angular.json`.
<!-- * Learn more about *configuration options for Angular(links to new guide or topics TBD)*. -->
## CLI command-language syntax
@ -72,20 +71,20 @@ Command syntax is shown as follows:
* Most commands, and some options, have aliases. Aliases are shown in the syntax statement for each command.
* Option names are prefixed with a double dash (--).
Option aliases are prefixed with a single dash (-).
* Option names are prefixed with a double dash (--).
Option aliases are prefixed with a single dash (-).
Arguments are not prefixed.
For example: `ng build my-app -c production`
* Typically, the name of a generated artifact can be given as an argument to the command or specified with the --name option.
* Typically, the name of a generated artifact can be given as an argument to the command or specified with the --name option.
* Argument and option names can be given in either
[camelCase or dash-case](guide/glossary#case-types).
* Argument and option names can be given in either
[camelCase or dash-case](guide/glossary#case-types).
`--myOptionName` is equivalent to `--my-option-name`.
### Boolean and enumerated options
Boolean options have two forms: `--thisOption` sets the flag, `--noThisOption` clears it.
Boolean options have two forms: `--thisOption` sets the flag, `--noThisOption` clears it.
If neither option is supplied, the flag remains in its default state, as listed in the reference documentation.
Allowed values are given with each enumerated option description, with the default value in **bold**.
@ -96,7 +95,6 @@ Options that specify files can be given as absolute paths, or as paths relative
### Schematics
The [ng generate](cli/generate) and [ng add](cli/add) commands take as an argument the artifact or library to be generated or added to the current project.
In addition to any general options, each artifact or library defines its own options in a *schematic*.
Schematic options are supplied to the command in the same format as immediate command options.
The [ng generate](cli/generate) and [ng add](cli/add) commands take as an argument the artifact or library to be generated or added to the current project.
In addition to any general options, each artifact or library defines its own options in a *schematic*.
Schematic options are supplied to the command in the same format as immediate command options.

View File

@ -72,15 +72,15 @@
<h2>You submitted the following:</h2>
<div class="row">
<div class="col-xs-3">Name</div>
<div class="col-xs-9 pull-left">{{ model.name }}</div>
<div class="col-xs-9">{{ model.name }}</div>
</div>
<div class="row">
<div class="col-xs-3">Alter Ego</div>
<div class="col-xs-9 pull-left">{{ model.alterEgo }}</div>
<div class="col-xs-9">{{ model.alterEgo }}</div>
</div>
<div class="row">
<div class="col-xs-3">Power</div>
<div class="col-xs-9 pull-left">{{ model.power }}</div>
<div class="col-xs-9">{{ model.power }}</div>
</div>
<br>
<button class="btn btn-primary" (click)="submitted=false">Edit</button>

View File

@ -2,7 +2,7 @@
<h4>Hero Search</h4>
<!-- #docregion input -->
<input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
<input #searchBox id="search-box" (input)="search(searchBox.value)" />
<!-- #enddocregion input -->
<ul class="search-result">

View File

@ -1,7 +1,11 @@
// #docregion , init
import { InMemoryDbService } from 'angular-in-memory-web-api';
import { Hero } from './hero';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class InMemoryDataService implements InMemoryDbService {
createDb() {
const heroes = [

View File

@ -1,7 +1,7 @@
<div id="search-component">
<h4>Hero Search</h4>
<input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
<input #searchBox id="search-box" (input)="search(searchBox.value)" />
<ul class="search-result">
<li *ngFor="let hero of heroes | async" >

View File

@ -56,7 +56,9 @@ A component and its template together define a *view*. A component can contain a
When you create a component, it's associated directly with a single view, called the *host view*. The host view can be the root of a view hierarchy, which can contain *embedded views*, which are in turn the host views of other components. Those components can be in the same NgModule, or can be imported from other NgModules. Views in the tree can be nested to any depth.
<div class="alert is-helpful">
**Note:** The hierarchical structure of views is a key factor in the way Angular detects and responds to changes in the DOM and app data.
**Note:** The hierarchical structure of views is a key factor in the way Angular detects and responds to changes in the DOM and app data.
</div>
## NgModules and JavaScript modules

View File

@ -48,7 +48,7 @@ Similarly, use the `@Injectable()` decorator to indicate that a component or oth
* An injector creates dependencies, and maintains a *container* of dependency instances that it reuses if possible.
* A *provider* is an object that tell an injector how to obtain or create a dependency.
* A *provider* is an object that tells an injector how to obtain or create a dependency.
For any dependency that you need in your app, you must register a provider with the app's injector,
so that the injector can use the provider to create new instances.

View File

@ -32,11 +32,11 @@ For example:
The base file `environment.ts`, contains the default environment settings. For example:
<code-example language="none" class="code-shell">
```
export const environment = {
production: false
};
</code-example>
```
The `build` command uses this as the build target when no environment is specified.
You can add further variables, either as additional properties on the environment object, or as separate objects.
@ -144,7 +144,11 @@ You can add additional configurations as required. To add a staging environment,
You can add more configuration options to this target environment as well.
Any option that your build supports can be overridden in a build target configuration.
To build using the staging configuration, run `ng build --configuration=staging`.
To build using the staging configuration, run the following command:
<code-example language="sh" class="code-shell">
ng build --configuration=staging
</code-example>
You can also configure the `serve` command to use the targeted build configuration if you add it to the "serve:configurations" section of `angular.json`:

View File

@ -41,7 +41,7 @@ When the workspace file structure is in place, you can use the `ng generate` com
<div class="alert is-helpful">
Besides using the CLI on the command line, you can also use an interactive development environment like [Angular Console](https://angular.console.com), or manipulate files directly in the app's source folder and configuration files.
Besides using the CLI on the command line, you can also use an interactive development environment like [Angular Console](https://angularconsole.com/), or manipulate files directly in the app's source folder and configuration files.
</div>

View File

@ -81,7 +81,7 @@ The following support packages are included as dependencies in the default `pack
Package name | Description
---------------------------------------- | --------------------------------------------------
[**rxjs**](https://github.com/ReactiveX/rxjs) | Many Angular APIs return [_observables_](guide/glossary#observable). RxJS is an implementation of the proposed [Observables specification](https://github.com/zenparsing/es-observable) currently before the [TC39](http://www.ecma-international.org/memento/TC39.htm) committee, which determines standards for the JavaScript language.
[**rxjs**](https://github.com/ReactiveX/rxjs) | Many Angular APIs return [_observables_](guide/glossary#observable). RxJS is an implementation of the proposed [Observables specification](https://github.com/tc39/proposal-observable) currently before the [TC39](https://www.ecma-international.org/memento/tc39-m.htm) committee, which determines standards for the JavaScript language.
[**zone.js**](https://github.com/angular/zone.js) | Angular relies on zone.js to run Angular's change detection processes when native JavaScript operations raise events. Zone.js is an implementation of a [specification](https://gist.github.com/mhevery/63fdcdf7c65886051d55) currently before the [TC39](http://www.ecma-international.org/memento/TC39.htm) committee that determines standards for the JavaScript language.

View File

@ -62,7 +62,7 @@ Your app may have to launch faster to engage these users before they decide to d
With Angular Universal, you can generate landing pages for the app that look like the complete app.
The pages are pure HTML, and can display even if JavaScript is disabled.
The pages don't handle browser events, but they _do_ support navigation through the site using `[routerLink](guide/router#router-link)`.
The pages don't handle browser events, but they _do_ support navigation through the site using [`routerLink`](guide/router#router-link).
In practice, you'll serve a static version of the landing page to hold the user's attention.
At the same time, you'll load the full Angular app behind it.

View File

@ -78,7 +78,7 @@ Each target object specifies the `builder` for that target, which is the npm pac
}
</code-example>
* The `architect/build` section configures defaults for options of the `ng build` command. See [Build target]{#build-target} below for more information.
* The `architect/build` section configures defaults for options of the `ng build` command. See [Build target](#build-target) below for more information.
* The `architect/serve` section overrides build defaults and supplies additional serve defaults for the `ng serve` command. In addition to the options available for the `ng build` command, it adds options related to serving the app.

View File

@ -22,6 +22,12 @@
"rev": true,
"title": "Made with Angular",
"url": "https://www.madewithangular.com/"
},
"angular-subreddit": {
"desc": "An Angular-dedicated subreddit.",
"rev": true,
"title": "Angular Subreddit",
"url": "https://www.reddit.com/r/Angular2/"
}
}
},
@ -149,6 +155,13 @@
"rev": true,
"title": "AngularCommerce",
"url": "https://github.com/NodeArt/angular-commerce"
},
"ngx-api-utils": {
"desc": "ngx-api-utils is a lean library of utilities and helpers to quickly integrate any HTTP API (REST, Ajax, and any other) with Angular.",
"logo": "",
"rev": true,
"title": "ngx-api-utils",
"url": "https://github.com/ngx-api-utils/ngx-api-utils"
}
}
},

View File

@ -19,7 +19,7 @@ To set up your development environment, follow these instructions in [Getting St
<div class="alert is-helpful">
**Note:**: You do not need to complete the entire Getting Started. After you complete the above two sections of Getting Started, your environment is set up. Continue below to create the Tour of Heroes workspace and an initial app project.
**Note:** You do not need to complete the entire Getting Started. After you complete the above two sections of Getting Started, your environment is set up. Continue below to create the Tour of Heroes workspace and an initial app project.
</div>

View File

@ -77,8 +77,13 @@ _after importing the `HttpClientModule`_,
The `forRoot()` configuration method takes an `InMemoryDataService` class
that primes the in-memory database.
The _Tour of Heroes_ sample creates such a class
`src/app/in-memory-data.service.ts` which has the following content:
The class `src/app/in-memory-data.service.ts` is generated by the following command:
<code-example language="sh" class="code-shell">
ng generate service InMemoryData
</code-example>
This class has the following content:
<code-example path="toh-pt6/src/app/in-memory-data.service.ts" region="init" header="src/app/in-memory-data.service.ts" linenums="false"></code-example>
@ -461,7 +466,7 @@ Replace the generated `HeroSearchComponent` _template_ with a text box and a lis
Add private CSS styles to `hero-search.component.css`
as listed in the [final code review](#herosearchcomponent) below.
As the user types in the search box, a *keyup* event binding calls the component's `search()`
As the user types in the search box, an *input* event binding calls the component's `search()`
method with the new search box value.
{@a asyncpipe}
@ -511,7 +516,7 @@ You can also push values into that `Observable` by calling its `next(value)` met
as the `search()` method does.
The `search()` method is called via an _event binding_ to the
textbox's `keystroke` event.
textbox's `input` event.
<code-example path="toh-pt6/src/app/hero-search/hero-search.component.html" region="input"></code-example>

View File

@ -18,7 +18,7 @@
"build-for": "yarn ~~build --configuration",
"prebuild-local": "yarn setup-local",
"build-local": "yarn ~~build --configuration=stable",
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js b50950b97",
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js a176d127a",
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
"test": "yarn check-env && ng test",
"pree2e": "yarn check-env && yarn update-webdriver",
@ -71,7 +71,7 @@
},
"engines": {
"node": ">=10.9.0 <11.0.0",
"yarn": ">=1.9.2 <2.0.0"
"yarn": ">=1.10.1 <1.13.0"
},
"private": true,
"dependencies": {

View File

@ -1,5 +1,7 @@
.cli-name {
font-weight: bold;
.kwd { color: initial } /* override code format */
}
.cli-option-syntax {

View File

@ -1,9 +1,5 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
// Reflect.metadata polyfill is only needed in the JIT mode which we use only for unit tests
import 'core-js/es6/reflect';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {

View File

@ -12,7 +12,7 @@
},
"engines": {
"node": ">=10.9.0 <11.0.0",
"yarn": ">=1.9.2 <2.0.0"
"yarn": ">=1.10.1 <1.13.0"
},
"keywords": [],
"author": "",

View File

@ -114,23 +114,22 @@ All the tests are executed on our Continuous Integration infrastructure and a PR
## <a name="clang-format"></a> Formatting your source code
Angular uses [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to format the source code. If the source code
is not properly formatted, the CI will fail and the PR can not be merged.
Angular uses [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to format the source code.
If the source code is not properly formatted, the CI will fail and the PR can not be merged.
You can automatically format your code by running:
- `gulp format`: format all source code
- `gulp format:changed`: re-format only edited source code.
``` shell
$ gulp format
```
There is a handy [clang-format extension](https://marketplace.visualstudio.com/items?itemName=xaver.clang-format)
for Visual Studio Code. Use the following settings to format your code when you save a file:
A better way is to set up your IDE to format the changed file on each file save.
### VS Code
1. Install [Clang-Format](https://marketplace.visualstudio.com/items?itemName=xaver.clang-format) extension for VS Code.
2. Open `settings.json` in your workspace and add these lines:
```json
{
"editor.formatOnSave": true,
"clang-format.executable": "${workspaceRoot}/node_modules/.bin/clang-format"
}
"files.autoSave": "onFocusChange",
"editor.formatOnSave": true,
"clang-format.executable": "PATH_TO_YOUR_WORKSPACE/angular/node_modules/.bin/clang-format",
```
## Linting/verifying your source code
@ -167,3 +166,33 @@ $ ./scripts/publish/publish-build-artifacts.sh [github username]
The script will publish the build snapshot to a branch with the same name as your current branch,
and create it if it doesn't exist.
## Bazel support
### VS Code
1. Install [Bazel](https://marketplace.visualstudio.com/items?itemName=DevonDCarew.bazel-code) extension for VS Code.
2. Open `settings.json` in your workspace and add these lines:
```json
"files.associations": {
"*.bazel": "bazel"
},
```
## General IDE settings
### VS Code
1. Open `settings.json` in your workspace and add these lines:
```json
"editor.tabSize": 2,
"files.exclude": {
"bazel-out": true,
".idea": true,
".bowerrc": true,
".circleci": true,
".github": true,
"dist/**": true,
"node_modules/**": true,
".rpt2_cache": true,
".vscode": true
},
```

View File

@ -3,7 +3,7 @@
"master": {
"uncompressed": {
"runtime": 1497,
"main": 181839,
"main": 185238,
"polyfills": 59608
}
}

View File

@ -36,7 +36,7 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_reposi
check_bazel_version("0.17.0")
node_repositories(
node_version = "10.9.0",
yarn_version = "1.9.2",
yarn_version = "1.12.1",
)
yarn_install(

View File

@ -13,8 +13,8 @@
"@angular/bazel": "file:../angular/dist/packages-dist/bazel",
"@angular/compiler": "file:../angular/dist/packages-dist/compiler",
"@angular/compiler-cli": "file:../angular/dist/packages-dist/compiler-cli",
"@bazel/karma": "0.20.3",
"@bazel/typescript": "0.20.3",
"@bazel/karma": "0.21.0",
"@bazel/typescript": "0.21.0",
"@types/jasmine": "2.8.8",
"@types/source-map": "0.5.1",
"protractor": "5.1.2",

View File

@ -24,13 +24,19 @@ ivy-ngcc
if [[ $? != 0 ]]; then exit 1; fi
# Did it compile @angular/core/ApplicationModule correctly?
grep "ApplicationModule.ngModuleDef = ɵdefineNgModule" node_modules/@angular/core/fesm2015/core.js
grep "ApplicationModule.ngModuleDef = defineNgModule" node_modules/@angular/core/fesm2015/core.js
if [[ $? != 0 ]]; then exit 1; fi
grep "ApplicationModule.ngModuleDef = ɵdefineNgModule" node_modules/@angular/core/fesm5/core.js
grep "ApplicationModule.ngModuleDef = defineNgModule" node_modules/@angular/core/fesm5/core.js
if [[ $? != 0 ]]; then exit 1; fi
grep "ApplicationModule.ngModuleDef = ɵngcc0.ɵdefineNgModule" node_modules/@angular/core/esm2015/src/application_module.js
grep "ApplicationModule.ngModuleDef = ɵngcc0.defineNgModule" node_modules/@angular/core/esm2015/src/application_module.js
if [[ $? != 0 ]]; then exit 1; fi
grep "ApplicationModule.ngModuleDef = ɵngcc0.ɵdefineNgModule" node_modules/@angular/core/esm5/src/application_module.js
grep "ApplicationModule.ngModuleDef = ɵngcc0.defineNgModule" node_modules/@angular/core/esm5/src/application_module.js
if [[ $? != 0 ]]; then exit 1; fi
# Did it transform @angular/core typing files correctly?
grep "import [*] as ɵngcc0 from './r3_symbols';" node_modules/@angular/core/src/application_module.d.ts
if [[ $? != 0 ]]; then exit 1; fi
grep "static ngInjectorDef: ɵngcc0.InjectorDef<ApplicationModule>;" node_modules/@angular/core/src/application_module.d.ts
if [[ $? != 0 ]]; then exit 1; fi
# Can it be safely run again (as a noop)?

View File

@ -1,7 +1,7 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ng_module", "ng_rollup_bundle")
load("//packages/bazel:index.bzl", "protractor_web_test")
load("//packages/bazel:index.bzl", "protractor_web_test_suite")
load("@build_bazel_rules_typescript//:defs.bzl", "ts_devserver")
ng_module(
@ -50,7 +50,7 @@ ts_devserver(
tags = ["ivy-only"],
)
protractor_web_test(
protractor_web_test_suite(
name = "perf",
configuration = "//:protractor-perf.conf.js",
data = [
@ -64,8 +64,6 @@ protractor_web_test(
on_prepare = ":protractor.on_prepare.js",
server = ":devserver",
tags = [
"fixme-ivy-aot",
"fixme-ivy-jit",
"ivy-only",
],
deps = [

View File

@ -1,7 +1,7 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ng_module", "ng_rollup_bundle")
load("//packages/bazel:index.bzl", "protractor_web_test")
load("//packages/bazel:index.bzl", "protractor_web_test_suite")
load("@build_bazel_rules_typescript//:defs.bzl", "ts_devserver")
ng_module(
@ -48,7 +48,7 @@ ts_devserver(
tags = ["ivy-only"],
)
protractor_web_test(
protractor_web_test_suite(
name = "perf",
configuration = "//:protractor-perf.conf.js",
data = [
@ -58,8 +58,6 @@ protractor_web_test(
on_prepare = ":protractor.on_prepare.js",
server = ":devserver",
tags = [
"fixme-ivy-aot",
"fixme-ivy-jit",
"ivy-only",
],
deps = [

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "7.1.0-beta.2",
"version": "7.1.0",
"private": true,
"branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps",
@ -9,7 +9,7 @@
"license": "MIT",
"engines": {
"node": ">=10.9.0 <11.0.0",
"yarn": ">=1.9.2 <2.0.0"
"yarn": ">=1.10.1 <1.13.0"
},
"repository": {
"type": "git",
@ -28,13 +28,16 @@
"commitmsg": "node ./scripts/git/commit-msg.js",
"test-ivy-aot": "bazel test --define=compile=aot --build_tag_filters=-no-ivy-aot,-fixme-ivy-aot --test_tag_filters=-no-ivy-aot,-fixme-ivy-aot",
"test-fixme-ivy-aot": "bazel test --define=compile=aot --build_tag_filters=-no-ivy-aot --test_tag_filters=-no-ivy-aot",
"list-fixme-ivy-targets": "bazel query --output=label 'attr(\"tags\", \"\\[.*fixme-ivy.*\\]\", //...) except kind(\"sh_binary\", //...) except kind(\"devmode_js_sources\", //...)' | sort",
"bazel": "bazel"
},
"// 1": "dependencies are used locally and by bazel",
"dependencies": {
"@angular-devkit/schematics": "^0.5.5",
"@bazel/typescript": "0.20.3",
"@schematics/angular": "^0.5.4",
"@angular-devkit/architect": "^0.10.6",
"@angular-devkit/core": "^7.0.4",
"@angular-devkit/schematics": "^7.0.4",
"@bazel/typescript": "0.21.0",
"@schematics/angular": "^7.0.4",
"@types/chokidar": "1.7.3",
"@types/convert-source-map": "^1.5.1",
"@types/diff": "^3.2.2",
@ -88,7 +91,7 @@
"devDependencies": {
"@bazel/bazel": "^0.18.1",
"@bazel/ibazel": "^0.1.1",
"@bazel/karma": "0.20.3",
"@bazel/karma": "0.21.0",
"@types/angular": "^1.6.47",
"@types/base64-js": "1.2.5",
"@types/jasminewd2": "^2.0.4",

View File

@ -7,14 +7,17 @@ npm_package(
"package.json",
"protractor-utils.js",
"//packages/bazel/src:package_assets",
"//packages/bazel/src/schematics:package_assets",
],
packages = [
"//packages/bazel/docs",
],
tags = ["release-with-framework"],
deps = [
"//packages/bazel/src/builders",
"//packages/bazel/src/ng_package:lib",
"//packages/bazel/src/ngc-wrapped:ngc_lib",
"//packages/bazel/src/protractor/utils",
"//packages/bazel/src/schematics/bazel-workspace",
],
)

View File

@ -25,15 +25,15 @@ def rules_angular_dependencies():
_maybe(
http_archive,
name = "build_bazel_rules_nodejs",
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.15.3.zip",
strip_prefix = "rules_nodejs-0.15.3",
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.16.2.zip",
strip_prefix = "rules_nodejs-0.16.2",
)
_maybe(
http_archive,
name = "build_bazel_rules_typescript",
url = "https://github.com/bazelbuild/rules_typescript/archive/8ea1a55cf5cf8be84ddfeefc0940769b80da792f.zip",
strip_prefix = "rules_typescript-8ea1a55cf5cf8be84ddfeefc0940769b80da792f",
url = "https://github.com/bazelbuild/rules_typescript/archive/0.21.0.zip",
strip_prefix = "rules_typescript-0.21.0",
)
# Needed for Remote Execution

View File

@ -12,7 +12,11 @@
},
"typings": "./src/ngc-wrapped/index.d.ts",
"dependencies": {
"@bazel/typescript": "^0.20.3",
"@angular-devkit/architect": "^0.10.6",
"@angular-devkit/core": "^7.0.4",
"@angular-devkit/schematics": "^7.0.4",
"@bazel/typescript": "^0.21.0",
"@schematics/angular": "^7.0.4",
"@types/node": "6.0.84",
"shelljs": "0.8.2",
"tsickle": "0.32.1"
@ -25,6 +29,8 @@
"type": "git",
"url": "https://github.com/angular/angular.git"
},
"builders": "./src/builders/builders.json",
"schematics": "./src/schematics/collection.json",
"ng-update": {
"packageGroup": "NG_UPDATE_PACKAGE_GROUP"
},

View File

@ -0,0 +1,30 @@
package(default_visibility = ["//visibility:public"])
filegroup(
name = "package_assets",
srcs = [
"builders.json",
],
visibility = ["//packages/bazel:__subpackages__"],
)
load("//tools:defaults.bzl", "ts_library")
ts_library(
name = "builders",
srcs = [
"bazel.ts",
"index.ts",
"schema.d.ts",
],
data = [
"schema.json",
],
deps = [
"@ngdeps//@angular-devkit/architect",
"@ngdeps//@angular-devkit/core",
"@ngdeps//@types/node",
"@rxjs",
"@rxjs//operators",
],
)

View File

@ -0,0 +1,43 @@
/**
* @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
*/
/// <reference types='node'/>
import {spawn, spawnSync} from 'child_process';
import {Observable, Subject} from 'rxjs';
export type Executable = 'bazel' | 'ibazel';
export type Command = 'build' | 'test' | 'run' | 'coverage' | 'query';
export function runBazel(
projectDir: string, executable: Executable, command: Command, workspaceTarget: string,
flags: string[]): Observable<void> {
const doneSubject = new Subject<void>();
const buildProcess = spawn(executable, [command, workspaceTarget, ...flags], {
cwd: projectDir,
stdio: 'inherit',
shell: false,
});
buildProcess.once('close', (code: number) => {
if (code === 0) {
doneSubject.next();
} else {
doneSubject.error(`${executable} failed with code ${code}.`);
}
});
return doneSubject.asObservable();
}
export function checkInstallation(executable: Executable, projectDir: string) {
const child = spawnSync(executable, ['version'], {
cwd: projectDir,
shell: false,
});
return child.status === 0;
}

View File

@ -0,0 +1,9 @@
{
"builders": {
"build": {
"class": "./index#Builder",
"schema": "./schema.json",
"description": "Executes Bazel on a target."
}
}
}

View File

@ -0,0 +1,40 @@
/**
* @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
*
* @fileoverview Bazel bundle builder
*/
import {BuildEvent, Builder as BuilderInterface, BuilderConfiguration, BuilderContext} from '@angular-devkit/architect';
import {getSystemPath, resolve} from '@angular-devkit/core';
import {Observable, of } from 'rxjs';
import {catchError, map, tap} from 'rxjs/operators';
import {checkInstallation, runBazel} from './bazel';
import {Schema} from './schema';
export class Builder implements BuilderInterface<Schema> {
constructor(private context: BuilderContext) {}
run(builderConfig: BuilderConfiguration<Partial<Schema>>): Observable<BuildEvent> {
const projectRoot = getSystemPath(resolve(this.context.workspace.root, builderConfig.root));
const targetLabel = builderConfig.options.targetLabel;
const executable = builderConfig.options.watch ? 'ibazel' : 'bazel';
if (!checkInstallation(executable, projectRoot)) {
throw new Error(
`Could not run ${executable}. Please make sure that the ` +
`"${executable}" command is available in the $PATH.`);
}
// TODO: Support passing flags.
return runBazel(
projectRoot, executable, builderConfig.options.bazelCommand !, targetLabel !,
[] /* flags */)
.pipe(map(() => ({success: true})), catchError(() => of ({success: false})), );
}
}

24
packages/bazel/src/builders/schema.d.ts vendored Normal file
View File

@ -0,0 +1,24 @@
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
// tslint:disable:no-global-tslint-disable
// tslint:disable
/**
* Options for Bazel Builder
*/
export interface Schema {
bazelCommand: BazelCommand;
/**
* Target to be executed under Bazel.
*/
targetLabel: string;
watch?: boolean;
}
export enum BazelCommand {
Build = 'build',
Run = 'run',
Test = 'test',
}

View File

@ -0,0 +1,29 @@
{
"$schema": "http://json-schema.org/schema",
"title": "Bazel builder schema",
"description": "Options for Bazel Builder",
"type": "object",
"properties": {
"targetLabel": {
"type": "string",
"description": "Target to be executed under Bazel."
},
"bazelCommand": {
"type": "string",
"enum": [
"run",
"build",
"test"
]
},
"watch": {
"type": "boolean",
"default": false
}
},
"additionalProperties": false,
"required": [
"targetLabel",
"bazelCommand"
]
}

View File

@ -21,9 +21,6 @@ const NGC_ASSETS = /\.(css|html|ngsummary\.json)$/;
const BAZEL_BIN = /\b(blaze|bazel)-out\b.*?\bbin\b/;
// TODO(alexeagle): probably not needed, see
// https://github.com/bazelbuild/rules_typescript/issues/28
const ALLOW_NON_HERMETIC_READS = true;
// Note: We compile the content of node_modules with plain ngc command line.
const ALL_DEPS_COMPILED_WITH_BAZEL = false;
@ -58,7 +55,6 @@ export function runOneBuild(args: string[], inputs?: {[path: string]: string}):
const compilerOpts = ng.createNgCompilerOptions(basePath, config, tsOptions);
const tsHost = ts.createCompilerHost(compilerOpts, true);
const {diagnostics} = compile({
allowNonHermeticReads: ALLOW_NON_HERMETIC_READS,
allDepsCompiledWithBazel: ALL_DEPS_COMPILED_WITH_BAZEL,
compilerOpts,
tsHost,
@ -84,9 +80,8 @@ export function relativeToRootDirs(filePath: string, rootDirs: string[]): string
return filePath;
}
export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true, compilerOpts,
tsHost, bazelOpts, files, inputs, expectedOuts, gatherDiagnostics}: {
allowNonHermeticReads: boolean,
export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost, bazelOpts, files,
inputs, expectedOuts, gatherDiagnostics}: {
allDepsCompiledWithBazel?: boolean,
compilerOpts: ng.CompilerOptions,
tsHost: ts.CompilerHost, inputs?: {[path: string]: string},
@ -104,7 +99,7 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
}
if (inputs) {
fileLoader = new CachedFileLoader(fileCache, allowNonHermeticReads);
fileLoader = new CachedFileLoader(fileCache);
// Resolve the inputs to absolute paths to match TypeScript internals
const resolvedInputs: {[path: string]: string} = {};
const inputKeys = Object.keys(inputs);
@ -186,8 +181,7 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
}
const bazelHost = new CompilerHost(
files, compilerOpts, bazelOpts, tsHost, fileLoader, allowNonHermeticReads,
generatedFileModuleResolver);
files, compilerOpts, bazelOpts, tsHost, fileLoader, generatedFileModuleResolver);
// Also need to disable decorator downleveling in the BazelHost in Ivy mode.
if (isInIvyMode) {

View File

@ -5,9 +5,6 @@
* 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
*/
const path = require('path');
const DEBUG = false;
const configPath = 'TMPL_config';
@ -32,15 +29,44 @@ function setConf(conf, name, value, msg) {
conf[name] = value;
}
function mergeCapabilities(conf, capabilities) {
if (conf.capabilities) {
if (conf.capabilities.browserName === capabilities.browserName) {
// there are capabilities to merge
if (capabilities.browserName === 'chrome') {
conf.capabilities.chromeOptions = conf.capabilities.chromeOptions || {};
conf.capabilities.chromeOptions.binary = capabilities.chromeOptions.binary;
conf.capabilities.chromeOptions.args = conf.capabilities.chromeOptions.args || [];
conf.capabilities.chromeOptions.args.push(...capabilities.chromeOptions.args);
console.warn(
`Your protractor configuration specifies capabilities for browser '${conf.capabilities.browserName}'
which will be merged with capabilities provided by Bazel resulting in:`,
JSON.stringify(conf.capabilities, null, 2));
} else {
// TODO(gmagolan): implement firefox support for protractor
throw new Error(
`Unexpected browserName ${capabilities.browserName} for capabilities merging`);
}
} else {
console.warn(
`Your protractor configuration specifies capabilities for browser '${conf.capabilities.browserName}' which will be overwritten by Bazel`);
conf.capabilities = capabilities;
}
} else {
conf.capabilities = capabilities;
}
}
let conf = {};
// Import the user's base protractor configuration if specified
if (configPath) {
const baseConf = require(configPath);
if (!baseConf.config) {
throw new Error('Invalid base protractor configration. Expected config to be exported.');
throw new Error('Invalid base protractor configuration. Expected config to be exported.');
}
conf = baseConf.config;
if (DEBUG) console.info(`Base protractor configuration: ${JSON.stringify(conf, null, 2)}`);
}
// Import the user's on prepare function if specified
@ -109,8 +135,8 @@ if (process.env['WEB_TEST_METADATA']) {
}
setConf(conf, 'directConnect', true, 'is set to true for chrome');
setConf(conf, 'chromeDriver', chromeDriver, 'is determined by the browsers attribute');
setConf(
conf, 'capabilities', {
mergeCapabilities(
conf, {
browserName: 'chrome',
chromeOptions: {
binary: chromeBin,
@ -131,7 +157,7 @@ if (process.env['WEB_TEST_METADATA']) {
// }
// setConf(conf, 'seleniumAddress', process.env.WEB_TEST_HTTP_SERVER.trim() + "/wd/hub", 'is
// configured by Bazel for firefox browser')
// setConf(conf, 'capabilities', {
// mergeCapabilities(conf, {
// browserName: "firefox",
// 'moz:firefoxOptions': {
// binary: firefoxBin,

View File

@ -0,0 +1,20 @@
package(default_visibility = ["//visibility:public"])
filegroup(
name = "package_assets",
srcs = [
"collection.json",
],
visibility = ["//packages/bazel:__subpackages__"],
)
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
jasmine_node_test(
name = "test",
bootstrap = ["angular/tools/testing/init_node_spec.js"],
deps = [
"//packages/bazel/src/schematics/bazel-workspace:test",
"//tools/testing:node",
],
)

View File

@ -0,0 +1,32 @@
# Schematics for Bazel
## Development notes
To test any local changes, run
```shell
bazel build //packages/bazel:npm_package
```
then `cd` to the npm package in the `dist` folder and run `yarn link`.
Next run `yarn link` again in the directory where the `ng` command is invoked.
Make sure the `ng` command is local, and not the global installation.
## Generate .d.ts file from JSON schema
The script to generate `.d.ts` file is located in the
[Angular CLI](https://github.com/angular/angular-cli) repo. Make sure
the CLI repository is checked out on your local machine.
Then, in the CLI repository, run the following command
```shell
bazel run //tools:quicktype_runner -- \
~/Documents/GitHub/angular/packages/bazel/src/schematics/ng-new/schema.json \
~/Documents/GitHub/angular/packages/bazel/src/schematics/ng-new/schema.d.ts
```
## TODOs
1. Make the `ts_json_schema` rule re-usable and portable.
2. Add comments in BUILD files. See discussion [here](https://github.com/angular/angular/pull/26971#discussion_r231325683).

View File

@ -0,0 +1,34 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
ts_library(
name = "bazel-workspace",
srcs = [
"index.ts",
"schema.d.ts",
],
data = glob(["files/**/*"]) + [
"schema.json",
],
deps = [
"@ngdeps//@angular-devkit/core",
"@ngdeps//@angular-devkit/schematics",
"@ngdeps//@schematics/angular",
],
)
ts_library(
name = "test",
testonly = True,
srcs = [
"index_spec.ts",
],
data = [
"//packages/bazel/src/schematics:package_assets",
],
deps = [
":bazel-workspace",
"@ngdeps//@angular-devkit/schematics",
],
)

View File

@ -0,0 +1,7 @@
package(default_visibility = ["//visibility:public"])
# This export allows targets in other packages to reference files that live
# in this package.
exports_files([
"tsconfig.json",
])

View File

@ -0,0 +1,76 @@
# WARNING: This file is generated and it's not meant to be edited.
# Before making any changes, please read Bazel documentation.
# https://docs.bazel.build/versions/master/be/workspace.html
# The WORKSPACE file tells Bazel that this directory is a "workspace", which is like a project root.
# The content of this file specifies all the external dependencies Bazel needs to perform a build.
####################################
# ESModule imports (and TypeScript imports) can be absolute starting with the workspace name.
# The name of the workspace should match the npm package where we publish, so that these
# imports also make sense when referencing the published package.
workspace(name = "<%= utils.underscore(name) %>")
# The @angular repo contains rule for building Angular applications
# Provides "build_bazel_rules_typescript"
ANGULAR_VERSION = "<%= ANGULAR_VERSION %>"
http_archive(
name = "angular",
url = "https://github.com/angular/angular/archive/%s.zip" % ANGULAR_VERSION,
strip_prefix = "angular-%s" % ANGULAR_VERSION,
)
# RxJS
RXJS_VERSION = "<%= RXJS_VERSION %>"
http_archive(
name = "rxjs",
url = "https://registry.yarnpkg.com/rxjs/-/rxjs-%s.tgz" % RXJS_VERSION,
strip_prefix = "package/src",
)
# Rules for compiling sass
RULES_SASS_VERSION = "<%= RULES_SASS_VERSION %>"
http_archive(
name = "io_bazel_rules_sass",
url = "https://github.com/bazelbuild/rules_sass/archive/%s.zip" % RULES_SASS_VERSION,
strip_prefix = "rules_sass-%s" % RULES_SASS_VERSION,
)
####################################
# Load and install our dependencies downloaded above.
load("@angular//packages/bazel:package.bzl", "rules_angular_dependencies")
rules_angular_dependencies()
load("@build_bazel_rules_typescript//:package.bzl", "rules_typescript_dependencies")
rules_typescript_dependencies()
# build_bazel_rules_nodejs is loaded transitively through rules_typescript_dependencies.
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
# 0.18.0 is needed for .bazelignore
check_bazel_version("0.18.0")
node_repositories()
yarn_install(
name = "npm",
package_json = "//:package.json",
yarn_lock = "//:yarn.lock",
)
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
go_rules_dependencies()
go_register_toolchains()
load("@io_bazel_rules_webtesting//web:repositories.bzl", "browser_repositories", "web_test_repositories")
web_test_repositories()
browser_repositories(
chromium = True,
firefox = True,
)
load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace", "check_rules_typescript_version")
ts_setup_workspace()
load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories")
sass_repositories()
load("@angular//:index.bzl", "ng_setup_workspace")
ng_setup_workspace()

View File

@ -0,0 +1,2 @@
dist
node_modules

View File

@ -0,0 +1,19 @@
# Make TypeScript and Angular compilation fast, by keeping a few copies of the
# compiler running as daemons, and cache SourceFile AST's to reduce parse time.
build --strategy=TypeScriptCompile=worker
build --strategy=AngularTemplateCompile=worker
# Don't create bazel-* symlinks in the WORKSPACE directory, except `bazel-out`,
# which is mandatory.
# These require .gitignore and may scare users.
# Also, it's a workaround for https://github.com/bazelbuild/rules_typescript/issues/12
# which affects the common case of having `tsconfig.json` in the WORKSPACE directory.
#
# Instead, the output will appear in `dist/bin`. You'll need to ignore the
# `bazel-out` directory that is created in the workspace root.
build --symlink_prefix=dist/
test --test_output=errors
# Use the Angular 6 compiler
build --define=compile=legacy

View File

@ -0,0 +1,46 @@
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
load("@angular//:index.bzl", "protractor_web_test_suite")
ts_library(
name = "e2e_lib",
testonly = 1,
srcs = glob(["src/**/*.ts"]),
tsconfig = ":tsconfig.e2e.json",
deps = [
"@npm//@types/jasmine",
"@npm//@types/jasminewd2",
"@npm//@types/node",
"@npm//jasmine",
"@npm//protractor",
]
)
protractor_web_test_suite(
name = "prodserver_test",
data = [
"@angular//packages/bazel/src/protractor/utils",
"@npm//protractor",
],
on_prepare = ":protractor.on-prepare.js",
server = "//src:prodserver",
deps = [":e2e_lib"],
)
protractor_web_test_suite(
name = "devserver_test",
data = [
"@angular//packages/bazel/src/protractor/utils",
"@npm//protractor",
],
on_prepare = ":protractor.on-prepare.js",
server = "//src:devserver",
deps = [":e2e_lib"],
)
# Default target in this package is to run the e2e tests on the devserver.
# This is a faster round-trip but doesn't exercise production optimizations like
# code-splitting and lazy loading.
alias(
name = "e2e",
actual = "devserver_test",
)

View File

@ -0,0 +1,23 @@
// The function exported from this file is used by the protractor_web_test_suite.
// It is passed to the `onPrepare` configuration setting in protractor and executed
// before running tests.
//
// If the function returns a promise, as it does here, protractor will wait
// for the promise to resolve before running tests.
const protractorUtils = require('@angular/bazel/protractor-utils');
const protractor = require('protractor');
module.exports = function(config) {
// In this example, `@angular/bazel/protractor-utils` is used to run
// the server. protractorUtils.runServer() runs the server on a randomly
// selected port (given a port flag to pass to the server as an argument).
// The port used is returned in serverSpec and the protractor serverUrl
// is the configured.
const portFlag = config.server.endsWith('prodserver') ? '-p' : '-port';
return protractorUtils.runServer(config.workspace, config.server, portFlag, [])
.then(serverSpec => {
const serverUrl = `http://localhost:${serverSpec.port}`;
protractor.browser.baseUrl = serverUrl;
});
};

View File

@ -0,0 +1,90 @@
package(default_visibility = ["//visibility:public"])
load("@angular//:index.bzl", "ng_module")
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library", "ts_web_test_suite")
load("@build_bazel_rules_nodejs//:defs.bzl", "rollup_bundle", "history_server")
load("@build_bazel_rules_typescript//:defs.bzl", "ts_devserver")
ng_module(
name = "src",
srcs = glob(["**/*.ts"], exclude = ["**/*.spec.ts", "test.ts"]),
assets = glob([
"**/*.css",
"**/*.html",
]),
deps = [
"@angular//packages/core",
"@angular//packages/platform-browser",
"@npm//@types",
],
)
rollup_bundle(
name = "bundle",
entry_point = "src/main",
deps = ["//src"],
)
# Needed because the prodserver only loads static files that appear under this
# package.
genrule(
name = "zonejs",
srcs = ["@npm//node_modules/zone.js:dist/zone.min.js"],
outs = ["zone.min.js"],
cmd = "cp $< $@",
)
history_server(
name = "prodserver",
data = [
"index.html",
":bundle",
":zonejs",
],
)
ts_devserver(
name = "devserver",
port = 4200,
additional_root_paths = [
"npm/node_modules/zone.js/dist",
"npm/node_modules/tslib",
],
entry_module = "<%= name %>/src/main",
serving_path = "/bundle.min.js",
static_files = [
"@npm//node_modules/zone.js:dist/zone.min.js",
"@npm//node_modules/tslib:tslib.js",
"index.html",
],
deps = [":src"],
)
ts_library(
name = "test_lib",
testonly = 1,
srcs = glob(["**/*.spec.ts"]),
deps = [
":src",
"@angular//packages/core/testing",
"@angular//packages/platform-browser-dynamic/testing",
"@npm//@types",
],
)
ts_web_test_suite(
name = "test",
srcs = ["@npm//node_modules/tslib:tslib.js"],
# do not sort
bootstrap = [
"@npm//node_modules/zone.js:dist/zone-testing-bundle.js",
"@npm//node_modules/reflect-metadata:Reflect.js",
],
browsers = [
"@io_bazel_rules_webtesting//browsers:chromium-local",
],
deps = [
":test_lib",
"@npm//karma-jasmine",
],
)

View File

@ -0,0 +1,47 @@
/**
* @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
*
* @fileoverview Schematics for bazel-workspace
*/
import {strings} from '@angular-devkit/core';
import {Rule, SchematicContext, SchematicsException, Tree, apply, applyTemplates, mergeWith, move, url} from '@angular-devkit/schematics';
import {getWorkspace} from '@schematics/angular/utility/config';
import {validateProjectName} from '@schematics/angular/utility/validation';
import {Schema as BazelWorkspaceOptions} from './schema';
export default function(options: BazelWorkspaceOptions): Rule {
return (host: Tree, context: SchematicContext) => {
if (!options.name) {
throw new SchematicsException(`Invalid options, "name" is required.`);
}
validateProjectName(options.name);
let newProjectRoot = '';
try {
const workspace = getWorkspace(host);
newProjectRoot = workspace.newProjectRoot || '';
} catch {
}
const appDir = `${newProjectRoot}/${options.name}`;
const workspaceVersions = {
'ANGULAR_VERSION': '7.0.2',
'RULES_SASS_VERSION': '1.14.1',
'RXJS_VERSION': '6.3.3',
};
return mergeWith(apply(url('./files'), [
applyTemplates({
utils: strings,
...options,
'dot': '.', ...workspaceVersions,
}),
move(appDir),
]));
};
}

View File

@ -0,0 +1,47 @@
/**
* @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 {SchematicTestRunner} from '@angular-devkit/schematics/testing';
describe('Bazel-workspace Schematic', () => {
const schematicRunner =
new SchematicTestRunner('@angular/bazel', require.resolve('../collection.json'), );
const defaultOptions = {
name: 'demo',
};
it('should generate Bazel workspace files', () => {
const options = {...defaultOptions};
const host = schematicRunner.runSchematic('bazel-workspace', options);
const files = host.files;
expect(files).toContain('/demo/.bazelignore');
expect(files).toContain('/demo/.bazelrc');
expect(files).toContain('/demo/BUILD.bazel');
expect(files).toContain('/demo/src/BUILD.bazel');
expect(files).toContain('/demo/WORKSPACE');
expect(files).toContain('/demo/yarn.lock');
});
describe('WORKSPACE', () => {
it('should contain project name', () => {
const options = {...defaultOptions};
const host = schematicRunner.runSchematic('bazel-workspace', options);
expect(host.files).toContain('/demo/WORKSPACE');
const content = host.readContent('/demo/WORKSPACE');
expect(content).toContain('workspace(name = "demo")');
});
it('should convert dashes in name to underscore', () => {
const options = {...defaultOptions, name: 'demo-project'};
const host = schematicRunner.runSchematic('bazel-workspace', options);
expect(host.files).toContain('/demo-project/WORKSPACE');
const content = host.readContent('/demo-project/WORKSPACE');
expect(content).toContain('workspace(name = "demo_project"');
});
});
});

View File

@ -0,0 +1,13 @@
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE
// THE CORRESPONDING JSON SCHEMA FILE. See README.md.
// tslint:disable:no-global-tslint-disable
// tslint:disable
export interface Schema {
/**
* The name of the project.
*/
name: string;
}

View File

@ -0,0 +1,20 @@
{
"$schema": "http://json-schema.org/schema",
"id": "SchematicsAngularBazelWorkspace",
"title": "Angular Bazel Workspace Schema",
"type": "object",
"properties": {
"name": {
"description": "The name of the project.",
"type": "string",
"format": "html-selector",
"$default": {
"$source": "argv",
"index": 0
}
}
},
"required": [
"name"
]
}

View File

@ -0,0 +1,12 @@
{
"name": "@angular/bazel",
"version": "0.1",
"schematics": {
"bazel-workspace": {
"factory": "./bazel-workspace",
"schema": "./bazel-workspace/schema.json",
"description": "Setup Bazel workspace",
"hidden": true
}
}
}

View File

@ -70,7 +70,7 @@ export function createTsConfig(options: TsConfigOptions) {
'tsickleExternsPath': '',
// we don't copy the node_modules into our tmp dir, so we should look in
// the original workspace directory for it
'nodeModulesPrefix': '../angular/external/ngdeps/node_modules',
'nodeModulesPrefix': '../ngdeps/node_modules',
},
'files': options.files,
'angularCompilerOptions': {

View File

@ -19,9 +19,6 @@ ts_library(
jasmine_node_test(
name = "test",
bootstrap = ["angular/tools/testing/init_node_spec.js"],
tags = [
"fixme-ivy-aot",
],
deps = [
":test_lib",
"//tools/testing:node",
@ -30,9 +27,6 @@ jasmine_node_test(
ts_web_test_suite(
name = "test_web",
tags = [
"fixme-ivy-aot",
],
deps = [
":test_lib",
],

View File

@ -6,13 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/
import {HttpClient} from '@angular/common/http/src/client';
import {HttpErrorResponse, HttpEventType, HttpResponse} from '@angular/common/http/src/response';
import {HttpClientTestingBackend} from '@angular/common/http/testing/src/backend';
import {ddescribe, describe, fit, it} from '@angular/core/testing/src/testing_internal';
import {toArray} from 'rxjs/operators';
import {HttpClient} from '../src/client';
import {HttpErrorResponse, HttpEventType, HttpResponse} from '../src/response';
import {HttpClientTestingBackend} from '../testing/src/backend';
{
describe('HttpClient', () => {
let client: HttpClient = null !;

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {HttpHeaders} from '../src/headers';
import {HttpHeaders} from '@angular/common/http/src/headers';
{
describe('HttpHeaders', () => {

View File

@ -6,13 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/
import {JSONP_ERR_NO_CALLBACK, JSONP_ERR_WRONG_METHOD, JSONP_ERR_WRONG_RESPONSE_TYPE, JsonpClientBackend} from '@angular/common/http/src/jsonp';
import {HttpRequest} from '@angular/common/http/src/request';
import {HttpErrorResponse, HttpEventType} from '@angular/common/http/src/response';
import {ddescribe, describe, it} from '@angular/core/testing/src/testing_internal';
import {toArray} from 'rxjs/operators';
import {JSONP_ERR_NO_CALLBACK, JSONP_ERR_WRONG_METHOD, JSONP_ERR_WRONG_RESPONSE_TYPE, JsonpClientBackend} from '../src/jsonp';
import {HttpRequest} from '../src/request';
import {HttpErrorResponse, HttpEventType} from '../src/response';
import {MockDocument} from './jsonp_mock';
function runOnlyCallback(home: any, data: Object) {

View File

@ -6,20 +6,19 @@
* found in the LICENSE file at https://angular.io/license
*/
import {HttpHandler} from '@angular/common/http/src/backend';
import {HttpClient} from '@angular/common/http/src/client';
import {HTTP_INTERCEPTORS, HttpInterceptor} from '@angular/common/http/src/interceptor';
import {HttpRequest} from '@angular/common/http/src/request';
import {HttpEvent, HttpResponse} from '@angular/common/http/src/response';
import {HttpTestingController} from '@angular/common/http/testing/src/api';
import {HttpClientTestingModule} from '@angular/common/http/testing/src/module';
import {TestRequest} from '@angular/common/http/testing/src/request';
import {Injectable, Injector} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {Observable} from 'rxjs';
import {map} from 'rxjs/operators';
import {HttpHandler} from '../src/backend';
import {HttpClient} from '../src/client';
import {HTTP_INTERCEPTORS, HttpInterceptor} from '../src/interceptor';
import {HttpRequest} from '../src/request';
import {HttpEvent, HttpResponse} from '../src/response';
import {HttpTestingController} from '../testing/src/api';
import {HttpClientTestingModule} from '../testing/src/module';
import {TestRequest} from '../testing/src/request';
class TestInterceptor implements HttpInterceptor {
constructor(private value: string) {}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {HttpParams} from '../src/params';
import {HttpParams} from '@angular/common/http/src/params';
{
describe('HttpUrlEncodedParams', () => {

View File

@ -6,12 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
import {HttpHeaders} from '@angular/common/http/src/headers';
import {HttpParams} from '@angular/common/http/src/params';
import {HttpRequest} from '@angular/common/http/src/request';
import {ddescribe, describe, it} from '@angular/core/testing/src/testing_internal';
import {HttpHeaders} from '../src/headers';
import {HttpParams} from '../src/params';
import {HttpRequest} from '../src/request';
const TEST_URL = 'http://angular.io';
const TEST_STRING = `I'm a body!`;
@ -161,4 +160,4 @@ const TEST_STRING = `I'm a body!`;
});
});
});
}
}

View File

@ -6,11 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {HttpHeaders} from '@angular/common/http/src/headers';
import {HttpResponse} from '@angular/common/http/src/response';
import {ddescribe, describe, it} from '@angular/core/testing/src/testing_internal';
import {HttpHeaders} from '../src/headers';
import {HttpResponse} from '../src/response';
{
describe('HttpResponse', () => {
describe('constructor()', () => {
@ -79,4 +78,4 @@ import {HttpResponse} from '../src/response';
});
});
});
}
}

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {HttpHeaders} from '../src/headers';
import {XhrFactory} from '../src/xhr';
import {HttpHeaders} from '@angular/common/http/src/headers';
import {XhrFactory} from '@angular/common/http/src/xhr';
export class MockXhrFactory implements XhrFactory {
// TODO(issue/24571): remove '!'.
@ -119,4 +119,4 @@ export class MockXMLHttpRequest {
}
abort() { this.mockAborted = true; }
}
}

View File

@ -6,14 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {HttpRequest} from '@angular/common/http/src/request';
import {HttpDownloadProgressEvent, HttpErrorResponse, HttpEvent, HttpEventType, HttpHeaderResponse, HttpResponse, HttpResponseBase, HttpUploadProgressEvent} from '@angular/common/http/src/response';
import {HttpXhrBackend} from '@angular/common/http/src/xhr';
import {ddescribe, describe, fit, it} from '@angular/core/testing/src/testing_internal';
import {Observable} from 'rxjs';
import {toArray} from 'rxjs/operators';
import {HttpRequest} from '../src/request';
import {HttpDownloadProgressEvent, HttpErrorResponse, HttpEvent, HttpEventType, HttpHeaderResponse, HttpResponse, HttpResponseBase, HttpUploadProgressEvent} from '../src/response';
import {HttpXhrBackend} from '../src/xhr';
import {MockXhrFactory} from './xhr_mock';
function trackEvents(obs: Observable<HttpEvent<any>>): HttpEvent<any>[] {

View File

@ -6,11 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
import {HttpHeaders} from '../src/headers';
import {HttpRequest} from '../src/request';
import {HttpXsrfCookieExtractor, HttpXsrfInterceptor, HttpXsrfTokenExtractor} from '../src/xsrf';
import {HttpHeaders} from '@angular/common/http/src/headers';
import {HttpRequest} from '@angular/common/http/src/request';
import {HttpXsrfCookieExtractor, HttpXsrfInterceptor, HttpXsrfTokenExtractor} from '@angular/common/http/src/xsrf';
import {HttpClientTestingBackend} from '../testing/src/backend';
import {HttpClientTestingBackend} from '@angular/common/http/testing/src/backend';
class SampleTokenExtractor extends HttpXsrfTokenExtractor {
constructor(private token: string|null) { super(); }
@ -88,4 +88,4 @@ class SampleTokenExtractor extends HttpXsrfTokenExtractor {
function getParseCount(extractor: HttpXsrfCookieExtractor): number {
return (extractor as any).parseCount;
}
}

View File

@ -15,15 +15,13 @@ ts_library(
"//packages/platform-browser",
"//packages/platform-browser-dynamic",
"//packages/platform-browser/testing",
"//packages/private/testing",
],
)
jasmine_node_test(
name = "test",
bootstrap = ["angular/tools/testing/init_node_spec.js"],
tags = [
"fixme-ivy-aot",
],
deps = [
":test_lib",
"//tools/testing:node",
@ -32,9 +30,6 @@ jasmine_node_test(
ts_web_test_suite(
name = "test_web",
tags = [
"fixme-ivy-aot",
],
deps = [
":test_lib",
],

View File

@ -7,20 +7,17 @@
*/
import {DeprecatedI18NPipesModule, Plural} from '@angular/common';
import {DEPRECATED_PLURAL_FN, getPluralCase} from '@angular/common/src/i18n/localization';
import {TestBed, inject} from '@angular/core/testing';
import {DeprecatedI18NPipesModule} from '../src/common_module';
import {Plural} from '../src/i18n/locale_data_api';
import {DEPRECATED_PLURAL_FN, getPluralCase} from '../src/i18n/localization';
{
describe('DeprecatedI18NPipesModule', () => {
beforeEach(() => { TestBed.configureTestingModule({imports: [DeprecatedI18NPipesModule]}); });
describe('DeprecatedI18NPipesModule', () => {
beforeEach(() => { TestBed.configureTestingModule({imports: [DeprecatedI18NPipesModule]}); });
it('should define the token DEPRECATED_PLURAL_FN',
inject(
[DEPRECATED_PLURAL_FN],
(injectedGetPluralCase?: (locale: string, value: number | string) => Plural) => {
expect(injectedGetPluralCase).toEqual(getPluralCase);
}));
});
}
it('should define the token DEPRECATED_PLURAL_FN',
inject(
[DEPRECATED_PLURAL_FN],
(injectedGetPluralCase?: (locale: string, value: number | string) => Plural) => {
expect(injectedGetPluralCase).toEqual(getPluralCase);
}));
});

View File

@ -8,206 +8,209 @@
import {CommonModule} from '@angular/common';
import {NgComponentOutlet} from '@angular/common/src/directives/ng_component_outlet';
import {Compiler, Component, ComponentRef, Inject, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, Optional, QueryList, StaticProvider, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {TestBed, async, fakeAsync} from '@angular/core/testing';
import {Compiler, Component, ComponentRef, Inject, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, Optional, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {TestBed, async} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {fixmeIvy} from '@angular/private/testing';
{
describe('insert/remove', () => {
describe('insert/remove', () => {
beforeEach(() => { TestBed.configureTestingModule({imports: [TestModule]}); });
beforeEach(() => { TestBed.configureTestingModule({imports: [TestModule]}); });
it('should do nothing if component is null', async(() => {
const template = `<ng-template *ngComponentOutlet="currentComponent"></ng-template>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
it('should do nothing if component is null', async(() => {
const template = `<ng-template *ngComponentOutlet="currentComponent"></ng-template>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
fixture.componentInstance.currentComponent = null;
fixture.detectChanges();
fixture.componentInstance.currentComponent = null;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
}));
expect(fixture.nativeElement).toHaveText('');
}));
it('should insert content specified by a component', async(() => {
let fixture = TestBed.createComponent(TestComponent);
it('should insert content specified by a component', async(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
}));
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
}));
it('should emit a ComponentRef once a component was created', async(() => {
let fixture = TestBed.createComponent(TestComponent);
it('should emit a ComponentRef once a component was created', async(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.componentInstance.cmpRef = null;
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.componentInstance.cmpRef = null;
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
expect(fixture.componentInstance.cmpRef).toBeAnInstanceOf(ComponentRef);
expect(fixture.componentInstance.cmpRef !.instance).toBeAnInstanceOf(InjectedComponent);
}));
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
expect(fixture.componentInstance.cmpRef).toBeAnInstanceOf(ComponentRef);
expect(fixture.componentInstance.cmpRef !.instance).toBeAnInstanceOf(InjectedComponent);
}));
it('should clear view if component becomes null', async(() => {
let fixture = TestBed.createComponent(TestComponent);
it('should clear view if component becomes null', async(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
fixture.componentInstance.currentComponent = null;
fixture.componentInstance.currentComponent = null;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
}));
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
}));
it('should swap content if component changes', async(() => {
let fixture = TestBed.createComponent(TestComponent);
it('should swap content if component changes', async(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
fixture.componentInstance.currentComponent = InjectedComponentAgain;
fixture.componentInstance.currentComponent = InjectedComponentAgain;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('bar');
}));
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('bar');
}));
it('should use the injector, if one supplied', async(() => {
let fixture = TestBed.createComponent(TestComponent);
fixmeIvy('FW-642: ASSERTION ERROR: Slot should have been initialized to NO_CHANGE') &&
it('should use the injector, if one supplied', async(() => {
let fixture = TestBed.createComponent(TestComponent);
const uniqueValue = {};
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.componentInstance.injector = Injector.create(
[{provide: TEST_TOKEN, useValue: uniqueValue}], fixture.componentRef.injector);
const uniqueValue = {};
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.componentInstance.injector = Injector.create(
[{provide: TEST_TOKEN, useValue: uniqueValue}], fixture.componentRef.injector);
fixture.detectChanges();
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef !;
expect(cmpRef).toBeAnInstanceOf(ComponentRef);
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
expect(cmpRef.instance.testToken).toBe(uniqueValue);
fixture.detectChanges();
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef !;
expect(cmpRef).toBeAnInstanceOf(ComponentRef);
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
expect(cmpRef.instance.testToken).toBe(uniqueValue);
}));
}));
it('should resolve a with injector', async(() => {
let fixture = TestBed.createComponent(TestComponent);
it('should resolve a with injector', async(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.componentInstance.cmpRef = null;
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.detectChanges();
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef !;
expect(cmpRef).toBeAnInstanceOf(ComponentRef);
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
expect(cmpRef.instance.testToken).toBeNull();
}));
fixture.componentInstance.cmpRef = null;
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.detectChanges();
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef !;
expect(cmpRef).toBeAnInstanceOf(ComponentRef);
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
expect(cmpRef.instance.testToken).toBeNull();
}));
it('should render projectable nodes, if supplied', async(() => {
const template = `<ng-template>projected foo</ng-template>${TEST_CMP_TEMPLATE}`;
TestBed.overrideComponent(TestComponent, {set: {template: template}})
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
it('should render projectable nodes, if supplied', async(() => {
const template = `<ng-template>projected foo</ng-template>${TEST_CMP_TEMPLATE}`;
TestBed.overrideComponent(TestComponent, {set: {template: template}})
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
TestBed
.overrideComponent(InjectedComponent, {set: {template: `<ng-content></ng-content>`}})
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
TestBed.overrideComponent(InjectedComponent, {set: {template: `<ng-content></ng-content>`}})
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
let fixture = TestBed.createComponent(TestComponent);
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.componentInstance.projectables =
[fixture.componentInstance.vcRef
.createEmbeddedView(fixture.componentInstance.tplRefs.first)
.rootNodes];
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.componentInstance.projectables =
[fixture.componentInstance.vcRef
.createEmbeddedView(fixture.componentInstance.tplRefs.first)
.rootNodes];
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('projected foo');
}));
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('projected foo');
}));
it('should resolve components from other modules, if supplied', async(() => {
const compiler = TestBed.get(Compiler) as Compiler;
let fixture = TestBed.createComponent(TestComponent);
fixmeIvy('FW-561: Runtime compiler is not loaded') &&
it('should resolve components from other modules, if supplied', async(() => {
const compiler = TestBed.get(Compiler) as Compiler;
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
fixture.componentInstance.currentComponent = Module2InjectedComponent;
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
fixture.componentInstance.currentComponent = Module2InjectedComponent;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('baz');
}));
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('baz');
}));
it('should clean up moduleRef, if supplied', async(() => {
let destroyed = false;
const compiler = TestBed.get(Compiler) as Compiler;
const fixture = TestBed.createComponent(TestComponent);
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
fixture.componentInstance.currentComponent = Module2InjectedComponent;
fixture.detectChanges();
fixmeIvy('FW-561: Runtime compiler is not loaded') &&
it('should clean up moduleRef, if supplied', async(() => {
let destroyed = false;
const compiler = TestBed.get(Compiler) as Compiler;
const fixture = TestBed.createComponent(TestComponent);
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
fixture.componentInstance.currentComponent = Module2InjectedComponent;
fixture.detectChanges();
const moduleRef = fixture.componentInstance.ngComponentOutlet['_moduleRef'] !;
spyOn(moduleRef, 'destroy').and.callThrough();
const moduleRef = fixture.componentInstance.ngComponentOutlet['_moduleRef'] !;
spyOn(moduleRef, 'destroy').and.callThrough();
expect(moduleRef.destroy).not.toHaveBeenCalled();
fixture.destroy();
expect(moduleRef.destroy).toHaveBeenCalled();
}));
expect(moduleRef.destroy).not.toHaveBeenCalled();
fixture.destroy();
expect(moduleRef.destroy).toHaveBeenCalled();
}));
it('should not re-create moduleRef when it didn\'t actually change', async(() => {
const compiler = TestBed.get(Compiler) as Compiler;
const fixture = TestBed.createComponent(TestComponent);
fixmeIvy('FW-561: Runtime compiler is not loaded') &&
it('should not re-create moduleRef when it didn\'t actually change', async(() => {
const compiler = TestBed.get(Compiler) as Compiler;
const fixture = TestBed.createComponent(TestComponent);
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
fixture.componentInstance.currentComponent = Module2InjectedComponent;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('baz');
const moduleRef = fixture.componentInstance.ngComponentOutlet['_moduleRef'];
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
fixture.componentInstance.currentComponent = Module2InjectedComponent;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('baz');
const moduleRef = fixture.componentInstance.ngComponentOutlet['_moduleRef'];
fixture.componentInstance.currentComponent = Module2InjectedComponent2;
fixture.detectChanges();
fixture.componentInstance.currentComponent = Module2InjectedComponent2;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('baz2');
expect(moduleRef).toBe(fixture.componentInstance.ngComponentOutlet['_moduleRef']);
}));
expect(fixture.nativeElement).toHaveText('baz2');
expect(moduleRef).toBe(fixture.componentInstance.ngComponentOutlet['_moduleRef']);
}));
it('should re-create moduleRef when changed', async(() => {
const compiler = TestBed.get(Compiler) as Compiler;
const fixture = TestBed.createComponent(TestComponent);
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
fixture.componentInstance.currentComponent = Module2InjectedComponent;
fixture.detectChanges();
fixmeIvy('FW-561: Runtime compiler is not loaded') &&
it('should re-create moduleRef when changed', async(() => {
const compiler = TestBed.get(Compiler) as Compiler;
const fixture = TestBed.createComponent(TestComponent);
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
fixture.componentInstance.currentComponent = Module2InjectedComponent;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('baz');
expect(fixture.nativeElement).toHaveText('baz');
fixture.componentInstance.module = compiler.compileModuleSync(TestModule3);
fixture.componentInstance.currentComponent = Module3InjectedComponent;
fixture.detectChanges();
fixture.componentInstance.module = compiler.compileModuleSync(TestModule3);
fixture.componentInstance.currentComponent = Module3InjectedComponent;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('bat');
}));
});
}
expect(fixture.nativeElement).toHaveText('bat');
}));
});
const TEST_TOKEN = new InjectionToken('TestToken');
@Component({selector: 'injected-component', template: 'foo'})

View File

@ -11,204 +11,200 @@ import {Component, ContentChildren, Directive, Injectable, NO_ERRORS_SCHEMA, OnD
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
{
describe('NgTemplateOutlet', () => {
let fixture: ComponentFixture<any>;
describe('NgTemplateOutlet', () => {
let fixture: ComponentFixture<any>;
function setTplRef(value: any): void { fixture.componentInstance.currentTplRef = value; }
function setTplRef(value: any): void { fixture.componentInstance.currentTplRef = value; }
function detectChangesAndExpectText(text: string): void {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText(text);
}
function detectChangesAndExpectText(text: string): void {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText(text);
}
afterEach(() => { fixture = null as any; });
afterEach(() => { fixture = null as any; });
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestComponent, CaptureTplRefs, DestroyableCmpt],
imports: [CommonModule],
providers: [DestroyedSpyService]
});
});
// https://github.com/angular/angular/issues/14778
it('should accept the component as the context', async(() => {
const template = `<ng-container *ngTemplateOutlet="tpl; context: this"></ng-container>` +
`<ng-template #tpl>{{context.foo}}</ng-template>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('bar');
}));
it('should do nothing if templateRef is `null`', async(() => {
const template = `<ng-container [ngTemplateOutlet]="null"></ng-container>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('');
}));
it('should insert content specified by TemplateRef', async(() => {
const template = `<ng-template #tpl>foo</ng-template>` +
`<ng-container [ngTemplateOutlet]="tpl"></ng-container>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('foo');
}));
it('should clear content if TemplateRef becomes `null`', async(() => {
const template = `<tpl-refs #refs="tplRefs"><ng-template>foo</ng-template></tpl-refs>` +
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
fixture = createTestComponent(template);
fixture.detectChanges();
const refs = fixture.debugElement.children[0].references !['refs'];
setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('foo');
setTplRef(null);
detectChangesAndExpectText('');
}));
it('should swap content if TemplateRef changes', async(() => {
const template =
`<tpl-refs #refs="tplRefs"><ng-template>foo</ng-template><ng-template>bar</ng-template></tpl-refs>` +
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
fixture = createTestComponent(template);
fixture.detectChanges();
const refs = fixture.debugElement.children[0].references !['refs'];
setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('foo');
setTplRef(refs.tplRefs.last);
detectChangesAndExpectText('bar');
}));
it('should display template if context is `null`', async(() => {
const template = `<ng-template #tpl>foo</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: null"></ng-container>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('foo');
}));
it('should reflect initial context and changes', async(() => {
const template = `<ng-template let-foo="foo" #tpl>{{foo}}</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: context"></ng-container>`;
fixture = createTestComponent(template);
fixture.detectChanges();
detectChangesAndExpectText('bar');
fixture.componentInstance.context.foo = 'alter-bar';
detectChangesAndExpectText('alter-bar');
}));
it('should reflect user defined `$implicit` property in the context', async(() => {
const template = `<ng-template let-ctx #tpl>{{ctx.foo}}</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: context"></ng-container>`;
fixture = createTestComponent(template);
fixture.componentInstance.context = {$implicit: {foo: 'bra'}};
detectChangesAndExpectText('bra');
}));
it('should reflect context re-binding', async(() => {
const template =
`<ng-template let-shawshank="shawshank" #tpl>{{shawshank}}</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: context"></ng-container>`;
fixture = createTestComponent(template);
fixture.componentInstance.context = {shawshank: 'brooks'};
detectChangesAndExpectText('brooks');
fixture.componentInstance.context = {shawshank: 'was here'};
detectChangesAndExpectText('was here');
}));
it('should update but not destroy embedded view when context values change', () => {
const template =
`<ng-template let-foo="foo" #tpl><destroyable-cmpt></destroyable-cmpt>:{{foo}}</ng-template>` +
`<ng-template [ngTemplateOutlet]="tpl" [ngTemplateOutletContext]="{foo: value}"></ng-template>`;
fixture = createTestComponent(template);
const spyService = fixture.debugElement.injector.get(DestroyedSpyService);
detectChangesAndExpectText('Content to destroy:bar');
expect(spyService.destroyed).toBeFalsy();
fixture.componentInstance.value = 'baz';
detectChangesAndExpectText('Content to destroy:baz');
expect(spyService.destroyed).toBeFalsy();
});
it('should recreate embedded view when context shape changes', () => {
const template =
`<ng-template let-foo="foo" #tpl><destroyable-cmpt></destroyable-cmpt>:{{foo}}</ng-template>` +
`<ng-template [ngTemplateOutlet]="tpl" [ngTemplateOutletContext]="context"></ng-template>`;
fixture = createTestComponent(template);
const spyService = fixture.debugElement.injector.get(DestroyedSpyService);
detectChangesAndExpectText('Content to destroy:bar');
expect(spyService.destroyed).toBeFalsy();
fixture.componentInstance.context = {foo: 'baz', other: true};
detectChangesAndExpectText('Content to destroy:baz');
expect(spyService.destroyed).toBeTruthy();
});
it('should destroy embedded view when context value changes and templateRef becomes undefined',
() => {
const template =
`<ng-template let-foo="foo" #tpl><destroyable-cmpt></destroyable-cmpt>:{{foo}}</ng-template>` +
`<ng-template [ngTemplateOutlet]="value === 'bar' ? tpl : undefined" [ngTemplateOutletContext]="{foo: value}"></ng-template>`;
fixture = createTestComponent(template);
const spyService = fixture.debugElement.injector.get(DestroyedSpyService);
detectChangesAndExpectText('Content to destroy:bar');
expect(spyService.destroyed).toBeFalsy();
fixture.componentInstance.value = 'baz';
detectChangesAndExpectText('');
expect(spyService.destroyed).toBeTruthy();
});
it('should not try to update null / undefined context when context changes but template stays the same',
() => {
const template = `<ng-template let-foo="foo" #tpl>{{foo}}</ng-template>` +
`<ng-template [ngTemplateOutlet]="tpl" [ngTemplateOutletContext]="value === 'bar' ? null : undefined"></ng-template>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('');
fixture.componentInstance.value = 'baz';
detectChangesAndExpectText('');
});
it('should not try to update null / undefined context when template changes', () => {
const template = `<ng-template let-foo="foo" #tpl1>{{foo}}</ng-template>` +
`<ng-template let-foo="foo" #tpl2>{{foo}}</ng-template>` +
`<ng-template [ngTemplateOutlet]="value === 'bar' ? tpl1 : tpl2" [ngTemplateOutletContext]="value === 'bar' ? null : undefined"></ng-template>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('');
fixture.componentInstance.value = 'baz';
detectChangesAndExpectText('');
});
it('should not try to update context on undefined view', () => {
const template = `<ng-template let-foo="foo" #tpl>{{foo}}</ng-template>` +
`<ng-template [ngTemplateOutlet]="value === 'bar' ? null : undefined" [ngTemplateOutletContext]="{foo: value}"></ng-template>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('');
fixture.componentInstance.value = 'baz';
detectChangesAndExpectText('');
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestComponent, CaptureTplRefs, DestroyableCmpt],
imports: [CommonModule],
providers: [DestroyedSpyService]
});
});
}
// https://github.com/angular/angular/issues/14778
it('should accept the component as the context', async(() => {
const template = `<ng-container *ngTemplateOutlet="tpl; context: this"></ng-container>` +
`<ng-template #tpl>{{context.foo}}</ng-template>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('bar');
}));
it('should do nothing if templateRef is `null`', async(() => {
const template = `<ng-container [ngTemplateOutlet]="null"></ng-container>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('');
}));
it('should insert content specified by TemplateRef', async(() => {
const template = `<ng-template #tpl>foo</ng-template>` +
`<ng-container [ngTemplateOutlet]="tpl"></ng-container>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('foo');
}));
it('should clear content if TemplateRef becomes `null`', async(() => {
const template = `<tpl-refs #refs="tplRefs"><ng-template>foo</ng-template></tpl-refs>` +
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
fixture = createTestComponent(template);
fixture.detectChanges();
const refs = fixture.debugElement.children[0].references !['refs'];
setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('foo');
setTplRef(null);
detectChangesAndExpectText('');
}));
it('should swap content if TemplateRef changes', async(() => {
const template =
`<tpl-refs #refs="tplRefs"><ng-template>foo</ng-template><ng-template>bar</ng-template></tpl-refs>` +
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
fixture = createTestComponent(template);
fixture.detectChanges();
const refs = fixture.debugElement.children[0].references !['refs'];
setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('foo');
setTplRef(refs.tplRefs.last);
detectChangesAndExpectText('bar');
}));
it('should display template if context is `null`', async(() => {
const template = `<ng-template #tpl>foo</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: null"></ng-container>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('foo');
}));
it('should reflect initial context and changes', async(() => {
const template = `<ng-template let-foo="foo" #tpl>{{foo}}</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: context"></ng-container>`;
fixture = createTestComponent(template);
fixture.detectChanges();
detectChangesAndExpectText('bar');
fixture.componentInstance.context.foo = 'alter-bar';
detectChangesAndExpectText('alter-bar');
}));
it('should reflect user defined `$implicit` property in the context', async(() => {
const template = `<ng-template let-ctx #tpl>{{ctx.foo}}</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: context"></ng-container>`;
fixture = createTestComponent(template);
fixture.componentInstance.context = {$implicit: {foo: 'bra'}};
detectChangesAndExpectText('bra');
}));
it('should reflect context re-binding', async(() => {
const template = `<ng-template let-shawshank="shawshank" #tpl>{{shawshank}}</ng-template>` +
`<ng-container *ngTemplateOutlet="tpl; context: context"></ng-container>`;
fixture = createTestComponent(template);
fixture.componentInstance.context = {shawshank: 'brooks'};
detectChangesAndExpectText('brooks');
fixture.componentInstance.context = {shawshank: 'was here'};
detectChangesAndExpectText('was here');
}));
it('should update but not destroy embedded view when context values change', () => {
const template =
`<ng-template let-foo="foo" #tpl><destroyable-cmpt></destroyable-cmpt>:{{foo}}</ng-template>` +
`<ng-template [ngTemplateOutlet]="tpl" [ngTemplateOutletContext]="{foo: value}"></ng-template>`;
fixture = createTestComponent(template);
const spyService = fixture.debugElement.injector.get(DestroyedSpyService);
detectChangesAndExpectText('Content to destroy:bar');
expect(spyService.destroyed).toBeFalsy();
fixture.componentInstance.value = 'baz';
detectChangesAndExpectText('Content to destroy:baz');
expect(spyService.destroyed).toBeFalsy();
});
it('should recreate embedded view when context shape changes', () => {
const template =
`<ng-template let-foo="foo" #tpl><destroyable-cmpt></destroyable-cmpt>:{{foo}}</ng-template>` +
`<ng-template [ngTemplateOutlet]="tpl" [ngTemplateOutletContext]="context"></ng-template>`;
fixture = createTestComponent(template);
const spyService = fixture.debugElement.injector.get(DestroyedSpyService);
detectChangesAndExpectText('Content to destroy:bar');
expect(spyService.destroyed).toBeFalsy();
fixture.componentInstance.context = {foo: 'baz', other: true};
detectChangesAndExpectText('Content to destroy:baz');
expect(spyService.destroyed).toBeTruthy();
});
it('should destroy embedded view when context value changes and templateRef becomes undefined', () => {
const template =
`<ng-template let-foo="foo" #tpl><destroyable-cmpt></destroyable-cmpt>:{{foo}}</ng-template>` +
`<ng-template [ngTemplateOutlet]="value === 'bar' ? tpl : undefined" [ngTemplateOutletContext]="{foo: value}"></ng-template>`;
fixture = createTestComponent(template);
const spyService = fixture.debugElement.injector.get(DestroyedSpyService);
detectChangesAndExpectText('Content to destroy:bar');
expect(spyService.destroyed).toBeFalsy();
fixture.componentInstance.value = 'baz';
detectChangesAndExpectText('');
expect(spyService.destroyed).toBeTruthy();
});
it('should not try to update null / undefined context when context changes but template stays the same',
() => {
const template = `<ng-template let-foo="foo" #tpl>{{foo}}</ng-template>` +
`<ng-template [ngTemplateOutlet]="tpl" [ngTemplateOutletContext]="value === 'bar' ? null : undefined"></ng-template>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('');
fixture.componentInstance.value = 'baz';
detectChangesAndExpectText('');
});
it('should not try to update null / undefined context when template changes', () => {
const template = `<ng-template let-foo="foo" #tpl1>{{foo}}</ng-template>` +
`<ng-template let-foo="foo" #tpl2>{{foo}}</ng-template>` +
`<ng-template [ngTemplateOutlet]="value === 'bar' ? tpl1 : tpl2" [ngTemplateOutletContext]="value === 'bar' ? null : undefined"></ng-template>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('');
fixture.componentInstance.value = 'baz';
detectChangesAndExpectText('');
});
it('should not try to update context on undefined view', () => {
const template = `<ng-template let-foo="foo" #tpl>{{foo}}</ng-template>` +
`<ng-template [ngTemplateOutlet]="value === 'bar' ? null : undefined" [ngTemplateOutletContext]="{foo: value}"></ng-template>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('');
fixture.componentInstance.value = 'baz';
detectChangesAndExpectText('');
});
});
@Injectable()
class DestroyedSpyService {

View File

@ -12,8 +12,8 @@ import localeZgh from '@angular/common/locales/zgh';
import localeFr from '@angular/common/locales/fr';
import {LOCALE_ID} from '@angular/core';
import {TestBed, inject} from '@angular/core/testing';
import {NgLocaleLocalization, NgLocalization, getPluralCategory, DEPRECATED_PLURAL_FN, getPluralCase} from '../../src/i18n/localization';
import {Plural} from '../../src/i18n/locale_data_api';
import {NgLocaleLocalization, NgLocalization, getPluralCategory, DEPRECATED_PLURAL_FN, getPluralCase} from '@angular/common/src/i18n/localization';
import {Plural} from '@angular/common';
import {registerLocaleData} from '../../src/i18n/locale_data';
{

View File

@ -6,37 +6,35 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Location} from '../../src/location/location';
import {Location} from '@angular/common';
{
const baseUrl = '/base';
const baseUrl = '/base';
describe('Location Class', () => {
describe('stripTrailingSlash', () => {
it('should strip single character slash', () => {
const input = '/';
expect(Location.stripTrailingSlash(input)).toBe('');
});
describe('Location Class', () => {
describe('stripTrailingSlash', () => {
it('should strip single character slash', () => {
const input = '/';
expect(Location.stripTrailingSlash(input)).toBe('');
});
it('should normalize strip a trailing slash', () => {
const input = baseUrl + '/';
expect(Location.stripTrailingSlash(input)).toBe(baseUrl);
});
it('should normalize strip a trailing slash', () => {
const input = baseUrl + '/';
expect(Location.stripTrailingSlash(input)).toBe(baseUrl);
});
it('should ignore query params when stripping a slash', () => {
const input = baseUrl + '/?param=1';
expect(Location.stripTrailingSlash(input)).toBe(baseUrl + '?param=1');
});
it('should ignore query params when stripping a slash', () => {
const input = baseUrl + '/?param=1';
expect(Location.stripTrailingSlash(input)).toBe(baseUrl + '?param=1');
});
it('should not remove slashes inside query params', () => {
const input = baseUrl + '?test/?=3';
expect(Location.stripTrailingSlash(input)).toBe(input);
});
it('should not remove slashes inside query params', () => {
const input = baseUrl + '?test/?=3';
expect(Location.stripTrailingSlash(input)).toBe(input);
});
it('should not remove slashes after a pound sign', () => {
const input = baseUrl + '#test/?=3';
expect(Location.stripTrailingSlash(input)).toBe(input);
});
it('should not remove slashes after a pound sign', () => {
const input = baseUrl + '#test/?=3';
expect(Location.stripTrailingSlash(input)).toBe(input);
});
});
}
});

View File

@ -7,13 +7,8 @@
*/
import {KeyValuePipe} from '@angular/common';
import {EventEmitter, KeyValueDiffers, WrappedValue, ɵdefaultKeyValueDiffers as defaultKeyValueDiffers} from '@angular/core';
import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
import {defaultComparator} from '../../src/pipes/keyvalue_pipe';
import {SpyChangeDetectorRef} from '../spies';
import {defaultComparator} from '@angular/common/src/pipes/keyvalue_pipe';
import {ɵdefaultKeyValueDiffers as defaultKeyValueDiffers} from '@angular/core';
describe('KeyValuePipe', () => {
it('should return null when given null', () => {

View File

@ -54,7 +54,8 @@ describe('Ivy NgModule', () => {
}
expect(() => createInjector(AModule))
.toThrowError('Circular dependency: type AModule ends up importing itself.');
.toThrowError(
'Circular dependency in DI detected for type AModule. Dependency path: AModule > BModule > AModule.');
});
it('merges imports and exports', () => {

View File

@ -19,6 +19,7 @@
"magic-string": "^0.25.0",
"shelljs": "^0.8.1",
"source-map": "^0.6.1",
"tslib": "^1.9.0",
"yargs": "9.0.1"
},
"peerDependencies": {

View File

@ -0,0 +1,31 @@
/**
* @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';
/**
* A bundle represents the currently compiled entry point format, containing
* information that is necessary for compiling @angular/core with ngcc.
*/
export interface BundleInfo {
isCore: boolean;
isFlat: boolean;
rewriteCoreImportsTo: ts.SourceFile|null;
rewriteCoreDtsImportsTo: ts.SourceFile|null;
}
export function createBundleInfo(
isCore: boolean, rewriteCoreImportsTo: ts.SourceFile | null,
rewriteCoreDtsImportsTo: ts.SourceFile | null): BundleInfo {
return {
isCore,
isFlat: rewriteCoreImportsTo === null,
rewriteCoreImportsTo: rewriteCoreImportsTo,
rewriteCoreDtsImportsTo: rewriteCoreDtsImportsTo,
};
}

View File

@ -17,15 +17,15 @@ export class DependencyHost {
/**
* Get a list of the resolved paths to all the dependencies of this entry point.
* @param from An absolute path to the file whose dependencies we want to get.
* @param resolved A set that will have the resolved dependencies added to it.
* @param resolved A set that will have the absolute paths of resolved entry points added to it.
* @param missing A set that will have the dependencies that could not be found added to it.
* @param deepImports A set that will have the import paths that exist but cannot be mapped to
* entry-points, i.e. deep-imports.
* @param internal A set that is used to track internal dependencies to prevent getting stuck in a
* circular dependency loop.
* @returns an object containing an array of absolute paths to `resolved` depenendencies and an
* array of import specifiers for dependencies that were `missing`.
*/
computeDependencies(
from: string, resolved: Set<string>, missing: Set<string>,
from: string, resolved: Set<string>, missing: Set<string>, deepImports: Set<string>,
internal: Set<string> = new Set()): void {
const fromContents = fs.readFileSync(from, 'utf8');
if (!this.hasImportOrReeportStatements(fromContents)) {
@ -48,14 +48,22 @@ export class DependencyHost {
// Avoid circular dependencies
if (!internal.has(internalDependency)) {
internal.add(internalDependency);
this.computeDependencies(internalDependency, resolved, missing, internal);
this.computeDependencies(
internalDependency, resolved, missing, deepImports, internal);
}
} else {
const externalDependency = this.tryResolveExternal(from, importPath);
if (externalDependency !== null) {
resolved.add(externalDependency);
const resolvedEntryPoint = this.tryResolveEntryPoint(from, importPath);
if (resolvedEntryPoint !== null) {
resolved.add(resolvedEntryPoint);
} else {
missing.add(importPath);
// If the import could not be resolved as entry point, it either does not exist
// at all or is a deep import.
const deeplyImportedFile = this.tryResolve(from, importPath);
if (deeplyImportedFile !== null) {
deepImports.add(importPath);
} else {
missing.add(importPath);
}
}
}
});
@ -91,9 +99,9 @@ export class DependencyHost {
* @returns the resolved path to the entry point directory of the import or null
* if it cannot be resolved.
*/
tryResolveExternal(from: string, to: string): string|null {
const externalDependency = this.tryResolve(from, `${to}/package.json`);
return externalDependency && path.dirname(externalDependency);
tryResolveEntryPoint(from: string, to: string): string|null {
const entryPoint = this.tryResolve(from, `${to}/package.json`);
return entryPoint && path.dirname(entryPoint);
}
/**

View File

@ -87,7 +87,8 @@ export class DependencyResolver {
const dependencies = new Set<string>();
const missing = new Set<string>();
this.host.computeDependencies(entryPointPath, dependencies, missing);
const deepImports = new Set<string>();
this.host.computeDependencies(entryPointPath, dependencies, missing, deepImports);
if (missing.size > 0) {
// This entry point has dependencies that are missing
@ -109,6 +110,13 @@ export class DependencyResolver {
}
});
}
if (deepImports.size) {
const imports = Array.from(deepImports).map(i => `'${i}'`).join(', ');
console.warn(
`Entry point '${entryPoint.name}' contains deep imports into ${imports}. ` +
`This is probably not a problem, but may cause the compilation of entry points to be out of order.`);
}
});
// The map now only holds entry-points that ngcc cares about and whose dependencies

View File

@ -50,6 +50,21 @@ interface EntryPointPackageJson {
typings?: string; // TypeScript .d.ts files
}
/**
* Parses the JSON from a package.json file.
* @param packageJsonPath the absolute path to the package.json file.
* @returns JSON from the package.json file if it is valid, `null` otherwise.
*/
function loadEntryPointPackage(packageJsonPath: string): {[key: string]: any}|null {
try {
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
} catch (e) {
// We may have run into a package.json with unexpected symbols
console.warn(`Failed to read entry point info from ${packageJsonPath} with error ${e}.`);
return null;
}
}
/**
* Try to get entry point info from the given path.
* @param pkgPath the absolute path to the containing npm package
@ -62,6 +77,11 @@ export function getEntryPointInfo(pkgPath: string, entryPoint: string): EntryPoi
return null;
}
const entryPointPackageJson = loadEntryPointPackage(packageJsonPath);
if (!entryPointPackageJson) {
return null;
}
// If there is `esm2015` then `es2015` will be FESM2015, otherwise ESM2015.
// If there is `esm5` then `module` will be FESM5, otherwise it will be ESM5.
const {
@ -75,8 +95,7 @@ export function getEntryPointInfo(pkgPath: string, entryPoint: string): EntryPoi
esm2015,
esm5,
main
}: EntryPointPackageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
} = entryPointPackageJson;
// Minimum requirement is that we have typings and one of esm2015 or fesm2015 formats.
if (!typings || !(fesm2015 || esm2015)) {
return null;

View File

@ -20,6 +20,7 @@ import {EsmRenderer} from '../rendering/esm_renderer';
import {FileInfo, Renderer} from '../rendering/renderer';
import {checkMarkerFile, writeMarkerFile} from './build_marker';
import {BundleInfo, createBundleInfo} from './bundle';
import {EntryPoint, EntryPointFormat} from './entry_point';
/**
@ -70,27 +71,40 @@ export class Transformer {
const host = ts.createCompilerHost(options);
const rootDirs = this.getRootDirs(host, options);
const isCore = entryPoint.name === '@angular/core';
const r3SymbolsPath = isCore ? this.findR3SymbolsPath(dirname(entryPointFilePath)) : null;
const r3SymbolsPath =
isCore ? this.findR3SymbolsPath(dirname(entryPointFilePath), 'r3_symbols.js') : null;
const rootPaths = r3SymbolsPath ? [entryPointFilePath, r3SymbolsPath] : [entryPointFilePath];
const packageProgram = ts.createProgram(rootPaths, options, host);
console.time(entryPoint.name + '(dtsmappper creation)');
const dtsFilePath = entryPoint.typings;
const dtsProgram = transformDts ? ts.createProgram([entryPoint.typings], options, host) : null;
console.timeEnd(entryPoint.name + '(dtsmappper creation)');
const reflectionHost = this.getHost(isCore, format, packageProgram, dtsFilePath, dtsProgram);
const r3SymbolsFile = r3SymbolsPath && packageProgram.getSourceFile(r3SymbolsPath) || null;
// Create the program for processing DTS files if enabled for this format.
const dtsFilePath = entryPoint.typings;
let dtsProgram: ts.Program|null = null;
let r3SymbolsDtsFile: ts.SourceFile|null = null;
if (transformDts) {
console.time(`${entryPoint.name} (dtsMapper creation)`);
const r3SymbolsDtsPath =
isCore ? this.findR3SymbolsPath(dirname(dtsFilePath), 'r3_symbols.d.ts') : null;
const rootDtsPaths = r3SymbolsDtsPath ? [dtsFilePath, r3SymbolsDtsPath] : [dtsFilePath];
dtsProgram = ts.createProgram(rootDtsPaths, options, host);
r3SymbolsDtsFile = r3SymbolsDtsPath && dtsProgram.getSourceFile(r3SymbolsDtsPath) || null;
console.timeEnd(`${entryPoint.name} (dtsMapper creation)`);
}
const bundle = createBundleInfo(isCore, r3SymbolsFile, r3SymbolsDtsFile);
const reflectionHost = this.getHost(isCore, format, packageProgram, dtsFilePath, dtsProgram);
// Parse and analyze the files.
const {decorationAnalyses, switchMarkerAnalyses} =
this.analyzeProgram(packageProgram, reflectionHost, rootDirs, isCore);
console.time(entryPoint.name + '(rendering)');
console.time(`${entryPoint.name} (rendering)`);
// Transform the source files and source maps.
const renderer = this.getRenderer(
format, packageProgram, reflectionHost, isCore, r3SymbolsFile, transformDts);
const renderer = this.getRenderer(format, packageProgram, reflectionHost, bundle, transformDts);
const renderedFiles =
renderer.renderProgram(packageProgram, decorationAnalyses, switchMarkerAnalyses);
console.timeEnd(entryPoint.name + '(rendering)');
console.timeEnd(`${entryPoint.name} (rendering)`);
// Write out all the transformed files.
renderedFiles.forEach(file => this.writeFile(file));
@ -125,17 +139,15 @@ export class Transformer {
}
getRenderer(
format: string, program: ts.Program, host: NgccReflectionHost, isCore: boolean,
rewriteCoreImportsTo: ts.SourceFile|null, transformDts: boolean): Renderer {
format: string, program: ts.Program, host: NgccReflectionHost, bundle: BundleInfo,
transformDts: boolean): Renderer {
switch (format) {
case 'esm2015':
case 'fesm2015':
return new EsmRenderer(
host, isCore, rewriteCoreImportsTo, this.sourcePath, this.targetPath, transformDts);
return new EsmRenderer(host, bundle, this.sourcePath, this.targetPath, transformDts);
case 'esm5':
case 'fesm5':
return new Esm5Renderer(
host, isCore, rewriteCoreImportsTo, this.sourcePath, this.targetPath, transformDts);
return new Esm5Renderer(host, bundle, this.sourcePath, this.targetPath, transformDts);
default:
throw new Error(`Renderer for "${format}" not yet implemented.`);
}
@ -162,8 +174,8 @@ export class Transformer {
writeFileSync(file.path, file.contents, 'utf8');
}
findR3SymbolsPath(directory: string): string|null {
const r3SymbolsFilePath = resolve(directory, 'r3_symbols.js');
findR3SymbolsPath(directory: string, fileName: string): string|null {
const r3SymbolsFilePath = resolve(directory, fileName);
if (existsSync(r3SymbolsFilePath)) {
return r3SymbolsFilePath;
}
@ -181,7 +193,7 @@ export class Transformer {
});
for (const subDirectory of subDirectories) {
const r3SymbolsFilePath = this.findR3SymbolsPath(resolve(directory, subDirectory));
const r3SymbolsFilePath = this.findR3SymbolsPath(resolve(directory, subDirectory), fileName);
if (r3SymbolsFilePath) {
return r3SymbolsFilePath;
}

View File

@ -9,14 +9,14 @@ import * as ts from 'typescript';
import MagicString from 'magic-string';
import {NgccReflectionHost} from '../host/ngcc_host';
import {CompiledClass} from '../analysis/decoration_analyzer';
import {BundleInfo} from '../packages/bundle';
import {EsmRenderer} from './esm_renderer';
export class Esm5Renderer extends EsmRenderer {
constructor(
protected host: NgccReflectionHost, protected isCore: boolean,
protected rewriteCoreImportsTo: ts.SourceFile|null, protected sourcePath: string,
protected targetPath: string, transformDts: boolean) {
super(host, isCore, rewriteCoreImportsTo, sourcePath, targetPath, transformDts);
protected host: NgccReflectionHost, protected bundle: BundleInfo,
protected sourcePath: string, protected targetPath: string, transformDts: boolean) {
super(host, bundle, sourcePath, targetPath, transformDts);
}
/**

View File

@ -9,14 +9,14 @@ import * as ts from 'typescript';
import MagicString from 'magic-string';
import {NgccReflectionHost, POST_R3_MARKER, PRE_R3_MARKER, SwitchableVariableDeclaration} from '../host/ngcc_host';
import {CompiledClass} from '../analysis/decoration_analyzer';
import {BundleInfo} from '../packages/bundle';
import {Renderer} from './renderer';
export class EsmRenderer extends Renderer {
constructor(
protected host: NgccReflectionHost, protected isCore: boolean,
protected rewriteCoreImportsTo: ts.SourceFile|null, protected sourcePath: string,
protected targetPath: string, transformDts: boolean) {
super(host, isCore, rewriteCoreImportsTo, sourcePath, targetPath, transformDts);
protected host: NgccReflectionHost, protected bundle: BundleInfo,
protected sourcePath: string, protected targetPath: string, transformDts: boolean) {
super(host, bundle, sourcePath, targetPath, transformDts);
}
/**

View File

@ -12,10 +12,11 @@ import {ImportManager} from '../../../ngtsc/translator';
export class NgccImportManager extends ImportManager {
constructor(private isFlat: boolean, isCore: boolean, prefix?: string) { super(isCore, prefix); }
generateNamedImport(moduleName: string, symbol: string): string|null {
generateNamedImport(moduleName: string, symbol: string):
{moduleImport: string | null, symbol: string} {
if (this.isFlat && this.isCore && moduleName === '@angular/core') {
return null;
return {moduleImport: null, symbol: this.rewriteSymbol(moduleName, symbol)};
}
return super.generateNamedImport(moduleName, symbol);
}
}
}

View File

@ -19,6 +19,7 @@ import {translateStatement, translateType} from '../../../ngtsc/translator';
import {NgccImportManager} from './ngcc_import_manager';
import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/decoration_analyzer';
import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer';
import {BundleInfo} from '../packages/bundle';
import {IMPORT_PREFIX} from '../constants';
import {NgccReflectionHost, SwitchableVariableDeclaration} from '../host/ngcc_host';
@ -55,9 +56,9 @@ interface DtsClassInfo {
*/
export abstract class Renderer {
constructor(
protected host: NgccReflectionHost, protected isCore: boolean,
protected rewriteCoreImportsTo: ts.SourceFile|null, protected sourcePath: string,
protected targetPath: string, protected transformDts: boolean) {}
protected host: NgccReflectionHost, protected bundle: BundleInfo,
protected sourcePath: string, protected targetPath: string, protected transformDts: boolean) {
}
renderProgram(
program: ts.Program, decorationAnalyses: DecorationAnalyses,
@ -101,7 +102,7 @@ export abstract class Renderer {
if (compiledFile) {
const importManager =
new NgccImportManager(!this.rewriteCoreImportsTo, this.isCore, IMPORT_PREFIX);
new NgccImportManager(this.bundle.isFlat, this.bundle.isCore, IMPORT_PREFIX);
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
compiledFile.compiledClasses.forEach(clazz => {
@ -116,8 +117,8 @@ export abstract class Renderer {
compiledFile.sourceFile);
this.addImports(
outputText,
importManager.getAllImports(compiledFile.sourceFile.fileName, this.rewriteCoreImportsTo));
outputText, importManager.getAllImports(
compiledFile.sourceFile.fileName, this.bundle.rewriteCoreImportsTo));
// TODO: remove contructor param metadata and property decorators (we need info from the
// handlers to do this)
@ -130,7 +131,7 @@ export abstract class Renderer {
renderDtsFile(dtsFile: ts.SourceFile, dtsClasses: DtsClassInfo[]): FileInfo[] {
const input = this.extractSourceMap(dtsFile);
const outputText = new MagicString(input.source);
const importManager = new NgccImportManager(false, this.isCore, IMPORT_PREFIX);
const importManager = new NgccImportManager(false, this.bundle.isCore, IMPORT_PREFIX);
dtsClasses.forEach(dtsClass => {
const endOfClass = dtsClass.dtsDeclaration.getEnd();
@ -142,7 +143,8 @@ export abstract class Renderer {
});
this.addImports(
outputText, importManager.getAllImports(dtsFile.fileName, this.rewriteCoreImportsTo));
outputText,
importManager.getAllImports(dtsFile.fileName, this.bundle.rewriteCoreDtsImportsTo));
return this.renderSourceAndMap(dtsFile, input, outputText);
}

View File

@ -27,61 +27,88 @@ describe('DependencyHost', () => {
it('should not generate a TS AST if the source does not contain any imports or re-exports',
() => {
spyOn(ts, 'createSourceFile');
host.computeDependencies('/no/imports/or/re-exports.js', new Set(), new Set());
host.computeDependencies('/no/imports/or/re-exports.js', new Set(), new Set(), new Set());
expect(ts.createSourceFile).not.toHaveBeenCalled();
});
it('should resolve all the external imports of the source file', () => {
spyOn(host, 'tryResolveExternal')
spyOn(host, 'tryResolveEntryPoint')
.and.callFake((from: string, importPath: string) => `RESOLVED/${importPath}`);
const resolved = new Set();
const missing = new Set();
host.computeDependencies('/external/imports.js', resolved, missing);
const deepImports = new Set();
host.computeDependencies('/external/imports.js', resolved, missing, deepImports);
expect(resolved.size).toBe(2);
expect(resolved.has('RESOLVED/path/to/x'));
expect(resolved.has('RESOLVED/path/to/y'));
expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
expect(resolved.has('RESOLVED/path/to/y')).toBe(true);
});
it('should resolve all the external re-exports of the source file', () => {
spyOn(host, 'tryResolveExternal')
spyOn(host, 'tryResolveEntryPoint')
.and.callFake((from: string, importPath: string) => `RESOLVED/${importPath}`);
const resolved = new Set();
const missing = new Set();
host.computeDependencies('/external/re-exports.js', resolved, missing);
const deepImports = new Set();
host.computeDependencies('/external/re-exports.js', resolved, missing, deepImports);
expect(resolved.size).toBe(2);
expect(resolved.has('RESOLVED/path/to/x'));
expect(resolved.has('RESOLVED/path/to/y'));
expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
expect(resolved.has('RESOLVED/path/to/y')).toBe(true);
});
it('should capture missing external imports', () => {
spyOn(host, 'tryResolveExternal')
spyOn(host, 'tryResolveEntryPoint')
.and.callFake(
(from: string, importPath: string) =>
importPath === 'missing' ? null : `RESOLVED/${importPath}`);
spyOn(host, 'tryResolve').and.callFake(() => null);
const resolved = new Set();
const missing = new Set();
host.computeDependencies('/external/imports-missing.js', resolved, missing);
const deepImports = new Set();
host.computeDependencies('/external/imports-missing.js', resolved, missing, deepImports);
expect(resolved.size).toBe(1);
expect(resolved.has('RESOLVED/path/to/x'));
expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
expect(missing.size).toBe(1);
expect(missing.has('missing'));
expect(missing.has('missing')).toBe(true);
expect(deepImports.size).toBe(0);
});
it('should not register deep imports as missing', () => {
// This scenario verifies the behavior of the dependency analysis when an external import
// is found that does not map to an entry-point but still exists on disk, i.e. a deep import.
// Such deep imports are captured for diagnostics purposes.
const tryResolveEntryPoint = (from: string, importPath: string) =>
importPath === 'deep/import' ? null : `RESOLVED/${importPath}`;
spyOn(host, 'tryResolveEntryPoint').and.callFake(tryResolveEntryPoint);
spyOn(host, 'tryResolve')
.and.callFake((from: string, importPath: string) => `RESOLVED/${importPath}`);
const resolved = new Set();
const missing = new Set();
const deepImports = new Set();
host.computeDependencies('/external/deep-import.js', resolved, missing, deepImports);
expect(resolved.size).toBe(0);
expect(missing.size).toBe(0);
expect(deepImports.size).toBe(1);
expect(deepImports.has('deep/import')).toBe(true);
});
it('should recurse into internal dependencies', () => {
spyOn(host, 'resolveInternal')
.and.callFake(
(from: string, importPath: string) => path.join('/internal', importPath + '.js'));
spyOn(host, 'tryResolveExternal')
spyOn(host, 'tryResolveEntryPoint')
.and.callFake((from: string, importPath: string) => `RESOLVED/${importPath}`);
const getDependenciesSpy = spyOn(host, 'computeDependencies').and.callThrough();
const resolved = new Set();
const missing = new Set();
host.computeDependencies('/internal/outer.js', resolved, missing);
expect(getDependenciesSpy).toHaveBeenCalledWith('/internal/outer.js', resolved, missing);
const deepImports = new Set();
host.computeDependencies('/internal/outer.js', resolved, missing, deepImports);
expect(getDependenciesSpy)
.toHaveBeenCalledWith('/internal/inner.js', resolved, missing, jasmine.any(Set));
.toHaveBeenCalledWith('/internal/outer.js', resolved, missing, deepImports);
expect(getDependenciesSpy)
.toHaveBeenCalledWith(
'/internal/inner.js', resolved, missing, deepImports, jasmine.any(Set));
expect(resolved.size).toBe(1);
expect(resolved.has('RESOLVED/path/to/y'));
expect(resolved.has('RESOLVED/path/to/y')).toBe(true);
});
@ -89,14 +116,15 @@ describe('DependencyHost', () => {
spyOn(host, 'resolveInternal')
.and.callFake(
(from: string, importPath: string) => path.join('/internal', importPath + '.js'));
spyOn(host, 'tryResolveExternal')
spyOn(host, 'tryResolveEntryPoint')
.and.callFake((from: string, importPath: string) => `RESOLVED/${importPath}`);
const resolved = new Set();
const missing = new Set();
host.computeDependencies('/internal/circular-a.js', resolved, missing);
const deepImports = new Set();
host.computeDependencies('/internal/circular-a.js', resolved, missing, deepImports);
expect(resolved.size).toBe(2);
expect(resolved.has('RESOLVED/path/to/x'));
expect(resolved.has('RESOLVED/path/to/y'));
expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
expect(resolved.has('RESOLVED/path/to/y')).toBe(true);
});
function createMockFileSystem() {
@ -105,6 +133,7 @@ describe('DependencyHost', () => {
'/external/imports.js': `import {X} from 'path/to/x';\nimport {Y} from 'path/to/y';`,
'/external/re-exports.js': `export {X} from 'path/to/x';\nexport {Y} from 'path/to/y';`,
'/external/imports-missing.js': `import {X} from 'path/to/x';\nimport {Y} from 'missing';`,
'/external/deep-import.js': `import {Y} from 'deep/import';`,
'/internal/outer.js': `import {X} from './inner';`,
'/internal/inner.js': `import {Y} from 'path/to/y';`,
'/internal/circular-a.js': `import {B} from './circular-b'; import {X} from 'path/to/x';`,
@ -131,18 +160,18 @@ describe('DependencyHost', () => {
describe('tryResolveExternal', () => {
it('should call `tryResolve`, appending `package.json` to the target path', () => {
const tryResolveSpy = spyOn(host, 'tryResolve').and.returnValue('PATH/TO/RESOLVED');
host.tryResolveExternal('SOURCE_PATH', 'TARGET_PATH');
host.tryResolveEntryPoint('SOURCE_PATH', 'TARGET_PATH');
expect(tryResolveSpy).toHaveBeenCalledWith('SOURCE_PATH', 'TARGET_PATH/package.json');
});
it('should return the directory containing the result from `tryResolve', () => {
spyOn(host, 'tryResolve').and.returnValue('PATH/TO/RESOLVED');
expect(host.tryResolveExternal('SOURCE_PATH', 'TARGET_PATH')).toEqual('PATH/TO');
expect(host.tryResolveEntryPoint('SOURCE_PATH', 'TARGET_PATH')).toEqual('PATH/TO');
});
it('should return null if `tryResolve` returns null', () => {
spyOn(host, 'tryResolve').and.returnValue(null);
expect(host.tryResolveExternal('SOURCE_PATH', 'TARGET_PATH')).toEqual(null);
expect(host.tryResolveEntryPoint('SOURCE_PATH', 'TARGET_PATH')).toEqual(null);
});
});

View File

@ -78,6 +78,11 @@ describe('getEntryPointInfo()', () => {
umd: `/some_package/material_style/bundles/material_style.umd.js`,
});
});
it('should return null if the package.json is not valid JSON', () => {
const entryPoint = getEntryPointInfo('/some_package', '/some_package/unexpected_symbols');
expect(entryPoint).toBe(null);
});
});
function createMockFileSystem() {
@ -115,8 +120,15 @@ function createMockFileSystem() {
"module": "./esm5/material_style.es5.js",
"es2015": "./esm2015/material_style.js"
}`,
'material_style.metadata.json': 'some meta data'
}
'material_style.metadata.json': 'some meta data',
},
'unexpected_symbols': {
// package.json might not be a valid JSON
// for example, @schematics/angular contains a package.json blueprint
// with unexpected symbols
'package.json':
'{"devDependencies": {<% if (!minimal) { %>"@types/jasmine": "~2.8.8" <% } %>}}',
},
}
});
}

View File

@ -12,6 +12,7 @@ import MagicString from 'magic-string';
import {makeProgram} from '../helpers/utils';
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {createBundleInfo} from '../../src/packages/bundle';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {EsmRenderer} from '../../src/rendering/esm_renderer';
@ -23,7 +24,8 @@ function setup(file: {name: string, contents: string}, transformDts: boolean = f
const decorationAnalyses =
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program);
const renderer = new EsmRenderer(host, false, null, dir, dir, false);
const bundle = createBundleInfo(false, null, null);
const renderer = new EsmRenderer(host, bundle, dir, dir, false);
return {host, program, sourceFile, renderer, decorationAnalyses, switchMarkerAnalyses};
}

View File

@ -10,6 +10,7 @@ import MagicString from 'magic-string';
import {makeProgram, getDeclaration} from '../helpers/utils';
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {createBundleInfo} from '../../src/packages/bundle';
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
import {Esm5Renderer} from '../../src/rendering/esm5_renderer';
@ -20,7 +21,8 @@ function setup(file: {name: string, contents: string}) {
const decorationAnalyses =
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program);
const renderer = new Esm5Renderer(host, false, null, '', '', false);
const bundle = createBundleInfo(false, null, null);
const renderer = new Esm5Renderer(host, bundle, '', '', false);
return {host, program, sourceFile, renderer, decorationAnalyses, switchMarkerAnalyses};
}

View File

@ -13,11 +13,14 @@ import {fromObject, generateMapFileComment} from 'convert-source-map';
import {makeProgram} from '../helpers/utils';
import {CompiledClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {BundleInfo, createBundleInfo} from '../../src/packages/bundle';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {Renderer} from '../../src/rendering/renderer';
class TestRenderer extends Renderer {
constructor(host: Esm2015ReflectionHost) { super(host, false, null, '/src', '/dist', false); }
constructor(host: Esm2015ReflectionHost, bundle: BundleInfo) {
super(host, bundle, '/src', '/dist', false);
}
addImports(output: MagicString, imports: {name: string, as: string}[]) {
output.prepend('\n// ADD IMPORTS\n');
}
@ -35,13 +38,19 @@ class TestRenderer extends Renderer {
}
}
function createTestRenderer(file: {name: string, contents: string}) {
const program = makeProgram(file);
const host = new Esm2015ReflectionHost(false, program.getTypeChecker());
function createTestRenderer(
files: {name: string, contents: string}[],
options: {isCore?: boolean, rewriteCoreImportsTo?: string} = {}) {
const program = makeProgram(...files);
const rewriteCoreImportsTo =
options.rewriteCoreImportsTo ? program.getSourceFile(options.rewriteCoreImportsTo) ! : null;
const bundle = createBundleInfo(options.isCore || false, rewriteCoreImportsTo, null);
const host = new Esm2015ReflectionHost(bundle.isCore, program.getTypeChecker());
const decorationAnalyses =
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
new DecorationAnalyzer(program.getTypeChecker(), host, [''], bundle.isCore)
.analyzeProgram(program);
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program);
const renderer = new TestRenderer(host);
const renderer = new TestRenderer(host, bundle);
spyOn(renderer, 'addImports').and.callThrough();
spyOn(renderer, 'addDefinitions').and.callThrough();
spyOn(renderer, 'removeDecorators').and.callThrough();
@ -94,7 +103,7 @@ describe('Renderer', () => {
it('should render the modified contents; and a new map file, if the original provided no map file.',
() => {
const {renderer, program, decorationAnalyses, switchMarkerAnalyses} =
createTestRenderer(INPUT_PROGRAM);
createTestRenderer([INPUT_PROGRAM]);
const result = renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
expect(result[0].path).toEqual('/dist/file.js');
expect(result[0].contents)
@ -106,7 +115,7 @@ describe('Renderer', () => {
it('should call addImports with the source code and info about the core Angular library.',
() => {
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} =
createTestRenderer(INPUT_PROGRAM);
createTestRenderer([INPUT_PROGRAM]);
renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
const addImportsSpy = renderer.addImports as jasmine.Spy;
expect(addImportsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
@ -118,7 +127,7 @@ describe('Renderer', () => {
it('should call addDefinitions with the source code, the analyzed class and the renderered definitions.',
() => {
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} =
createTestRenderer(INPUT_PROGRAM);
createTestRenderer([INPUT_PROGRAM]);
renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
expect(addDefinitionsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
@ -137,7 +146,7 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
it('should call removeDecorators with the source code, a map of class decorators that have been analyzed',
() => {
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} =
createTestRenderer(INPUT_PROGRAM);
createTestRenderer([INPUT_PROGRAM]);
renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
const removeDecoratorsSpy = renderer.removeDecorators as jasmine.Spy;
expect(removeDecoratorsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
@ -157,10 +166,10 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
it('should merge any inline source map from the original file and write the output as an inline source map',
() => {
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = createTestRenderer({
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = createTestRenderer([{
...INPUT_PROGRAM,
contents: INPUT_PROGRAM.contents + '\n' + INPUT_PROGRAM_MAP.toComment()
});
}]);
const result = renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
expect(result[0].path).toEqual('/dist/file.js');
expect(result[0].contents)
@ -172,10 +181,10 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
() => {
// Mock out reading the map file from disk
spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON());
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = createTestRenderer({
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = createTestRenderer([{
...INPUT_PROGRAM,
contents: INPUT_PROGRAM.contents + '\n//# sourceMappingURL=file.js.map'
});
}]);
const result = renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
expect(result[0].path).toEqual('/dist/file.js');
expect(result[0].contents)
@ -183,5 +192,48 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""
expect(result[1].path).toEqual('/dist/file.js.map');
expect(result[1].contents).toEqual(MERGED_OUTPUT_PROGRAM_MAP.toJSON());
});
describe('@angular/core support', () => {
it('should render relative imports in ESM bundles', () => {
const R3_SYMBOLS_FILE = {
name: '/src/r3_symbols.js',
contents: `export const NgModule = () => null;`
};
const CORE_FILE = {
name: '/src/core.js',
contents:
`import { NgModule } from './ng_module';\nexport class MyModule {}\nMyModule.decorators = [\n { type: NgModule, args: [] }\n];\n`
};
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = createTestRenderer(
[R3_SYMBOLS_FILE, CORE_FILE],
{isCore: true, rewriteCoreImportsTo: R3_SYMBOLS_FILE.name});
renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
expect(addDefinitionsSpy.calls.first().args[2])
.toContain(`/*@__PURE__*/ ɵngcc0.setClassMetadata(`);
const addImportsSpy = renderer.addImports as jasmine.Spy;
expect(addImportsSpy.calls.first().args[1]).toEqual([{name: './r3_symbols', as: 'ɵngcc0'}]);
});
it('should render no imports in FESM bundles', () => {
const CORE_FILE = {
name: '/src/core.js',
contents: `export const NgModule = () => null;
export class MyModule {}\nMyModule.decorators = [\n { type: NgModule, args: [] }\n];\n`
};
const {decorationAnalyses, program, renderer, switchMarkerAnalyses} =
createTestRenderer([CORE_FILE], {isCore: true});
renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses);
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
expect(addDefinitionsSpy.calls.first().args[2])
.toContain(`/*@__PURE__*/ setClassMetadata(`);
const addImportsSpy = renderer.addImports as jasmine.Spy;
expect(addImportsSpy.calls.first().args[1]).toEqual([]);
});
});
});
});

View File

@ -17,7 +17,7 @@ import * as ts from 'typescript';
import {ClassMemberKind, ReflectionHost} from '../../host';
const TS_DTS_JS_EXTENSION = /(\.d)?\.ts|\.js$/;
const TS_DTS_JS_EXTENSION = /(?:\.d)?\.ts$|\.js$/;
/**
* Represents a value which cannot be determined statically.

View File

@ -42,6 +42,7 @@ function flipIvySwitchInFile(sf: ts.SourceFile): ts.SourceFile {
// Only update the statements in the SourceFile if any have changed.
if (newStatements !== undefined) {
sf = ts.getMutableClone(sf);
sf.statements = ts.createNodeArray(newStatements);
}
return sf;
@ -119,9 +120,6 @@ function flipIvySwitchesInVariableStatement(
/* name */ decl.name,
/* type */ decl.type,
/* initializer */ newIdentifier);
// Keeping parent pointers up to date is important for emit.
newIdentifier.parent = newDeclarations[i];
}
const newDeclList = ts.updateVariableDeclarationList(
@ -133,12 +131,6 @@ function flipIvySwitchesInVariableStatement(
/* modifiers */ stmt.modifiers,
/* declarationList */ newDeclList);
// Keeping parent pointers up to date is important for emit.
for (const decl of newDeclarations) {
decl.parent = newDeclList;
}
newDeclList.parent = newStmt;
newStmt.parent = stmt.parent;
return newStmt;
}

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