ripple 0.2.107 → 0.2.108

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 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.107",
6
+ "version": "0.2.108",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -147,7 +147,7 @@ function RipplePlugin(config) {
147
147
 
148
148
  if (code === 35) {
149
149
  // # character
150
- // Look ahead to see if this is followed by [ for tuple syntax
150
+ // Look ahead to see if this is followed by [ for tuple syntax or 'server' keyword
151
151
  if (this.pos + 1 < this.input.length) {
152
152
  const nextChar = this.input.charCodeAt(this.pos + 1);
153
153
  if (nextChar === 91 || nextChar === 123) {
@@ -162,9 +162,27 @@ function RipplePlugin(config) {
162
162
  return this.finishToken(tt.bracketL, '#[');
163
163
  }
164
164
  }
165
+
166
+ // Check if this is #server
167
+ if (this.input.slice(this.pos, this.pos + 7) === '#server') {
168
+ // Check that next char after 'server' is whitespace or {
169
+ const charAfter =
170
+ this.pos + 7 < this.input.length ? this.input.charCodeAt(this.pos + 7) : -1;
171
+ if (
172
+ charAfter === 123 ||
173
+ charAfter === 32 ||
174
+ charAfter === 9 ||
175
+ charAfter === 10 ||
176
+ charAfter === 13 ||
177
+ charAfter === -1
178
+ ) {
179
+ // { or whitespace or EOF
180
+ this.pos += 7; // consume '#server'
181
+ return this.finishToken(tt.name, '#server');
182
+ }
183
+ }
165
184
  }
166
185
  }
167
-
168
186
  if (code === 64) {
169
187
  // @ character
170
188
  // Look ahead to see if this is followed by a valid identifier character or opening paren
@@ -398,6 +416,14 @@ function RipplePlugin(config) {
398
416
  return super.checkLValSimple(expr, bindingType, checkClashes);
399
417
  }
400
418
 
419
+ parseServerBlock() {
420
+ const node = this.startNode();
421
+ this.next();
422
+ node.body = this.parseBlock();
423
+ this.awaitPos = 0;
424
+ return this.finishNode(node, 'ServerBlock');
425
+ }
426
+
401
427
  parseTrackedArrayExpression() {
402
428
  const node = this.startNode();
403
429
  this.next(); // consume the '#['
@@ -1250,6 +1276,10 @@ function RipplePlugin(config) {
1250
1276
  return node;
1251
1277
  }
1252
1278
 
1279
+ if (this.value === '#server') {
1280
+ return this.parseServerBlock();
1281
+ }
1282
+
1253
1283
  if (this.value === 'component') {
1254
1284
  const node = this.startNode();
1255
1285
  node.type = 'Component';
@@ -1326,7 +1356,7 @@ function RipplePlugin(config) {
1326
1356
  this.unexpected();
1327
1357
  }
1328
1358
  const node = this.parseElement();
1329
-
1359
+
1330
1360
  if (!node) {
1331
1361
  this.unexpected();
1332
1362
  }
@@ -3,6 +3,7 @@ import { walk } from 'zimmerframe';
3
3
  import { create_scopes, ScopeRoot } from '../../scope.js';
4
4
  import {
5
5
  get_delegated_event,
6
+ get_parent_block_node,
6
7
  is_element_dom_element,
7
8
  is_inside_component,
8
9
  is_ripple_track_call,
@@ -299,7 +300,10 @@ const visitors = {
299
300
  // Validate that each cases ends in a break statement, except for the last case
300
301
  const last = switch_case.consequent?.[switch_case.consequent.length - 1];
301
302
 
302
- if (last.type !== 'BreakStatement' && node.cases.indexOf(switch_case) !== node.cases.length - 1) {
303
+ if (
304
+ last.type !== 'BreakStatement' &&
305
+ node.cases.indexOf(switch_case) !== node.cases.length - 1
306
+ ) {
303
307
  error(
304
308
  'Template switch cases must end with a break statement (with the exception of the last case).',
305
309
  context.state.analysis.module.filename,
@@ -634,6 +638,15 @@ const visitors = {
634
638
  context.state.metadata.await = true;
635
639
  }
636
640
  }
641
+ const parent_block = get_parent_block_node(context);
642
+
643
+ if (parent_block !== null && parent_block.type !== 'Component') {
644
+ error(
645
+ '`await` expressions can only currently be used at the top-level of a component body. Support for using them in control flow statements will be added in the future.',
646
+ context.state.analysis.module.filename,
647
+ node,
648
+ );
649
+ }
637
650
 
638
651
  context.next();
639
652
  },
@@ -1282,6 +1282,10 @@ const visitors = {
1282
1282
  return b.block(statements);
1283
1283
  },
1284
1284
 
1285
+ ServerBlock() {
1286
+ return b.empty;
1287
+ },
1288
+
1285
1289
  Program(node, context) {
1286
1290
  const statements = [];
1287
1291
 
@@ -96,12 +96,7 @@ const visitors = {
96
96
  context.state.stylesheets.push(node.css);
97
97
  // Register CSS hash during rendering
98
98
  body_statements.unshift(
99
- b.stmt(
100
- b.call(
101
- b.member(b.id('__output'), b.id('register_css')),
102
- b.literal(node.css.hash),
103
- ),
104
- ),
99
+ b.stmt(b.call(b.member(b.id('__output'), b.id('register_css')), b.literal(node.css.hash))),
105
100
  );
106
101
  }
107
102
 
@@ -195,10 +190,11 @@ const visitors = {
195
190
  let class_attribute = null;
196
191
 
197
192
  const handle_static_attr = (name, value) => {
198
- const attr_str = ` ${name}${is_boolean_attribute(name) && value === true
199
- ? ''
200
- : `="${value === true ? '' : escape_html(value, true)}"`
201
- }`;
193
+ const attr_str = ` ${name}${
194
+ is_boolean_attribute(name) && value === true
195
+ ? ''
196
+ : `="${value === true ? '' : escape_html(value, true)}"`
197
+ }`;
202
198
 
203
199
  if (is_spreading) {
204
200
  // For spread attributes, store just the actual value, not the full attribute string
@@ -387,12 +383,13 @@ const visitors = {
387
383
  const conditional_await = b.conditional(
388
384
  b.member(visit(node.id, state), b.id('async')),
389
385
  b.await(component_call),
390
- component_call
386
+ component_call,
391
387
  );
392
388
  state.init.push(b.stmt(conditional_await));
393
389
  }
394
390
  }
395
- }, ForOfStatement(node, context) {
391
+ },
392
+ ForOfStatement(node, context) {
396
393
  if (!is_inside_component(context)) {
397
394
  context.next();
398
395
  return;
@@ -483,22 +480,26 @@ const visitors = {
483
480
 
484
481
  // For SSR with pending block: render the resolved content wrapped in async
485
482
  // In a streaming SSR implementation, we'd render pending first, then stream resolved
486
- const try_statements = node.handler !== null
487
- ? [
488
- b.try(
489
- b.block(body),
490
- b.catch_clause(
491
- node.handler.param || b.id('error'),
492
- b.block(
493
- transform_body(node.handler.body.body, {
494
- ...context,
495
- state: { ...context.state, scope: context.state.scopes.get(node.handler.body) },
496
- }),
483
+ const try_statements =
484
+ node.handler !== null
485
+ ? [
486
+ b.try(
487
+ b.block(body),
488
+ b.catch_clause(
489
+ node.handler.param || b.id('error'),
490
+ b.block(
491
+ transform_body(node.handler.body.body, {
492
+ ...context,
493
+ state: {
494
+ ...context.state,
495
+ scope: context.state.scopes.get(node.handler.body),
496
+ },
497
+ }),
498
+ ),
499
+ ),
497
500
  ),
498
- ),
499
- ),
500
- ]
501
- : body;
501
+ ]
502
+ : body;
502
503
 
503
504
  context.state.init.push(
504
505
  b.stmt(b.await(b.call('_$_.async', b.thunk(b.block(try_statements), true)))),
@@ -514,10 +515,7 @@ const visitors = {
514
515
  context.state.init.push(
515
516
  b.try(
516
517
  b.block(body),
517
- b.catch_clause(
518
- node.handler.param || b.id('error'),
519
- b.block(handler_body),
520
- ),
518
+ b.catch_clause(node.handler.param || b.id('error'), b.block(handler_body)),
521
519
  ),
522
520
  );
523
521
  } else {
@@ -599,6 +597,10 @@ const visitors = {
599
597
  state.init.push(b.stmt(b.call(b.member(b.id('__output'), b.id('push')), expression)));
600
598
  }
601
599
  },
600
+
601
+ ServerBlock(node, context) {
602
+ return context.visit(node.body);
603
+ },
602
604
  };
603
605
 
604
606
  export function transform_server(filename, source, analysis) {
@@ -637,9 +639,7 @@ export function transform_server(filename, source, analysis) {
637
639
  for (const metadata of state.component_metadata) {
638
640
  if (metadata.async) {
639
641
  program.body.push(
640
- b.stmt(
641
- b.assignment('=', b.member(b.id(metadata.id), b.id('async')), b.true),
642
- ),
642
+ b.stmt(b.assignment('=', b.member(b.id(metadata.id), b.id('async')), b.true)),
643
643
  );
644
644
  }
645
645
  }
@@ -284,9 +284,9 @@ export interface TransformContext {
284
284
  /** Compiler state */
285
285
  state: CompilerState;
286
286
  /** AST path */
287
- path?: RippleNode[];
287
+ path: RippleNode[];
288
288
  /** Visit function */
289
- visit?: (node: any, state?: any) => any;
289
+ visit: (node: any, state?: any) => any;
290
290
  /** Transform metadata */
291
291
  metadata?: any;
292
292
  }
@@ -823,6 +823,34 @@ function normalize_child(node, normalized, context) {
823
823
  }
824
824
  }
825
825
 
826
+ /**
827
+ * @param {TransformContext} context
828
+ */
829
+ export function get_parent_block_node(context) {
830
+ const path = context.path;
831
+
832
+ for (let i = path.length - 1; i >= 0; i -= 1) {
833
+ const context_node = path[i];
834
+ if (
835
+ context_node.type === 'IfStatement' ||
836
+ context_node.type === 'ForOfStatement' ||
837
+ context_node.type === 'SwitchStatement' ||
838
+ context_node.type === 'TryStatement' ||
839
+ context_node.type === 'Component'
840
+ ) {
841
+ return context_node;
842
+ }
843
+ if (
844
+ context_node.type === 'FunctionExpression' ||
845
+ context_node.type === 'ArrowFunctionExpression' ||
846
+ context_node.type === 'FunctionDeclaration'
847
+ ) {
848
+ return null;
849
+ }
850
+ }
851
+ return null;
852
+ }
853
+
826
854
  /**
827
855
  * Builds a getter for a tracked identifier
828
856
  * @param {Identifier} node
@@ -323,13 +323,13 @@ export function is_destroyed(target_block) {
323
323
  * @param {Node} end
324
324
  */
325
325
  export function remove_block_dom(node, end) {
326
- while (node !== null) {
327
- /** @type {Node | null} */
328
- var next = node === end ? null : next_sibling(node);
326
+ while (node !== null) {
327
+ /** @type {Node | null} */
328
+ var next = node === end ? null : next_sibling(node);
329
329
 
330
- /** @type {Element | Text | Comment} */ (node).remove();
331
- node = next;
332
- }
330
+ /** @type {Element | Text | Comment} */ (node).remove();
331
+ node = next;
332
+ }
333
333
  }
334
334
 
335
335
  /**
@@ -344,7 +344,7 @@ export function destroy_block(block, remove_dom = true) {
344
344
 
345
345
  if ((remove_dom && (f & (BRANCH_BLOCK | ROOT_BLOCK)) !== 0) || (f & HEAD_BLOCK) !== 0) {
346
346
  var s = block.s;
347
- remove_block_dom(s.start, s.end);
347
+ remove_block_dom(s.start, s.end);
348
348
  removed = true;
349
349
  }
350
350
 
@@ -18,10 +18,10 @@ export function composite(get_component, node, props) {
18
18
  render(() => {
19
19
  var component = get_component();
20
20
 
21
- if (b !== null) {
22
- destroy_block(b);
23
- b = null;
24
- }
21
+ if (b !== null) {
22
+ destroy_block(b);
23
+ b = null;
24
+ }
25
25
 
26
26
  b = branch(() => {
27
27
  var block = active_block;
@@ -6,55 +6,55 @@ import { active_component } from './runtime.js';
6
6
  * @template T
7
7
  */
8
8
  export class Context {
9
- /**
10
- * @param {T} initial_value
11
- */
12
- constructor(initial_value) {
13
- /** @type {T} */
14
- this._v = initial_value;
15
- }
16
-
17
- get() {
18
- const component = active_component;
19
- const context = this;
20
-
21
- if (component === null) {
22
- throw new Error('No active component found, cannot get context');
23
- }
24
- /** @type {Component | null} */
25
- let current_component = component;
26
-
27
- while (current_component !== null) {
28
- const context_map = current_component.c;
29
-
30
- if (context_map?.has(context)) {
31
- return context_map.get(context);
32
- }
33
-
34
- current_component = current_component.p;
35
- }
36
-
37
- return context._v;
38
- }
39
-
40
- /**
41
- * @template T
42
- * @param {T} value
43
- */
44
- set(value) {
45
- const component = active_component;
46
- const context = this;
47
-
48
- if (component === null) {
49
- throw new Error('No active component found, cannot set context');
50
- }
51
-
52
- let current_context = component.c;
53
-
54
- if (current_context === null) {
55
- current_context = component.c = new Map();
56
- }
57
-
58
- current_context.set(context, value);
59
- }
9
+ /**
10
+ * @param {T} initial_value
11
+ */
12
+ constructor(initial_value) {
13
+ /** @type {T} */
14
+ this._v = initial_value;
15
+ }
16
+
17
+ get() {
18
+ const component = active_component;
19
+ const context = this;
20
+
21
+ if (component === null) {
22
+ throw new Error('No active component found, cannot get context');
23
+ }
24
+ /** @type {Component | null} */
25
+ let current_component = component;
26
+
27
+ while (current_component !== null) {
28
+ const context_map = current_component.c;
29
+
30
+ if (context_map?.has(context)) {
31
+ return context_map.get(context);
32
+ }
33
+
34
+ current_component = current_component.p;
35
+ }
36
+
37
+ return context._v;
38
+ }
39
+
40
+ /**
41
+ * @template T
42
+ * @param {T} value
43
+ */
44
+ set(value) {
45
+ const component = active_component;
46
+ const context = this;
47
+
48
+ if (component === null) {
49
+ throw new Error('No active component found, cannot set context');
50
+ }
51
+
52
+ let current_context = component.c;
53
+
54
+ if (current_context === null) {
55
+ current_context = component.c = new Map();
56
+ }
57
+
58
+ current_context.set(context, value);
59
+ }
60
60
  }
@@ -1,7 +1,7 @@
1
1
  import { DEV } from 'esm-env';
2
2
 
3
3
  export function remove_ssr_css() {
4
- if (!document || typeof requestAnimationFrame !== "function") {
4
+ if (!document || typeof requestAnimationFrame !== 'function') {
5
5
  return;
6
6
  }
7
7
 
@@ -22,7 +22,7 @@ function remove_styles() {
22
22
  }
23
23
 
24
24
  function remove() {
25
- document.querySelectorAll("style[data-ripple-ssr]").forEach((el) => el.remove());
25
+ document.querySelectorAll('style[data-ripple-ssr]').forEach((el) => el.remove());
26
26
  }
27
27
 
28
28
  /**
@@ -26,7 +26,7 @@ export function html(node, get_html, svg = false, mathml = false) {
26
26
  else if (mathml) html = `<math>${html}</math>`;
27
27
 
28
28
  if (block.s !== null && block.s.start !== null) {
29
- remove_block_dom(block.s.start, /** @type {Node} */(block.s.end));
29
+ remove_block_dom(block.s.start, /** @type {Node} */ (block.s.end));
30
30
  block.s.start = block.s.end = null;
31
31
  }
32
32
 
@@ -35,14 +35,17 @@ export function html(node, get_html, svg = false, mathml = false) {
35
35
  var node = create_fragment_from_html(html);
36
36
 
37
37
  if (svg || mathml) {
38
- node = /** @type {Element} */(first_child(node));
38
+ node = /** @type {Element} */ (first_child(node));
39
39
  }
40
40
 
41
- assign_nodes(/** @type {Element} */(first_child(node)), /** @type {Element} */(node.lastChild));
41
+ assign_nodes(
42
+ /** @type {Element} */ (first_child(node)),
43
+ /** @type {Element} */ (node.lastChild),
44
+ );
42
45
 
43
46
  if (svg || mathml) {
44
47
  while (first_child(node)) {
45
- anchor.before(/** @type {Element} */(first_child(node)));
48
+ anchor.before(/** @type {Element} */ (first_child(node)));
46
49
  }
47
50
  } else {
48
51
  anchor.before(node);
@@ -69,4 +69,4 @@ export { head } from './head.js';
69
69
 
70
70
  export { script } from './script.js';
71
71
 
72
- export { html } from './html.js';
72
+ export { html } from './html.js';