Compare commits

...

96 Commits

Author SHA1 Message Date
cda205deb4 release: cut the zone.js-0.10.2 release (#32128)
PR Close #32128
2019-08-13 16:55:04 -07:00
466f5c67d6 docs(zone.js): update release docs instructions (#32128)
PR Close #32128
2019-08-13 16:55:03 -07:00
b9dfe66028 perf(ivy): split view processing into render (create) and refresh (update) pass (#32020)
PR Close #32020
2019-08-13 15:22:42 -07:00
4d96cf5197 ci: update material-unit-tests job to latest commit (#31650)
Updates the `material-unit-tests` job to the latest commit
on the components repository. 097f4335a4e0b6e6b579829ae3a9cffce6292d2b.

This commit ensures that the postinstall script does not run NGC
on schematic code from `@angular/core`. Running NGC on the
generated schematic code can cause unexpected issues as some
migrations import types directly from `@angular/compiler-cli`
while the entry-point is not usable in all cases.

See: https://github.com/angular/angular/issues/29220.

PR Close #31650
2019-08-13 14:40:52 -07:00
024c31da25 feat(core): add undecorated classes migration schematic (#31650)
Introduces a new migration schematic that follows the given
migration plan: https://hackmd.io/@alx/S1XKqMZeS.

First case: The schematic detects decorated directives which
inherit a constructor. The migration ensures that all base
classes until the class with the explicit constructor are
properly decorated with "@Directive()" or "@Component". In
case one of these classes is not decorated, the schematic
adds the abstract "@Directive()" decorator automatically.

Second case: The schematic detects undecorated declarations
and copies the inherited "@Directive()", "@Component" or
"@Pipe" decorator to the undecorated derived class. This
involves non-trivial import rewriting, identifier aliasing
and AOT metadata serializing
(as decorators are not always part of source files)

PR Close #31650
2019-08-13 14:40:52 -07:00
5064dc75ac fix(common): update $locationShim to notify onChange listeners before emitting AngularJS events (#32037)
The $locationShim has onChange listeners to allow for synchronization logic between
AngularJS and Angular. When the AngularJS routing events are emitted first, this can
cause Angular code to be out of sync. Notifying the listeners earlier solves the
problem.

PR Close #32037
2019-08-13 14:23:57 -07:00
bef27f2a28 docs(forms): use a number as input value for formControlName (#30606)
PR #29473 changed the docs to use a string as the input value of `formControlName`, as it used to only accept a string.
This has been changed, and `formControlName` now accepts a string or a number, so the example in the docs can use a binding as they used to.

PR Close #30606
2019-08-13 14:21:25 -07:00
628b0c1154 feat(forms): formControlName also accepts a number (#30606)
This commit relaxes the type of the `formControlName` input to accept both a `string` and a `number`.

Currently, when using a `FormArray`, most templates look like:

```
<div formArrayName="tags">
  <div *ngFor="let tag of tagsArray.controls; index as i">
    <input [formControlName]="i">
  </div>
</div>
```

Here `formControlName` receives a number whereas its input type is a string.

This is fine for VE and `fullTemplateTypeCheck`, but not for Ivy which does a more thorough type checking on inputs with `fullTemplateTypeCheck` enabled and throws `Type 'number' is not assignable to type 'string'`. It is fixable by using `formControlName="{{i}}"` but you have to know the difference between `a="{{b}}"` and `[a]="b"` and change it all over the application codebase. This commit allows the existing code to still type-check.

PR Close #30606
2019-08-13 14:21:25 -07:00
e4d5102b17 build: ensure schematics are built with typescript strict flag (#31967)
Follow-up to #30993 where we build all Angular packages with
the TypeScript `--strict` flag. The flag improves overall code
health and also helps us catch issues easier.

PR Close #31967
2019-08-13 11:39:00 -07:00
b25f925311 docs: edit location doc (#32042)
PR Close #32042
2019-08-13 11:37:13 -07:00
7d4c9e4b67 docs: update marketing resources with Angular UI Toolkit (#31969)
PR Close #31969
2019-08-13 11:36:37 -07:00
a5f39aeda6 refactor(language-service): Return ts.Diagnostic[] for getDiagnostics (#32115)
Part 2/3 of language service refactoring:
Now that the language service is a proper tsserver plugin, all LS
interfaces should return TS values. This PR refactors the
ng.getDiagnostics() API to return ts.Diagnostic[] instead of
ng.Diagnostic[].

PR Close #32115
2019-08-13 11:20:27 -07:00
a91ab15525 fix(language-service): Remove 'context' used for module resolution (#32015)
The language service relies on a "context" file that is used as the
canonical "containing file" when performing module resolution.
This file is unnecessary since the language service host's current
directory always default to the location of tsconfig.json for the
project, which would give the correct result.

This refactoring allows us to simplify the "typescript host" and also
removes the need for custom logic to find tsconfig.json.

PR Close #32015
2019-08-13 11:19:18 -07:00
a95f860a96 docs: correct description of animation example (#32009)
PR Close #32009
2019-08-13 11:16:31 -07:00
6215636bc6 ci: use local strategy for AngularTemplateCompile and TypescriptCompile on CI (#32112)
PR Close #32112
2019-08-13 09:57:50 -07:00
3cd8a2b907 docs: update events page and fix ordering (#32106)
PR Close #32106
2019-08-13 09:57:28 -07:00
ebcf79d7f3 docs(zone.js): update DEVELOPER.md for changelog instruction (#32016)
PR Close #32016
2019-08-13 09:53:00 -07:00
a91a55a6d2 fix(zone.js): browser-legacy should not reference Zone (#32016)
PR Close #32016
2019-08-13 09:53:00 -07:00
48487cb70e ci: add ivy commits to generated CHANGELOG (#32114)
Historically, we've cleaned Ivy commits out of the CHANGELOG because
Ivy was not available except as a preview. Given that Ivy will soon
be the default in 9.0.0, it no longer makes sense to remove the Ivy
commits from the log. This changes the gulp changelog task so that
Ivy commits are included by default.

PR Close #32114
2019-08-12 16:03:37 -07:00
c68371ed14 docs: format currency api (#32107)
PR Close #32107
2019-08-12 15:16:15 -07:00
9406104c0a release: cut the v9.0.0-next.2 release 2019-08-12 13:46:30 -07:00
c0194e0115 docs: release notes for the v8.2.2 release 2019-08-12 13:42:07 -07:00
914900a561 refactor(ivy): remove load instruction (#32067)
These changes remove the `ɵɵload` instruction which isn't being generated anymore.

PR Close #32067
2019-08-12 12:55:18 -07:00
4ea3e7e000 refactor(ivy): combine query load instructions (#32100)
Combines the `loadViewQuery` and `loadContentQuery` instructions since they have the exact same internal logic. Based on a discussion here: https://github.com/angular/angular/pull/32067#pullrequestreview-273001730

PR Close #32100
2019-08-12 10:32:08 -07:00
6eb9c2fab0 perf(ivy): don't read global state when interpolated values don't change (#32093)
PR Close #32093
2019-08-12 10:31:48 -07:00
7b9414565e style: fix inline comment typo (#32090)
PR Close #32090
2019-08-12 07:06:00 -07:00
37de490e23 Revert "feat(compiler): allow selector-less directives as base classes (#31379)" (#32089)
This reverts commit f90c7a9df0 due to breakages in G3.

PR Close #32089
2019-08-09 18:20:53 -07:00
753080133b Revert "docs(forms): use a number as input value for formControlName (#30606)" (#32088)
This reverts commit bed680cff8.

PR Close #32088
2019-08-09 17:29:27 -07:00
9a37e827e2 Revert "feat(forms): formControlName also accepts a number (#30606)" (#32088)
This reverts commit a647298412.

PR Close #32088
2019-08-09 17:29:27 -07:00
3df54be9e4 build: ensure fixup commits match an earlier, unmerged commit (#32023)
Previously, `validate-commit-message` would treat `fixup! `-prefixed
commits like this:
- It would strip the `fixup! ` prefix.
- It would validate the rest of the commit message header as any other
  commit.

However, fixup commits are special in that they need to exactly match an
earlier commit message header (sans the `fixup! ` prefix) in order for
git to treat them correctly. Otherwise, they will not be squashed into
the original commits and will be merged as is. Fixup commits can end up
not matching their original commit for several reasons (e.g. accidental
typo, changing the original commit message, etc.).

This commit prevents invalid fixup commits to pass validation by
ensuring that they match an earlier (unmerged) commit (i.e. a commit
between the current HEAD and the BASE commit).

NOTE: This new behavior is currently not activated in the pre-commit git
      hook, that is used to validate commit messages (because the
      preceding, unmerged commits are not available there). It _is_
      activated in `gulp validate-commit-message`, which is run as part
      of the `lint` job on CI and thus will detect invalid commits,
      before their getting merged.

PR Close #32023
2019-08-09 15:12:38 -07:00
c0d5684078 fix: do not allow squash! commits when merging (#32023)
While `fixup! ` is fine, `squash! ` means that the commit message needs
tweaking, which cannot be done automatically during merging (i.e. it
should be done by the PR author).

Previously, `validate-commit-message` would always allow
`squash! `-prefixed commits, which would cause problems during merging.

This commit changes `validate-commit-message` to make it configurable
whether such commits are allowed and configures the
`gulp validate-commit-message` task, which is run as part of the `lint`
job on CI, to not allow them.

NOTE: This new check is disabled in the pre-commit git hook that is used
      to validate commit messages, because these commits might still be
      useful during development.

PR Close #32023
2019-08-09 15:12:37 -07:00
2b289250d8 refactor: clean up validate-commit-message script (#32023)
This sets the ground for adding stricter rules for fixup commits in a
follow-up PR.

PR Close #32023
2019-08-09 15:12:37 -07:00
ddd02044ea test: clean up and re-organize validate-commit-message tests (#32023)
Mainly making the tests more closely follow the order of checks in the
function implementation, so that it is easier to follow.

PR Close #32023
2019-08-09 15:12:37 -07:00
c198a27a3c test: update golden files (#32069)
PR Close #32069
2019-08-09 14:15:25 -07:00
4f37487b1c fix(bazel): disable treeshaking when generating FESM and UMD bundles (#32069)
There has been a regression where enabling rollup treeshaking causes errors during runtime because it will drop const access which will always evaluate to true or false. However, such `const` in `@angular/core` cannot be dropped because their value is changed when NGCC is run on `@angular/core`

VE
```
const SWITCH_IVY_ENABLED__POST_R3__ = true;
const SWITCH_IVY_ENABLED__PRE_R3__ = false;
const ivyEnabled = SWITCH_IVY_ENABLED__PRE_R3__;
```

Ivy (After NGCC)
```
const SWITCH_IVY_ENABLED__POST_R3__ = true;
const SWITCH_IVY_ENABLED__PRE_R3__ = false;
const ivyEnabled = SWITCH_IVY_ENABLED__POST_R3__;
```

FESM2015
```
load(path) {
	/** @type {?} */
	const legacyOfflineMode = this._compiler instanceof Compiler;
	return legacyOfflineMode ? this.loadFactory(path) : this.loadAndCompile(path);
}
```

ESM2015
```
 load(path) {
	/** @type {?} */
	const legacyOfflineMode = !ivyEnabled && this._compiler instanceof Compiler;
	return legacyOfflineMode ? this.loadFactory(path) : this.loadAndCompile(path);
}
```

From the above we can see that `ivyEnabled ` is being treeshaken away when generating the FESM bundle which is causing runtime errors such as `Cannot find module './lazy/lazy.module.ngfactory'` since in Ivy we will always load the factories.

PR Close #32069
2019-08-09 14:15:25 -07:00
0ddf0c4895 fix(compiler): do not remove whitespace wrapping i18n expansions (#31962)
Similar to interpolation, we do not want to completely remove whitespace
nodes that are siblings of an expansion.

For example, the following template

```html
<div>
  <strong>items left<strong> {count, plural, =1 {item} other {items}}
</div>
```

was being collapsed to

```html
<div><strong>items left<strong>{count, plural, =1 {item} other {items}}</div>
```

which results in the text looking like

```
items left4
```

instead it should be collapsed to

```html
<div><strong>items left<strong> {count, plural, =1 {item} other {items}}</div>
```

which results in the text looking like

```
items left 4
```

---

**Analysis of the code and manual testing has shown that this does not cause
the generated ids to change, so there is no breaking change here.**

PR Close #31962
2019-08-09 12:03:50 -07:00
fd6ed1713d ci: remove codefresh config and supporting files (#32058)
PR Close #32058
2019-08-09 10:53:20 -07:00
eb5412d76f fix(ivy): reuse compilation scope for incremental template changes. (#31932)
Previously if only a component template changed then we would know to
rebuild its component source file. But the compilation was incorrect if the
component was part of an NgModule, since we were not capturing the
compilation scope information that had a been acquired from the NgModule
and was not being regenerated since we were not needing to recompile
the NgModule.

Now we register compilation scope information for each component, via the
`ComponentScopeRegistry` interface, so that it is available for incremental
compilation.

The `ComponentDecoratorHandler` now reads the compilation scope from a
`ComponentScopeReader` interface which is implemented as a compound
reader composed of the original `LocalModuleScopeRegistry` and the
`IncrementalState`.

Fixes #31654

PR Close #31932
2019-08-09 10:50:40 -07:00
7533338362 build: update tslint to version ~5.18.0 (#31879)
PR Close #31879
2019-08-09 10:47:43 -07:00
9896d438c0 refactor(core): move renderer2 migration lint rule into google3 folder (#31817)
Moves the `renderer_to_renderer2` migration google3 tslint rule
into the new `google3` directory. This is done for consistency
as we recently moved all google3 migration rules into a new
`google3` folder (see: f69e4e6f77).

PR Close #31817
2019-08-09 10:46:45 -07:00
684579b338 build: create google3 migration tests bazel target (#31817)
Creates a separate bazel target for the google3 migration
tests. The benefit is that it's faster to run tests for
public migrations in development. Google3 lint rules are
usually another story/implementation and the tests are quite
slow due to how TSLint applies replacements.

Additionally if something changes in the google3 tslint rules,
the tests which aren't affected re-run unnecessarily.

PR Close #31817
2019-08-09 10:46:45 -07:00
695f322dc1 docs: clarify pipe naming (#31806)
PR Close #31806
2019-08-09 10:45:49 -07:00
f90c7a9df0 feat(compiler): allow selector-less directives as base classes (#31379)
In Angular today, the following pattern works:

```typescript
export class BaseDir {
  constructor(@Inject(ViewContainerRef) protected vcr: ViewContainerRef) {}
}

@Directive({
  selector: '[child]',
})
export class ChildDir extends BaseDir {
  // constructor inherited from BaseDir
}
```

A decorated child class can inherit a constructor from an undecorated base
class, so long as the base class has metadata of its own (for JIT mode).
This pattern works regardless of metadata in AOT.

In Angular Ivy, this pattern does not work: without the @Directive
annotation identifying the base class as a directive, information about its
constructor parameters will not be captured by the Ivy compiler. This is a
result of Ivy's locality principle, which is the basis behind a number of
compilation optimizations.

As a solution, @Directive() without a selector will be interpreted as a
"directive base class" annotation. Such a directive cannot be declared in an
NgModule, but can be inherited from. To implement this, a few changes are
made to the ngc compiler:

* the error for a selector-less directive is now generated when an NgModule
  declaring it is processed, not when the directive itself is processed.
* selector-less directives are not tracked along with other directives in
  the compiler, preventing other errors (like their absence in an NgModule)
  from being generated from them.

PR Close #31379
2019-08-09 10:45:22 -07:00
f2466cf4ee docs: fix cli builder doc (#31305)
PR Close #31305
2019-08-09 10:42:23 -07:00
bed680cff8 docs(forms): use a number as input value for formControlName (#30606)
PR #29473 changed the docs to use a string as the input value of `formControlName`, as it used to only accept a string.
This has been changed, and `formControlName` now accepts a string or a number, so the example in the docs can use a binding as they used to.

PR Close #30606
2019-08-09 10:39:23 -07:00
a647298412 feat(forms): formControlName also accepts a number (#30606)
This commit relaxes the type of the `formControlName` input to accept both a `string` and a `number`.

Currently, when using a `FormArray`, most templates look like:

```
<div formArrayName="tags">
  <div *ngFor="let tag of tagsArray.controls; index as i">
    <input [formControlName]="i">
  </div>
</div>
```

Here `formControlName` receives a number whereas its input type is a string.

This is fine for VE and `fullTemplateTypeCheck`, but not for Ivy which does a more thorough type checking on inputs with `fullTemplateTypeCheck` enabled and throws `Type 'number' is not assignable to type 'string'`. It is fixable by using `formControlName="{{i}}"` but you have to know the difference between `a="{{b}}"` and `[a]="b"` and change it all over the application codebase. This commit allows the existing code to still type-check.

PR Close #30606
2019-08-09 10:39:22 -07:00
2abbe98e33 release: cut the v9.0.0-next.1 release 2019-08-08 14:03:23 -07:00
7613f13e54 docs: release notes for the v8.2.1 release 2019-08-08 13:53:11 -07:00
4b8cdd4b57 docs(router): rename incorrect class names (#31815)
PR Close #31815
2019-08-08 12:16:46 -07:00
17e289c39f docs: correct description of output decorator and add links to guide (#31780)
PR Close #31780
2019-08-08 12:15:29 -07:00
2913340af7 docs: update rxjs refs (#31780)
PR Close #31780
2019-08-08 12:15:29 -07:00
f8c27d42ed docs: add note on compatibility mode (#31808)
PR Close #31808
2019-08-08 12:13:57 -07:00
17bb633031 docs: remove download for testing examples (#31949)
Examples for testing are designed specifically to run in StackBlitz and
are not regular cli projects (e.g. their `main.ts` files may load and run
tests instead of bootstrapping an app). They are not intended to be
downloaded and built/run locally using the cli.

In order to avoid confusing users, this commit removes the download links
from the guide. If desired, the projects can still be exported locally
via StackBlitz's UI.

Related to #31020 and #31937.

PR Close #31949
2019-08-08 12:13:29 -07:00
9106271f2c refactor(ivy): remove dependency on global state from isCreationMode (#31959)
PR Close #31959
2019-08-08 12:13:00 -07:00
48a3741d5a refactor(ivy): remove global state access in elementCreate (#31959)
PR Close #31959
2019-08-08 12:13:00 -07:00
9d1f43f3ba perf(ivy): remove unnecessary view type checks (#31959)
PR Close #31959
2019-08-08 12:13:00 -07:00
6f98107d5e perf(ivy): remove global state access in createEmbeddedViewAndNode (#31959)
PR Close #31959
2019-08-08 12:12:59 -07:00
a8e2ee1343 fix(language-service): Make Definition and QuickInfo compatible with TS LS (#31972)
Now that the Angular LS is a proper tsserver plugin, it does not make
sense for it to maintain its own language service API.

This is part one of the effort to remove our custom LanguageService
interface.
This interface is cumbersome because we have to do two transformations:
  ng def -> ts def -> lsp definition

The TS LS interface is more comprehensive, so this allows the Angular LS
to return more information.

PR Close #31972
2019-08-08 12:00:56 -07:00
e906a4f0d8 docs: fix commit link typo in changelog (#32006)
PR Close #32006
2019-08-08 11:48:25 -07:00
b5b33d12d6 docs: remove extraneous word from sentence in styleguide (#32038)
PR Close #32038
2019-08-08 11:43:12 -07:00
22d3cabc10 docs: add missing Oxford comma in styleguide (#32040)
PR Close #32040
2019-08-08 11:42:36 -07:00
a06043b703 perf(ivy): remove global state access from setUpAttributes (#32041)
PR Close #32041
2019-08-08 11:39:20 -07:00
4689ea2727 refactor(ivy): remove unused code (#32041)
PR Close #32041
2019-08-08 11:39:20 -07:00
939529ce5d docs: remove duplicate sentence in toh-pt5 (#32021)
PR Close #32021
2019-08-08 11:19:37 -07:00
46304a4f83 feat(ivy): show error when trying to publish NGCC'd packages (#32031)
Publishing of NGCC packages should not be allowed. It is easy for a user to publish an NGCC'd version of a library they have workspace libraries which are being used in a workspace application.

If a users builds a library and afterwards the application, the library will be transformed with NGCC and since NGCC taints the distributed files that should be published.

With this change we use the npm/yarn `prepublishOnly` hook to display and error and abort the process with a non zero error code when a user tries to publish an NGCC version of the package.

More info: https://docs.npmjs.com/misc/scripts

PR Close #32031
2019-08-08 11:17:38 -07:00
f7eebd0227 build(docs-infra): upgrade cli command docs sources to bb4be27da (#32036)
Updating [angular#master](https://github.com/angular/angular/tree/master) from [cli-builds#master](https://github.com/angular/cli-builds/tree/master).

##
Relevant changes in [commit range](403bcb01c...bb4be27da):

**Modified**
- help/new.json

##

PR Close #32036
2019-08-08 11:16:49 -07:00
8af2cc1efe docs: use proper case in styleguide sentence (#32039)
PR Close #32039
2019-08-08 11:15:52 -07:00
e5a89e047c docs: update node:8-browsers to 10 in testing (#32049)
Fixes #31993

PR Close #32049
2019-08-08 11:15:23 -07:00
29d3b68554 fix(ivy): ngcc - correctly update package.json when createNewEntryPointFormats is true (#32052)
Previously, when run with `createNewEntryPointFormats: true`, `ngcc`
would only update `package.json` with the new entry-point for the first
format property that mapped to a format-path. Subsequent properties
mapping to the same format-path would be detected as processed and not
have their new entry-point format recorded in `package.json`.

This commit fixes this by ensuring `package.json` is updated for all
matching format properties, when writing an `EntryPointBundle`.

PR Close #32052
2019-08-08 11:14:38 -07:00
93d27eefd5 refactor(ivy): ngcc - remove redundant entryPoint argument from writeBundle() (#32052)
The entry-point is already available through the `bundle` argument, so
passing it separately is redundant.

PR Close #32052
2019-08-08 11:14:38 -07:00
ed70f73794 refactor(ivy): ngcc - remove formatProperty from EntryPointBundle (#32052)
Remove the `formatProperty` property from the `EntryPointBundle`
interface, because the property is not directly related to that type.

It was only used in one place, when calling `fileWriter.writeBundle()`,
but we can pass `formatProperty` directrly to `writeBundle()`.

PR Close #32052
2019-08-08 11:14:38 -07:00
ef12e10e59 refactor(ivy): ngcc - split work into distinct analyze/compile/execute phases (#32052)
This refactoring more clearly separates the different phases of the work
performed by `ngcc`, setting the ground for being able to run each phase
independently in the future and improve performance via parallelization.

Inspired by/Based on @alxhub's prototype: alxhub/angular@cb631bdb1

PR Close #32052
2019-08-08 11:14:38 -07:00
2954d1b5ca refactor(ivy): ngcc - only try to process the necessary properties (#32052)
This change basically moves some checks to happen up front and ensures
we don't try to process any more properties than we absolutely need.
(The properties would not be processed before either, but we would
consider them, before finding out that they have already been processed
or that they do not exist in the entry-point's `package.json`.)

This change should make no difference in the work done by `ngcc`, but it
transforms the code in a way that makes the actual work known earlier,
thus making it easier to parallelize the processing of each property in
the future.

PR Close #32052
2019-08-08 11:14:38 -07:00
3077c9a1f8 refactor(ivy): ngcc - make EntryPointJsonProperty-related types and checks a little more strict (#32052)
PR Close #32052
2019-08-08 11:14:38 -07:00
9537b2ff84 refactor(ivy): ngcc - fix return type on makeEntryPointBundle() (#32052)
In commit 7b55ba58b (part of PR #29092), the implementation of
`makeEntryPointBundle()` was changed such that it now always return
`EntryPointBundle` (and not `null`).
However, the return type was not updated and as result we continued to
unnecessarily handle `null` as a potential return value in some places.

This commit fixes the return type to reflect the implementation and
removes the redundant code that was dealing with `null`.

PR Close #32052
2019-08-08 11:14:37 -07:00
961d663fbe fix(ivy): ngcc - report an error if a target has missing dependencies (#31872)
Previously, we either crashed with an obscure error or silently did
nothing. Now we throw an exception but with a helpful message.

PR Close #31872
2019-08-05 13:06:49 -07:00
57e15fc08b fix(ivy): ngcc - do not consider builtin NodeJS modules as missing (#31872)
ngcc analyzes the dependency structure of the entrypoints it needs to
process, as the compilation of entrypoints is ordering sensitive: any
dependent upon entrypoint must be compiled before its dependees. As part
of the analysis of the dependency graph, it is detected when a
dependency of entrypoint is not installed, in which case that entrypoint
will be marked as ignored.

For libraries that work with Angular Universal to run in NodeJS, imports
into builtin NodeJS modules can be present. ngcc's dependency analyzer
can only resolve imports within the TypeScript compilation, which
builtin modules are not part of. Therefore, such imports would
erroneously cause the entrypoint to become ignored.

This commit fixes the problem by taking the NodeJS builtins into account
when dealing with missing imports.

Fixes #31522

PR Close #31872
2019-08-05 13:06:49 -07:00
b70746a113 fix(ivy): ngcc - prevent crash when analyzed target is ignored (#31872)
ngcc analyzes the dependency structure of the entrypoints it needs to
process, as the compilation of entrypoints is ordering sensitive: any
dependent upon entrypoint must be compiled before its dependees. As part
of the analysis of the dependency graph, it is detected when a
dependency of entrypoint is not installed, in which case that entrypoint
will be marked as ignored.

When a target entrypoint to compile is provided, it could occur that
given target is considered ignored because one of its dependencies might
be missing. This situation was not dealt with currently, instead
resulting in a crash of ngcc.

This commit prevents the crash by taking the above scenario into account.

PR Close #31872
2019-08-05 13:06:49 -07:00
0709ed4c2b docs: add doc for compiler options (#31829)
PR Close #31829
2019-08-05 13:01:10 -07:00
fa699f65d7 docs: updated doc for timezone param in DatePipe (#31827)
PR Close #31827
2019-08-05 13:00:26 -07:00
18bc4eda9f docs: updated doc for timezone param in DatePipe (#31827)
PR Close #31827
2019-08-05 13:00:26 -07:00
f542649b2b docs: fix typo in Workspace Config guide code snippet (sourceMaps --> sourceMap) (#31957)
PR Close #31957
2019-08-05 12:57:18 -07:00
a574e462c9 build(docs-infra): ensure the locally built packages exist and are up-to-date in yarn setup-local (#31985)
The `setup-local` npm script uses `NgPackagesInstaller` to replace the
Angular packages with the locally built ones. Previously, it would (a)
assume that the packages were built and (b) it would do anything if the
currently installed versions already correspond to locally built
packages (even if not the latest version).
This could lead to all sorts of errors, such as:
- Confusing error messages, `dist/packages-dist/` was missing.
- Using outdated build artifacts from `dist/packages-dist/` without a
  warning.
- Not installing the latest locally built packages, because the ones
  installed already corresponded to locally built (but older) ones.

This commit fixes these issues by ensuring that:
- The local packages are always built before being used by
  `NgPackagesInstaller`.
- The local packages are installed, even if the ones install already
  correspond to local packages.

NOTE: Special `*-ci` scripts are introduced (for use on CI) that skip
      building the local packages, since that step would have been taken
      care of (in a more efficient way) in a previous CI step.

PR Close #31985
2019-08-05 12:56:44 -07:00
65cafa0eec fix(docs-infra): correctly handle multiple occurrences of an option in NgPackagesInstaller (#31985)
PR Close #31985
2019-08-05 12:56:43 -07:00
18aa173d39 feat(docs-infra): support building the local Angular packages in NgPackagesInstaller (#31985)
Previously, when `NgPackagesInstaller` needed to install the local
Angular packages to a project, it assumed the `dist/packages-dist/`
would exist and contain up-to-date built packages. I.e. it was up to the
user to have built the packages first (by running the appropriate
script).
However, many people not familiar with the `aio/` infrastructure assumed
that `yarn build-local` or `yarn build-with-ivy` would take care of all
the necessary steps.

To avoid getting confusing errors (or worse yet, using outdated local
packages), `NgPackagesInstaller` now has an option to build the packages
before using them.
One caveat is that the build script does not currently work on Windows,
so a warning is printed instead, letting the user know they need to
(somehow) take care of it themselves.

NOTE 1: Since the build script is using bazel, running it should not be
        expensive if the directory is up-to-date (i.e. no changes have
        been made to source code after the last build).
NOTE 2: This commit adds support for `--build-packages`, but does not
        change the default behavior (not building the packages). It will
        be turned on in a subsequent commit.

PR Close #31985
2019-08-05 12:56:43 -07:00
bc8eb8508b refactor(docs-infra): minor refactorings and code simplification in NgPackagesInstaller (#31985)
PR Close #31985
2019-08-05 12:56:43 -07:00
7db269ba6a fix(ivy): ngcc - correctly detect formats processed in previous runs (#32003)
Previously, `ngcc` would avoid processing a `formatPath` that a property
in `package.json` mapped to, if either the _property_ was marked as
processed or the `formatPath` (i.e. the file(s)) was processed in the
same `ngcc` run (since the `compiledFormats` set was not persisted
across runs).
This could lead in a situation where a `formatPath` would be compiled
twice (if for example properties `a` and `b` both mapped to the same
`formatPath` and one would run `ngcc` for property `a` and then `b`).

This commit fixes it by ensuring that as soon as a `formatPath` has been
processed all corresponding properties are marked as processed (which
persists across `ngcc` runs).

PR Close #32003
2019-08-05 12:54:17 -07:00
8e5567d964 perf(ivy): ngcc - avoid unnecessary operations when we only need one format processed (#32003)
Previously, when `ngcc` was called with `compileAllFormats === false`
(i.e. how `@angular/cli` calls it), it would not attempt to process
more properties, once the first was successfully processed. However, it
_would_ continue looping over them and perform some unnecessary
operations, such as:
- Determining the format each property maps to (which can be an
  expensive operation for some properties mapping to either UMD or
  CommonJS).
- Checking whether each property has been processed (which involves
  checking whether any property has been processed with a different
  version of `ngcc` each time).
- Potentially marking properties as processed (which involves a
  file-write operation).

This commit avoids the unnecessary operations by entirely skipping
subsequent properties, once the first one has been successfully
processed. While this theoretically improves performance, it is not
expected to have any noticeable impact in practice, since the list of
`propertiesToConsider` is typically small and the most expensive
operation (marking a property as processed) has low likelihood of
happening (plus these operations are a tiny fraction of `ngcc`'s work).

PR Close #32003
2019-08-05 12:54:17 -07:00
541ce98432 perf(ivy): ngcc - avoid unnecessary file-write operations when marking properties as processed (#32003)
Previously, when `ngcc` needed to mark multiple properties as processed
(e.g. a processed format property and `typings` or all supported
properties for a non-Angular entry-point), it would update each one
separately and write the file to disk multiple times.

This commit changes this, so that multiple properties can be updated at
once with one file-write operation. While this theoretically improves
performance (reducing the I/O operations), it is not expected to have
any noticeable impact in practice, since these operations are a tiny
fraction of `ngcc`'s work.

This change will be useful for a subsequent change to mark all
properties that map to the same `formatPath` as processed, once it is
processed the first time.

PR Close #32003
2019-08-05 12:54:17 -07:00
e7e3f5d952 refactor(ivy): ngcc - remove unused check for format support (#32003)
Now that `ngcc` supports all `EntryPointFormat`s, there is no need to
check if a format is supported, so this operation was a no-op.

PR Close #32003
2019-08-05 12:54:17 -07:00
382d3ed1d2 fix(docs-infra): ui polish (#31013)
- Change margin to the header so that the the label aligns with the header
- Make code in API pages pre-wrap so that you can see the actual text on the screen - helps with issue #27296
- Modified text to be title case in label API to be consistent with rest of app labels
- Removed unused table of contents SCSS file
- TOC SCSS file reorg cleanup
- Soften headers font-weight in API pages
- Make linenums ordered list inside code examples always show as numbers

PR Close #31013
2019-08-05 12:53:02 -07:00
a07de82f79 refactor(upgrade): avoid using static inherited method (#31986)
Using `ParentInjectorPromise.all()` (which is a static method inherited
from `SyncPromise`) causes Closure Compiler (or some related tool) to
complain:

```
TypeError: ...$packages$upgrade$src$common$src$downgrade_component_ParentInjectorPromise.all is not a function
```

Switching to `SyncPromise.all()` (the static method on the parent class)
to avoid this error.

PR Close #31986
2019-08-05 10:01:33 -07:00
2e84f4e0cd build(docs-infra): fix StackBlitz projects and ZIPs for testing guide examples (#31937)
- Update the `stackblitz.json` config files (used for generating
  Stackblitz projects and ZIP archives) to include the correct files
  (taking into account the current layout of the example projects).
- Update the boilerplate files for `testing` examples to match the
  current layout of the example projects.

PR Close #31937
2019-08-05 09:54:52 -07:00
a5b12db7d6 build(docs-infra): correct list of files included in ZIPs to match current layout (#31937)
PR Close #31937
2019-08-05 09:54:52 -07:00
8b94d6a402 build(docs-infra): remove unused StackBlitz/ZIP configurations for setup example (#31937)
The generated StackBlitz project and ZIP archives are no longer used
(and they are broken anyway), so removing them altogether to avoid
confusion.

Note: Still keeping the example project, because that is referenced in
the `setup` guide.

PR Close #31937
2019-08-05 09:54:52 -07:00
96cbcd6da4 feat(zone.js): support Promise.allSettled (#31849)
PR Close #31849
2019-08-05 09:54:37 -07:00
203 changed files with 7012 additions and 2549 deletions

View File

@ -37,5 +37,5 @@ build --verbose_failures=true
# > Example job: https://circleci.com/gh/angular/angular/385517
# We expect that TypeScript compilations will parallelize wider than the number of local cores anyway
# so we should saturate remote workers with TS compilations
build --strategy=TypeScriptCompile=standalone
build --strategy=AngularTemplateCompile=standalone
build --strategy=AngularTemplateCompile=local
build --strategy=TypeScriptCompile=local

View File

@ -143,7 +143,7 @@ var_14: &notify_dev_infra_on_fail
# Cache key for the Material unit tests job. **Note** when updating the SHA in the cache keys,
# also update the SHA for the "MATERIAL_REPO_COMMIT" environment variable.
var_15: &material_unit_tests_cache_key v4-angular-material-701302dc482d7e4b77990b24e3b5ab330bbf1aa5
var_15: &material_unit_tests_cache_key v4-angular-material-097f4335a4e0b6e6b579829ae3a9cffce6292d2b
var_16: &material_unit_tests_cache_key_short v4-angular-material
version: 2
@ -321,7 +321,7 @@ jobs:
- *attach_workspace
- *init_environment
# Build aio (with local Angular packages)
- run: yarn --cwd aio build-local --progress=false
- run: yarn --cwd aio build-local-ci
# Run unit tests
- run: yarn --cwd aio test --progress=false --watch=false
# Run e2e tests
@ -340,7 +340,7 @@ jobs:
- *attach_workspace
- *init_environment
# Build aio with Ivy (using local Angular packages)
- run: yarn --cwd aio build-with-ivy --progress=false
- run: yarn --cwd aio build-with-ivy-ci
# Run unit tests
- run: yarn --cwd aio test --progress=false --watch=false
# Run e2e tests

View File

@ -79,7 +79,7 @@ setPublicVar MATERIAL_REPO_TMP_DIR "/tmp/material2"
setPublicVar MATERIAL_REPO_URL "https://github.com/angular/material2.git"
setPublicVar MATERIAL_REPO_BRANCH "master"
# **NOTE**: When updating the commit SHA, also update the cache key in the CircleCI "config.yml".
setPublicVar MATERIAL_REPO_COMMIT "701302dc482d7e4b77990b24e3b5ab330bbf1aa5"
setPublicVar MATERIAL_REPO_COMMIT "097f4335a4e0b6e6b579829ae3a9cffce6292d2b"
# Source `$BASH_ENV` to make the variables available immediately.
source $BASH_ENV;

View File

@ -1,126 +0,0 @@
# escape=`
ARG core=mcr.microsoft.com/windows/servercore:1809
ARG target=mcr.microsoft.com/powershell:windowsservercore-1809
FROM $core as download
ARG node_version=10.13.0
ARG yarn_version=1.13.0
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
ENV GPG_VERSION 2.3.4
RUN Invoke-WebRequest $('https://files.gpg4win.org/gpg4win-vanilla-{0}.exe' -f $env:GPG_VERSION) -OutFile 'gpg4win.exe' -UseBasicParsing ; `
Start-Process .\gpg4win.exe -ArgumentList '/S' -NoNewWindow -Wait
RUN @( `
'94AE36675C464D64BAFA68DD7434390BDBE9B9C5', `
'FD3A5288F042B6850C66B31F09FE44734EB7990E', `
'71DCFD284A79C3B38668286BC97EC7A07EDE3FC1', `
'DD8F2338BAE7501E3DD5AC78C273792F7D83545D', `
'C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8', `
'B9AE9905FFD7803F25714661B63B535A4C206CA9', `
'77984A986EBC2AA786BC0F66B01FBB92821C587A', `
'8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600', `
'4ED778F539E3634C779C87C6D7062848A1AB005C', `
'A48C2BEE680E841632CD4E44F07496B3EB3C1762', `
'B9E2F5981AA6E0CD28160D9FF13993A75599653C' `
) | foreach { `
gpg --keyserver ha.pool.sks-keyservers.net --recv-keys $_ ; `
}
ENV NODE_VERSION=$node_version
RUN Invoke-WebRequest $('https://nodejs.org/dist/v{0}/SHASUMS256.txt.asc' -f $env:NODE_VERSION) -OutFile 'SHASUMS256.txt.asc' -UseBasicParsing ; `
gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc
RUN Invoke-WebRequest $('https://nodejs.org/dist/v{0}/node-v{0}-win-x64.zip' -f $env:NODE_VERSION) -OutFile 'node.zip' -UseBasicParsing ; `
$sum = $(cat SHASUMS256.txt.asc | sls $(' node-v{0}-win-x64.zip' -f $env:NODE_VERSION)) -Split ' ' ; `
if ((Get-FileHash node.zip -Algorithm sha256).Hash -ne $sum[0]) { Write-Error 'SHA256 mismatch' } ; `
Expand-Archive node.zip -DestinationPath C:\ ; `
Rename-Item -Path $('C:\node-v{0}-win-x64' -f $env:NODE_VERSION) -NewName 'C:\nodejs'
ENV YARN_VERSION=$yarn_version
RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; `
Invoke-WebRequest $('https://yarnpkg.com/downloads/{0}/yarn-{0}.msi' -f $env:YARN_VERSION) -OutFile yarn.msi -UseBasicParsing ; `
$sig = Get-AuthenticodeSignature yarn.msi ; `
if ($sig.Status -ne 'Valid') { Write-Error 'Authenticode signature is not valid' } ; `
Write-Output $sig.SignerCertificate.Thumbprint ; `
if (@( `
'7E253367F8A102A91D04829E37F3410F14B68A5F', `
'AF764E1EA56C762617BDC757C8B0F3780A0CF5F9' `
) -notcontains $sig.SignerCertificate.Thumbprint) { Write-Error 'Unknown signer certificate' } ; `
Start-Process msiexec.exe -ArgumentList '/i', 'yarn.msi', '/quiet', '/norestart' -NoNewWindow -Wait
ENV GIT_VERSION 2.20.1
ENV GIT_DOWNLOAD_URL https://github.com/git-for-windows/git/releases/download/v${GIT_VERSION}.windows.1/MinGit-${GIT_VERSION}-busybox-64-bit.zip
ENV GIT_SHA256 9817ab455d9cbd0b09d8664b4afbe4bbf78d18b556b3541d09238501a749486c
RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; `
Invoke-WebRequest -UseBasicParsing $env:GIT_DOWNLOAD_URL -OutFile git.zip; `
if ((Get-FileHash git.zip -Algorithm sha256).Hash -ne $env:GIT_SHA256) {exit 1} ; `
Expand-Archive git.zip -DestinationPath C:\git; `
Remove-Item git.zip
FROM $target as baseimage
ENV NPM_CONFIG_LOGLEVEL info
COPY --from=download /nodejs /nodejs
COPY --from=download [ "/Program Files (x86)/yarn", "/yarn" ]
COPY --from=download /git /git
ARG SETX=/M
RUN setx %SETX% PATH "%PATH%;C:\nodejs;C:\yarn\bin;C:\git\cmd;C:\git\mingw64\bin;C:\git\usr\bin"
CMD [ "node.exe" ]
FROM baseimage
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
# Install Bazel prereqs on Windows (https://docs.bazel.build/versions/master/install-windows.html)
# Install MSYS2
RUN Invoke-WebRequest -UseBasicParsing 'https://www.7-zip.org/a/7z1805-x64.exe' -OutFile 7z.exe; `
Start-Process -FilePath 'C:\\7z.exe' -ArgumentList '/S', '/D=C:\\7zip0' -NoNewWindow -Wait; `
Invoke-WebRequest -UseBasicParsing 'http://repo.msys2.org/distrib/x86_64/msys2-base-x86_64-20180531.tar.xz' -OutFile msys2.tar.xz; `
Start-Process -FilePath 'C:\\7zip\\7z' -ArgumentList 'e', 'msys2.tar.xz' -Wait; `
Start-Process -FilePath 'C:\\7zip\\7z' -ArgumentList 'x', 'msys2.tar', '-oC:\\' -Wait; `
Remove-Item msys2.tar.xz; `
Remove-Item msys2.tar; `
Remove-Item 7z.exe; `
Remove-Item -Recurse 7zip; `
[Environment]::SetEnvironmentVariable('Path', $env:Path + ';C:\msys64\usr\bin', [System.EnvironmentVariableTarget]::Machine); `
[Environment]::SetEnvironmentVariable('BAZEL_SH', 'C:\msys64\usr\bin\bash.exe', [System.EnvironmentVariableTarget]::Machine)
# Install MSYS2 packages
RUN C:\msys64\usr\bin\bash.exe -l -c \"pacman --needed --noconfirm -S zip unzip patch diffutils git\"
# Install VS Build Tools (required to build C++ targets)
RUN Invoke-WebRequest -UseBasicParsing https://download.visualstudio.microsoft.com/download/pr/df649173-11e9-4af2-8eb7-0eb02ba8958a/cadb5bdac41e55bb8f6a6b7c45273370/vs_buildtools.exe -OutFile vs_BuildTools.exe; `
# Installer won't detect DOTNET_SKIP_FIRST_TIME_EXPERIENCE if ENV is used, must use setx /M
setx /M DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1; `
Start-Process vs_BuildTools.exe `
-ArgumentList `
'--add', 'Microsoft.VisualStudio.Workload.VCTools', `
'--add', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', `
'--add', 'Microsoft.Component.VC.Runtime.UCRTSDK', `
'--add', 'Microsoft.VisualStudio.Component.Windows10SDK.17763', `
'--quiet', '--norestart', '--nocache' `
-NoNewWindow -Wait; `
Remove-Item -Force vs_buildtools.exe; `
Remove-Item -Force -Recurse \"${Env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\"; `
Remove-Item -Force -Recurse ${Env:TEMP}\*; `
Remove-Item -Force -Recurse \"${Env:ProgramData}\Package Cache\"; `
[Environment]::SetEnvironmentVariable('BAZEL_VC', \"${Env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\BuildTools\VC\", [System.EnvironmentVariableTarget]::Machine)
# Install Python (required to build Python targets)
RUN Invoke-WebRequest -UseBasicParsing https://www.python.org/ftp/python/3.5.1/python-3.5.1.exe -OutFile python-3.5.1.exe; `
Start-Process python-3.5.1.exe -ArgumentList '/quiet InstallAllUsers=1 PrependPath=1' -Wait; `
Remove-Item -Force python-3.5.1.exe
CMD ["cmd.exe"]

View File

@ -1,33 +0,0 @@
# CodeFresh configuration
[![Codefresh build status](https://g.codefresh.io/api/badges/pipeline/angular/angular%2Fangular%2Fangular?type=cf-1)](https://g.codefresh.io/public/accounts/angular/pipelines/angular/angular/angular)
This folder contains configuration for the [CodeFresh](<https://codefresh.io/>) based CI checks for this repository.
## The build pipeline
CodeFresh uses a several pipeline for each repository. The `codefresh.yml` file defines pipeline [build steps](https://codefresh.io/docs/docs/configure-ci-cd-pipeline/introduction-to-codefresh-pipelines/) for this repository.
Run results can be seen in the GitHub checks interface and in the [public pipeline](https://g.codefresh.io/public/accounts/angular/pipelines/angular/angular/angular)
Although most configuration is done via `pipeline.yml`, some options are only available in the online [pipeline settings](https://g.codefresh.io/pipelines/angular/services?repoOwner=angular&repoName=angular&project=angular%2Fangular&context=github&serviceName=angular%2Fangular), which needs a login to access.
## Caretaker
CodeFresh status can be found at <http://status.codefresh.io/>.
Issues related to the CodeFresh setup should be escalated to the Tools Team via the current caretaker, followed by Alex Eagle and Filipe Silva.
## Rollout strategy
Currently it is only used for tests on Windows platforms, on the master branch, and without pushing user-facing reports. It's only possible to see current builds in the [public pipeline dashboard](https://g.codefresh.io/public/accounts/angular/pipelines/angular/angular/angular).
After a week or two of running like this, we should reassess how stable and reliable it is.
Next steps include:
- building PRs
- showing build status publicly
- blocking PRs that break the build
- expanding the test suite

View File

@ -1,38 +0,0 @@
# These options are enabled when running on CI
# We do this by copying this file to /etc/bazel.bazelrc at the start of the build.
# See documentation in /docs/BAZEL.md
# Save built files and downloaded repositories in a location that can be cached by CodeFresh and
# shared between builds. This helps speed up the analysis time significantly with Bazel managed node
# dependencies on the CI.
# https://codefresh.io/docs/docs/configure-ci-cd-pipeline/introduction-to-codefresh-pipelines/#caching-the-artifacts-of-your-build-system
build --repository_cache=C:/codefresh/volume/bazel_repository_cache
# Setting the output_base to a Docker volume is currently broken because of a Docker bug on Windows:
# https://github.com/moby/moby/issues/37024
# This affects Bazel because bazel_output_base\external\bazel_tools is an absolute path junction.
# When its fixed we can uncomment this line, and use a different output_base for Ivy tests (they
# use a separate compiler and destructively replace the cache).
# startup --output_base=C:/codefresh/volume/bazel_output_base
# Don't be spammy in the logs
# TODO(gmagolan): Hide progress again once build performance improves
# Presently, CircleCI can timeout during bazel test ... with the following
# error: Too long with no output (exceeded 10m0s)
build --noshow_progress
# Print all the options that apply to the build.
# This helps us diagnose which options override others
# (e.g. /etc/bazel.bazelrc vs. tools/bazel.rc)
build --announce_rc
# 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 CodeFresh VMs
# TODO(filipesilva): determine the correct memory limit
build --local_resources=10240,8.0,1.0
# Retry in the event of flakes, eg. https://circleci.com/gh/angular/angular/31309
test --flaky_test_attempts=2
# More details on failures
build --verbose_failures=true

View File

@ -1,28 +0,0 @@
version: '1.0'
steps:
BuildImage:
title: Build Docker image
type: build
image_name: node-bazel-windows
working_directory: ./.codefresh
no_cf_cache: true
build_arguments:
- node_version=10.13.0
- yarn_version=1.13.0
dockerfile: ./Dockerfile.win-1809
RunTests:
title: Run Bazel tests
image: ${{BuildImage}}
commands:
# Install dependencies
- yarn install --frozen-lockfile --non-interactive --network-timeout 100000 --no-progress
# Add Bazel CI config
- copy .codefresh\bazel.rc %ProgramData%\bazel.bazelrc
# Run tests
# At the moment 'browser:chromium-local' are broken in CI while locally they work
# VE
- yarn bazel test --build_tag_filters=-ivy-only --test_tag_filters=-ivy-only,-browser:chromium-local //...
# Ivy
- yarn bazel test --define=compile=aot --build_tag_filters=-no-ivy-aot,-fixme-ivy-aot --test_tag_filters=-no-ivy-aot,-fixme-ivy-aot,-browser:chromium-local //...

1
.github/CODEOWNERS vendored
View File

@ -884,7 +884,6 @@ testing/** @angular/fw-test
/* @angular/fw-dev-infra
/.buildkite/** @angular/fw-dev-infra
/.circleci/** @angular/fw-dev-infra
/.codefresh/** @angular/fw-dev-infra
/.devcontainer/** @angular/fw-dev-infra
/.github/** @angular/fw-dev-infra
/.vscode/** @angular/fw-dev-infra

View File

@ -1,3 +1,52 @@
<a name="9.0.0-next.2"></a>
# [9.0.0-next.2](https://github.com/angular/angular/compare/9.0.0-next.1...9.0.0-next.2) (2019-08-12)
### Bug Fixes
* **bazel:** disable treeshaking when generating FESM and UMD bundles ([#32069](https://github.com/angular/angular/issues/32069)) ([4f37487](https://github.com/angular/angular/commit/4f37487))
* **compiler:** do not remove whitespace wrapping i18n expansions ([#31962](https://github.com/angular/angular/issues/31962)) ([0ddf0c4](https://github.com/angular/angular/commit/0ddf0c4))
* **ivy:** reuse compilation scope for incremental template changes. ([#31932](https://github.com/angular/angular/issues/31932)) ([eb5412d](https://github.com/angular/angular/commit/eb5412d)), closes [#31654](https://github.com/angular/angular/issues/31654)
### Performance Improvements
* **ivy:** don't read global state when interpolated values don't change ([#32093](https://github.com/angular/angular/issues/32093)) ([6eb9c2f](https://github.com/angular/angular/commit/6eb9c2f))
<a name="8.2.2"></a>
## [8.2.2](https://github.com/angular/angular/compare/8.2.1...8.2.2) (2019-08-12)
### Bug Fixes
* **bazel:** disable treeshaking when generating FESM and UMD bundles ([#32069](https://github.com/angular/angular/issues/32069)) ([3420d29](https://github.com/angular/angular/commit/3420d29))
<a name="9.0.0-next.1"></a>
# [9.0.0-next.1](https://github.com/angular/angular/compare/9.0.0-next.0...9.0.0-next.1) (2019-08-08)
### Bug Fixes
* **language-service:** getSourceFile() should only be called on TS files ([#31920](https://github.com/angular/angular/issues/31920)) ([e8b8f6d](https://github.com/angular/angular/commit/e8b8f6d))
* **language-service:** Make Definition and QuickInfo compatible with TS LS ([#31972](https://github.com/angular/angular/issues/31972)) ([a8e2ee1](https://github.com/angular/angular/commit/a8e2ee1))
* **upgrade:** compile downgraded components synchronously (if possible) ([#31840](https://github.com/angular/angular/issues/31840)) ([c1ae612](https://github.com/angular/angular/commit/c1ae612)), closes [#27217](https://github.com/angular/angular/issues/27217) [#30330](https://github.com/angular/angular/issues/30330)
<a name="8.2.1"></a>
## [8.2.1](https://github.com/angular/angular/compare/8.2.0...8.2.1) (2019-08-08)
### Bug Fixes
* **upgrade:** compile downgraded components synchronously (if possible) ([#31840](https://github.com/angular/angular/issues/31840)) ([04ebd59](https://github.com/angular/angular/commit/04ebd59)), closes [#27217](https://github.com/angular/angular/issues/27217) [#30330](https://github.com/angular/angular/issues/30330)
<a name="9.0.0-next.0"></a>
# [9.0.0-next.0](https://github.com/angular/angular/compare/8.2.0-next.2...9.0.0-next.0) (2019-07-31)
@ -10,7 +59,7 @@
### Features
* **core:** TypeScript 3.5 support ([#31615](https://github.com/angular/angular/issues/31615)) ([6ece7db]
* **core:** TypeScript 3.5 support ([#31615](https://github.com/angular/angular/issues/31615)) ([6ece7db](https://github.com/angular/angular/commit/6ece7db))
* **core:** add automatic migration from Renderer to Renderer2 ([#30936](https://github.com/angular/angular/issues/30936)) ([c095597](https://github.com/angular/angular/commit/c095597))
* **bazel:** compile targets used for indexing by Kythe with Ivy ([#31786](https://github.com/angular/angular/issues/31786)) ([82055b2](https://github.com/angular/angular/commit/82055b2))
* **upgrade:** support $element in upgraded component template/templateUrl functions ([#31637](https://github.com/angular/angular/issues/31637)) ([29e1c53](https://github.com/angular/angular/commit/29e1c53))

View File

@ -14,10 +14,12 @@ Here are the most important tasks you might need to use:
* `yarn` - install all the dependencies.
* `yarn setup` - install all the dependencies, boilerplate, stackblitz, zips and run dgeni on the docs.
* `yarn setup-local` - same as `setup`, but use the locally built Angular packages for aio and docs examples boilerplate.
* `yarn setup-local` - same as `setup`, but build the Angular packages from the source code and use these locally built versions (instead of the ones fetched from npm) for aio and docs examples boilerplate.
* `yarn build` - create a production build of the application (after installing dependencies, boilerplate, etc).
* `yarn build-local` - same as `build`, but use `setup-local` instead of `setup`.
* `yarn build-with-ivy` - same as `build-local`, but in addition also turns on `ivy` mode in aio.
(Note: To turn on `ivy` mode in examples, see `yarn boilerplate:add` below.)
* `yarn start` - run a development web server that watches the files; then builds the doc-viewer and reloads the page, as necessary.
* `yarn serve-and-sync` - run both the `docs-watch` and `start` in the same console.
@ -31,7 +33,10 @@ Here are the most important tasks you might need to use:
* `yarn docs-lint` - check that the doc gen code follows our style rules.
* `yarn docs-test` - run the unit tests for the doc generation code.
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally. Add the option `--local` to use your local version of Angular contained in the "dist" folder.
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally.
- Add the option `--local` to use your local version of Angular contained in the "dist" folder.
- Add the option `--ivy` to turn on `ivy` mode.
* `yarn boilerplate:remove` - remove all the boilerplate code that was added via `yarn boilerplate:add`.
* `yarn generate-stackblitz` - generate the stackblitz files that are used by the `live-example` tags in the docs.
* `yarn generate-zips` - generate the zip files from the examples. Zip available via the `live-example` tags in the docs.

View File

@ -1,12 +0,0 @@
{
"description": "Quickstart AppComponent Testing",
"files":[
"src/browser-test-shim.js",
"src/app/app.component.ts",
"src/app/app.component.spec.ts",
"src/quickstart-specs.html"
],
"main": "src/quickstart-specs.html",
"file": "src/app/app.component.spec.ts",
"tags": ["quickstart", "setup", "testing"]
}

View File

@ -1,36 +0,0 @@
<!-- Run application specs in a browser -->
<!-- #docregion -->
<!DOCTYPE html>
<html>
<head>
<base href="/">
<title>1st Specs</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
</head>
<body>
<!-- Polyfills -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
<script src="node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
<script src="node_modules/jasmine-core/lib/jasmine-core/boot.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/zone.js/dist/zone-testing.js"></script>
<!-- #docregion files -->
<script>
var __spec_files__ = [
'app/app.component.spec'
];
</script>
<!-- #enddocregion files-->
<script src="browser-test-shim.js"></script>
</body>
</html>

View File

@ -1,12 +0,0 @@
{
"description": "QuickStart Setup",
"files": [
"src/app/app.component.ts",
"src/app/app.module.ts",
"src/index.html",
"src/main.ts",
"src/styles.css"
],
"file": "src/app/app.component.ts",
"tags": ["quickstart", "setup", "seed"]
}

View File

@ -1,24 +1,22 @@
{
"description": "Testing - specs",
"files":[
"src/expected.ts",
"src/index-specs.html",
"src/main-specs.ts",
"src/styles.css",
"src/test.css",
"src/tests.sb.ts",
"e2e/src/**/*.ts",
"src/app/**/*.css",
"src/app/**/*.html",
"src/app/**/*.ts",
"src/app/**/*.spec.ts",
"src/testing/*.ts",
"src/testing/**/*",
"!src/main.ts",
"!src/app/bag/*.*",
"!src/app/1st.spec.ts",
"src/expected.ts",
"src/test.css",
"src/tests.sb.ts",
"src/main-specs.ts",
"src/index-specs.html"
"src/**/*.spec.ts"
],
"main": "src/index-specs.html",
"tags": ["testing"]

View File

@ -1,3 +1,3 @@
const jasmineRequire = require('jasmine-core/lib/jasmine-core/jasmine.js');
import jasmineRequire from 'jasmine-core/lib/jasmine-core/jasmine.js';
window['jasmineRequire'] = jasmineRequire;

View File

@ -1,19 +1,18 @@
{
"description": "Heroes Test App",
"files":[
"src/index.html",
"src/main.ts",
"src/styles.css",
"src/test.css",
"e2e/src/**/*.ts",
"src/app/**/*.css",
"src/app/**/*.html",
"src/app/**/*.ts",
"!src/app/bag/*.*",
"!src/test.ts",
"src/test.css",
"src/main.ts",
"src/index.html"
"!src/**/*.spec.ts"
],
"tags": ["testing"]
}

View File

@ -0,0 +1,148 @@
# Angular compiler options
When you use [AOT compilation](guide/aot-compiler), you can control how your application is compiled by specifying *template* compiler options in the `tsconfig.json` [TypeScript configuration file](guide/typescript-configuration).
The template options object, `angularCompilerOptions`, is a sibling to the `compilerOptions` object that supplies standard options to the TypeScript compiler.
```json
{
"compilerOptions": {
"experimentalDecorators": true,
...
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"preserveWhitespaces": true,
...
}
}
```
This page describes the available Angular template compiler options.
### `allowEmptyCodegenFiles`
When true, generate all possible files even if they are empty. Default is false. Used by the Bazel build rules to simplify how Bazel rules track file dependencies. Do not use this option outside of the Bazel rules.
### `annotationsAs`
Modifies how Angular-specific annotations are emitted to improve tree-shaking. Non-Angular annotations are not affected. One of `static fields` (the default) or `decorators`.
* By default, the compiler replaces decorators with a static field in the class, which allows advanced tree-shakers like [Closure compiler](https://github.com/google/closure-compiler) to remove unused classes.
* The `decorators` value leaves the decorators in place, which makes compilation faster. TypeScript emits calls to the` __decorate` helper. Use `--emitDecoratorMetadata` for runtime reflection (but note taht the resulting code will not properly tree-shake.
### `annotateForClosureCompiler`
When true, use [Tsickle](https://github.com/angular/tsickle) to annotate the emitted JavaScript with [JSDoc](http://usejsdoc.org/) comments needed by the
[Closure Compiler](https://github.com/google/closure-compiler). Default is false.
### `disableExpressionLowering`
When true (the default), transforms code that is or could be used in an annotation, to allow it to be imported from template factory modules. See [metadata rewriting](guide/aot-compiler#metadata-rewriting) for more information.
When `false`, disables this rewriting, requiring the rewriting to be done manually.
### `disableTypeScriptVersionCheck`
When `true`, the compiler does not check the TypeScript version and does not report an error when an unsupported version of TypeScript is used. Not recommended, as unsupported versions of TypeScript might have undefined behavior. Default is false.
### `enableResourceInlining`
When true, replaces the `templateUrl` and `styleUrls` property in all `@Component` decorators with inlined contents in `template` and `styles` properties.
When enabled, the `.js` output of `ngc` does not include any lazy-loaded template or style URLs.
{@a enablelegacytemplate}
### `enableLegacyTemplate`
When true, enables use of the `<template>` element, which was deprecated in Angular 4.0, in favor of `<ng-template>` (to avoid colliding with the DOM's element of the same name). Default is false. Might be required by some third-party Angular libraries. |
### `flatModuleId`
The module ID to use for importing a flat module (when `flatModuleOutFile` is true). References generated by the template compiler use this module name when importing symbols
from the flat module. Ignored if `flatModuleOutFile` is false.
### `flatModuleOutFile`
When true, generates a flat module index of the given file name and the corresponding flat module metadata. Use to create flat modules that are packaged similarly to `@angular/core` and `@angular/common`. When this option is used, the `package.json` for the library should refer
to the generated flat module index instead of the library index file.
Produces only one `.metadata.json` file, which contains all the metadata necessary
for symbols exported from the library index. In the generated `.ngfactory.js` files, the flat
module index is used to import symbols that includes both the public API from the library index
as well as shrowded internal symbols.
By default the `.ts` file supplied in the `files` field is assumed to be the library index.
If more than one `.ts` file is specified, `libraryIndex` is used to select the file to use.
If more than one `.ts` file is supplied without a `libraryIndex`, an error is produced.
A flat module index `.d.ts` and `.js` is created with the given `flatModuleOutFile` name in the same location as the library index `.d.ts` file.
For example, if a library uses the `public_api.ts` file as the library index of the module, the `tsconfig.json` `files` field would be `["public_api.ts"]`.
The `flatModuleOutFile` options could then be set to (for example) `"index.js"`, which produces `index.d.ts` and `index.metadata.json` files.
The `module` field of the library's `package.json` would be `"index.js"` and the `typings` field
would be `"index.d.ts"`.
### `fullTemplateTypeCheck`
When true (recommended), enables the [binding expression validation](guide/aot-compiler#binding-expression-validation) phase of the template compiler, which uses TypeScript to validate binding expressions.
Default is currently false.
### `generateCodeForLibraries`
When true (the default), generates factory files (`.ngfactory.js` and `.ngstyle.js`)
for `.d.ts` files with a corresponding `.metadata.json` file.
When false, factory files are generated only for `.ts` files. Do this when using factory summaries.
### `preserveWhitespaces`
When false (the default), removes blank text nodes from compiled templates, which results in smaller emitted template factory modules. Set to true to preserve blank text nodes.
### `skipMetadataEmit`
When true, does not to produce `.metadata.json` files. Default is `false`.
The `.metadata.json` files contain information needed by the template compiler from a `.ts`
file that is not included in the `.d.ts` file produced by the TypeScript compiler.
This information includes, for example, the content of annotations (such as a component's template), which TypeScript emits to the `.js` file but not to the `.d.ts` file.
You can set to `true` when using factory summaries, because the factory summaries
include a copy of the information that is in the `.metadata.json` file.
Set to `true` if you are using TypeScript's `--outFile` option, because the metadata files
are not valid for this style of TypeScript output. However, we do not recommend using `--outFile` with Angular. Use a bundler, such as [webpack](https://webpack.js.org/), instead.
### `skipTemplateCodegen`
When true, does not emit `.ngfactory.js` and `.ngstyle.js` files. This turns off most of the template compiler and disables the reporting of template diagnostics.
Can be used to instruct the template compiler to produce `.metadata.json` files for distribution with an `npm` package while avoiding the production of `.ngfactory.js` and `.ngstyle.js` files that cannot be distributed to `npm`.
### `strictMetadataEmit`
When true, reports an error to the `.metadata.json` file if `"skipMetadataEmit"` is `false`.
Default is false. Use only when `"skipMetadataEmit"` is false and `"skipTemplateCodeGen"` is true.
This option is intended to validate the `.metadata.json` files emitted for bundling with an `npm` package. The validation is strict and can emit errors for metadata that would never produce an error when used by the template compiler. You can choose to suppress the error emitted by this option for an exported symbol by including `@dynamic` in the comment documenting the symbol.
It is valid for `.metadata.json` files to contain errors.
The template compiler reports these errors if the metadata is used to determine the contents of an annotation.
The metadata collector cannot predict the symbols that are designed for use in an annotation, so it preemptively includes error nodes in the metadata for the exported symbols.
The template compiler can then use the error nodes to report an error if these symbols are used.
If the client of a library intends to use a symbol in an annotation, the template compiler does not normally report this until the client uses the symbol.
This option allows detection of these errors during the build phase of
the library and is used, for example, in producing Angular libraries themselves.
### `strictInjectionParameters`
When true (recommended), reports an error for a supplied parameter whose injection type cannot be determined. When false (currently the default), constructor parameters of classes marked with `@Injectable` whose type cannot be resolved produce a warning.
### `trace`
When true, prints extra information while compiling templates. Default is false.

View File

@ -79,11 +79,9 @@ there are fewer opportunities for injection attacks.
When you use the Angular AOT compiler, you can control your app compilation in two ways:
* By providing template compiler options in the `tsconfig.json` file.
* By [specifying Angular metadata](#metadata-aot), as described below.
For more information, see [Angular template compiler options](#compiler-options).
* By [specifying Angular metadata](#metadata-aot).
* By providing options in the `tsconfig.json` [TypeScript configuration file](guide/typescript-configuration). See [Angular compiler options](guide/angular-compiler-options).
{@a metadata-aot}
@ -1165,7 +1163,7 @@ Chuck: After reviewing your PR comment I'm still at a loss. See [comment there](
In the validation phase, the Angular template compiler uses the TypeScript compiler to validate the
binding expressions in templates. Enable this phase explicitly by adding the compiler
option `"fullTemplateTypeCheck"` in the `"angularCompilerOptions"` of the project's `tsconfig.json` (see
[Angular Compiler Options](#compiler-options)).
[Angular Compiler Options](guide/angular-compiler-options)).
Template validation produces error messages when a type error is detected in a template binding
expression, similar to how type errors are reported by the TypeScript compiler against code in a `.ts`
@ -1329,198 +1327,3 @@ Similar to TypeScript Compiler, Angular Compiler also supports `extends` in the
}
```
More information about tsconfig extends can be found in the [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html).
{@a compiler-options}
## Angular template compiler options
The template compiler options are specified as members of the `"angularCompilerOptions"` object in the `tsconfig.json` file. Specify template compiler options along with the options supplied to the TypeScript compiler as shown here:
```json
{
"compilerOptions": {
"experimentalDecorators": true,
...
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"preserveWhitespaces": true,
...
}
}
```
The following section describes the Angular's template compiler options.
### *enableResourceInlining*
This option instructs the compiler to replace the `templateUrl` and `styleUrls` property in all `@Component` decorators with inlined contents in `template` and `styles` properties.
When enabled, the `.js` output of `ngc` will have no lazy-loaded `templateUrl` or `styleUrls`.
### *skipMetadataEmit*
This option tells the compiler not to produce `.metadata.json` files.
The option is `false` by default.
`.metadata.json` files contain information needed by the template compiler from a `.ts`
file that is not included in the `.d.ts` file produced by the TypeScript compiler. This information contains,
for example, the content of annotations (such as a component's template), which TypeScript
emits to the `.js` file but not to the `.d.ts` file.
This option should be set to `true` if you are using TypeScript's `--outFile` option, because the metadata files
are not valid for this style of TypeScript output. It is not recommended to use `--outFile` with
Angular. Use a bundler, such as [webpack](https://webpack.js.org/), instead.
This option can also be set to `true` when using factory summaries because the factory summaries
include a copy of the information that is in the `.metadata.json` file.
### *strictMetadataEmit*
This option tells the template compiler to report an error to the `.metadata.json`
file if `"skipMetadataEmit"` is `false`. This option is `false` by default. This should only be used when `"skipMetadataEmit"` is `false` and `"skipTemplateCodeGen"` is `true`.
This option is intended to validate the `.metadata.json` files emitted for bundling with an `npm` package. The validation is strict and can emit errors for metadata that would never produce an error when used by the template compiler. You can choose to suppress the error emitted by this option for an exported symbol by including `@dynamic` in the comment documenting the symbol.
It is valid for `.metadata.json` files to contain errors. The template compiler reports these errors
if the metadata is used to determine the contents of an annotation. The metadata
collector cannot predict the symbols that are designed for use in an annotation, so it will preemptively
include error nodes in the metadata for the exported symbols. The template compiler can then use the error
nodes to report an error if these symbols are used. If the client of a library intends to use a symbol in an annotation, the template compiler will not normally report
this until the client uses the symbol. This option allows detecting these errors during the build phase of
the library and is used, for example, in producing Angular libraries themselves.
### *skipTemplateCodegen*
This option tells the compiler to suppress emitting `.ngfactory.js` and `.ngstyle.js` files. When set,
this turns off most of the template compiler and disables reporting template diagnostics.
This option can be used to instruct the
template compiler to produce `.metadata.json` files for distribution with an `npm` package while
avoiding the production of `.ngfactory.js` and `.ngstyle.js` files that cannot be distributed to
`npm`.
### *strictInjectionParameters*
When set to `true`, this options tells the compiler to report an error for a parameter supplied
whose injection type cannot be determined. When this option is not provided or is `false`, constructor parameters of classes marked with `@Injectable` whose type cannot be resolved will
produce a warning.
*Note*: It is recommended to change this option explicitly to `true` as this option will default to `true` in the future.
### *flatModuleOutFile*
When set to `true`, this option tells the template compiler to generate a flat module
index of the given file name and the corresponding flat module metadata. Use this option when creating
flat modules that are packaged similarly to `@angular/core` and `@angular/common`. When this option
is used, the `package.json` for the library should refer
to the generated flat module index instead of the library index file. With this
option only one `.metadata.json` file is produced, which contains all the metadata necessary
for symbols exported from the library index. In the generated `.ngfactory.js` files, the flat
module index is used to import symbols that includes both the public API from the library index
as well as shrowded internal symbols.
By default the `.ts` file supplied in the `files` field is assumed to be the library index.
If more than one `.ts` file is specified, `libraryIndex` is used to select the file to use.
If more than one `.ts` file is supplied without a `libraryIndex`, an error is produced. A flat module
index `.d.ts` and `.js` will be created with the given `flatModuleOutFile` name in the same
location as the library index `.d.ts` file. For example, if a library uses the
`public_api.ts` file as the library index of the module, the `tsconfig.json` `files` field
would be `["public_api.ts"]`. The `flatModuleOutFile` options could then be set to, for
example `"index.js"`, which produces `index.d.ts` and `index.metadata.json` files. The
library's `package.json`'s `module` field would be `"index.js"` and the `typings` field
would be `"index.d.ts"`.
### *flatModuleId*
This option specifies the preferred module id to use for importing a flat module.
References generated by the template compiler will use this module name when importing symbols
from the flat module.
This is only meaningful when `flatModuleOutFile` is also supplied. Otherwise the compiler ignores
this option.
### *generateCodeForLibraries*
This option tells the template compiler to generate factory files (`.ngfactory.js` and `.ngstyle.js`)
for `.d.ts` files with a corresponding `.metadata.json` file. This option defaults to
`true`. When this option is `false`, factory files are generated only for `.ts` files.
This option should be set to `false` when using factory summaries.
### *fullTemplateTypeCheck*
This option tells the compiler to enable the [binding expression validation](#binding-expression-validation)
phase of the template compiler which uses TypeScript to validate binding expressions.
This option is `false` by default.
*Note*: It is recommended to set this to `true` because this option will default to `true` in the future.
### *annotateForClosureCompiler*
This option tells the compiler to use [Tsickle](https://github.com/angular/tsickle) to annotate the emitted
JavaScript with [JSDoc](http://usejsdoc.org/) comments needed by the
[Closure Compiler](https://github.com/google/closure-compiler). This option defaults to `false`.
### *annotationsAs*
Use this option to modify how the Angular specific annotations are emitted to improve tree-shaking. Non-Angular
annotations and decorators are unaffected. Default is `static fields`.
<style>
td, th {vertical-align: top}
</style>
<table>
<tr>
<th>Value</th>
<th>Description</th>
</tr>
<tr>
<td><code>decorators</code></td>
<td>Leave the decorators in place. This makes compilation faster. TypeScript will emit calls to the __decorate helper. Use <code>--emitDecoratorMetadata</code> for runtime reflection. However, the resulting code will not properly tree-shake.</td>
</tr>
<tr>
<td><code>static fields</code></td>
<td>Replace decorators with a static field in the class. Allows advanced tree-shakers like
<a href="https://github.com/google/closure-compiler">Closure compiler</a> to remove unused classes.</td>
</tr>
</table>
### *trace*
This tells the compiler to print extra information while compiling templates.
### *enableLegacyTemplate*
Use of the `<template>` element was deprecated starting in Angular 4.0 in favor of using
`<ng-template>` to avoid colliding with the DOM's element of the same name. Setting this option to
`true` enables the use of the deprecated `<template>` element. This option
is `false` by default. This option might be required by some third-party Angular libraries.
### *disableExpressionLowering*
The Angular template compiler transforms code that is used, or could be used, in an annotation
to allow it to be imported from template factory modules. See
[metadata rewriting](#metadata-rewriting) for more information.
Setting this option to `false` disables this rewriting, requiring the rewriting to be
done manually.
### *disableTypeScriptVersionCheck*
When `true`, this option tells the compiler not to check the TypeScript version.
The compiler will skip checking and will not error out when an unsupported version of TypeScript is used.
Setting this option to `true` is not recommended because unsupported versions of TypeScript might have undefined behavior.
This option is `false` by default.
### *preserveWhitespaces*
This option tells the compiler whether to remove blank text nodes from compiled templates.
As of v6, this option is `false` by default, which results in smaller emitted template factory modules.
### *allowEmptyCodegenFiles*
Tells the compiler to generate all the possible generated files even if they are empty. This option is
`false` by default. This is an option used by the Bazel build rules and is needed to simplify
how Bazel rules track file dependencies. It is not recommended to use this option outside of the Bazel
rules.

View File

@ -53,7 +53,7 @@ Angular supports most recent browsers. This includes the following specific vers
IE
</td>
<td>
11, 10, 9
11, 10, 9 ("compatibility view" mode not supported)
</td>
</tr>
<tr>
@ -183,7 +183,7 @@ These are the polyfills required to run an Angular application on each supported
<td>
Chrome, Firefox, Edge, <br>
Safari, Android, IE10+
Safari, Android, IE 10+
</td>
<td>
@ -197,7 +197,7 @@ These are the polyfills required to run an Angular application on each supported
<tr style="vertical-align: top">
<td>
IE9
IE 9
</td>
<td>
@ -275,7 +275,7 @@ Some features of Angular may require additional polyfills.
</td>
<td>
All but Chrome, Firefox, Edge, IE11 and Safari 10
All but Chrome, Firefox, Edge, IE 11 and Safari 10
</td>
</tr>
@ -294,7 +294,7 @@ Some features of Angular may require additional polyfills.
</td>
<td>
IE10, IE11
IE 10, IE 11
</td>
</tr>

View File

@ -253,7 +253,7 @@ In the `package.json` file, add a `builders` key that tells the Architect tool w
</code-example>
The official name of our builder is now ` @example/command-runner:command`.
The first part of this is the package name (resolved using node resolution), and the second part is the builder name (resolved using the `builder.json` file).
The first part of this is the package name (resolved using node resolution), and the second part is the builder name (resolved using the `builders.json` file).
Using one of our `options` is very straightforward, we did this in the previous section when we accessed `options.command`.
@ -279,28 +279,28 @@ By default, for example, the `build` command runs the builder `@angular-devkit/
"myApp": {
...
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/myApp",
"index": "src/index.html",
...
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
...
}
}
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/myApp",
"index": "src/index.html",
...
},
...
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
...
}
}
},
...
</code-example>
@ -419,15 +419,13 @@ We need to update the `angular.json` file to add a target for this builder to th
"projects": {
"builder-test": {
"architect": {
"builder-test": {
"touch": {
"builder": "@example/command-runner:command",
"options": {
"command": "touch",
"args": [
"src/main.ts"
]
}
"touch": {
"builder": "@example/command-runner:command",
"options": {
"command": "touch",
"args": [
"src/main.ts"
]
}
},
"build": {
@ -497,14 +495,14 @@ The test uses the builder to run the `ls` command, then validates that it ran su
<code-example language="typescript">
import { Architect, ArchitectHost } from '@angular-devkit/architect';
import { Architect } from '@angular-devkit/architect';
import { TestingArchitectHost } from '@angular-devkit/architect/testing';
// Our builder forwards the STDOUT of the command to the logger.
import { logging, schema } from '@angular-devkit/core';
describe('Command Runner Builder', () => {
let architect: Architect;
let architectHost: ArchitectHost;
let architectHost: TestingArchitectHost;
beforeEach(async () => {
const registry = new schema.CoreSchemaRegistry();

View File

@ -30,7 +30,7 @@ The Filter/Stagger tab in the live example shows a list of heroes with an introd
The following example demonstrates how to use `query()` and `stagger()` functions on the entry of an animated element.
* Use `query()` to look for any element entering or leaving the page. The query specifies elements meeting certain CSS class criteria.
* Use `query()` to look for an element entering the page that meets certain criteria.
* For each of these elements, use `style()` to set the same initial style for the element. Make it invisible and use `transform` to move it out of position so that it can slide into place.

View File

@ -178,7 +178,7 @@ For more information, see [/deep/, >>>, and ::ng-deep](guide/component-styles#de
{@a template-tag}
### &lt;template&gt; tag
The `<template>` tag was deprecated in v4 to avoid colliding with the DOM's element of the same name (such as when using web components). Use `<ng-template>` instead. For more information, see the [Ahead-of-Time Compilation](guide/aot-compiler#enablelegacytemplate) guide.
The `<template>` tag was deprecated in v4 to avoid colliding with the DOM's element of the same name (such as when using web components). Use `<ng-template>` instead. For more information, see the [Ahead-of-Time Compilation](guide/angular-compiler-options#enablelegacytemplate) guide.

View File

@ -2,15 +2,17 @@
Angular makes use of observables as an interface to handle a variety of common asynchronous operations. For example:
* The `EventEmitter` class extends `Observable`.
* You can define [custom events](guide/template-syntax#custom-events-with-eventemitter) that send observable output data from a child to a parent component.
* The HTTP module uses observables to handle AJAX requests and responses.
* The Router and Forms modules use observables to listen for and respond to user-input events.
## Event emitter
## Transmitting data between components
Angular provides an `EventEmitter` class that is used when publishing values from a component through the `@Output()` decorator. `EventEmitter` extends `Observable`, adding an `emit()` method so it can send arbitrary values. When you call `emit()`, it passes the emitted value to the `next()` method of any subscribed observer.
Angular provides an `EventEmitter` class that is used when publishing values from a component through the [`@Output()` decorator](guide/template-syntax#how-to-use-output).
`EventEmitter` extends [RxJS `Subject`](https://rxjs.dev/api/index/class/Subject), adding an `emit()` method so it can send arbitrary values.
When you call `emit()`, it passes the emitted value to the `next()` method of any subscribed observer.
A good example of usage can be found on the [EventEmitter](https://angular.io/api/core/EventEmitter) documentation. Here is the example component that listens for open and close events:
A good example of usage can be found in the [EventEmitter](https://angular.io/api/core/EventEmitter) documentation. Here is the example component that listens for open and close events:
`<zippy (open)="onOpen($event)" (close)="onClose($event)"></zippy>`

View File

@ -1,6 +1,6 @@
# The RxJS library
Reactive programming is an asynchronous programming paradigm concerned with data streams and the propagation of change ([Wikipedia](https://en.wikipedia.org/wiki/Reactive_programming)). RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using observables that makes it easier to compose asynchronous or callback-based code ([RxJS Docs](http://reactivex.io/rxjs/)).
Reactive programming is an asynchronous programming paradigm concerned with data streams and the propagation of change ([Wikipedia](https://en.wikipedia.org/wiki/Reactive_programming)). RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using observables that makes it easier to compose asynchronous or callback-based code. See ([RxJS Docs](https://rxjs.dev/guide/overview)).
RxJS provides an implementation of the `Observable` type, which is needed until the type becomes part of the language and until browsers support it. The library also provides utility functions for creating and working with observables. These utility functions can be used for:
@ -45,7 +45,7 @@ The `pipe()` function is also a method on the RxJS `Observable`, so you use this
### Common operators
RxJS provides many operators, but only a handful are used frequently. For a list of operators and usage samples, visit the [RxJS API Documentation](https://rxjs-dev.firebaseapp.com/api).
RxJS provides many operators, but only a handful are used frequently. For a list of operators and usage samples, visit the [RxJS API Documentation](https://rxjs.dev/api).
<div class="alert is-helpful">
Note that, for Angular apps, we prefer combining operators with pipes, rather than chaining. Chaining is used in many RxJS examples.

View File

@ -927,7 +927,7 @@ As always, strive for consistency.
<div class="s-rule do">
**Do** use a custom prefix for a component selector.
For example, the prefix `toh` represents from **T**our **o**f **H**eroes and the prefix `admin` represents an admin feature area.
For example, the prefix `toh` represents **T**our **o**f **H**eroes and the prefix `admin` represents an admin feature area.
</div>
@ -1080,6 +1080,10 @@ For example, the prefix `toh` represents from **T**our **o**f **H**eroes and the
**Do** use consistent names for all pipes, named after their feature.
The pipe class name should use [UpperCamelCase](guide/glossary#case-types)
(the general convention for class names),
and the corresponding `name` string should use *lowerCamelCase*.
The `name` string cannot use hyphens ("dash-case" or "kebab-case").
</div>
@ -1670,7 +1674,7 @@ keep the **F**lattest structure you can, and
**Why?** LIFT Provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly.
**Why?** LIFT provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly.
To confirm your intuition about a particular structure, ask:
_can I quickly open and start work in all of the related files for this feature_?
@ -1690,7 +1694,7 @@ _can I quickly open and start work in all of the related files for this feature_
**Do** make locating code intuitive, simple and fast.
**Do** make locating code intuitive, simple, and fast.
</div>

View File

@ -6,14 +6,14 @@ This guide offers tips and techniques for unit and integration testing Angular a
The guide presents tests of a sample application created with the [Angular CLI](cli). This sample application is much like the one created in the [_Tour of Heroes_ tutorial](tutorial).
The sample application and all tests in this guide are available for inspection and experimentation:
- <live-example embedded-style>Sample app</live-example>
- <live-example stackblitz="specs">Tests</live-example>
- <live-example embedded-style noDownload>Sample app</live-example>
- <live-example stackblitz="specs" noDownload>Tests</live-example>
<hr>
## Setup
The Angular CLI downloads and install everything you need to test an Angular application with the [Jasmine test framework](https://jasmine.github.io/).
The Angular CLI downloads and installs everything you need to test an Angular application with the [Jasmine test framework](https://jasmine.github.io/).
The project you create with the CLI is immediately ready to test.
Just run the [`ng test`](cli/test) CLI command:
@ -116,7 +116,7 @@ jobs:
build:
working_directory: ~/my-project
docker:
- image: circleci/node:8-browsers
- image: circleci/node:10-browsers
steps:
- checkout
- restore_cache:

View File

@ -348,7 +348,7 @@ For example:
<code-example language="json">
"sourceMaps": { "scripts": true, "styles": false, "hidden": true, "vendor": true }
"sourceMap": { "scripts": true, "styles": false, "hidden": true, "vendor": true }
</code-example>

View File

@ -13,36 +13,18 @@
</tr>
</thead>
<tbody>
<!-- ng-conf 2019-->
<tr>
<th><a href="https://ng-conf.org/" title="ng-conf">ng-conf</a></th>
<td>Salt Lake City, Utah</td>
<td>May 1-3, 2019</td>
</tr>
<!-- ngVikings 2019-->
<tr>
<th><a href="https://ngvikings.org/" title="ngVikings">ngVikings</a></th>
<td>Copenhagen, Denmark</td>
<td>May 26 (workshops), 27-28 (conference), 2019</td>
</tr>
<!-- ngJapan-->
<tr>
<th><a href="https://ngjapan.org" title="ng-japan">ng-japan</a></th>
<td>Tokyo, Japan</td>
<td>July 13, 2019</td>
</tr>
<!-- AngularConnect 2019-->
<tr>
<th><a href="https://www.angularconnect.com/?utm_source=angular.io&utm_medium=referral" title="AngularConnect">AngularConnect</a></th>
<td>London, UK</td>
<td>September 19-20, 2019</td>
</tr>
<!-- NG-DE 2019-->
<tr>
<th><a href="https://ng-de.org/" title="NG-DE">NG-DE</a></th>
<td>Berlin, Germany</td>
<td>August 29th workshops, 30-31 conference, 2019</td>
</tr>
<!-- AngularConnect 2019-->
<tr>
<th><a href="https://www.angularconnect.com/?utm_source=angular.io&utm_medium=referral" title="AngularConnect">AngularConnect</a></th>
<td>London, UK</td>
<td>September 19-20, 2019</td>
</tr>
<!-- ReactiveConf 2019 -->
<tr>
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
@ -62,6 +44,24 @@
</tr>
</thead>
<tbody>
<!-- ngJapan-->
<tr>
<th><a href="https://ngjapan.org" title="ng-japan">ng-japan</a></th>
<td>Tokyo, Japan</td>
<td>July 13, 2019</td>
</tr>
<!-- ngVikings 2019-->
<tr>
<th><a href="https://ngvikings.org/" title="ngVikings">ngVikings</a></th>
<td>Copenhagen, Denmark</td>
<td>May 26 (workshops), 27-28 (conference), 2019</td>
</tr>
<!-- ng-conf 2019-->
<tr>
<th><a href="https://ng-conf.org/" title="ng-conf">ng-conf</a></th>
<td>Salt Lake City, Utah</td>
<td>May 1-3, 2019</td>
</tr>
<!-- ng-India 2019-->
<tr>
<th><a href="https://www.ng-ind.com/" title="ng-India">ng-India</a></th>

View File

@ -259,6 +259,12 @@
"UI Components": {
"order": 4,
"resources": {
"AngularUIToolkit": {
"desc": "Angular UI Toolkit: 115 professionally maintained UI components ranging from a robust grid to charts and more. Try for free & build Angular apps faster.",
"rev": true,
"title": "Angular UI Toolkit",
"url": "https://www.angular-ui-tools.com"
},
"IgniteUIforAngular": {
"desc": "Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps.",
"rev": true,

View File

@ -597,6 +597,11 @@
"title": "Ahead-of-Time Compilation",
"tooltip": "Learn why and how to use the Ahead-of-Time (AOT) compiler."
},
{
"url": "guide/angular-compiler-options",
"title": "Compiler Options",
"tooltip": "Configuration options for the AOT compiler."
},
{
"url": "guide/build",
"title": "Building & Serving",

View File

@ -91,8 +91,6 @@ configures it with the `routes` in one step by calling
Next, `AppRoutingModule` exports `RouterModule` so it will be available throughout the app.
Open the `AppComponent` template and replace the `<app-heroes>` element with a `<router-outlet>` element.
<code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts (exports array)" region="export-routermodule">
</code-example>

View File

@ -8,7 +8,7 @@
"scripts": {
"preinstall": "node ../tools/yarn/check-yarn.js",
"postinstall": "node tools/cli-patches/patch.js",
"aio-use-local": "node tools/ng-packages-installer overwrite . --debug",
"aio-use-local": "node tools/ng-packages-installer overwrite . --debug --force --build-packages",
"aio-use-npm": "node tools/ng-packages-installer restore .",
"aio-check-local": "node tools/ng-packages-installer check .",
"ng": "yarn check-env && ng",
@ -17,18 +17,22 @@
"build": "yarn ~~build",
"prebuild-local": "yarn setup-local",
"build-local": "yarn ~~build",
"prebuild-local-ci": "yarn setup-local --no-build-packages",
"build-local-ci": "yarn ~~build --progress=false",
"prebuild-with-ivy": "yarn setup-local && node scripts/switch-to-ivy",
"build-with-ivy": "yarn ~~build",
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js 403bcb01c",
"prebuild-with-ivy-ci": "yarn setup-local --no-build-packages && node scripts/switch-to-ivy",
"build-with-ivy-ci": "yarn ~~build --progress=false",
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js bb4be27da",
"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",
"e2e": "ng e2e --no-webdriver-update",
"presetup": "yarn --cwd .. install && yarn install --frozen-lockfile && yarn ~~check-env && yarn ~~clean-generated && yarn boilerplate:remove",
"setup": "yarn aio-use-npm && yarn example-use-npm",
"setup": "yarn example-use-npm && yarn aio-use-npm",
"postsetup": "yarn boilerplate:add && yarn extract-cli-command-docs && yarn docs",
"presetup-local": "yarn presetup",
"setup-local": "yarn aio-use-local && yarn example-use-local",
"setup-local": "yarn example-use-local && yarn aio-use-local",
"postsetup-local": "yarn postsetup",
"set-opensearch-url": "node --eval \"const sh = require('shelljs'); sh.set('-e'); sh.sed('-i', /PLACEHOLDER_URL/g, process.argv[1], 'dist/assets/opensearch.xml');\"",
"presmoke-tests": "yarn update-webdriver",
@ -39,7 +43,7 @@
"test-pwa-score-localhost": "run-p --race \"~~light-server -s dist -p 4200 --quiet\" \"test-pwa-score http://localhost:4200 {1} {2}\" --",
"example-e2e": "yarn example-check-local && node ./tools/examples/run-example-e2e",
"example-lint": "tslint --config \"content/examples/tslint.json\" \"content/examples/**/*.ts\" --exclude \"content/examples/styleguide/**/*.avoid.ts\"",
"example-use-local": "node tools/ng-packages-installer overwrite ./tools/examples/shared --debug",
"example-use-local": "node tools/ng-packages-installer overwrite ./tools/examples/shared --debug --force",
"example-use-npm": "node tools/ng-packages-installer restore ./tools/examples/shared",
"example-check-local": "node tools/ng-packages-installer check ./tools/examples/shared",
"deploy-production": "scripts/deploy-to-firebase.sh",

View File

@ -9,5 +9,4 @@
@import 'marketing-layout';
@import 'not-found';
@import 'sidenav';
@import 'table-of-contents';
@import 'top-menu';

View File

@ -1,10 +0,0 @@
nav#main-table-of-contents {
width: 200px;
height: 900px;
position: fixed;
right: 0;
top: 50px;
bottom: 100px;
margin-left: 32px;
background-color: $blue;
}

View File

@ -2,112 +2,96 @@
max-width: 1200px;
table {
margin: 12px 0 24px;
margin: 12px 0 24px;
th {
text-transform: none;
@include font-size(16);
font-weight: bold;
th {
text-transform: none;
@include font-size(16);
font-weight: bold;
}
tr {
border-bottom: 1px solid $lightgray;
}
td {
vertical-align: middle;
}
// This is overriding a style here:
// https://github.com/angular/angular/blob/95993e1/aio/src/styles/2-modules/_table.scss#L58-L62
tbody > tr > td tr td:first-child {
@media screen and (max-width: 480px) {
background-color: inherit;
}
}
hr {
margin: 16px 0;
}
tr:last-child {
border-bottom: none;
}
&.item-table {
td {
padding: 32px;
}
}
&.list-table {
td {
padding: 16px 24px;
}
}
.short-description {
margin-left: 0;
}
&.parameters-table {
margin-top: 0;
@include font-size(14);
box-shadow: none;
tr {
border-bottom: 1px solid $lightgray;
@media screen and (max-width: 480px) {
display: flex;
flex-direction: column;
}
td:first-child {
font-weight: 600;
padding-left: 16px;
width: 20%;
}
}
td {
vertical-align: middle;
padding: 8px 8px 8px 0;
border: 0;
vertical-align: top;
}
}
// This is overriding a style here:
// https://github.com/angular/angular/blob/95993e1/aio/src/styles/2-modules/_table.scss#L58-L62
tbody > tr > td tr td:first-child {
@media screen and (max-width: 480px) {
background-color: inherit;
}
}
hr {
margin: 16px 0;
}
tr:last-child {
border-bottom: none;
}
&.item-table {
td {
padding: 32px;
}
}
&.list-table {
td {
padding: 16px 24px;
}
}
.short-description {
margin-left: 0;
}
&.parameters-table {
margin-top: 0;
@include font-size(14);
box-shadow: none;
tr {
@media screen and (max-width: 480px) {
display: flex;
flex-direction: column;
}
td:first-child {
font-weight: 600;
padding-left: 16px;
width: 20%;
}
}
td {
padding: 8px 8px 8px 0;
border: 0;
vertical-align: top;
}
}
&.property-table {
td {
vertical-align: top;
}
&.property-table {
td {
vertical-align: top;
}
}
}
.class-overview {
position: relative;
code-example {
clear: left;
clear: left;
}
}
.short-description {
margin: 6px 0 0 10px;
}
}
.breadcrumb-container {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.api-header {
display: flex;
align-items: center;
@media screen and (max-width: 600px) {
flex-direction: column;
align-items: flex-start;
margin-top: 8px;
}
h1 {
@ -117,10 +101,12 @@
.github-links {
float: right;
.material-icons {
border-radius: 4px;
padding: 4px;
@include font-size(20);
&:hover {
background-color: $mist;
}
@ -128,19 +114,20 @@
}
.api-body {
.class-overview {
position: relative;
code-example {
clear: left;
clear: left;
}
}
.method-table, .option-table, .list-table {
.method-table,
.option-table,
.list-table {
td > code {
background-color: inherit;
white-space: pre;
white-space: pre-wrap;
}
.with-github-links {
@ -161,7 +148,7 @@
h3 {
margin: 6px 0;
font-weight: bold;
font-weight: 500;
clear: left;
}
@ -172,10 +159,9 @@
}
}
.api-heading {
padding: 5px 0;
@include font-size(14);
margin: 16px;
}
.parameters-table {
@ -244,7 +230,9 @@
}
.from-constructor, .read-only-property, .write-only-property {
.from-constructor,
.read-only-property,
.write-only-property {
@include font-size(12);
font-weight: 600;
@include letter-spacing(0.5);
@ -259,7 +247,8 @@
padding: 0;
}
.selector-list, .inherited-members-list {
.selector-list,
.inherited-members-list {
ul {
padding: 0;
li {
@ -270,7 +259,8 @@
}
.selector-list {
li, a {
li,
a {
font-weight: bold;
i {
font-weight: normal;
@ -279,6 +269,39 @@
}
}
.breadcrumb-container {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.api-header {
display: flex;
align-items: center;
margin-top: -4px;
@media screen and (max-width: 600px) {
flex-direction: column;
align-items: flex-start;
}
h1 {
margin: 0;
}
}
.github-links {
float: right;
.material-icons {
border-radius: 4px;
padding: 4px;
font-size: 20px;
&:hover {
background-color: $mist;
}
}
}
.deprecated-api-item {
text-decoration: line-through;
}

View File

@ -1,6 +1,11 @@
code-example, code-tabs {
code-example,
code-tabs {
clear: both;
display: block;
ol {
list-style: decimal;
}
}
code-example {

View File

@ -30,159 +30,160 @@
&:hover {
color: $accentblue;
}
}
}
button.toc-heading,
button.toc-more-items {
cursor: pointer;
display: inline-block;
background: 0;
background-color: transparent;
border: none;
box-shadow: none;
padding: 0;
text-align: start;
button {
&.toc-heading,
&.toc-more-items {
cursor: pointer;
display: inline-block;
background: 0;
background-color: transparent;
border: none;
box-shadow: none;
padding: 0;
text-align: start;
&.embedded:focus {
outline: none;
background: $lightgray;
}
}
button.toc-heading {
mat-icon.rotating-icon {
height: 18px;
width: 18px;
position: relative;
left: -4px;
top: 5px;
}
&:hover:not(.embedded) {
color: $accentblue;
}
}
button.toc-more-items {
color: $mediumgray;
top: 10px;
position: relative;
&:hover {
color: $accentblue;
}
&::after {
content: 'expand_less';
}
&.collapsed::after {
content: 'more_horiz';
}
}
.mat-icon {
&.collapsed {
@include rotate(0deg);
}
&:not(.collapsed) {
@include rotate(90deg);
}
}
ul.toc-list {
list-style-type: none;
margin: 0;
padding: 0 8px 0 0;
@media (max-width: 800px) {
width: auto;
}
li {
box-sizing: border-box;
@include font-size(12);
@include line-height(16);
padding: 3px 0 3px 12px;
position: relative;
transition: all 0.3s ease-in-out;
&.h1:after {
content: '';
display: block;
height: 1px;
width: 40%;
margin: 7px 0 4px 0;
&.embedded:focus {
outline: none;
background: $lightgray;
clear: both;
}
}
&.toc-heading {
mat-icon.rotating-icon {
height: 18px;
width: 18px;
position: relative;
left: -4px;
top: 5px;
}
&.h3 {
padding-left: 24px;
&:hover:not(.embedded) {
color: $accentblue;
}
}
a {
color: lighten($darkgray, 10);
overflow: visible;
@include font-size(12);
display: table-cell;
}
&.toc-more-items {
color: $mediumgray;
top: 10px;
position: relative;
&:hover {
* {
color: $accentblue;
}
color: $accentblue;
}
&.active {
* {
color: $blue;
font-weight: 500;
&::after {
content: 'expand_less';
}
&:before {
content: '';
border-radius: 50%;
left: -3px;
top: 12px;
background: $blue;
position: absolute;
width: 6px;
height: 6px;
&.collapsed::after {
content: 'more_horiz';
}
}
.mat-icon {
&.collapsed {
@include rotate(0deg);
}
&:not(.collapsed) {
@include rotate(90deg);
}
}
ul.toc-list {
list-style-type: none;
margin: 0;
padding: 0 8px 0 0;
@media (max-width: 800px) {
width: auto;
}
li {
box-sizing: border-box;
@include font-size(12);
@include line-height(16);
padding: 3px 0 3px 12px;
position: relative;
transition: all 0.3s ease-in-out;
&.h1:after {
content: '';
display: block;
height: 1px;
width: 40%;
margin: 7px 0 4px 0;
background: $lightgray;
clear: both;
}
&.h3 {
padding-left: 24px;
}
a {
color: lighten($darkgray, 10);
overflow: visible;
@include font-size(12);
display: table-cell;
}
&:hover {
* {
color: $accentblue;
}
}
&.active {
* {
color: $blue;
font-weight: 500;
&:before {
content: '';
border-radius: 50%;
left: -3px;
top: 12px;
background: $blue;
position: absolute;
width: 6px;
height: 6px;
}
}
}
}
}
&:not(.embedded) li {
&:before {
border-left: 1px solid $lightgray;
bottom: 0;
content: '';
left: 0;
position: absolute;
top: 0;
}
&:not(.embedded) li {
&:before {
border-left: 1px solid $lightgray;
bottom: 0;
content: '';
left: 0;
position: absolute;
top: 0;
}
&:first-child:before {
top: 13px;
}
&:first-child:before {
top: 13px;
}
&:last-child:before {
bottom: calc(100% - 14px);
}
&:last-child:before {
bottom: calc(100% - 14px);
}
&:not(.active):hover a:before {
content: '';
border-radius: 50%;
left: -3px;
top: 12px;
background: $lightgray;
position: absolute;
width: 6px;
height: 6px;
&:not(.active):hover a:before {
content: '';
border-radius: 50%;
left: -3px;
top: 12px;
background: $lightgray;
position: absolute;
width: 6px;
height: 6px;
}
}
}
}

View File

@ -84,25 +84,23 @@ class ExampleZipper {
const outputFileName = path.join(outputDirName, relativeDirName, exampleZipName + '.zip');
let defaultIncludes = ['**/*.ts', '**/*.js', '**/*.es6', '**/*.css', '**/*.html', '**/*.md', '**/*.json', '**/*.png', '**/*.svg'];
let alwaysIncludes = [
'bs-config.json',
'e2e/protractor.conf.js',
'angular.json',
'.editorconfig',
'.gitignore',
'tslint.json',
'angular.json',
'browserslist',
'bs-config.json',
'karma.conf.js',
'karma-test-shim.js',
'tsconfig.json',
'src/testing/**/*',
'src/.babelrc',
'src/browserslist',
'tsconfig.*',
'tslint.*',
'e2e/protractor.conf.js',
'e2e/tsconfig.json',
'src/favicon.ico',
'src/karma.conf.js',
'src/polyfills.ts',
'src/test.ts',
'src/typings.d.ts',
'src/environments/**/*',
'src/tsconfig.*',
'src/tslint.*',
'src/testing/**/*',
// Only ignore root package.json
'!package.json'
];

View File

@ -46,6 +46,8 @@ if (argv.ivy) {
* Must be used in conjunction with --setup as this is when the packages are copied.
* e.g. --setup --local
*
* --ivy to turn on `ivy` mode
*
* --shard to shard the specs into groups to allow you to run them in parallel
* e.g. --shard=0/2 // the even specs: 0, 2, 4, etc
* e.g. --shard=1/2 // the odd specs: 1, 3, 5, etc

View File

@ -1,5 +1,5 @@
{
"extends": "../tsconfig.json",
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"types": []

View File

@ -1,5 +1,5 @@
{
"extends": "../tsconfig.json",
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"types": [
@ -8,8 +8,8 @@
]
},
"files": [
"test.ts",
"polyfills.ts"
"src/test.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",

View File

@ -14,7 +14,8 @@ const LOCAL_MARKER_PATH = 'node_modules/_local_.json';
const PACKAGE_JSON_REGEX = /^[^/]+\/package\.json$/;
const ANGULAR_ROOT_DIR = path.resolve(__dirname, '../../..');
const ANGULAR_DIST_PACKAGES = path.resolve(ANGULAR_ROOT_DIR, 'dist/packages-dist');
const ANGULAR_DIST_PACKAGES = path.join(ANGULAR_ROOT_DIR, 'dist/packages-dist');
const ANGULAR_DIST_PACKAGES_BUILD_CMD = path.join(ANGULAR_ROOT_DIR, 'scripts/build-packages-dist.sh');
/**
* A tool that can install Angular dependencies for a project from NPM or from the
@ -29,16 +30,17 @@ class NgPackagesInstaller {
* Create a new installer for a project in the specified directory.
*
* @param {string} projectDir - the path to the directory containing the project.
* @param {object} options - a hash of options for the install
* * `debug` (`boolean`) - whether to display debug messages.
* * `force` (`boolean`) - whether to force a local installation
* even if there is a local marker file.
* * `ignorePackages` (`string[]`) - a collection of names of packages
* that should not be copied over.
* @param {object} options - a hash of options for the install:
* * `debug` (`boolean`) - whether to display debug messages.
* * `force` (`boolean`) - whether to force a local installation even if there is a local marker file.
* * `buildPackages` (`boolean`) - whether to build the local Angular packages before using them.
* (NOTE: Building the packages is currently not supported on Windows, so a message is printed instead.)
* * `ignorePackages` (`string[]`) - a collection of names of packages that should not be copied over.
*/
constructor(projectDir, options = {}) {
this.debug = options.debug;
this.force = options.force;
this.debug = this._parseBooleanArg(options.debug);
this.force = this._parseBooleanArg(options.force);
this.buildPackages = this._parseBooleanArg(options.buildPackages);
this.ignorePackages = options.ignorePackages || [];
this.projectDir = path.resolve(projectDir);
this.localMarkerPath = path.resolve(this.projectDir, LOCAL_MARKER_PATH);
@ -160,6 +162,31 @@ class NgPackagesInstaller {
});
}
/**
* Build the local Angular packages.
*
* NOTE:
* Building the packages is currently not supported on Windows, so a message is printed instead, prompting the user to
* do it themselves (e.g. using Windows Subsystem for Linux or a docker container).
*/
_buildDistPackages() {
const canBuild = process.platform !== 'win32';
if (canBuild) {
this._log(`Building the Angular packages with: ${ANGULAR_DIST_PACKAGES_BUILD_CMD}`);
shelljs.exec(ANGULAR_DIST_PACKAGES_BUILD_CMD);
} else {
this._warn([
'Automatically building the local Angular packages is currently not supported on Windows.',
`Please, ensure '${ANGULAR_DIST_PACKAGES}' exists and is up-to-date (e.g. by running ` +
`'${ANGULAR_DIST_PACKAGES_BUILD_CMD}' in Git Bash for Windows, Windows Subsystem for Linux or a Linux ` +
'docker container or VM).',
'',
'Proceeding anyway...',
].join('\n'));
}
}
_collectDependencies(dependencies, packages) {
const peerDependencies = Object.create(null);
const mergedDependencies = Object.assign(Object.create(null), dependencies);
@ -184,32 +211,35 @@ class NgPackagesInstaller {
/**
* A hash of Angular package configs.
* (Detected as directories in '/packages/' that contain a top-level 'package.json' file.)
* (Detected as directories in '/dist/packages-dist/' that contain a top-level 'package.json' file.)
*/
_getDistPackages() {
const packageConfigs = Object.create(null);
const distDir = ANGULAR_DIST_PACKAGES;
[ANGULAR_DIST_PACKAGES].forEach(distDir => {
this._log(`Angular distributable directory: ${distDir}.`);
shelljs
.find(distDir)
.map(filePath => filePath.slice(distDir.length + 1))
.filter(filePath => PACKAGE_JSON_REGEX.test(filePath))
.forEach(packagePath => {
const packageName = `@angular/${packagePath.slice(0, -PACKAGE_JSON.length -1)}`;
if (this.ignorePackages.indexOf(packageName) === -1) {
const packageConfig = require(path.resolve(distDir, packagePath));
packageConfigs[packageName] = {
parentDir: distDir,
packageJsonPath: path.resolve(distDir, packagePath),
config: packageConfig
};
} else {
this._log('Ignoring package', packageName);
}
});
this._log(`Angular distributable directory: ${distDir}.`);
});
if (this.buildPackages) {
this._buildDistPackages();
}
shelljs
.find(distDir)
.map(filePath => filePath.slice(distDir.length + 1))
.filter(filePath => PACKAGE_JSON_REGEX.test(filePath))
.forEach(packagePath => {
const packageName = `@angular/${packagePath.slice(0, -PACKAGE_JSON.length -1)}`;
if (this.ignorePackages.indexOf(packageName) === -1) {
const packageConfig = require(path.resolve(distDir, packagePath));
packageConfigs[packageName] = {
parentDir: distDir,
packageJsonPath: path.resolve(distDir, packagePath),
config: packageConfig
};
} else {
this._log('Ignoring package', packageName);
}
});
this._log('Found the following Angular distributables:', Object.keys(packageConfigs).map(key => `\n - ${key}`));
return packageConfigs;
@ -234,6 +264,21 @@ class NgPackagesInstaller {
}
}
/**
* Extract the value for a boolean cli argument/option. When passing an option multiple times, `yargs` parses it as an
* array of boolean values. In that case, we only care about the last occurrence.
*
* This can be useful, for example, when one has a base command with the option turned on and another command
* (building on top of the first one) turning the option off:
* ```
* "base-command": "my-script --foo --bar",
* "no-bar-command": "yarn base-command --no-bar",
* ```
*/
_parseBooleanArg(value) {
return Array.isArray(value) ? value.pop() : value;
}
/**
* Parse and return a `yarn.lock` file.
*/
@ -254,17 +299,28 @@ class NgPackagesInstaller {
const restoreCmd = `node ${relativeScriptPath} restore ${absoluteProjectDir}`;
// Log a warning.
this._warn([
`The project at "${absoluteProjectDir}" is running against the local Angular build.`,
'',
'To restore the npm packages run:',
'',
` "${restoreCmd}"`,
].join('\n'));
}
/**
* Log a warning message do draw user's attention.
* @param {...string[]} messages - The messages to be logged.
*/
_warn(...messages) {
const lines = messages.join(' ').split('\n');
console.warn(chalk.yellow([
'',
'!'.repeat(110),
'!!!',
'!!! WARNING',
'!!!',
`!!! The project at "${absoluteProjectDir}" is running against the local Angular build.`,
'!!!',
'!!! To restore the npm packages run:',
'!!!',
`!!! "${restoreCmd}"`,
...lines.map(line => `!!! ${line}`),
'!!!',
'!'.repeat(110),
'',
@ -287,24 +343,27 @@ class NgPackagesInstaller {
function main() {
shelljs.set('-e');
const createInstaller = argv => {
const {projectDir, ...options} = argv;
return new NgPackagesInstaller(projectDir, options);
};
yargs
.usage('$0 <cmd> [args]')
.option('debug', { describe: 'Print additional debug information.', default: false })
.option('force', { describe: 'Force the command to execute even if not needed.', default: false })
.option('build-packages', { describe: 'Build the local Angular packages, before using them.', default: false })
.option('ignore-packages', { describe: 'List of Angular packages that should not be used in local mode.', default: [], array: true })
.command('overwrite <projectDir> [--force] [--debug] [--ignore-packages package1 package2]', 'Install dependencies from the locally built Angular distributables.', () => {}, argv => {
const installer = new NgPackagesInstaller(argv.projectDir, argv);
installer.installLocalDependencies();
createInstaller(argv).installLocalDependencies();
})
.command('restore <projectDir> [--debug]', 'Install dependencies from the npm registry.', () => {}, argv => {
const installer = new NgPackagesInstaller(argv.projectDir, argv);
installer.restoreNpmDependencies();
createInstaller(argv).restoreNpmDependencies();
})
.command('check <projectDir> [--debug]', 'Check that dependencies came from npm. Otherwise display a warning message.', () => {}, argv => {
const installer = new NgPackagesInstaller(argv.projectDir, argv);
installer.checkDependencies();
createInstaller(argv).checkDependencies();
})
.demandCommand(1, 'Please supply a command from the list above.')
.strict()

View File

@ -8,13 +8,14 @@ const shelljs = require('shelljs');
const NgPackagesInstaller = require('./index');
describe('NgPackagesInstaller', () => {
const rootDir = 'root/dir';
const absoluteRootDir = path.resolve(rootDir);
const nodeModulesDir = path.resolve(absoluteRootDir, 'node_modules');
const packageJsonPath = path.resolve(absoluteRootDir, 'package.json');
const yarnLockPath = path.resolve(absoluteRootDir, 'yarn.lock');
const packagesDir = path.resolve(path.resolve(__dirname, '../../../dist/packages-dist'));
const toolsDir = path.resolve(path.resolve(__dirname, '../../../dist/tools/@angular'));
const projectDir = 'root/dir';
const absoluteProjectDir = path.resolve(projectDir);
const nodeModulesDir = path.resolve(absoluteProjectDir, 'node_modules');
const packageJsonPath = path.resolve(absoluteProjectDir, 'package.json');
const yarnLockPath = path.resolve(absoluteProjectDir, 'yarn.lock');
const ngRootDir = path.resolve(__dirname, '../../..');
const packagesDir = path.join(ngRootDir, 'dist/packages-dist');
const toolsDir = path.join(ngRootDir, 'dist/tools/@angular');
let installer;
beforeEach(() => {
@ -25,7 +26,7 @@ describe('NgPackagesInstaller', () => {
spyOn(shelljs, 'rm');
spyOn(console, 'log');
spyOn(console, 'warn');
installer = new NgPackagesInstaller(rootDir);
installer = new NgPackagesInstaller(projectDir);
});
describe('checkDependencies()', () => {
@ -36,14 +37,14 @@ describe('NgPackagesInstaller', () => {
it('should not print a warning if there is no _local_.json file', () => {
fs.existsSync.and.returnValue(false);
installer.checkDependencies();
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(rootDir, 'node_modules/_local_.json'));
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(projectDir, 'node_modules/_local_.json'));
expect(installer._printWarning).not.toHaveBeenCalled();
});
it('should print a warning if there is a _local_.json file', () => {
fs.existsSync.and.returnValue(true);
installer.checkDependencies();
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(rootDir, 'node_modules/_local_.json'));
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(projectDir, 'node_modules/_local_.json'));
expect(installer._printWarning).toHaveBeenCalled();
});
});
@ -242,7 +243,68 @@ describe('NgPackagesInstaller', () => {
});
});
describe('_buildDistPackages()', () => {
// Call `_buildDistPackages()` with a mock `process.platform` value.
const buildDistPackagesOnPlatform = platform => {
const originalDescriptor = Object.getOwnPropertyDescriptor(process, 'platform');
Object.defineProperty(process, 'platform', {...originalDescriptor, value: platform});
installer._buildDistPackages();
Object.defineProperty(process, 'platform', originalDescriptor);
};
it('should build the local packages, when not on Windows', () => {
const buildScript = path.join(ngRootDir, 'scripts/build-packages-dist.sh');
buildDistPackagesOnPlatform('linux');
expect(shelljs.exec).toHaveBeenCalledWith(buildScript);
shelljs.exec.calls.reset();
buildDistPackagesOnPlatform('darwin');
expect(shelljs.exec).toHaveBeenCalledWith(buildScript);
shelljs.exec.calls.reset();
buildDistPackagesOnPlatform('anythingButWindows :(');
expect(shelljs.exec).toHaveBeenCalledWith(buildScript);
// Ensure that the script does actually exist (e.g. it was not renamed/moved).
fs.existsSync.and.callThrough();
expect(fs.existsSync(buildScript)).toBe(true);
});
it('should print a warning, when on Windows', () => {
buildDistPackagesOnPlatform('win32');
const warning = console.warn.calls.argsFor(0)[0];
expect(shelljs.exec).not.toHaveBeenCalled();
expect(warning).toContain(
'Automatically building the local Angular packages is currently not supported on Windows.');
expect(warning).toContain('Git Bash for Windows');
expect(warning).toContain('Windows Subsystem for Linux');
expect(warning).toContain('Linux docker container or VM');
});
});
describe('_getDistPackages()', () => {
beforeEach(() => spyOn(NgPackagesInstaller.prototype, '_buildDistPackages'));
it('should not build the local packages by default', () => {
installer._getDistPackages();
expect(installer._buildDistPackages).not.toHaveBeenCalled();
});
it('should build the local packages, if `buildPackages` is true', () => {
installer = new NgPackagesInstaller(projectDir, {buildPackages: true});
installer._getDistPackages();
expect(installer._buildDistPackages).toHaveBeenCalledTimes(1);
});
it('should not build the local packages by default', () => {
installer._getDistPackages();
expect(installer._buildDistPackages).not.toHaveBeenCalled();
});
it('should include top level Angular packages', () => {
const ngPackages = installer._getDistPackages();
const expectedValue = jasmine.objectContaining({
@ -269,7 +331,7 @@ describe('NgPackagesInstaller', () => {
});
it('should not include packages that have been ignored', () => {
installer = new NgPackagesInstaller(rootDir, { ignorePackages: ['@angular/router'] });
installer = new NgPackagesInstaller(projectDir, { ignorePackages: ['@angular/router'] });
const ngPackages = installer._getDistPackages();
expect(ngPackages['@angular/common']).toBeDefined();
@ -283,9 +345,9 @@ describe('NgPackagesInstaller', () => {
});
it('should assign the debug property from the options', () => {
installer = new NgPackagesInstaller(rootDir, { debug: true });
installer = new NgPackagesInstaller(projectDir, { debug: true });
expect(installer.debug).toBe(true);
installer = new NgPackagesInstaller(rootDir, { });
installer = new NgPackagesInstaller(projectDir, { });
expect(installer.debug).toBe(undefined);
});
@ -350,7 +412,7 @@ describe('NgPackagesInstaller', () => {
expect(console.warn.calls.argsFor(0)[0]).toMatch(restoreCmdRe1);
// When run for a different directory...
const dir2 = rootDir;
const dir2 = projectDir;
const restoreCmdRe2 = RegExp(`\\bnode .*?ng-packages-installer/index restore .*?${path.resolve(dir1)}\\b`);
installer = new NgPackagesInstaller(dir2);
installer._printWarning('');
@ -361,14 +423,14 @@ describe('NgPackagesInstaller', () => {
describe('_installDeps()', () => {
it('should run yarn install with the given options', () => {
installer._installDeps('option-1', 'option-2');
expect(shelljs.exec).toHaveBeenCalledWith('yarn install option-1 option-2', { cwd: absoluteRootDir });
expect(shelljs.exec).toHaveBeenCalledWith('yarn install option-1 option-2', { cwd: absoluteProjectDir });
});
});
describe('local marker helpers', () => {
let installer;
beforeEach(() => {
installer = new NgPackagesInstaller(rootDir);
installer = new NgPackagesInstaller(projectDir);
});
describe('_checkLocalMarker', () => {

View File

@ -208,9 +208,9 @@
<code>{$ renderMemberSyntax(property) $}</code>
</td>
<td>
{%- if (property.isGetAccessor or property.isReadonly) and not property.isSetAccessor %}<span class='read-only-property'>Read-only.</span>{% endif %}
{%- if property.isSetAccessor and not property.isGetAccessor %}<span class='write-only-property'>Write-only.</span>{% endif %}
{% if property.constructorParamDoc %} <span class='from-constructor'>Declared in constructor.</span>{% endif %}
{%- if (property.isGetAccessor or property.isReadonly) and not property.isSetAccessor %}<span class='read-only-property'>Read-Only</span>{% endif %}
{%- if property.isSetAccessor and not property.isGetAccessor %}<span class='write-only-property'>Write-Only</span>{% endif %}
{% if property.constructorParamDoc %} <span class='from-constructor'>Declared in Constructor</span>{% endif %}
{% if property.shortDescription %}{$ property.shortDescription | marked $}{% endif %}
{$ (property.description or property.constructorParamDoc.description) | marked $}
{%- if property.see.length %}

View File

@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime": 1440,
"main": 14021,
"main": 13517,
"polyfills": 43567
}
}
@ -34,4 +34,4 @@
}
}
}
}
}

View File

@ -45,7 +45,7 @@
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "file:../../node_modules/protractor",
"ts-node": "~7.0.0",
"tslint": "~5.11.0",
"tslint": "~5.18.0",
"typescript": "file:../../node_modules/typescript"
}
}

View File

@ -45,7 +45,7 @@
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "file:../../node_modules/protractor",
"ts-node": "~7.0.0",
"tslint": "~5.11.0",
"tslint": "~5.18.0",
"typescript": "file:../../node_modules/typescript"
}
}

View File

@ -45,7 +45,7 @@
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "file:../../node_modules/protractor",
"ts-node": "~4.1.0",
"tslint": "~5.13.0",
"tslint": "~5.18.0",
"typescript": "file:../../node_modules/typescript"
}
}

View File

@ -20,12 +20,12 @@
],
"textSpan": {
"start": {
"line": 7,
"offset": 30
"line": 5,
"offset": 26
},
"end": {
"line": 7,
"offset": 47
"line": 5,
"offset": 30
}
}
}

View File

@ -5,7 +5,7 @@
"request_seq": 2,
"success": true,
"body": {
"kind": "",
"kind": "property",
"kindModifiers": "",
"start": {
"line": 5,
@ -15,7 +15,7 @@
"line": 5,
"offset": 30
},
"displayString": "property name of AppComponent",
"displayString": "(property) AppComponent.name",
"documentation": "",
"tags": []
}

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "9.0.0-next.0",
"version": "9.0.0-next.2",
"private": true,
"description": "Angular - a web framework for modern web apps",
"homepage": "https://github.com/angular/angular",
@ -166,4 +166,4 @@
"resolutions": {
"natives": "1.1.6"
}
}
}

View File

@ -112,11 +112,13 @@ def _rollup(ctx, bundle_name, rollup_config, entry_point, inputs, js_output, for
args.add("--amd.id", package_name)
# After updating to build_bazel_rules_nodejs 0.27.0+, rollup has been updated to v1.3.1
# which tree shakes @__PURE__ annotations by default. We turn this feature off
# for ng_package as Angular bundles contain these annotations and there are
# test failures if they are removed. See comments in
# https://github.com/angular/angular/pull/29210 for more information.
args.add("--no-treeshake.annotations")
# which tree shakes @__PURE__ annotations and const variables which are later amended by NGCC.
# We turn this feature off for ng_package as Angular bundles contain these and there are
# test failures if they are removed.
# See comments in:
# https://github.com/angular/angular/pull/29210
# https://github.com/angular/angular/pull/32069
args.add("--no-treeshake")
# Note: if the input has external source maps then we need to also install and use
# `rollup-plugin-sourcemaps`, which will require us to use rollup.config.js file instead

View File

@ -133,6 +133,10 @@ describe('@angular/core ng_package', () => {
} else {
it('should have decorators',
() => { expect(shx.cat('fesm5/core.js')).toContain('__decorate'); });
// See: https://github.com/angular/angular/pull/32069
it('should retain access to const',
() => { expect(shx.cat('fesm5/core.js')).toContain('!ivyEnabled'); });
}
it('should load tslib from external bundle', () => {

View File

@ -111,6 +111,41 @@ Hello
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
t[p[i]] = s[p[i]];
return t;
}
function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@ -119,6 +154,136 @@ Hello
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function __param(paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
}
function __metadata(metadataKey, metadataValue) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
}
function __awaiter(thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
function __exportStar(m, exports) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
function __values(o) {
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0;
if (m) return m.call(o);
return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
}
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
}
function __spread() {
for (var ar = [], i = 0; i < arguments.length; i++)
ar = ar.concat(__read(arguments[i]));
return ar;
}
function __await(v) {
return this instanceof __await ? (this.v = v, this) : new __await(v);
}
function __asyncGenerator(thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
}
function __asyncDelegator(o) {
var i, p;
return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; }
}
function __asyncValues(o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
}
function __makeTemplateObject(cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
};
function __importStar(mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result.default = mod;
return result;
}
function __importDefault(mod) {
return (mod && mod.__esModule) ? mod : { default: mod };
}
/**
* @license
* Copyright Google Inc. All Rights Reserved.
@ -210,6 +375,41 @@ e.SecondaryModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})});
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
t[p[i]] = s[p[i]];
return t;
}
function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@ -218,6 +418,136 @@ e.SecondaryModule=o,e.a=1,Object.defineProperty(e,"__esModule",{value:!0})});
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function __param(paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
}
function __metadata(metadataKey, metadataValue) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
}
function __awaiter(thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
function __exportStar(m, exports) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
function __values(o) {
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0;
if (m) return m.call(o);
return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
}
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
}
function __spread() {
for (var ar = [], i = 0; i < arguments.length; i++)
ar = ar.concat(__read(arguments[i]));
return ar;
}
function __await(v) {
return this instanceof __await ? (this.v = v, this) : new __await(v);
}
function __asyncGenerator(thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
}
function __asyncDelegator(o) {
var i, p;
return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; }
}
function __asyncValues(o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
}
function __makeTemplateObject(cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
};
function __importStar(mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result.default = mod;
return result;
}
function __importDefault(mod) {
return (mod && mod.__esModule) ? mod : { default: mod };
}
/**
* @license
* Copyright Google Inc. All Rights Reserved.

View File

@ -134,7 +134,8 @@ function formatNumberToLocaleString(
* such as "$" or "Canadian Dollar". Used in output string, but does not affect the operation
* of the function.
* @param currencyCode The [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217)
* currency code to use in the result string, such as `USD` for the US dollar and `EUR` for the euro.
* currency code, such as `USD` for the US dollar and `EUR` for the euro.
* Used to determine the number of digits in the decimal part.
* @param digitInfo Decimal representation options, specified by a string in the following format:
* `{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}`. See `DecimalPipe` for more details.
*

View File

@ -25,12 +25,12 @@ export interface PopStateEvent {
*
* A service that applications can use to interact with a browser's URL.
*
* Depending on the `LocationStrategy` used, `Location` will either persist
* Depending on the `LocationStrategy` used, `Location` persists
* to the URL's path or the URL's hash segment.
*
* @usageNotes
*
* It's better to use the {@link Router#navigate} service to trigger route changes. Use
* It's better to use the `Router#navigate` service to trigger route changes. Use
* `Location` only if you need to interact with or create normalized URLs outside of
* routing.
*
@ -77,9 +77,9 @@ export class Location {
}
/**
* Returns the normalized URL path.
* Normalizes the URL path for this location.
*
* @param includeHash Whether path has an anchor fragment.
* @param includeHash True to include an anchor fragment in the path.
*
* @returns The normalized URL path.
*/
@ -90,17 +90,18 @@ export class Location {
}
/**
* Returns the current value of the history.state object.
* Reports the current state of the location history.
* @returns The current value of the `history.state` object.
*/
getState(): unknown { return this._platformLocation.getState(); }
/**
* Normalizes the given path and compares to the current normalized path.
*
* @param path The given URL path
* @param query Query parameters
* @param path The given URL path.
* @param query Query parameters.
*
* @returns `true` if the given URL path is equal to the current normalized path, `false`
* @returns True if the given URL path is equal to the current normalized path, false
* otherwise.
*/
isCurrentPathEqualTo(path: string, query: string = ''): boolean {
@ -108,23 +109,21 @@ export class Location {
}
/**
* Given a string representing a URL, returns the URL path after stripping the
* trailing slashes.
* Normalizes a URL path by stripping any trailing slashes.
*
* @param url String representing a URL.
*
* @returns Normalized URL string.
* @returns The normalized URL string.
*/
normalize(url: string): string {
return Location.stripTrailingSlash(_stripBaseHref(this._baseHref, _stripIndexHtml(url)));
}
/**
* Given a string representing a URL, returns the platform-specific external URL path.
* If the given URL doesn't begin with a leading slash (`'/'`), this method adds one
* before normalizing. This method also adds a hash if `HashLocationStrategy` is
* used, or the `APP_BASE_HREF` if the `PathLocationStrategy` is in use.
*
* Normalizes an external URL path.
* If the given URL doesn't begin with a leading slash (`'/'`), adds one
* before normalizing. Adds a hash if `HashLocationStrategy` is
* in use, or the `APP_BASE_HREF` if the `PathLocationStrategy` is in use.
*
* @param url String representing a URL.
*
@ -139,12 +138,12 @@ export class Location {
// TODO: rename this method to pushState
/**
* Changes the browsers URL to a normalized version of the given URL, and pushes a
* Changes the browser's URL to a normalized version of a given URL, and pushes a
* new item onto the platform's history.
*
* @param path URL path to normalizze
* @param query Query parameters
* @param state Location history state
* @param path URL path to normalize.
* @param query Query parameters.
* @param state Location history state.
*
*/
go(path: string, query: string = '', state: any = null): void {
@ -157,9 +156,9 @@ export class Location {
* Changes the browser's URL to a normalized version of the given URL, and replaces
* the top item on the platform's history stack.
*
* @param path URL path to normalizze
* @param query Query parameters
* @param state Location history state
* @param path URL path to normalize.
* @param query Query parameters.
* @param state Location history state.
*/
replaceState(path: string, query: string = '', state: any = null): void {
this._platformStrategy.replaceState(state, '', path, query);
@ -178,8 +177,10 @@ export class Location {
back(): void { this._platformStrategy.back(); }
/**
* Register URL change listeners. This API can be used to catch updates performed by the Angular
* framework. These are not detectible through "popstate" or "hashchange" events.
* Registers a URL change listener. Use to catch updates performed by the Angular
* framework that are not detectible through "popstate" or "hashchange" events.
*
* @param fn The change handler function, which take a URL and a location history state.
*/
onUrlChange(fn: (url: string, state: unknown) => void) {
this._urlChangeListeners.push(fn);
@ -192,7 +193,7 @@ export class Location {
}
/**
* Subscribe to the platform's `popState` events.
* Subscribes to the platform's `popState` events.
*
* @param value Event that is triggered when the state history changes.
* @param exception The exception to throw.
@ -206,25 +207,24 @@ export class Location {
}
/**
* Given a string of url parameters, prepend with `?` if needed, otherwise return the
* parameters as is.
* Normalizes URL parameters by prepending with `?` if needed.
*
* @param params String of URL parameters
* @param params String of URL parameters.
*
* @returns URL parameters prepended with `?` or the parameters as is.
* @returns The normalized URL parameters string.
*/
public static normalizeQueryParams(params: string): string {
return params && params[0] !== '?' ? '?' + params : params;
}
/**
* Given 2 parts of a URL, join them with a slash if needed.
* Joins two parts of a URL with a slash if needed.
*
* @param start URL string
* @param end URL string
*
*
* @returns Given URL strings joined with a slash, if needed.
* @returns The joined URL string.
*/
public static joinWithSlash(start: string, end: string): string {
if (start.length == 0) {
@ -250,14 +250,13 @@ export class Location {
}
/**
* If URL has a trailing slash, remove it, otherwise return the URL as is. The
* method looks for the first occurrence of either `#`, `?`, or the end of the
* Removes a trailing slash from a URL string if needed.
* Looks for the first occurrence of either `#`, `?`, or the end of the
* line as `/` characters and removes the trailing slash if one exists.
*
* @param url URL string
* @param url URL string.
*
* @returns Returns a URL string after removing the trailing slash if one exists, otherwise
* returns the string as is.
* @returns The URL string, modified if needed.
*/
public static stripTrailingSlash(url: string): string {
const match = url.match(/#|\?|$/);

View File

@ -161,8 +161,8 @@ export class DatePipe implements PipeTransform {
* @param format The date/time components to include, using predefined options or a
* custom format string.
* @param timezone A timezone offset (such as `'+0430'`), or a standard
* UTC/GMT or continental US timezone abbreviation. Default is
* the local system timezone of the end-user's machine.
* UTC/GMT or continental US timezone abbreviation.
* When not supplied, uses the end-user's local system timezone.
* @param locale A locale code for the locale format rules to use.
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).

View File

@ -144,6 +144,7 @@ export class $locationShim {
this.$$parse(oldUrl);
this.state(oldState);
this.setBrowserUrlWithFallback(oldUrl, false, oldState);
this.$$notifyChangeListeners(this.url(), this.$$state, oldUrl, oldState);
} else {
this.initalizing = false;
$rootScope.$broadcast('$locationChangeSuccess', newUrl, oldUrl, newState, oldState);
@ -199,6 +200,9 @@ export class $locationShim {
}
$rootScope.$broadcast(
'$locationChangeSuccess', newUrl, oldUrl, this.$$state, oldState);
if (urlOrStateChanged) {
this.$$notifyChangeListeners(this.url(), this.$$state, oldUrl, oldState);
}
}
});
}
@ -415,7 +419,6 @@ export class $locationShim {
// state object; this makes possible quick checking if the state changed in the digest
// loop. Checking deep equality would be too expensive.
this.$$state = this.browserState();
this.$$notifyChangeListeners(url, state, oldUrl, oldState);
} catch (e) {
// Restore old values if pushState fails
this.url(oldUrl);

View File

@ -628,6 +628,7 @@ describe('$location.onChange()', () => {
let $location: $locationShim;
let upgradeModule: UpgradeModule;
let mock$rootScope: $rootScopeMock;
beforeEach(() => {
TestBed.configureTestingModule({
@ -640,6 +641,7 @@ describe('$location.onChange()', () => {
upgradeModule = TestBed.get(UpgradeModule);
upgradeModule.$injector = {get: injectorFactory()};
mock$rootScope = upgradeModule.$injector.get('$rootScope');
});
beforeEach(inject([$locationShim], (loc: $locationShim) => { $location = loc; }));
@ -674,17 +676,44 @@ describe('$location.onChange()', () => {
$location.onChange(changeListener);
// Mock out setting browserUrl
($location as any).browserUrl = (url: string, replace: boolean, state: unknown) => {};
const newState = {foo: 'bar'};
($location as any).setBrowserUrlWithFallback('/newUrl', false, newState);
$location.state(newState);
$location.path('/newUrl');
mock$rootScope.runWatchers();
expect(onChangeVals.url).toBe('/newUrl');
expect(onChangeVals.state).toBe(newState);
expect(onChangeVals.oldUrl).toBe('/');
expect(onChangeVals.state).toEqual(newState);
expect(onChangeVals.oldUrl).toBe('http://host.com');
expect(onChangeVals.oldState).toBe(null);
});
it('should call changeListeners after $locationChangeSuccess', () => {
let changeListenerCalled = false;
let locationChangeSuccessEmitted = false;
function changeListener(url: string, state: unknown, oldUrl: string, oldState: unknown) {
changeListenerCalled = true;
}
$location.onChange(changeListener);
mock$rootScope.$on('$locationChangeSuccess', () => {
// Ensure that the changeListener hasn't been called yet
expect(changeListenerCalled).toBe(false);
locationChangeSuccessEmitted = true;
});
// Update state and run watchers
const stateValue = {foo: 'bar'};
$location.state(stateValue);
mock$rootScope.runWatchers();
// Ensure that change listeners are called and location events are emitted
expect(changeListenerCalled).toBe(true);
expect(locationChangeSuccessEmitted).toBe(true);
});
it('should call forward errors to error handler', () => {
let error !: Error;
@ -696,10 +725,8 @@ describe('$location.onChange()', () => {
$location.onChange(changeListener, errorHandler);
// Mock out setting browserUrl
($location as any).browserUrl = (url: string, replace: boolean, state: unknown) => {};
($location as any).setBrowserUrlWithFallback('/newUrl');
$location.url('/newUrl');
mock$rootScope.runWatchers();
expect(error.message).toBe('Handle error');
});

View File

@ -75,7 +75,7 @@ export class DecorationAnalyzer {
new BaseDefDecoratorHandler(this.reflectionHost, this.evaluator, this.isCore),
new ComponentDecoratorHandler(
this.reflectionHost, this.evaluator, this.fullRegistry, this.fullMetaReader,
this.scopeRegistry, this.isCore, this.resourceManager, this.rootDirs,
this.scopeRegistry, this.scopeRegistry, this.isCore, this.resourceManager, this.rootDirs,
/* defaultPreserveWhitespaces */ false,
/* i18nUseExternalIds */ true, this.moduleResolver, this.cycleAnalyzer, this.refEmitter,
NOOP_DEFAULT_IMPORT_RECORDER),

View File

@ -9,9 +9,11 @@
import {DepGraph} from 'dependency-graph';
import {AbsoluteFsPath, FileSystem, resolve} from '../../../src/ngtsc/file_system';
import {Logger} from '../logging/logger';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointFormat} from '../packages/entry_point';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from '../packages/entry_point';
import {DependencyHost, DependencyInfo} from './dependency_host';
const builtinNodeJsModules = new Set<string>(require('module').builtinModules);
/**
* Holds information about entry points that are removed because
* they have dependencies that are missing (directly or transitively).
@ -81,7 +83,7 @@ export class DependencyResolver {
let sortedEntryPointNodes: string[];
if (target) {
if (target.compiledByAngular) {
if (target.compiledByAngular && graph.hasNode(target.path)) {
sortedEntryPointNodes = graph.dependenciesOf(target.path);
sortedEntryPointNodes.push(target.path);
} else {
@ -128,10 +130,12 @@ export class DependencyResolver {
angularEntryPoints.forEach(entryPoint => {
const {dependencies, missing, deepImports} = this.getEntryPointDependencies(entryPoint);
if (missing.size > 0) {
const missingDependencies = Array.from(missing).filter(dep => !builtinNodeJsModules.has(dep));
if (missingDependencies.length > 0) {
// This entry point has dependencies that are missing
// so remove it from the graph.
removeNodes(entryPoint, Array.from(missing));
removeNodes(entryPoint, missingDependencies);
} else {
dependencies.forEach(dependencyPath => {
if (!graph.hasNode(entryPoint.path)) {
@ -173,16 +177,16 @@ export class DependencyResolver {
private getEntryPointFormatInfo(entryPoint: EntryPoint):
{format: EntryPointFormat, path: AbsoluteFsPath} {
const properties = Object.keys(entryPoint.packageJson);
for (let i = 0; i < properties.length; i++) {
const property = properties[i] as EntryPointJsonProperty;
const format = getEntryPointFormat(this.fs, entryPoint, property);
for (const property of SUPPORTED_FORMAT_PROPERTIES) {
const formatPath = entryPoint.packageJson[property];
if (formatPath === undefined) continue;
if (format === 'esm2015' || format === 'esm5' || format === 'umd' || format === 'commonjs') {
const formatPath = entryPoint.packageJson[property] !;
return {format, path: resolve(entryPoint.path, formatPath)};
}
const format = getEntryPointFormat(this.fs, entryPoint, property);
if (format === undefined) continue;
return {format, path: resolve(entryPoint.path, formatPath)};
}
throw new Error(
`There is no appropriate source code format in '${entryPoint.path}' entry-point.`);
}

View File

@ -0,0 +1,74 @@
/**
* @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 {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point';
/** The type of the function that analyzes entry-points and creates the list of tasks. */
export type AnalyzeFn = () => {
processingMetadataPerEntryPoint: Map<string, EntryPointProcessingMetadata>;
tasks: Task[];
};
/**
* The type of the function that creates the `compile()` function, which in turn can be used to
* process tasks.
*/
export type CreateCompileFn =
(onTaskCompleted: (task: Task, outcome: TaskProcessingOutcome) => void) => (task: Task) => void;
/**
* The type of the function that orchestrates and executes the required work (i.e. analyzes the
* entry-points, processes the resulting tasks, does book-keeping and validates the final outcome).
*/
export type ExecuteFn = (analyzeFn: AnalyzeFn, createCompileFn: CreateCompileFn) => void;
/** Represents metadata related to the processing of an entry-point. */
export interface EntryPointProcessingMetadata {
/**
* Whether the typings for the entry-point have been successfully processed (or were already
* processed).
*/
hasProcessedTypings: boolean;
/**
* Whether at least one format has been successfully processed (or was already processed) for the
* entry-point.
*/
hasAnyProcessedFormat: boolean;
}
/** Represents a unit of work: processing a specific format property of an entry-point. */
export interface Task {
/** The `EntryPoint` which needs to be processed as part of the task. */
entryPoint: EntryPoint;
/**
* The `package.json` format property to process (i.e. the property which points to the file that
* is the program entry-point).
*/
formatProperty: EntryPointJsonProperty;
/**
* The list of all format properties (including `task.formatProperty`) that should be marked as
* processed once the taksk has been completed, because they point to the format-path that will be
* processed as part of the task.
*/
formatPropertiesToMarkAsProcessed: EntryPointJsonProperty[];
/** Whether to also process typings for this entry-point as part of the task. */
processDts: boolean;
}
/** Represents the outcome of processing a `Task`. */
export const enum TaskProcessingOutcome {
/** The target format property was already processed - didn't have to do anything. */
AlreadyProcessed,
/** Successfully processed the target format property. */
Processed,
}

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AbsoluteFsPath, FileSystem, absoluteFrom, dirname, getFileSystem, resolve} from '../../src/ngtsc/file_system';
import {CommonJsDependencyHost} from './dependencies/commonjs_dependency_host';
import {DependencyResolver, InvalidEntryPoint, SortedEntryPointsInfo} from './dependencies/dependency_resolver';
import {EsmDependencyHost} from './dependencies/esm_dependency_host';
@ -13,11 +14,12 @@ import {ModuleResolver} from './dependencies/module_resolver';
import {UmdDependencyHost} from './dependencies/umd_dependency_host';
import {DirectoryWalkerEntryPointFinder} from './entry_point_finder/directory_walker_entry_point_finder';
import {TargetedEntryPointFinder} from './entry_point_finder/targeted_entry_point_finder';
import {AnalyzeFn, CreateCompileFn, EntryPointProcessingMetadata, ExecuteFn, Task, TaskProcessingOutcome} from './execution/api';
import {ConsoleLogger, LogLevel} from './logging/console_logger';
import {Logger} from './logging/logger';
import {hasBeenProcessed, markAsProcessed} from './packages/build_marker';
import {NgccConfiguration} from './packages/configuration';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from './packages/entry_point';
import {EntryPoint, EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from './packages/entry_point';
import {makeEntryPointBundle} from './packages/entry_point_bundle';
import {Transformer} from './packages/transformer';
import {PathMappings} from './utils';
@ -25,6 +27,7 @@ import {FileWriter} from './writing/file_writer';
import {InPlaceFileWriter} from './writing/in_place_file_writer';
import {NewEntryPointFileWriter} from './writing/new_entry_point_file_writer';
/**
* The options to configure the ngcc compiler.
*/
@ -68,8 +71,6 @@ export interface NgccOptions {
fileSystem?: FileSystem;
}
const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015', 'umd', 'commonjs'];
/**
* This is the main entry-point into ngcc (aNGular Compatibility Compiler).
*
@ -83,82 +84,171 @@ export function mainNgcc(
compileAllFormats = true, createNewEntryPointFormats = false,
logger = new ConsoleLogger(LogLevel.info), pathMappings}: NgccOptions): void {
const fileSystem = getFileSystem();
const transformer = new Transformer(fileSystem, logger);
const moduleResolver = new ModuleResolver(fileSystem, pathMappings);
const esmDependencyHost = new EsmDependencyHost(fileSystem, moduleResolver);
const umdDependencyHost = new UmdDependencyHost(fileSystem, moduleResolver);
const commonJsDependencyHost = new CommonJsDependencyHost(fileSystem, moduleResolver);
const resolver = new DependencyResolver(fileSystem, logger, {
esm5: esmDependencyHost,
esm2015: esmDependencyHost,
umd: umdDependencyHost,
commonjs: commonJsDependencyHost
});
const absBasePath = absoluteFrom(basePath);
const config = new NgccConfiguration(fileSystem, dirname(absBasePath));
const fileWriter = getFileWriter(fileSystem, createNewEntryPointFormats);
const entryPoints = getEntryPoints(
fileSystem, config, logger, resolver, absBasePath, targetEntryPointPath, pathMappings,
propertiesToConsider, compileAllFormats);
for (const entryPoint of entryPoints) {
// Are we compiling the Angular core?
const isCore = entryPoint.name === '@angular/core';
const compiledFormats = new Set<string>();
const entryPointPackageJson = entryPoint.packageJson;
const entryPointPackageJsonPath = fileSystem.resolve(entryPoint.path, 'package.json');
// The function for performing the analysis.
const analyzeFn: AnalyzeFn = () => {
const supportedPropertiesToConsider = ensureSupportedProperties(propertiesToConsider);
const hasProcessedDts = hasBeenProcessed(entryPointPackageJson, 'typings');
const moduleResolver = new ModuleResolver(fileSystem, pathMappings);
const esmDependencyHost = new EsmDependencyHost(fileSystem, moduleResolver);
const umdDependencyHost = new UmdDependencyHost(fileSystem, moduleResolver);
const commonJsDependencyHost = new CommonJsDependencyHost(fileSystem, moduleResolver);
const dependencyResolver = new DependencyResolver(fileSystem, logger, {
esm5: esmDependencyHost,
esm2015: esmDependencyHost,
umd: umdDependencyHost,
commonjs: commonJsDependencyHost
});
for (let i = 0; i < propertiesToConsider.length; i++) {
const property = propertiesToConsider[i] as EntryPointJsonProperty;
const formatPath = entryPointPackageJson[property];
const format = getEntryPointFormat(fileSystem, entryPoint, property);
const absBasePath = absoluteFrom(basePath);
const config = new NgccConfiguration(fileSystem, dirname(absBasePath));
const entryPoints = getEntryPoints(
fileSystem, config, logger, dependencyResolver, absBasePath, targetEntryPointPath,
pathMappings, supportedPropertiesToConsider, compileAllFormats);
// No format then this property is not supposed to be compiled.
if (!formatPath || !format || SUPPORTED_FORMATS.indexOf(format) === -1) continue;
const processingMetadataPerEntryPoint = new Map<string, EntryPointProcessingMetadata>();
const tasks: Task[] = [];
if (hasBeenProcessed(entryPointPackageJson, property)) {
compiledFormats.add(formatPath);
logger.debug(`Skipping ${entryPoint.name} : ${property} (already compiled).`);
continue;
for (const entryPoint of entryPoints) {
const packageJson = entryPoint.packageJson;
const hasProcessedTypings = hasBeenProcessed(packageJson, 'typings');
const {propertiesToProcess, propertyToPropertiesToMarkAsProcessed} =
getPropertiesToProcessAndMarkAsProcessed(packageJson, supportedPropertiesToConsider);
let processDts = !hasProcessedTypings;
for (const formatProperty of propertiesToProcess) {
const formatPropertiesToMarkAsProcessed =
propertyToPropertiesToMarkAsProcessed.get(formatProperty) !;
tasks.push({entryPoint, formatProperty, formatPropertiesToMarkAsProcessed, processDts});
// Only process typings for the first property (if not already processed).
processDts = false;
}
const isFirstFormat = compiledFormats.size === 0;
const processDts = !hasProcessedDts && isFirstFormat;
// We don't break if this if statement fails because we still want to mark
// the property as processed even if its underlying format has been built already.
if (!compiledFormats.has(formatPath) && (compileAllFormats || isFirstFormat)) {
const bundle = makeEntryPointBundle(
fileSystem, entryPoint, formatPath, isCore, property, format, processDts, pathMappings,
true);
if (bundle) {
logger.info(`Compiling ${entryPoint.name} : ${property} as ${format}`);
const transformedFiles = transformer.transform(bundle);
fileWriter.writeBundle(entryPoint, bundle, transformedFiles);
compiledFormats.add(formatPath);
} else {
logger.warn(
`Skipping ${entryPoint.name} : ${format} (no valid entry point file for this format).`);
}
}
// Either this format was just compiled or its underlying format was compiled because of a
// previous property.
if (compiledFormats.has(formatPath)) {
markAsProcessed(fileSystem, entryPointPackageJson, entryPointPackageJsonPath, property);
if (processDts) {
markAsProcessed(fileSystem, entryPointPackageJson, entryPointPackageJsonPath, 'typings');
}
}
processingMetadataPerEntryPoint.set(entryPoint.path, {
hasProcessedTypings,
hasAnyProcessedFormat: false,
});
}
if (compiledFormats.size === 0) {
return {processingMetadataPerEntryPoint, tasks};
};
// The function for creating the `compile()` function.
const createCompileFn: CreateCompileFn = onTaskCompleted => {
const fileWriter = getFileWriter(fileSystem, createNewEntryPointFormats);
const transformer = new Transformer(fileSystem, logger);
return (task: Task) => {
const {entryPoint, formatProperty, formatPropertiesToMarkAsProcessed, processDts} = task;
const isCore = entryPoint.name === '@angular/core'; // Are we compiling the Angular core?
const packageJson = entryPoint.packageJson;
const formatPath = packageJson[formatProperty];
const format = getEntryPointFormat(fileSystem, entryPoint, formatProperty);
// All properties listed in `propertiesToProcess` are guaranteed to point to a format-path
// (i.e. they exist in `entryPointPackageJson`). Furthermore, they are also guaranteed to be
// among `SUPPORTED_FORMAT_PROPERTIES`.
// Based on the above, `formatPath` should always be defined and `getEntryPointFormat()`
// should always return a format here (and not `undefined`).
if (!formatPath || !format) {
// This should never happen.
throw new Error(
`Invariant violated: No format-path or format for ${entryPoint.path} : ` +
`${formatProperty} (formatPath: ${formatPath} | format: ${format})`);
}
// The format-path which the property maps to is already processed - nothing to do.
if (hasBeenProcessed(packageJson, formatProperty)) {
logger.debug(`Skipping ${entryPoint.name} : ${formatProperty} (already compiled).`);
onTaskCompleted(task, TaskProcessingOutcome.AlreadyProcessed);
return;
}
const bundle = makeEntryPointBundle(
fileSystem, entryPoint, formatPath, isCore, format, processDts, pathMappings, true);
logger.info(`Compiling ${entryPoint.name} : ${formatProperty} as ${format}`);
const transformedFiles = transformer.transform(bundle);
fileWriter.writeBundle(bundle, transformedFiles, formatPropertiesToMarkAsProcessed);
onTaskCompleted(task, TaskProcessingOutcome.Processed);
};
};
// The function for actually planning and getting the work done.
const executeFn: ExecuteFn = (analyzeFn: AnalyzeFn, createCompileFn: CreateCompileFn) => {
const {processingMetadataPerEntryPoint, tasks} = analyzeFn();
const compile = createCompileFn((task, outcome) => {
const {entryPoint, formatPropertiesToMarkAsProcessed, processDts} = task;
const processingMeta = processingMetadataPerEntryPoint.get(entryPoint.path) !;
processingMeta.hasAnyProcessedFormat = true;
if (outcome === TaskProcessingOutcome.Processed) {
const packageJsonPath = fileSystem.resolve(entryPoint.path, 'package.json');
const propsToMarkAsProcessed: (EntryPointJsonProperty | 'typings')[] =
[...formatPropertiesToMarkAsProcessed];
if (processDts) {
processingMeta.hasProcessedTypings = true;
propsToMarkAsProcessed.push('typings');
}
markAsProcessed(
fileSystem, entryPoint.packageJson, packageJsonPath, propsToMarkAsProcessed);
}
});
// Process all tasks.
for (const task of tasks) {
const processingMeta = processingMetadataPerEntryPoint.get(task.entryPoint.path) !;
// If we only need one format processed and we already have one for the corresponding
// entry-point, skip the task.
if (!compileAllFormats && processingMeta.hasAnyProcessedFormat) continue;
compile(task);
}
// Check for entry-points for which we could not process any format at all.
const unprocessedEntryPointPaths =
Array.from(processingMetadataPerEntryPoint.entries())
.filter(([, processingMeta]) => !processingMeta.hasAnyProcessedFormat)
.map(([entryPointPath]) => `\n - ${entryPointPath}`)
.join('');
if (unprocessedEntryPointPaths) {
throw new Error(
`Failed to compile any formats for entry-point at (${entryPoint.path}). Tried ${propertiesToConsider}.`);
'Failed to compile any formats for the following entry-points (tried ' +
`${propertiesToConsider.join(', ')}): ${unprocessedEntryPointPaths}`);
}
};
return executeFn(analyzeFn, createCompileFn);
}
function ensureSupportedProperties(properties: string[]): EntryPointJsonProperty[] {
// Short-circuit the case where `properties` has fallen back to the default value:
// `SUPPORTED_FORMAT_PROPERTIES`
if (properties === SUPPORTED_FORMAT_PROPERTIES) return SUPPORTED_FORMAT_PROPERTIES;
const supportedProperties: EntryPointJsonProperty[] = [];
for (const prop of properties as EntryPointJsonProperty[]) {
if (SUPPORTED_FORMAT_PROPERTIES.indexOf(prop) !== -1) {
supportedProperties.push(prop);
}
}
if (supportedProperties.length === 0) {
throw new Error(
`No supported format property to consider among [${properties.join(', ')}]. ` +
`Supported properties: ${SUPPORTED_FORMAT_PROPERTIES.join(', ')}`);
}
return supportedProperties;
}
function getFileWriter(fs: FileSystem, createNewEntryPointFormats: boolean): FileWriter {
@ -192,8 +282,15 @@ function getTargetedEntryPoints(
const finder = new TargetedEntryPointFinder(
fs, config, logger, resolver, basePath, absoluteTargetEntryPointPath, pathMappings);
const entryPointInfo = finder.findEntryPoints();
const invalidTarget = entryPointInfo.invalidEntryPoints.find(
i => i.entryPoint.path === absoluteTargetEntryPointPath);
if (invalidTarget !== undefined) {
throw new Error(
`The target entry-point "${invalidTarget.entryPoint.name}" has missing dependencies:\n` +
invalidTarget.missingDependencies.map(dep => ` - ${dep}\n`));
}
if (entryPointInfo.entryPoints.length === 0) {
markNonAngularPackageAsProcessed(fs, absoluteTargetEntryPointPath, propertiesToConsider);
markNonAngularPackageAsProcessed(fs, absoluteTargetEntryPointPath);
}
return entryPointInfo;
}
@ -242,14 +339,13 @@ function hasProcessedTargetEntryPoint(
* So mark all formats in this entry-point as processed so that clients of ngcc can avoid
* triggering ngcc for this entry-point in the future.
*/
function markNonAngularPackageAsProcessed(
fs: FileSystem, path: AbsoluteFsPath, propertiesToConsider: string[]) {
function markNonAngularPackageAsProcessed(fs: FileSystem, path: AbsoluteFsPath) {
const packageJsonPath = resolve(path, 'package.json');
const packageJson = JSON.parse(fs.readFile(packageJsonPath));
propertiesToConsider.forEach(formatProperty => {
if (packageJson[formatProperty])
markAsProcessed(fs, packageJson, packageJsonPath, formatProperty as EntryPointJsonProperty);
});
// Note: We are marking all supported properties as processed, even if they don't exist in the
// `package.json` file. While this is redundant, it is also harmless.
markAsProcessed(fs, packageJson, packageJsonPath, SUPPORTED_FORMAT_PROPERTIES);
}
function logInvalidEntryPoints(logger: Logger, invalidEntryPoints: InvalidEntryPoint[]): void {
@ -260,3 +356,60 @@ function logInvalidEntryPoints(logger: Logger, invalidEntryPoints: InvalidEntryP
invalidEntryPoint.missingDependencies.map(dep => ` - ${dep}`).join('\n'));
});
}
/**
* This function computes and returns the following:
* - `propertiesToProcess`: An (ordered) list of properties that exist and need to be processed,
* based on the specified `propertiesToConsider`, the properties in `package.json` and their
* corresponding format-paths. NOTE: Only one property per format-path needs to be processed.
* - `propertyToPropertiesToMarkAsProcessed`: A mapping from each property in `propertiesToProcess`
* to the list of other properties in `package.json` that need to be marked as processed as soon
* as of the former being processed.
*/
function getPropertiesToProcessAndMarkAsProcessed(
packageJson: EntryPointPackageJson, propertiesToConsider: EntryPointJsonProperty[]): {
propertiesToProcess: EntryPointJsonProperty[];
propertyToPropertiesToMarkAsProcessed: Map<EntryPointJsonProperty, EntryPointJsonProperty[]>;
} {
const formatPathsToConsider = new Set<string>();
const propertiesToProcess: EntryPointJsonProperty[] = [];
for (const prop of propertiesToConsider) {
// Ignore properties that are not in `package.json`.
if (!packageJson.hasOwnProperty(prop)) continue;
const formatPath = packageJson[prop] !;
// Ignore properties that map to the same format-path as a preceding property.
if (formatPathsToConsider.has(formatPath)) continue;
// Process this property, because it is the first one to map to this format-path.
formatPathsToConsider.add(formatPath);
propertiesToProcess.push(prop);
}
const formatPathToProperties: {[formatPath: string]: EntryPointJsonProperty[]} = {};
for (const prop of SUPPORTED_FORMAT_PROPERTIES) {
// Ignore properties that are not in `package.json`.
if (!packageJson.hasOwnProperty(prop)) continue;
const formatPath = packageJson[prop] !;
// Ignore properties that do not map to a format-path that will be considered.
if (!formatPathsToConsider.has(formatPath)) continue;
// Add this property to the map.
const list = formatPathToProperties[formatPath] || (formatPathToProperties[formatPath] = []);
list.push(prop);
}
const propertyToPropertiesToMarkAsProcessed =
new Map<EntryPointJsonProperty, EntryPointJsonProperty[]>();
for (const prop of propertiesToConsider) {
const formatPath = packageJson[prop] !;
const propertiesToMarkAsProcessed = formatPathToProperties[formatPath];
propertyToPropertiesToMarkAsProcessed.set(prop, propertiesToMarkAsProcessed);
}
return {propertiesToProcess, propertyToPropertiesToMarkAsProcessed};
}

View File

@ -23,7 +23,7 @@ export const NGCC_VERSION = '0.0.0-PLACEHOLDER';
* @throws Error if the entry-point has already been processed with a different ngcc version.
*/
export function hasBeenProcessed(
packageJson: EntryPointPackageJson, format: EntryPointJsonProperty): boolean {
packageJson: EntryPointPackageJson, format: EntryPointJsonProperty | 'typings'): boolean {
if (!packageJson.__processed_by_ivy_ngcc__) {
return false;
}
@ -38,17 +38,36 @@ export function hasBeenProcessed(
}
/**
* Write a build marker for the given entry-point and format property, to indicate that it has
* Write a build marker for the given entry-point and format properties, to indicate that they have
* been compiled by this version of ngcc.
*
* @param entryPoint the entry-point to write a marker.
* @param format the property in the package.json of the format for which we are writing the marker.
* @param fs The current file-system being used.
* @param packageJson The parsed contents of the `package.json` file for the entry-point.
* @param packageJsonPath The absolute path to the `package.json` file.
* @param properties The properties in the `package.json` of the formats for which we are writing
* the marker.
*/
export function markAsProcessed(
fs: FileSystem, packageJson: EntryPointPackageJson, packageJsonPath: AbsoluteFsPath,
format: EntryPointJsonProperty) {
if (!packageJson.__processed_by_ivy_ngcc__) packageJson.__processed_by_ivy_ngcc__ = {};
packageJson.__processed_by_ivy_ngcc__[format] = NGCC_VERSION;
properties: (EntryPointJsonProperty | 'typings')[]) {
const processed =
packageJson.__processed_by_ivy_ngcc__ || (packageJson.__processed_by_ivy_ngcc__ = {});
for (const prop of properties) {
processed[prop] = NGCC_VERSION;
}
const scripts = packageJson.scripts || (packageJson.scripts = {});
scripts.prepublishOnly__ivy_ngcc_bak =
scripts.prepublishOnly__ivy_ngcc_bak || scripts.prepublishOnly;
scripts.prepublishOnly = 'node --eval \"console.error(\'' +
'ERROR: Trying to publish a package that has been compiled by NGCC. This is not allowed.\\n' +
'Please delete and rebuild the package, without compiling with NGCC, before attempting to publish.\\n' +
'Note that NGCC may have been run by importing this package into another project that is being built with Ivy enabled.\\n' +
'\')\" ' +
'&& exit 1';
// Just in case this package.json was synthesized due to a custom configuration
// we will ensure that the path to the containing folder exists before we write the file.
fs.ensureDir(dirname(packageJsonPath));

View File

@ -55,10 +55,11 @@ export interface PackageJsonFormatProperties {
*/
export interface EntryPointPackageJson extends PackageJsonFormatProperties {
name: string;
__processed_by_ivy_ngcc__?: {[key: string]: string};
scripts?: Record<string, string>;
__processed_by_ivy_ngcc__?: Record<string, string>;
}
export type EntryPointJsonProperty = keyof(PackageJsonFormatProperties);
export type EntryPointJsonProperty = Exclude<keyof PackageJsonFormatProperties, 'types'|'typings'>;
// We need to keep the elements of this const and the `EntryPointJsonProperty` type in sync.
export const SUPPORTED_FORMAT_PROPERTIES: EntryPointJsonProperty[] =
['fesm2015', 'fesm5', 'es2015', 'esm2015', 'esm5', 'main', 'module'];
@ -122,7 +123,8 @@ export function getEntryPointInfo(
* @returns An entry-point format or `undefined` if none match the given property.
*/
export function getEntryPointFormat(
fs: FileSystem, entryPoint: EntryPoint, property: string): EntryPointFormat|undefined {
fs: FileSystem, entryPoint: EntryPoint, property: EntryPointJsonProperty): EntryPointFormat|
undefined {
switch (property) {
case 'fesm2015':
return 'esm2015';

View File

@ -10,7 +10,7 @@ import {AbsoluteFsPath, FileSystem, absoluteFrom} from '../../../src/ngtsc/file_
import {NgtscCompilerHost} from '../../../src/ngtsc/file_system/src/compiler_host';
import {PathMappings} from '../utils';
import {BundleProgram, makeBundleProgram} from './bundle_program';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty} from './entry_point';
import {EntryPoint, EntryPointFormat} from './entry_point';
import {NgccSourcesCompilerHost} from './ngcc_compiler_host';
/**
@ -19,7 +19,6 @@ import {NgccSourcesCompilerHost} from './ngcc_compiler_host';
*/
export interface EntryPointBundle {
entryPoint: EntryPoint;
formatProperty: EntryPointJsonProperty;
format: EntryPointFormat;
isCore: boolean;
isFlatCore: boolean;
@ -34,7 +33,6 @@ export interface EntryPointBundle {
* @param entryPoint The entry-point that contains the bundle.
* @param formatPath The path to the source files for this bundle.
* @param isCore This entry point is the Angular core package.
* @param formatProperty The property in the package.json that holds the formatPath.
* @param format The underlying format of the bundle.
* @param transformDts Whether to transform the typings along with this bundle.
* @param pathMappings An optional set of mappings to use when compiling files.
@ -43,8 +41,8 @@ export interface EntryPointBundle {
*/
export function makeEntryPointBundle(
fs: FileSystem, entryPoint: EntryPoint, formatPath: string, isCore: boolean,
formatProperty: EntryPointJsonProperty, format: EntryPointFormat, transformDts: boolean,
pathMappings?: PathMappings, mirrorDtsFromSrc: boolean = false): EntryPointBundle|null {
format: EntryPointFormat, transformDts: boolean, pathMappings?: PathMappings,
mirrorDtsFromSrc: boolean = false): EntryPointBundle {
// Create the TS program and necessary helpers.
const options: ts.CompilerOptions = {
allowJs: true,
@ -69,7 +67,7 @@ export function makeEntryPointBundle(
null;
const isFlatCore = isCore && src.r3SymbolsFile === null;
return {entryPoint, format, formatProperty, rootDirs, isCore, isFlatCore, src, dts};
return {entryPoint, format, rootDirs, isCore, isFlatCore, src, dts};
}
function computePotentialDtsFilesFromJsFiles(
@ -88,4 +86,4 @@ function computePotentialDtsFilesFromJsFiles(
}
}
return additionalFiles;
}
}

View File

@ -6,7 +6,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {EntryPoint} from '../packages/entry_point';
import {EntryPointJsonProperty} from '../packages/entry_point';
import {EntryPointBundle} from '../packages/entry_point_bundle';
import {FileToWrite} from '../rendering/utils';
@ -14,6 +14,7 @@ import {FileToWrite} from '../rendering/utils';
* Responsible for writing out the transformed files to disk.
*/
export interface FileWriter {
writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileToWrite[]):
void;
writeBundle(
bundle: EntryPointBundle, transformedFiles: FileToWrite[],
formatProperties: EntryPointJsonProperty[]): void;
}

View File

@ -7,7 +7,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {FileSystem, absoluteFrom, dirname} from '../../../src/ngtsc/file_system';
import {EntryPoint} from '../packages/entry_point';
import {EntryPointJsonProperty} from '../packages/entry_point';
import {EntryPointBundle} from '../packages/entry_point_bundle';
import {FileToWrite} from '../rendering/utils';
import {FileWriter} from './file_writer';
@ -19,7 +19,9 @@ import {FileWriter} from './file_writer';
export class InPlaceFileWriter implements FileWriter {
constructor(protected fs: FileSystem) {}
writeBundle(_entryPoint: EntryPoint, _bundle: EntryPointBundle, transformedFiles: FileToWrite[]) {
writeBundle(
_bundle: EntryPointBundle, transformedFiles: FileToWrite[],
_formatProperties?: EntryPointJsonProperty[]) {
transformedFiles.forEach(file => this.writeFileAndBackup(file));
}

View File

@ -25,12 +25,15 @@ const NGCC_DIRECTORY = '__ivy_ngcc__';
* `InPlaceFileWriter`).
*/
export class NewEntryPointFileWriter extends InPlaceFileWriter {
writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileToWrite[]) {
writeBundle(
bundle: EntryPointBundle, transformedFiles: FileToWrite[],
formatProperties: EntryPointJsonProperty[]) {
// The new folder is at the root of the overall package
const entryPoint = bundle.entryPoint;
const ngccFolder = join(entryPoint.package, NGCC_DIRECTORY);
this.copyBundle(bundle, entryPoint.package, ngccFolder);
transformedFiles.forEach(file => this.writeFile(file, entryPoint.package, ngccFolder));
this.updatePackageJson(entryPoint, bundle.formatProperty, ngccFolder);
this.updatePackageJson(entryPoint, formatProperties, ngccFolder);
}
protected copyBundle(
@ -60,12 +63,18 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
}
protected updatePackageJson(
entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty, ngccFolder: AbsoluteFsPath) {
const formatPath = join(entryPoint.path, entryPoint.packageJson[formatProperty] !);
const newFormatPath = join(ngccFolder, relative(entryPoint.package, formatPath));
const newFormatProperty = formatProperty + '_ivy_ngcc';
(entryPoint.packageJson as any)[newFormatProperty] = relative(entryPoint.path, newFormatPath);
entryPoint: EntryPoint, formatProperties: EntryPointJsonProperty[],
ngccFolder: AbsoluteFsPath) {
const packageJson = entryPoint.packageJson;
for (const formatProperty of formatProperties) {
const formatPath = join(entryPoint.path, packageJson[formatProperty] !);
const newFormatPath = join(ngccFolder, relative(entryPoint.package, formatPath));
const newFormatProperty = formatProperty + '_ivy_ngcc';
(packageJson as any)[newFormatProperty] = relative(entryPoint.path, newFormatPath);
}
this.fs.writeFile(
join(entryPoint.path, 'package.json'), JSON.stringify(entryPoint.packageJson));
join(entryPoint.path, 'package.json'), `${JSON.stringify(packageJson, null, 2)}\n`);
}
}

View File

@ -96,8 +96,7 @@ runInEachFileSystem(() => {
loadTestFiles(testFiles);
loadFakeCore(getFileSystem());
const rootFiles = getRootFiles(testFiles);
const bundle =
makeTestEntryPointBundle('test-package', 'es2015', 'esm2015', false, rootFiles);
const bundle = makeTestEntryPointBundle('test-package', 'esm2015', false, rootFiles);
program = bundle.src.program;
const reflectionHost =
@ -373,4 +372,4 @@ class MockMigration implements Migration {
this.log.push(`${this.name}:${clazz.name.text}`);
return null;
}
}
}

View File

@ -330,7 +330,7 @@ runInEachFileSystem(() => {
loadTestFiles(TEST_PROGRAM);
loadTestFiles(TEST_DTS_PROGRAM);
const bundle = makeTestEntryPointBundle(
'test-package', 'esm2015', 'esm2015', false, getRootFiles(TEST_PROGRAM),
'test-package', 'esm2015', false, getRootFiles(TEST_PROGRAM),
getRootFiles(TEST_DTS_PROGRAM));
program = bundle.src.program;
dtsProgram = bundle.dts;

View File

@ -236,8 +236,7 @@ runInEachFileSystem(() => {
loadTestFiles(jsProgram);
loadTestFiles(dtsProgram);
const {src: {program}, dts} = makeTestEntryPointBundle(
'test-package', 'esm2015', 'esm2015', false, getRootFiles(jsProgram),
getRootFiles(dtsProgram));
'test-package', 'esm2015', false, getRootFiles(jsProgram), getRootFiles(dtsProgram));
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
const referencesRegistry = new NgccReferencesRegistry(host);
const analyzer = new PrivateDeclarationsAnalyzer(host, referencesRegistry);

View File

@ -72,7 +72,7 @@ runInEachFileSystem(() => {
it('should check for switchable markers in all the files of the program', () => {
loadTestFiles(TEST_PROGRAM);
const bundle = makeTestEntryPointBundle(
'test', 'esm2015', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
'test', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
const program = bundle.src.program;
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const analyzer = new SwitchMarkerAnalyzer(host, bundle.entryPoint.package);
@ -103,7 +103,7 @@ runInEachFileSystem(() => {
it('should ignore files that are outside the package', () => {
loadTestFiles(TEST_PROGRAM);
const bundle = makeTestEntryPointBundle(
'test', 'esm2015', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
'test', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
const program = bundle.src.program;
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const analyzer = new SwitchMarkerAnalyzer(host, bundle.entryPoint.package);
@ -114,4 +114,4 @@ runInEachFileSystem(() => {
});
});
});
});
});

View File

@ -179,6 +179,32 @@ runInEachFileSystem(() => {
expect(sorted.entryPoints).toEqual([fifth]);
});
it('should not process the provided target if it has missing dependencies', () => {
spyOn(host, 'findDependencies').and.callFake(createFakeComputeDependencies({
[_('/first/index.js')]: {resolved: [], missing: ['/missing']},
}));
const entryPoints = [first];
let sorted: SortedEntryPointsInfo;
sorted = resolver.sortEntryPointsByDependency(entryPoints, first);
expect(sorted.entryPoints).toEqual([]);
expect(sorted.invalidEntryPoints[0].entryPoint).toEqual(first);
expect(sorted.invalidEntryPoints[0].missingDependencies).toEqual(['/missing']);
});
it('should not consider builtin NodeJS modules as missing dependency', () => {
spyOn(host, 'findDependencies').and.callFake(createFakeComputeDependencies({
[_('/first/index.js')]: {resolved: [], missing: ['fs']},
}));
const entryPoints = [first];
let sorted: SortedEntryPointsInfo;
sorted = resolver.sortEntryPointsByDependency(entryPoints, first);
expect(sorted.entryPoints).toEqual([first]);
expect(sorted.invalidEntryPoints).toEqual([]);
expect(sorted.ignoredDependencies).toEqual([]);
});
it('should use the appropriate DependencyHost for each entry-point', () => {
const esm5Host = new EsmDependencyHost(fs, moduleResolver);
const esm2015Host = new EsmDependencyHost(fs, moduleResolver);

View File

@ -9,7 +9,7 @@ import * as ts from 'typescript';
import {AbsoluteFsPath, NgtscCompilerHost, absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
import {TestFile} from '../../../src/ngtsc/file_system/testing';
import {BundleProgram, makeBundleProgram} from '../../src/packages/bundle_program';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty} from '../../src/packages/entry_point';
import {EntryPoint, EntryPointFormat} from '../../src/packages/entry_point';
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
import {NgccSourcesCompilerHost} from '../../src/packages/ngcc_compiler_host';
@ -32,19 +32,13 @@ export function makeTestEntryPoint(
* @param dtsFiles The typings files to include the bundle.
*/
export function makeTestEntryPointBundle(
packageName: string, formatProperty: EntryPointJsonProperty, format: EntryPointFormat,
isCore: boolean, srcRootNames: AbsoluteFsPath[],
packageName: string, format: EntryPointFormat, isCore: boolean, srcRootNames: AbsoluteFsPath[],
dtsRootNames?: AbsoluteFsPath[]): EntryPointBundle {
const entryPoint = makeTestEntryPoint(packageName);
const src = makeTestBundleProgram(srcRootNames[0], isCore);
const dts = dtsRootNames ? makeTestDtsBundleProgram(dtsRootNames[0], isCore) : null;
const isFlatCore = isCore && src.r3SymbolsFile === null;
return {
entryPoint,
formatProperty,
format,
rootDirs: [absoluteFrom('/')], src, dts, isCore, isFlatCore
};
return {entryPoint, format, rootDirs: [absoluteFrom('/')], src, dts, isCore, isFlatCore};
}
export function makeTestBundleProgram(

View File

@ -86,12 +86,26 @@ runInEachFileSystem(() => {
// `test-package` has no Angular but is marked as processed.
expect(loadPackage('test-package').__processed_by_ivy_ngcc__).toEqual({
es2015: '0.0.0-PLACEHOLDER',
esm2015: '0.0.0-PLACEHOLDER',
esm5: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
fesm5: '0.0.0-PLACEHOLDER',
main: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
});
// * `core` is a dependency of `test-package`, but it is not processed, since test-package
// was not processed.
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeUndefined();
});
it('should report an error if a dependency of the target does not exist', () => {
expect(() => {
mainNgcc({basePath: '/node_modules', targetEntryPointPath: 'invalid-package'});
})
.toThrowError(
'The target entry-point "invalid-package" has missing dependencies:\n - @angular/missing\n');
});
});
describe('early skipping of target entry-point', () => {
@ -157,6 +171,23 @@ runInEachFileSystem(() => {
]);
});
});
it('should skip all processing if the first matching `propertyToConsider` is marked as processed',
() => {
const logger = new MockLogger();
markPropertiesAsProcessed('@angular/common/http/testing', ['esm2015']);
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: '@angular/common/http/testing',
// Simulate a property that does not exist on the package.json and will be ignored.
propertiesToConsider: ['missing', 'esm2015', 'esm5'],
compileAllFormats: false, logger,
});
expect(logger.logs.debug).toContain([
'The target entry-point has already been processed'
]);
});
});
@ -164,13 +195,22 @@ runInEachFileSystem(() => {
const basePath = _('/node_modules');
const targetPackageJsonPath = join(basePath, packagePath, 'package.json');
const targetPackage = loadPackage(packagePath);
markAsProcessed(fs, targetPackage, targetPackageJsonPath, 'typings');
properties.forEach(
property => markAsProcessed(fs, targetPackage, targetPackageJsonPath, property));
markAsProcessed(fs, targetPackage, targetPackageJsonPath, ['typings', ...properties]);
}
describe('with propertiesToConsider', () => {
it('should complain if none of the properties in the `propertiesToConsider` list is supported',
() => {
const propertiesToConsider = ['es1337', 'fesm42'];
const errorMessage =
'No supported format property to consider among [es1337, fesm42]. Supported ' +
'properties: fesm2015, fesm5, es2015, esm2015, esm5, main, module';
expect(() => mainNgcc({basePath: '/node_modules', propertiesToConsider}))
.toThrowError(errorMessage);
});
it('should only compile the entry-point formats given in the `propertiesToConsider` list',
() => {
mainNgcc({
@ -210,6 +250,33 @@ runInEachFileSystem(() => {
typings: '0.0.0-PLACEHOLDER',
});
});
it('should mark all matching properties as processed in order not to compile them on a subsequent run',
() => {
const logger = new MockLogger();
const logs = logger.logs.debug;
// `fesm2015` and `es2015` map to the same file: `./fesm2015/common.js`
mainNgcc({
basePath: '/node_modules/@angular/common',
propertiesToConsider: ['fesm2015'], logger,
});
expect(logs).not.toContain(['Skipping @angular/common : es2015 (already compiled).']);
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
// Now, compiling `es2015` should be a no-op.
mainNgcc({
basePath: '/node_modules/@angular/common',
propertiesToConsider: ['es2015'], logger,
});
expect(logs).toContain(['Skipping @angular/common : es2015 (already compiled).']);
});
});
describe('with compileAllFormats set to false', () => {
@ -256,6 +323,7 @@ runInEachFileSystem(() => {
});
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
fesm5: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
@ -268,6 +336,7 @@ runInEachFileSystem(() => {
});
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
esm5: '0.0.0-PLACEHOLDER',
fesm5: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
@ -313,6 +382,28 @@ runInEachFileSystem(() => {
.toMatch(ANGULAR_CORE_IMPORT_REGEX);
expect(fs.exists(_(`/node_modules/@angular/common/common.d.ts.__ivy_ngcc_bak`))).toBe(true);
});
it('should update `package.json` for all matching format properties', () => {
mainNgcc({
basePath: '/node_modules/@angular/core',
createNewEntryPointFormats: true,
propertiesToConsider: ['fesm2015', 'fesm5'],
});
const pkg: any = loadPackage('@angular/core');
// `es2015` is an alias of `fesm2015`.
expect(pkg.fesm2015).toEqual('./fesm2015/core.js');
expect(pkg.es2015).toEqual('./fesm2015/core.js');
expect(pkg.fesm2015_ivy_ngcc).toEqual('__ivy_ngcc__/fesm2015/core.js');
expect(pkg.es2015_ivy_ngcc).toEqual('__ivy_ngcc__/fesm2015/core.js');
// `module` is an alias of `fesm5`.
expect(pkg.fesm5).toEqual('./fesm5/core.js');
expect(pkg.module).toEqual('./fesm5/core.js');
expect(pkg.fesm5_ivy_ngcc).toEqual('__ivy_ngcc__/fesm5/core.js');
expect(pkg.module_ivy_ngcc).toEqual('__ivy_ngcc__/fesm5/core.js');
});
});
describe('logger', () => {
@ -342,6 +433,7 @@ runInEachFileSystem(() => {
});
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toEqual({
@ -399,6 +491,7 @@ runInEachFileSystem(() => {
});
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
});
@ -425,6 +518,7 @@ runInEachFileSystem(() => {
// We process core but not core/testing.
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('@angular/core/testing').__processed_by_ivy_ngcc__).toBeUndefined();
@ -432,6 +526,7 @@ runInEachFileSystem(() => {
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toBeUndefined();
expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
});
@ -482,6 +577,30 @@ runInEachFileSystem(() => {
contents: `export declare class AppComponent {};`
},
]);
// An Angular package that has a missing dependency
loadTestFiles([
{
name: _('/node_modules/invalid-package/package.json'),
contents: '{"name": "invalid-package", "es2015": "./index.js", "typings": "./index.d.ts"}'
},
{
name: _('/node_modules/invalid-package/index.js'),
contents: `
import {AppModule} from "@angular/missing";
import {Component} from '@angular/core';
export class AppComponent {};
AppComponent.decorators = [
{ type: Component, args: [{selector: 'app', template: '<h2>Hello</h2>'}] }
];
`
},
{
name: _('/node_modules/invalid-package/index.d.ts'),
contents: `export declare class AppComponent {}`
},
{name: _('/node_modules/invalid-package/index.metadata.json'), contents: 'DUMMY DATA'},
]);
}
});
});

View File

@ -161,7 +161,7 @@ runInEachFileSystem(() => {
loadFakeCore(getFileSystem());
const errors: ts.Diagnostic[] = [];
const rootFiles = getRootFiles(testFiles);
const bundle = makeTestEntryPointBundle('test-package', 'es2015', 'esm2015', false, rootFiles);
const bundle = makeTestEntryPointBundle('test-package', 'esm2015', false, rootFiles);
const program = bundle.src.program;
const reflectionHost =

View File

@ -79,31 +79,73 @@ runInEachFileSystem(() => {
});
describe('markAsProcessed', () => {
it('should write a property in the package.json containing the version placeholder', () => {
it('should write properties in the package.json containing the version placeholder', () => {
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
const fs = getFileSystem();
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
expect(pkg.scripts).toBeUndefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'fesm2015');
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5']);
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBeUndefined();
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined();
expect(pkg.scripts.prepublishOnly).toBeDefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'esm5');
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['esm2015', 'esm5']);
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm5).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.scripts.prepublishOnly).toBeDefined();
});
it('should update the packageJson object in-place', () => {
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
const fs = getFileSystem();
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
const pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'fesm2015');
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.scripts).toBeUndefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5']);
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBeUndefined();
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined();
expect(pkg.scripts.prepublishOnly).toBeDefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['esm2015', 'esm5']);
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.scripts.prepublishOnly).toBeDefined();
});
it('should one perform one write operation for all updated properties', () => {
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
const fs = getFileSystem();
const writeFileSpy = spyOn(fs, 'writeFile');
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5', 'esm2015', 'esm5']);
expect(writeFileSpy).toHaveBeenCalledTimes(1);
});
it(`should keep backup of existing 'prepublishOnly' script`, () => {
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
const fs = getFileSystem();
const prepublishOnly = 'existing script';
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
pkg.scripts = {prepublishOnly};
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015']);
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.scripts.prepublishOnly).toContain('This is not allowed');
expect(pkg.scripts.prepublishOnly__ivy_ngcc_bak).toBe(prepublishOnly);
});
});

View File

@ -144,8 +144,7 @@ runInEachFileSystem(() => {
typings: absoluteFrom('/node_modules/test/index.d.ts'),
compiledByAngular: true,
};
const esm5bundle =
makeEntryPointBundle(fs, entryPoint, './index.js', false, 'esm5', 'esm5', true) !;
const esm5bundle = makeEntryPointBundle(fs, entryPoint, './index.js', false, 'esm5', true);
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
.toEqual(jasmine.arrayWithExactContents([
@ -192,8 +191,8 @@ runInEachFileSystem(() => {
compiledByAngular: true,
};
const esm5bundle = makeEntryPointBundle(
fs, entryPoint, './index.js', false, 'esm5', 'esm5', /* transformDts */ true,
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ true) !;
fs, entryPoint, './index.js', false, 'esm5', /* transformDts */ true,
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ true);
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
.toContain(absoluteFrom('/node_modules/test/internal.js'));
expect(esm5bundle.dts !.program.getSourceFiles().map(sf => sf.fileName))
@ -213,8 +212,8 @@ runInEachFileSystem(() => {
compiledByAngular: true,
};
const esm5bundle = makeEntryPointBundle(
fs, entryPoint, './index.js', false, 'esm5', 'esm5', /* transformDts */ true,
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ false) !;
fs, entryPoint, './index.js', false, 'esm5', /* transformDts */ true,
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ false);
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
.toContain(absoluteFrom('/node_modules/test/internal.js'));
expect(esm5bundle.dts !.program.getSourceFiles().map(sf => sf.fileName))

View File

@ -149,8 +149,7 @@ exports.D = D;
loadTestFiles([file]);
const fs = getFileSystem();
const logger = new MockLogger();
const bundle =
makeTestEntryPointBundle('test-package', 'module', 'commonjs', false, [file.name]);
const bundle = makeTestEntryPointBundle('test-package', 'commonjs', false, [file.name]);
const typeChecker = bundle.src.program.getTypeChecker();
const host = new CommonJsReflectionHost(logger, false, bundle.src.program, bundle.src.host);
const referencesRegistry = new NgccReferencesRegistry(host);

View File

@ -61,8 +61,7 @@ function createTestRenderer(
const fs = getFileSystem();
const isCore = packageName === '@angular/core';
const bundle = makeTestEntryPointBundle(
'test-package', 'es2015', 'esm2015', isCore, getRootFiles(files),
dtsFiles && getRootFiles(dtsFiles));
'test-package', 'esm2015', isCore, getRootFiles(files), dtsFiles && getRootFiles(dtsFiles));
const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts);
const referencesRegistry = new NgccReferencesRegistry(host);

View File

@ -26,7 +26,7 @@ function setup(file: {name: AbsoluteFsPath, contents: string}) {
loadTestFiles([file]);
const fs = getFileSystem();
const logger = new MockLogger();
const bundle = makeTestEntryPointBundle('test-package', 'module', 'esm5', false, [file.name]);
const bundle = makeTestEntryPointBundle('test-package', 'esm5', false, [file.name]);
const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm5ReflectionHost(logger, false, typeChecker);
const referencesRegistry = new NgccReferencesRegistry(host);

View File

@ -31,8 +31,7 @@ function setup(files: TestFile[], dtsFiles?: TestFile[]) {
}
const logger = new MockLogger();
const bundle = makeTestEntryPointBundle(
'test-package', 'es2015', 'esm2015', false, getRootFiles(files),
dtsFiles && getRootFiles(dtsFiles)) !;
'test-package', 'esm2015', false, getRootFiles(files), dtsFiles && getRootFiles(dtsFiles)) !;
const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm2015ReflectionHost(logger, false, typeChecker, bundle.dts);
const referencesRegistry = new NgccReferencesRegistry(host);

View File

@ -63,8 +63,7 @@ function createTestRenderer(
const fs = getFileSystem();
const isCore = packageName === '@angular/core';
const bundle = makeTestEntryPointBundle(
'test-package', 'es2015', 'esm2015', isCore, getRootFiles(files),
dtsFiles && getRootFiles(dtsFiles));
'test-package', 'esm2015', isCore, getRootFiles(files), dtsFiles && getRootFiles(dtsFiles));
const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts);
const referencesRegistry = new NgccReferencesRegistry(host);
@ -309,7 +308,7 @@ runInEachFileSystem(() => {
ɵngcc0.ɵɵstaticViewQuery(_c0, true);
} if (rf & 2) {
var _t;
ɵngcc0.ɵɵqueryRefresh(_t = ɵngcc0.ɵɵloadViewQuery()) && (ctx.test = _t.first);
ɵngcc0.ɵɵqueryRefresh(_t = ɵngcc0.ɵɵloadQuery()) && (ctx.test = _t.first);
} } });`);
});

View File

@ -25,7 +25,7 @@ function setup(file: TestFile) {
loadTestFiles([file]);
const fs = getFileSystem();
const logger = new MockLogger();
const bundle = makeTestEntryPointBundle('test-package', 'esm5', 'esm5', false, [file.name]);
const bundle = makeTestEntryPointBundle('test-package', 'esm5', false, [file.name]);
const src = bundle.src;
const host = new UmdReflectionHost(logger, false, src.program, src.host);
const referencesRegistry = new NgccReferencesRegistry(host);

View File

@ -8,7 +8,6 @@
import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
import {loadTestFiles} from '../../../test/helpers';
import {EntryPoint} from '../../src/packages/entry_point';
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
import {InPlaceFileWriter} from '../../src/writing/in_place_file_writer';
@ -32,7 +31,7 @@ runInEachFileSystem(() => {
it('should write all the FileInfo to the disk', () => {
const fs = getFileSystem();
const fileWriter = new InPlaceFileWriter(fs);
fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [
fileWriter.writeBundle({} as EntryPointBundle, [
{path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
{path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'},
{path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'},
@ -49,7 +48,7 @@ runInEachFileSystem(() => {
it('should create backups of all files that previously existed', () => {
const fs = getFileSystem();
const fileWriter = new InPlaceFileWriter(fs);
fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [
fileWriter.writeBundle({} as EntryPointBundle, [
{path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
{path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'},
{path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'},
@ -72,10 +71,7 @@ runInEachFileSystem(() => {
const absoluteBackupPath = _('/package/path/already-backed-up.js');
expect(
() => fileWriter.writeBundle(
{} as EntryPoint, {} as EntryPointBundle,
[
{path: absoluteBackupPath, contents: 'MODIFIED BACKED UP'},
]))
{} as EntryPointBundle, [{path: absoluteBackupPath, contents: 'MODIFIED BACKED UP'}]))
.toThrowError(
`Tried to overwrite ${absoluteBackupPath}.__ivy_ngcc_bak with an ngcc back up file, which is disallowed.`);
});

View File

@ -32,8 +32,15 @@ runInEachFileSystem(() => {
{
name: _('/node_modules/test/package.json'),
contents:
'{"module": "./esm5.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}'
contents: `
{
"module": "./esm5.js",
"fesm2015": "./es2015/index.js",
"fesm5": "./esm5.js",
"es2015": "./es2015/index.js",
"typings": "./index.d.ts"
}
`,
},
{name: _('/node_modules/test/index.d.ts'), contents: 'export declare class FooTop {}'},
{name: _('/node_modules/test/index.d.ts.map'), contents: 'ORIGINAL MAPPING DATA'},
@ -44,8 +51,15 @@ runInEachFileSystem(() => {
{name: _('/node_modules/test/es2015/foo.js'), contents: 'export class FooTop {}'},
{
name: _('/node_modules/test/a/package.json'),
contents:
`{"module": "./esm5.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}`
contents: `
{
"module": "./esm5.js",
"fesm2015": "./es2015/index.js",
"fesm5": "./esm5.js",
"es2015": "./es2015/index.js",
"typings": "./index.d.ts"
}
`,
},
{name: _('/node_modules/test/a/index.d.ts'), contents: 'export declare class FooA {}'},
{name: _('/node_modules/test/a/index.metadata.json'), contents: '...'},
@ -95,13 +109,16 @@ runInEachFileSystem(() => {
});
it('should write the modified files to a new folder', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [
{
path: _('/node_modules/test/esm5.js'),
contents: 'export function FooTop() {} // MODIFIED'
},
{path: _('/node_modules/test/esm5.js.map'), contents: 'MODIFIED MAPPING DATA'},
]);
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/esm5.js'),
contents: 'export function FooTop() {} // MODIFIED'
},
{path: _('/node_modules/test/esm5.js.map'), contents: 'MODIFIED MAPPING DATA'},
],
['module']);
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/esm5.js')))
.toEqual('export function FooTop() {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/esm5.js'))).toEqual('export function FooTop() {}');
@ -111,12 +128,15 @@ runInEachFileSystem(() => {
});
it('should also copy unmodified files in the program', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/es2015/foo.js'),
contents: 'export class FooTop {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/es2015/foo.js'),
contents: 'export class FooTop {} // MODIFIED'
},
],
['es2015']);
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/es2015/foo.js')))
.toEqual('export class FooTop {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/es2015/foo.js')))
@ -128,36 +148,77 @@ runInEachFileSystem(() => {
});
it('should update the package.json properties', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [
{
path: _('/node_modules/test/esm5.js'),
contents: 'export function FooTop() {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/esm5.js'),
contents: 'export function FooTop() {} // MODIFIED'
},
],
['module']);
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
}));
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/es2015/foo.js'),
contents: 'export class FooTop {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/es2015/foo.js'),
contents: 'export class FooTop {} // MODIFIED'
},
],
['es2015']);
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
es2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js',
}));
});
it('should be able to update multiple package.json properties at once', () => {
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/esm5.js'),
contents: 'export function FooTop() {} // MODIFIED'
},
],
['module', 'fesm5']);
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
fesm5_ivy_ngcc: '__ivy_ngcc__/esm5.js',
}));
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/es2015/foo.js'),
contents: 'export class FooTop {} // MODIFIED'
},
],
['es2015', 'fesm2015']);
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
fesm5_ivy_ngcc: '__ivy_ngcc__/esm5.js',
es2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js',
fesm2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js',
}));
});
it('should overwrite and backup typings files', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/index.d.ts'),
contents: 'export declare class FooTop {} // MODIFIED'
},
{path: _('/node_modules/test/index.d.ts.map'), contents: 'MODIFIED MAPPING DATA'},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/index.d.ts'),
contents: 'export declare class FooTop {} // MODIFIED'
},
{path: _('/node_modules/test/index.d.ts.map'), contents: 'MODIFIED MAPPING DATA'},
],
['es2015']);
expect(fs.readFile(_('/node_modules/test/index.d.ts')))
.toEqual('export declare class FooTop {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/index.d.ts.__ivy_ngcc_bak')))
@ -184,24 +245,30 @@ runInEachFileSystem(() => {
});
it('should write the modified file to a new folder', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [
{
path: _('/node_modules/test/a/esm5.js'),
contents: 'export function FooA() {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/a/esm5.js'),
contents: 'export function FooA() {} // MODIFIED'
},
],
['module']);
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/esm5.js')))
.toEqual('export function FooA() {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/a/esm5.js'))).toEqual('export function FooA() {}');
});
it('should also copy unmodified files in the program', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/a/es2015/foo.js'),
contents: 'export class FooA {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/a/es2015/foo.js'),
contents: 'export class FooA {} // MODIFIED'
},
],
['es2015']);
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/es2015/foo.js')))
.toEqual('export class FooA {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/a/es2015/foo.js')))
@ -213,35 +280,76 @@ runInEachFileSystem(() => {
});
it('should update the package.json properties', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [
{
path: _('/node_modules/test/a/esm5.js'),
contents: 'export function FooA() {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/a/esm5.js'),
contents: 'export function FooA() {} // MODIFIED'
},
],
['module']);
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
}));
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/a/es2015/foo.js'),
contents: 'export class FooA {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/a/es2015/foo.js'),
contents: 'export class FooA {} // MODIFIED'
},
],
['es2015']);
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
es2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js',
}));
});
it('should be able to update multiple package.json properties at once', () => {
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/a/esm5.js'),
contents: 'export function FooA() {} // MODIFIED'
},
],
['module', 'fesm5']);
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
fesm5_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
}));
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/a/es2015/foo.js'),
contents: 'export class FooA {} // MODIFIED'
},
],
['es2015', 'fesm2015']);
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
fesm5_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
es2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js',
fesm2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js',
}));
});
it('should overwrite and backup typings files', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/a/index.d.ts'),
contents: 'export declare class FooA {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/a/index.d.ts'),
contents: 'export declare class FooA {} // MODIFIED'
},
],
['es2015']);
expect(fs.readFile(_('/node_modules/test/a/index.d.ts')))
.toEqual('export declare class FooA {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/a/index.d.ts.__ivy_ngcc_bak')))
@ -262,12 +370,15 @@ runInEachFileSystem(() => {
});
it('should write the modified file to a new folder', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [
{
path: _('/node_modules/test/lib/esm5.js'),
contents: 'export function FooB() {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/lib/esm5.js'),
contents: 'export function FooB() {} // MODIFIED'
},
],
['module']);
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/esm5.js')))
.toEqual('export function FooB() {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/lib/esm5.js')))
@ -275,12 +386,15 @@ runInEachFileSystem(() => {
});
it('should also copy unmodified files in the program', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
],
['es2015']);
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/es2015/foo.js')))
.toEqual('export class FooB {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/lib/es2015/foo.js')))
@ -293,43 +407,55 @@ runInEachFileSystem(() => {
it('should not copy typings files within the package (i.e. from a different entry-point)',
() => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
],
['es2015']);
expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/a/index.d.ts'))).toEqual(false);
});
it('should not copy files outside of the package', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
],
['es2015']);
expect(fs.exists(_('/node_modules/test/other/index.d.ts'))).toEqual(false);
expect(fs.exists(_('/node_modules/test/events/events.js'))).toEqual(false);
});
it('should update the package.json properties', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [
{
path: _('/node_modules/test/lib/esm5.js'),
contents: 'export function FooB() {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/lib/esm5.js'),
contents: 'export function FooB() {} // MODIFIED'
},
],
['module']);
expect(loadPackageJson(fs, '/node_modules/test/b')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js',
}));
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
],
['es2015']);
expect(loadPackageJson(fs, '/node_modules/test/b')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js',
es2015_ivy_ngcc: '../__ivy_ngcc__/lib/es2015/index.js',
@ -337,12 +463,15 @@ runInEachFileSystem(() => {
});
it('should overwrite and backup typings files', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/typings/index.d.ts'),
contents: 'export declare class FooB {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/typings/index.d.ts'),
contents: 'export declare class FooB {} // MODIFIED'
},
],
['es2015']);
expect(fs.readFile(_('/node_modules/test/typings/index.d.ts')))
.toEqual('export declare class FooB {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/typings/index.d.ts.__ivy_ngcc_bak')))
@ -356,7 +485,6 @@ runInEachFileSystem(() => {
fs: FileSystem, entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty,
format: EntryPointFormat): EntryPointBundle {
return makeEntryPointBundle(
fs, entryPoint, entryPoint.packageJson[formatProperty] !, false, formatProperty, format,
true) !;
fs, entryPoint, entryPoint.packageJson[formatProperty] !, false, format, true);
}
});

View File

@ -18,7 +18,7 @@ import {DirectiveMeta, MetadataReader, MetadataRegistry, extractDirectiveGuards}
import {flattenInheritedDirectiveMetadata} from '../../metadata/src/inheritance';
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {LocalModuleScopeRegistry} from '../../scope';
import {ComponentScopeReader, LocalModuleScopeRegistry} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
import {TypeCheckContext} from '../../typecheck';
import {NoopResourceDependencyRecorder, ResourceDependencyRecorder} from '../../util/src/resource_recorder';
@ -47,8 +47,8 @@ export class ComponentDecoratorHandler implements
constructor(
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
private metaRegistry: MetadataRegistry, private metaReader: MetadataReader,
private scopeRegistry: LocalModuleScopeRegistry, private isCore: boolean,
private resourceLoader: ResourceLoader, private rootDirs: string[],
private scopeReader: ComponentScopeReader, private scopeRegistry: LocalModuleScopeRegistry,
private isCore: boolean, private resourceLoader: ResourceLoader, private rootDirs: string[],
private defaultPreserveWhitespaces: boolean, private i18nUseExternalIds: boolean,
private moduleResolver: ModuleResolver, private cycleAnalyzer: CycleAnalyzer,
private refEmitter: ReferenceEmitter, private defaultImportRecorder: DefaultImportRecorder,
@ -327,7 +327,7 @@ export class ComponentDecoratorHandler implements
preserveWhitespaces: true,
leadingTriviaChars: [],
});
const scope = this.scopeRegistry.getScopeForComponent(node);
const scope = this.scopeReader.getScopeForComponent(node);
const selector = analysis.meta.selector;
const matcher = new SelectorMatcher<DirectiveMeta>();
if (scope !== null) {
@ -353,7 +353,7 @@ export class ComponentDecoratorHandler implements
if (!ts.isClassDeclaration(node)) {
return;
}
const scope = this.scopeRegistry.getScopeForComponent(node);
const scope = this.scopeReader.getScopeForComponent(node);
const matcher = new SelectorMatcher<DirectiveMeta>();
if (scope !== null) {
for (const meta of scope.compilation.directives) {
@ -377,7 +377,7 @@ export class ComponentDecoratorHandler implements
const context = node.getSourceFile();
// Check whether this component was registered with an NgModule. If so, it should be compiled
// under that module's compilation scope.
const scope = this.scopeRegistry.getScopeForComponent(node);
const scope = this.scopeReader.getScopeForComponent(node);
let metadata = analysis.meta;
if (scope !== null) {
// Replace the empty components and directives from the analyze() step with a fully expanded

View File

@ -60,7 +60,7 @@ runInEachFileSystem(() => {
const refEmitter = new ReferenceEmitter([]);
const handler = new ComponentDecoratorHandler(
reflectionHost, evaluator, metaRegistry, metaReader, scopeRegistry, false,
reflectionHost, evaluator, metaRegistry, metaReader, scopeRegistry, scopeRegistry, false,
new NoopResourceLoader(), [''], false, true, moduleResolver, cycleAnalyzer, refEmitter,
NOOP_DEFAULT_IMPORT_RECORDER);
const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration);

View File

@ -12,6 +12,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/scope",
"//packages/compiler-cli/src/ngtsc/util",
"@npm//typescript",
],

View File

@ -12,13 +12,14 @@ import {Reference} from '../../imports';
import {DirectiveMeta, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from '../../metadata';
import {DependencyTracker} from '../../partial_evaluator';
import {ClassDeclaration} from '../../reflection';
import {ComponentScopeReader, ComponentScopeRegistry, LocalModuleScope} from '../../scope';
import {ResourceDependencyRecorder} from '../../util/src/resource_recorder';
/**
* Accumulates state between compilations.
*/
export class IncrementalState implements DependencyTracker, MetadataReader, MetadataRegistry,
ResourceDependencyRecorder {
ResourceDependencyRecorder, ComponentScopeRegistry, ComponentScopeReader {
private constructor(
private unchangedFiles: Set<ts.SourceFile>,
private metadata: Map<ts.SourceFile, FileMetadata>,
@ -69,32 +70,56 @@ export class IncrementalState implements DependencyTracker, MetadataReader, Meta
}
getFileDependencies(file: ts.SourceFile): ts.SourceFile[] {
const meta = this.metadata.get(file);
return meta ? Array.from(meta.fileDependencies) : [];
if (!this.metadata.has(file)) {
return [];
}
const meta = this.metadata.get(file) !;
return Array.from(meta.fileDependencies);
}
getNgModuleMetadata(ref: Reference<ClassDeclaration>): NgModuleMeta|null {
const metadata = this.metadata.get(ref.node.getSourceFile()) || null;
return metadata && metadata.ngModuleMeta.get(ref.node) || null;
if (!this.metadata.has(ref.node.getSourceFile())) {
return null;
}
const metadata = this.metadata.get(ref.node.getSourceFile()) !;
if (!metadata.ngModuleMeta.has(ref.node)) {
return null;
}
return metadata.ngModuleMeta.get(ref.node) !;
}
registerNgModuleMetadata(meta: NgModuleMeta): void {
const metadata = this.ensureMetadata(meta.ref.node.getSourceFile());
metadata.ngModuleMeta.set(meta.ref.node, meta);
}
getDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta|null {
const metadata = this.metadata.get(ref.node.getSourceFile()) || null;
return metadata && metadata.directiveMeta.get(ref.node) || null;
if (!this.metadata.has(ref.node.getSourceFile())) {
return null;
}
const metadata = this.metadata.get(ref.node.getSourceFile()) !;
if (!metadata.directiveMeta.has(ref.node)) {
return null;
}
return metadata.directiveMeta.get(ref.node) !;
}
registerDirectiveMetadata(meta: DirectiveMeta): void {
const metadata = this.ensureMetadata(meta.ref.node.getSourceFile());
metadata.directiveMeta.set(meta.ref.node, meta);
}
getPipeMetadata(ref: Reference<ClassDeclaration>): PipeMeta|null {
const metadata = this.metadata.get(ref.node.getSourceFile()) || null;
return metadata && metadata.pipeMeta.get(ref.node) || null;
if (!this.metadata.has(ref.node.getSourceFile())) {
return null;
}
const metadata = this.metadata.get(ref.node.getSourceFile()) !;
if (!metadata.pipeMeta.has(ref.node)) {
return null;
}
return metadata.pipeMeta.get(ref.node) !;
}
registerPipeMetadata(meta: PipeMeta): void {
const metadata = this.ensureMetadata(meta.ref.node.getSourceFile());
metadata.pipeMeta.set(meta.ref.node, meta);
@ -105,6 +130,40 @@ export class IncrementalState implements DependencyTracker, MetadataReader, Meta
metadata.resourcePaths.add(resourcePath);
}
registerComponentScope(clazz: ClassDeclaration, scope: LocalModuleScope): void {
const metadata = this.ensureMetadata(clazz.getSourceFile());
metadata.componentScope.set(clazz, scope);
}
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null {
if (!this.metadata.has(clazz.getSourceFile())) {
return null;
}
const metadata = this.metadata.get(clazz.getSourceFile()) !;
if (!metadata.componentScope.has(clazz)) {
return null;
}
return metadata.componentScope.get(clazz) !;
}
setComponentAsRequiringRemoteScoping(clazz: ClassDeclaration): void {
const metadata = this.ensureMetadata(clazz.getSourceFile());
metadata.remoteScoping.add(clazz);
}
getRequiresRemoteScope(clazz: ClassDeclaration): boolean|null {
// TODO: https://angular-team.atlassian.net/browse/FW-1501
// Handle the incremental build case where a component requires remote scoping.
// This means that if the the component's template changes, it requires the module to be
// re-emitted.
// Also, we need to make sure the cycle detector works well across rebuilds.
if (!this.metadata.has(clazz.getSourceFile())) {
return null;
}
const metadata = this.metadata.get(clazz.getSourceFile()) !;
return metadata.remoteScoping.has(clazz);
}
private ensureMetadata(sf: ts.SourceFile): FileMetadata {
const metadata = this.metadata.get(sf) || new FileMetadata();
this.metadata.set(sf, metadata);
@ -131,4 +190,6 @@ class FileMetadata {
directiveMeta = new Map<ClassDeclaration, DirectiveMeta>();
ngModuleMeta = new Map<ClassDeclaration, NgModuleMeta>();
pipeMeta = new Map<ClassDeclaration, PipeMeta>();
componentScope = new Map<ClassDeclaration, LocalModuleScope>();
remoteScoping = new Set<ClassDeclaration>();
}

View File

@ -28,7 +28,7 @@ import {NOOP_PERF_RECORDER, PerfRecorder, PerfTracker} from './perf';
import {TypeScriptReflectionHost} from './reflection';
import {HostResourceLoader} from './resource_loader';
import {NgModuleRouteAnalyzer, entryPointKeyFor} from './routing';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from './scope';
import {CompoundComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from './scope';
import {FactoryGenerator, FactoryInfo, GeneratedShimsHostWrapper, ShimGenerator, SummaryGenerator, TypeCheckShimGenerator, generatedFactoryTransform} from './shims';
import {ivySwitchTransform} from './switch';
import {IvyCompilation, declarationTransformFactory, ivyTransformFactory} from './transform';
@ -476,7 +476,8 @@ export class NgtscProgram implements api.Program {
const localMetaReader = new CompoundMetadataReader([localMetaRegistry, this.incrementalState]);
const depScopeReader = new MetadataDtsModuleScopeResolver(dtsReader, aliasGenerator);
const scopeRegistry = new LocalModuleScopeRegistry(
localMetaReader, depScopeReader, this.refEmitter, aliasGenerator);
localMetaReader, depScopeReader, this.refEmitter, aliasGenerator, this.incrementalState);
const scopeReader = new CompoundComponentScopeReader([scopeRegistry, this.incrementalState]);
const metaRegistry =
new CompoundMetadataRegistry([localMetaRegistry, scopeRegistry, this.incrementalState]);
@ -502,10 +503,11 @@ export class NgtscProgram implements api.Program {
const handlers = [
new BaseDefDecoratorHandler(this.reflector, evaluator, this.isCore),
new ComponentDecoratorHandler(
this.reflector, evaluator, metaRegistry, this.metaReader !, scopeRegistry, this.isCore,
this.resourceManager, this.rootDirs, this.options.preserveWhitespaces || false,
this.options.i18nUseExternalIds !== false, this.moduleResolver, this.cycleAnalyzer,
this.refEmitter, this.defaultImportTracker, this.incrementalState),
this.reflector, evaluator, metaRegistry, this.metaReader !, scopeReader, scopeRegistry,
this.isCore, this.resourceManager, this.rootDirs,
this.options.preserveWhitespaces || false, this.options.i18nUseExternalIds !== false,
this.moduleResolver, this.cycleAnalyzer, this.refEmitter, this.defaultImportTracker,
this.incrementalState),
new DirectiveDecoratorHandler(
this.reflector, evaluator, metaRegistry, this.defaultImportTracker, this.isCore),
new InjectableDecoratorHandler(

View File

@ -7,5 +7,6 @@
*/
export {ExportScope, ScopeData} from './src/api';
export {ComponentScopeReader, ComponentScopeRegistry, CompoundComponentScopeReader} from './src/component_scope';
export {DtsModuleScopeResolver, MetadataDtsModuleScopeResolver} from './src/dependency';
export {LocalModuleScope, LocalModuleScopeRegistry, LocalNgModuleData} from './src/local';

View File

@ -0,0 +1,67 @@
/**
* @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 {ClassDeclaration} from '../../reflection';
import {LocalModuleScope} from './local';
/**
* Register information about the compilation scope of components.
*/
export interface ComponentScopeRegistry {
registerComponentScope(clazz: ClassDeclaration, scope: LocalModuleScope): void;
setComponentAsRequiringRemoteScoping(clazz: ClassDeclaration): void;
}
/**
* Read information about the compilation scope of components.
*/
export interface ComponentScopeReader {
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null;
getRequiresRemoteScope(clazz: ClassDeclaration): boolean|null;
}
/**
* A noop registry that doesn't do anything.
*
* This can be used in tests and cases where we don't care about the compilation scopes
* being registered.
*/
export class NoopComponentScopeRegistry implements ComponentScopeRegistry {
registerComponentScope(clazz: ClassDeclaration, scope: LocalModuleScope): void {}
setComponentAsRequiringRemoteScoping(clazz: ClassDeclaration): void {}
}
/**
* A `ComponentScopeReader` that reads from an ordered set of child readers until it obtains the
* requested scope.
*
* This is used to combine `ComponentScopeReader`s that read from different sources (e.g. from a
* registry and from the incremental state).
*/
export class CompoundComponentScopeReader implements ComponentScopeReader {
constructor(private readers: ComponentScopeReader[]) {}
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null {
for (const reader of this.readers) {
const meta = reader.getScopeForComponent(clazz);
if (meta !== null) {
return meta;
}
}
return null;
}
getRequiresRemoteScope(clazz: ClassDeclaration): boolean|null {
for (const reader of this.readers) {
const requiredScoping = reader.getRequiresRemoteScope(clazz);
if (requiredScoping !== null) {
return requiredScoping;
}
}
return null;
}
}

View File

@ -11,11 +11,12 @@ import * as ts from 'typescript';
import {ErrorCode, makeDiagnostic} from '../../diagnostics';
import {AliasGenerator, Reexport, Reference, ReferenceEmitter} from '../../imports';
import {DirectiveMeta, LocalMetadataRegistry, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from '../../metadata';
import {DirectiveMeta, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from '../../metadata';
import {ClassDeclaration} from '../../reflection';
import {identifierOfNode, nodeNameForError} from '../../util/src/typescript';
import {ExportScope, ScopeData} from './api';
import {ComponentScopeReader, ComponentScopeRegistry, NoopComponentScopeRegistry} from './component_scope';
import {DtsModuleScopeResolver} from './dependency';
export interface LocalNgModuleData {
@ -58,7 +59,7 @@ export interface CompilationScope extends ScopeData {
* The `LocalModuleScopeRegistry` is also capable of producing `ts.Diagnostic` errors when Angular
* semantics are violated.
*/
export class LocalModuleScopeRegistry implements MetadataRegistry {
export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScopeReader {
/**
* Tracks whether the registry has been asked to produce scopes for a module or component. Once
* this is true, the registry cannot accept registrations of new directives/pipes/modules as it
@ -102,7 +103,8 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
constructor(
private localReader: MetadataReader, private dependencyScopeReader: DtsModuleScopeResolver,
private refEmitter: ReferenceEmitter, private aliasGenerator: AliasGenerator|null) {}
private refEmitter: ReferenceEmitter, private aliasGenerator: AliasGenerator|null,
private componentScopeRegistry: ComponentScopeRegistry = new NoopComponentScopeRegistry()) {}
/**
* Add an NgModule's data to the registry.
@ -120,10 +122,13 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
registerPipeMetadata(pipe: PipeMeta): void {}
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null {
if (!this.declarationToModule.has(clazz)) {
return null;
const scope = !this.declarationToModule.has(clazz) ?
null :
this.getScopeOfModule(this.declarationToModule.get(clazz) !);
if (scope !== null) {
this.componentScopeRegistry.registerComponentScope(clazz, scope);
}
return this.getScopeOfModule(this.declarationToModule.get(clazz) !);
return scope;
}
/**
@ -383,6 +388,7 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
*/
setComponentAsRequiringRemoteScoping(node: ClassDeclaration): void {
this.remoteScoping.add(node);
this.componentScopeRegistry.setComponentAsRequiringRemoteScoping(node);
}
/**

View File

@ -1535,8 +1535,8 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.someDirs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDirs = $tmp$);
}
},
consts: 1,
@ -1593,8 +1593,8 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.myRefs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRefs = $tmp$);
}
},
@ -1646,8 +1646,8 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.foo = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.foo = $tmp$.first);
}
},
consts: 1,
@ -1711,10 +1711,10 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.myRefs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.someDirs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRefs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDirs = $tmp$);
}
},
@ -1775,8 +1775,8 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.someDirList = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDirList = $tmp$);
}
},
ngContentSelectors: _c0,
@ -1835,8 +1835,8 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.myRefs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRefs = $tmp$);
}
},
@ -1897,8 +1897,8 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.foo = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.foo = $tmp$.first);
}
},
ngContentSelectors: $_c1$,
@ -1964,10 +1964,10 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.myRefs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.someDirs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRefs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDirs = $tmp$);
}
},
@ -3144,7 +3144,7 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.something = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.something = $tmp$.first);
}
}
});
@ -3189,7 +3189,7 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.something = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.something = $tmp$);
}
}
});
@ -3232,7 +3232,7 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.something = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.something = $tmp$.first);
}
}
});
@ -3277,7 +3277,7 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.something = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.something = $tmp$);
}
}
});

View File

@ -2600,14 +2600,16 @@ describe('i18n support in the view compiler', () => {
function MyComponent_div_2_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelementStart(0, "div", $_c2$);
$r3$.ɵɵi18n(1, $I18N_3$);
i0.ɵɵtext(1, " ");
$r3$.ɵɵi18n(2, $I18N_3$);
i0.ɵɵtext(3, " ");
$r3$.ɵɵelementEnd();
}
if (rf & 2) {
const $ctx_r0$ = $r3$.ɵɵnextContext();
$r3$.ɵɵselect(1);
$r3$.ɵɵselect(2);
$r3$.ɵɵi18nExp($ctx_r0$.age);
$r3$.ɵɵi18nApply(1);
$r3$.ɵɵi18nApply(2);
}
}
const $_c3$ = ["title", "icu and text"];
@ -2646,7 +2648,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵi18n(1, $I18N_0$);
$r3$.ɵɵelementEnd();
$r3$.ɵɵtemplate(2, MyComponent_div_2_Template, 2, 1, "div", $_c0$);
$r3$.ɵɵtemplate(2, MyComponent_div_2_Template, 4, 1, "div", $_c0$);
$r3$.ɵɵtemplate(3, MyComponent_div_3_Template, 4, 2, "div", $_c1$);
}
if (rf & 2) {
@ -2730,7 +2732,7 @@ describe('i18n support in the view compiler', () => {
const $_c2$ = [1, "other"];
var $I18N_0$;
if (ngI18nClosureMode) {
const $MSG_EXTERNAL_5791551881115084301$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}", {
const $MSG_EXTERNAL_5791551881115084301$$APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}", {
"startBoldText": "\uFFFD#2\uFFFD",
"closeBoldText": "\uFFFD/#2\uFFFD",
"startTagDiv": "\uFFFD#3\uFFFD",
@ -2742,7 +2744,7 @@ describe('i18n support in the view compiler', () => {
$I18N_0$ = $MSG_EXTERNAL_5791551881115084301$$APP_SPEC_TS_0$;
}
else {
$I18N_0$ = $r3$.ɵɵi18nLocalize("{$icu}{$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}", {
$I18N_0$ = $r3$.ɵɵi18nLocalize(" {$icu} {$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}", {
"startBoldText": "\uFFFD#2\uFFFD",
"closeBoldText": "\uFFFD/#2\uFFFD",
"startTagDiv": "\uFFFD#3\uFFFD",
@ -2848,14 +2850,14 @@ describe('i18n support in the view compiler', () => {
});
var $I18N_0$;
if (ngI18nClosureMode) {
const $MSG_EXTERNAL_2967249209167308918$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$icu_1}", {
const $MSG_EXTERNAL_2967249209167308918$$APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$icu_1} ", {
"icu": $I18N_1$,
"icu_1": $I18N_2$
});
$I18N_0$ = $MSG_EXTERNAL_2967249209167308918$$APP_SPEC_TS_0$;
}
else {
$I18N_0$ = $r3$.ɵɵi18nLocalize("{$icu}{$icu_1}", {
$I18N_0$ = $r3$.ɵɵi18nLocalize(" {$icu} {$icu_1} ", {
"icu": $I18N_1$,
"icu_1": $I18N_2$
});
@ -2930,7 +2932,7 @@ describe('i18n support in the view compiler', () => {
});
var $I18N_0$;
if (ngI18nClosureMode) {
const $MSG_APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startTagDiv}{$icu}{$closeTagDiv}{$startTagDiv_1}{$icu}{$closeTagDiv}", {
const $MSG_APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$startTagDiv} {$icu} {$closeTagDiv}{$startTagDiv_1} {$icu} {$closeTagDiv}", {
"startTagDiv": "\uFFFD#2\uFFFD",
"closeTagDiv": "[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]",
"startTagDiv_1": "\uFFFD*3:1\uFFFD\uFFFD#1:1\uFFFD",
@ -2939,7 +2941,7 @@ describe('i18n support in the view compiler', () => {
$I18N_0$ = $MSG_APP_SPEC_TS_0$;
}
else {
$I18N_0$ = $r3$.ɵɵi18nLocalize("{$icu}{$startTagDiv}{$icu}{$closeTagDiv}{$startTagDiv_1}{$icu}{$closeTagDiv}", {
$I18N_0$ = $r3$.ɵɵi18nLocalize(" {$icu} {$startTagDiv} {$icu} {$closeTagDiv}{$startTagDiv_1} {$icu} {$closeTagDiv}", {
"startTagDiv": "\uFFFD#2\uFFFD",
"closeTagDiv": "[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]",
"startTagDiv_1": "\uFFFD*3:1\uFFFD\uFFFD#1:1\uFFFD",
@ -3000,19 +3002,26 @@ describe('i18n support in the view compiler', () => {
`;
const output = String.raw `
var $I18N_0$;
var $I18N_1$;
if (ngI18nClosureMode) {
const $MSG_EXTERNAL_343563413083115114$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT_1, select, male {male of age: {VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other}}");
$I18N_0$ = $MSG_EXTERNAL_343563413083115114$$APP_SPEC_TS_0$;
$I18N_1$ = $MSG_EXTERNAL_343563413083115114$$APP_SPEC_TS_0$;
}
else {
$I18N_0$ = $r3$.ɵɵi18nLocalize("{VAR_SELECT_1, select, male {male of age: {VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other}}");
$I18N_1$ = $r3$.ɵɵi18nLocalize("{VAR_SELECT_1, select, male {male of age: {VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other}}");
}
$I18N_0$ = $r3$.ɵɵi18nPostprocess($I18N_0$, {
$I18N_1$ = $r3$.ɵɵi18nPostprocess($I18N_1$, {
"VAR_SELECT": "\uFFFD0\uFFFD",
"VAR_SELECT_1": "\uFFFD1\uFFFD"
});
var $I18N_0$;
if (ngI18nClosureMode) {
const $MSG_EXTERNAL_3052001905251380936$$APP_SPEC_TS_3$ = goog.getMsg(" {$icu} ", { "icu": $I18N_1$ });
$I18N_0$ = $MSG_EXTERNAL_3052001905251380936$$APP_SPEC_TS_3$;
}
else {
$I18N_0$ = i0.ɵɵi18nLocalize(" {$icu} ", { "icu": $I18N_1$ });
} …
consts: 2,
vars: 2,
template: function MyComponent_Template(rf, ctx) {
@ -3117,7 +3126,7 @@ describe('i18n support in the view compiler', () => {
});
var $I18N_0$;
if (ngI18nClosureMode) {
const $MSG_EXTERNAL_1194472282609532229$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", {
const $MSG_EXTERNAL_1194472282609532229$$APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}", {
"startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD",
"closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD",
"icu": $I18N_1$,
@ -3126,7 +3135,7 @@ describe('i18n support in the view compiler', () => {
$I18N_0$ = $MSG_EXTERNAL_1194472282609532229$$APP_SPEC_TS_0$;
}
else {
$I18N_0$ = $r3$.ɵɵi18nLocalize("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", {
$I18N_0$ = $r3$.ɵɵi18nLocalize(" {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}", {
"startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD",
"closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD",
"icu": $I18N_1$,
@ -3207,7 +3216,7 @@ describe('i18n support in the view compiler', () => {
});
var $I18N_0$;
if (ngI18nClosureMode) {
const $MSG_EXTERNAL_7186042105600518133$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", {
const $MSG_EXTERNAL_7186042105600518133$$APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}", {
"startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD",
"closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD",
"icu": $I18N_1$,
@ -3216,7 +3225,7 @@ describe('i18n support in the view compiler', () => {
$I18N_0$ = $MSG_EXTERNAL_7186042105600518133$$APP_SPEC_TS_0$;
}
else {
$I18N_0$ = $r3$.ɵɵi18nLocalize("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", {
$I18N_0$ = $r3$.ɵɵi18nLocalize(" {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}", {
"startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD",
"closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD",
"icu": $I18N_1$,

View File

@ -7,12 +7,10 @@
*/
import {StaticSymbol} from '@angular/compiler';
import {CompilerHost} from '@angular/compiler-cli';
import {ReflectorHost} from '@angular/language-service/src/reflector_host';
import * as ts from 'typescript';
import {getExpressionDiagnostics, getTemplateExpressionDiagnostics} from '../../src/diagnostics/expression_diagnostics';
import {CompilerOptions} from '../../src/transformers/api';
import {getTemplateExpressionDiagnostics} from '../../src/diagnostics/expression_diagnostics';
import {Directory} from '../mocks';
import {DiagnosticContext, MockLanguageServiceHost, getDiagnosticTemplateInfo} from './mocks';
@ -31,10 +29,7 @@ describe('expression diagnostics', () => {
service = ts.createLanguageService(host, registry);
const program = service.getProgram() !;
const checker = program.getTypeChecker();
const options: CompilerOptions = Object.create(host.getCompilationSettings());
options.genDir = '/dist';
options.basePath = '/src';
const symbolResolverHost = new ReflectorHost(() => program !, host, options);
const symbolResolverHost = new ReflectorHost(() => program !, host);
context = new DiagnosticContext(service, program !, checker, symbolResolverHost);
type = context.getStaticSymbol('app/app.component.ts', 'AppComponent');
});

View File

@ -6,15 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
import {StaticSymbol} from '@angular/compiler';
import {CompilerHost} from '@angular/compiler-cli';
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, arrayToMockDir, arrayToMockMap, isSource, settings, setup, toMockFileArray} from '@angular/compiler/test/aot/test_util';
import {ReflectorHost} from '@angular/language-service/src/reflector_host';
import * as ts from 'typescript';
import {BuiltinType, Symbol, SymbolQuery, SymbolTable} from '../../src/diagnostics/symbols';
import {getSymbolQuery, toSymbolTableFactory} from '../../src/diagnostics/typescript_symbols';
import {CompilerOptions} from '../../src/transformers/api';
import {Directory} from '../mocks';
import {DiagnosticContext, MockLanguageServiceHost} from './mocks';
@ -42,10 +38,7 @@ describe('symbol query', () => {
program = service.getProgram() !;
checker = program.getTypeChecker();
sourceFile = program.getSourceFile('/quickstart/app/app.component.ts') !;
const options: CompilerOptions = Object.create(host.getCompilationSettings());
options.genDir = '/dist';
options.basePath = '/quickstart';
const symbolResolverHost = new ReflectorHost(() => program, host, options);
const symbolResolverHost = new ReflectorHost(() => program, host);
context = new DiagnosticContext(service, program, checker, symbolResolverHost);
query = getSymbolQuery(program, checker, sourceFile, emptyPipes);
});

View File

@ -195,6 +195,25 @@ runInEachFileSystem(() => {
expect(written).toContain('/foo_module.js');
});
it('should rebuild only a Component (but with the correct CompilationScope) if its template has changed',
() => {
setupFooBarProgram(env);
// Make a change to the template of BarComponent.
env.write('bar_component.html', '<div bar>changed</div>');
env.driveMain();
const written = env.getFilesWrittenSinceLastFlush();
expect(written).not.toContain('/bar_directive.js');
expect(written).toContain('/bar_component.js');
expect(written).not.toContain('/bar_module.js');
expect(written).not.toContain('/foo_component.js');
expect(written).not.toContain('/foo_pipe.js');
expect(written).not.toContain('/foo_module.js');
// Ensure that the used directives are included in the component's generated template.
expect(env.getContents('/built/bar_component.js')).toMatch(/directives:\s*\[.+\.BarDir\]/);
});
it('should rebuild everything if a typings file changes', () => {
setupFooBarProgram(env);
@ -280,9 +299,10 @@ runInEachFileSystem(() => {
env.write('bar_component.ts', `
import {Component} from '@angular/core';
@Component({selector: 'bar', template: 'bar'})
@Component({selector: 'bar', templateUrl: './bar_component.html'})
export class BarCmp {}
`);
env.write('bar_component.html', '<div bar></div>');
env.write('bar_directive.ts', `
import {Directive} from '@angular/core';

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