Skip to content
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

Closed
wants to merge 2 commits into from
Closed

Re-exports in ngtsc #28852

wants to merge 2 commits into from

Conversation

alxhub
Copy link
Member

@alxhub alxhub commented Feb 20, 2019

No description provided.

@benlesh benlesh added area: core Issues related to the framework runtime comp: ivy labels Feb 20, 2019
@ngbot ngbot bot added this to the needsTriage milestone Feb 20, 2019
Copy link
Contributor

@AndrewKushnir AndrewKushnir left a 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),
Copy link
Contributor

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?

Copy link
Member Author

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) {
Copy link
Contributor

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?

Copy link
Member Author

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
Copy link
Contributor

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.

Copy link
Member Author

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`.
Copy link
Contributor

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?

Copy link
Member Author

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) {
Copy link
Contributor

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?

Copy link
Member Author

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');
Copy link
Contributor

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?

Copy link
Member Author

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`);
Copy link
Contributor

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.

Copy link
Member Author

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];
Copy link
Contributor

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?

Copy link
Member Author

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 {
Copy link
Contributor

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).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@alxhub alxhub force-pushed the ngtsc-reexports branch 2 times, most recently from ea6f022 to d8d8bb3 Compare February 21, 2019 22:30
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.
@alxhub alxhub added the target: major This PR is targeted for the next major release label Feb 22, 2019
@alxhub alxhub marked this pull request as ready for review February 22, 2019 19:46
@alxhub alxhub requested review from a team as code owners February 22, 2019 19:46
@alxhub alxhub added action: merge The PR is ready for merge by the caretaker merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note labels Feb 22, 2019
@alxhub
Copy link
Member Author

alxhub commented Feb 22, 2019

Caretaker: I am the approver for compiler/ngcc. This PR is otherwise approved by @AndrewKushnir :)

@benlesh benlesh closed this in 15c065f Feb 22, 2019
benlesh pushed a commit that referenced this pull request Feb 22, 2019
…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
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 14, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: core Issues related to the framework runtime cla: yes merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants