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-
|
|
2056
|
+
"lastUpdated": "2026-01-28T17:24:41.869Z"
|
|
2063
2057
|
}
|
package/machinery/compiler.js
CHANGED
|
@@ -419,38 +419,78 @@ export async function bundleJuxFilesToRouter(projectRoot, distDir, options = {})
|
|
|
419
419
|
}
|
|
420
420
|
|
|
421
421
|
/**
|
|
422
|
-
* Extract
|
|
423
|
-
*
|
|
424
|
-
*
|
|
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
|
|
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
|
-
//
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
//
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
-
|
|
444
|
-
|
|
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
|
-
|
|
451
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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
|
-
//
|
|
490
|
-
|
|
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
|
-
//
|
|
493
|
-
|
|
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
|
|
496
|
-
|
|
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
|
-
${
|
|
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.
|
|
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",
|