ripple 0.2.193 → 0.2.194

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.193",
6
+ "version": "0.2.194",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -92,6 +92,6 @@
92
92
  "vscode-languageserver-types": "^3.17.5"
93
93
  },
94
94
  "peerDependencies": {
95
- "ripple": "0.2.193"
95
+ "ripple": "0.2.194"
96
96
  }
97
97
  }
@@ -126,11 +126,11 @@ const visitors = {
126
126
  ServerBlock(node, context) {
127
127
  node.metadata = {
128
128
  ...node.metadata,
129
- exports: [],
129
+ exports: new Set(),
130
130
  };
131
131
  context.visit(node.body, {
132
132
  ...context.state,
133
- inside_server_block: true,
133
+ ancestor_server_block: node,
134
134
  });
135
135
  },
136
136
 
@@ -141,7 +141,7 @@ const visitors = {
141
141
  if (
142
142
  is_reference(node, /** @type {AST.Node} */ (parent)) &&
143
143
  binding &&
144
- context.state.inside_server_block &&
144
+ context.state.ancestor_server_block &&
145
145
  binding.node !== node // Don't check the declaration itself
146
146
  ) {
147
147
  /** @type {ScopeInterface | null} */
@@ -607,20 +607,17 @@ const visitors = {
607
607
  },
608
608
 
609
609
  ExportNamedDeclaration(node, context) {
610
- if (!context.state.inside_server_block) {
610
+ const server_block = context.state.ancestor_server_block;
611
+
612
+ if (!server_block) {
611
613
  return context.next();
612
614
  }
613
615
 
614
- const server_block = /** @type {AST.ServerBlock} */ (
615
- context.path.find((n) => n.type === 'ServerBlock')
616
- );
617
616
  const exports = server_block.metadata.exports;
618
617
  const declaration = /** @type {AST.RippleExportNamedDeclaration} */ (node).declaration;
619
618
 
620
619
  if (declaration && declaration.type === 'FunctionDeclaration') {
621
- if (!exports.includes(declaration.id.name)) {
622
- exports.push(declaration.id.name);
623
- }
620
+ exports.add(declaration.id.name);
624
621
  } else if (declaration && declaration.type === 'Component') {
625
622
  error(
626
623
  'Not implemented: Exported component declaration not supported in server blocks.',
@@ -631,14 +628,48 @@ const visitors = {
631
628
  // TODO: the client and server rendering doesn't currently support components
632
629
  // If we're going to support this, we need to account also for anonymous object declaration
633
630
  // and specifiers
634
- // if (!exports.includes(/** @type {AST.Identifier} */ (declaration.id).name)) {
635
- // exports.push(/** @type {AST.Identifier} */ (declaration.id).name);
636
- // }
631
+ // exports.add(/** @type {AST.Identifier} */ (declaration.id).name);
637
632
  } else if (declaration && declaration.type === 'VariableDeclaration') {
638
- // TODO: allow exporting consts when hydration is supported
639
633
  for (const decl of declaration.declarations) {
634
+ if (decl.init !== undefined && decl.init !== null) {
635
+ if (decl.id.type === 'Identifier') {
636
+ if (
637
+ decl.init.type === 'FunctionExpression' ||
638
+ decl.init.type === 'ArrowFunctionExpression'
639
+ ) {
640
+ exports.add(decl.id.name);
641
+ continue;
642
+ } else if (decl.init.type === 'Identifier') {
643
+ const name = decl.init.name;
644
+ const binding = context.state.scope.get(name);
645
+ if (binding && is_binding_function(binding, context.state.scope)) {
646
+ exports.add(decl.id.name);
647
+ continue;
648
+ }
649
+ } else if (decl.init.type === 'MemberExpression') {
650
+ error(
651
+ 'Not implemented: Exported member expressions are not supported in server blocks.',
652
+ context.state.analysis.module.filename,
653
+ decl.init,
654
+ context.state.loose ? context.state.analysis.errors : undefined,
655
+ );
656
+ continue;
657
+ }
658
+ } else if (decl.id.type === 'ObjectPattern' || decl.id.type === 'ArrayPattern') {
659
+ const paths = extract_paths(decl.id);
660
+ for (const path of paths) {
661
+ error(
662
+ 'Not implemented: Exported object or array patterns are not supported in server blocks.',
663
+ context.state.analysis.module.filename,
664
+ path.node,
665
+ context.state.loose ? context.state.analysis.errors : undefined,
666
+ );
667
+ }
668
+ }
669
+ }
670
+ // TODO: allow exporting consts when hydration is supported
640
671
  error(
641
- 'Not implemented: Exported variable declaration not supported in server blocks.',
672
+ `Not implemented: Exported '${decl.id.type}' type is not supported in server blocks.`,
642
673
  context.state.analysis.module.filename,
643
674
  decl,
644
675
  context.state.loose ? context.state.analysis.errors : undefined,
@@ -651,9 +682,7 @@ const visitors = {
651
682
  const is_function = binding && is_binding_function(binding, context.state.scope);
652
683
 
653
684
  if (is_function) {
654
- if (!exports.includes(name)) {
655
- exports.push(name);
656
- }
685
+ exports.add(name);
657
686
  continue;
658
687
  }
659
688
 
@@ -1007,7 +1036,7 @@ const visitors = {
1007
1036
  }
1008
1037
 
1009
1038
  if (parent_block !== null && parent_block.type !== 'Component') {
1010
- if (context.state.inside_server_block === false) {
1039
+ if (!context.state.ancestor_server_block) {
1011
1040
  // we want the error to live on the `await` keyword vs the whole expression
1012
1041
  const adjusted_node /** @type {AST.AwaitExpression} */ = {
1013
1042
  ...node,
@@ -1066,7 +1095,7 @@ export function analyze(ast, filename, options = {}) {
1066
1095
  scopes,
1067
1096
  analysis,
1068
1097
  inside_head: false,
1069
- inside_server_block: false,
1098
+ ancestor_server_block: undefined,
1070
1099
  to_ts: options.to_ts ?? false,
1071
1100
  loose: options.loose ?? false,
1072
1101
  metadata: {},
@@ -402,15 +402,13 @@ const visitors = {
402
402
  ServerIdentifier(node, context) {
403
403
  const id = b.id(SERVER_IDENTIFIER);
404
404
  id.metadata.source_name = '#server';
405
- id.loc = node.loc;
406
- return id;
405
+ return {...node, ...id};
407
406
  },
408
407
 
409
408
  StyleIdentifier(node, context) {
410
409
  const id = b.id(STYLE_IDENTIFIER);
411
410
  id.metadata.source_name = '#style';
412
- id.loc = node.loc;
413
- return id;
411
+ return {...node, ...id};
414
412
  },
415
413
 
416
414
  ImportDeclaration(node, context) {
@@ -420,7 +418,7 @@ const visitors = {
420
418
  return b.empty;
421
419
  }
422
420
 
423
- if (state.to_ts && state.inside_server_block) {
421
+ if (state.to_ts && state.ancestor_server_block) {
424
422
  /** @type {AST.VariableDeclaration[]} */
425
423
  const locals = state.server_block_locals;
426
424
  for (const spec of node.specifiers) {
@@ -2082,7 +2080,7 @@ const visitors = {
2082
2080
  return b.empty;
2083
2081
  }
2084
2082
 
2085
- if (context.state.to_ts && context.state.inside_server_block) {
2083
+ if (context.state.to_ts && context.state.ancestor_server_block) {
2086
2084
  // All validation errors will be handled in the analysis phase
2087
2085
  // So we can safely print these
2088
2086
  if (node.declaration) {
@@ -2230,7 +2228,7 @@ const visitors = {
2230
2228
  const block = /** @type {AST.BlockStatement} */ (
2231
2229
  context.visit(node.body, {
2232
2230
  ...context.state,
2233
- inside_server_block: true,
2231
+ ancestor_server_block: node,
2234
2232
  server_block_locals,
2235
2233
  })
2236
2234
  );
@@ -2258,35 +2256,31 @@ const visitors = {
2258
2256
  return server_const;
2259
2257
  }
2260
2258
 
2261
- const exports = node.metadata.exports;
2262
-
2263
2259
  if (!context.state.serverIdentifierPresent) {
2264
2260
  // no point printing the client-side block if #server.func is not used
2265
2261
  return b.empty;
2266
2262
  }
2267
2263
 
2268
2264
  const file_path = context.state.filename;
2269
-
2270
- return b.var(
2271
- SERVER_IDENTIFIER,
2272
- b.object(
2273
- /** @type {AST.ServerBlock['metadata']['exports']} */ (exports).map((name) => {
2274
- const func_path = file_path + '#' + name;
2275
- // needs to be a sha256 hash of func_path, to avoid leaking file structure
2276
- const hash = createHash('sha256').update(func_path).digest('hex').slice(0, 8);
2277
-
2278
- return b.prop(
2279
- 'init',
2280
- b.id(name),
2281
- b.function(
2282
- null,
2283
- [b.rest(b.id('args'))],
2284
- b.block([b.return(b.call('_$_.rpc', b.literal(hash), b.id('args')))]),
2285
- ),
2286
- );
2287
- }),
2288
- ),
2289
- );
2265
+ /** @type {AST.Property[]} */
2266
+ const props = [];
2267
+ for (const name of node.metadata.exports) {
2268
+ const func_path = file_path + '#' + name;
2269
+ // needs to be a sha256 hash of func_path, to avoid leaking file structure
2270
+ const hash = createHash('sha256').update(func_path).digest('hex').slice(0, 8);
2271
+ props.push(
2272
+ b.prop(
2273
+ 'init',
2274
+ b.id(name),
2275
+ b.function(
2276
+ null,
2277
+ [b.rest(b.id('args'))],
2278
+ b.block([b.return(b.call('_$_.rpc', b.literal(hash), b.id('args')))]),
2279
+ ),
2280
+ ),
2281
+ );
2282
+ }
2283
+ return b.var(SERVER_IDENTIFIER, b.object(props));
2290
2284
  },
2291
2285
 
2292
2286
  Program(node, context) {
@@ -3699,7 +3693,7 @@ export function transform_client(filename, source, analysis, to_ts, minify_css)
3699
3693
  flush_node: null,
3700
3694
  scope: analysis.scope,
3701
3695
  scopes: analysis.scopes,
3702
- inside_server_block: false,
3696
+ ancestor_server_block: undefined,
3703
3697
  serverIdentifierPresent: analysis.metadata.serverIdentifierPresent,
3704
3698
  server_block_locals: [],
3705
3699
  stylesheets: [],
@@ -348,10 +348,12 @@ const visitors = {
348
348
  if (!context.state.to_ts && node.exportKind === 'type') {
349
349
  return b.empty;
350
350
  }
351
- if (!context.state.inside_server_block) {
351
+ if (!context.state.ancestor_server_block) {
352
352
  return context.next();
353
353
  }
354
354
  const declaration = node.declaration;
355
+ /** @type {AST.Statement[]} */
356
+ const statements = [];
355
357
 
356
358
  if (declaration && declaration.type === 'FunctionDeclaration') {
357
359
  const name = declaration.id.name;
@@ -367,9 +369,59 @@ const visitors = {
367
369
  (context.visit(declaration)),
368
370
  ),
369
371
  );
372
+ } else if (declaration && declaration.type === 'VariableDeclaration') {
373
+ for (const decl of declaration.declarations) {
374
+ if (decl.init !== undefined && decl.init !== null) {
375
+ if (decl.id.type === 'Identifier') {
376
+ const name = decl.id.name;
377
+ if (
378
+ decl.init.type === 'FunctionExpression' ||
379
+ decl.init.type === 'ArrowFunctionExpression'
380
+ ) {
381
+ if (context.state.server_exported_names.includes(name)) {
382
+ continue;
383
+ }
384
+ context.state.server_exported_names.push(name);
385
+ statements.push(
386
+ b.stmt(
387
+ b.assignment(
388
+ '=',
389
+ b.member(b.id('_$_server_$_'), b.id(name)),
390
+ /** @type {AST.Expression} */
391
+ (context.visit(decl.init)),
392
+ ),
393
+ ),
394
+ );
395
+ } else if (decl.init.type === 'Identifier') {
396
+ if (context.state.server_exported_names.includes(name)) {
397
+ continue;
398
+ }
399
+ context.state.server_exported_names.push(name);
400
+
401
+ statements.push(
402
+ b.stmt(
403
+ b.assignment(
404
+ '=',
405
+ b.member(b.id('_$_server_$_'), b.id(name)),
406
+ b.id(decl.init.name),
407
+ ),
408
+ ),
409
+ );
410
+ } else {
411
+ // TODO allow exporting variables that are not functions
412
+ throw new Error('Not implemented');
413
+ }
414
+ } else {
415
+ // TODO allow exporting variables that are not functions
416
+ throw new Error('Not implemented');
417
+ }
418
+ } else {
419
+ // TODO allow exporting uninitialized variables
420
+ throw new Error('Not implemented');
421
+ }
422
+ // TODO: allow exporting consts when hydration is supported
423
+ }
370
424
  } else if (node.specifiers) {
371
- /** @type {AST.Statement[]} */
372
- const statements = [];
373
425
  for (const specifier of node.specifiers) {
374
426
  const name = /** @type {AST.Identifier} */ (specifier.local).name;
375
427
  if (context.state.server_exported_names.includes(name)) {
@@ -387,12 +439,12 @@ const visitors = {
387
439
  b.stmt(b.assignment('=', b.member(b.id('_$_server_$_'), b.id(name)), specifier.local)),
388
440
  );
389
441
  }
390
-
391
- return statements.length ? b.block(statements) : b.empty;
392
442
  } else {
393
443
  // TODO
394
444
  throw new Error('Not implemented');
395
445
  }
446
+
447
+ return statements.length ? b.block(statements) : b.empty;
396
448
  },
397
449
 
398
450
  VariableDeclaration(node, context) {
@@ -882,7 +934,7 @@ const visitors = {
882
934
  return b.empty;
883
935
  }
884
936
 
885
- if (state.inside_server_block) {
937
+ if (state.ancestor_server_block) {
886
938
  if (!node.specifiers.length) {
887
939
  return b.empty;
888
940
  }
@@ -1096,13 +1148,13 @@ const visitors = {
1096
1148
  const block = /** @type {AST.BlockStatement} */ (
1097
1149
  context.visit(node.body, {
1098
1150
  ...context.state,
1099
- inside_server_block: true,
1151
+ ancestor_server_block: node,
1100
1152
  server_block_locals,
1101
1153
  server_exported_names: [],
1102
1154
  })
1103
1155
  );
1104
1156
 
1105
- if (exports.length === 0) {
1157
+ if (exports.size === 0) {
1106
1158
  return {
1107
1159
  ...block,
1108
1160
  body: [...server_block_locals, ...block.body],
@@ -1159,7 +1211,7 @@ export function transform_server(filename, source, analysis, minify_css) {
1159
1211
  serverIdentifierPresent: analysis.metadata.serverIdentifierPresent,
1160
1212
  stylesheets: [],
1161
1213
  component_metadata,
1162
- inside_server_block: false,
1214
+ ancestor_server_block: undefined,
1163
1215
  server_block_locals: [],
1164
1216
  server_exported_names: [],
1165
1217
  filename,
@@ -278,7 +278,7 @@ declare module 'estree' {
278
278
  type: 'ServerBlock';
279
279
  body: ServerBlockStatement;
280
280
  metadata: BaseNodeMetaData & {
281
- exports: string[];
281
+ exports: Set<string>;
282
282
  };
283
283
  }
284
284
 
@@ -1123,7 +1123,7 @@ export interface BaseState {
1123
1123
  scope: ScopeInterface;
1124
1124
  scopes: Map<AST.Node | AST.Node[], ScopeInterface>;
1125
1125
  serverIdentifierPresent: boolean;
1126
- inside_server_block: boolean;
1126
+ ancestor_server_block: AST.ServerBlock | undefined;
1127
1127
  inside_head?: boolean;
1128
1128
 
1129
1129
  /** Common For All */
@@ -766,9 +766,9 @@ export function is_binding_function(binding, scope, visited = new Set()) {
766
766
 
767
767
  // Follow identifier references (e.g., const alias = myFunc)
768
768
  if (initial.type === 'Identifier') {
769
- const nextBinding = scope.get(initial.name);
770
- if (nextBinding) {
771
- return is_binding_function(nextBinding, scope, visited);
769
+ const next_binding = scope.get(initial.name);
770
+ if (next_binding) {
771
+ return is_binding_function(next_binding, scope, visited);
772
772
  }
773
773
  }
774
774
 
@@ -1,7 +1,58 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
- exports[`compiler success tests > compiles TSInstantiationExpression 1`] = `
4
- "import * as _$_ from 'ripple/internal/client';
3
+ exports[`compiler server block tests > compiles server block with with only supported types 1`] = `
4
+ "import * as _$_ from 'ripple/internal/server';
5
+
6
+ export const _$_server_$_ = (() => {
7
+ var _$_server_$_ = {};
8
+
9
+ function something() {
10
+ return 'unexported function';
11
+ }
12
+
13
+ _$_server_$_.fetchUser = async function fetchUser(id) {
14
+ const response = await fetch(\`/api/user/\${id}\`);
15
+ const data = await response.json();
16
+
17
+ return data;
18
+ };
19
+
20
+ const fetchUserAlias = fetchUser;
21
+ const AliasForFetchUserAlias = fetchUserAlias;
22
+
23
+ {
24
+ _$_server_$_.AnotherAlias = AliasForFetchUserAlias;
25
+ }
26
+
27
+ {
28
+ _$_server_$_.func = function test() {
29
+ return 'test';
30
+ };
31
+ }
32
+
33
+ {
34
+ _$_server_$_.func2 = function () {
35
+ return 'test';
36
+ };
37
+ }
38
+
39
+ {
40
+ _$_server_$_.func3 = () => {
41
+ return 'test';
42
+ };
43
+ }
44
+
45
+ {
46
+ _$_server_$_.fetchUserAlias = fetchUserAlias;
47
+ _$_server_$_.AliasForFetchUserAlias = AliasForFetchUserAlias;
48
+ }
49
+
50
+ return _$_server_$_;
51
+ })();"
52
+ `;
53
+
54
+ exports[`compiler typescript tests > compiles TSInstantiationExpression 1`] = `
55
+ "import * as _$_ from 'ripple/internal/server';
5
56
 
6
57
  function makeBox(value) {
7
58
  return { value };
@@ -13,7 +64,7 @@ const ErrorMap = (Map);
13
64
  const errorMap = new ErrorMap();"
14
65
  `;
15
66
 
16
- exports[`compiler success tests > compiles imported component with conditional async in SSR 1`] = `
67
+ exports[`compiler typescript tests > compiles imported component with conditional async in SSR 1`] = `
17
68
  "import * as _$_ from 'ripple/internal/server';
18
69
 
19
70
  import { ChildComponent } from './Child.ripple';
@@ -36,8 +87,8 @@ export async function App(__output) {
36
87
  }"
37
88
  `;
38
89
 
39
- exports[`compiler success tests > removes type assertions from function parameters and leaves default values 1`] = `
40
- "import * as _$_ from 'ripple/internal/client';
90
+ exports[`compiler typescript tests > removes type assertions from function parameters and leaves default values 1`] = `
91
+ "import * as _$_ from 'ripple/internal/server';
41
92
 
42
93
  function getString(e = 'test') {
43
94
  return e;
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { compile } from 'ripple/compiler';
3
3
 
4
- describe('compiler success tests', () => {
4
+ describe('compiler typescript tests', () => {
5
5
  it('compiles TSInstantiationExpression', () => {
6
6
  const source = `function makeBox<T,>(value: T) {
7
7
  return { value };
@@ -11,7 +11,7 @@ const stringBox = makeStringBox('abc');
11
11
  const ErrorMap = Map<string, Error>;
12
12
  const errorMap = new ErrorMap();`;
13
13
 
14
- const result = compile(source, 'test.ripple', { mode: 'client' });
14
+ const result = compile(source, 'test.ripple', { mode: 'server' });
15
15
 
16
16
  expect(result.js.code).toMatchSnapshot();
17
17
  });
@@ -39,8 +39,92 @@ function getString(e: string = 'test') {
39
39
  return e;
40
40
  }`;
41
41
 
42
- const result = compile(source, 'test.ripple', { mode: 'client' });
42
+ const result = compile(source, 'test.ripple', { mode: 'server' });
43
43
 
44
44
  expect(result.js.code).toMatchSnapshot();
45
45
  });
46
46
  });
47
+
48
+ describe('compiler server block tests', () => {
49
+ it('compiles server block with with only supported types', () => {
50
+ const source = `
51
+ #server {
52
+ function something() {
53
+ return 'unexported function';
54
+ }
55
+
56
+ export async function fetchUser(id) {
57
+ const response = await fetch(\`/api/user/\${id}\`);
58
+ const data = await response.json();
59
+ return data;
60
+ }
61
+
62
+ const fetchUserAlias = fetchUser;
63
+
64
+ const AliasForFetchUserAlias = fetchUserAlias;
65
+
66
+ export const AnotherAlias = AliasForFetchUserAlias;
67
+
68
+ export const func = function test() {
69
+ return 'test';
70
+ };
71
+
72
+ export const func2 = function () {
73
+ return 'test';
74
+ };
75
+
76
+ export const func3 = () => {
77
+ return 'test';
78
+ };
79
+
80
+ export { fetchUserAlias, AliasForFetchUserAlias };
81
+ }`;
82
+
83
+ const result = compile(source, 'test.ripple', { mode: 'server' });
84
+ expect(result.js.code).toMatchSnapshot();
85
+ });
86
+
87
+ it('throws error for unsupported exported object pattern in server block', () => {
88
+ const source = `
89
+ #server {
90
+ const obj = { fn1: () => {}, fn2: () => {} };
91
+
92
+ export const { fn1, fn2 } = obj;
93
+ }`;
94
+
95
+ expect(() => compile(source, 'test.ripple', { mode: 'server' })).toThrowError();
96
+ });
97
+
98
+ it('throws error for unsupported exported array pattern in server block', () => {
99
+ const source = `
100
+ #server {
101
+ const arr = [() => {}, () => {}];
102
+
103
+ export const [fnarr1, fnarr2] = arr;
104
+ }`;
105
+
106
+ expect(() => compile(source, 'test.ripple', { mode: 'server' })).toThrowError();
107
+ });
108
+
109
+ it('throws error for unsupported exported member expression via object in server block', () => {
110
+ const source = `
111
+ #server {
112
+ const obj = { fn1: () => {}, fn2: () => {} };
113
+
114
+ export const objProp = obj.fn1;
115
+ }`;
116
+
117
+ expect(() => compile(source, 'test.ripple', { mode: 'server' })).toThrowError();
118
+ });
119
+
120
+ it('throws error for unsupported exported member expression via array in server block', () => {
121
+ const source = `
122
+ #server {
123
+ const arr = [() => {}, () => {}];
124
+
125
+ export const arrIndex0 = arr[0];
126
+ }`;
127
+
128
+ expect(() => compile(source, 'test.ripple', { mode: 'server' })).toThrowError();
129
+ });
130
+ });