ripple 0.3.80 → 0.3.82

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/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # ripple
2
2
 
3
+ ## 0.3.82
4
+
5
+ ### Patch Changes
6
+
7
+ - [`67f3794`](https://github.com/Ripple-TS/ripple/commit/67f3794d2f1ffd55dd23a47327d925d9a76a4171)
8
+ Thanks [@leonidaz](https://github.com/leonidaz)! - Accept a component function
9
+ as the `children` prop in `mount()` and `hydrate()`. Compiled component call
10
+ sites normalize `children` via `normalize_children`, but props passed through
11
+ the mount options skipped that step, so a plain component function would be
12
+ rendered as text. The same normalization is now applied to `options.props`,
13
+ which lets bootstrap code hydrate a layout with its page as `children` without
14
+ reaching into runtime internals.
15
+ - Updated dependencies
16
+ [[`b104604`](https://github.com/Ripple-TS/ripple/commit/b10460473fec0ee68b4963cbc2a3d9d5bb3bc633)]:
17
+ - @tsrx/core@0.1.30
18
+ - @tsrx/ripple@0.1.30
19
+
20
+ ## 0.3.81
21
+
22
+ ### Patch Changes
23
+
24
+ - Updated dependencies
25
+ [[`67de047`](https://github.com/Ripple-TS/ripple/commit/67de047d103f39673b25910e1a97760278820999),
26
+ [`3b6fb73`](https://github.com/Ripple-TS/ripple/commit/3b6fb73170d4ad6a383befdda951ce0da4fcbb46),
27
+ [`1c645c8`](https://github.com/Ripple-TS/ripple/commit/1c645c8f854df23bb1271b3402d1885616b525cd),
28
+ [`b1256fd`](https://github.com/Ripple-TS/ripple/commit/b1256fdb5bf279ee7dd20bf1a71dcfccc47e279c)]:
29
+ - @tsrx/core@0.1.29
30
+ - @tsrx/ripple@0.1.29
31
+
3
32
  ## 0.3.80
4
33
 
5
34
  ### Patch Changes
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.3.80",
6
+ "version": "0.3.82",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -67,15 +67,14 @@
67
67
  "#client": "./src/runtime/internal/client/types.d.ts",
68
68
  "#client/constants": "./src/internal/client/constants.js",
69
69
  "#server": "./src/runtime/internal/server/types.d.ts",
70
- "#public": "./types/index.d.ts",
71
- "#helpers": "./src/helpers.d.ts"
70
+ "#public": "./types/index.d.ts"
72
71
  },
73
72
  "dependencies": {
74
73
  "clsx": "^2.1.1",
75
74
  "devalue": "^5.8.1",
76
75
  "esm-env": "^1.2.2",
77
- "@tsrx/core": "0.1.28",
78
- "@tsrx/ripple": "0.1.28"
76
+ "@tsrx/core": "0.1.30",
77
+ "@tsrx/ripple": "0.1.30"
79
78
  },
80
79
  "devDependencies": {
81
80
  "@types/estree": "^1.0.8",
@@ -1,5 +1,5 @@
1
1
  import type { AddEventObject, FragmentProps, RefKey, RefValue, TSRXElement } from '#public';
2
- import type { Nullable } from '#helpers';
2
+ import type { Nullable } from '@tsrx/core/types/helpers';
3
3
  export type { RefValue } from '#public';
4
4
 
5
5
  /**
@@ -11,6 +11,7 @@ import { render_component } from './internal/client/component.js';
11
11
  import { create_anchor } from './internal/client/utils.js';
12
12
  import { try_block } from './internal/client/try.js';
13
13
  import { remove_ssr_css } from './internal/client/css.js';
14
+ import { normalize_children } from './element.js';
14
15
  import {
15
16
  clear_track_hash_reference,
16
17
  hydrate_node,
@@ -57,6 +58,22 @@ function render_root_boundary(anchor, render_content, boundary) {
57
58
  );
58
59
  }
59
60
 
61
+ /**
62
+ * Apply the same children normalization that compiled component call sites
63
+ * get, so `mount`/`hydrate` accept a component function as the `children`
64
+ * prop.
65
+ *
66
+ * @param {Record<string, any> | undefined} props
67
+ * @returns {Record<string, any>}
68
+ */
69
+ function normalize_props(props) {
70
+ if (props?.children != null) {
71
+ return { ...props, children: normalize_children(props.children) };
72
+ }
73
+
74
+ return props || {};
75
+ }
76
+
60
77
  /**
61
78
  * @param {Function} component
62
79
  * @param {{ props?: Record<string, any>, target: HTMLElement, rootBoundary?: RootBoundaryOptions }} options
@@ -66,7 +83,7 @@ export function mount(component, options) {
66
83
  init_operations();
67
84
  remove_ssr_css();
68
85
 
69
- const props = options.props || {};
86
+ const props = normalize_props(options.props);
70
87
  const target = options.target;
71
88
  const anchor = create_anchor();
72
89
 
@@ -104,7 +121,7 @@ export function hydrate(component, options) {
104
121
  init_operations();
105
122
  remove_ssr_css();
106
123
 
107
- const props = options.props || {};
124
+ const props = normalize_props(options.props);
108
125
  const target = options.target;
109
126
  const was_hydrating = hydrating;
110
127
  const previous_hydrate_node = hydrate_node;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @import { Component, Dependency, Block, TryBlockWithCatch } from '#server';
3
- * @import { NestedArray } from '#helpers';
3
+ * @import { NestedArray } from '@tsrx/core/types/helpers';
4
4
  * @import { Props } from '#public';
5
5
  * @import { RenderResult, BaseRenderOptions, RenderStreamResult, Stream, StreamSink } from 'ripple/server';
6
6
  */
@@ -44,15 +44,15 @@ describe('compiler > basics', () => {
44
44
  const style3 = 'div {color: green }';
45
45
 
46
46
  let input = source.replace('__STYLE__', style1);
47
- let ast = parse(input);
47
+ let ast = parse(input, 'App.tsrx');
48
48
  expect(get_returned_tsrx(ast.body[0]).children.at(-1).children[0].source).toEqual(style1);
49
49
 
50
50
  input = source.replace('__STYLE__', style2);
51
- ast = parse(input);
51
+ ast = parse(input, 'App.tsrx');
52
52
  expect(get_returned_tsrx(ast.body[0]).children.at(-1).children[0].source).toEqual(style2);
53
53
 
54
54
  input = source.replace('__STYLE__', style3);
55
- ast = parse(input);
55
+ ast = parse(input, 'App.tsrx');
56
56
  expect(get_returned_tsrx(ast.body[0]).children.at(-1).children[0].source).toEqual(style3);
57
57
  });
58
58
 
@@ -67,7 +67,7 @@ describe('compiler > basics', () => {
67
67
  </>
68
68
  }`;
69
69
 
70
- const ast = parse(source);
70
+ const ast = parse(source, 'App.tsrx');
71
71
  const elements = get_returned_tsrx(ast.body[0]).children.filter(
72
72
  (node: AST.Node) => node.type === 'Element',
73
73
  ) as AST.Element[];
@@ -89,7 +89,7 @@ describe('compiler > basics', () => {
89
89
  <div>{text markup}</div>
90
90
  }`;
91
91
 
92
- expect(() => parse(invalid_source)).toThrow();
92
+ expect(() => parse(invalid_source, 'App.tsrx')).toThrow();
93
93
  });
94
94
 
95
95
  it('optimizes string-shaped expressions as text nodes', () => {
@@ -192,7 +192,7 @@ export function App() @{
192
192
  </>
193
193
  }`;
194
194
 
195
- const ast = parse(source);
195
+ const ast = parse(source, 'App.tsrx');
196
196
  const declaration = (ast.body[0] as AST.VariableDeclaration).declarations[0];
197
197
  const fragment = get_returned_tsrx(declaration) as any;
198
198
 
@@ -211,7 +211,7 @@ export function App() @{
211
211
  </>
212
212
  }`;
213
213
 
214
- const ast = parse(source);
214
+ const ast = parse(source, 'App.tsrx');
215
215
  const declaration = (ast.body[0] as AST.VariableDeclaration).declarations[0];
216
216
  const fragment = get_returned_tsrx(declaration) as any;
217
217
 
@@ -1050,7 +1050,7 @@ function App() @{
1050
1050
  }
1051
1051
  `;
1052
1052
 
1053
- const ast = parse(source);
1053
+ const ast = parse(source, 'App.tsrx');
1054
1054
  const ast_json = JSON.stringify(ast);
1055
1055
 
1056
1056
  expect(ast_json).toContain('"name":"effect"');
@@ -1223,7 +1223,7 @@ export function App() @{
1223
1223
  const source = `function TodoList({ items }: { items: { text: string }[] }) @{
1224
1224
  <ul>var a = "123"</ul>
1225
1225
  }`;
1226
- const ast = parse(source);
1226
+ const ast = parse(source, 'App.tsrx');
1227
1227
  const returned = get_returned_tsrx(ast.body[0]);
1228
1228
  const ul =
1229
1229
  returned.type === 'Element'
@@ -1262,6 +1262,6 @@ export function App() @{
1262
1262
  <4;</ul>
1263
1263
  }`;
1264
1264
 
1265
- expect(() => parse(source)).toThrow();
1265
+ expect(() => parse(source, 'App.tsrx')).toThrow();
1266
1266
  });
1267
1267
  });
@@ -270,5 +270,80 @@ export function App() @{
270
270
  expect(code).toContain('highlight');
271
271
  expect(code).toMatch(/tsrx-[a-z0-9]+ highlight/);
272
272
  });
273
+
274
+ const unreachable_selectors_source = `
275
+ export function App() @{
276
+ const styles = <style>
277
+ div {
278
+ color: red;
279
+ }
280
+ .parent .card {
281
+ font-weight: bold;
282
+ }
283
+ .card {
284
+ color: green;
285
+ &:hover {
286
+ color: blue;
287
+ }
288
+ }
289
+ :global(.badge) {
290
+ padding: 0;
291
+ }
292
+ :global(body) {
293
+ margin: 0;
294
+ }
295
+ </style>;
296
+
297
+ <div class={styles.card}>{'text'}</div>
298
+ }`;
299
+
300
+ it('prunes style expression selectors the class map cannot reach in client mode', () => {
301
+ const { css, cssHash } = compile(unreachable_selectors_source, 'test.tsrx');
302
+
303
+ expect(css).toContain('/* (unused) div {');
304
+ expect(css).toContain('/* (unused) .parent .card {');
305
+ expect(css).toContain(`.card.${cssHash}`);
306
+ expect(css).toContain('&:hover {');
307
+ expect(css).toContain('.badge {');
308
+ expect(css).not.toContain(`.badge.${cssHash}`);
309
+ expect(css).toContain('/* (unused) :global(body) {');
310
+ });
311
+
312
+ it('prunes style expression selectors the class map cannot reach in server mode', () => {
313
+ const { css, cssHash } = compile(unreachable_selectors_source, 'test.tsrx', {
314
+ mode: 'server',
315
+ });
316
+
317
+ expect(css).toContain('/* (unused) div {');
318
+ expect(css).toContain('/* (unused) .parent .card {');
319
+ expect(css).toContain(`.card.${cssHash}`);
320
+ expect(css).toContain('&:hover {');
321
+ expect(css).toContain('.badge {');
322
+ expect(css).not.toContain(`.badge.${cssHash}`);
323
+ expect(css).toContain('/* (unused) :global(body) {');
324
+ });
325
+
326
+ it('keeps all selectors of a free-standing style block', () => {
327
+ const source = `
328
+ export function App() @{
329
+ <>
330
+ <div class="card">{'text'}</div>
331
+
332
+ <style>
333
+ div {
334
+ color: red;
335
+ }
336
+ .card {
337
+ color: green;
338
+ }
339
+ </style>
340
+ </>
341
+ }`;
342
+ const { css, cssHash } = compile(source, 'test.tsrx');
343
+
344
+ expect(css).not.toContain('(unused)');
345
+ expect(css).toContain(`div.${cssHash}`);
346
+ expect(css).toContain(`.card.${cssHash}`);
347
+ });
273
348
  });
274
349
  });
@@ -336,5 +336,65 @@ export function App() @{
336
336
  expect(body).toMatch(/tsrx-[a-z0-9]+/);
337
337
  expect(body).toContain('styled');
338
338
  });
339
+
340
+ it('prunes style expression selectors the class map cannot reach', () => {
341
+ const source = `
342
+ export function App() @{
343
+ const styles = <style>
344
+ div {
345
+ color: red;
346
+ }
347
+ .parent .card {
348
+ font-weight: bold;
349
+ }
350
+ .card {
351
+ color: green;
352
+ &:hover {
353
+ color: blue;
354
+ }
355
+ }
356
+ :global(.badge) {
357
+ padding: 0;
358
+ }
359
+ :global(body) {
360
+ margin: 0;
361
+ }
362
+ </style>;
363
+
364
+ <div class={styles.card}>{'text'}</div>
365
+ }`;
366
+ const { css, cssHash } = compile(source, 'test.tsrx', { mode: 'server' });
367
+
368
+ expect(css).toContain('/* (unused) div {');
369
+ expect(css).toContain('/* (unused) .parent .card {');
370
+ expect(css).toContain(`.card.${cssHash}`);
371
+ expect(css).toContain('&:hover {');
372
+ expect(css).toContain('.badge {');
373
+ expect(css).not.toContain(`.badge.${cssHash}`);
374
+ expect(css).toContain('/* (unused) :global(body) {');
375
+ });
376
+
377
+ it('keeps all selectors of a free-standing style block', () => {
378
+ const source = `
379
+ export function App() @{
380
+ <>
381
+ <div class="card">{'text'}</div>
382
+
383
+ <style>
384
+ div {
385
+ color: red;
386
+ }
387
+ .card {
388
+ color: green;
389
+ }
390
+ </style>
391
+ </>
392
+ }`;
393
+ const { css, cssHash } = compile(source, 'test.tsrx', { mode: 'server' });
394
+
395
+ expect(css).not.toContain('(unused)');
396
+ expect(css).toContain(`div.${cssHash}`);
397
+ expect(css).toContain(`.card.${cssHash}`);
398
+ });
339
399
  });
340
400
  });
@@ -20,7 +20,7 @@ function get_diagnostics(source) {
20
20
  paths: {
21
21
  '@tsrx/core/runtime/ref': ['packages/tsrx/types/runtime/ref.d.ts'],
22
22
  '@tsrx/core/types': ['packages/tsrx/types/index.d.ts'],
23
- '#helpers': ['packages/ripple/src/helpers.d.ts'],
23
+ '@tsrx/core/types/helpers': ['packages/tsrx/types/helpers.d.ts'],
24
24
  '#public': ['packages/ripple/types/index.d.ts'],
25
25
  ripple: ['packages/ripple/types/index.d.ts'],
26
26
  'ripple/jsx-runtime': ['packages/ripple/src/jsx-runtime.d.ts'],
package/src/helpers.d.ts DELETED
@@ -1,11 +0,0 @@
1
- export type RequireAllOrNone<T, K extends keyof T> =
2
- | (T & Required<Pick<T, K>>)
3
- | (T & { [P in K]?: never });
4
-
5
- export type RequiredPresent<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
6
-
7
- export type Nullable<T> = T | null;
8
-
9
- export type Nullish<T> = T | null | undefined;
10
-
11
- export type NestedArray<T> = (T | NestedArray<T>)[];