-
Notifications
You must be signed in to change notification settings - Fork 24.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Re-exports in ngtsc #28852
Re-exports in ngtsc #28852
Conversation
499fc1d
to
10c8496
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work @alxhub 👍 Just left a few nits. Thank you.
]; | ||
const afterDeclarationsTransforms = [ | ||
declarationTransformFactory(compilation), | ||
aliasTransformFactory(compilation.exportStatements), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: may be add a comment on why we run aliasTransformFactory
again as a part of afterDeclarationsTransforms
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually removed it. Putting the re-exports in the .d.ts file is good behavior, but it breaks tsickle :-/.
// source file as the re-exporting NgModule. This can happen if both a directive, its | ||
// NgModule, and the re-exporting NgModule are all in the same file. In this case, | ||
// no import alias is needed as it would go to the same file anyway. | ||
for (const directive of exportScope.exported.directives) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic to handle Directives and Pipes looks very similar. May be extract it out to a separate function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahaha, I did this independently of this comment :D nice!
* file, or in a .ts file with a handwritten definition). | ||
* | ||
* @param clazz the class of interest | ||
* @param ngModuleImportedFrom module specifier of the import path to assume for all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Params docs don't seem to correspond actual function signature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
private readModuleMetadataFromClass(ref: Reference<ts.Declaration>): RawDependencyMetadata|null { | ||
const clazz = ref.node; | ||
const resolutionContext = clazz.getSourceFile().fileName; | ||
// This operation is explicitly not memoized, as it depends on `ngModuleImportedFrom`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ngModuleImportedFrom
doesn't seem to exist in the scope of this fn, old comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, fixed.
// Validate that the shape of the ngModuleDef type is correct. | ||
ngModuleDef.type === null || !ts.isTypeReferenceNode(ngModuleDef.type) || | ||
ngModuleDef.type.typeArguments === undefined || | ||
ngModuleDef.type.typeArguments.length !== 4) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be add a comment on what 4
means or declare a const?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the comment covers it - a correctly shaped ngModuleDef type should have 4 type arguments.
const expr = this.refEmitter.emit(ref.cloneWithNoIdentifiers(), sourceFile); | ||
if (!(expr instanceof ExternalExpr) || expr.value.moduleName === null || | ||
expr.value.name === null) { | ||
throw new Error('Expected ExternalExpr'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be add more info into the error, like exportName
, so it's easier to debug if needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
const type = element.exprName; | ||
const {node, from} = reflectTypeEntityToDeclaration(type, checker); | ||
if (!ts.isClassDeclaration(node)) { | ||
throw new Error(`Expected ClassDeclaration`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd propose to add more info into the error message, like ngModuleImportedFrom
and id/name of the def if this info is available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
const exportedSymbol = symbol.exports !.get(name as ts.__String); | ||
if (exportedSymbol !== undefined) { | ||
const decl = exportedSymbol.valueDeclaration as ts.ClassDeclaration; | ||
const specifier = /.*node_modules\/(\w+)\/index\.d\.ts$/.exec(sf.fileName) ![1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this RegExp seems identical with the one used in testHost.fileNameToModuleName
fn, may be move it in a const?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
const {resolver, refs} = makeTestEnv( | ||
{ | ||
'deep': ` | ||
export declare class DeepDir { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: formatting (content should be moved to the right).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
ea6f022
to
d8d8bb3
Compare
This commit splits apart selector_scope.ts in ngtsc and extracts the logic into two separate classes, the LocalModuleScopeRegistry and the DtsModuleScopeResolver. The logic is cleaned up significantly and new tests are added to verify behavior. LocalModuleScopeRegistry implements the NgModule semantics for compilation scopes, and handles NgModules declared in the current compilation unit. DtsModuleScopeResolver implements simpler logic for export scopes and handles NgModules declared in .d.ts files. This is done in preparation for the addition of re-export logic to solve StrictDeps issues.
In certain configurations (such as the g3 repository) which have lots of small compilation units as well as strict dependency checking on generated code, ngtsc's default strategy of directly importing directives/pipes into components will not work. To handle these cases, an additional mode is introduced, and is enabled when using the FileToModuleHost provided by such compilation environments. In this mode, when ngtsc encounters an NgModule which re-exports another from a different file, it will re-export all the directives it contains at the ES2015 level. The exports will have a predictable name based on the FileToModuleHost. For example, if the host says that a directive Foo is from the 'root/external/foo' module, ngtsc will add: ``` export {Foo as ɵng$root$external$foo$$Foo} from 'root/external/foo'; ``` Consumers of the re-exported directive will then import it via this path instead of directly from root/external/foo, preserving strict dependency semantics.
d8d8bb3
to
d4e743f
Compare
Caretaker: I am the approver for compiler/ngcc. This PR is otherwise approved by @AndrewKushnir :) |
…rts (#28852) In certain configurations (such as the g3 repository) which have lots of small compilation units as well as strict dependency checking on generated code, ngtsc's default strategy of directly importing directives/pipes into components will not work. To handle these cases, an additional mode is introduced, and is enabled when using the FileToModuleHost provided by such compilation environments. In this mode, when ngtsc encounters an NgModule which re-exports another from a different file, it will re-export all the directives it contains at the ES2015 level. The exports will have a predictable name based on the FileToModuleHost. For example, if the host says that a directive Foo is from the 'root/external/foo' module, ngtsc will add: ``` export {Foo as ɵng$root$external$foo$$Foo} from 'root/external/foo'; ``` Consumers of the re-exported directive will then import it via this path instead of directly from root/external/foo, preserving strict dependency semantics. PR Close #28852
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. |
No description provided.