pulse-js-framework 1.11.2 → 1.11.4

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.
Files changed (48) hide show
  1. package/cli/analyze.js +21 -8
  2. package/cli/build.js +83 -56
  3. package/cli/dev.js +108 -94
  4. package/cli/docs-test.js +52 -33
  5. package/cli/index.js +81 -51
  6. package/cli/mobile.js +92 -40
  7. package/cli/release.js +64 -46
  8. package/cli/scaffold.js +14 -13
  9. package/compiler/lexer.js +55 -54
  10. package/compiler/parser/core.js +1 -0
  11. package/compiler/parser/state.js +6 -12
  12. package/compiler/parser/style.js +17 -20
  13. package/compiler/parser/view.js +1 -3
  14. package/compiler/preprocessor.js +124 -262
  15. package/compiler/sourcemap.js +10 -4
  16. package/compiler/transformer/expressions.js +122 -106
  17. package/compiler/transformer/index.js +2 -4
  18. package/compiler/transformer/style.js +74 -7
  19. package/compiler/transformer/view.js +86 -36
  20. package/loader/esbuild-plugin-server-components.js +209 -0
  21. package/loader/esbuild-plugin.js +41 -93
  22. package/loader/parcel-plugin.js +37 -97
  23. package/loader/rollup-plugin-server-components.js +30 -169
  24. package/loader/rollup-plugin.js +27 -78
  25. package/loader/shared.js +362 -0
  26. package/loader/swc-plugin.js +65 -82
  27. package/loader/vite-plugin-server-components.js +30 -171
  28. package/loader/vite-plugin.js +25 -10
  29. package/loader/webpack-loader-server-components.js +21 -134
  30. package/loader/webpack-loader.js +25 -80
  31. package/package.json +52 -12
  32. package/runtime/dom-selector.js +2 -1
  33. package/runtime/form.js +4 -3
  34. package/runtime/http.js +6 -1
  35. package/runtime/logger.js +44 -24
  36. package/runtime/router/utils.js +14 -7
  37. package/runtime/security.js +13 -1
  38. package/runtime/server-components/actions-server.js +23 -19
  39. package/runtime/server-components/error-sanitizer.js +18 -18
  40. package/runtime/server-components/security.js +41 -24
  41. package/runtime/ssr-preload.js +5 -3
  42. package/runtime/testing.js +759 -0
  43. package/runtime/utils.js +3 -2
  44. package/server/utils.js +15 -9
  45. package/sw/index.js +2 -0
  46. package/types/loaders.d.ts +1043 -0
  47. package/compiler/parser/_extract.js +0 -393
  48. package/loader/README.md +0 -509
@@ -10,18 +10,73 @@
10
10
 
11
11
  import { createRequire } from 'module';
12
12
 
13
- // Cache for the sass module (null = not checked, false = not available, object = module)
14
- // Separate sync/async caches so sync failure doesn't prevent async import from succeeding
15
- let sassModule = null;
16
- let sassModuleAsync = null;
13
+ // ============================================================================
14
+ // Module Loading Factory eliminates duplication across SASS/LESS/Stylus
15
+ // ============================================================================
17
16
 
18
- // Cache for the less module
19
- let lessModule = null;
20
- let lessModuleAsync = null;
17
+ /**
18
+ * Create a cached module loader (sync + async) for an optional dependency
19
+ * @param {string} packageName - npm package name to load
20
+ * @returns {{ loadSync, loadAsync, isAvailable, isAvailableAsync, reset }}
21
+ */
22
+ function createModuleLoader(packageName) {
23
+ let syncCache = null;
24
+ let asyncCache = null;
25
+
26
+ /** @param {Error} err */
27
+ function isModuleNotFound(err) {
28
+ return err && (err.code === 'MODULE_NOT_FOUND' || err.code === 'ERR_MODULE_NOT_FOUND');
29
+ }
21
30
 
22
- // Cache for the stylus module
23
- let stylusModule = null;
24
- let stylusModuleAsync = null;
31
+ function loadSync() {
32
+ if (syncCache !== null) return syncCache;
33
+ try {
34
+ const require = createRequire(import.meta.url);
35
+ syncCache = require(packageName);
36
+ return syncCache;
37
+ } catch (err) {
38
+ if (isModuleNotFound(err)) {
39
+ syncCache = false;
40
+ return false;
41
+ }
42
+ // Corrupted install, syntax error, etc. — surface the real error
43
+ console.warn(`[pulse] Failed to load '${packageName}': ${err.message}`);
44
+ syncCache = false;
45
+ return false;
46
+ }
47
+ }
48
+
49
+ async function loadAsync() {
50
+ if (asyncCache !== null) return asyncCache;
51
+ if (syncCache !== null && syncCache !== false) return syncCache;
52
+ try {
53
+ const mod = await import(packageName);
54
+ syncCache = mod;
55
+ asyncCache = mod;
56
+ return mod;
57
+ } catch (err) {
58
+ if (isModuleNotFound(err)) {
59
+ const syncResult = loadSync();
60
+ asyncCache = syncResult || false;
61
+ return syncResult;
62
+ }
63
+ console.warn(`[pulse] Failed to load '${packageName}': ${err.message}`);
64
+ asyncCache = false;
65
+ return false;
66
+ }
67
+ }
68
+
69
+ function isAvailable() { return loadSync() !== false; }
70
+ async function isAvailableAsync() { return (await loadAsync()) !== false; }
71
+ function reset() { syncCache = null; asyncCache = null; }
72
+
73
+ return { loadSync, loadAsync, isAvailable, isAvailableAsync, reset };
74
+ }
75
+
76
+ // Create loaders for each preprocessor
77
+ const sassLoader = createModuleLoader('sass');
78
+ const lessLoader = createModuleLoader('less');
79
+ const stylusLoader = createModuleLoader('stylus');
25
80
 
26
81
  /**
27
82
  * Patterns that indicate SASS/SCSS syntax
@@ -51,13 +106,13 @@ const SASS_PATTERNS = [
51
106
  */
52
107
  const LESS_PATTERNS = [
53
108
  /@[\w-]+\s*:/, // Variables: @primary-color:
54
- /\.[\w-]+\s*\([^)]*\)\s*\{/, // Mixins: .button-styles() { }
55
- /\.[\w-]+\s*>\s*\([^)]*\)\s*\{/, // Parametric mixins: .button-styles > () { }
56
- /\.[\w-]+\([^)]*\);/, // Mixin calls: .button-styles();
109
+ /(?<!\()\.[\w-]+\s*\([^)]*\)\s*\{/, // Mixins: .button-styles() { } (not inside :not(.x) etc.)
110
+ /(?<!\()\.[\w-]+\s*>\s*\([^)]*\)\s*\{/, // Parametric mixins: .button-styles > () { }
111
+ /(?<!\()\.[\w-]+\([^)]*\);/, // Mixin calls: .button-styles(); (not inside pseudo-class parens)
57
112
  /&:extend\(/, // Extend: &:extend(.class)
58
113
  /@\{[\w-]+\}/, // Interpolation: @{variable}
59
114
  /when\s*\(/, // Guards: when (@a > 0)
60
- /\.[\w-]+\(\)\s*when/, // Guarded mixins
115
+ /(?<!\()\.[\w-]+\(\)\s*when/, // Guarded mixins
61
116
  /@import\s+\(.*\)\s+['"]/, // Import options: @import (less) "file"
62
117
  /@plugin\s+['"][^'"]+['"]/, // Plugin: @plugin "plugin-name"
63
118
  ];
@@ -79,16 +134,6 @@ const STYLUS_PATTERNS = [
79
134
  /^[\w-]+\s*\?=/m, // Conditional assignment: var ?= value
80
135
  ];
81
136
 
82
- /**
83
- * Patterns that indicate EITHER SASS or LESS syntax
84
- * These are shared between both preprocessors
85
- */
86
- const SHARED_PATTERNS = [
87
- /&\s*\{/, // Parent selector nesting
88
- /&:[\w-]+/, // Parent pseudo-class: &:hover
89
- /&\.[\w-]+/, // Parent class: &.active
90
- ];
91
-
92
137
  /**
93
138
  * Check if CSS contains exclusively SASS/SCSS-specific syntax
94
139
  * (excludes shared patterns that could be LESS or Stylus)
@@ -163,55 +208,10 @@ export function detectPreprocessor(css) {
163
208
  return 'none';
164
209
  }
165
210
 
166
- /**
167
- * Try to load the sass module from the user's project (sync version)
168
- * Uses require() for compatibility with sync contexts
169
- * @returns {object|false} The sass module or false if not available
170
- */
171
- export function tryLoadSassSync() {
172
- // Return cached result if already checked
173
- if (sassModule !== null) {
174
- return sassModule;
175
- }
176
-
177
- try {
178
- // Use createRequire to load sass synchronously from the user's project
179
- const require = createRequire(import.meta.url);
180
- sassModule = require('sass');
181
- return sassModule;
182
- } catch {
183
- // sass not installed in user's project
184
- sassModule = false;
185
- return false;
186
- }
187
- }
188
-
189
- /**
190
- * Try to load the sass module from the user's project (async version)
191
- * @returns {Promise<object|false>} The sass module or false if not available
192
- */
193
- export async function tryLoadSass() {
194
- // Return cached async result first, then sync cache
195
- if (sassModuleAsync !== null) {
196
- return sassModuleAsync;
197
- }
198
- if (sassModule !== null && sassModule !== false) {
199
- return sassModule;
200
- }
201
-
202
- try {
203
- // Try to import sass from the user's project
204
- const mod = await import('sass');
205
- sassModule = mod;
206
- sassModuleAsync = mod;
207
- return mod;
208
- } catch {
209
- // Fall back to sync require
210
- const syncResult = tryLoadSassSync();
211
- sassModuleAsync = syncResult || false;
212
- return syncResult;
213
- }
214
- }
211
+ /** @returns {object|false} The sass module or false */
212
+ export function tryLoadSassSync() { return sassLoader.loadSync(); }
213
+ /** @returns {Promise<object|false>} The sass module or false */
214
+ export async function tryLoadSass() { return sassLoader.loadAsync(); }
215
215
 
216
216
  /**
217
217
  * Compile SASS/SCSS to CSS (sync version)
@@ -299,90 +299,45 @@ export async function compileSass(scss, options = {}) {
299
299
  // with support for both SASS and LESS auto-detection
300
300
 
301
301
  /**
302
- * Check if sass package is available in user's project
303
- * @returns {boolean}
304
- */
305
- export function isSassAvailable() {
306
- const sass = tryLoadSassSync();
307
- return sass !== false;
308
- }
309
-
310
- /**
311
- * Check if sass package is available (async)
312
- * @returns {Promise<boolean>}
313
- */
314
- export async function isSassAvailableAsync() {
315
- const sass = await tryLoadSass();
316
- return sass !== false;
317
- }
318
-
319
- /**
320
- * Get sass version if available
321
- * @returns {string|null}
322
- */
323
- export function getSassVersion() {
324
- const sass = tryLoadSassSync();
325
- if (sass && sass.info) {
326
- // sass.info is a string like "dart-sass\t1.77.0"
327
- const match = sass.info.match(/(\d+\.\d+\.\d+)/);
302
+ * Generate utility functions for a preprocessor module loader.
303
+ * Eliminates boilerplate duplication across SASS/LESS/Stylus.
304
+ * @param {object} loader - Module loader from createModuleLoader()
305
+ * @param {function} versionExtractor - (mod) => string|null — extracts version from loaded module
306
+ * @returns {{ isAvailable, isAvailableAsync, getVersion, reset }}
307
+ */
308
+ function createPreprocessorUtils(loader, versionExtractor) {
309
+ return {
310
+ tryLoadSync: () => loader.loadSync(),
311
+ tryLoadAsync: () => loader.loadAsync(),
312
+ isAvailable: () => loader.isAvailable(),
313
+ isAvailableAsync: () => loader.isAvailableAsync(),
314
+ getVersion: () => {
315
+ const mod = loader.loadSync();
316
+ return mod ? versionExtractor(mod) : null;
317
+ },
318
+ reset: () => loader.reset(),
319
+ };
320
+ }
321
+
322
+ const sassUtils = createPreprocessorUtils(sassLoader, mod => {
323
+ if (mod && mod.info) {
324
+ const match = mod.info.match(/(\d+\.\d+\.\d+)/);
328
325
  return match ? match[1] : null;
329
326
  }
330
327
  return null;
331
- }
328
+ });
332
329
 
333
- /**
334
- * Reset the cached sass module (for testing)
335
- */
336
- export function resetSassCache() {
337
- sassModule = null;
338
- }
330
+ export function isSassAvailable() { return sassUtils.isAvailable(); }
331
+ export async function isSassAvailableAsync() { return sassUtils.isAvailableAsync(); }
332
+ export function getSassVersion() { return sassUtils.getVersion(); }
333
+ export function resetSassCache() { sassUtils.reset(); }
339
334
 
340
335
  // ===== LESS SUPPORT =====
341
336
 
342
- /**
343
- * Try to load the less module from the user's project (sync version)
344
- * @returns {object|false} The less module or false if not available
345
- */
346
- export function tryLoadLessSync() {
347
- // Return cached result if already checked
348
- if (lessModule !== null) {
349
- return lessModule;
350
- }
351
-
352
- try {
353
- const require = createRequire(import.meta.url);
354
- lessModule = require('less');
355
- return lessModule;
356
- } catch {
357
- // less not installed in user's project
358
- lessModule = false;
359
- return false;
360
- }
361
- }
362
-
363
- /**
364
- * Try to load the less module from the user's project (async version)
365
- * @returns {Promise<object|false>} The less module or false if not available
366
- */
367
- export async function tryLoadLess() {
368
- if (lessModuleAsync !== null) {
369
- return lessModuleAsync;
370
- }
371
- if (lessModule !== null && lessModule !== false) {
372
- return lessModule;
373
- }
374
-
375
- try {
376
- const mod = await import('less');
377
- lessModule = mod;
378
- lessModuleAsync = mod;
379
- return mod;
380
- } catch {
381
- const syncResult = tryLoadLessSync();
382
- lessModuleAsync = syncResult || false;
383
- return syncResult;
384
- }
385
- }
337
+ /** @returns {object|false} The less module or false */
338
+ export function tryLoadLessSync() { return lessLoader.loadSync(); }
339
+ /** @returns {Promise<object|false>} The less module or false */
340
+ export async function tryLoadLess() { return lessLoader.loadAsync(); }
386
341
 
387
342
  /**
388
343
  * Compile LESS to CSS (async - LESS is async-only)
@@ -448,89 +403,24 @@ export function compileLessSync(_less, _options = {}) {
448
403
  return null;
449
404
  }
450
405
 
451
- /**
452
- * Check if less package is available in user's project
453
- * @returns {boolean}
454
- */
455
- export function isLessAvailable() {
456
- const less = tryLoadLessSync();
457
- return less !== false;
458
- }
459
-
460
- /**
461
- * Check if less package is available (async)
462
- * @returns {Promise<boolean>}
463
- */
464
- export async function isLessAvailableAsync() {
465
- const less = await tryLoadLess();
466
- return less !== false;
467
- }
468
-
469
- /**
470
- * Get less version if available
471
- * @returns {string|null}
472
- */
473
- export function getLessVersion() {
474
- const less = tryLoadLessSync();
475
- if (less && less.version) {
476
- return Array.isArray(less.version) ? less.version.join('.') : less.version;
406
+ const lessUtils = createPreprocessorUtils(lessLoader, mod => {
407
+ if (mod && mod.version) {
408
+ return Array.isArray(mod.version) ? mod.version.join('.') : mod.version;
477
409
  }
478
410
  return null;
479
- }
411
+ });
480
412
 
481
- /**
482
- * Reset the cached less module (for testing)
483
- */
484
- export function resetLessCache() {
485
- lessModule = null;
486
- }
413
+ export function isLessAvailable() { return lessUtils.isAvailable(); }
414
+ export async function isLessAvailableAsync() { return lessUtils.isAvailableAsync(); }
415
+ export function getLessVersion() { return lessUtils.getVersion(); }
416
+ export function resetLessCache() { lessUtils.reset(); }
487
417
 
488
418
  // ===== STYLUS SUPPORT =====
489
419
 
490
- /**
491
- * Try to load the stylus module from the user's project (sync version)
492
- * @returns {object|false} The stylus module or false if not available
493
- */
494
- export function tryLoadStylusSync() {
495
- // Return cached result if already checked
496
- if (stylusModule !== null) {
497
- return stylusModule;
498
- }
499
-
500
- try {
501
- const require = createRequire(import.meta.url);
502
- stylusModule = require('stylus');
503
- return stylusModule;
504
- } catch {
505
- // stylus not installed in user's project
506
- stylusModule = false;
507
- return false;
508
- }
509
- }
510
-
511
- /**
512
- * Try to load the stylus module from the user's project (async version)
513
- * @returns {Promise<object|false>} The stylus module or false if not available
514
- */
515
- export async function tryLoadStylus() {
516
- if (stylusModuleAsync !== null) {
517
- return stylusModuleAsync;
518
- }
519
- if (stylusModule !== null && stylusModule !== false) {
520
- return stylusModule;
521
- }
522
-
523
- try {
524
- const mod = await import('stylus');
525
- stylusModule = mod;
526
- stylusModuleAsync = mod;
527
- return mod;
528
- } catch {
529
- const syncResult = tryLoadStylusSync();
530
- stylusModuleAsync = syncResult || false;
531
- return syncResult;
532
- }
533
- }
420
+ /** @returns {object|false} The stylus module or false */
421
+ export function tryLoadStylusSync() { return stylusLoader.loadSync(); }
422
+ /** @returns {Promise<object|false>} The stylus module or false */
423
+ export async function tryLoadStylus() { return stylusLoader.loadAsync(); }
534
424
 
535
425
  /**
536
426
  * Compile Stylus to CSS (async)
@@ -638,42 +528,14 @@ export function compileStylusSync(stylus, options = {}) {
638
528
  }
639
529
  }
640
530
 
641
- /**
642
- * Check if stylus package is available in user's project
643
- * @returns {boolean}
644
- */
645
- export function isStylusAvailable() {
646
- const stylus = tryLoadStylusSync();
647
- return stylus !== false;
648
- }
649
-
650
- /**
651
- * Check if stylus package is available (async)
652
- * @returns {Promise<boolean>}
653
- */
654
- export async function isStylusAvailableAsync() {
655
- const stylus = await tryLoadStylus();
656
- return stylus !== false;
657
- }
658
-
659
- /**
660
- * Get stylus version if available
661
- * @returns {string|null}
662
- */
663
- export function getStylusVersion() {
664
- const stylus = tryLoadStylusSync();
665
- if (stylus && stylus.version) {
666
- return stylus.version;
667
- }
668
- return null;
669
- }
531
+ const stylusUtils = createPreprocessorUtils(stylusLoader, mod => {
532
+ return (mod && mod.version) ? mod.version : null;
533
+ });
670
534
 
671
- /**
672
- * Reset the cached stylus module (for testing)
673
- */
674
- export function resetStylusCache() {
675
- stylusModule = null;
676
- }
535
+ export function isStylusAvailable() { return stylusUtils.isAvailable(); }
536
+ export async function isStylusAvailableAsync() { return stylusUtils.isAvailableAsync(); }
537
+ export function getStylusVersion() { return stylusUtils.getVersion(); }
538
+ export function resetStylusCache() { stylusUtils.reset(); }
677
539
 
678
540
  /**
679
541
  * Preprocess CSS - auto-detect and compile SASS, LESS, or Stylus if detected (sync)
@@ -53,6 +53,10 @@ export class SourceMapGenerator {
53
53
  this.names = [];
54
54
  this.mappings = [];
55
55
 
56
+ // O(1) lookup maps for deduplication
57
+ this._sourceIndex = new Map();
58
+ this._nameIndex = new Map();
59
+
56
60
  // Current state for relative encoding
57
61
  this._lastGeneratedLine = 0;
58
62
  this._lastGeneratedColumn = 0;
@@ -69,11 +73,12 @@ export class SourceMapGenerator {
69
73
  * @returns {number} Source index
70
74
  */
71
75
  addSource(source, content = null) {
72
- let index = this.sources.indexOf(source);
73
- if (index === -1) {
76
+ let index = this._sourceIndex.get(source);
77
+ if (index === undefined) {
74
78
  index = this.sources.length;
75
79
  this.sources.push(source);
76
80
  this.sourcesContent.push(content);
81
+ this._sourceIndex.set(source, index);
77
82
  }
78
83
  return index;
79
84
  }
@@ -84,10 +89,11 @@ export class SourceMapGenerator {
84
89
  * @returns {number} Name index
85
90
  */
86
91
  addName(name) {
87
- let index = this.names.indexOf(name);
88
- if (index === -1) {
92
+ let index = this._nameIndex.get(name);
93
+ if (index === undefined) {
89
94
  index = this.names.length;
90
95
  this.names.push(name);
96
+ this._nameIndex.set(name, index);
91
97
  }
92
98
  return index;
93
99
  }