In Aurelia v1, the second parameter of PLATFORM.moduleName advises the bundler to divide a specific set of app code into a separate bundle. How do we accomplish this in v2? Any chance we can also use dynamic imports in supported browsers to load the bundles on-demand?
That is a webpack thing only.
I understand the limitation, should have made that clear. Just curious if we’ll see it in Aurelia 2 and how that will work since PLATFORM.moduleName will no longer benecessary, as I understand it.
Aurelia 2 doesn’t have runtime magic, this simplified lots of implementation. For dynamic loading, Aurelia 2 relies on standard ESM feature: dynamic import().
In Aurelia 2, To use a resource (custom element, or route component), the code needs to explicitly import that resource.
In js/ts
import {SomeResource} from './some-resource';
// Then use SomeResource directly in code
In html
<import from="./some-resource"></import>
You can write <require from...
too, require and import tags are same in Aurelia 2.
The Aurelia 2 conventions is implemented as a bundler plugin (for webpack, it’s @aurelia/webpack-loader
), the plugin translates html into js code, the above import tag is translated into
import * as d0 from './some-resource';
// Then d0 is used as a dependency of this custom element
This is very different from Aurelia 1, where Aurelia 1 deals with <require>
tag at runtime. Aurelia 2 does it at compile time.
So, there is no magic to accept a module id string as a resource in Aurelia 2, it’s all translated into static import.
In Aurelia 1, PLATFORM.moduleName
is introduced as a hint for webpack to pick up the module id string, in order to bring in dependency.
In Aurelia 1, module id string gave Aurelia the flexibility to lazy load a resource. It only starts to request the resource module when it’s firstly used in the app UI. Whether it’s in currently loaded js bundle (or call chunk), or additional bundle to be loaded, it doesn’t matter.
In Aurelia 2, most resources are loaded statically, so you lose the default lazy load behaviour in Aurelia 1. In some use case you do need lazy loading, for example a route component, Aurelia 2 allows you to supply a promise, normally implemented with dynamic import().
() => import('./another-route')
This is a pattern you probably have seen in many other frameworks’ router.
I am pretty sure the new router in Aurelia 2 supports lazy loading, but I have not tried it. @jwx probably can provide you a better example.
When you use dynamic import() to lazy load a module, some bundlers provide automatic code splitting, some may require little config. You can refer to bundler document for the details.
@huochunpeng Thanks for the explanation of how this is done in Aurelia 2. What about global custom elements? Is it still possible to lazy load them in Aurelia 2?
We have implemented our own way of lazy loading those in Aurelia 1 as described here: Lazy load global custom elements.
The 2 main API surfaces for lazy loading in au2 are:
- Dynamic composition (e.g. passing the promise from
import('./my-module.js')
to<au-compose>
) - Routing (either manually dynamically loading a module and passing it into the router, or having lazy loaded child routes (API for this still in progress))
What’s the benefit of lazy loaded global resources instead of lazy loaded scoped resources though? Assuming bundlers take care of the same module only being loaded once…
Do you know wheather the webpack chunkname syntax will be supported in Aurelia 2?
import(/* webpackChunkName: "my-module/lazy" */ './my-module')
We use this to structure the output of a big business application with lots of modules. In Aurelia 1 we’re able to write it like:
PLATFORM.moduleName('./my-module', 'my-module/lazy')
Yes, this will work OOTB because we use native modules
Thanks!
In our use case, our customers can build their own “apps” based on Aurelia and install them into our platform (also based on Aurelia). We then guarantee that they can use a set of custom elements build by us, e.g. <sci-list-view>, <sci-grid>
, etc. We have a lot of these <sci-*>
custom elements build with Aurelia, but we don’t want to load these just before they are actually required by some component in one of our customer’s apps - otherwise we would have a very slow startup of the entire platform.
This is our use-case for lazy-loading custom elements Hope it makes sense.
With Aurelia 2 quickly approaching now, I would like to ask about the use case of >100 global custom elements: are there any way they can be lazy-loaded, like you can do in Aurelia 1?
Yes, there are many ways custom elements can be lazy loaded. Via routing, via compose, via an AppTask
, and of course also by hand using the compiler directly inside some arbitrary method or hook.
That monkey patch that was given to you for v1 is certainly not going to work, though, because we don’t use requirejs
anymore and so the same conventions won’t work. Which is probably for the better…
Anyway, I’m guessing that when you are dynamically lazy loading components you’re also dynamically lazy compiling the html that consumes them? In that case compose
is probably the easiest option.
<au-compose component.bind="component"></au-compose>
export class ThingWrapper {
async binding() {
const lazyComponents = // await import(..) or await fetch(...) or some such
const dynamicTemplate = // await import(..) or await fetch(...) or some such
this.component = { template: dynamicTemplate, dependencies: lazyComponents };
}
}
Something like that…
Is there a plan to bring lazy loading to HTML templates too? Somthing like:
<import lazy from="./some-resource"></import>
How would that work @timfish ? I mean it sounds really interesting but what would some-resource be in that case and when would it be evaluated? On first render?
I have no idea how it would work, I was just thinking about it after looking at the “direct routing” examples and wondering how I would get a bundler to split the output into chunks based around routing.
My huge assumption is that when HTML conventions are converted to decorators, a dynamic import()
could be just inserted somewhere . For this to work, custom elements would need to support async dependencies and I don’t know if that’s possible…
const SomeResource = async () => {
const i = await import('./some-resource');
return i.SomeResource;
}
@customElement({
name: 'some-element',
template,
dependencies: [SomeResource],
})
export class SomeElement {
//
}
But in this case as shown by you it would be always lazy right? So why explicitely mention lazy in the import?
As far as I understand it, this always ends up in the main bundle:
<import from="./some-resource"></import>
For Aurelia v2, the only way to split bundles is to use dynamic import()
so it would be nice to be able to make something load via dynamic via HTML conventions. Perhaps lazy
is not the best name for the attribute. Maybe async
?
<import async from="./some-resource"></import>
At the moment, chunking only happens at the router, or compose. There’s this GH issue for adding v1 style chunking back 🙋 feature request: Add a load step before a composition · Issue #1126 · aurelia/aurelia · GitHub
@bigopon, are you saying that chunking will already occur automagically when using direct routing or at least when the router changes are merged?
Looks like I need to play around and see what current output we get from all the conventions!
I meant we get chunking behavior only when using dynamic import at either compose, or router. The convention isn’t automagic yet
I started this thread but think my answer might already be answered here.
I want to do lazy loading outside without using any bundling at all, just vanilla JS and Aurelia2 as a script-tag. It seems like <au-compose>
or maybe even a plain <import>
would work since I am not bundling?