Compare commits
50 Commits
zone.js-0.
...
9.0.0-next
Author | SHA1 | Date | |
---|---|---|---|
2abbe98e33 | |||
7613f13e54 | |||
4b8cdd4b57 | |||
17e289c39f | |||
2913340af7 | |||
f8c27d42ed | |||
17bb633031 | |||
9106271f2c | |||
48a3741d5a | |||
9d1f43f3ba | |||
6f98107d5e | |||
a8e2ee1343 | |||
e906a4f0d8 | |||
b5b33d12d6 | |||
22d3cabc10 | |||
a06043b703 | |||
4689ea2727 | |||
939529ce5d | |||
46304a4f83 | |||
f7eebd0227 | |||
8af2cc1efe | |||
e5a89e047c | |||
29d3b68554 | |||
93d27eefd5 | |||
ed70f73794 | |||
ef12e10e59 | |||
2954d1b5ca | |||
3077c9a1f8 | |||
9537b2ff84 | |||
961d663fbe | |||
57e15fc08b | |||
b70746a113 | |||
0709ed4c2b | |||
fa699f65d7 | |||
18bc4eda9f | |||
f542649b2b | |||
a574e462c9 | |||
65cafa0eec | |||
18aa173d39 | |||
bc8eb8508b | |||
7db269ba6a | |||
8e5567d964 | |||
541ce98432 | |||
e7e3f5d952 | |||
382d3ed1d2 | |||
a07de82f79 | |||
2e84f4e0cd | |||
a5b12db7d6 | |||
8b94d6a402 | |||
96cbcd6da4 |
@ -321,7 +321,7 @@ jobs:
|
|||||||
- *attach_workspace
|
- *attach_workspace
|
||||||
- *init_environment
|
- *init_environment
|
||||||
# Build aio (with local Angular packages)
|
# Build aio (with local Angular packages)
|
||||||
- run: yarn --cwd aio build-local --progress=false
|
- run: yarn --cwd aio build-local-ci
|
||||||
# Run unit tests
|
# Run unit tests
|
||||||
- run: yarn --cwd aio test --progress=false --watch=false
|
- run: yarn --cwd aio test --progress=false --watch=false
|
||||||
# Run e2e tests
|
# Run e2e tests
|
||||||
@ -340,7 +340,7 @@ jobs:
|
|||||||
- *attach_workspace
|
- *attach_workspace
|
||||||
- *init_environment
|
- *init_environment
|
||||||
# Build aio with Ivy (using local Angular packages)
|
# Build aio with Ivy (using local Angular packages)
|
||||||
- run: yarn --cwd aio build-with-ivy --progress=false
|
- run: yarn --cwd aio build-with-ivy-ci
|
||||||
# Run unit tests
|
# Run unit tests
|
||||||
- run: yarn --cwd aio test --progress=false --watch=false
|
- run: yarn --cwd aio test --progress=false --watch=false
|
||||||
# Run e2e tests
|
# Run e2e tests
|
||||||
|
24
CHANGELOG.md
24
CHANGELOG.md
@ -1,3 +1,25 @@
|
|||||||
|
<a name="9.0.0-next.1"></a>
|
||||||
|
# [9.0.0-next.1](https://github.com/angular/angular/compare/9.0.0-next.0...9.0.0-next.1) (2019-08-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **language-service:** getSourceFile() should only be called on TS files ([#31920](https://github.com/angular/angular/issues/31920)) ([e8b8f6d](https://github.com/angular/angular/commit/e8b8f6d))
|
||||||
|
* **language-service:** Make Definition and QuickInfo compatible with TS LS ([#31972](https://github.com/angular/angular/issues/31972)) ([a8e2ee1](https://github.com/angular/angular/commit/a8e2ee1))
|
||||||
|
* **upgrade:** compile downgraded components synchronously (if possible) ([#31840](https://github.com/angular/angular/issues/31840)) ([c1ae612](https://github.com/angular/angular/commit/c1ae612)), closes [#27217](https://github.com/angular/angular/issues/27217) [#30330](https://github.com/angular/angular/issues/30330)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="8.2.1"></a>
|
||||||
|
## [8.2.1](https://github.com/angular/angular/compare/8.2.0...8.2.1) (2019-08-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **upgrade:** compile downgraded components synchronously (if possible) ([#31840](https://github.com/angular/angular/issues/31840)) ([04ebd59](https://github.com/angular/angular/commit/04ebd59)), closes [#27217](https://github.com/angular/angular/issues/27217) [#30330](https://github.com/angular/angular/issues/30330)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="9.0.0-next.0"></a>
|
<a name="9.0.0-next.0"></a>
|
||||||
# [9.0.0-next.0](https://github.com/angular/angular/compare/8.2.0-next.2...9.0.0-next.0) (2019-07-31)
|
# [9.0.0-next.0](https://github.com/angular/angular/compare/8.2.0-next.2...9.0.0-next.0) (2019-07-31)
|
||||||
|
|
||||||
@ -10,7 +32,7 @@
|
|||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* **core:** TypeScript 3.5 support ([#31615](https://github.com/angular/angular/issues/31615)) ([6ece7db]
|
* **core:** TypeScript 3.5 support ([#31615](https://github.com/angular/angular/issues/31615)) ([6ece7db](https://github.com/angular/angular/commit/6ece7db))
|
||||||
* **core:** add automatic migration from Renderer to Renderer2 ([#30936](https://github.com/angular/angular/issues/30936)) ([c095597](https://github.com/angular/angular/commit/c095597))
|
* **core:** add automatic migration from Renderer to Renderer2 ([#30936](https://github.com/angular/angular/issues/30936)) ([c095597](https://github.com/angular/angular/commit/c095597))
|
||||||
* **bazel:** compile targets used for indexing by Kythe with Ivy ([#31786](https://github.com/angular/angular/issues/31786)) ([82055b2](https://github.com/angular/angular/commit/82055b2))
|
* **bazel:** compile targets used for indexing by Kythe with Ivy ([#31786](https://github.com/angular/angular/issues/31786)) ([82055b2](https://github.com/angular/angular/commit/82055b2))
|
||||||
* **upgrade:** support $element in upgraded component template/templateUrl functions ([#31637](https://github.com/angular/angular/issues/31637)) ([29e1c53](https://github.com/angular/angular/commit/29e1c53))
|
* **upgrade:** support $element in upgraded component template/templateUrl functions ([#31637](https://github.com/angular/angular/issues/31637)) ([29e1c53](https://github.com/angular/angular/commit/29e1c53))
|
||||||
|
@ -14,10 +14,12 @@ Here are the most important tasks you might need to use:
|
|||||||
|
|
||||||
* `yarn` - install all the dependencies.
|
* `yarn` - install all the dependencies.
|
||||||
* `yarn setup` - install all the dependencies, boilerplate, stackblitz, zips and run dgeni on the docs.
|
* `yarn setup` - install all the dependencies, boilerplate, stackblitz, zips and run dgeni on the docs.
|
||||||
* `yarn setup-local` - same as `setup`, but use the locally built Angular packages for aio and docs examples boilerplate.
|
* `yarn setup-local` - same as `setup`, but build the Angular packages from the source code and use these locally built versions (instead of the ones fetched from npm) for aio and docs examples boilerplate.
|
||||||
|
|
||||||
* `yarn build` - create a production build of the application (after installing dependencies, boilerplate, etc).
|
* `yarn build` - create a production build of the application (after installing dependencies, boilerplate, etc).
|
||||||
* `yarn build-local` - same as `build`, but use `setup-local` instead of `setup`.
|
* `yarn build-local` - same as `build`, but use `setup-local` instead of `setup`.
|
||||||
|
* `yarn build-with-ivy` - same as `build-local`, but in addition also turns on `ivy` mode in aio.
|
||||||
|
(Note: To turn on `ivy` mode in examples, see `yarn boilerplate:add` below.)
|
||||||
|
|
||||||
* `yarn start` - run a development web server that watches the files; then builds the doc-viewer and reloads the page, as necessary.
|
* `yarn start` - run a development web server that watches the files; then builds the doc-viewer and reloads the page, as necessary.
|
||||||
* `yarn serve-and-sync` - run both the `docs-watch` and `start` in the same console.
|
* `yarn serve-and-sync` - run both the `docs-watch` and `start` in the same console.
|
||||||
@ -31,7 +33,10 @@ Here are the most important tasks you might need to use:
|
|||||||
* `yarn docs-lint` - check that the doc gen code follows our style rules.
|
* `yarn docs-lint` - check that the doc gen code follows our style rules.
|
||||||
* `yarn docs-test` - run the unit tests for the doc generation code.
|
* `yarn docs-test` - run the unit tests for the doc generation code.
|
||||||
|
|
||||||
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally. Add the option `--local` to use your local version of Angular contained in the "dist" folder.
|
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally.
|
||||||
|
- Add the option `--local` to use your local version of Angular contained in the "dist" folder.
|
||||||
|
- Add the option `--ivy` to turn on `ivy` mode.
|
||||||
|
|
||||||
* `yarn boilerplate:remove` - remove all the boilerplate code that was added via `yarn boilerplate:add`.
|
* `yarn boilerplate:remove` - remove all the boilerplate code that was added via `yarn boilerplate:add`.
|
||||||
* `yarn generate-stackblitz` - generate the stackblitz files that are used by the `live-example` tags in the docs.
|
* `yarn generate-stackblitz` - generate the stackblitz files that are used by the `live-example` tags in the docs.
|
||||||
* `yarn generate-zips` - generate the zip files from the examples. Zip available via the `live-example` tags in the docs.
|
* `yarn generate-zips` - generate the zip files from the examples. Zip available via the `live-example` tags in the docs.
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"description": "Quickstart AppComponent Testing",
|
|
||||||
"files":[
|
|
||||||
"src/browser-test-shim.js",
|
|
||||||
"src/app/app.component.ts",
|
|
||||||
"src/app/app.component.spec.ts",
|
|
||||||
"src/quickstart-specs.html"
|
|
||||||
],
|
|
||||||
"main": "src/quickstart-specs.html",
|
|
||||||
"file": "src/app/app.component.spec.ts",
|
|
||||||
"tags": ["quickstart", "setup", "testing"]
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
<!-- Run application specs in a browser -->
|
|
||||||
<!-- #docregion -->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<base href="/">
|
|
||||||
<title>1st Specs</title>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="stylesheet" href="styles.css">
|
|
||||||
<link rel="stylesheet" href="node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- Polyfills -->
|
|
||||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
|
||||||
|
|
||||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
|
||||||
|
|
||||||
<script src="node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
|
|
||||||
<script src="node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
|
|
||||||
<script src="node_modules/jasmine-core/lib/jasmine-core/boot.js"></script>
|
|
||||||
|
|
||||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
|
||||||
<script src="node_modules/zone.js/dist/zone-testing.js"></script>
|
|
||||||
|
|
||||||
<!-- #docregion files -->
|
|
||||||
<script>
|
|
||||||
var __spec_files__ = [
|
|
||||||
'app/app.component.spec'
|
|
||||||
];
|
|
||||||
</script>
|
|
||||||
<!-- #enddocregion files-->
|
|
||||||
<script src="browser-test-shim.js"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"description": "QuickStart Setup",
|
|
||||||
"files": [
|
|
||||||
"src/app/app.component.ts",
|
|
||||||
"src/app/app.module.ts",
|
|
||||||
"src/index.html",
|
|
||||||
"src/main.ts",
|
|
||||||
"src/styles.css"
|
|
||||||
],
|
|
||||||
"file": "src/app/app.component.ts",
|
|
||||||
"tags": ["quickstart", "setup", "seed"]
|
|
||||||
}
|
|
@ -1,24 +1,22 @@
|
|||||||
{
|
{
|
||||||
"description": "Testing - specs",
|
"description": "Testing - specs",
|
||||||
"files":[
|
"files":[
|
||||||
|
"src/expected.ts",
|
||||||
|
"src/index-specs.html",
|
||||||
|
"src/main-specs.ts",
|
||||||
"src/styles.css",
|
"src/styles.css",
|
||||||
|
"src/test.css",
|
||||||
|
"src/tests.sb.ts",
|
||||||
|
|
||||||
|
"e2e/src/**/*.ts",
|
||||||
|
|
||||||
"src/app/**/*.css",
|
"src/app/**/*.css",
|
||||||
"src/app/**/*.html",
|
"src/app/**/*.html",
|
||||||
"src/app/**/*.ts",
|
"src/app/**/*.ts",
|
||||||
"src/app/**/*.spec.ts",
|
|
||||||
|
|
||||||
"src/testing/*.ts",
|
"src/testing/**/*",
|
||||||
|
|
||||||
"!src/main.ts",
|
"src/**/*.spec.ts"
|
||||||
"!src/app/bag/*.*",
|
|
||||||
"!src/app/1st.spec.ts",
|
|
||||||
|
|
||||||
"src/expected.ts",
|
|
||||||
"src/test.css",
|
|
||||||
"src/tests.sb.ts",
|
|
||||||
"src/main-specs.ts",
|
|
||||||
"src/index-specs.html"
|
|
||||||
],
|
],
|
||||||
"main": "src/index-specs.html",
|
"main": "src/index-specs.html",
|
||||||
"tags": ["testing"]
|
"tags": ["testing"]
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
const jasmineRequire = require('jasmine-core/lib/jasmine-core/jasmine.js');
|
import jasmineRequire from 'jasmine-core/lib/jasmine-core/jasmine.js';
|
||||||
|
|
||||||
window['jasmineRequire'] = jasmineRequire;
|
window['jasmineRequire'] = jasmineRequire;
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
{
|
{
|
||||||
"description": "Heroes Test App",
|
"description": "Heroes Test App",
|
||||||
"files":[
|
"files":[
|
||||||
|
"src/index.html",
|
||||||
|
"src/main.ts",
|
||||||
"src/styles.css",
|
"src/styles.css",
|
||||||
|
"src/test.css",
|
||||||
|
|
||||||
|
"e2e/src/**/*.ts",
|
||||||
|
|
||||||
"src/app/**/*.css",
|
"src/app/**/*.css",
|
||||||
"src/app/**/*.html",
|
"src/app/**/*.html",
|
||||||
"src/app/**/*.ts",
|
"src/app/**/*.ts",
|
||||||
|
|
||||||
"!src/app/bag/*.*",
|
"!src/**/*.spec.ts"
|
||||||
|
|
||||||
"!src/test.ts",
|
|
||||||
|
|
||||||
"src/test.css",
|
|
||||||
"src/main.ts",
|
|
||||||
"src/index.html"
|
|
||||||
],
|
],
|
||||||
"tags": ["testing"]
|
"tags": ["testing"]
|
||||||
}
|
}
|
||||||
|
148
aio/content/guide/angular-compiler-options.md
Normal file
148
aio/content/guide/angular-compiler-options.md
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
# Angular compiler options
|
||||||
|
|
||||||
|
When you use [AOT compilation](guide/aot-compiler), you can control how your application is compiled by specifying *template* compiler options in the `tsconfig.json` [TypeScript configuration file](guide/typescript-configuration).
|
||||||
|
|
||||||
|
The template options object, `angularCompilerOptions`, is a sibling to the `compilerOptions` object that supplies standard options to the TypeScript compiler.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"fullTemplateTypeCheck": true,
|
||||||
|
"preserveWhitespaces": true,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
This page describes the available Angular template compiler options.
|
||||||
|
|
||||||
|
### `allowEmptyCodegenFiles`
|
||||||
|
|
||||||
|
When true, generate all possible files even if they are empty. Default is false. Used by the Bazel build rules to simplify how Bazel rules track file dependencies. Do not use this option outside of the Bazel rules.
|
||||||
|
|
||||||
|
### `annotationsAs`
|
||||||
|
|
||||||
|
Modifies how Angular-specific annotations are emitted to improve tree-shaking. Non-Angular annotations are not affected. One of `static fields` (the default) or `decorators`.
|
||||||
|
|
||||||
|
* By default, the compiler replaces decorators with a static field in the class, which allows advanced tree-shakers like [Closure compiler](https://github.com/google/closure-compiler) to remove unused classes.
|
||||||
|
|
||||||
|
* The `decorators` value leaves the decorators in place, which makes compilation faster. TypeScript emits calls to the` __decorate` helper. Use `--emitDecoratorMetadata` for runtime reflection (but note taht the resulting code will not properly tree-shake.
|
||||||
|
|
||||||
|
### `annotateForClosureCompiler`
|
||||||
|
|
||||||
|
When true, use [Tsickle](https://github.com/angular/tsickle) to annotate the emitted JavaScript with [JSDoc](http://usejsdoc.org/) comments needed by the
|
||||||
|
[Closure Compiler](https://github.com/google/closure-compiler). Default is false.
|
||||||
|
|
||||||
|
### `disableExpressionLowering`
|
||||||
|
|
||||||
|
When true (the default), transforms code that is or could be used in an annotation, to allow it to be imported from template factory modules. See [metadata rewriting](guide/aot-compiler#metadata-rewriting) for more information.
|
||||||
|
|
||||||
|
When `false`, disables this rewriting, requiring the rewriting to be done manually.
|
||||||
|
|
||||||
|
### `disableTypeScriptVersionCheck`
|
||||||
|
|
||||||
|
When `true`, the compiler does not check the TypeScript version and does not report an error when an unsupported version of TypeScript is used. Not recommended, as unsupported versions of TypeScript might have undefined behavior. Default is false.
|
||||||
|
|
||||||
|
### `enableResourceInlining`
|
||||||
|
|
||||||
|
When true, replaces the `templateUrl` and `styleUrls` property in all `@Component` decorators with inlined contents in `template` and `styles` properties.
|
||||||
|
|
||||||
|
When enabled, the `.js` output of `ngc` does not include any lazy-loaded template or style URLs.
|
||||||
|
|
||||||
|
|
||||||
|
{@a enablelegacytemplate}
|
||||||
|
|
||||||
|
### `enableLegacyTemplate`
|
||||||
|
|
||||||
|
When true, enables use of the `<template>` element, which was deprecated in Angular 4.0, in favor of `<ng-template>` (to avoid colliding with the DOM's element of the same name). Default is false. Might be required by some third-party Angular libraries. |
|
||||||
|
|
||||||
|
### `flatModuleId`
|
||||||
|
|
||||||
|
The module ID to use for importing a flat module (when `flatModuleOutFile` is true). References generated by the template compiler use this module name when importing symbols
|
||||||
|
from the flat module. Ignored if `flatModuleOutFile` is false.
|
||||||
|
|
||||||
|
### `flatModuleOutFile`
|
||||||
|
|
||||||
|
When true, generates a flat module index of the given file name and the corresponding flat module metadata. Use to create flat modules that are packaged similarly to `@angular/core` and `@angular/common`. When this option is used, the `package.json` for the library should refer
|
||||||
|
to the generated flat module index instead of the library index file.
|
||||||
|
|
||||||
|
Produces only one `.metadata.json` file, which contains all the metadata necessary
|
||||||
|
for symbols exported from the library index. In the generated `.ngfactory.js` files, the flat
|
||||||
|
module index is used to import symbols that includes both the public API from the library index
|
||||||
|
as well as shrowded internal symbols.
|
||||||
|
|
||||||
|
By default the `.ts` file supplied in the `files` field is assumed to be the library index.
|
||||||
|
If more than one `.ts` file is specified, `libraryIndex` is used to select the file to use.
|
||||||
|
If more than one `.ts` file is supplied without a `libraryIndex`, an error is produced.
|
||||||
|
|
||||||
|
A flat module index `.d.ts` and `.js` is created with the given `flatModuleOutFile` name in the same location as the library index `.d.ts` file.
|
||||||
|
|
||||||
|
For example, if a library uses the `public_api.ts` file as the library index of the module, the `tsconfig.json` `files` field would be `["public_api.ts"]`.
|
||||||
|
The `flatModuleOutFile` options could then be set to (for example) `"index.js"`, which produces `index.d.ts` and `index.metadata.json` files.
|
||||||
|
The `module` field of the library's `package.json` would be `"index.js"` and the `typings` field
|
||||||
|
would be `"index.d.ts"`.
|
||||||
|
|
||||||
|
### `fullTemplateTypeCheck`
|
||||||
|
|
||||||
|
When true (recommended), enables the [binding expression validation](guide/aot-compiler#binding-expression-validation) phase of the template compiler, which uses TypeScript to validate binding expressions.
|
||||||
|
|
||||||
|
Default is currently false.
|
||||||
|
|
||||||
|
### `generateCodeForLibraries`
|
||||||
|
|
||||||
|
When true (the default), generates factory files (`.ngfactory.js` and `.ngstyle.js`)
|
||||||
|
for `.d.ts` files with a corresponding `.metadata.json` file.
|
||||||
|
|
||||||
|
When false, factory files are generated only for `.ts` files. Do this when using factory summaries.
|
||||||
|
|
||||||
|
|
||||||
|
### `preserveWhitespaces`
|
||||||
|
|
||||||
|
When false (the default), removes blank text nodes from compiled templates, which results in smaller emitted template factory modules. Set to true to preserve blank text nodes.
|
||||||
|
|
||||||
|
### `skipMetadataEmit`
|
||||||
|
|
||||||
|
When true, does not to produce `.metadata.json` files. Default is `false`.
|
||||||
|
|
||||||
|
The `.metadata.json` files contain information needed by the template compiler from a `.ts`
|
||||||
|
file that is not included in the `.d.ts` file produced by the TypeScript compiler.
|
||||||
|
This information includes, for example, the content of annotations (such as a component's template), which TypeScript emits to the `.js` file but not to the `.d.ts` file.
|
||||||
|
|
||||||
|
You can set to `true` when using factory summaries, because the factory summaries
|
||||||
|
include a copy of the information that is in the `.metadata.json` file.
|
||||||
|
|
||||||
|
Set to `true` if you are using TypeScript's `--outFile` option, because the metadata files
|
||||||
|
are not valid for this style of TypeScript output. However, we do not recommend using `--outFile` with Angular. Use a bundler, such as [webpack](https://webpack.js.org/), instead.
|
||||||
|
|
||||||
|
### `skipTemplateCodegen`
|
||||||
|
|
||||||
|
When true, does not emit `.ngfactory.js` and `.ngstyle.js` files. This turns off most of the template compiler and disables the reporting of template diagnostics.
|
||||||
|
|
||||||
|
Can be used to instruct the template compiler to produce `.metadata.json` files for distribution with an `npm` package while avoiding the production of `.ngfactory.js` and `.ngstyle.js` files that cannot be distributed to `npm`.
|
||||||
|
|
||||||
|
### `strictMetadataEmit`
|
||||||
|
|
||||||
|
When true, reports an error to the `.metadata.json` file if `"skipMetadataEmit"` is `false`.
|
||||||
|
Default is false. Use only when `"skipMetadataEmit"` is false and `"skipTemplateCodeGen"` is true.
|
||||||
|
|
||||||
|
This option is intended to validate the `.metadata.json` files emitted for bundling with an `npm` package. The validation is strict and can emit errors for metadata that would never produce an error when used by the template compiler. You can choose to suppress the error emitted by this option for an exported symbol by including `@dynamic` in the comment documenting the symbol.
|
||||||
|
|
||||||
|
It is valid for `.metadata.json` files to contain errors.
|
||||||
|
The template compiler reports these errors if the metadata is used to determine the contents of an annotation.
|
||||||
|
The metadata collector cannot predict the symbols that are designed for use in an annotation, so it preemptively includes error nodes in the metadata for the exported symbols.
|
||||||
|
The template compiler can then use the error nodes to report an error if these symbols are used.
|
||||||
|
|
||||||
|
If the client of a library intends to use a symbol in an annotation, the template compiler does not normally report this until the client uses the symbol.
|
||||||
|
This option allows detection of these errors during the build phase of
|
||||||
|
the library and is used, for example, in producing Angular libraries themselves.
|
||||||
|
|
||||||
|
### `strictInjectionParameters`
|
||||||
|
|
||||||
|
When true (recommended), reports an error for a supplied parameter whose injection type cannot be determined. When false (currently the default), constructor parameters of classes marked with `@Injectable` whose type cannot be resolved produce a warning.
|
||||||
|
|
||||||
|
### `trace`
|
||||||
|
|
||||||
|
When true, prints extra information while compiling templates. Default is false.
|
@ -79,11 +79,9 @@ there are fewer opportunities for injection attacks.
|
|||||||
|
|
||||||
When you use the Angular AOT compiler, you can control your app compilation in two ways:
|
When you use the Angular AOT compiler, you can control your app compilation in two ways:
|
||||||
|
|
||||||
* By providing template compiler options in the `tsconfig.json` file.
|
* By [specifying Angular metadata](#metadata-aot), as described below.
|
||||||
|
|
||||||
For more information, see [Angular template compiler options](#compiler-options).
|
* By providing options in the `tsconfig.json` [TypeScript configuration file](guide/typescript-configuration). See [Angular compiler options](guide/angular-compiler-options).
|
||||||
|
|
||||||
* By [specifying Angular metadata](#metadata-aot).
|
|
||||||
|
|
||||||
|
|
||||||
{@a metadata-aot}
|
{@a metadata-aot}
|
||||||
@ -1165,7 +1163,7 @@ Chuck: After reviewing your PR comment I'm still at a loss. See [comment there](
|
|||||||
In the validation phase, the Angular template compiler uses the TypeScript compiler to validate the
|
In the validation phase, the Angular template compiler uses the TypeScript compiler to validate the
|
||||||
binding expressions in templates. Enable this phase explicitly by adding the compiler
|
binding expressions in templates. Enable this phase explicitly by adding the compiler
|
||||||
option `"fullTemplateTypeCheck"` in the `"angularCompilerOptions"` of the project's `tsconfig.json` (see
|
option `"fullTemplateTypeCheck"` in the `"angularCompilerOptions"` of the project's `tsconfig.json` (see
|
||||||
[Angular Compiler Options](#compiler-options)).
|
[Angular Compiler Options](guide/angular-compiler-options)).
|
||||||
|
|
||||||
Template validation produces error messages when a type error is detected in a template binding
|
Template validation produces error messages when a type error is detected in a template binding
|
||||||
expression, similar to how type errors are reported by the TypeScript compiler against code in a `.ts`
|
expression, similar to how type errors are reported by the TypeScript compiler against code in a `.ts`
|
||||||
@ -1329,198 +1327,3 @@ Similar to TypeScript Compiler, Angular Compiler also supports `extends` in the
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
More information about tsconfig extends can be found in the [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html).
|
More information about tsconfig extends can be found in the [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html).
|
||||||
|
|
||||||
{@a compiler-options}
|
|
||||||
## Angular template compiler options
|
|
||||||
|
|
||||||
The template compiler options are specified as members of the `"angularCompilerOptions"` object in the `tsconfig.json` file. Specify template compiler options along with the options supplied to the TypeScript compiler as shown here:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
...
|
|
||||||
},
|
|
||||||
"angularCompilerOptions": {
|
|
||||||
"fullTemplateTypeCheck": true,
|
|
||||||
"preserveWhitespaces": true,
|
|
||||||
...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The following section describes the Angular's template compiler options.
|
|
||||||
|
|
||||||
### *enableResourceInlining*
|
|
||||||
This option instructs the compiler to replace the `templateUrl` and `styleUrls` property in all `@Component` decorators with inlined contents in `template` and `styles` properties.
|
|
||||||
When enabled, the `.js` output of `ngc` will have no lazy-loaded `templateUrl` or `styleUrls`.
|
|
||||||
|
|
||||||
### *skipMetadataEmit*
|
|
||||||
|
|
||||||
This option tells the compiler not to produce `.metadata.json` files.
|
|
||||||
The option is `false` by default.
|
|
||||||
|
|
||||||
`.metadata.json` files contain information needed by the template compiler from a `.ts`
|
|
||||||
file that is not included in the `.d.ts` file produced by the TypeScript compiler. This information contains,
|
|
||||||
for example, the content of annotations (such as a component's template), which TypeScript
|
|
||||||
emits to the `.js` file but not to the `.d.ts` file.
|
|
||||||
|
|
||||||
This option should be set to `true` if you are using TypeScript's `--outFile` option, because the metadata files
|
|
||||||
are not valid for this style of TypeScript output. It is not recommended to use `--outFile` with
|
|
||||||
Angular. Use a bundler, such as [webpack](https://webpack.js.org/), instead.
|
|
||||||
|
|
||||||
This option can also be set to `true` when using factory summaries because the factory summaries
|
|
||||||
include a copy of the information that is in the `.metadata.json` file.
|
|
||||||
|
|
||||||
### *strictMetadataEmit*
|
|
||||||
|
|
||||||
This option tells the template compiler to report an error to the `.metadata.json`
|
|
||||||
file if `"skipMetadataEmit"` is `false`. This option is `false` by default. This should only be used when `"skipMetadataEmit"` is `false` and `"skipTemplateCodeGen"` is `true`.
|
|
||||||
|
|
||||||
This option is intended to validate the `.metadata.json` files emitted for bundling with an `npm` package. The validation is strict and can emit errors for metadata that would never produce an error when used by the template compiler. You can choose to suppress the error emitted by this option for an exported symbol by including `@dynamic` in the comment documenting the symbol.
|
|
||||||
|
|
||||||
It is valid for `.metadata.json` files to contain errors. The template compiler reports these errors
|
|
||||||
if the metadata is used to determine the contents of an annotation. The metadata
|
|
||||||
collector cannot predict the symbols that are designed for use in an annotation, so it will preemptively
|
|
||||||
include error nodes in the metadata for the exported symbols. The template compiler can then use the error
|
|
||||||
nodes to report an error if these symbols are used. If the client of a library intends to use a symbol in an annotation, the template compiler will not normally report
|
|
||||||
this until the client uses the symbol. This option allows detecting these errors during the build phase of
|
|
||||||
the library and is used, for example, in producing Angular libraries themselves.
|
|
||||||
|
|
||||||
### *skipTemplateCodegen*
|
|
||||||
|
|
||||||
This option tells the compiler to suppress emitting `.ngfactory.js` and `.ngstyle.js` files. When set,
|
|
||||||
this turns off most of the template compiler and disables reporting template diagnostics.
|
|
||||||
This option can be used to instruct the
|
|
||||||
template compiler to produce `.metadata.json` files for distribution with an `npm` package while
|
|
||||||
avoiding the production of `.ngfactory.js` and `.ngstyle.js` files that cannot be distributed to
|
|
||||||
`npm`.
|
|
||||||
|
|
||||||
### *strictInjectionParameters*
|
|
||||||
|
|
||||||
When set to `true`, this options tells the compiler to report an error for a parameter supplied
|
|
||||||
whose injection type cannot be determined. When this option is not provided or is `false`, constructor parameters of classes marked with `@Injectable` whose type cannot be resolved will
|
|
||||||
produce a warning.
|
|
||||||
|
|
||||||
*Note*: It is recommended to change this option explicitly to `true` as this option will default to `true` in the future.
|
|
||||||
|
|
||||||
### *flatModuleOutFile*
|
|
||||||
|
|
||||||
When set to `true`, this option tells the template compiler to generate a flat module
|
|
||||||
index of the given file name and the corresponding flat module metadata. Use this option when creating
|
|
||||||
flat modules that are packaged similarly to `@angular/core` and `@angular/common`. When this option
|
|
||||||
is used, the `package.json` for the library should refer
|
|
||||||
to the generated flat module index instead of the library index file. With this
|
|
||||||
option only one `.metadata.json` file is produced, which contains all the metadata necessary
|
|
||||||
for symbols exported from the library index. In the generated `.ngfactory.js` files, the flat
|
|
||||||
module index is used to import symbols that includes both the public API from the library index
|
|
||||||
as well as shrowded internal symbols.
|
|
||||||
|
|
||||||
By default the `.ts` file supplied in the `files` field is assumed to be the library index.
|
|
||||||
If more than one `.ts` file is specified, `libraryIndex` is used to select the file to use.
|
|
||||||
If more than one `.ts` file is supplied without a `libraryIndex`, an error is produced. A flat module
|
|
||||||
index `.d.ts` and `.js` will be created with the given `flatModuleOutFile` name in the same
|
|
||||||
location as the library index `.d.ts` file. For example, if a library uses the
|
|
||||||
`public_api.ts` file as the library index of the module, the `tsconfig.json` `files` field
|
|
||||||
would be `["public_api.ts"]`. The `flatModuleOutFile` options could then be set to, for
|
|
||||||
example `"index.js"`, which produces `index.d.ts` and `index.metadata.json` files. The
|
|
||||||
library's `package.json`'s `module` field would be `"index.js"` and the `typings` field
|
|
||||||
would be `"index.d.ts"`.
|
|
||||||
|
|
||||||
### *flatModuleId*
|
|
||||||
|
|
||||||
This option specifies the preferred module id to use for importing a flat module.
|
|
||||||
References generated by the template compiler will use this module name when importing symbols
|
|
||||||
from the flat module.
|
|
||||||
This is only meaningful when `flatModuleOutFile` is also supplied. Otherwise the compiler ignores
|
|
||||||
this option.
|
|
||||||
|
|
||||||
### *generateCodeForLibraries*
|
|
||||||
|
|
||||||
This option tells the template compiler to generate factory files (`.ngfactory.js` and `.ngstyle.js`)
|
|
||||||
for `.d.ts` files with a corresponding `.metadata.json` file. This option defaults to
|
|
||||||
`true`. When this option is `false`, factory files are generated only for `.ts` files.
|
|
||||||
|
|
||||||
This option should be set to `false` when using factory summaries.
|
|
||||||
|
|
||||||
### *fullTemplateTypeCheck*
|
|
||||||
|
|
||||||
This option tells the compiler to enable the [binding expression validation](#binding-expression-validation)
|
|
||||||
phase of the template compiler which uses TypeScript to validate binding expressions.
|
|
||||||
|
|
||||||
This option is `false` by default.
|
|
||||||
|
|
||||||
*Note*: It is recommended to set this to `true` because this option will default to `true` in the future.
|
|
||||||
|
|
||||||
### *annotateForClosureCompiler*
|
|
||||||
|
|
||||||
This option tells the compiler to use [Tsickle](https://github.com/angular/tsickle) to annotate the emitted
|
|
||||||
JavaScript with [JSDoc](http://usejsdoc.org/) comments needed by the
|
|
||||||
[Closure Compiler](https://github.com/google/closure-compiler). This option defaults to `false`.
|
|
||||||
|
|
||||||
### *annotationsAs*
|
|
||||||
|
|
||||||
Use this option to modify how the Angular specific annotations are emitted to improve tree-shaking. Non-Angular
|
|
||||||
annotations and decorators are unaffected. Default is `static fields`.
|
|
||||||
|
|
||||||
<style>
|
|
||||||
td, th {vertical-align: top}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th>Value</th>
|
|
||||||
<th>Description</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>decorators</code></td>
|
|
||||||
<td>Leave the decorators in place. This makes compilation faster. TypeScript will emit calls to the __decorate helper. Use <code>--emitDecoratorMetadata</code> for runtime reflection. However, the resulting code will not properly tree-shake.</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>static fields</code></td>
|
|
||||||
<td>Replace decorators with a static field in the class. Allows advanced tree-shakers like
|
|
||||||
<a href="https://github.com/google/closure-compiler">Closure compiler</a> to remove unused classes.</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
### *trace*
|
|
||||||
|
|
||||||
This tells the compiler to print extra information while compiling templates.
|
|
||||||
|
|
||||||
### *enableLegacyTemplate*
|
|
||||||
|
|
||||||
Use of the `<template>` element was deprecated starting in Angular 4.0 in favor of using
|
|
||||||
`<ng-template>` to avoid colliding with the DOM's element of the same name. Setting this option to
|
|
||||||
`true` enables the use of the deprecated `<template>` element. This option
|
|
||||||
is `false` by default. This option might be required by some third-party Angular libraries.
|
|
||||||
|
|
||||||
### *disableExpressionLowering*
|
|
||||||
|
|
||||||
The Angular template compiler transforms code that is used, or could be used, in an annotation
|
|
||||||
to allow it to be imported from template factory modules. See
|
|
||||||
[metadata rewriting](#metadata-rewriting) for more information.
|
|
||||||
|
|
||||||
Setting this option to `false` disables this rewriting, requiring the rewriting to be
|
|
||||||
done manually.
|
|
||||||
|
|
||||||
### *disableTypeScriptVersionCheck*
|
|
||||||
|
|
||||||
When `true`, this option tells the compiler not to check the TypeScript version.
|
|
||||||
The compiler will skip checking and will not error out when an unsupported version of TypeScript is used.
|
|
||||||
Setting this option to `true` is not recommended because unsupported versions of TypeScript might have undefined behavior.
|
|
||||||
|
|
||||||
This option is `false` by default.
|
|
||||||
|
|
||||||
### *preserveWhitespaces*
|
|
||||||
|
|
||||||
This option tells the compiler whether to remove blank text nodes from compiled templates.
|
|
||||||
As of v6, this option is `false` by default, which results in smaller emitted template factory modules.
|
|
||||||
|
|
||||||
### *allowEmptyCodegenFiles*
|
|
||||||
|
|
||||||
Tells the compiler to generate all the possible generated files even if they are empty. This option is
|
|
||||||
`false` by default. This is an option used by the Bazel build rules and is needed to simplify
|
|
||||||
how Bazel rules track file dependencies. It is not recommended to use this option outside of the Bazel
|
|
||||||
rules.
|
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ Angular supports most recent browsers. This includes the following specific vers
|
|||||||
IE
|
IE
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
11, 10, 9
|
11, 10, 9 ("compatibility view" mode not supported)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -183,7 +183,7 @@ These are the polyfills required to run an Angular application on each supported
|
|||||||
|
|
||||||
<td>
|
<td>
|
||||||
Chrome, Firefox, Edge, <br>
|
Chrome, Firefox, Edge, <br>
|
||||||
Safari, Android, IE10+
|
Safari, Android, IE 10+
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
@ -197,7 +197,7 @@ These are the polyfills required to run an Angular application on each supported
|
|||||||
<tr style="vertical-align: top">
|
<tr style="vertical-align: top">
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
IE9
|
IE 9
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
@ -275,7 +275,7 @@ Some features of Angular may require additional polyfills.
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
All but Chrome, Firefox, Edge, IE11 and Safari 10
|
All but Chrome, Firefox, Edge, IE 11 and Safari 10
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
@ -294,7 +294,7 @@ Some features of Angular may require additional polyfills.
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
IE10, IE11
|
IE 10, IE 11
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -178,7 +178,7 @@ For more information, see [/deep/, >>>, and ::ng-deep](guide/component-styles#de
|
|||||||
{@a template-tag}
|
{@a template-tag}
|
||||||
### <template> tag
|
### <template> tag
|
||||||
|
|
||||||
The `<template>` tag was deprecated in v4 to avoid colliding with the DOM's element of the same name (such as when using web components). Use `<ng-template>` instead. For more information, see the [Ahead-of-Time Compilation](guide/aot-compiler#enablelegacytemplate) guide.
|
The `<template>` tag was deprecated in v4 to avoid colliding with the DOM's element of the same name (such as when using web components). Use `<ng-template>` instead. For more information, see the [Ahead-of-Time Compilation](guide/angular-compiler-options#enablelegacytemplate) guide.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,15 +2,17 @@
|
|||||||
|
|
||||||
Angular makes use of observables as an interface to handle a variety of common asynchronous operations. For example:
|
Angular makes use of observables as an interface to handle a variety of common asynchronous operations. For example:
|
||||||
|
|
||||||
* The `EventEmitter` class extends `Observable`.
|
* You can define [custom events](guide/template-syntax#custom-events-with-eventemitter) that send observable output data from a child to a parent component.
|
||||||
* The HTTP module uses observables to handle AJAX requests and responses.
|
* The HTTP module uses observables to handle AJAX requests and responses.
|
||||||
* The Router and Forms modules use observables to listen for and respond to user-input events.
|
* The Router and Forms modules use observables to listen for and respond to user-input events.
|
||||||
|
|
||||||
## Event emitter
|
## Transmitting data between components
|
||||||
|
|
||||||
Angular provides an `EventEmitter` class that is used when publishing values from a component through the `@Output()` decorator. `EventEmitter` extends `Observable`, adding an `emit()` method so it can send arbitrary values. When you call `emit()`, it passes the emitted value to the `next()` method of any subscribed observer.
|
Angular provides an `EventEmitter` class that is used when publishing values from a component through the [`@Output()` decorator](guide/template-syntax#how-to-use-output).
|
||||||
|
`EventEmitter` extends [RxJS `Subject`](https://rxjs.dev/api/index/class/Subject), adding an `emit()` method so it can send arbitrary values.
|
||||||
|
When you call `emit()`, it passes the emitted value to the `next()` method of any subscribed observer.
|
||||||
|
|
||||||
A good example of usage can be found on the [EventEmitter](https://angular.io/api/core/EventEmitter) documentation. Here is the example component that listens for open and close events:
|
A good example of usage can be found in the [EventEmitter](https://angular.io/api/core/EventEmitter) documentation. Here is the example component that listens for open and close events:
|
||||||
|
|
||||||
`<zippy (open)="onOpen($event)" (close)="onClose($event)"></zippy>`
|
`<zippy (open)="onOpen($event)" (close)="onClose($event)"></zippy>`
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# The RxJS library
|
# The RxJS library
|
||||||
|
|
||||||
Reactive programming is an asynchronous programming paradigm concerned with data streams and the propagation of change ([Wikipedia](https://en.wikipedia.org/wiki/Reactive_programming)). RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using observables that makes it easier to compose asynchronous or callback-based code ([RxJS Docs](http://reactivex.io/rxjs/)).
|
Reactive programming is an asynchronous programming paradigm concerned with data streams and the propagation of change ([Wikipedia](https://en.wikipedia.org/wiki/Reactive_programming)). RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using observables that makes it easier to compose asynchronous or callback-based code. See ([RxJS Docs](https://rxjs.dev/guide/overview)).
|
||||||
|
|
||||||
RxJS provides an implementation of the `Observable` type, which is needed until the type becomes part of the language and until browsers support it. The library also provides utility functions for creating and working with observables. These utility functions can be used for:
|
RxJS provides an implementation of the `Observable` type, which is needed until the type becomes part of the language and until browsers support it. The library also provides utility functions for creating and working with observables. These utility functions can be used for:
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ The `pipe()` function is also a method on the RxJS `Observable`, so you use this
|
|||||||
|
|
||||||
### Common operators
|
### Common operators
|
||||||
|
|
||||||
RxJS provides many operators, but only a handful are used frequently. For a list of operators and usage samples, visit the [RxJS API Documentation](https://rxjs-dev.firebaseapp.com/api).
|
RxJS provides many operators, but only a handful are used frequently. For a list of operators and usage samples, visit the [RxJS API Documentation](https://rxjs.dev/api).
|
||||||
|
|
||||||
<div class="alert is-helpful">
|
<div class="alert is-helpful">
|
||||||
Note that, for Angular apps, we prefer combining operators with pipes, rather than chaining. Chaining is used in many RxJS examples.
|
Note that, for Angular apps, we prefer combining operators with pipes, rather than chaining. Chaining is used in many RxJS examples.
|
||||||
|
@ -927,7 +927,7 @@ As always, strive for consistency.
|
|||||||
<div class="s-rule do">
|
<div class="s-rule do">
|
||||||
|
|
||||||
**Do** use a custom prefix for a component selector.
|
**Do** use a custom prefix for a component selector.
|
||||||
For example, the prefix `toh` represents from **T**our **o**f **H**eroes and the prefix `admin` represents an admin feature area.
|
For example, the prefix `toh` represents **T**our **o**f **H**eroes and the prefix `admin` represents an admin feature area.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1670,7 +1670,7 @@ keep the **F**lattest structure you can, and
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Why?** LIFT Provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly.
|
**Why?** LIFT provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly.
|
||||||
To confirm your intuition about a particular structure, ask:
|
To confirm your intuition about a particular structure, ask:
|
||||||
_can I quickly open and start work in all of the related files for this feature_?
|
_can I quickly open and start work in all of the related files for this feature_?
|
||||||
|
|
||||||
@ -1690,7 +1690,7 @@ _can I quickly open and start work in all of the related files for this feature_
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Do** make locating code intuitive, simple and fast.
|
**Do** make locating code intuitive, simple, and fast.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,14 +6,14 @@ This guide offers tips and techniques for unit and integration testing Angular a
|
|||||||
The guide presents tests of a sample application created with the [Angular CLI](cli). This sample application is much like the one created in the [_Tour of Heroes_ tutorial](tutorial).
|
The guide presents tests of a sample application created with the [Angular CLI](cli). This sample application is much like the one created in the [_Tour of Heroes_ tutorial](tutorial).
|
||||||
The sample application and all tests in this guide are available for inspection and experimentation:
|
The sample application and all tests in this guide are available for inspection and experimentation:
|
||||||
|
|
||||||
- <live-example embedded-style>Sample app</live-example>
|
- <live-example embedded-style noDownload>Sample app</live-example>
|
||||||
- <live-example stackblitz="specs">Tests</live-example>
|
- <live-example stackblitz="specs" noDownload>Tests</live-example>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
The Angular CLI downloads and install everything you need to test an Angular application with the [Jasmine test framework](https://jasmine.github.io/).
|
The Angular CLI downloads and installs everything you need to test an Angular application with the [Jasmine test framework](https://jasmine.github.io/).
|
||||||
|
|
||||||
The project you create with the CLI is immediately ready to test.
|
The project you create with the CLI is immediately ready to test.
|
||||||
Just run the [`ng test`](cli/test) CLI command:
|
Just run the [`ng test`](cli/test) CLI command:
|
||||||
@ -116,7 +116,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
working_directory: ~/my-project
|
working_directory: ~/my-project
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:8-browsers
|
- image: circleci/node:10-browsers
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
|
@ -348,7 +348,7 @@ For example:
|
|||||||
|
|
||||||
<code-example language="json">
|
<code-example language="json">
|
||||||
|
|
||||||
"sourceMaps": { "scripts": true, "styles": false, "hidden": true, "vendor": true }
|
"sourceMap": { "scripts": true, "styles": false, "hidden": true, "vendor": true }
|
||||||
|
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
@ -597,6 +597,11 @@
|
|||||||
"title": "Ahead-of-Time Compilation",
|
"title": "Ahead-of-Time Compilation",
|
||||||
"tooltip": "Learn why and how to use the Ahead-of-Time (AOT) compiler."
|
"tooltip": "Learn why and how to use the Ahead-of-Time (AOT) compiler."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/angular-compiler-options",
|
||||||
|
"title": "Compiler Options",
|
||||||
|
"tooltip": "Configuration options for the AOT compiler."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/build",
|
"url": "guide/build",
|
||||||
"title": "Building & Serving",
|
"title": "Building & Serving",
|
||||||
|
@ -91,8 +91,6 @@ configures it with the `routes` in one step by calling
|
|||||||
|
|
||||||
Next, `AppRoutingModule` exports `RouterModule` so it will be available throughout the app.
|
Next, `AppRoutingModule` exports `RouterModule` so it will be available throughout the app.
|
||||||
|
|
||||||
Open the `AppComponent` template and replace the `<app-heroes>` element with a `<router-outlet>` element.
|
|
||||||
|
|
||||||
<code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts (exports array)" region="export-routermodule">
|
<code-example path="toh-pt5/src/app/app-routing.module.ts" header="src/app/app-routing.module.ts (exports array)" region="export-routermodule">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "node ../tools/yarn/check-yarn.js",
|
"preinstall": "node ../tools/yarn/check-yarn.js",
|
||||||
"postinstall": "node tools/cli-patches/patch.js",
|
"postinstall": "node tools/cli-patches/patch.js",
|
||||||
"aio-use-local": "node tools/ng-packages-installer overwrite . --debug",
|
"aio-use-local": "node tools/ng-packages-installer overwrite . --debug --force --build-packages",
|
||||||
"aio-use-npm": "node tools/ng-packages-installer restore .",
|
"aio-use-npm": "node tools/ng-packages-installer restore .",
|
||||||
"aio-check-local": "node tools/ng-packages-installer check .",
|
"aio-check-local": "node tools/ng-packages-installer check .",
|
||||||
"ng": "yarn check-env && ng",
|
"ng": "yarn check-env && ng",
|
||||||
@ -17,18 +17,22 @@
|
|||||||
"build": "yarn ~~build",
|
"build": "yarn ~~build",
|
||||||
"prebuild-local": "yarn setup-local",
|
"prebuild-local": "yarn setup-local",
|
||||||
"build-local": "yarn ~~build",
|
"build-local": "yarn ~~build",
|
||||||
|
"prebuild-local-ci": "yarn setup-local --no-build-packages",
|
||||||
|
"build-local-ci": "yarn ~~build --progress=false",
|
||||||
"prebuild-with-ivy": "yarn setup-local && node scripts/switch-to-ivy",
|
"prebuild-with-ivy": "yarn setup-local && node scripts/switch-to-ivy",
|
||||||
"build-with-ivy": "yarn ~~build",
|
"build-with-ivy": "yarn ~~build",
|
||||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js 403bcb01c",
|
"prebuild-with-ivy-ci": "yarn setup-local --no-build-packages && node scripts/switch-to-ivy",
|
||||||
|
"build-with-ivy-ci": "yarn ~~build --progress=false",
|
||||||
|
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js bb4be27da",
|
||||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
|
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
|
||||||
"test": "yarn check-env && ng test",
|
"test": "yarn check-env && ng test",
|
||||||
"pree2e": "yarn check-env && yarn update-webdriver",
|
"pree2e": "yarn check-env && yarn update-webdriver",
|
||||||
"e2e": "ng e2e --no-webdriver-update",
|
"e2e": "ng e2e --no-webdriver-update",
|
||||||
"presetup": "yarn --cwd .. install && yarn install --frozen-lockfile && yarn ~~check-env && yarn ~~clean-generated && yarn boilerplate:remove",
|
"presetup": "yarn --cwd .. install && yarn install --frozen-lockfile && yarn ~~check-env && yarn ~~clean-generated && yarn boilerplate:remove",
|
||||||
"setup": "yarn aio-use-npm && yarn example-use-npm",
|
"setup": "yarn example-use-npm && yarn aio-use-npm",
|
||||||
"postsetup": "yarn boilerplate:add && yarn extract-cli-command-docs && yarn docs",
|
"postsetup": "yarn boilerplate:add && yarn extract-cli-command-docs && yarn docs",
|
||||||
"presetup-local": "yarn presetup",
|
"presetup-local": "yarn presetup",
|
||||||
"setup-local": "yarn aio-use-local && yarn example-use-local",
|
"setup-local": "yarn example-use-local && yarn aio-use-local",
|
||||||
"postsetup-local": "yarn postsetup",
|
"postsetup-local": "yarn postsetup",
|
||||||
"set-opensearch-url": "node --eval \"const sh = require('shelljs'); sh.set('-e'); sh.sed('-i', /PLACEHOLDER_URL/g, process.argv[1], 'dist/assets/opensearch.xml');\"",
|
"set-opensearch-url": "node --eval \"const sh = require('shelljs'); sh.set('-e'); sh.sed('-i', /PLACEHOLDER_URL/g, process.argv[1], 'dist/assets/opensearch.xml');\"",
|
||||||
"presmoke-tests": "yarn update-webdriver",
|
"presmoke-tests": "yarn update-webdriver",
|
||||||
@ -39,7 +43,7 @@
|
|||||||
"test-pwa-score-localhost": "run-p --race \"~~light-server -s dist -p 4200 --quiet\" \"test-pwa-score http://localhost:4200 {1} {2}\" --",
|
"test-pwa-score-localhost": "run-p --race \"~~light-server -s dist -p 4200 --quiet\" \"test-pwa-score http://localhost:4200 {1} {2}\" --",
|
||||||
"example-e2e": "yarn example-check-local && node ./tools/examples/run-example-e2e",
|
"example-e2e": "yarn example-check-local && node ./tools/examples/run-example-e2e",
|
||||||
"example-lint": "tslint --config \"content/examples/tslint.json\" \"content/examples/**/*.ts\" --exclude \"content/examples/styleguide/**/*.avoid.ts\"",
|
"example-lint": "tslint --config \"content/examples/tslint.json\" \"content/examples/**/*.ts\" --exclude \"content/examples/styleguide/**/*.avoid.ts\"",
|
||||||
"example-use-local": "node tools/ng-packages-installer overwrite ./tools/examples/shared --debug",
|
"example-use-local": "node tools/ng-packages-installer overwrite ./tools/examples/shared --debug --force",
|
||||||
"example-use-npm": "node tools/ng-packages-installer restore ./tools/examples/shared",
|
"example-use-npm": "node tools/ng-packages-installer restore ./tools/examples/shared",
|
||||||
"example-check-local": "node tools/ng-packages-installer check ./tools/examples/shared",
|
"example-check-local": "node tools/ng-packages-installer check ./tools/examples/shared",
|
||||||
"deploy-production": "scripts/deploy-to-firebase.sh",
|
"deploy-production": "scripts/deploy-to-firebase.sh",
|
||||||
|
@ -9,5 +9,4 @@
|
|||||||
@import 'marketing-layout';
|
@import 'marketing-layout';
|
||||||
@import 'not-found';
|
@import 'not-found';
|
||||||
@import 'sidenav';
|
@import 'sidenav';
|
||||||
@import 'table-of-contents';
|
|
||||||
@import 'top-menu';
|
@import 'top-menu';
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
nav#main-table-of-contents {
|
|
||||||
width: 200px;
|
|
||||||
height: 900px;
|
|
||||||
position: fixed;
|
|
||||||
right: 0;
|
|
||||||
top: 50px;
|
|
||||||
bottom: 100px;
|
|
||||||
margin-left: 32px;
|
|
||||||
background-color: $blue;
|
|
||||||
}
|
|
@ -2,112 +2,96 @@
|
|||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
|
|
||||||
table {
|
table {
|
||||||
margin: 12px 0 24px;
|
margin: 12px 0 24px;
|
||||||
|
|
||||||
th {
|
th {
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
@include font-size(16);
|
@include font-size(16);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
border-bottom: 1px solid $lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is overriding a style here:
|
||||||
|
// https://github.com/angular/angular/blob/95993e1/aio/src/styles/2-modules/_table.scss#L58-L62
|
||||||
|
tbody > tr > td tr td:first-child {
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
background-color: inherit;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.item-table {
|
||||||
|
td {
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.list-table {
|
||||||
|
td {
|
||||||
|
padding: 16px 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.short-description {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.parameters-table {
|
||||||
|
margin-top: 0;
|
||||||
|
@include font-size(14);
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
tr {
|
tr {
|
||||||
border-bottom: 1px solid $lightgray;
|
@media screen and (max-width: 480px) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:first-child {
|
||||||
|
font-weight: 600;
|
||||||
|
padding-left: 16px;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
vertical-align: middle;
|
padding: 8px 8px 8px 0;
|
||||||
|
border: 0;
|
||||||
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This is overriding a style here:
|
&.property-table {
|
||||||
// https://github.com/angular/angular/blob/95993e1/aio/src/styles/2-modules/_table.scss#L58-L62
|
td {
|
||||||
tbody > tr > td tr td:first-child {
|
vertical-align: top;
|
||||||
@media screen and (max-width: 480px) {
|
|
||||||
background-color: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
margin: 16px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.item-table {
|
|
||||||
td {
|
|
||||||
padding: 32px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.list-table {
|
|
||||||
td {
|
|
||||||
padding: 16px 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.short-description {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.parameters-table {
|
|
||||||
margin-top: 0;
|
|
||||||
@include font-size(14);
|
|
||||||
box-shadow: none;
|
|
||||||
|
|
||||||
tr {
|
|
||||||
@media screen and (max-width: 480px) {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
td:first-child {
|
|
||||||
font-weight: 600;
|
|
||||||
padding-left: 16px;
|
|
||||||
width: 20%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 8px 8px 8px 0;
|
|
||||||
border: 0;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.property-table {
|
|
||||||
td {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.class-overview {
|
.class-overview {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
code-example {
|
code-example {
|
||||||
clear: left;
|
clear: left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.short-description {
|
.short-description {
|
||||||
margin: 6px 0 0 10px;
|
margin-top: 8px;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumb-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.api-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
@media screen and (max-width: 600px) {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@ -117,10 +101,12 @@
|
|||||||
|
|
||||||
.github-links {
|
.github-links {
|
||||||
float: right;
|
float: right;
|
||||||
|
|
||||||
.material-icons {
|
.material-icons {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
@include font-size(20);
|
@include font-size(20);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $mist;
|
background-color: $mist;
|
||||||
}
|
}
|
||||||
@ -128,19 +114,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.api-body {
|
.api-body {
|
||||||
|
|
||||||
.class-overview {
|
.class-overview {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
code-example {
|
code-example {
|
||||||
clear: left;
|
clear: left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.method-table, .option-table, .list-table {
|
.method-table,
|
||||||
|
.option-table,
|
||||||
|
.list-table {
|
||||||
td > code {
|
td > code {
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
white-space: pre;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.with-github-links {
|
.with-github-links {
|
||||||
@ -161,7 +148,7 @@
|
|||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin: 6px 0;
|
margin: 6px 0;
|
||||||
font-weight: bold;
|
font-weight: 500;
|
||||||
clear: left;
|
clear: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,10 +159,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.api-heading {
|
.api-heading {
|
||||||
padding: 5px 0;
|
|
||||||
@include font-size(14);
|
@include font-size(14);
|
||||||
|
margin: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.parameters-table {
|
.parameters-table {
|
||||||
@ -244,7 +230,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.from-constructor, .read-only-property, .write-only-property {
|
.from-constructor,
|
||||||
|
.read-only-property,
|
||||||
|
.write-only-property {
|
||||||
@include font-size(12);
|
@include font-size(12);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@include letter-spacing(0.5);
|
@include letter-spacing(0.5);
|
||||||
@ -259,7 +247,8 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selector-list, .inherited-members-list {
|
.selector-list,
|
||||||
|
.inherited-members-list {
|
||||||
ul {
|
ul {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
li {
|
li {
|
||||||
@ -270,7 +259,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.selector-list {
|
.selector-list {
|
||||||
li, a {
|
li,
|
||||||
|
a {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
i {
|
i {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@ -279,6 +269,39 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.breadcrumb-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: -4px;
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.github-links {
|
||||||
|
float: right;
|
||||||
|
.material-icons {
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 20px;
|
||||||
|
&:hover {
|
||||||
|
background-color: $mist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.deprecated-api-item {
|
.deprecated-api-item {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
code-example, code-tabs {
|
code-example,
|
||||||
|
code-tabs {
|
||||||
clear: both;
|
clear: both;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
|
ol {
|
||||||
|
list-style: decimal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
code-example {
|
code-example {
|
||||||
|
@ -30,159 +30,160 @@
|
|||||||
&:hover {
|
&:hover {
|
||||||
color: $accentblue;
|
color: $accentblue;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button.toc-heading,
|
button {
|
||||||
button.toc-more-items {
|
&.toc-heading,
|
||||||
cursor: pointer;
|
&.toc-more-items {
|
||||||
display: inline-block;
|
cursor: pointer;
|
||||||
background: 0;
|
display: inline-block;
|
||||||
background-color: transparent;
|
background: 0;
|
||||||
border: none;
|
background-color: transparent;
|
||||||
box-shadow: none;
|
border: none;
|
||||||
padding: 0;
|
box-shadow: none;
|
||||||
text-align: start;
|
padding: 0;
|
||||||
|
text-align: start;
|
||||||
|
|
||||||
&.embedded:focus {
|
&.embedded:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
background: $lightgray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button.toc-heading {
|
|
||||||
mat-icon.rotating-icon {
|
|
||||||
height: 18px;
|
|
||||||
width: 18px;
|
|
||||||
position: relative;
|
|
||||||
left: -4px;
|
|
||||||
top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover:not(.embedded) {
|
|
||||||
color: $accentblue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button.toc-more-items {
|
|
||||||
color: $mediumgray;
|
|
||||||
top: 10px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $accentblue;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: 'expand_less';
|
|
||||||
}
|
|
||||||
|
|
||||||
&.collapsed::after {
|
|
||||||
content: 'more_horiz';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-icon {
|
|
||||||
&.collapsed {
|
|
||||||
@include rotate(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.collapsed) {
|
|
||||||
@include rotate(90deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.toc-list {
|
|
||||||
list-style-type: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 8px 0 0;
|
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
box-sizing: border-box;
|
|
||||||
@include font-size(12);
|
|
||||||
@include line-height(16);
|
|
||||||
padding: 3px 0 3px 12px;
|
|
||||||
position: relative;
|
|
||||||
transition: all 0.3s ease-in-out;
|
|
||||||
|
|
||||||
&.h1:after {
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
height: 1px;
|
|
||||||
width: 40%;
|
|
||||||
margin: 7px 0 4px 0;
|
|
||||||
background: $lightgray;
|
background: $lightgray;
|
||||||
clear: both;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.toc-heading {
|
||||||
|
mat-icon.rotating-icon {
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
position: relative;
|
||||||
|
left: -4px;
|
||||||
|
top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.h3 {
|
&:hover:not(.embedded) {
|
||||||
padding-left: 24px;
|
color: $accentblue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
&.toc-more-items {
|
||||||
color: lighten($darkgray, 10);
|
color: $mediumgray;
|
||||||
overflow: visible;
|
top: 10px;
|
||||||
@include font-size(12);
|
position: relative;
|
||||||
display: table-cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
* {
|
color: $accentblue;
|
||||||
color: $accentblue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&::after {
|
||||||
* {
|
content: 'expand_less';
|
||||||
color: $blue;
|
}
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
&:before {
|
&.collapsed::after {
|
||||||
content: '';
|
content: 'more_horiz';
|
||||||
border-radius: 50%;
|
}
|
||||||
left: -3px;
|
}
|
||||||
top: 12px;
|
|
||||||
background: $blue;
|
.mat-icon {
|
||||||
position: absolute;
|
&.collapsed {
|
||||||
width: 6px;
|
@include rotate(0deg);
|
||||||
height: 6px;
|
}
|
||||||
|
|
||||||
|
&:not(.collapsed) {
|
||||||
|
@include rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.toc-list {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 8px 0 0;
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
box-sizing: border-box;
|
||||||
|
@include font-size(12);
|
||||||
|
@include line-height(16);
|
||||||
|
padding: 3px 0 3px 12px;
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
|
||||||
|
&.h1:after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
height: 1px;
|
||||||
|
width: 40%;
|
||||||
|
margin: 7px 0 4px 0;
|
||||||
|
background: $lightgray;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.h3 {
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: lighten($darkgray, 10);
|
||||||
|
overflow: visible;
|
||||||
|
@include font-size(12);
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
* {
|
||||||
|
color: $accentblue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
* {
|
||||||
|
color: $blue;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
border-radius: 50%;
|
||||||
|
left: -3px;
|
||||||
|
top: 12px;
|
||||||
|
background: $blue;
|
||||||
|
position: absolute;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.embedded) li {
|
&:not(.embedded) li {
|
||||||
&:before {
|
&:before {
|
||||||
border-left: 1px solid $lightgray;
|
border-left: 1px solid $lightgray;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
content: '';
|
content: '';
|
||||||
left: 0;
|
left: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:first-child:before {
|
&:first-child:before {
|
||||||
top: 13px;
|
top: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child:before {
|
&:last-child:before {
|
||||||
bottom: calc(100% - 14px);
|
bottom: calc(100% - 14px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.active):hover a:before {
|
&:not(.active):hover a:before {
|
||||||
content: '';
|
content: '';
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
left: -3px;
|
left: -3px;
|
||||||
top: 12px;
|
top: 12px;
|
||||||
background: $lightgray;
|
background: $lightgray;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 6px;
|
width: 6px;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,25 +84,23 @@ class ExampleZipper {
|
|||||||
const outputFileName = path.join(outputDirName, relativeDirName, exampleZipName + '.zip');
|
const outputFileName = path.join(outputDirName, relativeDirName, exampleZipName + '.zip');
|
||||||
let defaultIncludes = ['**/*.ts', '**/*.js', '**/*.es6', '**/*.css', '**/*.html', '**/*.md', '**/*.json', '**/*.png', '**/*.svg'];
|
let defaultIncludes = ['**/*.ts', '**/*.js', '**/*.es6', '**/*.css', '**/*.html', '**/*.md', '**/*.json', '**/*.png', '**/*.svg'];
|
||||||
let alwaysIncludes = [
|
let alwaysIncludes = [
|
||||||
'bs-config.json',
|
|
||||||
'e2e/protractor.conf.js',
|
|
||||||
'angular.json',
|
|
||||||
'.editorconfig',
|
'.editorconfig',
|
||||||
'.gitignore',
|
'.gitignore',
|
||||||
'tslint.json',
|
'angular.json',
|
||||||
|
'browserslist',
|
||||||
|
'bs-config.json',
|
||||||
|
'karma.conf.js',
|
||||||
'karma-test-shim.js',
|
'karma-test-shim.js',
|
||||||
'tsconfig.json',
|
'tsconfig.*',
|
||||||
'src/testing/**/*',
|
'tslint.*',
|
||||||
'src/.babelrc',
|
'e2e/protractor.conf.js',
|
||||||
'src/browserslist',
|
'e2e/tsconfig.json',
|
||||||
'src/favicon.ico',
|
'src/favicon.ico',
|
||||||
'src/karma.conf.js',
|
|
||||||
'src/polyfills.ts',
|
'src/polyfills.ts',
|
||||||
'src/test.ts',
|
'src/test.ts',
|
||||||
'src/typings.d.ts',
|
'src/typings.d.ts',
|
||||||
'src/environments/**/*',
|
'src/environments/**/*',
|
||||||
'src/tsconfig.*',
|
'src/testing/**/*',
|
||||||
'src/tslint.*',
|
|
||||||
// Only ignore root package.json
|
// Only ignore root package.json
|
||||||
'!package.json'
|
'!package.json'
|
||||||
];
|
];
|
||||||
|
@ -46,6 +46,8 @@ if (argv.ivy) {
|
|||||||
* Must be used in conjunction with --setup as this is when the packages are copied.
|
* Must be used in conjunction with --setup as this is when the packages are copied.
|
||||||
* e.g. --setup --local
|
* e.g. --setup --local
|
||||||
*
|
*
|
||||||
|
* --ivy to turn on `ivy` mode
|
||||||
|
*
|
||||||
* --shard to shard the specs into groups to allow you to run them in parallel
|
* --shard to shard the specs into groups to allow you to run them in parallel
|
||||||
* e.g. --shard=0/2 // the even specs: 0, 2, 4, etc
|
* e.g. --shard=0/2 // the even specs: 0, 2, 4, etc
|
||||||
* e.g. --shard=1/2 // the odd specs: 1, 3, 5, etc
|
* e.g. --shard=1/2 // the odd specs: 1, 3, 5, etc
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "../out-tsc/app",
|
"outDir": "../out-tsc/app",
|
||||||
"types": []
|
"types": []
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "../out-tsc/spec",
|
"outDir": "../out-tsc/spec",
|
||||||
"types": [
|
"types": [
|
||||||
@ -8,8 +8,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"test.ts",
|
"src/test.ts",
|
||||||
"polyfills.ts"
|
"src/polyfills.ts"
|
||||||
],
|
],
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.spec.ts",
|
"src/**/*.spec.ts",
|
||||||
|
@ -14,7 +14,8 @@ const LOCAL_MARKER_PATH = 'node_modules/_local_.json';
|
|||||||
const PACKAGE_JSON_REGEX = /^[^/]+\/package\.json$/;
|
const PACKAGE_JSON_REGEX = /^[^/]+\/package\.json$/;
|
||||||
|
|
||||||
const ANGULAR_ROOT_DIR = path.resolve(__dirname, '../../..');
|
const ANGULAR_ROOT_DIR = path.resolve(__dirname, '../../..');
|
||||||
const ANGULAR_DIST_PACKAGES = path.resolve(ANGULAR_ROOT_DIR, 'dist/packages-dist');
|
const ANGULAR_DIST_PACKAGES = path.join(ANGULAR_ROOT_DIR, 'dist/packages-dist');
|
||||||
|
const ANGULAR_DIST_PACKAGES_BUILD_CMD = path.join(ANGULAR_ROOT_DIR, 'scripts/build-packages-dist.sh');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A tool that can install Angular dependencies for a project from NPM or from the
|
* A tool that can install Angular dependencies for a project from NPM or from the
|
||||||
@ -29,16 +30,17 @@ class NgPackagesInstaller {
|
|||||||
* Create a new installer for a project in the specified directory.
|
* Create a new installer for a project in the specified directory.
|
||||||
*
|
*
|
||||||
* @param {string} projectDir - the path to the directory containing the project.
|
* @param {string} projectDir - the path to the directory containing the project.
|
||||||
* @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
|
* * `force` (`boolean`) - whether to force a local installation even if there is a local marker file.
|
||||||
* even if there is a local marker file.
|
* * `buildPackages` (`boolean`) - whether to build the local Angular packages before using them.
|
||||||
* * `ignorePackages` (`string[]`) - a collection of names of packages
|
* (NOTE: Building the packages is currently not supported on Windows, so a message is printed instead.)
|
||||||
* that should not be copied over.
|
* * `ignorePackages` (`string[]`) - a collection of names of packages that should not be copied over.
|
||||||
*/
|
*/
|
||||||
constructor(projectDir, options = {}) {
|
constructor(projectDir, options = {}) {
|
||||||
this.debug = options.debug;
|
this.debug = this._parseBooleanArg(options.debug);
|
||||||
this.force = options.force;
|
this.force = this._parseBooleanArg(options.force);
|
||||||
|
this.buildPackages = this._parseBooleanArg(options.buildPackages);
|
||||||
this.ignorePackages = options.ignorePackages || [];
|
this.ignorePackages = options.ignorePackages || [];
|
||||||
this.projectDir = path.resolve(projectDir);
|
this.projectDir = path.resolve(projectDir);
|
||||||
this.localMarkerPath = path.resolve(this.projectDir, LOCAL_MARKER_PATH);
|
this.localMarkerPath = path.resolve(this.projectDir, LOCAL_MARKER_PATH);
|
||||||
@ -160,6 +162,31 @@ class NgPackagesInstaller {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the local Angular packages.
|
||||||
|
*
|
||||||
|
* NOTE:
|
||||||
|
* Building the packages is currently not supported on Windows, so a message is printed instead, prompting the user to
|
||||||
|
* do it themselves (e.g. using Windows Subsystem for Linux or a docker container).
|
||||||
|
*/
|
||||||
|
_buildDistPackages() {
|
||||||
|
const canBuild = process.platform !== 'win32';
|
||||||
|
|
||||||
|
if (canBuild) {
|
||||||
|
this._log(`Building the Angular packages with: ${ANGULAR_DIST_PACKAGES_BUILD_CMD}`);
|
||||||
|
shelljs.exec(ANGULAR_DIST_PACKAGES_BUILD_CMD);
|
||||||
|
} else {
|
||||||
|
this._warn([
|
||||||
|
'Automatically building the local Angular packages is currently not supported on Windows.',
|
||||||
|
`Please, ensure '${ANGULAR_DIST_PACKAGES}' exists and is up-to-date (e.g. by running ` +
|
||||||
|
`'${ANGULAR_DIST_PACKAGES_BUILD_CMD}' in Git Bash for Windows, Windows Subsystem for Linux or a Linux ` +
|
||||||
|
'docker container or VM).',
|
||||||
|
'',
|
||||||
|
'Proceeding anyway...',
|
||||||
|
].join('\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_collectDependencies(dependencies, packages) {
|
_collectDependencies(dependencies, packages) {
|
||||||
const peerDependencies = Object.create(null);
|
const peerDependencies = Object.create(null);
|
||||||
const mergedDependencies = Object.assign(Object.create(null), dependencies);
|
const mergedDependencies = Object.assign(Object.create(null), dependencies);
|
||||||
@ -184,32 +211,35 @@ class NgPackagesInstaller {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A hash of Angular package configs.
|
* A hash of Angular package configs.
|
||||||
* (Detected as directories in '/packages/' that contain a top-level 'package.json' file.)
|
* (Detected as directories in '/dist/packages-dist/' that contain a top-level 'package.json' file.)
|
||||||
*/
|
*/
|
||||||
_getDistPackages() {
|
_getDistPackages() {
|
||||||
const packageConfigs = Object.create(null);
|
const packageConfigs = Object.create(null);
|
||||||
|
const distDir = ANGULAR_DIST_PACKAGES;
|
||||||
|
|
||||||
[ANGULAR_DIST_PACKAGES].forEach(distDir => {
|
this._log(`Angular distributable directory: ${distDir}.`);
|
||||||
this._log(`Angular distributable directory: ${distDir}.`);
|
|
||||||
shelljs
|
|
||||||
.find(distDir)
|
|
||||||
.map(filePath => filePath.slice(distDir.length + 1))
|
|
||||||
.filter(filePath => PACKAGE_JSON_REGEX.test(filePath))
|
|
||||||
.forEach(packagePath => {
|
|
||||||
const packageName = `@angular/${packagePath.slice(0, -PACKAGE_JSON.length -1)}`;
|
|
||||||
if (this.ignorePackages.indexOf(packageName) === -1) {
|
|
||||||
const packageConfig = require(path.resolve(distDir, packagePath));
|
|
||||||
packageConfigs[packageName] = {
|
|
||||||
parentDir: distDir,
|
|
||||||
packageJsonPath: path.resolve(distDir, packagePath),
|
|
||||||
config: packageConfig
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
this._log('Ignoring package', packageName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
if (this.buildPackages) {
|
||||||
|
this._buildDistPackages();
|
||||||
|
}
|
||||||
|
|
||||||
|
shelljs
|
||||||
|
.find(distDir)
|
||||||
|
.map(filePath => filePath.slice(distDir.length + 1))
|
||||||
|
.filter(filePath => PACKAGE_JSON_REGEX.test(filePath))
|
||||||
|
.forEach(packagePath => {
|
||||||
|
const packageName = `@angular/${packagePath.slice(0, -PACKAGE_JSON.length -1)}`;
|
||||||
|
if (this.ignorePackages.indexOf(packageName) === -1) {
|
||||||
|
const packageConfig = require(path.resolve(distDir, packagePath));
|
||||||
|
packageConfigs[packageName] = {
|
||||||
|
parentDir: distDir,
|
||||||
|
packageJsonPath: path.resolve(distDir, packagePath),
|
||||||
|
config: packageConfig
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this._log('Ignoring package', packageName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this._log('Found the following Angular distributables:', Object.keys(packageConfigs).map(key => `\n - ${key}`));
|
this._log('Found the following Angular distributables:', Object.keys(packageConfigs).map(key => `\n - ${key}`));
|
||||||
return packageConfigs;
|
return packageConfigs;
|
||||||
@ -234,6 +264,21 @@ class NgPackagesInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the value for a boolean cli argument/option. When passing an option multiple times, `yargs` parses it as an
|
||||||
|
* array of boolean values. In that case, we only care about the last occurrence.
|
||||||
|
*
|
||||||
|
* This can be useful, for example, when one has a base command with the option turned on and another command
|
||||||
|
* (building on top of the first one) turning the option off:
|
||||||
|
* ```
|
||||||
|
* "base-command": "my-script --foo --bar",
|
||||||
|
* "no-bar-command": "yarn base-command --no-bar",
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
_parseBooleanArg(value) {
|
||||||
|
return Array.isArray(value) ? value.pop() : value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse and return a `yarn.lock` file.
|
* Parse and return a `yarn.lock` file.
|
||||||
*/
|
*/
|
||||||
@ -254,17 +299,28 @@ class NgPackagesInstaller {
|
|||||||
const restoreCmd = `node ${relativeScriptPath} restore ${absoluteProjectDir}`;
|
const restoreCmd = `node ${relativeScriptPath} restore ${absoluteProjectDir}`;
|
||||||
|
|
||||||
// Log a warning.
|
// Log a warning.
|
||||||
|
this._warn([
|
||||||
|
`The project at "${absoluteProjectDir}" is running against the local Angular build.`,
|
||||||
|
'',
|
||||||
|
'To restore the npm packages run:',
|
||||||
|
'',
|
||||||
|
` "${restoreCmd}"`,
|
||||||
|
].join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a warning message do draw user's attention.
|
||||||
|
* @param {...string[]} messages - The messages to be logged.
|
||||||
|
*/
|
||||||
|
_warn(...messages) {
|
||||||
|
const lines = messages.join(' ').split('\n');
|
||||||
console.warn(chalk.yellow([
|
console.warn(chalk.yellow([
|
||||||
'',
|
'',
|
||||||
'!'.repeat(110),
|
'!'.repeat(110),
|
||||||
'!!!',
|
'!!!',
|
||||||
'!!! WARNING',
|
'!!! WARNING',
|
||||||
'!!!',
|
'!!!',
|
||||||
`!!! The project at "${absoluteProjectDir}" is running against the local Angular build.`,
|
...lines.map(line => `!!! ${line}`),
|
||||||
'!!!',
|
|
||||||
'!!! To restore the npm packages run:',
|
|
||||||
'!!!',
|
|
||||||
`!!! "${restoreCmd}"`,
|
|
||||||
'!!!',
|
'!!!',
|
||||||
'!'.repeat(110),
|
'!'.repeat(110),
|
||||||
'',
|
'',
|
||||||
@ -287,24 +343,27 @@ class NgPackagesInstaller {
|
|||||||
function main() {
|
function main() {
|
||||||
shelljs.set('-e');
|
shelljs.set('-e');
|
||||||
|
|
||||||
|
const createInstaller = argv => {
|
||||||
|
const {projectDir, ...options} = argv;
|
||||||
|
return new NgPackagesInstaller(projectDir, options);
|
||||||
|
};
|
||||||
|
|
||||||
yargs
|
yargs
|
||||||
.usage('$0 <cmd> [args]')
|
.usage('$0 <cmd> [args]')
|
||||||
|
|
||||||
.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('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 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 distributables.', () => {}, argv => {
|
||||||
const installer = new NgPackagesInstaller(argv.projectDir, argv);
|
createInstaller(argv).installLocalDependencies();
|
||||||
installer.installLocalDependencies();
|
|
||||||
})
|
})
|
||||||
.command('restore <projectDir> [--debug]', 'Install dependencies from the npm registry.', () => {}, argv => {
|
.command('restore <projectDir> [--debug]', 'Install dependencies from the npm registry.', () => {}, argv => {
|
||||||
const installer = new NgPackagesInstaller(argv.projectDir, argv);
|
createInstaller(argv).restoreNpmDependencies();
|
||||||
installer.restoreNpmDependencies();
|
|
||||||
})
|
})
|
||||||
.command('check <projectDir> [--debug]', 'Check that dependencies came from npm. Otherwise display a warning message.', () => {}, argv => {
|
.command('check <projectDir> [--debug]', 'Check that dependencies came from npm. Otherwise display a warning message.', () => {}, argv => {
|
||||||
const installer = new NgPackagesInstaller(argv.projectDir, argv);
|
createInstaller(argv).checkDependencies();
|
||||||
installer.checkDependencies();
|
|
||||||
})
|
})
|
||||||
.demandCommand(1, 'Please supply a command from the list above.')
|
.demandCommand(1, 'Please supply a command from the list above.')
|
||||||
.strict()
|
.strict()
|
||||||
|
@ -8,13 +8,14 @@ const shelljs = require('shelljs');
|
|||||||
const NgPackagesInstaller = require('./index');
|
const NgPackagesInstaller = require('./index');
|
||||||
|
|
||||||
describe('NgPackagesInstaller', () => {
|
describe('NgPackagesInstaller', () => {
|
||||||
const rootDir = 'root/dir';
|
const projectDir = 'root/dir';
|
||||||
const absoluteRootDir = path.resolve(rootDir);
|
const absoluteProjectDir = path.resolve(projectDir);
|
||||||
const nodeModulesDir = path.resolve(absoluteRootDir, 'node_modules');
|
const nodeModulesDir = path.resolve(absoluteProjectDir, 'node_modules');
|
||||||
const packageJsonPath = path.resolve(absoluteRootDir, 'package.json');
|
const packageJsonPath = path.resolve(absoluteProjectDir, 'package.json');
|
||||||
const yarnLockPath = path.resolve(absoluteRootDir, 'yarn.lock');
|
const yarnLockPath = path.resolve(absoluteProjectDir, 'yarn.lock');
|
||||||
const packagesDir = path.resolve(path.resolve(__dirname, '../../../dist/packages-dist'));
|
const ngRootDir = path.resolve(__dirname, '../../..');
|
||||||
const toolsDir = path.resolve(path.resolve(__dirname, '../../../dist/tools/@angular'));
|
const packagesDir = path.join(ngRootDir, 'dist/packages-dist');
|
||||||
|
const toolsDir = path.join(ngRootDir, 'dist/tools/@angular');
|
||||||
let installer;
|
let installer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -25,7 +26,7 @@ describe('NgPackagesInstaller', () => {
|
|||||||
spyOn(shelljs, 'rm');
|
spyOn(shelljs, 'rm');
|
||||||
spyOn(console, 'log');
|
spyOn(console, 'log');
|
||||||
spyOn(console, 'warn');
|
spyOn(console, 'warn');
|
||||||
installer = new NgPackagesInstaller(rootDir);
|
installer = new NgPackagesInstaller(projectDir);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('checkDependencies()', () => {
|
describe('checkDependencies()', () => {
|
||||||
@ -36,14 +37,14 @@ describe('NgPackagesInstaller', () => {
|
|||||||
it('should not print a warning if there is no _local_.json file', () => {
|
it('should not print a warning if there is no _local_.json file', () => {
|
||||||
fs.existsSync.and.returnValue(false);
|
fs.existsSync.and.returnValue(false);
|
||||||
installer.checkDependencies();
|
installer.checkDependencies();
|
||||||
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(rootDir, 'node_modules/_local_.json'));
|
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(projectDir, 'node_modules/_local_.json'));
|
||||||
expect(installer._printWarning).not.toHaveBeenCalled();
|
expect(installer._printWarning).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print a warning if there is a _local_.json file', () => {
|
it('should print a warning if there is a _local_.json file', () => {
|
||||||
fs.existsSync.and.returnValue(true);
|
fs.existsSync.and.returnValue(true);
|
||||||
installer.checkDependencies();
|
installer.checkDependencies();
|
||||||
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(rootDir, 'node_modules/_local_.json'));
|
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(projectDir, 'node_modules/_local_.json'));
|
||||||
expect(installer._printWarning).toHaveBeenCalled();
|
expect(installer._printWarning).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -242,7 +243,68 @@ describe('NgPackagesInstaller', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('_buildDistPackages()', () => {
|
||||||
|
// Call `_buildDistPackages()` with a mock `process.platform` value.
|
||||||
|
const buildDistPackagesOnPlatform = platform => {
|
||||||
|
const originalDescriptor = Object.getOwnPropertyDescriptor(process, 'platform');
|
||||||
|
Object.defineProperty(process, 'platform', {...originalDescriptor, value: platform});
|
||||||
|
installer._buildDistPackages();
|
||||||
|
Object.defineProperty(process, 'platform', originalDescriptor);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should build the local packages, when not on Windows', () => {
|
||||||
|
const buildScript = path.join(ngRootDir, 'scripts/build-packages-dist.sh');
|
||||||
|
|
||||||
|
buildDistPackagesOnPlatform('linux');
|
||||||
|
expect(shelljs.exec).toHaveBeenCalledWith(buildScript);
|
||||||
|
|
||||||
|
shelljs.exec.calls.reset();
|
||||||
|
|
||||||
|
buildDistPackagesOnPlatform('darwin');
|
||||||
|
expect(shelljs.exec).toHaveBeenCalledWith(buildScript);
|
||||||
|
|
||||||
|
shelljs.exec.calls.reset();
|
||||||
|
|
||||||
|
buildDistPackagesOnPlatform('anythingButWindows :(');
|
||||||
|
expect(shelljs.exec).toHaveBeenCalledWith(buildScript);
|
||||||
|
|
||||||
|
// Ensure that the script does actually exist (e.g. it was not renamed/moved).
|
||||||
|
fs.existsSync.and.callThrough();
|
||||||
|
expect(fs.existsSync(buildScript)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should print a warning, when on Windows', () => {
|
||||||
|
buildDistPackagesOnPlatform('win32');
|
||||||
|
const warning = console.warn.calls.argsFor(0)[0];
|
||||||
|
|
||||||
|
expect(shelljs.exec).not.toHaveBeenCalled();
|
||||||
|
expect(warning).toContain(
|
||||||
|
'Automatically building the local Angular packages is currently not supported on Windows.');
|
||||||
|
expect(warning).toContain('Git Bash for Windows');
|
||||||
|
expect(warning).toContain('Windows Subsystem for Linux');
|
||||||
|
expect(warning).toContain('Linux docker container or VM');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('_getDistPackages()', () => {
|
describe('_getDistPackages()', () => {
|
||||||
|
beforeEach(() => spyOn(NgPackagesInstaller.prototype, '_buildDistPackages'));
|
||||||
|
|
||||||
|
it('should not build the local packages by default', () => {
|
||||||
|
installer._getDistPackages();
|
||||||
|
expect(installer._buildDistPackages).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should build the local packages, if `buildPackages` is true', () => {
|
||||||
|
installer = new NgPackagesInstaller(projectDir, {buildPackages: true});
|
||||||
|
installer._getDistPackages();
|
||||||
|
expect(installer._buildDistPackages).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not build the local packages by default', () => {
|
||||||
|
installer._getDistPackages();
|
||||||
|
expect(installer._buildDistPackages).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('should include top level Angular packages', () => {
|
it('should include top level Angular packages', () => {
|
||||||
const ngPackages = installer._getDistPackages();
|
const ngPackages = installer._getDistPackages();
|
||||||
const expectedValue = jasmine.objectContaining({
|
const expectedValue = jasmine.objectContaining({
|
||||||
@ -269,7 +331,7 @@ describe('NgPackagesInstaller', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not include packages that have been ignored', () => {
|
it('should not include packages that have been ignored', () => {
|
||||||
installer = new NgPackagesInstaller(rootDir, { ignorePackages: ['@angular/router'] });
|
installer = new NgPackagesInstaller(projectDir, { ignorePackages: ['@angular/router'] });
|
||||||
const ngPackages = installer._getDistPackages();
|
const ngPackages = installer._getDistPackages();
|
||||||
|
|
||||||
expect(ngPackages['@angular/common']).toBeDefined();
|
expect(ngPackages['@angular/common']).toBeDefined();
|
||||||
@ -283,9 +345,9 @@ describe('NgPackagesInstaller', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should assign the debug property from the options', () => {
|
it('should assign the debug property from the options', () => {
|
||||||
installer = new NgPackagesInstaller(rootDir, { debug: true });
|
installer = new NgPackagesInstaller(projectDir, { debug: true });
|
||||||
expect(installer.debug).toBe(true);
|
expect(installer.debug).toBe(true);
|
||||||
installer = new NgPackagesInstaller(rootDir, { });
|
installer = new NgPackagesInstaller(projectDir, { });
|
||||||
expect(installer.debug).toBe(undefined);
|
expect(installer.debug).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -350,7 +412,7 @@ describe('NgPackagesInstaller', () => {
|
|||||||
expect(console.warn.calls.argsFor(0)[0]).toMatch(restoreCmdRe1);
|
expect(console.warn.calls.argsFor(0)[0]).toMatch(restoreCmdRe1);
|
||||||
|
|
||||||
// When run for a different directory...
|
// When run for a different directory...
|
||||||
const dir2 = rootDir;
|
const dir2 = projectDir;
|
||||||
const restoreCmdRe2 = RegExp(`\\bnode .*?ng-packages-installer/index restore .*?${path.resolve(dir1)}\\b`);
|
const restoreCmdRe2 = RegExp(`\\bnode .*?ng-packages-installer/index restore .*?${path.resolve(dir1)}\\b`);
|
||||||
installer = new NgPackagesInstaller(dir2);
|
installer = new NgPackagesInstaller(dir2);
|
||||||
installer._printWarning('');
|
installer._printWarning('');
|
||||||
@ -361,14 +423,14 @@ describe('NgPackagesInstaller', () => {
|
|||||||
describe('_installDeps()', () => {
|
describe('_installDeps()', () => {
|
||||||
it('should run yarn install with the given options', () => {
|
it('should run yarn install with the given options', () => {
|
||||||
installer._installDeps('option-1', 'option-2');
|
installer._installDeps('option-1', 'option-2');
|
||||||
expect(shelljs.exec).toHaveBeenCalledWith('yarn install option-1 option-2', { cwd: absoluteRootDir });
|
expect(shelljs.exec).toHaveBeenCalledWith('yarn install option-1 option-2', { cwd: absoluteProjectDir });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('local marker helpers', () => {
|
describe('local marker helpers', () => {
|
||||||
let installer;
|
let installer;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
installer = new NgPackagesInstaller(rootDir);
|
installer = new NgPackagesInstaller(projectDir);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('_checkLocalMarker', () => {
|
describe('_checkLocalMarker', () => {
|
||||||
|
@ -208,9 +208,9 @@
|
|||||||
<code>{$ renderMemberSyntax(property) $}</code>
|
<code>{$ renderMemberSyntax(property) $}</code>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{%- if (property.isGetAccessor or property.isReadonly) and not property.isSetAccessor %}<span class='read-only-property'>Read-only.</span>{% endif %}
|
{%- if (property.isGetAccessor or property.isReadonly) and not property.isSetAccessor %}<span class='read-only-property'>Read-Only</span>{% endif %}
|
||||||
{%- if property.isSetAccessor and not property.isGetAccessor %}<span class='write-only-property'>Write-only.</span>{% endif %}
|
{%- if property.isSetAccessor and not property.isGetAccessor %}<span class='write-only-property'>Write-Only</span>{% endif %}
|
||||||
{% if property.constructorParamDoc %} <span class='from-constructor'>Declared in constructor.</span>{% endif %}
|
{% if property.constructorParamDoc %} <span class='from-constructor'>Declared in Constructor</span>{% endif %}
|
||||||
{% if property.shortDescription %}{$ property.shortDescription | marked $}{% endif %}
|
{% if property.shortDescription %}{$ property.shortDescription | marked $}{% endif %}
|
||||||
{$ (property.description or property.constructorParamDoc.description) | marked $}
|
{$ (property.description or property.constructorParamDoc.description) | marked $}
|
||||||
{%- if property.see.length %}
|
{%- if property.see.length %}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"master": {
|
"master": {
|
||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"runtime": 1440,
|
"runtime": 1440,
|
||||||
"main": 14021,
|
"main": 13517,
|
||||||
"polyfills": 43567
|
"polyfills": 43567
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,4 +34,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -20,12 +20,12 @@
|
|||||||
],
|
],
|
||||||
"textSpan": {
|
"textSpan": {
|
||||||
"start": {
|
"start": {
|
||||||
"line": 7,
|
"line": 5,
|
||||||
"offset": 30
|
"offset": 26
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"line": 7,
|
"line": 5,
|
||||||
"offset": 47
|
"offset": 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"request_seq": 2,
|
"request_seq": 2,
|
||||||
"success": true,
|
"success": true,
|
||||||
"body": {
|
"body": {
|
||||||
"kind": "",
|
"kind": "property",
|
||||||
"kindModifiers": "",
|
"kindModifiers": "",
|
||||||
"start": {
|
"start": {
|
||||||
"line": 5,
|
"line": 5,
|
||||||
@ -15,7 +15,7 @@
|
|||||||
"line": 5,
|
"line": 5,
|
||||||
"offset": 30
|
"offset": 30
|
||||||
},
|
},
|
||||||
"displayString": "property name of AppComponent",
|
"displayString": "(property) AppComponent.name",
|
||||||
"documentation": "",
|
"documentation": "",
|
||||||
"tags": []
|
"tags": []
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "angular-srcs",
|
"name": "angular-srcs",
|
||||||
"version": "9.0.0-next.0",
|
"version": "9.0.0-next.1",
|
||||||
"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",
|
||||||
|
@ -161,8 +161,8 @@ export class DatePipe implements PipeTransform {
|
|||||||
* @param format The date/time components to include, using predefined options or a
|
* @param format The date/time components to include, using predefined options or a
|
||||||
* custom format string.
|
* custom format string.
|
||||||
* @param timezone A timezone offset (such as `'+0430'`), or a standard
|
* @param timezone A timezone offset (such as `'+0430'`), or a standard
|
||||||
* UTC/GMT or continental US timezone abbreviation. Default is
|
* UTC/GMT or continental US timezone abbreviation.
|
||||||
* the local system timezone of the end-user's machine.
|
* When not supplied, uses the end-user's local system timezone.
|
||||||
* @param locale A locale code for the locale format rules to use.
|
* @param locale A locale code for the locale format rules to use.
|
||||||
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
|
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
|
||||||
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
|
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
|
||||||
|
@ -9,9 +9,11 @@
|
|||||||
import {DepGraph} from 'dependency-graph';
|
import {DepGraph} from 'dependency-graph';
|
||||||
import {AbsoluteFsPath, FileSystem, resolve} from '../../../src/ngtsc/file_system';
|
import {AbsoluteFsPath, FileSystem, resolve} from '../../../src/ngtsc/file_system';
|
||||||
import {Logger} from '../logging/logger';
|
import {Logger} from '../logging/logger';
|
||||||
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointFormat} from '../packages/entry_point';
|
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from '../packages/entry_point';
|
||||||
import {DependencyHost, DependencyInfo} from './dependency_host';
|
import {DependencyHost, DependencyInfo} from './dependency_host';
|
||||||
|
|
||||||
|
const builtinNodeJsModules = new Set<string>(require('module').builtinModules);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds information about entry points that are removed because
|
* Holds information about entry points that are removed because
|
||||||
* they have dependencies that are missing (directly or transitively).
|
* they have dependencies that are missing (directly or transitively).
|
||||||
@ -81,7 +83,7 @@ export class DependencyResolver {
|
|||||||
|
|
||||||
let sortedEntryPointNodes: string[];
|
let sortedEntryPointNodes: string[];
|
||||||
if (target) {
|
if (target) {
|
||||||
if (target.compiledByAngular) {
|
if (target.compiledByAngular && graph.hasNode(target.path)) {
|
||||||
sortedEntryPointNodes = graph.dependenciesOf(target.path);
|
sortedEntryPointNodes = graph.dependenciesOf(target.path);
|
||||||
sortedEntryPointNodes.push(target.path);
|
sortedEntryPointNodes.push(target.path);
|
||||||
} else {
|
} else {
|
||||||
@ -128,10 +130,12 @@ export class DependencyResolver {
|
|||||||
angularEntryPoints.forEach(entryPoint => {
|
angularEntryPoints.forEach(entryPoint => {
|
||||||
const {dependencies, missing, deepImports} = this.getEntryPointDependencies(entryPoint);
|
const {dependencies, missing, deepImports} = this.getEntryPointDependencies(entryPoint);
|
||||||
|
|
||||||
if (missing.size > 0) {
|
const missingDependencies = Array.from(missing).filter(dep => !builtinNodeJsModules.has(dep));
|
||||||
|
|
||||||
|
if (missingDependencies.length > 0) {
|
||||||
// This entry point has dependencies that are missing
|
// This entry point has dependencies that are missing
|
||||||
// so remove it from the graph.
|
// so remove it from the graph.
|
||||||
removeNodes(entryPoint, Array.from(missing));
|
removeNodes(entryPoint, missingDependencies);
|
||||||
} else {
|
} else {
|
||||||
dependencies.forEach(dependencyPath => {
|
dependencies.forEach(dependencyPath => {
|
||||||
if (!graph.hasNode(entryPoint.path)) {
|
if (!graph.hasNode(entryPoint.path)) {
|
||||||
@ -173,16 +177,16 @@ export class DependencyResolver {
|
|||||||
|
|
||||||
private getEntryPointFormatInfo(entryPoint: EntryPoint):
|
private getEntryPointFormatInfo(entryPoint: EntryPoint):
|
||||||
{format: EntryPointFormat, path: AbsoluteFsPath} {
|
{format: EntryPointFormat, path: AbsoluteFsPath} {
|
||||||
const properties = Object.keys(entryPoint.packageJson);
|
for (const property of SUPPORTED_FORMAT_PROPERTIES) {
|
||||||
for (let i = 0; i < properties.length; i++) {
|
const formatPath = entryPoint.packageJson[property];
|
||||||
const property = properties[i] as EntryPointJsonProperty;
|
if (formatPath === undefined) continue;
|
||||||
const format = getEntryPointFormat(this.fs, entryPoint, property);
|
|
||||||
|
|
||||||
if (format === 'esm2015' || format === 'esm5' || format === 'umd' || format === 'commonjs') {
|
const format = getEntryPointFormat(this.fs, entryPoint, property);
|
||||||
const formatPath = entryPoint.packageJson[property] !;
|
if (format === undefined) continue;
|
||||||
return {format, path: resolve(entryPoint.path, formatPath)};
|
|
||||||
}
|
return {format, path: resolve(entryPoint.path, formatPath)};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`There is no appropriate source code format in '${entryPoint.path}' entry-point.`);
|
`There is no appropriate source code format in '${entryPoint.path}' entry-point.`);
|
||||||
}
|
}
|
||||||
|
74
packages/compiler-cli/ngcc/src/execution/api.ts
Normal file
74
packages/compiler-cli/ngcc/src/execution/api.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point';
|
||||||
|
|
||||||
|
/** The type of the function that analyzes entry-points and creates the list of tasks. */
|
||||||
|
export type AnalyzeFn = () => {
|
||||||
|
processingMetadataPerEntryPoint: Map<string, EntryPointProcessingMetadata>;
|
||||||
|
tasks: Task[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the function that creates the `compile()` function, which in turn can be used to
|
||||||
|
* process tasks.
|
||||||
|
*/
|
||||||
|
export type CreateCompileFn =
|
||||||
|
(onTaskCompleted: (task: Task, outcome: TaskProcessingOutcome) => void) => (task: Task) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the function that orchestrates and executes the required work (i.e. analyzes the
|
||||||
|
* entry-points, processes the resulting tasks, does book-keeping and validates the final outcome).
|
||||||
|
*/
|
||||||
|
export type ExecuteFn = (analyzeFn: AnalyzeFn, createCompileFn: CreateCompileFn) => void;
|
||||||
|
|
||||||
|
/** Represents metadata related to the processing of an entry-point. */
|
||||||
|
export interface EntryPointProcessingMetadata {
|
||||||
|
/**
|
||||||
|
* Whether the typings for the entry-point have been successfully processed (or were already
|
||||||
|
* processed).
|
||||||
|
*/
|
||||||
|
hasProcessedTypings: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether at least one format has been successfully processed (or was already processed) for the
|
||||||
|
* entry-point.
|
||||||
|
*/
|
||||||
|
hasAnyProcessedFormat: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Represents a unit of work: processing a specific format property of an entry-point. */
|
||||||
|
export interface Task {
|
||||||
|
/** The `EntryPoint` which needs to be processed as part of the task. */
|
||||||
|
entryPoint: EntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `package.json` format property to process (i.e. the property which points to the file that
|
||||||
|
* is the program entry-point).
|
||||||
|
*/
|
||||||
|
formatProperty: EntryPointJsonProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of all format properties (including `task.formatProperty`) that should be marked as
|
||||||
|
* processed once the taksk has been completed, because they point to the format-path that will be
|
||||||
|
* processed as part of the task.
|
||||||
|
*/
|
||||||
|
formatPropertiesToMarkAsProcessed: EntryPointJsonProperty[];
|
||||||
|
|
||||||
|
/** Whether to also process typings for this entry-point as part of the task. */
|
||||||
|
processDts: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Represents the outcome of processing a `Task`. */
|
||||||
|
export const enum TaskProcessingOutcome {
|
||||||
|
/** The target format property was already processed - didn't have to do anything. */
|
||||||
|
AlreadyProcessed,
|
||||||
|
|
||||||
|
/** Successfully processed the target format property. */
|
||||||
|
Processed,
|
||||||
|
}
|
@ -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 {AbsoluteFsPath, FileSystem, absoluteFrom, dirname, getFileSystem, resolve} from '../../src/ngtsc/file_system';
|
import {AbsoluteFsPath, FileSystem, absoluteFrom, dirname, getFileSystem, resolve} from '../../src/ngtsc/file_system';
|
||||||
|
|
||||||
import {CommonJsDependencyHost} from './dependencies/commonjs_dependency_host';
|
import {CommonJsDependencyHost} from './dependencies/commonjs_dependency_host';
|
||||||
import {DependencyResolver, InvalidEntryPoint, SortedEntryPointsInfo} from './dependencies/dependency_resolver';
|
import {DependencyResolver, InvalidEntryPoint, SortedEntryPointsInfo} from './dependencies/dependency_resolver';
|
||||||
import {EsmDependencyHost} from './dependencies/esm_dependency_host';
|
import {EsmDependencyHost} from './dependencies/esm_dependency_host';
|
||||||
@ -13,11 +14,12 @@ import {ModuleResolver} from './dependencies/module_resolver';
|
|||||||
import {UmdDependencyHost} from './dependencies/umd_dependency_host';
|
import {UmdDependencyHost} from './dependencies/umd_dependency_host';
|
||||||
import {DirectoryWalkerEntryPointFinder} from './entry_point_finder/directory_walker_entry_point_finder';
|
import {DirectoryWalkerEntryPointFinder} from './entry_point_finder/directory_walker_entry_point_finder';
|
||||||
import {TargetedEntryPointFinder} from './entry_point_finder/targeted_entry_point_finder';
|
import {TargetedEntryPointFinder} from './entry_point_finder/targeted_entry_point_finder';
|
||||||
|
import {AnalyzeFn, CreateCompileFn, EntryPointProcessingMetadata, ExecuteFn, Task, TaskProcessingOutcome} from './execution/api';
|
||||||
import {ConsoleLogger, LogLevel} from './logging/console_logger';
|
import {ConsoleLogger, LogLevel} from './logging/console_logger';
|
||||||
import {Logger} from './logging/logger';
|
import {Logger} from './logging/logger';
|
||||||
import {hasBeenProcessed, markAsProcessed} from './packages/build_marker';
|
import {hasBeenProcessed, markAsProcessed} from './packages/build_marker';
|
||||||
import {NgccConfiguration} from './packages/configuration';
|
import {NgccConfiguration} from './packages/configuration';
|
||||||
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from './packages/entry_point';
|
import {EntryPoint, EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from './packages/entry_point';
|
||||||
import {makeEntryPointBundle} from './packages/entry_point_bundle';
|
import {makeEntryPointBundle} from './packages/entry_point_bundle';
|
||||||
import {Transformer} from './packages/transformer';
|
import {Transformer} from './packages/transformer';
|
||||||
import {PathMappings} from './utils';
|
import {PathMappings} from './utils';
|
||||||
@ -25,6 +27,7 @@ import {FileWriter} from './writing/file_writer';
|
|||||||
import {InPlaceFileWriter} from './writing/in_place_file_writer';
|
import {InPlaceFileWriter} from './writing/in_place_file_writer';
|
||||||
import {NewEntryPointFileWriter} from './writing/new_entry_point_file_writer';
|
import {NewEntryPointFileWriter} from './writing/new_entry_point_file_writer';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The options to configure the ngcc compiler.
|
* The options to configure the ngcc compiler.
|
||||||
*/
|
*/
|
||||||
@ -68,8 +71,6 @@ export interface NgccOptions {
|
|||||||
fileSystem?: FileSystem;
|
fileSystem?: FileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015', 'umd', 'commonjs'];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the main entry-point into ngcc (aNGular Compatibility Compiler).
|
* This is the main entry-point into ngcc (aNGular Compatibility Compiler).
|
||||||
*
|
*
|
||||||
@ -83,82 +84,171 @@ export function mainNgcc(
|
|||||||
compileAllFormats = true, createNewEntryPointFormats = false,
|
compileAllFormats = true, createNewEntryPointFormats = false,
|
||||||
logger = new ConsoleLogger(LogLevel.info), pathMappings}: NgccOptions): void {
|
logger = new ConsoleLogger(LogLevel.info), pathMappings}: NgccOptions): void {
|
||||||
const fileSystem = getFileSystem();
|
const fileSystem = getFileSystem();
|
||||||
const transformer = new Transformer(fileSystem, logger);
|
|
||||||
const moduleResolver = new ModuleResolver(fileSystem, pathMappings);
|
|
||||||
const esmDependencyHost = new EsmDependencyHost(fileSystem, moduleResolver);
|
|
||||||
const umdDependencyHost = new UmdDependencyHost(fileSystem, moduleResolver);
|
|
||||||
const commonJsDependencyHost = new CommonJsDependencyHost(fileSystem, moduleResolver);
|
|
||||||
const resolver = new DependencyResolver(fileSystem, logger, {
|
|
||||||
esm5: esmDependencyHost,
|
|
||||||
esm2015: esmDependencyHost,
|
|
||||||
umd: umdDependencyHost,
|
|
||||||
commonjs: commonJsDependencyHost
|
|
||||||
});
|
|
||||||
const absBasePath = absoluteFrom(basePath);
|
|
||||||
const config = new NgccConfiguration(fileSystem, dirname(absBasePath));
|
|
||||||
const fileWriter = getFileWriter(fileSystem, createNewEntryPointFormats);
|
|
||||||
const entryPoints = getEntryPoints(
|
|
||||||
fileSystem, config, logger, resolver, absBasePath, targetEntryPointPath, pathMappings,
|
|
||||||
propertiesToConsider, compileAllFormats);
|
|
||||||
for (const entryPoint of entryPoints) {
|
|
||||||
// Are we compiling the Angular core?
|
|
||||||
const isCore = entryPoint.name === '@angular/core';
|
|
||||||
|
|
||||||
const compiledFormats = new Set<string>();
|
// The function for performing the analysis.
|
||||||
const entryPointPackageJson = entryPoint.packageJson;
|
const analyzeFn: AnalyzeFn = () => {
|
||||||
const entryPointPackageJsonPath = fileSystem.resolve(entryPoint.path, 'package.json');
|
const supportedPropertiesToConsider = ensureSupportedProperties(propertiesToConsider);
|
||||||
|
|
||||||
const hasProcessedDts = hasBeenProcessed(entryPointPackageJson, 'typings');
|
const moduleResolver = new ModuleResolver(fileSystem, pathMappings);
|
||||||
|
const esmDependencyHost = new EsmDependencyHost(fileSystem, moduleResolver);
|
||||||
|
const umdDependencyHost = new UmdDependencyHost(fileSystem, moduleResolver);
|
||||||
|
const commonJsDependencyHost = new CommonJsDependencyHost(fileSystem, moduleResolver);
|
||||||
|
const dependencyResolver = new DependencyResolver(fileSystem, logger, {
|
||||||
|
esm5: esmDependencyHost,
|
||||||
|
esm2015: esmDependencyHost,
|
||||||
|
umd: umdDependencyHost,
|
||||||
|
commonjs: commonJsDependencyHost
|
||||||
|
});
|
||||||
|
|
||||||
for (let i = 0; i < propertiesToConsider.length; i++) {
|
const absBasePath = absoluteFrom(basePath);
|
||||||
const property = propertiesToConsider[i] as EntryPointJsonProperty;
|
const config = new NgccConfiguration(fileSystem, dirname(absBasePath));
|
||||||
const formatPath = entryPointPackageJson[property];
|
const entryPoints = getEntryPoints(
|
||||||
const format = getEntryPointFormat(fileSystem, entryPoint, property);
|
fileSystem, config, logger, dependencyResolver, absBasePath, targetEntryPointPath,
|
||||||
|
pathMappings, supportedPropertiesToConsider, compileAllFormats);
|
||||||
|
|
||||||
// No format then this property is not supposed to be compiled.
|
const processingMetadataPerEntryPoint = new Map<string, EntryPointProcessingMetadata>();
|
||||||
if (!formatPath || !format || SUPPORTED_FORMATS.indexOf(format) === -1) continue;
|
const tasks: Task[] = [];
|
||||||
|
|
||||||
if (hasBeenProcessed(entryPointPackageJson, property)) {
|
for (const entryPoint of entryPoints) {
|
||||||
compiledFormats.add(formatPath);
|
const packageJson = entryPoint.packageJson;
|
||||||
logger.debug(`Skipping ${entryPoint.name} : ${property} (already compiled).`);
|
const hasProcessedTypings = hasBeenProcessed(packageJson, 'typings');
|
||||||
continue;
|
const {propertiesToProcess, propertyToPropertiesToMarkAsProcessed} =
|
||||||
|
getPropertiesToProcessAndMarkAsProcessed(packageJson, supportedPropertiesToConsider);
|
||||||
|
let processDts = !hasProcessedTypings;
|
||||||
|
|
||||||
|
for (const formatProperty of propertiesToProcess) {
|
||||||
|
const formatPropertiesToMarkAsProcessed =
|
||||||
|
propertyToPropertiesToMarkAsProcessed.get(formatProperty) !;
|
||||||
|
tasks.push({entryPoint, formatProperty, formatPropertiesToMarkAsProcessed, processDts});
|
||||||
|
|
||||||
|
// Only process typings for the first property (if not already processed).
|
||||||
|
processDts = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFirstFormat = compiledFormats.size === 0;
|
processingMetadataPerEntryPoint.set(entryPoint.path, {
|
||||||
const processDts = !hasProcessedDts && isFirstFormat;
|
hasProcessedTypings,
|
||||||
|
hasAnyProcessedFormat: false,
|
||||||
// We don't break if this if statement fails because we still want to mark
|
});
|
||||||
// the property as processed even if its underlying format has been built already.
|
|
||||||
if (!compiledFormats.has(formatPath) && (compileAllFormats || isFirstFormat)) {
|
|
||||||
const bundle = makeEntryPointBundle(
|
|
||||||
fileSystem, entryPoint, formatPath, isCore, property, format, processDts, pathMappings,
|
|
||||||
true);
|
|
||||||
if (bundle) {
|
|
||||||
logger.info(`Compiling ${entryPoint.name} : ${property} as ${format}`);
|
|
||||||
const transformedFiles = transformer.transform(bundle);
|
|
||||||
fileWriter.writeBundle(entryPoint, bundle, transformedFiles);
|
|
||||||
compiledFormats.add(formatPath);
|
|
||||||
} else {
|
|
||||||
logger.warn(
|
|
||||||
`Skipping ${entryPoint.name} : ${format} (no valid entry point file for this format).`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Either this format was just compiled or its underlying format was compiled because of a
|
|
||||||
// previous property.
|
|
||||||
if (compiledFormats.has(formatPath)) {
|
|
||||||
markAsProcessed(fileSystem, entryPointPackageJson, entryPointPackageJsonPath, property);
|
|
||||||
if (processDts) {
|
|
||||||
markAsProcessed(fileSystem, entryPointPackageJson, entryPointPackageJsonPath, 'typings');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compiledFormats.size === 0) {
|
return {processingMetadataPerEntryPoint, tasks};
|
||||||
|
};
|
||||||
|
|
||||||
|
// The function for creating the `compile()` function.
|
||||||
|
const createCompileFn: CreateCompileFn = onTaskCompleted => {
|
||||||
|
const fileWriter = getFileWriter(fileSystem, createNewEntryPointFormats);
|
||||||
|
const transformer = new Transformer(fileSystem, logger);
|
||||||
|
|
||||||
|
return (task: Task) => {
|
||||||
|
const {entryPoint, formatProperty, formatPropertiesToMarkAsProcessed, processDts} = task;
|
||||||
|
|
||||||
|
const isCore = entryPoint.name === '@angular/core'; // Are we compiling the Angular core?
|
||||||
|
const packageJson = entryPoint.packageJson;
|
||||||
|
const formatPath = packageJson[formatProperty];
|
||||||
|
const format = getEntryPointFormat(fileSystem, entryPoint, formatProperty);
|
||||||
|
|
||||||
|
// All properties listed in `propertiesToProcess` are guaranteed to point to a format-path
|
||||||
|
// (i.e. they exist in `entryPointPackageJson`). Furthermore, they are also guaranteed to be
|
||||||
|
// among `SUPPORTED_FORMAT_PROPERTIES`.
|
||||||
|
// Based on the above, `formatPath` should always be defined and `getEntryPointFormat()`
|
||||||
|
// should always return a format here (and not `undefined`).
|
||||||
|
if (!formatPath || !format) {
|
||||||
|
// This should never happen.
|
||||||
|
throw new Error(
|
||||||
|
`Invariant violated: No format-path or format for ${entryPoint.path} : ` +
|
||||||
|
`${formatProperty} (formatPath: ${formatPath} | format: ${format})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The format-path which the property maps to is already processed - nothing to do.
|
||||||
|
if (hasBeenProcessed(packageJson, formatProperty)) {
|
||||||
|
logger.debug(`Skipping ${entryPoint.name} : ${formatProperty} (already compiled).`);
|
||||||
|
onTaskCompleted(task, TaskProcessingOutcome.AlreadyProcessed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundle = makeEntryPointBundle(
|
||||||
|
fileSystem, entryPoint, formatPath, isCore, format, processDts, pathMappings, true);
|
||||||
|
|
||||||
|
logger.info(`Compiling ${entryPoint.name} : ${formatProperty} as ${format}`);
|
||||||
|
|
||||||
|
const transformedFiles = transformer.transform(bundle);
|
||||||
|
fileWriter.writeBundle(bundle, transformedFiles, formatPropertiesToMarkAsProcessed);
|
||||||
|
|
||||||
|
onTaskCompleted(task, TaskProcessingOutcome.Processed);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// The function for actually planning and getting the work done.
|
||||||
|
const executeFn: ExecuteFn = (analyzeFn: AnalyzeFn, createCompileFn: CreateCompileFn) => {
|
||||||
|
const {processingMetadataPerEntryPoint, tasks} = analyzeFn();
|
||||||
|
const compile = createCompileFn((task, outcome) => {
|
||||||
|
const {entryPoint, formatPropertiesToMarkAsProcessed, processDts} = task;
|
||||||
|
const processingMeta = processingMetadataPerEntryPoint.get(entryPoint.path) !;
|
||||||
|
processingMeta.hasAnyProcessedFormat = true;
|
||||||
|
|
||||||
|
if (outcome === TaskProcessingOutcome.Processed) {
|
||||||
|
const packageJsonPath = fileSystem.resolve(entryPoint.path, 'package.json');
|
||||||
|
const propsToMarkAsProcessed: (EntryPointJsonProperty | 'typings')[] =
|
||||||
|
[...formatPropertiesToMarkAsProcessed];
|
||||||
|
|
||||||
|
if (processDts) {
|
||||||
|
processingMeta.hasProcessedTypings = true;
|
||||||
|
propsToMarkAsProcessed.push('typings');
|
||||||
|
}
|
||||||
|
|
||||||
|
markAsProcessed(
|
||||||
|
fileSystem, entryPoint.packageJson, packageJsonPath, propsToMarkAsProcessed);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process all tasks.
|
||||||
|
for (const task of tasks) {
|
||||||
|
const processingMeta = processingMetadataPerEntryPoint.get(task.entryPoint.path) !;
|
||||||
|
|
||||||
|
// If we only need one format processed and we already have one for the corresponding
|
||||||
|
// entry-point, skip the task.
|
||||||
|
if (!compileAllFormats && processingMeta.hasAnyProcessedFormat) continue;
|
||||||
|
|
||||||
|
compile(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for entry-points for which we could not process any format at all.
|
||||||
|
const unprocessedEntryPointPaths =
|
||||||
|
Array.from(processingMetadataPerEntryPoint.entries())
|
||||||
|
.filter(([, processingMeta]) => !processingMeta.hasAnyProcessedFormat)
|
||||||
|
.map(([entryPointPath]) => `\n - ${entryPointPath}`)
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
if (unprocessedEntryPointPaths) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to compile any formats for entry-point at (${entryPoint.path}). Tried ${propertiesToConsider}.`);
|
'Failed to compile any formats for the following entry-points (tried ' +
|
||||||
|
`${propertiesToConsider.join(', ')}): ${unprocessedEntryPointPaths}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return executeFn(analyzeFn, createCompileFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureSupportedProperties(properties: string[]): EntryPointJsonProperty[] {
|
||||||
|
// Short-circuit the case where `properties` has fallen back to the default value:
|
||||||
|
// `SUPPORTED_FORMAT_PROPERTIES`
|
||||||
|
if (properties === SUPPORTED_FORMAT_PROPERTIES) return SUPPORTED_FORMAT_PROPERTIES;
|
||||||
|
|
||||||
|
const supportedProperties: EntryPointJsonProperty[] = [];
|
||||||
|
|
||||||
|
for (const prop of properties as EntryPointJsonProperty[]) {
|
||||||
|
if (SUPPORTED_FORMAT_PROPERTIES.indexOf(prop) !== -1) {
|
||||||
|
supportedProperties.push(prop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (supportedProperties.length === 0) {
|
||||||
|
throw new Error(
|
||||||
|
`No supported format property to consider among [${properties.join(', ')}]. ` +
|
||||||
|
`Supported properties: ${SUPPORTED_FORMAT_PROPERTIES.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return supportedProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFileWriter(fs: FileSystem, createNewEntryPointFormats: boolean): FileWriter {
|
function getFileWriter(fs: FileSystem, createNewEntryPointFormats: boolean): FileWriter {
|
||||||
@ -192,8 +282,15 @@ function getTargetedEntryPoints(
|
|||||||
const finder = new TargetedEntryPointFinder(
|
const finder = new TargetedEntryPointFinder(
|
||||||
fs, config, logger, resolver, basePath, absoluteTargetEntryPointPath, pathMappings);
|
fs, config, logger, resolver, basePath, absoluteTargetEntryPointPath, pathMappings);
|
||||||
const entryPointInfo = finder.findEntryPoints();
|
const entryPointInfo = finder.findEntryPoints();
|
||||||
|
const invalidTarget = entryPointInfo.invalidEntryPoints.find(
|
||||||
|
i => i.entryPoint.path === absoluteTargetEntryPointPath);
|
||||||
|
if (invalidTarget !== undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`The target entry-point "${invalidTarget.entryPoint.name}" has missing dependencies:\n` +
|
||||||
|
invalidTarget.missingDependencies.map(dep => ` - ${dep}\n`));
|
||||||
|
}
|
||||||
if (entryPointInfo.entryPoints.length === 0) {
|
if (entryPointInfo.entryPoints.length === 0) {
|
||||||
markNonAngularPackageAsProcessed(fs, absoluteTargetEntryPointPath, propertiesToConsider);
|
markNonAngularPackageAsProcessed(fs, absoluteTargetEntryPointPath);
|
||||||
}
|
}
|
||||||
return entryPointInfo;
|
return entryPointInfo;
|
||||||
}
|
}
|
||||||
@ -242,14 +339,13 @@ function hasProcessedTargetEntryPoint(
|
|||||||
* So mark all formats in this entry-point as processed so that clients of ngcc can avoid
|
* So mark all formats in this entry-point as processed so that clients of ngcc can avoid
|
||||||
* triggering ngcc for this entry-point in the future.
|
* triggering ngcc for this entry-point in the future.
|
||||||
*/
|
*/
|
||||||
function markNonAngularPackageAsProcessed(
|
function markNonAngularPackageAsProcessed(fs: FileSystem, path: AbsoluteFsPath) {
|
||||||
fs: FileSystem, path: AbsoluteFsPath, propertiesToConsider: string[]) {
|
|
||||||
const packageJsonPath = resolve(path, 'package.json');
|
const packageJsonPath = resolve(path, 'package.json');
|
||||||
const packageJson = JSON.parse(fs.readFile(packageJsonPath));
|
const packageJson = JSON.parse(fs.readFile(packageJsonPath));
|
||||||
propertiesToConsider.forEach(formatProperty => {
|
|
||||||
if (packageJson[formatProperty])
|
// Note: We are marking all supported properties as processed, even if they don't exist in the
|
||||||
markAsProcessed(fs, packageJson, packageJsonPath, formatProperty as EntryPointJsonProperty);
|
// `package.json` file. While this is redundant, it is also harmless.
|
||||||
});
|
markAsProcessed(fs, packageJson, packageJsonPath, SUPPORTED_FORMAT_PROPERTIES);
|
||||||
}
|
}
|
||||||
|
|
||||||
function logInvalidEntryPoints(logger: Logger, invalidEntryPoints: InvalidEntryPoint[]): void {
|
function logInvalidEntryPoints(logger: Logger, invalidEntryPoints: InvalidEntryPoint[]): void {
|
||||||
@ -260,3 +356,60 @@ function logInvalidEntryPoints(logger: Logger, invalidEntryPoints: InvalidEntryP
|
|||||||
invalidEntryPoint.missingDependencies.map(dep => ` - ${dep}`).join('\n'));
|
invalidEntryPoint.missingDependencies.map(dep => ` - ${dep}`).join('\n'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function computes and returns the following:
|
||||||
|
* - `propertiesToProcess`: An (ordered) list of properties that exist and need to be processed,
|
||||||
|
* based on the specified `propertiesToConsider`, the properties in `package.json` and their
|
||||||
|
* corresponding format-paths. NOTE: Only one property per format-path needs to be processed.
|
||||||
|
* - `propertyToPropertiesToMarkAsProcessed`: A mapping from each property in `propertiesToProcess`
|
||||||
|
* to the list of other properties in `package.json` that need to be marked as processed as soon
|
||||||
|
* as of the former being processed.
|
||||||
|
*/
|
||||||
|
function getPropertiesToProcessAndMarkAsProcessed(
|
||||||
|
packageJson: EntryPointPackageJson, propertiesToConsider: EntryPointJsonProperty[]): {
|
||||||
|
propertiesToProcess: EntryPointJsonProperty[];
|
||||||
|
propertyToPropertiesToMarkAsProcessed: Map<EntryPointJsonProperty, EntryPointJsonProperty[]>;
|
||||||
|
} {
|
||||||
|
const formatPathsToConsider = new Set<string>();
|
||||||
|
|
||||||
|
const propertiesToProcess: EntryPointJsonProperty[] = [];
|
||||||
|
for (const prop of propertiesToConsider) {
|
||||||
|
// Ignore properties that are not in `package.json`.
|
||||||
|
if (!packageJson.hasOwnProperty(prop)) continue;
|
||||||
|
|
||||||
|
const formatPath = packageJson[prop] !;
|
||||||
|
|
||||||
|
// Ignore properties that map to the same format-path as a preceding property.
|
||||||
|
if (formatPathsToConsider.has(formatPath)) continue;
|
||||||
|
|
||||||
|
// Process this property, because it is the first one to map to this format-path.
|
||||||
|
formatPathsToConsider.add(formatPath);
|
||||||
|
propertiesToProcess.push(prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatPathToProperties: {[formatPath: string]: EntryPointJsonProperty[]} = {};
|
||||||
|
for (const prop of SUPPORTED_FORMAT_PROPERTIES) {
|
||||||
|
// Ignore properties that are not in `package.json`.
|
||||||
|
if (!packageJson.hasOwnProperty(prop)) continue;
|
||||||
|
|
||||||
|
const formatPath = packageJson[prop] !;
|
||||||
|
|
||||||
|
// Ignore properties that do not map to a format-path that will be considered.
|
||||||
|
if (!formatPathsToConsider.has(formatPath)) continue;
|
||||||
|
|
||||||
|
// Add this property to the map.
|
||||||
|
const list = formatPathToProperties[formatPath] || (formatPathToProperties[formatPath] = []);
|
||||||
|
list.push(prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
const propertyToPropertiesToMarkAsProcessed =
|
||||||
|
new Map<EntryPointJsonProperty, EntryPointJsonProperty[]>();
|
||||||
|
for (const prop of propertiesToConsider) {
|
||||||
|
const formatPath = packageJson[prop] !;
|
||||||
|
const propertiesToMarkAsProcessed = formatPathToProperties[formatPath];
|
||||||
|
propertyToPropertiesToMarkAsProcessed.set(prop, propertiesToMarkAsProcessed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {propertiesToProcess, propertyToPropertiesToMarkAsProcessed};
|
||||||
|
}
|
||||||
|
@ -23,7 +23,7 @@ export const NGCC_VERSION = '0.0.0-PLACEHOLDER';
|
|||||||
* @throws Error if the entry-point has already been processed with a different ngcc version.
|
* @throws Error if the entry-point has already been processed with a different ngcc version.
|
||||||
*/
|
*/
|
||||||
export function hasBeenProcessed(
|
export function hasBeenProcessed(
|
||||||
packageJson: EntryPointPackageJson, format: EntryPointJsonProperty): boolean {
|
packageJson: EntryPointPackageJson, format: EntryPointJsonProperty | 'typings'): boolean {
|
||||||
if (!packageJson.__processed_by_ivy_ngcc__) {
|
if (!packageJson.__processed_by_ivy_ngcc__) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -38,17 +38,36 @@ export function hasBeenProcessed(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write a build marker for the given entry-point and format property, to indicate that it has
|
* Write a build marker for the given entry-point and format properties, to indicate that they have
|
||||||
* been compiled by this version of ngcc.
|
* been compiled by this version of ngcc.
|
||||||
*
|
*
|
||||||
* @param entryPoint the entry-point to write a marker.
|
* @param fs The current file-system being used.
|
||||||
* @param format the property in the package.json of the format for which we are writing the marker.
|
* @param packageJson The parsed contents of the `package.json` file for the entry-point.
|
||||||
|
* @param packageJsonPath The absolute path to the `package.json` file.
|
||||||
|
* @param properties The properties in the `package.json` of the formats for which we are writing
|
||||||
|
* the marker.
|
||||||
*/
|
*/
|
||||||
export function markAsProcessed(
|
export function markAsProcessed(
|
||||||
fs: FileSystem, packageJson: EntryPointPackageJson, packageJsonPath: AbsoluteFsPath,
|
fs: FileSystem, packageJson: EntryPointPackageJson, packageJsonPath: AbsoluteFsPath,
|
||||||
format: EntryPointJsonProperty) {
|
properties: (EntryPointJsonProperty | 'typings')[]) {
|
||||||
if (!packageJson.__processed_by_ivy_ngcc__) packageJson.__processed_by_ivy_ngcc__ = {};
|
const processed =
|
||||||
packageJson.__processed_by_ivy_ngcc__[format] = NGCC_VERSION;
|
packageJson.__processed_by_ivy_ngcc__ || (packageJson.__processed_by_ivy_ngcc__ = {});
|
||||||
|
|
||||||
|
for (const prop of properties) {
|
||||||
|
processed[prop] = NGCC_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scripts = packageJson.scripts || (packageJson.scripts = {});
|
||||||
|
scripts.prepublishOnly__ivy_ngcc_bak =
|
||||||
|
scripts.prepublishOnly__ivy_ngcc_bak || scripts.prepublishOnly;
|
||||||
|
|
||||||
|
scripts.prepublishOnly = 'node --eval \"console.error(\'' +
|
||||||
|
'ERROR: Trying to publish a package that has been compiled by NGCC. This is not allowed.\\n' +
|
||||||
|
'Please delete and rebuild the package, without compiling with NGCC, before attempting to publish.\\n' +
|
||||||
|
'Note that NGCC may have been run by importing this package into another project that is being built with Ivy enabled.\\n' +
|
||||||
|
'\')\" ' +
|
||||||
|
'&& exit 1';
|
||||||
|
|
||||||
// Just in case this package.json was synthesized due to a custom configuration
|
// Just in case this package.json was synthesized due to a custom configuration
|
||||||
// we will ensure that the path to the containing folder exists before we write the file.
|
// we will ensure that the path to the containing folder exists before we write the file.
|
||||||
fs.ensureDir(dirname(packageJsonPath));
|
fs.ensureDir(dirname(packageJsonPath));
|
||||||
|
@ -55,10 +55,11 @@ export interface PackageJsonFormatProperties {
|
|||||||
*/
|
*/
|
||||||
export interface EntryPointPackageJson extends PackageJsonFormatProperties {
|
export interface EntryPointPackageJson extends PackageJsonFormatProperties {
|
||||||
name: string;
|
name: string;
|
||||||
__processed_by_ivy_ngcc__?: {[key: string]: string};
|
scripts?: Record<string, string>;
|
||||||
|
__processed_by_ivy_ngcc__?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EntryPointJsonProperty = keyof(PackageJsonFormatProperties);
|
export type EntryPointJsonProperty = Exclude<keyof PackageJsonFormatProperties, 'types'|'typings'>;
|
||||||
// We need to keep the elements of this const and the `EntryPointJsonProperty` type in sync.
|
// We need to keep the elements of this const and the `EntryPointJsonProperty` type in sync.
|
||||||
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'];
|
||||||
@ -122,7 +123,8 @@ export function getEntryPointInfo(
|
|||||||
* @returns An entry-point format or `undefined` if none match the given property.
|
* @returns An entry-point format or `undefined` if none match the given property.
|
||||||
*/
|
*/
|
||||||
export function getEntryPointFormat(
|
export function getEntryPointFormat(
|
||||||
fs: FileSystem, entryPoint: EntryPoint, property: string): EntryPointFormat|undefined {
|
fs: FileSystem, entryPoint: EntryPoint, property: EntryPointJsonProperty): EntryPointFormat|
|
||||||
|
undefined {
|
||||||
switch (property) {
|
switch (property) {
|
||||||
case 'fesm2015':
|
case 'fesm2015':
|
||||||
return 'esm2015';
|
return 'esm2015';
|
||||||
|
@ -10,7 +10,7 @@ import {AbsoluteFsPath, FileSystem, absoluteFrom} from '../../../src/ngtsc/file_
|
|||||||
import {NgtscCompilerHost} from '../../../src/ngtsc/file_system/src/compiler_host';
|
import {NgtscCompilerHost} from '../../../src/ngtsc/file_system/src/compiler_host';
|
||||||
import {PathMappings} from '../utils';
|
import {PathMappings} from '../utils';
|
||||||
import {BundleProgram, makeBundleProgram} from './bundle_program';
|
import {BundleProgram, makeBundleProgram} from './bundle_program';
|
||||||
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty} from './entry_point';
|
import {EntryPoint, EntryPointFormat} from './entry_point';
|
||||||
import {NgccSourcesCompilerHost} from './ngcc_compiler_host';
|
import {NgccSourcesCompilerHost} from './ngcc_compiler_host';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,7 +19,6 @@ import {NgccSourcesCompilerHost} from './ngcc_compiler_host';
|
|||||||
*/
|
*/
|
||||||
export interface EntryPointBundle {
|
export interface EntryPointBundle {
|
||||||
entryPoint: EntryPoint;
|
entryPoint: EntryPoint;
|
||||||
formatProperty: EntryPointJsonProperty;
|
|
||||||
format: EntryPointFormat;
|
format: EntryPointFormat;
|
||||||
isCore: boolean;
|
isCore: boolean;
|
||||||
isFlatCore: boolean;
|
isFlatCore: boolean;
|
||||||
@ -34,7 +33,6 @@ export interface EntryPointBundle {
|
|||||||
* @param entryPoint The entry-point that contains the bundle.
|
* @param entryPoint The entry-point that contains the bundle.
|
||||||
* @param formatPath The path to the source files for this bundle.
|
* @param formatPath The path to the source files for this bundle.
|
||||||
* @param isCore This entry point is the Angular core package.
|
* @param isCore This entry point is the Angular core package.
|
||||||
* @param formatProperty The property in the package.json that holds the formatPath.
|
|
||||||
* @param format The underlying format of the bundle.
|
* @param format The underlying format of the bundle.
|
||||||
* @param transformDts Whether to transform the typings along with this bundle.
|
* @param transformDts Whether to transform the typings along with this bundle.
|
||||||
* @param pathMappings An optional set of mappings to use when compiling files.
|
* @param pathMappings An optional set of mappings to use when compiling files.
|
||||||
@ -43,8 +41,8 @@ export interface EntryPointBundle {
|
|||||||
*/
|
*/
|
||||||
export function makeEntryPointBundle(
|
export function makeEntryPointBundle(
|
||||||
fs: FileSystem, entryPoint: EntryPoint, formatPath: string, isCore: boolean,
|
fs: FileSystem, entryPoint: EntryPoint, formatPath: string, isCore: boolean,
|
||||||
formatProperty: EntryPointJsonProperty, format: EntryPointFormat, transformDts: boolean,
|
format: EntryPointFormat, transformDts: boolean, pathMappings?: PathMappings,
|
||||||
pathMappings?: PathMappings, mirrorDtsFromSrc: boolean = false): EntryPointBundle|null {
|
mirrorDtsFromSrc: boolean = false): EntryPointBundle {
|
||||||
// Create the TS program and necessary helpers.
|
// Create the TS program and necessary helpers.
|
||||||
const options: ts.CompilerOptions = {
|
const options: ts.CompilerOptions = {
|
||||||
allowJs: true,
|
allowJs: true,
|
||||||
@ -69,7 +67,7 @@ export function makeEntryPointBundle(
|
|||||||
null;
|
null;
|
||||||
const isFlatCore = isCore && src.r3SymbolsFile === null;
|
const isFlatCore = isCore && src.r3SymbolsFile === null;
|
||||||
|
|
||||||
return {entryPoint, format, formatProperty, rootDirs, isCore, isFlatCore, src, dts};
|
return {entryPoint, format, rootDirs, isCore, isFlatCore, src, dts};
|
||||||
}
|
}
|
||||||
|
|
||||||
function computePotentialDtsFilesFromJsFiles(
|
function computePotentialDtsFilesFromJsFiles(
|
||||||
@ -88,4 +86,4 @@ function computePotentialDtsFilesFromJsFiles(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return additionalFiles;
|
return additionalFiles;
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,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 {EntryPoint} from '../packages/entry_point';
|
import {EntryPointJsonProperty} from '../packages/entry_point';
|
||||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||||
import {FileToWrite} from '../rendering/utils';
|
import {FileToWrite} from '../rendering/utils';
|
||||||
|
|
||||||
@ -14,6 +14,7 @@ import {FileToWrite} from '../rendering/utils';
|
|||||||
* Responsible for writing out the transformed files to disk.
|
* Responsible for writing out the transformed files to disk.
|
||||||
*/
|
*/
|
||||||
export interface FileWriter {
|
export interface FileWriter {
|
||||||
writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileToWrite[]):
|
writeBundle(
|
||||||
void;
|
bundle: EntryPointBundle, transformedFiles: FileToWrite[],
|
||||||
|
formatProperties: EntryPointJsonProperty[]): void;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {FileSystem, absoluteFrom, dirname} from '../../../src/ngtsc/file_system';
|
import {FileSystem, absoluteFrom, dirname} from '../../../src/ngtsc/file_system';
|
||||||
import {EntryPoint} from '../packages/entry_point';
|
import {EntryPointJsonProperty} from '../packages/entry_point';
|
||||||
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
||||||
import {FileToWrite} from '../rendering/utils';
|
import {FileToWrite} from '../rendering/utils';
|
||||||
import {FileWriter} from './file_writer';
|
import {FileWriter} from './file_writer';
|
||||||
@ -19,7 +19,9 @@ import {FileWriter} from './file_writer';
|
|||||||
export class InPlaceFileWriter implements FileWriter {
|
export class InPlaceFileWriter implements FileWriter {
|
||||||
constructor(protected fs: FileSystem) {}
|
constructor(protected fs: FileSystem) {}
|
||||||
|
|
||||||
writeBundle(_entryPoint: EntryPoint, _bundle: EntryPointBundle, transformedFiles: FileToWrite[]) {
|
writeBundle(
|
||||||
|
_bundle: EntryPointBundle, transformedFiles: FileToWrite[],
|
||||||
|
_formatProperties?: EntryPointJsonProperty[]) {
|
||||||
transformedFiles.forEach(file => this.writeFileAndBackup(file));
|
transformedFiles.forEach(file => this.writeFileAndBackup(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,12 +25,15 @@ const NGCC_DIRECTORY = '__ivy_ngcc__';
|
|||||||
* `InPlaceFileWriter`).
|
* `InPlaceFileWriter`).
|
||||||
*/
|
*/
|
||||||
export class NewEntryPointFileWriter extends InPlaceFileWriter {
|
export class NewEntryPointFileWriter extends InPlaceFileWriter {
|
||||||
writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileToWrite[]) {
|
writeBundle(
|
||||||
|
bundle: EntryPointBundle, transformedFiles: FileToWrite[],
|
||||||
|
formatProperties: EntryPointJsonProperty[]) {
|
||||||
// The new folder is at the root of the overall package
|
// The new folder is at the root of the overall package
|
||||||
|
const entryPoint = bundle.entryPoint;
|
||||||
const ngccFolder = join(entryPoint.package, NGCC_DIRECTORY);
|
const ngccFolder = join(entryPoint.package, NGCC_DIRECTORY);
|
||||||
this.copyBundle(bundle, entryPoint.package, ngccFolder);
|
this.copyBundle(bundle, entryPoint.package, ngccFolder);
|
||||||
transformedFiles.forEach(file => this.writeFile(file, entryPoint.package, ngccFolder));
|
transformedFiles.forEach(file => this.writeFile(file, entryPoint.package, ngccFolder));
|
||||||
this.updatePackageJson(entryPoint, bundle.formatProperty, ngccFolder);
|
this.updatePackageJson(entryPoint, formatProperties, ngccFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected copyBundle(
|
protected copyBundle(
|
||||||
@ -60,12 +63,18 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected updatePackageJson(
|
protected updatePackageJson(
|
||||||
entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty, ngccFolder: AbsoluteFsPath) {
|
entryPoint: EntryPoint, formatProperties: EntryPointJsonProperty[],
|
||||||
const formatPath = join(entryPoint.path, entryPoint.packageJson[formatProperty] !);
|
ngccFolder: AbsoluteFsPath) {
|
||||||
const newFormatPath = join(ngccFolder, relative(entryPoint.package, formatPath));
|
const packageJson = entryPoint.packageJson;
|
||||||
const newFormatProperty = formatProperty + '_ivy_ngcc';
|
|
||||||
(entryPoint.packageJson as any)[newFormatProperty] = relative(entryPoint.path, newFormatPath);
|
for (const formatProperty of formatProperties) {
|
||||||
|
const formatPath = join(entryPoint.path, packageJson[formatProperty] !);
|
||||||
|
const newFormatPath = join(ngccFolder, relative(entryPoint.package, formatPath));
|
||||||
|
const newFormatProperty = formatProperty + '_ivy_ngcc';
|
||||||
|
(packageJson as any)[newFormatProperty] = relative(entryPoint.path, newFormatPath);
|
||||||
|
}
|
||||||
|
|
||||||
this.fs.writeFile(
|
this.fs.writeFile(
|
||||||
join(entryPoint.path, 'package.json'), JSON.stringify(entryPoint.packageJson));
|
join(entryPoint.path, 'package.json'), `${JSON.stringify(packageJson, null, 2)}\n`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,8 +96,7 @@ runInEachFileSystem(() => {
|
|||||||
loadTestFiles(testFiles);
|
loadTestFiles(testFiles);
|
||||||
loadFakeCore(getFileSystem());
|
loadFakeCore(getFileSystem());
|
||||||
const rootFiles = getRootFiles(testFiles);
|
const rootFiles = getRootFiles(testFiles);
|
||||||
const bundle =
|
const bundle = makeTestEntryPointBundle('test-package', 'esm2015', false, rootFiles);
|
||||||
makeTestEntryPointBundle('test-package', 'es2015', 'esm2015', false, rootFiles);
|
|
||||||
program = bundle.src.program;
|
program = bundle.src.program;
|
||||||
|
|
||||||
const reflectionHost =
|
const reflectionHost =
|
||||||
@ -373,4 +372,4 @@ class MockMigration implements Migration {
|
|||||||
this.log.push(`${this.name}:${clazz.name.text}`);
|
this.log.push(`${this.name}:${clazz.name.text}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -330,7 +330,7 @@ runInEachFileSystem(() => {
|
|||||||
loadTestFiles(TEST_PROGRAM);
|
loadTestFiles(TEST_PROGRAM);
|
||||||
loadTestFiles(TEST_DTS_PROGRAM);
|
loadTestFiles(TEST_DTS_PROGRAM);
|
||||||
const bundle = makeTestEntryPointBundle(
|
const bundle = makeTestEntryPointBundle(
|
||||||
'test-package', 'esm2015', 'esm2015', false, getRootFiles(TEST_PROGRAM),
|
'test-package', 'esm2015', false, getRootFiles(TEST_PROGRAM),
|
||||||
getRootFiles(TEST_DTS_PROGRAM));
|
getRootFiles(TEST_DTS_PROGRAM));
|
||||||
program = bundle.src.program;
|
program = bundle.src.program;
|
||||||
dtsProgram = bundle.dts;
|
dtsProgram = bundle.dts;
|
||||||
|
@ -236,8 +236,7 @@ runInEachFileSystem(() => {
|
|||||||
loadTestFiles(jsProgram);
|
loadTestFiles(jsProgram);
|
||||||
loadTestFiles(dtsProgram);
|
loadTestFiles(dtsProgram);
|
||||||
const {src: {program}, dts} = makeTestEntryPointBundle(
|
const {src: {program}, dts} = makeTestEntryPointBundle(
|
||||||
'test-package', 'esm2015', 'esm2015', false, getRootFiles(jsProgram),
|
'test-package', 'esm2015', false, getRootFiles(jsProgram), getRootFiles(dtsProgram));
|
||||||
getRootFiles(dtsProgram));
|
|
||||||
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
|
||||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||||
const analyzer = new PrivateDeclarationsAnalyzer(host, referencesRegistry);
|
const analyzer = new PrivateDeclarationsAnalyzer(host, referencesRegistry);
|
||||||
|
@ -72,7 +72,7 @@ runInEachFileSystem(() => {
|
|||||||
it('should check for switchable markers in all the files of the program', () => {
|
it('should check for switchable markers in all the files of the program', () => {
|
||||||
loadTestFiles(TEST_PROGRAM);
|
loadTestFiles(TEST_PROGRAM);
|
||||||
const bundle = makeTestEntryPointBundle(
|
const bundle = makeTestEntryPointBundle(
|
||||||
'test', 'esm2015', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
|
'test', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
|
||||||
const program = bundle.src.program;
|
const program = bundle.src.program;
|
||||||
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||||
const analyzer = new SwitchMarkerAnalyzer(host, bundle.entryPoint.package);
|
const analyzer = new SwitchMarkerAnalyzer(host, bundle.entryPoint.package);
|
||||||
@ -103,7 +103,7 @@ runInEachFileSystem(() => {
|
|||||||
it('should ignore files that are outside the package', () => {
|
it('should ignore files that are outside the package', () => {
|
||||||
loadTestFiles(TEST_PROGRAM);
|
loadTestFiles(TEST_PROGRAM);
|
||||||
const bundle = makeTestEntryPointBundle(
|
const bundle = makeTestEntryPointBundle(
|
||||||
'test', 'esm2015', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
|
'test', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
|
||||||
const program = bundle.src.program;
|
const program = bundle.src.program;
|
||||||
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||||
const analyzer = new SwitchMarkerAnalyzer(host, bundle.entryPoint.package);
|
const analyzer = new SwitchMarkerAnalyzer(host, bundle.entryPoint.package);
|
||||||
@ -114,4 +114,4 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -179,6 +179,32 @@ runInEachFileSystem(() => {
|
|||||||
expect(sorted.entryPoints).toEqual([fifth]);
|
expect(sorted.entryPoints).toEqual([fifth]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not process the provided target if it has missing dependencies', () => {
|
||||||
|
spyOn(host, 'findDependencies').and.callFake(createFakeComputeDependencies({
|
||||||
|
[_('/first/index.js')]: {resolved: [], missing: ['/missing']},
|
||||||
|
}));
|
||||||
|
const entryPoints = [first];
|
||||||
|
let sorted: SortedEntryPointsInfo;
|
||||||
|
|
||||||
|
sorted = resolver.sortEntryPointsByDependency(entryPoints, first);
|
||||||
|
expect(sorted.entryPoints).toEqual([]);
|
||||||
|
expect(sorted.invalidEntryPoints[0].entryPoint).toEqual(first);
|
||||||
|
expect(sorted.invalidEntryPoints[0].missingDependencies).toEqual(['/missing']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not consider builtin NodeJS modules as missing dependency', () => {
|
||||||
|
spyOn(host, 'findDependencies').and.callFake(createFakeComputeDependencies({
|
||||||
|
[_('/first/index.js')]: {resolved: [], missing: ['fs']},
|
||||||
|
}));
|
||||||
|
const entryPoints = [first];
|
||||||
|
let sorted: SortedEntryPointsInfo;
|
||||||
|
|
||||||
|
sorted = resolver.sortEntryPointsByDependency(entryPoints, first);
|
||||||
|
expect(sorted.entryPoints).toEqual([first]);
|
||||||
|
expect(sorted.invalidEntryPoints).toEqual([]);
|
||||||
|
expect(sorted.ignoredDependencies).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should use the appropriate DependencyHost for each entry-point', () => {
|
it('should use the appropriate DependencyHost for each entry-point', () => {
|
||||||
const esm5Host = new EsmDependencyHost(fs, moduleResolver);
|
const esm5Host = new EsmDependencyHost(fs, moduleResolver);
|
||||||
const esm2015Host = new EsmDependencyHost(fs, moduleResolver);
|
const esm2015Host = new EsmDependencyHost(fs, moduleResolver);
|
||||||
|
@ -9,7 +9,7 @@ import * as ts from 'typescript';
|
|||||||
import {AbsoluteFsPath, NgtscCompilerHost, absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
|
import {AbsoluteFsPath, NgtscCompilerHost, absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
|
||||||
import {TestFile} from '../../../src/ngtsc/file_system/testing';
|
import {TestFile} from '../../../src/ngtsc/file_system/testing';
|
||||||
import {BundleProgram, makeBundleProgram} from '../../src/packages/bundle_program';
|
import {BundleProgram, makeBundleProgram} from '../../src/packages/bundle_program';
|
||||||
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty} from '../../src/packages/entry_point';
|
import {EntryPoint, EntryPointFormat} from '../../src/packages/entry_point';
|
||||||
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
|
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
|
||||||
import {NgccSourcesCompilerHost} from '../../src/packages/ngcc_compiler_host';
|
import {NgccSourcesCompilerHost} from '../../src/packages/ngcc_compiler_host';
|
||||||
|
|
||||||
@ -32,19 +32,13 @@ export function makeTestEntryPoint(
|
|||||||
* @param dtsFiles The typings files to include the bundle.
|
* @param dtsFiles The typings files to include the bundle.
|
||||||
*/
|
*/
|
||||||
export function makeTestEntryPointBundle(
|
export function makeTestEntryPointBundle(
|
||||||
packageName: string, formatProperty: EntryPointJsonProperty, format: EntryPointFormat,
|
packageName: string, format: EntryPointFormat, isCore: boolean, srcRootNames: AbsoluteFsPath[],
|
||||||
isCore: boolean, srcRootNames: AbsoluteFsPath[],
|
|
||||||
dtsRootNames?: AbsoluteFsPath[]): EntryPointBundle {
|
dtsRootNames?: AbsoluteFsPath[]): EntryPointBundle {
|
||||||
const entryPoint = makeTestEntryPoint(packageName);
|
const entryPoint = makeTestEntryPoint(packageName);
|
||||||
const src = makeTestBundleProgram(srcRootNames[0], isCore);
|
const src = makeTestBundleProgram(srcRootNames[0], isCore);
|
||||||
const dts = dtsRootNames ? makeTestDtsBundleProgram(dtsRootNames[0], isCore) : null;
|
const dts = dtsRootNames ? makeTestDtsBundleProgram(dtsRootNames[0], isCore) : null;
|
||||||
const isFlatCore = isCore && src.r3SymbolsFile === null;
|
const isFlatCore = isCore && src.r3SymbolsFile === null;
|
||||||
return {
|
return {entryPoint, format, rootDirs: [absoluteFrom('/')], src, dts, isCore, isFlatCore};
|
||||||
entryPoint,
|
|
||||||
formatProperty,
|
|
||||||
format,
|
|
||||||
rootDirs: [absoluteFrom('/')], src, dts, isCore, isFlatCore
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeTestBundleProgram(
|
export function makeTestBundleProgram(
|
||||||
|
@ -86,12 +86,26 @@ runInEachFileSystem(() => {
|
|||||||
// `test-package` has no Angular but is marked as processed.
|
// `test-package` has no Angular but is marked as processed.
|
||||||
expect(loadPackage('test-package').__processed_by_ivy_ngcc__).toEqual({
|
expect(loadPackage('test-package').__processed_by_ivy_ngcc__).toEqual({
|
||||||
es2015: '0.0.0-PLACEHOLDER',
|
es2015: '0.0.0-PLACEHOLDER',
|
||||||
|
esm2015: '0.0.0-PLACEHOLDER',
|
||||||
|
esm5: '0.0.0-PLACEHOLDER',
|
||||||
|
fesm2015: '0.0.0-PLACEHOLDER',
|
||||||
|
fesm5: '0.0.0-PLACEHOLDER',
|
||||||
|
main: '0.0.0-PLACEHOLDER',
|
||||||
|
module: '0.0.0-PLACEHOLDER',
|
||||||
});
|
});
|
||||||
|
|
||||||
// * `core` is a dependency of `test-package`, but it is not processed, since test-package
|
// * `core` is a dependency of `test-package`, but it is not processed, since test-package
|
||||||
// was not processed.
|
// was not processed.
|
||||||
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeUndefined();
|
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should report an error if a dependency of the target does not exist', () => {
|
||||||
|
expect(() => {
|
||||||
|
mainNgcc({basePath: '/node_modules', targetEntryPointPath: 'invalid-package'});
|
||||||
|
})
|
||||||
|
.toThrowError(
|
||||||
|
'The target entry-point "invalid-package" has missing dependencies:\n - @angular/missing\n');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('early skipping of target entry-point', () => {
|
describe('early skipping of target entry-point', () => {
|
||||||
@ -157,6 +171,23 @@ runInEachFileSystem(() => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should skip all processing if the first matching `propertyToConsider` is marked as processed',
|
||||||
|
() => {
|
||||||
|
const logger = new MockLogger();
|
||||||
|
markPropertiesAsProcessed('@angular/common/http/testing', ['esm2015']);
|
||||||
|
mainNgcc({
|
||||||
|
basePath: '/node_modules',
|
||||||
|
targetEntryPointPath: '@angular/common/http/testing',
|
||||||
|
// Simulate a property that does not exist on the package.json and will be ignored.
|
||||||
|
propertiesToConsider: ['missing', 'esm2015', 'esm5'],
|
||||||
|
compileAllFormats: false, logger,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(logger.logs.debug).toContain([
|
||||||
|
'The target entry-point has already been processed'
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -164,13 +195,22 @@ runInEachFileSystem(() => {
|
|||||||
const basePath = _('/node_modules');
|
const basePath = _('/node_modules');
|
||||||
const targetPackageJsonPath = join(basePath, packagePath, 'package.json');
|
const targetPackageJsonPath = join(basePath, packagePath, 'package.json');
|
||||||
const targetPackage = loadPackage(packagePath);
|
const targetPackage = loadPackage(packagePath);
|
||||||
markAsProcessed(fs, targetPackage, targetPackageJsonPath, 'typings');
|
markAsProcessed(fs, targetPackage, targetPackageJsonPath, ['typings', ...properties]);
|
||||||
properties.forEach(
|
|
||||||
property => markAsProcessed(fs, targetPackage, targetPackageJsonPath, property));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
describe('with propertiesToConsider', () => {
|
describe('with propertiesToConsider', () => {
|
||||||
|
it('should complain if none of the properties in the `propertiesToConsider` list is supported',
|
||||||
|
() => {
|
||||||
|
const propertiesToConsider = ['es1337', 'fesm42'];
|
||||||
|
const errorMessage =
|
||||||
|
'No supported format property to consider among [es1337, fesm42]. Supported ' +
|
||||||
|
'properties: fesm2015, fesm5, es2015, esm2015, esm5, main, module';
|
||||||
|
|
||||||
|
expect(() => mainNgcc({basePath: '/node_modules', propertiesToConsider}))
|
||||||
|
.toThrowError(errorMessage);
|
||||||
|
});
|
||||||
|
|
||||||
it('should only compile the entry-point formats given in the `propertiesToConsider` list',
|
it('should only compile the entry-point formats given in the `propertiesToConsider` list',
|
||||||
() => {
|
() => {
|
||||||
mainNgcc({
|
mainNgcc({
|
||||||
@ -210,6 +250,33 @@ runInEachFileSystem(() => {
|
|||||||
typings: '0.0.0-PLACEHOLDER',
|
typings: '0.0.0-PLACEHOLDER',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should mark all matching properties as processed in order not to compile them on a subsequent run',
|
||||||
|
() => {
|
||||||
|
const logger = new MockLogger();
|
||||||
|
const logs = logger.logs.debug;
|
||||||
|
|
||||||
|
// `fesm2015` and `es2015` map to the same file: `./fesm2015/common.js`
|
||||||
|
mainNgcc({
|
||||||
|
basePath: '/node_modules/@angular/common',
|
||||||
|
propertiesToConsider: ['fesm2015'], logger,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(logs).not.toContain(['Skipping @angular/common : es2015 (already compiled).']);
|
||||||
|
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({
|
||||||
|
es2015: '0.0.0-PLACEHOLDER',
|
||||||
|
fesm2015: '0.0.0-PLACEHOLDER',
|
||||||
|
typings: '0.0.0-PLACEHOLDER',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now, compiling `es2015` should be a no-op.
|
||||||
|
mainNgcc({
|
||||||
|
basePath: '/node_modules/@angular/common',
|
||||||
|
propertiesToConsider: ['es2015'], logger,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(logs).toContain(['Skipping @angular/common : es2015 (already compiled).']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with compileAllFormats set to false', () => {
|
describe('with compileAllFormats set to false', () => {
|
||||||
@ -256,6 +323,7 @@ runInEachFileSystem(() => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
|
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
|
||||||
|
fesm5: '0.0.0-PLACEHOLDER',
|
||||||
module: '0.0.0-PLACEHOLDER',
|
module: '0.0.0-PLACEHOLDER',
|
||||||
typings: '0.0.0-PLACEHOLDER',
|
typings: '0.0.0-PLACEHOLDER',
|
||||||
});
|
});
|
||||||
@ -268,6 +336,7 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
|
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
|
||||||
esm5: '0.0.0-PLACEHOLDER',
|
esm5: '0.0.0-PLACEHOLDER',
|
||||||
|
fesm5: '0.0.0-PLACEHOLDER',
|
||||||
module: '0.0.0-PLACEHOLDER',
|
module: '0.0.0-PLACEHOLDER',
|
||||||
typings: '0.0.0-PLACEHOLDER',
|
typings: '0.0.0-PLACEHOLDER',
|
||||||
});
|
});
|
||||||
@ -313,6 +382,28 @@ runInEachFileSystem(() => {
|
|||||||
.toMatch(ANGULAR_CORE_IMPORT_REGEX);
|
.toMatch(ANGULAR_CORE_IMPORT_REGEX);
|
||||||
expect(fs.exists(_(`/node_modules/@angular/common/common.d.ts.__ivy_ngcc_bak`))).toBe(true);
|
expect(fs.exists(_(`/node_modules/@angular/common/common.d.ts.__ivy_ngcc_bak`))).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update `package.json` for all matching format properties', () => {
|
||||||
|
mainNgcc({
|
||||||
|
basePath: '/node_modules/@angular/core',
|
||||||
|
createNewEntryPointFormats: true,
|
||||||
|
propertiesToConsider: ['fesm2015', 'fesm5'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const pkg: any = loadPackage('@angular/core');
|
||||||
|
|
||||||
|
// `es2015` is an alias of `fesm2015`.
|
||||||
|
expect(pkg.fesm2015).toEqual('./fesm2015/core.js');
|
||||||
|
expect(pkg.es2015).toEqual('./fesm2015/core.js');
|
||||||
|
expect(pkg.fesm2015_ivy_ngcc).toEqual('__ivy_ngcc__/fesm2015/core.js');
|
||||||
|
expect(pkg.es2015_ivy_ngcc).toEqual('__ivy_ngcc__/fesm2015/core.js');
|
||||||
|
|
||||||
|
// `module` is an alias of `fesm5`.
|
||||||
|
expect(pkg.fesm5).toEqual('./fesm5/core.js');
|
||||||
|
expect(pkg.module).toEqual('./fesm5/core.js');
|
||||||
|
expect(pkg.fesm5_ivy_ngcc).toEqual('__ivy_ngcc__/fesm5/core.js');
|
||||||
|
expect(pkg.module_ivy_ngcc).toEqual('__ivy_ngcc__/fesm5/core.js');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('logger', () => {
|
describe('logger', () => {
|
||||||
@ -342,6 +433,7 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
|
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
|
||||||
es2015: '0.0.0-PLACEHOLDER',
|
es2015: '0.0.0-PLACEHOLDER',
|
||||||
|
fesm2015: '0.0.0-PLACEHOLDER',
|
||||||
typings: '0.0.0-PLACEHOLDER',
|
typings: '0.0.0-PLACEHOLDER',
|
||||||
});
|
});
|
||||||
expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toEqual({
|
expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toEqual({
|
||||||
@ -399,6 +491,7 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
|
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
|
||||||
es2015: '0.0.0-PLACEHOLDER',
|
es2015: '0.0.0-PLACEHOLDER',
|
||||||
|
fesm2015: '0.0.0-PLACEHOLDER',
|
||||||
typings: '0.0.0-PLACEHOLDER',
|
typings: '0.0.0-PLACEHOLDER',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -425,6 +518,7 @@ runInEachFileSystem(() => {
|
|||||||
// We process core but not core/testing.
|
// We process core but not core/testing.
|
||||||
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
|
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
|
||||||
es2015: '0.0.0-PLACEHOLDER',
|
es2015: '0.0.0-PLACEHOLDER',
|
||||||
|
fesm2015: '0.0.0-PLACEHOLDER',
|
||||||
typings: '0.0.0-PLACEHOLDER',
|
typings: '0.0.0-PLACEHOLDER',
|
||||||
});
|
});
|
||||||
expect(loadPackage('@angular/core/testing').__processed_by_ivy_ngcc__).toBeUndefined();
|
expect(loadPackage('@angular/core/testing').__processed_by_ivy_ngcc__).toBeUndefined();
|
||||||
@ -432,6 +526,7 @@ runInEachFileSystem(() => {
|
|||||||
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toBeUndefined();
|
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toBeUndefined();
|
||||||
expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({
|
expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({
|
||||||
es2015: '0.0.0-PLACEHOLDER',
|
es2015: '0.0.0-PLACEHOLDER',
|
||||||
|
fesm2015: '0.0.0-PLACEHOLDER',
|
||||||
typings: '0.0.0-PLACEHOLDER',
|
typings: '0.0.0-PLACEHOLDER',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -482,6 +577,30 @@ runInEachFileSystem(() => {
|
|||||||
contents: `export declare class AppComponent {};`
|
contents: `export declare class AppComponent {};`
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// An Angular package that has a missing dependency
|
||||||
|
loadTestFiles([
|
||||||
|
{
|
||||||
|
name: _('/node_modules/invalid-package/package.json'),
|
||||||
|
contents: '{"name": "invalid-package", "es2015": "./index.js", "typings": "./index.d.ts"}'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: _('/node_modules/invalid-package/index.js'),
|
||||||
|
contents: `
|
||||||
|
import {AppModule} from "@angular/missing";
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
export class AppComponent {};
|
||||||
|
AppComponent.decorators = [
|
||||||
|
{ type: Component, args: [{selector: 'app', template: '<h2>Hello</h2>'}] }
|
||||||
|
];
|
||||||
|
`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: _('/node_modules/invalid-package/index.d.ts'),
|
||||||
|
contents: `export declare class AppComponent {}`
|
||||||
|
},
|
||||||
|
{name: _('/node_modules/invalid-package/index.metadata.json'), contents: 'DUMMY DATA'},
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -161,7 +161,7 @@ runInEachFileSystem(() => {
|
|||||||
loadFakeCore(getFileSystem());
|
loadFakeCore(getFileSystem());
|
||||||
const errors: ts.Diagnostic[] = [];
|
const errors: ts.Diagnostic[] = [];
|
||||||
const rootFiles = getRootFiles(testFiles);
|
const rootFiles = getRootFiles(testFiles);
|
||||||
const bundle = makeTestEntryPointBundle('test-package', 'es2015', 'esm2015', false, rootFiles);
|
const bundle = makeTestEntryPointBundle('test-package', 'esm2015', false, rootFiles);
|
||||||
const program = bundle.src.program;
|
const program = bundle.src.program;
|
||||||
|
|
||||||
const reflectionHost =
|
const reflectionHost =
|
||||||
|
@ -79,31 +79,73 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('markAsProcessed', () => {
|
describe('markAsProcessed', () => {
|
||||||
it('should write a property in the package.json containing the version placeholder', () => {
|
it('should write properties in the package.json containing the version placeholder', () => {
|
||||||
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
|
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
|
||||||
const fs = getFileSystem();
|
const fs = getFileSystem();
|
||||||
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
||||||
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
|
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
|
||||||
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
|
expect(pkg.scripts).toBeUndefined();
|
||||||
|
|
||||||
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'fesm2015');
|
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5']);
|
||||||
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
||||||
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
|
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
|
||||||
|
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
|
||||||
|
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBeUndefined();
|
||||||
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined();
|
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined();
|
||||||
|
expect(pkg.scripts.prepublishOnly).toBeDefined();
|
||||||
|
|
||||||
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'esm5');
|
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['esm2015', 'esm5']);
|
||||||
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
||||||
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
|
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
|
||||||
expect(pkg.__processed_by_ivy_ngcc__.esm5).toEqual('0.0.0-PLACEHOLDER');
|
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
|
||||||
|
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBe('0.0.0-PLACEHOLDER');
|
||||||
|
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBe('0.0.0-PLACEHOLDER');
|
||||||
|
expect(pkg.scripts.prepublishOnly).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update the packageJson object in-place', () => {
|
it('should update the packageJson object in-place', () => {
|
||||||
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
|
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
|
||||||
const fs = getFileSystem();
|
const fs = getFileSystem();
|
||||||
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
const pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
||||||
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
|
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
|
||||||
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'fesm2015');
|
expect(pkg.scripts).toBeUndefined();
|
||||||
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
|
|
||||||
|
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5']);
|
||||||
|
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
|
||||||
|
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
|
||||||
|
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBeUndefined();
|
||||||
|
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined();
|
||||||
|
expect(pkg.scripts.prepublishOnly).toBeDefined();
|
||||||
|
|
||||||
|
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['esm2015', 'esm5']);
|
||||||
|
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
|
||||||
|
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
|
||||||
|
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBe('0.0.0-PLACEHOLDER');
|
||||||
|
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBe('0.0.0-PLACEHOLDER');
|
||||||
|
expect(pkg.scripts.prepublishOnly).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should one perform one write operation for all updated properties', () => {
|
||||||
|
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
|
||||||
|
const fs = getFileSystem();
|
||||||
|
const writeFileSpy = spyOn(fs, 'writeFile');
|
||||||
|
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
||||||
|
|
||||||
|
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5', 'esm2015', 'esm5']);
|
||||||
|
expect(writeFileSpy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should keep backup of existing 'prepublishOnly' script`, () => {
|
||||||
|
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
|
||||||
|
const fs = getFileSystem();
|
||||||
|
const prepublishOnly = 'existing script';
|
||||||
|
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
||||||
|
pkg.scripts = {prepublishOnly};
|
||||||
|
|
||||||
|
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015']);
|
||||||
|
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
|
||||||
|
expect(pkg.scripts.prepublishOnly).toContain('This is not allowed');
|
||||||
|
expect(pkg.scripts.prepublishOnly__ivy_ngcc_bak).toBe(prepublishOnly);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -144,8 +144,7 @@ runInEachFileSystem(() => {
|
|||||||
typings: absoluteFrom('/node_modules/test/index.d.ts'),
|
typings: absoluteFrom('/node_modules/test/index.d.ts'),
|
||||||
compiledByAngular: true,
|
compiledByAngular: true,
|
||||||
};
|
};
|
||||||
const esm5bundle =
|
const esm5bundle = makeEntryPointBundle(fs, entryPoint, './index.js', false, 'esm5', true);
|
||||||
makeEntryPointBundle(fs, entryPoint, './index.js', false, 'esm5', 'esm5', true) !;
|
|
||||||
|
|
||||||
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
|
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
|
||||||
.toEqual(jasmine.arrayWithExactContents([
|
.toEqual(jasmine.arrayWithExactContents([
|
||||||
@ -192,8 +191,8 @@ runInEachFileSystem(() => {
|
|||||||
compiledByAngular: true,
|
compiledByAngular: true,
|
||||||
};
|
};
|
||||||
const esm5bundle = makeEntryPointBundle(
|
const esm5bundle = makeEntryPointBundle(
|
||||||
fs, entryPoint, './index.js', false, 'esm5', 'esm5', /* transformDts */ true,
|
fs, entryPoint, './index.js', false, 'esm5', /* transformDts */ true,
|
||||||
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ true) !;
|
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ true);
|
||||||
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
|
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
|
||||||
.toContain(absoluteFrom('/node_modules/test/internal.js'));
|
.toContain(absoluteFrom('/node_modules/test/internal.js'));
|
||||||
expect(esm5bundle.dts !.program.getSourceFiles().map(sf => sf.fileName))
|
expect(esm5bundle.dts !.program.getSourceFiles().map(sf => sf.fileName))
|
||||||
@ -213,8 +212,8 @@ runInEachFileSystem(() => {
|
|||||||
compiledByAngular: true,
|
compiledByAngular: true,
|
||||||
};
|
};
|
||||||
const esm5bundle = makeEntryPointBundle(
|
const esm5bundle = makeEntryPointBundle(
|
||||||
fs, entryPoint, './index.js', false, 'esm5', 'esm5', /* transformDts */ true,
|
fs, entryPoint, './index.js', false, 'esm5', /* transformDts */ true,
|
||||||
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ false) !;
|
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ false);
|
||||||
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
|
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
|
||||||
.toContain(absoluteFrom('/node_modules/test/internal.js'));
|
.toContain(absoluteFrom('/node_modules/test/internal.js'));
|
||||||
expect(esm5bundle.dts !.program.getSourceFiles().map(sf => sf.fileName))
|
expect(esm5bundle.dts !.program.getSourceFiles().map(sf => sf.fileName))
|
||||||
|
@ -149,8 +149,7 @@ exports.D = D;
|
|||||||
loadTestFiles([file]);
|
loadTestFiles([file]);
|
||||||
const fs = getFileSystem();
|
const fs = getFileSystem();
|
||||||
const logger = new MockLogger();
|
const logger = new MockLogger();
|
||||||
const bundle =
|
const bundle = makeTestEntryPointBundle('test-package', 'commonjs', false, [file.name]);
|
||||||
makeTestEntryPointBundle('test-package', 'module', 'commonjs', false, [file.name]);
|
|
||||||
const typeChecker = bundle.src.program.getTypeChecker();
|
const typeChecker = bundle.src.program.getTypeChecker();
|
||||||
const host = new CommonJsReflectionHost(logger, false, bundle.src.program, bundle.src.host);
|
const host = new CommonJsReflectionHost(logger, false, bundle.src.program, bundle.src.host);
|
||||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||||
|
@ -61,8 +61,7 @@ function createTestRenderer(
|
|||||||
const fs = getFileSystem();
|
const fs = getFileSystem();
|
||||||
const isCore = packageName === '@angular/core';
|
const isCore = packageName === '@angular/core';
|
||||||
const bundle = makeTestEntryPointBundle(
|
const bundle = makeTestEntryPointBundle(
|
||||||
'test-package', 'es2015', 'esm2015', isCore, getRootFiles(files),
|
'test-package', 'esm2015', isCore, getRootFiles(files), dtsFiles && getRootFiles(dtsFiles));
|
||||||
dtsFiles && getRootFiles(dtsFiles));
|
|
||||||
const typeChecker = bundle.src.program.getTypeChecker();
|
const typeChecker = bundle.src.program.getTypeChecker();
|
||||||
const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts);
|
const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts);
|
||||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||||
|
@ -26,7 +26,7 @@ function setup(file: {name: AbsoluteFsPath, contents: string}) {
|
|||||||
loadTestFiles([file]);
|
loadTestFiles([file]);
|
||||||
const fs = getFileSystem();
|
const fs = getFileSystem();
|
||||||
const logger = new MockLogger();
|
const logger = new MockLogger();
|
||||||
const bundle = makeTestEntryPointBundle('test-package', 'module', 'esm5', false, [file.name]);
|
const bundle = makeTestEntryPointBundle('test-package', 'esm5', false, [file.name]);
|
||||||
const typeChecker = bundle.src.program.getTypeChecker();
|
const typeChecker = bundle.src.program.getTypeChecker();
|
||||||
const host = new Esm5ReflectionHost(logger, false, typeChecker);
|
const host = new Esm5ReflectionHost(logger, false, typeChecker);
|
||||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||||
|
@ -31,8 +31,7 @@ function setup(files: TestFile[], dtsFiles?: TestFile[]) {
|
|||||||
}
|
}
|
||||||
const logger = new MockLogger();
|
const logger = new MockLogger();
|
||||||
const bundle = makeTestEntryPointBundle(
|
const bundle = makeTestEntryPointBundle(
|
||||||
'test-package', 'es2015', 'esm2015', false, getRootFiles(files),
|
'test-package', 'esm2015', false, getRootFiles(files), dtsFiles && getRootFiles(dtsFiles)) !;
|
||||||
dtsFiles && getRootFiles(dtsFiles)) !;
|
|
||||||
const typeChecker = bundle.src.program.getTypeChecker();
|
const typeChecker = bundle.src.program.getTypeChecker();
|
||||||
const host = new Esm2015ReflectionHost(logger, false, typeChecker, bundle.dts);
|
const host = new Esm2015ReflectionHost(logger, false, typeChecker, bundle.dts);
|
||||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||||
|
@ -63,8 +63,7 @@ function createTestRenderer(
|
|||||||
const fs = getFileSystem();
|
const fs = getFileSystem();
|
||||||
const isCore = packageName === '@angular/core';
|
const isCore = packageName === '@angular/core';
|
||||||
const bundle = makeTestEntryPointBundle(
|
const bundle = makeTestEntryPointBundle(
|
||||||
'test-package', 'es2015', 'esm2015', isCore, getRootFiles(files),
|
'test-package', 'esm2015', isCore, getRootFiles(files), dtsFiles && getRootFiles(dtsFiles));
|
||||||
dtsFiles && getRootFiles(dtsFiles));
|
|
||||||
const typeChecker = bundle.src.program.getTypeChecker();
|
const typeChecker = bundle.src.program.getTypeChecker();
|
||||||
const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts);
|
const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts);
|
||||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||||
|
@ -25,7 +25,7 @@ function setup(file: TestFile) {
|
|||||||
loadTestFiles([file]);
|
loadTestFiles([file]);
|
||||||
const fs = getFileSystem();
|
const fs = getFileSystem();
|
||||||
const logger = new MockLogger();
|
const logger = new MockLogger();
|
||||||
const bundle = makeTestEntryPointBundle('test-package', 'esm5', 'esm5', false, [file.name]);
|
const bundle = makeTestEntryPointBundle('test-package', 'esm5', false, [file.name]);
|
||||||
const src = bundle.src;
|
const src = bundle.src;
|
||||||
const host = new UmdReflectionHost(logger, false, src.program, src.host);
|
const host = new UmdReflectionHost(logger, false, src.program, src.host);
|
||||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
|
import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
|
||||||
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 {EntryPoint} from '../../src/packages/entry_point';
|
|
||||||
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
|
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
|
||||||
import {InPlaceFileWriter} from '../../src/writing/in_place_file_writer';
|
import {InPlaceFileWriter} from '../../src/writing/in_place_file_writer';
|
||||||
|
|
||||||
@ -32,7 +31,7 @@ runInEachFileSystem(() => {
|
|||||||
it('should write all the FileInfo to the disk', () => {
|
it('should write all the FileInfo to the disk', () => {
|
||||||
const fs = getFileSystem();
|
const fs = getFileSystem();
|
||||||
const fileWriter = new InPlaceFileWriter(fs);
|
const fileWriter = new InPlaceFileWriter(fs);
|
||||||
fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [
|
fileWriter.writeBundle({} as EntryPointBundle, [
|
||||||
{path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
|
{path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
|
||||||
{path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'},
|
{path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'},
|
||||||
{path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'},
|
{path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'},
|
||||||
@ -49,7 +48,7 @@ runInEachFileSystem(() => {
|
|||||||
it('should create backups of all files that previously existed', () => {
|
it('should create backups of all files that previously existed', () => {
|
||||||
const fs = getFileSystem();
|
const fs = getFileSystem();
|
||||||
const fileWriter = new InPlaceFileWriter(fs);
|
const fileWriter = new InPlaceFileWriter(fs);
|
||||||
fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [
|
fileWriter.writeBundle({} as EntryPointBundle, [
|
||||||
{path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
|
{path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
|
||||||
{path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'},
|
{path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'},
|
||||||
{path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'},
|
{path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'},
|
||||||
@ -72,10 +71,7 @@ runInEachFileSystem(() => {
|
|||||||
const absoluteBackupPath = _('/package/path/already-backed-up.js');
|
const absoluteBackupPath = _('/package/path/already-backed-up.js');
|
||||||
expect(
|
expect(
|
||||||
() => fileWriter.writeBundle(
|
() => fileWriter.writeBundle(
|
||||||
{} as EntryPoint, {} as EntryPointBundle,
|
{} as EntryPointBundle, [{path: absoluteBackupPath, contents: 'MODIFIED BACKED UP'}]))
|
||||||
[
|
|
||||||
{path: absoluteBackupPath, contents: 'MODIFIED BACKED UP'},
|
|
||||||
]))
|
|
||||||
.toThrowError(
|
.toThrowError(
|
||||||
`Tried to overwrite ${absoluteBackupPath}.__ivy_ngcc_bak with an ngcc back up file, which is disallowed.`);
|
`Tried to overwrite ${absoluteBackupPath}.__ivy_ngcc_bak with an ngcc back up file, which is disallowed.`);
|
||||||
});
|
});
|
||||||
|
@ -32,8 +32,15 @@ runInEachFileSystem(() => {
|
|||||||
|
|
||||||
{
|
{
|
||||||
name: _('/node_modules/test/package.json'),
|
name: _('/node_modules/test/package.json'),
|
||||||
contents:
|
contents: `
|
||||||
'{"module": "./esm5.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}'
|
{
|
||||||
|
"module": "./esm5.js",
|
||||||
|
"fesm2015": "./es2015/index.js",
|
||||||
|
"fesm5": "./esm5.js",
|
||||||
|
"es2015": "./es2015/index.js",
|
||||||
|
"typings": "./index.d.ts"
|
||||||
|
}
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
{name: _('/node_modules/test/index.d.ts'), contents: 'export declare class FooTop {}'},
|
{name: _('/node_modules/test/index.d.ts'), contents: 'export declare class FooTop {}'},
|
||||||
{name: _('/node_modules/test/index.d.ts.map'), contents: 'ORIGINAL MAPPING DATA'},
|
{name: _('/node_modules/test/index.d.ts.map'), contents: 'ORIGINAL MAPPING DATA'},
|
||||||
@ -44,8 +51,15 @@ runInEachFileSystem(() => {
|
|||||||
{name: _('/node_modules/test/es2015/foo.js'), contents: 'export class FooTop {}'},
|
{name: _('/node_modules/test/es2015/foo.js'), contents: 'export class FooTop {}'},
|
||||||
{
|
{
|
||||||
name: _('/node_modules/test/a/package.json'),
|
name: _('/node_modules/test/a/package.json'),
|
||||||
contents:
|
contents: `
|
||||||
`{"module": "./esm5.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}`
|
{
|
||||||
|
"module": "./esm5.js",
|
||||||
|
"fesm2015": "./es2015/index.js",
|
||||||
|
"fesm5": "./esm5.js",
|
||||||
|
"es2015": "./es2015/index.js",
|
||||||
|
"typings": "./index.d.ts"
|
||||||
|
}
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
{name: _('/node_modules/test/a/index.d.ts'), contents: 'export declare class FooA {}'},
|
{name: _('/node_modules/test/a/index.d.ts'), contents: 'export declare class FooA {}'},
|
||||||
{name: _('/node_modules/test/a/index.metadata.json'), contents: '...'},
|
{name: _('/node_modules/test/a/index.metadata.json'), contents: '...'},
|
||||||
@ -95,13 +109,16 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should write the modified files to a new folder', () => {
|
it('should write the modified files to a new folder', () => {
|
||||||
fileWriter.writeBundle(entryPoint, esm5bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm5bundle,
|
||||||
path: _('/node_modules/test/esm5.js'),
|
[
|
||||||
contents: 'export function FooTop() {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/esm5.js'),
|
||||||
{path: _('/node_modules/test/esm5.js.map'), contents: 'MODIFIED MAPPING DATA'},
|
contents: 'export function FooTop() {} // MODIFIED'
|
||||||
]);
|
},
|
||||||
|
{path: _('/node_modules/test/esm5.js.map'), contents: 'MODIFIED MAPPING DATA'},
|
||||||
|
],
|
||||||
|
['module']);
|
||||||
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/esm5.js')))
|
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/esm5.js')))
|
||||||
.toEqual('export function FooTop() {} // MODIFIED');
|
.toEqual('export function FooTop() {} // MODIFIED');
|
||||||
expect(fs.readFile(_('/node_modules/test/esm5.js'))).toEqual('export function FooTop() {}');
|
expect(fs.readFile(_('/node_modules/test/esm5.js'))).toEqual('export function FooTop() {}');
|
||||||
@ -111,12 +128,15 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should also copy unmodified files in the program', () => {
|
it('should also copy unmodified files in the program', () => {
|
||||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm2015bundle,
|
||||||
path: _('/node_modules/test/es2015/foo.js'),
|
[
|
||||||
contents: 'export class FooTop {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/es2015/foo.js'),
|
||||||
]);
|
contents: 'export class FooTop {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['es2015']);
|
||||||
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/es2015/foo.js')))
|
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/es2015/foo.js')))
|
||||||
.toEqual('export class FooTop {} // MODIFIED');
|
.toEqual('export class FooTop {} // MODIFIED');
|
||||||
expect(fs.readFile(_('/node_modules/test/es2015/foo.js')))
|
expect(fs.readFile(_('/node_modules/test/es2015/foo.js')))
|
||||||
@ -128,36 +148,77 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update the package.json properties', () => {
|
it('should update the package.json properties', () => {
|
||||||
fileWriter.writeBundle(entryPoint, esm5bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm5bundle,
|
||||||
path: _('/node_modules/test/esm5.js'),
|
[
|
||||||
contents: 'export function FooTop() {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/esm5.js'),
|
||||||
]);
|
contents: 'export function FooTop() {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['module']);
|
||||||
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
|
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
|
||||||
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
|
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm2015bundle,
|
||||||
path: _('/node_modules/test/es2015/foo.js'),
|
[
|
||||||
contents: 'export class FooTop {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/es2015/foo.js'),
|
||||||
]);
|
contents: 'export class FooTop {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['es2015']);
|
||||||
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
|
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
|
||||||
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
|
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
|
||||||
es2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js',
|
es2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js',
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be able to update multiple package.json properties at once', () => {
|
||||||
|
fileWriter.writeBundle(
|
||||||
|
esm5bundle,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
path: _('/node_modules/test/esm5.js'),
|
||||||
|
contents: 'export function FooTop() {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['module', 'fesm5']);
|
||||||
|
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
|
||||||
|
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
|
||||||
|
fesm5_ivy_ngcc: '__ivy_ngcc__/esm5.js',
|
||||||
|
}));
|
||||||
|
|
||||||
|
fileWriter.writeBundle(
|
||||||
|
esm2015bundle,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
path: _('/node_modules/test/es2015/foo.js'),
|
||||||
|
contents: 'export class FooTop {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['es2015', 'fesm2015']);
|
||||||
|
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
|
||||||
|
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
|
||||||
|
fesm5_ivy_ngcc: '__ivy_ngcc__/esm5.js',
|
||||||
|
es2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js',
|
||||||
|
fesm2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js',
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
it('should overwrite and backup typings files', () => {
|
it('should overwrite and backup typings files', () => {
|
||||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm2015bundle,
|
||||||
path: _('/node_modules/test/index.d.ts'),
|
[
|
||||||
contents: 'export declare class FooTop {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/index.d.ts'),
|
||||||
{path: _('/node_modules/test/index.d.ts.map'), contents: 'MODIFIED MAPPING DATA'},
|
contents: 'export declare class FooTop {} // MODIFIED'
|
||||||
]);
|
},
|
||||||
|
{path: _('/node_modules/test/index.d.ts.map'), contents: 'MODIFIED MAPPING DATA'},
|
||||||
|
],
|
||||||
|
['es2015']);
|
||||||
expect(fs.readFile(_('/node_modules/test/index.d.ts')))
|
expect(fs.readFile(_('/node_modules/test/index.d.ts')))
|
||||||
.toEqual('export declare class FooTop {} // MODIFIED');
|
.toEqual('export declare class FooTop {} // MODIFIED');
|
||||||
expect(fs.readFile(_('/node_modules/test/index.d.ts.__ivy_ngcc_bak')))
|
expect(fs.readFile(_('/node_modules/test/index.d.ts.__ivy_ngcc_bak')))
|
||||||
@ -184,24 +245,30 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should write the modified file to a new folder', () => {
|
it('should write the modified file to a new folder', () => {
|
||||||
fileWriter.writeBundle(entryPoint, esm5bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm5bundle,
|
||||||
path: _('/node_modules/test/a/esm5.js'),
|
[
|
||||||
contents: 'export function FooA() {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/a/esm5.js'),
|
||||||
]);
|
contents: 'export function FooA() {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['module']);
|
||||||
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/esm5.js')))
|
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/esm5.js')))
|
||||||
.toEqual('export function FooA() {} // MODIFIED');
|
.toEqual('export function FooA() {} // MODIFIED');
|
||||||
expect(fs.readFile(_('/node_modules/test/a/esm5.js'))).toEqual('export function FooA() {}');
|
expect(fs.readFile(_('/node_modules/test/a/esm5.js'))).toEqual('export function FooA() {}');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should also copy unmodified files in the program', () => {
|
it('should also copy unmodified files in the program', () => {
|
||||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm2015bundle,
|
||||||
path: _('/node_modules/test/a/es2015/foo.js'),
|
[
|
||||||
contents: 'export class FooA {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/a/es2015/foo.js'),
|
||||||
]);
|
contents: 'export class FooA {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['es2015']);
|
||||||
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/es2015/foo.js')))
|
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/es2015/foo.js')))
|
||||||
.toEqual('export class FooA {} // MODIFIED');
|
.toEqual('export class FooA {} // MODIFIED');
|
||||||
expect(fs.readFile(_('/node_modules/test/a/es2015/foo.js')))
|
expect(fs.readFile(_('/node_modules/test/a/es2015/foo.js')))
|
||||||
@ -213,35 +280,76 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update the package.json properties', () => {
|
it('should update the package.json properties', () => {
|
||||||
fileWriter.writeBundle(entryPoint, esm5bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm5bundle,
|
||||||
path: _('/node_modules/test/a/esm5.js'),
|
[
|
||||||
contents: 'export function FooA() {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/a/esm5.js'),
|
||||||
]);
|
contents: 'export function FooA() {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['module']);
|
||||||
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
|
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
|
||||||
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
|
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm2015bundle,
|
||||||
path: _('/node_modules/test/a/es2015/foo.js'),
|
[
|
||||||
contents: 'export class FooA {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/a/es2015/foo.js'),
|
||||||
]);
|
contents: 'export class FooA {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['es2015']);
|
||||||
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
|
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
|
||||||
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
|
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
|
||||||
es2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js',
|
es2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js',
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be able to update multiple package.json properties at once', () => {
|
||||||
|
fileWriter.writeBundle(
|
||||||
|
esm5bundle,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
path: _('/node_modules/test/a/esm5.js'),
|
||||||
|
contents: 'export function FooA() {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['module', 'fesm5']);
|
||||||
|
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
|
||||||
|
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
|
||||||
|
fesm5_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
|
||||||
|
}));
|
||||||
|
|
||||||
|
fileWriter.writeBundle(
|
||||||
|
esm2015bundle,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
path: _('/node_modules/test/a/es2015/foo.js'),
|
||||||
|
contents: 'export class FooA {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['es2015', 'fesm2015']);
|
||||||
|
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
|
||||||
|
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
|
||||||
|
fesm5_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
|
||||||
|
es2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js',
|
||||||
|
fesm2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js',
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
it('should overwrite and backup typings files', () => {
|
it('should overwrite and backup typings files', () => {
|
||||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm2015bundle,
|
||||||
path: _('/node_modules/test/a/index.d.ts'),
|
[
|
||||||
contents: 'export declare class FooA {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/a/index.d.ts'),
|
||||||
]);
|
contents: 'export declare class FooA {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['es2015']);
|
||||||
expect(fs.readFile(_('/node_modules/test/a/index.d.ts')))
|
expect(fs.readFile(_('/node_modules/test/a/index.d.ts')))
|
||||||
.toEqual('export declare class FooA {} // MODIFIED');
|
.toEqual('export declare class FooA {} // MODIFIED');
|
||||||
expect(fs.readFile(_('/node_modules/test/a/index.d.ts.__ivy_ngcc_bak')))
|
expect(fs.readFile(_('/node_modules/test/a/index.d.ts.__ivy_ngcc_bak')))
|
||||||
@ -262,12 +370,15 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should write the modified file to a new folder', () => {
|
it('should write the modified file to a new folder', () => {
|
||||||
fileWriter.writeBundle(entryPoint, esm5bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm5bundle,
|
||||||
path: _('/node_modules/test/lib/esm5.js'),
|
[
|
||||||
contents: 'export function FooB() {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/lib/esm5.js'),
|
||||||
]);
|
contents: 'export function FooB() {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['module']);
|
||||||
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/esm5.js')))
|
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/esm5.js')))
|
||||||
.toEqual('export function FooB() {} // MODIFIED');
|
.toEqual('export function FooB() {} // MODIFIED');
|
||||||
expect(fs.readFile(_('/node_modules/test/lib/esm5.js')))
|
expect(fs.readFile(_('/node_modules/test/lib/esm5.js')))
|
||||||
@ -275,12 +386,15 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should also copy unmodified files in the program', () => {
|
it('should also copy unmodified files in the program', () => {
|
||||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm2015bundle,
|
||||||
path: _('/node_modules/test/lib/es2015/foo.js'),
|
[
|
||||||
contents: 'export class FooB {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/lib/es2015/foo.js'),
|
||||||
]);
|
contents: 'export class FooB {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['es2015']);
|
||||||
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/es2015/foo.js')))
|
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/es2015/foo.js')))
|
||||||
.toEqual('export class FooB {} // MODIFIED');
|
.toEqual('export class FooB {} // MODIFIED');
|
||||||
expect(fs.readFile(_('/node_modules/test/lib/es2015/foo.js')))
|
expect(fs.readFile(_('/node_modules/test/lib/es2015/foo.js')))
|
||||||
@ -293,43 +407,55 @@ runInEachFileSystem(() => {
|
|||||||
|
|
||||||
it('should not copy typings files within the package (i.e. from a different entry-point)',
|
it('should not copy typings files within the package (i.e. from a different entry-point)',
|
||||||
() => {
|
() => {
|
||||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm2015bundle,
|
||||||
path: _('/node_modules/test/lib/es2015/foo.js'),
|
[
|
||||||
contents: 'export class FooB {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/lib/es2015/foo.js'),
|
||||||
]);
|
contents: 'export class FooB {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['es2015']);
|
||||||
expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/a/index.d.ts'))).toEqual(false);
|
expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/a/index.d.ts'))).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not copy files outside of the package', () => {
|
it('should not copy files outside of the package', () => {
|
||||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm2015bundle,
|
||||||
path: _('/node_modules/test/lib/es2015/foo.js'),
|
[
|
||||||
contents: 'export class FooB {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/lib/es2015/foo.js'),
|
||||||
]);
|
contents: 'export class FooB {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['es2015']);
|
||||||
expect(fs.exists(_('/node_modules/test/other/index.d.ts'))).toEqual(false);
|
expect(fs.exists(_('/node_modules/test/other/index.d.ts'))).toEqual(false);
|
||||||
expect(fs.exists(_('/node_modules/test/events/events.js'))).toEqual(false);
|
expect(fs.exists(_('/node_modules/test/events/events.js'))).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update the package.json properties', () => {
|
it('should update the package.json properties', () => {
|
||||||
fileWriter.writeBundle(entryPoint, esm5bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm5bundle,
|
||||||
path: _('/node_modules/test/lib/esm5.js'),
|
[
|
||||||
contents: 'export function FooB() {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/lib/esm5.js'),
|
||||||
]);
|
contents: 'export function FooB() {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['module']);
|
||||||
expect(loadPackageJson(fs, '/node_modules/test/b')).toEqual(jasmine.objectContaining({
|
expect(loadPackageJson(fs, '/node_modules/test/b')).toEqual(jasmine.objectContaining({
|
||||||
module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js',
|
module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm2015bundle,
|
||||||
path: _('/node_modules/test/lib/es2015/foo.js'),
|
[
|
||||||
contents: 'export class FooB {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/lib/es2015/foo.js'),
|
||||||
]);
|
contents: 'export class FooB {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['es2015']);
|
||||||
expect(loadPackageJson(fs, '/node_modules/test/b')).toEqual(jasmine.objectContaining({
|
expect(loadPackageJson(fs, '/node_modules/test/b')).toEqual(jasmine.objectContaining({
|
||||||
module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js',
|
module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js',
|
||||||
es2015_ivy_ngcc: '../__ivy_ngcc__/lib/es2015/index.js',
|
es2015_ivy_ngcc: '../__ivy_ngcc__/lib/es2015/index.js',
|
||||||
@ -337,12 +463,15 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should overwrite and backup typings files', () => {
|
it('should overwrite and backup typings files', () => {
|
||||||
fileWriter.writeBundle(entryPoint, esm2015bundle, [
|
fileWriter.writeBundle(
|
||||||
{
|
esm2015bundle,
|
||||||
path: _('/node_modules/test/typings/index.d.ts'),
|
[
|
||||||
contents: 'export declare class FooB {} // MODIFIED'
|
{
|
||||||
},
|
path: _('/node_modules/test/typings/index.d.ts'),
|
||||||
]);
|
contents: 'export declare class FooB {} // MODIFIED'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['es2015']);
|
||||||
expect(fs.readFile(_('/node_modules/test/typings/index.d.ts')))
|
expect(fs.readFile(_('/node_modules/test/typings/index.d.ts')))
|
||||||
.toEqual('export declare class FooB {} // MODIFIED');
|
.toEqual('export declare class FooB {} // MODIFIED');
|
||||||
expect(fs.readFile(_('/node_modules/test/typings/index.d.ts.__ivy_ngcc_bak')))
|
expect(fs.readFile(_('/node_modules/test/typings/index.d.ts.__ivy_ngcc_bak')))
|
||||||
@ -356,7 +485,6 @@ runInEachFileSystem(() => {
|
|||||||
fs: FileSystem, entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty,
|
fs: FileSystem, entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty,
|
||||||
format: EntryPointFormat): EntryPointBundle {
|
format: EntryPointFormat): EntryPointBundle {
|
||||||
return makeEntryPointBundle(
|
return makeEntryPointBundle(
|
||||||
fs, entryPoint, entryPoint.packageJson[formatProperty] !, false, formatProperty, format,
|
fs, entryPoint, entryPoint.packageJson[formatProperty] !, false, format, true);
|
||||||
true) !;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -11,12 +11,16 @@
|
|||||||
import {Subject, Subscription} from 'rxjs';
|
import {Subject, Subscription} from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use in directives and components to emit custom events synchronously
|
* Use in components with the `@Output` directive to emit custom events
|
||||||
* or asynchronously, and register handlers for those events by subscribing
|
* synchronously or asynchronously, and register handlers for those events
|
||||||
* to an instance.
|
* by subscribing to an instance.
|
||||||
*
|
*
|
||||||
* @usageNotes
|
* @usageNotes
|
||||||
*
|
*
|
||||||
|
* Extends
|
||||||
|
* [RxJS `Subject`](https://rxjs.dev/api/index/class/Subject)
|
||||||
|
* for Angular by adding the `emit()` method.
|
||||||
|
*
|
||||||
* In the following example, a component defines two output properties
|
* In the following example, a component defines two output properties
|
||||||
* that create event emitters. When the title is clicked, the emitter
|
* that create event emitters. When the title is clicked, the emitter
|
||||||
* emits an open or close event to toggle the current visibility state.
|
* emits an open or close event to toggle the current visibility state.
|
||||||
@ -54,6 +58,7 @@ import {Subject, Subscription} from 'rxjs';
|
|||||||
* <zippy (open)="onOpen($event)" (close)="onClose($event)"></zippy>
|
* <zippy (open)="onOpen($event)" (close)="onClose($event)"></zippy>
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
|
* @see [Observables in Angular](guide/observables-in-angular)
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
export class EventEmitter<T extends any> extends Subject<T> {
|
export class EventEmitter<T extends any> extends Subject<T> {
|
||||||
|
@ -678,6 +678,8 @@ export interface InputDecorator {
|
|||||||
* })
|
* })
|
||||||
* class App {}
|
* class App {}
|
||||||
* ```
|
* ```
|
||||||
|
*
|
||||||
|
* @see [Input and Output properties](guide/template-syntax#input-and-output-properties)
|
||||||
*/
|
*/
|
||||||
(bindingPropertyName?: string): any;
|
(bindingPropertyName?: string): any;
|
||||||
new (bindingPropertyName?: string): any;
|
new (bindingPropertyName?: string): any;
|
||||||
@ -721,6 +723,8 @@ export interface OutputDecorator {
|
|||||||
*
|
*
|
||||||
* See `Input` decorator for an example of providing a binding name.
|
* See `Input` decorator for an example of providing a binding name.
|
||||||
*
|
*
|
||||||
|
* @see [Input and Output properties](guide/template-syntax#input-and-output-properties)
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
(bindingPropertyName?: string): any;
|
(bindingPropertyName?: string): any;
|
||||||
new (bindingPropertyName?: string): any;
|
new (bindingPropertyName?: string): any;
|
||||||
|
@ -138,7 +138,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
|||||||
namespaceHTMLInternal();
|
namespaceHTMLInternal();
|
||||||
const hostRNode = rootSelectorOrNode ?
|
const hostRNode = rootSelectorOrNode ?
|
||||||
locateHostElement(rendererFactory, rootSelectorOrNode) :
|
locateHostElement(rendererFactory, rootSelectorOrNode) :
|
||||||
elementCreate(this.selector, rendererFactory.createRenderer(null, this.componentDef));
|
elementCreate(this.selector, rendererFactory.createRenderer(null, this.componentDef), null);
|
||||||
|
|
||||||
const rootFlags = this.componentDef.onPush ? LViewFlags.Dirty | LViewFlags.IsRoot :
|
const rootFlags = this.componentDef.onPush ? LViewFlags.Dirty | LViewFlags.IsRoot :
|
||||||
LViewFlags.CheckAlways | LViewFlags.IsRoot;
|
LViewFlags.CheckAlways | LViewFlags.IsRoot;
|
||||||
|
@ -16,7 +16,7 @@ import {isContentQueryHost} from '../interfaces/type_checks';
|
|||||||
import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
|
import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
|
||||||
import {assertNodeType} from '../node_assert';
|
import {assertNodeType} from '../node_assert';
|
||||||
import {appendChild} from '../node_manipulation';
|
import {appendChild} from '../node_manipulation';
|
||||||
import {decreaseElementDepthCount, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, getSelectedIndex, increaseElementDepthCount, setIsNotParent, setPreviousOrParentTNode} from '../state';
|
import {decreaseElementDepthCount, getElementDepthCount, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, increaseElementDepthCount, setIsNotParent, setPreviousOrParentTNode} from '../state';
|
||||||
import {registerInitialStylingOnTNode} from '../styling_next/instructions';
|
import {registerInitialStylingOnTNode} from '../styling_next/instructions';
|
||||||
import {StylingMapArray, TStylingContext} from '../styling_next/interfaces';
|
import {StylingMapArray, TStylingContext} from '../styling_next/interfaces';
|
||||||
import {getInitialStylingValue, hasClassInput, hasStyleInput} from '../styling_next/util';
|
import {getInitialStylingValue, hasClassInput, hasStyleInput} from '../styling_next/util';
|
||||||
@ -52,13 +52,13 @@ export function ɵɵelementStart(
|
|||||||
|
|
||||||
ngDevMode && ngDevMode.rendererCreateElement++;
|
ngDevMode && ngDevMode.rendererCreateElement++;
|
||||||
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
|
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
|
||||||
const native = lView[index + HEADER_OFFSET] = elementCreate(name);
|
|
||||||
const renderer = lView[RENDERER];
|
const renderer = lView[RENDERER];
|
||||||
|
const native = lView[index + HEADER_OFFSET] = elementCreate(name, renderer, getNamespace());
|
||||||
const tNode =
|
const tNode =
|
||||||
getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.Element, name, attrs || null);
|
getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.Element, name, attrs || null);
|
||||||
|
|
||||||
if (attrs != null) {
|
if (attrs != null) {
|
||||||
const lastAttrIndex = setUpAttributes(native, attrs);
|
const lastAttrIndex = setUpAttributes(renderer, native, attrs);
|
||||||
if (tView.firstTemplatePass) {
|
if (tView.firstTemplatePass) {
|
||||||
registerInitialStylingOnTNode(tNode, attrs, lastAttrIndex);
|
registerInitialStylingOnTNode(tNode, attrs, lastAttrIndex);
|
||||||
}
|
}
|
||||||
@ -209,7 +209,7 @@ export function ɵɵelementHostAttrs(attrs: TAttributes) {
|
|||||||
// errors...
|
// errors...
|
||||||
if (tNode.type === TNodeType.Element) {
|
if (tNode.type === TNodeType.Element) {
|
||||||
const native = getNativeByTNode(tNode, lView) as RElement;
|
const native = getNativeByTNode(tNode, lView) as RElement;
|
||||||
const lastAttrIndex = setUpAttributes(native, attrs);
|
const lastAttrIndex = setUpAttributes(lView[RENDERER], native, attrs);
|
||||||
if (tView.firstTemplatePass) {
|
if (tView.firstTemplatePass) {
|
||||||
const stylingNeedsToBeRendered = registerInitialStylingOnTNode(tNode, attrs, lastAttrIndex);
|
const stylingNeedsToBeRendered = registerInitialStylingOnTNode(tNode, attrs, lastAttrIndex);
|
||||||
|
|
||||||
|
@ -11,11 +11,11 @@ import {assertLContainerOrUndefined} from '../assert';
|
|||||||
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
|
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
|
||||||
import {RenderFlags} from '../interfaces/definition';
|
import {RenderFlags} from '../interfaces/definition';
|
||||||
import {TContainerNode, TNodeType} from '../interfaces/node';
|
import {TContainerNode, TNodeType} from '../interfaces/node';
|
||||||
import {FLAGS, LView, LViewFlags, PARENT, QUERIES, TVIEW, TView, T_HOST} from '../interfaces/view';
|
import {FLAGS, LView, LViewFlags, PARENT, TVIEW, TView, T_HOST} from '../interfaces/view';
|
||||||
import {assertNodeType} from '../node_assert';
|
import {assertNodeType} from '../node_assert';
|
||||||
import {insertView, removeView} from '../node_manipulation';
|
import {insertView, removeView} from '../node_manipulation';
|
||||||
import {enterView, getIsParent, getLView, getPreviousOrParentTNode, isCreationMode, leaveView, setIsParent, setPreviousOrParentTNode} from '../state';
|
import {enterView, getIsParent, getLView, getPreviousOrParentTNode, leaveView, setIsParent, setPreviousOrParentTNode} from '../state';
|
||||||
import {resetPreOrderHookFlags} from '../util/view_utils';
|
import {isCreationMode, resetPreOrderHookFlags} from '../util/view_utils';
|
||||||
import {assignTViewNodeToLView, createLView, createTView, refreshDescendantViews} from './shared';
|
import {assignTViewNodeToLView, createLView, createTView, refreshDescendantViews} from './shared';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,13 +28,13 @@ import {isComponent, isComponentDef, isContentQueryHost, isLContainer, isRootVie
|
|||||||
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view';
|
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view';
|
||||||
import {assertNodeOfPossibleTypes, assertNodeType} from '../node_assert';
|
import {assertNodeOfPossibleTypes, assertNodeType} from '../node_assert';
|
||||||
import {isNodeMatchingSelectorList} from '../node_selector_matcher';
|
import {isNodeMatchingSelectorList} from '../node_selector_matcher';
|
||||||
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, incrementActiveDirectiveId, isCreationMode, leaveView, namespaceHTMLInternal, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
|
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, getSelectedIndex, incrementActiveDirectiveId, leaveView, namespaceHTMLInternal, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
|
||||||
import {renderStylingMap} from '../styling_next/bindings';
|
import {renderStylingMap} from '../styling_next/bindings';
|
||||||
import {NO_CHANGE} from '../tokens';
|
import {NO_CHANGE} from '../tokens';
|
||||||
import {ANIMATION_PROP_PREFIX, isAnimationProp} from '../util/attrs_utils';
|
import {ANIMATION_PROP_PREFIX, isAnimationProp} from '../util/attrs_utils';
|
||||||
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
|
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
|
||||||
import {getLViewParent, getRootContext} from '../util/view_traversal_utils';
|
import {getLViewParent} from '../util/view_traversal_utils';
|
||||||
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils';
|
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils';
|
||||||
|
|
||||||
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNodeInitialData, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLView, cloneToTViewData} from './lview_debug';
|
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNodeInitialData, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLView, cloneToTViewData} from './lview_debug';
|
||||||
import {selectInternal} from './select';
|
import {selectInternal} from './select';
|
||||||
@ -197,26 +197,19 @@ function refreshChildComponents(hostLView: LView, components: number[]): void {
|
|||||||
/**
|
/**
|
||||||
* Creates a native element from a tag name, using a renderer.
|
* Creates a native element from a tag name, using a renderer.
|
||||||
* @param name the tag name
|
* @param name the tag name
|
||||||
* @param overriddenRenderer Optional A renderer to override the default one
|
* @param renderer A renderer to use
|
||||||
* @returns the element created
|
* @returns the element created
|
||||||
*/
|
*/
|
||||||
export function elementCreate(name: string, overriddenRenderer?: Renderer3): RElement {
|
export function elementCreate(
|
||||||
let native: RElement;
|
name: string, renderer: Renderer3, namespace: string | null): RElement {
|
||||||
const rendererToUse = overriddenRenderer || getLView()[RENDERER];
|
if (isProceduralRenderer(renderer)) {
|
||||||
|
return renderer.createElement(name, namespace);
|
||||||
const namespace = getNamespace();
|
|
||||||
|
|
||||||
if (isProceduralRenderer(rendererToUse)) {
|
|
||||||
native = rendererToUse.createElement(name, namespace);
|
|
||||||
} else {
|
} else {
|
||||||
if (namespace === null) {
|
return namespace === null ? renderer.createElement(name) :
|
||||||
native = rendererToUse.createElement(name);
|
renderer.createElementNS(namespace, name);
|
||||||
} else {
|
|
||||||
native = rendererToUse.createElementNS(namespace, name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return native;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createLView<T>(
|
export function createLView<T>(
|
||||||
parentLView: LView | null, tView: TView, context: T | null, flags: LViewFlags,
|
parentLView: LView | null, tView: TView, context: T | null, flags: LViewFlags,
|
||||||
host: RElement | null, tHostNode: TViewNode | TElementNode | null,
|
host: RElement | null, tHostNode: TViewNode | TElementNode | null,
|
||||||
@ -369,16 +362,11 @@ export function allocExpando(view: LView, numSlotsToAlloc: number) {
|
|||||||
//////////////////////////
|
//////////////////////////
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for creating the LViewNode of a dynamic embedded view,
|
* Used for creating the LView of a dynamic embedded view, either through
|
||||||
* either through ViewContainerRef.createEmbeddedView() or TemplateRef.createEmbeddedView().
|
* ViewContainerRef.createEmbeddedView() or TemplateRef.createEmbeddedView().
|
||||||
* Such lViewNode will then be renderer with renderEmbeddedTemplate() (see below).
|
|
||||||
*/
|
*/
|
||||||
export function createEmbeddedViewAndNode<T>(
|
export function createEmbeddedViewAndNode<T>(
|
||||||
tView: TView, context: T, declarationView: LView, injectorIndex: number): LView {
|
tView: TView, context: T, declarationView: LView, injectorIndex: number): LView {
|
||||||
const _isParent = getIsParent();
|
|
||||||
const _previousOrParentTNode = getPreviousOrParentTNode();
|
|
||||||
setPreviousOrParentTNode(null !, true);
|
|
||||||
|
|
||||||
const lView = createLView(declarationView, tView, context, LViewFlags.CheckAlways, null, null);
|
const lView = createLView(declarationView, tView, context, LViewFlags.CheckAlways, null, null);
|
||||||
lView[DECLARATION_VIEW] = declarationView;
|
lView[DECLARATION_VIEW] = declarationView;
|
||||||
|
|
||||||
@ -388,12 +376,12 @@ export function createEmbeddedViewAndNode<T>(
|
|||||||
tView.node !.injectorIndex = injectorIndex;
|
tView.node !.injectorIndex = injectorIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPreviousOrParentTNode(_previousOrParentTNode, _isParent);
|
|
||||||
return lView;
|
return lView;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for rendering embedded views (e.g. dynamically created views)
|
* Used for rendering views in a LContainer (embedded views or root component views for dynamically
|
||||||
|
* created components).
|
||||||
*
|
*
|
||||||
* Dynamically created views must store/retrieve their TViews differently from component views
|
* Dynamically created views must store/retrieve their TViews differently from component views
|
||||||
* because their template functions are nested in the template functions of their hosts, creating
|
* because their template functions are nested in the template functions of their hosts, creating
|
||||||
@ -407,22 +395,20 @@ export function renderEmbeddedTemplate<T>(viewToRender: LView, tView: TView, con
|
|||||||
const _isParent = getIsParent();
|
const _isParent = getIsParent();
|
||||||
const _previousOrParentTNode = getPreviousOrParentTNode();
|
const _previousOrParentTNode = getPreviousOrParentTNode();
|
||||||
let oldView: LView;
|
let oldView: LView;
|
||||||
if (viewToRender[FLAGS] & LViewFlags.IsRoot) {
|
// Will become true if the `try` block executes with no errors.
|
||||||
// This is a root view inside the view tree
|
let safeToRunHooks = false;
|
||||||
tickRootContext(getRootContext(viewToRender));
|
try {
|
||||||
} else {
|
oldView = enterView(viewToRender, viewToRender[T_HOST]);
|
||||||
// Will become true if the `try` block executes with no errors.
|
resetPreOrderHookFlags(viewToRender);
|
||||||
let safeToRunHooks = false;
|
const templateFn = tView.template;
|
||||||
try {
|
if (templateFn !== null) {
|
||||||
oldView = enterView(viewToRender, viewToRender[T_HOST]);
|
executeTemplate(viewToRender, templateFn, getRenderFlags(viewToRender), context);
|
||||||
resetPreOrderHookFlags(viewToRender);
|
|
||||||
executeTemplate(viewToRender, tView.template !, getRenderFlags(viewToRender), context);
|
|
||||||
refreshDescendantViews(viewToRender);
|
|
||||||
safeToRunHooks = true;
|
|
||||||
} finally {
|
|
||||||
leaveView(oldView !, safeToRunHooks);
|
|
||||||
setPreviousOrParentTNode(_previousOrParentTNode, _isParent);
|
|
||||||
}
|
}
|
||||||
|
refreshDescendantViews(viewToRender);
|
||||||
|
safeToRunHooks = true;
|
||||||
|
} finally {
|
||||||
|
leaveView(oldView !, safeToRunHooks);
|
||||||
|
setPreviousOrParentTNode(_previousOrParentTNode, _isParent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, getBinding, updateBinding} from './bindings';
|
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, getBinding, updateBinding} from './bindings';
|
||||||
import {getBindingRoot, getLView, isCreationMode} from './state';
|
import {getBindingRoot, getLView} from './state';
|
||||||
|
import {isCreationMode} from './util/view_utils';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,7 +44,7 @@ export function ɵɵpureFunction0<T>(slotOffset: number, pureFn: () => T, thisAr
|
|||||||
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
|
// TODO(kara): use bindingRoot instead of bindingStartIndex when implementing host bindings
|
||||||
const bindingIndex = getBindingRoot() + slotOffset;
|
const bindingIndex = getBindingRoot() + slotOffset;
|
||||||
const lView = getLView();
|
const lView = getLView();
|
||||||
return isCreationMode() ?
|
return isCreationMode(lView) ?
|
||||||
updateBinding(lView, bindingIndex, thisArg ? pureFn.call(thisArg) : pureFn()) :
|
updateBinding(lView, bindingIndex, thisArg ? pureFn.call(thisArg) : pureFn()) :
|
||||||
getBinding(lView, bindingIndex);
|
getBinding(lView, bindingIndex);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,8 @@ import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, u
|
|||||||
import {LQueries, LQuery, TQueries, TQuery, TQueryMetadata, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
|
import {LQueries, LQuery, TQueries, TQuery, TQueryMetadata, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
|
||||||
import {DECLARATION_LCONTAINER, LView, PARENT, QUERIES, TVIEW, TView} from './interfaces/view';
|
import {DECLARATION_LCONTAINER, LView, PARENT, QUERIES, TVIEW, TView} from './interfaces/view';
|
||||||
import {assertNodeOfPossibleTypes} from './node_assert';
|
import {assertNodeOfPossibleTypes} from './node_assert';
|
||||||
import {getCurrentQueryIndex, getLView, getPreviousOrParentTNode, isCreationMode, setCurrentQueryIndex} from './state';
|
import {getCurrentQueryIndex, getLView, getPreviousOrParentTNode, setCurrentQueryIndex} from './state';
|
||||||
|
import {isCreationMode} from './util/view_utils';
|
||||||
import {createContainerRef, createElementRef, createTemplateRef} from './view_engine_compatibility';
|
import {createContainerRef, createElementRef, createTemplateRef} from './view_engine_compatibility';
|
||||||
|
|
||||||
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4;
|
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4;
|
||||||
@ -392,7 +393,7 @@ export function ɵɵqueryRefresh(queryList: QueryList<any>): boolean {
|
|||||||
setCurrentQueryIndex(queryIndex + 1);
|
setCurrentQueryIndex(queryIndex + 1);
|
||||||
|
|
||||||
const tQuery = getTQuery(lView[TVIEW], queryIndex);
|
const tQuery = getTQuery(lView[TVIEW], queryIndex);
|
||||||
if (queryList.dirty && (isCreationMode() === tQuery.metadata.isStatic)) {
|
if (queryList.dirty && (isCreationMode(lView) === tQuery.metadata.isStatic)) {
|
||||||
if (tQuery.matches === null) {
|
if (tQuery.matches === null) {
|
||||||
queryList.reset([]);
|
queryList.reset([]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -15,7 +15,7 @@ import {ComponentDef, DirectiveDef} from './interfaces/definition';
|
|||||||
import {TElementNode, TNode, TViewNode} from './interfaces/node';
|
import {TElementNode, TNode, TViewNode} from './interfaces/node';
|
||||||
import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, InitPhaseState, LView, LViewFlags, OpaqueViewState, TVIEW} from './interfaces/view';
|
import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, InitPhaseState, LView, LViewFlags, OpaqueViewState, TVIEW} from './interfaces/view';
|
||||||
import {resetAllStylingState, resetStylingState} from './styling_next/state';
|
import {resetAllStylingState, resetStylingState} from './styling_next/state';
|
||||||
import {resetPreOrderHookFlags} from './util/view_utils';
|
import {isCreationMode, resetPreOrderHookFlags} from './util/view_utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -334,12 +334,6 @@ export function setIsParent(): void {
|
|||||||
isParent = true;
|
isParent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Checks whether a given view is in creation mode */
|
|
||||||
export function isCreationMode(view: LView = lView): boolean {
|
|
||||||
return (view[FLAGS] & LViewFlags.CreationMode) === LViewFlags.CreationMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State of the current view being processed.
|
* State of the current view being processed.
|
||||||
*
|
*
|
||||||
|
@ -7,9 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
import {AttributeMarker, TAttributes} from '../interfaces/node';
|
import {AttributeMarker, TAttributes} from '../interfaces/node';
|
||||||
import {CssSelector} from '../interfaces/projection';
|
import {CssSelector} from '../interfaces/projection';
|
||||||
import {ProceduralRenderer3, RElement, isProceduralRenderer} from '../interfaces/renderer';
|
import {ProceduralRenderer3, RElement, Renderer3, isProceduralRenderer} from '../interfaces/renderer';
|
||||||
import {RENDERER} from '../interfaces/view';
|
|
||||||
import {getLView} from '../state';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,13 +32,12 @@ import {getLView} from '../state';
|
|||||||
* Note that this instruction does not support assigning style and class values to
|
* Note that this instruction does not support assigning style and class values to
|
||||||
* an element. See `elementStart` and `elementHostAttrs` to learn how styling values
|
* an element. See `elementStart` and `elementHostAttrs` to learn how styling values
|
||||||
* are applied to an element.
|
* are applied to an element.
|
||||||
*
|
* @param renderer The renderer to be used
|
||||||
* @param native The element that the attributes will be assigned to
|
* @param native The element that the attributes will be assigned to
|
||||||
* @param attrs The attribute array of values that will be assigned to the element
|
* @param attrs The attribute array of values that will be assigned to the element
|
||||||
* @returns the index value that was last accessed in the attributes array
|
* @returns the index value that was last accessed in the attributes array
|
||||||
*/
|
*/
|
||||||
export function setUpAttributes(native: RElement, attrs: TAttributes): number {
|
export function setUpAttributes(renderer: Renderer3, native: RElement, attrs: TAttributes): number {
|
||||||
const renderer = getLView()[RENDERER];
|
|
||||||
const isProc = isProceduralRenderer(renderer);
|
const isProc = isProceduralRenderer(renderer);
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
@ -91,17 +88,6 @@ export function setUpAttributes(native: RElement, attrs: TAttributes): number {
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function attrsStylingIndexOf(attrs: TAttributes, startIndex: number): number {
|
|
||||||
for (let i = startIndex; i < attrs.length; i++) {
|
|
||||||
const val = attrs[i];
|
|
||||||
if (val === AttributeMarker.Classes || val === AttributeMarker.Styles) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test whether the given value is a marker that indicates that the following
|
* Test whether the given value is a marker that indicates that the following
|
||||||
* attribute values in a `TAttributes` array are only the names of attributes,
|
* attribute values in a `TAttributes` array are only the names of attributes,
|
||||||
|
@ -158,6 +158,11 @@ export function readPatchedLView(target: any): LView|null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Checks whether a given view is in creation mode */
|
||||||
|
export function isCreationMode(view: LView): boolean {
|
||||||
|
return (view[FLAGS] & LViewFlags.CreationMode) === LViewFlags.CreationMode;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a boolean for whether the view is attached to the change detection tree.
|
* Returns a boolean for whether the view is attached to the change detection tree.
|
||||||
*
|
*
|
||||||
|
@ -404,12 +404,6 @@
|
|||||||
{
|
{
|
||||||
"name": "getRenderParent"
|
"name": "getRenderParent"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "getRootContext"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "getRootView"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "getSelectedIndex"
|
"name": "getSelectedIndex"
|
||||||
},
|
},
|
||||||
@ -548,12 +542,6 @@
|
|||||||
{
|
{
|
||||||
"name": "postProcessDirective"
|
"name": "postProcessDirective"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "readPatchedData"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "readPatchedLView"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "refreshChildComponents"
|
"name": "refreshChildComponents"
|
||||||
},
|
},
|
||||||
@ -578,9 +566,6 @@
|
|||||||
{
|
{
|
||||||
"name": "renderComponent"
|
"name": "renderComponent"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "renderComponentOrTemplate"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "renderEmbeddedTemplate"
|
"name": "renderEmbeddedTemplate"
|
||||||
},
|
},
|
||||||
@ -686,9 +671,6 @@
|
|||||||
{
|
{
|
||||||
"name": "throwMultipleComponentError"
|
"name": "throwMultipleComponentError"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "tickRootContext"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "unwrapRNode"
|
"name": "unwrapRNode"
|
||||||
},
|
},
|
||||||
|
@ -326,12 +326,6 @@
|
|||||||
{
|
{
|
||||||
"name": "getRenderParent"
|
"name": "getRenderParent"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "getRootContext"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "getRootView"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "getSelectedIndex"
|
"name": "getSelectedIndex"
|
||||||
},
|
},
|
||||||
@ -413,12 +407,6 @@
|
|||||||
{
|
{
|
||||||
"name": "postProcessBaseDirective"
|
"name": "postProcessBaseDirective"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "readPatchedData"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "readPatchedLView"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "refreshChildComponents"
|
"name": "refreshChildComponents"
|
||||||
},
|
},
|
||||||
@ -434,9 +422,6 @@
|
|||||||
{
|
{
|
||||||
"name": "renderComponent"
|
"name": "renderComponent"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "renderComponentOrTemplate"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "renderEmbeddedTemplate"
|
"name": "renderEmbeddedTemplate"
|
||||||
},
|
},
|
||||||
@ -500,9 +485,6 @@
|
|||||||
{
|
{
|
||||||
"name": "syncViewWithBlueprint"
|
"name": "syncViewWithBlueprint"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "tickRootContext"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "unwrapRNode"
|
"name": "unwrapRNode"
|
||||||
},
|
},
|
||||||
|
@ -899,12 +899,6 @@
|
|||||||
{
|
{
|
||||||
"name": "getRenderer"
|
"name": "getRenderer"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "getRootContext"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "getRootView"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "getSelectedIndex"
|
"name": "getSelectedIndex"
|
||||||
},
|
},
|
||||||
|
@ -6,28 +6,50 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as tss from 'typescript/lib/tsserverlibrary';
|
import * as ts from 'typescript'; // used as value and is provided at runtime
|
||||||
|
|
||||||
import {TemplateInfo} from './common';
|
import {TemplateInfo} from './common';
|
||||||
import {locateSymbol} from './locate_symbol';
|
import {locateSymbol} from './locate_symbol';
|
||||||
import {Location} from './types';
|
import {Span} from './types';
|
||||||
|
|
||||||
export function getDefinition(info: TemplateInfo): Location[]|undefined {
|
/**
|
||||||
const result = locateSymbol(info);
|
* Convert Angular Span to TypeScript TextSpan. Angular Span has 'start' and
|
||||||
return result && result.symbol.definition;
|
* 'end' whereas TS TextSpan has 'start' and 'length'.
|
||||||
}
|
* @param span Angular Span
|
||||||
|
*/
|
||||||
export function ngLocationToTsDefinitionInfo(loc: Location): tss.DefinitionInfo {
|
function ngSpanToTsTextSpan(span: Span): ts.TextSpan {
|
||||||
return {
|
return {
|
||||||
fileName: loc.fileName,
|
start: span.start,
|
||||||
textSpan: {
|
length: span.end - span.start,
|
||||||
start: loc.span.start,
|
};
|
||||||
length: loc.span.end - loc.span.start,
|
}
|
||||||
},
|
|
||||||
// TODO(kyliau): Provide more useful info for name, kind and containerKind
|
export function getDefinitionAndBoundSpan(info: TemplateInfo): ts.DefinitionInfoAndBoundSpan|
|
||||||
name: '', // should be name of symbol but we don't have enough information here.
|
undefined {
|
||||||
kind: tss.ScriptElementKind.unknown,
|
const symbolInfo = locateSymbol(info);
|
||||||
containerName: loc.fileName,
|
if (!symbolInfo) {
|
||||||
containerKind: tss.ScriptElementKind.unknown,
|
return;
|
||||||
|
}
|
||||||
|
const textSpan = ngSpanToTsTextSpan(symbolInfo.span);
|
||||||
|
const {symbol} = symbolInfo;
|
||||||
|
const {container, definition: locations} = symbol;
|
||||||
|
if (!locations || !locations.length) {
|
||||||
|
// symbol.definition is really the locations of the symbol. There could be
|
||||||
|
// more than one. No meaningful info could be provided without any location.
|
||||||
|
return {textSpan};
|
||||||
|
}
|
||||||
|
const containerKind = container ? container.kind : ts.ScriptElementKind.unknown;
|
||||||
|
const containerName = container ? container.name : '';
|
||||||
|
const definitions = locations.map((location) => {
|
||||||
|
return {
|
||||||
|
kind: symbol.kind as ts.ScriptElementKind,
|
||||||
|
name: symbol.name,
|
||||||
|
containerKind: containerKind as ts.ScriptElementKind,
|
||||||
|
containerName: containerName,
|
||||||
|
textSpan: ngSpanToTsTextSpan(location.span),
|
||||||
|
fileName: location.fileName,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
definitions, textSpan,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -6,23 +6,42 @@
|
|||||||
* 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';
|
||||||
import {TemplateInfo} from './common';
|
import {TemplateInfo} from './common';
|
||||||
import {locateSymbol} from './locate_symbol';
|
import {locateSymbol} from './locate_symbol';
|
||||||
import {Hover, HoverTextSection, Symbol} from './types';
|
|
||||||
|
|
||||||
export function getHover(info: TemplateInfo): Hover|undefined {
|
// Reverse mappings of enum would generate strings
|
||||||
const result = locateSymbol(info);
|
const SYMBOL_SPACE = ts.SymbolDisplayPartKind[ts.SymbolDisplayPartKind.space];
|
||||||
if (result) {
|
const SYMBOL_PUNC = ts.SymbolDisplayPartKind[ts.SymbolDisplayPartKind.punctuation];
|
||||||
return {text: hoverTextOf(result.symbol), span: result.span};
|
|
||||||
|
export function getHover(info: TemplateInfo): ts.QuickInfo|undefined {
|
||||||
|
const symbolInfo = locateSymbol(info);
|
||||||
|
if (!symbolInfo) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
const {symbol, span} = symbolInfo;
|
||||||
|
const containerDisplayParts: ts.SymbolDisplayPart[] = symbol.container ?
|
||||||
|
[
|
||||||
|
{text: symbol.container.name, kind: symbol.container.kind},
|
||||||
|
{text: '.', kind: SYMBOL_PUNC},
|
||||||
|
] :
|
||||||
|
[];
|
||||||
|
return {
|
||||||
|
kind: symbol.kind as ts.ScriptElementKind,
|
||||||
|
kindModifiers: '', // kindModifier info not available on 'ng.Symbol'
|
||||||
|
textSpan: {
|
||||||
|
start: span.start,
|
||||||
|
length: span.end - span.start,
|
||||||
|
},
|
||||||
|
// this would generate a string like '(property) ClassX.propY'
|
||||||
|
// 'kind' in displayParts does not really matter because it's dropped when
|
||||||
|
// displayParts get converted to string.
|
||||||
|
displayParts: [
|
||||||
|
{text: '(', kind: SYMBOL_PUNC}, {text: symbol.kind, kind: symbol.kind},
|
||||||
|
{text: ')', kind: SYMBOL_PUNC}, {text: ' ', kind: SYMBOL_SPACE}, ...containerDisplayParts,
|
||||||
|
{text: symbol.name, kind: symbol.kind},
|
||||||
|
// TODO: Append type info as well, but Symbol doesn't expose that!
|
||||||
|
// Ideally hover text should be like '(property) ClassX.propY: string'
|
||||||
|
],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function hoverTextOf(symbol: Symbol): HoverTextSection[] {
|
|
||||||
const result: HoverTextSection[] =
|
|
||||||
[{text: symbol.kind}, {text: ' '}, {text: symbol.name, language: symbol.language}];
|
|
||||||
const container = symbol.container;
|
|
||||||
if (container) {
|
|
||||||
result.push({text: ' of '}, {text: container.name, language: container.language});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
import {CompileMetadataResolver, CompilePipeSummary} from '@angular/compiler';
|
import {CompileMetadataResolver, CompilePipeSummary} from '@angular/compiler';
|
||||||
import {DiagnosticTemplateInfo, getTemplateExpressionDiagnostics} from '@angular/compiler-cli/src/language_services';
|
import {DiagnosticTemplateInfo, getTemplateExpressionDiagnostics} from '@angular/compiler-cli/src/language_services';
|
||||||
|
import * as tss from 'typescript/lib/tsserverlibrary';
|
||||||
import {getTemplateCompletions} from './completions';
|
import {getTemplateCompletions} from './completions';
|
||||||
import {getDefinition} from './definitions';
|
import {getDefinitionAndBoundSpan} from './definitions';
|
||||||
import {getDeclarationDiagnostics} from './diagnostics';
|
import {getDeclarationDiagnostics} from './diagnostics';
|
||||||
import {getHover} from './hover';
|
import {getHover} from './hover';
|
||||||
import {Completion, Diagnostic, DiagnosticKind, Diagnostics, Hover, LanguageService, LanguageServiceHost, Location, Span, TemplateSource} from './types';
|
import {Completion, Diagnostic, DiagnosticKind, Diagnostics, Hover, LanguageService, LanguageServiceHost, Location, Span, TemplateSource} from './types';
|
||||||
@ -30,8 +30,6 @@ export function createLanguageService(host: LanguageServiceHost): LanguageServic
|
|||||||
class LanguageServiceImpl implements LanguageService {
|
class LanguageServiceImpl implements LanguageService {
|
||||||
constructor(private host: LanguageServiceHost) {}
|
constructor(private host: LanguageServiceHost) {}
|
||||||
|
|
||||||
private get metadataResolver(): CompileMetadataResolver { return this.host.resolver; }
|
|
||||||
|
|
||||||
getTemplateReferences(): string[] { return this.host.getTemplateReferences(); }
|
getTemplateReferences(): string[] { return this.host.getTemplateReferences(); }
|
||||||
|
|
||||||
getDiagnostics(fileName: string): Diagnostic[] {
|
getDiagnostics(fileName: string): Diagnostic[] {
|
||||||
@ -65,14 +63,14 @@ class LanguageServiceImpl implements LanguageService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefinitionAt(fileName: string, position: number): Location[]|undefined {
|
getDefinitionAt(fileName: string, position: number): tss.DefinitionInfoAndBoundSpan|undefined {
|
||||||
let templateInfo = this.host.getTemplateAstAtPosition(fileName, position);
|
let templateInfo = this.host.getTemplateAstAtPosition(fileName, position);
|
||||||
if (templateInfo) {
|
if (templateInfo) {
|
||||||
return getDefinition(templateInfo);
|
return getDefinitionAndBoundSpan(templateInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getHoverAt(fileName: string, position: number): Hover|undefined {
|
getHoverAt(fileName: string, position: number): tss.QuickInfo|undefined {
|
||||||
let templateInfo = this.host.getTemplateAstAtPosition(fileName, position);
|
let templateInfo = this.host.getTemplateAstAtPosition(fileName, position);
|
||||||
if (templateInfo) {
|
if (templateInfo) {
|
||||||
return getHover(templateInfo);
|
return getHover(templateInfo);
|
||||||
|
@ -9,9 +9,8 @@
|
|||||||
import * as ts from 'typescript'; // used as value, passed in by tsserver at runtime
|
import * as ts from 'typescript'; // used as value, passed in by tsserver at runtime
|
||||||
import * as tss from 'typescript/lib/tsserverlibrary'; // used as type only
|
import * as tss from 'typescript/lib/tsserverlibrary'; // used as type only
|
||||||
|
|
||||||
import {ngLocationToTsDefinitionInfo} from './definitions';
|
|
||||||
import {createLanguageService} from './language_service';
|
import {createLanguageService} from './language_service';
|
||||||
import {Completion, Diagnostic, DiagnosticMessageChain, Location} from './types';
|
import {Completion, Diagnostic, DiagnosticMessageChain} from './types';
|
||||||
import {TypeScriptServiceHost} from './typescript_host';
|
import {TypeScriptServiceHost} from './typescript_host';
|
||||||
|
|
||||||
const projectHostMap = new WeakMap<tss.server.Project, TypeScriptServiceHost>();
|
const projectHostMap = new WeakMap<tss.server.Project, TypeScriptServiceHost>();
|
||||||
@ -76,13 +75,13 @@ export function create(info: tss.server.PluginCreateInfo): tss.LanguageService {
|
|||||||
// This effectively disables native TS features and is meant for internal
|
// This effectively disables native TS features and is meant for internal
|
||||||
// use only.
|
// use only.
|
||||||
const angularOnly = config ? config.angularOnly === true : false;
|
const angularOnly = config ? config.angularOnly === true : false;
|
||||||
const proxy: tss.LanguageService = Object.assign({}, tsLS);
|
|
||||||
const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS);
|
const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS);
|
||||||
const ngLS = createLanguageService(ngLSHost);
|
const ngLS = createLanguageService(ngLSHost);
|
||||||
projectHostMap.set(project, ngLSHost);
|
projectHostMap.set(project, ngLSHost);
|
||||||
|
|
||||||
proxy.getCompletionsAtPosition = function(
|
function getCompletionsAtPosition(
|
||||||
fileName: string, position: number, options: tss.GetCompletionsAtPositionOptions|undefined) {
|
fileName: string, position: number,
|
||||||
|
options: tss.GetCompletionsAtPositionOptions | undefined) {
|
||||||
if (!angularOnly) {
|
if (!angularOnly) {
|
||||||
const results = tsLS.getCompletionsAtPosition(fileName, position, options);
|
const results = tsLS.getCompletionsAtPosition(fileName, position, options);
|
||||||
if (results && results.entries.length) {
|
if (results && results.entries.length) {
|
||||||
@ -100,39 +99,20 @@ export function create(info: tss.server.PluginCreateInfo): tss.LanguageService {
|
|||||||
isNewIdentifierLocation: false,
|
isNewIdentifierLocation: false,
|
||||||
entries: results.map(completionToEntry),
|
entries: results.map(completionToEntry),
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
proxy.getQuickInfoAtPosition = function(fileName: string, position: number): tss.QuickInfo |
|
function getQuickInfoAtPosition(fileName: string, position: number): tss.QuickInfo|undefined {
|
||||||
undefined {
|
if (!angularOnly) {
|
||||||
if (!angularOnly) {
|
const result = tsLS.getQuickInfoAtPosition(fileName, position);
|
||||||
const result = tsLS.getQuickInfoAtPosition(fileName, position);
|
if (result) {
|
||||||
if (result) {
|
// If TS could answer the query, then return results immediately.
|
||||||
// If TS could answer the query, then return results immediately.
|
return result;
|
||||||
return result;
|
}
|
||||||
}
|
}
|
||||||
}
|
return ngLS.getHoverAt(fileName, position);
|
||||||
const result = ngLS.getHoverAt(fileName, position);
|
}
|
||||||
if (!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
// TODO(kyliau): Provide more useful info for kind and kindModifiers
|
|
||||||
kind: ts.ScriptElementKind.unknown,
|
|
||||||
kindModifiers: ts.ScriptElementKindModifier.none,
|
|
||||||
textSpan: {
|
|
||||||
start: result.span.start,
|
|
||||||
length: result.span.end - result.span.start,
|
|
||||||
},
|
|
||||||
displayParts: result.text.map((part) => {
|
|
||||||
return {
|
|
||||||
text: part.text,
|
|
||||||
kind: part.language || 'angular',
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
proxy.getSemanticDiagnostics = function(fileName: string): tss.Diagnostic[] {
|
function getSemanticDiagnostics(fileName: string): tss.Diagnostic[] {
|
||||||
const results: tss.Diagnostic[] = [];
|
const results: tss.Diagnostic[] = [];
|
||||||
if (!angularOnly) {
|
if (!angularOnly) {
|
||||||
const tsResults = tsLS.getSemanticDiagnostics(fileName);
|
const tsResults = tsLS.getSemanticDiagnostics(fileName);
|
||||||
@ -146,48 +126,43 @@ export function create(info: tss.server.PluginCreateInfo): tss.LanguageService {
|
|||||||
const sourceFile = fileName.endsWith('.ts') ? ngLSHost.getSourceFile(fileName) : undefined;
|
const sourceFile = fileName.endsWith('.ts') ? ngLSHost.getSourceFile(fileName) : undefined;
|
||||||
results.push(...ngResults.map(d => diagnosticToDiagnostic(d, sourceFile)));
|
results.push(...ngResults.map(d => diagnosticToDiagnostic(d, sourceFile)));
|
||||||
return results;
|
return results;
|
||||||
};
|
}
|
||||||
|
|
||||||
proxy.getDefinitionAtPosition = function(fileName: string, position: number):
|
function getDefinitionAtPosition(
|
||||||
ReadonlyArray<tss.DefinitionInfo>|
|
fileName: string, position: number): ReadonlyArray<tss.DefinitionInfo>|undefined {
|
||||||
undefined {
|
if (!angularOnly) {
|
||||||
if (!angularOnly) {
|
const results = tsLS.getDefinitionAtPosition(fileName, position);
|
||||||
const results = tsLS.getDefinitionAtPosition(fileName, position);
|
if (results) {
|
||||||
if (results) {
|
// If TS could answer the query, then return results immediately.
|
||||||
// If TS could answer the query, then return results immediately.
|
return results;
|
||||||
return results;
|
}
|
||||||
}
|
}
|
||||||
}
|
const result = ngLS.getDefinitionAt(fileName, position);
|
||||||
const results = ngLS.getDefinitionAt(fileName, position);
|
if (!result || !result.definitions || !result.definitions.length) {
|
||||||
if (!results) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
return result.definitions;
|
||||||
return results.map(ngLocationToTsDefinitionInfo);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
proxy.getDefinitionAndBoundSpan = function(fileName: string, position: number):
|
function getDefinitionAndBoundSpan(
|
||||||
tss.DefinitionInfoAndBoundSpan |
|
fileName: string, position: number): tss.DefinitionInfoAndBoundSpan|undefined {
|
||||||
undefined {
|
if (!angularOnly) {
|
||||||
if (!angularOnly) {
|
const result = tsLS.getDefinitionAndBoundSpan(fileName, position);
|
||||||
const result = tsLS.getDefinitionAndBoundSpan(fileName, position);
|
if (result) {
|
||||||
if (result) {
|
// If TS could answer the query, then return results immediately.
|
||||||
// If TS could answer the query, then return results immediately.
|
return result;
|
||||||
return result;
|
}
|
||||||
}
|
}
|
||||||
}
|
return ngLS.getDefinitionAt(fileName, position);
|
||||||
const results = ngLS.getDefinitionAt(fileName, position);
|
}
|
||||||
if (!results || !results.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const {span} = results[0];
|
|
||||||
return {
|
|
||||||
definitions: results.map(ngLocationToTsDefinitionInfo),
|
|
||||||
textSpan: {
|
|
||||||
start: span.start,
|
|
||||||
length: span.end - span.start,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const proxy: tss.LanguageService = Object.assign(
|
||||||
|
// First clone the original TS language service
|
||||||
|
{}, tsLS,
|
||||||
|
// Then override the methods supported by Angular language service
|
||||||
|
{
|
||||||
|
getCompletionsAtPosition, getQuickInfoAtPosition, getSemanticDiagnostics,
|
||||||
|
getDefinitionAtPosition, getDefinitionAndBoundSpan,
|
||||||
|
});
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
import {CompileDirectiveMetadata, CompileMetadataResolver, CompilePipeSummary, NgAnalyzedModules, StaticSymbol} from '@angular/compiler';
|
import {CompileDirectiveMetadata, CompileMetadataResolver, CompilePipeSummary, NgAnalyzedModules, StaticSymbol} from '@angular/compiler';
|
||||||
import {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from '@angular/compiler-cli/src/language_services';
|
import {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from '@angular/compiler-cli/src/language_services';
|
||||||
|
import * as tss from 'typescript/lib/tsserverlibrary';
|
||||||
|
|
||||||
import {AstResult, TemplateInfo} from './common';
|
import {AstResult, TemplateInfo} from './common';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -394,12 +396,12 @@ export interface LanguageService {
|
|||||||
/**
|
/**
|
||||||
* Return the definition location for the symbol at position.
|
* Return the definition location for the symbol at position.
|
||||||
*/
|
*/
|
||||||
getDefinitionAt(fileName: string, position: number): Location[]|undefined;
|
getDefinitionAt(fileName: string, position: number): tss.DefinitionInfoAndBoundSpan|undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the hover information for the symbol at position.
|
* Return the hover information for the symbol at position.
|
||||||
*/
|
*/
|
||||||
getHoverAt(fileName: string, position: number): Hover|undefined;
|
getHoverAt(fileName: string, position: number): tss.QuickInfo|undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the pipes that are available at the given position.
|
* Return the pipes that are available at the given position.
|
||||||
|
@ -9,159 +9,319 @@
|
|||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {createLanguageService} from '../src/language_service';
|
import {createLanguageService} from '../src/language_service';
|
||||||
import {Span} from '../src/types';
|
import {LanguageService} from '../src/types';
|
||||||
import {TypeScriptServiceHost} from '../src/typescript_host';
|
import {TypeScriptServiceHost} from '../src/typescript_host';
|
||||||
|
|
||||||
import {toh} from './test_data';
|
import {toh} from './test_data';
|
||||||
|
import {MockTypescriptHost} from './test_utils';
|
||||||
import {MockTypescriptHost,} from './test_utils';
|
|
||||||
|
|
||||||
describe('definitions', () => {
|
describe('definitions', () => {
|
||||||
let documentRegistry = ts.createDocumentRegistry();
|
let mockHost: MockTypescriptHost;
|
||||||
let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
|
let service: ts.LanguageService;
|
||||||
let service = ts.createLanguageService(mockHost, documentRegistry);
|
let ngHost: TypeScriptServiceHost;
|
||||||
let ngHost = new TypeScriptServiceHost(mockHost, service);
|
let ngService: LanguageService;
|
||||||
let ngService = createLanguageService(ngHost);
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Create a new mockHost every time to reset any files that are overridden.
|
||||||
|
mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
|
||||||
|
service = ts.createLanguageService(mockHost);
|
||||||
|
ngHost = new TypeScriptServiceHost(mockHost, service);
|
||||||
|
ngService = createLanguageService(ngHost);
|
||||||
|
});
|
||||||
|
|
||||||
it('should be able to find field in an interpolation', () => {
|
it('should be able to find field in an interpolation', () => {
|
||||||
localReference(
|
const fileName = addCode(`
|
||||||
` @Component({template: '{{«name»}}'}) export class MyComponent { «ᐱnameᐱ: string;» }`);
|
@Component({
|
||||||
|
template: '{{«name»}}'
|
||||||
|
})
|
||||||
|
export class MyComponent {
|
||||||
|
«ᐱnameᐱ: string;»
|
||||||
|
}`);
|
||||||
|
|
||||||
|
const marker = getReferenceMarkerFor(fileName, 'name');
|
||||||
|
const result = ngService.getDefinitionAt(fileName, marker.start);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
const {textSpan, definitions} = result !;
|
||||||
|
|
||||||
|
expect(textSpan).toEqual(marker);
|
||||||
|
expect(definitions).toBeDefined();
|
||||||
|
expect(definitions !.length).toBe(1);
|
||||||
|
const def = definitions ![0];
|
||||||
|
|
||||||
|
expect(def.fileName).toBe(fileName);
|
||||||
|
expect(def.name).toBe('name');
|
||||||
|
expect(def.kind).toBe('property');
|
||||||
|
expect(def.textSpan).toEqual(getDefinitionMarkerFor(fileName, 'name'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to find a field in a attribute reference', () => {
|
it('should be able to find a field in a attribute reference', () => {
|
||||||
localReference(
|
const fileName = addCode(`
|
||||||
` @Component({template: '<input [(ngModel)]="«name»">'}) export class MyComponent { «ᐱnameᐱ: string;» }`);
|
@Component({
|
||||||
|
template: '<input [(ngModel)]="«name»">'
|
||||||
|
})
|
||||||
|
export class MyComponent {
|
||||||
|
«ᐱnameᐱ: string;»
|
||||||
|
}`);
|
||||||
|
|
||||||
|
const marker = getReferenceMarkerFor(fileName, 'name');
|
||||||
|
const result = ngService.getDefinitionAt(fileName, marker.start);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
const {textSpan, definitions} = result !;
|
||||||
|
|
||||||
|
expect(textSpan).toEqual(marker);
|
||||||
|
expect(definitions).toBeDefined();
|
||||||
|
expect(definitions !.length).toBe(1);
|
||||||
|
const def = definitions ![0];
|
||||||
|
|
||||||
|
expect(def.fileName).toBe(fileName);
|
||||||
|
expect(def.name).toBe('name');
|
||||||
|
expect(def.kind).toBe('property');
|
||||||
|
expect(def.textSpan).toEqual(getDefinitionMarkerFor(fileName, 'name'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to find a method from a call', () => {
|
it('should be able to find a method from a call', () => {
|
||||||
localReference(
|
const fileName = addCode(`
|
||||||
` @Component({template: '<div (click)="«myClick»();"></div>'}) export class MyComponent { «ᐱmyClickᐱ() { }»}`);
|
@Component({
|
||||||
|
template: '<div (click)="~{start-my}«myClick»()~{end-my};"></div>'
|
||||||
|
})
|
||||||
|
export class MyComponent {
|
||||||
|
«ᐱmyClickᐱ() { }»
|
||||||
|
}`);
|
||||||
|
|
||||||
|
const marker = getReferenceMarkerFor(fileName, 'myClick');
|
||||||
|
const result = ngService.getDefinitionAt(fileName, marker.start);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
const {textSpan, definitions} = result !;
|
||||||
|
|
||||||
|
expect(textSpan).toEqual(getLocationMarkerFor(fileName, 'my'));
|
||||||
|
expect(definitions).toBeDefined();
|
||||||
|
expect(definitions !.length).toBe(1);
|
||||||
|
const def = definitions ![0];
|
||||||
|
|
||||||
|
expect(def.fileName).toBe(fileName);
|
||||||
|
expect(def.name).toBe('myClick');
|
||||||
|
expect(def.kind).toBe('method');
|
||||||
|
expect(def.textSpan).toEqual(getDefinitionMarkerFor(fileName, 'myClick'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to find a field reference in an *ngIf', () => {
|
it('should be able to find a field reference in an *ngIf', () => {
|
||||||
localReference(
|
const fileName = addCode(`
|
||||||
` @Component({template: '<div *ngIf="«include»"></div>'}) export class MyComponent { «ᐱincludeᐱ = true;»}`);
|
@Component({
|
||||||
|
template: '<div *ngIf="«include»"></div>'
|
||||||
|
})
|
||||||
|
export class MyComponent {
|
||||||
|
«ᐱincludeᐱ = true;»
|
||||||
|
}`);
|
||||||
|
|
||||||
|
const marker = getReferenceMarkerFor(fileName, 'include');
|
||||||
|
const result = ngService.getDefinitionAt(fileName, marker.start);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
const {textSpan, definitions} = result !;
|
||||||
|
|
||||||
|
expect(textSpan).toEqual(marker);
|
||||||
|
expect(definitions).toBeDefined();
|
||||||
|
expect(definitions !.length).toBe(1);
|
||||||
|
const def = definitions ![0];
|
||||||
|
|
||||||
|
expect(def.fileName).toBe(fileName);
|
||||||
|
expect(def.name).toBe('include');
|
||||||
|
expect(def.kind).toBe('property');
|
||||||
|
expect(def.textSpan).toEqual(getDefinitionMarkerFor(fileName, 'include'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to find a reference to a component', () => {
|
it('should be able to find a reference to a component', () => {
|
||||||
reference(
|
const fileName = addCode(`
|
||||||
'parsing-cases.ts',
|
@Component({
|
||||||
` @Component({template: '<«test-comp»></test-comp>'}) export class MyComponent { }`);
|
template: '~{start-my}<«test-comp»></test-comp>~{end-my}'
|
||||||
|
})
|
||||||
|
export class MyComponent { }`);
|
||||||
|
|
||||||
|
// Get the marker for «test-comp» in the code added above.
|
||||||
|
const marker = getReferenceMarkerFor(fileName, 'test-comp');
|
||||||
|
|
||||||
|
const result = ngService.getDefinitionAt(fileName, marker.start);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
const {textSpan, definitions} = result !;
|
||||||
|
|
||||||
|
// Get the marker for bounded text in the code added above.
|
||||||
|
const boundedText = getLocationMarkerFor(fileName, 'my');
|
||||||
|
expect(textSpan).toEqual(boundedText);
|
||||||
|
|
||||||
|
// There should be exactly 1 definition
|
||||||
|
expect(definitions).toBeDefined();
|
||||||
|
expect(definitions !.length).toBe(1);
|
||||||
|
const def = definitions ![0];
|
||||||
|
|
||||||
|
const refFileName = '/app/parsing-cases.ts';
|
||||||
|
expect(def.fileName).toBe(refFileName);
|
||||||
|
expect(def.name).toBe('TestComponent');
|
||||||
|
expect(def.kind).toBe('component');
|
||||||
|
expect(def.textSpan).toEqual(getLocationMarkerFor(refFileName, 'test-comp'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to find an event provider', () => {
|
it('should be able to find an event provider', () => {
|
||||||
reference(
|
const fileName = addCode(`
|
||||||
'/app/parsing-cases.ts', 'test',
|
@Component({
|
||||||
` @Component({template: '<test-comp («test»)="myHandler()"></div>'}) export class MyComponent { myHandler() {} }`);
|
template: '<test-comp ~{start-my}(«test»)="myHandler()"~{end-my}></div>'
|
||||||
|
})
|
||||||
|
export class MyComponent { myHandler() {} }`);
|
||||||
|
|
||||||
|
// Get the marker for «test» in the code added above.
|
||||||
|
const marker = getReferenceMarkerFor(fileName, 'test');
|
||||||
|
|
||||||
|
const result = ngService.getDefinitionAt(fileName, marker.start);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
const {textSpan, definitions} = result !;
|
||||||
|
|
||||||
|
// Get the marker for bounded text in the code added above
|
||||||
|
const boundedText = getLocationMarkerFor(fileName, 'my');
|
||||||
|
expect(textSpan).toEqual(boundedText);
|
||||||
|
|
||||||
|
// There should be exactly 1 definition
|
||||||
|
expect(definitions).toBeDefined();
|
||||||
|
expect(definitions !.length).toBe(1);
|
||||||
|
const def = definitions ![0];
|
||||||
|
|
||||||
|
const refFileName = '/app/parsing-cases.ts';
|
||||||
|
expect(def.fileName).toBe(refFileName);
|
||||||
|
expect(def.name).toBe('testEvent');
|
||||||
|
expect(def.kind).toBe('event');
|
||||||
|
expect(def.textSpan).toEqual(getDefinitionMarkerFor(refFileName, 'test'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to find an input provider', () => {
|
it('should be able to find an input provider', () => {
|
||||||
reference(
|
// '/app/parsing-cases.ts', 'tcName',
|
||||||
'/app/parsing-cases.ts', 'tcName',
|
const fileName = addCode(`
|
||||||
` @Component({template: '<test-comp [«tcName»]="name"></div>'}) export class MyComponent { name = 'my name'; }`);
|
@Component({
|
||||||
|
template: '<test-comp ~{start-my}[«tcName»]="name"~{end-my}></div>'
|
||||||
|
})
|
||||||
|
export class MyComponent {
|
||||||
|
name = 'my name';
|
||||||
|
}`);
|
||||||
|
|
||||||
|
// Get the marker for «test» in the code added above.
|
||||||
|
const marker = getReferenceMarkerFor(fileName, 'tcName');
|
||||||
|
|
||||||
|
const result = ngService.getDefinitionAt(fileName, marker.start);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
const {textSpan, definitions} = result !;
|
||||||
|
|
||||||
|
// Get the marker for bounded text in the code added above
|
||||||
|
const boundedText = getLocationMarkerFor(fileName, 'my');
|
||||||
|
expect(textSpan).toEqual(boundedText);
|
||||||
|
|
||||||
|
// There should be exactly 1 definition
|
||||||
|
expect(definitions).toBeDefined();
|
||||||
|
expect(definitions !.length).toBe(1);
|
||||||
|
const def = definitions ![0];
|
||||||
|
|
||||||
|
const refFileName = '/app/parsing-cases.ts';
|
||||||
|
expect(def.fileName).toBe(refFileName);
|
||||||
|
expect(def.name).toBe('name');
|
||||||
|
expect(def.kind).toBe('property');
|
||||||
|
expect(def.textSpan).toEqual(getDefinitionMarkerFor(refFileName, 'tcName'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to find a pipe', () => {
|
it('should be able to find a pipe', () => {
|
||||||
reference(
|
const fileName = addCode(`
|
||||||
'common.d.ts',
|
@Component({
|
||||||
` @Component({template: '<div *ngIf="input | «async»"></div>'}) export class MyComponent { input: EventEmitter; }`);
|
template: '<div *ngIf="~{start-my}input | «async»~{end-my}"></div>'
|
||||||
|
})
|
||||||
|
export class MyComponent {
|
||||||
|
input: EventEmitter;
|
||||||
|
}`);
|
||||||
|
|
||||||
|
// Get the marker for «test» in the code added above.
|
||||||
|
const marker = getReferenceMarkerFor(fileName, 'async');
|
||||||
|
|
||||||
|
const result = ngService.getDefinitionAt(fileName, marker.start);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
const {textSpan, definitions} = result !;
|
||||||
|
|
||||||
|
// Get the marker for bounded text in the code added above
|
||||||
|
const boundedText = getLocationMarkerFor(fileName, 'my');
|
||||||
|
expect(textSpan).toEqual(boundedText);
|
||||||
|
|
||||||
|
expect(definitions).toBeDefined();
|
||||||
|
expect(definitions !.length).toBe(4);
|
||||||
|
|
||||||
|
const refFileName = '/node_modules/@angular/common/common.d.ts';
|
||||||
|
for (const def of definitions !) {
|
||||||
|
expect(def.fileName).toBe(refFileName);
|
||||||
|
expect(def.name).toBe('async');
|
||||||
|
expect(def.kind).toBe('pipe');
|
||||||
|
// Not asserting the textSpan of definition because it's external file
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function localReference(code: string) {
|
/**
|
||||||
addCode(code, fileName => {
|
* Append a snippet of code to `app.component.ts` and return the file name.
|
||||||
const refResult = mockHost.getReferenceMarkers(fileName) !;
|
* There must not be any name collision with existing code.
|
||||||
for (const name in refResult.references) {
|
* @param code Snippet of code
|
||||||
const references = refResult.references[name];
|
*/
|
||||||
const definitions = refResult.definitions[name];
|
function addCode(code: string) {
|
||||||
expect(definitions).toBeDefined(); // If this fails the test data is wrong.
|
|
||||||
for (const reference of references) {
|
|
||||||
const definition = ngService.getDefinitionAt(fileName, reference.start);
|
|
||||||
if (definition) {
|
|
||||||
definition.forEach(d => expect(d.fileName).toEqual(fileName));
|
|
||||||
const match = matchingSpan(definition.map(d => d.span), definitions);
|
|
||||||
if (!match) {
|
|
||||||
throw new Error(
|
|
||||||
`Expected one of ${stringifySpans(definition.map(d => d.span))} to match one of ${stringifySpans(definitions)}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('Expected a definition');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function reference(referencedFile: string, code: string): void;
|
|
||||||
function reference(referencedFile: string, span: Span, code: string): void;
|
|
||||||
function reference(referencedFile: string, definition: string, code: string): void;
|
|
||||||
function reference(referencedFile: string, p1?: any, p2?: any): void {
|
|
||||||
const code: string = p2 ? p2 : p1;
|
|
||||||
const definition: string = p2 ? p1 : undefined;
|
|
||||||
let span: Span = p2 && p1.start != null ? p1 : undefined;
|
|
||||||
if (definition && !span) {
|
|
||||||
const referencedFileMarkers = mockHost.getReferenceMarkers(referencedFile) !;
|
|
||||||
expect(referencedFileMarkers).toBeDefined(); // If this fails the test data is wrong.
|
|
||||||
const spans = referencedFileMarkers.definitions[definition];
|
|
||||||
expect(spans).toBeDefined(); // If this fails the test data is wrong.
|
|
||||||
span = spans[0];
|
|
||||||
}
|
|
||||||
addCode(code, fileName => {
|
|
||||||
const refResult = mockHost.getReferenceMarkers(fileName) !;
|
|
||||||
let tests = 0;
|
|
||||||
for (const name in refResult.references) {
|
|
||||||
const references = refResult.references[name];
|
|
||||||
expect(reference).toBeDefined(); // If this fails the test data is wrong.
|
|
||||||
for (const reference of references) {
|
|
||||||
tests++;
|
|
||||||
const definition = ngService.getDefinitionAt(fileName, reference.start);
|
|
||||||
if (definition) {
|
|
||||||
definition.forEach(d => {
|
|
||||||
if (d.fileName.indexOf(referencedFile) < 0) {
|
|
||||||
throw new Error(
|
|
||||||
`Expected reference to file ${referencedFile}, received ${d.fileName}`);
|
|
||||||
}
|
|
||||||
if (span) {
|
|
||||||
expect(d.span).toEqual(span);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error('Expected a definition');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!tests) {
|
|
||||||
throw new Error('Expected at least one reference (test data error)');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addCode(code: string, cb: (fileName: string, content?: string) => void) {
|
|
||||||
const fileName = '/app/app.component.ts';
|
const fileName = '/app/app.component.ts';
|
||||||
const originalContent = mockHost.getFileContent(fileName);
|
const originalContent = mockHost.getFileContent(fileName);
|
||||||
const newContent = originalContent + code;
|
const newContent = originalContent + code;
|
||||||
mockHost.override(fileName, originalContent + code);
|
mockHost.override(fileName, newContent);
|
||||||
try {
|
return fileName;
|
||||||
cb(fileName, newContent);
|
}
|
||||||
} finally {
|
|
||||||
mockHost.override(fileName, undefined !);
|
/**
|
||||||
}
|
* Returns the definition marker ᐱselectorᐱ for the specified 'selector'.
|
||||||
|
* Asserts that marker exists.
|
||||||
|
* @param fileName name of the file
|
||||||
|
* @param selector name of the marker
|
||||||
|
*/
|
||||||
|
function getDefinitionMarkerFor(fileName: string, selector: string): ts.TextSpan {
|
||||||
|
const markers = mockHost.getReferenceMarkers(fileName);
|
||||||
|
expect(markers).toBeDefined();
|
||||||
|
expect(Object.keys(markers !.definitions)).toContain(selector);
|
||||||
|
expect(markers !.definitions[selector].length).toBe(1);
|
||||||
|
const marker = markers !.definitions[selector][0];
|
||||||
|
expect(marker.start).toBeLessThanOrEqual(marker.end);
|
||||||
|
return {
|
||||||
|
start: marker.start,
|
||||||
|
length: marker.end - marker.start,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the reference marker «selector» for the specified 'selector'.
|
||||||
|
* Asserts that marker exists.
|
||||||
|
* @param fileName name of the file
|
||||||
|
* @param selector name of the marker
|
||||||
|
*/
|
||||||
|
function getReferenceMarkerFor(fileName: string, selector: string): ts.TextSpan {
|
||||||
|
const markers = mockHost.getReferenceMarkers(fileName);
|
||||||
|
expect(markers).toBeDefined();
|
||||||
|
expect(Object.keys(markers !.references)).toContain(selector);
|
||||||
|
expect(markers !.references[selector].length).toBe(1);
|
||||||
|
const marker = markers !.references[selector][0];
|
||||||
|
expect(marker.start).toBeLessThanOrEqual(marker.end);
|
||||||
|
return {
|
||||||
|
start: marker.start,
|
||||||
|
length: marker.end - marker.start,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the location marker ~{selector} for the specified 'selector'.
|
||||||
|
* Asserts that marker exists.
|
||||||
|
* @param fileName name of the file
|
||||||
|
* @param selector name of the marker
|
||||||
|
*/
|
||||||
|
function getLocationMarkerFor(fileName: string, selector: string): ts.TextSpan {
|
||||||
|
const markers = mockHost.getMarkerLocations(fileName);
|
||||||
|
expect(markers).toBeDefined();
|
||||||
|
const start = markers ![`start-${selector}`];
|
||||||
|
expect(start).toBeDefined();
|
||||||
|
const end = markers ![`end-${selector}`];
|
||||||
|
expect(end).toBeDefined();
|
||||||
|
expect(start).toBeLessThanOrEqual(end);
|
||||||
|
return {
|
||||||
|
start: start,
|
||||||
|
length: end - start,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function matchingSpan(aSpans: Span[], bSpans: Span[]): Span|undefined {
|
|
||||||
for (const a of aSpans) {
|
|
||||||
for (const b of bSpans) {
|
|
||||||
if (a.start == b.start && a.end == b.end) {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringifySpan(span: Span) {
|
|
||||||
return span ? `(${span.start}-${span.end})` : '<undefined>';
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringifySpans(spans: Span[]) {
|
|
||||||
return spans ? `[${spans.map(stringifySpan).join(', ')}]` : '<empty>';
|
|
||||||
}
|
|
||||||
|
@ -28,43 +28,43 @@ describe('hover', () => {
|
|||||||
it('should be able to find field in an interpolation', () => {
|
it('should be able to find field in an interpolation', () => {
|
||||||
hover(
|
hover(
|
||||||
` @Component({template: '{{«name»}}'}) export class MyComponent { name: string; }`,
|
` @Component({template: '{{«name»}}'}) export class MyComponent { name: string; }`,
|
||||||
'property name of MyComponent');
|
'(property) MyComponent.name');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to find a field in a attribute reference', () => {
|
it('should be able to find a field in a attribute reference', () => {
|
||||||
hover(
|
hover(
|
||||||
` @Component({template: '<input [(ngModel)]="«name»">'}) export class MyComponent { name: string; }`,
|
` @Component({template: '<input [(ngModel)]="«name»">'}) export class MyComponent { name: string; }`,
|
||||||
'property name of MyComponent');
|
'(property) MyComponent.name');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to find a method from a call', () => {
|
it('should be able to find a method from a call', () => {
|
||||||
hover(
|
hover(
|
||||||
` @Component({template: '<div (click)="«ᐱmyClickᐱ()»;"></div>'}) export class MyComponent { myClick() { }}`,
|
` @Component({template: '<div (click)="«ᐱmyClickᐱ()»;"></div>'}) export class MyComponent { myClick() { }}`,
|
||||||
'method myClick of MyComponent');
|
'(method) MyComponent.myClick');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to find a field reference in an *ngIf', () => {
|
it('should be able to find a field reference in an *ngIf', () => {
|
||||||
hover(
|
hover(
|
||||||
` @Component({template: '<div *ngIf="«include»"></div>'}) export class MyComponent { include = true;}`,
|
` @Component({template: '<div *ngIf="«include»"></div>'}) export class MyComponent { include = true;}`,
|
||||||
'property include of MyComponent');
|
'(property) MyComponent.include');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to find a reference to a component', () => {
|
it('should be able to find a reference to a component', () => {
|
||||||
hover(
|
hover(
|
||||||
` @Component({template: '«<ᐱtestᐱ-comp></test-comp>»'}) export class MyComponent { }`,
|
` @Component({template: '«<ᐱtestᐱ-comp></test-comp>»'}) export class MyComponent { }`,
|
||||||
'component TestComponent');
|
'(component) TestComponent');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to find an event provider', () => {
|
it('should be able to find an event provider', () => {
|
||||||
hover(
|
hover(
|
||||||
` @Component({template: '<test-comp «(ᐱtestᐱ)="myHandler()"»></div>'}) export class MyComponent { myHandler() {} }`,
|
` @Component({template: '<test-comp «(ᐱtestᐱ)="myHandler()"»></div>'}) export class MyComponent { myHandler() {} }`,
|
||||||
'event testEvent of TestComponent');
|
'(event) TestComponent.testEvent');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to find an input provider', () => {
|
it('should be able to find an input provider', () => {
|
||||||
hover(
|
hover(
|
||||||
` @Component({template: '<test-comp «[ᐱtcNameᐱ]="name"»></div>'}) export class MyComponent { name = 'my name'; }`,
|
` @Component({template: '<test-comp «[ᐱtcNameᐱ]="name"»></div>'}) export class MyComponent { name = 'my name'; }`,
|
||||||
'property name of TestComponent');
|
'(property) TestComponent.name');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to ignore a reference declaration', () => {
|
it('should be able to ignore a reference declaration', () => {
|
||||||
@ -87,10 +87,13 @@ describe('hover', () => {
|
|||||||
[]).concat(markers.definitions[referenceName] || []);
|
[]).concat(markers.definitions[referenceName] || []);
|
||||||
for (const reference of references) {
|
for (const reference of references) {
|
||||||
tests++;
|
tests++;
|
||||||
const hover = ngService.getHoverAt(fileName, reference.start);
|
const quickInfo = ngService.getHoverAt(fileName, reference.start);
|
||||||
if (!hover) throw new Error(`Expected a hover at location ${reference.start}`);
|
if (!quickInfo) throw new Error(`Expected a hover at location ${reference.start}`);
|
||||||
expect(hover.span).toEqual(reference);
|
expect(quickInfo.textSpan).toEqual({
|
||||||
expect(toText(hover)).toEqual(hoverText);
|
start: reference.start,
|
||||||
|
length: reference.end - reference.start,
|
||||||
|
});
|
||||||
|
expect(toText(quickInfo)).toEqual(hoverText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expect(tests).toBeGreaterThan(0); // If this fails the test is wrong.
|
expect(tests).toBeGreaterThan(0); // If this fails the test is wrong.
|
||||||
@ -109,5 +112,8 @@ describe('hover', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toText(hover: Hover): string { return hover.text.map(h => h.text).join(''); }
|
function toText(quickInfo: ts.QuickInfo): string {
|
||||||
|
const displayParts = quickInfo.displayParts || [];
|
||||||
|
return displayParts.map(p => p.text).join('');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
@ -139,11 +139,11 @@ export class ForUsingComponent {
|
|||||||
@Component({template: '<div #div> <test-comp #test1> {{~{test-comp-content}}} {{test1.~{test-comp-after-test}name}} {{div.~{test-comp-after-div}.innerText}} </test-comp> </div> <test-comp #test2></test-comp>'})
|
@Component({template: '<div #div> <test-comp #test1> {{~{test-comp-content}}} {{test1.~{test-comp-after-test}name}} {{div.~{test-comp-after-div}.innerText}} </test-comp> </div> <test-comp #test2></test-comp>'})
|
||||||
export class References {}
|
export class References {}
|
||||||
|
|
||||||
@Component({selector: 'test-comp', template: '<div>Testing: {{name}}</div>'})
|
~{start-test-comp}@Component({selector: 'test-comp', template: '<div>Testing: {{name}}</div>'})
|
||||||
export class TestComponent {
|
export class TestComponent {
|
||||||
«@Input('ᐱtcNameᐱ') name = 'test';»
|
«@Input('ᐱtcNameᐱ') name = 'test';»
|
||||||
«@Output('ᐱtestᐱ') testEvent = new EventEmitter();»
|
«@Output('ᐱtestᐱ') testEvent = new EventEmitter();»
|
||||||
}
|
}~{end-test-comp}
|
||||||
|
|
||||||
@Component({templateUrl: 'test.ng'})
|
@Component({templateUrl: 'test.ng'})
|
||||||
export class TemplateReference {
|
export class TemplateReference {
|
||||||
|
@ -333,7 +333,7 @@ export type RunGuardsAndResolvers = 'pathParamsChange' | 'pathParamsOrQueryParam
|
|||||||
* and both of them require an ID parameter. You can accomplish this using a route
|
* and both of them require an ID parameter. You can accomplish this using a route
|
||||||
* that does not specify a component at the top level.
|
* that does not specify a component at the top level.
|
||||||
*
|
*
|
||||||
* In the following example, 'ChildCmp' and 'AuxCmp' are siblings.
|
* In the following example, 'MainChild' and 'AuxChild' are siblings.
|
||||||
* When navigating to 'parent/10/(a//aux:b)', the route instantiates
|
* When navigating to 'parent/10/(a//aux:b)', the route instantiates
|
||||||
* the main child and aux child components next to each other.
|
* the main child and aux child components next to each other.
|
||||||
* For this to work, the application component must have the primary and aux outlets defined.
|
* For this to work, the application component must have the primary and aux outlets defined.
|
||||||
|
@ -196,7 +196,11 @@ export function downgradeComponent(info: {
|
|||||||
wrapCallback(() => doDowngrade(pInjector, mInjector))();
|
wrapCallback(() => doDowngrade(pInjector, mInjector))();
|
||||||
};
|
};
|
||||||
|
|
||||||
ParentInjectorPromise.all([finalParentInjector, finalModuleInjector])
|
// NOTE:
|
||||||
|
// Not using `ParentInjectorPromise.all()` (which is inherited from `SyncPromise`), because
|
||||||
|
// Closure Compiler (or some related tool) complains:
|
||||||
|
// `TypeError: ...$src$downgrade_component_ParentInjectorPromise.all is not a function`
|
||||||
|
SyncPromise.all([finalParentInjector, finalModuleInjector])
|
||||||
.then(([pInjector, mInjector]) => downgradeFn(pInjector, mInjector));
|
.then(([pInjector, mInjector]) => downgradeFn(pInjector, mInjector));
|
||||||
|
|
||||||
ranAsync = true;
|
ranAsync = true;
|
||||||
|
@ -286,7 +286,20 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
|
|||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
static all<R>(values: any): Promise<R> {
|
static all<R>(values: any): Promise<R> { return ZoneAwarePromise.allWithCallback(values); }
|
||||||
|
|
||||||
|
static allSettled<R>(values: any): Promise<R> {
|
||||||
|
const P = this && this.prototype instanceof ZoneAwarePromise ? this : ZoneAwarePromise;
|
||||||
|
return P.allWithCallback(values, {
|
||||||
|
thenCallback: (value: any) => ({status: 'fulfilled', value}),
|
||||||
|
errorCallback: (err: any) => ({status: 'rejected', reason: err})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static allWithCallback<R>(values: any, callback?: {
|
||||||
|
thenCallback: (value: any) => any,
|
||||||
|
errorCallback: (err: any) => any
|
||||||
|
}): Promise<R> {
|
||||||
let resolve: (v: any) => void;
|
let resolve: (v: any) => void;
|
||||||
let reject: (v: any) => void;
|
let reject: (v: any) => void;
|
||||||
let promise = new this<R>((res, rej) => {
|
let promise = new this<R>((res, rej) => {
|
||||||
@ -305,13 +318,29 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
|
|||||||
}
|
}
|
||||||
|
|
||||||
const curValueIndex = valueIndex;
|
const curValueIndex = valueIndex;
|
||||||
value.then((value: any) => {
|
try {
|
||||||
resolvedValues[curValueIndex] = value;
|
value.then(
|
||||||
unresolvedCount--;
|
(value: any) => {
|
||||||
if (unresolvedCount === 0) {
|
resolvedValues[curValueIndex] = callback ? callback.thenCallback(value) : value;
|
||||||
resolve !(resolvedValues);
|
unresolvedCount--;
|
||||||
}
|
if (unresolvedCount === 0) {
|
||||||
}, reject !);
|
resolve !(resolvedValues);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err: any) => {
|
||||||
|
if (!callback) {
|
||||||
|
reject !(err);
|
||||||
|
} else {
|
||||||
|
resolvedValues[curValueIndex] = callback.errorCallback(err);
|
||||||
|
unresolvedCount--;
|
||||||
|
if (unresolvedCount === 0) {
|
||||||
|
resolve !(resolvedValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (thenErr) {
|
||||||
|
reject !(thenErr);
|
||||||
|
}
|
||||||
|
|
||||||
unresolvedCount++;
|
unresolvedCount++;
|
||||||
valueIndex++;
|
valueIndex++;
|
||||||
|
@ -518,4 +518,98 @@ describe(
|
|||||||
testPromiseSubClass(done);
|
testPromiseSubClass(done);
|
||||||
} : function() { testPromiseSubClass(); });
|
} : function() { testPromiseSubClass(); });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Promise.allSettled', () => {
|
||||||
|
const yes = function makeFulfilledResult(value: any) {
|
||||||
|
return {status: 'fulfilled', value: value};
|
||||||
|
};
|
||||||
|
const no = function makeRejectedResult(reason: any) {
|
||||||
|
return {status: 'rejected', reason: reason};
|
||||||
|
};
|
||||||
|
const a = {};
|
||||||
|
const b = {};
|
||||||
|
const c = {};
|
||||||
|
const allSettled = (Promise as any).allSettled;
|
||||||
|
it('no promise values', (done: DoneFn) => {
|
||||||
|
allSettled([a, b, c]).then((results: any[]) => {
|
||||||
|
expect(results).toEqual([yes(a), yes(b), yes(c)]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('all fulfilled', (done: DoneFn) => {
|
||||||
|
allSettled([
|
||||||
|
Promise.resolve(a), Promise.resolve(b), Promise.resolve(c)
|
||||||
|
]).then((results: any[]) => {
|
||||||
|
expect(results).toEqual([yes(a), yes(b), yes(c)]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('all rejected', (done: DoneFn) => {
|
||||||
|
allSettled([
|
||||||
|
Promise.reject(a), Promise.reject(b), Promise.reject(c)
|
||||||
|
]).then((results: any[]) => {
|
||||||
|
expect(results).toEqual([no(a), no(b), no(c)]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('mixed', (done: DoneFn) => {
|
||||||
|
allSettled([a, Promise.resolve(b), Promise.reject(c)]).then((results: any[]) => {
|
||||||
|
expect(results).toEqual([yes(a), yes(b), no(c)]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('mixed should in zone', (done: DoneFn) => {
|
||||||
|
const zone = Zone.current.fork({name: 'settled'});
|
||||||
|
const bPromise = Promise.resolve(b);
|
||||||
|
const cPromise = Promise.reject(c);
|
||||||
|
zone.run(() => {
|
||||||
|
allSettled([a, bPromise, cPromise]).then((results: any[]) => {
|
||||||
|
expect(results).toEqual([yes(a), yes(b), no(c)]);
|
||||||
|
expect(Zone.current.name).toEqual(zone.name);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('poisoned .then', (done: DoneFn) => {
|
||||||
|
const promise = new Promise(function() {});
|
||||||
|
promise.then = function() { throw new EvalError(); };
|
||||||
|
allSettled([promise]).then(
|
||||||
|
() => { fail('should not reach here'); },
|
||||||
|
(reason: any) => {
|
||||||
|
expect(reason instanceof EvalError).toBe(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const Subclass = (function() {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-new-func
|
||||||
|
return Function(
|
||||||
|
'class Subclass extends Promise { constructor(...args) { super(...args); this.thenArgs = []; } then(...args) { Subclass.thenArgs.push(args); this.thenArgs.push(args); return super.then(...args); } } Subclass.thenArgs = []; return Subclass;')();
|
||||||
|
} catch (e) { /**/
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}());
|
||||||
|
|
||||||
|
describe('inheritance', () => {
|
||||||
|
it('preserves correct subclass', () => {
|
||||||
|
const promise = allSettled.call(Subclass, [1]);
|
||||||
|
expect(promise instanceof Subclass).toBe(true);
|
||||||
|
expect(promise.constructor).toEqual(Subclass);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invoke the subclass', () => {
|
||||||
|
Subclass.thenArgs.length = 0;
|
||||||
|
|
||||||
|
const original = Subclass.resolve();
|
||||||
|
expect(Subclass.thenArgs.length).toBe(0);
|
||||||
|
expect(original.thenArgs.length).toBe(0);
|
||||||
|
|
||||||
|
allSettled.call(Subclass, [original]);
|
||||||
|
|
||||||
|
expect(original.thenArgs.length).toBe(1);
|
||||||
|
expect(Subclass.thenArgs.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}));
|
}));
|
||||||
|
Reference in New Issue
Block a user