Skip to content
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

fix(core/component): implement lazy loading of the components' templates #1466

Merged
merged 10 commits into from
Nov 15, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Reloading now occurs for unloaded components or when explicitly specified with `
This happened because we used `await` and this task could be executed after the component was destroyed.
So we replaced `await` with `SyncPromise`. `bBottomSlide`
* Fix error "ctx.$vueWatch is not a function" caused by the incorrect fix in the v4.0.0-beta.146 `core/component/watch`
* Fixed endless attempts to load a component template that is not in use.
Added a 10-second limit for attempts to load the template. `core/component/decorators/component`
* Default `forceUpdate` param of a property no longer overrides its value inherited from parent component `core/component/decorators/prop`
* Fixed typo: `"prop"` -> `"props"` when inheriting parent properties `core/component/decorators/factory`

Expand Down
4 changes: 3 additions & 1 deletion src/core/component/decorators/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ Changelog
> - :house: [Internal]
> - :nail_care: [Polish]

## v4.0.0-beta.?? (2024-11-??)
## v4.0.0-beta.?? (2024-10-??)

#### :bug: Bug Fix

* Fixed endless attempts to load a component template that is not in use.
Added a 10-second limit for attempts to load the template.
* Default `forceUpdate` param of a property no longer overrides its value inherited from parent component
* Fixed typo: `"prop"` -> `"props"` when inheriting parent properties

Expand Down
52 changes: 43 additions & 9 deletions src/core/component/decorators/component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/

import { identity } from 'core/functools';
import log from 'core/log';

import {

Expand All @@ -38,13 +39,15 @@ import {
} from 'core/component/meta';

import { initEmitter } from 'core/component/event';
import { getComponent, ComponentEngine } from 'core/component/engines';
import { getComponent, ComponentEngine, AsyncComponentOptions } from 'core/component/engines';

import { getComponentMods, getInfoFromConstructor } from 'core/component/reflect';
import { registerComponent, registerParentComponents } from 'core/component/init';

import type { ComponentConstructor, ComponentOptions } from 'core/component/interface';

const logger = log.namespace('core/component');

const OVERRIDDEN = Symbol('This class is overridden in the child layer');

/**
Expand Down Expand Up @@ -226,7 +229,7 @@ export function component(opts?: ComponentOptions): Function {
fillMeta(meta, target);

if (!componentInfo.isAbstract) {
Promise.resolve(loadTemplate(meta.component)).catch(stderr);
Promise.resolve(loadTemplate(meta.component, true)).catch(stderr);
}

} else if (meta.params.root) {
Expand All @@ -241,11 +244,34 @@ export function component(opts?: ComponentOptions): Function {
}
}

function loadTemplate(component: object): CanPromise<ComponentOptions> {
let resolve: Function = identity;
return meta.params.tpl === false ? attachTemplatesAndResolve() : waitComponentTemplates();
function loadTemplate(
component: object,
isFunctional: boolean = false
): ComponentOptions | AsyncComponentOptions {
let
resolve: Function = identity,
reject: Function;

if (meta.params.tpl === false) {
return attachTemplatesAndResolve();
}

if (TPLS[meta.componentName] != null) {
return waitComponentTemplates();
}

if (isFunctional) {
logger.info('loadTemplate', `Template missing for functional component: ${meta.componentName}`);
}

function waitComponentTemplates() {
return {
loader: () => Promise.resolve(waitComponentTemplates(Date.now())),
onError(error: Error) {
logger.error('async-loader', error, meta.componentName);
}
};

function waitComponentTemplates(startedLoadingAt: number = 0) {
const fns = TPLS[meta.componentName];

if (fns != null) {
Expand All @@ -257,17 +283,25 @@ export function component(opts?: ComponentOptions): Function {
return;
}

// Return promise on first try
if (resolve === identity) {
return new Promise((r) => {
resolve = r;
return new Promise((res, rej) => {
resolve = res;
reject = rej;
retry();
});
}

retry();

function retry() {
requestIdleCallback(waitComponentTemplates, {timeout: 50});
// The template should be loaded in 10 seconds after it was requested by the render engine
if (Date.now() - startedLoadingAt > (10).seconds()) {
shining-mind marked this conversation as resolved.
Show resolved Hide resolved
reject(new Error('The component template could not be loaded in 10 seconds'));

} else {
requestIdleCallback(waitComponentTemplates.bind(null, startedLoadingAt), {timeout: 50});
}
}
}

Expand Down
19 changes: 14 additions & 5 deletions src/core/component/engines/vue3/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import { makeLazy } from 'core/lazy';

import { createApp, createSSRApp, defineAsyncComponent, App, Component } from 'vue';
import type { CreateAppFunction } from 'core/component/engines/interface';
import type { AsyncComponentOptions, CreateAppFunction } from 'core/component/engines/interface';

let
ssrContext = SSR || HYDRATION;
Expand Down Expand Up @@ -109,7 +109,11 @@ const Vue = makeLazy(
const staticComponent = Vue.component.length > 0 ? Vue.component : null;

Vue.component = Object.cast(
function component(this: App, name: string, component?: Component): CanUndef<Component> | App {
function component(
this: App,
name: string,
component?: Component | AsyncComponentOptions
): CanUndef<Component> | App {
const
ctx = Object.getPrototypeOf(this),
originalComponent = staticComponent ?? ctx.component;
Expand All @@ -122,13 +126,18 @@ Vue.component = Object.cast(
return originalComponent.call(ctx, name);
}

if (Object.isPromise(component)) {
const promise = component;
component = defineAsyncComponent(Object.cast(() => promise));
if (isAsyncComponentOptions(component)) {
component = defineAsyncComponent(component);
}

return originalComponent.call(ctx, name, component);
}
);

function isAsyncComponentOptions(obj: object): obj is AsyncComponentOptions {
// Just in case check there is no setup property
// to not treat regular component options as async component options
return 'loader' in obj && !('setup' in obj);
}

export default Vue;
Loading