feat(ivy): fix and enable esm2015/esm5 processing in ngcc#25090
feat(ivy): fix and enable esm2015/esm5 processing in ngcc#25090gkalpak wants to merge 8 commits into
Conversation
|
So there's good news and bad news. 👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there. 😕 The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that here in the pull request. Note to project maintainer: This is a terminal state, meaning the |
|
You can preview 1ce3cc5 at https://pr25090-1ce3cc5.ngbuilds.io/. |
1ce3cc5 to
9cdcb36
Compare
|
Rebased on #25060 (which was rebased on master, now that #24897 has been merged). |
|
You can preview 9cdcb36 at https://pr25090-9cdcb36.ngbuilds.io/. |
| * Check whether the given node actually represents a class. | ||
| */ | ||
| isClass(node: ts.Declaration): boolean; | ||
| isClass(node: ts.Node): boolean; |
There was a problem hiding this comment.
Can you describe the rationale for this change? In my mind the class should always be a declaration.
In ES6:
class Foo {} // ts.ClassDeclaration
In ES5:
// ts.VariableDeclaration
var Foo = /** @class */ (function () {
function Foo() {
}
return Foo;
}());
There was a problem hiding this comment.
The rational is described in Esm5ReflectionHost's getClassSymbol() method: https://github.com/angular/angular/pull/25090/files#diff-ac5866175696e5b67c53651096f39094R49
Basically, in some cases we end up passing the inner function declaration (from inside the IIFE) to isClass().
There was a problem hiding this comment.
Maybe there is a better way to avoid that, but I couldn't find any.
| // It might be the function expression inside the IIFE. We need to go 5 levels up... | ||
|
|
||
| // 1. IIFE body. | ||
| let outerNode = node.parent; |
There was a problem hiding this comment.
Just a cautionary note - different versions of TypeScript generate slightly different syntax for class declarations ;)
There was a problem hiding this comment.
😞
No idea how to work around it other than having a different implementation for each version of TS 😒
|
|
||
| return this.checker.getSymbolAtLocation(innerClassIdentifier); | ||
| } | ||
|
|
| const options: ts.CompilerOptions = {allowJs: true, rootDir: entryPointPath}; | ||
| const options: ts.CompilerOptions = { | ||
| allowJs: true, | ||
| maxNodeModuleJsDepth: Infinity, |
| const barNode = | ||
| getDeclaration(program, FUNCTION_BODY_FILE.name, 'bar', ts.isFunctionDeclaration) !; | ||
| const barDef = host.getDefinitionOfFunction(barNode); | ||
| expect(barDef.node).toBe(barNode); |
There was a problem hiding this comment.
Geez, we need a better way to write tests like these. I'm gonna work on that.
| const commonPath = path.join(nodeModulesPath, '@angular/common'); | ||
| const exitCode = mainNgcc([commonPath, 'esm2015']); | ||
|
|
||
| console.warn(find('node_modules_ngtsc').filter(p => p.endsWith('.js') || p.endsWith('map'))); |
There was a problem hiding this comment.
Are these console.warn statements intentional?
There was a problem hiding this comment.
Yes 😁
At this point, the e2e tests consist of us looking at the printed result and concluding it "looks reasonable". We will have better e2e tests soon 😁
(Note, the console.warns are not introduced in this PR btw; they are already present in other tests.)
| } else if (entry instanceof Reference) { | ||
| if (!entry.expressable) { | ||
| throw new Error(`Value at position ${idx} in ${name} array is not expressable`); | ||
| } else if (!this.reflector.isClass(entry.node)) { |
There was a problem hiding this comment.
if we add an additional ts.isDeclaration(entry.node) here then we don't need to change the signature of isClass.
There was a problem hiding this comment.
But this will potentially hide errors (where entry.node is a Declaration but not a class) and not help in other places where isClass() might be called with the inner function declaration on ESM5.
Generally, I think this should be the reflector's concern.
There was a problem hiding this comment.
I looked again and I agree that we need to change the isClass to take a ts.Node.
ngtsc's static resolver can evaluate function calls where parameters have default values. In TypeScript code these default values live on the function definition, but in ES5 code the default values are represented by statements in the function body. A new ReflectionHost method getDefinitionOfFunction() abstracts over this difference, and allows the static reflector to more accurately evaluate ES5 code.
In some code formats (e.g. ES5) methods can actually be function
expressions. For example:
```js
function MyClass() {}
// this static method is declared as a function expression
MyClass.staticMethod = function() { ... };
```
Since non-flat module formats (esm2015, esm5) have different structure than their flat counterparts (and since we are operating on JS files inside `node_modules/`, we need to configure TS to include deeply nested JS files (by specifying a sufficiently high `maxNodeModuleJsDepth`). Remains to be determined if this has any (noticeable) performance implications.
9cdcb36 to
af5a439
Compare
|
Rebased on master and addressed (some) comments. |
|
You can preview af5a439 at https://pr25090-af5a439.ngbuilds.io/. |
|
Superceded by #25203 |
|
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Sits on top of #25060.
PR Checklist
Docs have been added / updated (for bug fixes / features)PR Type
Does this PR introduce a breaking change?