This refactoring more clearly separates the different phases of the work
performed by `ngcc`, setting the ground for being able to run each phase
independently in the future and improve performance via parallelization.
Inspired by/Based on @alxhub's prototype: alxhub/angular@cb631bdb1
PR Close#32052
This change basically moves some checks to happen up front and ensures
we don't try to process any more properties than we absolutely need.
(The properties would not be processed before either, but we would
consider them, before finding out that they have already been processed
or that they do not exist in the entry-point's `package.json`.)
This change should make no difference in the work done by `ngcc`, but it
transforms the code in a way that makes the actual work known earlier,
thus making it easier to parallelize the processing of each property in
the future.
PR Close#32052
In commit 7b55ba58b (part of PR #29092), the implementation of
`makeEntryPointBundle()` was changed such that it now always return
`EntryPointBundle` (and not `null`).
However, the return type was not updated and as result we continued to
unnecessarily handle `null` as a potential return value in some places.
This commit fixes the return type to reflect the implementation and
removes the redundant code that was dealing with `null`.
PR Close#32052
Previously, `ngcc` would avoid processing a `formatPath` that a property
in `package.json` mapped to, if either the _property_ was marked as
processed or the `formatPath` (i.e. the file(s)) was processed in the
same `ngcc` run (since the `compiledFormats` set was not persisted
across runs).
This could lead in a situation where a `formatPath` would be compiled
twice (if for example properties `a` and `b` both mapped to the same
`formatPath` and one would run `ngcc` for property `a` and then `b`).
This commit fixes it by ensuring that as soon as a `formatPath` has been
processed all corresponding properties are marked as processed (which
persists across `ngcc` runs).
PR Close#32003
Previously, when `ngcc` was called with `compileAllFormats === false`
(i.e. how `@angular/cli` calls it), it would not attempt to process
more properties, once the first was successfully processed. However, it
_would_ continue looping over them and perform some unnecessary
operations, such as:
- Determining the format each property maps to (which can be an
expensive operation for some properties mapping to either UMD or
CommonJS).
- Checking whether each property has been processed (which involves
checking whether any property has been processed with a different
version of `ngcc` each time).
- Potentially marking properties as processed (which involves a
file-write operation).
This commit avoids the unnecessary operations by entirely skipping
subsequent properties, once the first one has been successfully
processed. While this theoretically improves performance, it is not
expected to have any noticeable impact in practice, since the list of
`propertiesToConsider` is typically small and the most expensive
operation (marking a property as processed) has low likelihood of
happening (plus these operations are a tiny fraction of `ngcc`'s work).
PR Close#32003
Previously, when `ngcc` needed to mark multiple properties as processed
(e.g. a processed format property and `typings` or all supported
properties for a non-Angular entry-point), it would update each one
separately and write the file to disk multiple times.
This commit changes this, so that multiple properties can be updated at
once with one file-write operation. While this theoretically improves
performance (reducing the I/O operations), it is not expected to have
any noticeable impact in practice, since these operations are a tiny
fraction of `ngcc`'s work.
This change will be useful for a subsequent change to mark all
properties that map to the same `formatPath` as processed, once it is
processed the first time.
PR Close#32003
Previously, ngcc had to walk the entire `node_modules` tree looking for
entry-points, even if it only needed to process a single target entry-point
and its dependencies.
This added up to a few seconds to each execution of ngcc, which is noticeable
when being run via the CLI integration.
Now, if an entry-point target is provided, only that target and its entry-points
are considered rather than the whole folder tree.
PR Close#30525
This message gets called if a format has already been
compiled and we only want the first. So the message itself
is wrong but it is also not very useful anyway.
PR Close#30525
If a package delcares a class internally on an NgModule, ngcc
needs to be able to add a public export to this class's type.
Previously, if the typing file for the declared is not imported
from the typings entry-point file, then ngcc cannot find it.
Now we try to guess the .d.ts files from the equivalent .js
files.
PR Close#31411
There are scenarios where it is not possible for ngcc to guess the format
or configuration of an entry-point just from the files on disk.
Such scenarios include:
1) Unwanted entry-points: A spurious package.json makes ngcc think
there is an entry-point when there should not be one.
2) Deep-import entry-points: some packages allow deep-imports but do not
provide package.json files to indicate to ngcc that the imported path is
actually an entry-point to be processed.
3) Invalid/missing package.json properties: For example, an entry-point
that does not provide a valid property to a required format.
The configuration is provided by one or more `ngcc.config.js` files:
* If placed at the root of the project, this file can provide configuration
for named packages (and their entry-points) that have been npm installed
into the project.
* If published as part of a package, the file can provide configuration
for entry-points of the package.
The configured of a package at the project level will override any
configuration provided by the package itself.
PR Close#30591
To improve cross platform support, all file access (and path manipulation)
is now done through a well known interface (`FileSystem`).
For testing a number of `MockFileSystem` implementations are provided.
These provide an in-memory file-system which emulates operating systems
like OS/X, Unix and Windows.
The current file system is always available via the static method,
`FileSystem.getFileSystem()`. This is also used by a number of static
methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass
`FileSystem` objects around all the time. The result of this is that one
must be careful to ensure that the file-system has been initialized before
using any of these static methods. To prevent this happening accidentally
the current file system always starts out as an instance of `InvalidFileSystem`,
which will throw an error if any of its methods are called.
You can set the current file-system by calling `FileSystem.setFileSystem()`.
During testing you can call the helper function `initMockFileSystem(os)`
which takes a string name of the OS to emulate, and will also monkey-patch
aspects of the TypeScript library to ensure that TS is also using the
current file-system.
Finally there is the `NgtscCompilerHost` to be used for any TypeScript
compilation, which uses a given file-system.
All tests that interact with the file-system should be tested against each
of the mock file-systems. A series of helpers have been provided to support
such tests:
* `runInEachFileSystem()` - wrap your tests in this helper to run all the
wrapped tests in each of the mock file-systems.
* `addTestFilesToFileSystem()` - use this to add files and their contents
to the mock file system for testing.
* `loadTestFilesFromDisk()` - use this to load a mirror image of files on
disk into the in-memory mock file-system.
* `loadFakeCore()` - use this to load a fake version of `@angular/core`
into the mock file-system.
All ngcc and ngtsc source and tests now use this virtual file-system setup.
PR Close#30921
This commit introduces a new interface, which abstracts access
to the underlying `FileSystem`. There is initially one concrete
implementation, `NodeJsFileSystem`, which is simply wrapping the
`fs` library of NodeJs.
Going forward, we can provide a `MockFileSystem` for test, which
should allow us to stop using `mock-fs` for most of the unit tests.
We could also implement a `CachedFileSystem` that may improve the
performance of ngcc.
PR Close#29643
By passing a `pathMappings` configuration (a subset of the
`ts.CompilerOptions` interface), we can instuct ngcc to process
additional paths outside the `node_modules` folder.
PR Close#29643
When working out the dependencies between entry-points
ngcc must parse the import statements and then resolve the
import path to the actual file. This is complicated because module
resolution is not trivial.
Previously ngcc used the node.js `require.resolve`, with some
hacking to resolve modules. This change refactors the `DependencyHost`
to use a new custom `ModuleResolver`, which is optimized for this use
case.
Moreover, because we are in full control of the resolution,
we can support TS `paths` aliases, where not all imports come from
`node_modules`. This is the case in some CLI projects where there are
compiled libraries that are stored locally in a `dist` folder.
See //FW-1210.
PR Close#29643
The `Transformer` and `Renderer` classes do not
actually need a `sourcePath` value as by the time
they are doing their work we are only working directly
with full absolute paths.
PR Close#29643
Previously we always walked the whole folder tree looking for
entry-points before we tested whether a target package had been
processed already. This could take >10secs!
This commit does a quick check of the target package before doing
the full walk which brings down the execution time for ngcc in this
case dramatically.
```
$ time ./node_modules/.bin/ivy-ngcc -t @angular/common/http/testing
Compiling @angular/core : fesm2015 as esm2015
Compiling @angular/core : fesm5 as esm5
Compiling @angular/core : esm2015 as esm2015
Compiling @angular/core : esm5 as esm5
Compiling @angular/common/http : fesm2015 as esm2015
Compiling @angular/common/http : fesm5 as esm5
Compiling @angular/common/http : esm2015 as esm2015
Compiling @angular/common/http : esm5 as esm5
Compiling @angular/common/http/testing : fesm2015 as esm2015
Compiling @angular/common/http/testing : fesm5 as esm5
Compiling @angular/common/http/testing : esm2015 as esm2015
Compiling @angular/common/http/testing : esm5 as esm5
real 0m19.766s
user 0m28.533s
sys 0m2.262s
```
```
$ time ./node_modules/.bin/ivy-ngcc -t @angular/common/http/testing
The target entry-point has already been processed
real 0m0.666s
user 0m0.605s
sys 0m0.113s
```
PR Close#29740
With the new API, where you can choose to only process the first
matching format, it is possible to process an entry-point multiple
times, if you pass in a different format each time.
Previously, ngcc would always try to process the typings files for
the entry-point along with processing the first format of the current
execution of ngcc. But this meant that it would be trying to process
the typings a second time.
Now we only process the typings if they have not already been
processed as part of processing another format in another
even if it was in a different execution of ngcc.
PR Close#29657
We have already removed this concept from the public API. This just cleans it out altogether.
The `targetPath` was an alternative output path to the original `basePath`.
This is not really a very useful concept, since the actual target path
of each output file is more complex and not consistently relative to the `basePath`.
PR Close#29556
This commit adds a `NewEntryPointFileWriter` that will be used in
webpack integration. Instead of overwriting files in-place, this `FileWriter`
will make a copy of the TS program files and write the transformed files
there. It also updates the package.json with new properties that can be
used to access the new entry-point format.
FW-1121
PR Close#29092
If `targetEntryPointPath` is provided to `mainNgcc` then we will now mark all
the `propertiesToConsider` for that entry-point if we determine that
it does not contain code that was compiled by Angular (for instance it has
no `...metadata.json` file).
The commit also renames `__modified_by_ngcc__` to `__processed_by_ivy_ngcc__`, since
there may be entry-points that are marked despite ngcc not actually compiling anything.
PR Close#29092
Now the public API does not contain internal types, such as `AbsoluteFsPath` and
`EntryPointJsonProperty`. Instead we just accept strings and then guard them in
`mainNgcc` as appropriate.
A new public API function (`hasBeenProcessed`) has been exported to allow programmatic
checking of the build marker when the package.json contents are already known.
PR Close#29092
Previously we always considered all the properties in the package.json
if no `propertiesToConsidere` were provided.
But this results in computing a new set of properties for each entry-point
plus iterating through many of the package.json properties that are
not related to bundle-format paths.
PR Close#29092
By default ngcc will compile all the format properties specified. With this
change you can configure ngcc so that it will stop compiling an entry-point
after the first property that matches the `propertiesToConsider`.
PR Close#29092
By ensuring that EntryPointBundle contains everything that `Transformer.transform()`
needs to do its work, we can simplify its signature.
PR Close#29092
Now that we are using package.json properties to indicate which
entry-point format to compile, it turns out that we don't really
need to distinguish between flat and non-flat formats, unless we
are compiling `@angular/core`.
PR Close#29092
You can now specify a list of properties in the package.json that
should be considered (in order) to find the path to the format to compile.
The build marker system has been updated to store the markers in
the package.json rather than an additional external file.
Also instead of tracking the underlying bundle format that was compiled,
it now tracks the package.json property.
BREAKING CHANGE:
The `proertiesToConsider` option replaces the previous `formats` option,
which specified the final bundle format, rather than the property in the
package.json.
If you were using this option to compile only specific bundle formats,
you must now modify your usage to pass in the properties in the package.json
that map to the format that you wish to compile.
In the CLI, the `--formats` is no longer available. Instead use the
`--properties` option.
FW-1120
PR Close#29092
You can now, programmatically, specify an entry-point where
the ngcc compilation will occur.
Only this entry-point and its dependencies will be compiled.
FW-1119
PR Close#29092
The `mainNgcc()` function has been refactored to make it easier to call
ngcc from JavaScript, rather than via the command line.
For example, the `yargs` argument parsing and the exception
handling/logging have moved to the `main-ngcc.ts`
file so that it is only used for the command line version.
FW-1118
PR Close#29092