ripple 0.2.142 → 0.2.144
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/package.json +2 -2
- package/src/compiler/phases/1-parse/index.js +50 -45
- package/src/compiler/phases/2-analyze/index.js +7 -4
- package/src/compiler/phases/3-transform/client/index.js +4 -2
- package/src/compiler/scope.js +4 -1
- package/src/runtime/internal/client/render.js +22 -14
- package/tests/client/basic/basic.components.test.ripple +36 -0
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Ripple is an elegant TypeScript UI framework",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.144",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -81,6 +81,6 @@
|
|
|
81
81
|
"typescript": "^5.9.2"
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
|
84
|
-
"ripple": "0.2.
|
|
84
|
+
"ripple": "0.2.144"
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -491,6 +491,7 @@ function RipplePlugin(config) {
|
|
|
491
491
|
forInit,
|
|
492
492
|
);
|
|
493
493
|
}
|
|
494
|
+
|
|
494
495
|
/**
|
|
495
496
|
* Parse expression atom - handles TrackedArray and TrackedObject literals
|
|
496
497
|
* @param {any} [refDestructuringErrors]
|
|
@@ -524,6 +525,11 @@ function RipplePlugin(config) {
|
|
|
524
525
|
return this.parseTrackedObjectExpression();
|
|
525
526
|
}
|
|
526
527
|
|
|
528
|
+
// Check if this is a component expression (e.g., in object literal values)
|
|
529
|
+
if (this.type === tt.name && this.value === 'component') {
|
|
530
|
+
return this.parseComponent();
|
|
531
|
+
}
|
|
532
|
+
|
|
527
533
|
return super.parseExprAtom(refDestructuringErrors, forNew, forInit);
|
|
528
534
|
}
|
|
529
535
|
|
|
@@ -714,33 +720,55 @@ function RipplePlugin(config) {
|
|
|
714
720
|
return this.finishNode(node, 'TrackedObjectExpression');
|
|
715
721
|
}
|
|
716
722
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
723
|
+
/**
|
|
724
|
+
* Parse a component - common implementation used by statements, expressions, and export defaults
|
|
725
|
+
* @param {Object} options - Parsing options
|
|
726
|
+
* @param {boolean} [options.requireName=false] - Whether component name is required
|
|
727
|
+
* @param {boolean} [options.isDefault=false] - Whether this is an export default component
|
|
728
|
+
* @param {boolean} [options.declareName=false] - Whether to declare the name in scope
|
|
729
|
+
* @returns {any} Component node
|
|
730
|
+
*/
|
|
731
|
+
parseComponent({ requireName = false, isDefault = false, declareName = false } = {}) {
|
|
732
|
+
const node = this.startNode();
|
|
733
|
+
node.type = 'Component';
|
|
734
|
+
node.css = null;
|
|
735
|
+
node.default = isDefault;
|
|
736
|
+
this.next(); // consume 'component'
|
|
737
|
+
this.enterScope(0);
|
|
726
738
|
|
|
739
|
+
if (requireName) {
|
|
740
|
+
node.id = this.parseIdent();
|
|
741
|
+
if (declareName) {
|
|
742
|
+
this.declareName(node.id.name, 'var', node.id.start);
|
|
743
|
+
}
|
|
744
|
+
} else {
|
|
727
745
|
node.id = this.type.label === 'name' ? this.parseIdent() : null;
|
|
746
|
+
if (declareName && node.id) {
|
|
747
|
+
this.declareName(node.id.name, 'var', node.id.start);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
728
750
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
751
|
+
this.parseFunctionParams(node);
|
|
752
|
+
this.eat(tt.braceL);
|
|
753
|
+
node.body = [];
|
|
754
|
+
this.#path.push(node);
|
|
733
755
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
756
|
+
this.parseTemplateBody(node.body);
|
|
757
|
+
this.#path.pop();
|
|
758
|
+
this.exitScope();
|
|
737
759
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
760
|
+
this.next();
|
|
761
|
+
skipWhitespace(this);
|
|
762
|
+
this.finishNode(node, 'Component');
|
|
763
|
+
this.awaitPos = 0;
|
|
742
764
|
|
|
743
|
-
|
|
765
|
+
return node;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
parseExportDefaultDeclaration() {
|
|
769
|
+
// Check if this is "export default component"
|
|
770
|
+
if (this.value === 'component') {
|
|
771
|
+
return this.parseComponent({ isDefault: true });
|
|
744
772
|
}
|
|
745
773
|
|
|
746
774
|
return super.parseExportDefaultDeclaration();
|
|
@@ -1650,31 +1678,8 @@ function RipplePlugin(config) {
|
|
|
1650
1678
|
|
|
1651
1679
|
if (this.value === 'component') {
|
|
1652
1680
|
this.awaitPos = 0;
|
|
1653
|
-
|
|
1654
|
-
node.type = 'Component';
|
|
1655
|
-
node.css = null;
|
|
1656
|
-
this.next();
|
|
1657
|
-
this.enterScope(0);
|
|
1658
|
-
node.id = this.parseIdent();
|
|
1659
|
-
this.declareName(node.id.name, 'var', node.id.start);
|
|
1660
|
-
this.parseFunctionParams(node);
|
|
1661
|
-
this.eat(tt.braceL);
|
|
1662
|
-
node.body = [];
|
|
1663
|
-
this.#path.push(node);
|
|
1664
|
-
|
|
1665
|
-
this.parseTemplateBody(node.body);
|
|
1666
|
-
|
|
1667
|
-
this.#path.pop();
|
|
1668
|
-
this.exitScope();
|
|
1669
|
-
|
|
1670
|
-
this.next();
|
|
1671
|
-
skipWhitespace(this);
|
|
1672
|
-
this.finishNode(node, 'Component');
|
|
1673
|
-
this.awaitPos = 0;
|
|
1674
|
-
|
|
1675
|
-
return node;
|
|
1681
|
+
return this.parseComponent({ requireName: true, declareName: true });
|
|
1676
1682
|
}
|
|
1677
|
-
|
|
1678
1683
|
if (this.type.label === '@') {
|
|
1679
1684
|
// Try to parse as an expression statement first using tryParse
|
|
1680
1685
|
// This allows us to handle Ripple @ syntax like @count++ without
|
|
@@ -356,10 +356,13 @@ const visitors = {
|
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
// Store component metadata in analysis
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
359
|
+
// Only add metadata if component has a name (not anonymous)
|
|
360
|
+
if (node.id) {
|
|
361
|
+
context.state.analysis.component_metadata.push({
|
|
362
|
+
id: node.id.name,
|
|
363
|
+
async: metadata.await,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
363
366
|
},
|
|
364
367
|
|
|
365
368
|
ForStatement(node, context) {
|
|
@@ -925,8 +925,10 @@ const visitors = {
|
|
|
925
925
|
const args = [handler, b.id('__block'), ...hoisted_params];
|
|
926
926
|
delegated_assignment = b.array(args);
|
|
927
927
|
} else if (
|
|
928
|
-
handler.type === 'Identifier' &&
|
|
929
|
-
|
|
928
|
+
(handler.type === 'Identifier' &&
|
|
929
|
+
is_declared_function_within_component(handler, context)) ||
|
|
930
|
+
handler.type === 'ArrowFunctionExpression' ||
|
|
931
|
+
handler.type === 'FunctionExpression'
|
|
930
932
|
) {
|
|
931
933
|
delegated_assignment = handler;
|
|
932
934
|
} else {
|
package/src/compiler/scope.js
CHANGED
|
@@ -150,7 +150,10 @@ export function create_scopes(ast, root, parent) {
|
|
|
150
150
|
const scope = state.scope.child();
|
|
151
151
|
scopes.set(node, scope);
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
// Only declare the component name if it has an id (not anonymous)
|
|
154
|
+
if (node.id) {
|
|
155
|
+
scope.declare(node.id, 'normal', 'component');
|
|
156
|
+
}
|
|
154
157
|
|
|
155
158
|
add_params(scope, node.params);
|
|
156
159
|
next({ scope });
|
|
@@ -194,7 +194,12 @@ function set_attribute_helper(element, key, value) {
|
|
|
194
194
|
* @returns {string}
|
|
195
195
|
*/
|
|
196
196
|
function to_class(value, hash) {
|
|
197
|
-
return value == null
|
|
197
|
+
return value == null
|
|
198
|
+
? (hash ?? '')
|
|
199
|
+
: // Fast-path for string values
|
|
200
|
+
typeof value === 'string'
|
|
201
|
+
? value + (hash ? ' ' + hash : '')
|
|
202
|
+
: clsx([value, hash]);
|
|
198
203
|
}
|
|
199
204
|
|
|
200
205
|
/**
|
|
@@ -207,24 +212,27 @@ function to_class(value, hash) {
|
|
|
207
212
|
export function set_class(dom, value, hash, is_html = true) {
|
|
208
213
|
// @ts-expect-error need to add __className to patched prototype
|
|
209
214
|
var prev_class_name = dom.__className;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
dom.className = next_class_name;
|
|
215
|
+
|
|
216
|
+
if (prev_class_name !== value) {
|
|
217
|
+
var next_class_name = to_class(value, hash);
|
|
218
|
+
|
|
219
|
+
if (prev_class_name !== next_class_name) {
|
|
220
|
+
// Removing the attribute when the value is only an empty string causes
|
|
221
|
+
// peformance issues vs simply making the className an empty string. So
|
|
222
|
+
// we should only remove the class if the the value is nullish.
|
|
223
|
+
if (value == null && !hash) {
|
|
224
|
+
dom.removeAttribute('class');
|
|
221
225
|
} else {
|
|
222
|
-
|
|
226
|
+
if (is_html) {
|
|
227
|
+
dom.className = next_class_name;
|
|
228
|
+
} else {
|
|
229
|
+
dom.setAttribute('class', next_class_name);
|
|
230
|
+
}
|
|
223
231
|
}
|
|
224
232
|
}
|
|
225
233
|
|
|
226
234
|
// @ts-expect-error need to add __className to patched prototype
|
|
227
|
-
dom.__className =
|
|
235
|
+
dom.__className = value;
|
|
228
236
|
}
|
|
229
237
|
}
|
|
230
238
|
|
|
@@ -232,4 +232,40 @@ describe('basic client > components & composition', () => {
|
|
|
232
232
|
flushSync();
|
|
233
233
|
expect(countSpan.textContent).toBe('1');
|
|
234
234
|
});
|
|
235
|
+
|
|
236
|
+
it('renders components as named and anonymous properties', () => {
|
|
237
|
+
const UI = {
|
|
238
|
+
span: component Span(){
|
|
239
|
+
<span>{'Hello from Span'}</span>
|
|
240
|
+
},
|
|
241
|
+
button: component({ children }) {
|
|
242
|
+
<button>
|
|
243
|
+
<children />
|
|
244
|
+
</button>
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
component App(){
|
|
249
|
+
<div>
|
|
250
|
+
<h1>{'Component as Property Test'}</h1>
|
|
251
|
+
<UI.span />
|
|
252
|
+
<UI.button>
|
|
253
|
+
component children() {
|
|
254
|
+
<span>{'Click me!'}</span>
|
|
255
|
+
}
|
|
256
|
+
</UI.button>
|
|
257
|
+
</div>
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
render(App);
|
|
261
|
+
|
|
262
|
+
const heading = container.querySelector('h1');
|
|
263
|
+
const span = container.querySelector('span');
|
|
264
|
+
const button = container.querySelector('button');
|
|
265
|
+
const buttonSpan = button.querySelector('span');
|
|
266
|
+
|
|
267
|
+
expect(heading.textContent).toBe('Component as Property Test');
|
|
268
|
+
expect(span.textContent).toBe('Hello from Span');
|
|
269
|
+
expect(buttonSpan.textContent).toBe('Click me!');
|
|
270
|
+
});
|
|
235
271
|
});
|