ripple 0.2.208 → 0.2.210
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 +43 -0
- package/README.md +2 -1
- package/package.json +2 -6
- package/shims/rollup-estree-types.d.ts +1 -1
- package/src/compiler/index.d.ts +1 -0
- package/src/compiler/index.js +7 -1
- package/src/compiler/phases/1-parse/index.js +15 -6
- package/src/compiler/phases/2-analyze/css-analyze.js +100 -104
- package/src/compiler/phases/2-analyze/index.js +215 -2
- package/src/compiler/phases/3-transform/client/index.js +388 -50
- package/src/compiler/phases/3-transform/segments.js +123 -39
- package/src/compiler/phases/3-transform/server/index.js +266 -13
- package/src/compiler/types/index.d.ts +16 -3
- package/src/compiler/utils.js +1 -15
- package/src/constants.js +0 -2
- package/src/helpers.d.ts +4 -0
- package/src/html-tree-validation.js +211 -0
- package/src/jsx-runtime.d.ts +260 -259
- package/src/jsx-runtime.js +12 -12
- package/src/runtime/array.js +17 -17
- package/src/runtime/create-subscriber.js +1 -1
- package/src/runtime/index-client.js +1 -5
- package/src/runtime/index-server.js +15 -0
- package/src/runtime/internal/client/compat.js +3 -3
- package/src/runtime/internal/client/composite.js +6 -1
- package/src/runtime/internal/client/head.js +50 -4
- package/src/runtime/internal/client/html.js +73 -12
- package/src/runtime/internal/client/hydration.js +12 -0
- package/src/runtime/internal/client/index.js +1 -1
- package/src/runtime/internal/client/portal.js +54 -29
- package/src/runtime/internal/client/rpc.js +3 -1
- package/src/runtime/internal/client/switch.js +5 -0
- package/src/runtime/internal/client/template.js +117 -11
- package/src/runtime/internal/client/try.js +1 -0
- package/src/runtime/internal/server/index.js +113 -1
- package/src/runtime/internal/server/rpc.js +4 -4
- package/src/runtime/map.js +2 -2
- package/src/runtime/object.js +6 -6
- package/src/runtime/proxy.js +12 -11
- package/src/runtime/reactive-value.js +9 -1
- package/src/runtime/set.js +12 -7
- package/src/runtime/url-search-params.js +0 -1
- package/src/server/index.js +4 -0
- package/src/utils/hashing.js +15 -0
- package/src/utils/normalize_css_property_name.js +1 -1
- package/tests/client/array/array.mutations.test.ripple +8 -8
- package/tests/client/basic/basic.errors.test.ripple +28 -0
- package/tests/client/basic/basic.events.test.ripple +6 -3
- package/tests/client/basic/basic.utilities.test.ripple +1 -1
- package/tests/client/compiler/compiler.regex.test.ripple +10 -8
- package/tests/client/composite/composite.generics.test.ripple +5 -2
- package/tests/client/dynamic-elements.test.ripple +30 -1
- package/tests/client/function-overload-import.ripple +6 -7
- package/tests/client/html.test.ripple +0 -1
- package/tests/client/object.test.ripple +2 -2
- package/tests/client/portal.test.ripple +3 -3
- package/tests/client/return.test.ripple +2500 -0
- package/tests/client/try.test.ripple +69 -0
- package/tests/client/typescript-generics.test.ripple +1 -1
- package/tests/client/url/url.derived.test.ripple +1 -1
- package/tests/client/url/url.parsing.test.ripple +3 -3
- package/tests/client/url/url.partial-removal.test.ripple +7 -7
- package/tests/client/url/url.reactivity.test.ripple +15 -15
- package/tests/client/url/url.serialization.test.ripple +2 -2
- package/tests/hydration/basic.test.js +23 -0
- package/tests/hydration/build-components.js +10 -4
- package/tests/hydration/compiled/client/basic.js +165 -3
- package/tests/hydration/compiled/client/for.js +1140 -23
- package/tests/hydration/compiled/client/head.js +234 -0
- package/tests/hydration/compiled/client/html.js +135 -0
- package/tests/hydration/compiled/client/portal.js +172 -0
- package/tests/hydration/compiled/client/reactivity.js +3 -1
- package/tests/hydration/compiled/client/return.js +1976 -0
- package/tests/hydration/compiled/client/switch.js +162 -0
- package/tests/hydration/compiled/server/basic.js +249 -0
- package/tests/hydration/compiled/server/events.js +1 -1
- package/tests/hydration/compiled/server/for.js +891 -1
- package/tests/hydration/compiled/server/head.js +291 -0
- package/tests/hydration/compiled/server/html.js +133 -0
- package/tests/hydration/compiled/server/if.js +1 -1
- package/tests/hydration/compiled/server/portal.js +250 -0
- package/tests/hydration/compiled/server/reactivity.js +1 -1
- package/tests/hydration/compiled/server/return.js +1969 -0
- package/tests/hydration/compiled/server/switch.js +130 -0
- package/tests/hydration/components/basic.ripple +55 -0
- package/tests/hydration/components/for.ripple +403 -0
- package/tests/hydration/components/head.ripple +111 -0
- package/tests/hydration/components/html.ripple +38 -0
- package/tests/hydration/components/portal.ripple +49 -0
- package/tests/hydration/components/return.ripple +564 -0
- package/tests/hydration/components/switch.ripple +51 -0
- package/tests/hydration/for.test.js +363 -0
- package/tests/hydration/head.test.js +105 -0
- package/tests/hydration/html.test.js +46 -0
- package/tests/hydration/portal.test.js +71 -0
- package/tests/hydration/return.test.js +544 -0
- package/tests/hydration/switch.test.js +42 -0
- package/tests/server/basic.attributes.test.ripple +1 -1
- package/tests/server/compiler.test.ripple +22 -0
- package/tests/server/composite.test.ripple +5 -2
- package/tests/server/html-nesting-validation.test.ripple +237 -0
- package/tests/server/return.test.ripple +1379 -0
- package/tests/setup-hydration.js +6 -1
- package/tests/utils/escaping.test.js +3 -1
- package/tests/utils/normalize_css_property_name.test.js +0 -1
- package/tests/utils/patterns.test.js +6 -2
- package/tests/utils/sanitize_template_string.test.js +3 -2
- package/types/server.d.ts +16 -0
|
@@ -675,22 +675,18 @@ export function convert_source_map_to_mappings(
|
|
|
675
675
|
|
|
676
676
|
if (opening.loc) {
|
|
677
677
|
// Add tokens for '<' and '>' brackets to ensure auto-close feature works
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
loc:
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
mappingData: mapping_data_verify_only,
|
|
689
|
-
});
|
|
690
|
-
}
|
|
678
|
+
tokens.push({
|
|
679
|
+
source: '<',
|
|
680
|
+
generated: '<',
|
|
681
|
+
loc: {
|
|
682
|
+
start: { line: opening.loc.start.line, column: opening.loc.start.column },
|
|
683
|
+
end: { line: opening.loc.start.line, column: opening.loc.start.column + 1 },
|
|
684
|
+
},
|
|
685
|
+
metadata: {},
|
|
686
|
+
mappingData: mapping_data_verify_only,
|
|
687
|
+
});
|
|
691
688
|
|
|
692
689
|
if (!opening.selfClosing) {
|
|
693
|
-
// Add '>' bracket
|
|
694
690
|
tokens.push({
|
|
695
691
|
source: '>',
|
|
696
692
|
generated: '>',
|
|
@@ -715,17 +711,28 @@ export function convert_source_map_to_mappings(
|
|
|
715
711
|
}
|
|
716
712
|
}
|
|
717
713
|
|
|
718
|
-
if (closing) {
|
|
719
|
-
// Add the whole closing tag
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
mapping_data_verify_only,
|
|
726
|
-
),
|
|
714
|
+
if (closing || opening.selfClosing) {
|
|
715
|
+
// Add the whole closing tag or the self-closing
|
|
716
|
+
const mapping = get_mapping_from_node(
|
|
717
|
+
closing ? closing : opening,
|
|
718
|
+
src_to_gen_map,
|
|
719
|
+
gen_line_offsets,
|
|
720
|
+
mapping_data_verify_only,
|
|
727
721
|
);
|
|
728
722
|
|
|
723
|
+
// The generated code includes a semicolon after the closing or self-closed tag
|
|
724
|
+
// We're extending the mapping to include the semicolon
|
|
725
|
+
// because the diagnostics errors can include the whole element
|
|
726
|
+
// and we need to account for the semicolon as it's a part of the diagnostic
|
|
727
|
+
// At the same time, we could've instead applied this logic to the whole `node` element
|
|
728
|
+
// but since we already map the opening - start, we just need the proper end
|
|
729
|
+
// and it was causing some issues with mappings
|
|
730
|
+
mapping.generatedLengths = [mapping.generatedLengths[0] + 1];
|
|
731
|
+
mapping.data.customData.generatedLengths = mapping.generatedLengths;
|
|
732
|
+
mappings.push(mapping);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (closing) {
|
|
729
736
|
visit(closing);
|
|
730
737
|
}
|
|
731
738
|
|
|
@@ -838,26 +845,86 @@ export function convert_source_map_to_mappings(
|
|
|
838
845
|
if (node.test) {
|
|
839
846
|
visit(node.test);
|
|
840
847
|
}
|
|
848
|
+
|
|
841
849
|
if (node.consequent) {
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
+
if (node.consequent.loc) {
|
|
851
|
+
// We're mapping only the brackets because mapping the whole thing
|
|
852
|
+
// would be way too broad and causes
|
|
853
|
+
// issues with partial mapping of something inside the body that we need
|
|
854
|
+
tokens.push(
|
|
855
|
+
{
|
|
856
|
+
source: '{',
|
|
857
|
+
generated: '{',
|
|
858
|
+
loc: {
|
|
859
|
+
start: {
|
|
860
|
+
line: node.consequent.loc.start.line,
|
|
861
|
+
column: node.consequent.loc.start.column,
|
|
862
|
+
},
|
|
863
|
+
end: {
|
|
864
|
+
line: node.consequent.loc.start.line,
|
|
865
|
+
column: node.consequent.loc.start.column + 1,
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
metadata: {},
|
|
869
|
+
mappingData: mapping_data_verify_only,
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
source: '}',
|
|
873
|
+
generated: '}',
|
|
874
|
+
loc: {
|
|
875
|
+
start: {
|
|
876
|
+
line: node.consequent.loc.end.line,
|
|
877
|
+
column: node.consequent.loc.end.column - 1,
|
|
878
|
+
},
|
|
879
|
+
end: {
|
|
880
|
+
line: node.consequent.loc.end.line,
|
|
881
|
+
column: node.consequent.loc.end.column,
|
|
882
|
+
},
|
|
883
|
+
},
|
|
884
|
+
metadata: {},
|
|
885
|
+
mappingData: mapping_data_verify_only,
|
|
886
|
+
},
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
|
|
850
890
|
visit(node.consequent);
|
|
851
891
|
}
|
|
892
|
+
|
|
852
893
|
if (node.alternate) {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
894
|
+
if (node.alternate.loc) {
|
|
895
|
+
tokens.push(
|
|
896
|
+
{
|
|
897
|
+
source: '{',
|
|
898
|
+
generated: '{',
|
|
899
|
+
loc: {
|
|
900
|
+
start: {
|
|
901
|
+
line: node.alternate.loc.start.line,
|
|
902
|
+
column: node.alternate.loc.start.column,
|
|
903
|
+
},
|
|
904
|
+
end: {
|
|
905
|
+
line: node.alternate.loc.start.line,
|
|
906
|
+
column: node.alternate.loc.start.column + 1,
|
|
907
|
+
},
|
|
908
|
+
},
|
|
909
|
+
metadata: {},
|
|
910
|
+
mappingData: mapping_data_verify_only,
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
source: '}',
|
|
914
|
+
generated: '}',
|
|
915
|
+
loc: {
|
|
916
|
+
start: {
|
|
917
|
+
line: node.alternate.loc.end.line,
|
|
918
|
+
column: node.alternate.loc.end.column - 1,
|
|
919
|
+
},
|
|
920
|
+
end: { line: node.alternate.loc.end.line, column: node.alternate.loc.end.column },
|
|
921
|
+
},
|
|
922
|
+
metadata: {},
|
|
923
|
+
mappingData: mapping_data_verify_only,
|
|
924
|
+
},
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
|
|
861
928
|
visit(node.alternate);
|
|
862
929
|
}
|
|
863
930
|
|
|
@@ -1198,6 +1265,23 @@ export function convert_source_map_to_mappings(
|
|
|
1198
1265
|
if (node.argument) {
|
|
1199
1266
|
visit(node.argument);
|
|
1200
1267
|
}
|
|
1268
|
+
|
|
1269
|
+
if (node.type === 'ReturnStatement' && node.loc) {
|
|
1270
|
+
const mapping = get_mapping_from_node(
|
|
1271
|
+
node,
|
|
1272
|
+
src_to_gen_map,
|
|
1273
|
+
gen_line_offsets,
|
|
1274
|
+
mapping_data_verify_only,
|
|
1275
|
+
);
|
|
1276
|
+
// We're only mapping the 'return' keyword, otherwise the mapping would be too broad
|
|
1277
|
+
// and likely may cause issues with partial mappings of something inside the return statement that we need
|
|
1278
|
+
const return_keyword_length = 'return'.length;
|
|
1279
|
+
mapping.lengths = [return_keyword_length];
|
|
1280
|
+
mapping.generatedLengths = [return_keyword_length];
|
|
1281
|
+
mapping.data.customData.generatedLengths = mapping.generatedLengths;
|
|
1282
|
+
|
|
1283
|
+
mappings.push(mapping);
|
|
1284
|
+
}
|
|
1201
1285
|
return;
|
|
1202
1286
|
} else if (node.type === 'ExpressionStatement') {
|
|
1203
1287
|
if (node.expression) {
|
|
@@ -24,8 +24,8 @@ import {
|
|
|
24
24
|
is_void_element,
|
|
25
25
|
normalize_children,
|
|
26
26
|
is_binding_function,
|
|
27
|
-
build_getter,
|
|
28
27
|
is_element_dynamic,
|
|
28
|
+
hash,
|
|
29
29
|
} from '../../../utils.js';
|
|
30
30
|
import { escape } from '../../../../utils/escaping.js';
|
|
31
31
|
import { is_event_attribute } from '../../../../utils/events.js';
|
|
@@ -38,6 +38,66 @@ import {
|
|
|
38
38
|
} from '../../../identifier-utils.js';
|
|
39
39
|
import { BLOCK_CLOSE, BLOCK_OPEN } from '../../../../constants.js';
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Checks if a node is template or control-flow content that should be wrapped when return flags are active
|
|
43
|
+
* @param {AST.Node} node
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
function is_template_or_control_flow(node) {
|
|
47
|
+
return (
|
|
48
|
+
node.type === 'Element' ||
|
|
49
|
+
node.type === 'Text' ||
|
|
50
|
+
node.type === 'Html' ||
|
|
51
|
+
node.type === 'TsxCompat' ||
|
|
52
|
+
node.type === 'IfStatement' ||
|
|
53
|
+
node.type === 'ForOfStatement' ||
|
|
54
|
+
node.type === 'TryStatement' ||
|
|
55
|
+
node.type === 'SwitchStatement'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Builds a negated AND condition from return flag names: !__r_1 && !__r_2 && ...
|
|
61
|
+
* @param {string[]} flags
|
|
62
|
+
* @returns {AST.Expression}
|
|
63
|
+
*/
|
|
64
|
+
function build_return_guard(flags) {
|
|
65
|
+
/** @type {AST.Expression} */
|
|
66
|
+
let condition = b.unary('!', b.id(flags[0]));
|
|
67
|
+
for (let i = 1; i < flags.length; i++) {
|
|
68
|
+
condition = b.logical('&&', condition, b.unary('!', b.id(flags[i])));
|
|
69
|
+
}
|
|
70
|
+
return condition;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Collects all unique return statements from the direct children of a body
|
|
75
|
+
* @param {AST.Node[]} children
|
|
76
|
+
* @returns {AST.ReturnStatement[]}
|
|
77
|
+
*/
|
|
78
|
+
function collect_returns_from_children(children) {
|
|
79
|
+
/** @type {AST.ReturnStatement[]} */
|
|
80
|
+
const returns = [];
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
for (const node of children) {
|
|
83
|
+
if (node.type === 'ReturnStatement') {
|
|
84
|
+
if (!seen.has(node)) {
|
|
85
|
+
seen.add(node);
|
|
86
|
+
returns.push(node);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (node.metadata?.returns) {
|
|
90
|
+
for (const ret of node.metadata.returns) {
|
|
91
|
+
if (!seen.has(ret)) {
|
|
92
|
+
seen.add(ret);
|
|
93
|
+
returns.push(ret);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return returns;
|
|
99
|
+
}
|
|
100
|
+
|
|
41
101
|
/**
|
|
42
102
|
* @param {AST.Node[]} children
|
|
43
103
|
* @param {TransformServerContext} context
|
|
@@ -46,10 +106,45 @@ function transform_children(children, context) {
|
|
|
46
106
|
const { visit, state } = context;
|
|
47
107
|
const normalized = normalize_children(children, context);
|
|
48
108
|
|
|
49
|
-
|
|
109
|
+
const all_returns = collect_returns_from_children(normalized);
|
|
110
|
+
/** @type {Map<AST.ReturnStatement, { name: string, tracked: boolean }>} */
|
|
111
|
+
const return_flags = new Map([...(state.return_flags || [])]);
|
|
112
|
+
/** @type {AST.ReturnStatement[]} */
|
|
113
|
+
const new_returns = [];
|
|
114
|
+
for (const ret of all_returns) {
|
|
115
|
+
if (!return_flags.has(ret)) {
|
|
116
|
+
return_flags.set(ret, { name: state.scope.generate('__r'), tracked: false });
|
|
117
|
+
new_returns.push(ret);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const ret of new_returns) {
|
|
122
|
+
const info = /** @type {{ name: string, tracked: boolean }} */ (return_flags.get(ret));
|
|
123
|
+
state.init?.push(b.var(b.id(info.name), b.false));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Track accumulated return flags as we process children
|
|
127
|
+
/** @type {string[]} */
|
|
128
|
+
let accumulated_flags = [];
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @param {AST.ReturnStatement[] | undefined} returns
|
|
132
|
+
*/
|
|
133
|
+
const push_return_flags = (returns) => {
|
|
134
|
+
if (!returns) return;
|
|
135
|
+
for (const ret of returns) {
|
|
136
|
+
const info = return_flags.get(ret);
|
|
137
|
+
if (info && !accumulated_flags.includes(info.name)) {
|
|
138
|
+
accumulated_flags.push(info.name);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** @param {AST.Node} node */
|
|
144
|
+
const process_node = (node) => {
|
|
50
145
|
if (node.type === 'BreakStatement') {
|
|
51
146
|
state.init?.push(b.break);
|
|
52
|
-
|
|
147
|
+
return;
|
|
53
148
|
}
|
|
54
149
|
if (
|
|
55
150
|
node.type === 'VariableDeclaration' ||
|
|
@@ -60,21 +155,88 @@ function transform_children(children, context) {
|
|
|
60
155
|
node.type === 'ClassDeclaration' ||
|
|
61
156
|
node.type === 'TSTypeAliasDeclaration' ||
|
|
62
157
|
node.type === 'TSInterfaceDeclaration' ||
|
|
158
|
+
node.type === 'ReturnStatement' ||
|
|
63
159
|
node.type === 'Component'
|
|
64
160
|
) {
|
|
65
161
|
const metadata = { await: false };
|
|
66
|
-
state.init?.push(
|
|
162
|
+
state.init?.push(
|
|
163
|
+
/** @type {AST.Statement} */ (visit(node, { ...state, return_flags, metadata })),
|
|
164
|
+
);
|
|
67
165
|
if (metadata.await) {
|
|
68
166
|
state.init?.push(b.if(b.call('_$_.aborted'), b.return(null)));
|
|
69
167
|
if (state.metadata?.await === false) {
|
|
70
168
|
state.metadata.await = true;
|
|
71
169
|
}
|
|
72
170
|
}
|
|
171
|
+
if (node.type === 'ReturnStatement') {
|
|
172
|
+
const info = return_flags.get(node);
|
|
173
|
+
if (info && !accumulated_flags.includes(info.name)) {
|
|
174
|
+
accumulated_flags.push(info.name);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
73
177
|
} else {
|
|
74
|
-
visit(node, state);
|
|
178
|
+
visit(node, { ...state, return_flags });
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/** @type {AST.Node[]} */
|
|
183
|
+
let pending_group = [];
|
|
184
|
+
/** @type {string[]} */
|
|
185
|
+
let pending_guard_flags = [];
|
|
186
|
+
|
|
187
|
+
const flush_pending_group = () => {
|
|
188
|
+
if (pending_group.length === 0) return;
|
|
189
|
+
|
|
190
|
+
const group = pending_group;
|
|
191
|
+
const guard_flags = pending_guard_flags;
|
|
192
|
+
pending_group = [];
|
|
193
|
+
pending_guard_flags = [];
|
|
194
|
+
|
|
195
|
+
/** @type {AST.Statement[]} */
|
|
196
|
+
const wrapped = [];
|
|
197
|
+
const saved_init = state.init;
|
|
198
|
+
state.init = wrapped;
|
|
199
|
+
|
|
200
|
+
for (const group_node of group) {
|
|
201
|
+
process_node(group_node);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
state.init = saved_init;
|
|
205
|
+
if (wrapped.length === 0) return;
|
|
206
|
+
|
|
207
|
+
const guard = build_return_guard(guard_flags);
|
|
208
|
+
state.init?.push(
|
|
209
|
+
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_OPEN))),
|
|
210
|
+
);
|
|
211
|
+
state.init?.push(b.if(guard, b.block(wrapped)));
|
|
212
|
+
state.init?.push(
|
|
213
|
+
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_CLOSE))),
|
|
214
|
+
);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
for (let idx = 0; idx < normalized.length; idx++) {
|
|
218
|
+
const node = normalized[idx];
|
|
219
|
+
|
|
220
|
+
if (accumulated_flags.length > 0 && is_template_or_control_flow(node)) {
|
|
221
|
+
if (pending_group.length === 0) {
|
|
222
|
+
pending_guard_flags = [...accumulated_flags];
|
|
223
|
+
}
|
|
224
|
+
pending_group.push(node);
|
|
225
|
+
|
|
226
|
+
if (node.metadata?.has_return && node.metadata.returns) {
|
|
227
|
+
flush_pending_group();
|
|
228
|
+
push_return_flags(node.metadata.returns);
|
|
229
|
+
}
|
|
230
|
+
continue;
|
|
75
231
|
}
|
|
232
|
+
|
|
233
|
+
flush_pending_group();
|
|
234
|
+
process_node(node);
|
|
235
|
+
push_return_flags(node.metadata?.has_return ? node.metadata.returns : undefined);
|
|
76
236
|
}
|
|
77
237
|
|
|
238
|
+
flush_pending_group();
|
|
239
|
+
|
|
78
240
|
const head_elements = /** @type {AST.Element[]} */ (
|
|
79
241
|
children.filter(
|
|
80
242
|
(node) => node.type === 'Element' && node.id.type === 'Identifier' && node.id.name === 'head',
|
|
@@ -85,8 +247,21 @@ function transform_children(children, context) {
|
|
|
85
247
|
state.init?.push(
|
|
86
248
|
b.stmt(b.assignment('=', b.member(b.id('__output'), b.id('target')), b.literal('head'))),
|
|
87
249
|
);
|
|
88
|
-
for (
|
|
250
|
+
for (let i = 0; i < head_elements.length; i++) {
|
|
251
|
+
const head_element = head_elements[i];
|
|
252
|
+
// Generate a hash for this head element to match client-side hydration
|
|
253
|
+
// Use both filename and index to ensure uniqueness
|
|
254
|
+
const hash_source = `${context.state.filename}:head:${i}:${head_element.start ?? 0}`;
|
|
255
|
+
const hash_value = hash(hash_source);
|
|
256
|
+
|
|
257
|
+
// Emit hydration marker comment with hash
|
|
258
|
+
state.init?.push(
|
|
259
|
+
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(`<!--${hash_value}-->`))),
|
|
260
|
+
);
|
|
261
|
+
|
|
89
262
|
transform_children(head_element.children, context);
|
|
263
|
+
|
|
264
|
+
// No closing marker needed for head elements - the hash is sufficient
|
|
90
265
|
}
|
|
91
266
|
state.init?.push(
|
|
92
267
|
b.stmt(b.assignment('=', b.member(b.id('__output'), b.id('target')), b.literal(null))),
|
|
@@ -504,6 +679,7 @@ const visitors = {
|
|
|
504
679
|
const is_void = dynamic_name
|
|
505
680
|
? false
|
|
506
681
|
: is_void_element(/** @type {AST.Identifier} */ (node.id).name);
|
|
682
|
+
const use_self_closing_syntax = node.selfClosing && (is_void || !!dynamic_name);
|
|
507
683
|
const tag_name = dynamic_name
|
|
508
684
|
? dynamic_name
|
|
509
685
|
: b.literal(/** @type {AST.Identifier} */ (node.id).name);
|
|
@@ -659,11 +835,28 @@ const visitors = {
|
|
|
659
835
|
b.stmt(
|
|
660
836
|
b.call(
|
|
661
837
|
b.member(b.id('__output'), b.id('push')),
|
|
662
|
-
b.literal(
|
|
838
|
+
b.literal(use_self_closing_syntax ? ' />' : '>'),
|
|
663
839
|
),
|
|
664
840
|
),
|
|
665
841
|
);
|
|
666
842
|
|
|
843
|
+
// In dev mode, emit push_element for runtime nesting validation
|
|
844
|
+
if (state.dev && !dynamic_name) {
|
|
845
|
+
const element_name = /** @type {AST.Identifier} */ (node.id).name;
|
|
846
|
+
const loc = node.loc;
|
|
847
|
+
state.init?.push(
|
|
848
|
+
b.stmt(
|
|
849
|
+
b.call(
|
|
850
|
+
'_$_.push_element',
|
|
851
|
+
b.literal(element_name),
|
|
852
|
+
b.literal(state.filename),
|
|
853
|
+
b.literal(loc?.start.line ?? 0),
|
|
854
|
+
b.literal(loc?.start.column ?? 0),
|
|
855
|
+
),
|
|
856
|
+
),
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
|
|
667
860
|
if (!is_void) {
|
|
668
861
|
/** @type {AST.Statement[]} */
|
|
669
862
|
const init = [];
|
|
@@ -690,7 +883,7 @@ const visitors = {
|
|
|
690
883
|
state.init?.push(b.block(init));
|
|
691
884
|
}
|
|
692
885
|
|
|
693
|
-
if (!
|
|
886
|
+
if (!use_self_closing_syntax) {
|
|
694
887
|
state.init?.push(
|
|
695
888
|
b.stmt(
|
|
696
889
|
b.call(
|
|
@@ -703,6 +896,11 @@ const visitors = {
|
|
|
703
896
|
);
|
|
704
897
|
}
|
|
705
898
|
}
|
|
899
|
+
|
|
900
|
+
// In dev mode, emit pop_element after the element is fully rendered
|
|
901
|
+
if (state.dev && !dynamic_name) {
|
|
902
|
+
state.init?.push(b.stmt(b.call('_$_.pop_element')));
|
|
903
|
+
}
|
|
706
904
|
} else {
|
|
707
905
|
/** @type {(AST.Property | AST.SpreadElement)[]} */
|
|
708
906
|
const props = [];
|
|
@@ -904,9 +1102,17 @@ const visitors = {
|
|
|
904
1102
|
);
|
|
905
1103
|
}
|
|
906
1104
|
|
|
1105
|
+
context.state.init?.push(
|
|
1106
|
+
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_OPEN))),
|
|
1107
|
+
);
|
|
1108
|
+
|
|
907
1109
|
context.state.init?.push(
|
|
908
1110
|
b.switch(/** @type {AST.Expression} */ (context.visit(node.discriminant)), cases),
|
|
909
1111
|
);
|
|
1112
|
+
|
|
1113
|
+
context.state.init?.push(
|
|
1114
|
+
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(BLOCK_CLOSE))),
|
|
1115
|
+
);
|
|
910
1116
|
},
|
|
911
1117
|
|
|
912
1118
|
ForOfStatement(node, context) {
|
|
@@ -995,6 +1201,17 @@ const visitors = {
|
|
|
995
1201
|
);
|
|
996
1202
|
},
|
|
997
1203
|
|
|
1204
|
+
ReturnStatement(node, context) {
|
|
1205
|
+
if (!is_inside_component(context)) {
|
|
1206
|
+
return context.next();
|
|
1207
|
+
}
|
|
1208
|
+
const info = context.state.return_flags?.get(node);
|
|
1209
|
+
if (info) {
|
|
1210
|
+
return b.stmt(b.assignment('=', b.id(info.name), b.true));
|
|
1211
|
+
}
|
|
1212
|
+
return context.next();
|
|
1213
|
+
},
|
|
1214
|
+
|
|
998
1215
|
AssignmentExpression(node, context) {
|
|
999
1216
|
const left = node.left;
|
|
1000
1217
|
|
|
@@ -1296,14 +1513,48 @@ const visitors = {
|
|
|
1296
1513
|
visit(node.expression, { ...state, metadata })
|
|
1297
1514
|
);
|
|
1298
1515
|
|
|
1299
|
-
// For
|
|
1516
|
+
// For literal values, compute hash at build time
|
|
1300
1517
|
if (expression.type === 'Literal') {
|
|
1518
|
+
const value = String(expression.value ?? '');
|
|
1519
|
+
const hash_value = hash(value);
|
|
1520
|
+
// Push hash comment
|
|
1521
|
+
state.init?.push(
|
|
1522
|
+
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(`<!--${hash_value}-->`))),
|
|
1523
|
+
);
|
|
1524
|
+
// Push the HTML content
|
|
1525
|
+
state.init?.push(b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(value))));
|
|
1526
|
+
// Push empty comment as end marker
|
|
1301
1527
|
state.init?.push(
|
|
1302
|
-
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(
|
|
1528
|
+
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal('<!---->'))),
|
|
1303
1529
|
);
|
|
1304
1530
|
} else {
|
|
1305
|
-
//
|
|
1306
|
-
|
|
1531
|
+
// For dynamic values, compute hash at runtime
|
|
1532
|
+
// Create a variable to store the value
|
|
1533
|
+
const value_id = state.scope?.generate('html_value');
|
|
1534
|
+
if (value_id) {
|
|
1535
|
+
state.init?.push(
|
|
1536
|
+
b.const(value_id, b.call(b.id('String'), b.logical('??', expression, b.literal('')))),
|
|
1537
|
+
);
|
|
1538
|
+
// Compute hash at runtime using _$_.hash and push as comment
|
|
1539
|
+
state.init?.push(
|
|
1540
|
+
b.stmt(
|
|
1541
|
+
b.call(
|
|
1542
|
+
b.member(b.id('__output'), b.id('push')),
|
|
1543
|
+
b.binary(
|
|
1544
|
+
'+',
|
|
1545
|
+
b.binary('+', b.literal('<!--'), b.call('_$_.hash', b.id(value_id))),
|
|
1546
|
+
b.literal('-->'),
|
|
1547
|
+
),
|
|
1548
|
+
),
|
|
1549
|
+
),
|
|
1550
|
+
);
|
|
1551
|
+
// Push the HTML content
|
|
1552
|
+
state.init?.push(b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.id(value_id))));
|
|
1553
|
+
// Push empty comment as end marker
|
|
1554
|
+
state.init?.push(
|
|
1555
|
+
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal('<!---->'))),
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1307
1558
|
}
|
|
1308
1559
|
},
|
|
1309
1560
|
|
|
@@ -1390,9 +1641,10 @@ const visitors = {
|
|
|
1390
1641
|
* @param {string} source
|
|
1391
1642
|
* @param {AnalysisResult} analysis
|
|
1392
1643
|
* @param {boolean} minify_css
|
|
1644
|
+
* @param {boolean} [dev]
|
|
1393
1645
|
* @returns {{ ast: AST.Program; js: { code: string; map: RawSourceMap | null }; css: string; }}
|
|
1394
1646
|
*/
|
|
1395
|
-
export function transform_server(filename, source, analysis, minify_css) {
|
|
1647
|
+
export function transform_server(filename, source, analysis, minify_css, dev = false) {
|
|
1396
1648
|
// Use component metadata collected during the analyze phase
|
|
1397
1649
|
const component_metadata = analysis.component_metadata || [];
|
|
1398
1650
|
|
|
@@ -1413,6 +1665,7 @@ export function transform_server(filename, source, analysis, minify_css) {
|
|
|
1413
1665
|
// TODO: should we remove all `to_ts` usages we use the client rendering for that?
|
|
1414
1666
|
to_ts: false,
|
|
1415
1667
|
metadata: {},
|
|
1668
|
+
dev,
|
|
1416
1669
|
};
|
|
1417
1670
|
|
|
1418
1671
|
state.imports.add(`import * as _$_ from 'ripple/internal/server'`);
|
|
@@ -26,6 +26,10 @@ interface BaseNodeMetaData {
|
|
|
26
26
|
parenthesized?: boolean;
|
|
27
27
|
elementLeadingComments?: AST.Comment[];
|
|
28
28
|
inside_component_top_level?: boolean;
|
|
29
|
+
returns?: AST.ReturnStatement[];
|
|
30
|
+
has_return?: boolean;
|
|
31
|
+
is_reactive?: boolean;
|
|
32
|
+
lone_return?: boolean;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
interface FunctionMetaData extends BaseNodeMetaData {
|
|
@@ -95,14 +99,18 @@ declare module 'estree' {
|
|
|
95
99
|
|
|
96
100
|
// These 3 are needed so that Literal can extend TrackedNode
|
|
97
101
|
// since Literal is a union type we have to extend each individually
|
|
98
|
-
interface SimpleLiteral extends AST.
|
|
99
|
-
interface RegExpLiteral extends AST.
|
|
100
|
-
interface BigIntLiteral extends AST.
|
|
102
|
+
interface SimpleLiteral extends AST.LiteralTrackedNode {}
|
|
103
|
+
interface RegExpLiteral extends AST.LiteralTrackedNode {}
|
|
104
|
+
interface BigIntLiteral extends AST.LiteralTrackedNode {}
|
|
101
105
|
|
|
102
106
|
interface TrackedNode {
|
|
103
107
|
tracked?: boolean;
|
|
104
108
|
}
|
|
105
109
|
|
|
110
|
+
interface LiteralTrackedNode extends AST.TrackedNode {
|
|
111
|
+
was_expression?: boolean;
|
|
112
|
+
}
|
|
113
|
+
|
|
106
114
|
// Include TypeScript node types and Ripple-specific nodes in NodeMap
|
|
107
115
|
interface NodeMap {
|
|
108
116
|
Component: Component;
|
|
@@ -132,6 +140,7 @@ declare module 'estree' {
|
|
|
132
140
|
ServerIdentifier: ServerIdentifier;
|
|
133
141
|
Text: TextNode;
|
|
134
142
|
JSXEmptyExpression: ESTreeJSX.JSXEmptyExpression;
|
|
143
|
+
ParenthesizedExpression: ParenthesizedExpression;
|
|
135
144
|
}
|
|
136
145
|
|
|
137
146
|
// Missing estree type
|
|
@@ -1212,6 +1221,8 @@ export interface TransformServerState extends BaseState {
|
|
|
1212
1221
|
server_exported_names: string[];
|
|
1213
1222
|
dynamicElementName?: AST.TemplateLiteral;
|
|
1214
1223
|
applyParentCssScope?: AST.CSS.StyleSheet['hash'];
|
|
1224
|
+
dev?: boolean;
|
|
1225
|
+
return_flags?: Map<AST.ReturnStatement, { name: string; tracked: boolean }>;
|
|
1215
1226
|
}
|
|
1216
1227
|
|
|
1217
1228
|
type UpdateList = Array<
|
|
@@ -1243,6 +1254,8 @@ export interface TransformClientState extends BaseState {
|
|
|
1243
1254
|
update: UpdateList | null;
|
|
1244
1255
|
errors: RippleCompileError[];
|
|
1245
1256
|
applyParentCssScope?: AST.CSS.StyleSheet['hash'];
|
|
1257
|
+
skip_children_traversal: boolean;
|
|
1258
|
+
return_flags?: Map<AST.ReturnStatement, { name: string; tracked: boolean }>;
|
|
1246
1259
|
}
|
|
1247
1260
|
|
|
1248
1261
|
/** Override zimmerframe types and provide our own */
|
package/src/compiler/utils.js
CHANGED
|
@@ -7,7 +7,7 @@ import { build_assignment_value, extract_paths } from '../utils/ast.js';
|
|
|
7
7
|
import * as b from '../utils/builders.js';
|
|
8
8
|
import { is_capture_event, is_non_delegated, normalize_event_name } from '../utils/events.js';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
export { hash } from '../utils/hashing.js';
|
|
11
11
|
|
|
12
12
|
const VOID_ELEMENT_NAMES = [
|
|
13
13
|
'area',
|
|
@@ -555,20 +555,6 @@ export function escape_html(value, is_attr = false) {
|
|
|
555
555
|
return escaped + str.substring(last);
|
|
556
556
|
}
|
|
557
557
|
|
|
558
|
-
/**
|
|
559
|
-
* Hashes a string to a base36 value
|
|
560
|
-
* @param {string} str
|
|
561
|
-
* @returns {string}
|
|
562
|
-
*/
|
|
563
|
-
export function hash(str) {
|
|
564
|
-
str = str.replace(regex_return_characters, '');
|
|
565
|
-
let hash = 5381;
|
|
566
|
-
let i = str.length;
|
|
567
|
-
|
|
568
|
-
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
|
|
569
|
-
return (hash >>> 0).toString(36);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
558
|
/**
|
|
573
559
|
* Returns true if node is a DOM element (not a component)
|
|
574
560
|
* @param {AST.Node} node
|
package/src/constants.js
CHANGED
|
@@ -6,12 +6,10 @@ export const TEMPLATE_SVG_NAMESPACE = 1 << 5;
|
|
|
6
6
|
export const TEMPLATE_MATHML_NAMESPACE = 1 << 6;
|
|
7
7
|
|
|
8
8
|
export const HYDRATION_START = '[';
|
|
9
|
-
export const HYDRATION_START_ELSE = '[!';
|
|
10
9
|
export const HYDRATION_END = ']';
|
|
11
10
|
export const HYDRATION_ERROR = {};
|
|
12
11
|
|
|
13
12
|
export const BLOCK_OPEN = `<!--${HYDRATION_START}-->`;
|
|
14
|
-
export const BLOCK_OPEN_ELSE = `<!--${HYDRATION_START_ELSE}-->`;
|
|
15
13
|
export const BLOCK_CLOSE = `<!--${HYDRATION_END}-->`;
|
|
16
14
|
export const EMPTY_COMMENT = `<!---->`;
|
|
17
15
|
|