pulse-js-framework 1.7.3 → 1.7.5
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/cli/analyze.js +127 -46
- package/cli/build.js +148 -34
- package/cli/dev.js +20 -5
- package/cli/format.js +64 -8
- package/cli/lint.js +112 -27
- package/cli/utils/cli-ui.js +452 -0
- package/compiler/parser.js +19 -2
- package/core/errors.js +281 -6
- package/package.json +7 -2
- package/runtime/async.js +282 -14
- package/runtime/dom-adapter.js +920 -0
- package/runtime/dom.js +331 -162
- package/runtime/logger.js +144 -69
- package/runtime/logger.prod.js +43 -18
- package/runtime/pulse.js +202 -80
- package/runtime/router.js +27 -39
- package/runtime/store.js +10 -7
- package/runtime/utils.js +279 -18
package/cli/analyze.js
CHANGED
|
@@ -7,6 +7,7 @@ import { readFileSync, statSync, existsSync } from 'fs';
|
|
|
7
7
|
import { join, dirname, basename, relative } from 'path';
|
|
8
8
|
import { findPulseFiles, parseArgs, formatBytes, relativePath, resolveImportPath } from './utils/file-utils.js';
|
|
9
9
|
import { log } from './logger.js';
|
|
10
|
+
import { createTimer, formatDuration, createSpinner, createBarChart, createTree, createTable, printSection } from './utils/cli-ui.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Analyze the bundle/project
|
|
@@ -384,74 +385,111 @@ function formatConsoleOutput(analysis, verbose = false) {
|
|
|
384
385
|
lines.push(' PULSE BUNDLE ANALYSIS');
|
|
385
386
|
lines.push('═'.repeat(60));
|
|
386
387
|
|
|
387
|
-
// Summary
|
|
388
|
+
// Summary table
|
|
388
389
|
lines.push('');
|
|
389
|
-
lines.push(
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
390
|
+
lines.push(createTable(
|
|
391
|
+
['Metric', 'Value'],
|
|
392
|
+
[
|
|
393
|
+
['Total files', String(analysis.summary.totalFiles)],
|
|
394
|
+
['.pulse files', String(analysis.summary.pulseFiles)],
|
|
395
|
+
['.js files', String(analysis.summary.jsFiles)],
|
|
396
|
+
['Total size', analysis.summary.totalSizeFormatted]
|
|
397
|
+
],
|
|
398
|
+
{ align: ['left', 'right'] }
|
|
399
|
+
));
|
|
400
|
+
|
|
401
|
+
// Complexity bar chart (top 5)
|
|
397
402
|
if (analysis.complexity.length > 0) {
|
|
398
|
-
|
|
399
|
-
lines.push(' COMPLEXITY (Top 5)');
|
|
400
|
-
lines.push(' ' + '─'.repeat(40));
|
|
403
|
+
printSection('COMPONENT COMPLEXITY');
|
|
401
404
|
const top5 = analysis.complexity.slice(0, 5);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
+
const chartData = top5.map(comp => ({
|
|
406
|
+
label: comp.componentName.slice(0, 15).padEnd(15),
|
|
407
|
+
value: comp.complexity,
|
|
408
|
+
color: comp.complexity > 20 ? 'red' : comp.complexity > 10 ? 'yellow' : 'green'
|
|
409
|
+
}));
|
|
410
|
+
lines.push(createBarChart(chartData, { maxWidth: 30, showValues: true }));
|
|
411
|
+
|
|
412
|
+
// Detailed metrics table for verbose mode
|
|
413
|
+
if (verbose) {
|
|
414
|
+
lines.push('');
|
|
415
|
+
lines.push(createTable(
|
|
416
|
+
['Component', 'State', 'Actions', 'Depth', 'Directives', 'Score'],
|
|
417
|
+
top5.map(c => [
|
|
418
|
+
c.componentName,
|
|
419
|
+
String(c.stateCount),
|
|
420
|
+
String(c.actionCount),
|
|
421
|
+
String(c.viewDepth),
|
|
422
|
+
String(c.directiveCount),
|
|
423
|
+
String(c.complexity)
|
|
424
|
+
]),
|
|
425
|
+
{ align: ['left', 'right', 'right', 'right', 'right', 'right'] }
|
|
426
|
+
));
|
|
405
427
|
}
|
|
406
428
|
}
|
|
407
429
|
|
|
408
|
-
// Dead code
|
|
430
|
+
// Dead code warnings
|
|
409
431
|
if (analysis.deadCode.length > 0) {
|
|
410
|
-
|
|
411
|
-
lines.push(' DEAD CODE');
|
|
412
|
-
lines.push(' ' + '─'.repeat(40));
|
|
432
|
+
printSection('DEAD CODE DETECTED');
|
|
413
433
|
for (const dead of analysis.deadCode) {
|
|
414
434
|
lines.push(` ⚠ ${dead.file}`);
|
|
415
|
-
lines.push(` ${dead.message}`);
|
|
435
|
+
lines.push(` └─ ${dead.message}`);
|
|
416
436
|
}
|
|
417
437
|
}
|
|
418
438
|
|
|
419
|
-
// File breakdown (verbose)
|
|
439
|
+
// File size breakdown with bar chart (verbose)
|
|
420
440
|
if (verbose && analysis.fileBreakdown.length > 0) {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
441
|
+
printSection('FILE SIZE BREAKDOWN');
|
|
442
|
+
const top10Files = analysis.fileBreakdown.slice(0, 10);
|
|
443
|
+
const sizeChartData = top10Files.map(file => ({
|
|
444
|
+
label: basename(file.path).slice(0, 20).padEnd(20),
|
|
445
|
+
value: file.size,
|
|
446
|
+
color: file.type === 'pulse' ? 'cyan' : 'blue'
|
|
447
|
+
}));
|
|
448
|
+
lines.push(createBarChart(sizeChartData, { maxWidth: 25, showValues: true, unit: 'B' }));
|
|
428
449
|
}
|
|
429
450
|
|
|
430
|
-
// Import graph (verbose)
|
|
451
|
+
// Import graph as tree (verbose)
|
|
431
452
|
if (verbose && analysis.importGraph.edges.length > 0) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (analysis.importGraph.edges.length > 20) {
|
|
439
|
-
lines.push(` ... and ${analysis.importGraph.edges.length - 20} more`);
|
|
453
|
+
printSection('IMPORT DEPENDENCY TREE');
|
|
454
|
+
|
|
455
|
+
// Build tree structure from edges
|
|
456
|
+
const tree = buildDependencyTree(analysis.importGraph);
|
|
457
|
+
if (tree) {
|
|
458
|
+
lines.push(createTree(tree));
|
|
440
459
|
}
|
|
460
|
+
|
|
461
|
+
// Show edge count summary
|
|
462
|
+
lines.push(` Total dependencies: ${analysis.importGraph.edges.length}`);
|
|
441
463
|
}
|
|
442
464
|
|
|
443
465
|
// State usage (verbose)
|
|
444
466
|
if (verbose && analysis.stateUsage.length > 0) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
467
|
+
printSection('STATE VARIABLES');
|
|
468
|
+
|
|
469
|
+
// Show shared state as a table
|
|
470
|
+
const sharedState = analysis.stateUsage.filter(s => s.isShared);
|
|
471
|
+
if (sharedState.length > 0) {
|
|
472
|
+
lines.push(' Shared state (used in multiple files):');
|
|
473
|
+
lines.push(createTable(
|
|
474
|
+
['Variable', 'Files'],
|
|
475
|
+
sharedState.slice(0, 5).map(s => [s.name, String(s.files.length)]),
|
|
476
|
+
{ align: ['left', 'right'] }
|
|
477
|
+
));
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// State tree visualization
|
|
481
|
+
for (const state of analysis.stateUsage.slice(0, 5)) {
|
|
482
|
+
const shared = state.isShared ? ' \x1b[33m(shared)\x1b[0m' : '';
|
|
450
483
|
lines.push(` ${state.name}${shared}`);
|
|
451
|
-
for (
|
|
452
|
-
|
|
484
|
+
for (let i = 0; i < state.files.length; i++) {
|
|
485
|
+
const isLast = i === state.files.length - 1;
|
|
486
|
+
const prefix = isLast ? '└── ' : '├── ';
|
|
487
|
+
lines.push(` ${prefix}${state.files[i]}`);
|
|
453
488
|
}
|
|
454
489
|
}
|
|
490
|
+
if (analysis.stateUsage.length > 5) {
|
|
491
|
+
lines.push(` ... and ${analysis.stateUsage.length - 5} more state variables`);
|
|
492
|
+
}
|
|
455
493
|
}
|
|
456
494
|
|
|
457
495
|
lines.push('');
|
|
@@ -460,6 +498,44 @@ function formatConsoleOutput(analysis, verbose = false) {
|
|
|
460
498
|
return lines.join('\n');
|
|
461
499
|
}
|
|
462
500
|
|
|
501
|
+
/**
|
|
502
|
+
* Build a dependency tree structure for visualization
|
|
503
|
+
*/
|
|
504
|
+
function buildDependencyTree(importGraph) {
|
|
505
|
+
if (!importGraph.edges.length) return null;
|
|
506
|
+
|
|
507
|
+
// Find entry points (files that aren't imported by others)
|
|
508
|
+
const imported = new Set(importGraph.edges.map(e => e.to));
|
|
509
|
+
const entryPoints = importGraph.nodes.filter(n =>
|
|
510
|
+
!imported.has(n) && (n.includes('main') || n.includes('index') || n.includes('App'))
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
if (entryPoints.length === 0 && importGraph.nodes.length > 0) {
|
|
514
|
+
entryPoints.push(importGraph.nodes[0]);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Build tree recursively (limit depth to avoid infinite loops)
|
|
518
|
+
function buildNode(name, visited = new Set(), depth = 0) {
|
|
519
|
+
if (depth > 5 || visited.has(name)) {
|
|
520
|
+
return { name: basename(name) + (visited.has(name) ? ' (circular)' : ' ...'), children: [] };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
visited.add(name);
|
|
524
|
+
const children = importGraph.edges
|
|
525
|
+
.filter(e => e.from === name)
|
|
526
|
+
.slice(0, 5) // Limit children
|
|
527
|
+
.map(e => buildNode(e.to, new Set(visited), depth + 1));
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
name: basename(name),
|
|
531
|
+
children
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Return first entry point's tree
|
|
536
|
+
return buildNode(entryPoints[0]);
|
|
537
|
+
}
|
|
538
|
+
|
|
463
539
|
/**
|
|
464
540
|
* Main analyze command handler
|
|
465
541
|
*/
|
|
@@ -478,10 +554,14 @@ export async function runAnalyze(args) {
|
|
|
478
554
|
process.exit(1);
|
|
479
555
|
}
|
|
480
556
|
|
|
481
|
-
|
|
557
|
+
const timer = createTimer();
|
|
558
|
+
const spinner = createSpinner('Analyzing bundle...');
|
|
482
559
|
|
|
483
560
|
try {
|
|
484
561
|
const analysis = await analyzeBundle(root);
|
|
562
|
+
const elapsed = timer.elapsed();
|
|
563
|
+
|
|
564
|
+
spinner.success(`Analysis complete (${formatDuration(elapsed)})`);
|
|
485
565
|
|
|
486
566
|
if (json) {
|
|
487
567
|
log.info(JSON.stringify(analysis, null, 2));
|
|
@@ -494,7 +574,8 @@ export async function runAnalyze(args) {
|
|
|
494
574
|
log.warn(`\nWarning: ${analysis.deadCode.length} potentially unused file(s) found.`);
|
|
495
575
|
}
|
|
496
576
|
} catch (error) {
|
|
497
|
-
|
|
577
|
+
spinner.fail('Analysis failed');
|
|
578
|
+
log.error(error.message);
|
|
498
579
|
process.exit(1);
|
|
499
580
|
}
|
|
500
581
|
}
|
package/cli/build.js
CHANGED
|
@@ -8,6 +8,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSy
|
|
|
8
8
|
import { join, extname, relative, dirname } from 'path';
|
|
9
9
|
import { compile } from '../compiler/index.js';
|
|
10
10
|
import { log } from './logger.js';
|
|
11
|
+
import { createTimer, createProgressBar, formatDuration, createSpinner } from './utils/cli-ui.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Build project for production
|
|
@@ -15,21 +16,23 @@ import { log } from './logger.js';
|
|
|
15
16
|
export async function buildProject(args) {
|
|
16
17
|
const root = process.cwd();
|
|
17
18
|
const outDir = join(root, 'dist');
|
|
19
|
+
const timer = createTimer();
|
|
18
20
|
|
|
19
21
|
// Check if vite is available
|
|
20
22
|
try {
|
|
21
23
|
const viteConfig = join(root, 'vite.config.js');
|
|
22
24
|
if (existsSync(viteConfig)) {
|
|
23
|
-
|
|
25
|
+
const spinner = createSpinner('Building with Vite...');
|
|
24
26
|
const { build } = await import('vite');
|
|
25
27
|
await build({ root });
|
|
28
|
+
spinner.success(`Built with Vite in ${timer.format()}`);
|
|
26
29
|
return;
|
|
27
30
|
}
|
|
28
31
|
} catch (e) {
|
|
29
32
|
// Vite not available, use built-in build
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
log.info('Building with Pulse compiler
|
|
35
|
+
log.info('Building with Pulse compiler...\n');
|
|
33
36
|
|
|
34
37
|
// Create output directory
|
|
35
38
|
if (!existsSync(outDir)) {
|
|
@@ -42,10 +45,19 @@ export async function buildProject(args) {
|
|
|
42
45
|
copyDir(publicDir, outDir);
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
//
|
|
48
|
+
// Count files for progress bar
|
|
46
49
|
const srcDir = join(root, 'src');
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
const fileCount = existsSync(srcDir) ? countFiles(srcDir) : 0;
|
|
51
|
+
|
|
52
|
+
// Process source files with progress bar
|
|
53
|
+
if (existsSync(srcDir) && fileCount > 0) {
|
|
54
|
+
const progress = createProgressBar({
|
|
55
|
+
total: fileCount,
|
|
56
|
+
label: 'Compiling',
|
|
57
|
+
width: 25
|
|
58
|
+
});
|
|
59
|
+
processDirectory(srcDir, join(outDir, 'assets'), progress);
|
|
60
|
+
progress.done();
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
// Copy and process index.html
|
|
@@ -68,20 +80,45 @@ export async function buildProject(args) {
|
|
|
68
80
|
// Bundle runtime
|
|
69
81
|
bundleRuntime(outDir);
|
|
70
82
|
|
|
83
|
+
const elapsed = timer.elapsed();
|
|
71
84
|
log.success(`
|
|
72
|
-
Build complete
|
|
85
|
+
✓ Build complete in ${formatDuration(elapsed)}
|
|
73
86
|
|
|
74
|
-
Output
|
|
87
|
+
Output: ${relative(root, outDir)}/
|
|
88
|
+
Files: ${fileCount} processed
|
|
75
89
|
|
|
76
|
-
To preview
|
|
77
|
-
|
|
90
|
+
To preview:
|
|
91
|
+
pulse preview
|
|
78
92
|
`);
|
|
79
93
|
}
|
|
80
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Count files in a directory recursively
|
|
97
|
+
*/
|
|
98
|
+
function countFiles(dir) {
|
|
99
|
+
let count = 0;
|
|
100
|
+
const files = readdirSync(dir);
|
|
101
|
+
|
|
102
|
+
for (const file of files) {
|
|
103
|
+
const fullPath = join(dir, file);
|
|
104
|
+
const stat = statSync(fullPath);
|
|
105
|
+
if (stat.isDirectory()) {
|
|
106
|
+
count += countFiles(fullPath);
|
|
107
|
+
} else {
|
|
108
|
+
count++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return count;
|
|
113
|
+
}
|
|
114
|
+
|
|
81
115
|
/**
|
|
82
116
|
* Process a directory of source files
|
|
117
|
+
* @param {string} srcDir - Source directory
|
|
118
|
+
* @param {string} outDir - Output directory
|
|
119
|
+
* @param {Object} [progress] - Progress bar instance
|
|
83
120
|
*/
|
|
84
|
-
function processDirectory(srcDir, outDir) {
|
|
121
|
+
function processDirectory(srcDir, outDir, progress = null) {
|
|
85
122
|
if (!existsSync(outDir)) {
|
|
86
123
|
mkdirSync(outDir, { recursive: true });
|
|
87
124
|
}
|
|
@@ -93,7 +130,7 @@ function processDirectory(srcDir, outDir) {
|
|
|
93
130
|
const stat = statSync(srcPath);
|
|
94
131
|
|
|
95
132
|
if (stat.isDirectory()) {
|
|
96
|
-
processDirectory(srcPath, join(outDir, file));
|
|
133
|
+
processDirectory(srcPath, join(outDir, file), progress);
|
|
97
134
|
} else if (file.endsWith('.pulse')) {
|
|
98
135
|
// Compile .pulse files
|
|
99
136
|
const source = readFileSync(srcPath, 'utf-8');
|
|
@@ -105,13 +142,13 @@ function processDirectory(srcDir, outDir) {
|
|
|
105
142
|
if (result.success) {
|
|
106
143
|
const outPath = join(outDir, file.replace('.pulse', '.js'));
|
|
107
144
|
writeFileSync(outPath, result.code);
|
|
108
|
-
log.info(` Compiled: ${file}`);
|
|
109
145
|
} else {
|
|
110
146
|
log.error(` Error compiling ${file}:`);
|
|
111
147
|
for (const error of result.errors) {
|
|
112
148
|
log.error(` ${error.message}`);
|
|
113
149
|
}
|
|
114
150
|
}
|
|
151
|
+
if (progress) progress.tick();
|
|
115
152
|
} else if (file.endsWith('.js') || file.endsWith('.mjs')) {
|
|
116
153
|
// Process JS files - rewrite imports
|
|
117
154
|
let content = readFileSync(srcPath, 'utf-8');
|
|
@@ -130,11 +167,12 @@ function processDirectory(srcDir, outDir) {
|
|
|
130
167
|
|
|
131
168
|
const outPath = join(outDir, file);
|
|
132
169
|
writeFileSync(outPath, content);
|
|
133
|
-
|
|
170
|
+
if (progress) progress.tick();
|
|
134
171
|
} else {
|
|
135
172
|
// Copy other files
|
|
136
173
|
const outPath = join(outDir, file);
|
|
137
174
|
copyFileSync(srcPath, outPath);
|
|
175
|
+
if (progress) progress.tick();
|
|
138
176
|
}
|
|
139
177
|
}
|
|
140
178
|
}
|
|
@@ -185,28 +223,104 @@ function readRuntimeFile(filename) {
|
|
|
185
223
|
|
|
186
224
|
/**
|
|
187
225
|
* Minify JavaScript code (simple minification)
|
|
188
|
-
* String-aware: preserves content inside string literals
|
|
226
|
+
* String and regex-aware: preserves content inside string and regex literals
|
|
189
227
|
*/
|
|
190
228
|
export function minifyJS(code) {
|
|
191
|
-
// Extract strings to protect them from minification
|
|
192
|
-
const
|
|
193
|
-
const placeholder = '\
|
|
194
|
-
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
229
|
+
// Extract strings and regexes to protect them from minification
|
|
230
|
+
const preserved = [];
|
|
231
|
+
const placeholder = '\x00PRE';
|
|
232
|
+
|
|
233
|
+
// State machine to properly handle strings and regexes
|
|
234
|
+
let result = '';
|
|
235
|
+
let i = 0;
|
|
236
|
+
|
|
237
|
+
while (i < code.length) {
|
|
238
|
+
const char = code[i];
|
|
239
|
+
const next = code[i + 1];
|
|
240
|
+
|
|
241
|
+
// Skip single-line comments
|
|
242
|
+
if (char === '/' && next === '/') {
|
|
243
|
+
while (i < code.length && code[i] !== '\n') i++;
|
|
244
|
+
continue;
|
|
201
245
|
}
|
|
202
|
-
);
|
|
203
246
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
247
|
+
// Skip multi-line comments
|
|
248
|
+
if (char === '/' && next === '*') {
|
|
249
|
+
i += 2;
|
|
250
|
+
while (i < code.length - 1 && !(code[i] === '*' && code[i + 1] === '/')) i++;
|
|
251
|
+
i += 2;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Handle string literals
|
|
256
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
257
|
+
const quote = char;
|
|
258
|
+
let str = char;
|
|
259
|
+
i++;
|
|
260
|
+
while (i < code.length) {
|
|
261
|
+
const c = code[i];
|
|
262
|
+
str += c;
|
|
263
|
+
if (c === '\\' && i + 1 < code.length) {
|
|
264
|
+
i++;
|
|
265
|
+
str += code[i];
|
|
266
|
+
} else if (c === quote) {
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
i++;
|
|
270
|
+
}
|
|
271
|
+
i++;
|
|
272
|
+
preserved.push(str);
|
|
273
|
+
result += placeholder + (preserved.length - 1) + '\x00';
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Handle regex literals (after = : ( , [ ! & | ? ; { or keywords)
|
|
278
|
+
if (char === '/') {
|
|
279
|
+
// Look back to determine if this is a regex
|
|
280
|
+
const lookback = result.slice(-20).trim();
|
|
281
|
+
const isRegexContext = lookback === '' ||
|
|
282
|
+
/[=:(\[,!&|?;{]$/.test(lookback) ||
|
|
283
|
+
/\breturn$/.test(lookback) ||
|
|
284
|
+
/\bthrow$/.test(lookback) ||
|
|
285
|
+
/\btypeof$/.test(lookback);
|
|
286
|
+
|
|
287
|
+
if (isRegexContext && next !== '/' && next !== '*') {
|
|
288
|
+
let regex = char;
|
|
289
|
+
i++;
|
|
290
|
+
let inCharClass = false;
|
|
291
|
+
while (i < code.length) {
|
|
292
|
+
const c = code[i];
|
|
293
|
+
regex += c;
|
|
294
|
+
if (c === '\\' && i + 1 < code.length) {
|
|
295
|
+
i++;
|
|
296
|
+
regex += code[i];
|
|
297
|
+
} else if (c === '[') {
|
|
298
|
+
inCharClass = true;
|
|
299
|
+
} else if (c === ']') {
|
|
300
|
+
inCharClass = false;
|
|
301
|
+
} else if (c === '/' && !inCharClass) {
|
|
302
|
+
// End of regex, collect flags
|
|
303
|
+
i++;
|
|
304
|
+
while (i < code.length && /[gimsuvy]/.test(code[i])) {
|
|
305
|
+
regex += code[i];
|
|
306
|
+
i++;
|
|
307
|
+
}
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
i++;
|
|
311
|
+
}
|
|
312
|
+
preserved.push(regex);
|
|
313
|
+
result += placeholder + (preserved.length - 1) + '\x00';
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
result += char;
|
|
319
|
+
i++;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Apply minification to non-preserved parts
|
|
323
|
+
let minified = result
|
|
210
324
|
// Remove leading/trailing whitespace per line
|
|
211
325
|
.split('\n')
|
|
212
326
|
.map(line => line.trim())
|
|
@@ -214,7 +328,7 @@ export function minifyJS(code) {
|
|
|
214
328
|
.join('\n')
|
|
215
329
|
// Collapse multiple newlines
|
|
216
330
|
.replace(/\n{2,}/g, '\n')
|
|
217
|
-
// Remove spaces around operators
|
|
331
|
+
// Remove spaces around operators
|
|
218
332
|
.replace(/\s*([{};,:])\s*/g, '$1')
|
|
219
333
|
.replace(/\s*=\s*/g, '=')
|
|
220
334
|
.replace(/\s*\(\s*/g, '(')
|
|
@@ -223,10 +337,10 @@ export function minifyJS(code) {
|
|
|
223
337
|
.replace(/\s+/g, ' ')
|
|
224
338
|
.trim();
|
|
225
339
|
|
|
226
|
-
// Restore strings
|
|
340
|
+
// Restore preserved strings and regexes
|
|
227
341
|
minified = minified.replace(
|
|
228
342
|
new RegExp(placeholder.replace('\x00', '\\x00') + '(\\d+)\\x00', 'g'),
|
|
229
|
-
(_, index) =>
|
|
343
|
+
(_, index) => preserved[parseInt(index)]
|
|
230
344
|
);
|
|
231
345
|
|
|
232
346
|
return minified;
|
package/cli/dev.js
CHANGED
|
@@ -128,7 +128,10 @@ export async function startDevServer(args) {
|
|
|
128
128
|
});
|
|
129
129
|
|
|
130
130
|
if (result.success) {
|
|
131
|
-
res.writeHead(200, {
|
|
131
|
+
res.writeHead(200, {
|
|
132
|
+
'Content-Type': 'application/javascript',
|
|
133
|
+
'Cache-Control': 'no-cache, no-store, must-revalidate'
|
|
134
|
+
});
|
|
132
135
|
res.end(result.code);
|
|
133
136
|
} else {
|
|
134
137
|
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
@@ -146,7 +149,10 @@ export async function startDevServer(args) {
|
|
|
146
149
|
if (pathname.endsWith('.js') || pathname.endsWith('.mjs')) {
|
|
147
150
|
if (existsSync(filePath)) {
|
|
148
151
|
const content = readFileSync(filePath, 'utf-8');
|
|
149
|
-
res.writeHead(200, {
|
|
152
|
+
res.writeHead(200, {
|
|
153
|
+
'Content-Type': 'application/javascript',
|
|
154
|
+
'Cache-Control': 'no-cache, no-store, must-revalidate'
|
|
155
|
+
});
|
|
150
156
|
res.end(content);
|
|
151
157
|
return;
|
|
152
158
|
}
|
|
@@ -164,7 +170,10 @@ export async function startDevServer(args) {
|
|
|
164
170
|
});
|
|
165
171
|
|
|
166
172
|
if (result.success) {
|
|
167
|
-
res.writeHead(200, {
|
|
173
|
+
res.writeHead(200, {
|
|
174
|
+
'Content-Type': 'application/javascript',
|
|
175
|
+
'Cache-Control': 'no-cache, no-store, must-revalidate'
|
|
176
|
+
});
|
|
168
177
|
res.end(result.code);
|
|
169
178
|
} else {
|
|
170
179
|
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
@@ -183,7 +192,10 @@ export async function startDevServer(args) {
|
|
|
183
192
|
const modulePath = join(root, '..', 'pulse', pathname.replace('/node_modules/pulse-js-framework/', ''));
|
|
184
193
|
if (existsSync(modulePath)) {
|
|
185
194
|
const content = readFileSync(modulePath, 'utf-8');
|
|
186
|
-
res.writeHead(200, {
|
|
195
|
+
res.writeHead(200, {
|
|
196
|
+
'Content-Type': 'application/javascript',
|
|
197
|
+
'Cache-Control': 'no-cache, no-store, must-revalidate'
|
|
198
|
+
});
|
|
187
199
|
res.end(content);
|
|
188
200
|
return;
|
|
189
201
|
}
|
|
@@ -203,7 +215,10 @@ export async function startDevServer(args) {
|
|
|
203
215
|
for (const runtimePath of possiblePaths) {
|
|
204
216
|
if (existsSync(runtimePath)) {
|
|
205
217
|
const content = readFileSync(runtimePath, 'utf-8');
|
|
206
|
-
res.writeHead(200, {
|
|
218
|
+
res.writeHead(200, {
|
|
219
|
+
'Content-Type': 'application/javascript',
|
|
220
|
+
'Cache-Control': 'no-cache, no-store, must-revalidate'
|
|
221
|
+
});
|
|
207
222
|
res.end(content);
|
|
208
223
|
return;
|
|
209
224
|
}
|