slicejs-web-framework 3.2.0 → 3.2.2
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.
|
@@ -65,6 +65,20 @@ export default class Controller {
|
|
|
65
65
|
return importPromise;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
buildBundleImportPath(bundleInfo) {
|
|
69
|
+
if (!bundleInfo || typeof bundleInfo.file !== 'string' || bundleInfo.file.length === 0) {
|
|
70
|
+
throw new Error('Bundle file is required');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const basePath = `/bundles/${bundleInfo.file}`;
|
|
74
|
+
const bundleHash = typeof bundleInfo.hash === 'string' ? bundleInfo.hash.trim() : '';
|
|
75
|
+
if (!bundleHash) {
|
|
76
|
+
return basePath;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return `${basePath}?v=${encodeURIComponent(bundleHash)}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
68
82
|
/**
|
|
69
83
|
* Validate Bundling V2 module contract.
|
|
70
84
|
* Requires named exports: SLICE_BUNDLE_META and registerAll.
|
|
@@ -126,7 +140,7 @@ export default class Controller {
|
|
|
126
140
|
await this.loadBundleWithDependencies(dependencyName, loadingStack);
|
|
127
141
|
}
|
|
128
142
|
|
|
129
|
-
const bundlePath =
|
|
143
|
+
const bundlePath = this.buildBundleImportPath(bundleInfo);
|
|
130
144
|
const bundleModule = await this.importBundleOnce(bundlePath);
|
|
131
145
|
const { metadata, registerAll } = await this.validateBundleModule(bundleModule, resolvedBundleName);
|
|
132
146
|
|
|
@@ -379,6 +379,66 @@ test('loadBundle registers vendor-shared dependencies from registerAll return wh
|
|
|
379
379
|
}
|
|
380
380
|
});
|
|
381
381
|
|
|
382
|
+
test('loadBundle appends bundle hash as query param when importing bundle files', async () => {
|
|
383
|
+
const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
|
|
384
|
+
const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
|
|
385
|
+
await writeFile(
|
|
386
|
+
loaderPath,
|
|
387
|
+
`export async function resolve(specifier, context, nextResolve) {
|
|
388
|
+
if (specifier === '/Components/components.js') {
|
|
389
|
+
return {
|
|
390
|
+
shortCircuit: true,
|
|
391
|
+
url: 'data:text/javascript,export default {};',
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
return nextResolve(specifier, context);
|
|
395
|
+
}
|
|
396
|
+
`,
|
|
397
|
+
'utf8'
|
|
398
|
+
);
|
|
399
|
+
register(pathToFileURL(loaderPath).href);
|
|
400
|
+
|
|
401
|
+
const controllerModuleUrl = new URL('../Components/Structural/Controller/Controller.js', import.meta.url).href;
|
|
402
|
+
const { default: Controller } = await import(controllerModuleUrl);
|
|
403
|
+
const controller = new Controller();
|
|
404
|
+
const originalSlice = globalThis.slice;
|
|
405
|
+
|
|
406
|
+
controller.bundleConfig = {
|
|
407
|
+
bundles: {
|
|
408
|
+
routes: {
|
|
409
|
+
dashboard: {
|
|
410
|
+
file: 'slice-bundle.dashboard.js',
|
|
411
|
+
hash: 'abc123hash',
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
let importedPath = null;
|
|
418
|
+
controller.importBundleOnce = async (bundlePath) => {
|
|
419
|
+
importedPath = bundlePath;
|
|
420
|
+
return {
|
|
421
|
+
SLICE_BUNDLE_META: {
|
|
422
|
+
bundleKey: 'dashboard',
|
|
423
|
+
type: 'route',
|
|
424
|
+
},
|
|
425
|
+
registerAll: async () => {},
|
|
426
|
+
};
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
globalThis.slice = {
|
|
431
|
+
stylesManager: {},
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
await controller.loadBundle('dashboard');
|
|
435
|
+
assert.equal(importedPath, '/bundles/slice-bundle.dashboard.js?v=abc123hash');
|
|
436
|
+
} finally {
|
|
437
|
+
globalThis.slice = originalSlice;
|
|
438
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
382
442
|
test('loadBundle dedupes concurrent and repeated alias/case requests using canonical bundle key', async () => {
|
|
383
443
|
const tempDir = await mkdtemp(path.join(tmpdir(), 'slice-controller-loader-'));
|
|
384
444
|
const loaderPath = path.join(tempDir, 'components-alias-loader.mjs');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slicejs-web-framework",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20"
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"author": "",
|
|
22
22
|
"license": "ISC",
|
|
23
23
|
"type": "module",
|
|
24
|
+
"types": "./types/index.d.ts",
|
|
24
25
|
"dependencies": {
|
|
25
26
|
"express": "^4.19.2"
|
|
26
27
|
},
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
export type SliceMode = 'development' | 'production' | string;
|
|
2
|
+
|
|
3
|
+
export interface RouteConfig {
|
|
4
|
+
path: string;
|
|
5
|
+
component: string;
|
|
6
|
+
children?: RouteConfig[];
|
|
7
|
+
metadata?: Record<string, unknown>;
|
|
8
|
+
fullPath?: string;
|
|
9
|
+
parentPath?: string | null;
|
|
10
|
+
parentRoute?: RouteConfig | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RouteInfo {
|
|
14
|
+
path: string;
|
|
15
|
+
component: string;
|
|
16
|
+
params: Record<string, string>;
|
|
17
|
+
query: Record<string, string>;
|
|
18
|
+
metadata: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RouteMatch {
|
|
22
|
+
route: RouteConfig | null;
|
|
23
|
+
params: Record<string, string>;
|
|
24
|
+
childRoute?: RouteConfig;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type RouterNext = (arg?: void | false | string | { path: string; replace?: boolean }) => void;
|
|
28
|
+
|
|
29
|
+
export interface SliceControllerApi {
|
|
30
|
+
componentCategories: Map<string, string>;
|
|
31
|
+
templates: Map<string, HTMLTemplateElement>;
|
|
32
|
+
classes: Map<string, new (...args: any[]) => HTMLElement>;
|
|
33
|
+
requestedStyles: Set<string>;
|
|
34
|
+
activeComponents: Map<string, HTMLElement>;
|
|
35
|
+
getComponent(sliceId: string): HTMLElement | undefined;
|
|
36
|
+
getComponentCategory(componentSliceId: string): string | undefined;
|
|
37
|
+
fetchText(
|
|
38
|
+
componentName: string,
|
|
39
|
+
resourceType: 'html' | 'css' | 'theme' | 'styles',
|
|
40
|
+
componentCategory?: string,
|
|
41
|
+
customPath?: string
|
|
42
|
+
): Promise<string>;
|
|
43
|
+
setComponentProps(component: HTMLElement, props: Record<string, unknown>): void;
|
|
44
|
+
destroyComponent(components: HTMLElement | string | Array<HTMLElement | string>): number;
|
|
45
|
+
destroyByContainer(container: HTMLElement): number;
|
|
46
|
+
destroyByPattern(pattern: string | RegExp): number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface SliceStylesManagerApi {
|
|
50
|
+
componentStyles: HTMLStyleElement;
|
|
51
|
+
themeManager?: SliceThemeManagerApi;
|
|
52
|
+
init(): Promise<void>;
|
|
53
|
+
appendComponentStyles(cssText: string): void;
|
|
54
|
+
registerComponentStyles(componentName: string, cssText: string): void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface SliceThemeManagerApi {
|
|
58
|
+
currentTheme: string | null;
|
|
59
|
+
applyTheme(themeName: string): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface SliceLoggerApi {
|
|
63
|
+
logError(componentSliceId: string, message: string, error?: unknown): void;
|
|
64
|
+
logWarning(componentSliceId: string, message: string): void;
|
|
65
|
+
logInfo(componentSliceId: string, message: string): void;
|
|
66
|
+
getLogs(): unknown[];
|
|
67
|
+
clearLogs(): void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface SliceEventBindingApi {
|
|
71
|
+
subscribe(eventName: string, callback: (data?: unknown) => void): string | null;
|
|
72
|
+
subscribeOnce(eventName: string, callback: (data?: unknown) => void): string | null;
|
|
73
|
+
emit(eventName: string, data?: unknown): void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface SliceEventManagerApi {
|
|
77
|
+
init(): boolean;
|
|
78
|
+
subscribe(
|
|
79
|
+
eventName: string,
|
|
80
|
+
callback: (data?: unknown) => void,
|
|
81
|
+
options?: { component?: HTMLElement }
|
|
82
|
+
): string | null;
|
|
83
|
+
subscribeOnce(
|
|
84
|
+
eventName: string,
|
|
85
|
+
callback: (data?: unknown) => void,
|
|
86
|
+
options?: { component?: HTMLElement }
|
|
87
|
+
): string | null;
|
|
88
|
+
unsubscribe(eventName: string, subscriptionId: string): boolean;
|
|
89
|
+
emit(eventName: string, ...data: unknown[]): void;
|
|
90
|
+
bind(component: HTMLElement): SliceEventBindingApi | null;
|
|
91
|
+
cleanupComponent(sliceId: string): number;
|
|
92
|
+
hasSubscribers(eventName: string): boolean;
|
|
93
|
+
subscriberCount(eventName: string): number;
|
|
94
|
+
clear(): void;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface SliceContextOptions {
|
|
98
|
+
persist?: boolean;
|
|
99
|
+
storageKey?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface SliceContextManagerApi {
|
|
103
|
+
init(): boolean;
|
|
104
|
+
create(name: string, initialState?: Record<string, unknown>, options?: SliceContextOptions): boolean;
|
|
105
|
+
getState<T = unknown>(name: string): T | null;
|
|
106
|
+
setState<T = unknown>(name: string, updater: T | ((prevState: T) => T)): void;
|
|
107
|
+
watch<TSelected = unknown>(
|
|
108
|
+
name: string,
|
|
109
|
+
component: HTMLElement,
|
|
110
|
+
callback: (value: TSelected) => void,
|
|
111
|
+
selector?: (state: unknown) => TSelected
|
|
112
|
+
): string | null;
|
|
113
|
+
has(name: string): boolean;
|
|
114
|
+
destroy(name: string): boolean;
|
|
115
|
+
list(): string[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface SliceRouterApi {
|
|
119
|
+
activeRoute: RouteConfig | null;
|
|
120
|
+
pathToRouteMap: Map<string, RouteConfig>;
|
|
121
|
+
init(): void;
|
|
122
|
+
start(): Promise<void>;
|
|
123
|
+
beforeEach(guard: (to: RouteInfo, from: RouteInfo, next: RouterNext) => void | Promise<void>): void;
|
|
124
|
+
afterEach(guard: (to: RouteInfo, from: RouteInfo) => void): void;
|
|
125
|
+
navigate(path: string, redirectChain?: string[], options?: { replace?: boolean }): Promise<void>;
|
|
126
|
+
matchRoute(path: string): RouteMatch;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface SlicePanelDebuggerApi {
|
|
130
|
+
init(): Promise<void>;
|
|
131
|
+
toggle(): void;
|
|
132
|
+
open(): void;
|
|
133
|
+
close(): void;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface SliceDebuggerApi {
|
|
137
|
+
enableDebugMode(): Promise<boolean>;
|
|
138
|
+
attachDebugMode(component: HTMLElement): void;
|
|
139
|
+
hide(): void;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface SliceLoadingApi {
|
|
143
|
+
start?: () => void;
|
|
144
|
+
stop?: () => void;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface SlicePaths {
|
|
148
|
+
routesFile?: string;
|
|
149
|
+
themes?: string;
|
|
150
|
+
styles?: string;
|
|
151
|
+
structuralComponentFolderPath?: string;
|
|
152
|
+
components?: Record<string, { path: string; type?: string }>;
|
|
153
|
+
[key: string]: unknown;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface SliceFrameworkClasses {
|
|
157
|
+
Controller?: new () => SliceControllerApi;
|
|
158
|
+
StylesManager?: new () => SliceStylesManagerApi;
|
|
159
|
+
Router?: new (routes: RouteConfig[]) => SliceRouterApi;
|
|
160
|
+
Logger?: new () => SliceLoggerApi;
|
|
161
|
+
EventManager?: new () => SliceEventManagerApi;
|
|
162
|
+
ContextManager?: new () => SliceContextManagerApi;
|
|
163
|
+
Debugger?: new () => SliceDebuggerApi;
|
|
164
|
+
EventManagerDebugger?: new () => SlicePanelDebuggerApi;
|
|
165
|
+
ContextManagerDebugger?: new () => SlicePanelDebuggerApi;
|
|
166
|
+
ThemeManager?: new () => SliceThemeManagerApi;
|
|
167
|
+
[key: string]: unknown;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface SliceApi {
|
|
171
|
+
frameworkClasses: SliceFrameworkClasses | null;
|
|
172
|
+
controller: SliceControllerApi;
|
|
173
|
+
stylesManager: SliceStylesManagerApi;
|
|
174
|
+
logger: SliceLoggerApi;
|
|
175
|
+
router: SliceRouterApi;
|
|
176
|
+
events: SliceEventManagerApi;
|
|
177
|
+
context: SliceContextManagerApi;
|
|
178
|
+
debugger?: SliceDebuggerApi;
|
|
179
|
+
eventsDebugger?: SlicePanelDebuggerApi;
|
|
180
|
+
contextDebugger?: SlicePanelDebuggerApi;
|
|
181
|
+
loading?: SliceLoadingApi;
|
|
182
|
+
paths: SlicePaths;
|
|
183
|
+
_mode: SliceMode;
|
|
184
|
+
getClass<T = unknown>(module: string): Promise<T | undefined>;
|
|
185
|
+
isProduction(): boolean;
|
|
186
|
+
getEnv(name: string, fallbackValue?: string): string | undefined;
|
|
187
|
+
getPublicEnv(): Record<string, string>;
|
|
188
|
+
getComponent(componentSliceId: string): HTMLElement | undefined;
|
|
189
|
+
build<T = HTMLElement>(componentName: string, props?: Record<string, unknown>): Promise<T | null>;
|
|
190
|
+
setTheme(themeName: string): Promise<void>;
|
|
191
|
+
readonly theme: string | null;
|
|
192
|
+
attachTemplate(componentInstance: HTMLElement): void;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
declare class Slice {
|
|
196
|
+
constructor(sliceConfig: Record<string, unknown>, frameworkClasses?: SliceFrameworkClasses | null);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export default Slice;
|
|
200
|
+
|
|
201
|
+
declare global {
|
|
202
|
+
interface Window {
|
|
203
|
+
slice: SliceApi;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const slice: SliceApi;
|
|
207
|
+
}
|