Skip to content

Commit f1b645e

Browse files
committed
feature #2463 [Stimulus] Fasten lazy loading + add debug lazy:loading and lazy:loaded (smnandre)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [Stimulus] Fasten lazy loading + add debug `lazy:loading` and `lazy:loaded` | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Issues | Fix #... | License | MIT This PR.. 1. improves the controller **lazy loading** by tuning algorithm (early exit, remove await, ...) 2. introduces two **new debug events** in the console to ease DX with lazy controllers * `my-controller#lazy:loading` when the lazy controller is detected in the DOM * `my-controller#lazy:loaded` after the file has been downloaded and imported I would really like some feedback / tests IRL (especially with Webpack i'm not used to) Commits ------- 3b8e1ce [Stimulus] Fasten lazy loading + add debug `lazy:loading` and `lazy:loaded`
2 parents 562c079 + 3b8e1ce commit f1b645e

File tree

2 files changed

+78
-44
lines changed

2 files changed

+78
-44
lines changed

src/StimulusBundle/assets/dist/loader.js

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,50 @@ class StimulusLazyControllerHandler {
2525
this.lazyLoadNewControllers(document.documentElement);
2626
}
2727
lazyLoadExistingControllers(element) {
28-
this.queryControllerNamesWithin(element).forEach((controllerName) => this.loadLazyController(controllerName));
28+
Array.from(element.querySelectorAll(`[${controllerAttribute}]`))
29+
.flatMap(extractControllerNamesFrom)
30+
.forEach((controllerName) => this.loadLazyController(controllerName));
2931
}
30-
async loadLazyController(name) {
31-
if (canRegisterController(name, this.application)) {
32-
if (this.lazyControllers[name] === undefined) {
33-
return;
34-
}
35-
const controllerModule = await this.lazyControllers[name]();
36-
registerController(name, controllerModule.default, this.application);
32+
loadLazyController(name) {
33+
if (!this.lazyControllers[name]) {
34+
return;
35+
}
36+
const controllerLoader = this.lazyControllers[name];
37+
delete this.lazyControllers[name];
38+
if (!canRegisterController(name, this.application)) {
39+
return;
3740
}
41+
this.application.logDebugActivity(name, 'lazy:loading');
42+
controllerLoader()
43+
.then((controllerModule) => {
44+
this.application.logDebugActivity(name, 'lazy:loaded');
45+
registerController(name, controllerModule.default, this.application);
46+
})
47+
.catch((error) => {
48+
console.error(`Error loading controller "${name}":`, error);
49+
});
3850
}
3951
lazyLoadNewControllers(element) {
52+
if (Object.keys(this.lazyControllers).length === 0) {
53+
return;
54+
}
4055
new MutationObserver((mutationsList) => {
41-
for (const { attributeName, target, type } of mutationsList) {
42-
switch (type) {
43-
case 'attributes': {
44-
if (attributeName === controllerAttribute &&
45-
target.getAttribute(controllerAttribute)) {
46-
extractControllerNamesFrom(target).forEach((controllerName) => this.loadLazyController(controllerName));
56+
for (const mutation of mutationsList) {
57+
switch (mutation.type) {
58+
case 'childList': {
59+
for (const node of mutation.addedNodes) {
60+
if (node instanceof Element) {
61+
extractControllerNamesFrom(node).forEach((controllerName) => {
62+
this.loadLazyController(controllerName);
63+
});
64+
}
4765
}
4866
break;
4967
}
50-
case 'childList': {
51-
this.lazyLoadExistingControllers(target);
68+
case 'attributes': {
69+
if (mutation.attributeName === controllerAttribute) {
70+
extractControllerNamesFrom(mutation.target).forEach((controllerName) => this.loadLazyController(controllerName));
71+
}
5272
}
5373
}
5474
}
@@ -58,9 +78,6 @@ class StimulusLazyControllerHandler {
5878
childList: true,
5979
});
6080
}
61-
queryControllerNamesWithin(element) {
62-
return Array.from(element.querySelectorAll(`[${controllerAttribute}]`)).flatMap(extractControllerNamesFrom);
63-
}
6481
}
6582
function registerController(name, controller, application) {
6683
if (canRegisterController(name, application)) {

src/StimulusBundle/assets/src/loader.ts

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -64,40 +64,61 @@ class StimulusLazyControllerHandler {
6464
}
6565

6666
private lazyLoadExistingControllers(element: Element) {
67-
this.queryControllerNamesWithin(element).forEach((controllerName) => this.loadLazyController(controllerName));
67+
Array.from(element.querySelectorAll(`[${controllerAttribute}]`))
68+
.flatMap(extractControllerNamesFrom)
69+
.forEach((controllerName) => this.loadLazyController(controllerName));
6870
}
6971

70-
private async loadLazyController(name: string) {
71-
if (canRegisterController(name, this.application)) {
72-
if (this.lazyControllers[name] === undefined) {
73-
return;
74-
}
72+
private loadLazyController(name: string) {
73+
if (!this.lazyControllers[name]) {
74+
return;
75+
}
7576

76-
const controllerModule = await this.lazyControllers[name]();
77+
// Delete the loader to avoid loading it twice
78+
const controllerLoader = this.lazyControllers[name];
79+
delete this.lazyControllers[name];
7780

78-
registerController(name, controllerModule.default, this.application);
81+
if (!canRegisterController(name, this.application)) {
82+
return;
7983
}
84+
85+
this.application.logDebugActivity(name, 'lazy:loading');
86+
87+
controllerLoader()
88+
.then((controllerModule) => {
89+
this.application.logDebugActivity(name, 'lazy:loaded');
90+
registerController(name, controllerModule.default, this.application);
91+
})
92+
.catch((error) => {
93+
console.error(`Error loading controller "${name}":`, error);
94+
});
8095
}
8196

8297
private lazyLoadNewControllers(element: Element) {
98+
if (Object.keys(this.lazyControllers).length === 0) {
99+
return;
100+
}
83101
new MutationObserver((mutationsList) => {
84-
for (const { attributeName, target, type } of mutationsList) {
85-
switch (type) {
86-
case 'attributes': {
87-
if (
88-
attributeName === controllerAttribute &&
89-
(target as Element).getAttribute(controllerAttribute)
90-
) {
91-
extractControllerNamesFrom(target as Element).forEach((controllerName) =>
92-
this.loadLazyController(controllerName)
93-
);
102+
for (const mutation of mutationsList) {
103+
switch (mutation.type) {
104+
case 'childList': {
105+
// @ts-ignore
106+
for (const node of mutation.addedNodes) {
107+
if (node instanceof Element) {
108+
extractControllerNamesFrom(node).forEach((controllerName) => {
109+
this.loadLazyController(controllerName);
110+
});
111+
}
94112
}
95-
96113
break;
97114
}
98115

99-
case 'childList': {
100-
this.lazyLoadExistingControllers(target as Element);
116+
case 'attributes': {
117+
if (mutation.attributeName === controllerAttribute) {
118+
extractControllerNamesFrom(mutation.target as Element).forEach((controllerName) =>
119+
this.loadLazyController(controllerName)
120+
);
121+
}
101122
}
102123
}
103124
}
@@ -107,10 +128,6 @@ class StimulusLazyControllerHandler {
107128
childList: true,
108129
});
109130
}
110-
111-
private queryControllerNamesWithin(element: Element): string[] {
112-
return Array.from(element.querySelectorAll(`[${controllerAttribute}]`)).flatMap(extractControllerNamesFrom);
113-
}
114131
}
115132

116133
function registerController(name: string, controller: ControllerConstructor, application: Application) {

0 commit comments

Comments
 (0)