Aurelia 2 bundling portions of application

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?

2 Likes

That is a webpack thing only.

1 Like

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.

1 Like

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.

5 Likes

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

1 Like

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…

2 Likes

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')
1 Like

Yes, this will work OOTB because we use native modules

2 Likes

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 :slight_smile: Hope it makes sense.

2 Likes

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?

1 Like

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…

1 Like

Is there a plan to bring lazy loading to HTML templates too? Somthing like:

<import lazy from="./some-resource"></import>
1 Like

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?

1 Like

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 :laughing:. 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 {
  //
}
1 Like

But in this case as shown by you it would be always lazy right? So why explicitely mention lazy in the import?

1 Like

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>
1 Like

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!

1 Like

:sweat: I meant we get chunking behavior only when using dynamic import at either compose, or router. The convention isn’t automagic yet

1 Like

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?