ripple 0.2.76 → 0.2.78

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.
@@ -3,93 +3,366 @@ import { walk } from 'zimmerframe';
3
3
  import ts from 'esrap/languages/ts';
4
4
  import path from 'node:path';
5
5
  import { print } from 'esrap';
6
+ import {
7
+ build_getter,
8
+ escape_html,
9
+ is_boolean_attribute,
10
+ is_element_dom_element,
11
+ is_inside_component,
12
+ is_void_element,
13
+ normalize_children,
14
+ } from '../../../utils.js';
15
+ import is_reference from 'is-reference';
16
+ import { escape } from '../../../../utils/escaping.js';
17
+ import { is_event_attribute } from '../../../../utils/events.js';
6
18
 
7
19
  function add_ripple_internal_import(context) {
8
- if (!context.state.to_ts) {
9
- if (!context.state.imports.has(`import * as _$_ from 'ripple/internal/server'`)) {
10
- context.state.imports.add(`import * as _$_ from 'ripple/internal/server'`);
11
- }
12
- }
20
+ if (!context.state.to_ts) {
21
+ if (!context.state.imports.has(`import * as _$_ from 'ripple/internal/server'`)) {
22
+ context.state.imports.add(`import * as _$_ from 'ripple/internal/server'`);
23
+ }
24
+ }
25
+ }
26
+
27
+ function transform_children(children, context) {
28
+ const { visit, state, root } = context;
29
+ const normalized = normalize_children(children);
30
+
31
+ for (const node of normalized) {
32
+ if (
33
+ node.type === 'VariableDeclaration' ||
34
+ node.type === 'ExpressionStatement' ||
35
+ node.type === 'ThrowStatement' ||
36
+ node.type === 'FunctionDeclaration' ||
37
+ node.type === 'DebuggerStatement' ||
38
+ node.type === 'ClassDeclaration' ||
39
+ node.type === 'TSTypeAliasDeclaration' ||
40
+ node.type === 'TSInterfaceDeclaration' ||
41
+ node.type === 'Component'
42
+ ) {
43
+ const metadata = { await: false };
44
+ state.init.push(visit(node, { ...state, metadata }));
45
+ if (metadata.await) {
46
+ state.init.push(b.if(b.call('_$_.aborted'), b.return(null)));
47
+ if (state.metadata?.await === false) {
48
+ state.metadata.await = true;
49
+ }
50
+ }
51
+ } else {
52
+ visit(node, { ...state, root: false });
53
+ }
54
+ }
13
55
  }
14
56
 
15
57
  function transform_body(body, { visit, state }) {
16
- const body_state = {
17
- ...state,
18
- init: [],
19
- metadata: state.metadata,
20
- };
58
+ const body_state = {
59
+ ...state,
60
+ init: [],
61
+ metadata: state.metadata,
62
+ };
63
+
64
+ transform_children(body, { visit, state: body_state, root: true });
21
65
 
22
- return body_state.init;
66
+ return body_state.init;
23
67
  }
24
68
 
25
69
  const visitors = {
26
- _: function set_scope(node, { next, state }) {
27
- const scope = state.scopes.get(node);
28
-
29
- if (scope && scope !== state.scope) {
30
- return next({ ...state, scope });
31
- } else {
32
- return next();
33
- }
34
- },
35
-
36
- Component(node, context) {
37
- add_ripple_internal_import(context);
38
-
39
- const metadata = { await: false };
40
- const body_statements = [
41
- b.stmt(b.call('_$_.push_component')),
42
- ...transform_body(node.body, {
43
- ...context,
44
- state: { ...context.state, component: node, metadata },
45
- }),
46
- b.stmt(b.call('_$_.pop_component')),
47
- ];
48
-
49
- if (node.css !== null && node.css) {
50
- context.state.stylesheets.push(node.css);
51
- }
52
-
53
- return b.function(
54
- node.id,
55
- node.params.length > 0 ? [b.id('__output'), node.params[0]] : [b.id('__output')],
56
- b.block([
57
- ...(metadata.await
58
- ? [b.stmt(b.call('_$_.async', b.thunk(b.block(body_statements), true)))]
59
- : body_statements),
60
- ]),
61
- );
62
- },
70
+ _: function set_scope(node, { next, state }) {
71
+ const scope = state.scopes.get(node);
72
+
73
+ if (scope && scope !== state.scope) {
74
+ return next({ ...state, scope });
75
+ } else {
76
+ return next();
77
+ }
78
+ },
79
+
80
+ Component(node, context) {
81
+ add_ripple_internal_import(context);
82
+
83
+ const metadata = { await: false };
84
+ const body_statements = [
85
+ b.stmt(b.call('_$_.push_component')),
86
+ ...transform_body(node.body, {
87
+ ...context,
88
+ state: { ...context.state, component: node, metadata },
89
+ }),
90
+ b.stmt(b.call('_$_.pop_component')),
91
+ ];
92
+
93
+ if (node.css !== null && node.css) {
94
+ context.state.stylesheets.push(node.css);
95
+ }
96
+
97
+ return b.function(
98
+ node.id,
99
+ node.params.length > 0 ? [b.id('__output'), node.params[0]] : [b.id('__output')],
100
+ b.block([
101
+ ...(metadata.await
102
+ ? [b.stmt(b.call('_$_.async', b.thunk(b.block(body_statements), true)))]
103
+ : body_statements),
104
+ ]),
105
+ );
106
+ },
107
+
108
+ Element(node, context) {
109
+ const { state, visit } = context;
110
+
111
+ const is_dom_element = is_element_dom_element(node);
112
+ const is_spreading = node.attributes.some((attr) => attr.type === 'SpreadAttribute');
113
+ const spread_attributes = is_spreading ? [] : null;
114
+
115
+ if (is_dom_element) {
116
+ const is_void = is_void_element(node.id.name);
117
+
118
+ state.init.push(
119
+ b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(`<${node.id.name}`))),
120
+ );
121
+ let class_attribute = null;
122
+
123
+ const handle_static_attr = (name, value) => {
124
+ const attr_value = b.literal(
125
+ ` ${name}${
126
+ is_boolean_attribute(name) && value === true
127
+ ? ''
128
+ : `="${value === true ? '' : escape_html(value, true)}"`
129
+ }`,
130
+ );
131
+
132
+ if (is_spreading) {
133
+ // For spread attributes, store just the actual value, not the full attribute string
134
+ const actual_value =
135
+ is_boolean_attribute(name) && value === true
136
+ ? b.literal(true)
137
+ : b.literal(value === true ? '' : value);
138
+ spread_attributes.push(b.prop('init', b.literal(name), actual_value));
139
+ } else {
140
+ state.init.push(
141
+ b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(String(attr_value)))),
142
+ );
143
+ }
144
+ };
145
+
146
+ for (const attr of node.attributes) {
147
+ if (attr.type === 'Attribute') {
148
+ if (attr.name.type === 'Identifier') {
149
+ const name = attr.name.name;
150
+
151
+ if (attr.value === null) {
152
+ handle_static_attr(name, true);
153
+ continue;
154
+ }
155
+
156
+ if (attr.value.type === 'Literal' && name !== 'class') {
157
+ handle_static_attr(name, attr.value.value);
158
+ continue;
159
+ }
160
+
161
+ if (name === 'class') {
162
+ class_attribute = attr;
163
+
164
+ continue;
165
+ }
166
+
167
+ if (is_event_attribute(name)) {
168
+ continue;
169
+ }
170
+ const metadata = { tracking: false, await: false };
171
+ const expression = visit(attr.value, { ...state, metadata });
172
+
173
+ state.init.push(
174
+ b.stmt(
175
+ b.call(
176
+ b.member(b.id('__output'), b.id('push')),
177
+ b.call('_$_.attr', b.literal(name), expression),
178
+ ),
179
+ ),
180
+ );
181
+ }
182
+ } else if (attr.type === 'SpreadAttribute') {
183
+ spread_attributes.push(b.spread(visit(attr.argument, state)));
184
+ }
185
+ }
186
+
187
+ if (class_attribute !== null) {
188
+ if (class_attribute.value.type === 'Literal') {
189
+ let value = class_attribute.value.value;
190
+
191
+ if (node.metadata.scoped && state.component.css) {
192
+ value = `${state.component.css.hash} ${value}`;
193
+ }
194
+
195
+ handle_static_attr(class_attribute.name.name, value);
196
+ } else {
197
+ const metadata = { tracking: false, await: false };
198
+ let expression = visit(class_attribute.value, { ...state, metadata });
199
+
200
+ if (node.metadata.scoped && state.component.css) {
201
+ expression = b.binary('+', b.literal(state.component.css.hash + ' '), expression);
202
+ }
203
+
204
+ state.init.push(
205
+ b.stmt(
206
+ b.call(
207
+ b.member(b.id('__output'), b.id('push')),
208
+ b.call('_$_.attr', b.literal('class'), expression),
209
+ ),
210
+ ),
211
+ );
212
+ }
213
+ } else if (node.metadata.scoped && state.component.css) {
214
+ const value = state.component.css.hash;
215
+
216
+ // TOOO
217
+ }
218
+
219
+ if (spread_attributes !== null && spread_attributes.length > 0) {
220
+ state.init.push(
221
+ b.stmt(
222
+ b.call(
223
+ b.member(b.id('__output'), b.id('push')),
224
+ b.call(
225
+ '_$_.spread_attrs',
226
+ b.object(spread_attributes),
227
+ node.metadata.scoped && state.component.css
228
+ ? b.literal(state.component.css.hash)
229
+ : undefined,
230
+ ),
231
+ ),
232
+ ),
233
+ );
234
+ }
235
+
236
+ state.init.push(b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(`>`))));
237
+
238
+ if (!is_void) {
239
+ transform_children(node.children, { visit, state: { ...state, root: false } });
240
+
241
+ state.init.push(
242
+ b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(`</${node.id.name}>`))),
243
+ );
244
+ }
245
+ } else {
246
+ const props = [];
247
+
248
+ for (const attr of node.attributes) {
249
+ if (attr.type === 'Attribute') {
250
+ if (attr.name.type === 'Identifier') {
251
+ const metadata = { tracking: false, await: false };
252
+ let property = visit(attr.value, { ...state, metadata });
253
+
254
+ props.push(b.prop('init', attr.name, property));
255
+ } else if (attr.type === 'SpreadAttribute') {
256
+ props.push(
257
+ b.spread(
258
+ visit(attr.argument, { ...state, metadata: { ...state.metadata, spread: true } }),
259
+ ),
260
+ );
261
+ }
262
+ }
263
+ }
264
+
265
+ state.init.push(b.stmt(b.call(node.id.name, b.id('__output'), b.object(props))));
266
+ }
267
+ },
268
+
269
+ ForOfStatement(node, context) {
270
+ if (!is_inside_component(context)) {
271
+ context.next();
272
+ return;
273
+ }
274
+ const body_scope = context.state.scopes.get(node.body);
275
+
276
+ const body = transform_body(node.body.body, {
277
+ ...context,
278
+ state: { ...context.state, scope: body_scope },
279
+ });
280
+
281
+ if (node.index) {
282
+ context.state.init.push(b.var(node.index, b.literal(0)));
283
+ body.push(b.update('++', node.index));
284
+ }
285
+
286
+ context.state.init.push(
287
+ b.for_of(context.visit(node.left), context.visit(node.right), b.block(body)),
288
+ );
289
+ },
290
+
291
+ IfStatement(node, context) {
292
+ if (!is_inside_component(context)) {
293
+ context.next();
294
+ return;
295
+ }
296
+
297
+ // TODO: alternative (else if / else)
298
+ context.state.init.push(
299
+ b.if(
300
+ context.visit(node.test),
301
+ b.block(
302
+ transform_body(node.consequent.body, {
303
+ ...context,
304
+ state: { ...context.state, scope: context.state.scopes.get(node.consequent) },
305
+ }),
306
+ ),
307
+ ),
308
+ );
309
+ },
310
+
311
+ Identifier(node, context) {
312
+ const parent = /** @type {Node} */ (context.path.at(-1));
313
+
314
+ if (is_reference(node, parent) && node.tracked) {
315
+ add_ripple_internal_import(context);
316
+ return b.call('_$_.get', build_getter(node, context));
317
+ }
318
+ },
319
+
320
+ Text(node, { visit, state }) {
321
+ const metadata = { await: false };
322
+ const expression = visit(node.expression, { ...state, metadata });
323
+
324
+ if (expression.type === 'Literal') {
325
+ state.init.push(
326
+ b.stmt(
327
+ b.call(b.member(b.id('__output'), b.id('push')), b.literal(escape(expression.value))),
328
+ ),
329
+ );
330
+ } else {
331
+ state.init.push(
332
+ b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.call('_$_.escape', expression))),
333
+ );
334
+ }
335
+ },
63
336
  };
64
337
 
65
338
  export function transform_server(filename, source, analysis) {
66
- const state = {
67
- imports: new Set(),
68
- init: null,
69
- scope: analysis.scope,
70
- scopes: analysis.scopes,
71
- stylesheets: [],
72
- };
73
-
74
- const program = /** @type {ESTree.Program} */ (
75
- walk(analysis.ast, { ...state, namespace: 'html' }, visitors)
76
- );
77
-
78
- for (const import_node of state.imports) {
79
- program.body.unshift(b.stmt(b.id(import_node)));
80
- }
81
-
82
- const js = print(program, ts(), {
83
- sourceMapContent: source,
84
- sourceMapSource: path.basename(filename),
85
- });
86
-
87
- // TODO: extract css
88
- const css = '';
89
-
90
- return {
91
- ast: program,
92
- js,
93
- css,
94
- };
339
+ const state = {
340
+ imports: new Set(),
341
+ init: null,
342
+ scope: analysis.scope,
343
+ scopes: analysis.scopes,
344
+ stylesheets: [],
345
+ };
346
+
347
+ const program = /** @type {ESTree.Program} */ (
348
+ walk(analysis.ast, { ...state, namespace: 'html' }, visitors)
349
+ );
350
+
351
+ for (const import_node of state.imports) {
352
+ program.body.unshift(b.stmt(b.id(import_node)));
353
+ }
354
+
355
+ const js = print(program, ts(), {
356
+ sourceMapContent: source,
357
+ sourceMapSource: path.basename(filename),
358
+ });
359
+
360
+ // TODO: extract css
361
+ const css = '';
362
+
363
+ return {
364
+ ast: program,
365
+ js,
366
+ css,
367
+ };
95
368
  }
@@ -647,3 +647,63 @@ export function is_element_dom_element(node) {
647
647
  !node.id.tracked
648
648
  );
649
649
  }
650
+
651
+ export function normalize_children(children) {
652
+ const normalized = [];
653
+
654
+ for (const node of children) {
655
+ normalize_child(node, normalized);
656
+ }
657
+
658
+ for (let i = normalized.length - 1; i >= 0; i--) {
659
+ const child = normalized[i];
660
+ const prev_child = normalized[i - 1];
661
+
662
+ if (child.type === 'Text' && prev_child?.type === 'Text') {
663
+ if (child.expression.type === 'Literal' && prev_child.expression.type === 'Literal') {
664
+ prev_child.expression = b.literal(
665
+ prev_child.expression.value + String(child.expression.value),
666
+ );
667
+ } else {
668
+ prev_child.expression = b.binary(
669
+ '+',
670
+ prev_child.expression,
671
+ b.call('String', child.expression),
672
+ );
673
+ }
674
+ normalized.splice(i, 1);
675
+ }
676
+ }
677
+
678
+ return normalized;
679
+ }
680
+
681
+ function normalize_child(node, normalized) {
682
+ if (node.type === 'EmptyStatement') {
683
+ return;
684
+ } else if (node.type === 'Element' && node.id.type === 'Identifier' && node.id.name === 'style') {
685
+ return;
686
+ } else {
687
+ normalized.push(node);
688
+ }
689
+ }
690
+
691
+ export function build_getter(node, context) {
692
+ const state = context.state;
693
+
694
+ for (let i = context.path.length - 1; i >= 0; i -= 1) {
695
+ const binding = state.scope.get(node.name);
696
+ const transform = binding?.transform;
697
+
698
+ // don't transform the declaration itself
699
+ if (node !== binding?.node) {
700
+ const read_fn = transform?.read;
701
+
702
+ if (read_fn) {
703
+ return read_fn(node, context.state?.metadata?.spread, context.visit);
704
+ }
705
+ }
706
+ }
707
+
708
+ return node;
709
+ }
@@ -20,6 +20,12 @@ export function mount(component, options) {
20
20
  const props = options.props || {};
21
21
  const target = options.target;
22
22
  const anchor = create_anchor();
23
+
24
+ // Clear target content in case of SSR
25
+ if (target.firstChild) {
26
+ target.textContent = '';
27
+ }
28
+
23
29
  target.append(anchor);
24
30
 
25
31
  const cleanup_events = handle_root_events(target);
@@ -0,0 +1,32 @@
1
+ import { DERIVED, TRACKED, UNINITIALIZED } from './internal/client/constants';
2
+ import { is_tracked_object } from './internal/client/utils';
3
+
4
+ export { create_context as createContext } from './internal/server/context.js';
5
+
6
+ export function effect() {
7
+ // NO-OP
8
+ }
9
+
10
+ export const TrackedArray = Array;
11
+
12
+ export function track(v, o) {
13
+ var is_tracked = is_tracked_object(v);
14
+
15
+ if (is_tracked) {
16
+ return v;
17
+ }
18
+
19
+ if (typeof v === 'function') {
20
+ return {
21
+ f: TRACKED | DERIVED,
22
+ fn: v,
23
+ v: UNINITIALIZED,
24
+ };
25
+ }
26
+
27
+ return {
28
+ f: TRACKED,
29
+ v,
30
+ };
31
+ }
32
+
@@ -762,6 +762,10 @@ export function set(tracked, value, block) {
762
762
  if (value !== old_value) {
763
763
  var tracked_block = tracked.b;
764
764
 
765
+ if (!tracked_block) {
766
+ debugger;
767
+ }
768
+
765
769
  if ((block.f & CONTAINS_TEARDOWN) !== 0) {
766
770
  if (teardown) {
767
771
  old_values.set(tracked, value);
@@ -0,0 +1,67 @@
1
+ import { active_component } from './index.js';
2
+
3
+ /**
4
+ * @template T
5
+ */
6
+ export class Context {
7
+ /**
8
+ * @param {T} initial_value
9
+ */
10
+ constructor(initial_value) {
11
+ /** @type {T} */
12
+ this._v = initial_value;
13
+ }
14
+
15
+ get() {
16
+ const component = active_component;
17
+ const context = this;
18
+
19
+ if (component === null) {
20
+ throw new Error('No active component found, cannot get context');
21
+ }
22
+ /** @type {Component | null} */
23
+ let current_component = component;
24
+
25
+ while (current_component !== null) {
26
+ const context_map = current_component.c;
27
+
28
+ if (context_map?.has(context)) {
29
+ return context_map.get(context);
30
+ }
31
+
32
+ current_component = current_component.p;
33
+ }
34
+
35
+ return context._v;
36
+ }
37
+
38
+ /**
39
+ * @template T
40
+ * @param {T} value
41
+ */
42
+ set(value) {
43
+ const component = active_component;
44
+ const context = this;
45
+
46
+ if (component === null) {
47
+ throw new Error('No active component found, cannot set context');
48
+ }
49
+
50
+ let current_context = component.c;
51
+
52
+ if (current_context === null) {
53
+ current_context = component.c = new Map();
54
+ }
55
+
56
+ current_context.set(context, value);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * @template T
62
+ * @param {T} initial_value
63
+ * @returns {Context<T>}
64
+ */
65
+ export function create_context(initial_value) {
66
+ return new Context(initial_value);
67
+ }