slicejs-web-framework 2.4.6 → 2.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -16,6 +16,7 @@ export default class Controller {
|
|
|
16
16
|
this.loadedBundles = new Set();
|
|
17
17
|
this.bundleConfig = null;
|
|
18
18
|
this.criticalBundleLoaded = false;
|
|
19
|
+
this.bundleImportPromises = new Map();
|
|
19
20
|
|
|
20
21
|
this.idCounter = 0;
|
|
21
22
|
}
|
|
@@ -36,9 +37,33 @@ export default class Controller {
|
|
|
36
37
|
// No bundles available, will use individual component loading
|
|
37
38
|
this.bundleConfig = null;
|
|
38
39
|
this.criticalBundleLoaded = false;
|
|
39
|
-
|
|
40
|
+
}
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Import a bundle URL once per page session.
|
|
45
|
+
* Reuses the same Promise for concurrent callers.
|
|
46
|
+
* @param {string} bundlePath
|
|
47
|
+
* @returns {Promise<any>}
|
|
48
|
+
*/
|
|
49
|
+
importBundleOnce(bundlePath) {
|
|
50
|
+
if (!bundlePath) {
|
|
51
|
+
return Promise.reject(new Error('Bundle path is required'));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (this.bundleImportPromises.has(bundlePath)) {
|
|
55
|
+
return this.bundleImportPromises.get(bundlePath);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const importPromise = import(bundlePath).catch((error) => {
|
|
59
|
+
this.bundleImportPromises.delete(bundlePath);
|
|
60
|
+
throw error;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
this.bundleImportPromises.set(bundlePath, importPromise);
|
|
64
|
+
return importPromise;
|
|
65
|
+
}
|
|
66
|
+
|
|
42
67
|
/**
|
|
43
68
|
* 📦 Loads a bundle by name or category
|
|
44
69
|
*/
|
|
@@ -73,19 +98,12 @@ export default class Controller {
|
|
|
73
98
|
|
|
74
99
|
const bundlePath = `/bundles/${bundleInfo.file}`;
|
|
75
100
|
|
|
76
|
-
|
|
77
|
-
if (!integrityOk) {
|
|
78
|
-
console.warn(`❌ Integrity check failed for bundle ${bundleName}`);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Dynamic import of the bundle
|
|
83
|
-
const bundleModule = await import(bundlePath);
|
|
101
|
+
await this.importBundleOnce(bundlePath);
|
|
84
102
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
103
|
+
if (window.__slicePendingRegistrations?.length) {
|
|
104
|
+
await Promise.all(window.__slicePendingRegistrations);
|
|
105
|
+
window.__slicePendingRegistrations = [];
|
|
106
|
+
}
|
|
89
107
|
|
|
90
108
|
this.loadedBundles.add(bundleName);
|
|
91
109
|
} catch (error) {
|
|
@@ -93,47 +111,6 @@ export default class Controller {
|
|
|
93
111
|
}
|
|
94
112
|
}
|
|
95
113
|
|
|
96
|
-
/**
|
|
97
|
-
* Verifies bundle integrity by comparing sha256 hash.
|
|
98
|
-
* @param {string} bundlePath
|
|
99
|
-
* @param {string|null} expectedIntegrity
|
|
100
|
-
* @returns {Promise<boolean>}
|
|
101
|
-
*/
|
|
102
|
-
async verifyBundleIntegrity(bundlePath, expectedIntegrity) {
|
|
103
|
-
if (!expectedIntegrity) {
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
const response = await fetch(bundlePath, { cache: 'no-store' });
|
|
109
|
-
if (!response.ok) {
|
|
110
|
-
console.warn(`Failed to fetch bundle for integrity check: ${response.status}`);
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const content = await response.text();
|
|
115
|
-
const actualIntegrity = await this.hashSha256(content);
|
|
116
|
-
return actualIntegrity === expectedIntegrity;
|
|
117
|
-
} catch (error) {
|
|
118
|
-
console.warn(`Integrity check error for ${bundlePath}:`, error);
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Calculates sha256 digest for a string.
|
|
125
|
-
* @param {string} content
|
|
126
|
-
* @returns {Promise<string>}
|
|
127
|
-
*/
|
|
128
|
-
async hashSha256(content) {
|
|
129
|
-
const encoder = new TextEncoder();
|
|
130
|
-
const data = encoder.encode(content);
|
|
131
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
132
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
133
|
-
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
134
|
-
return `sha256:${hashHex}`;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
114
|
/**
|
|
138
115
|
* 📦 Registers a bundle's components (called automatically by bundle files)
|
|
139
116
|
*/
|
|
@@ -10,12 +10,22 @@ export default class StylesManager {
|
|
|
10
10
|
/**
|
|
11
11
|
* Load global styles and initialize ThemeManager if enabled.
|
|
12
12
|
* @returns {Promise<void>}
|
|
13
|
-
|
|
13
|
+
*/
|
|
14
14
|
async init() {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const requestedStyles = Array.isArray(slice.stylesConfig.requestedStyles)
|
|
16
|
+
? slice.stylesConfig.requestedStyles
|
|
17
|
+
: [];
|
|
18
|
+
|
|
19
|
+
const styleResults = await Promise.all(
|
|
20
|
+
requestedStyles.map(async (styleName) => {
|
|
21
|
+
const styles = await slice.controller.fetchText(styleName, 'styles');
|
|
22
|
+
return { styleName, styles };
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
for (const { styleName, styles } of styleResults) {
|
|
27
|
+
this.appendComponentStyles(styles);
|
|
28
|
+
slice.logger.logInfo('StylesManager', `${styleName} styles loaded`);
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
if (slice.themeConfig.enabled) {
|
package/Slice/Slice.js
CHANGED
|
@@ -269,27 +269,20 @@ async function init() {
|
|
|
269
269
|
const envMode = envResult?.mode ?? null;
|
|
270
270
|
const bundleConfigJson = configResult;
|
|
271
271
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Pre-fetch critical bundle to warm the browser HTTP cache while the framework
|
|
283
|
-
// bundle is downloading and executing. fetch() downloads bytes without evaluating
|
|
284
|
-
// the module, so the auto-registration block runs safely later when window.slice
|
|
285
|
-
// already exists. Errors are silently ignored — import() will retry if needed.
|
|
286
|
-
const criticalBundleUrl = (resolvedMode === 'production' && bundleConfigJson?.bundles?.critical?.file)
|
|
287
|
-
? `/bundles/${bundleConfigJson.bundles.critical.file}`
|
|
288
|
-
: null;
|
|
289
|
-
if (criticalBundleUrl) {
|
|
290
|
-
fetch(criticalBundleUrl).catch(() => {});
|
|
272
|
+
// 3. Determine canonical mode: env endpoint takes precedence, then bundle config
|
|
273
|
+
let resolvedMode;
|
|
274
|
+
if (envMode) {
|
|
275
|
+
resolvedMode = envMode;
|
|
276
|
+
} else if (bundleConfigJson?.production) {
|
|
277
|
+
resolvedMode = 'production';
|
|
278
|
+
} else {
|
|
279
|
+
resolvedMode = 'development';
|
|
291
280
|
}
|
|
292
281
|
|
|
282
|
+
const criticalBundleUrl = (resolvedMode === 'production' && bundleConfigJson?.bundles?.critical?.file)
|
|
283
|
+
? `/bundles/${bundleConfigJson.bundles.critical.file}`
|
|
284
|
+
: null;
|
|
285
|
+
|
|
293
286
|
// 4. Load framework classes.
|
|
294
287
|
// In production the bundler generates slice-bundle.framework.js which
|
|
295
288
|
// sets window.SLICE_FRAMEWORK_CLASSES. In dev mode always use individual
|
|
@@ -337,18 +330,17 @@ async function init() {
|
|
|
337
330
|
if (window.slice.controller.bundleConfig) {
|
|
338
331
|
const config = window.slice.controller.bundleConfig;
|
|
339
332
|
|
|
340
|
-
|
|
341
|
-
|
|
333
|
+
const criticalFile = config?.bundles?.critical?.file;
|
|
334
|
+
if (criticalFile && criticalBundleUrl) {
|
|
342
335
|
// Bundle auto-registers itself on import via its own registration block.
|
|
343
336
|
// The block pushes its registerBundle() Promise to window.__slicePendingRegistrations
|
|
344
337
|
// so we can await full chunk processing before continuing to build('Loading').
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
338
|
+
await window.slice.controller.importBundleOnce(criticalBundleUrl);
|
|
339
|
+
if (window.__slicePendingRegistrations?.length) {
|
|
340
|
+
await Promise.all(window.__slicePendingRegistrations);
|
|
341
|
+
window.__slicePendingRegistrations = [];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
352
344
|
|
|
353
345
|
const routeBundles = config?.routeBundles || {};
|
|
354
346
|
const initialPath = window.location.pathname || '/';
|
|
@@ -360,9 +352,9 @@ async function init() {
|
|
|
360
352
|
const bundleInfo = config?.bundles?.routes?.[bundleName];
|
|
361
353
|
if (!bundleInfo?.file) continue;
|
|
362
354
|
// Bundle auto-registers itself on import.
|
|
363
|
-
await
|
|
355
|
+
await window.slice.controller.importBundleOnce(`/bundles/${bundleInfo.file}`);
|
|
364
356
|
}
|
|
365
|
-
|
|
357
|
+
};
|
|
366
358
|
|
|
367
359
|
if (typeof requestIdleCallback === 'function') {
|
|
368
360
|
requestIdleCallback(() => loadRouteBundles());
|
|
@@ -466,7 +458,8 @@ async function init() {
|
|
|
466
458
|
}
|
|
467
459
|
}
|
|
468
460
|
|
|
469
|
-
|
|
461
|
+
const stylesInitPromise = window.slice.stylesManager.init();
|
|
462
|
+
const routesModulePromise = import(slice.paths.routesFile);
|
|
470
463
|
|
|
471
464
|
if (sliceConfig.events?.ui?.shortcut || sliceConfig.context?.ui?.shortcut) {
|
|
472
465
|
const normalize = (value) => (typeof value === 'string' ? value.toLowerCase() : '');
|
|
@@ -496,7 +489,7 @@ async function init() {
|
|
|
496
489
|
});
|
|
497
490
|
}
|
|
498
491
|
|
|
499
|
-
const routesModule = await
|
|
492
|
+
const [, routesModule] = await Promise.all([stylesInitPromise, routesModulePromise]);
|
|
500
493
|
const routes = routesModule.default;
|
|
501
494
|
const RouterModule = window.slice.frameworkClasses?.Router
|
|
502
495
|
|| await window.slice.getClass(`${slice.paths.structuralComponentFolderPath}/Router/Router.js`);
|