malinajs 0.7.2-a4 → 0.7.2-a6

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
@@ -15,6 +15,7 @@
15
15
  * else-if
16
16
  * refactoring $onMount
17
17
  * optional deep checking for passed props: prop={value} prop|deep={value}
18
+ * keep-alive
18
19
 
19
20
  ## 0.6.x
20
21
 
package/malina-esbuild.js CHANGED
@@ -121,7 +121,6 @@ async function esbuild(options={}){
121
121
  outfile: 'public/bundle.js',
122
122
  minify: true,
123
123
  bundle: true,
124
- platform: 'node',
125
124
  plugins: [malinaPlugin()],
126
125
  ...esbuildConfig,
127
126
  ...options
package/malina.js CHANGED
@@ -713,6 +713,7 @@
713
713
  switch(node.type) {
714
714
  case 'node':
715
715
  case 'slot':
716
+ case 'block':
716
717
  case 'fragment':
717
718
  if(node.body) go(node.body, node);
718
719
  break
@@ -835,121 +836,103 @@
835
836
  go(data.body);
836
837
  }
837
838
 
838
- function parse() {
839
- let source = this.source;
840
- let index = 0;
839
+ class Reader {
840
+ constructor(source) {
841
+ if(source instanceof Reader) return source;
842
+ this.index = 0;
843
+ this.source = source;
844
+ }
841
845
 
842
- const readNext = () => {
843
- assert(index < source.length, 'EOF');
844
- return source[index++];
845
- };
846
+ read(pattern) {
847
+ assert(!this.end(), 'EOF');
848
+ if(pattern == null) {
849
+ return this.source[this.index++];
850
+ } else if(pattern instanceof RegExp) {
851
+ assert(pattern.source[0] == '^');
852
+ const rx = this.source.substring(this.index).match(pattern);
853
+ assert(rx && rx.index == 0, 'Wrong syntax');
854
+ let r = rx[1] || rx[0];
855
+ this.index += rx[0].length;
856
+ return r;
857
+ } else throw 'Not implemented';
858
+ }
846
859
 
847
- const probeNext = () => source[index];
860
+ probe(pattern) {
861
+ if(pattern instanceof RegExp) {
862
+ assert(pattern.source[0] == '^');
863
+ const r = this.source.substring(this.index).match(pattern);
864
+ if(r) return r[0];
865
+ } else {
866
+ if(this.source[this.index] == pattern[0] && this.source.substr(this.index, pattern.length) == pattern) return pattern;
867
+ }
868
+ return null;
869
+ }
848
870
 
849
- const readTag = () => {
850
- let start = index;
851
- let a = readNext();
852
- assert(a === '<', 'Tag error');
853
- let attributes = [];
854
- let begin = true;
855
- let name = '';
856
- let eq, attr_start;
857
- let elArg = null;
871
+ probeQuote() {
872
+ const a = this.source[this.index];
873
+ return a == '"' || a == "'" || a == '`';
874
+ }
858
875
 
859
- const error = (name) => {
860
- let e = new Error(name);
861
- e.details = source.substring(start, index);
862
- throw e;
863
- };
876
+ readIf(pattern) {
877
+ const r = this.probe(pattern);
878
+ if(r != null) this.index += r.length;
879
+ return r;
880
+ }
864
881
 
865
- function flush(shift) {
866
- if(!attr_start) return;
867
- shift = shift || 0;
868
- let end = index - 1 + shift;
869
- if(elArg === true) {
870
- elArg = source.substring(attr_start, end);
871
- attr_start = null;
872
- eq = null;
873
- return;
874
- }
875
- let a = {
876
- content: source.substring(attr_start, end)
877
- };
878
- if(eq) {
879
- a.name = source.substring(attr_start, eq);
880
- a.value = source.substring(eq + 1, end);
881
- if(a.value[0] == '"' || a.value[0] == '\'') a.value = a.value.substring(1);
882
- let i = a.value.length - 1;
883
- if(a.value[i] == '"' || a.value[i] == '\'') a.value = a.value.substring(0, i);
884
- } else a.name = a.content;
885
- attributes.push(a);
886
- attr_start = null;
887
- eq = null;
882
+ end() {
883
+ return this.index >= this.source.length;
884
+ }
885
+
886
+ skip() {
887
+ while(!this.end()) {
888
+ if(!this.source[this.index].match(/\s/)) break;
889
+ this.index++;
888
890
  }
891
+ }
889
892
 
893
+ readString() {
894
+ let q = this.read();
895
+ assert(q == '"' || q == '`' || q == `'`, 'Wrong syntax');
896
+ let a = null, p, result = q;
890
897
  while(true) {
891
- a = readNext();
892
- if(!begin && !attr_start && a.match(/\S/) && a != '/' && a != '>') attr_start = index - 1;
893
- if(a == '"' || a == "'" || a == '`') {
894
- while(a != readNext());
895
- continue;
896
- }
897
- if(a == '{') {
898
- index--;
899
- readBinding();
900
- flush(1);
901
- continue;
902
- }
903
- if(a == '}') error('Wrong attr');
904
- if(a == '<') error('Wrong tag');
905
- if(a == '/') {
906
- a = readNext();
907
- assert(a == '>');
908
- flush(-1);
909
- }
910
- if(a == '>') {
911
- flush();
912
- const voidTags = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
913
- let voidTag = voidTags.indexOf(name) >= 0;
914
- let closedTag = voidTag || source[index - 2] == '/';
915
- return {
916
- type: 'node',
917
- name,
918
- elArg,
919
- openTag: source.substring(start, index),
920
- start: start,
921
- end: index,
922
- closedTag,
923
- voidTag,
924
- attributes
925
- };
926
- }
927
- if(begin) {
928
- if(a.match(/[\da-zA-Z^\-]/)) {
929
- name += a;
930
- continue;
931
- } else {
932
- begin = false;
933
- if(a == ':') {
934
- elArg = true;
935
- attr_start = index;
936
- }
937
- }
938
- } else if(attr_start) {
939
- if(a == '=' && !eq) eq = index - 1;
940
- else if(a.match(/\s/)) flush();
941
- }
898
+ p = a;
899
+ a = this.read();
900
+ result += a;
901
+ if(a == q && p != '\\') break;
942
902
  }
943
- };
903
+ return result;
904
+ }
905
+
906
+ readAttribute() {
907
+ let name = '';
908
+ while(true) {
909
+ if(this.end()) break;
910
+ let a = this.source[this.index];
911
+ if(a == '=' || a == '/' || a == '>' || a == '\t' || a == '\n' || a == '\v' || a == '\f' || a == '\r' || a == ' ' || a == ' ') break;
912
+ name += a;
913
+ this.index++;
914
+ }
915
+ assert(name, 'Syntax error');
916
+ return name;
917
+ }
918
+
919
+ sub(start, end) {
920
+ return this.source.substring(start, end || this.index);
921
+ }
922
+ }
923
+
924
+ function parseHTML(source) {
925
+ const reader = new Reader(source);
944
926
 
945
- const readScript = (tag) => {
946
- let endTag = `</${tag}>`;
947
- let q, a, p, start = index;
927
+ const readScript = (reader) => {
928
+ const start = reader.index;
948
929
 
949
930
  const readRegExp = () => {
931
+ assert(reader.read() == '/');
932
+ let a, p, q;
950
933
  while(true) {
951
934
  p = a;
952
- a = readNext();
935
+ a = reader.read();
953
936
  if(q) {
954
937
  if(a != q) continue;
955
938
  if(p == '\\') continue;
@@ -962,108 +945,40 @@
962
945
  };
963
946
 
964
947
  while(true) {
965
- p = a;
966
- a = readNext();
967
- if(q) {
968
- if(a != q) continue;
969
- if(p == '\\') continue;
970
- q = null;
948
+ if(reader.probeQuote()) {
949
+ reader.readString();
971
950
  continue;
972
951
  }
973
- if(a == '"' || a == '\'' || a == '`') {
974
- q = a;
975
- continue;
976
- }
977
- if(a == '/') {
978
- let n = probeNext();
979
- if(n == '/') { // inline comment
980
- while(readNext() != '\n');
981
- a = null;
982
- continue
952
+ if(reader.probe('/')) {
953
+ if(reader.readIf('//')) {
954
+ while(reader.read() != '\n');
955
+ continue;
983
956
  }
984
- if(n == '*') {
985
- readNext();
986
- while(true) { // multiline comment
987
- if(readNext() == '*' && probeNext() == '/') break;
957
+ if(reader.readIf('/*')) {
958
+ while(true) {
959
+ if(reader.read() == '*' && reader.readIf('/')) break;
988
960
  }
989
- readNext();
990
- a = null;
991
961
  continue;
992
962
  }
993
963
  readRegExp();
994
964
  continue;
995
965
  }
996
- if(a == '<') {
997
- if(source.substring(index - 1, index + endTag.length - 1) == endTag) {
998
- let end = index - 1;
999
- index += endTag.length - 1;
1000
- return source.substring(start, end);
1001
- }
1002
- }
966
+ if(reader.readIf('</script>')) {
967
+ return reader.sub(start, reader.index - 9);
968
+ } else reader.read();
1003
969
  }
1004
970
  };
1005
971
 
1006
972
  const readStyle = () => {
1007
- let start = index;
1008
- let end = source.substring(start).indexOf('</style>') + start;
1009
- assert(end >= 0, '<style> is not closed');
1010
- index = end + 9;
1011
- return source.substring(start, end);
973
+ return reader.read(/^(.*?)<\/style>/s);
1012
974
  };
1013
975
 
1014
- const readBinding = () => {
1015
- let start = index;
1016
- assert(readNext() === '{', 'Bind error');
1017
- let a = null, p, q;
1018
- let bkt = 1;
1019
-
1020
- while(true) {
1021
- p = a;
1022
- a = readNext();
1023
-
1024
- if(q) {
1025
- if(a != q) continue;
1026
- if(p == '\\') continue;
1027
- q = null;
1028
- continue;
1029
- }
1030
- if(a == '"' || a == "'" || a == '`') {
1031
- q = a;
1032
- continue;
1033
- }
1034
- if(a == '*' && p == '/') {
1035
- // comment block
1036
- while(true) {
1037
- p = a;
1038
- a = readNext();
1039
- if(a == '/' && p == '*') break;
1040
- }
1041
- continue;
1042
- }
1043
-
1044
- if(a == '{') {
1045
- bkt++;
1046
- continue;
1047
- }
1048
- if(a == '}') {
1049
- bkt--;
1050
- if(bkt > 0) continue;
1051
- } else continue;
1052
-
1053
- return {
1054
- value: source.substring(start + 1, index - 1),
1055
- raw: source.substring(start, index)
1056
- };
1057
- }
976
+ const readComment = () => {
977
+ return reader.read(/^<!--.*?-->/s);
1058
978
  };
1059
979
 
1060
- const readComment = () => {
1061
- let start = index;
1062
- let end = source.indexOf('-->', start);
1063
- assert(end >= 0, 'Comment is not closed');
1064
- end += 3;
1065
- index = end;
1066
- return source.substring(start, end);
980
+ const readTemplate = () => {
981
+ return reader.read(/^(.*?)<\/template>/s);
1067
982
  };
1068
983
 
1069
984
  const go = (parent, push) => {
@@ -1086,12 +1001,11 @@
1086
1001
  textNode = null;
1087
1002
  };
1088
1003
 
1089
- while(index < source.length) {
1090
- let a = source[index];
1091
- if(a === '<' && source[index + 1].match(/\S/)) {
1004
+ while(!reader.end()) {
1005
+ if(reader.probe('<') && reader.probe(/^<\S/)) {
1092
1006
  flushText();
1093
1007
 
1094
- if(source.substring(index, index + 4) === '<!--') {
1008
+ if(reader.probe('<!--')) {
1095
1009
  push({
1096
1010
  type: 'comment',
1097
1011
  content: readComment()
@@ -1099,14 +1013,8 @@
1099
1013
  continue;
1100
1014
  }
1101
1015
 
1102
- if(source[index + 1] === '/') { // close tag
1103
- let name = '';
1104
- index += 2;
1105
- while(true) {
1106
- a = readNext();
1107
- if(a === '>') break;
1108
- name += a;
1109
- }
1016
+ if(reader.readIf('</')) { // close tag
1017
+ let name = reader.read(/^([^>]*)>/);
1110
1018
  name = name.trim();
1111
1019
  if(name) {
1112
1020
  name = name.split(':')[0];
@@ -1115,15 +1023,15 @@
1115
1023
  return;
1116
1024
  }
1117
1025
 
1118
- let tag = readTag();
1026
+ let tag = readTag(reader);
1119
1027
  push(tag);
1120
1028
  if(tag.name === 'script') {
1121
1029
  tag.type = 'script';
1122
- tag.content = readScript('script');
1030
+ tag.content = readScript(reader);
1123
1031
  continue;
1124
1032
  } else if(tag.name === 'template') {
1125
1033
  tag.type = 'template';
1126
- tag.content = readScript('template');
1034
+ tag.content = readTemplate();
1127
1035
  continue;
1128
1036
  } else if(tag.name === 'style') {
1129
1037
  tag.type = 'style';
@@ -1144,9 +1052,9 @@
1144
1052
  throw e;
1145
1053
  }
1146
1054
  continue;
1147
- } else if(a === '{') {
1148
- if(['#', '/', ':', '@', '*'].indexOf(source[index + 1]) >= 0) {
1149
- let bind = readBinding();
1055
+ } else if(reader.probe('{')) {
1056
+ if(reader.probe(/^\{[#/:@*]/)) {
1057
+ let bind = parseBinding(reader);
1150
1058
  if(bind.value[0] != '*') flushText();
1151
1059
  if(bind.value[0] == '*') {
1152
1060
  addText(bind.raw);
@@ -1245,14 +1153,29 @@
1245
1153
  } else if(bind.value == '/fragment') {
1246
1154
  assert(parent.type === 'fragment', 'Fragment error: /fragment');
1247
1155
  return;
1156
+ } else if(bind.value.match(/^#([\w\-]+)/)) {
1157
+ const name = bind.value.match(/^#([\w\-]+)/)[1];
1158
+ let tag = {
1159
+ type: 'block',
1160
+ value: bind.value,
1161
+ name,
1162
+ body: []
1163
+ };
1164
+ push(tag);
1165
+ go(tag);
1166
+ continue;
1167
+ } else if(bind.value.match(/^\/([\w\-]+)/)) {
1168
+ const name = bind.value.match(/^\/([\w\-]+)/)[1];
1169
+ assert(parent.type === 'block' && parent.name == name, `Fragment error: ${parent.name} - ${name}`);
1170
+ return;
1248
1171
  } else throw 'Error binding: ' + bind.value;
1249
1172
  } else {
1250
- addText(readBinding().raw);
1251
- continue;
1173
+ addText(parseBinding(reader).raw);
1174
+ continue;
1252
1175
  }
1253
1176
  }
1254
1177
 
1255
- addText(readNext());
1178
+ addText(reader.read());
1256
1179
  }
1257
1180
  flushText();
1258
1181
  assert(parent.type === 'root', 'File ends to early');
@@ -1264,9 +1187,41 @@
1264
1187
  };
1265
1188
  go(root);
1266
1189
 
1267
- this.DOM = root;
1190
+ return root;
1268
1191
  }
1269
1192
 
1193
+ function readTag(reader) {
1194
+ const start = reader.index;
1195
+ assert(reader.read() === '<', 'Tag error');
1196
+
1197
+ let name = reader.read(/^[\da-zA-Z^\-]+/);
1198
+ let elArg = null;
1199
+
1200
+ if(reader.readIf(':')) {
1201
+ elArg = reader.read(/^[^\s>/]+/);
1202
+ }
1203
+
1204
+ let attributes = parseAttibutes(reader, {closedByTag: true});
1205
+
1206
+ let closedTag = false;
1207
+ if(reader.readIf('/>')) closedTag = true;
1208
+ else assert(reader.readIf('>'));
1209
+
1210
+ const voidTags = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
1211
+ let voidTag = voidTags.indexOf(name) >= 0;
1212
+ if(voidTag) closedTag = true;
1213
+ return {
1214
+ type: 'node',
1215
+ name,
1216
+ elArg,
1217
+ openTag: reader.sub(start),
1218
+ start: start,
1219
+ end: reader.index,
1220
+ closedTag,
1221
+ voidTag,
1222
+ attributes
1223
+ };
1224
+ }
1270
1225
 
1271
1226
  function parseText(source) {
1272
1227
  let i = 0;
@@ -1336,7 +1291,94 @@
1336
1291
  return { result, parts, staticText };
1337
1292
  }
1338
1293
 
1339
- function parse$1() {
1294
+
1295
+ const parseBinding = (reader) => {
1296
+ let start = reader.index;
1297
+
1298
+ assert(reader.read() === '{', 'Bind error');
1299
+ let a = null, p, q;
1300
+ let bkt = 1;
1301
+
1302
+ while(true) {
1303
+ p = a;
1304
+ a = reader.read();
1305
+
1306
+ if(q) {
1307
+ if(a != q) continue;
1308
+ if(p == '\\') continue;
1309
+ q = null;
1310
+ continue;
1311
+ }
1312
+ if(a == '"' || a == "'" || a == '`') {
1313
+ q = a;
1314
+ continue;
1315
+ }
1316
+ if(a == '*' && p == '/') {
1317
+ // comment block
1318
+ while(true) {
1319
+ p = a;
1320
+ a = reader.read();
1321
+ if(a == '/' && p == '*') break;
1322
+ }
1323
+ continue;
1324
+ }
1325
+
1326
+ if(a == '{') {
1327
+ bkt++;
1328
+ continue;
1329
+ }
1330
+ if(a == '}') {
1331
+ bkt--;
1332
+ if(bkt > 0) continue;
1333
+ } else continue;
1334
+
1335
+ const raw = reader.sub(start);
1336
+ return {
1337
+ raw,
1338
+ value: raw.substring(1, raw.length - 1),
1339
+ };
1340
+ }
1341
+ };
1342
+
1343
+
1344
+ const parseAttibutes = (source, option={}) => {
1345
+ const r = new Reader(source);
1346
+ let result = [];
1347
+
1348
+ while(!r.end()) {
1349
+ r.skip();
1350
+ if(option.closedByTag) {
1351
+ if(r.probe('/>') || r.probe('>')) break;
1352
+ } else if(r.end()) break;
1353
+ let start = r.index;
1354
+ let name = r.readAttribute();
1355
+ assert(name, 'Wrong syntax');
1356
+ if(r.readIf('=')) {
1357
+ if(r.probe('{')) {
1358
+ const {raw} = parseBinding(r);
1359
+ result.push({name, value: raw, raw, content: r.sub(start)});
1360
+ } else if(r.probeQuote()) {
1361
+ const raw = r.readString();
1362
+ const value = raw.substring(1, raw.length - 1);
1363
+ result.push({name, value, raw, content: r.sub(start)});
1364
+ } else {
1365
+ const value = r.readIf(/^\S+/);
1366
+ result.push({name, value, raw: value, content: r.sub(start)});
1367
+ }
1368
+ } else {
1369
+ let value;
1370
+ if(name[0] == '{' && last(name) == '}' && !name.startsWith('{...')) {
1371
+ value = name;
1372
+ name = unwrapExp(name);
1373
+ }
1374
+ result.push({name, value, raw: value, content: r.sub(start)});
1375
+ }
1376
+ }
1377
+
1378
+ return result;
1379
+ };
1380
+
1381
+ function parse() {
1340
1382
  let source = this.scriptNodes.length ? this.scriptNodes[0].content : null;
1341
1383
  this.script = {
1342
1384
  source,
@@ -1820,6 +1862,12 @@
1820
1862
  }
1821
1863
  }));
1822
1864
 
1865
+ this.module.head.push(this.glob.keepAliveStore = xNode('$$keepAliveStore', {
1866
+ value: false
1867
+ }, (ctx, n) => {
1868
+ if(n.value) ctx.write(true, `const $$keepAliveStore = new Map();`);
1869
+ }));
1870
+
1823
1871
  this.module.top.push(xNode(this.glob.$onMount, {
1824
1872
  }, (ctx, n) => {
1825
1873
  if(n.value) ctx.write(true, `import { $onMount } from 'malinajs/runtime.js';`);
@@ -2137,7 +2185,11 @@
2137
2185
 
2138
2186
  if(n.name == 'component') {
2139
2187
  // dyn-component
2140
- const label = requireLabel(true);
2188
+ if(isRoot) {
2189
+ requireFragment = true;
2190
+ if(!tpl.getLast()) tpl.push(xNode('node:comment', { label: true }));
2191
+ }
2192
+ const label = requireLabel(true, isRoot);
2141
2193
  binds.push(this.makeComponentDyn(n, label));
2142
2194
  } else {
2143
2195
  const label = requireLabel();
@@ -2253,6 +2305,20 @@
2253
2305
  if(!n.closedTag) {
2254
2306
  go(n, false, el);
2255
2307
  }
2308
+ } else if(n.type === 'block') {
2309
+ if(n.name == 'keep-alive') {
2310
+ if(isRoot) requireFragment = true;
2311
+ binds.push(xNode('attach-fragment', {
2312
+ label: requireLabel(),
2313
+ block: this.makeKeepAlive(n)
2314
+ }, (ctx, n) => {
2315
+ if(n.label.node) ctx.write(true, `$runtime.insertBlock(${n.label.name}, `);
2316
+ else ctx.write(true, `$runtime.addBlock(${n.label.name}, `);
2317
+ ctx.add(n.block);
2318
+ ctx.write(')');
2319
+ }));
2320
+ return;
2321
+ } else wrapException(`wrong block: "${n.name}"`, n);
2256
2322
  } else if(n.type === 'each') {
2257
2323
  if(data.type == 'node' && data.body.length == 1) {
2258
2324
  let eachBlock = this.makeEachBlock(n, {
@@ -2559,7 +2625,7 @@
2559
2625
  console.log('Node: ', n);
2560
2626
  if(n.type == 'text') e.details = n.value.trim();
2561
2627
  else if(n.type == 'node') e.details = n.openTag.trim();
2562
- else if(n.type == 'each') e.details = n.value.trim();
2628
+ else if(n.type == 'each' || n.type == 'block') e.details = n.value.trim();
2563
2629
  else if(n.type == 'if') e.details = n.parts?.[0]?.value.trim() || 'if-block';
2564
2630
  }
2565
2631
  throw e;
@@ -4686,7 +4752,7 @@
4686
4752
  function makeDom(data) {
4687
4753
  function build(parent, list) {
4688
4754
  list.forEach(e => {
4689
- if(e.type == 'fragment' || e.type == 'slot') {
4755
+ if(e.type == 'fragment' || e.type == 'slot' || e.type == 'block') {
4690
4756
  if(e.body && e.body.length) build(parent, e.body);
4691
4757
  return;
4692
4758
  } else if(e.type == 'each') {
@@ -6105,7 +6171,7 @@
6105
6171
  }
6106
6172
 
6107
6173
 
6108
- function parseAttibutes(attributes) {
6174
+ function parseAttibutes$1(attributes) {
6109
6175
  let props = [];
6110
6176
  let events = [];
6111
6177
  let forwardAllEvents;
@@ -6150,7 +6216,7 @@
6150
6216
  let slot = null;
6151
6217
  if(node.body?.length) slot = this.buildBlock({ body: trimEmptyNodes(node.body) }, { inline: true });
6152
6218
 
6153
- let { props, events, forwardAllEvents, staticProps } = parseAttibutes.call(this, node.attributes);
6219
+ let { props, events, forwardAllEvents, staticProps } = parseAttibutes$1.call(this, node.attributes);
6154
6220
 
6155
6221
  return xNode('call-fragment', {
6156
6222
  $compile: [slot?.source],
@@ -6250,7 +6316,7 @@
6250
6316
  // assert(!data.slot.template.svg, 'SVG is not supported for exported fragment');
6251
6317
  }
6252
6318
 
6253
- let pa = parseAttibutes.call(this, node.attributes);
6319
+ let pa = parseAttibutes$1.call(this, node.attributes);
6254
6320
  data = { ...pa, ...data };
6255
6321
 
6256
6322
  return xNode('attach-exported-fragment', data, (ctx, n) => {
@@ -6634,7 +6700,44 @@
6634
6700
  return { event, fn, rootModifier };
6635
6701
  }
6636
6702
 
6637
- const version = '0.7.2-a4';
6703
+ function makeKeepAlive(node) {
6704
+ let block;
6705
+ if(node.body && node.body.length) {
6706
+ block = this.buildBlock({ body: trimEmptyNodes(node.body) }, { }).block;
6707
+ } else {
6708
+ this.warning(`Empty block: '${node.value}'`);
6709
+ return xNode('empty-block', (ctx, n) => {
6710
+ ctx.writeLine(`function $block() {};`);
6711
+ });
6712
+ }
6713
+
6714
+ let key = null;
6715
+ let args = node.value.substr(12);
6716
+ if(args) {
6717
+ args = parseAttibutes(args);
6718
+ const a = args.find(a => a.name == 'key');
6719
+ if(a) {
6720
+ let value = a.value;
6721
+ if(value[0] == '{') value = unwrapExp(value);
6722
+ key = `() => (${value})`;
6723
+ }
6724
+ }
6725
+
6726
+ if(!key) key = `() => '$$${this.uniqIndex++}'`;
6727
+
6728
+ this.glob.keepAliveStore.$value();
6729
+
6730
+ return xNode('keep-alive', {
6731
+ block,
6732
+ key
6733
+ }, (ctx, n) => {
6734
+ ctx.write(`$runtime.keepAlive($$keepAliveStore, ${n.key}, `);
6735
+ ctx.add(n.block);
6736
+ ctx.write(')');
6737
+ });
6738
+ }
6739
+
6740
+ const version = '0.7.2-a6';
6638
6741
 
6639
6742
 
6640
6743
  async function compile(source, config = {}) {
@@ -6680,6 +6783,7 @@
6680
6783
  inspectProp,
6681
6784
  attachPortal,
6682
6785
  makeEventProp,
6786
+ makeKeepAlive,
6683
6787
  checkRootName: checkRootName,
6684
6788
 
6685
6789
  inuse: {},
@@ -6709,12 +6813,14 @@
6709
6813
  detectDependency,
6710
6814
 
6711
6815
  DOM: null,
6712
- parseHTML: parse,
6816
+ parseHTML: function() {
6817
+ this.DOM = parseHTML(this.source);
6818
+ },
6713
6819
  compactDOM,
6714
6820
 
6715
6821
  script: null,
6716
6822
  scriptNodes: null,
6717
- js_parse: parse$1,
6823
+ js_parse: parse,
6718
6824
  js_transform: transform,
6719
6825
 
6720
6826
  styleNodes: null,
@@ -6874,6 +6980,7 @@
6874
6980
  }
6875
6981
 
6876
6982
  exports.compile = compile;
6983
+ exports.parseHTML = parseHTML;
6877
6984
  exports.version = version;
6878
6985
 
6879
6986
  Object.defineProperty(exports, '__esModule', { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "malinajs",
3
- "version": "0.7.2-a4",
3
+ "version": "0.7.2-a6",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "prepare": "npm run build",
package/runtime.js CHANGED
@@ -75,7 +75,7 @@ const cd_component = cd => {
75
75
  return cd.component;
76
76
  };
77
77
 
78
- const cd_new = () => new $ChangeDetector();
78
+ const cd_new = (parent) => new $ChangeDetector(parent);
79
79
 
80
80
  const cd_attach = (parent, cd) => {
81
81
  if(cd) {
@@ -414,7 +414,7 @@ const attachDynComponent = (label, exp, bind, parentLabel) => {
414
414
  if(component) {
415
415
  destroyList = current_destroyList = [];
416
416
  current_mountList = [];
417
- $cd = current_cd = cd_new();
417
+ $cd = current_cd = cd_new(parentCD);
418
418
  try {
419
419
  const $dom = bind(component).$dom;
420
420
  cd_attach(parentCD, $cd);
@@ -882,7 +882,7 @@ function ifBlock(label, fn, parts, parentLabel) {
882
882
  let $dom;
883
883
  destroyList = current_destroyList = [];
884
884
  let mountList = current_mountList = [];
885
- $cd = current_cd = cd_new();
885
+ $cd = current_cd = cd_new(parentCD);
886
886
  try {
887
887
  $dom = builder();
888
888
  } finally {
@@ -955,7 +955,7 @@ function awaitBlock(label, parentLabel, relation, fn, build_main, build_then, bu
955
955
 
956
956
  if(!builder) return;
957
957
  destroyList = current_destroyList = [];
958
- $cd = current_cd = cd_new();
958
+ $cd = current_cd = cd_new(parentCD);
959
959
  let $dom, mountList = current_mountList = [];
960
960
  try {
961
961
  $dom = builder(value);
@@ -1015,7 +1015,7 @@ const makeEachElseBlock = (fn) => {
1015
1015
  return (label, mode, parentCD) => {
1016
1016
  let first, last;
1017
1017
  let destroyList = current_destroyList = [];
1018
- let $cd = current_cd = cd_new();
1018
+ let $cd = current_cd = cd_new(parentCD);
1019
1019
  current_mountList = [];
1020
1020
  const parentNode = mode ? label : label.parentNode;
1021
1021
  try {
@@ -1173,7 +1173,7 @@ function $$eachBlock(label, mode, fn, getKey, bind, buildElseBlock) {
1173
1173
  let $dom, rebind,
1174
1174
  d = current_destroyList = [],
1175
1175
  m = current_mountList = [],
1176
- $cd = current_cd = cd_new();
1176
+ $cd = current_cd = cd_new(eachCD);
1177
1177
  try {
1178
1178
  ([$dom, rebind] = bind(item, i));
1179
1179
  } finally {
@@ -1247,4 +1247,42 @@ const makeSlot = (fr, fn) => {
1247
1247
  };
1248
1248
  };
1249
1249
 
1250
- export { $$eachBlock, $context, $digest, $onDestroy, $onMount, $tick, $watch, WatchObject, __app_onerror, __bindActionSubscribe, addBlock, addClass, addEvent, addStyles, attachAnchor, attachBlock, attachDynComponent, autoSubscribe, awaitBlock, bindAction, bindAttribute, bindAttributeBase, bindClass, bindClassExp, bindInput, bindStyle, bindText, callComponent, callComponentDyn, callExportedFragment, cd_attach, cd_component, cd_detach, cd_new, cloneDeep, compareArray, compareDeep, configure, createTextNode, current_cd, current_component, current_destroyList, current_mountList, deepComparator, destroyResults, eachDefaultKey, exportFragment, fire, htmlBlock, htmlBlockStatic, htmlToFragment, htmlToFragmentClean, ifBlock, ifBlockReadOnly, insertAfter, insertBlock, invokeSlot, invokeSlotBase, isArray, isFunction, iterNodes, keyComparator, makeAnchor, makeApply, makeBlock, makeBlockBound, makeClassResolver, makeComponent, makeEachBlock, makeEachElseBlock, makeEachSingleBlock, makeEmitter, makeExternalProperty, makeRootEvent, makeSlot, mergeAllEvents, mergeEvents, mount, mountStatic, noop, prefixPush, refer, removeElements, removeItem, setClassToElement, spreadAttributes, svgToFragment, unwrapProps };
1250
+ const keepAlive = (store, keyFn, builder) => {
1251
+ if(!store.$$d) store.$$d = [];
1252
+ const key = keyFn();
1253
+ let block = store.get(key);
1254
+ const parentCD = current_cd;
1255
+
1256
+ $onDestroy(() => {
1257
+ if(!block.fr) block.fr = new DocumentFragment();
1258
+ iterNodes(block.first, block.last, n => block.fr.appendChild(n));
1259
+ cd_detach(block.$cd);
1260
+ });
1261
+
1262
+ if(block) {
1263
+ cd_attach(parentCD, block.$cd);
1264
+ return block.fr;
1265
+ } else {
1266
+ let $dom, first, last, prev_destroyList = current_destroyList;
1267
+ let destroyList = current_destroyList = [];
1268
+ let $cd = current_cd = cd_new(parentCD);
1269
+ try {
1270
+ $dom = builder();
1271
+ } finally {
1272
+ current_destroyList = prev_destroyList;
1273
+ current_cd = parentCD;
1274
+ }
1275
+ cd_attach(parentCD, $cd);
1276
+ if($dom.nodeType == 11) {
1277
+ first = $dom.firstChild;
1278
+ last = $dom.lastChild;
1279
+ } else first = last = $dom;
1280
+
1281
+ store.$$d.push(() => safeGroupCall(destroyList));
1282
+ store.set(key, block = {first, last, $cd});
1283
+
1284
+ return $dom;
1285
+ }
1286
+ };
1287
+
1288
+ export { $$eachBlock, $context, $digest, $onDestroy, $onMount, $tick, $watch, WatchObject, __app_onerror, __bindActionSubscribe, addBlock, addClass, addEvent, addStyles, attachAnchor, attachBlock, attachDynComponent, autoSubscribe, awaitBlock, bindAction, bindAttribute, bindAttributeBase, bindClass, bindClassExp, bindInput, bindStyle, bindText, callComponent, callComponentDyn, callExportedFragment, cd_attach, cd_component, cd_detach, cd_new, cloneDeep, compareArray, compareDeep, configure, createTextNode, current_cd, current_component, current_destroyList, current_mountList, deepComparator, destroyResults, eachDefaultKey, exportFragment, fire, htmlBlock, htmlBlockStatic, htmlToFragment, htmlToFragmentClean, ifBlock, ifBlockReadOnly, insertAfter, insertBlock, invokeSlot, invokeSlotBase, isArray, isFunction, iterNodes, keepAlive, keyComparator, makeAnchor, makeApply, makeBlock, makeBlockBound, makeClassResolver, makeComponent, makeEachBlock, makeEachElseBlock, makeEachSingleBlock, makeEmitter, makeExternalProperty, makeRootEvent, makeSlot, mergeAllEvents, mergeEvents, mount, mountStatic, noop, prefixPush, refer, removeElements, removeItem, setClassToElement, spreadAttributes, svgToFragment, unwrapProps };