slicejs-cli 3.2.0 → 3.4.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/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
- package/.github/pull_request_template.md +22 -0
- package/CODE_OF_CONDUCT.md +126 -0
- package/ECOSYSTEM.md +9 -0
- package/LICENSE +21 -0
- package/README.md +171 -375
- package/client.js +664 -563
- package/commands/Print.js +167 -167
- package/commands/Validations.js +103 -103
- package/commands/build/build.js +40 -40
- package/commands/buildProduction/buildProduction.js +579 -579
- package/commands/bundle/bundle.js +235 -235
- 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 +265 -261
- package/commands/listComponents/listComponents.js +175 -175
- package/commands/startServer/startServer.js +264 -264
- package/commands/startServer/watchServer.js +79 -79
- package/commands/types/types.js +16 -9
- package/commands/utils/LocalCliDelegation.js +53 -53
- package/commands/utils/PathHelper.js +68 -68
- package/commands/utils/VersionChecker.js +167 -167
- package/commands/utils/bundling/BundleGenerator.js +2292 -2292
- package/commands/utils/bundling/DependencyAnalyzer.js +933 -933
- package/commands/utils/updateManager.js +453 -453
- package/package.json +46 -46
- package/post.js +66 -25
- package/tests/bundle-generator.test.js +708 -708
- package/tests/bundle-v2-register-output.test.js +470 -470
- package/tests/client-launcher-contract.test.js +211 -211
- package/tests/client-update-flow-contract.test.js +272 -272
- package/tests/dependency-analyzer.test.js +24 -24
- package/tests/local-cli-delegation.test.js +79 -79
- package/tests/postinstall-command.test.js +72 -0
- package/tests/update-manager-notifications.test.js +88 -88
- package/refactor.md +0 -271
|
@@ -1,470 +1,470 @@
|
|
|
1
|
-
import { test } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import BundleGenerator from '../commands/utils/bundling/BundleGenerator.js';
|
|
4
|
-
|
|
5
|
-
test('generated V2 bundle includes meta and registerAll exports', () => {
|
|
6
|
-
const generator = new BundleGenerator(import.meta.url, {
|
|
7
|
-
components: [],
|
|
8
|
-
routes: [],
|
|
9
|
-
metrics: {
|
|
10
|
-
totalComponents: 0,
|
|
11
|
-
totalRoutes: 0,
|
|
12
|
-
sharedPercentage: 0,
|
|
13
|
-
totalSize: 0
|
|
14
|
-
}
|
|
15
|
-
}, { output: 'src' });
|
|
16
|
-
|
|
17
|
-
const source = generator.generateBundleFileContent(
|
|
18
|
-
'slice-bundle.test.js',
|
|
19
|
-
'route',
|
|
20
|
-
[{ name: 'Button', category: 'Visual', categoryType: 'Visual', dependencies: new Set(), size: 100, js: '', html: '', css: '' }],
|
|
21
|
-
'/test'
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
assert.match(source, /export const SLICE_BUNDLE_META/);
|
|
25
|
-
assert.match(source, /export async function registerAll\(/);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('generateBundleFileContent deduplicates components by name', () => {
|
|
29
|
-
const generator = new BundleGenerator(import.meta.url, {
|
|
30
|
-
components: [],
|
|
31
|
-
routes: [],
|
|
32
|
-
metrics: {
|
|
33
|
-
totalComponents: 0,
|
|
34
|
-
totalRoutes: 0,
|
|
35
|
-
sharedPercentage: 0,
|
|
36
|
-
totalSize: 0
|
|
37
|
-
}
|
|
38
|
-
}, { output: 'src' });
|
|
39
|
-
|
|
40
|
-
const source = generator.generateBundleFileContent(
|
|
41
|
-
'slice-bundle.test.js',
|
|
42
|
-
'route',
|
|
43
|
-
[
|
|
44
|
-
{ name: 'Button', category: 'Visual', categoryType: 'Visual', dependencies: new Set(), size: 100, js: '', html: '', css: '' },
|
|
45
|
-
{ name: 'Button', category: 'Visual', categoryType: 'Visual', dependencies: new Set(), size: 100, js: '', html: '', css: '' }
|
|
46
|
-
],
|
|
47
|
-
'/test'
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
assert.equal((source.match(/const SLICE_CLASS_FACTORY_SliceComponent_Button/g) || []).length, 1);
|
|
51
|
-
assert.equal((source.match(/if \(!controller\.classes\.has\("Button"\)\) \{\s*controller\.classes\.set\("Button", SLICE_CLASS_FACTORY_SliceComponent_Button\(\)\);\s*\}/g) || []).length, 1);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('registerAll guards class, template, style, and category writes', () => {
|
|
55
|
-
const generator = new BundleGenerator(import.meta.url, {
|
|
56
|
-
components: [],
|
|
57
|
-
routes: [],
|
|
58
|
-
metrics: {
|
|
59
|
-
totalComponents: 0,
|
|
60
|
-
totalRoutes: 0,
|
|
61
|
-
sharedPercentage: 0,
|
|
62
|
-
totalSize: 0
|
|
63
|
-
}
|
|
64
|
-
}, { output: 'src' });
|
|
65
|
-
|
|
66
|
-
const source = generator.generateBundleFileContent(
|
|
67
|
-
'slice-bundle.test.js',
|
|
68
|
-
'route',
|
|
69
|
-
[{ name: 'Button', category: 'Visual', categoryType: 'Visual', dependencies: new Set(), size: 100, js: '', html: '<button>ok</button>', css: '.btn{}' }],
|
|
70
|
-
'/test'
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
assert.match(source, /if \(!controller\.classes\.has\("Button"\)\) \{\s*controller\.classes\.set\("Button", SLICE_CLASS_FACTORY_SliceComponent_Button\(\)\);\s*\}/);
|
|
74
|
-
assert.match(source, /if \(!controller\.templates\.has\("Button"\)\) \{\s*controller\.templates\.set\("Button", __templateElement_SliceComponent_Button\);\s*\}/);
|
|
75
|
-
assert.match(source, /if \(!stylesManager\.__sliceRegisteredComponentStyles\) \{\s*stylesManager\.__sliceRegisteredComponentStyles = new Set\(\);\s*\}/);
|
|
76
|
-
assert.match(source, /if \(!stylesManager\.__sliceRegisteredComponentStyles\.has\("Button"\)\) \{\s*stylesManager\.registerComponentStyles\("Button", ".*"\);\s*stylesManager\.__sliceRegisteredComponentStyles\.add\("Button"\);\s*\}/);
|
|
77
|
-
assert.match(source, /if \(!controller\.componentCategories\.has\("Button"\)\) \{\s*controller\.componentCategories\.set\("Button", "Visual"\);\s*\}/);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('registerAll stores templates as template elements', () => {
|
|
81
|
-
const generator = new BundleGenerator(import.meta.url, {
|
|
82
|
-
components: [],
|
|
83
|
-
routes: [],
|
|
84
|
-
metrics: {
|
|
85
|
-
totalComponents: 0,
|
|
86
|
-
totalRoutes: 0,
|
|
87
|
-
sharedPercentage: 0,
|
|
88
|
-
totalSize: 0
|
|
89
|
-
}
|
|
90
|
-
}, { output: 'src' });
|
|
91
|
-
|
|
92
|
-
const source = generator.generateBundleFileContent(
|
|
93
|
-
'slice-bundle.test.js',
|
|
94
|
-
'route',
|
|
95
|
-
[{ name: 'DocumentationPage', category: 'AppComponents', categoryType: 'Visual', dependencies: new Set(), size: 100, js: '', html: '<div>docs</div>', css: '' }],
|
|
96
|
-
'/docs'
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
assert.match(source, /const __templateElement_SliceComponent_DocumentationPage = document\.createElement\('template'\);/);
|
|
100
|
-
assert.match(source, /__templateElement_SliceComponent_DocumentationPage\.innerHTML = "<div>docs<\/div>";/);
|
|
101
|
-
assert.match(source, /controller\.templates\.set\("DocumentationPage", __templateElement_SliceComponent_DocumentationPage\);/);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test('bundle output inlines dependency modules and binds imported symbols in class factories', () => {
|
|
105
|
-
const generator = new BundleGenerator(import.meta.url, {
|
|
106
|
-
components: [],
|
|
107
|
-
routes: [],
|
|
108
|
-
metrics: {
|
|
109
|
-
totalComponents: 0,
|
|
110
|
-
totalRoutes: 0,
|
|
111
|
-
sharedPercentage: 0,
|
|
112
|
-
totalSize: 0
|
|
113
|
-
}
|
|
114
|
-
}, { output: 'src' });
|
|
115
|
-
|
|
116
|
-
const source = generator.generateBundleFileContent(
|
|
117
|
-
'slice-bundle.test.js',
|
|
118
|
-
'route',
|
|
119
|
-
[{
|
|
120
|
-
name: 'DocumentationPage',
|
|
121
|
-
category: 'AppComponents',
|
|
122
|
-
categoryType: 'Visual',
|
|
123
|
-
dependencies: new Set(),
|
|
124
|
-
size: 100,
|
|
125
|
-
js: 'class DocumentationPage extends HTMLElement { connectedCallback(){ return documentationRoutes.length; } }\nwindow.DocumentationPage = DocumentationPage;\nreturn DocumentationPage;',
|
|
126
|
-
html: '',
|
|
127
|
-
css: '',
|
|
128
|
-
externalDependencies: {
|
|
129
|
-
'App/documentationRoutes.js': {
|
|
130
|
-
content: 'export const documentationRoutes = ["/docs"];',
|
|
131
|
-
bindings: [{ type: 'named', importedName: 'documentationRoutes', localName: 'documentationRoutes' }]
|
|
132
|
-
},
|
|
133
|
-
'App/purify.js': {
|
|
134
|
-
content: 'export const purify = (value) => value;',
|
|
135
|
-
bindings: [{ type: 'default', importedName: 'default', localName: 'purify' }]
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}],
|
|
139
|
-
'/docs'
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
assert.match(source, /const SLICE_BUNDLE_DEPENDENCIES = \{\};/);
|
|
143
|
-
assert.match(source, /const __sliceSharedDeps = typeof window !== 'undefined' \? \(window\.__SLICE_SHARED_DEPS__ \|\| \{\}\) : \{\};/);
|
|
144
|
-
assert.match(source, /function __sliceResolveDefaultExport\(dep, depName, preferredKey\) \{/);
|
|
145
|
-
assert.match(source, /SLICE_BUNDLE_DEPENDENCIES\["App\/documentationRoutes\.js"\] = __sliceDepExports0;/);
|
|
146
|
-
assert.match(source, /const documentationRoutes = __sliceResolveBundleDependency\("App\/documentationRoutes\.js"\)\.documentationRoutes;/);
|
|
147
|
-
assert.match(source, /const purify = __sliceResolveDefaultExport\(__sliceResolveBundleDependency\("App\/purify\.js"\), "App\/purify\.js", "purifyData"\);/);
|
|
148
|
-
assert.doesNotMatch(source, /\.default !== undefined \?/);
|
|
149
|
-
assert.doesNotMatch(source, /SLICE_BUNDLE_DEPENDENCIES\["App\/purify\.js"\]\.purifyData/);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test('route bundle omits extracted vendor-shared modules from inline dependency block and keeps bindings', () => {
|
|
153
|
-
const generator = new BundleGenerator(import.meta.url, {
|
|
154
|
-
components: [],
|
|
155
|
-
routes: [],
|
|
156
|
-
metrics: {
|
|
157
|
-
totalComponents: 0,
|
|
158
|
-
totalRoutes: 0,
|
|
159
|
-
sharedPercentage: 0,
|
|
160
|
-
totalSize: 0
|
|
161
|
-
}
|
|
162
|
-
}, { output: 'src' });
|
|
163
|
-
|
|
164
|
-
generator.vendorShared.sharedDependencySet = new Set(['App/documentationRoutes.js']);
|
|
165
|
-
|
|
166
|
-
const source = generator.generateBundleFileContent(
|
|
167
|
-
'slice-bundle.test.js',
|
|
168
|
-
'route',
|
|
169
|
-
[{
|
|
170
|
-
name: 'DocumentationPage',
|
|
171
|
-
category: 'AppComponents',
|
|
172
|
-
categoryType: 'Visual',
|
|
173
|
-
dependencies: new Set(),
|
|
174
|
-
size: 100,
|
|
175
|
-
js: 'class DocumentationPage extends HTMLElement { connectedCallback(){ return [documentationRoutes.length, docsNs.documentationRoutes.length, purify(1)].length; } }\nwindow.DocumentationPage = DocumentationPage;\nreturn DocumentationPage;',
|
|
176
|
-
html: '',
|
|
177
|
-
css: '',
|
|
178
|
-
externalDependencies: {
|
|
179
|
-
'App/documentationRoutes.js': {
|
|
180
|
-
content: 'export const documentationRoutes = ["/docs"];',
|
|
181
|
-
bindings: [
|
|
182
|
-
{ type: 'named', importedName: 'documentationRoutes', localName: 'documentationRoutes' },
|
|
183
|
-
{ type: 'namespace', localName: 'docsNs' }
|
|
184
|
-
]
|
|
185
|
-
},
|
|
186
|
-
'App/purify.js': {
|
|
187
|
-
content: 'export default (value) => value;',
|
|
188
|
-
bindings: [{ type: 'default', importedName: 'default', localName: 'purify' }]
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}],
|
|
192
|
-
'/docs'
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
assert.match(source, /const __sliceSharedDeps = typeof window !== 'undefined' \? \(window\.__SLICE_SHARED_DEPS__ \|\| \{\}\) : \{\};/);
|
|
196
|
-
assert.match(source, /const __sliceResolveBundleDependency = \(depName\) => Object\.prototype\.hasOwnProperty\.call\(__sliceSharedDeps, depName\) \? __sliceSharedDeps\[depName\] : SLICE_BUNDLE_DEPENDENCIES\[depName\];/);
|
|
197
|
-
assert.doesNotMatch(source, /SLICE_BUNDLE_DEPENDENCIES\["App\/documentationRoutes\.js"\] = __sliceDepExports\d+;/);
|
|
198
|
-
assert.match(source, /SLICE_BUNDLE_DEPENDENCIES\["App\/purify\.js"\] = __sliceDepExports\d+;/);
|
|
199
|
-
assert.match(source, /const documentationRoutes = __sliceResolveBundleDependency\("App\/documentationRoutes\.js"\)\.documentationRoutes;/);
|
|
200
|
-
assert.match(source, /const docsNs = __sliceResolveBundleDependency\("App\/documentationRoutes\.js"\);/);
|
|
201
|
-
assert.match(source, /const purify = __sliceResolveDefaultExport\(__sliceResolveBundleDependency\("App\/purify\.js"\), "App\/purify\.js", "purifyData"\);/);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test('route bundle emits guarded shared deps resolver for non-browser contexts', () => {
|
|
205
|
-
const generator = new BundleGenerator(import.meta.url, {
|
|
206
|
-
components: [],
|
|
207
|
-
routes: [],
|
|
208
|
-
metrics: {
|
|
209
|
-
totalComponents: 0,
|
|
210
|
-
totalRoutes: 0,
|
|
211
|
-
sharedPercentage: 0,
|
|
212
|
-
totalSize: 0
|
|
213
|
-
}
|
|
214
|
-
}, { output: 'src' });
|
|
215
|
-
|
|
216
|
-
const source = generator.generateBundleFileContent(
|
|
217
|
-
'slice-bundle.test.js',
|
|
218
|
-
'route',
|
|
219
|
-
[{
|
|
220
|
-
name: 'DocsPage',
|
|
221
|
-
category: 'AppComponents',
|
|
222
|
-
categoryType: 'Visual',
|
|
223
|
-
dependencies: new Set(),
|
|
224
|
-
size: 100,
|
|
225
|
-
js: 'class DocsPage extends HTMLElement {}\nwindow.DocsPage = DocsPage;\nreturn DocsPage;',
|
|
226
|
-
html: '',
|
|
227
|
-
css: '',
|
|
228
|
-
externalDependencies: {
|
|
229
|
-
'App/deps.js': {
|
|
230
|
-
content: 'export const value = 1;',
|
|
231
|
-
bindings: [{ type: 'named', importedName: 'value', localName: 'value' }]
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}],
|
|
235
|
-
'/docs'
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
assert.match(source, /const __sliceSharedDeps = typeof window !== 'undefined' \? \(window\.__SLICE_SHARED_DEPS__ \|\| \{\}\) : \{\};/);
|
|
239
|
-
assert.match(source, /const __sliceResolveBundleDependency = \(depName\) => Object\.prototype\.hasOwnProperty\.call\(__sliceSharedDeps, depName\) \? __sliceSharedDeps\[depName\] : SLICE_BUNDLE_DEPENDENCIES\[depName\];/);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
test('non-route bundle does not emit shared resolver block', () => {
|
|
243
|
-
const generator = new BundleGenerator(import.meta.url, {
|
|
244
|
-
components: [],
|
|
245
|
-
routes: [],
|
|
246
|
-
metrics: {
|
|
247
|
-
totalComponents: 0,
|
|
248
|
-
totalRoutes: 0,
|
|
249
|
-
sharedPercentage: 0,
|
|
250
|
-
totalSize: 0
|
|
251
|
-
}
|
|
252
|
-
}, { output: 'src' });
|
|
253
|
-
|
|
254
|
-
const source = generator.generateBundleFileContent(
|
|
255
|
-
'slice-bundle.framework.js',
|
|
256
|
-
'framework',
|
|
257
|
-
[{
|
|
258
|
-
name: 'FrameworkComp',
|
|
259
|
-
category: 'Framework',
|
|
260
|
-
categoryType: 'Visual',
|
|
261
|
-
dependencies: new Set(),
|
|
262
|
-
size: 100,
|
|
263
|
-
js: 'class FrameworkComp extends HTMLElement {}\nwindow.FrameworkComp = FrameworkComp;\nreturn FrameworkComp;',
|
|
264
|
-
html: '',
|
|
265
|
-
css: '',
|
|
266
|
-
externalDependencies: {
|
|
267
|
-
'App/frameworkDep.js': {
|
|
268
|
-
content: 'export const dep = 1;',
|
|
269
|
-
bindings: [{ type: 'named', importedName: 'dep', localName: 'dep' }]
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}],
|
|
273
|
-
null
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
assert.doesNotMatch(source, /const __sliceSharedDeps = /);
|
|
277
|
-
assert.doesNotMatch(source, /const __sliceResolveBundleDependency = /);
|
|
278
|
-
assert.match(source, /const dep = SLICE_BUNDLE_DEPENDENCIES\["App\/frameworkDep\.js"\]\.dep;/);
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
test('route bundle keeps local fallback binding when shared map is unavailable', () => {
|
|
282
|
-
const generator = new BundleGenerator(import.meta.url, {
|
|
283
|
-
components: [],
|
|
284
|
-
routes: [],
|
|
285
|
-
metrics: {
|
|
286
|
-
totalComponents: 0,
|
|
287
|
-
totalRoutes: 0,
|
|
288
|
-
sharedPercentage: 0,
|
|
289
|
-
totalSize: 0
|
|
290
|
-
}
|
|
291
|
-
}, { output: 'src' });
|
|
292
|
-
|
|
293
|
-
const source = generator.generateBundleFileContent(
|
|
294
|
-
'slice-bundle.test.js',
|
|
295
|
-
'route',
|
|
296
|
-
[{
|
|
297
|
-
name: 'FallbackComp',
|
|
298
|
-
category: 'Visual',
|
|
299
|
-
categoryType: 'Visual',
|
|
300
|
-
dependencies: new Set(),
|
|
301
|
-
size: 100,
|
|
302
|
-
js: 'class FallbackComp extends HTMLElement {}\nwindow.FallbackComp = FallbackComp;\nreturn FallbackComp;',
|
|
303
|
-
html: '',
|
|
304
|
-
css: '',
|
|
305
|
-
externalDependencies: {
|
|
306
|
-
'App/local.js': {
|
|
307
|
-
content: 'export const local = 1;',
|
|
308
|
-
bindings: [{ type: 'named', importedName: 'local', localName: 'local' }]
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}],
|
|
312
|
-
'/fallback'
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
assert.match(source, /const __sliceResolveBundleDependency = \(depName\) => Object\.prototype\.hasOwnProperty\.call\(__sliceSharedDeps, depName\) \? __sliceSharedDeps\[depName\] : SLICE_BUNDLE_DEPENDENCIES\[depName\];/);
|
|
316
|
-
assert.match(source, /const local = __sliceResolveBundleDependency\("App\/local\.js"\)\.local;/);
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
test('bundle output hoists allowed absolute imports to module top-level', () => {
|
|
320
|
-
const generator = new BundleGenerator(import.meta.url, {
|
|
321
|
-
components: [],
|
|
322
|
-
routes: [],
|
|
323
|
-
metrics: {
|
|
324
|
-
totalComponents: 0,
|
|
325
|
-
totalRoutes: 0,
|
|
326
|
-
sharedPercentage: 0,
|
|
327
|
-
totalSize: 0
|
|
328
|
-
}
|
|
329
|
-
}, { output: 'src' });
|
|
330
|
-
|
|
331
|
-
const source = generator.generateBundleFileContent(
|
|
332
|
-
'slice-bundle.test.js',
|
|
333
|
-
'route',
|
|
334
|
-
[{
|
|
335
|
-
name: 'HeroCard',
|
|
336
|
-
category: 'Visual',
|
|
337
|
-
categoryType: 'Visual',
|
|
338
|
-
dependencies: new Set(),
|
|
339
|
-
size: 100,
|
|
340
|
-
js: 'class HeroCard extends HTMLElement {}\nwindow.HeroCard = HeroCard;\nreturn HeroCard;',
|
|
341
|
-
html: '',
|
|
342
|
-
css: '',
|
|
343
|
-
hoistedImports: ["import hero from '/public/hero.js';"]
|
|
344
|
-
}],
|
|
345
|
-
'/test'
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
assert.match(source, /import hero from '\/public\/hero\.js';/);
|
|
349
|
-
assert.doesNotMatch(source, /SLICE_CLASS_FACTORY_SliceComponent_HeroCard = \(\) => \{[\s\S]*import hero from '\/public\/hero\.js';/);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
test('generateBundleFileContent throws on hoisted import local binding collisions', () => {
|
|
353
|
-
const generator = new BundleGenerator(import.meta.url, {
|
|
354
|
-
components: [],
|
|
355
|
-
routes: [],
|
|
356
|
-
metrics: {
|
|
357
|
-
totalComponents: 0,
|
|
358
|
-
totalRoutes: 0,
|
|
359
|
-
sharedPercentage: 0,
|
|
360
|
-
totalSize: 0
|
|
361
|
-
}
|
|
362
|
-
}, { output: 'src' });
|
|
363
|
-
|
|
364
|
-
assert.throws(() => {
|
|
365
|
-
generator.generateBundleFileContent(
|
|
366
|
-
'slice-bundle.test.js',
|
|
367
|
-
'route',
|
|
368
|
-
[
|
|
369
|
-
{
|
|
370
|
-
name: 'CompA',
|
|
371
|
-
category: 'Visual',
|
|
372
|
-
categoryType: 'Visual',
|
|
373
|
-
dependencies: new Set(),
|
|
374
|
-
size: 100,
|
|
375
|
-
js: 'class CompA extends HTMLElement {}\nwindow.CompA = CompA;\nreturn CompA;',
|
|
376
|
-
html: '',
|
|
377
|
-
css: '',
|
|
378
|
-
hoistedImports: ["import foo from '/public/a.js';"]
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
name: 'CompB',
|
|
382
|
-
category: 'Visual',
|
|
383
|
-
categoryType: 'Visual',
|
|
384
|
-
dependencies: new Set(),
|
|
385
|
-
size: 100,
|
|
386
|
-
js: 'class CompB extends HTMLElement {}\nwindow.CompB = CompB;\nreturn CompB;',
|
|
387
|
-
html: '',
|
|
388
|
-
css: '',
|
|
389
|
-
hoistedImports: ["import foo from '/public/b.js';"]
|
|
390
|
-
}
|
|
391
|
-
],
|
|
392
|
-
'/test'
|
|
393
|
-
);
|
|
394
|
-
}, /Hoisted import binding collision: foo/);
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
test('generateBundleFileContent throws on reserved identifier collision', () => {
|
|
398
|
-
const generator = new BundleGenerator(import.meta.url, {
|
|
399
|
-
components: [],
|
|
400
|
-
routes: [],
|
|
401
|
-
metrics: {
|
|
402
|
-
totalComponents: 0,
|
|
403
|
-
totalRoutes: 0,
|
|
404
|
-
sharedPercentage: 0,
|
|
405
|
-
totalSize: 0
|
|
406
|
-
}
|
|
407
|
-
}, { output: 'src' });
|
|
408
|
-
|
|
409
|
-
assert.throws(() => {
|
|
410
|
-
generator.generateBundleFileContent(
|
|
411
|
-
'slice-bundle.test.js',
|
|
412
|
-
'route',
|
|
413
|
-
[{
|
|
414
|
-
name: 'CompMeta',
|
|
415
|
-
category: 'Visual',
|
|
416
|
-
categoryType: 'Visual',
|
|
417
|
-
dependencies: new Set(),
|
|
418
|
-
size: 100,
|
|
419
|
-
js: 'class CompMeta extends HTMLElement {}\nwindow.CompMeta = CompMeta;\nreturn CompMeta;',
|
|
420
|
-
html: '',
|
|
421
|
-
css: '',
|
|
422
|
-
hoistedImports: ["import SLICE_BUNDLE_META from '/public/meta.js';"]
|
|
423
|
-
}],
|
|
424
|
-
'/test'
|
|
425
|
-
);
|
|
426
|
-
}, /reserved identifier collision: SLICE_BUNDLE_META/);
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
test('generateBundleConfig emits vendor-shared metadata and route dependency graph edges', () => {
|
|
430
|
-
const generator = new BundleGenerator(import.meta.url, {
|
|
431
|
-
components: [],
|
|
432
|
-
routes: [],
|
|
433
|
-
metrics: {
|
|
434
|
-
totalComponents: 0,
|
|
435
|
-
totalRoutes: 1,
|
|
436
|
-
sharedPercentage: 0,
|
|
437
|
-
totalSize: 0
|
|
438
|
-
}
|
|
439
|
-
}, { output: 'src' });
|
|
440
|
-
|
|
441
|
-
generator.bundles.routes = {
|
|
442
|
-
docs: {
|
|
443
|
-
path: '/docs',
|
|
444
|
-
components: [{ name: 'DocumentationPage' }],
|
|
445
|
-
size: 100,
|
|
446
|
-
file: 'slice-bundle.docs.js'
|
|
447
|
-
}
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
generator.vendorShared.sharedDependencySet = new Set(['App/documentationRoutes.js']);
|
|
451
|
-
generator.vendorShared.bundleKeysUsingSharedDependencies = new Set(['docs']);
|
|
452
|
-
generator.vendorShared.bundle = {
|
|
453
|
-
file: 'slice-bundle.vendor-shared.js',
|
|
454
|
-
size: 2048,
|
|
455
|
-
hash: 'deadbeef',
|
|
456
|
-
integrity: 'sha256:deadbeef'
|
|
457
|
-
};
|
|
458
|
-
|
|
459
|
-
const config = generator.generateBundleConfig(null);
|
|
460
|
-
|
|
461
|
-
assert.equal(config.bundles.vendorShared.bundleKey, 'vendor-shared');
|
|
462
|
-
assert.equal(config.bundles.vendorShared.type, 'vendor-shared');
|
|
463
|
-
assert.equal(config.bundles.vendorShared.dependencyCount, 1);
|
|
464
|
-
assert.deepEqual(config.bundles.routes.docs.dependencies, ['critical', 'vendor-shared']);
|
|
465
|
-
assert.deepEqual(config.routeBundles['/docs'], ['critical', 'vendor-shared', 'docs']);
|
|
466
|
-
assert.deepEqual(config.routeDependencyGraph['/docs'].edges, [
|
|
467
|
-
{ from: 'critical', to: 'docs' },
|
|
468
|
-
{ from: 'vendor-shared', to: 'docs' }
|
|
469
|
-
]);
|
|
470
|
-
});
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import BundleGenerator from '../commands/utils/bundling/BundleGenerator.js';
|
|
4
|
+
|
|
5
|
+
test('generated V2 bundle includes meta and registerAll exports', () => {
|
|
6
|
+
const generator = new BundleGenerator(import.meta.url, {
|
|
7
|
+
components: [],
|
|
8
|
+
routes: [],
|
|
9
|
+
metrics: {
|
|
10
|
+
totalComponents: 0,
|
|
11
|
+
totalRoutes: 0,
|
|
12
|
+
sharedPercentage: 0,
|
|
13
|
+
totalSize: 0
|
|
14
|
+
}
|
|
15
|
+
}, { output: 'src' });
|
|
16
|
+
|
|
17
|
+
const source = generator.generateBundleFileContent(
|
|
18
|
+
'slice-bundle.test.js',
|
|
19
|
+
'route',
|
|
20
|
+
[{ name: 'Button', category: 'Visual', categoryType: 'Visual', dependencies: new Set(), size: 100, js: '', html: '', css: '' }],
|
|
21
|
+
'/test'
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
assert.match(source, /export const SLICE_BUNDLE_META/);
|
|
25
|
+
assert.match(source, /export async function registerAll\(/);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('generateBundleFileContent deduplicates components by name', () => {
|
|
29
|
+
const generator = new BundleGenerator(import.meta.url, {
|
|
30
|
+
components: [],
|
|
31
|
+
routes: [],
|
|
32
|
+
metrics: {
|
|
33
|
+
totalComponents: 0,
|
|
34
|
+
totalRoutes: 0,
|
|
35
|
+
sharedPercentage: 0,
|
|
36
|
+
totalSize: 0
|
|
37
|
+
}
|
|
38
|
+
}, { output: 'src' });
|
|
39
|
+
|
|
40
|
+
const source = generator.generateBundleFileContent(
|
|
41
|
+
'slice-bundle.test.js',
|
|
42
|
+
'route',
|
|
43
|
+
[
|
|
44
|
+
{ name: 'Button', category: 'Visual', categoryType: 'Visual', dependencies: new Set(), size: 100, js: '', html: '', css: '' },
|
|
45
|
+
{ name: 'Button', category: 'Visual', categoryType: 'Visual', dependencies: new Set(), size: 100, js: '', html: '', css: '' }
|
|
46
|
+
],
|
|
47
|
+
'/test'
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
assert.equal((source.match(/const SLICE_CLASS_FACTORY_SliceComponent_Button/g) || []).length, 1);
|
|
51
|
+
assert.equal((source.match(/if \(!controller\.classes\.has\("Button"\)\) \{\s*controller\.classes\.set\("Button", SLICE_CLASS_FACTORY_SliceComponent_Button\(\)\);\s*\}/g) || []).length, 1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('registerAll guards class, template, style, and category writes', () => {
|
|
55
|
+
const generator = new BundleGenerator(import.meta.url, {
|
|
56
|
+
components: [],
|
|
57
|
+
routes: [],
|
|
58
|
+
metrics: {
|
|
59
|
+
totalComponents: 0,
|
|
60
|
+
totalRoutes: 0,
|
|
61
|
+
sharedPercentage: 0,
|
|
62
|
+
totalSize: 0
|
|
63
|
+
}
|
|
64
|
+
}, { output: 'src' });
|
|
65
|
+
|
|
66
|
+
const source = generator.generateBundleFileContent(
|
|
67
|
+
'slice-bundle.test.js',
|
|
68
|
+
'route',
|
|
69
|
+
[{ name: 'Button', category: 'Visual', categoryType: 'Visual', dependencies: new Set(), size: 100, js: '', html: '<button>ok</button>', css: '.btn{}' }],
|
|
70
|
+
'/test'
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
assert.match(source, /if \(!controller\.classes\.has\("Button"\)\) \{\s*controller\.classes\.set\("Button", SLICE_CLASS_FACTORY_SliceComponent_Button\(\)\);\s*\}/);
|
|
74
|
+
assert.match(source, /if \(!controller\.templates\.has\("Button"\)\) \{\s*controller\.templates\.set\("Button", __templateElement_SliceComponent_Button\);\s*\}/);
|
|
75
|
+
assert.match(source, /if \(!stylesManager\.__sliceRegisteredComponentStyles\) \{\s*stylesManager\.__sliceRegisteredComponentStyles = new Set\(\);\s*\}/);
|
|
76
|
+
assert.match(source, /if \(!stylesManager\.__sliceRegisteredComponentStyles\.has\("Button"\)\) \{\s*stylesManager\.registerComponentStyles\("Button", ".*"\);\s*stylesManager\.__sliceRegisteredComponentStyles\.add\("Button"\);\s*\}/);
|
|
77
|
+
assert.match(source, /if \(!controller\.componentCategories\.has\("Button"\)\) \{\s*controller\.componentCategories\.set\("Button", "Visual"\);\s*\}/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('registerAll stores templates as template elements', () => {
|
|
81
|
+
const generator = new BundleGenerator(import.meta.url, {
|
|
82
|
+
components: [],
|
|
83
|
+
routes: [],
|
|
84
|
+
metrics: {
|
|
85
|
+
totalComponents: 0,
|
|
86
|
+
totalRoutes: 0,
|
|
87
|
+
sharedPercentage: 0,
|
|
88
|
+
totalSize: 0
|
|
89
|
+
}
|
|
90
|
+
}, { output: 'src' });
|
|
91
|
+
|
|
92
|
+
const source = generator.generateBundleFileContent(
|
|
93
|
+
'slice-bundle.test.js',
|
|
94
|
+
'route',
|
|
95
|
+
[{ name: 'DocumentationPage', category: 'AppComponents', categoryType: 'Visual', dependencies: new Set(), size: 100, js: '', html: '<div>docs</div>', css: '' }],
|
|
96
|
+
'/docs'
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
assert.match(source, /const __templateElement_SliceComponent_DocumentationPage = document\.createElement\('template'\);/);
|
|
100
|
+
assert.match(source, /__templateElement_SliceComponent_DocumentationPage\.innerHTML = "<div>docs<\/div>";/);
|
|
101
|
+
assert.match(source, /controller\.templates\.set\("DocumentationPage", __templateElement_SliceComponent_DocumentationPage\);/);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('bundle output inlines dependency modules and binds imported symbols in class factories', () => {
|
|
105
|
+
const generator = new BundleGenerator(import.meta.url, {
|
|
106
|
+
components: [],
|
|
107
|
+
routes: [],
|
|
108
|
+
metrics: {
|
|
109
|
+
totalComponents: 0,
|
|
110
|
+
totalRoutes: 0,
|
|
111
|
+
sharedPercentage: 0,
|
|
112
|
+
totalSize: 0
|
|
113
|
+
}
|
|
114
|
+
}, { output: 'src' });
|
|
115
|
+
|
|
116
|
+
const source = generator.generateBundleFileContent(
|
|
117
|
+
'slice-bundle.test.js',
|
|
118
|
+
'route',
|
|
119
|
+
[{
|
|
120
|
+
name: 'DocumentationPage',
|
|
121
|
+
category: 'AppComponents',
|
|
122
|
+
categoryType: 'Visual',
|
|
123
|
+
dependencies: new Set(),
|
|
124
|
+
size: 100,
|
|
125
|
+
js: 'class DocumentationPage extends HTMLElement { connectedCallback(){ return documentationRoutes.length; } }\nwindow.DocumentationPage = DocumentationPage;\nreturn DocumentationPage;',
|
|
126
|
+
html: '',
|
|
127
|
+
css: '',
|
|
128
|
+
externalDependencies: {
|
|
129
|
+
'App/documentationRoutes.js': {
|
|
130
|
+
content: 'export const documentationRoutes = ["/docs"];',
|
|
131
|
+
bindings: [{ type: 'named', importedName: 'documentationRoutes', localName: 'documentationRoutes' }]
|
|
132
|
+
},
|
|
133
|
+
'App/purify.js': {
|
|
134
|
+
content: 'export const purify = (value) => value;',
|
|
135
|
+
bindings: [{ type: 'default', importedName: 'default', localName: 'purify' }]
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}],
|
|
139
|
+
'/docs'
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
assert.match(source, /const SLICE_BUNDLE_DEPENDENCIES = \{\};/);
|
|
143
|
+
assert.match(source, /const __sliceSharedDeps = typeof window !== 'undefined' \? \(window\.__SLICE_SHARED_DEPS__ \|\| \{\}\) : \{\};/);
|
|
144
|
+
assert.match(source, /function __sliceResolveDefaultExport\(dep, depName, preferredKey\) \{/);
|
|
145
|
+
assert.match(source, /SLICE_BUNDLE_DEPENDENCIES\["App\/documentationRoutes\.js"\] = __sliceDepExports0;/);
|
|
146
|
+
assert.match(source, /const documentationRoutes = __sliceResolveBundleDependency\("App\/documentationRoutes\.js"\)\.documentationRoutes;/);
|
|
147
|
+
assert.match(source, /const purify = __sliceResolveDefaultExport\(__sliceResolveBundleDependency\("App\/purify\.js"\), "App\/purify\.js", "purifyData"\);/);
|
|
148
|
+
assert.doesNotMatch(source, /\.default !== undefined \?/);
|
|
149
|
+
assert.doesNotMatch(source, /SLICE_BUNDLE_DEPENDENCIES\["App\/purify\.js"\]\.purifyData/);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('route bundle omits extracted vendor-shared modules from inline dependency block and keeps bindings', () => {
|
|
153
|
+
const generator = new BundleGenerator(import.meta.url, {
|
|
154
|
+
components: [],
|
|
155
|
+
routes: [],
|
|
156
|
+
metrics: {
|
|
157
|
+
totalComponents: 0,
|
|
158
|
+
totalRoutes: 0,
|
|
159
|
+
sharedPercentage: 0,
|
|
160
|
+
totalSize: 0
|
|
161
|
+
}
|
|
162
|
+
}, { output: 'src' });
|
|
163
|
+
|
|
164
|
+
generator.vendorShared.sharedDependencySet = new Set(['App/documentationRoutes.js']);
|
|
165
|
+
|
|
166
|
+
const source = generator.generateBundleFileContent(
|
|
167
|
+
'slice-bundle.test.js',
|
|
168
|
+
'route',
|
|
169
|
+
[{
|
|
170
|
+
name: 'DocumentationPage',
|
|
171
|
+
category: 'AppComponents',
|
|
172
|
+
categoryType: 'Visual',
|
|
173
|
+
dependencies: new Set(),
|
|
174
|
+
size: 100,
|
|
175
|
+
js: 'class DocumentationPage extends HTMLElement { connectedCallback(){ return [documentationRoutes.length, docsNs.documentationRoutes.length, purify(1)].length; } }\nwindow.DocumentationPage = DocumentationPage;\nreturn DocumentationPage;',
|
|
176
|
+
html: '',
|
|
177
|
+
css: '',
|
|
178
|
+
externalDependencies: {
|
|
179
|
+
'App/documentationRoutes.js': {
|
|
180
|
+
content: 'export const documentationRoutes = ["/docs"];',
|
|
181
|
+
bindings: [
|
|
182
|
+
{ type: 'named', importedName: 'documentationRoutes', localName: 'documentationRoutes' },
|
|
183
|
+
{ type: 'namespace', localName: 'docsNs' }
|
|
184
|
+
]
|
|
185
|
+
},
|
|
186
|
+
'App/purify.js': {
|
|
187
|
+
content: 'export default (value) => value;',
|
|
188
|
+
bindings: [{ type: 'default', importedName: 'default', localName: 'purify' }]
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}],
|
|
192
|
+
'/docs'
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
assert.match(source, /const __sliceSharedDeps = typeof window !== 'undefined' \? \(window\.__SLICE_SHARED_DEPS__ \|\| \{\}\) : \{\};/);
|
|
196
|
+
assert.match(source, /const __sliceResolveBundleDependency = \(depName\) => Object\.prototype\.hasOwnProperty\.call\(__sliceSharedDeps, depName\) \? __sliceSharedDeps\[depName\] : SLICE_BUNDLE_DEPENDENCIES\[depName\];/);
|
|
197
|
+
assert.doesNotMatch(source, /SLICE_BUNDLE_DEPENDENCIES\["App\/documentationRoutes\.js"\] = __sliceDepExports\d+;/);
|
|
198
|
+
assert.match(source, /SLICE_BUNDLE_DEPENDENCIES\["App\/purify\.js"\] = __sliceDepExports\d+;/);
|
|
199
|
+
assert.match(source, /const documentationRoutes = __sliceResolveBundleDependency\("App\/documentationRoutes\.js"\)\.documentationRoutes;/);
|
|
200
|
+
assert.match(source, /const docsNs = __sliceResolveBundleDependency\("App\/documentationRoutes\.js"\);/);
|
|
201
|
+
assert.match(source, /const purify = __sliceResolveDefaultExport\(__sliceResolveBundleDependency\("App\/purify\.js"\), "App\/purify\.js", "purifyData"\);/);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('route bundle emits guarded shared deps resolver for non-browser contexts', () => {
|
|
205
|
+
const generator = new BundleGenerator(import.meta.url, {
|
|
206
|
+
components: [],
|
|
207
|
+
routes: [],
|
|
208
|
+
metrics: {
|
|
209
|
+
totalComponents: 0,
|
|
210
|
+
totalRoutes: 0,
|
|
211
|
+
sharedPercentage: 0,
|
|
212
|
+
totalSize: 0
|
|
213
|
+
}
|
|
214
|
+
}, { output: 'src' });
|
|
215
|
+
|
|
216
|
+
const source = generator.generateBundleFileContent(
|
|
217
|
+
'slice-bundle.test.js',
|
|
218
|
+
'route',
|
|
219
|
+
[{
|
|
220
|
+
name: 'DocsPage',
|
|
221
|
+
category: 'AppComponents',
|
|
222
|
+
categoryType: 'Visual',
|
|
223
|
+
dependencies: new Set(),
|
|
224
|
+
size: 100,
|
|
225
|
+
js: 'class DocsPage extends HTMLElement {}\nwindow.DocsPage = DocsPage;\nreturn DocsPage;',
|
|
226
|
+
html: '',
|
|
227
|
+
css: '',
|
|
228
|
+
externalDependencies: {
|
|
229
|
+
'App/deps.js': {
|
|
230
|
+
content: 'export const value = 1;',
|
|
231
|
+
bindings: [{ type: 'named', importedName: 'value', localName: 'value' }]
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}],
|
|
235
|
+
'/docs'
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
assert.match(source, /const __sliceSharedDeps = typeof window !== 'undefined' \? \(window\.__SLICE_SHARED_DEPS__ \|\| \{\}\) : \{\};/);
|
|
239
|
+
assert.match(source, /const __sliceResolveBundleDependency = \(depName\) => Object\.prototype\.hasOwnProperty\.call\(__sliceSharedDeps, depName\) \? __sliceSharedDeps\[depName\] : SLICE_BUNDLE_DEPENDENCIES\[depName\];/);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('non-route bundle does not emit shared resolver block', () => {
|
|
243
|
+
const generator = new BundleGenerator(import.meta.url, {
|
|
244
|
+
components: [],
|
|
245
|
+
routes: [],
|
|
246
|
+
metrics: {
|
|
247
|
+
totalComponents: 0,
|
|
248
|
+
totalRoutes: 0,
|
|
249
|
+
sharedPercentage: 0,
|
|
250
|
+
totalSize: 0
|
|
251
|
+
}
|
|
252
|
+
}, { output: 'src' });
|
|
253
|
+
|
|
254
|
+
const source = generator.generateBundleFileContent(
|
|
255
|
+
'slice-bundle.framework.js',
|
|
256
|
+
'framework',
|
|
257
|
+
[{
|
|
258
|
+
name: 'FrameworkComp',
|
|
259
|
+
category: 'Framework',
|
|
260
|
+
categoryType: 'Visual',
|
|
261
|
+
dependencies: new Set(),
|
|
262
|
+
size: 100,
|
|
263
|
+
js: 'class FrameworkComp extends HTMLElement {}\nwindow.FrameworkComp = FrameworkComp;\nreturn FrameworkComp;',
|
|
264
|
+
html: '',
|
|
265
|
+
css: '',
|
|
266
|
+
externalDependencies: {
|
|
267
|
+
'App/frameworkDep.js': {
|
|
268
|
+
content: 'export const dep = 1;',
|
|
269
|
+
bindings: [{ type: 'named', importedName: 'dep', localName: 'dep' }]
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}],
|
|
273
|
+
null
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
assert.doesNotMatch(source, /const __sliceSharedDeps = /);
|
|
277
|
+
assert.doesNotMatch(source, /const __sliceResolveBundleDependency = /);
|
|
278
|
+
assert.match(source, /const dep = SLICE_BUNDLE_DEPENDENCIES\["App\/frameworkDep\.js"\]\.dep;/);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('route bundle keeps local fallback binding when shared map is unavailable', () => {
|
|
282
|
+
const generator = new BundleGenerator(import.meta.url, {
|
|
283
|
+
components: [],
|
|
284
|
+
routes: [],
|
|
285
|
+
metrics: {
|
|
286
|
+
totalComponents: 0,
|
|
287
|
+
totalRoutes: 0,
|
|
288
|
+
sharedPercentage: 0,
|
|
289
|
+
totalSize: 0
|
|
290
|
+
}
|
|
291
|
+
}, { output: 'src' });
|
|
292
|
+
|
|
293
|
+
const source = generator.generateBundleFileContent(
|
|
294
|
+
'slice-bundle.test.js',
|
|
295
|
+
'route',
|
|
296
|
+
[{
|
|
297
|
+
name: 'FallbackComp',
|
|
298
|
+
category: 'Visual',
|
|
299
|
+
categoryType: 'Visual',
|
|
300
|
+
dependencies: new Set(),
|
|
301
|
+
size: 100,
|
|
302
|
+
js: 'class FallbackComp extends HTMLElement {}\nwindow.FallbackComp = FallbackComp;\nreturn FallbackComp;',
|
|
303
|
+
html: '',
|
|
304
|
+
css: '',
|
|
305
|
+
externalDependencies: {
|
|
306
|
+
'App/local.js': {
|
|
307
|
+
content: 'export const local = 1;',
|
|
308
|
+
bindings: [{ type: 'named', importedName: 'local', localName: 'local' }]
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}],
|
|
312
|
+
'/fallback'
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
assert.match(source, /const __sliceResolveBundleDependency = \(depName\) => Object\.prototype\.hasOwnProperty\.call\(__sliceSharedDeps, depName\) \? __sliceSharedDeps\[depName\] : SLICE_BUNDLE_DEPENDENCIES\[depName\];/);
|
|
316
|
+
assert.match(source, /const local = __sliceResolveBundleDependency\("App\/local\.js"\)\.local;/);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('bundle output hoists allowed absolute imports to module top-level', () => {
|
|
320
|
+
const generator = new BundleGenerator(import.meta.url, {
|
|
321
|
+
components: [],
|
|
322
|
+
routes: [],
|
|
323
|
+
metrics: {
|
|
324
|
+
totalComponents: 0,
|
|
325
|
+
totalRoutes: 0,
|
|
326
|
+
sharedPercentage: 0,
|
|
327
|
+
totalSize: 0
|
|
328
|
+
}
|
|
329
|
+
}, { output: 'src' });
|
|
330
|
+
|
|
331
|
+
const source = generator.generateBundleFileContent(
|
|
332
|
+
'slice-bundle.test.js',
|
|
333
|
+
'route',
|
|
334
|
+
[{
|
|
335
|
+
name: 'HeroCard',
|
|
336
|
+
category: 'Visual',
|
|
337
|
+
categoryType: 'Visual',
|
|
338
|
+
dependencies: new Set(),
|
|
339
|
+
size: 100,
|
|
340
|
+
js: 'class HeroCard extends HTMLElement {}\nwindow.HeroCard = HeroCard;\nreturn HeroCard;',
|
|
341
|
+
html: '',
|
|
342
|
+
css: '',
|
|
343
|
+
hoistedImports: ["import hero from '/public/hero.js';"]
|
|
344
|
+
}],
|
|
345
|
+
'/test'
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
assert.match(source, /import hero from '\/public\/hero\.js';/);
|
|
349
|
+
assert.doesNotMatch(source, /SLICE_CLASS_FACTORY_SliceComponent_HeroCard = \(\) => \{[\s\S]*import hero from '\/public\/hero\.js';/);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test('generateBundleFileContent throws on hoisted import local binding collisions', () => {
|
|
353
|
+
const generator = new BundleGenerator(import.meta.url, {
|
|
354
|
+
components: [],
|
|
355
|
+
routes: [],
|
|
356
|
+
metrics: {
|
|
357
|
+
totalComponents: 0,
|
|
358
|
+
totalRoutes: 0,
|
|
359
|
+
sharedPercentage: 0,
|
|
360
|
+
totalSize: 0
|
|
361
|
+
}
|
|
362
|
+
}, { output: 'src' });
|
|
363
|
+
|
|
364
|
+
assert.throws(() => {
|
|
365
|
+
generator.generateBundleFileContent(
|
|
366
|
+
'slice-bundle.test.js',
|
|
367
|
+
'route',
|
|
368
|
+
[
|
|
369
|
+
{
|
|
370
|
+
name: 'CompA',
|
|
371
|
+
category: 'Visual',
|
|
372
|
+
categoryType: 'Visual',
|
|
373
|
+
dependencies: new Set(),
|
|
374
|
+
size: 100,
|
|
375
|
+
js: 'class CompA extends HTMLElement {}\nwindow.CompA = CompA;\nreturn CompA;',
|
|
376
|
+
html: '',
|
|
377
|
+
css: '',
|
|
378
|
+
hoistedImports: ["import foo from '/public/a.js';"]
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: 'CompB',
|
|
382
|
+
category: 'Visual',
|
|
383
|
+
categoryType: 'Visual',
|
|
384
|
+
dependencies: new Set(),
|
|
385
|
+
size: 100,
|
|
386
|
+
js: 'class CompB extends HTMLElement {}\nwindow.CompB = CompB;\nreturn CompB;',
|
|
387
|
+
html: '',
|
|
388
|
+
css: '',
|
|
389
|
+
hoistedImports: ["import foo from '/public/b.js';"]
|
|
390
|
+
}
|
|
391
|
+
],
|
|
392
|
+
'/test'
|
|
393
|
+
);
|
|
394
|
+
}, /Hoisted import binding collision: foo/);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test('generateBundleFileContent throws on reserved identifier collision', () => {
|
|
398
|
+
const generator = new BundleGenerator(import.meta.url, {
|
|
399
|
+
components: [],
|
|
400
|
+
routes: [],
|
|
401
|
+
metrics: {
|
|
402
|
+
totalComponents: 0,
|
|
403
|
+
totalRoutes: 0,
|
|
404
|
+
sharedPercentage: 0,
|
|
405
|
+
totalSize: 0
|
|
406
|
+
}
|
|
407
|
+
}, { output: 'src' });
|
|
408
|
+
|
|
409
|
+
assert.throws(() => {
|
|
410
|
+
generator.generateBundleFileContent(
|
|
411
|
+
'slice-bundle.test.js',
|
|
412
|
+
'route',
|
|
413
|
+
[{
|
|
414
|
+
name: 'CompMeta',
|
|
415
|
+
category: 'Visual',
|
|
416
|
+
categoryType: 'Visual',
|
|
417
|
+
dependencies: new Set(),
|
|
418
|
+
size: 100,
|
|
419
|
+
js: 'class CompMeta extends HTMLElement {}\nwindow.CompMeta = CompMeta;\nreturn CompMeta;',
|
|
420
|
+
html: '',
|
|
421
|
+
css: '',
|
|
422
|
+
hoistedImports: ["import SLICE_BUNDLE_META from '/public/meta.js';"]
|
|
423
|
+
}],
|
|
424
|
+
'/test'
|
|
425
|
+
);
|
|
426
|
+
}, /reserved identifier collision: SLICE_BUNDLE_META/);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test('generateBundleConfig emits vendor-shared metadata and route dependency graph edges', () => {
|
|
430
|
+
const generator = new BundleGenerator(import.meta.url, {
|
|
431
|
+
components: [],
|
|
432
|
+
routes: [],
|
|
433
|
+
metrics: {
|
|
434
|
+
totalComponents: 0,
|
|
435
|
+
totalRoutes: 1,
|
|
436
|
+
sharedPercentage: 0,
|
|
437
|
+
totalSize: 0
|
|
438
|
+
}
|
|
439
|
+
}, { output: 'src' });
|
|
440
|
+
|
|
441
|
+
generator.bundles.routes = {
|
|
442
|
+
docs: {
|
|
443
|
+
path: '/docs',
|
|
444
|
+
components: [{ name: 'DocumentationPage' }],
|
|
445
|
+
size: 100,
|
|
446
|
+
file: 'slice-bundle.docs.js'
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
generator.vendorShared.sharedDependencySet = new Set(['App/documentationRoutes.js']);
|
|
451
|
+
generator.vendorShared.bundleKeysUsingSharedDependencies = new Set(['docs']);
|
|
452
|
+
generator.vendorShared.bundle = {
|
|
453
|
+
file: 'slice-bundle.vendor-shared.js',
|
|
454
|
+
size: 2048,
|
|
455
|
+
hash: 'deadbeef',
|
|
456
|
+
integrity: 'sha256:deadbeef'
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
const config = generator.generateBundleConfig(null);
|
|
460
|
+
|
|
461
|
+
assert.equal(config.bundles.vendorShared.bundleKey, 'vendor-shared');
|
|
462
|
+
assert.equal(config.bundles.vendorShared.type, 'vendor-shared');
|
|
463
|
+
assert.equal(config.bundles.vendorShared.dependencyCount, 1);
|
|
464
|
+
assert.deepEqual(config.bundles.routes.docs.dependencies, ['critical', 'vendor-shared']);
|
|
465
|
+
assert.deepEqual(config.routeBundles['/docs'], ['critical', 'vendor-shared', 'docs']);
|
|
466
|
+
assert.deepEqual(config.routeDependencyGraph['/docs'].edges, [
|
|
467
|
+
{ from: 'critical', to: 'docs' },
|
|
468
|
+
{ from: 'vendor-shared', to: 'docs' }
|
|
469
|
+
]);
|
|
470
|
+
});
|