babel-plugin-jsx-dom-expressions 0.34.11 → 0.35.0-beta.0

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.
Files changed (2) hide show
  1. package/index.js +269 -115
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -219,6 +219,14 @@ const reservedNameSpaces = new Set([
219
219
  "attr"
220
220
  ]);
221
221
 
222
+ const nonSpreadNameSpaces = new Set([
223
+ "class",
224
+ "style",
225
+ "use",
226
+ "prop",
227
+ "attr"
228
+ ]);
229
+
222
230
  function getConfig(path) {
223
231
  return path.hub.file.metadata.config;
224
232
  }
@@ -429,7 +437,7 @@ function transformCondition(path, inline, deep) {
429
437
  if (!t__namespace.isBinaryExpression(cond))
430
438
  cond = t__namespace.unaryExpression("!", t__namespace.unaryExpression("!", cond, true), true);
431
439
  id = inline
432
- ? t__namespace.callExpression(memo, [t__namespace.arrowFunctionExpression([], cond), t__namespace.booleanLiteral(true)])
440
+ ? t__namespace.callExpression(memo, [t__namespace.arrowFunctionExpression([], cond)])
433
441
  : path.scope.generateUidIdentifier("_c$");
434
442
  expr.test = t__namespace.callExpression(id, []);
435
443
  if (t__namespace.isConditionalExpression(expr.consequent) || t__namespace.isLogicalExpression(expr.consequent)) {
@@ -455,7 +463,7 @@ function transformCondition(path, inline, deep) {
455
463
  if (!t__namespace.isBinaryExpression(cond))
456
464
  cond = t__namespace.unaryExpression("!", t__namespace.unaryExpression("!", cond, true), true);
457
465
  id = inline
458
- ? t__namespace.callExpression(memo, [t__namespace.arrowFunctionExpression([], cond), t__namespace.booleanLiteral(true)])
466
+ ? t__namespace.callExpression(memo, [t__namespace.arrowFunctionExpression([], cond)])
459
467
  : path.scope.generateUidIdentifier("_c$");
460
468
  nextPath.node.left = t__namespace.callExpression(id, []);
461
469
  }
@@ -466,7 +474,7 @@ function transformCondition(path, inline, deep) {
466
474
  t__namespace.variableDeclarator(
467
475
  id,
468
476
  config.memoWrapper
469
- ? t__namespace.callExpression(memo, [t__namespace.arrowFunctionExpression([], cond), t__namespace.booleanLiteral(true)])
477
+ ? t__namespace.callExpression(memo, [t__namespace.arrowFunctionExpression([], cond)])
470
478
  : t__namespace.arrowFunctionExpression([], cond)
471
479
  )
472
480
  ]),
@@ -534,6 +542,13 @@ function escapeHTML(s, attr) {
534
542
  return left < s.length ? out + s.substring(left) : out;
535
543
  }
536
544
 
545
+ function canNativeSpread(key, { checkNameSpaces } = {}) {
546
+ if (checkNameSpaces && key.includes(":") && nonSpreadNameSpaces.has(key.split(":")[0])) return false;
547
+ // TODO: figure out how to detect definitely function ref
548
+ if (key === "ref") return false;
549
+ return true;
550
+ }
551
+
537
552
  function transformElement$3(path, info) {
538
553
  let tagName = getTagName(path.node),
539
554
  config = getConfig(path),
@@ -681,6 +696,7 @@ function transformAttributes$2(path, results) {
681
696
  let elem = results.id,
682
697
  hasHydratableEvent = false,
683
698
  children,
699
+ spreadExpr,
684
700
  attributes = path.get("openingElement").get("attributes");
685
701
  const tagName = getTagName(path.node),
686
702
  isSVG = SVGElements.has(tagName),
@@ -688,15 +704,29 @@ function transformAttributes$2(path, results) {
688
704
  hasChildren = path.node.children.length > 0,
689
705
  config = getConfig(path);
690
706
 
707
+ // preprocess spreads
708
+ if (attributes.some(attribute => t__namespace.isJSXSpreadAttribute(attribute.node))) {
709
+ [attributes, spreadExpr] = processSpreads$1(path, attributes, { elem, isSVG, hasChildren });
710
+ path.get("openingElement").set(
711
+ "attributes",
712
+ attributes.map(a => a.node)
713
+ );
714
+ //NOTE: can't be checked at compile time so add to compiled output
715
+ hasHydratableEvent = true;
716
+ }
717
+
691
718
  // preprocess styles
692
- const styleAttribute = attributes.find(
693
- a =>
694
- a.node.name &&
695
- a.node.name.name === "style" &&
696
- t__namespace.isJSXExpressionContainer(a.node.value) &&
697
- t__namespace.isObjectExpression(a.node.value.expression) &&
698
- !a.node.value.expression.properties.some(p => t__namespace.isSpreadElement(p))
699
- );
719
+ const styleAttribute = path
720
+ .get("openingElement")
721
+ .get("attributes")
722
+ .find(
723
+ a =>
724
+ a.node.name &&
725
+ a.node.name.name === "style" &&
726
+ t__namespace.isJSXExpressionContainer(a.node.value) &&
727
+ t__namespace.isObjectExpression(a.node.value.expression) &&
728
+ !a.node.value.expression.properties.some(p => t__namespace.isSpreadElement(p))
729
+ );
700
730
  if (styleAttribute) {
701
731
  let i = 0,
702
732
  leading = styleAttribute.node.value.expression.leadingComments;
@@ -816,31 +846,6 @@ function transformAttributes$2(path, results) {
816
846
  .get("attributes")
817
847
  .forEach(attribute => {
818
848
  const node = attribute.node;
819
- if (t__namespace.isJSXSpreadAttribute(node)) {
820
- results.exprs.push(
821
- t__namespace.expressionStatement(
822
- t__namespace.callExpression(
823
- registerImportMethod(attribute, "spread", getRendererConfig(path, "dom").moduleName),
824
- [
825
- elem,
826
- isDynamic(attribute.get("argument"), {
827
- checkMember: true
828
- })
829
- ? t__namespace.isCallExpression(node.argument) && !node.argument.arguments.length
830
- ? node.argument.callee
831
- : t__namespace.arrowFunctionExpression([], node.argument)
832
- : node.argument,
833
- t__namespace.booleanLiteral(isSVG),
834
- t__namespace.booleanLiteral(hasChildren)
835
- ]
836
- )
837
- )
838
- );
839
- //NOTE: can't be checked at compile time so add to compiled output
840
- hasHydratableEvent = true;
841
- return;
842
- }
843
-
844
849
  let value = node.value,
845
850
  key = t__namespace.isJSXNamespacedName(node.name)
846
851
  ? `${node.name.namespace.name}:${node.name.name.name}`
@@ -1131,6 +1136,7 @@ function transformAttributes$2(path, results) {
1131
1136
  if (!hasChildren && children) {
1132
1137
  path.node.children.push(children);
1133
1138
  }
1139
+ if (spreadExpr) results.exprs.push(spreadExpr);
1134
1140
 
1135
1141
  results.hasHydratableEvent = results.hasHydratableEvent || hasHydratableEvent;
1136
1142
  }
@@ -1333,6 +1339,87 @@ function contextToCustomElement(path, results) {
1333
1339
  );
1334
1340
  }
1335
1341
 
1342
+ function processSpreads$1(path, attributes, { elem, isSVG, hasChildren }) {
1343
+ // TODO: skip but collect the names of any properties after the last spread to not overwrite them
1344
+ const filteredAttributes = [];
1345
+ const spreadArgs = [];
1346
+ let runningObject = [];
1347
+ let runningDynamic = false;
1348
+ let dynamicSpread = false;
1349
+ let firstSpread = false;
1350
+ attributes.forEach(attribute => {
1351
+ const node = attribute.node;
1352
+ const key =
1353
+ !t__namespace.isJSXSpreadAttribute(node) &&
1354
+ (t__namespace.isJSXNamespacedName(node.name)
1355
+ ? `${node.name.namespace.name}:${node.name.name.name}`
1356
+ : node.name.name);
1357
+ if (t__namespace.isJSXSpreadAttribute(node)) {
1358
+ firstSpread = true;
1359
+ if (runningObject.length) {
1360
+ spreadArgs.push(
1361
+ runningDynamic
1362
+ ? t__namespace.arrowFunctionExpression([], t__namespace.objectExpression(runningObject))
1363
+ : t__namespace.objectExpression(runningObject)
1364
+ );
1365
+ runningDynamic = false;
1366
+ runningObject = [];
1367
+ }
1368
+ spreadArgs.push(
1369
+ isDynamic(attribute.get("argument"), {
1370
+ checkMember: true
1371
+ }) && (dynamicSpread = true)
1372
+ ? t__namespace.isCallExpression(node.argument) &&
1373
+ !node.argument.arguments.length &&
1374
+ !t__namespace.isCallExpression(node.argument.callee) &&
1375
+ !t__namespace.isMemberExpression(node.argument.callee)
1376
+ ? node.argument.callee
1377
+ : t__namespace.arrowFunctionExpression([], node.argument)
1378
+ : node.argument
1379
+ );
1380
+ } else if (
1381
+ (firstSpread ||
1382
+ (t__namespace.isJSXExpressionContainer(node.value) &&
1383
+ isDynamic(attribute.get("value").get("expression"), { checkMember: true }))) &&
1384
+ canNativeSpread(key, { checkNameSpaces: true })
1385
+ ) {
1386
+ const isContainer = t__namespace.isJSXExpressionContainer(node.value);
1387
+ runningDynamic =
1388
+ runningDynamic ||
1389
+ (isContainer && isDynamic(attribute.get("value").get("expression"), { checkMember: true }));
1390
+ runningObject.push(
1391
+ t__namespace.objectProperty(
1392
+ t__namespace.stringLiteral(key),
1393
+ isContainer ? node.value.expression : node.value || t__namespace.stringLiteral("")
1394
+ )
1395
+ );
1396
+ } else filteredAttributes.push(attribute);
1397
+ });
1398
+
1399
+ if (runningObject.length) {
1400
+ spreadArgs.push(
1401
+ runningDynamic
1402
+ ? t__namespace.arrowFunctionExpression([], t__namespace.objectExpression(runningObject))
1403
+ : t__namespace.objectExpression(runningObject)
1404
+ );
1405
+ }
1406
+
1407
+ const props =
1408
+ spreadArgs.length === 1 && !dynamicSpread
1409
+ ? spreadArgs[0]
1410
+ : t__namespace.callExpression(registerImportMethod(path, "mergeProps"), spreadArgs);
1411
+
1412
+ return [
1413
+ filteredAttributes,
1414
+ t__namespace.expressionStatement(
1415
+ t__namespace.callExpression(
1416
+ registerImportMethod(path, "spread", getRendererConfig(path, "dom").moduleName),
1417
+ [elem, props, t__namespace.booleanLiteral(isSVG), t__namespace.booleanLiteral(hasChildren)]
1418
+ )
1419
+ )
1420
+ ];
1421
+ }
1422
+
1336
1423
  function createTemplate$2(path, result, wrap) {
1337
1424
  const config = getConfig(path);
1338
1425
  if (result.id) {
@@ -1952,18 +2039,12 @@ function transformChildren$1(path, results, { hydratable }) {
1952
2039
  filteredChildren.forEach(node => {
1953
2040
  if (t__namespace.isJSXElement(node.node) && getTagName(node.node) === "head") {
1954
2041
  const child = transformNode(node, { doNotEscape, hydratable: false });
1955
- registerImportMethod(path, "NoHydration");
2042
+ registerImportMethod(path, "setHydratable");
1956
2043
  results.template.push("");
1957
2044
  results.templateValues.push(
1958
- t__namespace.callExpression(t__namespace.identifier("_$NoHydration"), [
1959
- t__namespace.objectExpression([
1960
- t__namespace.objectMethod(
1961
- "get",
1962
- t__namespace.identifier("children"),
1963
- [],
1964
- t__namespace.blockStatement([t__namespace.returnStatement(createTemplate$1(path, child))])
1965
- )
1966
- ])
2045
+ t__namespace.callExpression(t__namespace.identifier("_$setHydratable"), [
2046
+ t__namespace.arrowFunctionExpression([], createTemplate$1(path, child)),
2047
+ t__namespace.booleanLiteral(false)
1967
2048
  ])
1968
2049
  );
1969
2050
  return;
@@ -1973,7 +2054,8 @@ function transformChildren$1(path, results, { hydratable }) {
1973
2054
  appendToTemplate(results.template, child.template);
1974
2055
  results.templateValues.push.apply(results.templateValues, child.templateValues || []);
1975
2056
  if (child.exprs.length) {
1976
- if (!doNotEscape && !child.spreadElement) child.exprs[0] = escapeExpression(path, child.exprs[0]);
2057
+ if (!doNotEscape && !child.spreadElement)
2058
+ child.exprs[0] = escapeExpression(path, child.exprs[0]);
1977
2059
 
1978
2060
  // boxed by textNodes
1979
2061
  if (markers && !child.spreadElement) {
@@ -2014,51 +2096,56 @@ function createElement(path, { topLevel, hydratable }) {
2014
2096
  if (attributes.length === 1) {
2015
2097
  transformed = attributes[0].node.argument;
2016
2098
  } else {
2017
- transformed = t__namespace.arrowFunctionExpression([], t__namespace.objectExpression(
2018
- attributes.reduce((memo, attribute) => {
2019
- const node = attribute.node;
2020
-
2021
- if (t__namespace.isJSXSpreadAttribute(node)) {
2022
- const expr = node.argument;
2023
- memo.push(t__namespace.spreadElement(expr));
2024
- return memo;
2025
- }
2026
- let value = node.value,
2027
- key = t__namespace.isJSXNamespacedName(node.name)
2028
- ? `${node.name.namespace.name}:${node.name.name.name}`
2029
- : node.name.name,
2030
- reservedNameSpace =
2031
- t__namespace.isJSXNamespacedName(node.name) && reservedNameSpaces.has(node.name.namespace.name);
2032
- if (
2033
- ((t__namespace.isJSXNamespacedName(node.name) && reservedNameSpace) || ChildProperties.has(key)) &&
2034
- !t__namespace.isJSXExpressionContainer(value)
2035
- ) {
2036
- node.value = value = t__namespace.JSXExpressionContainer(value || t__namespace.JSXEmptyExpression());
2037
- }
2099
+ transformed = t__namespace.arrowFunctionExpression(
2100
+ [],
2101
+ t__namespace.objectExpression(
2102
+ attributes.reduce((memo, attribute) => {
2103
+ const node = attribute.node;
2104
+
2105
+ if (t__namespace.isJSXSpreadAttribute(node)) {
2106
+ const expr = node.argument;
2107
+ memo.push(t__namespace.spreadElement(expr));
2108
+ return memo;
2109
+ }
2110
+ let value = node.value,
2111
+ key = t__namespace.isJSXNamespacedName(node.name)
2112
+ ? `${node.name.namespace.name}:${node.name.name.name}`
2113
+ : node.name.name,
2114
+ reservedNameSpace =
2115
+ t__namespace.isJSXNamespacedName(node.name) && reservedNameSpaces.has(node.name.namespace.name);
2116
+ if (
2117
+ ((t__namespace.isJSXNamespacedName(node.name) && reservedNameSpace) || ChildProperties.has(key)) &&
2118
+ !t__namespace.isJSXExpressionContainer(value)
2119
+ ) {
2120
+ node.value = value = t__namespace.JSXExpressionContainer(value || t__namespace.JSXEmptyExpression());
2121
+ }
2038
2122
 
2039
- if (
2040
- t__namespace.isJSXExpressionContainer(value) &&
2041
- (reservedNameSpace ||
2042
- ChildProperties.has(key) ||
2043
- !(t__namespace.isStringLiteral(value.expression) || t__namespace.isNumericLiteral(value.expression)))
2044
- ) {
2045
2123
  if (
2046
- key === "ref" ||
2047
- key.startsWith("use:") ||
2048
- key.startsWith("prop:") ||
2049
- key.startsWith("on")
2050
- )
2051
- return memo;
2124
+ t__namespace.isJSXExpressionContainer(value) &&
2125
+ (reservedNameSpace ||
2126
+ ChildProperties.has(key) ||
2127
+ !(t__namespace.isStringLiteral(value.expression) || t__namespace.isNumericLiteral(value.expression)))
2128
+ ) {
2129
+ if (
2130
+ key === "ref" ||
2131
+ key.startsWith("use:") ||
2132
+ key.startsWith("prop:") ||
2133
+ key.startsWith("on")
2134
+ )
2135
+ return memo;
2052
2136
 
2053
- memo.push(t__namespace.objectProperty(t__namespace.stringLiteral(key), value.expression));
2054
- } else {
2055
- if (key === "$ServerOnly") return memo;
2056
- if (t__namespace.isJSXExpressionContainer(value)) value = value.expression;
2057
- memo.push(t__namespace.objectProperty(t__namespace.stringLiteral(key), value ? value : t__namespace.booleanLiteral(true)));
2058
- }
2059
- return memo;
2060
- }, [])
2061
- ));
2137
+ memo.push(t__namespace.objectProperty(t__namespace.stringLiteral(key), value.expression));
2138
+ } else {
2139
+ if (key === "$ServerOnly") return memo;
2140
+ if (t__namespace.isJSXExpressionContainer(value)) value = value.expression;
2141
+ memo.push(
2142
+ t__namespace.objectProperty(t__namespace.stringLiteral(key), value ? value : t__namespace.booleanLiteral(true))
2143
+ );
2144
+ }
2145
+ return memo;
2146
+ }, [])
2147
+ )
2148
+ );
2062
2149
  }
2063
2150
 
2064
2151
  const exprs = [
@@ -2114,41 +2201,26 @@ function transformElement$1(path, info) {
2114
2201
  }
2115
2202
 
2116
2203
  function transformAttributes(path, results) {
2117
- let children;
2204
+ let children, spreadExpr;
2205
+ let attributes = path.get("openingElement").get("attributes");
2118
2206
  const elem = results.id,
2119
2207
  hasChildren = path.node.children.length > 0,
2120
2208
  config = getConfig(path);
2121
2209
 
2210
+ // preprocess spreads
2211
+ if (attributes.some(attribute => t__namespace.isJSXSpreadAttribute(attribute.node))) {
2212
+ [attributes, spreadExpr] = processSpreads(path, attributes, { elem, hasChildren });
2213
+ path.get("openingElement").set(
2214
+ "attributes",
2215
+ attributes.map(a => a.node)
2216
+ );
2217
+ }
2218
+
2122
2219
  path
2123
2220
  .get("openingElement")
2124
2221
  .get("attributes")
2125
2222
  .forEach(attribute => {
2126
2223
  const node = attribute.node;
2127
- if (t__namespace.isJSXSpreadAttribute(node)) {
2128
- results.exprs.push(
2129
- t__namespace.expressionStatement(
2130
- t__namespace.callExpression(
2131
- registerImportMethod(
2132
- attribute,
2133
- "spread",
2134
- getRendererConfig(path, "universal").moduleName
2135
- ),
2136
- [
2137
- elem,
2138
- isDynamic(attribute.get("argument"), {
2139
- checkMember: true
2140
- })
2141
- ? t__namespace.isCallExpression(node.argument) && !node.argument.arguments.length
2142
- ? node.argument.callee
2143
- : t__namespace.arrowFunctionExpression([], node.argument)
2144
- : node.argument,
2145
- t__namespace.booleanLiteral(hasChildren)
2146
- ]
2147
- )
2148
- )
2149
- );
2150
- return;
2151
- }
2152
2224
 
2153
2225
  let value = node.value,
2154
2226
  key = t__namespace.isJSXNamespacedName(node.name)
@@ -2273,6 +2345,7 @@ function transformAttributes(path, results) {
2273
2345
  results.exprs.push(t__namespace.expressionStatement(setAttr(attribute, elem, key, value)));
2274
2346
  }
2275
2347
  });
2348
+ if (spreadExpr) results.exprs.push(spreadExpr);
2276
2349
  if (!hasChildren && children) {
2277
2350
  path.node.children.push(children);
2278
2351
  }
@@ -2366,6 +2439,87 @@ function nextChild(children, index) {
2366
2439
  return children[index + 1] && (children[index + 1].id || nextChild(children, index + 1));
2367
2440
  }
2368
2441
 
2442
+ function processSpreads(path, attributes, { elem, hasChildren }) {
2443
+ // TODO: skip but collect the names of any properties after the last spread to not overwrite them
2444
+ const filteredAttributes = [];
2445
+ const spreadArgs = [];
2446
+ let runningObject = [];
2447
+ let runningDynamic = false;
2448
+ let dynamicSpread = false;
2449
+ let firstSpread = false;
2450
+ attributes.forEach(attribute => {
2451
+ const node = attribute.node;
2452
+ const key =
2453
+ !t__namespace.isJSXSpreadAttribute(node) &&
2454
+ (t__namespace.isJSXNamespacedName(node.name)
2455
+ ? `${node.name.namespace.name}:${node.name.name.name}`
2456
+ : node.name.name);
2457
+ if (t__namespace.isJSXSpreadAttribute(node)) {
2458
+ firstSpread = true;
2459
+ if (runningObject.length) {
2460
+ spreadArgs.push(
2461
+ runningDynamic
2462
+ ? t__namespace.arrowFunctionExpression([], t__namespace.objectExpression(runningObject))
2463
+ : t__namespace.objectExpression(runningObject)
2464
+ );
2465
+ runningDynamic = false;
2466
+ runningObject = [];
2467
+ }
2468
+ spreadArgs.push(
2469
+ isDynamic(attribute.get("argument"), {
2470
+ checkMember: true
2471
+ }) && (dynamicSpread = true)
2472
+ ? t__namespace.isCallExpression(node.argument) &&
2473
+ !node.argument.arguments.length &&
2474
+ !t__namespace.isCallExpression(node.argument.callee) &&
2475
+ !t__namespace.isMemberExpression(node.argument.callee)
2476
+ ? node.argument.callee
2477
+ : t__namespace.arrowFunctionExpression([], node.argument)
2478
+ : node.argument
2479
+ );
2480
+ } else if (
2481
+ (firstSpread ||
2482
+ (t__namespace.isJSXExpressionContainer(node.value) &&
2483
+ isDynamic(attribute.get("value").get("expression"), { checkMember: true }))) &&
2484
+ canNativeSpread(key)
2485
+ ) {
2486
+ const isContainer = t__namespace.isJSXExpressionContainer(node.value);
2487
+ runningDynamic =
2488
+ runningDynamic ||
2489
+ (isContainer && isDynamic(attribute.get("value").get("expression"), { checkMember: true }));
2490
+ runningObject.push(
2491
+ t__namespace.objectProperty(
2492
+ t__namespace.stringLiteral(key),
2493
+ isContainer ? node.value.expression : node.value || t__namespace.stringLiteral("")
2494
+ )
2495
+ );
2496
+ } else filteredAttributes.push(attribute);
2497
+ });
2498
+
2499
+ if (runningObject.length) {
2500
+ spreadArgs.push(
2501
+ runningDynamic
2502
+ ? t__namespace.arrowFunctionExpression([], t__namespace.objectExpression(runningObject))
2503
+ : t__namespace.objectExpression(runningObject)
2504
+ );
2505
+ }
2506
+
2507
+ const props =
2508
+ spreadArgs.length === 1 && !dynamicSpread
2509
+ ? spreadArgs[0]
2510
+ : t__namespace.callExpression(registerImportMethod(path, "mergeProps"), spreadArgs);
2511
+
2512
+ return [
2513
+ filteredAttributes,
2514
+ t__namespace.expressionStatement(
2515
+ t__namespace.callExpression(
2516
+ registerImportMethod(path, "spread", getRendererConfig(path, "universal").moduleName),
2517
+ [elem, props, t__namespace.booleanLiteral(hasChildren)]
2518
+ )
2519
+ )
2520
+ ];
2521
+ }
2522
+
2369
2523
  function createTemplate(path, result, wrap) {
2370
2524
  const config = getConfig(path);
2371
2525
  if (result.id) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "babel-plugin-jsx-dom-expressions",
3
3
  "description": "A JSX to DOM plugin that wraps expressions for fine grained change detection",
4
- "version": "0.34.11",
4
+ "version": "0.35.0-beta.0",
5
5
  "author": "Ryan Carniato",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -27,5 +27,5 @@
27
27
  "peerDependencies": {
28
28
  "@babel/core": "^7.0.0"
29
29
  },
30
- "gitHead": "ed99d8856de6b9f993d3ec94afa24f288b09a37b"
30
+ "gitHead": "a4e7a727b92834f6f2550ff7314fe2e57715cadb"
31
31
  }