-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcore.scanner.mjs
More file actions
130 lines (111 loc) · 4.36 KB
/
core.scanner.mjs
File metadata and controls
130 lines (111 loc) · 4.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**
* @fileoverview DOM Scanner - Discovers and groups elements requiring dynamic modules
* @module core.scanner
* @version 3.0.0
* @author hnldesign
* @since 2022
*/
import {logger} from "./core.log.mjs";
export const NAME = 'core.scanner';
/**
* Scans DOM for elements with data-requires attribute and groups them by module path.
*
* Elements are categorized as immediate (load now) or deferred (lazy load) based on
* the data-require-lazy="true" attribute. Comma-separated module paths are split
* and each path gets its own array of requesting elements.
*
* @param {Function} [callback] - Called with (modules, deferred, stats) when scan completes
* - modules: Object mapping module paths to element arrays (immediate load)
* - deferred: Object mapping module paths to element arrays (lazy load)
* - stats: Object with {immediate, lazy, total} counts
* @returns {{modules: Object<string, HTMLElement[]>, deferred: Object<string, HTMLElement[]>, stats: {immediate: number, lazy: number, total: number}}}
* Scan results object containing categorized modules and statistics
*
* @example
* // Scan and process results
* const {modules, deferred, stats} = domScanner((mods, def, stats) => {
* console.log(`Found ${stats.immediate} immediate, ${stats.lazy} lazy modules`);
* });
*
* @example
* // Element with comma-separated modules
* <div data-requires="./slider.mjs,./analytics.mjs"></div>
* // Results in:
* modules['./slider.mjs'] = [div]
* modules['./analytics.mjs'] = [div]
*
* @example
* // Lazy loading element
* <div data-requires="./gallery.mjs" data-require-lazy="true"></div>
* // Results in:
* deferred['./gallery.mjs'] = [div]
*/
export function domScanner(callback) {
logger.info(NAME, 'Scanning DOM for data-requires modules...');
const modules = {};
const deferred = {};
const elements = document.querySelectorAll('[data-requires]');
const elementCount = elements.length;
// Early exit - avoid forEach overhead
if (elementCount === 0) {
logger.info(NAME, 'Scan complete: 0 modules found.');
if (typeof callback === 'function') {
callback.call(null, modules, deferred, {immediate: 0, lazy: 0, total: 0});
}
return {modules, deferred, stats: {immediate: 0, lazy: 0, total: 0}};
}
// Process elements in single pass - initialize tracking AND categorize
for (let i = 0; i < elementCount; i++) {
const element = elements[i];
const requiresAttr = element.dataset.requires;
// Skip empty/whitespace-only
if (!requiresAttr || !requiresAttr.trim()) continue;
const isLazy = element.dataset.requireLazy && element.dataset.requireLazy !== 'false';
const targetBucket = isLazy ? deferred : modules;
// Split and filter module paths (done once per element)
const modulePaths = requiresAttr.split(',');
let validPathCount = 0;
for (let j = 0; j < modulePaths.length; j++) {
const modulePath = modulePaths[j].trim();
if (!modulePath) continue;
validPathCount++;
// Lazily initialize array
if (!targetBucket[modulePath]) {
targetBucket[modulePath] = [];
}
targetBucket[modulePath].push(element);
}
// Initialize state tracking with actual valid path count
if (validPathCount > 0) {
element._moduleTracking = {
required: validPathCount,
loaded: 0
};
element.classList.add('module-pending');
element.dataset.requiresState = 'pending';
}
}
// Calculate stats once
const immediateKeys = Object.keys(modules);
const deferredKeys = Object.keys(deferred);
const stats = {
immediate: immediateKeys.length,
lazy: deferredKeys.length,
total: immediateKeys.length + deferredKeys.length
};
// Log results
if (stats.total === 0) {
logger.info(NAME, 'Scan complete: 0 modules found.');
} else {
logger.info(
NAME,
'Scan complete: ' + stats.immediate + ' module(s) found' +
(stats.lazy ? ', ' + stats.lazy + ' lazy module(s)' : '') + '.'
);
}
// Invoke callback
if (typeof callback === 'function') {
callback.call(null, modules, deferred, stats);
}
return {modules, deferred, stats};
}