slicejs-cli 3.5.1 → 3.6.1
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 +81 -26
- package/client.js +73 -23
- package/commands/buildProduction/buildProduction.js +6 -3
- package/commands/doctor/doctor.js +68 -3
- package/commands/getComponent/getComponent.js +33 -25
- package/commands/init/init.js +176 -49
- package/commands/utils/PackageManager.js +148 -0
- package/commands/utils/VersionChecker.js +6 -4
- package/commands/utils/sliceScripts.js +23 -0
- package/commands/utils/updateManager.js +54 -35
- package/package.json +12 -1
- package/post.js +13 -19
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -29
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -25
- package/.github/pull_request_template.md +0 -22
- package/.github/workflows/ci.yml +0 -43
- package/AGENTS.md +0 -247
- package/CODE_OF_CONDUCT.md +0 -126
- package/ECOSYSTEM.md +0 -9
- package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +0 -182
- package/playwright.config.js +0 -51
- package/tests/build-command-integration.test.js +0 -87
- package/tests/build-production-e2e.test.js +0 -140
- package/tests/builder-edge-cases.test.js +0 -322
- package/tests/bundle-generate-e2e.test.js +0 -115
- package/tests/bundle-generator.test.js +0 -691
- package/tests/bundle-v2-register-output.test.js +0 -470
- package/tests/bundling-dependency-edges.test.js +0 -127
- package/tests/bundling-imports-unit.test.js +0 -267
- package/tests/client-launcher-contract.test.js +0 -211
- package/tests/client-update-flow-contract.test.js +0 -272
- package/tests/commands-component-crud.test.js +0 -102
- package/tests/commands-doctor.test.js +0 -80
- package/tests/commands-version-checker.test.js +0 -37
- package/tests/component-registry-parse.test.js +0 -34
- package/tests/dependency-analyzer.test.js +0 -24
- package/tests/e2e/bundles.spec.js +0 -91
- package/tests/e2e/dependency-scenarios.spec.js +0 -56
- package/tests/e2e/fixtures/components/Service/FetchManager/FetchManager.js +0 -136
- package/tests/e2e/fixtures/components/Service/IndexedDbManager/IndexedDbManager.js +0 -149
- package/tests/e2e/fixtures/components/Service/LocalStorageManager/LocalStorageManager.js +0 -45
- package/tests/e2e/fixtures/components/Visual/Button/Button.css +0 -106
- package/tests/e2e/fixtures/components/Visual/Button/Button.html +0 -5
- package/tests/e2e/fixtures/components/Visual/Button/Button.js +0 -158
- package/tests/e2e/fixtures/components/Visual/Link/Link.js +0 -33
- package/tests/e2e/fixtures/components/Visual/Loading/Loading.css +0 -56
- package/tests/e2e/fixtures/components/Visual/Loading/Loading.html +0 -83
- package/tests/e2e/fixtures/components/Visual/Loading/Loading.js +0 -164
- package/tests/e2e/fixtures/components/Visual/MultiRoute/MultiRoute.js +0 -167
- package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.css +0 -116
- package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.html +0 -44
- package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.js +0 -180
- package/tests/e2e/fixtures/components/Visual/NotFound/NotFound.js +0 -20
- package/tests/e2e/fixtures/components/Visual/Route/Route.js +0 -181
- package/tests/e2e/fixtures/components/registry.json +0 -12
- package/tests/e2e/fixtures/vendor-components.mjs +0 -65
- package/tests/e2e/navigation.spec.js +0 -44
- package/tests/e2e/render.spec.js +0 -34
- package/tests/e2e/serve.mjs +0 -264
- package/tests/e2e/shared-deps.spec.js +0 -61
- package/tests/e2e/unminified.spec.js +0 -33
- package/tests/e2e-serve.test.js +0 -148
- package/tests/fixtures/components.js +0 -8
- package/tests/fixtures/sliceConfig.json +0 -74
- package/tests/getcomponent.test.js +0 -407
- package/tests/helpers/setup.js +0 -102
- package/tests/init-command-contract.test.js +0 -46
- package/tests/local-cli-delegation.test.js +0 -81
- package/tests/path-helper.test.js +0 -206
- package/tests/perf-budget.test.js +0 -86
- package/tests/postinstall-command.test.js +0 -72
- package/tests/types-breakage.test.js +0 -491
- package/tests/types-generator-errors.test.js +0 -361
- package/tests/types-generator.test.js +0 -346
- package/tests/update-manager-notifications.test.js +0 -88
|
@@ -1,470 +0,0 @@
|
|
|
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,127 +0,0 @@
|
|
|
1
|
-
import { test, describe } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { parse } from '@babel/parser';
|
|
4
|
-
import BundleGenerator from '../commands/utils/bundling/BundleGenerator.js';
|
|
5
|
-
|
|
6
|
-
const MODULE_URL = import.meta.url;
|
|
7
|
-
|
|
8
|
-
function makeGenerator() {
|
|
9
|
-
const gen = new BundleGenerator(MODULE_URL, null, {});
|
|
10
|
-
gen.sliceConfig = {};
|
|
11
|
-
return gen;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function evalSnippet(code) {
|
|
15
|
-
// eslint-disable-next-line no-new-func
|
|
16
|
-
return new Function(code)();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Build the dependency-exports object the bundler would emit for a single
|
|
20
|
-
// shared module, then read back its keys at runtime.
|
|
21
|
-
function evalDepExports(content, moduleName = 'shared/util.js') {
|
|
22
|
-
const gen = makeGenerator();
|
|
23
|
-
const block = gen.buildV2DependencyModuleBlockFromModules([{ name: moduleName, content }]);
|
|
24
|
-
// The block declares `const __sliceDepExports0 = {}` and fills it; expose it.
|
|
25
|
-
return evalSnippet(`${block}\nreturn typeof __sliceDepExports0 !== 'undefined' ? __sliceDepExports0 : {};`);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
describe('shared dependency module transforms', () => {
|
|
29
|
-
test('plain named exports resolve', () => {
|
|
30
|
-
const d = evalDepExports('export const a = 1;\nexport const b = 2;');
|
|
31
|
-
assert.equal(d.a, 1);
|
|
32
|
-
assert.equal(d.b, 2);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('multiline export list resolves every name', () => {
|
|
36
|
-
const d = evalDepExports('const a = 1;\nconst b = 2;\nconst c = 3;\nexport {\n a,\n b,\n c\n};');
|
|
37
|
-
assert.deepEqual([d.a, d.b, d.c], [1, 2, 3]);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('export { x as default } exposes a usable default', () => {
|
|
41
|
-
// A consumer doing `import def from '...'` resolves default via the runtime
|
|
42
|
-
// helper, which first checks `.default`. So the exports object should carry
|
|
43
|
-
// a `default` (or the basename-Data fallback) for the aliased-default form.
|
|
44
|
-
const d = evalDepExports('const x = 7;\nexport { x as default };', 'shared/util.js');
|
|
45
|
-
const resolvable = d.default !== undefined || d.utilData !== undefined;
|
|
46
|
-
assert.ok(resolvable, 'aliased default export is not resolvable by the bundle');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('destructuring export does not silently drop the binding', () => {
|
|
50
|
-
// `export const { a } = obj` is a valid ESM export; the consumer expects `a`.
|
|
51
|
-
const d = evalDepExports('const obj = { a: 5 };\nexport const { a } = obj;');
|
|
52
|
-
assert.equal(d.a, 5, 'destructured export binding was dropped');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test('exports that reference one another resolve at runtime', () => {
|
|
56
|
-
// A helper export referencing a constant export must still find it — the
|
|
57
|
-
// transform has to keep a usable local binding, not only the exports field.
|
|
58
|
-
const d = evalDepExports(
|
|
59
|
-
"export const TAG = 'x';\nexport function badge(label) { return '[' + TAG + '] ' + label; }"
|
|
60
|
-
);
|
|
61
|
-
assert.equal(d.TAG, 'x');
|
|
62
|
-
assert.equal(typeof d.badge, 'function');
|
|
63
|
-
assert.equal(d.badge('hi'), '[x] hi');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('a transitive dependency is inlined and bound (topological order)', () => {
|
|
67
|
-
const gen = makeGenerator();
|
|
68
|
-
// mid imports leaf; pass mid FIRST so the topological sort must reorder.
|
|
69
|
-
const block = gen.buildV2DependencyModuleBlockFromModules([
|
|
70
|
-
{
|
|
71
|
-
name: 'shared/mid.js',
|
|
72
|
-
content: "import { LEAF } from './leaf.js';\nexport function midValue() { return 'mid:' + LEAF; }",
|
|
73
|
-
moduleImports: [
|
|
74
|
-
{ depName: 'shared/leaf.js', bindings: [{ type: 'named', importedName: 'LEAF', localName: 'LEAF' }] },
|
|
75
|
-
],
|
|
76
|
-
},
|
|
77
|
-
{ name: 'shared/leaf.js', content: "export const LEAF = 'leaf-value';", moduleImports: [] },
|
|
78
|
-
]);
|
|
79
|
-
const deps = evalSnippet(`${block}\nreturn SLICE_BUNDLE_DEPENDENCIES;`);
|
|
80
|
-
assert.equal(deps['shared/leaf.js'].LEAF, 'leaf-value');
|
|
81
|
-
assert.equal(deps['shared/mid.js'].midValue(), 'mid:leaf-value');
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe('transitive dependencies of a shared module', () => {
|
|
86
|
-
function bundleWithDependency(depName, depContent, bindings) {
|
|
87
|
-
const gen = makeGenerator();
|
|
88
|
-
const comp = {
|
|
89
|
-
name: 'Widget',
|
|
90
|
-
category: 'Visual',
|
|
91
|
-
categoryType: 'Visual',
|
|
92
|
-
js: 'const C = class {};\nreturn C;',
|
|
93
|
-
hoistedImports: [],
|
|
94
|
-
html: '',
|
|
95
|
-
css: '',
|
|
96
|
-
externalDependencies: { [depName]: { content: depContent, bindings } },
|
|
97
|
-
size: 100,
|
|
98
|
-
};
|
|
99
|
-
return gen.generateBundleFileContent('slice-bundle.critical.js', 'critical', [comp]);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
test('a relative import inside a shared module must not leak into the bundle', () => {
|
|
103
|
-
const code = bundleWithDependency(
|
|
104
|
-
'shared/helper.js',
|
|
105
|
-
"import deep from './deep.js';\nexport const helper = () => deep;",
|
|
106
|
-
[{ type: 'named', importedName: 'helper', localName: 'helper' }]
|
|
107
|
-
);
|
|
108
|
-
// Whatever the strategy, the emitted bundle must not contain an unresolved
|
|
109
|
-
// relative import — it would 404 / fail to resolve in production.
|
|
110
|
-
assert.ok(
|
|
111
|
-
!/import\s+[^;]*from\s+['"]\.\.?\//.test(code),
|
|
112
|
-
'unresolved relative import leaked from a transitive dependency'
|
|
113
|
-
);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test('a bare import inside a shared module must not leak into the bundle', () => {
|
|
117
|
-
const code = bundleWithDependency(
|
|
118
|
-
'shared/helper.js',
|
|
119
|
-
"import 'side-effect-polyfill';\nexport const helper = () => 1;",
|
|
120
|
-
[{ type: 'named', importedName: 'helper', localName: 'helper' }]
|
|
121
|
-
);
|
|
122
|
-
assert.ok(
|
|
123
|
-
!/import\s+['"][^'"]+['"]/.test(code),
|
|
124
|
-
'unresolved bare import leaked from a transitive dependency'
|
|
125
|
-
);
|
|
126
|
-
});
|
|
127
|
-
});
|