Framework: question/suggestion: lazy load of plugins and global resources

Created on 29 Jun 2015  路  9Comments  路  Source: aurelia/framework

When I record plugins and global resources during startup of Aurelia I realize that they are loaded via the browser request.

You can postpone this for loading when someone really use a feature?

question

Most helpful comment

The basic idea is to create an instance of FrameworkConfiguration which is part of the aurelia-framework library. This is the class that is the use API. You can then use this configuration object to configure a bunch of plugins, global resources, etc. It won't do anything until you call apply().

An example:

let config = new FrameworkConfiguration(aurelia);
config.plugin('foo');

//some time passes

config.apply().then(() => {
  //it's all ready to go now
});

If you want to take this farther, you could then store these configuration objects somewhere by "name" and you could associate those names with route configuration settings. Then, you could write a pipeline step for the router that checks to see if there's a configuration that needs to be loaded (which hasn't been loaded already), you could then lookup the configuration and call its apply method during the pipeline step. This would enable per-route dynamically loaded plugins :)

Another way you could leverage this is to write a custom resource for the view pipeline. You could then require a configuration into your view, ensuing that it is applied before the view is compiled.

If you wanted maximum flexiblity, you could build both of these systems on top of the same shared lookup mechanism.

Note, we do plan to have something like this out-of-the-box in the future. If you build this out and want to submit a PR, we can work with you to refine it and perhaps merge it. Or if it's not quite generalizable, then we can use it as a starting point for the official feature.

All 9 comments

You can load plugins and global resources at any time during your application's lifetime. If you want to postpone it, you can do that.

Please, could you provide an example of how to do this?

The basic idea is to create an instance of FrameworkConfiguration which is part of the aurelia-framework library. This is the class that is the use API. You can then use this configuration object to configure a bunch of plugins, global resources, etc. It won't do anything until you call apply().

An example:

let config = new FrameworkConfiguration(aurelia);
config.plugin('foo');

//some time passes

config.apply().then(() => {
  //it's all ready to go now
});

If you want to take this farther, you could then store these configuration objects somewhere by "name" and you could associate those names with route configuration settings. Then, you could write a pipeline step for the router that checks to see if there's a configuration that needs to be loaded (which hasn't been loaded already), you could then lookup the configuration and call its apply method during the pipeline step. This would enable per-route dynamically loaded plugins :)

Another way you could leverage this is to write a custom resource for the view pipeline. You could then require a configuration into your view, ensuing that it is applied before the view is compiled.

If you wanted maximum flexiblity, you could build both of these systems on top of the same shared lookup mechanism.

Note, we do plan to have something like this out-of-the-box in the future. If you build this out and want to submit a PR, we can work with you to refine it and perhaps merge it. Or if it's not quite generalizable, then we can use it as a starting point for the official feature.

Thanks for your quick answer.
I think the only thing I'm missing is how can I get the aurelia component to pass it in FrameworkConfiguration(aurelia);

You can inject Aurelia into any class. It's also provided via the configure method of your main module.
Also, another idea about how to handle this is by having a custom setting and pipeline step for routing that simply loads a special module first, looks for an exported configure method, passes it a new framework configuration instance and then applies it. This would mean that you could basically keep your feature setup like normal, just not register it at all, but teach the router how to load that and register it before processing the route.

Hi @EisenbergEffect
I'm trying to create a 'addPreactivateStep' pipeline. How can I load file with the name configure.ts inside the same folder of that particular moduleId before calling next?

So far I only have the default code for adding pipeline.

class ConfigureRouteStep implements PipelineStep {
    run(instruction: NavigationInstruction, next: Next): Promise<any> {
        if(instruction.config.settings.hasConfiguration) {
            if(!instruction.config.settings.isConfigured){
                //Load default export for configuration.ts here using promise/something
                // and pass existing FrameworkConfiguration and next to be called after apply in config.
                return next(); // Call next() after config.apply()
                //Tag instruction.config.settings.isConfigured = true
            }
        }

        return next();
    }
}

This would be one of my config.map object
{ route: 'content', name: 'custcontentomer', title: 'Content', moduleId: 'ad-content/index', nav: true, settings: { hasConfiguration: true } }

This is an example of _configuration.ts_


import { FrameworkConfiguration } from 'aurelia-framework'
import { Next } from "aurelia-router";

export function configure(config: FrameworkConfiguration, next: Next) {
    return config        
        .globalResources('./about')
        .apply()
        .then(() => {
            next()
        });
}

@EisenbergEffect I'm curious if any of this has gotten easier with Aurelia 1.2? Lazy loading custom components in the context of a router has become a first-class citizen. It's amazing and just works. I'm wondering how hard it would be for us to have something similar in the configure function? I'm envisioning something like this:

import {PLATFORM} from 'aurelia-pal';

export function configure(aurelia) {
  aurelia.use
    .standardConfiguration()
    .developmentLogging()
    .globalResources(
      PLATFORM.moduleName('custom-component', 'custom-component'),
    );
  aurelia.start().then(() => aurelia.setRoot(PLATFORM.moduleName('app')));
}

The idea here would be that by adding a second parameter to PLATFORM.moduleName, Webpack would create a dedicated chunk file for custom-component. Just like it does in configureRouter.

I tested this locally in 1.2 just to make sure it didn't already do this, and it doesn't.

How hard would it be to get this functional?

@martynchamberlin Great question. I think we discussed this back when we were working on the moduleName api. @jods4 may be best to comment.

You can code split your resource with moduleName but Aurelia will still load them when applying the config so you cannot lazy load them for real, i.e. just in time when loading a view that needs them.

We discussed this with @bigopon, who's trying to add new APIs that don't rely on module names and it's an improvement that we would like to make.

It requires more than just changing bundling or registration, though. We need to teach Aurelia to load a global resource only when it's required by a view.

The API _might_ look something like this (doesn't work today):

// Concept, doesn't work.
aurelia.use.globalResources("my-custom-element", () => import("my-custom-element"))

The first argument would be a tag name for custom element. When Aurelia sees this tag for the first time, it would use the factory (second argument) to load it just in time.

This enables globally registering custom elements that are large but used on many views.
Today the same can be achieved with local resources, which is not fun when the same element is used by many views.

Was this page helpful?
0 / 5 - 0 ratings