slicejs-cli 2.8.5 → 2.9.0
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/README.md +347 -315
- package/client.js +526 -539
- package/commands/Print.js +167 -167
- package/commands/Validations.js +103 -103
- package/commands/build/build.js +40 -0
- package/commands/buildProduction/buildProduction.js +45 -10
- package/commands/bundle/bundle.js +235 -231
- package/commands/createComponent/VisualComponentTemplate.js +55 -55
- package/commands/createComponent/createComponent.js +126 -126
- package/commands/deleteComponent/deleteComponent.js +77 -77
- package/commands/doctor/doctor.js +369 -369
- package/commands/getComponent/getComponent.js +747 -747
- package/commands/init/init.js +238 -238
- package/commands/listComponents/listComponents.js +175 -175
- package/commands/startServer/startServer.js +260 -270
- package/commands/startServer/watchServer.js +79 -79
- package/commands/utils/PathHelper.js +68 -68
- package/commands/utils/VersionChecker.js +167 -167
- package/commands/utils/bundling/BundleGenerator.js +1287 -783
- package/commands/utils/bundling/DependencyAnalyzer.js +859 -679
- package/commands/utils/updateManager.js +437 -384
- package/package.json +46 -46
- package/post.js +25 -25
- package/refactor.md +271 -271
- package/tests/bundle-generator.test.js +38 -0
- package/tests/dependency-analyzer.test.js +24 -0
|
@@ -1,783 +1,1287 @@
|
|
|
1
|
-
// cli/utils/bundling/BundleGenerator.js
|
|
2
|
-
import fs from 'fs-extra';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import crypto from 'crypto';
|
|
5
|
-
import { parse } from '@babel/parser';
|
|
6
|
-
import traverse from '@babel/traverse';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
this.
|
|
13
|
-
this.
|
|
14
|
-
this.
|
|
15
|
-
this.
|
|
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
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
config
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
const totalSize =
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
for (const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
allComponents
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
'
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
return
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
: (
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
size:
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
.
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
//
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
/**
|
|
757
|
-
*
|
|
758
|
-
*/
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1
|
+
// cli/utils/bundling/BundleGenerator.js
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import crypto from 'crypto';
|
|
5
|
+
import { parse } from '@babel/parser';
|
|
6
|
+
import traverse from '@babel/traverse';
|
|
7
|
+
import { minify as terserMinify } from 'terser';
|
|
8
|
+
import { getSrcPath, getComponentsJsPath, getDistPath } from '../PathHelper.js';
|
|
9
|
+
|
|
10
|
+
export default class BundleGenerator {
|
|
11
|
+
constructor(moduleUrl, analysisData, options = {}) {
|
|
12
|
+
this.moduleUrl = moduleUrl;
|
|
13
|
+
this.analysisData = analysisData;
|
|
14
|
+
this.srcPath = getSrcPath(moduleUrl);
|
|
15
|
+
this.distPath = getDistPath(moduleUrl);
|
|
16
|
+
this.output = options.output || 'src';
|
|
17
|
+
this.bundlesPath = this.output === 'dist'
|
|
18
|
+
? path.join(this.distPath, 'bundles')
|
|
19
|
+
: path.join(this.srcPath, 'bundles');
|
|
20
|
+
this.componentsPath = path.dirname(getComponentsJsPath(moduleUrl));
|
|
21
|
+
this.options = {
|
|
22
|
+
minify: !!options.minify,
|
|
23
|
+
obfuscate: !!options.obfuscate
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Configuration
|
|
27
|
+
this.config = {
|
|
28
|
+
maxCriticalSize: 50 * 1024, // 50KB
|
|
29
|
+
maxCriticalComponents: 15,
|
|
30
|
+
minSharedUsage: 3, // Minimum routes to be considered "shared"
|
|
31
|
+
strategy: 'hybrid' // 'global', 'hybrid', 'per-route'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
this.bundles = {
|
|
35
|
+
critical: {
|
|
36
|
+
components: [],
|
|
37
|
+
size: 0,
|
|
38
|
+
file: 'slice-bundle.critical.js'
|
|
39
|
+
},
|
|
40
|
+
routes: {}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Computes deterministic integrity hash for bundle metadata.
|
|
46
|
+
* @param {Array} components
|
|
47
|
+
* @param {string} type
|
|
48
|
+
* @param {string|null} routePath
|
|
49
|
+
* @param {string} bundleKey
|
|
50
|
+
* @param {string} fileName
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
computeBundleIntegrity(components, type, routePath, bundleKey, fileName) {
|
|
54
|
+
const metadata = {
|
|
55
|
+
version: '2.0.0',
|
|
56
|
+
type,
|
|
57
|
+
route: routePath,
|
|
58
|
+
bundleKey,
|
|
59
|
+
file: fileName,
|
|
60
|
+
generated: 'static',
|
|
61
|
+
totalSize: components.reduce((sum, c) => sum + c.size, 0),
|
|
62
|
+
componentCount: components.length,
|
|
63
|
+
strategy: this.config.strategy
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const payload = {
|
|
67
|
+
metadata,
|
|
68
|
+
components: components.reduce((acc, comp) => {
|
|
69
|
+
acc[comp.name] = {
|
|
70
|
+
name: comp.name,
|
|
71
|
+
category: comp.category,
|
|
72
|
+
categoryType: comp.categoryType,
|
|
73
|
+
componentDependencies: Array.from(comp.dependencies)
|
|
74
|
+
};
|
|
75
|
+
return acc;
|
|
76
|
+
}, {})
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return `sha256:${crypto.createHash('sha256')
|
|
80
|
+
.update(JSON.stringify(payload))
|
|
81
|
+
.digest('hex')}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Generates all bundles
|
|
86
|
+
*/
|
|
87
|
+
async generate() {
|
|
88
|
+
console.log('🔨 Generating bundles...');
|
|
89
|
+
|
|
90
|
+
// 0. Create bundles directory
|
|
91
|
+
await fs.ensureDir(this.bundlesPath);
|
|
92
|
+
if (this.output === 'dist') {
|
|
93
|
+
await fs.ensureDir(this.distPath);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 1. Determine optimal strategy
|
|
97
|
+
this.determineStrategy();
|
|
98
|
+
|
|
99
|
+
// 2. Identify critical components
|
|
100
|
+
this.identifyCriticalComponents();
|
|
101
|
+
|
|
102
|
+
// 3. Assign components to routes
|
|
103
|
+
this.assignRouteComponents();
|
|
104
|
+
|
|
105
|
+
// 4. Generate bundle files
|
|
106
|
+
const files = await this.generateBundleFiles();
|
|
107
|
+
|
|
108
|
+
// 5. Generate framework bundle (structural)
|
|
109
|
+
const frameworkComponents = this.collectFrameworkComponents();
|
|
110
|
+
let frameworkBundle = null;
|
|
111
|
+
if (frameworkComponents.length > 0) {
|
|
112
|
+
frameworkBundle = await this.createFrameworkBundle(frameworkComponents);
|
|
113
|
+
files.push(frameworkBundle);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 6. Generate configuration
|
|
117
|
+
const config = this.generateBundleConfig(frameworkBundle);
|
|
118
|
+
|
|
119
|
+
console.log('✅ Bundles generated successfully');
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
bundles: this.bundles,
|
|
123
|
+
config,
|
|
124
|
+
files
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Determines the optimal bundling strategy
|
|
130
|
+
*/
|
|
131
|
+
determineStrategy() {
|
|
132
|
+
const { metrics } = this.analysisData;
|
|
133
|
+
const { totalComponents, sharedPercentage } = metrics;
|
|
134
|
+
|
|
135
|
+
// Strategy based on size and usage pattern
|
|
136
|
+
if (totalComponents < 20 || sharedPercentage > 60) {
|
|
137
|
+
this.config.strategy = 'global';
|
|
138
|
+
console.log('📦 Strategy: Global Bundle (small project or highly shared)');
|
|
139
|
+
} else if (totalComponents < 100) {
|
|
140
|
+
this.config.strategy = 'hybrid';
|
|
141
|
+
console.log('📦 Strategy: Hybrid (critical + grouped routes)');
|
|
142
|
+
} else {
|
|
143
|
+
this.config.strategy = 'per-route';
|
|
144
|
+
console.log('📦 Strategy: Per Route (large project)');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Identifies critical components for the initial bundle
|
|
150
|
+
*/
|
|
151
|
+
identifyCriticalComponents() {
|
|
152
|
+
const { components } = this.analysisData;
|
|
153
|
+
|
|
154
|
+
// Filter critical candidates
|
|
155
|
+
const candidates = components
|
|
156
|
+
.filter(comp => {
|
|
157
|
+
// Shared components (used in 3+ routes)
|
|
158
|
+
const isShared = comp.routes.size >= this.config.minSharedUsage;
|
|
159
|
+
|
|
160
|
+
// Structural components (Navbar, Footer, etc.)
|
|
161
|
+
const isStructural = comp.categoryType === 'Structural' ||
|
|
162
|
+
['Navbar', 'Footer', 'Layout'].includes(comp.name);
|
|
163
|
+
|
|
164
|
+
// Small and highly used components (only if used in 3+ routes)
|
|
165
|
+
const isSmallAndUseful = comp.size < 2000 && comp.routes.size >= 3;
|
|
166
|
+
|
|
167
|
+
return isShared || isStructural || isSmallAndUseful;
|
|
168
|
+
})
|
|
169
|
+
.sort((a, b) => {
|
|
170
|
+
// Prioritize by: (usage * 10) - size
|
|
171
|
+
const priorityA = (a.routes.size * 10) - (a.size / 1000);
|
|
172
|
+
const priorityB = (b.routes.size * 10) - (b.size / 1000);
|
|
173
|
+
return priorityB - priorityA;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const loadingComponent = components.find((comp) => comp.name === 'Loading');
|
|
177
|
+
if (loadingComponent && !candidates.includes(loadingComponent)) {
|
|
178
|
+
candidates.unshift(loadingComponent);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Fill critical bundle up to limit
|
|
182
|
+
for (const comp of candidates) {
|
|
183
|
+
const dependencies = this.getComponentDependencies(comp);
|
|
184
|
+
const totalSize = comp.size + dependencies.reduce((sum, dep) => sum + dep.size, 0);
|
|
185
|
+
const totalCount = 1 + dependencies.length;
|
|
186
|
+
|
|
187
|
+
const wouldExceedSize = this.bundles.critical.size + totalSize > this.config.maxCriticalSize;
|
|
188
|
+
const wouldExceedCount = this.bundles.critical.components.length + totalCount > this.config.maxCriticalComponents;
|
|
189
|
+
|
|
190
|
+
if ((wouldExceedSize || wouldExceedCount) && comp.name !== 'Loading') continue;
|
|
191
|
+
|
|
192
|
+
// Add component and its dependencies
|
|
193
|
+
if (!this.bundles.critical.components.find(c => c.name === comp.name)) {
|
|
194
|
+
this.bundles.critical.components.push(comp);
|
|
195
|
+
this.bundles.critical.size += comp.size;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
for (const dep of dependencies) {
|
|
199
|
+
if (!this.bundles.critical.components.find(c => c.name === dep.name)) {
|
|
200
|
+
this.bundles.critical.components.push(dep);
|
|
201
|
+
this.bundles.critical.size += dep.size;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
console.log(`✓ Critical bundle: ${this.bundles.critical.components.length} components, ${(this.bundles.critical.size / 1024).toFixed(1)} KB`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Assigns remaining components to route bundles
|
|
211
|
+
*/
|
|
212
|
+
assignRouteComponents() {
|
|
213
|
+
const criticalNames = new Set(this.bundles.critical.components.map(c => c.name));
|
|
214
|
+
|
|
215
|
+
if (this.config.strategy === 'hybrid') {
|
|
216
|
+
this.assignHybridBundles(criticalNames);
|
|
217
|
+
} else {
|
|
218
|
+
this.assignPerRouteBundles(criticalNames);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Assigns components to per-route bundles
|
|
224
|
+
*/
|
|
225
|
+
assignPerRouteBundles(criticalNames) {
|
|
226
|
+
for (const route of this.analysisData.routes) {
|
|
227
|
+
const routePath = route.path;
|
|
228
|
+
// Get all route dependencies
|
|
229
|
+
const routeComponents = this.getRouteComponents(route.component);
|
|
230
|
+
|
|
231
|
+
// Include dependencies for all route components
|
|
232
|
+
const allComponents = new Set();
|
|
233
|
+
for (const comp of routeComponents) {
|
|
234
|
+
allComponents.add(comp);
|
|
235
|
+
const dependencies = this.getComponentDependencies(comp);
|
|
236
|
+
for (const dep of dependencies) {
|
|
237
|
+
allComponents.add(dep);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Filter those already in critical
|
|
242
|
+
const uniqueComponents = Array.from(allComponents).filter(comp =>
|
|
243
|
+
!criticalNames.has(comp.name)
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (uniqueComponents.length === 0) continue;
|
|
247
|
+
|
|
248
|
+
const routeKey = this.routeToFileName(routePath);
|
|
249
|
+
const totalSize = uniqueComponents.reduce((sum, c) => sum + c.size, 0);
|
|
250
|
+
|
|
251
|
+
this.bundles.routes[routeKey] = {
|
|
252
|
+
path: routePath,
|
|
253
|
+
components: uniqueComponents,
|
|
254
|
+
size: totalSize,
|
|
255
|
+
file: `slice-bundle.${routeKey}.js`
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
console.log(`✓ Bundle ${routeKey}: ${uniqueComponents.length} components, ${(totalSize / 1024).toFixed(1)} KB`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Gets all component dependencies transitively
|
|
264
|
+
*/
|
|
265
|
+
getComponentDependencies(component, visited = new Set()) {
|
|
266
|
+
if (visited.has(component.name)) return [];
|
|
267
|
+
visited.add(component.name);
|
|
268
|
+
|
|
269
|
+
const dependencies = [];
|
|
270
|
+
|
|
271
|
+
// Add direct dependencies
|
|
272
|
+
for (const depName of component.dependencies) {
|
|
273
|
+
const depComp = this.analysisData.components.find(c => c.name === depName);
|
|
274
|
+
if (depComp && !visited.has(depName)) {
|
|
275
|
+
dependencies.push(depComp);
|
|
276
|
+
// Add transitive dependencies
|
|
277
|
+
dependencies.push(...this.getComponentDependencies(depComp, visited));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return dependencies;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Assigns components to hybrid bundles (grouped by category)
|
|
286
|
+
*/
|
|
287
|
+
assignHybridBundles(criticalNames) {
|
|
288
|
+
const routeGroups = new Map();
|
|
289
|
+
|
|
290
|
+
// First, handle MultiRoute groups
|
|
291
|
+
if (this.analysisData.routeGroups) {
|
|
292
|
+
for (const [groupKey, groupData] of this.analysisData.routeGroups) {
|
|
293
|
+
if (groupData.type === 'multiroute') {
|
|
294
|
+
// Create a bundle for this MultiRoute group
|
|
295
|
+
const allComponents = new Set();
|
|
296
|
+
|
|
297
|
+
// Add the main component (MultiRoute handler)
|
|
298
|
+
const mainComponent = this.analysisData.components.find(c => c.name === groupData.component);
|
|
299
|
+
if (mainComponent) {
|
|
300
|
+
allComponents.add(mainComponent);
|
|
301
|
+
|
|
302
|
+
// Add all components used by this MultiRoute
|
|
303
|
+
const routeComponents = this.getRouteComponents(mainComponent.name);
|
|
304
|
+
for (const comp of routeComponents) {
|
|
305
|
+
allComponents.add(comp);
|
|
306
|
+
// Add transitive dependencies
|
|
307
|
+
const dependencies = this.getComponentDependencies(comp);
|
|
308
|
+
for (const dep of dependencies) {
|
|
309
|
+
allComponents.add(dep);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Filter those already in critical
|
|
315
|
+
const uniqueComponents = Array.from(allComponents).filter(comp =>
|
|
316
|
+
!criticalNames.has(comp.name)
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
if (uniqueComponents.length > 0) {
|
|
320
|
+
const totalSize = uniqueComponents.reduce((sum, c) => sum + c.size, 0);
|
|
321
|
+
|
|
322
|
+
this.bundles.routes[groupKey] = {
|
|
323
|
+
paths: groupData.routes,
|
|
324
|
+
components: uniqueComponents,
|
|
325
|
+
size: totalSize,
|
|
326
|
+
file: `slice-bundle.${this.routeToFileName(groupKey)}.js`
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
console.log(`✓ Bundle ${groupKey}: ${uniqueComponents.length} components, ${(totalSize / 1024).toFixed(1)} KB (${groupData.routes.length} routes)`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Group remaining routes by category (skip those already handled by MultiRoute)
|
|
336
|
+
for (const route of this.analysisData.routes) {
|
|
337
|
+
// Check if this route is already handled by a MultiRoute group
|
|
338
|
+
const isHandledByMultiRoute = this.analysisData.routeGroups &&
|
|
339
|
+
Array.from(this.analysisData.routeGroups.values()).some(group =>
|
|
340
|
+
group.type === 'multiroute' && group.routes.includes(route.path)
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
if (!isHandledByMultiRoute) {
|
|
344
|
+
const category = this.categorizeRoute(route.path);
|
|
345
|
+
if (!routeGroups.has(category)) {
|
|
346
|
+
routeGroups.set(category, []);
|
|
347
|
+
}
|
|
348
|
+
routeGroups.get(category).push(route);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Create bundles for each group
|
|
353
|
+
for (const [category, routes] of routeGroups) {
|
|
354
|
+
const allComponents = new Set();
|
|
355
|
+
|
|
356
|
+
// Collect all unique components for this category (including dependencies)
|
|
357
|
+
for (const route of routes) {
|
|
358
|
+
const routeComponents = this.getRouteComponents(route.component);
|
|
359
|
+
for (const comp of routeComponents) {
|
|
360
|
+
allComponents.add(comp);
|
|
361
|
+
// Add transitive dependencies
|
|
362
|
+
const dependencies = this.getComponentDependencies(comp);
|
|
363
|
+
for (const dep of dependencies) {
|
|
364
|
+
allComponents.add(dep);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Filter those already in critical
|
|
370
|
+
const uniqueComponents = Array.from(allComponents).filter(comp =>
|
|
371
|
+
!criticalNames.has(comp.name)
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
if (uniqueComponents.length === 0) continue;
|
|
375
|
+
|
|
376
|
+
const totalSize = uniqueComponents.reduce((sum, c) => sum + c.size, 0);
|
|
377
|
+
const routePaths = routes.map(r => r.path);
|
|
378
|
+
|
|
379
|
+
this.bundles.routes[category] = {
|
|
380
|
+
paths: routePaths,
|
|
381
|
+
components: uniqueComponents,
|
|
382
|
+
size: totalSize,
|
|
383
|
+
file: `slice-bundle.${this.routeToFileName(category)}.js`
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
console.log(`✓ Bundle ${category}: ${uniqueComponents.length} components, ${(totalSize / 1024).toFixed(1)} KB (${routes.length} routes)`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Categorizes a route path for grouping, considering MultiRoute context
|
|
392
|
+
*/
|
|
393
|
+
categorizeRoute(routePath) {
|
|
394
|
+
// Check if this route belongs to a MultiRoute handler
|
|
395
|
+
if (this.analysisData.routeGroups) {
|
|
396
|
+
for (const [groupKey, groupData] of this.analysisData.routeGroups) {
|
|
397
|
+
if (groupData.type === 'multiroute' && groupData.routes.includes(routePath)) {
|
|
398
|
+
return groupKey; // Return the MultiRoute group key
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Default categorization
|
|
404
|
+
const path = routePath.toLowerCase();
|
|
405
|
+
|
|
406
|
+
if (path === '/' || path === '/home') return 'home';
|
|
407
|
+
if (path.includes('docum') || path.includes('documentation')) return 'documentation';
|
|
408
|
+
if (path.includes('component') || path.includes('visual') || path.includes('card') ||
|
|
409
|
+
path.includes('button') || path.includes('input') || path.includes('switch') ||
|
|
410
|
+
path.includes('checkbox') || path.includes('select') || path.includes('details') ||
|
|
411
|
+
path.includes('grid') || path.includes('loading') || path.includes('layout') ||
|
|
412
|
+
path.includes('navbar') || path.includes('treeview') || path.includes('multiroute')) return 'components';
|
|
413
|
+
if (path.includes('theme') || path.includes('slice') || path.includes('config')) return 'configuration';
|
|
414
|
+
if (path.includes('routing') || path.includes('guard')) return 'routing';
|
|
415
|
+
if (path.includes('service') || path.includes('command')) return 'services';
|
|
416
|
+
if (path.includes('structural') || path.includes('lifecycle') || path.includes('static') ||
|
|
417
|
+
path.includes('build')) return 'advanced';
|
|
418
|
+
if (path.includes('playground') || path.includes('creator')) return 'tools';
|
|
419
|
+
if (path.includes('about') || path.includes('404')) return 'misc';
|
|
420
|
+
|
|
421
|
+
return 'general';
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Gets all components needed for a route
|
|
426
|
+
*/
|
|
427
|
+
getRouteComponents(componentName) {
|
|
428
|
+
const result = [];
|
|
429
|
+
const visited = new Set();
|
|
430
|
+
|
|
431
|
+
const traverse = (name) => {
|
|
432
|
+
if (visited.has(name)) return;
|
|
433
|
+
visited.add(name);
|
|
434
|
+
|
|
435
|
+
const component = this.analysisData.components.find(c => c.name === name);
|
|
436
|
+
if (!component) return;
|
|
437
|
+
|
|
438
|
+
result.push(component);
|
|
439
|
+
|
|
440
|
+
// Add dependencies recursively
|
|
441
|
+
for (const dep of component.dependencies) {
|
|
442
|
+
traverse(dep);
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
traverse(componentName);
|
|
447
|
+
return result;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Generates the physical bundle files
|
|
452
|
+
*/
|
|
453
|
+
async generateBundleFiles() {
|
|
454
|
+
const files = [];
|
|
455
|
+
|
|
456
|
+
// 1. Critical bundle
|
|
457
|
+
if (this.bundles.critical.components.length > 0) {
|
|
458
|
+
const criticalFile = await this.createBundleFile(
|
|
459
|
+
this.bundles.critical.components,
|
|
460
|
+
'critical',
|
|
461
|
+
null
|
|
462
|
+
);
|
|
463
|
+
const criticalIntegrity = this.computeBundleIntegrity(
|
|
464
|
+
this.bundles.critical.components,
|
|
465
|
+
'critical',
|
|
466
|
+
null,
|
|
467
|
+
'critical',
|
|
468
|
+
criticalFile.file
|
|
469
|
+
);
|
|
470
|
+
this.bundles.critical.integrity = criticalIntegrity;
|
|
471
|
+
this.bundles.critical.hash = criticalFile.hash;
|
|
472
|
+
files.push(criticalFile);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// 2. Route bundles
|
|
476
|
+
for (const [routeKey, bundle] of Object.entries(this.bundles.routes)) {
|
|
477
|
+
const routeIdentifier = Array.isArray(bundle.path || bundle.paths)
|
|
478
|
+
? routeKey
|
|
479
|
+
: (bundle.path || bundle.paths || routeKey);
|
|
480
|
+
|
|
481
|
+
const routeFile = await this.createBundleFile(
|
|
482
|
+
bundle.components,
|
|
483
|
+
'route',
|
|
484
|
+
routeIdentifier
|
|
485
|
+
);
|
|
486
|
+
const routeIntegrity = this.computeBundleIntegrity(
|
|
487
|
+
bundle.components,
|
|
488
|
+
'route',
|
|
489
|
+
routeIdentifier,
|
|
490
|
+
this.routeToFileName(routeIdentifier),
|
|
491
|
+
routeFile.file
|
|
492
|
+
);
|
|
493
|
+
const matchingBundle = Object.values(this.bundles.routes)
|
|
494
|
+
.find((entry) => entry.file === routeFile.file);
|
|
495
|
+
if (matchingBundle) {
|
|
496
|
+
matchingBundle.hash = routeFile.hash;
|
|
497
|
+
matchingBundle.integrity = routeIntegrity;
|
|
498
|
+
}
|
|
499
|
+
files.push(routeFile);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return files;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Creates a bundle file
|
|
507
|
+
*/
|
|
508
|
+
async createBundleFile(components, type, routePath) {
|
|
509
|
+
const routeKey = routePath ? this.routeToFileName(routePath) : 'critical';
|
|
510
|
+
const fileName = `slice-bundle.${routeKey}.js`;
|
|
511
|
+
const filePath = path.join(this.bundlesPath, fileName);
|
|
512
|
+
|
|
513
|
+
const bundleContent = await this.generateBundleContent(
|
|
514
|
+
components,
|
|
515
|
+
type,
|
|
516
|
+
routePath,
|
|
517
|
+
routeKey,
|
|
518
|
+
fileName
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
const finalContent = await this.applyBundleTransforms(bundleContent, fileName);
|
|
522
|
+
|
|
523
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
524
|
+
await fs.writeFile(filePath, finalContent, 'utf-8');
|
|
525
|
+
|
|
526
|
+
const hash = crypto.createHash('sha256').update(finalContent).digest('hex');
|
|
527
|
+
|
|
528
|
+
return {
|
|
529
|
+
name: routeKey,
|
|
530
|
+
file: fileName,
|
|
531
|
+
path: filePath,
|
|
532
|
+
size: Buffer.byteLength(bundleContent, 'utf-8'),
|
|
533
|
+
hash,
|
|
534
|
+
componentCount: components.length
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async applyBundleTransforms(bundleContent, fileName) {
|
|
539
|
+
if (!this.options.minify && !this.options.obfuscate) {
|
|
540
|
+
return bundleContent;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const result = await terserMinify(bundleContent, {
|
|
544
|
+
compress: this.options.minify ? {
|
|
545
|
+
drop_console: false,
|
|
546
|
+
drop_debugger: true,
|
|
547
|
+
passes: 1
|
|
548
|
+
} : false,
|
|
549
|
+
mangle: this.options.obfuscate ? {
|
|
550
|
+
properties: false
|
|
551
|
+
} : false,
|
|
552
|
+
keep_fnames: true,
|
|
553
|
+
keep_classnames: true,
|
|
554
|
+
format: {
|
|
555
|
+
comments: false
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
if (result.error) {
|
|
560
|
+
throw new Error(`Terser failed for ${fileName}: ${result.error.message}`);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return result.code || bundleContent;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Analyzes dependencies of a JavaScript file using simple regex
|
|
569
|
+
*/
|
|
570
|
+
analyzeDependencies(jsContent, componentPath) {
|
|
571
|
+
const dependencies = [];
|
|
572
|
+
|
|
573
|
+
const resolveImportPath = (importPath) => {
|
|
574
|
+
const resolvedPath = path.resolve(componentPath, importPath);
|
|
575
|
+
let finalPath = resolvedPath;
|
|
576
|
+
const ext = path.extname(resolvedPath);
|
|
577
|
+
if (!ext) {
|
|
578
|
+
const extensions = ['.js', '.json', '.mjs'];
|
|
579
|
+
for (const extension of extensions) {
|
|
580
|
+
if (fs.existsSync(resolvedPath + extension)) {
|
|
581
|
+
finalPath = resolvedPath + extension;
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return fs.existsSync(finalPath) ? finalPath : null;
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
try {
|
|
591
|
+
const ast = parse(jsContent, {
|
|
592
|
+
sourceType: 'module',
|
|
593
|
+
plugins: ['jsx']
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
traverse.default(ast, {
|
|
597
|
+
ImportDeclaration(pathNode) {
|
|
598
|
+
const importPath = pathNode.node.source.value;
|
|
599
|
+
if (!importPath.startsWith('./') && !importPath.startsWith('../')) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const resolvedPath = resolveImportPath(importPath);
|
|
604
|
+
if (!resolvedPath) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const bindings = pathNode.node.specifiers.map(spec => {
|
|
609
|
+
if (spec.type === 'ImportDefaultSpecifier') {
|
|
610
|
+
return {
|
|
611
|
+
type: 'default',
|
|
612
|
+
importedName: 'default',
|
|
613
|
+
localName: spec.local.name
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (spec.type === 'ImportSpecifier') {
|
|
618
|
+
return {
|
|
619
|
+
type: 'named',
|
|
620
|
+
importedName: spec.imported.name,
|
|
621
|
+
localName: spec.local.name
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (spec.type === 'ImportNamespaceSpecifier') {
|
|
626
|
+
return {
|
|
627
|
+
type: 'namespace',
|
|
628
|
+
localName: spec.local.name
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return null;
|
|
633
|
+
}).filter(Boolean);
|
|
634
|
+
|
|
635
|
+
dependencies.push({
|
|
636
|
+
path: resolvedPath,
|
|
637
|
+
bindings
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
} catch (error) {
|
|
642
|
+
console.warn(`Warning: Could not analyze dependencies for ${componentPath}:`, error.message);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return dependencies;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Generates the content of a bundle
|
|
650
|
+
*/
|
|
651
|
+
async generateBundleContent(components, type, routePath, bundleKey, fileName) {
|
|
652
|
+
const componentsData = {};
|
|
653
|
+
|
|
654
|
+
for (const comp of components) {
|
|
655
|
+
const fileBaseName = comp.fileName || comp.name;
|
|
656
|
+
const jsPath = path.join(comp.path, `${fileBaseName}.js`);
|
|
657
|
+
const jsContent = await fs.readFile(jsPath, 'utf-8');
|
|
658
|
+
|
|
659
|
+
const dependencyContents = await this.buildDependencyContents(jsContent, comp.path);
|
|
660
|
+
|
|
661
|
+
let htmlContent = null;
|
|
662
|
+
let cssContent = null;
|
|
663
|
+
|
|
664
|
+
const htmlPath = path.join(comp.path, `${fileBaseName}.html`);
|
|
665
|
+
const cssPath = path.join(comp.path, `${fileBaseName}.css`);
|
|
666
|
+
|
|
667
|
+
if (await fs.pathExists(htmlPath)) {
|
|
668
|
+
htmlContent = await fs.readFile(htmlPath, 'utf-8');
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (await fs.pathExists(cssPath)) {
|
|
672
|
+
cssContent = await fs.readFile(cssPath, 'utf-8');
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const componentKey = comp.isFramework ? `Framework/Structural/${comp.name}` : comp.name;
|
|
676
|
+
componentsData[componentKey] = {
|
|
677
|
+
name: comp.name,
|
|
678
|
+
category: comp.category,
|
|
679
|
+
categoryType: comp.categoryType,
|
|
680
|
+
isFramework: !!comp.isFramework,
|
|
681
|
+
js: this.cleanJavaScript(jsContent, comp.name),
|
|
682
|
+
externalDependencies: dependencyContents, // Files imported with import statements
|
|
683
|
+
componentDependencies: Array.from(comp.dependencies), // Other components this one depends on
|
|
684
|
+
html: htmlContent,
|
|
685
|
+
css: cssContent,
|
|
686
|
+
size: comp.size
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const metadata = {
|
|
691
|
+
version: '2.0.0',
|
|
692
|
+
type,
|
|
693
|
+
route: routePath,
|
|
694
|
+
bundleKey,
|
|
695
|
+
file: fileName,
|
|
696
|
+
generated: new Date().toISOString(),
|
|
697
|
+
totalSize: components.reduce((sum, c) => sum + c.size, 0),
|
|
698
|
+
componentCount: components.length,
|
|
699
|
+
strategy: this.config.strategy,
|
|
700
|
+
minified: this.options.minify,
|
|
701
|
+
obfuscated: this.options.obfuscate
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
return this.formatBundleFile(componentsData, metadata);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
async buildDependencyContents(jsContent, componentPath) {
|
|
708
|
+
const dependencies = this.analyzeDependencies(jsContent, componentPath);
|
|
709
|
+
const dependencyContents = {};
|
|
710
|
+
|
|
711
|
+
for (const dep of dependencies) {
|
|
712
|
+
const depPath = dep.path;
|
|
713
|
+
try {
|
|
714
|
+
const depContent = await fs.readFile(depPath, 'utf-8');
|
|
715
|
+
const depName = path
|
|
716
|
+
.relative(this.srcPath, depPath)
|
|
717
|
+
.replace(/\\/g, '/');
|
|
718
|
+
dependencyContents[depName] = {
|
|
719
|
+
content: depContent,
|
|
720
|
+
bindings: dep.bindings || []
|
|
721
|
+
};
|
|
722
|
+
} catch (error) {
|
|
723
|
+
console.warn(`Warning: Could not read dependency ${depPath}:`, error.message);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
return dependencyContents;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Cleans JavaScript code by removing imports/exports and ensuring class is available globally
|
|
732
|
+
*/
|
|
733
|
+
cleanJavaScript(code, componentName) {
|
|
734
|
+
// Remove export default
|
|
735
|
+
code = code.replace(/export\s+default\s+/g, '');
|
|
736
|
+
|
|
737
|
+
// Remove imports (components will already be available)
|
|
738
|
+
code = code.replace(/import\s+.*?from\s+['"].*?['"];?\s*/g, '');
|
|
739
|
+
|
|
740
|
+
// Make sure the class is available globally for bundle evaluation
|
|
741
|
+
// Preserve original customElements.define if it exists
|
|
742
|
+
if (code.includes('customElements.define')) {
|
|
743
|
+
// Add global assignment before customElements.define
|
|
744
|
+
code = code.replace(/customElements\.define\([^;]+\);?\s*$/, `window.${componentName} = ${componentName};\n$&`);
|
|
745
|
+
} else {
|
|
746
|
+
// If no customElements.define found, just assign to global
|
|
747
|
+
code += `\nwindow.${componentName} = ${componentName};`;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Add return statement for bundle evaluation compatibility
|
|
751
|
+
code += `\nreturn ${componentName};`;
|
|
752
|
+
|
|
753
|
+
return code;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Formats the bundle file
|
|
758
|
+
*/
|
|
759
|
+
formatBundleFile(componentsData, metadata) {
|
|
760
|
+
const integrityPayload = {
|
|
761
|
+
metadata: {
|
|
762
|
+
...metadata,
|
|
763
|
+
generated: 'static'
|
|
764
|
+
},
|
|
765
|
+
components: Object.fromEntries(
|
|
766
|
+
Object.entries(componentsData).map(([name, data]) => [
|
|
767
|
+
name,
|
|
768
|
+
{
|
|
769
|
+
name: data.name,
|
|
770
|
+
category: data.category,
|
|
771
|
+
categoryType: data.categoryType,
|
|
772
|
+
componentDependencies: data.componentDependencies
|
|
773
|
+
}
|
|
774
|
+
])
|
|
775
|
+
)
|
|
776
|
+
};
|
|
777
|
+
const integrity = `sha256:${crypto
|
|
778
|
+
.createHash('sha256')
|
|
779
|
+
.update(JSON.stringify(integrityPayload))
|
|
780
|
+
.digest('hex')}`;
|
|
781
|
+
|
|
782
|
+
const dependencyBlock = this.buildDependencyModuleBlock(componentsData);
|
|
783
|
+
const componentBlock = this.buildComponentBundleBlock(componentsData);
|
|
784
|
+
|
|
785
|
+
return `/**
|
|
786
|
+
* Slice.js Bundle
|
|
787
|
+
* Type: ${metadata.type}
|
|
788
|
+
* Generated: ${metadata.generated}
|
|
789
|
+
* Strategy: ${metadata.strategy}
|
|
790
|
+
* Components: ${metadata.componentCount}
|
|
791
|
+
* Total Size: ${(metadata.totalSize / 1024).toFixed(1)} KB
|
|
792
|
+
*/
|
|
793
|
+
|
|
794
|
+
${dependencyBlock}
|
|
795
|
+
${componentBlock}
|
|
796
|
+
|
|
797
|
+
export const SLICE_BUNDLE = {
|
|
798
|
+
metadata: ${JSON.stringify({ ...metadata, integrity }, null, 2)},
|
|
799
|
+
components: SLICE_BUNDLE_COMPONENTS
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
// Auto-registration of components
|
|
803
|
+
if (window.slice && window.slice.controller) {
|
|
804
|
+
slice.controller.registerBundle(SLICE_BUNDLE);
|
|
805
|
+
}
|
|
806
|
+
`;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
buildDependencyModuleBlock(componentsData) {
|
|
810
|
+
const dependencyModules = this.collectDependencyModules(componentsData);
|
|
811
|
+
if (dependencyModules.length === 0) {
|
|
812
|
+
return 'const SLICE_BUNDLE_DEPENDENCIES = {};';
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const lines = ['const SLICE_BUNDLE_DEPENDENCIES = {};'];
|
|
816
|
+
dependencyModules.forEach((module, index) => {
|
|
817
|
+
const exportVar = `__sliceDepExports${index}`;
|
|
818
|
+
const content = this.transformDependencyContent(module.content, exportVar, module.name);
|
|
819
|
+
lines.push(`// Dependency: ${module.name}`);
|
|
820
|
+
lines.push(`const ${exportVar} = {};`);
|
|
821
|
+
lines.push(content.trim());
|
|
822
|
+
lines.push(`SLICE_BUNDLE_DEPENDENCIES[${JSON.stringify(module.name)}] = ${exportVar};`);
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
return `${lines.join('\n')}`;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
collectDependencyModules(componentsData) {
|
|
829
|
+
const modules = new Map();
|
|
830
|
+
Object.values(componentsData).forEach((component) => {
|
|
831
|
+
Object.entries(component.externalDependencies || {}).forEach(([name, entry]) => {
|
|
832
|
+
if (modules.has(name)) return;
|
|
833
|
+
const content = typeof entry === 'string' ? entry : entry.content;
|
|
834
|
+
modules.set(name, { name, content });
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
return Array.from(modules.values());
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
transformDependencyContent(content, exportVar, moduleName) {
|
|
841
|
+
const baseName = moduleName.split('/').pop().replace(/\.[^.]+$/, '');
|
|
842
|
+
const dataName = baseName ? `${baseName}Data` : null;
|
|
843
|
+
const exportPrefix = dataName ? `${exportVar}.${dataName} = ` : `${exportVar}.default = `;
|
|
844
|
+
|
|
845
|
+
return content
|
|
846
|
+
.replace(/export\s+const\s+(\w+)\s*=\s*/g, `${exportVar}.$1 = `)
|
|
847
|
+
.replace(/export\s+let\s+(\w+)\s*=\s*/g, `${exportVar}.$1 = `)
|
|
848
|
+
.replace(/export\s+var\s+(\w+)\s*=\s*/g, `${exportVar}.$1 = `)
|
|
849
|
+
.replace(/export\s+function\s+(\w+)/g, `${exportVar}.$1 = function`)
|
|
850
|
+
.replace(/export\s+default\s+/g, exportPrefix)
|
|
851
|
+
.replace(/export\s*{\s*([^}]+)\s*}/g, (match, exportsStr) => {
|
|
852
|
+
return exportsStr
|
|
853
|
+
.split(',')
|
|
854
|
+
.map((exp) => {
|
|
855
|
+
const cleanExp = exp.trim();
|
|
856
|
+
const varName = cleanExp.split(' as ')[0].trim();
|
|
857
|
+
return `${exportVar}.${varName} = ${varName};`;
|
|
858
|
+
})
|
|
859
|
+
.join('\n');
|
|
860
|
+
})
|
|
861
|
+
.replace(/^\s*export\s+/gm, '');
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
buildComponentBundleBlock(componentsData) {
|
|
865
|
+
const componentEntries = [];
|
|
866
|
+
const componentDefs = [];
|
|
867
|
+
const frameworkEntries = [];
|
|
868
|
+
|
|
869
|
+
Object.entries(componentsData).forEach(([name, data]) => {
|
|
870
|
+
const classVar = this.toSafeIdentifier(name);
|
|
871
|
+
const bindings = this.buildDependencyBindings(data.externalDependencies || {});
|
|
872
|
+
|
|
873
|
+
componentDefs.push(`const ${classVar} = (() => {\n${bindings}\n${data.js}\nreturn ${name};\n})();`);
|
|
874
|
+
|
|
875
|
+
if (data.isFramework) {
|
|
876
|
+
frameworkEntries.push(`${JSON.stringify(data.name)}: ${classVar}`);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
componentEntries.push(
|
|
880
|
+
`${JSON.stringify(name)}: {\n` +
|
|
881
|
+
` name: ${JSON.stringify(data.name)},\n` +
|
|
882
|
+
` category: ${JSON.stringify(data.category)},\n` +
|
|
883
|
+
` categoryType: ${JSON.stringify(data.categoryType)},\n` +
|
|
884
|
+
` componentDependencies: ${JSON.stringify(data.componentDependencies)},\n` +
|
|
885
|
+
` html: ${JSON.stringify(data.html)},\n` +
|
|
886
|
+
` css: ${JSON.stringify(data.css)},\n` +
|
|
887
|
+
` size: ${JSON.stringify(data.size)},\n` +
|
|
888
|
+
` class: ${classVar}\n` +
|
|
889
|
+
`}`
|
|
890
|
+
);
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
const frameworkBlock = frameworkEntries.length > 0
|
|
894
|
+
? `const SLICE_FRAMEWORK_CLASSES = {\n${frameworkEntries.join(',\n')}\n};\nwindow.SLICE_FRAMEWORK_CLASSES = SLICE_FRAMEWORK_CLASSES;`
|
|
895
|
+
: '';
|
|
896
|
+
|
|
897
|
+
return `${componentDefs.join('\n\n')}\n\nconst SLICE_BUNDLE_COMPONENTS = {\n${componentEntries.join(',\n')}\n};\n${frameworkBlock}`;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
buildDependencyBindings(externalDependencies) {
|
|
901
|
+
const lines = [];
|
|
902
|
+
Object.entries(externalDependencies).forEach(([name, entry]) => {
|
|
903
|
+
const bindings = typeof entry === 'string' ? [] : entry.bindings || [];
|
|
904
|
+
const depVar = `SLICE_BUNDLE_DEPENDENCIES[${JSON.stringify(name)}]`;
|
|
905
|
+
const baseName = name.split('/').pop().replace(/\.[^.]+$/, '');
|
|
906
|
+
const dataName = baseName ? `${baseName}Data` : null;
|
|
907
|
+
|
|
908
|
+
bindings.forEach((binding) => {
|
|
909
|
+
if (!binding?.localName) return;
|
|
910
|
+
if (binding.type === 'default') {
|
|
911
|
+
const fallback = dataName ? `${depVar}.${dataName}` : `${depVar}.default`;
|
|
912
|
+
lines.push(`const ${binding.localName} = ${depVar}.default !== undefined ? ${depVar}.default : ${fallback};`);
|
|
913
|
+
}
|
|
914
|
+
if (binding.type === 'named') {
|
|
915
|
+
lines.push(`const ${binding.localName} = ${depVar}.${binding.importedName};`);
|
|
916
|
+
}
|
|
917
|
+
if (binding.type === 'namespace') {
|
|
918
|
+
lines.push(`const ${binding.localName} = ${depVar};`);
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
return lines.join('\n');
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
toSafeIdentifier(name) {
|
|
927
|
+
const cleaned = name.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
928
|
+
if (/^\d/.test(cleaned)) {
|
|
929
|
+
return `SliceComponent_${cleaned}`;
|
|
930
|
+
}
|
|
931
|
+
return `SliceComponent_${cleaned}`;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Generates the bundle configuration
|
|
936
|
+
*/
|
|
937
|
+
generateBundleConfig(frameworkBundle = null) {
|
|
938
|
+
const config = {
|
|
939
|
+
version: '2.0.0',
|
|
940
|
+
strategy: this.config.strategy,
|
|
941
|
+
minified: this.options.minify,
|
|
942
|
+
obfuscated: this.options.obfuscate,
|
|
943
|
+
production: true,
|
|
944
|
+
generated: new Date().toISOString(),
|
|
945
|
+
|
|
946
|
+
stats: {
|
|
947
|
+
totalComponents: this.analysisData.metrics.totalComponents,
|
|
948
|
+
totalRoutes: this.analysisData.metrics.totalRoutes,
|
|
949
|
+
sharedComponents: this.bundles.critical.components.length,
|
|
950
|
+
sharedPercentage: this.analysisData.metrics.sharedPercentage,
|
|
951
|
+
totalSize: this.analysisData.metrics.totalSize,
|
|
952
|
+
criticalSize: this.bundles.critical.size
|
|
953
|
+
},
|
|
954
|
+
|
|
955
|
+
bundles: {
|
|
956
|
+
framework: {
|
|
957
|
+
file: 'slice-bundle.framework.js',
|
|
958
|
+
size: 0,
|
|
959
|
+
hash: null,
|
|
960
|
+
integrity: null,
|
|
961
|
+
components: []
|
|
962
|
+
},
|
|
963
|
+
critical: {
|
|
964
|
+
file: this.bundles.critical.file,
|
|
965
|
+
size: this.bundles.critical.size,
|
|
966
|
+
hash: this.bundles.critical.hash || null,
|
|
967
|
+
integrity: this.bundles.critical.integrity || null,
|
|
968
|
+
components: this.bundles.critical.components.map(c => c.name)
|
|
969
|
+
},
|
|
970
|
+
routes: {}
|
|
971
|
+
},
|
|
972
|
+
routeBundles: {}
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
for (const [key, bundle] of Object.entries(this.bundles.routes)) {
|
|
976
|
+
const routeIdentifier = Array.isArray(bundle.path || bundle.paths)
|
|
977
|
+
? key
|
|
978
|
+
: (bundle.path || bundle.paths || key);
|
|
979
|
+
|
|
980
|
+
config.bundles.routes[key] = {
|
|
981
|
+
path: bundle.path || bundle.paths || key, // Support both single path and array of paths, fallback to key
|
|
982
|
+
file: `slice-bundle.${this.routeToFileName(routeIdentifier)}.js`,
|
|
983
|
+
size: bundle.size,
|
|
984
|
+
hash: bundle.hash || null,
|
|
985
|
+
integrity: bundle.integrity || null,
|
|
986
|
+
components: bundle.components.map(c => c.name),
|
|
987
|
+
dependencies: ['critical']
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
const paths = Array.isArray(config.bundles.routes[key].path)
|
|
991
|
+
? config.bundles.routes[key].path
|
|
992
|
+
: [config.bundles.routes[key].path];
|
|
993
|
+
|
|
994
|
+
for (const routePath of paths) {
|
|
995
|
+
if (!config.routeBundles[routePath]) {
|
|
996
|
+
config.routeBundles[routePath] = ['critical'];
|
|
997
|
+
}
|
|
998
|
+
if (!config.routeBundles[routePath].includes(key)) {
|
|
999
|
+
config.routeBundles[routePath].push(key);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (frameworkBundle) {
|
|
1005
|
+
config.bundles.framework = {
|
|
1006
|
+
file: frameworkBundle.file,
|
|
1007
|
+
size: frameworkBundle.size,
|
|
1008
|
+
hash: frameworkBundle.hash,
|
|
1009
|
+
integrity: frameworkBundle.integrity,
|
|
1010
|
+
components: frameworkBundle.components || []
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
return config;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
collectFrameworkComponents() {
|
|
1018
|
+
return this.analysisData.components.filter((comp) => comp.isFramework);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
async createFrameworkBundle(components) {
|
|
1022
|
+
const fileName = 'slice-bundle.framework.js';
|
|
1023
|
+
const filePath = path.join(this.bundlesPath, fileName);
|
|
1024
|
+
return this.generateFrameworkBundleFile(components, fileName, filePath);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
async generateFrameworkBundleFile(components, fileName, filePath) {
|
|
1028
|
+
const componentsData = {};
|
|
1029
|
+
const componentsMap = await this.loadComponentsMap();
|
|
1030
|
+
const metadata = {
|
|
1031
|
+
version: '2.0.0',
|
|
1032
|
+
type: 'framework',
|
|
1033
|
+
route: null,
|
|
1034
|
+
bundleKey: 'framework',
|
|
1035
|
+
file: fileName,
|
|
1036
|
+
generated: new Date().toISOString(),
|
|
1037
|
+
totalSize: components.reduce((sum, c) => sum + c.size, 0),
|
|
1038
|
+
componentCount: components.length,
|
|
1039
|
+
strategy: this.config.strategy,
|
|
1040
|
+
minified: this.options.minify,
|
|
1041
|
+
obfuscated: this.options.obfuscate
|
|
1042
|
+
};
|
|
1043
|
+
|
|
1044
|
+
components.forEach((comp) => {
|
|
1045
|
+
const componentKey = `Framework/Structural/${comp.name}`;
|
|
1046
|
+
const fileBaseName = comp.fileName || comp.name;
|
|
1047
|
+
const jsPath = path.join(comp.path, `${fileBaseName}.js`);
|
|
1048
|
+
const jsContent = fs.readFileSync(jsPath, 'utf-8');
|
|
1049
|
+
const dependencyContents = this.buildDependencyContentsSync(jsContent, comp.path);
|
|
1050
|
+
componentsData[componentKey] = {
|
|
1051
|
+
name: comp.name,
|
|
1052
|
+
category: comp.category,
|
|
1053
|
+
categoryType: comp.categoryType,
|
|
1054
|
+
isFramework: true,
|
|
1055
|
+
js: this.cleanJavaScript(jsContent, comp.name),
|
|
1056
|
+
externalDependencies: dependencyContents,
|
|
1057
|
+
componentDependencies: Array.from(comp.dependencies),
|
|
1058
|
+
html: fs.existsSync(path.join(comp.path, `${fileBaseName}.html`))
|
|
1059
|
+
? fs.readFileSync(path.join(comp.path, `${fileBaseName}.html`), 'utf-8')
|
|
1060
|
+
: null,
|
|
1061
|
+
css: fs.existsSync(path.join(comp.path, `${fileBaseName}.css`))
|
|
1062
|
+
? fs.readFileSync(path.join(comp.path, `${fileBaseName}.css`), 'utf-8')
|
|
1063
|
+
: null,
|
|
1064
|
+
size: comp.size
|
|
1065
|
+
};
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
const prelude = `const components = ${JSON.stringify(componentsMap)};`;
|
|
1069
|
+
const bundleContent = `${prelude}\n${this.formatBundleFile(componentsData, metadata)}`;
|
|
1070
|
+
const finalContent = await this.applyBundleTransforms(bundleContent, fileName);
|
|
1071
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
1072
|
+
await fs.writeFile(filePath, finalContent, 'utf-8');
|
|
1073
|
+
|
|
1074
|
+
const hash = crypto.createHash('sha256').update(finalContent).digest('hex');
|
|
1075
|
+
const integrity = this.computeBundleIntegrity(components, 'framework', null, 'framework', fileName);
|
|
1076
|
+
|
|
1077
|
+
return {
|
|
1078
|
+
name: 'framework',
|
|
1079
|
+
file: fileName,
|
|
1080
|
+
size: Buffer.byteLength(bundleContent, 'utf-8'),
|
|
1081
|
+
hash,
|
|
1082
|
+
integrity,
|
|
1083
|
+
componentCount: components.length,
|
|
1084
|
+
components: components.map((comp) => `Framework/Structural/${comp.name}`)
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
buildDependencyContentsSync(jsContent, componentPath) {
|
|
1089
|
+
const dependencies = this.analyzeDependencies(jsContent, componentPath);
|
|
1090
|
+
const dependencyContents = {};
|
|
1091
|
+
|
|
1092
|
+
for (const dep of dependencies) {
|
|
1093
|
+
const depPath = dep.path;
|
|
1094
|
+
try {
|
|
1095
|
+
const depContent = fs.readFileSync(depPath, 'utf-8');
|
|
1096
|
+
const depName = path
|
|
1097
|
+
.relative(this.srcPath, depPath)
|
|
1098
|
+
.replace(/\\/g, '/');
|
|
1099
|
+
dependencyContents[depName] = {
|
|
1100
|
+
content: depContent,
|
|
1101
|
+
bindings: dep.bindings || []
|
|
1102
|
+
};
|
|
1103
|
+
} catch (error) {
|
|
1104
|
+
console.warn(`Warning: Could not read dependency ${depPath}:`, error.message);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
return dependencyContents;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
stripImports(code) {
|
|
1112
|
+
return code.replace(/import\s+.*?from\s+['"].*?['"];?\s*/g, '');
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
async loadComponentsMap() {
|
|
1116
|
+
const componentsConfigPath = path.join(this.componentsPath, 'components.js');
|
|
1117
|
+
if (!await fs.pathExists(componentsConfigPath)) {
|
|
1118
|
+
return {};
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
const content = await fs.readFile(componentsConfigPath, 'utf-8');
|
|
1122
|
+
return this.parseComponentsConfig(content);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
parseComponentsConfig(content) {
|
|
1126
|
+
try {
|
|
1127
|
+
const ast = parse(content, {
|
|
1128
|
+
sourceType: 'module',
|
|
1129
|
+
plugins: ['jsx']
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
let componentsNode = null;
|
|
1133
|
+
|
|
1134
|
+
traverse.default(ast, {
|
|
1135
|
+
VariableDeclarator(path) {
|
|
1136
|
+
if (path.node.id?.type === 'Identifier' && path.node.id.name === 'components') {
|
|
1137
|
+
componentsNode = path.node.init;
|
|
1138
|
+
path.stop();
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
if (!componentsNode || componentsNode.type !== 'ObjectExpression') {
|
|
1144
|
+
throw new Error('components object not found');
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
const config = {};
|
|
1148
|
+
for (const prop of componentsNode.properties) {
|
|
1149
|
+
if (prop.type !== 'ObjectProperty') continue;
|
|
1150
|
+
|
|
1151
|
+
const key = this.extractStringValue(prop.key);
|
|
1152
|
+
const value = this.extractStringValue(prop.value);
|
|
1153
|
+
|
|
1154
|
+
if (!key || !value) {
|
|
1155
|
+
throw new Error('Invalid components entry');
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
config[key] = value;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
return config;
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
console.warn(`Could not parse components.js: ${error.message}`);
|
|
1164
|
+
return {};
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
extractStringValue(node) {
|
|
1169
|
+
if (!node) return null;
|
|
1170
|
+
|
|
1171
|
+
if (node.type === 'StringLiteral') {
|
|
1172
|
+
return node.value;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
if (node.type === 'Identifier') {
|
|
1176
|
+
return node.name;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {
|
|
1180
|
+
return node.quasis.map((q) => q.value.cooked).join('');
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* Converts a route to filename
|
|
1188
|
+
*/
|
|
1189
|
+
routeToFileName(routePath) {
|
|
1190
|
+
if (routePath === '/') return 'home';
|
|
1191
|
+
return routePath
|
|
1192
|
+
.replace(/^\//, '')
|
|
1193
|
+
.replace(/\//g, '-')
|
|
1194
|
+
.replace(/[^a-zA-Z0-9-]/g, '')
|
|
1195
|
+
.toLowerCase();
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
/**
|
|
1199
|
+
* Saves the configuration to file
|
|
1200
|
+
*/
|
|
1201
|
+
async saveBundleConfig(config) {
|
|
1202
|
+
// Ensure bundles directory exists
|
|
1203
|
+
await fs.ensureDir(this.bundlesPath);
|
|
1204
|
+
|
|
1205
|
+
// Save JSON config
|
|
1206
|
+
const configPath = path.join(this.bundlesPath, 'bundle.config.json');
|
|
1207
|
+
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
1208
|
+
|
|
1209
|
+
// Generate JavaScript module for direct import
|
|
1210
|
+
const jsConfigPath = path.join(this.bundlesPath, 'bundle.config.js');
|
|
1211
|
+
const jsConfig = this.generateBundleConfigJS(config);
|
|
1212
|
+
await fs.writeFile(jsConfigPath, jsConfig, 'utf-8');
|
|
1213
|
+
|
|
1214
|
+
console.log(`✓ Configuration saved to ${configPath}`);
|
|
1215
|
+
console.log(`✓ JavaScript config generated: ${jsConfigPath}`);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Creates a default bundle config file if none exists
|
|
1220
|
+
*/
|
|
1221
|
+
async createDefaultBundleConfig() {
|
|
1222
|
+
const defaultConfigPath = path.join(this.srcPath, 'bundles', 'bundle.config.js');
|
|
1223
|
+
|
|
1224
|
+
// Only create if it doesn't exist
|
|
1225
|
+
if (await fs.pathExists(defaultConfigPath)) {
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
await fs.ensureDir(path.dirname(defaultConfigPath));
|
|
1230
|
+
|
|
1231
|
+
const defaultConfig = `/**
|
|
1232
|
+
* Slice.js Bundle Configuration
|
|
1233
|
+
* Default empty configuration - no bundles available
|
|
1234
|
+
* Run 'slice build' to generate optimized bundles
|
|
1235
|
+
*/
|
|
1236
|
+
|
|
1237
|
+
// No bundles available - using individual component loading
|
|
1238
|
+
export const SLICE_BUNDLE_CONFIG = null;
|
|
1239
|
+
|
|
1240
|
+
// No auto-initialization needed for default config
|
|
1241
|
+
`;
|
|
1242
|
+
|
|
1243
|
+
await fs.writeFile(defaultConfigPath, defaultConfig, 'utf-8');
|
|
1244
|
+
console.log(`✓ Default bundle config created: ${defaultConfigPath}`);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
/**
|
|
1248
|
+
* Generates JavaScript module for direct import
|
|
1249
|
+
*/
|
|
1250
|
+
generateBundleConfigJS(config) {
|
|
1251
|
+
return `/**
|
|
1252
|
+
* Slice.js Bundle Configuration
|
|
1253
|
+
* Generated: ${new Date().toISOString()}
|
|
1254
|
+
* Strategy: ${config.strategy}
|
|
1255
|
+
*/
|
|
1256
|
+
|
|
1257
|
+
// Direct bundle configuration (no fetch required)
|
|
1258
|
+
export const SLICE_BUNDLE_CONFIG = ${JSON.stringify(config, null, 2)};
|
|
1259
|
+
|
|
1260
|
+
// Auto-initialization if slice is available
|
|
1261
|
+
if (typeof window !== 'undefined' && window.slice && window.slice.controller) {
|
|
1262
|
+
window.slice.controller.bundleConfig = SLICE_BUNDLE_CONFIG;
|
|
1263
|
+
|
|
1264
|
+
// Load critical bundle automatically
|
|
1265
|
+
if (SLICE_BUNDLE_CONFIG.bundles.critical && !window.slice.controller.criticalBundleLoaded) {
|
|
1266
|
+
(async () => {
|
|
1267
|
+
const bundlePath = "/bundles/" + SLICE_BUNDLE_CONFIG.bundles.critical.file;
|
|
1268
|
+
const integrity = SLICE_BUNDLE_CONFIG.bundles.critical.integrity;
|
|
1269
|
+
|
|
1270
|
+
if (typeof window.slice.controller.verifyBundleIntegrity === 'function') {
|
|
1271
|
+
const ok = await window.slice.controller.verifyBundleIntegrity(bundlePath, integrity);
|
|
1272
|
+
if (!ok) {
|
|
1273
|
+
console.warn('Failed to load critical bundle: integrity check failed');
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
import('./slice-bundle.critical.js').catch(err =>
|
|
1279
|
+
console.warn('Failed to load critical bundle:', err)
|
|
1280
|
+
);
|
|
1281
|
+
window.slice.controller.criticalBundleLoaded = true;
|
|
1282
|
+
})();
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
`;
|
|
1286
|
+
}
|
|
1287
|
+
}
|