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
feat(router): auto-unwrap default exports when lazy loading #47586
Conversation
6444f44
to
2b5209b
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.
reviewed-for: public-api
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.
Reviewed-for: public-api
(just one comment about the DX for error cases)
expect(root.nativeElement.innerHTML).toContain('default exported'); | ||
}); | ||
|
||
it('should work for loadChildren', async () => { |
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'm wondering what the DX would look like in case of missing default export? Can we add a test to check that?
I was thinking of a couple scenarios:
- no exports at all (we can have an error message explaining this)
- no default exports, but other symbols are exported (we can also throw a message explaining that)
WDYT?
(we can also do that in a followup PR if needed, it's not a blocker)
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.
TypeScript will fail in both cases:
If there are no exports, the TypeScript compiler will error with File 'the/imported/thing' is not a module.
If there is no default export, it would fail the DefaultExport<T>
type.
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.
LGTM
Reviewed-for: public-api
Reviewed-for: fw-core
FYI, it looks like this change would require an update to the local patches in g3, adding the "blocked" label for now... |
2b5209b
to
32ddfb9
Compare
Caretaker: a g3 patch (cl/478602165) is required |
@@ -357,7 +357,7 @@ export interface IsActiveMatchOptions { | |||
} | |||
|
|||
// @public | |||
export type LoadChildren = LoadChildrenCallback; | |||
export type LoadChildren = LoadChildrenCallback | ɵDeprecatedLoadChildren; |
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.
ɵDeprecatedLoadChildren
now appears in the public API even though it's not exported and is unused externally. What about moving the LoadChildren
type to its own file that we patch in g3 to add ɵDeprecatedLoadChildren
?
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 - that's fine. The goal is to not need to touch the definition of LoadChildren
at all in the patch file - that's what leads to brittle patch files.
@@ -122,6 +123,9 @@ export class RouterConfigLoader { | |||
|
|||
private loadModuleFactoryOrRoutes(loadChildren: LoadChildren): | |||
Observable<NgModuleFactory<any>|Routes> { | |||
if (typeof loadChildren === 'string') { | |||
return deprecatedLoadChildrenString(this.injector, loadChildren); |
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.
What if we unconditionally call deprecatedLoadChildrenString
from here and make it an empty function externally? We at least have a single file we can patch in g3 then.
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.
This doesn't work, because the typeof
needs to be performed within loadModuleFactoryOrRoutes
to narrow the type of loadChildren
for the rest of the 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.
Okay, this can work with a cast, so I applied that change.
This commit breaks out the code in @angular/router needed to support the deprecated `loadChildren` signature into a separate file, which reduces the opportunity for conflicts when patching that behavior in g3.
When using `loadChildren` or `loadComponent`, a common pattern is to pass a function that returns a `Promise` from a dynamic import: ```typescript { path: 'lazy', loadComponent: () => import('./lazy-file').then(m => m.LazyCmp), } ``` The `.then` part of the expression selects the particular exported component symbol from the dynamically imported ES module. ES modules can have a "default export", created with the `export default` modifier: ```typescript @component({...}) export default class LazyCmp { ... } ``` This default export is made available to dynamic imports under the well- known key of `'default'`, per the ES module spec: https://tc39.es/ecma262/#table-export-forms-mapping-to-exportentry-records This commit adds a feature to the router to automatically dereference such default exports. With this logic, when `export default` is used, a `.then` operation to select the particular exported symbol is no longer required: ```typescript { path: 'lazy', loadComponent: () => import('./lazy-file'), } ``` The above `loadComponent` operation will automatically use the `default` export of the `lazy-file` ES module. This functionality works for `loadChildren` as well.
32ddfb9
to
942d6c5
Compare
This PR was merged into the repository by commit da58801. |
When using `loadChildren` or `loadComponent`, a common pattern is to pass a function that returns a `Promise` from a dynamic import: ```typescript { path: 'lazy', loadComponent: () => import('./lazy-file').then(m => m.LazyCmp), } ``` The `.then` part of the expression selects the particular exported component symbol from the dynamically imported ES module. ES modules can have a "default export", created with the `export default` modifier: ```typescript @component({...}) export default class LazyCmp { ... } ``` This default export is made available to dynamic imports under the well- known key of `'default'`, per the ES module spec: https://tc39.es/ecma262/#table-export-forms-mapping-to-exportentry-records This commit adds a feature to the router to automatically dereference such default exports. With this logic, when `export default` is used, a `.then` operation to select the particular exported symbol is no longer required: ```typescript { path: 'lazy', loadComponent: () => import('./lazy-file'), } ``` The above `loadComponent` operation will automatically use the `default` export of the `lazy-file` ES module. This functionality works for `loadChildren` as well. PR Close #47586
…47586) When using `loadChildren` or `loadComponent`, a common pattern is to pass a function that returns a `Promise` from a dynamic import: ```typescript { path: 'lazy', loadComponent: () => import('./lazy-file').then(m => m.LazyCmp), } ``` The `.then` part of the expression selects the particular exported component symbol from the dynamically imported ES module. ES modules can have a "default export", created with the `export default` modifier: ```typescript @component({...}) export default class LazyCmp { ... } ``` This default export is made available to dynamic imports under the well- known key of `'default'`, per the ES module spec: https://tc39.es/ecma262/#table-export-forms-mapping-to-exportentry-records This commit adds a feature to the router to automatically dereference such default exports. With this logic, when `export default` is used, a `.then` operation to select the particular exported symbol is no longer required: ```typescript { path: 'lazy', loadComponent: () => import('./lazy-file'), } ``` The above `loadComponent` operation will automatically use the `default` export of the `lazy-file` ES module. This functionality works for `loadChildren` as well. PR Close angular#47586
…47586) When using `loadChildren` or `loadComponent`, a common pattern is to pass a function that returns a `Promise` from a dynamic import: ```typescript { path: 'lazy', loadComponent: () => import('./lazy-file').then(m => m.LazyCmp), } ``` The `.then` part of the expression selects the particular exported component symbol from the dynamically imported ES module. ES modules can have a "default export", created with the `export default` modifier: ```typescript @component({...}) export default class LazyCmp { ... } ``` This default export is made available to dynamic imports under the well- known key of `'default'`, per the ES module spec: https://tc39.es/ecma262/#table-export-forms-mapping-to-exportentry-records This commit adds a feature to the router to automatically dereference such default exports. With this logic, when `export default` is used, a `.then` operation to select the particular exported symbol is no longer required: ```typescript { path: 'lazy', loadComponent: () => import('./lazy-file'), } ``` The above `loadComponent` operation will automatically use the `default` export of the `lazy-file` ES module. This functionality works for `loadChildren` as well. PR Close angular#47586
This commit breaks out the code in @angular/router needed to support the deprecated `loadChildren` signature into a separate file, which reduces the opportunity for conflicts when patching that behavior in g3. PR Close angular#47586
…47586) When using `loadChildren` or `loadComponent`, a common pattern is to pass a function that returns a `Promise` from a dynamic import: ```typescript { path: 'lazy', loadComponent: () => import('./lazy-file').then(m => m.LazyCmp), } ``` The `.then` part of the expression selects the particular exported component symbol from the dynamically imported ES module. ES modules can have a "default export", created with the `export default` modifier: ```typescript @component({...}) export default class LazyCmp { ... } ``` This default export is made available to dynamic imports under the well- known key of `'default'`, per the ES module spec: https://tc39.es/ecma262/#table-export-forms-mapping-to-exportentry-records This commit adds a feature to the router to automatically dereference such default exports. With this logic, when `export default` is used, a `.then` operation to select the particular exported symbol is no longer required: ```typescript { path: 'lazy', loadComponent: () => import('./lazy-file'), } ``` The above `loadComponent` operation will automatically use the `default` export of the `lazy-file` ES module. This functionality works for `loadChildren` as well. PR Close angular#47586
This commit breaks out the code in @angular/router needed to support the deprecated `loadChildren` signature into a separate file, which reduces the opportunity for conflicts when patching that behavior in g3. PR Close angular#47586
…47586) When using `loadChildren` or `loadComponent`, a common pattern is to pass a function that returns a `Promise` from a dynamic import: ```typescript { path: 'lazy', loadComponent: () => import('./lazy-file').then(m => m.LazyCmp), } ``` The `.then` part of the expression selects the particular exported component symbol from the dynamically imported ES module. ES modules can have a "default export", created with the `export default` modifier: ```typescript @component({...}) export default class LazyCmp { ... } ``` This default export is made available to dynamic imports under the well- known key of `'default'`, per the ES module spec: https://tc39.es/ecma262/#table-export-forms-mapping-to-exportentry-records This commit adds a feature to the router to automatically dereference such default exports. With this logic, when `export default` is used, a `.then` operation to select the particular exported symbol is no longer required: ```typescript { path: 'lazy', loadComponent: () => import('./lazy-file'), } ``` The above `loadComponent` operation will automatically use the `default` export of the `lazy-file` ES module. This functionality works for `loadChildren` as well. PR Close angular#47586
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. |
When using
loadChildren
orloadComponent
, a common pattern is to pass a function that returns aPromise
from a dynamic import:The
.then
part of the expression selects the particular exported component symbol from the dynamically imported ES module.ES modules can have a "default export", created with the
export default
modifier:This default export is made available to dynamic imports under the well- known key of
'default'
, per the ES module spec:https://tc39.es/ecma262/#table-export-forms-mapping-to-exportentry-records
This commit adds a feature to the router to automatically dereference such default exports. With this logic, when
export default
is used, a.then
operation to select the particular exported symbol is no longer required:The above
loadComponent
operation will automatically use thedefault
export of thelazy-file
ES module.This functionality works for
loadChildren
as well.