solenya-group/solenya

Lazy Loading

Opened this issue · 1 comments

For larger applications, performance can be improved by lazily loading modules.

Initial thinking:

Consider the samples app. The 'Samples' class is the root component, and must be loaded to see anything at all. However, the 'Samples' class has many child component properties for each demo, e.g. 'Counter', 'BMI' etc. Let's suppose we want these child components to be lazily loaded, only when the user navigates to that particular demo.

Currently, the samples app loads all the child components eagerly. To load each child lazily, each property would have to use a 'require' to dynamically pull in that sample. This is complicated by the deserializer (i.e. class-transformer). The deserializer also references each child component via the @type decorator, whereby the constructor for each child component is invoked on deserialization.

A potential solution is to use an alternative decorator to class-transformer's @type, so deserialization is averted. The raw json object representing the serialized type can still however, be loaded into memory, awaiting potential lazy deserialization. When a property (e.g. the BMI property of the Samples component) is accessed, a dynamically generated property gets that raw json object representing the serialized BMI component, and calls the deserializer on that json object, and loads the BMI component. Interestingly, this should actually speed up serialization, since child components that were never loaded can just verbatim use the existing json objects.

To lazy-load a Solenya Component w/ Webpack 4 today we can use dynamic imports. First:

We're now ready to dynamically import the 'myComponent' module (and remove the static imports to your component). A common pattern will be to use lazy loading in conjunction with routing. So in the beforeNavigate method on your parent component:

    async beforeNavigate (childPath: string) {
        if (pathHead (childPath) == "deep-learning")
            attachLazy (this, () => this.myComponent, await import (/* webpackChunkName: "myComponent" */ './myComponent'))        
        return true
    }

Where attachLazy just sets the child to a field on the parent component, then attaches, as follows:

const attachLazy = async (parent: Component, prop: PropertyRef<any>, module: any) => {    
    const child = parent[getPropertyKey(prop)] = new module.default() as Component
    child.attach (parent.app!, parent)
}

Regarding the comment above - that's focused on the issue with serialization. That can be addressed later.