ripple 0.3.13 → 0.3.15

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 (70) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/package.json +5 -30
  3. package/src/runtime/array.js +38 -38
  4. package/src/runtime/create-subscriber.js +2 -2
  5. package/src/runtime/internal/client/bindings.js +4 -6
  6. package/src/runtime/internal/client/events.js +8 -3
  7. package/src/runtime/internal/client/hmr.js +5 -17
  8. package/src/runtime/internal/client/runtime.js +1 -0
  9. package/src/runtime/internal/server/blocks.js +7 -9
  10. package/src/runtime/internal/server/index.js +14 -22
  11. package/src/runtime/media-query.js +34 -33
  12. package/src/runtime/object.js +7 -10
  13. package/src/runtime/proxy.js +2 -3
  14. package/src/runtime/reactive-value.js +23 -21
  15. package/src/utils/ast.js +1 -1
  16. package/src/utils/attributes.js +43 -0
  17. package/src/utils/builders.js +2 -2
  18. package/tests/client/basic/basic.components.test.rsrx +103 -1
  19. package/tests/client/basic/basic.errors.test.rsrx +1 -1
  20. package/tests/client/basic/basic.styling.test.rsrx +1 -1
  21. package/tests/client/compiler/compiler.assignments.test.rsrx +1 -1
  22. package/tests/client/compiler/compiler.attributes.test.rsrx +1 -1
  23. package/tests/client/compiler/compiler.basic.test.rsrx +51 -14
  24. package/tests/client/compiler/compiler.tracked-access.test.rsrx +1 -1
  25. package/tests/client/compiler/compiler.try-in-function.test.rsrx +1 -1
  26. package/tests/client/compiler/compiler.typescript.test.rsrx +1 -1
  27. package/tests/client/css/global-additional-cases.test.rsrx +1 -1
  28. package/tests/client/css/global-advanced-selectors.test.rsrx +1 -1
  29. package/tests/client/css/global-at-rules.test.rsrx +1 -1
  30. package/tests/client/css/global-basic.test.rsrx +1 -1
  31. package/tests/client/css/global-classes-ids.test.rsrx +1 -1
  32. package/tests/client/css/global-combinators.test.rsrx +1 -1
  33. package/tests/client/css/global-complex-nesting.test.rsrx +1 -1
  34. package/tests/client/css/global-edge-cases.test.rsrx +1 -1
  35. package/tests/client/css/global-keyframes.test.rsrx +1 -1
  36. package/tests/client/css/global-nested.test.rsrx +1 -1
  37. package/tests/client/css/global-pseudo.test.rsrx +1 -1
  38. package/tests/client/css/global-scoping.test.rsrx +1 -1
  39. package/tests/client/css/style-identifier.test.rsrx +1 -1
  40. package/tests/client/return.test.rsrx +1 -1
  41. package/tests/hydration/build-components.js +1 -1
  42. package/tests/server/basic.components.test.rsrx +114 -0
  43. package/tests/server/compiler.test.rsrx +38 -1
  44. package/tests/server/style-identifier.test.rsrx +1 -1
  45. package/tests/setup-server.js +1 -1
  46. package/tests/utils/compiler-compat-config.test.js +1 -1
  47. package/types/index.d.ts +1 -1
  48. package/src/compiler/comment-utils.js +0 -91
  49. package/src/compiler/errors.js +0 -77
  50. package/src/compiler/identifier-utils.js +0 -80
  51. package/src/compiler/index.d.ts +0 -127
  52. package/src/compiler/index.js +0 -89
  53. package/src/compiler/phases/1-parse/index.js +0 -3007
  54. package/src/compiler/phases/1-parse/style.js +0 -704
  55. package/src/compiler/phases/2-analyze/css-analyze.js +0 -160
  56. package/src/compiler/phases/2-analyze/index.js +0 -2208
  57. package/src/compiler/phases/2-analyze/prune.js +0 -1131
  58. package/src/compiler/phases/2-analyze/validation.js +0 -168
  59. package/src/compiler/phases/3-transform/client/index.js +0 -5264
  60. package/src/compiler/phases/3-transform/segments.js +0 -2125
  61. package/src/compiler/phases/3-transform/server/index.js +0 -1749
  62. package/src/compiler/phases/3-transform/stylesheet.js +0 -545
  63. package/src/compiler/scope.js +0 -476
  64. package/src/compiler/source-map-utils.js +0 -358
  65. package/src/compiler/types/acorn.d.ts +0 -11
  66. package/src/compiler/types/estree-jsx.d.ts +0 -11
  67. package/src/compiler/types/estree.d.ts +0 -11
  68. package/src/compiler/types/index.d.ts +0 -1411
  69. package/src/compiler/types/parse.d.ts +0 -1723
  70. package/src/compiler/utils.js +0 -1258
@@ -1,6 +1,5 @@
1
1
  import type {
2
2
  Tracked,
3
- Props,
4
3
  PropsWithChildren,
5
4
  PropsWithExtras,
6
5
  Component,
@@ -414,4 +413,107 @@ describe('basic client > components & composition', () => {
414
413
  button.click();
415
414
  }).not.toThrow();
416
415
  });
416
+
417
+ it('renders explicit children prop without spread', () => {
418
+ component Card(props: PropsWithChildren<{}>) {
419
+ <div class="card">{props.children}</div>
420
+ }
421
+
422
+ component App() {
423
+ <Card children="fallback text" />
424
+ }
425
+
426
+ render(App);
427
+ expect(container.querySelector('.card').textContent).toBe('fallback text');
428
+ });
429
+
430
+ it('renders explicit children before spread', () => {
431
+ component Card(props: PropsWithChildren<{ id: string }>) {
432
+ <div class="card">{props.children}</div>
433
+ }
434
+
435
+ component App() {
436
+ const extra = { id: '1' };
437
+ <Card children="fallback text" {...extra} />
438
+ }
439
+
440
+ render(App);
441
+ expect(container.querySelector('.card').textContent).toBe('fallback text');
442
+ });
443
+
444
+ it('renders spread before explicit children', () => {
445
+ component Card(props: PropsWithChildren<{ id: string }>) {
446
+ <div class="card">{props.children}</div>
447
+ }
448
+
449
+ component App() {
450
+ const extra = { id: '1' };
451
+ <Card {...extra} children="fallback text" />
452
+ }
453
+
454
+ render(App);
455
+ expect(container.querySelector('.card').textContent).toBe('fallback text');
456
+ });
457
+
458
+ it('template children override explicit children before spread', () => {
459
+ component Card(props: PropsWithChildren<{ id: string }>) {
460
+ <div class="card">{props.children}</div>
461
+ }
462
+
463
+ component App() {
464
+ const extra = { id: '1' };
465
+ <Card children="fallback text" {...extra}>
466
+ <span>{'template content'}</span>
467
+ </Card>
468
+ }
469
+
470
+ render(App);
471
+ expect(container.querySelector('.card span').textContent).toBe('template content');
472
+ expect(container.querySelector('.card').textContent).toBe('template content');
473
+ });
474
+
475
+ it('template children override explicit children after spread', () => {
476
+ component Card(props: PropsWithChildren<{ id: string }>) {
477
+ <div class="card">{props.children}</div>
478
+ }
479
+
480
+ component App() {
481
+ const extra = { id: '1' };
482
+ <Card {...extra} children="fallback text">
483
+ <span>{'template content'}</span>
484
+ </Card>
485
+ }
486
+
487
+ render(App);
488
+ expect(container.querySelector('.card span').textContent).toBe('template content');
489
+ expect(container.querySelector('.card').textContent).toBe('template content');
490
+ });
491
+
492
+ it('spread can override explicit children when no template children', () => {
493
+ component Card(props: PropsWithChildren<{ id: string }>) {
494
+ <div class="card">{props.children}</div>
495
+ }
496
+
497
+ component App() {
498
+ const extra = { id: '1', children: 'from spread' };
499
+ <Card children="explicit" {...extra} />
500
+ }
501
+
502
+ render(App);
503
+ expect(container.querySelector('.card').textContent).toBe('from spread');
504
+ });
505
+
506
+ it('explicit children overrides spread children when it comes after', () => {
507
+ component Card(props: PropsWithChildren<{ id: string }>) {
508
+ <div class="card">{props.children}</div>
509
+ }
510
+
511
+ component App() {
512
+ const extra = { id: '1', children: 'from spread' };
513
+ <Card {...extra} children="explicit" />
514
+ }
515
+
516
+ render(App);
517
+ expect(container.querySelector('.card').textContent).toBe('explicit');
518
+ });
417
519
  });
@@ -1,5 +1,5 @@
1
1
  import { flushSync, track, untrack } from 'ripple';
2
- import { compile } from 'ripple/compiler';
2
+ import { compile } from '@tsrx/ripple';
3
3
 
4
4
  describe('basic client > errors', () => {
5
5
  it('renders with error handling simulation', () => {
@@ -1,4 +1,4 @@
1
- import { compile } from 'ripple/compiler';
1
+ import { compile } from '@tsrx/ripple';
2
2
 
3
3
  describe('basic client > styling', () => {
4
4
  it('renders with styling scoped to component', () => {
@@ -1,5 +1,5 @@
1
1
  import { RippleArray, effect, track, untrack } from 'ripple';
2
- import { compile } from 'ripple/compiler';
2
+ import { compile } from '@tsrx/ripple';
3
3
 
4
4
  const EFFECT_BODY_REGEX = /_\$\_\.effect\(\(\) => \{([\s\S]*?)\n\t\}\);/;
5
5
 
@@ -1,4 +1,4 @@
1
- import { parse, compile } from 'ripple/compiler';
1
+ import { parse, compile } from '@tsrx/ripple';
2
2
 
3
3
  describe('compiler > attributes', () => {
4
4
  it('generates valid JavaScript for component props with hyphenated attributes', () => {
@@ -1,5 +1,5 @@
1
- import { parse, compile, compile_to_volar_mappings } from 'ripple/compiler';
2
- import { obfuscate_identifier } from 'ripple/compiler/internal/identifier/utils';
1
+ import { parse, compile, compile_to_volar_mappings } from '@tsrx/ripple';
2
+ import { obfuscateIdentifier } from '@tsrx/core';
3
3
  import type * as AST from 'estree';
4
4
 
5
5
  function count_occurrences(string: string, subString: string): number {
@@ -59,7 +59,7 @@ describe('compiler > basics', () => {
59
59
  const explicit_text = elements[1].children[0] as AST.TextNode;
60
60
 
61
61
  expect(elements).toHaveLength(2);
62
- expect(expression.type).toBe('RippleExpression');
62
+ expect(expression.type).toBe('TSRXExpression');
63
63
  expect((expression.expression as AST.Identifier).name).toBe('markup');
64
64
  expect(explicit_text.type).toBe('Text');
65
65
  expect((explicit_text.expression as AST.Identifier).name).toBe('markup');
@@ -321,15 +321,15 @@ describe('compiler > basics', () => {
321
321
  // `;
322
322
  // const result = compile_to_volar_mappings(source, 'test.ripple').code;
323
323
 
324
- // expect(count_occurrences(result, obfuscate_identifier('RippleArray'))).toBe(2);
324
+ // expect(count_occurrences(result, obfuscateIdentifier('RippleArray'))).toBe(2);
325
325
  // expect(count_occurrences(result, 'TA')).toBe(1);
326
- // expect(count_occurrences(result, obfuscate_identifier('RippleObject'))).toBe(2);
326
+ // expect(count_occurrences(result, obfuscateIdentifier('RippleObject'))).toBe(2);
327
327
  // expect(count_occurrences(result, 'TO')).toBe(1);
328
- // expect(count_occurrences(result, obfuscate_identifier('RippleSet'))).toBe(2);
328
+ // expect(count_occurrences(result, obfuscateIdentifier('RippleSet'))).toBe(2);
329
329
  // expect(count_occurrences(result, 'TS')).toBe(1);
330
- // expect(count_occurrences(result, obfuscate_identifier('RippleMap'))).toBe(2);
330
+ // expect(count_occurrences(result, obfuscateIdentifier('RippleMap'))).toBe(2);
331
331
  // expect(count_occurrences(result, 'TM')).toBe(1);
332
- // expect(count_occurrences(result, obfuscate_identifier('createRefKey'))).toBe(2);
332
+ // expect(count_occurrences(result, obfuscateIdentifier('createRefKey'))).toBe(2);
333
333
  // expect(count_occurrences(result, 'crk')).toBe(1);
334
334
  // },
335
335
  // );
@@ -347,11 +347,11 @@ describe('compiler > basics', () => {
347
347
  // `;
348
348
  // const result = compile_to_volar_mappings(source, 'test.ripple').code;
349
349
 
350
- // expect(count_occurrences(result, obfuscate_identifier('RippleArray'))).toBe(2);
351
- // expect(count_occurrences(result, obfuscate_identifier('RippleObject'))).toBe(2);
352
- // expect(count_occurrences(result, obfuscate_identifier('RippleSet'))).toBe(2);
353
- // expect(count_occurrences(result, obfuscate_identifier('RippleMap'))).toBe(2);
354
- // expect(count_occurrences(result, obfuscate_identifier('createRefKey'))).toBe(2);
350
+ // expect(count_occurrences(result, obfuscateIdentifier('RippleArray'))).toBe(2);
351
+ // expect(count_occurrences(result, obfuscateIdentifier('RippleObject'))).toBe(2);
352
+ // expect(count_occurrences(result, obfuscateIdentifier('RippleSet'))).toBe(2);
353
+ // expect(count_occurrences(result, obfuscateIdentifier('RippleMap'))).toBe(2);
354
+ // expect(count_occurrences(result, obfuscateIdentifier('createRefKey'))).toBe(2);
355
355
  // });
356
356
 
357
357
  it('prints longhand tracked property values in to_ts output while preserving [\'#v\']', () => {
@@ -535,8 +535,9 @@ export component App() {
535
535
 
536
536
  const result = compile(source, 'test.ripple', { mode: 'client' }).js.code;
537
537
 
538
+ // Template children should take precedence - explicit children prop should be removed
538
539
  expect((result.match(/children:/g) || []).length).toBe(1);
539
- expect(result).toContain('children: _$_.normalize_children(fallback) ?? _$_.ripple_element(');
540
+ expect(result).toContain('children: _$_.ripple_element(');
540
541
  });
541
542
 
542
543
  it('should not error on `this` MemberExpression with a UpdateExpression', () => {
@@ -654,4 +655,40 @@ export component App() {}`;
654
655
 
655
656
  expect(result).toContain('class StringMap extends Map<string, string> {}');
656
657
  });
658
+
659
+ it('wraps children in normalize_children for explicit children prop passed to component', () => {
660
+ const source = `
661
+ component Card(props) {
662
+ <div>{props.children}</div>
663
+ }
664
+
665
+ export component App() {
666
+ const content = 'hello';
667
+
668
+ <Card children={content} />
669
+ }
670
+ `;
671
+
672
+ const result = compile(source, 'test.ripple', { mode: 'client' }).js.code;
673
+
674
+ expect(result).toContain('_$_.normalize_children(');
675
+ });
676
+
677
+ it('uses spread_props for spreads that may contain children', () => {
678
+ const source = `
679
+ component Card(props) {
680
+ <div>{props.children}</div>
681
+ }
682
+
683
+ export component App() {
684
+ const props = { children: 'hello' };
685
+
686
+ <Card {...props} />
687
+ }
688
+ `;
689
+
690
+ const result = compile(source, 'test.ripple', { mode: 'client' }).js.code;
691
+
692
+ expect(result).toContain('_$_.spread_props(');
693
+ });
657
694
  });
@@ -1,4 +1,4 @@
1
- import { compile } from 'ripple/compiler';
1
+ import { compile } from '@tsrx/ripple';
2
2
 
3
3
  describe('Compiler: Tracked Object Direct Access Checks', () => {
4
4
  it('should error on direct access to __v of a tracked object', () => {
@@ -1,4 +1,4 @@
1
- import { compile_to_volar_mappings } from 'ripple/compiler';
1
+ import { compile_to_volar_mappings } from '@tsrx/ripple';
2
2
 
3
3
  function count_occurrences(string: string, sub_string: string): number {
4
4
  let count = 0;
@@ -1,4 +1,4 @@
1
- import { compile } from 'ripple/compiler';
1
+ import { compile } from '@tsrx/ripple';
2
2
 
3
3
  describe('compiler > typescript', () => {
4
4
  it('compiles TSInstantiationExpression', () => {
@@ -1,5 +1,5 @@
1
1
  import { track } from 'ripple';
2
- import { compile } from 'ripple/compiler';
2
+ import { compile } from '@tsrx/ripple';
3
3
 
4
4
  describe('CSS :global additional use cases', () => {
5
5
  it('handles :global as modifier with dot notation', () => {
@@ -1,4 +1,4 @@
1
- import { compile } from 'ripple/compiler';
1
+ import { compile } from '@tsrx/ripple';
2
2
 
3
3
  describe('CSS :global with advanced selectors', () => {
4
4
  it('handles :global with ::slotted()', () => {
@@ -1,4 +1,4 @@
1
- import { compile } from 'ripple/compiler';
1
+ import { compile } from '@tsrx/ripple';
2
2
 
3
3
  describe('CSS :global with @media and @supports', () => {
4
4
  it('handles :global inside @media queries', () => {
@@ -1,4 +1,4 @@
1
- import { compile } from 'ripple/compiler';
1
+ import { compile } from '@tsrx/ripple';
2
2
 
3
3
  describe('CSS :global basic tests', () => {
4
4
  it('applies global selector to all elements', () => {
@@ -1,4 +1,4 @@
1
- import { compile } from 'ripple/compiler';
1
+ import { compile } from '@tsrx/ripple';
2
2
 
3
3
  describe('CSS :global with classes and IDs', () => {
4
4
  it('handles :global with single class', () => {
@@ -1,4 +1,4 @@
1
- import { compile } from 'ripple/compiler';
1
+ import { compile } from '@tsrx/ripple';
2
2
 
3
3
  describe('CSS :global with combinators', () => {
4
4
  it('handles :global with child combinator', () => {
@@ -1,4 +1,4 @@
1
- import { compile } from 'ripple/compiler';
1
+ import { compile } from '@tsrx/ripple';
2
2
 
3
3
  describe('CSS :global with complex nesting', () => {
4
4
  it('handles :global block with nested selectors', () => {
@@ -1,4 +1,4 @@
1
- import { compile } from 'ripple/compiler';
1
+ import { compile } from '@tsrx/ripple';
2
2
 
3
3
  describe('CSS :global edge cases', () => {
4
4
  it('handles multiple :global selectors in one rule', () => {
@@ -1,4 +1,4 @@
1
- import { compile } from 'ripple/compiler';
1
+ import { compile } from '@tsrx/ripple';
2
2
 
3
3
  describe('CSS :global with keyframes', () => {
4
4
  it('handles -global- prefix for keyframes', () => {
@@ -1,4 +1,4 @@
1
- import { compile } from 'ripple/compiler';
1
+ import { compile } from '@tsrx/ripple';
2
2
 
3
3
  describe('CSS :global nested blocks', () => {
4
4
  it('handles nested global blocks', () => {
@@ -1,4 +1,4 @@
1
- import { compile } from 'ripple/compiler';
1
+ import { compile } from '@tsrx/ripple';
2
2
 
3
3
  describe('CSS :global with pseudo-classes', () => {
4
4
  it('handles :global with :has()', () => {
@@ -1,4 +1,4 @@
1
- import { compile } from 'ripple/compiler';
1
+ import { compile } from '@tsrx/ripple';
2
2
 
3
3
  describe('CSS :global scoping verification', () => {
4
4
  it('verifies scoped styles are isolated', () => {
@@ -1,5 +1,5 @@
1
1
  import { track } from 'ripple';
2
- import { compile } from 'ripple/compiler';
2
+ import { compile } from '@tsrx/ripple';
3
3
 
4
4
  describe('#style identifier', () => {
5
5
  describe('basic usage with components', () => {
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { flushSync, track } from 'ripple';
3
- import { compile } from 'ripple/compiler';
3
+ import { compile } from '@tsrx/ripple';
4
4
 
5
5
  describe('returns in prohibited scopes', () => {
6
6
  it('throws error when return is used in module scope', () => {
@@ -4,7 +4,7 @@
4
4
  * Or used as vitest globalSetup
5
5
  */
6
6
 
7
- import { compile } from 'ripple/compiler';
7
+ import { compile } from '@tsrx/ripple';
8
8
  import { readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';
9
9
  import { join, basename } from 'path';
10
10
  import { fileURLToPath } from 'url';
@@ -235,4 +235,118 @@ describe('basic server > components & composition', () => {
235
235
 
236
236
  expect(document.querySelector('div')).toBeNull();
237
237
  });
238
+
239
+ it('renders explicit children prop without spread', async () => {
240
+ component Card(props: PropsWithChildren<{}>) {
241
+ <div class="card">{props.children}</div>
242
+ }
243
+
244
+ component App() {
245
+ <Card children="fallback text" />
246
+ }
247
+
248
+ const { body } = await render(App);
249
+ const { document } = parseHtml(body);
250
+ expect(document.querySelector('.card').textContent).toBe('fallback text');
251
+ });
252
+
253
+ it('renders explicit children before spread', async () => {
254
+ component Card(props: PropsWithChildren<{ id: string }>) {
255
+ <div class="card">{props.children}</div>
256
+ }
257
+
258
+ component App() {
259
+ const extra = { id: '1' };
260
+ <Card children="fallback text" {...extra} />
261
+ }
262
+
263
+ const { body } = await render(App);
264
+ const { document } = parseHtml(body);
265
+ expect(document.querySelector('.card').textContent).toBe('fallback text');
266
+ });
267
+
268
+ it('renders spread before explicit children', async () => {
269
+ component Card(props: PropsWithChildren<{ id: string }>) {
270
+ <div class="card">{props.children}</div>
271
+ }
272
+
273
+ component App() {
274
+ const extra = { id: '1' };
275
+ <Card {...extra} children="fallback text" />
276
+ }
277
+
278
+ const { body } = await render(App);
279
+ const { document } = parseHtml(body);
280
+ expect(document.querySelector('.card').textContent).toBe('fallback text');
281
+ });
282
+
283
+ it('template children override explicit children before spread', async () => {
284
+ component Card(props: PropsWithChildren<{ id: string }>) {
285
+ <div class="card">{props.children}</div>
286
+ }
287
+
288
+ component App() {
289
+ const extra = { id: '1' };
290
+ <Card children="fallback text" {...extra}>
291
+ <span>{'template content'}</span>
292
+ </Card>
293
+ }
294
+
295
+ const { body } = await render(App);
296
+ const { document } = parseHtml(body);
297
+ expect(document.querySelector('.card span').textContent).toBe('template content');
298
+ expect(document.querySelector('.card').textContent).toBe('template content');
299
+ });
300
+
301
+ it('template children override explicit children after spread', async () => {
302
+ component Card(props: PropsWithChildren<{ id: string }>) {
303
+ <div class="card">{props.children}</div>
304
+ }
305
+
306
+ component App() {
307
+ const extra = { id: '1' };
308
+ <Card {...extra} children="fallback text">
309
+ <span>{'template content'}</span>
310
+ </Card>
311
+ }
312
+
313
+ const { body } = await render(App);
314
+ const { document } = parseHtml(body);
315
+ expect(document.querySelector('.card span').textContent).toBe('template content');
316
+ expect(document.querySelector('.card').textContent).toBe('template content');
317
+ });
318
+
319
+ it('spread can override explicit children when no template children', async () => {
320
+ component Card(props: PropsWithChildren<{ id: string }>) {
321
+ <div class="card">{props.children}</div>
322
+ }
323
+
324
+ component App() {
325
+ const extra = { id: '1', children: 'from spread' };
326
+ <Card
327
+ // @ts-ignore
328
+ children="explicit"
329
+ {...extra}
330
+ />
331
+ }
332
+
333
+ const { body } = await render(App);
334
+ const { document } = parseHtml(body);
335
+ expect(document.querySelector('.card').textContent).toBe('from spread');
336
+ });
337
+
338
+ it('explicit children overrides spread children when it comes after', async () => {
339
+ component Card(props: PropsWithChildren<{ id: string }>) {
340
+ <div class="card">{props.children}</div>
341
+ }
342
+
343
+ component App() {
344
+ const extra = { id: '1', children: 'from spread' };
345
+ <Card {...extra} children="explicit" />
346
+ }
347
+
348
+ const { body } = await render(App);
349
+ const { document } = parseHtml(body);
350
+ expect(document.querySelector('.card').textContent).toBe('explicit');
351
+ });
238
352
  });
@@ -112,8 +112,9 @@ export component App() {
112
112
 
113
113
  const result = compile(source, 'test.ripple', { mode: 'server' }).js.code;
114
114
 
115
+ // Template children should take precedence - explicit children prop should be removed
115
116
  expect((result.match(/children:/g) || []).length).toBe(1);
116
- expect(result).toContain('children: _$_.normalize_children(fallback) ?? _$_.ripple_element(');
117
+ expect(result).toContain('children: _$_.ripple_element(');
117
118
  });
118
119
  });
119
120
 
@@ -199,4 +200,40 @@ describe('compiler server block tests', () => {
199
200
 
200
201
  expect(() => compile(source, 'test.ripple', { mode: 'server' })).toThrowError();
201
202
  });
203
+
204
+ it('wraps children in normalize_children for explicit children prop passed to component', () => {
205
+ const source = `
206
+ component Card(props) {
207
+ <div>{props.children}</div>
208
+ }
209
+
210
+ export component App() {
211
+ const content = 'hello';
212
+
213
+ <Card children={content} />
214
+ }
215
+ `;
216
+
217
+ const result = compile(source, 'test.ripple', { mode: 'server' }).js.code;
218
+
219
+ expect(result).toContain('_$_.normalize_children(');
220
+ });
221
+
222
+ it('passes spread through to component when spread may contain children', () => {
223
+ const source = `
224
+ component Card(props) {
225
+ <div>{props.children}</div>
226
+ }
227
+
228
+ export component App() {
229
+ const props = { children: 'hello' };
230
+
231
+ <Card {...props} />
232
+ }
233
+ `;
234
+
235
+ const result = compile(source, 'test.ripple', { mode: 'server' }).js.code;
236
+
237
+ expect(result).toContain('...props');
238
+ });
202
239
  });
@@ -1,5 +1,5 @@
1
1
  import { track } from 'ripple';
2
- import { compile } from 'ripple/compiler';
2
+ import { compile } from '@tsrx/ripple';
3
3
 
4
4
  describe('#style identifier (server)', () => {
5
5
  describe('basic usage with components', () => {
@@ -1,6 +1,6 @@
1
1
  import { expect } from 'vitest';
2
2
  import { render } from 'ripple/server';
3
- import { compile } from 'ripple/compiler';
3
+ import { compile } from '@tsrx/ripple';
4
4
  import { parseHTML } from 'linkedom';
5
5
 
6
6
  globalThis.render = render;
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { compile } from 'ripple/compiler';
2
+ import { compile } from '@tsrx/ripple';
3
3
 
4
4
  const source = `
5
5
  component App() {
package/types/index.d.ts CHANGED
@@ -8,7 +8,7 @@ export type RippleElement = {
8
8
  };
9
9
 
10
10
  /** Type for implicit children fragments rendered with `{children}`. */
11
- export type Children = RippleElement;
11
+ export type Children = RippleElement | Component | string | number | boolean | null | undefined;
12
12
 
13
13
  export function mount(
14
14
  component: Component,