juxscript 1.0.37 → 1.0.39

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.
@@ -0,0 +1,244 @@
1
+ # JUX Deprecation System
2
+
3
+ This document explains how deprecation warnings are implemented in JUX components to help users migrate to newer patterns while maintaining backward compatibility.
4
+
5
+ ## Overview
6
+
7
+ JUX uses a **console-based deprecation warning system** that:
8
+ - Shows warnings **once per feature per page load** (no spam)
9
+ - Includes **removal date and version** information
10
+ - Provides **migration guidance** via documentation links
11
+ - Warns on **constructor options, fluent methods, and sync bindings**
12
+
13
+ ## Implementation Pattern
14
+
15
+ ### 1. Define the Deprecation Message
16
+
17
+ At the top of any component file:
18
+
19
+ ```typescript
20
+ // filepath: /path/to/component.ts
21
+
22
+ // Deprecation warning template
23
+ const DEPRECATION_WARNING = (feature: string) =>
24
+ `[JUX Deprecation Warning] ComponentName.${feature} will be removed in v1.0.30+ (January 30, 2026). ` +
25
+ `Please use CSS or the .style() method instead. See: [docs placeholder]`;
26
+
27
+ // Track which warnings have been shown (prevents spam)
28
+ const _shownWarnings = new Set<string>();
29
+
30
+ function warnOnce(feature: string): void {
31
+ if (!_shownWarnings.has(feature)) {
32
+ console.warn(DEPRECATION_WARNING(feature));
33
+ _shownWarnings.add(feature);
34
+ }
35
+ }
36
+ ```
37
+
38
+ ### 2. Warn in Constructor (Options)
39
+
40
+ ```typescript
41
+ export class Container extends BaseComponent<ContainerState> {
42
+ constructor(id: string, options: ContainerOptions = {}) {
43
+ // Check for deprecated options and warn
44
+ if (options.direction !== undefined) warnOnce('direction (option)');
45
+ if (options.gap !== undefined) warnOnce('gap (option)');
46
+ if (options.padding !== undefined) warnOnce('padding (option)');
47
+
48
+ super(id, {
49
+ direction: options.direction ?? 'column',
50
+ gap: options.gap ?? 0,
51
+ padding: options.padding ?? '0'
52
+ });
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### 3. Warn in Fluent Methods
58
+
59
+ ```typescript
60
+ export class Container extends BaseComponent<ContainerState> {
61
+
62
+ direction(value: 'row' | 'column'): this {
63
+ warnOnce('direction()');
64
+ this.state.direction = value;
65
+ return this;
66
+ }
67
+
68
+ gap(value: number | string): this {
69
+ warnOnce('gap()');
70
+ this.state.gap = value;
71
+ return this;
72
+ }
73
+
74
+ padding(value: string): this {
75
+ warnOnce('padding()');
76
+ this.state.padding = value;
77
+ return this;
78
+ }
79
+ }
80
+ ```
81
+
82
+ ### 4. Warn in Sync Bindings (Optional)
83
+
84
+ If a deprecated property can be synced with state:
85
+
86
+ ```typescript
87
+ render(targetId?: string): this {
88
+ // ...existing render code...
89
+
90
+ this._syncBindings.forEach(({ property, stateObj, toComponent }) => {
91
+ const transform = toComponent || ((v: any) => v);
92
+
93
+ if (property === 'direction') {
94
+ warnOnce('direction (sync)');
95
+ stateObj.subscribe((val: any) => {
96
+ this.state.direction = String(transform(val));
97
+ // ...apply changes to DOM...
98
+ });
99
+ }
100
+ // ...existing code...
101
+ });
102
+
103
+ return this;
104
+ }
105
+ ```
106
+
107
+ ## User Experience
108
+
109
+ When a user writes deprecated code:
110
+
111
+ ```javascript
112
+ // This will show a console warning
113
+ jux.container('my-container')
114
+ .direction('row') // ⚠️ Warning: Container.direction() will be removed...
115
+ .gap(20) // ⚠️ Warning: Container.gap() will be removed...
116
+ .render('#app');
117
+
118
+ // Subsequent calls DON'T show warnings (no spam)
119
+ jux.container('another')
120
+ .gap(10) // Silent (already warned once)
121
+ .render('#app');
122
+ ```
123
+
124
+ **Console output:**
125
+ ```
126
+ [JUX Deprecation Warning] Container.direction() will be removed in v1.0.30+
127
+ (January 30, 2026). Please use CSS or the .style() method instead.
128
+ See: [docs placeholder]
129
+
130
+ [JUX Deprecation Warning] Container.gap() will be removed in v1.0.30+
131
+ (January 30, 2026). Please use CSS or the .style() method instead.
132
+ See: [docs placeholder]
133
+ ```
134
+
135
+ ## Migration Path
136
+
137
+ Users should migrate from deprecated properties to CSS:
138
+
139
+ ```javascript
140
+ // ❌ Old (deprecated)
141
+ jux.container('box')
142
+ .direction('row')
143
+ .gap(20)
144
+ .padding('1rem')
145
+ .render('#app');
146
+
147
+ // ✅ New (recommended)
148
+ jux.container('box')
149
+ .style('display: flex; flex-direction: row; gap: 20px; padding: 1rem;')
150
+ .render('#app');
151
+
152
+ // ✅ Or use a CSS class
153
+ jux.container('box')
154
+ .class('flex-row gap-20 p-4')
155
+ .render('#app');
156
+ ```
157
+
158
+ ## Why This Approach?
159
+
160
+ 1. **Non-breaking**: Old code continues to work during deprecation period
161
+ 2. **Gradual migration**: Developers can update at their own pace
162
+ 3. **Clear timeline**: Version and date make planning easy
163
+ 4. **No console spam**: Each warning shown only once per page load
164
+ 5. **Clear guidance**: Links to migration documentation
165
+
166
+ ## Adding New Deprecations
167
+
168
+ To deprecate a feature in any component:
169
+
170
+ ### Step 1: Add Warning Infrastructure
171
+
172
+ ```typescript
173
+ const DEPRECATION_WARNING = (feature: string) =>
174
+ `[JUX Deprecation Warning] YourComponent.${feature} will be removed in vX.X.X+ (Date). ` +
175
+ `Migration guide: [docs placeholder]`;
176
+
177
+ const _shownWarnings = new Set<string>();
178
+
179
+ function warnOnce(feature: string): void {
180
+ if (!_shownWarnings.has(feature)) {
181
+ console.warn(DEPRECATION_WARNING(feature));
182
+ _shownWarnings.add(feature);
183
+ }
184
+ }
185
+ ```
186
+
187
+ ### Step 2: Call warnOnce() Everywhere the Feature is Used
188
+
189
+ - In constructor for options
190
+ - In fluent methods
191
+ - In sync binding handlers (if applicable)
192
+
193
+ ### Step 3: Update Documentation
194
+
195
+ - Add migration examples to docs
196
+ - Update changelog
197
+ - Set removal date (typically 6-12 months out)
198
+
199
+ ### Step 4: Add to Current Deprecations Table Below
200
+
201
+ ## Current Deprecations
202
+
203
+ | Component | Feature | Replacement | Removal Version | Removal Date |
204
+ |-----------|---------|-------------|-----------------|--------------|
205
+ | Container | `.direction()` | `.style('flex-direction: ...')` | v1.0.30+ | Jan 30, 2026 |
206
+ | Container | `.gap()` | `.style('gap: ...')` | v1.0.30+ | Jan 30, 2026 |
207
+ | Container | `.wrap()` | `.style('flex-wrap: ...')` | v1.0.30+ | Jan 30, 2026 |
208
+ | Container | `.align()` | `.style('align-items: ...')` | v1.0.30+ | Jan 30, 2026 |
209
+ | Container | `.justify()` | `.style('justify-content: ...')` | v1.0.30+ | Jan 30, 2026 |
210
+ | Container | `.padding()` | `.style('padding: ...')` | v1.0.30+ | Jan 30, 2026 |
211
+
212
+ ## Testing Deprecation Warnings
213
+
214
+ You can verify deprecation warnings are working:
215
+
216
+ ```javascript
217
+ // Open browser console and run:
218
+ jux.container('test')
219
+ .direction('row') // Should show warning
220
+ .gap(20) // Should show warning
221
+ .render('#app');
222
+
223
+ jux.container('test2')
224
+ .direction('row') // Should NOT show warning (already shown)
225
+ .render('#app');
226
+ ```
227
+
228
+ ## Best Practices
229
+
230
+ 1. **Give users time**: At least 6 months before removal
231
+ 2. **Be specific**: Show exact feature name in warning
232
+ 3. **Provide alternatives**: Always suggest what to use instead
233
+ 4. **Document migrations**: Create clear before/after examples
234
+ 5. **Version appropriately**: Use semver correctly (major bump for removals)
235
+
236
+ ## Questions?
237
+
238
+ - Migration guides: [docs placeholder - coming soon]
239
+ - Breaking changes: [docs placeholder - coming soon]
240
+ - Changelog: [GitHub releases placeholder]
241
+
242
+ ---
243
+
244
+ **Last updated**: January 28, 2026
Binary file
Binary file
@@ -101,12 +101,6 @@
101
101
  "description": "List component",
102
102
  "constructor": "jux.list(id: string, options: ListOptions = {})",
103
103
  "fluentMethods": [
104
- {
105
- "name": "items",
106
- "params": "(value)",
107
- "returns": "this",
108
- "description": "Set items"
109
- },
110
104
  {
111
105
  "name": "addItem",
112
106
  "params": "(value)",
@@ -2059,5 +2053,5 @@
2059
2053
  }
2060
2054
  ],
2061
2055
  "version": "1.0.0",
2062
- "lastUpdated": "2026-01-28T15:24:08.460Z"
2056
+ "lastUpdated": "2026-01-28T17:24:41.869Z"
2063
2057
  }
@@ -419,38 +419,78 @@ export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {})
419
419
  }
420
420
 
421
421
  /**
422
- * Extract shared module exports from a .jux file
423
- *
424
- * @param {string} juxContent - Original .jux file content
425
- * @param {string} moduleName - Module identifier
426
- * @returns {string} Shared module code
422
+ * Extract and replace all string literals with placeholders
423
+ * Handles: template literals WITHOUT interpolations, single quotes, double quotes
424
+ * SKIPS: Template literals WITH ${} interpolations (those are dynamic code)
427
425
  */
428
- function extractSharedModule(juxContent, moduleName) {
429
- // Remove string literals temporarily to avoid false matches
426
+ function extractStrings(code) {
430
427
  const strings = [];
428
+ let result = code;
429
+
430
+ // 1. Template literals WITHOUT interpolations (static strings only)
431
+ // Match backticks that DON'T contain ${
432
+ result = result.replace(/`([^`$\\]|\\[^$])*`/g, (match) => {
433
+ // Double-check it doesn't contain ${
434
+ if (!match.includes('${')) {
435
+ strings.push(match);
436
+ return `__STRING_${strings.length - 1}__`;
437
+ }
438
+ return match; // Leave dynamic templates alone
439
+ });
431
440
 
432
- // Handle template literals, single quotes, and double quotes separately
433
- let code = juxContent
434
- .replace(/`(?:\\.|[^`\\])*`/g, (match) => { strings.push(match); return `__STRING_${strings.length - 1}__`; })
435
- .replace(/"(?:\\.|[^"\\])*"/g, (match) => { strings.push(match); return `__STRING_${strings.length - 1}__`; })
436
- .replace(/'(?:\\.|[^'\\])*'/g, (match) => { strings.push(match); return `__STRING_${strings.length - 1}__`; });
441
+ // 2. Double-quoted strings (with escaped quotes)
442
+ result = result.replace(/"(?:[^"\\]|\\.)*"/g, (match) => {
443
+ strings.push(match);
444
+ return `__STRING_${strings.length - 1}__`;
445
+ });
437
446
 
438
- // Remove ALL imports - including _dev-imports.js
439
- code = code.replace(/import\s+\{[^}]+\}\s+from\s+__STRING_\d+__;?\s*/g, '');
440
- code = code.replace(/import\s+\*\s+as\s+\w+\s+from\s+__STRING_\d+__;?\s*/g, '');
441
- code = code.replace(/import\s+__STRING_\d+__;?\s*/g, '');
447
+ // 3. Single-quoted strings (with escaped quotes)
448
+ result = result.replace(/'(?:[^'\\]|\\.)*'/g, (match) => {
449
+ strings.push(match);
450
+ return `__STRING_${strings.length - 1}__`;
451
+ });
442
452
 
443
- // Convert exports to declarations (keep function keyword for functions)
444
- code = code.replace(/export\s+const\s+/g, 'const ');
445
- code = code.replace(/export\s+let\s+/g, 'let ');
446
- code = code.replace(/export\s+function\s+/g, 'function ');
447
- code = code.replace(/export\s+class\s+/g, 'class ');
448
- code = code.replace(/export\s+\{([^}]+)\}\s*;?\s*/g, '');
453
+ return { code: result, strings };
454
+ }
449
455
 
450
- // Restore string literals
451
- code = code.replace(/__STRING_(\d+)__/g, (_, index) => strings[index]);
456
+ /**
457
+ * Restore string placeholders back to original strings
458
+ */
459
+ function restoreStrings(code, strings) {
460
+ return code.replace(/__STRING_(\d+)__/g, (match, index) => {
461
+ const idx = parseInt(index, 10);
462
+ if (idx >= 0 && idx < strings.length) {
463
+ return strings[idx];
464
+ }
465
+ console.warn(`[Compiler] String placeholder ${match} not found (idx: ${idx}, available: ${strings.length})`);
466
+ return match; // Leave as-is for debugging
467
+ });
468
+ }
452
469
 
453
- return code;
470
+ /**
471
+ * Extract shared module exports from a .jux file
472
+ *
473
+ * @param {string} juxContent - Original .jux file content
474
+ * @param {string} moduleName - Module identifier
475
+ * @returns {string} Shared module code
476
+ */
477
+ function extractSharedModule(juxContent, moduleName) {
478
+ const { code, strings } = extractStrings(juxContent);
479
+
480
+ // Remove ALL imports
481
+ let result = code.replace(/import\s+\{[^}]+\}\s+from\s+__STRING_\d+__;?\s*/g, '');
482
+ result = result.replace(/import\s+\*\s+as\s+\w+\s+from\s+__STRING_\d+__;?\s*/g, '');
483
+ result = result.replace(/import\s+__STRING_\d+__;?\s*/g, '');
484
+
485
+ // Convert exports to declarations
486
+ result = result.replace(/export\s+const\s+/g, 'const ');
487
+ result = result.replace(/export\s+let\s+/g, 'let ');
488
+ result = result.replace(/export\s+function\s+/g, 'function ');
489
+ result = result.replace(/export\s+class\s+/g, 'class ');
490
+ result = result.replace(/export\s+\{([^}]+)\}\s*;?\s*/g, '');
491
+
492
+ // Restore strings
493
+ return restoreStrings(result, strings);
454
494
  }
455
495
 
456
496
  /**
@@ -464,36 +504,27 @@ function extractSharedModule(juxContent, moduleName) {
464
504
  * @returns {string} View function code
465
505
  */
466
506
  function transformJuxToViewFunction(juxContent, functionName, pageName, relativePath, sharedModules) {
467
- // Remove string literals temporarily to avoid false matches
468
- const strings = [];
469
-
470
- // Handle template literals, single quotes, and double quotes separately
471
- let code = juxContent
472
- .replace(/`(?:\\.|[^`\\])*`/g, (match) => { strings.push(match); return `__STRING_${strings.length - 1}__`; })
473
- .replace(/"(?:\\.|[^"\\])*"/g, (match) => { strings.push(match); return `__STRING_${strings.length - 1}__`; })
474
- .replace(/'(?:\\.|[^'\\])*'/g, (match) => { strings.push(match); return `__STRING_${strings.length - 1}__`; });
475
-
476
- // Remove ALL imports - now matching against placeholders
477
- code = code.replace(/import\s+\{[^}]+\}\s+from\s+__STRING_\d+__;?\s*/g, '');
478
- code = code.replace(/import\s+\*\s+as\s+\w+\s+from\s+__STRING_\d+__;?\s*/g, '');
479
- code = code.replace(/import\s+__STRING_\d+__;?\s*/g, '');
507
+ const { code, strings } = extractStrings(juxContent);
480
508
 
481
- // Handle exports - convert named exports to const declarations
482
- code = code.replace(/export\s+const\s+(\w+)\s*=/g, 'const $1 =');
483
- code = code.replace(/export\s+let\s+(\w+)\s*=/g, 'let $1 =');
484
- code = code.replace(/export\s+function\s+(\w+)/g, 'const $1 = function');
485
- code = code.replace(/export\s+class\s+/g, 'class ');
486
- code = code.replace(/export\s+default\s+/g, '');
487
- code = code.replace(/export\s+\{([^}]+)\}\s*;?\s*/g, '');
509
+ // Remove ALL imports
510
+ let result = code.replace(/import\s+\{[^}]+\}\s+from\s+__STRING_\d+__;?\s*/g, '');
511
+ result = result.replace(/import\s+\*\s+as\s+\w+\s+from\s+__STRING_\d+__;?\s*/g, '');
512
+ result = result.replace(/import\s+__STRING_\d+__;?\s*/g, '');
488
513
 
489
- // Only replace .renderTo(container) calls - leave explicit render targets alone
490
- code = code.replace(/\.renderTo\(container\)/g, '.render("#app")');
514
+ // Handle exports
515
+ result = result.replace(/export\s+const\s+(\w+)\s*=/g, 'const $1 =');
516
+ result = result.replace(/export\s+let\s+(\w+)\s*=/g, 'let $1 =');
517
+ result = result.replace(/export\s+function\s+(\w+)/g, 'const $1 = function');
518
+ result = result.replace(/export\s+class\s+/g, 'class ');
519
+ result = result.replace(/export\s+default\s+/g, '');
520
+ result = result.replace(/export\s+\{([^}]+)\}\s*;?\s*/g, '');
491
521
 
492
- // Only replace empty .render() calls (no arguments)
493
- code = code.replace(/\.render\(\s*\)/g, '.render("#app")');
522
+ // Replace render patterns
523
+ result = result.replace(/\.renderTo\(container\)/g, '.render("#app")');
524
+ result = result.replace(/\.render\(\s*\)/g, '.render("#app")');
494
525
 
495
- // Restore string literals
496
- code = code.replace(/__STRING_(\d+)__/g, (_, index) => strings[index]);
526
+ // Restore strings
527
+ result = restoreStrings(result, strings);
497
528
 
498
529
  const cleanName = functionName
499
530
  .replace(/[-_]/g, ' ')
@@ -504,7 +535,7 @@ function transformJuxToViewFunction(juxContent, functionName, pageName, relative
504
535
  return `
505
536
  // View: ${cleanName}
506
537
  function ${cleanName}() {
507
- ${code}
538
+ ${result}
508
539
 
509
540
  return document.getElementById('app');
510
541
  }`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.0.37",
3
+ "version": "1.0.39",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "lib/jux.js",
@@ -28,6 +28,7 @@
28
28
  "files": [
29
29
  "lib",
30
30
  "bin",
31
+ "docs",
31
32
  "machinery",
32
33
  "presets",
33
34
  "types",