Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
ecac8c032b | |||
ce728dd323 | |||
20dd0ac4f2 | |||
0f4d424da9 | |||
6f2fd6e4b4 | |||
8a68a7c490 | |||
800e31b6d3 | |||
0d784e0b00 | |||
8b0e26f786 | |||
6ff6159e32 | |||
db9704a433 | |||
add3e18e9d | |||
b71cdf99fc | |||
f882ff01f3 | |||
2184ad5436 | |||
83d7819bfc | |||
4cc519eb10 | |||
8bcb0784bc | |||
9f68ff9e5b | |||
689964b20f | |||
677d666a20 | |||
4d7b176329 | |||
6b0e247412 | |||
9e23a69d1b | |||
63bde80ca7 | |||
5ea9a61a87 | |||
5a09ea328f | |||
000c834554 | |||
d24ce21c45 | |||
68ca32fe51 | |||
4fe3f37b3c | |||
e0cf3ad4b3 | |||
5abf068b23 | |||
28d2bf7d7c | |||
51c1911c56 | |||
eaf5b5856d | |||
9a9cc01be1 | |||
6330200004 | |||
645fa40571 | |||
2cccf59415 | |||
2a9b254ca5 | |||
78f968b7dd | |||
2eaf420bdf | |||
07efe2a6aa | |||
773d7b86aa |
@ -29,7 +29,7 @@ var_4_win: &cache_key_win_fallback v5-angular-win-node-12-
|
|||||||
|
|
||||||
# Cache key for the `components-repo-unit-tests` job. **Note** when updating the SHA in the
|
# Cache key for the `components-repo-unit-tests` job. **Note** when updating the SHA in the
|
||||||
# cache keys also update the SHA for the "COMPONENTS_REPO_COMMIT" environment variable.
|
# cache keys also update the SHA for the "COMPONENTS_REPO_COMMIT" environment variable.
|
||||||
var_5: &components_repo_unit_tests_cache_key v5-angular-components-2ec7254f88c4865e0de251f74c27e64c9d00d40a
|
var_5: &components_repo_unit_tests_cache_key v5-angular-components-598db096e668aa7e9debd56eedfd127b7a55e371
|
||||||
var_6: &components_repo_unit_tests_cache_key_fallback v5-angular-components-
|
var_6: &components_repo_unit_tests_cache_key_fallback v5-angular-components-
|
||||||
|
|
||||||
# Workspace initially persisted by the `setup` job, and then enhanced by `build-npm-packages` and
|
# Workspace initially persisted by the `setup` job, and then enhanced by `build-npm-packages` and
|
||||||
@ -465,12 +465,14 @@ jobs:
|
|||||||
- when:
|
- when:
|
||||||
condition: << parameters.ivy >>
|
condition: << parameters.ivy >>
|
||||||
steps:
|
steps:
|
||||||
# Rename the Ivy packages dist folder to "dist/packages-dist" as the AIO
|
# Rename the "dist/*-dist-ivy-aot" packages directories (persisted to the workspace by
|
||||||
# package installer picks up the locally built packages from that location.
|
# the `build-ivy-npm-packages` job) to "dist/*-dist" as the AIO package installer
|
||||||
|
# picks up the locally built packages from that location.
|
||||||
# *Note*: We could also adjust the packages installer, but given we won't have
|
# *Note*: We could also adjust the packages installer, but given we won't have
|
||||||
# two different folders of Angular distributions in the future, we should keep
|
# two different folders of Angular distributions in the future, we should keep
|
||||||
# the packages installer unchanged.
|
# the packages installer unchanged.
|
||||||
- run: mv dist/packages-dist-ivy-aot dist/packages-dist
|
- run: mv dist/packages-dist-ivy-aot dist/packages-dist
|
||||||
|
- run: mv dist/zone.js-dist-ivy-aot dist/zone.js-dist
|
||||||
# Run examples tests. The "CIRCLE_NODE_INDEX" will be set if "parallelism" is enabled.
|
# Run examples tests. The "CIRCLE_NODE_INDEX" will be set if "parallelism" is enabled.
|
||||||
# Since the parallelism is set to "5", there will be five parallel CircleCI containers.
|
# Since the parallelism is set to "5", there will be five parallel CircleCI containers.
|
||||||
# with either "0", "1", etc as node index. This can be passed to the "--shard" argument.
|
# with either "0", "1", etc as node index. This can be passed to the "--shard" argument.
|
||||||
@ -552,6 +554,7 @@ jobs:
|
|||||||
root: *workspace_location
|
root: *workspace_location
|
||||||
paths:
|
paths:
|
||||||
- ng/dist/packages-dist-ivy-aot
|
- ng/dist/packages-dist-ivy-aot
|
||||||
|
- ng/dist/zone.js-dist-ivy-aot
|
||||||
|
|
||||||
# We run a subset of the integration tests outside of Bazel that track
|
# We run a subset of the integration tests outside of Bazel that track
|
||||||
# payload size.
|
# payload size.
|
||||||
|
@ -68,7 +68,7 @@ setPublicVar COMPONENTS_REPO_TMP_DIR "/tmp/angular-components-repo"
|
|||||||
setPublicVar COMPONENTS_REPO_URL "https://github.com/angular/components.git"
|
setPublicVar COMPONENTS_REPO_URL "https://github.com/angular/components.git"
|
||||||
setPublicVar COMPONENTS_REPO_BRANCH "master"
|
setPublicVar COMPONENTS_REPO_BRANCH "master"
|
||||||
# **NOTE**: When updating the commit SHA, also update the cache key in the CircleCI `config.yml`.
|
# **NOTE**: When updating the commit SHA, also update the cache key in the CircleCI `config.yml`.
|
||||||
setPublicVar COMPONENTS_REPO_COMMIT "2ec7254f88c4865e0de251f74c27e64c9d00d40a"
|
setPublicVar COMPONENTS_REPO_COMMIT "598db096e668aa7e9debd56eedfd127b7a55e371"
|
||||||
|
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
|
@ -546,7 +546,6 @@ groups:
|
|||||||
conditions:
|
conditions:
|
||||||
- >
|
- >
|
||||||
contains_any_globs(files, [
|
contains_any_globs(files, [
|
||||||
'modules/benchmarks_external/**',
|
|
||||||
'modules/benchmarks/**'
|
'modules/benchmarks/**'
|
||||||
])
|
])
|
||||||
reviewers:
|
reviewers:
|
||||||
@ -928,6 +927,7 @@ groups:
|
|||||||
'.github/**',
|
'.github/**',
|
||||||
'.vscode/**',
|
'.vscode/**',
|
||||||
'.yarn/**',
|
'.yarn/**',
|
||||||
|
'dev-infra/**',
|
||||||
'docs/BAZEL.md',
|
'docs/BAZEL.md',
|
||||||
'docs/CARETAKER.md',
|
'docs/CARETAKER.md',
|
||||||
'docs/COMMITTER.md',
|
'docs/COMMITTER.md',
|
||||||
@ -952,6 +952,7 @@ groups:
|
|||||||
'tools/browsers/**',
|
'tools/browsers/**',
|
||||||
'tools/build/**',
|
'tools/build/**',
|
||||||
'tools/circular_dependency_test/**',
|
'tools/circular_dependency_test/**',
|
||||||
|
'tools/contributing-stats/**',
|
||||||
'tools/gulp-tasks/**',
|
'tools/gulp-tasks/**',
|
||||||
'tools/ng_rollup_bundle/**',
|
'tools/ng_rollup_bundle/**',
|
||||||
'tools/ngcontainer/**',
|
'tools/ngcontainer/**',
|
||||||
|
28
CHANGELOG.md
28
CHANGELOG.md
@ -1,3 +1,31 @@
|
|||||||
|
<a name="9.0.6"></a>
|
||||||
|
## [9.0.6](https://github.com/angular/angular/compare/9.0.5...9.0.6) (2020-03-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **bazel:** do not use manifest paths for generated imports within compilation unit ([#35841](https://github.com/angular/angular/issues/35841)) ([5ea9a61](https://github.com/angular/angular/commit/5ea9a61))
|
||||||
|
* **compiler:** process `imports` first and `declarations` second while calculating scopes ([#35850](https://github.com/angular/angular/issues/35850)) ([6f2fd6e](https://github.com/angular/angular/commit/6f2fd6e)), closes [#35502](https://github.com/angular/angular/issues/35502)
|
||||||
|
* **core:** add `noSideEffects()` to `make*Decorator()` functions ([#35769](https://github.com/angular/angular/issues/35769)) ([#35846](https://github.com/angular/angular/issues/35846)) ([4fe3f37](https://github.com/angular/angular/commit/4fe3f37))
|
||||||
|
* **core:** add `noSideEffects()` to `ɵɵdefineComponent()` ([#35769](https://github.com/angular/angular/issues/35769)) ([#35846](https://github.com/angular/angular/issues/35846)) ([68ca32f](https://github.com/angular/angular/commit/68ca32f))
|
||||||
|
* **core:** remove side effects from `ɵɵgetInheritedFactory()` ([#35769](https://github.com/angular/angular/issues/35769)) ([#35846](https://github.com/angular/angular/issues/35846)) ([000c834](https://github.com/angular/angular/commit/000c834))
|
||||||
|
* **core:** remove side effects from `ɵɵNgOnChangesFeature()` ([#35769](https://github.com/angular/angular/issues/35769)) ([#35846](https://github.com/angular/angular/issues/35846)) ([d24ce21](https://github.com/angular/angular/commit/d24ce21))
|
||||||
|
* **core:** undecorated-classes-with-di migration should handle libraries generated with CLI versions past v6.2.0 ([#35824](https://github.com/angular/angular/issues/35824)) ([eaf5b58](https://github.com/angular/angular/commit/eaf5b58)), closes [#34985](https://github.com/angular/angular/issues/34985)
|
||||||
|
* **language-service:** resolve the variable from the template context first ([#35982](https://github.com/angular/angular/issues/35982)) ([f882ff0](https://github.com/angular/angular/commit/f882ff0))
|
||||||
|
* **localize:** improve matching and parsing of XLIFF 1.2 translation files ([#35793](https://github.com/angular/angular/issues/35793)) ([677d666](https://github.com/angular/angular/commit/677d666))
|
||||||
|
* **localize:** improve matching and parsing of XLIFF 2.0 translation files ([#35793](https://github.com/angular/angular/issues/35793)) ([689964b](https://github.com/angular/angular/commit/689964b))
|
||||||
|
* **localize:** improve matching and parsing of XTB translation files ([#35793](https://github.com/angular/angular/issues/35793)) ([9f68ff9](https://github.com/angular/angular/commit/9f68ff9))
|
||||||
|
* **localize:** merge translation from all XLIFF `<file>` elements ([#35936](https://github.com/angular/angular/issues/35936)) ([83d7819](https://github.com/angular/angular/commit/83d7819)), closes [#35839](https://github.com/angular/angular/issues/35839)
|
||||||
|
* **platform-browser:** add missing peerDependency on `[@angular](https://github.com/angular)/animations` ([#35949](https://github.com/angular/angular/issues/35949)) ([db9704a](https://github.com/angular/angular/commit/db9704a)), closes [#35888](https://github.com/angular/angular/issues/35888)
|
||||||
|
* **router:** state data missing in routerLink ([#33203](https://github.com/angular/angular/issues/33203)) ([773d7b8](https://github.com/angular/angular/commit/773d7b8))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **ngcc:** reduce directory traversing ([#35756](https://github.com/angular/angular/issues/35756)) ([2eaf420](https://github.com/angular/angular/commit/2eaf420)), closes [#35717](https://github.com/angular/angular/issues/35717)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="9.0.5"></a>
|
<a name="9.0.5"></a>
|
||||||
## [9.0.5](https://github.com/angular/angular/compare/9.0.4...9.0.5) (2020-03-04)
|
## [9.0.5](https://github.com/angular/angular/compare/9.0.4...9.0.5) (2020-03-04)
|
||||||
|
|
||||||
|
@ -158,11 +158,11 @@ export class Provider6bComponent {
|
|||||||
|
|
||||||
// #docregion silent-logger
|
// #docregion silent-logger
|
||||||
// An object in the shape of the logger service
|
// An object in the shape of the logger service
|
||||||
export function SilentLoggerFn() {}
|
function silentLoggerFn() {}
|
||||||
|
|
||||||
const silentLogger = {
|
export const SilentLogger = {
|
||||||
logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'],
|
logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'],
|
||||||
log: SilentLoggerFn
|
log: silentLoggerFn
|
||||||
};
|
};
|
||||||
// #enddocregion silent-logger
|
// #enddocregion silent-logger
|
||||||
|
|
||||||
@ -171,7 +171,7 @@ const silentLogger = {
|
|||||||
template: template,
|
template: template,
|
||||||
providers:
|
providers:
|
||||||
// #docregion providers-7
|
// #docregion providers-7
|
||||||
[{ provide: Logger, useValue: silentLogger }]
|
[{ provide: Logger, useValue: SilentLogger }]
|
||||||
// #enddocregion providers-7
|
// #enddocregion providers-7
|
||||||
})
|
})
|
||||||
export class Provider7Component {
|
export class Provider7Component {
|
||||||
|
@ -185,7 +185,7 @@ searchHeroes(term: string): Observable {
|
|||||||
|
|
||||||
let heroesURL = `${this.heroesURL}?${term}`;
|
let heroesURL = `${this.heroesURL}?${term}`;
|
||||||
return this.http.jsonp(heroesUrl, 'callback').pipe(
|
return this.http.jsonp(heroesUrl, 'callback').pipe(
|
||||||
catchError(this.handleError('searchHeroes', []) // then handle the error
|
catchError(this.handleError('searchHeroes', [])) // then handle the error
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
@ -218,7 +218,7 @@ zone.run(() => {
|
|||||||
This new context, `zoneThis`, can be retrieved from the `setTimeout()` callback function, and this context is the same when the `setTimeout()` is scheduled.
|
This new context, `zoneThis`, can be retrieved from the `setTimeout()` callback function, and this context is the same when the `setTimeout()` is scheduled.
|
||||||
To get the context, you can call [`Zone.current`](https://github.com/angular/angular/blob/master/packages/zone.js/lib/zone.ts).
|
To get the context, you can call [`Zone.current`](https://github.com/angular/angular/blob/master/packages/zone.js/lib/zone.ts).
|
||||||
|
|
||||||
### Zones and async lifecycle hooks
|
## Zones and async lifecycle hooks
|
||||||
|
|
||||||
Zone.js can create contexts that persist across asynchronous operations as well as provide lifecycle hooks for asynchronous operations.
|
Zone.js can create contexts that persist across asynchronous operations as well as provide lifecycle hooks for asynchronous operations.
|
||||||
|
|
||||||
@ -303,7 +303,7 @@ This service creates a zone named `angular` to automatically trigger change dete
|
|||||||
|
|
||||||
### NgZone `run()`/`runOutsideOfAngular()`
|
### NgZone `run()`/`runOutsideOfAngular()`
|
||||||
|
|
||||||
`Zone` handles most asynchronous APIs such as `setTimeout()`, `Promise.then(),and `addEventListener()`.
|
`Zone` handles most asynchronous APIs such as `setTimeout()`, `Promise.then()`, and `addEventListener()`.
|
||||||
For the full list, see the [Zone Module document](https://github.com/angular/angular/blob/master/packages/zone.js/MODULE.md).
|
For the full list, see the [Zone Module document](https://github.com/angular/angular/blob/master/packages/zone.js/MODULE.md).
|
||||||
Therefore in those asynchronous APIs, you don't need to trigger change detection manually.
|
Therefore in those asynchronous APIs, you don't need to trigger change detection manually.
|
||||||
|
|
||||||
@ -315,12 +315,12 @@ This function, and all asynchronous operations in that function, trigger change
|
|||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
constructor(private ngZone: NgZone) {}
|
constructor(private ngZone: NgZone) {}
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
// new async API is not handled by Zone, so you need to
|
// New async API is not handled by Zone, so you need to
|
||||||
// use ngZone.run to make the asynchronous operation in angular zone
|
// use ngZone.run() to make the asynchronous operation in the angular zone
|
||||||
// and trigger change detection automatically
|
// and trigger change detection automatically.
|
||||||
this.ngZone.run(() => {
|
this.ngZone.run(() => {
|
||||||
someNewAsyncAPI(() => {
|
someNewAsyncAPI(() => {
|
||||||
// update data of component
|
// update the data of the component
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -335,19 +335,20 @@ In that situation, you can use another NgZone method: [runOutsideAngular()](api/
|
|||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
constructor(private ngZone: NgZone) {}
|
constructor(private ngZone: NgZone) {}
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
// you know no data will be updated
|
// You know no data will be updated,
|
||||||
// you don't want to do change detection in this
|
// so you don't want to trigger change detection in this
|
||||||
// specified operation, you can call runOutsideAngular
|
// specified operation. Instead, call ngZone.runOutsideAngular()
|
||||||
this.ngZone.runOutsideAngular(() => {
|
this.ngZone.runOutsideAngular(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// do something will not update component data
|
// update component data
|
||||||
|
// but don't trigger change detection.
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Seting up Zone.js
|
### Setting up Zone.js
|
||||||
|
|
||||||
To make Zone.js available in Angular, you need to import the zone.js package.
|
To make Zone.js available in Angular, you need to import the zone.js package.
|
||||||
If you are using the Angular CLI, this step is done automatically, and you will see the following line in the `src/polyfills.ts`:
|
If you are using the Angular CLI, this step is done automatically, and you will see the following line in the `src/polyfills.ts`:
|
||||||
|
@ -13,12 +13,6 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<!-- ngIndia 2020 -->
|
|
||||||
<tr>
|
|
||||||
<th><a href="https://www.ng-ind.com/" title="ngIndia">ngIndia</a></th>
|
|
||||||
<td>Delhi, India</td>
|
|
||||||
<td>Feb 29, 2020</td>
|
|
||||||
</tr>
|
|
||||||
<!-- ng-conf 2020 -->
|
<!-- ng-conf 2020 -->
|
||||||
<tr>
|
<tr>
|
||||||
<th><a href="https://ng-conf.org/" title="ng-conf">ng-conf</a></th>
|
<th><a href="https://ng-conf.org/" title="ng-conf">ng-conf</a></th>
|
||||||
@ -38,6 +32,12 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<!-- ngIndia 2020 -->
|
||||||
|
<tr>
|
||||||
|
<th><a href="https://www.ng-ind.com/" title="ngIndia">ngIndia</a></th>
|
||||||
|
<td>Delhi, India</td>
|
||||||
|
<td>Feb 29, 2020</td>
|
||||||
|
</tr>
|
||||||
<!-- ReactiveConf 2019 -->
|
<!-- ReactiveConf 2019 -->
|
||||||
<tr>
|
<tr>
|
||||||
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
|
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
|
||||||
|
@ -106,7 +106,7 @@ aio-search-results {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.not-found {
|
.no-results {
|
||||||
color: $darkgray;
|
color: $darkgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,11 +14,12 @@ const LOCAL_MARKER_PATH = 'node_modules/_local_.json';
|
|||||||
|
|
||||||
const ANGULAR_ROOT_DIR = path.resolve(__dirname, '../../..');
|
const ANGULAR_ROOT_DIR = path.resolve(__dirname, '../../..');
|
||||||
const ANGULAR_DIST_PACKAGES_DIR = path.join(ANGULAR_ROOT_DIR, 'dist/packages-dist');
|
const ANGULAR_DIST_PACKAGES_DIR = path.join(ANGULAR_ROOT_DIR, 'dist/packages-dist');
|
||||||
|
const ZONEJS_DIST_PACKAGES_DIR = path.join(ANGULAR_ROOT_DIR, 'dist/zone.js-dist');
|
||||||
const DIST_PACKAGES_BUILD_SCRIPT = path.join(ANGULAR_ROOT_DIR, 'scripts/build/build-packages-dist.js');
|
const DIST_PACKAGES_BUILD_SCRIPT = path.join(ANGULAR_ROOT_DIR, 'scripts/build/build-packages-dist.js');
|
||||||
const DIST_PACKAGES_BUILD_CMD = `"${process.execPath}" "${DIST_PACKAGES_BUILD_SCRIPT}"`;
|
const DIST_PACKAGES_BUILD_CMD = `"${process.execPath}" "${DIST_PACKAGES_BUILD_SCRIPT}"`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A tool that can install Angular dependencies for a project from NPM or from the
|
* A tool that can install Angular/Zone.js dependencies for a project from NPM or from the
|
||||||
* locally built distributables.
|
* locally built distributables.
|
||||||
*
|
*
|
||||||
* This tool is used to change dependencies of the `aio` application and the example
|
* This tool is used to change dependencies of the `aio` application and the example
|
||||||
@ -33,7 +34,7 @@ class NgPackagesInstaller {
|
|||||||
* @param {object} options - a hash of options for the install:
|
* @param {object} options - a hash of options for the install:
|
||||||
* * `debug` (`boolean`) - whether to display debug messages.
|
* * `debug` (`boolean`) - whether to display debug messages.
|
||||||
* * `force` (`boolean`) - whether to force a local installation even if there is a local marker file.
|
* * `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.
|
* * `buildPackages` (`boolean`) - whether to build the local Angular/Zone.js packages before using them.
|
||||||
* (NOTE: Building the packages is currently not supported on Windows, so a message is printed instead.)
|
* (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.
|
* * `ignorePackages` (`string[]`) - a collection of names of packages that should not be copied over.
|
||||||
*/
|
*/
|
||||||
@ -52,7 +53,7 @@ class NgPackagesInstaller {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the dependencies have been overridden with locally built
|
* Check whether the dependencies have been overridden with locally built
|
||||||
* Angular packages. This is done by checking for the `_local_.json` marker file.
|
* Angular/Zone.js packages. This is done by checking for the `_local_.json` marker file.
|
||||||
* This will emit a warning to the console if the dependencies have been overridden.
|
* This will emit a warning to the console if the dependencies have been overridden.
|
||||||
*/
|
*/
|
||||||
checkDependencies() {
|
checkDependencies() {
|
||||||
@ -62,8 +63,8 @@ class NgPackagesInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install locally built Angular dependencies, overriding the dependencies in the package.json
|
* Install locally built Angular/Zone.js dependencies, overriding the dependencies in the `package.json`.
|
||||||
* This will also write a "marker" file (`_local_.json`), which contains the overridden package.json
|
* This will also write a "marker" file (`_local_.json`), which contains the overridden `package.json`
|
||||||
* contents and acts as an indicator that dependencies have been overridden.
|
* contents and acts as an indicator that dependencies have been overridden.
|
||||||
*/
|
*/
|
||||||
installLocalDependencies() {
|
installLocalDependencies() {
|
||||||
@ -86,13 +87,13 @@ class NgPackagesInstaller {
|
|||||||
// Prevent accidental publishing of the package, if something goes wrong.
|
// Prevent accidental publishing of the package, if something goes wrong.
|
||||||
tmpConfig.private = true;
|
tmpConfig.private = true;
|
||||||
|
|
||||||
// Overwrite project dependencies/devDependencies to Angular packages with local files.
|
// Overwrite project dependencies/devDependencies to Angular/Zone.js packages with local files.
|
||||||
['dependencies', 'devDependencies'].forEach(prop => {
|
['dependencies', 'devDependencies'].forEach(prop => {
|
||||||
const deps = tmpConfig[prop] || {};
|
const deps = tmpConfig[prop] || {};
|
||||||
Object.keys(deps).forEach(key2 => {
|
Object.keys(deps).forEach(key2 => {
|
||||||
const pkg2 = packages[key2];
|
const pkg2 = packages[key2];
|
||||||
if (pkg2) {
|
if (pkg2) {
|
||||||
// point the core Angular packages at the distributable folder
|
// point the local packages at the distributable folder
|
||||||
deps[key2] = `file:${pkg2.packageDir}`;
|
deps[key2] = `file:${pkg2.packageDir}`;
|
||||||
this._log(`Overriding dependency of local ${key} with local package: ${key2}: ${deps[key2]}`);
|
this._log(`Overriding dependency of local ${key} with local package: ${key2}: ${deps[key2]}`);
|
||||||
}
|
}
|
||||||
@ -125,8 +126,8 @@ class NgPackagesInstaller {
|
|||||||
fs.writeFileSync(pathToPackageConfig, packageConfigFile);
|
fs.writeFileSync(pathToPackageConfig, packageConfigFile);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
// Restore local Angular packages dependencies to other Angular packages.
|
// Restore local Angular/Zone.js packages dependencies to other Angular packages.
|
||||||
this._log(`Restoring original ${PACKAGE_JSON} for local Angular packages.`);
|
this._log(`Restoring original ${PACKAGE_JSON} for local packages.`);
|
||||||
Object.keys(packages).forEach(key => {
|
Object.keys(packages).forEach(key => {
|
||||||
const pkg = packages[key];
|
const pkg = packages[key];
|
||||||
fs.writeFileSync(pkg.packageJsonPath, JSON.stringify(pkg.config, null, 2));
|
fs.writeFileSync(pkg.packageJsonPath, JSON.stringify(pkg.config, null, 2));
|
||||||
@ -167,7 +168,7 @@ class NgPackagesInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the local Angular packages.
|
* Build the local Angular/Zone.js packages.
|
||||||
*
|
*
|
||||||
* NOTE:
|
* NOTE:
|
||||||
* Building the packages is currently not supported on Windows, so a message is printed instead, prompting the user to
|
* Building the packages is currently not supported on Windows, so a message is printed instead, prompting the user to
|
||||||
@ -177,14 +178,14 @@ class NgPackagesInstaller {
|
|||||||
const canBuild = process.platform !== 'win32';
|
const canBuild = process.platform !== 'win32';
|
||||||
|
|
||||||
if (canBuild) {
|
if (canBuild) {
|
||||||
this._log(`Building the Angular packages with: ${DIST_PACKAGES_BUILD_SCRIPT}`);
|
this._log(`Building the local packages with: ${DIST_PACKAGES_BUILD_SCRIPT}`);
|
||||||
shelljs.exec(DIST_PACKAGES_BUILD_CMD);
|
shelljs.exec(DIST_PACKAGES_BUILD_CMD);
|
||||||
} else {
|
} else {
|
||||||
this._warn([
|
this._warn([
|
||||||
'Automatically building the local Angular packages is currently not supported on Windows.',
|
'Automatically building the local Angular/Zone.js packages is currently not supported on Windows.',
|
||||||
`Please, ensure '${ANGULAR_DIST_PACKAGES_DIR}' exists and is up-to-date (e.g. by running ` +
|
`Please, ensure '${ANGULAR_DIST_PACKAGES_DIR}' and '${ZONEJS_DIST_PACKAGES_DIR}' exist and are up-to-date ` +
|
||||||
`'${DIST_PACKAGES_BUILD_SCRIPT}' in Git Bash for Windows, Windows Subsystem for Linux or a Linux docker ` +
|
`(e.g. by running '${DIST_PACKAGES_BUILD_SCRIPT}' in Git Bash for Windows, Windows Subsystem for Linux or ` +
|
||||||
'container or VM).',
|
'a Linux docker container or VM).',
|
||||||
'',
|
'',
|
||||||
'Proceeding anyway...',
|
'Proceeding anyway...',
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
@ -204,7 +205,7 @@ class NgPackagesInstaller {
|
|||||||
// grab peer dependencies
|
// grab peer dependencies
|
||||||
const sourcePackagePeerDeps = sourcePackage.config.peerDependencies || {};
|
const sourcePackagePeerDeps = sourcePackage.config.peerDependencies || {};
|
||||||
Object.keys(sourcePackagePeerDeps)
|
Object.keys(sourcePackagePeerDeps)
|
||||||
// ignore peerDependencies which are already core Angular packages
|
// ignore peerDependencies which are already core Angular/Zone.js packages
|
||||||
.filter(key => !packages[key])
|
.filter(key => !packages[key])
|
||||||
.forEach(key => peerDependencies[key] = sourcePackagePeerDeps[key]);
|
.forEach(key => peerDependencies[key] = sourcePackagePeerDeps[key]);
|
||||||
}
|
}
|
||||||
@ -214,11 +215,13 @@ class NgPackagesInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A hash of Angular package configs.
|
* A hash of Angular/Zone.js package configs.
|
||||||
* (Detected as directories in '/dist/packages-dist/' that contain a top-level 'package.json' file.)
|
* (Detected as directories in '/dist/packages-dist/' and '/dist/zone.js-dist/' that contain a top-level
|
||||||
|
* 'package.json' file.)
|
||||||
*/
|
*/
|
||||||
_getDistPackages() {
|
_getDistPackages() {
|
||||||
this._log(`Angular distributable directory: ${ANGULAR_DIST_PACKAGES_DIR}.`);
|
this._log(`Angular distributable directory: ${ANGULAR_DIST_PACKAGES_DIR}.`);
|
||||||
|
this._log(`Zone.js distributable directory: ${ZONEJS_DIST_PACKAGES_DIR}.`);
|
||||||
|
|
||||||
if (this.buildPackages) {
|
if (this.buildPackages) {
|
||||||
this._buildDistPackages();
|
this._buildDistPackages();
|
||||||
@ -254,7 +257,10 @@ class NgPackagesInstaller {
|
|||||||
return packages;
|
return packages;
|
||||||
};
|
};
|
||||||
|
|
||||||
const packageConfigs = collectPackages(ANGULAR_DIST_PACKAGES_DIR);
|
const packageConfigs = {
|
||||||
|
...collectPackages(ANGULAR_DIST_PACKAGES_DIR),
|
||||||
|
...collectPackages(ZONEJS_DIST_PACKAGES_DIR),
|
||||||
|
};
|
||||||
|
|
||||||
this._log('Found the following Angular distributables:', Object.keys(packageConfigs).map(key => `\n - ${key}`));
|
this._log('Found the following Angular distributables:', Object.keys(packageConfigs).map(key => `\n - ${key}`));
|
||||||
return packageConfigs;
|
return packageConfigs;
|
||||||
@ -343,7 +349,7 @@ class NgPackagesInstaller {
|
|||||||
|
|
||||||
// Log a warning.
|
// Log a warning.
|
||||||
this._warn([
|
this._warn([
|
||||||
`The project at "${absoluteProjectDir}" is running against the local Angular build.`,
|
`The project at "${absoluteProjectDir}" is running against the local Angular/Zone.js build.`,
|
||||||
'',
|
'',
|
||||||
'To restore the npm packages run:',
|
'To restore the npm packages run:',
|
||||||
'',
|
'',
|
||||||
@ -396,10 +402,10 @@ function main() {
|
|||||||
|
|
||||||
.option('debug', { describe: 'Print additional debug information.', default: false })
|
.option('debug', { describe: 'Print additional debug information.', default: false })
|
||||||
.option('force', { describe: 'Force the command to execute even if not needed.', 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('build-packages', { describe: 'Build the local Angular/Zone.js 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 })
|
.option('ignore-packages', { describe: 'List of Angular/Zone.js 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 => {
|
.command('overwrite <projectDir> [--force] [--debug] [--ignore-packages package1 package2]', 'Install dependencies from the locally built Angular/Zone.js distributables.', () => {}, argv => {
|
||||||
createInstaller(argv).installLocalDependencies();
|
createInstaller(argv).installLocalDependencies();
|
||||||
})
|
})
|
||||||
.command('restore <projectDir> [--debug]', 'Install dependencies from the npm registry.', () => {}, argv => {
|
.command('restore <projectDir> [--debug]', 'Install dependencies from the npm registry.', () => {}, argv => {
|
||||||
|
@ -15,6 +15,7 @@ describe('NgPackagesInstaller', () => {
|
|||||||
const yarnLockPath = path.resolve(absoluteProjectDir, 'yarn.lock');
|
const yarnLockPath = path.resolve(absoluteProjectDir, 'yarn.lock');
|
||||||
const ngRootDir = path.resolve(__dirname, '../../..');
|
const ngRootDir = path.resolve(__dirname, '../../..');
|
||||||
const packagesDir = path.join(ngRootDir, 'dist/packages-dist');
|
const packagesDir = path.join(ngRootDir, 'dist/packages-dist');
|
||||||
|
const zoneJsDir = path.join(ngRootDir, 'dist/zone.js-dist');
|
||||||
const toolsDir = path.join(ngRootDir, 'dist/tools/@angular');
|
const toolsDir = path.join(ngRootDir, 'dist/tools/@angular');
|
||||||
let installer;
|
let installer;
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ describe('NgPackagesInstaller', () => {
|
|||||||
|
|
||||||
describe('installLocalDependencies()', () => {
|
describe('installLocalDependencies()', () => {
|
||||||
const copyJsonObj = obj => JSON.parse(JSON.stringify(obj));
|
const copyJsonObj = obj => JSON.parse(JSON.stringify(obj));
|
||||||
let dummyNgPackages, dummyPackage, dummyPackageJson, expectedModifiedPackage, expectedModifiedPackageJson;
|
let dummyLocalPackages, dummyPackage, dummyPackageJson, expectedModifiedPackage, expectedModifiedPackageJson;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(installer, '_checkLocalMarker');
|
spyOn(installer, '_checkLocalMarker');
|
||||||
@ -60,17 +61,18 @@ describe('NgPackagesInstaller', () => {
|
|||||||
|
|
||||||
spyOn(installer, '_parseLockfile').and.returnValue({
|
spyOn(installer, '_parseLockfile').and.returnValue({
|
||||||
'rxjs@^6.3.0': {version: '6.3.3'},
|
'rxjs@^6.3.0': {version: '6.3.3'},
|
||||||
'zone.js@^0.8.26': {version: '0.8.27'}
|
'rxjs-dev@^6.3.0': {version: '6.4.2'}
|
||||||
});
|
});
|
||||||
|
|
||||||
// These are the packages that are "found" in the dist directory
|
// These are the packages that are "found" in the dist directory
|
||||||
dummyNgPackages = {
|
dummyLocalPackages = {
|
||||||
'@angular/core': {
|
'@angular/core': {
|
||||||
packageDir: `${packagesDir}/core`,
|
packageDir: `${packagesDir}/core`,
|
||||||
packageJsonPath: `${packagesDir}/core/package.json`,
|
packageJsonPath: `${packagesDir}/core/package.json`,
|
||||||
config: {
|
config: {
|
||||||
peerDependencies: {
|
peerDependencies: {
|
||||||
'rxjs': '^6.4.0',
|
'rxjs': '^6.4.0',
|
||||||
|
'rxjs-dev': '^6.4.0',
|
||||||
'some-package': '5.0.1',
|
'some-package': '5.0.1',
|
||||||
'zone.js': '~0.8.26'
|
'zone.js': '~0.8.26'
|
||||||
}
|
}
|
||||||
@ -101,32 +103,40 @@ describe('NgPackagesInstaller', () => {
|
|||||||
devDependencies: { '@angular/common': '4.4.4-1ab23cd4' },
|
devDependencies: { '@angular/common': '4.4.4-1ab23cd4' },
|
||||||
peerDependencies: { tsickle: '^1.4.0' }
|
peerDependencies: { tsickle: '^1.4.0' }
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
'zone.js': {
|
||||||
|
packageDir: `${zoneJsDir}/zone.js`,
|
||||||
|
packageJsonPath: `${zoneJsDir}/zone.js/package.json`,
|
||||||
|
config: {
|
||||||
|
devDependencies: { typescript: '^2.4.2' }
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
spyOn(installer, '_getDistPackages').and.callFake(() => copyJsonObj(dummyNgPackages));
|
spyOn(installer, '_getDistPackages').and.callFake(() => copyJsonObj(dummyLocalPackages));
|
||||||
|
|
||||||
// This is the package.json in the "test" folder
|
// This is the package.json in the "test" folder
|
||||||
dummyPackage = {
|
dummyPackage = {
|
||||||
dependencies: {
|
dependencies: {
|
||||||
'@angular/core': '4.4.1',
|
'@angular/core': '4.4.1',
|
||||||
'@angular/common': '4.4.1',
|
'@angular/common': '4.4.1',
|
||||||
rxjs: '^6.3.0'
|
rxjs: '^6.3.0',
|
||||||
|
'zone.js': '^0.8.26'
|
||||||
},
|
},
|
||||||
devDependencies: {
|
devDependencies: {
|
||||||
'@angular/compiler-cli': '4.4.1',
|
'@angular/compiler-cli': '4.4.1',
|
||||||
'zone.js': '^0.8.26'
|
'rxjs-dev': '^6.3.0'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
dummyPackageJson = JSON.stringify(dummyPackage);
|
dummyPackageJson = JSON.stringify(dummyPackage);
|
||||||
fs.readFileSync.and.returnValue(dummyPackageJson);
|
fs.readFileSync.and.returnValue(dummyPackageJson);
|
||||||
|
|
||||||
// This is the package.json that is temporarily written to the "test" folder
|
// This is the package.json that is temporarily written to the "test" folder
|
||||||
// Note that the Angular (dev)dependencies have been modified to use a "file:" path
|
// Note that the Angular/Zone.js (dev)dependencies have been modified to use a "file:" path
|
||||||
// And that the peerDependencies from `dummyNgPackages` have been updated or added as
|
// and that the peerDependencies from `dummyLocalPackages` have been updated or added as
|
||||||
// (dev)dependencies (unless the current version in lockfile satisfies semver).
|
// (dev)dependencies (unless the current version in lockfile satisfies semver).
|
||||||
//
|
//
|
||||||
// For example, `zone.js@0.8.27` (from lockfile) satisfies `zone.js@~0.8.26` (from
|
// For example, `rxjs-dev@6.4.2` (from lockfile) satisfies `rxjs-dev@^6.4.0` (from
|
||||||
// `@angular/core`), thus `zone.js: ^0.8.26` (from original `package.json`) is retained.
|
// `@angular/core`), thus `rxjs-dev: ^6.3.0` (from original `package.json`) is retained.
|
||||||
// In contrast, `rxjs@6.3.3` (from lockfile) does not satisfy `rxjs@^6.4.0 (from
|
// In contrast, `rxjs@6.3.3` (from lockfile) does not satisfy `rxjs@^6.4.0 (from
|
||||||
// `@angular/core`), thus `rxjs: ^6.3.0` (from original `package.json`) is replaced with
|
// `@angular/core`), thus `rxjs: ^6.3.0` (from original `package.json`) is replaced with
|
||||||
// `rxjs: ^6.4.0` (from `@angular/core`).
|
// `rxjs: ^6.4.0` (from `@angular/core`).
|
||||||
@ -134,11 +144,12 @@ describe('NgPackagesInstaller', () => {
|
|||||||
dependencies: {
|
dependencies: {
|
||||||
'@angular/core': `file:${packagesDir}/core`,
|
'@angular/core': `file:${packagesDir}/core`,
|
||||||
'@angular/common': `file:${packagesDir}/common`,
|
'@angular/common': `file:${packagesDir}/common`,
|
||||||
'rxjs': '^6.4.0'
|
'rxjs': '^6.4.0',
|
||||||
|
'zone.js': `file:${zoneJsDir}/zone.js`,
|
||||||
},
|
},
|
||||||
devDependencies: {
|
devDependencies: {
|
||||||
'@angular/compiler-cli': `file:${toolsDir}/compiler-cli`,
|
'@angular/compiler-cli': `file:${toolsDir}/compiler-cli`,
|
||||||
'zone.js': '^0.8.26',
|
'rxjs-dev': '^6.3.0',
|
||||||
'some-package': '5.0.1',
|
'some-package': '5.0.1',
|
||||||
typescript: '^2.4.2'
|
typescript: '^2.4.2'
|
||||||
},
|
},
|
||||||
@ -182,31 +193,56 @@ describe('NgPackagesInstaller', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should temporarily overwrite the package.json files of local Angular packages', () => {
|
it('should temporarily overwrite the package.json files of local Angular packages', () => {
|
||||||
const pkgJsonFor = pkgName => dummyNgPackages[`@angular/${pkgName}`].packageJsonPath;
|
const pkgJsonPathFor = pkgName => dummyLocalPackages[pkgName].packageJsonPath;
|
||||||
const pkgConfigFor = pkgName => copyJsonObj(dummyNgPackages[`@angular/${pkgName}`].config);
|
const pkgConfigFor = pkgName => copyJsonObj(dummyLocalPackages[pkgName].config);
|
||||||
const overwriteConfigFor = (pkgName, newProps) => Object.assign(pkgConfigFor(pkgName), newProps);
|
const overwriteConfigFor = (pkgName, newProps) => Object.assign(pkgConfigFor(pkgName), newProps);
|
||||||
const stringifyConfig = config => JSON.stringify(config, null, 2);
|
const stringifyConfig = config => JSON.stringify(config, null, 2);
|
||||||
|
|
||||||
const allArgs = fs.writeFileSync.calls.allArgs();
|
const allArgs = fs.writeFileSync.calls.allArgs();
|
||||||
const firstFiveArgs = allArgs.slice(0, 5);
|
const firstSixArgs = allArgs.slice(0, 6);
|
||||||
const lastFiveArgs = allArgs.slice(-5);
|
const lastSixArgs = allArgs.slice(-6);
|
||||||
|
|
||||||
expect(firstFiveArgs).toEqual([
|
expect(firstSixArgs).toEqual([
|
||||||
[pkgJsonFor('core'), stringifyConfig(overwriteConfigFor('core', {private: true}))],
|
[
|
||||||
[pkgJsonFor('common'), stringifyConfig(overwriteConfigFor('common', {private: true}))],
|
pkgJsonPathFor('@angular/core'),
|
||||||
[pkgJsonFor('compiler'), stringifyConfig(overwriteConfigFor('compiler', {private: true}))],
|
stringifyConfig(overwriteConfigFor('@angular/core', {private: true})),
|
||||||
[pkgJsonFor('compiler-cli'), stringifyConfig(overwriteConfigFor('compiler-cli', {
|
],
|
||||||
private: true,
|
[
|
||||||
dependencies: { '@angular/tsc-wrapped': `file:${toolsDir}/tsc-wrapped` }
|
pkgJsonPathFor('@angular/common'),
|
||||||
}))],
|
stringifyConfig(overwriteConfigFor('@angular/common', {private: true})),
|
||||||
[pkgJsonFor('tsc-wrapped'), stringifyConfig(overwriteConfigFor('tsc-wrapped', {
|
],
|
||||||
private: true,
|
[
|
||||||
devDependencies: { '@angular/common': `file:${packagesDir}/common` }
|
pkgJsonPathFor('@angular/compiler'),
|
||||||
}))],
|
stringifyConfig(overwriteConfigFor('@angular/compiler', {private: true})),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
pkgJsonPathFor('@angular/compiler-cli'),
|
||||||
|
stringifyConfig(overwriteConfigFor('@angular/compiler-cli', {
|
||||||
|
private: true,
|
||||||
|
dependencies: { '@angular/tsc-wrapped': `file:${toolsDir}/tsc-wrapped` },
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
pkgJsonPathFor('@angular/tsc-wrapped'),
|
||||||
|
stringifyConfig(overwriteConfigFor('@angular/tsc-wrapped', {
|
||||||
|
private: true,
|
||||||
|
devDependencies: { '@angular/common': `file:${packagesDir}/common` },
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
pkgJsonPathFor('zone.js'),
|
||||||
|
stringifyConfig(overwriteConfigFor('zone.js', {private: true})),
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(lastFiveArgs).toEqual(['core', 'common', 'compiler', 'compiler-cli', 'tsc-wrapped']
|
expect(lastSixArgs).toEqual([
|
||||||
.map(pkgName => [pkgJsonFor(pkgName), stringifyConfig(pkgConfigFor(pkgName))]));
|
'@angular/core',
|
||||||
|
'@angular/common',
|
||||||
|
'@angular/compiler',
|
||||||
|
'@angular/compiler-cli',
|
||||||
|
'@angular/tsc-wrapped',
|
||||||
|
'zone.js',
|
||||||
|
].map(pkgName => [pkgJsonPathFor(pkgName), stringifyConfig(pkgConfigFor(pkgName))]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load the package.json', () => {
|
it('should load the package.json', () => {
|
||||||
@ -280,7 +316,7 @@ describe('NgPackagesInstaller', () => {
|
|||||||
|
|
||||||
expect(shelljs.exec).not.toHaveBeenCalled();
|
expect(shelljs.exec).not.toHaveBeenCalled();
|
||||||
expect(warning).toContain(
|
expect(warning).toContain(
|
||||||
'Automatically building the local Angular packages is currently not supported on Windows.');
|
'Automatically building the local Angular/Zone.js packages is currently not supported on Windows.');
|
||||||
expect(warning).toContain('Git Bash for Windows');
|
expect(warning).toContain('Git Bash for Windows');
|
||||||
expect(warning).toContain('Windows Subsystem for Linux');
|
expect(warning).toContain('Windows Subsystem for Linux');
|
||||||
expect(warning).toContain('Linux docker container or VM');
|
expect(warning).toContain('Linux docker container or VM');
|
||||||
@ -309,8 +345,8 @@ describe('NgPackagesInstaller', () => {
|
|||||||
expect(installer._buildDistPackages).not.toHaveBeenCalled();
|
expect(installer._buildDistPackages).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include top level Angular packages', () => {
|
it('should include top level Angular and Zone.js packages', () => {
|
||||||
const ngPackages = installer._getDistPackages();
|
const localPackages = installer._getDistPackages();
|
||||||
const expectedValue = jasmine.objectContaining({
|
const expectedValue = jasmine.objectContaining({
|
||||||
packageDir: jasmine.any(String),
|
packageDir: jasmine.any(String),
|
||||||
packageJsonPath: jasmine.any(String),
|
packageJsonPath: jasmine.any(String),
|
||||||
@ -318,28 +354,30 @@ describe('NgPackagesInstaller', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// For example...
|
// For example...
|
||||||
expect(ngPackages['@angular/common']).toEqual(expectedValue);
|
expect(localPackages['@angular/common']).toEqual(expectedValue);
|
||||||
expect(ngPackages['@angular/core']).toEqual(expectedValue);
|
expect(localPackages['@angular/core']).toEqual(expectedValue);
|
||||||
expect(ngPackages['@angular/router']).toEqual(expectedValue);
|
expect(localPackages['@angular/router']).toEqual(expectedValue);
|
||||||
expect(ngPackages['@angular/upgrade']).toEqual(expectedValue);
|
expect(localPackages['@angular/upgrade']).toEqual(expectedValue);
|
||||||
|
expect(localPackages['zone.js']).toEqual(expectedValue);
|
||||||
|
|
||||||
expect(ngPackages['@angular/upgrade/static']).not.toBeDefined();
|
expect(localPackages['@angular/upgrade/static']).not.toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should store each package\'s directory', () => {
|
it('should store each package\'s directory', () => {
|
||||||
const ngPackages = installer._getDistPackages();
|
const localPackages = installer._getDistPackages();
|
||||||
|
|
||||||
// For example...
|
// For example...
|
||||||
expect(ngPackages['@angular/core'].packageDir).toBe(path.join(packagesDir, 'core'));
|
expect(localPackages['@angular/core'].packageDir).toBe(path.join(packagesDir, 'core'));
|
||||||
expect(ngPackages['@angular/router'].packageDir).toBe(path.join(packagesDir, 'router'));
|
expect(localPackages['@angular/router'].packageDir).toBe(path.join(packagesDir, 'router'));
|
||||||
|
expect(localPackages['zone.js'].packageDir).toBe(path.join(zoneJsDir, 'zone.js'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not include packages that have been ignored', () => {
|
it('should not include packages that have been ignored', () => {
|
||||||
installer = new NgPackagesInstaller(projectDir, { ignorePackages: ['@angular/router'] });
|
installer = new NgPackagesInstaller(projectDir, { ignorePackages: ['@angular/router'] });
|
||||||
const ngPackages = installer._getDistPackages();
|
const localPackages = installer._getDistPackages();
|
||||||
|
|
||||||
expect(ngPackages['@angular/common']).toBeDefined();
|
expect(localPackages['@angular/common']).toBeDefined();
|
||||||
expect(ngPackages['@angular/router']).toBeUndefined();
|
expect(localPackages['@angular/router']).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -480,7 +518,7 @@ describe('NgPackagesInstaller', () => {
|
|||||||
describe('_printWarning()', () => {
|
describe('_printWarning()', () => {
|
||||||
it('should mention the message passed in the warning', () => {
|
it('should mention the message passed in the warning', () => {
|
||||||
installer._printWarning();
|
installer._printWarning();
|
||||||
expect(console.warn.calls.argsFor(0)[0]).toContain('is running against the local Angular build');
|
expect(console.warn.calls.argsFor(0)[0]).toContain('is running against the local Angular/Zone.js build');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should mention the command to restore the Angular packages in any warning', () => {
|
it('should mention the command to restore the Angular packages in any warning', () => {
|
||||||
|
38
dev-infra/BUILD.bazel
Normal file
38
dev-infra/BUILD.bazel
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm")
|
||||||
|
load("@npm_bazel_typescript//:index.bzl", "ts_library")
|
||||||
|
load("@npm_bazel_rollup//:index.bzl", "rollup_bundle")
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "cli",
|
||||||
|
srcs = [
|
||||||
|
"cli.ts",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//dev-infra/pullapprove",
|
||||||
|
"@npm//@types/node",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
rollup_bundle(
|
||||||
|
name = "bundle",
|
||||||
|
config_file = "rollup.config.js",
|
||||||
|
entry_point = ":cli.ts",
|
||||||
|
format = "umd",
|
||||||
|
sourcemap = "hidden",
|
||||||
|
deps = [
|
||||||
|
":cli",
|
||||||
|
"@npm//rollup-plugin-commonjs",
|
||||||
|
"@npm//rollup-plugin-node-resolve",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
pkg_npm(
|
||||||
|
name = "npm_package",
|
||||||
|
srcs = [
|
||||||
|
"package.json",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":bundle",
|
||||||
|
],
|
||||||
|
)
|
20
dev-infra/cli.ts
Normal file
20
dev-infra/cli.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @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 {verify} from './pullapprove/verify';
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
|
||||||
|
// TODO(josephperrott): Set up proper cli flag/command handling
|
||||||
|
switch (args[0]) {
|
||||||
|
case 'pullapprove:verify':
|
||||||
|
verify();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.info('No commands were matched');
|
||||||
|
}
|
10
dev-infra/package.json
Normal file
10
dev-infra/package.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "@angular/dev-infra-private",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "INTERNAL USE ONLY - Angular internal DevInfra tooling/scripts - INTERNAL USE ONLY",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": true,
|
||||||
|
"bin": {
|
||||||
|
"ng-dev": "./bundle.js"
|
||||||
|
}
|
||||||
|
}
|
19
dev-infra/pullapprove/BUILD.bazel
Normal file
19
dev-infra/pullapprove/BUILD.bazel
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
load("@npm_bazel_typescript//:index.bzl", "ts_library")
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "pullapprove",
|
||||||
|
srcs = [
|
||||||
|
"verify.ts",
|
||||||
|
],
|
||||||
|
visibility = ["//dev-infra:__subpackages__"],
|
||||||
|
deps = [
|
||||||
|
"@npm//@types/minimatch",
|
||||||
|
"@npm//@types/node",
|
||||||
|
"@npm//@types/shelljs",
|
||||||
|
"@npm//@types/yaml",
|
||||||
|
"@npm//minimatch",
|
||||||
|
"@npm//shelljs",
|
||||||
|
"@npm//tslib",
|
||||||
|
"@npm//yaml",
|
||||||
|
],
|
||||||
|
)
|
215
dev-infra/pullapprove/verify.ts
Normal file
215
dev-infra/pullapprove/verify.ts
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
/**
|
||||||
|
* @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 {readFileSync} from 'fs';
|
||||||
|
import {IMinimatch, Minimatch} from 'minimatch';
|
||||||
|
import * as path from 'path';
|
||||||
|
import {cd, exec, set} from 'shelljs';
|
||||||
|
import {parse as parseYaml} from 'yaml';
|
||||||
|
|
||||||
|
interface GlobMatcher {
|
||||||
|
group: string;
|
||||||
|
glob: string;
|
||||||
|
matcher: IMinimatch;
|
||||||
|
matchCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verify() {
|
||||||
|
// Exit early on shelljs errors
|
||||||
|
set('-e');
|
||||||
|
|
||||||
|
// Regex Matcher for contains_any_globs conditions
|
||||||
|
const CONTAINS_ANY_GLOBS_REGEX = /^'([^']+)',?$/;
|
||||||
|
// Full path of the angular project directory
|
||||||
|
const ANGULAR_PROJECT_DIR = process.cwd();
|
||||||
|
// Change to the Angular project directory
|
||||||
|
cd(ANGULAR_PROJECT_DIR);
|
||||||
|
|
||||||
|
// Whether to log verbosely
|
||||||
|
const VERBOSE_MODE = process.argv.includes('-v');
|
||||||
|
// Full path to PullApprove config file
|
||||||
|
const PULL_APPROVE_YAML_PATH = path.resolve(ANGULAR_PROJECT_DIR, '.pullapprove.yml');
|
||||||
|
// All relative path file names in the git repo, this is retrieved using git rather
|
||||||
|
// that a glob so that we only get files that are checked in, ignoring things like
|
||||||
|
// node_modules, .bazelrc.user, etc
|
||||||
|
const ALL_FILES = exec('git ls-tree --full-tree -r --name-only HEAD', {silent: true})
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.filter((_: string) => !!_);
|
||||||
|
if (!ALL_FILES.length) {
|
||||||
|
console.error(
|
||||||
|
`No files were found to be in the git tree, did you run this command from \n` +
|
||||||
|
`inside the angular repository?`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the glob matching information from each group's condition. */
|
||||||
|
function getGlobMatchersFromCondition(
|
||||||
|
groupName: string, condition: string): [GlobMatcher[], string[]] {
|
||||||
|
const trimmedCondition = condition.trim();
|
||||||
|
const globMatchers: GlobMatcher[] = [];
|
||||||
|
const badConditionLines: string[] = [];
|
||||||
|
|
||||||
|
// If the condition starts with contains_any_globs, evaluate all of the globs
|
||||||
|
if (trimmedCondition.startsWith('contains_any_globs')) {
|
||||||
|
trimmedCondition.split('\n')
|
||||||
|
.slice(1, -1)
|
||||||
|
.map(glob => {
|
||||||
|
const trimmedGlob = glob.trim();
|
||||||
|
const match = trimmedGlob.match(CONTAINS_ANY_GLOBS_REGEX);
|
||||||
|
if (!match) {
|
||||||
|
badConditionLines.push(trimmedGlob);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return match[1];
|
||||||
|
})
|
||||||
|
.filter(globString => !!globString)
|
||||||
|
.forEach(globString => globMatchers.push({
|
||||||
|
group: groupName,
|
||||||
|
glob: globString,
|
||||||
|
matcher: new Minimatch(globString, {dot: true}),
|
||||||
|
matchCount: 0,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return [globMatchers, badConditionLines];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create logs for each review group. */
|
||||||
|
function logGroups(groups: Map<string, Map<string, GlobMatcher>>) {
|
||||||
|
Array.from(groups.entries()).sort().forEach(([groupName, globs]) => {
|
||||||
|
console.groupCollapsed(groupName);
|
||||||
|
Array.from(globs.values())
|
||||||
|
.sort((a, b) => b.matchCount - a.matchCount)
|
||||||
|
.forEach(glob => console.info(`${glob.glob} - ${glob.matchCount}`));
|
||||||
|
console.groupEnd();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logs a header within a text drawn box. */
|
||||||
|
function logHeader(...params: string[]) {
|
||||||
|
const totalWidth = 80;
|
||||||
|
const fillWidth = totalWidth - 2;
|
||||||
|
const headerText = params.join(' ').substr(0, fillWidth);
|
||||||
|
const leftSpace = Math.ceil((fillWidth - headerText.length) / 2);
|
||||||
|
const rightSpace = fillWidth - leftSpace - headerText.length;
|
||||||
|
const fill = (count: number, content: string) => content.repeat(count);
|
||||||
|
|
||||||
|
console.info(`┌${fill(fillWidth, '─')}┐`);
|
||||||
|
console.info(`│${fill(leftSpace, ' ')}${headerText}${fill(rightSpace, ' ')}│`);
|
||||||
|
console.info(`└${fill(fillWidth, '─')}┘`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Runs the pull approve verification check on provided files. */
|
||||||
|
function runVerification(files: string[]) {
|
||||||
|
// All of the globs created for each group's conditions.
|
||||||
|
const allGlobs: GlobMatcher[] = [];
|
||||||
|
// The pull approve config file.
|
||||||
|
const pullApprove = readFileSync(PULL_APPROVE_YAML_PATH, {encoding: 'utf8'});
|
||||||
|
// All of the PullApprove groups, parsed from the PullApprove yaml file.
|
||||||
|
const parsedPullApproveGroups =
|
||||||
|
parseYaml(pullApprove).groups as{[key: string]: {conditions: string}};
|
||||||
|
// All files which were found to match a condition in PullApprove.
|
||||||
|
const matchedFiles = new Set<string>();
|
||||||
|
// All files which were not found to match a condition in PullApprove.
|
||||||
|
const unmatchedFiles = new Set<string>();
|
||||||
|
// All PullApprove groups which matched at least one file.
|
||||||
|
const matchedGroups = new Map<string, Map<string, GlobMatcher>>();
|
||||||
|
// All PullApprove groups which did not match at least one file.
|
||||||
|
const unmatchedGroups = new Map<string, Map<string, GlobMatcher>>();
|
||||||
|
// All condition lines which were not able to be correctly parsed, by group.
|
||||||
|
const badConditionLinesByGroup = new Map<string, string[]>();
|
||||||
|
// Total number of condition lines which were not able to be correctly parsed.
|
||||||
|
let badConditionLineCount = 0;
|
||||||
|
|
||||||
|
// Get all of the globs from the PullApprove group conditions.
|
||||||
|
Object.entries(parsedPullApproveGroups).forEach(([groupName, group]) => {
|
||||||
|
for (const condition of group.conditions) {
|
||||||
|
const [matchers, badConditions] = getGlobMatchersFromCondition(groupName, condition);
|
||||||
|
if (badConditions.length) {
|
||||||
|
badConditionLinesByGroup.set(groupName, badConditions);
|
||||||
|
badConditionLineCount += badConditions.length;
|
||||||
|
}
|
||||||
|
allGlobs.push(...matchers);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (badConditionLineCount) {
|
||||||
|
console.info(`Discovered ${badConditionLineCount} parsing errors in PullApprove conditions`);
|
||||||
|
console.info(`Attempted parsing using: ${CONTAINS_ANY_GLOBS_REGEX}`);
|
||||||
|
console.info();
|
||||||
|
console.info(`Unable to properly parse the following line(s) by group:`);
|
||||||
|
badConditionLinesByGroup.forEach((badConditionLines, groupName) => {
|
||||||
|
console.info(`- ${groupName}:`);
|
||||||
|
badConditionLines.forEach(line => console.info(` ${line}`));
|
||||||
|
});
|
||||||
|
console.info();
|
||||||
|
console.info(
|
||||||
|
`Please correct the invalid conditions, before PullApprove verification can be completed`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check each file for if it is matched by a PullApprove condition.
|
||||||
|
for (let file of files) {
|
||||||
|
const matched = allGlobs.filter(glob => glob.matcher.match(file));
|
||||||
|
matched.length ? matchedFiles.add(file) : unmatchedFiles.add(file);
|
||||||
|
matched.forEach(glob => glob.matchCount++);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add each glob for each group to a map either matched or unmatched.
|
||||||
|
allGlobs.forEach(glob => {
|
||||||
|
const groups = glob.matchCount ? matchedGroups : unmatchedGroups;
|
||||||
|
const globs = groups.get(glob.group) || new Map<string, GlobMatcher>();
|
||||||
|
// Set the globs map in the groups map
|
||||||
|
groups.set(glob.group, globs);
|
||||||
|
// Set the glob in the globs map
|
||||||
|
globs.set(glob.glob, glob);
|
||||||
|
});
|
||||||
|
|
||||||
|
// PullApprove is considered verified if no files or groups are found to be unsed.
|
||||||
|
const verificationSucceeded = !(unmatchedFiles.size || unmatchedGroups.size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overall result
|
||||||
|
*/
|
||||||
|
logHeader('Result');
|
||||||
|
if (verificationSucceeded) {
|
||||||
|
console.info('PullApprove verification succeeded!');
|
||||||
|
} else {
|
||||||
|
console.info(`PullApprove verification failed.\n`);
|
||||||
|
console.info(`Please update '.pullapprove.yml' to ensure that all necessary`);
|
||||||
|
console.info(`files/directories have owners and all patterns that appear in`);
|
||||||
|
console.info(`the file correspond to actual files/directories in the repo.`);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* File by file Summary
|
||||||
|
*/
|
||||||
|
logHeader('PullApprove file match results');
|
||||||
|
console.groupCollapsed(`Matched Files (${matchedFiles.size} files)`);
|
||||||
|
VERBOSE_MODE && matchedFiles.forEach(file => console.info(file));
|
||||||
|
console.groupEnd();
|
||||||
|
console.groupCollapsed(`Unmatched Files (${unmatchedFiles.size} files)`);
|
||||||
|
unmatchedFiles.forEach(file => console.info(file));
|
||||||
|
console.groupEnd();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group by group Summary
|
||||||
|
*/
|
||||||
|
logHeader('PullApprove group matches');
|
||||||
|
console.groupCollapsed(`Matched Groups (${matchedGroups.size} groups)`);
|
||||||
|
VERBOSE_MODE && logGroups(matchedGroups);
|
||||||
|
console.groupEnd();
|
||||||
|
console.groupCollapsed(`Unmatched Groups (${unmatchedGroups.size} groups)`);
|
||||||
|
logGroups(unmatchedGroups);
|
||||||
|
console.groupEnd();
|
||||||
|
|
||||||
|
// Provide correct exit code based on verification success.
|
||||||
|
process.exit(verificationSucceeded ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
runVerification(ALL_FILES);
|
||||||
|
}
|
16
dev-infra/rollup.config.js
Normal file
16
dev-infra/rollup.config.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const node = require('rollup-plugin-node-resolve');
|
||||||
|
const commonjs = require('rollup-plugin-commonjs');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
external: ['shelljs', 'minimatch', 'yaml'],
|
||||||
|
preferBuiltins: true,
|
||||||
|
output: {
|
||||||
|
banner: "#!/usr/bin/env node",
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
node({
|
||||||
|
mainFields: ['browser', 'es2015', 'module', 'jsnext:main', 'main'],
|
||||||
|
}),
|
||||||
|
commonjs(),
|
||||||
|
],
|
||||||
|
};
|
@ -60,7 +60,7 @@
|
|||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"bundle": "TODO(i): temporarily increase the payload size limit from 105779 - this is due to a closure issue related to ESM reexports that still needs to be investigated",
|
"bundle": "TODO(i): temporarily increase the payload size limit from 105779 - this is due to a closure issue related to ESM reexports that still needs to be investigated",
|
||||||
"bundle": "TODO(i): we should define ngDevMode to false in Closure, but --define only works in the global scope.",
|
"bundle": "TODO(i): we should define ngDevMode to false in Closure, but --define only works in the global scope.",
|
||||||
"bundle": 175498
|
"bundle": 170618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ function installLocalPackages() {
|
|||||||
local_packages+=("puppeteer@file:${pwd}/../node_modules/puppeteer")
|
local_packages+=("puppeteer@file:${pwd}/../node_modules/puppeteer")
|
||||||
local_packages+=("webdriver-manager@file:${pwd}/../node_modules/webdriver-manager")
|
local_packages+=("webdriver-manager@file:${pwd}/../node_modules/webdriver-manager")
|
||||||
|
|
||||||
yarn add --ignore-scripts --silent "${local_packages[@]}"
|
yarn add --ignore-scripts --silent "${local_packages[@]}" --cache-folder ./.yarn_local_cache
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchKarmaConf() {
|
function patchKarmaConf() {
|
||||||
@ -58,6 +58,8 @@ function testBazel() {
|
|||||||
# Create project
|
# Create project
|
||||||
ng new demo --collection=@angular/bazel --routing --skip-git --skip-install --style=scss
|
ng new demo --collection=@angular/bazel --routing --skip-git --skip-install --style=scss
|
||||||
cd demo
|
cd demo
|
||||||
|
# Use a local yarn cache folder so we don't access the global yarn cache
|
||||||
|
mkdir .yarn_local_cache
|
||||||
patchKarmaConf
|
patchKarmaConf
|
||||||
patchProtractorConf
|
patchProtractorConf
|
||||||
installLocalPackages
|
installLocalPackages
|
||||||
@ -79,7 +81,7 @@ function testNonBazel() {
|
|||||||
# disable CLI's version check (if version is 0.0.0, then no version check happens)
|
# disable CLI's version check (if version is 0.0.0, then no version check happens)
|
||||||
yarn --cwd node_modules/@angular/cli version --new-version 0.0.0 --no-git-tag-version
|
yarn --cwd node_modules/@angular/cli version --new-version 0.0.0 --no-git-tag-version
|
||||||
# re-add build-angular
|
# re-add build-angular
|
||||||
yarn add --dev file:../node_modules/@angular-devkit/build-angular
|
yarn add --dev file:../node_modules/@angular-devkit/build-angular --cache-folder ./.yarn_local_cache
|
||||||
ng build --progress=false
|
ng build --progress=false
|
||||||
ng test --progress=false --watch=false
|
ng test --progress=false --watch=false
|
||||||
ng e2e --port 0 --configuration=production --webdriver-update=false
|
ng e2e --port 0 --configuration=production --webdriver-update=false
|
||||||
|
@ -53,11 +53,13 @@ rm('-rf', `demo`);
|
|||||||
exec('ng version');
|
exec('ng version');
|
||||||
exec('ng new demo --skip-git --skip-install --style=css --no-interactive');
|
exec('ng new demo --skip-git --skip-install --style=css --no-interactive');
|
||||||
cd('demo');
|
cd('demo');
|
||||||
|
// Use a local yarn cache folder so we don't access the global yarn cache
|
||||||
|
exec('mkdir .yarn_local_cache');
|
||||||
|
|
||||||
// Install Angular packages that are built locally from HEAD and npm packages
|
// Install Angular packages that are built locally from HEAD and npm packages
|
||||||
// from root node modules that are to be kept in sync
|
// from root node modules that are to be kept in sync
|
||||||
const packageList = Object.keys(packages).map(p => `${p}@${packages[p]}`).join(' ');
|
const packageList = Object.keys(packages).map(p => `${p}@${packages[p]}`).join(' ');
|
||||||
exec(`yarn add --ignore-scripts --silent ${packageList}`);
|
exec(`yarn add --ignore-scripts --silent ${packageList} --cache-folder ./.yarn_local_cache`);
|
||||||
|
|
||||||
// Add @angular/elements
|
// Add @angular/elements
|
||||||
exec(bazelMappings ? `ng add "${bazelMappings['@angular/elements']}"` : `ng add "${__dirname}/../../dist/packages-dist/elements"`);
|
exec(bazelMappings ? `ng add "${bazelMappings['@angular/elements']}"` : `ng add "${__dirname}/../../dist/packages-dist/elements"`);
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
# How to run the benchmarks_external locally
|
|
||||||
|
|
||||||
$ cp -r ./modules/benchmarks_external ./dist/all/
|
|
||||||
$ ./node_modules/.bin/tsc -p modules --emitDecoratorMetadata -w
|
|
||||||
$ gulp serve
|
|
||||||
$ open http://localhost:8000/all/benchmarks_external/src/tree/index.html?bundles=false
|
|
@ -1,37 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {runClickBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
|
|
||||||
|
|
||||||
describe('ng1.x compiler benchmark', function() {
|
|
||||||
|
|
||||||
const URL = 'benchmarks_external/src/compiler/compiler_benchmark.html';
|
|
||||||
|
|
||||||
afterEach(verifyNoBrowserErrors);
|
|
||||||
|
|
||||||
it('should log withBinding stats', function(done) {
|
|
||||||
runClickBenchmark({
|
|
||||||
url: URL,
|
|
||||||
buttons: ['#compileWithBindings'],
|
|
||||||
id: 'ng1.compile.withBindings',
|
|
||||||
params: [{name: 'elements', value: 150, scale: 'linear'}],
|
|
||||||
waitForAngular2: false
|
|
||||||
}).then(done, done.fail);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should log noBindings stats', function(done) {
|
|
||||||
runClickBenchmark({
|
|
||||||
url: URL,
|
|
||||||
buttons: ['#compileNoBindings'],
|
|
||||||
id: 'ng1.compile.noBindings',
|
|
||||||
params: [{name: 'elements', value: 150, scale: 'linear'}],
|
|
||||||
waitForAngular2: false
|
|
||||||
}).then(done, done.fail);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {runClickBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
|
|
||||||
|
|
||||||
describe('ng1.x largetable benchmark', function() {
|
|
||||||
const URL = 'benchmarks_external/src/largetable/largetable_benchmark.html';
|
|
||||||
|
|
||||||
afterEach(verifyNoBrowserErrors);
|
|
||||||
|
|
||||||
['baselineBinding', 'baselineInterpolation', 'ngBind', 'ngBindOnce', 'interpolation',
|
|
||||||
'interpolationAttr', 'ngBindFn', 'interpolationFn', 'ngBindFilter', 'interpolationFilter']
|
|
||||||
.forEach(function(benchmarkType) {
|
|
||||||
it('should log the stats with: ' + benchmarkType, function(done) {
|
|
||||||
runClickBenchmark({
|
|
||||||
url: URL,
|
|
||||||
buttons: ['#destroyDom', '#createDom'],
|
|
||||||
id: 'ng1.largetable.' + benchmarkType,
|
|
||||||
params: [
|
|
||||||
{name: 'columns', value: 100, scale: 'sqrt'},
|
|
||||||
{name: 'rows', value: 20, scale: 'sqrt'},
|
|
||||||
{name: 'benchmarkType', value: benchmarkType}
|
|
||||||
],
|
|
||||||
waitForAngular2: false
|
|
||||||
}).then(done, done.fail);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,41 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {runBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
|
|
||||||
|
|
||||||
describe('ng-dart1.x naive infinite scroll benchmark', function() {
|
|
||||||
|
|
||||||
const URL = 'benchmarks_external/src/naive_infinite_scroll/index.html';
|
|
||||||
|
|
||||||
afterEach(verifyNoBrowserErrors);
|
|
||||||
|
|
||||||
[1, 2, 4].forEach(function(appSize) {
|
|
||||||
it('should run scroll benchmark and collect stats for appSize = ' + appSize, function(done) {
|
|
||||||
runBenchmark({
|
|
||||||
url: URL,
|
|
||||||
id: 'ng1-dart1.x.naive_infinite_scroll',
|
|
||||||
work: function() {
|
|
||||||
$('#reset-btn').click();
|
|
||||||
$('#run-btn').click();
|
|
||||||
let s = 1000;
|
|
||||||
if (appSize > 4) {
|
|
||||||
s = s + appSize * 100;
|
|
||||||
}
|
|
||||||
browser.sleep(s);
|
|
||||||
},
|
|
||||||
params: [
|
|
||||||
{name: 'appSize', value: appSize},
|
|
||||||
{name: 'iterationCount', value: 20, scale: 'linear'},
|
|
||||||
{name: 'scrollIncrement', value: 40}
|
|
||||||
],
|
|
||||||
waitForAngular2: false
|
|
||||||
}).then(done, done.fail);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -1,37 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {runClickBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
|
|
||||||
|
|
||||||
describe('react tree benchmark', function() {
|
|
||||||
|
|
||||||
const URL = 'benchmarks_external/src/tree/react/index.html';
|
|
||||||
|
|
||||||
afterEach(verifyNoBrowserErrors);
|
|
||||||
|
|
||||||
it('should log the stats (create)', function(done) {
|
|
||||||
runClickBenchmark({
|
|
||||||
url: URL,
|
|
||||||
buttons: ['#destroyDom', '#createDom'],
|
|
||||||
id: 'react.tree.create',
|
|
||||||
params: [{name: 'depth', value: 9, scale: 'log2'}],
|
|
||||||
waitForAngular2: false
|
|
||||||
}).then(done, done.fail);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should log the stats (update)', function(done) {
|
|
||||||
runClickBenchmark({
|
|
||||||
url: URL,
|
|
||||||
buttons: ['#createDom'],
|
|
||||||
id: 'react.tree.update',
|
|
||||||
params: [{name: 'depth', value: 9, scale: 'log2'}],
|
|
||||||
waitForAngular2: false
|
|
||||||
}).then(done, done.fail);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -1,37 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {runClickBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
|
|
||||||
|
|
||||||
describe('ng1.x tree benchmark', function() {
|
|
||||||
|
|
||||||
const URL = 'benchmarks_external/src/static_tree/tree_benchmark.html';
|
|
||||||
|
|
||||||
afterEach(verifyNoBrowserErrors);
|
|
||||||
|
|
||||||
it('should log the stats (create)', function(done) {
|
|
||||||
runClickBenchmark({
|
|
||||||
url: URL,
|
|
||||||
buttons: ['#destroyDom', '#createDom'],
|
|
||||||
id: 'ng1.static.tree.create',
|
|
||||||
params: [],
|
|
||||||
waitForAngular2: false
|
|
||||||
}).then(done, done.fail);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should log the stats (update)', function(done) {
|
|
||||||
runClickBenchmark({
|
|
||||||
url: URL,
|
|
||||||
buttons: ['#createDom'],
|
|
||||||
id: 'ng1.static.tree.update',
|
|
||||||
params: [],
|
|
||||||
waitForAngular2: false
|
|
||||||
}).then(done, done.fail);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -1,37 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {runClickBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
|
|
||||||
|
|
||||||
describe('ng1.x tree benchmark', function() {
|
|
||||||
|
|
||||||
const URL = 'benchmarks_external/src/tree/tree_benchmark.html';
|
|
||||||
|
|
||||||
afterEach(verifyNoBrowserErrors);
|
|
||||||
|
|
||||||
it('should log the stats (create)', function(done) {
|
|
||||||
runClickBenchmark({
|
|
||||||
url: URL,
|
|
||||||
buttons: ['#destroyDom', '#createDom'],
|
|
||||||
id: 'ng1.tree.create',
|
|
||||||
params: [{name: 'depth', value: 9, scale: 'log2'}],
|
|
||||||
waitForAngular2: false
|
|
||||||
}).then(done, done.fail);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should log the stats (update)', function(done) {
|
|
||||||
runClickBenchmark({
|
|
||||||
url: URL,
|
|
||||||
buttons: ['#createDom'],
|
|
||||||
id: 'ng1.tree.update',
|
|
||||||
params: [{name: 'depth', value: 9, scale: 'log2'}],
|
|
||||||
waitForAngular2: false
|
|
||||||
}).then(done, done.fail);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -1,91 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
(function(global: any /** TODO #9100 */) {
|
|
||||||
|
|
||||||
writeScriptTag('/all/benchmarks/vendor/core.js');
|
|
||||||
writeScriptTag('/all/benchmarks/vendor/zone.js');
|
|
||||||
writeScriptTag('/all/benchmarks/vendor/long-stack-trace-zone.js');
|
|
||||||
writeScriptTag('/all/benchmarks/vendor/system.src.js');
|
|
||||||
writeScriptTag('/all/benchmarks/vendor/Reflect.js', 'benchmarksBootstrap()');
|
|
||||||
|
|
||||||
(<any>global).benchmarksBootstrap = benchmarksBootstrap;
|
|
||||||
|
|
||||||
function benchmarksBootstrap() {
|
|
||||||
// check query param
|
|
||||||
const useBundles = location.search.indexOf('bundles=false') == -1;
|
|
||||||
if (useBundles) {
|
|
||||||
System.config({
|
|
||||||
map: {
|
|
||||||
'index': 'index.js',
|
|
||||||
'@angular/core': '/packages-dist/core/bundles/core.umd.js',
|
|
||||||
'@angular/common': '/packages-dist/common/bundles/common.umd.js',
|
|
||||||
'@angular/forms': '/packages-dist/forms/bundles/forms.umd.js',
|
|
||||||
'@angular/compiler': '/packages-dist/compiler/bundles/compiler.umd.js',
|
|
||||||
'@angular/platform-browser':
|
|
||||||
'/packages-dist/platform-browser/bundles/platform-browser.umd.js',
|
|
||||||
'@angular/platform-browser-dynamic':
|
|
||||||
'/packages-dist/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
|
|
||||||
'@angular/http': '/packages-dist/http/bundles/http.umd.js',
|
|
||||||
'@angular/upgrade': '/packages-dist/upgrade/bundles/upgrade.umd.js',
|
|
||||||
'@angular/router': '/packages-dist/router/bundles/router.umd.js',
|
|
||||||
'@angular/core/src/facade': '/all/@angular/core/src/facade',
|
|
||||||
'rxjs': 'node_modules/rxjs',
|
|
||||||
},
|
|
||||||
packages: {
|
|
||||||
'app': {defaultExtension: 'js'},
|
|
||||||
'@angular/core/src/facade': {defaultExtension: 'js'},
|
|
||||||
'rxjs/ajax': {main: 'index.js', defaultExtension: 'js' },
|
|
||||||
'rxjs/operators': {main: 'index.js', defaultExtension: 'js' },
|
|
||||||
'rxjs/testing': {main: 'index.js', defaultExtension: 'js' },
|
|
||||||
'rxjs/websocket': {main: 'index.js', defaultExtension: 'js' },
|
|
||||||
'rxjs': { main: 'index.js', defaultExtension: 'js' },
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.warn(
|
|
||||||
'Not using the Angular bundles. Don\'t use this configuration for e2e/performance tests!');
|
|
||||||
|
|
||||||
System.config({
|
|
||||||
map: {
|
|
||||||
'index': 'index.js',
|
|
||||||
'@angular': '/all/@angular',
|
|
||||||
'rxjs': 'node_modules/rxjs',
|
|
||||||
},
|
|
||||||
packages: {
|
|
||||||
'app': {defaultExtension: 'js'},
|
|
||||||
'@angular/core': {main: 'index.js', defaultExtension: 'js'},
|
|
||||||
'@angular/compiler': {main: 'index.js', defaultExtension: 'js'},
|
|
||||||
'@angular/router': {main: 'index.js', defaultExtension: 'js'},
|
|
||||||
'@angular/common': {main: 'index.js', defaultExtension: 'js'},
|
|
||||||
'@angular/forms': {main: 'index.js', defaultExtension: 'js'},
|
|
||||||
'@angular/platform-browser': {main: 'index.js', defaultExtension: 'js'},
|
|
||||||
'@angular/platform-browser-dynamic': {main: 'index.js', defaultExtension: 'js'},
|
|
||||||
'@angular/upgrade': {main: 'index.js', defaultExtension: 'js'},
|
|
||||||
'rxjs/ajax': {main: 'index.js', defaultExtension: 'js' },
|
|
||||||
'rxjs/operators': {main: 'index.js', defaultExtension: 'js' },
|
|
||||||
'rxjs/testing': {main: 'index.js', defaultExtension: 'js' },
|
|
||||||
'rxjs/websocket': {main: 'index.js', defaultExtension: 'js' },
|
|
||||||
'rxjs': { main: 'index.js', defaultExtension: 'js' },
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// BOOTSTRAP the app!
|
|
||||||
System.import('index').then(function(m: any /** TODO #9100 */) {
|
|
||||||
m.main();
|
|
||||||
}, console.error.bind(console));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function writeScriptTag(scriptUrl: any /** TODO #9100 */, onload?: any /** TODO #9100 */) {
|
|
||||||
document.write(`<script src="${scriptUrl}" onload="${onload}"></script>`);
|
|
||||||
}
|
|
||||||
}(window));
|
|
@ -1,53 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h2>Params</h2>
|
|
||||||
<form>
|
|
||||||
Elements:
|
|
||||||
<input type="number" name="elements" placeholder="elements" value="150">
|
|
||||||
<br>
|
|
||||||
<button>Apply</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h2>Actions</h2>
|
|
||||||
<p>
|
|
||||||
<button id="compileWithBindings">CompileWithBindings</button>
|
|
||||||
<button id="compileNoBindings">CompileNoBindings</button>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<template id="templateNoBindings">
|
|
||||||
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
|
|
||||||
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
|
|
||||||
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
|
|
||||||
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
|
|
||||||
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template id="templateWithBindings">
|
|
||||||
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
|
|
||||||
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
|
|
||||||
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
|
|
||||||
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
|
|
||||||
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
|
|
||||||
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
|
|
||||||
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
|
|
||||||
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
|
|
||||||
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
|
|
||||||
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
$SCRIPTS$
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,117 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
// compiler benchmark in AngularJS 1.x
|
|
||||||
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
|
|
||||||
declare var angular: any;
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
const ngEl = document.createElement('div');
|
|
||||||
angular.bootstrap(ngEl, ['app']);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadTemplate(templateId, repeatCount) {
|
|
||||||
const template = document.querySelectorAll(`#${templateId}`)[0];
|
|
||||||
const content = (<HTMLElement>template).innerHTML;
|
|
||||||
let result = '';
|
|
||||||
for (let i = 0; i < repeatCount; i++) {
|
|
||||||
result += content;
|
|
||||||
}
|
|
||||||
// replace [] binding syntax
|
|
||||||
result = result.replace(/[\[\]]/g, '');
|
|
||||||
|
|
||||||
// Use a DIV as container as Angular 1.3 does not know <template> elements...
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.innerHTML = result;
|
|
||||||
return div;
|
|
||||||
}
|
|
||||||
|
|
||||||
angular.module('app', [])
|
|
||||||
.directive('dir0',
|
|
||||||
[
|
|
||||||
'$parse',
|
|
||||||
function($parse) {
|
|
||||||
return {
|
|
||||||
compile: function($element, $attrs) {
|
|
||||||
const expr = $parse($attrs.attr0);
|
|
||||||
return ($scope) => $scope.$watch(expr, angular.noop);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
])
|
|
||||||
.directive('dir1',
|
|
||||||
[
|
|
||||||
'$parse',
|
|
||||||
function($parse) {
|
|
||||||
return {
|
|
||||||
compile: function($element, $attrs) {
|
|
||||||
const expr = $parse($attrs.attr1);
|
|
||||||
return ($scope) => $scope.$watch(expr, angular.noop);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
])
|
|
||||||
.directive('dir2',
|
|
||||||
[
|
|
||||||
'$parse',
|
|
||||||
function($parse) {
|
|
||||||
return {
|
|
||||||
compile: function($element, $attrs) {
|
|
||||||
const expr = $parse($attrs.attr2);
|
|
||||||
return ($scope) => $scope.$watch(expr, angular.noop);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
])
|
|
||||||
.directive('dir3',
|
|
||||||
[
|
|
||||||
'$parse',
|
|
||||||
function($parse) {
|
|
||||||
return {
|
|
||||||
compile: function($element, $attrs) {
|
|
||||||
const expr = $parse($attrs.attr3);
|
|
||||||
return ($scope) => $scope.$watch(expr, angular.noop);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
])
|
|
||||||
.directive('dir4',
|
|
||||||
[
|
|
||||||
'$parse',
|
|
||||||
function($parse) {
|
|
||||||
return {
|
|
||||||
compile: function($element, $attrs) {
|
|
||||||
const expr = $parse($attrs.attr4);
|
|
||||||
return ($scope) => $scope.$watch(expr, angular.noop);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
])
|
|
||||||
.run([
|
|
||||||
'$compile',
|
|
||||||
function($compile) {
|
|
||||||
const count = getIntParameter('elements');
|
|
||||||
const templateNoBindings = loadTemplate('templateNoBindings', count);
|
|
||||||
const templateWithBindings = loadTemplate('templateWithBindings', count);
|
|
||||||
|
|
||||||
bindAction('#compileWithBindings', compileWithBindings);
|
|
||||||
bindAction('#compileNoBindings', compileNoBindings);
|
|
||||||
|
|
||||||
function compileNoBindings() {
|
|
||||||
// Need to clone every time as the compiler might modify the template!
|
|
||||||
const cloned = templateNoBindings.cloneNode(true);
|
|
||||||
$compile(cloned);
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileWithBindings() {
|
|
||||||
// Need to clone every time as the compiler might modify the template!
|
|
||||||
const cloned = templateWithBindings.cloneNode(true);
|
|
||||||
$compile(cloned);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
@ -1,26 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="compiler/compiler_benchmark.html">Compiler benchmark</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="tree/tree_benchmark.html">Tree benchmark</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="static_tree/tree_benchmark.html">Static tree benchmark</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="tree/react/index.html">React Tree benchmark</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="largetable/largetable_benchmark.html">Largetable benchmark</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="naive_infinite_scroll/index.html">Naive infinite scroll benchmark</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,58 +0,0 @@
|
|||||||
<ng-switch on="benchmarkType">
|
|
||||||
<baseline-binding-table ng-switch-when="baselineBinding">
|
|
||||||
</baseline-binding-table>
|
|
||||||
<baseline-interpolation-table ng-switch-when="baselineInterpolation">
|
|
||||||
</baseline-interpolation-table>
|
|
||||||
<div ng-switch-when="ngBind">
|
|
||||||
<h2>baseline binding</h2>
|
|
||||||
<div ng-repeat="row in data">
|
|
||||||
<span ng-repeat="column in row">
|
|
||||||
<span ng-bind="column.i"></span>:<span ng-bind="column.j"></span>|
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ng-switch-when="ngBindOnce">
|
|
||||||
<h2>baseline binding once</h2>
|
|
||||||
<div ng-repeat="row in data">
|
|
||||||
<span ng-repeat="column in ::row">
|
|
||||||
<span ng-bind="::column.i"></span>:<span ng-bind="::column.j"></span>|
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ng-switch-when="interpolation">
|
|
||||||
<h2>baseline interpolation</h2>
|
|
||||||
<div ng-repeat="row in data">
|
|
||||||
<span ng-repeat="column in row">{{column.i}}:{{column.j}}|</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ng-switch-when="interpolationAttr">
|
|
||||||
<h2>attribute interpolation</h2>
|
|
||||||
<div ng-repeat="row in data">
|
|
||||||
<span ng-repeat="column in row" i="{{column.i}}" j="{{column.j}}">i,j attrs</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ng-switch-when="ngBindFn">
|
|
||||||
<h2>bindings with functions</h2>
|
|
||||||
<div ng-repeat="row in data">
|
|
||||||
<span ng-repeat="column in row"><span ng-bind="column.iFn()"></span>:<span ng-bind="column.jFn()"></span>|</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ng-switch-when="interpolationFn">
|
|
||||||
<h2>interpolation with functions</h2>
|
|
||||||
<div ng-repeat="row in data">
|
|
||||||
<span ng-repeat="column in row">{{column.iFn()}}:{{column.jFn()}}|</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ng-switch-when="ngBindFilter">
|
|
||||||
<h2>bindings with filter</h2>
|
|
||||||
<div ng-repeat="row in data">
|
|
||||||
<span ng-repeat="column in row"><span ng-bind="column.i | noop"></span>:<span ng-bind="column.j | noop"></span>|</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ng-switch-when="interpolationFilter">
|
|
||||||
<h2>interpolation with filter</h2>
|
|
||||||
<div ng-repeat="row in data">
|
|
||||||
<span ng-repeat="column in row">{{column.i | noop}}:{{column.j | noop}}|</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-switch>
|
|
@ -1,98 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h2>AngularJS/Dart 1.x largetable benchmark</h2>
|
|
||||||
<form>
|
|
||||||
<div>
|
|
||||||
rows:
|
|
||||||
<input type="number" name="rows" value="100">
|
|
||||||
columns:
|
|
||||||
<input type="number" name="columns" value="20">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
baseline binding:
|
|
||||||
<input type="radio"
|
|
||||||
name="benchmarkType"
|
|
||||||
value="baselineBinding"
|
|
||||||
id="baselineBinding"
|
|
||||||
checked>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
baseline interpolation:
|
|
||||||
<input type="radio"
|
|
||||||
name="benchmarkType"
|
|
||||||
value="baselineInterpolation"
|
|
||||||
id="baselineInterpolation">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
ngBind:
|
|
||||||
<input type="radio"
|
|
||||||
name="benchmarkType"
|
|
||||||
value="ngBind"
|
|
||||||
id="ngBind">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
ngBindOnce:
|
|
||||||
<input type="radio"
|
|
||||||
name="benchmarkType"
|
|
||||||
value="ngBindOnce"
|
|
||||||
id="ngBindOnce">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
interpolation:
|
|
||||||
<input type="radio"
|
|
||||||
name="benchmarkType"
|
|
||||||
value="interpolation"
|
|
||||||
id="interpolation">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
attribute interpolation:
|
|
||||||
<input type="radio"
|
|
||||||
name="benchmarkType"
|
|
||||||
value="interpolationAttr"
|
|
||||||
id="interpolationAttr">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
ngBind + fnInvocation:
|
|
||||||
<input type="radio"
|
|
||||||
name="benchmarkType"
|
|
||||||
value="ngBindFn"
|
|
||||||
id="ngBindFn">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
interpolation + fnInvocation:
|
|
||||||
<input type="radio"
|
|
||||||
name="benchmarkType"
|
|
||||||
value="interpolationFn"
|
|
||||||
id="interpolationFn">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
ngBind + filter:
|
|
||||||
<input type="radio"
|
|
||||||
name="benchmarkType"
|
|
||||||
value="ngBindFilter"
|
|
||||||
id="ngBindFilter">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
interpolation + filter:
|
|
||||||
<input type="radio"
|
|
||||||
name="benchmarkType"
|
|
||||||
value="interpolationFilter"
|
|
||||||
id="interpolationFilter">
|
|
||||||
</div>
|
|
||||||
<button>Apply</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<button id="destroyDom">destroyDom</button>
|
|
||||||
<button id="createDom">createDom</button>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<largetable></largetable>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
$SCRIPTS$
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,119 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {getIntParameter, getStringParameter, bindAction} from '@angular/testing/src/benchmark_util';
|
|
||||||
declare var angular: any;
|
|
||||||
|
|
||||||
const totalRows = getIntParameter('rows');
|
|
||||||
const totalColumns = getIntParameter('columns');
|
|
||||||
const benchmarkType = getStringParameter('benchmarkType');
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
angular.bootstrap(document.querySelector('largetable'), ['app']);
|
|
||||||
}
|
|
||||||
|
|
||||||
angular.module('app', [])
|
|
||||||
.config(function($compileProvider) {
|
|
||||||
if ($compileProvider.debugInfoEnabled) {
|
|
||||||
$compileProvider.debugInfoEnabled(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter('noop', function() { return function(input) { return input; }; })
|
|
||||||
.directive('largetable',
|
|
||||||
function() {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
templateUrl: 'largetable-js-template.html',
|
|
||||||
controller: 'DataController'
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.controller('DataController',
|
|
||||||
function($scope) {
|
|
||||||
bindAction('#destroyDom', destroyDom);
|
|
||||||
bindAction('#createDom', createDom);
|
|
||||||
|
|
||||||
function destroyDom() {
|
|
||||||
$scope.$apply(function() { $scope.benchmarkType = 'none'; });
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDom() {
|
|
||||||
$scope.$apply(function() { $scope.benchmarkType = benchmarkType; });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = $scope.data = [];
|
|
||||||
|
|
||||||
function iGetter() { return this.i; }
|
|
||||||
function jGetter() { return this.j; }
|
|
||||||
|
|
||||||
for (let i = 0; i < totalRows; i++) {
|
|
||||||
data[i] = [];
|
|
||||||
for (let j = 0; j < totalColumns; j++) {
|
|
||||||
data[i][j] = {i: i, j: j, iFn: iGetter, jFn: jGetter};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.directive('baselineBindingTable',
|
|
||||||
function() {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
link: function($scope, $element) {
|
|
||||||
let i, j, row, cell, comment;
|
|
||||||
const template = document.createElement('span');
|
|
||||||
template.setAttribute('ng-repeat', 'foo in foos');
|
|
||||||
template.classList.add('ng-scope');
|
|
||||||
template.appendChild(document.createElement('span'));
|
|
||||||
template.appendChild(document.createTextNode(':'));
|
|
||||||
template.appendChild(document.createElement('span'));
|
|
||||||
template.appendChild(document.createTextNode('|'));
|
|
||||||
|
|
||||||
for (i = 0; i < totalRows; i++) {
|
|
||||||
row = document.createElement('div');
|
|
||||||
$element[0].appendChild(row);
|
|
||||||
for (j = 0; j < totalColumns; j++) {
|
|
||||||
cell = template.cloneNode(true);
|
|
||||||
row.appendChild(cell);
|
|
||||||
cell.childNodes[0].textContent = i;
|
|
||||||
cell.childNodes[2].textContent = j;
|
|
||||||
cell.ng3992 = 'xxx';
|
|
||||||
comment = document.createComment('ngRepeat end: bar in foo');
|
|
||||||
row.appendChild(comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
comment = document.createComment('ngRepeat end: foo in foos');
|
|
||||||
$element[0].appendChild(comment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.directive('baselineInterpolationTable', function() {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
link: function($scope, $element) {
|
|
||||||
let i, j, row, cell, comment;
|
|
||||||
const template = document.createElement('span');
|
|
||||||
template.setAttribute('ng-repeat', 'foo in foos');
|
|
||||||
template.classList.add('ng-scope');
|
|
||||||
|
|
||||||
for (i = 0; i < totalRows; i++) {
|
|
||||||
row = document.createElement('div');
|
|
||||||
$element[0].appendChild(row);
|
|
||||||
for (j = 0; j < totalColumns; j++) {
|
|
||||||
cell = template.cloneNode(true);
|
|
||||||
row.appendChild(cell);
|
|
||||||
cell.textContent = '' + i + ':' + j + '|';
|
|
||||||
cell.ng3992 = 'xxx';
|
|
||||||
comment = document.createComment('ngRepeat end: bar in foo');
|
|
||||||
row.appendChild(comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
comment = document.createComment('ngRepeat end: foo in foos');
|
|
||||||
$element[0].appendChild(comment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
@ -1,20 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>AngularDart Scrolling Benchmark</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<form>
|
|
||||||
App size: <input type="text" name="appSize" value="1"><br>
|
|
||||||
Iteration count: <input type="text" name="iterationCount" value="1"><br>
|
|
||||||
Scroll increment: <input type="text" name="scrollIncrement" value="1"><br>
|
|
||||||
</form>
|
|
||||||
<div>
|
|
||||||
<button id="run-btn">Run</button>
|
|
||||||
<button id="reset-btn">Reset</button>
|
|
||||||
</div>
|
|
||||||
<scroll-app></scroll-app>
|
|
||||||
|
|
||||||
$SCRIPTS$
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||||||
<div>
|
|
||||||
<div id="scrollDiv"
|
|
||||||
ng-style="scrollDivStyle"
|
|
||||||
ng-scroll="onScroll()">
|
|
||||||
<div ng-style="paddingStyle"></div>
|
|
||||||
<div ng-style="innerStyle">
|
|
||||||
<scroll-item
|
|
||||||
ng-repeat="item in visibleItems"
|
|
||||||
offering="item">
|
|
||||||
</scroll-item>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,45 +0,0 @@
|
|||||||
<div ng-style="itemStyle">
|
|
||||||
<company-name company="offering.company"
|
|
||||||
cell-width="companyNameWidth">
|
|
||||||
</company-name>
|
|
||||||
|
|
||||||
<opportunity-name opportunity="offering.opportunity"
|
|
||||||
cell-width="opportunityNameWidth">
|
|
||||||
</opportunity-name>
|
|
||||||
|
|
||||||
<offering-name offering="offering"
|
|
||||||
cell-width="offeringNameWidth">
|
|
||||||
</offering-name>
|
|
||||||
|
|
||||||
<account-cell account="offering.account"
|
|
||||||
cell-width="accountCellWidth">
|
|
||||||
</account-cell>
|
|
||||||
|
|
||||||
<formatted-cell value="offering.basePoints"
|
|
||||||
cell-width="basePointsWidth">
|
|
||||||
</formatted-cell>
|
|
||||||
|
|
||||||
<formatted-cell value="offering.kickerPoints"
|
|
||||||
cell-width="kickerPointsWidth">
|
|
||||||
</formatted-cell>
|
|
||||||
|
|
||||||
<stage-buttons offering="offering"
|
|
||||||
cell-width="stageButtonsWidth">
|
|
||||||
</stage-buttons>
|
|
||||||
|
|
||||||
<formatted-cell value="offering.bundles"
|
|
||||||
cell-width="bundlesWidth">
|
|
||||||
</formatted-cell>
|
|
||||||
|
|
||||||
<formatted-cell value="offering.dueDate"
|
|
||||||
cell-width="dueDateWidth">
|
|
||||||
</formatted-cell>
|
|
||||||
|
|
||||||
<formatted-cell value="offering.endDate"
|
|
||||||
cell-width="endDateWidth">
|
|
||||||
</formatted-cell>
|
|
||||||
|
|
||||||
<formatted-cell value="offering.aatStatus"
|
|
||||||
cell-width="aatStatusWidth">
|
|
||||||
</formatted-cell>
|
|
||||||
</div>
|
|
@ -1,17 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h2>AngularJS/Dart 1.x static tree benchmark (depth 10)</h2>
|
|
||||||
<p>
|
|
||||||
<button id="destroyDom">destroyDom</button>
|
|
||||||
<button id="createDom">createDom</button>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<tree9 data="initData" ng-if="initData != null" class="app"></tree9>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
$SCRIPTS$
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,77 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
// static tree benchmark in AngularJS 1.x
|
|
||||||
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
|
|
||||||
declare var angular: any;
|
|
||||||
|
|
||||||
const MAX_DEPTH = 10;
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
angular.bootstrap(document.querySelector('.app'), ['app']);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addTreeDirective(module, level: number) {
|
|
||||||
let template;
|
|
||||||
if (level <= 0) {
|
|
||||||
template = `<span> {{data.value}}</span>`;
|
|
||||||
} else {
|
|
||||||
template = `<span> {{data.value}} <tree${level-1} data='data.right'></tree${level-1}><tree${level-1} data='data.left'></tree${level-1}></span>`;
|
|
||||||
}
|
|
||||||
module.directive(`tree${level}`, function() { return {scope: {data: '='}, template: template}; });
|
|
||||||
}
|
|
||||||
|
|
||||||
const module = angular.module('app', []);
|
|
||||||
for (let depth = 0; depth < MAX_DEPTH; depth++) {
|
|
||||||
addTreeDirective(module, depth);
|
|
||||||
}
|
|
||||||
module.config([
|
|
||||||
'$compileProvider',
|
|
||||||
function($compileProvider) { $compileProvider.debugInfoEnabled(false); }
|
|
||||||
])
|
|
||||||
.run([
|
|
||||||
'$rootScope',
|
|
||||||
function($rootScope) {
|
|
||||||
let count = 0;
|
|
||||||
$rootScope.initData = null;
|
|
||||||
|
|
||||||
bindAction('#destroyDom', destroyDom);
|
|
||||||
bindAction('#createDom', createDom);
|
|
||||||
|
|
||||||
function createData(): TreeNode {
|
|
||||||
const values = count++ % 2 == 0 ? ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*'] :
|
|
||||||
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', '-'];
|
|
||||||
return buildTree(MAX_DEPTH, values, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function destroyDom() {
|
|
||||||
$rootScope.$apply(function() { $rootScope.initData = null; });
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDom() {
|
|
||||||
$rootScope.$apply(function() { $rootScope.initData = createData(); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
class TreeNode {
|
|
||||||
value: string;
|
|
||||||
left: TreeNode;
|
|
||||||
right: TreeNode;
|
|
||||||
constructor(value, left, right) {
|
|
||||||
this.value = value;
|
|
||||||
this.left = left;
|
|
||||||
this.right = right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildTree(maxDepth, values, curDepth) {
|
|
||||||
if (maxDepth === curDepth) return new TreeNode('', null, null);
|
|
||||||
return new TreeNode(values[curDepth], buildTree(maxDepth, values, curDepth + 1),
|
|
||||||
buildTree(maxDepth, values, curDepth + 1));
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h2>Params</h2>
|
|
||||||
<form>
|
|
||||||
Depth:
|
|
||||||
<input type="number" name="depth" placeholder="depth" value="9">
|
|
||||||
<br>
|
|
||||||
<button>Apply</button>
|
|
||||||
</form>
|
|
||||||
<button id="destroyDom">destroyDom</button>
|
|
||||||
<button id="createDom">createDom</button>
|
|
||||||
|
|
||||||
<h2>React Tree Benchmark</h2>
|
|
||||||
<root-tree id="rootTree"></root-tree>
|
|
||||||
|
|
||||||
$SCRIPTS$
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,73 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
// tree benchmark in React
|
|
||||||
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
|
|
||||||
import * as React from './react.min';
|
|
||||||
|
|
||||||
const TreeComponent = React.createClass({
|
|
||||||
displayName: 'TreeComponent',
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
const treeNode = this.props.treeNode;
|
|
||||||
|
|
||||||
let left = null;
|
|
||||||
if (treeNode.left) {
|
|
||||||
left = React.createElement(
|
|
||||||
"span", {}, [React.createElement(TreeComponent, {treeNode: treeNode.left}, "")]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let right = null;
|
|
||||||
if (treeNode.right) {
|
|
||||||
right = React.createElement(
|
|
||||||
"span", {}, [React.createElement(TreeComponent, {treeNode: treeNode.right}, "")]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const span = React.createElement("span", {}, [" " + treeNode.value, left, right]);
|
|
||||||
|
|
||||||
return (React.createElement("tree", {}, [span]));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
let count = 0;
|
|
||||||
const maxDepth = getIntParameter('depth');
|
|
||||||
|
|
||||||
bindAction('#destroyDom', destroyDom);
|
|
||||||
bindAction('#createDom', createDom);
|
|
||||||
|
|
||||||
const empty = new TreeNode(0, null, null);
|
|
||||||
const rootComponent = React.render(React.createElement(TreeComponent, {treeNode: empty}, ""),
|
|
||||||
document.getElementById('rootTree'));
|
|
||||||
|
|
||||||
function destroyDom() { rootComponent.setProps({treeNode: empty}); }
|
|
||||||
|
|
||||||
function createDom() {
|
|
||||||
const values = count++ % 2 == 0 ? ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*'] :
|
|
||||||
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', '-'];
|
|
||||||
rootComponent.setProps({treeNode: buildTree(maxDepth, values, 0)});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TreeNode {
|
|
||||||
value: string;
|
|
||||||
left: TreeNode;
|
|
||||||
right: TreeNode;
|
|
||||||
|
|
||||||
constructor(value, left, right) {
|
|
||||||
this.value = value;
|
|
||||||
this.left = left;
|
|
||||||
this.right = right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildTree(maxDepth, values, curDepth) {
|
|
||||||
if (maxDepth === curDepth) return new TreeNode('', null, null);
|
|
||||||
return new TreeNode(values[curDepth], buildTree(maxDepth, values, curDepth + 1),
|
|
||||||
buildTree(maxDepth, values, curDepth + 1));
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
export var createElement: Function;
|
|
||||||
export var render: Function;
|
|
||||||
export var createClass: Function;
|
|
@ -1,25 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h2>Params</h2>
|
|
||||||
<form>
|
|
||||||
Depth:
|
|
||||||
<input type="number" name="depth" placeholder="depth" value="9">
|
|
||||||
<br>
|
|
||||||
<button>Apply</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h2>AngularJS/Dart 1.x tree benchmark</h2>
|
|
||||||
<p>
|
|
||||||
<button id="destroyDom">destroyDom</button>
|
|
||||||
<button id="createDom">createDom</button>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<tree data="initData"></tree>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
$SCRIPTS$
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,107 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
// tree benchmark in AngularJS 1.x
|
|
||||||
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
|
|
||||||
declare var angular: any;
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
angular.bootstrap(document.querySelector('tree'), ['app']);
|
|
||||||
}
|
|
||||||
|
|
||||||
angular.module('app', [])
|
|
||||||
.directive('tree',
|
|
||||||
function() {
|
|
||||||
return {
|
|
||||||
scope: {data: '='},
|
|
||||||
template: '<span> {{data.value}}' +
|
|
||||||
' <span tree-if="data.left"></span>' +
|
|
||||||
' <span tree-if="data.right"></span>' +
|
|
||||||
'</span>'
|
|
||||||
};
|
|
||||||
})
|
|
||||||
// special directive for "if" as angular 1.3 does not support
|
|
||||||
// recursive components.
|
|
||||||
.directive('treeIf',
|
|
||||||
[
|
|
||||||
'$compile',
|
|
||||||
'$parse',
|
|
||||||
function($compile, $parse) {
|
|
||||||
const transcludeFn;
|
|
||||||
return {
|
|
||||||
compile: function(element, attrs) {
|
|
||||||
const expr = $parse('!!' + attrs.treeIf);
|
|
||||||
const template = '<tree data="' + attrs.treeIf + '"></tree>';
|
|
||||||
let transclude;
|
|
||||||
return function($scope, $element, $attrs) {
|
|
||||||
if (!transclude) {
|
|
||||||
transclude = $compile(template);
|
|
||||||
}
|
|
||||||
let childScope;
|
|
||||||
let childElement;
|
|
||||||
$scope.$watch(expr, function(newValue) {
|
|
||||||
if (childScope) {
|
|
||||||
childScope.$destroy();
|
|
||||||
childElement.remove();
|
|
||||||
childScope = null;
|
|
||||||
childElement = null;
|
|
||||||
}
|
|
||||||
if (newValue) {
|
|
||||||
childScope = $scope.$new();
|
|
||||||
childElement = transclude(childScope,
|
|
||||||
function(clone) { $element.append(clone); });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
])
|
|
||||||
.config([
|
|
||||||
'$compileProvider',
|
|
||||||
function($compileProvider) { $compileProvider.debugInfoEnabled(false); }
|
|
||||||
])
|
|
||||||
.run([
|
|
||||||
'$rootScope',
|
|
||||||
function($rootScope) {
|
|
||||||
let count = 0;
|
|
||||||
const maxDepth = getIntParameter('depth');
|
|
||||||
|
|
||||||
bindAction('#destroyDom', destroyDom);
|
|
||||||
bindAction('#createDom', createDom);
|
|
||||||
|
|
||||||
function destroyDom() {
|
|
||||||
$rootScope.$apply(function() { $rootScope.initData = new TreeNode('', null, null); });
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDom() {
|
|
||||||
const values = count++ % 2 == 0 ? ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*'] :
|
|
||||||
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', '-'];
|
|
||||||
|
|
||||||
$rootScope.$apply(function() { $rootScope.initData = buildTree(maxDepth, values, 0); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
class TreeNode {
|
|
||||||
value: string;
|
|
||||||
left: TreeNode;
|
|
||||||
right: TreeNode;
|
|
||||||
constructor(value, left, right) {
|
|
||||||
this.value = value;
|
|
||||||
this.left = left;
|
|
||||||
this.right = right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildTree(maxDepth, values, curDepth) {
|
|
||||||
if (maxDepth === curDepth) return new TreeNode('', null, null);
|
|
||||||
return new TreeNode(values[curDepth], buildTree(maxDepth, values, curDepth + 1),
|
|
||||||
buildTree(maxDepth, values, curDepth + 1));
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "angular-srcs",
|
"name": "angular-srcs",
|
||||||
"version": "9.0.5",
|
"version": "9.0.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Angular - a web framework for modern web apps",
|
"description": "Angular - a web framework for modern web apps",
|
||||||
"homepage": "https://github.com/angular/angular",
|
"homepage": "https://github.com/angular/angular",
|
||||||
@ -78,7 +78,7 @@
|
|||||||
"@types/systemjs": "0.19.32",
|
"@types/systemjs": "0.19.32",
|
||||||
"@types/yaml": "^1.2.0",
|
"@types/yaml": "^1.2.0",
|
||||||
"@types/yargs": "^11.1.1",
|
"@types/yargs": "^11.1.1",
|
||||||
"@webcomponents/custom-elements": "^1.0.4",
|
"@webcomponents/custom-elements": "^1.1.0",
|
||||||
"angular": "npm:angular@1.7",
|
"angular": "npm:angular@1.7",
|
||||||
"angular-1.5": "npm:angular@1.5",
|
"angular-1.5": "npm:angular@1.5",
|
||||||
"angular-1.6": "npm:angular@1.6",
|
"angular-1.6": "npm:angular@1.6",
|
||||||
@ -147,6 +147,7 @@
|
|||||||
"@bazel/bazel": "2.1.0",
|
"@bazel/bazel": "2.1.0",
|
||||||
"@bazel/buildifier": "^0.29.0",
|
"@bazel/buildifier": "^0.29.0",
|
||||||
"@bazel/ibazel": "^0.11.1",
|
"@bazel/ibazel": "^0.11.1",
|
||||||
|
"@octokit/graphql": "^4.3.1",
|
||||||
"@types/minimist": "^1.2.0",
|
"@types/minimist": "^1.2.0",
|
||||||
"@yarnpkg/lockfile": "^1.1.0",
|
"@yarnpkg/lockfile": "^1.1.0",
|
||||||
"browserstacktunnel-wrapper": "2.0.1",
|
"browserstacktunnel-wrapper": "2.0.1",
|
||||||
@ -175,9 +176,11 @@
|
|||||||
"rewire": "2.5.2",
|
"rewire": "2.5.2",
|
||||||
"sauce-connect": "https://saucelabs.com/downloads/sc-4.5.1-linux.tar.gz",
|
"sauce-connect": "https://saucelabs.com/downloads/sc-4.5.1-linux.tar.gz",
|
||||||
"semver": "^6.3.0",
|
"semver": "^6.3.0",
|
||||||
|
"ts-node": "^8.6.2",
|
||||||
"tslint-eslint-rules": "5.4.0",
|
"tslint-eslint-rules": "5.4.0",
|
||||||
"tslint-no-toplevel-property-access": "0.0.2",
|
"tslint-no-toplevel-property-access": "0.0.2",
|
||||||
"tsutils": "2.27.2",
|
"tsutils": "2.27.2",
|
||||||
|
"typed-graphqlify": "^2.3.0",
|
||||||
"universal-analytics": "0.4.15",
|
"universal-analytics": "0.4.15",
|
||||||
"vlq": "0.2.2",
|
"vlq": "0.2.2",
|
||||||
"vrsource-tslint-rules": "5.1.1"
|
"vrsource-tslint-rules": "5.1.1"
|
||||||
|
@ -318,7 +318,12 @@ def _ngc_tsconfig(ctx, files, srcs, **kwargs):
|
|||||||
"createExternalSymbolFactoryReexports": (not _is_bazel()),
|
"createExternalSymbolFactoryReexports": (not _is_bazel()),
|
||||||
# FIXME: wrong place to de-dupe
|
# FIXME: wrong place to de-dupe
|
||||||
"expectedOut": depset([o.path for o in expected_outs]).to_list(),
|
"expectedOut": depset([o.path for o in expected_outs]).to_list(),
|
||||||
|
# We instruct the compiler to use the host for import generation in Blaze. By default,
|
||||||
|
# module names between source files of the same compilation unit are relative paths. This
|
||||||
|
# is not desired in google3 where the generated module names are used as qualified names
|
||||||
|
# for aliased exports. We disable relative paths and always use manifest paths in google3.
|
||||||
"_useHostForImportGeneration": (not _is_bazel()),
|
"_useHostForImportGeneration": (not _is_bazel()),
|
||||||
|
"_useManifestPathsAsModuleName": (not _is_bazel()),
|
||||||
}
|
}
|
||||||
|
|
||||||
if _should_produce_flat_module_outs(ctx):
|
if _should_produce_flat_module_outs(ctx):
|
||||||
|
@ -113,19 +113,17 @@ export function runOneBuild(args: string[], inputs?: {[path: string]: string}):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const expectedOuts = config['angularCompilerOptions']['expectedOut'];
|
// These are options passed through from the `ng_module` rule which aren't supported
|
||||||
|
// by the `@angular/compiler-cli` and are only intended for `ngc-wrapped`.
|
||||||
|
const {expectedOut, _useManifestPathsAsModuleName} = config['angularCompilerOptions'];
|
||||||
|
|
||||||
const {basePath} = ng.calcProjectFileAndBasePath(project);
|
const {basePath} = ng.calcProjectFileAndBasePath(project);
|
||||||
const compilerOpts = ng.createNgCompilerOptions(basePath, config, tsOptions);
|
const compilerOpts = ng.createNgCompilerOptions(basePath, config, tsOptions);
|
||||||
const tsHost = ts.createCompilerHost(compilerOpts, true);
|
const tsHost = ts.createCompilerHost(compilerOpts, true);
|
||||||
const {diagnostics} = compile({
|
const {diagnostics} = compile({
|
||||||
allDepsCompiledWithBazel: ALL_DEPS_COMPILED_WITH_BAZEL,
|
allDepsCompiledWithBazel: ALL_DEPS_COMPILED_WITH_BAZEL,
|
||||||
compilerOpts,
|
useManifestPathsAsModuleName: _useManifestPathsAsModuleName,
|
||||||
tsHost,
|
expectedOuts: expectedOut, compilerOpts, tsHost, bazelOpts, files, inputs,
|
||||||
bazelOpts,
|
|
||||||
files,
|
|
||||||
inputs,
|
|
||||||
expectedOuts
|
|
||||||
});
|
});
|
||||||
if (diagnostics.length) {
|
if (diagnostics.length) {
|
||||||
console.error(ng.formatDiagnostics(diagnostics));
|
console.error(ng.formatDiagnostics(diagnostics));
|
||||||
@ -144,9 +142,11 @@ export function relativeToRootDirs(filePath: string, rootDirs: string[]): string
|
|||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost, bazelOpts, files,
|
export function compile({allDepsCompiledWithBazel = true, useManifestPathsAsModuleName,
|
||||||
inputs, expectedOuts, gatherDiagnostics, bazelHost}: {
|
compilerOpts, tsHost, bazelOpts, files, inputs, expectedOuts,
|
||||||
|
gatherDiagnostics, bazelHost}: {
|
||||||
allDepsCompiledWithBazel?: boolean,
|
allDepsCompiledWithBazel?: boolean,
|
||||||
|
useManifestPathsAsModuleName?: boolean,
|
||||||
compilerOpts: ng.CompilerOptions,
|
compilerOpts: ng.CompilerOptions,
|
||||||
tsHost: ts.CompilerHost, inputs?: {[path: string]: string},
|
tsHost: ts.CompilerHost, inputs?: {[path: string]: string},
|
||||||
bazelOpts: BazelOptions,
|
bazelOpts: BazelOptions,
|
||||||
@ -199,13 +199,14 @@ export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost,
|
|||||||
throw new Error(`Couldn't find bazel bin in the rootDirs: ${compilerOpts.rootDirs}`);
|
throw new Error(`Couldn't find bazel bin in the rootDirs: ${compilerOpts.rootDirs}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const expectedOutsSet = new Set(expectedOuts.map(p => p.replace(/\\/g, '/')));
|
const expectedOutsSet = new Set(expectedOuts.map(p => convertToForwardSlashPath(p)));
|
||||||
|
|
||||||
const originalWriteFile = tsHost.writeFile.bind(tsHost);
|
const originalWriteFile = tsHost.writeFile.bind(tsHost);
|
||||||
tsHost.writeFile =
|
tsHost.writeFile =
|
||||||
(fileName: string, content: string, writeByteOrderMark: boolean,
|
(fileName: string, content: string, writeByteOrderMark: boolean,
|
||||||
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
|
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
|
||||||
const relative = relativeToRootDirs(fileName.replace(/\\/g, '/'), [compilerOpts.rootDir]);
|
const relative =
|
||||||
|
relativeToRootDirs(convertToForwardSlashPath(fileName), [compilerOpts.rootDir]);
|
||||||
if (expectedOutsSet.has(relative)) {
|
if (expectedOutsSet.has(relative)) {
|
||||||
expectedOutsSet.delete(relative);
|
expectedOutsSet.delete(relative);
|
||||||
originalWriteFile(fileName, content, writeByteOrderMark, onError, sourceFiles);
|
originalWriteFile(fileName, content, writeByteOrderMark, onError, sourceFiles);
|
||||||
@ -290,20 +291,32 @@ export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost,
|
|||||||
|
|
||||||
const ngHost = ng.createCompilerHost({options: compilerOpts, tsHost: bazelHost});
|
const ngHost = ng.createCompilerHost({options: compilerOpts, tsHost: bazelHost});
|
||||||
const fileNameToModuleNameCache = new Map<string, string>();
|
const fileNameToModuleNameCache = new Map<string, string>();
|
||||||
ngHost.fileNameToModuleName = (importedFilePath: string, containingFilePath: string) => {
|
ngHost.fileNameToModuleName = (importedFilePath: string, containingFilePath?: string) => {
|
||||||
|
const cacheKey = `${importedFilePath}:${containingFilePath}`;
|
||||||
// Memoize this lookup to avoid expensive re-parses of the same file
|
// Memoize this lookup to avoid expensive re-parses of the same file
|
||||||
// When run as a worker, the actual ts.SourceFile is cached
|
// When run as a worker, the actual ts.SourceFile is cached
|
||||||
// but when we don't run as a worker, there is no cache.
|
// but when we don't run as a worker, there is no cache.
|
||||||
// For one example target in g3, we saw a cache hit rate of 7590/7695
|
// For one example target in g3, we saw a cache hit rate of 7590/7695
|
||||||
if (fileNameToModuleNameCache.has(importedFilePath)) {
|
if (fileNameToModuleNameCache.has(cacheKey)) {
|
||||||
return fileNameToModuleNameCache.get(importedFilePath);
|
return fileNameToModuleNameCache.get(cacheKey);
|
||||||
}
|
}
|
||||||
const result = doFileNameToModuleName(importedFilePath);
|
const result = doFileNameToModuleName(importedFilePath, containingFilePath);
|
||||||
fileNameToModuleNameCache.set(importedFilePath, result);
|
fileNameToModuleNameCache.set(cacheKey, result);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
function doFileNameToModuleName(importedFilePath: string): string {
|
function doFileNameToModuleName(importedFilePath: string, containingFilePath?: string): string {
|
||||||
|
const relativeTargetPath =
|
||||||
|
relativeToRootDirs(importedFilePath, compilerOpts.rootDirs).replace(EXT, '');
|
||||||
|
const manifestTargetPath = `${bazelOpts.workspaceName}/${relativeTargetPath}`;
|
||||||
|
if (useManifestPathsAsModuleName === true) {
|
||||||
|
return manifestTargetPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unless manifest paths are explicitly enforced, we initially check if a module name is
|
||||||
|
// set for the given source file. The compiler host from `@bazel/typescript` sets source
|
||||||
|
// file module names if the compilation targets either UMD or AMD. To ensure that the AMD
|
||||||
|
// module names match, we first consider those.
|
||||||
try {
|
try {
|
||||||
const sourceFile = ngHost.getSourceFile(importedFilePath, ts.ScriptTarget.Latest);
|
const sourceFile = ngHost.getSourceFile(importedFilePath, ts.ScriptTarget.Latest);
|
||||||
if (sourceFile && sourceFile.moduleName) {
|
if (sourceFile && sourceFile.moduleName) {
|
||||||
@ -342,11 +355,31 @@ export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost,
|
|||||||
ngHost.amdModuleName) {
|
ngHost.amdModuleName) {
|
||||||
return ngHost.amdModuleName({ fileName: importedFilePath } as ts.SourceFile);
|
return ngHost.amdModuleName({ fileName: importedFilePath } as ts.SourceFile);
|
||||||
}
|
}
|
||||||
const result = relativeToRootDirs(importedFilePath, compilerOpts.rootDirs).replace(EXT, '');
|
|
||||||
if (result.startsWith(NODE_MODULES)) {
|
// If no AMD module name has been set for the source file by the `@bazel/typescript` compiler
|
||||||
return result.substr(NODE_MODULES.length);
|
// host, and the target file is not part of a flat module node module package, we use the
|
||||||
|
// following rules (in order):
|
||||||
|
// 1. If target file is part of `node_modules/`, we use the package module name.
|
||||||
|
// 2. If no containing file is specified, or the target file is part of a different
|
||||||
|
// compilation unit, we use a Bazel manifest path. Relative paths are not possible
|
||||||
|
// since we don't have a containing file, and the target file could be located in the
|
||||||
|
// output directory, or in an external Bazel repository.
|
||||||
|
// 3. If both rules above didn't match, we compute a relative path between the source files
|
||||||
|
// since they are part of the same compilation unit.
|
||||||
|
// Note that we don't want to always use (2) because it could mean that compilation outputs
|
||||||
|
// are always leaking Bazel-specific paths, and the output is not self-contained. This could
|
||||||
|
// break `esm2015` or `esm5` output for Angular package release output
|
||||||
|
// Omit the `node_modules` prefix if the module name of an NPM package is requested.
|
||||||
|
if (relativeTargetPath.startsWith(NODE_MODULES)) {
|
||||||
|
return relativeTargetPath.substr(NODE_MODULES.length);
|
||||||
|
} else if (
|
||||||
|
containingFilePath == null || !bazelOpts.compilationTargetSrc.includes(importedFilePath)) {
|
||||||
|
return manifestTargetPath;
|
||||||
}
|
}
|
||||||
return bazelOpts.workspaceName + '/' + result;
|
const containingFileDir =
|
||||||
|
path.dirname(relativeToRootDirs(containingFilePath, compilerOpts.rootDirs));
|
||||||
|
const relativeImportPath = path.posix.relative(containingFileDir, relativeTargetPath);
|
||||||
|
return relativeImportPath.startsWith('.') ? relativeImportPath : `./${relativeImportPath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngHost.toSummaryFileName = (fileName: string, referringSrcFileName: string) => path.posix.join(
|
ngHost.toSummaryFileName = (fileName: string, referringSrcFileName: string) => path.posix.join(
|
||||||
@ -464,6 +497,10 @@ function isCompilationTarget(bazelOpts: BazelOptions, sf: ts.SourceFile): boolea
|
|||||||
(bazelOpts.compilationTargetSrc.indexOf(sf.fileName) !== -1);
|
(bazelOpts.compilationTargetSrc.indexOf(sf.fileName) !== -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function convertToForwardSlashPath(filePath: string): string {
|
||||||
|
return filePath.replace(/\\/g, '/');
|
||||||
|
}
|
||||||
|
|
||||||
function gatherDiagnosticsForInputsOnly(
|
function gatherDiagnosticsForInputsOnly(
|
||||||
options: ng.CompilerOptions, bazelOpts: BazelOptions,
|
options: ng.CompilerOptions, bazelOpts: BazelOptions,
|
||||||
ngProgram: ng.Program): (ng.Diagnostic | ts.Diagnostic)[] {
|
ngProgram: ng.Program): (ng.Diagnostic | ts.Diagnostic)[] {
|
||||||
|
@ -32,6 +32,7 @@ ng_package(
|
|||||||
deps = [
|
deps = [
|
||||||
":example",
|
":example",
|
||||||
"//packages/bazel/test/ng_package/example/a11y",
|
"//packages/bazel/test/ng_package/example/a11y",
|
||||||
|
"//packages/bazel/test/ng_package/example/imports",
|
||||||
"//packages/bazel/test/ng_package/example/secondary",
|
"//packages/bazel/test/ng_package/example/secondary",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
12
packages/bazel/test/ng_package/example/imports/BUILD.bazel
Normal file
12
packages/bazel/test/ng_package/example/imports/BUILD.bazel
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
load("//tools:defaults.bzl", "ng_module")
|
||||||
|
|
||||||
|
package(default_visibility = ["//packages/bazel/test:__subpackages__"])
|
||||||
|
|
||||||
|
ng_module(
|
||||||
|
name = "imports",
|
||||||
|
srcs = glob(["*.ts"]),
|
||||||
|
module_name = "example/imports",
|
||||||
|
deps = [
|
||||||
|
"//packages/core",
|
||||||
|
],
|
||||||
|
)
|
9
packages/bazel/test/ng_package/example/imports/index.ts
Normal file
9
packages/bazel/test/ng_package/example/imports/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './public-api';
|
15
packages/bazel/test/ng_package/example/imports/public-api.ts
Normal file
15
packages/bazel/test/ng_package/example/imports/public-api.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* @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 {Injectable} from '@angular/core';
|
||||||
|
import {MySecondService} from './second';
|
||||||
|
|
||||||
|
@Injectable({providedIn: 'root'})
|
||||||
|
export class MyService {
|
||||||
|
constructor(public secondService: MySecondService) {}
|
||||||
|
}
|
13
packages/bazel/test/ng_package/example/imports/second.ts
Normal file
13
packages/bazel/test/ng_package/example/imports/second.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* @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 {Injectable} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({providedIn: 'root'})
|
||||||
|
export class MySecondService {
|
||||||
|
}
|
@ -13,6 +13,10 @@ bundles
|
|||||||
bundles/waffels-a11y.umd.js.map
|
bundles/waffels-a11y.umd.js.map
|
||||||
bundles/waffels-a11y.umd.min.js
|
bundles/waffels-a11y.umd.min.js
|
||||||
bundles/waffels-a11y.umd.min.js.map
|
bundles/waffels-a11y.umd.min.js.map
|
||||||
|
bundles/waffels-imports.umd.js
|
||||||
|
bundles/waffels-imports.umd.js.map
|
||||||
|
bundles/waffels-imports.umd.min.js
|
||||||
|
bundles/waffels-imports.umd.min.js.map
|
||||||
bundles/waffels-secondary.umd.js
|
bundles/waffels-secondary.umd.js
|
||||||
bundles/waffels-secondary.umd.js.map
|
bundles/waffels-secondary.umd.js.map
|
||||||
bundles/waffels-secondary.umd.min.js
|
bundles/waffels-secondary.umd.min.js
|
||||||
@ -29,6 +33,12 @@ esm2015
|
|||||||
esm2015/a11y/public-api.js
|
esm2015/a11y/public-api.js
|
||||||
esm2015/example.externs.js
|
esm2015/example.externs.js
|
||||||
esm2015/example.js
|
esm2015/example.js
|
||||||
|
esm2015/imports
|
||||||
|
esm2015/imports/imports.externs.js
|
||||||
|
esm2015/imports/imports.js
|
||||||
|
esm2015/imports/index.js
|
||||||
|
esm2015/imports/public-api.js
|
||||||
|
esm2015/imports/second.js
|
||||||
esm2015/index.js
|
esm2015/index.js
|
||||||
esm2015/mymodule.js
|
esm2015/mymodule.js
|
||||||
esm2015/secondary
|
esm2015/secondary
|
||||||
@ -42,6 +52,11 @@ esm5
|
|||||||
esm5/a11y/index.js
|
esm5/a11y/index.js
|
||||||
esm5/a11y/public-api.js
|
esm5/a11y/public-api.js
|
||||||
esm5/example.js
|
esm5/example.js
|
||||||
|
esm5/imports
|
||||||
|
esm5/imports/imports.js
|
||||||
|
esm5/imports/index.js
|
||||||
|
esm5/imports/public-api.js
|
||||||
|
esm5/imports/second.js
|
||||||
esm5/index.js
|
esm5/index.js
|
||||||
esm5/mymodule.js
|
esm5/mymodule.js
|
||||||
esm5/secondary
|
esm5/secondary
|
||||||
@ -54,6 +69,8 @@ extra-styles.css
|
|||||||
fesm2015
|
fesm2015
|
||||||
fesm2015/a11y.js
|
fesm2015/a11y.js
|
||||||
fesm2015/a11y.js.map
|
fesm2015/a11y.js.map
|
||||||
|
fesm2015/imports.js
|
||||||
|
fesm2015/imports.js.map
|
||||||
fesm2015/secondary.js
|
fesm2015/secondary.js
|
||||||
fesm2015/secondary.js.map
|
fesm2015/secondary.js.map
|
||||||
fesm2015/waffels.js
|
fesm2015/waffels.js
|
||||||
@ -61,10 +78,18 @@ fesm2015
|
|||||||
fesm5
|
fesm5
|
||||||
fesm5/a11y.js
|
fesm5/a11y.js
|
||||||
fesm5/a11y.js.map
|
fesm5/a11y.js.map
|
||||||
|
fesm5/imports.js
|
||||||
|
fesm5/imports.js.map
|
||||||
fesm5/secondary.js
|
fesm5/secondary.js
|
||||||
fesm5/secondary.js.map
|
fesm5/secondary.js.map
|
||||||
fesm5/waffels.js
|
fesm5/waffels.js
|
||||||
fesm5/waffels.js.map
|
fesm5/waffels.js.map
|
||||||
|
imports
|
||||||
|
imports/imports.d.ts
|
||||||
|
imports/imports.metadata.json
|
||||||
|
imports/package.json
|
||||||
|
imports.d.ts
|
||||||
|
imports.metadata.json
|
||||||
logo.png
|
logo.png
|
||||||
package.json
|
package.json
|
||||||
secondary
|
secondary
|
||||||
@ -444,6 +469,291 @@ var o=function n(e,t,o,r){var f,c=arguments.length,l=c<3?t:null===r?r=Object.get
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/e.A11yModule=o,Object.defineProperty(e,"__esModule",{value:!0})}));
|
*/e.A11yModule=o,Object.defineProperty(e,"__esModule",{value:!0})}));
|
||||||
|
|
||||||
|
--- bundles/waffels-imports.umd.js ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license Angular v0.0.0
|
||||||
|
* (c) 2010-2020 Google LLC. https://angular.io/
|
||||||
|
* License: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function (global, factory) {
|
||||||
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) :
|
||||||
|
typeof define === 'function' && define.amd ? define('example/imports', ['exports', '@angular/core'], factory) :
|
||||||
|
(global = global || self, factory((global.example = global.example || {}, global.example.imports = {}), global.ng.core));
|
||||||
|
}(this, (function (exports, i0) { 'use strict';
|
||||||
|
|
||||||
|
/*! *****************************************************************************
|
||||||
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||||
|
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||||
|
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||||
|
|
||||||
|
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 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
||||||
|
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;
|
||||||
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||||
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||||
|
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 __spreadArrays() {
|
||||||
|
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
|
||||||
|
for (var r = Array(s), k = 0, i = 0; i < il; i++)
|
||||||
|
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
|
||||||
|
r[k] = a[j];
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
|
||||||
|
var MySecondService = /** @class */ (function () {
|
||||||
|
function MySecondService() {
|
||||||
|
}
|
||||||
|
MySecondService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MySecondService_Factory() { return new MySecondService(); }, token: MySecondService, providedIn: "root" });
|
||||||
|
MySecondService = __decorate([
|
||||||
|
i0.Injectable({ providedIn: 'root' })
|
||||||
|
], MySecondService);
|
||||||
|
return MySecondService;
|
||||||
|
}());
|
||||||
|
|
||||||
|
var MyService = /** @class */ (function () {
|
||||||
|
function MyService(secondService) {
|
||||||
|
this.secondService = secondService;
|
||||||
|
}
|
||||||
|
MyService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MyService_Factory() { return new MyService(i0.ɵɵinject(MySecondService)); }, token: MyService, providedIn: "root" });
|
||||||
|
MyService = __decorate([
|
||||||
|
i0.Injectable({ providedIn: 'root' }),
|
||||||
|
__metadata("design:paramtypes", [MySecondService])
|
||||||
|
], MyService);
|
||||||
|
return MyService;
|
||||||
|
}());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated bundle index. Do not edit.
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.MyService = MyService;
|
||||||
|
exports.ɵangular_packages_bazel_test_ng_package_example_imports_imports_a = MySecondService;
|
||||||
|
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
|
||||||
|
})));
|
||||||
|
//# sourceMappingURL=waffels-imports.umd.js.map
|
||||||
|
|
||||||
|
|
||||||
|
--- bundles/waffels-imports.umd.min.js ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license Angular v0.0.0
|
||||||
|
* (c) 2010-2020 Google LLC. https://angular.io/
|
||||||
|
* License: MIT
|
||||||
|
*/
|
||||||
|
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core")):"function"==typeof define&&define.amd?define("example/imports",["exports","@angular/core"],t):t(((e=e||self).example=e.example||{},e.example.imports={}),e.ng.core)}(this,(function(e,t){"use strict";
|
||||||
|
/*! *****************************************************************************
|
||||||
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||||
|
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||||
|
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||||
|
|
||||||
|
See the Apache Version 2.0 License for specific language governing permissions
|
||||||
|
and limitations under the License.
|
||||||
|
***************************************************************************** */function n(e,t,n,o){var r,c=arguments.length,f=c<3?t:null===o?o=Object.getOwnPropertyDescriptor(t,n):o;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)f=Reflect.decorate(e,t,n,o);else for(var i=e.length-1;i>=0;i--)(r=e[i])&&(f=(c<3?r(f):c>3?r(t,n,f):r(t,n))||f);return c>3&&f&&Object.defineProperty(t,n,f),f}function o(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}var r=function(){function e(){}return e.ɵprov=t.ɵɵdefineInjectable({factory:function t(){return new e},token:e,providedIn:"root"}),e=n([t.Injectable({providedIn:"root"})],e)}(),c=function(){function e(e){this.secondService=e}return e.ɵprov=t.ɵɵdefineInjectable({factory:function n(){return new e(t.ɵɵinject(r))},token:e,providedIn:"root"}),e=n([t.Injectable({providedIn:"root"}),o("design:paramtypes",[r])],e)}();
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
e.MyService=c,e.ɵangular_packages_bazel_test_ng_package_example_imports_imports_a=r,Object.defineProperty(e,"__esModule",{value:!0})}));
|
||||||
|
|
||||||
--- bundles/waffels-secondary.umd.js ---
|
--- bundles/waffels-secondary.umd.js ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1092,6 +1402,106 @@ A11yModule.decorators = [
|
|||||||
export * from './index';
|
export * from './index';
|
||||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhhbXBsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2V4YW1wbGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLFNBQVMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9pbmRleCc7XG4iXX0=
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhhbXBsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2V4YW1wbGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLFNBQVMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9pbmRleCc7XG4iXX0=
|
||||||
|
|
||||||
|
--- esm2015/imports/imports.externs.js ---
|
||||||
|
|
||||||
|
/** @externs */
|
||||||
|
/**
|
||||||
|
* @externs
|
||||||
|
* @suppress {duplicate,checkTypes}
|
||||||
|
*/
|
||||||
|
// NOTE: generated by tsickle, do not edit.
|
||||||
|
|
||||||
|
|
||||||
|
--- esm2015/imports/imports.js ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated bundle index. Do not edit.
|
||||||
|
*/
|
||||||
|
export * from './index';
|
||||||
|
export { MySecondService as ɵangular_packages_bazel_test_ng_package_example_imports_imports_a } from './second';
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1wb3J0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2ltcG9ydHMvaW1wb3J0cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILGNBQWMsU0FBUyxDQUFDO0FBRXhCLE9BQU8sRUFBQyxlQUFlLElBQUksaUVBQWlFLEVBQUMsTUFBTSxVQUFVLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEdlbmVyYXRlZCBidW5kbGUgaW5kZXguIERvIG5vdCBlZGl0LlxuICovXG5cbmV4cG9ydCAqIGZyb20gJy4vaW5kZXgnO1xuXG5leHBvcnQge015U2Vjb25kU2VydmljZSBhcyDJtWFuZ3VsYXJfcGFja2FnZXNfYmF6ZWxfdGVzdF9uZ19wYWNrYWdlX2V4YW1wbGVfaW1wb3J0c19pbXBvcnRzX2F9IGZyb20gJy4vc2Vjb25kJzsiXX0=
|
||||||
|
|
||||||
|
--- esm2015/imports/index.js ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview added by tsickle
|
||||||
|
* Generated from: packages/bazel/test/ng_package/example/imports/index.ts
|
||||||
|
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
export { MyService } from './public-api';
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9wYWNrYWdlcy9iYXplbC90ZXN0L25nX3BhY2thZ2UvZXhhbXBsZS9pbXBvcnRzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQVFBLDBCQUFjLGNBQWMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCBHb29nbGUgSW5jLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICpcbiAqIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGFuIE1JVC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlXG4gKiBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlIGF0IGh0dHBzOi8vYW5ndWxhci5pby9saWNlbnNlXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9wdWJsaWMtYXBpJztcbiJdfQ==
|
||||||
|
|
||||||
|
--- esm2015/imports/public-api.js ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview added by tsickle
|
||||||
|
* Generated from: packages/bazel/test/ng_package/example/imports/public-api.ts
|
||||||
|
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @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 { Injectable } from '@angular/core';
|
||||||
|
import { MySecondService } from './second';
|
||||||
|
import * as i0 from "@angular/core";
|
||||||
|
import * as i1 from "./second";
|
||||||
|
export class MyService {
|
||||||
|
/**
|
||||||
|
* @param {?} secondService
|
||||||
|
*/
|
||||||
|
constructor(secondService) {
|
||||||
|
this.secondService = secondService;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MyService.decorators = [
|
||||||
|
{ type: Injectable, args: [{ providedIn: 'root' },] }
|
||||||
|
];
|
||||||
|
/** @nocollapse */
|
||||||
|
MyService.ctorParameters = () => [
|
||||||
|
{ type: MySecondService }
|
||||||
|
];
|
||||||
|
/** @nocollapse */ MyService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MyService_Factory() { return new MyService(i0.ɵɵinject(i1.MySecondService)); }, token: MyService, providedIn: "root" });
|
||||||
|
if (false) {
|
||||||
|
/** @type {?} */
|
||||||
|
MyService.prototype.secondService;
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2ltcG9ydHMvcHVibGljLWFwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7QUFRQSxPQUFPLEVBQUMsVUFBVSxFQUFDLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sRUFBQyxlQUFlLEVBQUMsTUFBTSxVQUFVLENBQUM7OztBQUd6QyxNQUFNLE9BQU8sU0FBUzs7OztJQUNwQixZQUFtQixhQUE4QjtRQUE5QixrQkFBYSxHQUFiLGFBQWEsQ0FBaUI7SUFBRyxDQUFDOzs7WUFGdEQsVUFBVSxTQUFDLEVBQUMsVUFBVSxFQUFFLE1BQU0sRUFBQzs7OztZQUZ4QixlQUFlOzs7OztJQUlULGtDQUFxQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCBHb29nbGUgSW5jLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICpcbiAqIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGFuIE1JVC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlXG4gKiBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlIGF0IGh0dHBzOi8vYW5ndWxhci5pby9saWNlbnNlXG4gKi9cblxuaW1wb3J0IHtJbmplY3RhYmxlfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7TXlTZWNvbmRTZXJ2aWNlfSBmcm9tICcuL3NlY29uZCc7XG5cbkBJbmplY3RhYmxlKHtwcm92aWRlZEluOiAncm9vdCd9KVxuZXhwb3J0IGNsYXNzIE15U2VydmljZSB7XG4gIGNvbnN0cnVjdG9yKHB1YmxpYyBzZWNvbmRTZXJ2aWNlOiBNeVNlY29uZFNlcnZpY2UpIHt9XG59XG4iXX0=
|
||||||
|
|
||||||
|
--- esm2015/imports/second.js ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview added by tsickle
|
||||||
|
* Generated from: packages/bazel/test/ng_package/example/imports/second.ts
|
||||||
|
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @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 { Injectable } from '@angular/core';
|
||||||
|
import * as i0 from "@angular/core";
|
||||||
|
export class MySecondService {
|
||||||
|
}
|
||||||
|
MySecondService.decorators = [
|
||||||
|
{ type: Injectable, args: [{ providedIn: 'root' },] }
|
||||||
|
];
|
||||||
|
/** @nocollapse */ MySecondService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MySecondService_Factory() { return new MySecondService(); }, token: MySecondService, providedIn: "root" });
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Vjb25kLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vcGFja2FnZXMvYmF6ZWwvdGVzdC9uZ19wYWNrYWdlL2V4YW1wbGUvaW1wb3J0cy9zZWNvbmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBUUEsT0FBTyxFQUFDLFVBQVUsRUFBQyxNQUFNLGVBQWUsQ0FBQzs7QUFHekMsTUFBTSxPQUFPLGVBQWU7OztZQUQzQixVQUFVLFNBQUMsRUFBQyxVQUFVLEVBQUUsTUFBTSxFQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAbGljZW5zZVxuICogQ29weXJpZ2h0IEdvb2dsZSBJbmMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4gKlxuICogVXNlIG9mIHRoaXMgc291cmNlIGNvZGUgaXMgZ292ZXJuZWQgYnkgYW4gTUlULXN0eWxlIGxpY2Vuc2UgdGhhdCBjYW4gYmVcbiAqIGZvdW5kIGluIHRoZSBMSUNFTlNFIGZpbGUgYXQgaHR0cHM6Ly9hbmd1bGFyLmlvL2xpY2Vuc2VcbiAqL1xuXG5pbXBvcnQge0luamVjdGFibGV9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuXG5ASW5qZWN0YWJsZSh7cHJvdmlkZWRJbjogJ3Jvb3QnfSlcbmV4cG9ydCBjbGFzcyBNeVNlY29uZFNlcnZpY2Uge1xufVxuIl19
|
||||||
|
|
||||||
--- esm2015/index.js ---
|
--- esm2015/index.js ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1240,6 +1650,79 @@ export { A11yModule };
|
|||||||
export * from './index';
|
export * from './index';
|
||||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhhbXBsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2V4YW1wbGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLFNBQVMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9pbmRleCc7XG4iXX0=
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhhbXBsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2V4YW1wbGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLFNBQVMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9pbmRleCc7XG4iXX0=
|
||||||
|
|
||||||
|
--- esm5/imports/imports.js ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated bundle index. Do not edit.
|
||||||
|
*/
|
||||||
|
export * from './index';
|
||||||
|
export { MySecondService as ɵangular_packages_bazel_test_ng_package_example_imports_imports_a } from './second';
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1wb3J0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2ltcG9ydHMvaW1wb3J0cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILGNBQWMsU0FBUyxDQUFDO0FBRXhCLE9BQU8sRUFBQyxlQUFlLElBQUksaUVBQWlFLEVBQUMsTUFBTSxVQUFVLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEdlbmVyYXRlZCBidW5kbGUgaW5kZXguIERvIG5vdCBlZGl0LlxuICovXG5cbmV4cG9ydCAqIGZyb20gJy4vaW5kZXgnO1xuXG5leHBvcnQge015U2Vjb25kU2VydmljZSBhcyDJtWFuZ3VsYXJfcGFja2FnZXNfYmF6ZWxfdGVzdF9uZ19wYWNrYWdlX2V4YW1wbGVfaW1wb3J0c19pbXBvcnRzX2F9IGZyb20gJy4vc2Vjb25kJzsiXX0=
|
||||||
|
|
||||||
|
--- esm5/imports/index.js ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
export * from './public-api';
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9wYWNrYWdlcy9iYXplbC90ZXN0L25nX3BhY2thZ2UvZXhhbXBsZS9pbXBvcnRzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7R0FNRztBQUVILGNBQWMsY0FBYyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAbGljZW5zZVxuICogQ29weXJpZ2h0IEdvb2dsZSBJbmMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4gKlxuICogVXNlIG9mIHRoaXMgc291cmNlIGNvZGUgaXMgZ292ZXJuZWQgYnkgYW4gTUlULXN0eWxlIGxpY2Vuc2UgdGhhdCBjYW4gYmVcbiAqIGZvdW5kIGluIHRoZSBMSUNFTlNFIGZpbGUgYXQgaHR0cHM6Ly9hbmd1bGFyLmlvL2xpY2Vuc2VcbiAqL1xuXG5leHBvcnQgKiBmcm9tICcuL3B1YmxpYy1hcGknO1xuIl19
|
||||||
|
|
||||||
|
--- esm5/imports/public-api.js ---
|
||||||
|
|
||||||
|
import { __decorate, __metadata } from "tslib";
|
||||||
|
/**
|
||||||
|
* @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 { Injectable } from '@angular/core';
|
||||||
|
import { MySecondService } from './second';
|
||||||
|
import * as i0 from "@angular/core";
|
||||||
|
import * as i1 from "./second";
|
||||||
|
var MyService = /** @class */ (function () {
|
||||||
|
function MyService(secondService) {
|
||||||
|
this.secondService = secondService;
|
||||||
|
}
|
||||||
|
MyService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MyService_Factory() { return new MyService(i0.ɵɵinject(i1.MySecondService)); }, token: MyService, providedIn: "root" });
|
||||||
|
MyService = __decorate([
|
||||||
|
Injectable({ providedIn: 'root' }),
|
||||||
|
__metadata("design:paramtypes", [MySecondService])
|
||||||
|
], MyService);
|
||||||
|
return MyService;
|
||||||
|
}());
|
||||||
|
export { MyService };
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2ltcG9ydHMvcHVibGljLWFwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7OztHQU1HO0FBRUgsT0FBTyxFQUFDLFVBQVUsRUFBQyxNQUFNLGVBQWUsQ0FBQztBQUN6QyxPQUFPLEVBQUMsZUFBZSxFQUFDLE1BQU0sVUFBVSxDQUFDOzs7QUFHekM7SUFDRSxtQkFBbUIsYUFBOEI7UUFBOUIsa0JBQWEsR0FBYixhQUFhLENBQWlCO0lBQUcsQ0FBQzs7SUFEMUMsU0FBUztRQURyQixVQUFVLENBQUMsRUFBQyxVQUFVLEVBQUUsTUFBTSxFQUFDLENBQUM7eUNBRUcsZUFBZTtPQUR0QyxTQUFTLENBRXJCO29CQWREO0NBY0MsQUFGRCxJQUVDO1NBRlksU0FBUyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCBHb29nbGUgSW5jLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICpcbiAqIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGFuIE1JVC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlXG4gKiBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlIGF0IGh0dHBzOi8vYW5ndWxhci5pby9saWNlbnNlXG4gKi9cblxuaW1wb3J0IHtJbmplY3RhYmxlfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7TXlTZWNvbmRTZXJ2aWNlfSBmcm9tICcuL3NlY29uZCc7XG5cbkBJbmplY3RhYmxlKHtwcm92aWRlZEluOiAncm9vdCd9KVxuZXhwb3J0IGNsYXNzIE15U2VydmljZSB7XG4gIGNvbnN0cnVjdG9yKHB1YmxpYyBzZWNvbmRTZXJ2aWNlOiBNeVNlY29uZFNlcnZpY2UpIHt9XG59XG4iXX0=
|
||||||
|
|
||||||
|
--- esm5/imports/second.js ---
|
||||||
|
|
||||||
|
import { __decorate } from "tslib";
|
||||||
|
/**
|
||||||
|
* @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 { Injectable } from '@angular/core';
|
||||||
|
import * as i0 from "@angular/core";
|
||||||
|
var MySecondService = /** @class */ (function () {
|
||||||
|
function MySecondService() {
|
||||||
|
}
|
||||||
|
MySecondService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MySecondService_Factory() { return new MySecondService(); }, token: MySecondService, providedIn: "root" });
|
||||||
|
MySecondService = __decorate([
|
||||||
|
Injectable({ providedIn: 'root' })
|
||||||
|
], MySecondService);
|
||||||
|
return MySecondService;
|
||||||
|
}());
|
||||||
|
export { MySecondService };
|
||||||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Vjb25kLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vcGFja2FnZXMvYmF6ZWwvdGVzdC9uZ19wYWNrYWdlL2V4YW1wbGUvaW1wb3J0cy9zZWNvbmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Ozs7R0FNRztBQUVILE9BQU8sRUFBQyxVQUFVLEVBQUMsTUFBTSxlQUFlLENBQUM7O0FBR3pDO0lBQUE7S0FDQzs7SUFEWSxlQUFlO1FBRDNCLFVBQVUsQ0FBQyxFQUFDLFVBQVUsRUFBRSxNQUFNLEVBQUMsQ0FBQztPQUNwQixlQUFlLENBQzNCOzBCQVpEO0NBWUMsQUFERCxJQUNDO1NBRFksZUFBZSIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCBHb29nbGUgSW5jLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICpcbiAqIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGFuIE1JVC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlXG4gKiBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlIGF0IGh0dHBzOi8vYW5ndWxhci5pby9saWNlbnNlXG4gKi9cblxuaW1wb3J0IHtJbmplY3RhYmxlfSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuQEluamVjdGFibGUoe3Byb3ZpZGVkSW46ICdyb290J30pXG5leHBvcnQgY2xhc3MgTXlTZWNvbmRTZXJ2aWNlIHtcbn1cbiJdfQ==
|
||||||
|
|
||||||
--- esm5/index.js ---
|
--- esm5/index.js ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1379,6 +1862,68 @@ export { A11yModule };
|
|||||||
//# sourceMappingURL=a11y.js.map
|
//# sourceMappingURL=a11y.js.map
|
||||||
|
|
||||||
|
|
||||||
|
--- fesm2015/imports.js ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license Angular v0.0.0
|
||||||
|
* (c) 2010-2020 Google LLC. https://angular.io/
|
||||||
|
* License: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable, ɵɵdefineInjectable, ɵɵinject } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview added by tsickle
|
||||||
|
* Generated from: packages/bazel/test/ng_package/example/imports/second.ts
|
||||||
|
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||||
|
*/
|
||||||
|
class MySecondService {
|
||||||
|
}
|
||||||
|
MySecondService.decorators = [
|
||||||
|
{ type: Injectable, args: [{ providedIn: 'root' },] }
|
||||||
|
];
|
||||||
|
/** @nocollapse */ MySecondService.ɵprov = ɵɵdefineInjectable({ factory: function MySecondService_Factory() { return new MySecondService(); }, token: MySecondService, providedIn: "root" });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview added by tsickle
|
||||||
|
* Generated from: packages/bazel/test/ng_package/example/imports/public-api.ts
|
||||||
|
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||||
|
*/
|
||||||
|
class MyService {
|
||||||
|
/**
|
||||||
|
* @param {?} secondService
|
||||||
|
*/
|
||||||
|
constructor(secondService) {
|
||||||
|
this.secondService = secondService;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MyService.decorators = [
|
||||||
|
{ type: Injectable, args: [{ providedIn: 'root' },] }
|
||||||
|
];
|
||||||
|
/** @nocollapse */
|
||||||
|
MyService.ctorParameters = () => [
|
||||||
|
{ type: MySecondService }
|
||||||
|
];
|
||||||
|
/** @nocollapse */ MyService.ɵprov = ɵɵdefineInjectable({ factory: function MyService_Factory() { return new MyService(ɵɵinject(MySecondService)); }, token: MyService, providedIn: "root" });
|
||||||
|
if (false) {
|
||||||
|
/** @type {?} */
|
||||||
|
MyService.prototype.secondService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview added by tsickle
|
||||||
|
* Generated from: packages/bazel/test/ng_package/example/imports/index.ts
|
||||||
|
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated bundle index. Do not edit.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { MyService, MySecondService as ɵangular_packages_bazel_test_ng_package_example_imports_imports_a };
|
||||||
|
//# sourceMappingURL=imports.js.map
|
||||||
|
|
||||||
|
|
||||||
--- fesm2015/secondary.js ---
|
--- fesm2015/secondary.js ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1494,6 +2039,55 @@ export { A11yModule };
|
|||||||
//# sourceMappingURL=a11y.js.map
|
//# sourceMappingURL=a11y.js.map
|
||||||
|
|
||||||
|
|
||||||
|
--- fesm5/imports.js ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license Angular v0.0.0
|
||||||
|
* (c) 2010-2020 Google LLC. https://angular.io/
|
||||||
|
* License: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { __decorate, __metadata } from 'tslib';
|
||||||
|
import { ɵɵdefineInjectable, Injectable, ɵɵinject } from '@angular/core';
|
||||||
|
|
||||||
|
var MySecondService = /** @class */ (function () {
|
||||||
|
function MySecondService() {
|
||||||
|
}
|
||||||
|
MySecondService.ɵprov = ɵɵdefineInjectable({ factory: function MySecondService_Factory() { return new MySecondService(); }, token: MySecondService, providedIn: "root" });
|
||||||
|
MySecondService = __decorate([
|
||||||
|
Injectable({ providedIn: 'root' })
|
||||||
|
], MySecondService);
|
||||||
|
return MySecondService;
|
||||||
|
}());
|
||||||
|
|
||||||
|
var MyService = /** @class */ (function () {
|
||||||
|
function MyService(secondService) {
|
||||||
|
this.secondService = secondService;
|
||||||
|
}
|
||||||
|
MyService.ɵprov = ɵɵdefineInjectable({ factory: function MyService_Factory() { return new MyService(ɵɵinject(MySecondService)); }, token: MyService, providedIn: "root" });
|
||||||
|
MyService = __decorate([
|
||||||
|
Injectable({ providedIn: 'root' }),
|
||||||
|
__metadata("design:paramtypes", [MySecondService])
|
||||||
|
], MyService);
|
||||||
|
return MyService;
|
||||||
|
}());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated bundle index. Do not edit.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { MyService, MySecondService as ɵangular_packages_bazel_test_ng_package_example_imports_imports_a };
|
||||||
|
//# sourceMappingURL=imports.js.map
|
||||||
|
|
||||||
|
|
||||||
--- fesm5/secondary.js ---
|
--- fesm5/secondary.js ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1581,6 +2175,61 @@ export { MyModule };
|
|||||||
//# sourceMappingURL=waffels.js.map
|
//# sourceMappingURL=waffels.js.map
|
||||||
|
|
||||||
|
|
||||||
|
--- imports/imports.d.ts ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license Angular v0.0.0
|
||||||
|
* (c) 2010-2020 Google LLC. https://angular.io/
|
||||||
|
* License: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
export declare class MyService {
|
||||||
|
secondService: ɵangular_packages_bazel_test_ng_package_example_imports_imports_a;
|
||||||
|
constructor(secondService: ɵangular_packages_bazel_test_ng_package_example_imports_imports_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export declare class ɵangular_packages_bazel_test_ng_package_example_imports_imports_a {
|
||||||
|
}
|
||||||
|
|
||||||
|
export { }
|
||||||
|
|
||||||
|
|
||||||
|
--- imports/imports.metadata.json ---
|
||||||
|
|
||||||
|
{"__symbolic":"module","version":4,"metadata":{"MyService":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Injectable","line":11,"character":1},"arguments":[{"providedIn":"root"}]}],"members":{"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","name":"ɵangular_packages_bazel_test_ng_package_example_imports_imports_a"}]}]},"statics":{"ɵprov":{}}},"ɵangular_packages_bazel_test_ng_package_example_imports_imports_a":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Injectable","line":10,"character":1},"arguments":[{"providedIn":"root"}]}],"members":{},"statics":{"ɵprov":{}}}},"origins":{"MyService":"./imports","ɵangular_packages_bazel_test_ng_package_example_imports_imports_a":"./imports"},"importAs":"example/imports"}
|
||||||
|
|
||||||
|
--- imports/package.json ---
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "example/imports",
|
||||||
|
"main": "../bundles/example-imports.umd.js",
|
||||||
|
"fesm5": "../fesm5/imports.js",
|
||||||
|
"fesm2015": "../fesm2015/imports.js",
|
||||||
|
"esm5": "../esm5/imports/imports.js",
|
||||||
|
"esm2015": "../esm2015/imports/imports.js",
|
||||||
|
"typings": "./imports.d.ts",
|
||||||
|
"module": "../fesm5/imports.js",
|
||||||
|
"es2015": "../fesm2015/imports.js"
|
||||||
|
}
|
||||||
|
|
||||||
|
--- imports.d.ts ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license Angular v0.0.0
|
||||||
|
* (c) 2010-2020 Google LLC. https://angular.io/
|
||||||
|
* License: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './imports/imports';
|
||||||
|
|
||||||
|
|
||||||
|
--- imports.metadata.json ---
|
||||||
|
|
||||||
|
{"__symbolic":"module","version":3,"metadata":{},"exports":[{"from":"./imports/imports"}],"flatModuleIndexRedirect":true,"importAs":"example/imports"}
|
||||||
|
|
||||||
|
|
||||||
--- logo.png ---
|
--- logo.png ---
|
||||||
|
|
||||||
9db278d630f5fabd8e7ba16c2e329a3a
|
9db278d630f5fabd8e7ba16c2e329a3a
|
||||||
|
@ -9,8 +9,9 @@ import {AbsoluteFsPath, FileSystem, join, resolve} from '../../../src/ngtsc/file
|
|||||||
import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver';
|
import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver';
|
||||||
import {Logger} from '../logging/logger';
|
import {Logger} from '../logging/logger';
|
||||||
import {NgccConfiguration} from '../packages/configuration';
|
import {NgccConfiguration} from '../packages/configuration';
|
||||||
import {EntryPoint, getEntryPointInfo} from '../packages/entry_point';
|
import {EntryPoint, INVALID_ENTRY_POINT, NO_ENTRY_POINT, getEntryPointInfo} from '../packages/entry_point';
|
||||||
import {PathMappings} from '../utils';
|
import {PathMappings} from '../utils';
|
||||||
|
import {NGCC_DIRECTORY} from '../writing/new_entry_point_file_writer';
|
||||||
import {EntryPointFinder} from './interface';
|
import {EntryPointFinder} from './interface';
|
||||||
import {getBasePaths} from './utils';
|
import {getBasePaths} from './utils';
|
||||||
|
|
||||||
@ -40,10 +41,24 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
|
|||||||
* The function will recurse into directories that start with `@...`, e.g. `@angular/...`.
|
* The function will recurse into directories that start with `@...`, e.g. `@angular/...`.
|
||||||
* @param sourceDirectory An absolute path to the root directory where searching begins.
|
* @param sourceDirectory An absolute path to the root directory where searching begins.
|
||||||
*/
|
*/
|
||||||
private walkDirectoryForEntryPoints(sourceDirectory: AbsoluteFsPath): EntryPoint[] {
|
walkDirectoryForEntryPoints(sourceDirectory: AbsoluteFsPath): EntryPoint[] {
|
||||||
const entryPoints = this.getEntryPointsForPackage(sourceDirectory);
|
const entryPoints = this.getEntryPointsForPackage(sourceDirectory);
|
||||||
|
if (entryPoints === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
if (entryPoints.length > 0) {
|
if (entryPoints.length > 0) {
|
||||||
// The `sourceDirectory` is an entry-point itself so no need to search its sub-directories.
|
// The `sourceDirectory` is an entry point itself so no need to search its sub-directories.
|
||||||
|
// Also check for any nested node_modules in this package but only if it was compiled by
|
||||||
|
// Angular.
|
||||||
|
// It is unlikely that a non Angular entry point has a dependency on an Angular library.
|
||||||
|
if (entryPoints.some(e => e.compiledByAngular)) {
|
||||||
|
const nestedNodeModulesPath = this.fs.join(sourceDirectory, 'node_modules');
|
||||||
|
if (this.fs.exists(nestedNodeModulesPath)) {
|
||||||
|
entryPoints.push(...this.walkDirectoryForEntryPoints(nestedNodeModulesPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return entryPoints;
|
return entryPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,23 +67,16 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
|
|||||||
// Not interested in hidden files
|
// Not interested in hidden files
|
||||||
.filter(p => !p.startsWith('.'))
|
.filter(p => !p.startsWith('.'))
|
||||||
// Ignore node_modules
|
// Ignore node_modules
|
||||||
.filter(p => p !== 'node_modules')
|
.filter(p => p !== 'node_modules' && p !== NGCC_DIRECTORY)
|
||||||
// Only interested in directories (and only those that are not symlinks)
|
// Only interested in directories (and only those that are not symlinks)
|
||||||
.filter(p => {
|
.filter(p => {
|
||||||
const stat = this.fs.lstat(resolve(sourceDirectory, p));
|
const stat = this.fs.lstat(resolve(sourceDirectory, p));
|
||||||
return stat.isDirectory() && !stat.isSymbolicLink();
|
return stat.isDirectory() && !stat.isSymbolicLink();
|
||||||
})
|
})
|
||||||
.forEach(p => {
|
.forEach(p => {
|
||||||
// Either the directory is a potential package or a namespace containing packages (e.g
|
// Package is a potential namespace containing packages (e.g `@angular`).
|
||||||
// `@angular`).
|
|
||||||
const packagePath = join(sourceDirectory, p);
|
const packagePath = join(sourceDirectory, p);
|
||||||
entryPoints.push(...this.walkDirectoryForEntryPoints(packagePath));
|
entryPoints.push(...this.walkDirectoryForEntryPoints(packagePath));
|
||||||
|
|
||||||
// Also check for any nested node_modules in this package
|
|
||||||
const nestedNodeModulesPath = join(packagePath, 'node_modules');
|
|
||||||
if (this.fs.exists(nestedNodeModulesPath)) {
|
|
||||||
entryPoints.push(...this.walkDirectoryForEntryPoints(nestedNodeModulesPath));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return entryPoints;
|
return entryPoints;
|
||||||
}
|
}
|
||||||
@ -76,9 +84,9 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
|
|||||||
/**
|
/**
|
||||||
* Recurse the folder structure looking for all the entry points
|
* Recurse the folder structure looking for all the entry points
|
||||||
* @param packagePath The absolute path to an npm package that may contain entry points
|
* @param packagePath The absolute path to an npm package that may contain entry points
|
||||||
* @returns An array of entry points that were discovered.
|
* @returns An array of entry points that were discovered or null when it's not a valid entrypoint
|
||||||
*/
|
*/
|
||||||
private getEntryPointsForPackage(packagePath: AbsoluteFsPath): EntryPoint[] {
|
private getEntryPointsForPackage(packagePath: AbsoluteFsPath): EntryPoint[]|null {
|
||||||
const entryPoints: EntryPoint[] = [];
|
const entryPoints: EntryPoint[] = [];
|
||||||
|
|
||||||
// Try to get an entry point from the top level package directory
|
// Try to get an entry point from the top level package directory
|
||||||
@ -86,20 +94,30 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
|
|||||||
getEntryPointInfo(this.fs, this.config, this.logger, packagePath, packagePath);
|
getEntryPointInfo(this.fs, this.config, this.logger, packagePath, packagePath);
|
||||||
|
|
||||||
// If there is no primary entry-point then exit
|
// If there is no primary entry-point then exit
|
||||||
if (topLevelEntryPoint === null) {
|
if (topLevelEntryPoint === NO_ENTRY_POINT) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (topLevelEntryPoint === INVALID_ENTRY_POINT) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise store it and search for secondary entry-points
|
// Otherwise store it and search for secondary entry-points
|
||||||
entryPoints.push(topLevelEntryPoint);
|
entryPoints.push(topLevelEntryPoint);
|
||||||
this.walkDirectory(packagePath, packagePath, (path, isDirectory) => {
|
this.walkDirectory(packagePath, packagePath, (path, isDirectory) => {
|
||||||
|
if (!path.endsWith('.js') && !isDirectory) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// If the path is a JS file then strip its extension and see if we can match an entry-point.
|
// If the path is a JS file then strip its extension and see if we can match an entry-point.
|
||||||
const possibleEntryPointPath = isDirectory ? path : stripJsExtension(path);
|
const possibleEntryPointPath = isDirectory ? path : stripJsExtension(path);
|
||||||
const subEntryPoint =
|
const subEntryPoint =
|
||||||
getEntryPointInfo(this.fs, this.config, this.logger, packagePath, possibleEntryPointPath);
|
getEntryPointInfo(this.fs, this.config, this.logger, packagePath, possibleEntryPointPath);
|
||||||
if (subEntryPoint !== null) {
|
if (subEntryPoint === NO_ENTRY_POINT || subEntryPoint === INVALID_ENTRY_POINT) {
|
||||||
entryPoints.push(subEntryPoint);
|
return false;
|
||||||
}
|
}
|
||||||
|
entryPoints.push(subEntryPoint);
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
return entryPoints;
|
return entryPoints;
|
||||||
@ -113,26 +131,25 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
|
|||||||
*/
|
*/
|
||||||
private walkDirectory(
|
private walkDirectory(
|
||||||
packagePath: AbsoluteFsPath, dir: AbsoluteFsPath,
|
packagePath: AbsoluteFsPath, dir: AbsoluteFsPath,
|
||||||
fn: (path: AbsoluteFsPath, isDirectory: boolean) => void) {
|
fn: (path: AbsoluteFsPath, isDirectory: boolean) => boolean) {
|
||||||
return this.fs
|
return this.fs
|
||||||
.readdir(dir)
|
.readdir(dir)
|
||||||
// Not interested in hidden files
|
// Not interested in hidden files
|
||||||
.filter(path => !path.startsWith('.'))
|
.filter(path => !path.startsWith('.'))
|
||||||
// Ignore node_modules
|
// Ignore node_modules
|
||||||
.filter(path => path !== 'node_modules')
|
.filter(path => path !== 'node_modules' && path !== NGCC_DIRECTORY)
|
||||||
.map(path => resolve(dir, path))
|
|
||||||
.forEach(path => {
|
.forEach(path => {
|
||||||
const stat = this.fs.lstat(path);
|
const absolutePath = resolve(dir, path);
|
||||||
|
const stat = this.fs.lstat(absolutePath);
|
||||||
|
|
||||||
if (stat.isSymbolicLink()) {
|
if (stat.isSymbolicLink()) {
|
||||||
// We are not interested in symbolic links
|
// We are not interested in symbolic links
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn(path, stat.isDirectory());
|
const containsEntryPoint = fn(absolutePath, stat.isDirectory());
|
||||||
|
if (containsEntryPoint) {
|
||||||
if (stat.isDirectory()) {
|
this.walkDirectory(packagePath, absolutePath, fn);
|
||||||
this.walkDirectory(packagePath, path, fn);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/depende
|
|||||||
import {Logger} from '../logging/logger';
|
import {Logger} from '../logging/logger';
|
||||||
import {hasBeenProcessed} from '../packages/build_marker';
|
import {hasBeenProcessed} from '../packages/build_marker';
|
||||||
import {NgccConfiguration} from '../packages/configuration';
|
import {NgccConfiguration} from '../packages/configuration';
|
||||||
import {EntryPoint, EntryPointJsonProperty, getEntryPointInfo} from '../packages/entry_point';
|
import {EntryPoint, EntryPointJsonProperty, INVALID_ENTRY_POINT, NO_ENTRY_POINT, getEntryPointInfo} from '../packages/entry_point';
|
||||||
import {PathMappings} from '../utils';
|
import {PathMappings} from '../utils';
|
||||||
import {EntryPointFinder} from './interface';
|
import {EntryPointFinder} from './interface';
|
||||||
import {getBasePaths} from './utils';
|
import {getBasePaths} from './utils';
|
||||||
@ -78,20 +78,26 @@ export class TargetedEntryPointFinder implements EntryPointFinder {
|
|||||||
private processNextPath(): void {
|
private processNextPath(): void {
|
||||||
const path = this.unprocessedPaths.shift() !;
|
const path = this.unprocessedPaths.shift() !;
|
||||||
const entryPoint = this.getEntryPoint(path);
|
const entryPoint = this.getEntryPoint(path);
|
||||||
if (entryPoint !== null && entryPoint.compiledByAngular) {
|
if (entryPoint === null || !entryPoint.compiledByAngular) {
|
||||||
this.unsortedEntryPoints.set(entryPoint.path, entryPoint);
|
return;
|
||||||
const deps = this.resolver.getEntryPointDependencies(entryPoint);
|
|
||||||
deps.dependencies.forEach(dep => {
|
|
||||||
if (!this.unsortedEntryPoints.has(dep)) {
|
|
||||||
this.unprocessedPaths.push(dep);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
this.unsortedEntryPoints.set(entryPoint.path, entryPoint);
|
||||||
|
const deps = this.resolver.getEntryPointDependencies(entryPoint);
|
||||||
|
deps.dependencies.forEach(dep => {
|
||||||
|
if (!this.unsortedEntryPoints.has(dep)) {
|
||||||
|
this.unprocessedPaths.push(dep);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getEntryPoint(entryPointPath: AbsoluteFsPath): EntryPoint|null {
|
private getEntryPoint(entryPointPath: AbsoluteFsPath): EntryPoint|null {
|
||||||
const packagePath = this.computePackagePath(entryPointPath);
|
const packagePath = this.computePackagePath(entryPointPath);
|
||||||
return getEntryPointInfo(this.fs, this.config, this.logger, packagePath, entryPointPath);
|
const entryPoint =
|
||||||
|
getEntryPointInfo(this.fs, this.config, this.logger, packagePath, entryPointPath);
|
||||||
|
if (entryPoint === NO_ENTRY_POINT || entryPoint === INVALID_ENTRY_POINT) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return entryPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -75,41 +75,77 @@ export type EntryPointJsonProperty = Exclude<PackageJsonFormatProperties, 'types
|
|||||||
export const SUPPORTED_FORMAT_PROPERTIES: EntryPointJsonProperty[] =
|
export const SUPPORTED_FORMAT_PROPERTIES: EntryPointJsonProperty[] =
|
||||||
['fesm2015', 'fesm5', 'es2015', 'esm2015', 'esm5', 'main', 'module'];
|
['fesm2015', 'fesm5', 'es2015', 'esm2015', 'esm5', 'main', 'module'];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path does not represent an entry-point:
|
||||||
|
* * there is no package.json at the path and there is no config to force an entry-point
|
||||||
|
* * or the entrypoint is `ignored` by a config.
|
||||||
|
*/
|
||||||
|
export const NO_ENTRY_POINT = 'no-entry-point';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path has a package.json, but it is not a valid entry-point for ngcc processing.
|
||||||
|
*/
|
||||||
|
export const INVALID_ENTRY_POINT = 'invalid-entry-point';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of calling `getEntryPointInfo()`.
|
||||||
|
*
|
||||||
|
* This will be an `EntryPoint` object if an Angular entry-point was identified;
|
||||||
|
* Otherwise it will be a flag indicating one of:
|
||||||
|
* * NO_ENTRY_POINT - the path is not an entry-point or ngcc is configured to ignore it
|
||||||
|
* * INVALID_ENTRY_POINT - the path was a non-processable entry-point that should be searched
|
||||||
|
* for sub-entry-points
|
||||||
|
*/
|
||||||
|
export type GetEntryPointResult = EntryPoint | typeof INVALID_ENTRY_POINT | typeof NO_ENTRY_POINT;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to create an entry-point from the given paths and properties.
|
* Try to create an entry-point from the given paths and properties.
|
||||||
*
|
*
|
||||||
* @param packagePath the absolute path to the containing npm package
|
* @param packagePath the absolute path to the containing npm package
|
||||||
* @param entryPointPath the absolute path to the potential entry-point.
|
* @param entryPointPath the absolute path to the potential entry-point.
|
||||||
* @returns An entry-point if it is valid, `null` otherwise.
|
* @returns
|
||||||
|
* - An entry-point if it is valid.
|
||||||
|
* - `undefined` when there is no package.json at the path and there is no config to force an
|
||||||
|
* entry-point or the entrypoint is `ignored`.
|
||||||
|
* - `null` there is a package.json but it is not a valid Angular compiled entry-point.
|
||||||
*/
|
*/
|
||||||
export function getEntryPointInfo(
|
export function getEntryPointInfo(
|
||||||
fs: FileSystem, config: NgccConfiguration, logger: Logger, packagePath: AbsoluteFsPath,
|
fs: FileSystem, config: NgccConfiguration, logger: Logger, packagePath: AbsoluteFsPath,
|
||||||
entryPointPath: AbsoluteFsPath): EntryPoint|null {
|
entryPointPath: AbsoluteFsPath): GetEntryPointResult {
|
||||||
const packageJsonPath = resolve(entryPointPath, 'package.json');
|
const packageJsonPath = resolve(entryPointPath, 'package.json');
|
||||||
const packageVersion = getPackageVersion(fs, packageJsonPath);
|
const packageVersion = getPackageVersion(fs, packageJsonPath);
|
||||||
const entryPointConfig =
|
const entryPointConfig =
|
||||||
config.getConfig(packagePath, packageVersion).entryPoints[entryPointPath];
|
config.getConfig(packagePath, packageVersion).entryPoints[entryPointPath];
|
||||||
if (entryPointConfig === undefined && !fs.exists(packageJsonPath)) {
|
const hasConfig = entryPointConfig !== undefined;
|
||||||
return null;
|
|
||||||
|
if (!hasConfig && !fs.exists(packageJsonPath)) {
|
||||||
|
// No package.json and no config
|
||||||
|
return NO_ENTRY_POINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entryPointConfig !== undefined && entryPointConfig.ignore === true) {
|
if (hasConfig && entryPointConfig.ignore === true) {
|
||||||
return null;
|
// Explicitly ignored
|
||||||
|
return NO_ENTRY_POINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadedEntryPointPackageJson =
|
const loadedEntryPointPackageJson = loadEntryPointPackage(fs, logger, packageJsonPath, hasConfig);
|
||||||
loadEntryPointPackage(fs, logger, packageJsonPath, entryPointConfig !== undefined);
|
const entryPointPackageJson = hasConfig ?
|
||||||
const entryPointPackageJson = mergeConfigAndPackageJson(
|
mergeConfigAndPackageJson(
|
||||||
loadedEntryPointPackageJson, entryPointConfig, packagePath, entryPointPath);
|
loadedEntryPointPackageJson, entryPointConfig, packagePath, entryPointPath) :
|
||||||
|
loadedEntryPointPackageJson;
|
||||||
|
|
||||||
if (entryPointPackageJson === null) {
|
if (entryPointPackageJson === null) {
|
||||||
return null;
|
// package.json exists but could not be parsed and there was no redeeming config
|
||||||
|
return INVALID_ENTRY_POINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We must have a typings property
|
|
||||||
const typings = entryPointPackageJson.typings || entryPointPackageJson.types ||
|
const typings = entryPointPackageJson.typings || entryPointPackageJson.types ||
|
||||||
guessTypingsFromPackageJson(fs, entryPointPath, entryPointPackageJson);
|
guessTypingsFromPackageJson(fs, entryPointPath, entryPointPackageJson);
|
||||||
if (typeof typings !== 'string') {
|
if (typeof typings !== 'string') {
|
||||||
return null;
|
// Missing the required `typings` property
|
||||||
|
return INVALID_ENTRY_POINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// An entry-point is assumed to be compiled by Angular if there is either:
|
// An entry-point is assumed to be compiled by Angular if there is either:
|
||||||
@ -198,22 +234,13 @@ function isUmdModule(fs: FileSystem, sourceFilePath: AbsoluteFsPath): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mergeConfigAndPackageJson(
|
function mergeConfigAndPackageJson(
|
||||||
entryPointPackageJson: EntryPointPackageJson | null,
|
entryPointPackageJson: EntryPointPackageJson | null, entryPointConfig: NgccEntryPointConfig,
|
||||||
entryPointConfig: NgccEntryPointConfig | undefined, packagePath: AbsoluteFsPath,
|
packagePath: AbsoluteFsPath, entryPointPath: AbsoluteFsPath): EntryPointPackageJson {
|
||||||
entryPointPath: AbsoluteFsPath): EntryPointPackageJson|null {
|
|
||||||
if (entryPointPackageJson !== null) {
|
if (entryPointPackageJson !== null) {
|
||||||
if (entryPointConfig === undefined) {
|
return {...entryPointPackageJson, ...entryPointConfig.override};
|
||||||
return entryPointPackageJson;
|
|
||||||
} else {
|
|
||||||
return {...entryPointPackageJson, ...entryPointConfig.override};
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (entryPointConfig === undefined) {
|
const name = `${basename(packagePath)}/${relative(packagePath, entryPointPath)}`;
|
||||||
return null;
|
return {name, ...entryPointConfig.override};
|
||||||
} else {
|
|
||||||
const name = `${basename(packagePath)}/${relative(packagePath, entryPointPath)}`;
|
|
||||||
return {name, ...entryPointConfig.override};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +136,71 @@ runInEachFileSystem(() => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle try to process nested node_modules of non Angular packages', () => {
|
||||||
|
const basePath = _Abs('/nested_node_modules/node_modules');
|
||||||
|
loadTestFiles([
|
||||||
|
...createPackage(basePath, 'outer', ['inner'], false),
|
||||||
|
...createPackage(_Abs(`${basePath}/outer/node_modules`), 'inner', undefined, false),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const finder = new DirectoryWalkerEntryPointFinder(
|
||||||
|
fs, config, logger, resolver, _Abs('/nested_node_modules/node_modules'), undefined);
|
||||||
|
const spy = spyOn(finder, 'walkDirectoryForEntryPoints').and.callThrough();
|
||||||
|
const {entryPoints} = finder.findEntryPoints();
|
||||||
|
expect(spy.calls.allArgs()).toEqual([
|
||||||
|
[_Abs(basePath)],
|
||||||
|
[_Abs(`${basePath}/outer`)],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(entryPoints).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not try to process deeply nested folders of non TypeScript packages', () => {
|
||||||
|
const basePath = _Abs('/namespaced/node_modules');
|
||||||
|
loadTestFiles([
|
||||||
|
...createNonTsPackage(_Abs(`${basePath}/@schematics`), 'angular'),
|
||||||
|
{
|
||||||
|
name: _Abs(`${basePath}/@schematics/angular/src/nested/index.js`),
|
||||||
|
contents: 'index',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const finder =
|
||||||
|
new DirectoryWalkerEntryPointFinder(fs, config, logger, resolver, basePath, undefined);
|
||||||
|
const spy = spyOn(finder, 'walkDirectoryForEntryPoints').and.callThrough();
|
||||||
|
const {entryPoints} = finder.findEntryPoints();
|
||||||
|
expect(spy.calls.allArgs()).toEqual([
|
||||||
|
[_Abs(basePath)],
|
||||||
|
[_Abs(`${basePath}/@schematics`)],
|
||||||
|
[_Abs(`${basePath}/@schematics/angular`)],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(entryPoints).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not try to process nested node_modules of non TypeScript packages', () => {
|
||||||
|
const basePath = _Abs('/namespaced/node_modules');
|
||||||
|
loadTestFiles([
|
||||||
|
...createNonTsPackage(_Abs(`${basePath}/@schematics`), 'angular'),
|
||||||
|
...createNonTsPackage(_Abs(`${basePath}/@schematics/angular/node_modules`), 'test'),
|
||||||
|
{
|
||||||
|
name: _Abs(`${basePath}/@schematics/angular/src/nested/index.js`),
|
||||||
|
contents: 'index',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const finder =
|
||||||
|
new DirectoryWalkerEntryPointFinder(fs, config, logger, resolver, basePath, undefined);
|
||||||
|
const spy = spyOn(finder, 'walkDirectoryForEntryPoints').and.callThrough();
|
||||||
|
const {entryPoints} = finder.findEntryPoints();
|
||||||
|
expect(spy.calls.allArgs()).toEqual([
|
||||||
|
[_Abs(basePath)],
|
||||||
|
[_Abs(`${basePath}/@schematics`)],
|
||||||
|
[_Abs(`${basePath}/@schematics/angular`)],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(entryPoints).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should handle dependencies via pathMappings', () => {
|
it('should handle dependencies via pathMappings', () => {
|
||||||
const basePath = _Abs('/path_mapped/node_modules');
|
const basePath = _Abs('/path_mapped/node_modules');
|
||||||
@ -195,8 +260,9 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function createPackage(
|
function createPackage(
|
||||||
basePath: AbsoluteFsPath, packageName: string, deps: string[] = []): TestFile[] {
|
basePath: AbsoluteFsPath, packageName: string, deps: string[] = [],
|
||||||
return [
|
isCompiledByAngular = true): TestFile[] {
|
||||||
|
const files: TestFile[] = [
|
||||||
{
|
{
|
||||||
name: _Abs(`${basePath}/${packageName}/package.json`),
|
name: _Abs(`${basePath}/${packageName}/package.json`),
|
||||||
contents: JSON.stringify({
|
contents: JSON.stringify({
|
||||||
@ -205,8 +271,29 @@ runInEachFileSystem(() => {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
name: _Abs(`${basePath}/${packageName}/fesm2015/${packageName}.js`),
|
||||||
|
contents: deps.map((dep, i) => `import * as i${i} from '${dep}';`).join('\n'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isCompiledByAngular) {
|
||||||
|
files.push({
|
||||||
name: _Abs(`${basePath}/${packageName}/${packageName}.metadata.json`),
|
name: _Abs(`${basePath}/${packageName}/${packageName}.metadata.json`),
|
||||||
contents: 'metadata info'
|
contents: 'metadata info'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNonTsPackage(
|
||||||
|
basePath: AbsoluteFsPath, packageName: string, deps: string[] = []): TestFile[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: _Abs(`${basePath}/${packageName}/package.json`),
|
||||||
|
contents: JSON.stringify({
|
||||||
|
fesm2015: `./fesm2015/${packageName}.js`,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: _Abs(`${basePath}/${packageName}/fesm2015/${packageName}.js`),
|
name: _Abs(`${basePath}/${packageName}/fesm2015/${packageName}.js`),
|
||||||
|
@ -10,7 +10,7 @@ import {AbsoluteFsPath, FileSystem, absoluteFrom, getFileSystem} from '../../../
|
|||||||
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
||||||
import {loadTestFiles} from '../../../test/helpers';
|
import {loadTestFiles} from '../../../test/helpers';
|
||||||
import {NgccConfiguration} from '../../src/packages/configuration';
|
import {NgccConfiguration} from '../../src/packages/configuration';
|
||||||
import {EntryPoint, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat, getEntryPointInfo} from '../../src/packages/entry_point';
|
import {EntryPoint, INVALID_ENTRY_POINT, NO_ENTRY_POINT, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat, getEntryPointInfo} from '../../src/packages/entry_point';
|
||||||
import {MockLogger} from '../helpers/mock_logger';
|
import {MockLogger} from '../helpers/mock_logger';
|
||||||
|
|
||||||
runInEachFileSystem(() => {
|
runInEachFileSystem(() => {
|
||||||
@ -55,7 +55,7 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if configured to ignore the specified entry-point', () => {
|
it('should return `NO_ENTRY_POINT` if configured to ignore the specified entry-point', () => {
|
||||||
loadTestFiles([
|
loadTestFiles([
|
||||||
{
|
{
|
||||||
name: _('/project/node_modules/some_package/valid_entry_point/package.json'),
|
name: _('/project/node_modules/some_package/valid_entry_point/package.json'),
|
||||||
@ -75,7 +75,7 @@ runInEachFileSystem(() => {
|
|||||||
const entryPoint = getEntryPointInfo(
|
const entryPoint = getEntryPointInfo(
|
||||||
fs, config, new MockLogger(), SOME_PACKAGE,
|
fs, config, new MockLogger(), SOME_PACKAGE,
|
||||||
_('/project/node_modules/some_package/valid_entry_point'));
|
_('/project/node_modules/some_package/valid_entry_point'));
|
||||||
expect(entryPoint).toBe(null);
|
expect(entryPoint).toBe(NO_ENTRY_POINT);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should override the properties on package.json if the entry-point is configured', () => {
|
it('should override the properties on package.json if the entry-point is configured', () => {
|
||||||
@ -116,7 +116,7 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if there is no package.json at the entry-point path', () => {
|
it('should return `NO_ENTRY_POINT` if there is no package.json at the entry-point path', () => {
|
||||||
loadTestFiles([
|
loadTestFiles([
|
||||||
{
|
{
|
||||||
name: _(
|
name: _(
|
||||||
@ -128,7 +128,7 @@ runInEachFileSystem(() => {
|
|||||||
const entryPoint = getEntryPointInfo(
|
const entryPoint = getEntryPointInfo(
|
||||||
fs, config, new MockLogger(), SOME_PACKAGE,
|
fs, config, new MockLogger(), SOME_PACKAGE,
|
||||||
_('/project/node_modules/some_package/missing_package_json'));
|
_('/project/node_modules/some_package/missing_package_json'));
|
||||||
expect(entryPoint).toBe(null);
|
expect(entryPoint).toBe(NO_ENTRY_POINT);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a configured entry-point if there is no package.json at the entry-point path',
|
it('should return a configured entry-point if there is no package.json at the entry-point path',
|
||||||
@ -165,26 +165,27 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should return null if there is no typings or types field in the package.json', () => {
|
it('should return `INVALID_ENTRY_POINT` if there is no typings or types field in the package.json',
|
||||||
loadTestFiles([
|
() => {
|
||||||
{
|
loadTestFiles([
|
||||||
name: _('/project/node_modules/some_package/missing_typings/package.json'),
|
{
|
||||||
contents: createPackageJson('missing_typings', {excludes: ['typings']})
|
name: _('/project/node_modules/some_package/missing_typings/package.json'),
|
||||||
},
|
contents: createPackageJson('missing_typings', {excludes: ['typings']})
|
||||||
{
|
},
|
||||||
name:
|
{
|
||||||
_('/project/node_modules/some_package/missing_typings/missing_typings.metadata.json'),
|
name: _(
|
||||||
contents: 'some meta data'
|
'/project/node_modules/some_package/missing_typings/missing_typings.metadata.json'),
|
||||||
},
|
contents: 'some meta data'
|
||||||
]);
|
},
|
||||||
const config = new NgccConfiguration(fs, _('/project'));
|
]);
|
||||||
const entryPoint = getEntryPointInfo(
|
const config = new NgccConfiguration(fs, _('/project'));
|
||||||
fs, config, new MockLogger(), SOME_PACKAGE,
|
const entryPoint = getEntryPointInfo(
|
||||||
_('/project/node_modules/some_package/missing_typings'));
|
fs, config, new MockLogger(), SOME_PACKAGE,
|
||||||
expect(entryPoint).toBe(null);
|
_('/project/node_modules/some_package/missing_typings'));
|
||||||
});
|
expect(entryPoint).toBe(INVALID_ENTRY_POINT);
|
||||||
|
});
|
||||||
|
|
||||||
it('should return null if the typings or types field is not a string in the package.json',
|
it('should return `INVALID_ENTRY_POINT` if the typings or types field is not a string in the package.json',
|
||||||
() => {
|
() => {
|
||||||
loadTestFiles([
|
loadTestFiles([
|
||||||
{
|
{
|
||||||
@ -201,7 +202,7 @@ runInEachFileSystem(() => {
|
|||||||
const entryPoint = getEntryPointInfo(
|
const entryPoint = getEntryPointInfo(
|
||||||
fs, config, new MockLogger(), SOME_PACKAGE,
|
fs, config, new MockLogger(), SOME_PACKAGE,
|
||||||
_('/project/node_modules/some_package/typings_array'));
|
_('/project/node_modules/some_package/typings_array'));
|
||||||
expect(entryPoint).toBe(null);
|
expect(entryPoint).toBe(INVALID_ENTRY_POINT);
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let prop of SUPPORTED_FORMAT_PROPERTIES) {
|
for (let prop of SUPPORTED_FORMAT_PROPERTIES) {
|
||||||
@ -358,7 +359,7 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if the package.json is not valid JSON', () => {
|
it('should return `INVALID_ENTRY_POINT` if the package.json is not valid JSON', () => {
|
||||||
loadTestFiles([
|
loadTestFiles([
|
||||||
// package.json might not be a valid JSON
|
// package.json might not be a valid JSON
|
||||||
// for example, @schematics/angular contains a package.json blueprint
|
// for example, @schematics/angular contains a package.json blueprint
|
||||||
@ -372,7 +373,7 @@ runInEachFileSystem(() => {
|
|||||||
const entryPoint = getEntryPointInfo(
|
const entryPoint = getEntryPointInfo(
|
||||||
fs, config, new MockLogger(), SOME_PACKAGE,
|
fs, config, new MockLogger(), SOME_PACKAGE,
|
||||||
_('/project/node_modules/some_package/unexpected_symbols'));
|
_('/project/node_modules/some_package/unexpected_symbols'));
|
||||||
expect(entryPoint).toBe(null);
|
expect(entryPoint).toBe(INVALID_ENTRY_POINT);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -391,9 +392,13 @@ runInEachFileSystem(() => {
|
|||||||
contents: createPackageJson('valid_entry_point')
|
contents: createPackageJson('valid_entry_point')
|
||||||
}]);
|
}]);
|
||||||
const config = new NgccConfiguration(fs, _('/project'));
|
const config = new NgccConfiguration(fs, _('/project'));
|
||||||
entryPoint = getEntryPointInfo(
|
const result = getEntryPointInfo(
|
||||||
fs, config, new MockLogger(), SOME_PACKAGE,
|
fs, config, new MockLogger(), SOME_PACKAGE,
|
||||||
_('/project/node_modules/some_package/valid_entry_point')) !;
|
_('/project/node_modules/some_package/valid_entry_point'));
|
||||||
|
if (result === NO_ENTRY_POINT || result === INVALID_ENTRY_POINT) {
|
||||||
|
return fail(`Expected an entry point but got ${result}`);
|
||||||
|
}
|
||||||
|
entryPoint = result;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return `esm2015` format for `fesm2015` property',
|
it('should return `esm2015` format for `fesm2015` property',
|
||||||
|
@ -9,7 +9,7 @@ import {FileSystem, absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_s
|
|||||||
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
||||||
import {loadTestFiles} from '../../../test/helpers';
|
import {loadTestFiles} from '../../../test/helpers';
|
||||||
import {NgccConfiguration} from '../../src/packages/configuration';
|
import {NgccConfiguration} from '../../src/packages/configuration';
|
||||||
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointInfo} from '../../src/packages/entry_point';
|
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, INVALID_ENTRY_POINT, NO_ENTRY_POINT, getEntryPointInfo} from '../../src/packages/entry_point';
|
||||||
import {EntryPointBundle, makeEntryPointBundle} from '../../src/packages/entry_point_bundle';
|
import {EntryPointBundle, makeEntryPointBundle} from '../../src/packages/entry_point_bundle';
|
||||||
import {FileWriter} from '../../src/writing/file_writer';
|
import {FileWriter} from '../../src/writing/file_writer';
|
||||||
import {NewEntryPointFileWriter} from '../../src/writing/new_entry_point_file_writer';
|
import {NewEntryPointFileWriter} from '../../src/writing/new_entry_point_file_writer';
|
||||||
@ -103,8 +103,12 @@ runInEachFileSystem(() => {
|
|||||||
fs = getFileSystem();
|
fs = getFileSystem();
|
||||||
fileWriter = new NewEntryPointFileWriter(fs, new DirectPackageJsonUpdater(fs));
|
fileWriter = new NewEntryPointFileWriter(fs, new DirectPackageJsonUpdater(fs));
|
||||||
const config = new NgccConfiguration(fs, _('/'));
|
const config = new NgccConfiguration(fs, _('/'));
|
||||||
entryPoint = getEntryPointInfo(
|
const result = getEntryPointInfo(
|
||||||
fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test')) !;
|
fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test')) !;
|
||||||
|
if (result === NO_ENTRY_POINT || result === INVALID_ENTRY_POINT) {
|
||||||
|
return fail(`Expected an entry point but got ${result}`);
|
||||||
|
}
|
||||||
|
entryPoint = result;
|
||||||
esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5');
|
esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5');
|
||||||
esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015');
|
esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015');
|
||||||
});
|
});
|
||||||
@ -239,8 +243,12 @@ runInEachFileSystem(() => {
|
|||||||
fs = getFileSystem();
|
fs = getFileSystem();
|
||||||
fileWriter = new NewEntryPointFileWriter(fs, new DirectPackageJsonUpdater(fs));
|
fileWriter = new NewEntryPointFileWriter(fs, new DirectPackageJsonUpdater(fs));
|
||||||
const config = new NgccConfiguration(fs, _('/'));
|
const config = new NgccConfiguration(fs, _('/'));
|
||||||
entryPoint = getEntryPointInfo(
|
const result = getEntryPointInfo(
|
||||||
fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/a')) !;
|
fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/a')) !;
|
||||||
|
if (result === NO_ENTRY_POINT || result === INVALID_ENTRY_POINT) {
|
||||||
|
return fail(`Expected an entry point but got ${result}`);
|
||||||
|
}
|
||||||
|
entryPoint = result;
|
||||||
esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5');
|
esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5');
|
||||||
esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015');
|
esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015');
|
||||||
});
|
});
|
||||||
@ -364,8 +372,12 @@ runInEachFileSystem(() => {
|
|||||||
fs = getFileSystem();
|
fs = getFileSystem();
|
||||||
fileWriter = new NewEntryPointFileWriter(fs, new DirectPackageJsonUpdater(fs));
|
fileWriter = new NewEntryPointFileWriter(fs, new DirectPackageJsonUpdater(fs));
|
||||||
const config = new NgccConfiguration(fs, _('/'));
|
const config = new NgccConfiguration(fs, _('/'));
|
||||||
entryPoint = getEntryPointInfo(
|
const result = getEntryPointInfo(
|
||||||
fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/b')) !;
|
fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/b')) !;
|
||||||
|
if (result === NO_ENTRY_POINT || result === INVALID_ENTRY_POINT) {
|
||||||
|
return fail(`Expected an entry point but got ${result}`);
|
||||||
|
}
|
||||||
|
entryPoint = result;
|
||||||
esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5');
|
esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5');
|
||||||
esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015');
|
esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015');
|
||||||
});
|
});
|
||||||
|
@ -39,7 +39,7 @@ ts_library(
|
|||||||
|
|
||||||
ts_library(
|
ts_library(
|
||||||
name = "api",
|
name = "api",
|
||||||
srcs = ["api.ts"],
|
srcs = glob(["api/**/*.ts"]),
|
||||||
deps = [
|
deps = [
|
||||||
"@npm//typescript",
|
"@npm//typescript",
|
||||||
],
|
],
|
||||||
|
11
packages/compiler-cli/src/ngtsc/core/api/index.ts
Normal file
11
packages/compiler-cli/src/ngtsc/core/api/index.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './src/interfaces';
|
||||||
|
export * from './src/options';
|
||||||
|
export * from './src/public_options';
|
65
packages/compiler-cli/src/ngtsc/core/api/src/interfaces.ts
Normal file
65
packages/compiler-cli/src/ngtsc/core/api/src/interfaces.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A host backed by a build system which has a unified view of the module namespace.
|
||||||
|
*
|
||||||
|
* Such a build system supports the `fileNameToModuleName` method provided by certain build system
|
||||||
|
* integrations (such as the integration with Bazel). See the docs on `fileNameToModuleName` for
|
||||||
|
* more details.
|
||||||
|
*/
|
||||||
|
export interface UnifiedModulesHost {
|
||||||
|
/**
|
||||||
|
* Converts a file path to a module name that can be used as an `import ...`.
|
||||||
|
*
|
||||||
|
* For example, such a host might determine that `/absolute/path/to/monorepo/lib/importedFile.ts`
|
||||||
|
* should be imported using a module specifier of `monorepo/lib/importedFile`.
|
||||||
|
*/
|
||||||
|
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A host which additionally tracks and produces "resources" (HTML templates, CSS
|
||||||
|
* files, etc).
|
||||||
|
*/
|
||||||
|
export interface ResourceHost {
|
||||||
|
/**
|
||||||
|
* Converts a file path for a resource that is used in a source file or another resource
|
||||||
|
* into a filepath.
|
||||||
|
*/
|
||||||
|
resourceNameToFileName(resourceName: string, containingFilePath: string): string|null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a referenced resource either statically or asynchronously. If the host returns a
|
||||||
|
* `Promise<string>` it is assumed the user of the corresponding `Program` will call
|
||||||
|
* `loadNgStructureAsync()`. Returning `Promise<string>` outside `loadNgStructureAsync()` will
|
||||||
|
* cause a diagnostics diagnostic error or an exception to be thrown.
|
||||||
|
*/
|
||||||
|
readResource(fileName: string): Promise<string>|string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the absolute paths to the changed files that triggered the current compilation
|
||||||
|
* or `undefined` if this is not an incremental build.
|
||||||
|
*/
|
||||||
|
getModifiedResourceFiles?(): Set<string>|undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `ts.CompilerHost` interface which supports some number of optional methods in addition to the
|
||||||
|
* core interface.
|
||||||
|
*/
|
||||||
|
export interface ExtendedTsCompilerHost extends ts.CompilerHost, Partial<ResourceHost>,
|
||||||
|
Partial<UnifiedModulesHost> {}
|
||||||
|
|
||||||
|
export interface LazyRoute {
|
||||||
|
route: string;
|
||||||
|
module: {name: string, filePath: string};
|
||||||
|
referencedModule: {name: string, filePath: string};
|
||||||
|
}
|
57
packages/compiler-cli/src/ngtsc/core/api/src/options.ts
Normal file
57
packages/compiler-cli/src/ngtsc/core/api/src/options.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {BazelAndG3Options, I18nOptions, LegacyNgcOptions, MiscOptions, NgcCompatibilityOptions, StrictTemplateOptions} from './public_options';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-public options which are useful during testing of the compiler.
|
||||||
|
*/
|
||||||
|
export interface TestOnlyOptions {
|
||||||
|
/**
|
||||||
|
* Whether to use the CompilerHost's fileNameToModuleName utility (if available) to generate
|
||||||
|
* import module specifiers. This is false by default, and exists to support running ngtsc
|
||||||
|
* within Google. This option is internal and is used by the ng_module.bzl rule to switch
|
||||||
|
* behavior between Bazel and Blaze.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
_useHostForImportGeneration?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn on template type-checking in the Ivy compiler.
|
||||||
|
*
|
||||||
|
* This is an internal flag being used to roll out template type-checking in ngtsc. Turning it on
|
||||||
|
* by default before it's ready might break other users attempting to test the new compiler's
|
||||||
|
* behavior.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
ivyTemplateTypeCheck?: boolean;
|
||||||
|
|
||||||
|
/** An option to enable ngtsc's internal performance tracing.
|
||||||
|
*
|
||||||
|
* This should be a path to a JSON file where trace information will be written. An optional 'ts:'
|
||||||
|
* prefix will cause the trace to be written via the TS host instead of directly to the filesystem
|
||||||
|
* (not all hosts support this mode of operation).
|
||||||
|
*
|
||||||
|
* This is currently not exposed to users as the trace format is still unstable.
|
||||||
|
*/
|
||||||
|
tracePerformance?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A merged interface of all of the various Angular compiler options, as well as the standard
|
||||||
|
* `ts.CompilerOptions`.
|
||||||
|
*
|
||||||
|
* Also includes a few miscellaneous options.
|
||||||
|
*/
|
||||||
|
export interface NgCompilerOptions extends ts.CompilerOptions, LegacyNgcOptions, BazelAndG3Options,
|
||||||
|
NgcCompatibilityOptions, StrictTemplateOptions, TestOnlyOptions, I18nOptions, MiscOptions {}
|
@ -6,63 +6,13 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A host backed by a build system which has a unified view of the module namespace.
|
|
||||||
*
|
|
||||||
* Such a build system supports the `fileNameToModuleName` method provided by certain build system
|
|
||||||
* integrations (such as the integration with Bazel). See the docs on `fileNameToModuleName` for
|
|
||||||
* more details.
|
|
||||||
*/
|
|
||||||
export interface UnifiedModulesHost {
|
|
||||||
/**
|
|
||||||
* Converts a file path to a module name that can be used as an `import ...`.
|
|
||||||
*
|
|
||||||
* For example, such a host might determine that `/absolute/path/to/monorepo/lib/importedFile.ts`
|
|
||||||
* should be imported using a module specifier of `monorepo/lib/importedFile`.
|
|
||||||
*/
|
|
||||||
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A host which additionally tracks and produces "resources" (HTML templates, CSS
|
|
||||||
* files, etc).
|
|
||||||
*/
|
|
||||||
export interface ResourceHost {
|
|
||||||
/**
|
|
||||||
* Converts a file path for a resource that is used in a source file or another resource
|
|
||||||
* into a filepath.
|
|
||||||
*/
|
|
||||||
resourceNameToFileName(resourceName: string, containingFilePath: string): string|null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load a referenced resource either statically or asynchronously. If the host returns a
|
|
||||||
* `Promise<string>` it is assumed the user of the corresponding `Program` will call
|
|
||||||
* `loadNgStructureAsync()`. Returning `Promise<string>` outside `loadNgStructureAsync()` will
|
|
||||||
* cause a diagnostics diagnostic error or an exception to be thrown.
|
|
||||||
*/
|
|
||||||
readResource(fileName: string): Promise<string>|string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the absolute paths to the changed files that triggered the current compilation
|
|
||||||
* or `undefined` if this is not an incremental build.
|
|
||||||
*/
|
|
||||||
getModifiedResourceFiles?(): Set<string>|undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A `ts.CompilerHost` interface which supports some number of optional methods in addition to the
|
|
||||||
* core interface.
|
|
||||||
*/
|
|
||||||
export interface ExtendedTsCompilerHost extends ts.CompilerHost, Partial<ResourceHost>,
|
|
||||||
Partial<UnifiedModulesHost> {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options supported by the legacy View Engine compiler, which are still consumed by the Angular Ivy
|
* Options supported by the legacy View Engine compiler, which are still consumed by the Angular Ivy
|
||||||
* compiler for backwards compatibility.
|
* compiler for backwards compatibility.
|
||||||
*
|
*
|
||||||
* These are expected to be removed at some point in the future.
|
* These are expected to be removed at some point in the future.
|
||||||
|
*
|
||||||
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
export interface LegacyNgcOptions {
|
export interface LegacyNgcOptions {
|
||||||
/** generate all possible generated files */
|
/** generate all possible generated files */
|
||||||
@ -134,6 +84,8 @@ export interface LegacyNgcOptions {
|
|||||||
* existing View Engine applications.
|
* existing View Engine applications.
|
||||||
*
|
*
|
||||||
* These are expected to be removed at some point in the future.
|
* These are expected to be removed at some point in the future.
|
||||||
|
*
|
||||||
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
export interface NgcCompatibilityOptions {
|
export interface NgcCompatibilityOptions {
|
||||||
/**
|
/**
|
||||||
@ -168,6 +120,8 @@ export interface NgcCompatibilityOptions {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Options related to template type-checking and its strictness.
|
* Options related to template type-checking and its strictness.
|
||||||
|
*
|
||||||
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
export interface StrictTemplateOptions {
|
export interface StrictTemplateOptions {
|
||||||
/**
|
/**
|
||||||
@ -291,6 +245,8 @@ export interface StrictTemplateOptions {
|
|||||||
/**
|
/**
|
||||||
* Options which control behavior useful for "monorepo" build cases using Bazel (such as the
|
* Options which control behavior useful for "monorepo" build cases using Bazel (such as the
|
||||||
* internal Google monorepo, g3).
|
* internal Google monorepo, g3).
|
||||||
|
*
|
||||||
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
export interface BazelAndG3Options {
|
export interface BazelAndG3Options {
|
||||||
/**
|
/**
|
||||||
@ -332,6 +288,8 @@ export interface BazelAndG3Options {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Options related to i18n compilation support.
|
* Options related to i18n compilation support.
|
||||||
|
*
|
||||||
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
export interface I18nOptions {
|
export interface I18nOptions {
|
||||||
/**
|
/**
|
||||||
@ -358,69 +316,19 @@ export interface I18nOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Non-public options which are useful during testing of the compiler.
|
* Miscellaneous options that don't fall into any other category
|
||||||
*/
|
|
||||||
export interface TestOnlyOptions {
|
|
||||||
/**
|
|
||||||
* Whether to use the CompilerHost's fileNameToModuleName utility (if available) to generate
|
|
||||||
* import module specifiers. This is false by default, and exists to support running ngtsc
|
|
||||||
* within Google. This option is internal and is used by the ng_module.bzl rule to switch
|
|
||||||
* behavior between Bazel and Blaze.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_useHostForImportGeneration?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turn on template type-checking in the Ivy compiler.
|
|
||||||
*
|
|
||||||
* This is an internal flag being used to roll out template type-checking in ngtsc. Turning it on
|
|
||||||
* by default before it's ready might break other users attempting to test the new compiler's
|
|
||||||
* behavior.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
ivyTemplateTypeCheck?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A merged interface of all of the various Angular compiler options, as well as the standard
|
|
||||||
* `ts.CompilerOptions`.
|
|
||||||
*
|
*
|
||||||
* Also includes a few miscellaneous options.
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
export interface NgCompilerOptions extends ts.CompilerOptions, LegacyNgcOptions, BazelAndG3Options,
|
export interface MiscOptions {
|
||||||
NgcCompatibilityOptions, StrictTemplateOptions, TestOnlyOptions, I18nOptions {
|
|
||||||
/**
|
/**
|
||||||
* Whether the compiler should avoid generating code for classes that haven't been exported.
|
* Whether the compiler should avoid generating code for classes that haven't been exported.
|
||||||
* This is only active when building with `enableIvy: true`. Defaults to `true`.
|
* This is only active when building with `enableIvy: true`. Defaults to `true`.
|
||||||
*/
|
*/
|
||||||
compileNonExportedClasses?: boolean;
|
compileNonExportedClasses?: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to remove blank text nodes from compiled templates. It is `false` by default starting
|
|
||||||
* from Angular 6.
|
|
||||||
*/
|
|
||||||
preserveWhitespaces?: boolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable TypeScript Version Check.
|
* Disable TypeScript Version Check.
|
||||||
*/
|
*/
|
||||||
disableTypeScriptVersionCheck?: boolean;
|
disableTypeScriptVersionCheck?: boolean;
|
||||||
|
|
||||||
/** An option to enable ngtsc's internal performance tracing.
|
|
||||||
*
|
|
||||||
* This should be a path to a JSON file where trace information will be written. An optional 'ts:'
|
|
||||||
* prefix will cause the trace to be written via the TS host instead of directly to the filesystem
|
|
||||||
* (not all hosts support this mode of operation).
|
|
||||||
*
|
|
||||||
* This is currently not exposed to users as the trace format is still unstable.
|
|
||||||
*/
|
|
||||||
tracePerformance?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LazyRoute {
|
|
||||||
route: string;
|
|
||||||
module: {name: string, filePath: string};
|
|
||||||
referencedModule: {name: string, filePath: string};
|
|
||||||
}
|
}
|
@ -296,9 +296,10 @@ export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScop
|
|||||||
const exportPipes = new Map<ts.Declaration, PipeMeta>();
|
const exportPipes = new Map<ts.Declaration, PipeMeta>();
|
||||||
|
|
||||||
// The algorithm is as follows:
|
// The algorithm is as follows:
|
||||||
// 1) Add directives/pipes declared in the NgModule to the compilation scope.
|
// 1) Add all of the directives/pipes from each NgModule imported into the current one to the
|
||||||
// 2) Add all of the directives/pipes from each NgModule imported into the current one to the
|
// compilation scope.
|
||||||
// compilation scope. At this point, the compilation scope is complete.
|
// 2) Add directives/pipes declared in the NgModule to the compilation scope. At this point, the
|
||||||
|
// compilation scope is complete.
|
||||||
// 3) For each entry in the NgModule's exports:
|
// 3) For each entry in the NgModule's exports:
|
||||||
// a) Attempt to resolve it as an NgModule with its own exported directives/pipes. If it is
|
// a) Attempt to resolve it as an NgModule with its own exported directives/pipes. If it is
|
||||||
// one, add them to the export scope of this NgModule.
|
// one, add them to the export scope of this NgModule.
|
||||||
@ -307,31 +308,7 @@ export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScop
|
|||||||
// c) If it's neither an NgModule nor a directive/pipe in the compilation scope, then this
|
// c) If it's neither an NgModule nor a directive/pipe in the compilation scope, then this
|
||||||
// is an error.
|
// is an error.
|
||||||
|
|
||||||
// 1) add declarations.
|
// 1) process imports.
|
||||||
for (const decl of ngModule.declarations) {
|
|
||||||
const directive = this.localReader.getDirectiveMetadata(decl);
|
|
||||||
const pipe = this.localReader.getPipeMetadata(decl);
|
|
||||||
if (directive !== null) {
|
|
||||||
compilationDirectives.set(decl.node, {...directive, ref: decl});
|
|
||||||
} else if (pipe !== null) {
|
|
||||||
compilationPipes.set(decl.node, {...pipe, ref: decl});
|
|
||||||
} else {
|
|
||||||
this.taintedModules.add(ngModule.ref.node);
|
|
||||||
|
|
||||||
const errorNode = decl.getOriginForDiagnostics(ngModule.rawDeclarations !);
|
|
||||||
diagnostics.push(makeDiagnostic(
|
|
||||||
ErrorCode.NGMODULE_INVALID_DECLARATION, errorNode,
|
|
||||||
`The class '${decl.node.name.text}' is listed in the declarations of the NgModule '${ngModule.ref.node.name.text}', but is not a directive, a component, or a pipe.
|
|
||||||
|
|
||||||
Either remove it from the NgModule's declarations, or add an appropriate Angular decorator.`,
|
|
||||||
[{node: decl.node.name, messageText: `'${decl.node.name.text}' is declared here.`}]));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
declared.add(decl.node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) process imports.
|
|
||||||
for (const decl of ngModule.imports) {
|
for (const decl of ngModule.imports) {
|
||||||
const importScope = this.getExportedScope(decl, diagnostics, ref.node, 'import');
|
const importScope = this.getExportedScope(decl, diagnostics, ref.node, 'import');
|
||||||
if (importScope === null) {
|
if (importScope === null) {
|
||||||
@ -353,6 +330,30 @@ Either remove it from the NgModule's declarations, or add an appropriate Angular
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2) add declarations.
|
||||||
|
for (const decl of ngModule.declarations) {
|
||||||
|
const directive = this.localReader.getDirectiveMetadata(decl);
|
||||||
|
const pipe = this.localReader.getPipeMetadata(decl);
|
||||||
|
if (directive !== null) {
|
||||||
|
compilationDirectives.set(decl.node, {...directive, ref: decl});
|
||||||
|
} else if (pipe !== null) {
|
||||||
|
compilationPipes.set(decl.node, {...pipe, ref: decl});
|
||||||
|
} else {
|
||||||
|
this.taintedModules.add(ngModule.ref.node);
|
||||||
|
|
||||||
|
const errorNode = decl.getOriginForDiagnostics(ngModule.rawDeclarations !);
|
||||||
|
diagnostics.push(makeDiagnostic(
|
||||||
|
ErrorCode.NGMODULE_INVALID_DECLARATION, errorNode,
|
||||||
|
`The class '${decl.node.name.text}' is listed in the declarations ` +
|
||||||
|
`of the NgModule '${ngModule.ref.node.name.text}', but is not a directive, a component, or a pipe. ` +
|
||||||
|
`Either remove it from the NgModule's declarations, or add an appropriate Angular decorator.`,
|
||||||
|
[{node: decl.node.name, messageText: `'${decl.node.name.text}' is declared here.`}]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
declared.add(decl.node);
|
||||||
|
}
|
||||||
|
|
||||||
// 3) process exports.
|
// 3) process exports.
|
||||||
// Exports can contain modules, components, or directives. They're processed differently.
|
// Exports can contain modules, components, or directives. They're processed differently.
|
||||||
// Modules are straightforward. Directives and pipes from exported modules are added to the
|
// Modules are straightforward. Directives and pipes from exported modules are added to the
|
||||||
|
@ -2542,7 +2542,7 @@ describe('compiler compliance', () => {
|
|||||||
type: LifecycleComp,
|
type: LifecycleComp,
|
||||||
selectors: [["lifecycle-comp"]],
|
selectors: [["lifecycle-comp"]],
|
||||||
inputs: {nameMin: ["name", "nameMin"]},
|
inputs: {nameMin: ["name", "nameMin"]},
|
||||||
features: [$r3$.ɵɵNgOnChangesFeature()],
|
features: [$r3$.ɵɵNgOnChangesFeature],
|
||||||
decls: 0,
|
decls: 0,
|
||||||
vars: 0,
|
vars: 0,
|
||||||
template: function LifecycleComp_Template(rf, ctx) {},
|
template: function LifecycleComp_Template(rf, ctx) {},
|
||||||
@ -2662,7 +2662,7 @@ describe('compiler compliance', () => {
|
|||||||
ForOfDirective.ɵdir = $r3$.ɵɵdefineDirective({
|
ForOfDirective.ɵdir = $r3$.ɵɵdefineDirective({
|
||||||
type: ForOfDirective,
|
type: ForOfDirective,
|
||||||
selectors: [["", "forOf", ""]],
|
selectors: [["", "forOf", ""]],
|
||||||
features: [$r3$.ɵɵNgOnChangesFeature()],
|
features: [$r3$.ɵɵNgOnChangesFeature],
|
||||||
inputs: {forOf: "forOf"}
|
inputs: {forOf: "forOf"}
|
||||||
});
|
});
|
||||||
`;
|
`;
|
||||||
@ -2742,7 +2742,7 @@ describe('compiler compliance', () => {
|
|||||||
ForOfDirective.ɵdir = $r3$.ɵɵdefineDirective({
|
ForOfDirective.ɵdir = $r3$.ɵɵdefineDirective({
|
||||||
type: ForOfDirective,
|
type: ForOfDirective,
|
||||||
selectors: [["", "forOf", ""]],
|
selectors: [["", "forOf", ""]],
|
||||||
features: [$r3$.ɵɵNgOnChangesFeature()],
|
features: [$r3$.ɵɵNgOnChangesFeature],
|
||||||
inputs: {forOf: "forOf"}
|
inputs: {forOf: "forOf"}
|
||||||
});
|
});
|
||||||
`;
|
`;
|
||||||
@ -3767,7 +3767,7 @@ describe('compiler compliance', () => {
|
|||||||
// ...
|
// ...
|
||||||
BaseClass.ɵdir = $r3$.ɵɵdefineDirective({
|
BaseClass.ɵdir = $r3$.ɵɵdefineDirective({
|
||||||
type: BaseClass,
|
type: BaseClass,
|
||||||
features: [$r3$.ɵɵNgOnChangesFeature()]
|
features: [$r3$.ɵɵNgOnChangesFeature]
|
||||||
});
|
});
|
||||||
// ...
|
// ...
|
||||||
`;
|
`;
|
||||||
|
@ -598,6 +598,213 @@ runInEachFileSystem(os => {
|
|||||||
expect(jsContents).toContain('outputs: { output: "output" }');
|
expect(jsContents).toContain('outputs: { output: "output" }');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should pick a Pipe defined in `declarations` over imported Pipes', () => {
|
||||||
|
env.tsconfig({});
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {Component, Pipe, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
// ModuleA classes
|
||||||
|
|
||||||
|
@Pipe({name: 'number'})
|
||||||
|
class PipeA {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [PipeA],
|
||||||
|
exports: [PipeA]
|
||||||
|
})
|
||||||
|
class ModuleA {}
|
||||||
|
|
||||||
|
// ModuleB classes
|
||||||
|
|
||||||
|
@Pipe({name: 'number'})
|
||||||
|
class PipeB {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app',
|
||||||
|
template: '{{ count | number }}'
|
||||||
|
})
|
||||||
|
export class App {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [ModuleA],
|
||||||
|
declarations: [PipeB, App],
|
||||||
|
})
|
||||||
|
class ModuleB {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
env.driveMain();
|
||||||
|
|
||||||
|
const jsContents = trim(env.getContents('test.js'));
|
||||||
|
expect(jsContents).toContain('pipes: [PipeB]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect imported module order when selecting Pipe (last imported Pipe is used)',
|
||||||
|
() => {
|
||||||
|
env.tsconfig({});
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {Component, Pipe, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
// ModuleA classes
|
||||||
|
|
||||||
|
@Pipe({name: 'number'})
|
||||||
|
class PipeA {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [PipeA],
|
||||||
|
exports: [PipeA]
|
||||||
|
})
|
||||||
|
class ModuleA {}
|
||||||
|
|
||||||
|
// ModuleB classes
|
||||||
|
|
||||||
|
@Pipe({name: 'number'})
|
||||||
|
class PipeB {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [PipeB],
|
||||||
|
exports: [PipeB]
|
||||||
|
})
|
||||||
|
class ModuleB {}
|
||||||
|
|
||||||
|
// ModuleC classes
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app',
|
||||||
|
template: '{{ count | number }}'
|
||||||
|
})
|
||||||
|
export class App {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [ModuleA, ModuleB],
|
||||||
|
declarations: [App],
|
||||||
|
})
|
||||||
|
class ModuleC {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
env.driveMain();
|
||||||
|
|
||||||
|
const jsContents = trim(env.getContents('test.js'));
|
||||||
|
expect(jsContents).toContain('pipes: [PipeB]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add Directives and Components from `declarations` at the end of the list', () => {
|
||||||
|
env.tsconfig({});
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {Component, Directive, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
// ModuleA classes
|
||||||
|
|
||||||
|
@Directive({selector: '[dir]'})
|
||||||
|
class DirectiveA {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'comp',
|
||||||
|
template: '...'
|
||||||
|
})
|
||||||
|
class ComponentA {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [DirectiveA, ComponentA],
|
||||||
|
exports: [DirectiveA, ComponentA]
|
||||||
|
})
|
||||||
|
class ModuleA {}
|
||||||
|
|
||||||
|
// ModuleB classes
|
||||||
|
|
||||||
|
@Directive({selector: '[dir]'})
|
||||||
|
class DirectiveB {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'comp',
|
||||||
|
template: '...',
|
||||||
|
})
|
||||||
|
export class ComponentB {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app',
|
||||||
|
template: \`
|
||||||
|
<div dir></div>
|
||||||
|
<comp></comp>
|
||||||
|
\`,
|
||||||
|
})
|
||||||
|
export class App {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [ModuleA],
|
||||||
|
declarations: [DirectiveB, ComponentB, App],
|
||||||
|
})
|
||||||
|
class ModuleB {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
env.driveMain();
|
||||||
|
|
||||||
|
const jsContents = trim(env.getContents('test.js'));
|
||||||
|
expect(jsContents).toContain('directives: [DirectiveA, DirectiveB, ComponentA, ComponentB]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect imported module order while processing Directives and Components', () => {
|
||||||
|
env.tsconfig({});
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {Component, Directive, NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
// ModuleA classes
|
||||||
|
|
||||||
|
@Directive({selector: '[dir]'})
|
||||||
|
class DirectiveA {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'comp',
|
||||||
|
template: '...'
|
||||||
|
})
|
||||||
|
class ComponentA {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [DirectiveA, ComponentA],
|
||||||
|
exports: [DirectiveA, ComponentA]
|
||||||
|
})
|
||||||
|
class ModuleA {}
|
||||||
|
|
||||||
|
// ModuleB classes
|
||||||
|
|
||||||
|
@Directive({selector: '[dir]'})
|
||||||
|
class DirectiveB {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'comp',
|
||||||
|
template: '...'
|
||||||
|
})
|
||||||
|
class ComponentB {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [DirectiveB, ComponentB],
|
||||||
|
exports: [DirectiveB, ComponentB]
|
||||||
|
})
|
||||||
|
class ModuleB {}
|
||||||
|
|
||||||
|
// ModuleC classes
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app',
|
||||||
|
template: \`
|
||||||
|
<div dir></div>
|
||||||
|
<comp></comp>
|
||||||
|
\`,
|
||||||
|
})
|
||||||
|
export class App {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [ModuleA, ModuleB],
|
||||||
|
declarations: [App],
|
||||||
|
})
|
||||||
|
class ModuleC {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
env.driveMain();
|
||||||
|
|
||||||
|
const jsContents = trim(env.getContents('test.js'));
|
||||||
|
expect(jsContents).toContain('directives: [DirectiveA, DirectiveB, ComponentA, ComponentB]');
|
||||||
|
});
|
||||||
|
|
||||||
it('should compile Components with a templateUrl in a different rootDir', () => {
|
it('should compile Components with a templateUrl in a different rootDir', () => {
|
||||||
env.tsconfig({}, ['./extraRootDir']);
|
env.tsconfig({}, ['./extraRootDir']);
|
||||||
env.write('extraRootDir/test.html', '<p>Hello World</p>');
|
env.write('extraRootDir/test.html', '<p>Hello World</p>');
|
||||||
|
@ -286,7 +286,7 @@ export class ASTWithSource extends AST {
|
|||||||
export class TemplateBinding {
|
export class TemplateBinding {
|
||||||
constructor(
|
constructor(
|
||||||
public span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public key: string,
|
public span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public key: string,
|
||||||
public keyIsVar: boolean, public name: string, public expression: ASTWithSource|null) {}
|
public keyIsVar: boolean, public name: string, public value: ASTWithSource|null) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AstVisitor {
|
export interface AstVisitor {
|
||||||
|
@ -118,12 +118,38 @@ export class Parser {
|
|||||||
span, span.toAbsolute(absoluteOffset), prefix, uninterpretedExpression, location);
|
span, span.toAbsolute(absoluteOffset), prefix, uninterpretedExpression, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseTemplateBindings(tplKey: string, tplValue: string, location: any, absoluteOffset: number):
|
/**
|
||||||
TemplateBindingParseResult {
|
* Parse microsyntax template expression and return a list of bindings or
|
||||||
const tokens = this._lexer.tokenize(tplValue);
|
* parsing errors in case the given expression is invalid.
|
||||||
|
*
|
||||||
|
* For example,
|
||||||
|
* ```
|
||||||
|
* <div *ngFor="let item of items">
|
||||||
|
* ^ `absoluteOffset` for `tplValue`
|
||||||
|
* ```
|
||||||
|
* contains three bindings:
|
||||||
|
* 1. ngFor -> null
|
||||||
|
* 2. item -> NgForOfContext.$implicit
|
||||||
|
* 3. ngForOf -> items
|
||||||
|
*
|
||||||
|
* This is apparent from the de-sugared template:
|
||||||
|
* ```
|
||||||
|
* <ng-template ngFor let-item [ngForOf]="items">
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param templateKey name of directive, without the * prefix. For example: ngIf, ngFor
|
||||||
|
* @param templateValue RHS of the microsyntax attribute
|
||||||
|
* @param templateUrl template filename if it's external, component filename if it's inline
|
||||||
|
* @param absoluteOffset absolute offset of the `tplValue`
|
||||||
|
*/
|
||||||
|
parseTemplateBindings(
|
||||||
|
templateKey: string, templateValue: string, templateUrl: string,
|
||||||
|
absoluteOffset: number): TemplateBindingParseResult {
|
||||||
|
const tokens = this._lexer.tokenize(templateValue);
|
||||||
return new _ParseAST(
|
return new _ParseAST(
|
||||||
tplValue, location, absoluteOffset, tokens, tplValue.length, false, this.errors, 0)
|
templateValue, templateUrl, absoluteOffset, tokens, templateValue.length,
|
||||||
.parseTemplateBindings(tplKey);
|
false /* parseAction */, this.errors, 0 /* relative offset */)
|
||||||
|
.parseTemplateBindings(templateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseInterpolation(
|
parseInterpolation(
|
||||||
@ -288,7 +314,7 @@ export class _ParseAST {
|
|||||||
|
|
||||||
advance() { this.index++; }
|
advance() { this.index++; }
|
||||||
|
|
||||||
optionalCharacter(code: number): boolean {
|
consumeOptionalCharacter(code: number): boolean {
|
||||||
if (this.next.isCharacter(code)) {
|
if (this.next.isCharacter(code)) {
|
||||||
this.advance();
|
this.advance();
|
||||||
return true;
|
return true;
|
||||||
@ -301,11 +327,11 @@ export class _ParseAST {
|
|||||||
peekKeywordAs(): boolean { return this.next.isKeywordAs(); }
|
peekKeywordAs(): boolean { return this.next.isKeywordAs(); }
|
||||||
|
|
||||||
expectCharacter(code: number) {
|
expectCharacter(code: number) {
|
||||||
if (this.optionalCharacter(code)) return;
|
if (this.consumeOptionalCharacter(code)) return;
|
||||||
this.error(`Missing expected ${String.fromCharCode(code)}`);
|
this.error(`Missing expected ${String.fromCharCode(code)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
optionalOperator(op: string): boolean {
|
consumeOptionalOperator(op: string): boolean {
|
||||||
if (this.next.isOperator(op)) {
|
if (this.next.isOperator(op)) {
|
||||||
this.advance();
|
this.advance();
|
||||||
return true;
|
return true;
|
||||||
@ -315,7 +341,7 @@ export class _ParseAST {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expectOperator(operator: string) {
|
expectOperator(operator: string) {
|
||||||
if (this.optionalOperator(operator)) return;
|
if (this.consumeOptionalOperator(operator)) return;
|
||||||
this.error(`Missing expected operator ${operator}`);
|
this.error(`Missing expected operator ${operator}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,11 +372,11 @@ export class _ParseAST {
|
|||||||
const expr = this.parsePipe();
|
const expr = this.parsePipe();
|
||||||
exprs.push(expr);
|
exprs.push(expr);
|
||||||
|
|
||||||
if (this.optionalCharacter(chars.$SEMICOLON)) {
|
if (this.consumeOptionalCharacter(chars.$SEMICOLON)) {
|
||||||
if (!this.parseAction) {
|
if (!this.parseAction) {
|
||||||
this.error('Binding expression cannot contain chained expression');
|
this.error('Binding expression cannot contain chained expression');
|
||||||
}
|
}
|
||||||
while (this.optionalCharacter(chars.$SEMICOLON)) {
|
while (this.consumeOptionalCharacter(chars.$SEMICOLON)) {
|
||||||
} // read all semicolons
|
} // read all semicolons
|
||||||
} else if (this.index < this.tokens.length) {
|
} else if (this.index < this.tokens.length) {
|
||||||
this.error(`Unexpected token '${this.next}'`);
|
this.error(`Unexpected token '${this.next}'`);
|
||||||
@ -363,7 +389,7 @@ export class _ParseAST {
|
|||||||
|
|
||||||
parsePipe(): AST {
|
parsePipe(): AST {
|
||||||
let result = this.parseExpression();
|
let result = this.parseExpression();
|
||||||
if (this.optionalOperator('|')) {
|
if (this.consumeOptionalOperator('|')) {
|
||||||
if (this.parseAction) {
|
if (this.parseAction) {
|
||||||
this.error('Cannot have a pipe in an action expression');
|
this.error('Cannot have a pipe in an action expression');
|
||||||
}
|
}
|
||||||
@ -373,13 +399,13 @@ export class _ParseAST {
|
|||||||
const name = this.expectIdentifierOrKeyword();
|
const name = this.expectIdentifierOrKeyword();
|
||||||
const nameSpan = this.sourceSpan(nameStart);
|
const nameSpan = this.sourceSpan(nameStart);
|
||||||
const args: AST[] = [];
|
const args: AST[] = [];
|
||||||
while (this.optionalCharacter(chars.$COLON)) {
|
while (this.consumeOptionalCharacter(chars.$COLON)) {
|
||||||
args.push(this.parseExpression());
|
args.push(this.parseExpression());
|
||||||
}
|
}
|
||||||
const {start} = result.span;
|
const {start} = result.span;
|
||||||
result =
|
result =
|
||||||
new BindingPipe(this.span(start), this.sourceSpan(start), result, name, args, nameSpan);
|
new BindingPipe(this.span(start), this.sourceSpan(start), result, name, args, nameSpan);
|
||||||
} while (this.optionalOperator('|'));
|
} while (this.consumeOptionalOperator('|'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -391,10 +417,10 @@ export class _ParseAST {
|
|||||||
const start = this.inputIndex;
|
const start = this.inputIndex;
|
||||||
const result = this.parseLogicalOr();
|
const result = this.parseLogicalOr();
|
||||||
|
|
||||||
if (this.optionalOperator('?')) {
|
if (this.consumeOptionalOperator('?')) {
|
||||||
const yes = this.parsePipe();
|
const yes = this.parsePipe();
|
||||||
let no: AST;
|
let no: AST;
|
||||||
if (!this.optionalCharacter(chars.$COLON)) {
|
if (!this.consumeOptionalCharacter(chars.$COLON)) {
|
||||||
const end = this.inputIndex;
|
const end = this.inputIndex;
|
||||||
const expression = this.input.substring(start, end);
|
const expression = this.input.substring(start, end);
|
||||||
this.error(`Conditional expression ${expression} requires all 3 expressions`);
|
this.error(`Conditional expression ${expression} requires all 3 expressions`);
|
||||||
@ -411,7 +437,7 @@ export class _ParseAST {
|
|||||||
parseLogicalOr(): AST {
|
parseLogicalOr(): AST {
|
||||||
// '||'
|
// '||'
|
||||||
let result = this.parseLogicalAnd();
|
let result = this.parseLogicalAnd();
|
||||||
while (this.optionalOperator('||')) {
|
while (this.consumeOptionalOperator('||')) {
|
||||||
const right = this.parseLogicalAnd();
|
const right = this.parseLogicalAnd();
|
||||||
const {start} = result.span;
|
const {start} = result.span;
|
||||||
result = new Binary(this.span(start), this.sourceSpan(start), '||', result, right);
|
result = new Binary(this.span(start), this.sourceSpan(start), '||', result, right);
|
||||||
@ -422,7 +448,7 @@ export class _ParseAST {
|
|||||||
parseLogicalAnd(): AST {
|
parseLogicalAnd(): AST {
|
||||||
// '&&'
|
// '&&'
|
||||||
let result = this.parseEquality();
|
let result = this.parseEquality();
|
||||||
while (this.optionalOperator('&&')) {
|
while (this.consumeOptionalOperator('&&')) {
|
||||||
const right = this.parseEquality();
|
const right = this.parseEquality();
|
||||||
const {start} = result.span;
|
const {start} = result.span;
|
||||||
result = new Binary(this.span(start), this.sourceSpan(start), '&&', result, right);
|
result = new Binary(this.span(start), this.sourceSpan(start), '&&', result, right);
|
||||||
@ -544,18 +570,18 @@ export class _ParseAST {
|
|||||||
let result = this.parsePrimary();
|
let result = this.parsePrimary();
|
||||||
const resultStart = result.span.start;
|
const resultStart = result.span.start;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (this.optionalCharacter(chars.$PERIOD)) {
|
if (this.consumeOptionalCharacter(chars.$PERIOD)) {
|
||||||
result = this.parseAccessMemberOrMethodCall(result, false);
|
result = this.parseAccessMemberOrMethodCall(result, false);
|
||||||
|
|
||||||
} else if (this.optionalOperator('?.')) {
|
} else if (this.consumeOptionalOperator('?.')) {
|
||||||
result = this.parseAccessMemberOrMethodCall(result, true);
|
result = this.parseAccessMemberOrMethodCall(result, true);
|
||||||
|
|
||||||
} else if (this.optionalCharacter(chars.$LBRACKET)) {
|
} else if (this.consumeOptionalCharacter(chars.$LBRACKET)) {
|
||||||
this.rbracketsExpected++;
|
this.rbracketsExpected++;
|
||||||
const key = this.parsePipe();
|
const key = this.parsePipe();
|
||||||
this.rbracketsExpected--;
|
this.rbracketsExpected--;
|
||||||
this.expectCharacter(chars.$RBRACKET);
|
this.expectCharacter(chars.$RBRACKET);
|
||||||
if (this.optionalOperator('=')) {
|
if (this.consumeOptionalOperator('=')) {
|
||||||
const value = this.parseConditional();
|
const value = this.parseConditional();
|
||||||
result = new KeyedWrite(
|
result = new KeyedWrite(
|
||||||
this.span(resultStart), this.sourceSpan(resultStart), result, key, value);
|
this.span(resultStart), this.sourceSpan(resultStart), result, key, value);
|
||||||
@ -563,7 +589,7 @@ export class _ParseAST {
|
|||||||
result = new KeyedRead(this.span(resultStart), this.sourceSpan(resultStart), result, key);
|
result = new KeyedRead(this.span(resultStart), this.sourceSpan(resultStart), result, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (this.optionalCharacter(chars.$LPAREN)) {
|
} else if (this.consumeOptionalCharacter(chars.$LPAREN)) {
|
||||||
this.rparensExpected++;
|
this.rparensExpected++;
|
||||||
const args = this.parseCallArguments();
|
const args = this.parseCallArguments();
|
||||||
this.rparensExpected--;
|
this.rparensExpected--;
|
||||||
@ -571,7 +597,7 @@ export class _ParseAST {
|
|||||||
result =
|
result =
|
||||||
new FunctionCall(this.span(resultStart), this.sourceSpan(resultStart), result, args);
|
new FunctionCall(this.span(resultStart), this.sourceSpan(resultStart), result, args);
|
||||||
|
|
||||||
} else if (this.optionalOperator('!')) {
|
} else if (this.consumeOptionalOperator('!')) {
|
||||||
result = new NonNullAssert(this.span(resultStart), this.sourceSpan(resultStart), result);
|
result = new NonNullAssert(this.span(resultStart), this.sourceSpan(resultStart), result);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -582,7 +608,7 @@ export class _ParseAST {
|
|||||||
|
|
||||||
parsePrimary(): AST {
|
parsePrimary(): AST {
|
||||||
const start = this.inputIndex;
|
const start = this.inputIndex;
|
||||||
if (this.optionalCharacter(chars.$LPAREN)) {
|
if (this.consumeOptionalCharacter(chars.$LPAREN)) {
|
||||||
this.rparensExpected++;
|
this.rparensExpected++;
|
||||||
const result = this.parsePipe();
|
const result = this.parsePipe();
|
||||||
this.rparensExpected--;
|
this.rparensExpected--;
|
||||||
@ -609,7 +635,7 @@ export class _ParseAST {
|
|||||||
this.advance();
|
this.advance();
|
||||||
return new ImplicitReceiver(this.span(start), this.sourceSpan(start));
|
return new ImplicitReceiver(this.span(start), this.sourceSpan(start));
|
||||||
|
|
||||||
} else if (this.optionalCharacter(chars.$LBRACKET)) {
|
} else if (this.consumeOptionalCharacter(chars.$LBRACKET)) {
|
||||||
this.rbracketsExpected++;
|
this.rbracketsExpected++;
|
||||||
const elements = this.parseExpressionList(chars.$RBRACKET);
|
const elements = this.parseExpressionList(chars.$RBRACKET);
|
||||||
this.rbracketsExpected--;
|
this.rbracketsExpected--;
|
||||||
@ -647,7 +673,7 @@ export class _ParseAST {
|
|||||||
if (!this.next.isCharacter(terminator)) {
|
if (!this.next.isCharacter(terminator)) {
|
||||||
do {
|
do {
|
||||||
result.push(this.parsePipe());
|
result.push(this.parsePipe());
|
||||||
} while (this.optionalCharacter(chars.$COMMA));
|
} while (this.consumeOptionalCharacter(chars.$COMMA));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -657,7 +683,7 @@ export class _ParseAST {
|
|||||||
const values: AST[] = [];
|
const values: AST[] = [];
|
||||||
const start = this.inputIndex;
|
const start = this.inputIndex;
|
||||||
this.expectCharacter(chars.$LBRACE);
|
this.expectCharacter(chars.$LBRACE);
|
||||||
if (!this.optionalCharacter(chars.$RBRACE)) {
|
if (!this.consumeOptionalCharacter(chars.$RBRACE)) {
|
||||||
this.rbracesExpected++;
|
this.rbracesExpected++;
|
||||||
do {
|
do {
|
||||||
const quoted = this.next.isString();
|
const quoted = this.next.isString();
|
||||||
@ -665,7 +691,7 @@ export class _ParseAST {
|
|||||||
keys.push({key, quoted});
|
keys.push({key, quoted});
|
||||||
this.expectCharacter(chars.$COLON);
|
this.expectCharacter(chars.$COLON);
|
||||||
values.push(this.parsePipe());
|
values.push(this.parsePipe());
|
||||||
} while (this.optionalCharacter(chars.$COMMA));
|
} while (this.consumeOptionalCharacter(chars.$COMMA));
|
||||||
this.rbracesExpected--;
|
this.rbracesExpected--;
|
||||||
this.expectCharacter(chars.$RBRACE);
|
this.expectCharacter(chars.$RBRACE);
|
||||||
}
|
}
|
||||||
@ -676,7 +702,7 @@ export class _ParseAST {
|
|||||||
const start = receiver.span.start;
|
const start = receiver.span.start;
|
||||||
const id = this.expectIdentifierOrKeyword();
|
const id = this.expectIdentifierOrKeyword();
|
||||||
|
|
||||||
if (this.optionalCharacter(chars.$LPAREN)) {
|
if (this.consumeOptionalCharacter(chars.$LPAREN)) {
|
||||||
this.rparensExpected++;
|
this.rparensExpected++;
|
||||||
const args = this.parseCallArguments();
|
const args = this.parseCallArguments();
|
||||||
this.expectCharacter(chars.$RPAREN);
|
this.expectCharacter(chars.$RPAREN);
|
||||||
@ -688,14 +714,14 @@ export class _ParseAST {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (isSafe) {
|
if (isSafe) {
|
||||||
if (this.optionalOperator('=')) {
|
if (this.consumeOptionalOperator('=')) {
|
||||||
this.error('The \'?.\' operator cannot be used in the assignment');
|
this.error('The \'?.\' operator cannot be used in the assignment');
|
||||||
return new EmptyExpr(this.span(start), this.sourceSpan(start));
|
return new EmptyExpr(this.span(start), this.sourceSpan(start));
|
||||||
} else {
|
} else {
|
||||||
return new SafePropertyRead(this.span(start), this.sourceSpan(start), receiver, id);
|
return new SafePropertyRead(this.span(start), this.sourceSpan(start), receiver, id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.optionalOperator('=')) {
|
if (this.consumeOptionalOperator('=')) {
|
||||||
if (!this.parseAction) {
|
if (!this.parseAction) {
|
||||||
this.error('Bindings cannot contain assignments');
|
this.error('Bindings cannot contain assignments');
|
||||||
return new EmptyExpr(this.span(start), this.sourceSpan(start));
|
return new EmptyExpr(this.span(start), this.sourceSpan(start));
|
||||||
@ -716,84 +742,208 @@ export class _ParseAST {
|
|||||||
const positionals: AST[] = [];
|
const positionals: AST[] = [];
|
||||||
do {
|
do {
|
||||||
positionals.push(this.parsePipe());
|
positionals.push(this.parsePipe());
|
||||||
} while (this.optionalCharacter(chars.$COMMA));
|
} while (this.consumeOptionalCharacter(chars.$COMMA));
|
||||||
return positionals as BindingPipe[];
|
return positionals as BindingPipe[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An identifier, a keyword, a string with an optional `-` in between.
|
* Parses an identifier, a keyword, a string with an optional `-` in between.
|
||||||
*/
|
*/
|
||||||
expectTemplateBindingKey(): string {
|
expectTemplateBindingKey(): {key: string, keySpan: ParseSpan} {
|
||||||
let result = '';
|
let result = '';
|
||||||
let operatorFound = false;
|
let operatorFound = false;
|
||||||
|
const start = this.inputIndex;
|
||||||
do {
|
do {
|
||||||
result += this.expectIdentifierOrKeywordOrString();
|
result += this.expectIdentifierOrKeywordOrString();
|
||||||
operatorFound = this.optionalOperator('-');
|
operatorFound = this.consumeOptionalOperator('-');
|
||||||
if (operatorFound) {
|
if (operatorFound) {
|
||||||
result += '-';
|
result += '-';
|
||||||
}
|
}
|
||||||
} while (operatorFound);
|
} while (operatorFound);
|
||||||
|
return {
|
||||||
return result.toString();
|
key: result,
|
||||||
|
keySpan: new ParseSpan(start, start + result.length),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses the AST for `<some-tag *tplKey=AST>`
|
/**
|
||||||
parseTemplateBindings(tplKey: string): TemplateBindingParseResult {
|
* Parse microsyntax template expression and return a list of bindings or
|
||||||
let firstBinding = true;
|
* parsing errors in case the given expression is invalid.
|
||||||
|
*
|
||||||
|
* For example,
|
||||||
|
* ```
|
||||||
|
* <div *ngFor="let item of items; index as i; trackBy: func">
|
||||||
|
* ```
|
||||||
|
* contains five bindings:
|
||||||
|
* 1. ngFor -> null
|
||||||
|
* 2. item -> NgForOfContext.$implicit
|
||||||
|
* 3. ngForOf -> items
|
||||||
|
* 4. i -> NgForOfContext.index
|
||||||
|
* 5. ngForTrackBy -> func
|
||||||
|
*
|
||||||
|
* For a full description of the microsyntax grammar, see
|
||||||
|
* https://gist.github.com/mhevery/d3530294cff2e4a1b3fe15ff75d08855
|
||||||
|
*
|
||||||
|
* @param templateKey name of the microsyntax directive, like ngIf, ngFor, without the *
|
||||||
|
*/
|
||||||
|
parseTemplateBindings(templateKey: string): TemplateBindingParseResult {
|
||||||
const bindings: TemplateBinding[] = [];
|
const bindings: TemplateBinding[] = [];
|
||||||
const warnings: string[] = [];
|
|
||||||
do {
|
// The first binding is for the template key itself
|
||||||
const start = this.inputIndex;
|
// In *ngFor="let item of items", key = "ngFor", value = null
|
||||||
let rawKey: string;
|
// In *ngIf="cond | pipe", key = "ngIf", value = "cond | pipe"
|
||||||
let key: string;
|
bindings.push(...this.parseDirectiveKeywordBindings(
|
||||||
let isVar: boolean = false;
|
templateKey, new ParseSpan(0, templateKey.length), this.absoluteOffset));
|
||||||
if (firstBinding) {
|
|
||||||
rawKey = key = tplKey;
|
while (this.index < this.tokens.length) {
|
||||||
firstBinding = false;
|
// If it starts with 'let', then this must be variable declaration
|
||||||
|
const letBinding = this.parseLetBinding();
|
||||||
|
if (letBinding) {
|
||||||
|
bindings.push(letBinding);
|
||||||
} else {
|
} else {
|
||||||
isVar = this.peekKeywordLet();
|
// Two possible cases here, either `value "as" key` or
|
||||||
if (isVar) this.advance();
|
// "directive-keyword expression". We don't know which case, but both
|
||||||
rawKey = this.expectTemplateBindingKey();
|
// "value" and "directive-keyword" are template binding key, so consume
|
||||||
key = isVar ? rawKey : tplKey + rawKey[0].toUpperCase() + rawKey.substring(1);
|
// the key first.
|
||||||
this.optionalCharacter(chars.$COLON);
|
const {key, keySpan} = this.expectTemplateBindingKey();
|
||||||
}
|
// Peek at the next token, if it is "as" then this must be variable
|
||||||
|
// declaration.
|
||||||
let name: string = null !;
|
const binding = this.parseAsBinding(key, keySpan, this.absoluteOffset);
|
||||||
let expression: ASTWithSource|null = null;
|
if (binding) {
|
||||||
if (isVar) {
|
bindings.push(binding);
|
||||||
if (this.optionalOperator('=')) {
|
|
||||||
name = this.expectTemplateBindingKey();
|
|
||||||
} else {
|
} else {
|
||||||
name = '\$implicit';
|
// Otherwise the key must be a directive keyword, like "of". Transform
|
||||||
|
// the key to actual key. Eg. of -> ngForOf, trackBy -> ngForTrackBy
|
||||||
|
const actualKey = templateKey + key[0].toUpperCase() + key.substring(1);
|
||||||
|
bindings.push(
|
||||||
|
...this.parseDirectiveKeywordBindings(actualKey, keySpan, this.absoluteOffset));
|
||||||
}
|
}
|
||||||
} else if (this.peekKeywordAs()) {
|
|
||||||
this.advance(); // consume `as`
|
|
||||||
name = rawKey;
|
|
||||||
key = this.expectTemplateBindingKey(); // read local var name
|
|
||||||
isVar = true;
|
|
||||||
} else if (this.next !== EOF && !this.peekKeywordLet()) {
|
|
||||||
const start = this.inputIndex;
|
|
||||||
const ast = this.parsePipe();
|
|
||||||
const source = this.input.substring(start - this.offset, this.inputIndex - this.offset);
|
|
||||||
expression =
|
|
||||||
new ASTWithSource(ast, source, this.location, this.absoluteOffset + start, this.errors);
|
|
||||||
}
|
}
|
||||||
|
this.consumeStatementTerminator();
|
||||||
|
}
|
||||||
|
|
||||||
bindings.push(new TemplateBinding(
|
return new TemplateBindingParseResult(bindings, [] /* warnings */, this.errors);
|
||||||
this.span(start), this.sourceSpan(start), key, isVar, name, expression));
|
}
|
||||||
if (this.peekKeywordAs() && !isVar) {
|
|
||||||
const letStart = this.inputIndex;
|
|
||||||
this.advance(); // consume `as`
|
|
||||||
const letName = this.expectTemplateBindingKey(); // read local var name
|
|
||||||
bindings.push(new TemplateBinding(
|
|
||||||
this.span(letStart), this.sourceSpan(letStart), letName, true, key, null !));
|
|
||||||
}
|
|
||||||
if (!this.optionalCharacter(chars.$SEMICOLON)) {
|
|
||||||
this.optionalCharacter(chars.$COMMA);
|
|
||||||
}
|
|
||||||
} while (this.index < this.tokens.length);
|
|
||||||
|
|
||||||
return new TemplateBindingParseResult(bindings, warnings, this.errors);
|
/**
|
||||||
|
* Parse a directive keyword, followed by a mandatory expression.
|
||||||
|
* For example, "of items", "trackBy: func".
|
||||||
|
* The bindings are: ngForOf -> items, ngForTrackBy -> func
|
||||||
|
* There could be an optional "as" binding that follows the expression.
|
||||||
|
* For example,
|
||||||
|
* ```
|
||||||
|
* *ngFor="let item of items | slice:0:1 as collection".`
|
||||||
|
* ^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^
|
||||||
|
* keyword bound target optional 'as' binding
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param key binding key, for example, ngFor, ngIf, ngForOf
|
||||||
|
* @param keySpan span of the key in the expression. keySpan might be different
|
||||||
|
* from `key.length`. For example, the span for key "ngForOf" is "of".
|
||||||
|
* @param absoluteOffset absolute offset of the attribute value
|
||||||
|
*/
|
||||||
|
private parseDirectiveKeywordBindings(key: string, keySpan: ParseSpan, absoluteOffset: number):
|
||||||
|
TemplateBinding[] {
|
||||||
|
const bindings: TemplateBinding[] = [];
|
||||||
|
this.consumeOptionalCharacter(chars.$COLON); // trackBy: trackByFunction
|
||||||
|
const valueExpr = this.getDirectiveBoundTarget();
|
||||||
|
const span = new ParseSpan(keySpan.start, this.inputIndex);
|
||||||
|
bindings.push(new TemplateBinding(
|
||||||
|
span, span.toAbsolute(absoluteOffset), key, false /* keyIsVar */, valueExpr?.source || '', valueExpr));
|
||||||
|
// The binding could optionally be followed by "as". For example,
|
||||||
|
// *ngIf="cond | pipe as x". In this case, the key in the "as" binding
|
||||||
|
// is "x" and the value is the template key itself ("ngIf"). Note that the
|
||||||
|
// 'key' in the current context now becomes the "value" in the next binding.
|
||||||
|
const asBinding = this.parseAsBinding(key, keySpan, absoluteOffset);
|
||||||
|
if (asBinding) {
|
||||||
|
bindings.push(asBinding);
|
||||||
|
}
|
||||||
|
this.consumeStatementTerminator();
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the expression AST for the bound target of a directive keyword
|
||||||
|
* binding. For example,
|
||||||
|
* ```
|
||||||
|
* *ngIf="condition | pipe".
|
||||||
|
* ^^^^^^^^^^^^^^^^ bound target for "ngIf"
|
||||||
|
* *ngFor="let item of items"
|
||||||
|
* ^^^^^ bound target for "ngForOf"
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
private getDirectiveBoundTarget(): ASTWithSource|null {
|
||||||
|
if (this.next === EOF || this.peekKeywordAs() || this.peekKeywordLet()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const ast = this.parsePipe(); // example: "condition | async"
|
||||||
|
const {start, end} = ast.span;
|
||||||
|
const value = this.input.substring(start, end);
|
||||||
|
return new ASTWithSource(ast, value, this.location, this.absoluteOffset + start, this.errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the binding for a variable declared using `as`. Note that the order
|
||||||
|
* of the key-value pair in this declaration is reversed. For example,
|
||||||
|
* ```
|
||||||
|
* *ngFor="let item of items; index as i"
|
||||||
|
* ^^^^^ ^
|
||||||
|
* value key
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param value name of the value in the declaration, "ngIf" in the example above
|
||||||
|
* @param valueSpan span of the value in the declaration
|
||||||
|
* @param absoluteOffset absolute offset of `value`
|
||||||
|
*/
|
||||||
|
private parseAsBinding(value: string, valueSpan: ParseSpan, absoluteOffset: number):
|
||||||
|
TemplateBinding|null {
|
||||||
|
if (!this.peekKeywordAs()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.advance(); // consume the 'as' keyword
|
||||||
|
const {key} = this.expectTemplateBindingKey();
|
||||||
|
const valueAst = new AST(valueSpan, valueSpan.toAbsolute(absoluteOffset));
|
||||||
|
const valueExpr = new ASTWithSource(
|
||||||
|
valueAst, value, this.location, absoluteOffset + valueSpan.start, this.errors);
|
||||||
|
const span = new ParseSpan(valueSpan.start, this.inputIndex);
|
||||||
|
return new TemplateBinding(
|
||||||
|
span, span.toAbsolute(absoluteOffset), key, true /* keyIsVar */, value, valueExpr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the binding for a variable declared using `let`. For example,
|
||||||
|
* ```
|
||||||
|
* *ngFor="let item of items; let i=index;"
|
||||||
|
* ^^^^^^^^ ^^^^^^^^^^^
|
||||||
|
* ```
|
||||||
|
* In the first binding, `item` is bound to `NgForOfContext.$implicit`.
|
||||||
|
* In the second binding, `i` is bound to `NgForOfContext.index`.
|
||||||
|
*/
|
||||||
|
private parseLetBinding(): TemplateBinding|null {
|
||||||
|
if (!this.peekKeywordLet()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const spanStart = this.inputIndex;
|
||||||
|
this.advance(); // consume the 'let' keyword
|
||||||
|
const {key} = this.expectTemplateBindingKey();
|
||||||
|
let valueExpr: ASTWithSource|null = null;
|
||||||
|
if (this.consumeOptionalOperator('=')) {
|
||||||
|
const {key: value, keySpan: valueSpan} = this.expectTemplateBindingKey();
|
||||||
|
const ast = new AST(valueSpan, valueSpan.toAbsolute(this.absoluteOffset));
|
||||||
|
valueExpr = new ASTWithSource(
|
||||||
|
ast, value, this.location, this.absoluteOffset + valueSpan.start, this.errors);
|
||||||
|
}
|
||||||
|
const spanEnd = this.inputIndex;
|
||||||
|
const span = new ParseSpan(spanStart, spanEnd);
|
||||||
|
return new TemplateBinding(
|
||||||
|
span, span.toAbsolute(this.absoluteOffset), key, true /* keyIsVar */, valueExpr?.source || '$implicit', valueExpr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consume the optional statement terminator: semicolon or comma.
|
||||||
|
*/
|
||||||
|
private consumeStatementTerminator() {
|
||||||
|
this.consumeOptionalCharacter(chars.$SEMICOLON) || this.consumeOptionalCharacter(chars.$COMMA);
|
||||||
}
|
}
|
||||||
|
|
||||||
error(message: string, index: number|null = null) {
|
error(message: string, index: number|null = null) {
|
||||||
|
@ -87,7 +87,7 @@ function baseDirectiveFields(
|
|||||||
*/
|
*/
|
||||||
function addFeatures(
|
function addFeatures(
|
||||||
definitionMap: DefinitionMap, meta: R3DirectiveMetadata | R3ComponentMetadata) {
|
definitionMap: DefinitionMap, meta: R3DirectiveMetadata | R3ComponentMetadata) {
|
||||||
// e.g. `features: [NgOnChangesFeature()]`
|
// e.g. `features: [NgOnChangesFeature]`
|
||||||
const features: o.Expression[] = [];
|
const features: o.Expression[] = [];
|
||||||
|
|
||||||
const providers = meta.providers;
|
const providers = meta.providers;
|
||||||
@ -107,7 +107,7 @@ function addFeatures(
|
|||||||
features.push(o.importExpr(R3.CopyDefinitionFeature));
|
features.push(o.importExpr(R3.CopyDefinitionFeature));
|
||||||
}
|
}
|
||||||
if (meta.lifecycle.usesOnChanges) {
|
if (meta.lifecycle.usesOnChanges) {
|
||||||
features.push(o.importExpr(R3.NgOnChangesFeature).callFn(EMPTY_ARRAY));
|
features.push(o.importExpr(R3.NgOnChangesFeature));
|
||||||
}
|
}
|
||||||
if (features.length) {
|
if (features.length) {
|
||||||
definitionMap.set('features', o.literalArr(features));
|
definitionMap.set('features', o.literalArr(features));
|
||||||
|
@ -134,10 +134,9 @@ export class BindingParser {
|
|||||||
const binding = bindings[i];
|
const binding = bindings[i];
|
||||||
if (binding.keyIsVar) {
|
if (binding.keyIsVar) {
|
||||||
targetVars.push(new ParsedVariable(binding.key, binding.name, sourceSpan));
|
targetVars.push(new ParsedVariable(binding.key, binding.name, sourceSpan));
|
||||||
} else if (binding.expression) {
|
} else if (binding.value) {
|
||||||
this._parsePropertyAst(
|
this._parsePropertyAst(
|
||||||
binding.key, binding.expression, sourceSpan, undefined, targetMatchableAttrs,
|
binding.key, binding.value, sourceSpan, undefined, targetMatchableAttrs, targetProps);
|
||||||
targetProps);
|
|
||||||
} else {
|
} else {
|
||||||
targetMatchableAttrs.push([binding.key, '']);
|
targetMatchableAttrs.push([binding.key, '']);
|
||||||
this.parseLiteralAttr(
|
this.parseLiteralAttr(
|
||||||
@ -165,8 +164,8 @@ export class BindingParser {
|
|||||||
this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceInfo, absoluteValueOffset);
|
this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceInfo, absoluteValueOffset);
|
||||||
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
||||||
bindingsResult.templateBindings.forEach((binding) => {
|
bindingsResult.templateBindings.forEach((binding) => {
|
||||||
if (binding.expression) {
|
if (binding.value) {
|
||||||
this._checkPipes(binding.expression, sourceSpan);
|
this._checkPipes(binding.value, sourceSpan);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
bindingsResult.warnings.forEach(
|
bindingsResult.warnings.forEach(
|
||||||
|
@ -248,14 +248,16 @@ describe('parser', () => {
|
|||||||
|
|
||||||
describe('parseTemplateBindings', () => {
|
describe('parseTemplateBindings', () => {
|
||||||
|
|
||||||
function keys(templateBindings: any[]) { return templateBindings.map(binding => binding.key); }
|
function keys(templateBindings: TemplateBinding[]) {
|
||||||
|
return templateBindings.map(binding => binding.key);
|
||||||
|
}
|
||||||
|
|
||||||
function keyValues(templateBindings: any[]) {
|
function keyValues(templateBindings: TemplateBinding[]) {
|
||||||
return templateBindings.map(binding => {
|
return templateBindings.map(binding => {
|
||||||
if (binding.keyIsVar) {
|
if (binding.keyIsVar) {
|
||||||
return 'let ' + binding.key + (binding.name == null ? '=null' : '=' + binding.name);
|
return 'let ' + binding.key + (binding.name == null ? '=null' : '=' + binding.name);
|
||||||
} else {
|
} else {
|
||||||
return binding.key + (binding.expression == null ? '' : `=${binding.expression}`);
|
return binding.key + (binding.value == null ? '' : `=${binding.value}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -265,9 +267,16 @@ describe('parser', () => {
|
|||||||
binding => source.substring(binding.span.start, binding.span.end));
|
binding => source.substring(binding.span.start, binding.span.end));
|
||||||
}
|
}
|
||||||
|
|
||||||
function exprSources(templateBindings: any[]) {
|
function exprSources(templateBindings: TemplateBinding[]) {
|
||||||
return templateBindings.map(
|
return templateBindings.map(binding => binding.value != null ? binding.value.source : null);
|
||||||
binding => binding.expression != null ? binding.expression.source : null);
|
}
|
||||||
|
|
||||||
|
function humanize(bindings: TemplateBinding[]): Array<[string, string | null, boolean]> {
|
||||||
|
return bindings.map(binding => {
|
||||||
|
const {key, value: expression, name, keyIsVar} = binding;
|
||||||
|
const value = keyIsVar ? name : (expression ? expression.source : expression);
|
||||||
|
return [key, value, keyIsVar];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should parse a key without a value',
|
it('should parse a key without a value',
|
||||||
@ -308,13 +317,51 @@ describe('parser', () => {
|
|||||||
|
|
||||||
it('should store the sources in the result', () => {
|
it('should store the sources in the result', () => {
|
||||||
const bindings = parseTemplateBindings('a', '1,b 2');
|
const bindings = parseTemplateBindings('a', '1,b 2');
|
||||||
expect(bindings[0].expression !.source).toEqual('1');
|
expect(bindings[0].value !.source).toEqual('1');
|
||||||
expect(bindings[1].expression !.source).toEqual('2');
|
expect(bindings[1].value !.source).toEqual('2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should store the passed-in location', () => {
|
it('should store the passed-in location', () => {
|
||||||
const bindings = parseTemplateBindings('a', '1,b 2', 'location');
|
const bindings = parseTemplateBindings('a', '1,b 2', 'location');
|
||||||
expect(bindings[0].expression !.location).toEqual('location');
|
expect(bindings[0].value !.location).toEqual('location');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support common usage of ngIf', () => {
|
||||||
|
const bindings = parseTemplateBindings('ngIf', 'cond | pipe as foo, let x; ngIf as y');
|
||||||
|
expect(humanize(bindings)).toEqual([
|
||||||
|
// [ key, value, keyIsVar ]
|
||||||
|
['ngIf', 'cond | pipe ', false],
|
||||||
|
['foo', 'ngIf', true],
|
||||||
|
['x', '$implicit', true],
|
||||||
|
['y', 'ngIf', true],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support common usage of ngFor', () => {
|
||||||
|
let bindings: TemplateBinding[];
|
||||||
|
bindings = parseTemplateBindings(
|
||||||
|
'ngFor', 'let item; of items | slice:0:1 as collection, trackBy: func; index as i');
|
||||||
|
expect(humanize(bindings)).toEqual([
|
||||||
|
// [ key, value, keyIsVar ]
|
||||||
|
['ngFor', null, false],
|
||||||
|
['item', '$implicit', true],
|
||||||
|
['ngForOf', 'items | slice:0:1 ', false],
|
||||||
|
['collection', 'ngForOf', true],
|
||||||
|
['ngForTrackBy', 'func', false],
|
||||||
|
['i', 'index', true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
bindings = parseTemplateBindings(
|
||||||
|
'ngFor', 'let item, of: [1,2,3] | pipe as items; let i=index, count as len');
|
||||||
|
expect(humanize(bindings)).toEqual([
|
||||||
|
// [ key, value, keyIsVar ]
|
||||||
|
['ngFor', null, false],
|
||||||
|
['item', '$implicit', true],
|
||||||
|
['ngForOf', '[1,2,3] | pipe ', false],
|
||||||
|
['items', 'ngForOf', true],
|
||||||
|
['i', 'index', true],
|
||||||
|
['len', 'count', true],
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support let notation', () => {
|
it('should support let notation', () => {
|
||||||
@ -369,7 +416,7 @@ describe('parser', () => {
|
|||||||
|
|
||||||
it('should parse pipes', () => {
|
it('should parse pipes', () => {
|
||||||
const bindings = parseTemplateBindings('key', 'value|pipe');
|
const bindings = parseTemplateBindings('key', 'value|pipe');
|
||||||
const ast = bindings[0].expression !.ast;
|
const ast = bindings[0].value !.ast;
|
||||||
expect(ast).toBeAnInstanceOf(BindingPipe);
|
expect(ast).toBeAnInstanceOf(BindingPipe);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -20,6 +20,19 @@ export function createNgcProgram(
|
|||||||
// NGC program. In order to ensure that the migration runs properly, we set "enableIvy" to false.
|
// NGC program. In order to ensure that the migration runs properly, we set "enableIvy" to false.
|
||||||
options.enableIvy = false;
|
options.enableIvy = false;
|
||||||
|
|
||||||
|
// Libraries which have been generated with CLI versions past v6.2.0, explicitly set the
|
||||||
|
// flat-module options in their tsconfig files. This is problematic because by default,
|
||||||
|
// those tsconfig files do not specify explicit source files which can be considered as
|
||||||
|
// entry point for the flat-module bundle. Therefore the `@angular/compiler-cli` is unable
|
||||||
|
// to determine the flat module entry point and throws a compile error. This is not an issue
|
||||||
|
// for the libraries built with `ng-packagr`, because the tsconfig files are modified in-memory
|
||||||
|
// to specify an explicit flat module entry-point. Our migrations don't distinguish between
|
||||||
|
// libraries and applications, and also don't run `ng-packagr`. To ensure that such libraries
|
||||||
|
// can be successfully migrated, we remove the flat-module options to eliminate the flat module
|
||||||
|
// entry-point requirement. More context: https://github.com/angular/angular/issues/34985.
|
||||||
|
options.flatModuleId = undefined;
|
||||||
|
options.flatModuleOutFile = undefined;
|
||||||
|
|
||||||
const host = createHost(options);
|
const host = createHost(options);
|
||||||
|
|
||||||
// For this migration, we never need to read resources and can just return
|
// For this migration, we never need to read resources and can just return
|
||||||
|
@ -1473,6 +1473,40 @@ describe('Undecorated classes with DI migration', () => {
|
|||||||
'TypeScript program failures');
|
'TypeScript program failures');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Regression test for: https://github.com/angular/angular/issues/34985.
|
||||||
|
it('should be able to migrate libraries with multiple source files and flat-module ' +
|
||||||
|
'options set',
|
||||||
|
async() => {
|
||||||
|
writeFile('/tsconfig.json', JSON.stringify({
|
||||||
|
compilerOptions: {
|
||||||
|
lib: ['es2015'],
|
||||||
|
},
|
||||||
|
angularCompilerOptions:
|
||||||
|
{flatModuleId: 'AUTOGENERATED', flatModuleOutFile: 'AUTOGENERATED'}
|
||||||
|
}));
|
||||||
|
|
||||||
|
writeFile('/second.ts', ``);
|
||||||
|
writeFile('/test.ts', `
|
||||||
|
import {Injectable, NgModule, NgZone} from '@angular/core';
|
||||||
|
|
||||||
|
export class BaseClass {
|
||||||
|
constructor(zone: NgZone) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({template: ''})
|
||||||
|
export class MyService extends BaseClass {}
|
||||||
|
|
||||||
|
@NgModule({providers: [MyService]})
|
||||||
|
export class AppModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
await runMigration();
|
||||||
|
|
||||||
|
expect(errorOutput.length).toBe(0);
|
||||||
|
expect(warnOutput.length).toBe(0);
|
||||||
|
expect(tree.readContent('/test.ts')).toMatch(/@Injectable\(\)\nexport class BaseClass {/);
|
||||||
|
});
|
||||||
|
|
||||||
it('should not throw if resources could not be read', async() => {
|
it('should not throw if resources could not be read', async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {Component, NgModule} from '@angular/core';
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
@ -289,56 +289,56 @@ export function ɵɵdefineComponent<T>(componentDefinition: {
|
|||||||
*/
|
*/
|
||||||
schemas?: SchemaMetadata[] | null;
|
schemas?: SchemaMetadata[] | null;
|
||||||
}): never {
|
}): never {
|
||||||
// Initialize ngDevMode. This must be the first statement in ɵɵdefineComponent.
|
return noSideEffects(() => {
|
||||||
// See the `initNgDevMode` docstring for more information.
|
// Initialize ngDevMode. This must be the first statement in ɵɵdefineComponent.
|
||||||
(typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode();
|
// See the `initNgDevMode` docstring for more information.
|
||||||
|
(typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode();
|
||||||
|
|
||||||
const type = componentDefinition.type;
|
const type = componentDefinition.type;
|
||||||
const typePrototype = type.prototype;
|
const typePrototype = type.prototype;
|
||||||
const declaredInputs: {[key: string]: string} = {} as any;
|
const declaredInputs: {[key: string]: string} = {} as any;
|
||||||
const def: Mutable<ComponentDef<any>, keyof ComponentDef<any>> = {
|
const def: Mutable<ComponentDef<any>, keyof ComponentDef<any>> = {
|
||||||
type: type,
|
type: type,
|
||||||
providersResolver: null,
|
providersResolver: null,
|
||||||
decls: componentDefinition.decls,
|
decls: componentDefinition.decls,
|
||||||
vars: componentDefinition.vars,
|
vars: componentDefinition.vars,
|
||||||
factory: null,
|
factory: null,
|
||||||
template: componentDefinition.template || null !,
|
template: componentDefinition.template || null !,
|
||||||
consts: componentDefinition.consts || null,
|
consts: componentDefinition.consts || null,
|
||||||
ngContentSelectors: componentDefinition.ngContentSelectors,
|
ngContentSelectors: componentDefinition.ngContentSelectors,
|
||||||
hostBindings: componentDefinition.hostBindings || null,
|
hostBindings: componentDefinition.hostBindings || null,
|
||||||
hostVars: componentDefinition.hostVars || 0,
|
hostVars: componentDefinition.hostVars || 0,
|
||||||
hostAttrs: componentDefinition.hostAttrs || null,
|
hostAttrs: componentDefinition.hostAttrs || null,
|
||||||
contentQueries: componentDefinition.contentQueries || null,
|
contentQueries: componentDefinition.contentQueries || null,
|
||||||
declaredInputs: declaredInputs,
|
declaredInputs: declaredInputs,
|
||||||
inputs: null !, // assigned in noSideEffects
|
inputs: null !, // assigned in noSideEffects
|
||||||
outputs: null !, // assigned in noSideEffects
|
outputs: null !, // assigned in noSideEffects
|
||||||
exportAs: componentDefinition.exportAs || null,
|
exportAs: componentDefinition.exportAs || null,
|
||||||
onChanges: null,
|
onChanges: null,
|
||||||
onInit: typePrototype.ngOnInit || null,
|
onInit: typePrototype.ngOnInit || null,
|
||||||
doCheck: typePrototype.ngDoCheck || null,
|
doCheck: typePrototype.ngDoCheck || null,
|
||||||
afterContentInit: typePrototype.ngAfterContentInit || null,
|
afterContentInit: typePrototype.ngAfterContentInit || null,
|
||||||
afterContentChecked: typePrototype.ngAfterContentChecked || null,
|
afterContentChecked: typePrototype.ngAfterContentChecked || null,
|
||||||
afterViewInit: typePrototype.ngAfterViewInit || null,
|
afterViewInit: typePrototype.ngAfterViewInit || null,
|
||||||
afterViewChecked: typePrototype.ngAfterViewChecked || null,
|
afterViewChecked: typePrototype.ngAfterViewChecked || null,
|
||||||
onDestroy: typePrototype.ngOnDestroy || null,
|
onDestroy: typePrototype.ngOnDestroy || null,
|
||||||
onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush,
|
onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush,
|
||||||
directiveDefs: null !, // assigned in noSideEffects
|
directiveDefs: null !, // assigned in noSideEffects
|
||||||
pipeDefs: null !, // assigned in noSideEffects
|
pipeDefs: null !, // assigned in noSideEffects
|
||||||
selectors: componentDefinition.selectors || EMPTY_ARRAY,
|
selectors: componentDefinition.selectors || EMPTY_ARRAY,
|
||||||
viewQuery: componentDefinition.viewQuery || null,
|
viewQuery: componentDefinition.viewQuery || null,
|
||||||
features: componentDefinition.features as DirectiveDefFeature[] || null,
|
features: componentDefinition.features as DirectiveDefFeature[] || null,
|
||||||
data: componentDefinition.data || {},
|
data: componentDefinition.data || {},
|
||||||
// TODO(misko): convert ViewEncapsulation into const enum so that it can be used directly in the
|
// TODO(misko): convert ViewEncapsulation into const enum so that it can be used directly in
|
||||||
// next line. Also `None` should be 0 not 2.
|
// the next line. Also `None` should be 0 not 2.
|
||||||
encapsulation: componentDefinition.encapsulation || ViewEncapsulation.Emulated,
|
encapsulation: componentDefinition.encapsulation || ViewEncapsulation.Emulated,
|
||||||
id: 'c',
|
id: 'c',
|
||||||
styles: componentDefinition.styles || EMPTY_ARRAY,
|
styles: componentDefinition.styles || EMPTY_ARRAY,
|
||||||
_: null as never,
|
_: null as never,
|
||||||
setInput: null,
|
setInput: null,
|
||||||
schemas: componentDefinition.schemas || null,
|
schemas: componentDefinition.schemas || null,
|
||||||
tView: null,
|
tView: null,
|
||||||
};
|
};
|
||||||
def._ = noSideEffects(() => {
|
|
||||||
const directiveTypes = componentDefinition.directives !;
|
const directiveTypes = componentDefinition.directives !;
|
||||||
const feature = componentDefinition.features;
|
const feature = componentDefinition.features;
|
||||||
const pipeTypes = componentDefinition.pipes !;
|
const pipeTypes = componentDefinition.pipes !;
|
||||||
@ -353,9 +353,9 @@ export function ɵɵdefineComponent<T>(componentDefinition: {
|
|||||||
def.pipeDefs = pipeTypes ?
|
def.pipeDefs = pipeTypes ?
|
||||||
() => (typeof pipeTypes === 'function' ? pipeTypes() : pipeTypes).map(extractPipeDef) :
|
() => (typeof pipeTypes === 'function' ? pipeTypes() : pipeTypes).map(extractPipeDef) :
|
||||||
null;
|
null;
|
||||||
}) as never;
|
|
||||||
|
|
||||||
return def as never;
|
return def as never;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,6 +14,7 @@ import {getInjectorDef} from '../di/interface/defs';
|
|||||||
import {InjectFlags} from '../di/interface/injector';
|
import {InjectFlags} from '../di/interface/injector';
|
||||||
import {Type} from '../interface/type';
|
import {Type} from '../interface/type';
|
||||||
import {assertDefined, assertEqual} from '../util/assert';
|
import {assertDefined, assertEqual} from '../util/assert';
|
||||||
|
import {noSideEffects} from '../util/closure';
|
||||||
|
|
||||||
import {assertDirectiveDef} from './assert';
|
import {assertDirectiveDef} from './assert';
|
||||||
import {getFactoryDef} from './definition';
|
import {getFactoryDef} from './definition';
|
||||||
@ -655,15 +656,17 @@ export function ɵɵgetFactoryOf<T>(type: Type<any>): FactoryFn<T>|null {
|
|||||||
* @codeGenApi
|
* @codeGenApi
|
||||||
*/
|
*/
|
||||||
export function ɵɵgetInheritedFactory<T>(type: Type<any>): (type: Type<T>) => T {
|
export function ɵɵgetInheritedFactory<T>(type: Type<any>): (type: Type<T>) => T {
|
||||||
const proto = Object.getPrototypeOf(type.prototype).constructor as Type<any>;
|
return noSideEffects(() => {
|
||||||
const factory = (proto as any)[NG_FACTORY_DEF] || ɵɵgetFactoryOf<T>(proto);
|
const proto = Object.getPrototypeOf(type.prototype).constructor as Type<any>;
|
||||||
if (factory !== null) {
|
const factory = (proto as any)[NG_FACTORY_DEF] || ɵɵgetFactoryOf<T>(proto);
|
||||||
return factory;
|
if (factory !== null) {
|
||||||
} else {
|
return factory;
|
||||||
// There is no factory defined. Either this was improper usage of inheritance
|
} else {
|
||||||
// (no Angular decorator on the superclass) or there is no constructor at all
|
// There is no factory defined. Either this was improper usage of inheritance
|
||||||
// in the inheritance chain. Since the two cases cannot be distinguished, the
|
// (no Angular decorator on the superclass) or there is no constructor at all
|
||||||
// latter has to be assumed.
|
// in the inheritance chain. Since the two cases cannot be distinguished, the
|
||||||
return (t) => new t();
|
// latter has to be assumed.
|
||||||
}
|
return (t) => new t();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -35,26 +35,26 @@ type OnChangesExpando = OnChanges & {
|
|||||||
* static ɵcmp = defineComponent({
|
* static ɵcmp = defineComponent({
|
||||||
* ...
|
* ...
|
||||||
* inputs: {name: 'publicName'},
|
* inputs: {name: 'publicName'},
|
||||||
* features: [NgOnChangesFeature()]
|
* features: [NgOnChangesFeature]
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @codeGenApi
|
* @codeGenApi
|
||||||
*/
|
*/
|
||||||
export function ɵɵNgOnChangesFeature<T>(): DirectiveDefFeature {
|
|
||||||
// This option ensures that the ngOnChanges lifecycle hook will be inherited
|
|
||||||
// from superclasses (in InheritDefinitionFeature).
|
|
||||||
(NgOnChangesFeatureImpl as DirectiveDefFeature).ngInherit = true;
|
|
||||||
return NgOnChangesFeatureImpl;
|
|
||||||
}
|
|
||||||
|
|
||||||
function NgOnChangesFeatureImpl<T>(definition: DirectiveDef<T>): void {
|
export function ɵɵNgOnChangesFeature<T>(definition: DirectiveDef<T>): void {
|
||||||
if (definition.type.prototype.ngOnChanges) {
|
if (definition.type.prototype.ngOnChanges) {
|
||||||
definition.setInput = ngOnChangesSetInput;
|
definition.setInput = ngOnChangesSetInput;
|
||||||
(definition as{onChanges: Function}).onChanges = wrapOnChanges();
|
(definition as{onChanges: Function}).onChanges = wrapOnChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This option ensures that the ngOnChanges lifecycle hook will be inherited
|
||||||
|
// from superclasses (in InheritDefinitionFeature).
|
||||||
|
/** @nocollapse */
|
||||||
|
// tslint:disable-next-line:no-toplevel-property-access
|
||||||
|
(ɵɵNgOnChangesFeature as DirectiveDefFeature).ngInherit = true;
|
||||||
|
|
||||||
function wrapOnChanges() {
|
function wrapOnChanges() {
|
||||||
return function wrapOnChangesHook_inPreviousChangesStorage(this: OnChanges) {
|
return function wrapOnChangesHook_inPreviousChangesStorage(this: OnChanges) {
|
||||||
const simpleChangesStore = getSimpleChangesStore(this);
|
const simpleChangesStore = getSimpleChangesStore(this);
|
||||||
|
@ -455,19 +455,6 @@ export function transitiveScopesFor<T>(moduleType: Type<T>): NgModuleTransitiveS
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
maybeUnwrapFn(def.declarations).forEach(declared => {
|
|
||||||
const declaredWithDefs = declared as Type<any>& { ɵpipe?: any; };
|
|
||||||
|
|
||||||
if (getPipeDef(declaredWithDefs)) {
|
|
||||||
scopes.compilation.pipes.add(declared);
|
|
||||||
} else {
|
|
||||||
// Either declared has a ɵcmp or ɵdir, or it's a component which hasn't
|
|
||||||
// had its template compiled yet. In either case, it gets added to the compilation's
|
|
||||||
// directives.
|
|
||||||
scopes.compilation.directives.add(declared);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
maybeUnwrapFn(def.imports).forEach(<I>(imported: Type<I>) => {
|
maybeUnwrapFn(def.imports).forEach(<I>(imported: Type<I>) => {
|
||||||
const importedType = imported as Type<I>& {
|
const importedType = imported as Type<I>& {
|
||||||
// If imported is an @NgModule:
|
// If imported is an @NgModule:
|
||||||
@ -485,6 +472,19 @@ export function transitiveScopesFor<T>(moduleType: Type<T>): NgModuleTransitiveS
|
|||||||
importedScope.exported.pipes.forEach(entry => scopes.compilation.pipes.add(entry));
|
importedScope.exported.pipes.forEach(entry => scopes.compilation.pipes.add(entry));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
maybeUnwrapFn(def.declarations).forEach(declared => {
|
||||||
|
const declaredWithDefs = declared as Type<any>& { ɵpipe?: any; };
|
||||||
|
|
||||||
|
if (getPipeDef(declaredWithDefs)) {
|
||||||
|
scopes.compilation.pipes.add(declared);
|
||||||
|
} else {
|
||||||
|
// Either declared has a ɵcmp or ɵdir, or it's a component which hasn't
|
||||||
|
// had its template compiled yet. In either case, it gets added to the compilation's
|
||||||
|
// directives.
|
||||||
|
scopes.compilation.directives.add(declared);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
maybeUnwrapFn(def.exports).forEach(<E>(exported: Type<E>) => {
|
maybeUnwrapFn(def.exports).forEach(<E>(exported: Type<E>) => {
|
||||||
const exportedType = exported as Type<E>& {
|
const exportedType = exported as Type<E>& {
|
||||||
// Components, Directives, NgModules, and Pipes can all be exported.
|
// Components, Directives, NgModules, and Pipes can all be exported.
|
||||||
|
@ -15,6 +15,6 @@
|
|||||||
* to something which is retained otherwise the call to `noSideEffects` will be removed by closure
|
* to something which is retained otherwise the call to `noSideEffects` will be removed by closure
|
||||||
* compiler.
|
* compiler.
|
||||||
*/
|
*/
|
||||||
export function noSideEffects(fn: () => void): string {
|
export function noSideEffects<T>(fn: () => T): T {
|
||||||
return '' + {toString: fn};
|
return {toString: fn}.toString() as unknown as T;
|
||||||
}
|
}
|
@ -8,6 +8,10 @@
|
|||||||
|
|
||||||
import {Type} from '../interface/type';
|
import {Type} from '../interface/type';
|
||||||
|
|
||||||
|
import {noSideEffects} from './closure';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface implemented by all Angular type decorators, which allows them to be used as
|
* An interface implemented by all Angular type decorators, which allows them to be used as
|
||||||
* decorators as well as Angular syntax.
|
* decorators as well as Angular syntax.
|
||||||
@ -44,39 +48,41 @@ export function makeDecorator<T>(
|
|||||||
additionalProcessing?: (type: Type<T>) => void,
|
additionalProcessing?: (type: Type<T>) => void,
|
||||||
typeFn?: (type: Type<T>, ...args: any[]) => void):
|
typeFn?: (type: Type<T>, ...args: any[]) => void):
|
||||||
{new (...args: any[]): any; (...args: any[]): any; (...args: any[]): (cls: any) => any;} {
|
{new (...args: any[]): any; (...args: any[]): any; (...args: any[]): (cls: any) => any;} {
|
||||||
const metaCtor = makeMetadataCtor(props);
|
return noSideEffects(() => {
|
||||||
|
const metaCtor = makeMetadataCtor(props);
|
||||||
|
|
||||||
function DecoratorFactory(
|
function DecoratorFactory(
|
||||||
this: unknown | typeof DecoratorFactory, ...args: any[]): (cls: Type<T>) => any {
|
this: unknown | typeof DecoratorFactory, ...args: any[]): (cls: Type<T>) => any {
|
||||||
if (this instanceof DecoratorFactory) {
|
if (this instanceof DecoratorFactory) {
|
||||||
metaCtor.call(this, ...args);
|
metaCtor.call(this, ...args);
|
||||||
return this as typeof DecoratorFactory;
|
return this as typeof DecoratorFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
const annotationInstance = new (DecoratorFactory as any)(...args);
|
||||||
|
return function TypeDecorator(cls: Type<T>) {
|
||||||
|
if (typeFn) typeFn(cls, ...args);
|
||||||
|
// Use of Object.defineProperty is important since it creates non-enumerable property which
|
||||||
|
// prevents the property is copied during subclassing.
|
||||||
|
const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
|
||||||
|
(cls as any)[ANNOTATIONS] :
|
||||||
|
Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS];
|
||||||
|
annotations.push(annotationInstance);
|
||||||
|
|
||||||
|
|
||||||
|
if (additionalProcessing) additionalProcessing(cls);
|
||||||
|
|
||||||
|
return cls;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const annotationInstance = new (DecoratorFactory as any)(...args);
|
if (parentClass) {
|
||||||
return function TypeDecorator(cls: Type<T>) {
|
DecoratorFactory.prototype = Object.create(parentClass.prototype);
|
||||||
if (typeFn) typeFn(cls, ...args);
|
}
|
||||||
// Use of Object.defineProperty is important since it creates non-enumerable property which
|
|
||||||
// prevents the property is copied during subclassing.
|
|
||||||
const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
|
|
||||||
(cls as any)[ANNOTATIONS] :
|
|
||||||
Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS];
|
|
||||||
annotations.push(annotationInstance);
|
|
||||||
|
|
||||||
|
DecoratorFactory.prototype.ngMetadataName = name;
|
||||||
if (additionalProcessing) additionalProcessing(cls);
|
(DecoratorFactory as any).annotationCls = DecoratorFactory;
|
||||||
|
return DecoratorFactory as any;
|
||||||
return cls;
|
});
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parentClass) {
|
|
||||||
DecoratorFactory.prototype = Object.create(parentClass.prototype);
|
|
||||||
}
|
|
||||||
|
|
||||||
DecoratorFactory.prototype.ngMetadataName = name;
|
|
||||||
(DecoratorFactory as any).annotationCls = DecoratorFactory;
|
|
||||||
return DecoratorFactory as any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeMetadataCtor(props?: (...args: any[]) => any): any {
|
function makeMetadataCtor(props?: (...args: any[]) => any): any {
|
||||||
@ -92,77 +98,82 @@ function makeMetadataCtor(props?: (...args: any[]) => any): any {
|
|||||||
|
|
||||||
export function makeParamDecorator(
|
export function makeParamDecorator(
|
||||||
name: string, props?: (...args: any[]) => any, parentClass?: any): any {
|
name: string, props?: (...args: any[]) => any, parentClass?: any): any {
|
||||||
const metaCtor = makeMetadataCtor(props);
|
return noSideEffects(() => {
|
||||||
function ParamDecoratorFactory(
|
const metaCtor = makeMetadataCtor(props);
|
||||||
this: unknown | typeof ParamDecoratorFactory, ...args: any[]): any {
|
function ParamDecoratorFactory(
|
||||||
if (this instanceof ParamDecoratorFactory) {
|
this: unknown | typeof ParamDecoratorFactory, ...args: any[]): any {
|
||||||
metaCtor.apply(this, args);
|
if (this instanceof ParamDecoratorFactory) {
|
||||||
return this;
|
metaCtor.apply(this, args);
|
||||||
}
|
return this;
|
||||||
const annotationInstance = new (<any>ParamDecoratorFactory)(...args);
|
|
||||||
|
|
||||||
(<any>ParamDecorator).annotation = annotationInstance;
|
|
||||||
return ParamDecorator;
|
|
||||||
|
|
||||||
function ParamDecorator(cls: any, unusedKey: any, index: number): any {
|
|
||||||
// Use of Object.defineProperty is important since it creates non-enumerable property which
|
|
||||||
// prevents the property is copied during subclassing.
|
|
||||||
const parameters = cls.hasOwnProperty(PARAMETERS) ?
|
|
||||||
(cls as any)[PARAMETERS] :
|
|
||||||
Object.defineProperty(cls, PARAMETERS, {value: []})[PARAMETERS];
|
|
||||||
|
|
||||||
// there might be gaps if some in between parameters do not have annotations.
|
|
||||||
// we pad with nulls.
|
|
||||||
while (parameters.length <= index) {
|
|
||||||
parameters.push(null);
|
|
||||||
}
|
}
|
||||||
|
const annotationInstance = new (<any>ParamDecoratorFactory)(...args);
|
||||||
|
|
||||||
(parameters[index] = parameters[index] || []).push(annotationInstance);
|
(<any>ParamDecorator).annotation = annotationInstance;
|
||||||
return cls;
|
return ParamDecorator;
|
||||||
|
|
||||||
|
function ParamDecorator(cls: any, unusedKey: any, index: number): any {
|
||||||
|
// Use of Object.defineProperty is important since it creates non-enumerable property which
|
||||||
|
// prevents the property is copied during subclassing.
|
||||||
|
const parameters = cls.hasOwnProperty(PARAMETERS) ?
|
||||||
|
(cls as any)[PARAMETERS] :
|
||||||
|
Object.defineProperty(cls, PARAMETERS, {value: []})[PARAMETERS];
|
||||||
|
|
||||||
|
// there might be gaps if some in between parameters do not have annotations.
|
||||||
|
// we pad with nulls.
|
||||||
|
while (parameters.length <= index) {
|
||||||
|
parameters.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
(parameters[index] = parameters[index] || []).push(annotationInstance);
|
||||||
|
return cls;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if (parentClass) {
|
||||||
if (parentClass) {
|
ParamDecoratorFactory.prototype = Object.create(parentClass.prototype);
|
||||||
ParamDecoratorFactory.prototype = Object.create(parentClass.prototype);
|
}
|
||||||
}
|
ParamDecoratorFactory.prototype.ngMetadataName = name;
|
||||||
ParamDecoratorFactory.prototype.ngMetadataName = name;
|
(<any>ParamDecoratorFactory).annotationCls = ParamDecoratorFactory;
|
||||||
(<any>ParamDecoratorFactory).annotationCls = ParamDecoratorFactory;
|
return ParamDecoratorFactory;
|
||||||
return ParamDecoratorFactory;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makePropDecorator(
|
export function makePropDecorator(
|
||||||
name: string, props?: (...args: any[]) => any, parentClass?: any,
|
name: string, props?: (...args: any[]) => any, parentClass?: any,
|
||||||
additionalProcessing?: (target: any, name: string, ...args: any[]) => void): any {
|
additionalProcessing?: (target: any, name: string, ...args: any[]) => void): any {
|
||||||
const metaCtor = makeMetadataCtor(props);
|
return noSideEffects(() => {
|
||||||
|
const metaCtor = makeMetadataCtor(props);
|
||||||
|
|
||||||
function PropDecoratorFactory(this: unknown | typeof PropDecoratorFactory, ...args: any[]): any {
|
function PropDecoratorFactory(
|
||||||
if (this instanceof PropDecoratorFactory) {
|
this: unknown | typeof PropDecoratorFactory, ...args: any[]): any {
|
||||||
metaCtor.apply(this, args);
|
if (this instanceof PropDecoratorFactory) {
|
||||||
return this;
|
metaCtor.apply(this, args);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoratorInstance = new (<any>PropDecoratorFactory)(...args);
|
||||||
|
|
||||||
|
function PropDecorator(target: any, name: string) {
|
||||||
|
const constructor = target.constructor;
|
||||||
|
// Use of Object.defineProperty is important since it creates non-enumerable property which
|
||||||
|
// prevents the property is copied during subclassing.
|
||||||
|
const meta = constructor.hasOwnProperty(PROP_METADATA) ?
|
||||||
|
(constructor as any)[PROP_METADATA] :
|
||||||
|
Object.defineProperty(constructor, PROP_METADATA, {value: {}})[PROP_METADATA];
|
||||||
|
meta[name] = meta.hasOwnProperty(name) && meta[name] || [];
|
||||||
|
meta[name].unshift(decoratorInstance);
|
||||||
|
|
||||||
|
if (additionalProcessing) additionalProcessing(target, name, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PropDecorator;
|
||||||
}
|
}
|
||||||
|
|
||||||
const decoratorInstance = new (<any>PropDecoratorFactory)(...args);
|
if (parentClass) {
|
||||||
|
PropDecoratorFactory.prototype = Object.create(parentClass.prototype);
|
||||||
function PropDecorator(target: any, name: string) {
|
|
||||||
const constructor = target.constructor;
|
|
||||||
// Use of Object.defineProperty is important since it creates non-enumerable property which
|
|
||||||
// prevents the property is copied during subclassing.
|
|
||||||
const meta = constructor.hasOwnProperty(PROP_METADATA) ?
|
|
||||||
(constructor as any)[PROP_METADATA] :
|
|
||||||
Object.defineProperty(constructor, PROP_METADATA, {value: {}})[PROP_METADATA];
|
|
||||||
meta[name] = meta.hasOwnProperty(name) && meta[name] || [];
|
|
||||||
meta[name].unshift(decoratorInstance);
|
|
||||||
|
|
||||||
if (additionalProcessing) additionalProcessing(target, name, ...args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return PropDecorator;
|
PropDecoratorFactory.prototype.ngMetadataName = name;
|
||||||
}
|
(<any>PropDecoratorFactory).annotationCls = PropDecoratorFactory;
|
||||||
|
return PropDecoratorFactory;
|
||||||
if (parentClass) {
|
});
|
||||||
PropDecoratorFactory.prototype = Object.create(parentClass.prototype);
|
|
||||||
}
|
|
||||||
|
|
||||||
PropDecoratorFactory.prototype.ngMetadataName = name;
|
|
||||||
(<any>PropDecoratorFactory).annotationCls = PropDecoratorFactory;
|
|
||||||
return PropDecoratorFactory;
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {Component, Directive, ElementRef, EventEmitter, Output, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
|
import {Component, Directive, ElementRef, EventEmitter, NgModule, Output, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
|
||||||
import {Input} from '@angular/core/src/metadata';
|
import {Input} from '@angular/core/src/metadata';
|
||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {By} from '@angular/platform-browser';
|
import {By} from '@angular/platform-browser';
|
||||||
@ -633,4 +633,96 @@ describe('directives', () => {
|
|||||||
expect(div.getAttribute('title')).toBe('a');
|
expect(div.getAttribute('title')).toBe('a');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('directives with the same selector', () => {
|
||||||
|
it('should process Directives from `declarations` list after imported ones', () => {
|
||||||
|
const log: string[] = [];
|
||||||
|
@Directive({selector: '[dir]'})
|
||||||
|
class DirectiveA {
|
||||||
|
constructor() { log.push('DirectiveA.constructor'); }
|
||||||
|
ngOnInit() { log.push('DirectiveA.ngOnInit'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [DirectiveA],
|
||||||
|
exports: [DirectiveA],
|
||||||
|
})
|
||||||
|
class ModuleA {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[dir]'})
|
||||||
|
class DirectiveB {
|
||||||
|
constructor() { log.push('DirectiveB.constructor'); }
|
||||||
|
ngOnInit() { log.push('DirectiveB.ngOnInit'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app',
|
||||||
|
template: '<div dir></div>',
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [ModuleA],
|
||||||
|
declarations: [DirectiveB, App],
|
||||||
|
});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(log).toEqual([
|
||||||
|
'DirectiveA.constructor', 'DirectiveB.constructor', 'DirectiveA.ngOnInit',
|
||||||
|
'DirectiveB.ngOnInit'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect imported module order', () => {
|
||||||
|
const log: string[] = [];
|
||||||
|
@Directive({selector: '[dir]'})
|
||||||
|
class DirectiveA {
|
||||||
|
constructor() { log.push('DirectiveA.constructor'); }
|
||||||
|
ngOnInit() { log.push('DirectiveA.ngOnInit'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [DirectiveA],
|
||||||
|
exports: [DirectiveA],
|
||||||
|
})
|
||||||
|
class ModuleA {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[dir]'})
|
||||||
|
class DirectiveB {
|
||||||
|
constructor() { log.push('DirectiveB.constructor'); }
|
||||||
|
ngOnInit() { log.push('DirectiveB.ngOnInit'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [DirectiveB],
|
||||||
|
exports: [DirectiveB],
|
||||||
|
})
|
||||||
|
class ModuleB {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app',
|
||||||
|
template: '<div dir></div>',
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [ModuleA, ModuleB],
|
||||||
|
declarations: [App],
|
||||||
|
});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(log).toEqual([
|
||||||
|
'DirectiveA.constructor', 'DirectiveB.constructor', 'DirectiveA.ngOnInit',
|
||||||
|
'DirectiveB.ngOnInit'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -110,6 +110,86 @@ describe('pipe', () => {
|
|||||||
expect(fixture.nativeElement.textContent).toEqual('value a b default 0 1 2 3');
|
expect(fixture.nativeElement.textContent).toEqual('value a b default 0 1 2 3');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should pick a Pipe defined in `declarations` over imported Pipes', () => {
|
||||||
|
@Pipe({name: 'number'})
|
||||||
|
class PipeA implements PipeTransform {
|
||||||
|
transform(value: any) { return `PipeA: ${value}`; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [PipeA],
|
||||||
|
exports: [PipeA],
|
||||||
|
})
|
||||||
|
class ModuleA {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pipe({name: 'number'})
|
||||||
|
class PipeB implements PipeTransform {
|
||||||
|
transform(value: any) { return `PipeB: ${value}`; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app',
|
||||||
|
template: '{{ count | number }}',
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
count = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [ModuleA],
|
||||||
|
declarations: [PipeB, App],
|
||||||
|
});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(fixture.nativeElement.textContent).toBe('PipeB: 10');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect imported module order when selecting Pipe (last imported Pipe is used)',
|
||||||
|
() => {
|
||||||
|
@Pipe({name: 'number'})
|
||||||
|
class PipeA implements PipeTransform {
|
||||||
|
transform(value: any) { return `PipeA: ${value}`; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [PipeA],
|
||||||
|
exports: [PipeA],
|
||||||
|
})
|
||||||
|
class ModuleA {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pipe({name: 'number'})
|
||||||
|
class PipeB implements PipeTransform {
|
||||||
|
transform(value: any) { return `PipeB: ${value}`; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [PipeB],
|
||||||
|
exports: [PipeB],
|
||||||
|
})
|
||||||
|
class ModuleB {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app',
|
||||||
|
template: '{{ count | number }}',
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
count = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [ModuleA, ModuleB],
|
||||||
|
declarations: [App],
|
||||||
|
});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(fixture.nativeElement.textContent).toBe('PipeB: 10');
|
||||||
|
});
|
||||||
|
|
||||||
it('should do nothing when no change', () => {
|
it('should do nothing when no change', () => {
|
||||||
let calls: any[] = [];
|
let calls: any[] = [];
|
||||||
|
|
||||||
|
@ -167,6 +167,9 @@
|
|||||||
{
|
{
|
||||||
"name": "attachPatchData"
|
"name": "attachPatchData"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "autoRegisterModuleById"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "baseResolveDirective"
|
"name": "baseResolveDirective"
|
||||||
},
|
},
|
||||||
|
@ -223,5 +223,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ɵɵinject"
|
"name": "ɵɵinject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "noSideEffects"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -41,7 +41,7 @@ NgIf.ɵfac = () =>
|
|||||||
NgTemplateOutlet.ɵdir = ɵɵdefineDirective({
|
NgTemplateOutlet.ɵdir = ɵɵdefineDirective({
|
||||||
type: NgTemplateOutletDef,
|
type: NgTemplateOutletDef,
|
||||||
selectors: [['', 'ngTemplateOutlet', '']],
|
selectors: [['', 'ngTemplateOutlet', '']],
|
||||||
features: [ɵɵNgOnChangesFeature()],
|
features: [ɵɵNgOnChangesFeature],
|
||||||
inputs:
|
inputs:
|
||||||
{ngTemplateOutlet: 'ngTemplateOutlet', ngTemplateOutletContext: 'ngTemplateOutletContext'}
|
{ngTemplateOutlet: 'ngTemplateOutlet', ngTemplateOutletContext: 'ngTemplateOutletContext'}
|
||||||
});
|
});
|
||||||
|
@ -30,6 +30,7 @@ filegroup(
|
|||||||
testonly = True,
|
testonly = True,
|
||||||
# do not sort
|
# do not sort
|
||||||
srcs = [
|
srcs = [
|
||||||
|
"@npm//:node_modules/core-js/client/core.js",
|
||||||
"@npm//:node_modules/@webcomponents/custom-elements/src/native-shim.js",
|
"@npm//:node_modules/@webcomponents/custom-elements/src/native-shim.js",
|
||||||
"@npm//:node_modules/reflect-metadata/Reflect.js",
|
"@npm//:node_modules/reflect-metadata/Reflect.js",
|
||||||
"//packages/zone.js/dist:zone.js",
|
"//packages/zone.js/dist:zone.js",
|
||||||
@ -42,11 +43,6 @@ karma_web_test_suite(
|
|||||||
bootstrap = [
|
bootstrap = [
|
||||||
":elements_test_bootstrap_scripts",
|
":elements_test_bootstrap_scripts",
|
||||||
],
|
],
|
||||||
tags = [
|
|
||||||
# FIXME: timed out in CI
|
|
||||||
"fixme-saucelabs-ivy",
|
|
||||||
"fixme-saucelabs-ve",
|
|
||||||
],
|
|
||||||
deps = [
|
deps = [
|
||||||
":test_lib",
|
":test_lib",
|
||||||
],
|
],
|
||||||
|
@ -567,8 +567,8 @@ class ExpressionVisitor extends NullTemplateVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (binding.expression && inSpan(valueRelativePosition, binding.expression.ast.span)) {
|
if (binding.value && inSpan(valueRelativePosition, binding.value.ast.span)) {
|
||||||
this.processExpressionCompletions(binding.expression.ast);
|
this.processExpressionCompletions(binding.value.ast);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,10 +91,8 @@ function getVarDeclarations(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (const variable of current.variables) {
|
for (const variable of current.variables) {
|
||||||
let symbol = info.members.get(variable.value);
|
let symbol = getVariableTypeFromDirectiveContext(variable.value, info.query, current);
|
||||||
if (!symbol) {
|
|
||||||
symbol = getVariableTypeFromDirectiveContext(variable.value, info.query, current);
|
|
||||||
}
|
|
||||||
const kind = info.query.getTypeKind(symbol);
|
const kind = info.query.getTypeKind(symbol);
|
||||||
if (kind === BuiltinType.Any || kind === BuiltinType.Unbound) {
|
if (kind === BuiltinType.Any || kind === BuiltinType.Unbound) {
|
||||||
// For special cases such as ngFor and ngIf, the any type is not very useful.
|
// For special cases such as ngFor and ngIf, the any type is not very useful.
|
||||||
|
@ -209,10 +209,10 @@ function getSymbolInMicrosyntax(info: AstResult, path: TemplateAstPath, attribut
|
|||||||
|
|
||||||
// Find the symbol that contains the position.
|
// Find the symbol that contains the position.
|
||||||
templateBindings.filter(tb => !tb.keyIsVar).forEach(tb => {
|
templateBindings.filter(tb => !tb.keyIsVar).forEach(tb => {
|
||||||
if (inSpan(valueRelativePosition, tb.expression?.ast.span)) {
|
if (inSpan(valueRelativePosition, tb.value?.ast.span)) {
|
||||||
const dinfo = diagnosticInfoFromTemplateInfo(info);
|
const dinfo = diagnosticInfoFromTemplateInfo(info);
|
||||||
const scope = getExpressionScope(dinfo, path);
|
const scope = getExpressionScope(dinfo, path);
|
||||||
result = getExpressionSymbol(scope, tb.expression !, path.position, info.template.query);
|
result = getExpressionSymbol(scope, tb.value !, path.position, info.template.query);
|
||||||
} else if (inSpan(valueRelativePosition, tb.span)) {
|
} else if (inSpan(valueRelativePosition, tb.span)) {
|
||||||
const template = path.first(EmbeddedTemplateAst);
|
const template = path.first(EmbeddedTemplateAst);
|
||||||
if (template) {
|
if (template) {
|
||||||
|
@ -170,6 +170,8 @@ export class TemplateReference {
|
|||||||
primitiveIndexType: {[name: string]: string} = {};
|
primitiveIndexType: {[name: string]: string} = {};
|
||||||
anyValue: any;
|
anyValue: any;
|
||||||
optional?: string;
|
optional?: string;
|
||||||
|
// Use to test the `index` variable conflict between the `ngFor` and component context.
|
||||||
|
index = null;
|
||||||
myClick(event: any) {}
|
myClick(event: any) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,4 +15,15 @@ export class Diagnostics {
|
|||||||
get hasErrors() { return this.messages.some(m => m.type === 'error'); }
|
get hasErrors() { return this.messages.some(m => m.type === 'error'); }
|
||||||
warn(message: string) { this.messages.push({type: 'warning', message}); }
|
warn(message: string) { this.messages.push({type: 'warning', message}); }
|
||||||
error(message: string) { this.messages.push({type: 'error', message}); }
|
error(message: string) { this.messages.push({type: 'error', message}); }
|
||||||
|
formatDiagnostics(message: string): string {
|
||||||
|
const errors = this.messages !.filter(d => d.type === 'error').map(d => ' - ' + d.message);
|
||||||
|
const warnings = this.messages !.filter(d => d.type === 'warning').map(d => ' - ' + d.message);
|
||||||
|
if (errors.length) {
|
||||||
|
message += '\nERRORS:\n' + errors.join('\n');
|
||||||
|
}
|
||||||
|
if (warnings.length) {
|
||||||
|
message += '\nWARNINGS:\n' + warnings.join('\n');
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,7 +142,7 @@ export function translateFiles({sourceRootPath, sourceFilePaths, translationFile
|
|||||||
[
|
[
|
||||||
new Xliff2TranslationParser(),
|
new Xliff2TranslationParser(),
|
||||||
new Xliff1TranslationParser(),
|
new Xliff1TranslationParser(),
|
||||||
new XtbTranslationParser(diagnostics),
|
new XtbTranslationParser(),
|
||||||
new SimpleJsonTranslationParser(),
|
new SimpleJsonTranslationParser(),
|
||||||
],
|
],
|
||||||
diagnostics);
|
diagnostics);
|
||||||
|
@ -14,7 +14,9 @@ import {TranslationParser} from './translation_parsers/translation_parser';
|
|||||||
* Use this class to load a collection of translation files from disk.
|
* Use this class to load a collection of translation files from disk.
|
||||||
*/
|
*/
|
||||||
export class TranslationLoader {
|
export class TranslationLoader {
|
||||||
constructor(private translationParsers: TranslationParser[], private diagnostics: Diagnostics) {}
|
constructor(
|
||||||
|
private translationParsers: TranslationParser<any>[],
|
||||||
|
/** @deprecated */ private diagnostics?: Diagnostics) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load and parse the translation files into a collection of `TranslationBundles`.
|
* Load and parse the translation files into a collection of `TranslationBundles`.
|
||||||
@ -34,22 +36,37 @@ export class TranslationLoader {
|
|||||||
return translationFilePaths.map((filePath, index) => {
|
return translationFilePaths.map((filePath, index) => {
|
||||||
const fileContents = FileUtils.readFile(filePath);
|
const fileContents = FileUtils.readFile(filePath);
|
||||||
for (const translationParser of this.translationParsers) {
|
for (const translationParser of this.translationParsers) {
|
||||||
if (translationParser.canParse(filePath, fileContents)) {
|
const result = translationParser.canParse(filePath, fileContents);
|
||||||
const providedLocale = translationFileLocales[index];
|
if (!result) {
|
||||||
const {locale: parsedLocale, translations} =
|
continue;
|
||||||
translationParser.parse(filePath, fileContents);
|
|
||||||
const locale = providedLocale || parsedLocale;
|
|
||||||
if (locale === undefined) {
|
|
||||||
throw new Error(
|
|
||||||
`The translation file "${filePath}" does not contain a target locale and no explicit locale was provided for this file.`);
|
|
||||||
}
|
|
||||||
if (parsedLocale !== undefined && providedLocale !== undefined &&
|
|
||||||
parsedLocale !== providedLocale) {
|
|
||||||
this.diagnostics.warn(
|
|
||||||
`The provided locale "${providedLocale}" does not match the target locale "${parsedLocale}" found in the translation file "${filePath}".`);
|
|
||||||
}
|
|
||||||
return {locale, translations};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const {locale: parsedLocale, translations, diagnostics} =
|
||||||
|
translationParser.parse(filePath, fileContents, result);
|
||||||
|
if (diagnostics.hasErrors) {
|
||||||
|
throw new Error(diagnostics.formatDiagnostics(
|
||||||
|
`The translation file "${filePath}" could not be parsed.`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const providedLocale = translationFileLocales[index];
|
||||||
|
const locale = providedLocale || parsedLocale;
|
||||||
|
if (locale === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`The translation file "${filePath}" does not contain a target locale and no explicit locale was provided for this file.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedLocale !== undefined && providedLocale !== undefined &&
|
||||||
|
parsedLocale !== providedLocale) {
|
||||||
|
diagnostics.warn(
|
||||||
|
`The provided locale "${providedLocale}" does not match the target locale "${parsedLocale}" found in the translation file "${filePath}".`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we were passed a diagnostics object then copy the messages over to it.
|
||||||
|
if (this.diagnostics) {
|
||||||
|
this.diagnostics.messages.push(...diagnostics.messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {locale, translations, diagnostics};
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`There is no "TranslationParser" that can parse this translation file: ${filePath}.`);
|
`There is no "TranslationParser" that can parse this translation file: ${filePath}.`);
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
import {ɵMessageId, ɵParsedTranslation, ɵparseTranslation} from '@angular/localize';
|
import {ɵMessageId, ɵParsedTranslation, ɵparseTranslation} from '@angular/localize';
|
||||||
import {extname} from 'path';
|
import {extname} from 'path';
|
||||||
|
import {Diagnostics} from '../../../diagnostics';
|
||||||
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
|
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,6 +33,6 @@ export class SimpleJsonTranslationParser implements TranslationParser {
|
|||||||
const targetMessage = translations[messageId];
|
const targetMessage = translations[messageId];
|
||||||
parsedTranslations[messageId] = ɵparseTranslation(targetMessage);
|
parsedTranslations[messageId] = ɵparseTranslation(targetMessage);
|
||||||
}
|
}
|
||||||
return {locale: parsedLocale, translations: parsedTranslations};
|
return {locale: parsedLocale, translations: parsedTranslations, diagnostics: new Diagnostics()};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {ɵMessageId, ɵParsedTranslation} from '@angular/localize/private';
|
import {ɵMessageId, ɵParsedTranslation} from '@angular/localize/private';
|
||||||
|
import {Diagnostics} from '../../../diagnostics';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object that holds translations that have been parsed from a translation file.
|
* An object that holds translations that have been parsed from a translation file.
|
||||||
@ -13,25 +14,62 @@ import {ɵMessageId, ɵParsedTranslation} from '@angular/localize/private';
|
|||||||
export interface ParsedTranslationBundle {
|
export interface ParsedTranslationBundle {
|
||||||
locale: string|undefined;
|
locale: string|undefined;
|
||||||
translations: Record<ɵMessageId, ɵParsedTranslation>;
|
translations: Record<ɵMessageId, ɵParsedTranslation>;
|
||||||
|
diagnostics: Diagnostics;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implement this interface to provide a class that can parse the contents of a translation file.
|
* Implement this interface to provide a class that can parse the contents of a translation file.
|
||||||
|
*
|
||||||
|
* The `canParse()` method can return a hint that can be used by the `parse()` method to speed up
|
||||||
|
* parsing. This allows the parser to do significant work to determine if the file can be parsed
|
||||||
|
* without duplicating the work when it comes to actually parsing the file.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* const parser: TranslationParser = getParser();
|
||||||
|
* const result = parser.canParse(filePath, content);
|
||||||
|
* if (result) {
|
||||||
|
* return parser.parse(filePath, content, result);
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export interface TranslationParser {
|
export interface TranslationParser<Hint = true> {
|
||||||
/**
|
/**
|
||||||
* Returns true if this parser can parse the given file.
|
* Can this parser parse the given file?
|
||||||
*
|
*
|
||||||
* @param filePath The absolute path to the translation file.
|
* @param filePath The absolute path to the translation file.
|
||||||
* @param contents The contents of the translation file.
|
* @param contents The contents of the translation file.
|
||||||
|
* @returns A hint, which can be used in doing the actual parsing, if the file can be parsed by
|
||||||
|
* this parser; false otherwise.
|
||||||
*/
|
*/
|
||||||
canParse(filePath: string, contents: string): boolean;
|
canParse(filePath: string, contents: string): Hint|false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the given file, extracting the target locale and translations.
|
* Parses the given file, extracting the target locale and translations.
|
||||||
*
|
*
|
||||||
|
* Note that this method should not throw an error. Check the `bundle.diagnostics` property for
|
||||||
|
* potential parsing errors and warnings.
|
||||||
|
*
|
||||||
* @param filePath The absolute path to the translation file.
|
* @param filePath The absolute path to the translation file.
|
||||||
* @param contents The contents of the translation file.
|
* @param contents The contents of the translation file.
|
||||||
|
* @param hint A value that can be used by the parser to speed up parsing of the file. This will
|
||||||
|
* have been provided as the return result from calling `canParse()`.
|
||||||
|
* @returns The translation bundle parsed from the file.
|
||||||
|
* @throws No errors. If there was a problem with parsing the bundle will contain errors
|
||||||
|
* in the `diagnostics` property.
|
||||||
|
*/
|
||||||
|
parse(filePath: string, contents: string, hint: Hint): ParsedTranslationBundle;
|
||||||
|
/**
|
||||||
|
* Parses the given file, extracting the target locale and translations.
|
||||||
|
*
|
||||||
|
* @deprecated This overload is kept for backward compatibility. Going forward use the Hint
|
||||||
|
* returned from `canParse()` so that this method can avoid duplicating effort.
|
||||||
|
*
|
||||||
|
* @param filePath The absolute path to the translation file.
|
||||||
|
* @param contents The contents of the translation file.
|
||||||
|
* @returns The translation bundle parsed from the file.
|
||||||
|
* @throws An error if there was a problem parsing this file.
|
||||||
*/
|
*/
|
||||||
parse(filePath: string, contents: string): ParsedTranslationBundle;
|
parse(filePath: string, contents: string): ParsedTranslationBundle;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {Element, LexerRange, Node, XmlParser} from '@angular/compiler';
|
import {Element, LexerRange, Node, ParseError, ParseErrorLevel, ParseSourceSpan, XmlParser} from '@angular/compiler';
|
||||||
|
import {Diagnostics} from '../../../diagnostics';
|
||||||
import {TranslationParseError} from './translation_parse_error';
|
import {TranslationParseError} from './translation_parse_error';
|
||||||
|
|
||||||
export function getAttrOrThrow(element: Element, attrName: string): string {
|
export function getAttrOrThrow(element: Element, attrName: string): string {
|
||||||
@ -22,6 +23,14 @@ export function getAttribute(element: Element, attrName: string): string|undefin
|
|||||||
return attr !== undefined ? attr.value : undefined;
|
return attr !== undefined ? attr.value : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the "contents" of an XML element.
|
||||||
|
*
|
||||||
|
* This would be equivalent to parsing the `innerHTML` string of an HTML document.
|
||||||
|
*
|
||||||
|
* @param element The element whose inner range we want to parse.
|
||||||
|
* @returns a collection of XML `Node` objects that were parsed from the element's contents.
|
||||||
|
*/
|
||||||
export function parseInnerRange(element: Element): Node[] {
|
export function parseInnerRange(element: Element): Node[] {
|
||||||
const xmlParser = new XmlParser();
|
const xmlParser = new XmlParser();
|
||||||
const xml = xmlParser.parse(
|
const xml = xmlParser.parse(
|
||||||
@ -33,6 +42,10 @@ export function parseInnerRange(element: Element): Node[] {
|
|||||||
return xml.rootNodes;
|
return xml.rootNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute a `LexerRange` that contains all the children of the given `element`.
|
||||||
|
* @param element The element whose inner range we want to compute.
|
||||||
|
*/
|
||||||
function getInnerRange(element: Element): LexerRange {
|
function getInnerRange(element: Element): LexerRange {
|
||||||
const start = element.startSourceSpan !.end;
|
const start = element.startSourceSpan !.end;
|
||||||
const end = element.endSourceSpan !.start;
|
const end = element.endSourceSpan !.start;
|
||||||
@ -43,3 +56,93 @@ function getInnerRange(element: Element): LexerRange {
|
|||||||
endPos: end.offset,
|
endPos: end.offset,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This "hint" object is used to pass information from `canParse()` to `parse()` for
|
||||||
|
* `TranslationParser`s that expect XML contents.
|
||||||
|
*
|
||||||
|
* This saves the `parse()` method from having to re-parse the XML.
|
||||||
|
*/
|
||||||
|
export interface XmlTranslationParserHint {
|
||||||
|
element: Element;
|
||||||
|
errors: ParseError[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can this XML be parsed for translations, given the expected `rootNodeName` and expected root node
|
||||||
|
* `attributes` that should appear in the file.
|
||||||
|
*
|
||||||
|
* @param filePath The path to the file being checked.
|
||||||
|
* @param contents The contents of the file being checked.
|
||||||
|
* @param rootNodeName The expected name of an XML root node that should exist.
|
||||||
|
* @param attributes The attributes (and their values) that should appear on the root node.
|
||||||
|
* @returns The `XmlTranslationParserHint` object for use by `TranslationParser.parse()` if the XML
|
||||||
|
* document has the expected format.
|
||||||
|
*/
|
||||||
|
export function canParseXml(
|
||||||
|
filePath: string, contents: string, rootNodeName: string,
|
||||||
|
attributes: Record<string, string>): XmlTranslationParserHint|false {
|
||||||
|
const xmlParser = new XmlParser();
|
||||||
|
const xml = xmlParser.parse(contents, filePath);
|
||||||
|
|
||||||
|
if (xml.rootNodes.length === 0 ||
|
||||||
|
xml.errors.some(error => error.level === ParseErrorLevel.ERROR)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootElements = xml.rootNodes.filter(isNamedElement(rootNodeName));
|
||||||
|
const rootElement = rootElements[0];
|
||||||
|
if (rootElement === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const attrKey of Object.keys(attributes)) {
|
||||||
|
const attr = rootElement.attrs.find(attr => attr.name === attrKey);
|
||||||
|
if (attr === undefined || attr.value !== attributes[attrKey]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootElements.length > 1) {
|
||||||
|
xml.errors.push(new ParseError(
|
||||||
|
xml.rootNodes[1].sourceSpan,
|
||||||
|
'Unexpected root node. XLIFF 1.2 files should only have a single <xliff> root node.',
|
||||||
|
ParseErrorLevel.WARNING));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {element: rootElement, errors: xml.errors};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a predicate, which can be used by things like `Array.filter()`, that will match a named
|
||||||
|
* XML Element from a collection of XML Nodes.
|
||||||
|
*
|
||||||
|
* @param name The expected name of the element to match.
|
||||||
|
*/
|
||||||
|
export function isNamedElement(name: string): (node: Node) => node is Element {
|
||||||
|
function predicate(node: Node): node is Element {
|
||||||
|
return node instanceof Element && node.name === name;
|
||||||
|
}
|
||||||
|
return predicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an XML parser related message to the given `diagnostics` object.
|
||||||
|
*/
|
||||||
|
export function addParseDiagnostic(
|
||||||
|
diagnostics: Diagnostics, sourceSpan: ParseSourceSpan, message: string,
|
||||||
|
level: ParseErrorLevel): void {
|
||||||
|
addParseError(diagnostics, new ParseError(sourceSpan, message, level));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy the formatted error message from the given `parseError` object into the given `diagnostics`
|
||||||
|
* object.
|
||||||
|
*/
|
||||||
|
export function addParseError(diagnostics: Diagnostics, parseError: ParseError): void {
|
||||||
|
if (parseError.level === ParseErrorLevel.ERROR) {
|
||||||
|
diagnostics.error(parseError.toString());
|
||||||
|
} else {
|
||||||
|
diagnostics.warn(parseError.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,19 +5,16 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {Element, Node, XmlParser, visitAll} from '@angular/compiler';
|
import {Element, ParseErrorLevel, visitAll} from '@angular/compiler';
|
||||||
import {ɵMessageId, ɵParsedTranslation} from '@angular/localize';
|
import {ɵParsedTranslation} from '@angular/localize';
|
||||||
import {extname} from 'path';
|
|
||||||
|
|
||||||
|
import {Diagnostics} from '../../../diagnostics';
|
||||||
import {BaseVisitor} from '../base_visitor';
|
import {BaseVisitor} from '../base_visitor';
|
||||||
import {MessageSerializer} from '../message_serialization/message_serializer';
|
import {MessageSerializer} from '../message_serialization/message_serializer';
|
||||||
import {TargetMessageRenderer} from '../message_serialization/target_message_renderer';
|
import {TargetMessageRenderer} from '../message_serialization/target_message_renderer';
|
||||||
|
|
||||||
import {TranslationParseError} from './translation_parse_error';
|
|
||||||
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
|
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
|
||||||
import {getAttrOrThrow, getAttribute, parseInnerRange} from './translation_utils';
|
import {XmlTranslationParserHint, addParseDiagnostic, addParseError, canParseXml, getAttribute, isNamedElement, parseInnerRange} from './translation_utils';
|
||||||
|
|
||||||
const XLIFF_1_2_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:1.2"/;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A translation parser that can load XLIFF 1.2 files.
|
* A translation parser that can load XLIFF 1.2 files.
|
||||||
@ -26,67 +23,132 @@ const XLIFF_1_2_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:1.2"/;
|
|||||||
* http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
|
* http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export class Xliff1TranslationParser implements TranslationParser {
|
export class Xliff1TranslationParser implements TranslationParser<XmlTranslationParserHint> {
|
||||||
canParse(filePath: string, contents: string): boolean {
|
canParse(filePath: string, contents: string): XmlTranslationParserHint|false {
|
||||||
return (extname(filePath) === '.xlf') && XLIFF_1_2_NS_REGEX.test(contents);
|
return canParseXml(filePath, contents, 'xliff', {version: '1.2'});
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(filePath: string, contents: string): ParsedTranslationBundle {
|
parse(filePath: string, contents: string, hint?: XmlTranslationParserHint):
|
||||||
const xmlParser = new XmlParser();
|
ParsedTranslationBundle {
|
||||||
const xml = xmlParser.parse(contents, filePath);
|
if (hint) {
|
||||||
const bundle = XliffFileElementVisitor.extractBundle(xml.rootNodes);
|
return this.extractBundle(hint);
|
||||||
if (bundle === undefined) {
|
} else {
|
||||||
|
return this.extractBundleDeprecated(filePath, contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractBundle({element, errors}: XmlTranslationParserHint): ParsedTranslationBundle {
|
||||||
|
const diagnostics = new Diagnostics();
|
||||||
|
errors.forEach(e => addParseError(diagnostics, e));
|
||||||
|
|
||||||
|
if (element.children.length === 0) {
|
||||||
|
addParseDiagnostic(
|
||||||
|
diagnostics, element.sourceSpan, 'Missing expected <file> element',
|
||||||
|
ParseErrorLevel.WARNING);
|
||||||
|
return {locale: undefined, translations: {}, diagnostics};
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = element.children.filter(isNamedElement('file'));
|
||||||
|
if (files.length === 0) {
|
||||||
|
addParseDiagnostic(
|
||||||
|
diagnostics, element.sourceSpan, 'No <file> elements found in <xliff>',
|
||||||
|
ParseErrorLevel.WARNING);
|
||||||
|
} else if (files.length > 1) {
|
||||||
|
addParseDiagnostic(
|
||||||
|
diagnostics, files[1].sourceSpan, 'More than one <file> element found in <xliff>',
|
||||||
|
ParseErrorLevel.WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundle: ParsedTranslationBundle = {locale: undefined, translations: {}, diagnostics};
|
||||||
|
const translationVisitor = new XliffTranslationVisitor();
|
||||||
|
const localesFound = new Set<string>();
|
||||||
|
for (const file of files) {
|
||||||
|
const locale = getAttribute(file, 'target-language');
|
||||||
|
if (locale !== undefined) {
|
||||||
|
localesFound.add(locale);
|
||||||
|
bundle.locale = locale;
|
||||||
|
}
|
||||||
|
visitAll(translationVisitor, file.children, bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localesFound.size > 1) {
|
||||||
|
addParseDiagnostic(
|
||||||
|
diagnostics, element.sourceSpan,
|
||||||
|
`More than one locale found in translation file: ${JSON.stringify(Array.from(localesFound))}. Using "${bundle.locale}"`,
|
||||||
|
ParseErrorLevel.WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractBundleDeprecated(filePath: string, contents: string) {
|
||||||
|
const hint = this.canParse(filePath, contents);
|
||||||
|
if (!hint) {
|
||||||
throw new Error(`Unable to parse "${filePath}" as XLIFF 1.2 format.`);
|
throw new Error(`Unable to parse "${filePath}" as XLIFF 1.2 format.`);
|
||||||
}
|
}
|
||||||
|
const bundle = this.extractBundle(hint);
|
||||||
|
if (bundle.diagnostics.hasErrors) {
|
||||||
|
const message =
|
||||||
|
bundle.diagnostics.formatDiagnostics(`Failed to parse "${filePath}" as XLIFF 1.2 format`);
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class XliffFileElementVisitor extends BaseVisitor {
|
class XliffFileElementVisitor extends BaseVisitor {
|
||||||
private bundle: ParsedTranslationBundle|undefined;
|
visitElement(fileElement: Element): any {
|
||||||
|
if (fileElement.name === 'file') {
|
||||||
static extractBundle(xliff: Node[]): ParsedTranslationBundle|undefined {
|
return {fileElement, locale: getAttribute(fileElement, 'target-language')};
|
||||||
const visitor = new this();
|
|
||||||
visitAll(visitor, xliff);
|
|
||||||
return visitor.bundle;
|
|
||||||
}
|
|
||||||
|
|
||||||
visitElement(element: Element): any {
|
|
||||||
if (element.name === 'file') {
|
|
||||||
this.bundle = {
|
|
||||||
locale: getAttribute(element, 'target-language'),
|
|
||||||
translations: XliffTranslationVisitor.extractTranslations(element)
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return visitAll(this, element.children);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class XliffTranslationVisitor extends BaseVisitor {
|
class XliffTranslationVisitor extends BaseVisitor {
|
||||||
private translations: Record<ɵMessageId, ɵParsedTranslation> = {};
|
visitElement(element: Element, bundle: ParsedTranslationBundle): void {
|
||||||
|
if (element.name === 'trans-unit') {
|
||||||
static extractTranslations(file: Element): Record<string, ɵParsedTranslation> {
|
this.visitTransUnitElement(element, bundle);
|
||||||
const visitor = new this();
|
} else {
|
||||||
visitAll(visitor, file.children);
|
visitAll(this, element.children, bundle);
|
||||||
return visitor.translations;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
visitElement(element: Element): any {
|
private visitTransUnitElement(element: Element, bundle: ParsedTranslationBundle): void {
|
||||||
if (element.name === 'trans-unit') {
|
// Error if no `id` attribute
|
||||||
const id = getAttrOrThrow(element, 'id');
|
const id = getAttribute(element, 'id');
|
||||||
if (this.translations[id] !== undefined) {
|
if (id === undefined) {
|
||||||
throw new TranslationParseError(
|
addParseDiagnostic(
|
||||||
element.sourceSpan, `Duplicated translations for message "${id}"`);
|
bundle.diagnostics, element.sourceSpan,
|
||||||
}
|
`Missing required "id" attribute on <trans-unit> element.`, ParseErrorLevel.ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const targetMessage = element.children.find(isTargetElement);
|
// Error if there is already a translation with the same id
|
||||||
if (targetMessage === undefined) {
|
if (bundle.translations[id] !== undefined) {
|
||||||
throw new TranslationParseError(element.sourceSpan, 'Missing required <target> element');
|
addParseDiagnostic(
|
||||||
|
bundle.diagnostics, element.sourceSpan, `Duplicated translations for message "${id}"`,
|
||||||
|
ParseErrorLevel.ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error if there is no `<target>` child element
|
||||||
|
const targetMessage = element.children.find(isNamedElement('target'));
|
||||||
|
if (targetMessage === undefined) {
|
||||||
|
addParseDiagnostic(
|
||||||
|
bundle.diagnostics, element.sourceSpan, 'Missing required <target> element',
|
||||||
|
ParseErrorLevel.ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
bundle.translations[id] = serializeTargetMessage(targetMessage);
|
||||||
|
} catch (e) {
|
||||||
|
// Capture any errors from serialize the target message
|
||||||
|
if (e.span && e.msg && e.level) {
|
||||||
|
addParseDiagnostic(bundle.diagnostics, e.span, e.msg, e.level);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
this.translations[id] = serializeTargetMessage(targetMessage);
|
|
||||||
} else {
|
|
||||||
return visitAll(this, element.children);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,7 +160,3 @@ function serializeTargetMessage(source: Element): ɵParsedTranslation {
|
|||||||
});
|
});
|
||||||
return serializer.serialize(parseInnerRange(source));
|
return serializer.serialize(parseInnerRange(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTargetElement(node: Node): node is Element {
|
|
||||||
return node instanceof Element && node.name === 'target';
|
|
||||||
}
|
|
||||||
|
@ -5,19 +5,16 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {Element, Node, XmlParser, visitAll} from '@angular/compiler';
|
import {Element, Node, ParseErrorLevel, visitAll} from '@angular/compiler';
|
||||||
import {ɵMessageId, ɵParsedTranslation} from '@angular/localize';
|
import {ɵParsedTranslation} from '@angular/localize';
|
||||||
import {extname} from 'path';
|
|
||||||
|
|
||||||
|
import {Diagnostics} from '../../../diagnostics';
|
||||||
import {BaseVisitor} from '../base_visitor';
|
import {BaseVisitor} from '../base_visitor';
|
||||||
import {MessageSerializer} from '../message_serialization/message_serializer';
|
import {MessageSerializer} from '../message_serialization/message_serializer';
|
||||||
import {TargetMessageRenderer} from '../message_serialization/target_message_renderer';
|
import {TargetMessageRenderer} from '../message_serialization/target_message_renderer';
|
||||||
|
|
||||||
import {TranslationParseError} from './translation_parse_error';
|
|
||||||
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
|
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
|
||||||
import {getAttrOrThrow, getAttribute, parseInnerRange} from './translation_utils';
|
import {XmlTranslationParserHint, addParseDiagnostic, addParseError, canParseXml, getAttribute, isNamedElement, parseInnerRange} from './translation_utils';
|
||||||
|
|
||||||
const XLIFF_2_0_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:2.0"/;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A translation parser that can load translations from XLIFF 2 files.
|
* A translation parser that can load translations from XLIFF 2 files.
|
||||||
@ -25,84 +22,126 @@ const XLIFF_2_0_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:2.0"/;
|
|||||||
* http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html
|
* http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export class Xliff2TranslationParser implements TranslationParser {
|
export class Xliff2TranslationParser implements TranslationParser<XmlTranslationParserHint> {
|
||||||
canParse(filePath: string, contents: string): boolean {
|
canParse(filePath: string, contents: string): XmlTranslationParserHint|false {
|
||||||
return (extname(filePath) === '.xlf') && XLIFF_2_0_NS_REGEX.test(contents);
|
return canParseXml(filePath, contents, 'xliff', {version: '2.0'});
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(filePath: string, contents: string): ParsedTranslationBundle {
|
parse(filePath: string, contents: string, hint?: XmlTranslationParserHint):
|
||||||
const xmlParser = new XmlParser();
|
ParsedTranslationBundle {
|
||||||
const xml = xmlParser.parse(contents, filePath);
|
if (hint) {
|
||||||
const bundle = Xliff2TranslationBundleVisitor.extractBundle(xml.rootNodes);
|
return this.extractBundle(hint);
|
||||||
if (bundle === undefined) {
|
} else {
|
||||||
|
return this.extractBundleDeprecated(filePath, contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractBundle({element, errors}: XmlTranslationParserHint): ParsedTranslationBundle {
|
||||||
|
const diagnostics = new Diagnostics();
|
||||||
|
errors.forEach(e => addParseError(diagnostics, e));
|
||||||
|
|
||||||
|
const locale = getAttribute(element, 'trgLang');
|
||||||
|
const files = element.children.filter(isFileElement);
|
||||||
|
if (files.length === 0) {
|
||||||
|
addParseDiagnostic(
|
||||||
|
diagnostics, element.sourceSpan, 'No <file> elements found in <xliff>',
|
||||||
|
ParseErrorLevel.WARNING);
|
||||||
|
} else if (files.length > 1) {
|
||||||
|
addParseDiagnostic(
|
||||||
|
diagnostics, files[1].sourceSpan, 'More than one <file> element found in <xliff>',
|
||||||
|
ParseErrorLevel.WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundle = {locale, translations: {}, diagnostics};
|
||||||
|
const translationVisitor = new Xliff2TranslationVisitor();
|
||||||
|
for (const file of files) {
|
||||||
|
visitAll(translationVisitor, file.children, {bundle});
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractBundleDeprecated(filePath: string, contents: string) {
|
||||||
|
const hint = this.canParse(filePath, contents);
|
||||||
|
if (!hint) {
|
||||||
throw new Error(`Unable to parse "${filePath}" as XLIFF 2.0 format.`);
|
throw new Error(`Unable to parse "${filePath}" as XLIFF 2.0 format.`);
|
||||||
}
|
}
|
||||||
|
const bundle = this.extractBundle(hint);
|
||||||
|
if (bundle.diagnostics.hasErrors) {
|
||||||
|
const message =
|
||||||
|
bundle.diagnostics.formatDiagnostics(`Failed to parse "${filePath}" as XLIFF 2.0 format`);
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BundleVisitorContext {
|
|
||||||
parsedLocale?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Xliff2TranslationBundleVisitor extends BaseVisitor {
|
interface TranslationVisitorContext {
|
||||||
private bundle: ParsedTranslationBundle|undefined;
|
unit?: string;
|
||||||
|
bundle: ParsedTranslationBundle;
|
||||||
static extractBundle(xliff: Node[]): ParsedTranslationBundle|undefined {
|
|
||||||
const visitor = new this();
|
|
||||||
visitAll(visitor, xliff, {});
|
|
||||||
return visitor.bundle;
|
|
||||||
}
|
|
||||||
|
|
||||||
visitElement(element: Element, {parsedLocale}: BundleVisitorContext): any {
|
|
||||||
if (element.name === 'xliff') {
|
|
||||||
parsedLocale = getAttribute(element, 'trgLang');
|
|
||||||
return visitAll(this, element.children, {parsedLocale});
|
|
||||||
} else if (element.name === 'file') {
|
|
||||||
this.bundle = {
|
|
||||||
locale: parsedLocale,
|
|
||||||
translations: Xliff2TranslationVisitor.extractTranslations(element)
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return visitAll(this, element.children, {parsedLocale});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Xliff2TranslationVisitor extends BaseVisitor {
|
class Xliff2TranslationVisitor extends BaseVisitor {
|
||||||
private translations: Record<ɵMessageId, ɵParsedTranslation> = {};
|
visitElement(element: Element, {bundle, unit}: TranslationVisitorContext): any {
|
||||||
|
|
||||||
static extractTranslations(file: Element): Record<string, ɵParsedTranslation> {
|
|
||||||
const visitor = new this();
|
|
||||||
visitAll(visitor, file.children);
|
|
||||||
return visitor.translations;
|
|
||||||
}
|
|
||||||
|
|
||||||
visitElement(element: Element, context: any): any {
|
|
||||||
if (element.name === 'unit') {
|
if (element.name === 'unit') {
|
||||||
const externalId = getAttrOrThrow(element, 'id');
|
this.visitUnitElement(element, bundle);
|
||||||
if (this.translations[externalId] !== undefined) {
|
|
||||||
throw new TranslationParseError(
|
|
||||||
element.sourceSpan, `Duplicated translations for message "${externalId}"`);
|
|
||||||
}
|
|
||||||
visitAll(this, element.children, {unit: externalId});
|
|
||||||
} else if (element.name === 'segment') {
|
} else if (element.name === 'segment') {
|
||||||
assertTranslationUnit(element, context);
|
this.visitSegmentElement(element, bundle, unit);
|
||||||
const targetMessage = element.children.find(isTargetElement);
|
|
||||||
if (targetMessage === undefined) {
|
|
||||||
throw new TranslationParseError(element.sourceSpan, 'Missing required <target> element');
|
|
||||||
}
|
|
||||||
this.translations[context.unit] = serializeTargetMessage(targetMessage);
|
|
||||||
} else {
|
} else {
|
||||||
return visitAll(this, element.children);
|
visitAll(this, element.children, {bundle, unit});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function assertTranslationUnit(segment: Element, context: any) {
|
private visitUnitElement(element: Element, bundle: ParsedTranslationBundle): void {
|
||||||
if (context === undefined || context.unit === undefined) {
|
// Error if no `id` attribute
|
||||||
throw new TranslationParseError(
|
const externalId = getAttribute(element, 'id');
|
||||||
segment.sourceSpan, 'Invalid <segment> element: should be a child of a <unit> element.');
|
if (externalId === undefined) {
|
||||||
|
addParseDiagnostic(
|
||||||
|
bundle.diagnostics, element.sourceSpan,
|
||||||
|
`Missing required "id" attribute on <trans-unit> element.`, ParseErrorLevel.ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error if there is already a translation with the same id
|
||||||
|
if (bundle.translations[externalId] !== undefined) {
|
||||||
|
addParseDiagnostic(
|
||||||
|
bundle.diagnostics, element.sourceSpan,
|
||||||
|
`Duplicated translations for message "${externalId}"`, ParseErrorLevel.ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitAll(this, element.children, {bundle, unit: externalId});
|
||||||
|
}
|
||||||
|
|
||||||
|
private visitSegmentElement(
|
||||||
|
element: Element, bundle: ParsedTranslationBundle, unit: string|undefined): void {
|
||||||
|
// A `<segment>` element must be below a `<unit>` element
|
||||||
|
if (unit === undefined) {
|
||||||
|
addParseDiagnostic(
|
||||||
|
bundle.diagnostics, element.sourceSpan,
|
||||||
|
'Invalid <segment> element: should be a child of a <unit> element.',
|
||||||
|
ParseErrorLevel.ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetMessage = element.children.find(isNamedElement('target'));
|
||||||
|
if (targetMessage === undefined) {
|
||||||
|
addParseDiagnostic(
|
||||||
|
bundle.diagnostics, element.sourceSpan, 'Missing required <target> element',
|
||||||
|
ParseErrorLevel.ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
bundle.translations[unit] = serializeTargetMessage(targetMessage);
|
||||||
|
} catch (e) {
|
||||||
|
// Capture any errors from serialize the target message
|
||||||
|
if (e.span && e.msg && e.level) {
|
||||||
|
addParseDiagnostic(bundle.diagnostics, e.span, e.msg, e.level);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +155,6 @@ function serializeTargetMessage(source: Element): ɵParsedTranslation {
|
|||||||
return serializer.serialize(parseInnerRange(source));
|
return serializer.serialize(parseInnerRange(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTargetElement(node: Node): node is Element {
|
function isFileElement(node: Node): node is Element {
|
||||||
return node instanceof Element && node.name === 'target';
|
return node instanceof Element && node.name === 'file';
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {Element, Node, XmlParser, visitAll} from '@angular/compiler';
|
import {Element, ParseErrorLevel, visitAll} from '@angular/compiler';
|
||||||
import {ɵParsedTranslation} from '@angular/localize';
|
import {ɵParsedTranslation} from '@angular/localize';
|
||||||
import {extname} from 'path';
|
import {extname} from 'path';
|
||||||
|
|
||||||
@ -14,83 +14,100 @@ import {BaseVisitor} from '../base_visitor';
|
|||||||
import {MessageSerializer} from '../message_serialization/message_serializer';
|
import {MessageSerializer} from '../message_serialization/message_serializer';
|
||||||
import {TargetMessageRenderer} from '../message_serialization/target_message_renderer';
|
import {TargetMessageRenderer} from '../message_serialization/target_message_renderer';
|
||||||
|
|
||||||
import {TranslationParseError} from './translation_parse_error';
|
|
||||||
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
|
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
|
||||||
import {getAttrOrThrow, parseInnerRange} from './translation_utils';
|
import {XmlTranslationParserHint, addParseDiagnostic, addParseError, canParseXml, getAttribute, parseInnerRange} from './translation_utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A translation parser that can load XB files.
|
* A translation parser that can load XB files.
|
||||||
*/
|
*/
|
||||||
export class XtbTranslationParser implements TranslationParser {
|
export class XtbTranslationParser implements TranslationParser<XmlTranslationParserHint> {
|
||||||
constructor(private diagnostics: Diagnostics) {}
|
canParse(filePath: string, contents: string): XmlTranslationParserHint|false {
|
||||||
|
|
||||||
canParse(filePath: string, contents: string): boolean {
|
|
||||||
const extension = extname(filePath);
|
const extension = extname(filePath);
|
||||||
return (extension === '.xtb' || extension === '.xmb') &&
|
if (extension !== '.xtb' && extension !== '.xmb') {
|
||||||
contents.includes('<translationbundle');
|
return false;
|
||||||
|
}
|
||||||
|
return canParseXml(filePath, contents, 'translationbundle', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(filePath: string, contents: string): ParsedTranslationBundle {
|
parse(filePath: string, contents: string, hint?: XmlTranslationParserHint):
|
||||||
const xmlParser = new XmlParser();
|
ParsedTranslationBundle {
|
||||||
const xml = xmlParser.parse(contents, filePath);
|
if (hint) {
|
||||||
const bundle = XtbVisitor.extractBundle(this.diagnostics, xml.rootNodes);
|
return this.extractBundle(hint);
|
||||||
if (bundle === undefined) {
|
} else {
|
||||||
throw new Error(`Unable to parse "${filePath}" as XTB/XMB format.`);
|
return this.extractBundleDeprecated(filePath, contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractBundle({element, errors}: XmlTranslationParserHint): ParsedTranslationBundle {
|
||||||
|
const langAttr = element.attrs.find((attr) => attr.name === 'lang');
|
||||||
|
const bundle: ParsedTranslationBundle = {
|
||||||
|
locale: langAttr && langAttr.value,
|
||||||
|
translations: {},
|
||||||
|
diagnostics: new Diagnostics()
|
||||||
|
};
|
||||||
|
errors.forEach(e => addParseError(bundle.diagnostics, e));
|
||||||
|
|
||||||
|
const bundleVisitor = new XtbVisitor();
|
||||||
|
visitAll(bundleVisitor, element.children, bundle);
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractBundleDeprecated(filePath: string, contents: string) {
|
||||||
|
const hint = this.canParse(filePath, contents);
|
||||||
|
if (!hint) {
|
||||||
|
throw new Error(`Unable to parse "${filePath}" as XMB/XTB format.`);
|
||||||
|
}
|
||||||
|
const bundle = this.extractBundle(hint);
|
||||||
|
if (bundle.diagnostics.hasErrors) {
|
||||||
|
const message =
|
||||||
|
bundle.diagnostics.formatDiagnostics(`Failed to parse "${filePath}" as XMB/XTB format`);
|
||||||
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class XtbVisitor extends BaseVisitor {
|
class XtbVisitor extends BaseVisitor {
|
||||||
static extractBundle(diagnostics: Diagnostics, messageBundles: Node[]): ParsedTranslationBundle
|
visitElement(element: Element, bundle: ParsedTranslationBundle): any {
|
||||||
|undefined {
|
|
||||||
const visitor = new this(diagnostics);
|
|
||||||
const bundles: ParsedTranslationBundle[] = visitAll(visitor, messageBundles, undefined);
|
|
||||||
return bundles[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(private diagnostics: Diagnostics) { super(); }
|
|
||||||
|
|
||||||
visitElement(element: Element, bundle: ParsedTranslationBundle|undefined): any {
|
|
||||||
switch (element.name) {
|
switch (element.name) {
|
||||||
case 'translationbundle':
|
|
||||||
if (bundle) {
|
|
||||||
throw new TranslationParseError(
|
|
||||||
element.sourceSpan, '<translationbundle> elements can not be nested');
|
|
||||||
}
|
|
||||||
const langAttr = element.attrs.find((attr) => attr.name === 'lang');
|
|
||||||
bundle = {locale: langAttr && langAttr.value, translations: {}};
|
|
||||||
visitAll(this, element.children, bundle);
|
|
||||||
return bundle;
|
|
||||||
|
|
||||||
case 'translation':
|
case 'translation':
|
||||||
if (!bundle) {
|
// Error if no `id` attribute
|
||||||
throw new TranslationParseError(
|
const id = getAttribute(element, 'id');
|
||||||
element.sourceSpan, '<translation> must be inside a <translationbundle>');
|
if (id === undefined) {
|
||||||
|
addParseDiagnostic(
|
||||||
|
bundle.diagnostics, element.sourceSpan,
|
||||||
|
`Missing required "id" attribute on <trans-unit> element.`, ParseErrorLevel.ERROR);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const id = getAttrOrThrow(element, 'id');
|
|
||||||
if (bundle.translations.hasOwnProperty(id)) {
|
// Error if there is already a translation with the same id
|
||||||
throw new TranslationParseError(
|
if (bundle.translations[id] !== undefined) {
|
||||||
element.sourceSpan, `Duplicated translations for message "${id}"`);
|
addParseDiagnostic(
|
||||||
} else {
|
bundle.diagnostics, element.sourceSpan, `Duplicated translations for message "${id}"`,
|
||||||
try {
|
ParseErrorLevel.ERROR);
|
||||||
bundle.translations[id] = serializeTargetMessage(element);
|
return;
|
||||||
} catch (error) {
|
}
|
||||||
if (typeof error === 'string') {
|
|
||||||
this.diagnostics.warn(
|
try {
|
||||||
`Could not parse message with id "${id}" - perhaps it has an unrecognised ICU format?\n` +
|
bundle.translations[id] = serializeTargetMessage(element);
|
||||||
error);
|
} catch (error) {
|
||||||
} else {
|
if (typeof error === 'string') {
|
||||||
throw error;
|
bundle.diagnostics.warn(
|
||||||
}
|
`Could not parse message with id "${id}" - perhaps it has an unrecognised ICU format?\n` +
|
||||||
|
error);
|
||||||
|
} else if (error.span && error.msg && error.level) {
|
||||||
|
addParseDiagnostic(bundle.diagnostics, error.span, error.msg, error.level);
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new TranslationParseError(element.sourceSpan, 'Unexpected tag');
|
addParseDiagnostic(
|
||||||
|
bundle.diagnostics, element.sourceSpan, `Unexpected <${element.name}> tag.`,
|
||||||
|
ParseErrorLevel.ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import {OutputPathFn} from './output_path';
|
|||||||
export interface TranslationBundle {
|
export interface TranslationBundle {
|
||||||
locale: string;
|
locale: string;
|
||||||
translations: Record<ɵMessageId, ɵParsedTranslation>;
|
translations: Record<ɵMessageId, ɵParsedTranslation>;
|
||||||
|
diagnostics?: Diagnostics;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user