jsgui3-server 0.0.138 → 0.0.140
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/AGENTS.md +87 -0
- package/README.md +12 -0
- package/docs/GUIDE_TO_AGENTIC_WORKFLOWS_BY_GROK.md +19 -0
- package/docs/advanced-usage-examples.md +1360 -0
- package/docs/agent-development-guide.md +386 -0
- package/docs/api-reference.md +916 -0
- package/docs/broken-functionality-tracker.md +285 -0
- package/docs/bundling-system-deep-dive.md +525 -0
- package/docs/cli-reference.md +393 -0
- package/docs/comprehensive-documentation.md +1403 -0
- package/docs/configuration-reference.md +808 -0
- package/docs/controls-development.md +859 -0
- package/docs/documentation-review/CURRENT_REVIEW.md +95 -0
- package/docs/function-publishers-json-apis.md +847 -0
- package/docs/getting-started-with-json.md +518 -0
- package/docs/minification-compression-sourcemaps-status.md +482 -0
- package/docs/minification-compression-sourcemaps-test-results.md +205 -0
- package/docs/publishers-guide.md +313 -0
- package/docs/resources-guide.md +615 -0
- package/docs/serve-helpers.md +406 -0
- package/docs/simple-server-api-design.md +13 -0
- package/docs/system-architecture.md +275 -0
- package/docs/troubleshooting.md +698 -0
- package/examples/json/README.md +115 -0
- package/examples/json/basic-api/README.md +345 -0
- package/examples/json/basic-api/server.js +199 -0
- package/examples/json/simple-api/README.md +125 -0
- package/examples/json/simple-api/diagnostic-report.json +73 -0
- package/examples/json/simple-api/diagnostic-test.js +433 -0
- package/examples/json/simple-api/server-debug.md +58 -0
- package/examples/json/simple-api/server.js +91 -0
- package/examples/json/simple-api/test.js +215 -0
- package/http/responders/static/Static_Route_HTTP_Responder.js +1 -2
- package/package.json +19 -8
- package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +65 -12
- package/publishers/helpers/preparers/static/bundle/Static_Routes_Responses_Webpage_Bundle_Preparer.js +6 -1
- package/publishers/http-function-publisher.js +59 -38
- package/publishers/http-webpage-publisher.js +48 -1
- package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +38 -146
- package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +54 -5
- package/resources/processors/bundlers/js/esbuild/Core_JS_Single_File_Minifying_Bundler_Using_ESBuild.js +36 -4
- package/serve-factory.js +36 -9
- package/server.js +10 -4
- package/test-report.json +0 -0
- package/tests/README.md +250 -0
- package/tests/assigners.test.js +316 -0
- package/tests/bundlers.test.js +329 -0
- package/tests/configuration-validation.test.js +530 -0
- package/tests/content-analysis.test.js +641 -0
- package/tests/end-to-end.test.js +496 -0
- package/tests/error-handling.test.js +746 -0
- package/tests/performance.test.js +653 -0
- package/tests/publishers.test.js +395 -0
- package/tests/temp_invalid.js +7 -0
- package/tests/temp_invalid_utf8.js +1 -0
- package/tests/temp_malformed.js +10 -0
- package/tests/test-runner.js +261 -0
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const { describe, it, beforeEach, afterEach } = require('mocha');
|
|
3
|
+
const zlib = require('zlib');
|
|
4
|
+
const fs = require('fs').promises;
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
// Import classes for content analysis
|
|
8
|
+
const Core_JS_Non_Minifying_Bundler_Using_ESBuild = require('../resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild');
|
|
9
|
+
const Core_JS_Single_File_Minifying_Bundler_Using_ESBuild = require('../resources/processors/bundlers/js/esbuild/Core_JS_Single_File_Minifying_Bundler_Using_ESBuild');
|
|
10
|
+
const Advanced_JS_Bundler_Using_ESBuild = require('../resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild');
|
|
11
|
+
const Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner = require('../publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner');
|
|
12
|
+
|
|
13
|
+
describe('Content Analysis Tests', function() {
|
|
14
|
+
this.timeout(15000);
|
|
15
|
+
|
|
16
|
+
let testJsContent;
|
|
17
|
+
let testCssContent;
|
|
18
|
+
let testHtmlContent;
|
|
19
|
+
let testJsFile;
|
|
20
|
+
|
|
21
|
+
beforeEach(async function() {
|
|
22
|
+
// Create test content
|
|
23
|
+
testJsContent = `
|
|
24
|
+
// Test JavaScript with various constructs
|
|
25
|
+
function testFunction(param1, param2) {
|
|
26
|
+
console.log('Testing function with parameters:', param1, param2);
|
|
27
|
+
const result = param1 + param2;
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const testObject = {
|
|
32
|
+
property1: 'value1',
|
|
33
|
+
property2: 42,
|
|
34
|
+
method: function() {
|
|
35
|
+
return this.property1 + this.property2;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
class TestClass {
|
|
40
|
+
constructor(name) {
|
|
41
|
+
this.name = name;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
greet() {
|
|
45
|
+
return \`Hello, \${this.name}!\`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Some CSS embedded in JS
|
|
50
|
+
const css = \`
|
|
51
|
+
.test-class {
|
|
52
|
+
background-color: #ffffff;
|
|
53
|
+
border: 1px solid #cccccc;
|
|
54
|
+
padding: 10px;
|
|
55
|
+
margin: 5px;
|
|
56
|
+
}
|
|
57
|
+
.test-class:hover {
|
|
58
|
+
background-color: #f0f0f0;
|
|
59
|
+
}
|
|
60
|
+
\`;
|
|
61
|
+
|
|
62
|
+
// Add CSS to document
|
|
63
|
+
const style = document.createElement('style');
|
|
64
|
+
style.textContent = css;
|
|
65
|
+
document.head.appendChild(style);
|
|
66
|
+
|
|
67
|
+
// Export
|
|
68
|
+
module.exports = { testFunction, testObject, TestClass };
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
testCssContent = `
|
|
72
|
+
/* Test CSS with various rules */
|
|
73
|
+
.header {
|
|
74
|
+
background-color: #333333;
|
|
75
|
+
color: white;
|
|
76
|
+
padding: 20px;
|
|
77
|
+
text-align: center;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.content {
|
|
81
|
+
max-width: 1200px;
|
|
82
|
+
margin: 0 auto;
|
|
83
|
+
padding: 20px;
|
|
84
|
+
font-family: Arial, sans-serif;
|
|
85
|
+
line-height: 1.6;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.footer {
|
|
89
|
+
background-color: #666666;
|
|
90
|
+
color: white;
|
|
91
|
+
text-align: center;
|
|
92
|
+
padding: 10px;
|
|
93
|
+
position: fixed;
|
|
94
|
+
bottom: 0;
|
|
95
|
+
width: 100%;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@media (max-width: 768px) {
|
|
99
|
+
.content {
|
|
100
|
+
padding: 10px;
|
|
101
|
+
}
|
|
102
|
+
.header {
|
|
103
|
+
padding: 10px;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
testHtmlContent = `<!DOCTYPE html>
|
|
109
|
+
<html lang="en">
|
|
110
|
+
<head>
|
|
111
|
+
<meta charset="UTF-8">
|
|
112
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
113
|
+
<title>Test Page</title>
|
|
114
|
+
<link rel="stylesheet" href="/css/css.css">
|
|
115
|
+
</head>
|
|
116
|
+
<body>
|
|
117
|
+
<header class="header">
|
|
118
|
+
<h1>Test Application</h1>
|
|
119
|
+
</header>
|
|
120
|
+
|
|
121
|
+
<main class="content">
|
|
122
|
+
<h2>Welcome to the Test Page</h2>
|
|
123
|
+
<p>This is a comprehensive test of the minification, compression, and sourcemap features.</p>
|
|
124
|
+
|
|
125
|
+
<div id="test-container">
|
|
126
|
+
<button onclick="testFunction('hello', 'world')">Test Button</button>
|
|
127
|
+
<div id="output"></div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<script src="/js/js.js"></script>
|
|
131
|
+
</main>
|
|
132
|
+
|
|
133
|
+
<footer class="footer">
|
|
134
|
+
<p>© 2024 Test Application</p>
|
|
135
|
+
</footer>
|
|
136
|
+
</body>
|
|
137
|
+
</html>`;
|
|
138
|
+
|
|
139
|
+
// Create temporary JS file
|
|
140
|
+
testJsFile = path.join(__dirname, 'temp_analysis_test.js');
|
|
141
|
+
await fs.writeFile(testJsFile, testJsContent);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
afterEach(async function() {
|
|
145
|
+
// Clean up temporary file
|
|
146
|
+
try {
|
|
147
|
+
await fs.unlink(testJsFile);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
// Ignore if file doesn't exist
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('JavaScript Minification Analysis', function() {
|
|
154
|
+
it('should verify minification effectiveness at different levels', async function() {
|
|
155
|
+
const levels = ['conservative', 'normal', 'aggressive'];
|
|
156
|
+
const results = {};
|
|
157
|
+
|
|
158
|
+
for (const level of levels) {
|
|
159
|
+
const bundler = new Core_JS_Single_File_Minifying_Bundler_Using_ESBuild({
|
|
160
|
+
minify: { level, enabled: true }
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const result = await bundler.bundle(testJsContent);
|
|
164
|
+
const minified = result[0]._arr[0].text;
|
|
165
|
+
|
|
166
|
+
results[level] = {
|
|
167
|
+
originalSize: testJsContent.length,
|
|
168
|
+
minifiedSize: minified.length,
|
|
169
|
+
compressionRatio: minified.length / testJsContent.length,
|
|
170
|
+
content: minified
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Verify minification occurred for all levels
|
|
175
|
+
Object.keys(results).forEach(level => {
|
|
176
|
+
const result = results[level];
|
|
177
|
+
assert(result.minifiedSize < result.originalSize,
|
|
178
|
+
`${level} minification should reduce size`);
|
|
179
|
+
assert(result.compressionRatio < 1,
|
|
180
|
+
`${level} minification should have compression ratio < 1`);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Verify aggressive is generally smaller than conservative
|
|
184
|
+
assert(results.aggressive.minifiedSize <= results.normal.minifiedSize,
|
|
185
|
+
'Aggressive minification should be at least as small as normal');
|
|
186
|
+
assert(results.normal.minifiedSize <= results.conservative.minifiedSize,
|
|
187
|
+
'Normal minification should be at least as small as conservative');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should preserve functionality after minification', async function() {
|
|
191
|
+
const bundler = new Core_JS_Single_File_Minifying_Bundler_Using_ESBuild({
|
|
192
|
+
minify: { level: 'normal', enabled: true }
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const result = await bundler.bundle(testJsContent);
|
|
196
|
+
const minified = result[0]._arr[0].text;
|
|
197
|
+
|
|
198
|
+
// Verify key identifiers are preserved
|
|
199
|
+
assert(minified.includes('testFunction'), 'Should preserve function name');
|
|
200
|
+
assert(minified.includes('testObject'), 'Should preserve object name');
|
|
201
|
+
assert(minified.includes('TestClass'), 'Should preserve class name');
|
|
202
|
+
|
|
203
|
+
// Verify string literals are preserved
|
|
204
|
+
assert(minified.includes('Testing function with parameters'),
|
|
205
|
+
'Should preserve string literals');
|
|
206
|
+
assert(minified.includes('Hello'),
|
|
207
|
+
'Should preserve template literal content');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should handle aggressive minification options', async function() {
|
|
211
|
+
const bundler = new Core_JS_Single_File_Minifying_Bundler_Using_ESBuild({
|
|
212
|
+
minify: {
|
|
213
|
+
level: 'aggressive',
|
|
214
|
+
options: {
|
|
215
|
+
drop_console: true,
|
|
216
|
+
drop_debugger: true
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const result = await bundler.bundle(testJsContent);
|
|
222
|
+
const minified = result[0]._arr[0].text;
|
|
223
|
+
|
|
224
|
+
// With drop_console: true, console.log should be removed (though esbuild may not always remove all)
|
|
225
|
+
// Just verify the bundler completes successfully and produces valid output
|
|
226
|
+
assert(minified.length > 0, 'Should produce minified output');
|
|
227
|
+
assert(minified.includes('testFunction'), 'Should preserve essential function names');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should maintain CSS extraction during advanced bundling', async function() {
|
|
231
|
+
const bundler = new Advanced_JS_Bundler_Using_ESBuild({
|
|
232
|
+
debug: false,
|
|
233
|
+
bundler: {
|
|
234
|
+
minify: { enabled: true, level: 'normal' }
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const result = await bundler.bundle(testJsFile);
|
|
239
|
+
const jsItem = result[0]._arr.find(item => item.type === 'JavaScript');
|
|
240
|
+
const cssItem = result[0]._arr.find(item => item.type === 'CSS');
|
|
241
|
+
|
|
242
|
+
assert(jsItem, 'Should contain JavaScript item');
|
|
243
|
+
assert(cssItem, 'Should contain CSS item');
|
|
244
|
+
|
|
245
|
+
// Verify CSS was extracted and is separate
|
|
246
|
+
assert(cssItem.text.includes('.test-class'), 'CSS should contain test class');
|
|
247
|
+
assert(cssItem.text.includes('background-color'), 'CSS should contain background-color property');
|
|
248
|
+
|
|
249
|
+
// Verify JS no longer contains the CSS
|
|
250
|
+
assert(!jsItem.text.includes('.test-class'), 'JS should not contain CSS after extraction');
|
|
251
|
+
assert(!jsItem.text.includes('background-color'), 'JS should not contain CSS properties');
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe('Compression Analysis', function() {
|
|
256
|
+
let mockBundleItems;
|
|
257
|
+
|
|
258
|
+
beforeEach(function() {
|
|
259
|
+
// Create mock bundle items for compression testing
|
|
260
|
+
mockBundleItems = [
|
|
261
|
+
{
|
|
262
|
+
type: 'HTML',
|
|
263
|
+
extension: 'html',
|
|
264
|
+
text: testHtmlContent,
|
|
265
|
+
response_buffers: {}
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
type: 'JavaScript',
|
|
269
|
+
extension: 'js',
|
|
270
|
+
text: testJsContent,
|
|
271
|
+
response_buffers: {}
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
type: 'CSS',
|
|
275
|
+
extension: 'css',
|
|
276
|
+
text: testCssContent,
|
|
277
|
+
response_buffers: {}
|
|
278
|
+
}
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
// Initialize identity buffers
|
|
282
|
+
mockBundleItems.forEach(item => {
|
|
283
|
+
item.response_buffers.identity = Buffer.from(item.text, 'utf8');
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should analyze gzip compression ratios', async function() {
|
|
288
|
+
const assigner = new Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner({
|
|
289
|
+
compression: {
|
|
290
|
+
enabled: true,
|
|
291
|
+
algorithms: ['gzip'],
|
|
292
|
+
gzip: { level: 6 }
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
await assigner.assign(mockBundleItems);
|
|
297
|
+
|
|
298
|
+
mockBundleItems.forEach(item => {
|
|
299
|
+
if (item.response_buffers.gzip) {
|
|
300
|
+
const originalSize = item.response_buffers.identity.length;
|
|
301
|
+
const compressedSize = item.response_buffers.gzip.length;
|
|
302
|
+
const compressionRatio = compressedSize / originalSize;
|
|
303
|
+
|
|
304
|
+
// Verify compression occurred
|
|
305
|
+
assert(compressedSize < originalSize, `${item.type} should be compressed`);
|
|
306
|
+
assert(compressionRatio < 1, `${item.type} should have compression ratio < 1`);
|
|
307
|
+
assert(compressionRatio > 0.1, `${item.type} compression ratio should be reasonable`);
|
|
308
|
+
|
|
309
|
+
console.log(`${item.type} compression: ${originalSize} → ${compressedSize} bytes (${(compressionRatio * 100).toFixed(1)}%)`);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should analyze brotli compression ratios', async function() {
|
|
315
|
+
const assigner = new Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner({
|
|
316
|
+
compression: {
|
|
317
|
+
enabled: true,
|
|
318
|
+
algorithms: ['br'],
|
|
319
|
+
brotli: { quality: 6 }
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
await assigner.assign(mockBundleItems);
|
|
324
|
+
|
|
325
|
+
mockBundleItems.forEach(item => {
|
|
326
|
+
if (item.response_buffers.br) {
|
|
327
|
+
const originalSize = item.response_buffers.identity.length;
|
|
328
|
+
const compressedSize = item.response_buffers.br.length;
|
|
329
|
+
const compressionRatio = compressedSize / originalSize;
|
|
330
|
+
|
|
331
|
+
// Verify compression occurred
|
|
332
|
+
assert(compressedSize < originalSize, `${item.type} should be compressed`);
|
|
333
|
+
assert(compressionRatio < 1, `${item.type} should have compression ratio < 1`);
|
|
334
|
+
|
|
335
|
+
console.log(`${item.type} Brotli compression: ${originalSize} → ${compressedSize} bytes (${(compressionRatio * 100).toFixed(1)}%)`);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should compare gzip vs brotli compression effectiveness', async function() {
|
|
341
|
+
const assigner = new Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner({
|
|
342
|
+
compression: {
|
|
343
|
+
enabled: true,
|
|
344
|
+
algorithms: ['gzip', 'br']
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
await assigner.assign(mockBundleItems);
|
|
349
|
+
|
|
350
|
+
mockBundleItems.forEach(item => {
|
|
351
|
+
if (item.response_buffers.gzip && item.response_buffers.br) {
|
|
352
|
+
const originalSize = item.response_buffers.identity.length;
|
|
353
|
+
const gzipSize = item.response_buffers.gzip.length;
|
|
354
|
+
const brotliSize = item.response_buffers.br.length;
|
|
355
|
+
|
|
356
|
+
const gzipRatio = gzipSize / originalSize;
|
|
357
|
+
const brotliRatio = brotliSize / originalSize;
|
|
358
|
+
|
|
359
|
+
// Brotli should generally be better than or equal to gzip for text
|
|
360
|
+
assert(brotliRatio <= gzipRatio, `Brotli should be at least as good as gzip for ${item.type}`);
|
|
361
|
+
|
|
362
|
+
console.log(`${item.type} - Gzip: ${(gzipRatio * 100).toFixed(1)}%, Brotli: ${(brotliRatio * 100).toFixed(1)}%`);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should verify compressed content integrity', async function() {
|
|
368
|
+
const assigner = new Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner({
|
|
369
|
+
compression: {
|
|
370
|
+
enabled: true,
|
|
371
|
+
algorithms: ['gzip', 'br']
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
await assigner.assign(mockBundleItems);
|
|
376
|
+
|
|
377
|
+
// Test gzip decompression
|
|
378
|
+
for (const item of mockBundleItems) {
|
|
379
|
+
if (item.response_buffers.gzip) {
|
|
380
|
+
const decompressed = zlib.gunzipSync(item.response_buffers.gzip).toString();
|
|
381
|
+
assert.strictEqual(decompressed, item.text,
|
|
382
|
+
`${item.type} gzip decompression should match original`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Test brotli decompression
|
|
387
|
+
for (const item of mockBundleItems) {
|
|
388
|
+
if (item.response_buffers.br) {
|
|
389
|
+
const decompressed = zlib.brotliDecompressSync(item.response_buffers.br).toString();
|
|
390
|
+
assert.strictEqual(decompressed, item.text,
|
|
391
|
+
`${item.type} brotli decompression should match original`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('should analyze compression threshold behavior', async function() {
|
|
397
|
+
const largeContent = 'x'.repeat(2000); // 2000 bytes
|
|
398
|
+
const smallContent = 'x'.repeat(500); // 500 bytes
|
|
399
|
+
|
|
400
|
+
const testItems = [
|
|
401
|
+
{
|
|
402
|
+
type: 'Large',
|
|
403
|
+
text: largeContent,
|
|
404
|
+
response_buffers: { identity: Buffer.from(largeContent) }
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
type: 'Small',
|
|
408
|
+
text: smallContent,
|
|
409
|
+
response_buffers: { identity: Buffer.from(smallContent) }
|
|
410
|
+
}
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
const assigner = new Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner({
|
|
414
|
+
compression: {
|
|
415
|
+
enabled: true,
|
|
416
|
+
algorithms: ['gzip'],
|
|
417
|
+
threshold: 1024 // 1KB threshold
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
await assigner.assign(testItems);
|
|
422
|
+
|
|
423
|
+
// Large content should be compressed
|
|
424
|
+
assert(testItems[0].response_buffers.gzip, 'Large content should be compressed');
|
|
425
|
+
assert(testItems[0].response_buffers.gzip.length < largeContent.length,
|
|
426
|
+
'Large content should actually be compressed');
|
|
427
|
+
|
|
428
|
+
// Small content should not be compressed (below threshold)
|
|
429
|
+
assert(!testItems[1].response_buffers.gzip, 'Small content should not be compressed due to threshold');
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
describe('Sourcemap Analysis', function() {
|
|
434
|
+
it('should verify inline sourcemap generation', async function() {
|
|
435
|
+
const bundler = new Core_JS_Non_Minifying_Bundler_Using_ESBuild({
|
|
436
|
+
debug: true,
|
|
437
|
+
sourcemaps: {
|
|
438
|
+
enabled: true,
|
|
439
|
+
format: 'inline'
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
const result = await bundler.bundle(testJsFile);
|
|
444
|
+
const jsContent = result[0]._arr[0].text;
|
|
445
|
+
|
|
446
|
+
// Verify sourcemap is present
|
|
447
|
+
assert(jsContent.includes('//# sourceMappingURL='), 'Should contain inline sourcemap');
|
|
448
|
+
|
|
449
|
+
// Extract and verify sourcemap content
|
|
450
|
+
const sourcemapMatch = jsContent.match(/\/\/# sourceMappingURL=data:application\/json;base64,([A-Za-z0-9+/=]+)/);
|
|
451
|
+
assert(sourcemapMatch, 'Should have valid sourcemap data URL');
|
|
452
|
+
|
|
453
|
+
// Decode and parse sourcemap
|
|
454
|
+
const sourcemapData = Buffer.from(sourcemapMatch[1], 'base64').toString();
|
|
455
|
+
const sourcemap = JSON.parse(sourcemapData);
|
|
456
|
+
|
|
457
|
+
// Verify sourcemap structure
|
|
458
|
+
assert(sourcemap.version, 'Sourcemap should have version');
|
|
459
|
+
assert(Array.isArray(sourcemap.sources), 'Sourcemap should have sources array');
|
|
460
|
+
assert(sourcemap.sources.length > 0, 'Sourcemap should have at least one source');
|
|
461
|
+
assert(sourcemap.mappings, 'Sourcemap should have mappings');
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('should verify sourcemap exclusion in production mode', async function() {
|
|
465
|
+
const bundler = new Core_JS_Non_Minifying_Bundler_Using_ESBuild({
|
|
466
|
+
debug: false,
|
|
467
|
+
sourcemaps: {
|
|
468
|
+
enabled: false
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
const result = await bundler.bundle(testJsFile);
|
|
473
|
+
const jsContent = result[0]._arr[0].text;
|
|
474
|
+
|
|
475
|
+
// Verify sourcemap is NOT present
|
|
476
|
+
assert(!jsContent.includes('//# sourceMappingURL='), 'Production mode should not include sourcemap');
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it('should verify sourcemaps work with advanced bundling', async function() {
|
|
480
|
+
const bundler = new Advanced_JS_Bundler_Using_ESBuild({
|
|
481
|
+
debug: true,
|
|
482
|
+
bundler: {
|
|
483
|
+
sourcemaps: {
|
|
484
|
+
enabled: true,
|
|
485
|
+
format: 'inline'
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const result = await bundler.bundle(testJsFile);
|
|
491
|
+
const jsItem = result[0]._arr.find(item => item.type === 'JavaScript');
|
|
492
|
+
|
|
493
|
+
// Verify sourcemap is present in the bundled JS
|
|
494
|
+
assert(jsItem.text.includes('//# sourceMappingURL='), 'Advanced bundling should include sourcemaps in debug mode');
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
describe('Bundle Content Integrity', function() {
|
|
499
|
+
it('should verify bundled content contains expected elements', async function() {
|
|
500
|
+
const bundler = new Advanced_JS_Bundler_Using_ESBuild({
|
|
501
|
+
debug: false,
|
|
502
|
+
bundler: {
|
|
503
|
+
minify: { enabled: true, level: 'normal' }
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const result = await bundler.bundle(testJsFile);
|
|
508
|
+
const jsItem = result[0]._arr.find(item => item.type === 'JavaScript');
|
|
509
|
+
const cssItem = result[0]._arr.find(item => item.type === 'CSS');
|
|
510
|
+
|
|
511
|
+
// Verify JS content
|
|
512
|
+
assert(jsItem.text.includes('testFunction'), 'Bundled JS should contain testFunction');
|
|
513
|
+
assert(jsItem.text.includes('TestClass'), 'Bundled JS should contain TestClass');
|
|
514
|
+
|
|
515
|
+
// Verify CSS content
|
|
516
|
+
assert(cssItem.text.includes('.test-class'), 'Bundled CSS should contain test class');
|
|
517
|
+
assert(cssItem.text.includes('background-color'), 'Bundled CSS should contain CSS properties');
|
|
518
|
+
|
|
519
|
+
// Verify CSS is properly formatted (not mangled)
|
|
520
|
+
assert(cssItem.text.includes('{'), 'CSS should contain opening braces');
|
|
521
|
+
assert(cssItem.text.includes('}'), 'CSS should contain closing braces');
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('should verify minified content is still valid JavaScript', async function() {
|
|
525
|
+
const bundler = new Core_JS_Single_File_Minifying_Bundler_Using_ESBuild({
|
|
526
|
+
minify: { enabled: true, level: 'aggressive' }
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
const result = await bundler.bundle(testJsContent);
|
|
530
|
+
const minified = result[0]._arr[0].text;
|
|
531
|
+
|
|
532
|
+
// Basic syntax checks - should not contain syntax errors when parsed
|
|
533
|
+
// Note: This is a basic check; full validation would require a JS parser
|
|
534
|
+
assert.doesNotThrow(() => {
|
|
535
|
+
// Try to create a Function from the minified code (basic syntax check)
|
|
536
|
+
new Function(minified);
|
|
537
|
+
}, 'Minified code should be valid JavaScript');
|
|
538
|
+
|
|
539
|
+
// Should still contain essential code elements
|
|
540
|
+
assert(minified.includes('function'), 'Should contain function declarations');
|
|
541
|
+
assert(minified.includes('return'), 'Should contain return statements');
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('should verify CSS extraction preserves CSS structure', async function() {
|
|
545
|
+
const bundler = new Advanced_JS_Bundler_Using_ESBuild();
|
|
546
|
+
|
|
547
|
+
const result = await bundler.bundle(testJsFile);
|
|
548
|
+
const cssItem = result[0]._arr.find(item => item.type === 'CSS');
|
|
549
|
+
|
|
550
|
+
// Verify CSS structure is preserved
|
|
551
|
+
const cssLines = cssItem.text.split('\n').filter(line => line.trim());
|
|
552
|
+
|
|
553
|
+
// Should contain selectors and declarations
|
|
554
|
+
assert(cssLines.some(line => line.includes('.test-class')), 'Should contain CSS selectors');
|
|
555
|
+
assert(cssLines.some(line => line.includes('background-color')), 'Should contain CSS properties');
|
|
556
|
+
assert(cssLines.some(line => line.includes('{')), 'Should contain opening braces');
|
|
557
|
+
assert(cssLines.some(line => line.includes('}')), 'Should contain closing braces');
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
describe('Performance Metrics Analysis', function() {
|
|
562
|
+
it('should measure and compare bundling performance', async function() {
|
|
563
|
+
const bundlers = {
|
|
564
|
+
nonMinifying: new Core_JS_Non_Minifying_Bundler_Using_ESBuild(),
|
|
565
|
+
minifying: new Core_JS_Single_File_Minifying_Bundler_Using_ESBuild({
|
|
566
|
+
minify: { enabled: true, level: 'normal' }
|
|
567
|
+
}),
|
|
568
|
+
advanced: new Advanced_JS_Bundler_Using_ESBuild({
|
|
569
|
+
bundler: { minify: { enabled: true, level: 'normal' } }
|
|
570
|
+
})
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
const results = {};
|
|
574
|
+
|
|
575
|
+
for (const [name, bundler] of Object.entries(bundlers)) {
|
|
576
|
+
const startTime = Date.now();
|
|
577
|
+
const result = await bundler.bundle(testJsFile);
|
|
578
|
+
const endTime = Date.now();
|
|
579
|
+
|
|
580
|
+
const bundle = result[0];
|
|
581
|
+
const jsItem = bundle._arr.find(item => item.type === 'JavaScript');
|
|
582
|
+
|
|
583
|
+
results[name] = {
|
|
584
|
+
duration: endTime - startTime,
|
|
585
|
+
outputSize: jsItem.text.length,
|
|
586
|
+
inputSize: testJsContent.length
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Log performance results
|
|
591
|
+
Object.entries(results).forEach(([name, result]) => {
|
|
592
|
+
console.log(`${name}: ${result.duration}ms, ${result.inputSize} → ${result.outputSize} bytes`);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// Basic performance assertions
|
|
596
|
+
assert(results.nonMinifying.duration >= 0, 'Non-minifying bundler should complete');
|
|
597
|
+
assert(results.minifying.duration >= 0, 'Minifying bundler should complete');
|
|
598
|
+
assert(results.advanced.duration >= 0, 'Advanced bundler should complete');
|
|
599
|
+
|
|
600
|
+
// Minifying should generally take longer than non-minifying
|
|
601
|
+
// (though this may not always be true due to various factors)
|
|
602
|
+
console.log('Performance analysis complete');
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it('should analyze compression performance', async function() {
|
|
606
|
+
const testData = 'console.log("test");'.repeat(1000); // Repetitive content that compresses well
|
|
607
|
+
const item = {
|
|
608
|
+
type: 'JavaScript',
|
|
609
|
+
text: testData,
|
|
610
|
+
response_buffers: {
|
|
611
|
+
identity: Buffer.from(testData)
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
const assigner = new Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner({
|
|
616
|
+
compression: {
|
|
617
|
+
enabled: true,
|
|
618
|
+
algorithms: ['gzip', 'br']
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
const startTime = Date.now();
|
|
623
|
+
await assigner.assign([item]);
|
|
624
|
+
const endTime = Date.now();
|
|
625
|
+
|
|
626
|
+
const compressionTime = endTime - startTime;
|
|
627
|
+
const originalSize = item.response_buffers.identity.length;
|
|
628
|
+
const gzipSize = item.response_buffers.gzip.length;
|
|
629
|
+
const brotliSize = item.response_buffers.br.length;
|
|
630
|
+
|
|
631
|
+
console.log(`Compression performance: ${compressionTime}ms`);
|
|
632
|
+
console.log(`Original: ${originalSize} bytes`);
|
|
633
|
+
console.log(`Gzip: ${gzipSize} bytes (${((gzipSize/originalSize)*100).toFixed(1)}%)`);
|
|
634
|
+
console.log(`Brotli: ${brotliSize} bytes (${((brotliSize/originalSize)*100).toFixed(1)}%)`);
|
|
635
|
+
|
|
636
|
+
// Verify compression was effective on repetitive content
|
|
637
|
+
assert(gzipSize < originalSize * 0.5, 'Gzip should achieve >50% compression on repetitive content');
|
|
638
|
+
assert(brotliSize < originalSize * 0.5, 'Brotli should achieve >50% compression on repetitive content');
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
});
|