@coherent.js/performance 1.0.0-beta.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.
- package/LICENSE +21 -0
- package/dist/index.js +1028 -0
- package/package.json +42 -0
- package/types/index.d.ts +166 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Thomas Drouvin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,1028 @@
|
|
|
1
|
+
// src/code-splitting.js
|
|
2
|
+
var CodeSplitter = class {
|
|
3
|
+
constructor(options = {}) {
|
|
4
|
+
this.options = {
|
|
5
|
+
preload: [],
|
|
6
|
+
prefetch: [],
|
|
7
|
+
timeout: 1e4,
|
|
8
|
+
retries: 3,
|
|
9
|
+
...options
|
|
10
|
+
};
|
|
11
|
+
this.modules = /* @__PURE__ */ new Map();
|
|
12
|
+
this.loading = /* @__PURE__ */ new Map();
|
|
13
|
+
this.failed = /* @__PURE__ */ new Set();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Dynamically import a module
|
|
17
|
+
*
|
|
18
|
+
* @param {string} path - Module path
|
|
19
|
+
* @param {Object} [options] - Import options
|
|
20
|
+
* @returns {Promise} Module exports
|
|
21
|
+
*/
|
|
22
|
+
async import(path, options = {}) {
|
|
23
|
+
if (this.modules.has(path)) {
|
|
24
|
+
return this.modules.get(path);
|
|
25
|
+
}
|
|
26
|
+
if (this.loading.has(path)) {
|
|
27
|
+
return this.loading.get(path);
|
|
28
|
+
}
|
|
29
|
+
const importPromise = this.loadModule(path, options);
|
|
30
|
+
this.loading.set(path, importPromise);
|
|
31
|
+
try {
|
|
32
|
+
const module = await importPromise;
|
|
33
|
+
this.modules.set(path, module);
|
|
34
|
+
this.loading.delete(path);
|
|
35
|
+
return module;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
this.loading.delete(path);
|
|
38
|
+
this.failed.add(path);
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Load module with retries
|
|
44
|
+
*/
|
|
45
|
+
async loadModule(path, options = {}) {
|
|
46
|
+
const maxRetries = options.retries ?? this.options.retries;
|
|
47
|
+
const timeout = options.timeout ?? this.options.timeout;
|
|
48
|
+
let lastError;
|
|
49
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
50
|
+
try {
|
|
51
|
+
const importPath = attempt > 0 ? `${path}?retry=${attempt}&t=${Date.now()}` : path;
|
|
52
|
+
const module = await Promise.race([
|
|
53
|
+
import(importPath),
|
|
54
|
+
new Promise(
|
|
55
|
+
(_, reject) => setTimeout(() => reject(new Error("Import timeout")), timeout)
|
|
56
|
+
)
|
|
57
|
+
]);
|
|
58
|
+
return module;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
lastError = error;
|
|
61
|
+
if (attempt < maxRetries) {
|
|
62
|
+
await new Promise(
|
|
63
|
+
(resolve) => setTimeout(resolve, Math.pow(2, attempt) * 1e3)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`Failed to load module ${path}: ${lastError.message}`);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Preload modules
|
|
72
|
+
*/
|
|
73
|
+
async preload(paths) {
|
|
74
|
+
const pathArray = Array.isArray(paths) ? paths : [paths];
|
|
75
|
+
return Promise.all(
|
|
76
|
+
pathArray.map((path) => this.import(path).catch((err) => {
|
|
77
|
+
console.warn(`Failed to preload ${path}:`, err);
|
|
78
|
+
return null;
|
|
79
|
+
}))
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Prefetch modules (low priority)
|
|
84
|
+
*/
|
|
85
|
+
prefetch(paths) {
|
|
86
|
+
const pathArray = Array.isArray(paths) ? paths : [paths];
|
|
87
|
+
if (typeof requestIdleCallback !== "undefined") {
|
|
88
|
+
requestIdleCallback(() => {
|
|
89
|
+
pathArray.forEach((path) => {
|
|
90
|
+
this.import(path).catch(() => {
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
} else {
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
pathArray.forEach((path) => {
|
|
97
|
+
this.import(path).catch(() => {
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}, 0);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Check if module is loaded
|
|
105
|
+
*/
|
|
106
|
+
isLoaded(path) {
|
|
107
|
+
return this.modules.has(path);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Check if module is loading
|
|
111
|
+
*/
|
|
112
|
+
isLoading(path) {
|
|
113
|
+
return this.loading.has(path);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Check if module failed to load
|
|
117
|
+
*/
|
|
118
|
+
hasFailed(path) {
|
|
119
|
+
return this.failed.has(path);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Clear cache
|
|
123
|
+
*/
|
|
124
|
+
clearCache(path = null) {
|
|
125
|
+
if (path) {
|
|
126
|
+
this.modules.delete(path);
|
|
127
|
+
this.failed.delete(path);
|
|
128
|
+
} else {
|
|
129
|
+
this.modules.clear();
|
|
130
|
+
this.failed.clear();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get statistics
|
|
135
|
+
*/
|
|
136
|
+
getStats() {
|
|
137
|
+
return {
|
|
138
|
+
loaded: this.modules.size,
|
|
139
|
+
loading: this.loading.size,
|
|
140
|
+
failed: this.failed.size,
|
|
141
|
+
modules: Array.from(this.modules.keys())
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
function createCodeSplitter(options = {}) {
|
|
146
|
+
return new CodeSplitter(options);
|
|
147
|
+
}
|
|
148
|
+
function lazy(loader, options = {}) {
|
|
149
|
+
let modulePromise = null;
|
|
150
|
+
let module = null;
|
|
151
|
+
let error = null;
|
|
152
|
+
return function LazyComponent(props = {}) {
|
|
153
|
+
if (module) {
|
|
154
|
+
const Component = module.default || module;
|
|
155
|
+
return Component(props);
|
|
156
|
+
}
|
|
157
|
+
if (error) {
|
|
158
|
+
if (options.errorComponent) {
|
|
159
|
+
return options.errorComponent({ error, retry: () => {
|
|
160
|
+
error = null;
|
|
161
|
+
modulePromise = null;
|
|
162
|
+
return LazyComponent(props);
|
|
163
|
+
} });
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
div: {
|
|
167
|
+
className: "lazy-error",
|
|
168
|
+
text: `Error loading component: ${error.message}`
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (!modulePromise) {
|
|
173
|
+
modulePromise = loader().then((mod) => {
|
|
174
|
+
module = mod;
|
|
175
|
+
return mod;
|
|
176
|
+
}).catch((err) => {
|
|
177
|
+
error = err;
|
|
178
|
+
throw err;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (options.loadingComponent) {
|
|
182
|
+
return options.loadingComponent(props);
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
div: {
|
|
186
|
+
className: "lazy-loading",
|
|
187
|
+
text: options.loadingText || "Loading..."
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function splitComponent(componentPath, options = {}) {
|
|
193
|
+
const splitter = new CodeSplitter(options);
|
|
194
|
+
return lazy(
|
|
195
|
+
() => splitter.import(componentPath),
|
|
196
|
+
options
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
function createRouteSplitter(routes) {
|
|
200
|
+
const splitter = new CodeSplitter();
|
|
201
|
+
const routeMap = /* @__PURE__ */ new Map();
|
|
202
|
+
for (const [path, config] of Object.entries(routes)) {
|
|
203
|
+
if (typeof config === "string") {
|
|
204
|
+
routeMap.set(path, {
|
|
205
|
+
loader: () => splitter.import(config)
|
|
206
|
+
});
|
|
207
|
+
} else {
|
|
208
|
+
routeMap.set(path, {
|
|
209
|
+
loader: () => splitter.import(config.component),
|
|
210
|
+
preload: config.preload || [],
|
|
211
|
+
...config
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
/**
|
|
217
|
+
* Load route component
|
|
218
|
+
*/
|
|
219
|
+
async loadRoute(path) {
|
|
220
|
+
const route = routeMap.get(path);
|
|
221
|
+
if (!route) {
|
|
222
|
+
throw new Error(`Route not found: ${path}`);
|
|
223
|
+
}
|
|
224
|
+
if (route.preload && route.preload.length > 0) {
|
|
225
|
+
splitter.prefetch(route.preload);
|
|
226
|
+
}
|
|
227
|
+
return await route.loader();
|
|
228
|
+
},
|
|
229
|
+
/**
|
|
230
|
+
* Preload route
|
|
231
|
+
*/
|
|
232
|
+
preloadRoute(path) {
|
|
233
|
+
const route = routeMap.get(path);
|
|
234
|
+
if (route) {
|
|
235
|
+
return route.loader();
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
/**
|
|
239
|
+
* Get all routes
|
|
240
|
+
*/
|
|
241
|
+
getRoutes() {
|
|
242
|
+
return Array.from(routeMap.keys());
|
|
243
|
+
},
|
|
244
|
+
/**
|
|
245
|
+
* Get splitter instance
|
|
246
|
+
*/
|
|
247
|
+
getSplitter() {
|
|
248
|
+
return splitter;
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
var BundleAnalyzer = class {
|
|
253
|
+
constructor() {
|
|
254
|
+
this.chunks = /* @__PURE__ */ new Map();
|
|
255
|
+
this.loadTimes = /* @__PURE__ */ new Map();
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Track chunk load
|
|
259
|
+
*/
|
|
260
|
+
trackLoad(chunkName, size, loadTime) {
|
|
261
|
+
this.chunks.set(chunkName, { size, loadTime });
|
|
262
|
+
this.loadTimes.set(chunkName, loadTime);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get bundle statistics
|
|
266
|
+
*/
|
|
267
|
+
getStats() {
|
|
268
|
+
const chunks = Array.from(this.chunks.entries());
|
|
269
|
+
const totalSize = chunks.reduce((sum, [, chunk]) => sum + chunk.size, 0);
|
|
270
|
+
const avgLoadTime = chunks.reduce((sum, [, chunk]) => sum + chunk.loadTime, 0) / chunks.length;
|
|
271
|
+
return {
|
|
272
|
+
totalChunks: chunks.length,
|
|
273
|
+
totalSize,
|
|
274
|
+
averageLoadTime: avgLoadTime,
|
|
275
|
+
chunks: chunks.map(([name, data]) => ({
|
|
276
|
+
name,
|
|
277
|
+
size: data.size,
|
|
278
|
+
loadTime: data.loadTime,
|
|
279
|
+
percentage: (data.size / totalSize * 100).toFixed(2)
|
|
280
|
+
}))
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Find largest chunks
|
|
285
|
+
*/
|
|
286
|
+
getLargestChunks(limit = 10) {
|
|
287
|
+
return Array.from(this.chunks.entries()).sort((a, b) => b[1].size - a[1].size).slice(0, limit).map(([name, data]) => ({ name, ...data }));
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Find slowest chunks
|
|
291
|
+
*/
|
|
292
|
+
getSlowestChunks(limit = 10) {
|
|
293
|
+
return Array.from(this.chunks.entries()).sort((a, b) => b[1].loadTime - a[1].loadTime).slice(0, limit).map(([name, data]) => ({ name, ...data }));
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// src/cache.js
|
|
298
|
+
var LRUCache = class {
|
|
299
|
+
constructor(options = {}) {
|
|
300
|
+
this.maxSize = options.maxSize || 100;
|
|
301
|
+
this.ttl = options.ttl || null;
|
|
302
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
303
|
+
this.accessOrder = [];
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Get value from cache
|
|
307
|
+
*/
|
|
308
|
+
get(key) {
|
|
309
|
+
if (!this.cache.has(key)) {
|
|
310
|
+
return void 0;
|
|
311
|
+
}
|
|
312
|
+
const entry = this.cache.get(key);
|
|
313
|
+
if (this.ttl && Date.now() - entry.timestamp > this.ttl) {
|
|
314
|
+
this.delete(key);
|
|
315
|
+
return void 0;
|
|
316
|
+
}
|
|
317
|
+
this.updateAccessOrder(key);
|
|
318
|
+
return entry.value;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Set value in cache
|
|
322
|
+
*/
|
|
323
|
+
set(key, value) {
|
|
324
|
+
if (this.cache.has(key)) {
|
|
325
|
+
this.delete(key);
|
|
326
|
+
}
|
|
327
|
+
if (this.cache.size >= this.maxSize) {
|
|
328
|
+
this.evict();
|
|
329
|
+
}
|
|
330
|
+
this.cache.set(key, {
|
|
331
|
+
value,
|
|
332
|
+
timestamp: Date.now()
|
|
333
|
+
});
|
|
334
|
+
this.accessOrder.push(key);
|
|
335
|
+
return this;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Check if key exists
|
|
339
|
+
*/
|
|
340
|
+
has(key) {
|
|
341
|
+
if (!this.cache.has(key)) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
const entry = this.cache.get(key);
|
|
345
|
+
if (this.ttl && Date.now() - entry.timestamp > this.ttl) {
|
|
346
|
+
this.delete(key);
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Delete key
|
|
353
|
+
*/
|
|
354
|
+
delete(key) {
|
|
355
|
+
this.cache.delete(key);
|
|
356
|
+
const index = this.accessOrder.indexOf(key);
|
|
357
|
+
if (index > -1) {
|
|
358
|
+
this.accessOrder.splice(index, 1);
|
|
359
|
+
}
|
|
360
|
+
return this;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Clear cache
|
|
364
|
+
*/
|
|
365
|
+
clear() {
|
|
366
|
+
this.cache.clear();
|
|
367
|
+
this.accessOrder = [];
|
|
368
|
+
return this;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Get cache size
|
|
372
|
+
*/
|
|
373
|
+
size() {
|
|
374
|
+
return this.cache.size;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Update access order
|
|
378
|
+
*/
|
|
379
|
+
updateAccessOrder(key) {
|
|
380
|
+
const index = this.accessOrder.indexOf(key);
|
|
381
|
+
if (index > -1) {
|
|
382
|
+
this.accessOrder.splice(index, 1);
|
|
383
|
+
}
|
|
384
|
+
this.accessOrder.push(key);
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Evict least recently used
|
|
388
|
+
*/
|
|
389
|
+
evict() {
|
|
390
|
+
if (this.accessOrder.length > 0) {
|
|
391
|
+
const oldest = this.accessOrder.shift();
|
|
392
|
+
this.cache.delete(oldest);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Get all keys
|
|
397
|
+
*/
|
|
398
|
+
keys() {
|
|
399
|
+
return Array.from(this.cache.keys());
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Get all values
|
|
403
|
+
*/
|
|
404
|
+
values() {
|
|
405
|
+
return Array.from(this.cache.values()).map((entry) => entry.value);
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Get statistics
|
|
409
|
+
*/
|
|
410
|
+
getStats() {
|
|
411
|
+
return {
|
|
412
|
+
size: this.cache.size,
|
|
413
|
+
maxSize: this.maxSize,
|
|
414
|
+
utilizationPercent: (this.cache.size / this.maxSize * 100).toFixed(2),
|
|
415
|
+
oldestKey: this.accessOrder[0],
|
|
416
|
+
newestKey: this.accessOrder[this.accessOrder.length - 1]
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
var MemoryCache = class {
|
|
421
|
+
constructor(options = {}) {
|
|
422
|
+
this.options = {
|
|
423
|
+
strategy: "lru",
|
|
424
|
+
// lru, lfu, fifo
|
|
425
|
+
maxSize: 100,
|
|
426
|
+
ttl: null,
|
|
427
|
+
...options
|
|
428
|
+
};
|
|
429
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
430
|
+
this.metadata = /* @__PURE__ */ new Map();
|
|
431
|
+
this.hits = 0;
|
|
432
|
+
this.misses = 0;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Get from cache
|
|
436
|
+
*/
|
|
437
|
+
get(key) {
|
|
438
|
+
if (!this.cache.has(key)) {
|
|
439
|
+
this.misses++;
|
|
440
|
+
return void 0;
|
|
441
|
+
}
|
|
442
|
+
const entry = this.cache.get(key);
|
|
443
|
+
if (entry.ttl && Date.now() > entry.expiresAt) {
|
|
444
|
+
this.delete(key);
|
|
445
|
+
this.misses++;
|
|
446
|
+
return void 0;
|
|
447
|
+
}
|
|
448
|
+
this.updateMetadata(key);
|
|
449
|
+
this.hits++;
|
|
450
|
+
return entry.value;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Set in cache
|
|
454
|
+
*/
|
|
455
|
+
set(key, value, options = {}) {
|
|
456
|
+
if (this.cache.size >= this.options.maxSize && !this.cache.has(key)) {
|
|
457
|
+
this.evict();
|
|
458
|
+
}
|
|
459
|
+
const ttl = options.ttl || this.options.ttl;
|
|
460
|
+
this.cache.set(key, {
|
|
461
|
+
value,
|
|
462
|
+
ttl,
|
|
463
|
+
expiresAt: ttl ? Date.now() + ttl : null,
|
|
464
|
+
createdAt: Date.now()
|
|
465
|
+
});
|
|
466
|
+
this.metadata.set(key, {
|
|
467
|
+
accessCount: 0,
|
|
468
|
+
lastAccess: Date.now()
|
|
469
|
+
});
|
|
470
|
+
return this;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Update metadata based on strategy
|
|
474
|
+
*/
|
|
475
|
+
updateMetadata(key) {
|
|
476
|
+
const meta = this.metadata.get(key);
|
|
477
|
+
if (meta) {
|
|
478
|
+
meta.accessCount++;
|
|
479
|
+
meta.lastAccess = Date.now();
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Evict based on strategy
|
|
484
|
+
*/
|
|
485
|
+
evict() {
|
|
486
|
+
let keyToEvict;
|
|
487
|
+
switch (this.options.strategy) {
|
|
488
|
+
case "lru":
|
|
489
|
+
keyToEvict = this.findLRU();
|
|
490
|
+
break;
|
|
491
|
+
case "lfu":
|
|
492
|
+
keyToEvict = this.findLFU();
|
|
493
|
+
break;
|
|
494
|
+
case "fifo":
|
|
495
|
+
keyToEvict = this.findFIFO();
|
|
496
|
+
break;
|
|
497
|
+
default:
|
|
498
|
+
keyToEvict = this.cache.keys().next().value;
|
|
499
|
+
}
|
|
500
|
+
if (keyToEvict) {
|
|
501
|
+
this.delete(keyToEvict);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Find least recently used key
|
|
506
|
+
*/
|
|
507
|
+
findLRU() {
|
|
508
|
+
let oldest = null;
|
|
509
|
+
let oldestTime = Infinity;
|
|
510
|
+
for (const [key, meta] of this.metadata.entries()) {
|
|
511
|
+
if (meta.lastAccess < oldestTime) {
|
|
512
|
+
oldestTime = meta.lastAccess;
|
|
513
|
+
oldest = key;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return oldest;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Find least frequently used key
|
|
520
|
+
*/
|
|
521
|
+
findLFU() {
|
|
522
|
+
let leastUsed = null;
|
|
523
|
+
let minCount = Infinity;
|
|
524
|
+
for (const [key, meta] of this.metadata.entries()) {
|
|
525
|
+
if (meta.accessCount < minCount) {
|
|
526
|
+
minCount = meta.accessCount;
|
|
527
|
+
leastUsed = key;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return leastUsed;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Find first in (oldest)
|
|
534
|
+
*/
|
|
535
|
+
findFIFO() {
|
|
536
|
+
let oldest = null;
|
|
537
|
+
let oldestTime = Infinity;
|
|
538
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
539
|
+
if (entry.createdAt < oldestTime) {
|
|
540
|
+
oldestTime = entry.createdAt;
|
|
541
|
+
oldest = key;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return oldest;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Check if key exists
|
|
548
|
+
*/
|
|
549
|
+
has(key) {
|
|
550
|
+
return this.cache.has(key);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Delete key
|
|
554
|
+
*/
|
|
555
|
+
delete(key) {
|
|
556
|
+
this.cache.delete(key);
|
|
557
|
+
this.metadata.delete(key);
|
|
558
|
+
return this;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Clear cache
|
|
562
|
+
*/
|
|
563
|
+
clear() {
|
|
564
|
+
this.cache.clear();
|
|
565
|
+
this.metadata.clear();
|
|
566
|
+
this.hits = 0;
|
|
567
|
+
this.misses = 0;
|
|
568
|
+
return this;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Get cache statistics
|
|
572
|
+
*/
|
|
573
|
+
getStats() {
|
|
574
|
+
const total = this.hits + this.misses;
|
|
575
|
+
const hitRate = total > 0 ? (this.hits / total * 100).toFixed(2) : 0;
|
|
576
|
+
return {
|
|
577
|
+
size: this.cache.size,
|
|
578
|
+
maxSize: this.options.maxSize,
|
|
579
|
+
hits: this.hits,
|
|
580
|
+
misses: this.misses,
|
|
581
|
+
hitRate: `${hitRate}%`,
|
|
582
|
+
strategy: this.options.strategy
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
var MemoCache = class {
|
|
587
|
+
constructor(options = {}) {
|
|
588
|
+
this.cache = new LRUCache(options);
|
|
589
|
+
this.keyGenerator = options.keyGenerator || this.defaultKeyGenerator;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Default key generator
|
|
593
|
+
*/
|
|
594
|
+
defaultKeyGenerator(...args) {
|
|
595
|
+
return JSON.stringify(args);
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Memoize a function
|
|
599
|
+
*/
|
|
600
|
+
memoize(fn) {
|
|
601
|
+
return (...args) => {
|
|
602
|
+
const key = this.keyGenerator(...args);
|
|
603
|
+
if (this.cache.has(key)) {
|
|
604
|
+
return this.cache.get(key);
|
|
605
|
+
}
|
|
606
|
+
const result = fn(...args);
|
|
607
|
+
this.cache.set(key, result);
|
|
608
|
+
return result;
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Clear memoization cache
|
|
613
|
+
*/
|
|
614
|
+
clear() {
|
|
615
|
+
this.cache.clear();
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Get statistics
|
|
619
|
+
*/
|
|
620
|
+
getStats() {
|
|
621
|
+
return this.cache.getStats();
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
var RenderCache = class {
|
|
625
|
+
constructor(options = {}) {
|
|
626
|
+
this.cache = new MemoryCache({
|
|
627
|
+
maxSize: options.maxSize || 50,
|
|
628
|
+
ttl: options.ttl || 6e4,
|
|
629
|
+
// 1 minute default
|
|
630
|
+
strategy: "lru"
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Generate cache key for component
|
|
635
|
+
*/
|
|
636
|
+
generateKey(component, props) {
|
|
637
|
+
const componentName = component.name || "anonymous";
|
|
638
|
+
const propsKey = this.hashProps(props);
|
|
639
|
+
return `${componentName}:${propsKey}`;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Hash props for cache key
|
|
643
|
+
*/
|
|
644
|
+
hashProps(props) {
|
|
645
|
+
try {
|
|
646
|
+
return JSON.stringify(props, Object.keys(props).sort());
|
|
647
|
+
} catch {
|
|
648
|
+
return String(Date.now());
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Get cached render
|
|
653
|
+
*/
|
|
654
|
+
get(component, props) {
|
|
655
|
+
const key = this.generateKey(component, props);
|
|
656
|
+
return this.cache.get(key);
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Cache render result
|
|
660
|
+
*/
|
|
661
|
+
set(component, props, result, options = {}) {
|
|
662
|
+
const key = this.generateKey(component, props);
|
|
663
|
+
this.cache.set(key, result, options);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Clear cache
|
|
667
|
+
*/
|
|
668
|
+
clear() {
|
|
669
|
+
this.cache.clear();
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Get statistics
|
|
673
|
+
*/
|
|
674
|
+
getStats() {
|
|
675
|
+
return this.cache.getStats();
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
function createCache(type = "lru", options = {}) {
|
|
679
|
+
switch (type) {
|
|
680
|
+
case "lru":
|
|
681
|
+
return new LRUCache(options);
|
|
682
|
+
case "memory":
|
|
683
|
+
return new MemoryCache(options);
|
|
684
|
+
case "memo":
|
|
685
|
+
return new MemoCache(options);
|
|
686
|
+
case "render":
|
|
687
|
+
return new RenderCache(options);
|
|
688
|
+
default:
|
|
689
|
+
return new LRUCache(options);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function memoize(fn, options = {}) {
|
|
693
|
+
const cache = new MemoCache(options);
|
|
694
|
+
return cache.memoize(fn);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// src/lazy-loading.js
|
|
698
|
+
var LazyLoader = class {
|
|
699
|
+
constructor(options = {}) {
|
|
700
|
+
this.options = {
|
|
701
|
+
rootMargin: "50px",
|
|
702
|
+
threshold: 0.01,
|
|
703
|
+
...options
|
|
704
|
+
};
|
|
705
|
+
this.observer = null;
|
|
706
|
+
this.observed = /* @__PURE__ */ new Set();
|
|
707
|
+
this.loaded = /* @__PURE__ */ new Set();
|
|
708
|
+
this.initObserver();
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Initialize Intersection Observer
|
|
712
|
+
*/
|
|
713
|
+
initObserver() {
|
|
714
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
this.observer = new IntersectionObserver(
|
|
718
|
+
(entries) => this.handleIntersection(entries),
|
|
719
|
+
{
|
|
720
|
+
rootMargin: this.options.rootMargin,
|
|
721
|
+
threshold: this.options.threshold
|
|
722
|
+
}
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Handle intersection
|
|
727
|
+
*/
|
|
728
|
+
handleIntersection(entries) {
|
|
729
|
+
entries.forEach((entry) => {
|
|
730
|
+
if (entry.isIntersecting) {
|
|
731
|
+
this.loadElement(entry.target);
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Observe an element
|
|
737
|
+
*/
|
|
738
|
+
observe(element) {
|
|
739
|
+
if (!this.observer || this.observed.has(element)) {
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
this.observer.observe(element);
|
|
743
|
+
this.observed.add(element);
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Unobserve an element
|
|
747
|
+
*/
|
|
748
|
+
unobserve(element) {
|
|
749
|
+
if (!this.observer) {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
this.observer.unobserve(element);
|
|
753
|
+
this.observed.delete(element);
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Load an element
|
|
757
|
+
*/
|
|
758
|
+
loadElement(element) {
|
|
759
|
+
if (this.loaded.has(element)) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
if (element.tagName === "IMG") {
|
|
763
|
+
this.loadImage(element);
|
|
764
|
+
} else if (element.tagName === "SCRIPT") {
|
|
765
|
+
this.loadScript(element);
|
|
766
|
+
} else if (element.tagName === "IFRAME") {
|
|
767
|
+
this.loadIframe(element);
|
|
768
|
+
}
|
|
769
|
+
this.loaded.add(element);
|
|
770
|
+
this.unobserve(element);
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Load image
|
|
774
|
+
*/
|
|
775
|
+
loadImage(img) {
|
|
776
|
+
const src = img.dataset.src;
|
|
777
|
+
const srcset = img.dataset.srcset;
|
|
778
|
+
if (src) {
|
|
779
|
+
img.src = src;
|
|
780
|
+
}
|
|
781
|
+
if (srcset) {
|
|
782
|
+
img.srcset = srcset;
|
|
783
|
+
}
|
|
784
|
+
img.classList.add("loaded");
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Load script
|
|
788
|
+
*/
|
|
789
|
+
loadScript(script) {
|
|
790
|
+
const src = script.dataset.src;
|
|
791
|
+
if (src) {
|
|
792
|
+
script.src = src;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Load iframe
|
|
797
|
+
*/
|
|
798
|
+
loadIframe(iframe) {
|
|
799
|
+
const src = iframe.dataset.src;
|
|
800
|
+
if (src) {
|
|
801
|
+
iframe.src = src;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Disconnect observer
|
|
806
|
+
*/
|
|
807
|
+
disconnect() {
|
|
808
|
+
if (this.observer) {
|
|
809
|
+
this.observer.disconnect();
|
|
810
|
+
}
|
|
811
|
+
this.observed.clear();
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
var ImageLazyLoader = class {
|
|
815
|
+
constructor(options = {}) {
|
|
816
|
+
this.options = {
|
|
817
|
+
placeholder: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1"%3E%3C/svg%3E',
|
|
818
|
+
loadingClass: "lazy-loading",
|
|
819
|
+
loadedClass: "lazy-loaded",
|
|
820
|
+
errorClass: "lazy-error",
|
|
821
|
+
...options
|
|
822
|
+
};
|
|
823
|
+
this.loader = new LazyLoader(options);
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Create lazy image component
|
|
827
|
+
*/
|
|
828
|
+
createImage(src, options = {}) {
|
|
829
|
+
return {
|
|
830
|
+
img: {
|
|
831
|
+
src: this.options.placeholder,
|
|
832
|
+
"data-src": src,
|
|
833
|
+
"data-srcset": options.srcset,
|
|
834
|
+
alt: options.alt || "",
|
|
835
|
+
className: this.options.loadingClass,
|
|
836
|
+
loading: "lazy",
|
|
837
|
+
onload: `this.classList.add("${this.options.loadedClass}")`,
|
|
838
|
+
onerror: `this.classList.add("${this.options.errorClass}")`
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Observe images
|
|
844
|
+
*/
|
|
845
|
+
observe(selector = "img[data-src]") {
|
|
846
|
+
if (typeof document === "undefined") {
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const images = document.querySelectorAll(selector);
|
|
850
|
+
images.forEach((img) => this.loader.observe(img));
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Load all images immediately
|
|
854
|
+
*/
|
|
855
|
+
loadAll() {
|
|
856
|
+
this.observed.forEach((element) => {
|
|
857
|
+
this.loader.loadElement(element);
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
var ResourcePreloader = class {
|
|
862
|
+
constructor() {
|
|
863
|
+
this.preloaded = /* @__PURE__ */ new Set();
|
|
864
|
+
this.preloading = /* @__PURE__ */ new Map();
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Preload an image
|
|
868
|
+
*/
|
|
869
|
+
async preloadImage(src) {
|
|
870
|
+
if (this.preloaded.has(src)) {
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
if (this.preloading.has(src)) {
|
|
874
|
+
return this.preloading.get(src);
|
|
875
|
+
}
|
|
876
|
+
const promise = new Promise((resolve, reject) => {
|
|
877
|
+
const img = new Image();
|
|
878
|
+
img.onload = () => {
|
|
879
|
+
this.preloaded.add(src);
|
|
880
|
+
this.preloading.delete(src);
|
|
881
|
+
resolve(img);
|
|
882
|
+
};
|
|
883
|
+
img.onerror = () => {
|
|
884
|
+
this.preloading.delete(src);
|
|
885
|
+
reject(new Error(`Failed to preload image: ${src}`));
|
|
886
|
+
};
|
|
887
|
+
img.src = src;
|
|
888
|
+
});
|
|
889
|
+
this.preloading.set(src, promise);
|
|
890
|
+
return promise;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Preload multiple images
|
|
894
|
+
*/
|
|
895
|
+
async preloadImages(sources) {
|
|
896
|
+
return Promise.all(sources.map((src) => this.preloadImage(src)));
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Preload a script
|
|
900
|
+
*/
|
|
901
|
+
async preloadScript(src) {
|
|
902
|
+
if (this.preloaded.has(src)) {
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
if (this.preloading.has(src)) {
|
|
906
|
+
return this.preloading.get(src);
|
|
907
|
+
}
|
|
908
|
+
const promise = new Promise((resolve, reject) => {
|
|
909
|
+
const link = document.createElement("link");
|
|
910
|
+
link.rel = "preload";
|
|
911
|
+
link.as = "script";
|
|
912
|
+
link.href = src;
|
|
913
|
+
link.onload = () => {
|
|
914
|
+
this.preloaded.add(src);
|
|
915
|
+
this.preloading.delete(src);
|
|
916
|
+
resolve();
|
|
917
|
+
};
|
|
918
|
+
link.onerror = () => {
|
|
919
|
+
this.preloading.delete(src);
|
|
920
|
+
reject(new Error(`Failed to preload script: ${src}`));
|
|
921
|
+
};
|
|
922
|
+
document.head.appendChild(link);
|
|
923
|
+
});
|
|
924
|
+
this.preloading.set(src, promise);
|
|
925
|
+
return promise;
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Prefetch a resource
|
|
929
|
+
*/
|
|
930
|
+
prefetch(href, options = {}) {
|
|
931
|
+
if (typeof document === "undefined") {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
const link = document.createElement("link");
|
|
935
|
+
link.rel = "prefetch";
|
|
936
|
+
link.href = href;
|
|
937
|
+
if (options.as) {
|
|
938
|
+
link.as = options.as;
|
|
939
|
+
}
|
|
940
|
+
document.head.appendChild(link);
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Check if resource is preloaded
|
|
944
|
+
*/
|
|
945
|
+
isPreloaded(src) {
|
|
946
|
+
return this.preloaded.has(src);
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Clear preload cache
|
|
950
|
+
*/
|
|
951
|
+
clear() {
|
|
952
|
+
this.preloaded.clear();
|
|
953
|
+
this.preloading.clear();
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
var ProgressiveImageLoader = class {
|
|
957
|
+
/**
|
|
958
|
+
* Create progressive image component
|
|
959
|
+
*/
|
|
960
|
+
createImage(lowResSrc, highResSrc, options = {}) {
|
|
961
|
+
return {
|
|
962
|
+
div: {
|
|
963
|
+
className: "progressive-image",
|
|
964
|
+
style: options.style || {},
|
|
965
|
+
children: [
|
|
966
|
+
{
|
|
967
|
+
img: {
|
|
968
|
+
src: lowResSrc,
|
|
969
|
+
className: "progressive-image-low",
|
|
970
|
+
alt: options.alt || "",
|
|
971
|
+
style: "filter: blur(10px); transition: opacity 0.3s;"
|
|
972
|
+
}
|
|
973
|
+
},
|
|
974
|
+
{
|
|
975
|
+
img: {
|
|
976
|
+
"data-src": highResSrc,
|
|
977
|
+
className: "progressive-image-high",
|
|
978
|
+
alt: options.alt || "",
|
|
979
|
+
style: "opacity: 0; transition: opacity 0.3s;",
|
|
980
|
+
onload: "this.style.opacity = 1; this.previousElementSibling.style.opacity = 0;"
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
]
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
function createLazyLoader(options = {}) {
|
|
989
|
+
return new LazyLoader(options);
|
|
990
|
+
}
|
|
991
|
+
function createImageLazyLoader(options = {}) {
|
|
992
|
+
return new ImageLazyLoader(options);
|
|
993
|
+
}
|
|
994
|
+
function createPreloader() {
|
|
995
|
+
return new ResourcePreloader();
|
|
996
|
+
}
|
|
997
|
+
function lazyImage(src, options = {}) {
|
|
998
|
+
const loader = new ImageLazyLoader();
|
|
999
|
+
return loader.createImage(src, options);
|
|
1000
|
+
}
|
|
1001
|
+
function progressiveImage(lowRes, highRes, options = {}) {
|
|
1002
|
+
const loader = new ProgressiveImageLoader();
|
|
1003
|
+
return loader.createImage(lowRes, highRes, options);
|
|
1004
|
+
}
|
|
1005
|
+
export {
|
|
1006
|
+
BundleAnalyzer,
|
|
1007
|
+
CodeSplitter,
|
|
1008
|
+
ImageLazyLoader,
|
|
1009
|
+
LRUCache,
|
|
1010
|
+
LazyLoader,
|
|
1011
|
+
MemoCache,
|
|
1012
|
+
MemoryCache,
|
|
1013
|
+
ProgressiveImageLoader,
|
|
1014
|
+
RenderCache,
|
|
1015
|
+
ResourcePreloader,
|
|
1016
|
+
createCache,
|
|
1017
|
+
createCodeSplitter,
|
|
1018
|
+
createImageLazyLoader,
|
|
1019
|
+
createLazyLoader,
|
|
1020
|
+
createPreloader,
|
|
1021
|
+
createRouteSplitter,
|
|
1022
|
+
lazy,
|
|
1023
|
+
lazyImage,
|
|
1024
|
+
memoize,
|
|
1025
|
+
progressiveImage,
|
|
1026
|
+
splitComponent
|
|
1027
|
+
};
|
|
1028
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@coherent.js/performance",
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
4
|
+
"description": "Performance optimization utilities for Coherent.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./code-splitting": "./dist/code-splitting.js",
|
|
10
|
+
"./cache": "./dist/cache.js",
|
|
11
|
+
"./lazy-loading": "./dist/lazy-loading.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"coherent",
|
|
15
|
+
"performance",
|
|
16
|
+
"code-splitting",
|
|
17
|
+
"caching",
|
|
18
|
+
"lazy-loading"
|
|
19
|
+
],
|
|
20
|
+
"author": "Coherent.js Team",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"@coherent.js/core": "1.0.0-beta.2"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/Tomdrouv1/coherent.js.git"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"types": "./types/index.d.ts",
|
|
33
|
+
"files": [
|
|
34
|
+
"LICENSE",
|
|
35
|
+
"README.md",
|
|
36
|
+
"types/"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "node build.mjs",
|
|
40
|
+
"clean": "rm -rf dist"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coherent.js Performance TypeScript Definitions
|
|
3
|
+
* @module @coherent.js/performance
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ===== Code Splitting Types =====
|
|
7
|
+
|
|
8
|
+
export interface SplitOptions {
|
|
9
|
+
loading?: any;
|
|
10
|
+
error?: any;
|
|
11
|
+
delay?: number;
|
|
12
|
+
timeout?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type LazyComponent = () => Promise<any>;
|
|
16
|
+
|
|
17
|
+
export class CodeSplitter {
|
|
18
|
+
constructor();
|
|
19
|
+
lazy(loader: LazyComponent, options?: SplitOptions): LazyComponent;
|
|
20
|
+
split(component: any, chunkName?: string): LazyComponent;
|
|
21
|
+
preload(loader: LazyComponent): Promise<any>;
|
|
22
|
+
prefetch(loader: LazyComponent): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createCodeSplitter(): CodeSplitter;
|
|
26
|
+
export function lazy(loader: LazyComponent, options?: SplitOptions): LazyComponent;
|
|
27
|
+
export function splitComponent(component: any, chunkName?: string): LazyComponent;
|
|
28
|
+
|
|
29
|
+
export interface RouteConfig {
|
|
30
|
+
path: string;
|
|
31
|
+
component: LazyComponent;
|
|
32
|
+
preload?: boolean;
|
|
33
|
+
prefetch?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createRouteSplitter(routes: RouteConfig[]): {
|
|
37
|
+
getRoute(path: string): RouteConfig | undefined;
|
|
38
|
+
preloadRoute(path: string): Promise<void>;
|
|
39
|
+
prefetchRoute(path: string): void;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// ===== Cache Types =====
|
|
43
|
+
|
|
44
|
+
export interface CacheEntry<T = any> {
|
|
45
|
+
value: T;
|
|
46
|
+
timestamp: number;
|
|
47
|
+
hits: number;
|
|
48
|
+
size?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CacheOptions {
|
|
52
|
+
maxSize?: number;
|
|
53
|
+
maxAge?: number;
|
|
54
|
+
onEvict?: (key: string, value: any) => void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class LRUCache<K = string, V = any> {
|
|
58
|
+
constructor(options?: CacheOptions);
|
|
59
|
+
get(key: K): V | undefined;
|
|
60
|
+
set(key: K, value: V): void;
|
|
61
|
+
has(key: K): boolean;
|
|
62
|
+
delete(key: K): boolean;
|
|
63
|
+
clear(): void;
|
|
64
|
+
size(): number;
|
|
65
|
+
keys(): K[];
|
|
66
|
+
values(): V[];
|
|
67
|
+
entries(): Array<[K, V]>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class MemoryCache<K = string, V = any> {
|
|
71
|
+
constructor(options?: CacheOptions);
|
|
72
|
+
get(key: K): V | undefined;
|
|
73
|
+
set(key: K, value: V, ttl?: number): void;
|
|
74
|
+
has(key: K): boolean;
|
|
75
|
+
delete(key: K): boolean;
|
|
76
|
+
clear(): void;
|
|
77
|
+
size(): number;
|
|
78
|
+
cleanup(): void;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface MemoOptions {
|
|
82
|
+
maxSize?: number;
|
|
83
|
+
keyGenerator?: (...args: any[]) => string;
|
|
84
|
+
ttl?: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export class MemoCache {
|
|
88
|
+
constructor(options?: MemoOptions);
|
|
89
|
+
memoize<T extends (...args: any[]) => any>(fn: T): T;
|
|
90
|
+
clear(fn?: Function): void;
|
|
91
|
+
has(fn: Function, args: any[]): boolean;
|
|
92
|
+
delete(fn: Function, args?: any[]): boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface RenderCacheOptions extends CacheOptions {
|
|
96
|
+
keyGenerator?: (component: any, props: any) => string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class RenderCache {
|
|
100
|
+
constructor(options?: RenderCacheOptions);
|
|
101
|
+
get(component: any, props: any): string | undefined;
|
|
102
|
+
set(component: any, props: any, html: string): void;
|
|
103
|
+
clear(component?: any): void;
|
|
104
|
+
size(): number;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function createCache<K = string, V = any>(type: 'lru' | 'memory' | 'memo' | 'render', options?: CacheOptions): LRUCache<K, V> | MemoryCache<K, V> | MemoCache | RenderCache;
|
|
108
|
+
export function memoize<T extends (...args: any[]) => any>(fn: T, options?: MemoOptions): T;
|
|
109
|
+
|
|
110
|
+
// ===== Lazy Loading Types =====
|
|
111
|
+
|
|
112
|
+
export interface LazyLoadOptions {
|
|
113
|
+
threshold?: number;
|
|
114
|
+
rootMargin?: string;
|
|
115
|
+
root?: Element | null;
|
|
116
|
+
onLoad?: () => void;
|
|
117
|
+
onError?: (error: Error) => void;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export class LazyLoader {
|
|
121
|
+
constructor(options?: LazyLoadOptions);
|
|
122
|
+
observe(element: Element, loader: () => Promise<any>): void;
|
|
123
|
+
unobserve(element: Element): void;
|
|
124
|
+
disconnect(): void;
|
|
125
|
+
loadAll(): Promise<void[]>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface ImageLazyLoadOptions extends LazyLoadOptions {
|
|
129
|
+
placeholder?: string;
|
|
130
|
+
blurDataURL?: string;
|
|
131
|
+
fadeIn?: boolean;
|
|
132
|
+
fadeInDuration?: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export class ImageLazyLoader extends LazyLoader {
|
|
136
|
+
constructor(options?: ImageLazyLoadOptions);
|
|
137
|
+
lazyImage(element: HTMLImageElement, src: string, options?: ImageLazyLoadOptions): void;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface PreloadOptions {
|
|
141
|
+
as?: 'script' | 'style' | 'image' | 'font' | 'fetch';
|
|
142
|
+
crossOrigin?: 'anonymous' | 'use-credentials';
|
|
143
|
+
type?: string;
|
|
144
|
+
media?: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export class ResourcePreloader {
|
|
148
|
+
constructor();
|
|
149
|
+
preload(url: string, options?: PreloadOptions): void;
|
|
150
|
+
prefetch(url: string): void;
|
|
151
|
+
preconnect(url: string, crossOrigin?: boolean): void;
|
|
152
|
+
dnsPrefetch(url: string): void;
|
|
153
|
+
hasPreloaded(url: string): boolean;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function createLazyLoader(options?: LazyLoadOptions): LazyLoader;
|
|
157
|
+
export function lazyImage(element: HTMLImageElement | string, src: string, options?: ImageLazyLoadOptions): void;
|
|
158
|
+
|
|
159
|
+
export interface ProgressiveImageOptions {
|
|
160
|
+
lowQualitySrc: string;
|
|
161
|
+
highQualitySrc: string;
|
|
162
|
+
placeholder?: string;
|
|
163
|
+
fadeInDuration?: number;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function progressiveImage(element: HTMLImageElement | string, options: ProgressiveImageOptions): Promise<void>;
|