react-native-boost 1.1.0 → 1.2.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.
@@ -127,10 +127,7 @@ const isReactNativeImport = (path, expectedImportedName) => {
127
127
  }
128
128
  return false;
129
129
  };
130
- const getViewAncestorClassification = (path) => {
131
- return classifyViewAncestors(path);
132
- };
133
- function classifyViewAncestors(path) {
130
+ const getAncestorClassification = (path) => {
134
131
  const context = {
135
132
  componentCache: /* @__PURE__ */ new WeakMap(),
136
133
  componentInProgress: /* @__PURE__ */ new WeakSet(),
@@ -147,7 +144,21 @@ function classifyViewAncestors(path) {
147
144
  ancestorPath = ancestorPath.parentPath;
148
145
  }
149
146
  return classification;
150
- }
147
+ };
148
+ const ancestorBailoutChecks = (path, dangerousOptimizationEnabled) => {
149
+ let classification;
150
+ const classify = () => classification != null ? classification : classification = getAncestorClassification(path);
151
+ return [
152
+ {
153
+ reason: "has Text ancestor",
154
+ shouldBail: () => classify() === "text"
155
+ },
156
+ {
157
+ reason: "has unresolved ancestor and dangerous optimization is disabled",
158
+ shouldBail: () => classify() === "unknown" && !dangerousOptimizationEnabled
159
+ }
160
+ ];
161
+ };
151
162
  function classifyJSXElementAsAncestor(path, context) {
152
163
  const openingElementName = path.node.openingElement.name;
153
164
  if (core.types.isJSXIdentifier(openingElementName)) {
@@ -675,18 +686,37 @@ function extractSelectableAndUpdateStyle(styleExpr) {
675
686
  }
676
687
  return void 0;
677
688
  }
678
- const isStringNode = (path, child) => {
679
- if (core.types.isJSXText(child) || core.types.isStringLiteral(child)) return true;
680
- if (core.types.isJSXExpressionContainer(child)) {
681
- const expression = child.expression;
682
- if (core.types.isIdentifier(expression)) {
683
- const binding = path.scope.getBinding(expression.name);
684
- if (binding && binding.path.node && core.types.isVariableDeclarator(binding.path.node)) {
685
- return !!binding.path.node.init && core.types.isStringLiteral(binding.path.node.init);
686
- }
687
- return false;
689
+ const isPrimitiveExpression = (path, expression, resolved = /* @__PURE__ */ new Set()) => {
690
+ if (core.types.isStringLiteral(expression) || core.types.isNumericLiteral(expression)) return true;
691
+ if (core.types.isTemplateLiteral(expression)) return true;
692
+ if (core.types.isBinaryExpression(expression)) {
693
+ const { left, right } = expression;
694
+ return core.types.isExpression(left) && isPrimitiveExpression(path, left, resolved) && isPrimitiveExpression(path, right, resolved);
695
+ }
696
+ if (core.types.isConditionalExpression(expression)) {
697
+ return isPrimitiveExpression(path, expression.consequent, resolved) && isPrimitiveExpression(path, expression.alternate, resolved);
698
+ }
699
+ if (core.types.isLogicalExpression(expression)) {
700
+ return isPrimitiveExpression(path, expression.left, resolved) && isPrimitiveExpression(path, expression.right, resolved);
701
+ }
702
+ if (core.types.isIdentifier(expression)) {
703
+ if (resolved.has(expression.name)) return false;
704
+ const binding = path.scope.getBinding(expression.name);
705
+ if (binding && binding.constant && binding.path.node && core.types.isVariableDeclarator(binding.path.node)) {
706
+ const init = binding.path.node.init;
707
+ return !!init && core.types.isExpression(init) && isPrimitiveExpression(path, init, new Set(resolved).add(expression.name));
688
708
  }
689
- if (core.types.isStringLiteral(expression)) return true;
709
+ return false;
710
+ }
711
+ return false;
712
+ };
713
+ const isPrimitiveChild = (path, child) => {
714
+ if (core.types.isJSXText(child)) return true;
715
+ if (core.types.isStringLiteral(child)) return true;
716
+ if (core.types.isJSXExpressionContainer(child)) {
717
+ const { expression } = child;
718
+ if (core.types.isJSXEmptyExpression(expression)) return false;
719
+ return isPrimitiveExpression(path, expression);
690
720
  }
691
721
  return false;
692
722
  };
@@ -725,6 +755,10 @@ const replaceWithNativeComponent = (path, parent, file, nativeComponentName) =>
725
755
  };
726
756
 
727
757
  const textBlacklistedProperties = /* @__PURE__ */ new Set([
758
+ // The `Text` wrapper translates `aria-hidden` into `accessibilityElementsHidden` /
759
+ // `importantForAccessibility`, which `processAccessibilityProps` does not yet handle. Passing it
760
+ // through would drop it, so bail. TODO: handle this in the runtime helper instead.
761
+ "aria-hidden",
728
762
  "id",
729
763
  "nativeID",
730
764
  "onLongPress",
@@ -742,7 +776,9 @@ const textBlacklistedProperties = /* @__PURE__ */ new Set([
742
776
  "selectionColor"
743
777
  // TODO: we can use react-native's internal `processColor` to process this at runtime
744
778
  ]);
745
- const textOptimizer = (path, logger) => {
779
+ const NORMALIZED_PROPERTIES = /* @__PURE__ */ new Set([...ACCESSIBILITY_PROPERTIES, "disabled"]);
780
+ const isNormalizedProperty = (attribute) => core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name) && NORMALIZED_PROPERTIES.has(attribute.name.name);
781
+ const textOptimizer = (path, logger, options, platform) => {
746
782
  if (!isValidJSXComponent(path, "Text")) return;
747
783
  if (!isReactNativeImport(path, "Text")) return;
748
784
  const parent = path.parent;
@@ -756,10 +792,13 @@ const textOptimizer = (path, logger) => {
756
792
  reason: "is a direct child of expo-router Link with asChild",
757
793
  shouldBail: () => hasExpoRouterLinkParentWithAsChild(path)
758
794
  },
795
+ // The local children check runs before the ancestor checks because it is cheap and prunes the
796
+ // common nested-element `Text` before the unbounded ancestor walk those checks trigger.
759
797
  {
760
- reason: "contains non-string children",
798
+ reason: "contains non-primitive children",
761
799
  shouldBail: () => hasInvalidChildren(path, parent)
762
- }
800
+ },
801
+ ...ancestorBailoutChecks(path, (options == null ? void 0 : options.dangerouslyOptimizeTextWithUnknownAncestors) === true)
763
802
  ];
764
803
  if (forced) {
765
804
  const overriddenReason = getFirstBailoutReason(overridableChecks);
@@ -791,18 +830,18 @@ const textOptimizer = (path, logger) => {
791
830
  fixNegativeNumberOfLines({ path, logger });
792
831
  addDefaultProperty(path, "allowFontScaling", core.types.booleanLiteral(true));
793
832
  addDefaultProperty(path, "ellipsizeMode", core.types.stringLiteral("tail"));
794
- processProps(path, file);
833
+ processProps(path, file, platform);
795
834
  replaceWithNativeComponent(path, parent, file, "NativeText");
796
835
  };
797
836
  function hasInvalidChildren(path, parent) {
798
837
  for (const attribute of path.node.attributes) {
799
838
  if (core.types.isJSXSpreadAttribute(attribute)) continue;
800
- if (core.types.isJSXIdentifier(attribute.name) && attribute.value && // For a "children" attribute, optimization is allowed only if it is a string
801
- attribute.name.name === "children" && !isStringNode(path, attribute.value)) {
839
+ if (core.types.isJSXIdentifier(attribute.name) && attribute.value && // For a "children" attribute, optimization is allowed only if it is a provable primitive
840
+ attribute.name.name === "children" && !isPrimitiveChild(path, attribute.value)) {
802
841
  return true;
803
842
  }
804
843
  }
805
- return !parent.children.every((child) => isStringNode(path, child));
844
+ return !parent.children.every((child) => isPrimitiveChild(path, child));
806
845
  }
807
846
  function fixNegativeNumberOfLines({ path, logger }) {
808
847
  for (const attribute of path.node.attributes) {
@@ -824,16 +863,15 @@ function fixNegativeNumberOfLines({ path, logger }) {
824
863
  }
825
864
  }
826
865
  }
827
- function processProps(path, file) {
866
+ function processProps(path, file, platform) {
828
867
  const currentAttributes = [...path.node.attributes];
829
868
  const { styleExpr, styleAttribute } = extractStyleAttribute(currentAttributes);
830
- const hasA11y = hasAccessibilityProperty(path, currentAttributes);
869
+ const shouldNormalize = hasAccessibilityProperty(path, currentAttributes) || currentAttributes.some(
870
+ (attribute) => core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name, { name: "disabled" })
871
+ );
831
872
  const spreadAttributes = [];
832
- if (hasA11y) {
833
- const accessibilityAttributes = currentAttributes.filter((attribute) => {
834
- if (!core.types.isJSXAttribute(attribute)) return false;
835
- return core.types.isJSXIdentifier(attribute.name) && ACCESSIBILITY_PROPERTIES.has(attribute.name.name);
836
- });
873
+ if (shouldNormalize) {
874
+ const normalizedAttributes = currentAttributes.filter((attribute) => isNormalizedProperty(attribute));
837
875
  const normalizeIdentifier = addFileImportHint({
838
876
  file,
839
877
  nameHint: "processAccessibilityProps",
@@ -841,7 +879,7 @@ function processProps(path, file) {
841
879
  importName: "processAccessibilityProps",
842
880
  moduleName: RUNTIME_MODULE_NAME
843
881
  });
844
- const accessibilityObject = buildPropertiesFromAttributes(accessibilityAttributes);
882
+ const accessibilityObject = buildPropertiesFromAttributes(normalizedAttributes);
845
883
  const accessibilityExpr = core.types.callExpression(core.types.identifier(normalizeIdentifier.name), [accessibilityObject]);
846
884
  spreadAttributes.push(core.types.jsxSpreadAttribute(accessibilityExpr));
847
885
  }
@@ -867,15 +905,31 @@ function processProps(path, file) {
867
905
  const remainingAttributes = [];
868
906
  for (const attribute of currentAttributes) {
869
907
  if (styleAttribute && attribute === styleAttribute) continue;
870
- if (hasA11y && core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name) && ACCESSIBILITY_PROPERTIES.has(attribute.name.name)) {
871
- continue;
872
- }
908
+ if (shouldNormalize && isNormalizedProperty(attribute)) continue;
873
909
  remainingAttributes.push(attribute);
874
910
  }
875
- path.node.attributes = [...spreadAttributes, selectableAttribute, ...remainingAttributes].filter(
911
+ const accessibleAttribute = shouldNormalize ? void 0 : buildAccessibleDefault(path, file, platform);
912
+ path.node.attributes = [...spreadAttributes, selectableAttribute, ...remainingAttributes, accessibleAttribute].filter(
876
913
  (attribute) => attribute !== void 0
877
914
  );
878
915
  }
916
+ function buildAccessibleDefault(path, file, platform) {
917
+ if (platform === "web") return void 0;
918
+ let value;
919
+ if (platform === "ios" || platform === "android") {
920
+ value = core.types.booleanLiteral(platform === "ios");
921
+ } else {
922
+ const accessibleIdentifier = addFileImportHint({
923
+ file,
924
+ nameHint: "getDefaultTextAccessible",
925
+ path,
926
+ importName: "getDefaultTextAccessible",
927
+ moduleName: RUNTIME_MODULE_NAME
928
+ });
929
+ value = core.types.callExpression(core.types.identifier(accessibleIdentifier.name), []);
930
+ }
931
+ return core.types.jsxAttribute(core.types.jsxIdentifier("accessible"), core.types.jsxExpressionContainer(value));
932
+ }
879
933
 
880
934
  const LOG_PREFIX = "[react-native-boost]";
881
935
  const ANSI_RESET = "\x1B[0m";
@@ -974,7 +1028,9 @@ function formatPathLocation(payloadPath) {
974
1028
  }
975
1029
 
976
1030
  const viewBlacklistedProperties = /* @__PURE__ */ new Set([
977
- // TODO: process a11y props at runtime
1031
+ // The `View` wrapper translates these into native props (e.g. `aria-*` → `accessibility*`,
1032
+ // `tabIndex` → `focusable`). The native host does not understand them, so passing them through
1033
+ // would silently drop them. TODO: process these at runtime instead of bailing.
978
1034
  "accessible",
979
1035
  "accessibilityLabel",
980
1036
  "accessibilityState",
@@ -982,37 +1038,29 @@ const viewBlacklistedProperties = /* @__PURE__ */ new Set([
982
1038
  "aria-checked",
983
1039
  "aria-disabled",
984
1040
  "aria-expanded",
1041
+ "aria-hidden",
985
1042
  "aria-label",
1043
+ "aria-labelledby",
1044
+ "aria-live",
986
1045
  "aria-selected",
1046
+ "aria-valuemax",
1047
+ "aria-valuemin",
1048
+ "aria-valuenow",
1049
+ "aria-valuetext",
987
1050
  "id",
988
1051
  "nativeID",
989
- "style"
990
- // TODO: process style at runtime
1052
+ "tabIndex"
991
1053
  ]);
992
1054
  const viewOptimizer = (path, logger, options) => {
993
1055
  if (!isValidJSXComponent(path, "View")) return;
994
1056
  if (!isReactNativeImport(path, "View")) return;
995
- let ancestorClassification;
996
- const getAncestorClassification = () => {
997
- if (!ancestorClassification) {
998
- ancestorClassification = getViewAncestorClassification(path);
999
- }
1000
- return ancestorClassification;
1001
- };
1002
1057
  const forced = isForcedLine(path);
1003
1058
  const overridableChecks = [
1004
1059
  {
1005
1060
  reason: "contains blacklisted props",
1006
1061
  shouldBail: () => hasBlacklistedProperty(path, viewBlacklistedProperties)
1007
1062
  },
1008
- {
1009
- reason: "has Text ancestor",
1010
- shouldBail: () => getAncestorClassification() === "text"
1011
- },
1012
- {
1013
- reason: "has unresolved ancestor and dangerous optimization is disabled",
1014
- shouldBail: () => getAncestorClassification() === "unknown" && (options == null ? void 0 : options.dangerouslyOptimizeViewWithUnknownAncestors) !== true
1015
- }
1063
+ ...ancestorBailoutChecks(path, (options == null ? void 0 : options.dangerouslyOptimizeViewWithUnknownAncestors) === true)
1016
1064
  ];
1017
1065
  if (forced) {
1018
1066
  const overriddenReason = getFirstBailoutReason(overridableChecks);
@@ -1047,6 +1095,7 @@ const viewOptimizer = (path, logger, options) => {
1047
1095
 
1048
1096
  var index = helperPluginUtils.declare((api) => {
1049
1097
  api.assertVersion(7);
1098
+ const platform = api.caller((caller) => caller == null ? void 0 : caller.platform);
1050
1099
  return {
1051
1100
  name: "react-native-boost",
1052
1101
  visitor: {
@@ -1056,7 +1105,7 @@ var index = helperPluginUtils.declare((api) => {
1056
1105
  const options = (_a = pluginState.opts) != null ? _a : {};
1057
1106
  const logger = getOrCreateLogger(pluginState, options);
1058
1107
  if (isIgnoredFile(path, (_b = options.ignores) != null ? _b : [])) return;
1059
- if (((_c = options.optimizations) == null ? void 0 : _c.text) !== false) textOptimizer(path, logger);
1108
+ if (((_c = options.optimizations) == null ? void 0 : _c.text) !== false) textOptimizer(path, logger, options, platform);
1060
1109
  if (((_d = options.optimizations) == null ? void 0 : _d.view) !== false) viewOptimizer(path, logger, options);
1061
1110
  }
1062
1111
  }