react-native-boost 1.0.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.
package/README.md CHANGED
@@ -20,10 +20,22 @@ The documentation is available at [react-native-boost.oss.kuatsu.de](https://rea
20
20
  The app in the `apps/example` directory serves as a benchmark for the performance of the plugin.
21
21
 
22
22
  <div align="center">
23
- <img src="./apps/docs/docs/introduction/img/benchmark-ios.png" width="500" />
23
+ <picture>
24
+ <source media="(prefers-color-scheme: dark)" srcset="./apps/docs/content/docs/information/img/fps-ios.svg" />
25
+ <img alt="React Native Boost — iOS frame rate vs render load" src="./apps/docs/content/docs/information/img/fps-ios-light.svg" width="640" />
26
+ </picture>
24
27
  </div>
25
28
 
26
- More benchmarks are available in the [docs](https://react-native-boost.oss.kuatsu.de/docs/introduction/benchmarks).
29
+ See the [benchmarks page](https://react-native-boost.oss.kuatsu.de/docs/information/benchmarks) for Android results and the full methodology.
30
+
31
+ ## Compatibility
32
+
33
+ | `react-native-boost` | React Native |
34
+ | -------------------- | ---------------- |
35
+ | `0.x` | All versions[^1] |
36
+ | `1.x` | `>=0.83` |
37
+
38
+ [^1]: Starting from React Native `0.80`, `react-native-boost@0` prints import deprecation warnings.
27
39
 
28
40
  ## Installation
29
41
 
@@ -59,11 +71,11 @@ yarn start --clear
59
71
 
60
72
  That's it! No imports in your code, rebuilding, or anything else is required.
61
73
 
62
- Optionally, you can configure the Babel plugin with a few options described in the [documentation](https://react-native-boost.oss.kuatsu.de/docs/babel-plugin/configure).
74
+ Optionally, you can configure the Babel plugin with a few options described in the [documentation](https://react-native-boost.oss.kuatsu.de/docs/configuration/configure).
63
75
 
64
76
  ## How it works
65
77
 
66
- A technical rundown of how the plugin works can be found in the [docs](https://react-native-boost.oss.kuatsu.de/docs/introduction/how-it-works).
78
+ A technical rundown of how the plugin works can be found in the [docs](https://react-native-boost.oss.kuatsu.de/docs/information/how-it-works).
67
79
 
68
80
  ## Contributing
69
81
 
@@ -40,17 +40,23 @@ const isIgnoredFile = (path, ignores) => {
40
40
  }
41
41
  return false;
42
42
  };
43
+ const isForcedLine = (path) => {
44
+ return hasDecoratorComment(path, "@boost-force");
45
+ };
43
46
  const isIgnoredLine = (path) => {
47
+ return hasDecoratorComment(path, "@boost-ignore");
48
+ };
49
+ function hasDecoratorComment(path, decorator) {
44
50
  var _a, _b, _c;
45
- if ((_a = path.node.leadingComments) == null ? void 0 : _a.some((comment) => comment.value.includes("@boost-ignore"))) {
51
+ if ((_a = path.node.leadingComments) == null ? void 0 : _a.some((comment) => comment.value.includes(decorator))) {
46
52
  return true;
47
53
  }
48
54
  const jsxElementPath = path.parentPath;
49
- if ((_b = jsxElementPath.node.leadingComments) == null ? void 0 : _b.some((comment) => comment.value.includes("@boost-ignore"))) {
55
+ if ((_b = jsxElementPath.node.leadingComments) == null ? void 0 : _b.some((comment) => comment.value.includes(decorator))) {
50
56
  return true;
51
57
  }
52
58
  const propertyPath = jsxElementPath.parentPath;
53
- if (propertyPath && propertyPath.isObjectProperty() && ((_c = propertyPath.node.leadingComments) == null ? void 0 : _c.some((comment) => comment.value.includes("@boost-ignore")))) {
59
+ if (propertyPath && propertyPath.isObjectProperty() && ((_c = propertyPath.node.leadingComments) == null ? void 0 : _c.some((comment) => comment.value.includes(decorator)))) {
54
60
  return true;
55
61
  }
56
62
  if (!jsxElementPath.parentPath) return false;
@@ -71,18 +77,18 @@ const isIgnoredLine = (path) => {
71
77
  ...expression.node.trailingComments || [],
72
78
  ...expression.node.innerComments || []
73
79
  ].map((comment) => comment.value.trim());
74
- if (comments.some((comment) => comment.includes("@boost-ignore"))) {
80
+ if (comments.some((comment) => comment.includes(decorator))) {
75
81
  return true;
76
82
  }
77
83
  }
78
84
  }
79
- if (sibling.node.leadingComments && sibling.node.leadingComments.some((comment) => comment.value.includes("@boost-ignore"))) {
85
+ if (sibling.node.leadingComments && sibling.node.leadingComments.some((comment) => comment.value.includes(decorator))) {
80
86
  return true;
81
87
  }
82
88
  break;
83
89
  }
84
90
  return false;
85
- };
91
+ }
86
92
  const isValidJSXComponent = (path, componentName) => {
87
93
  if (!types.isJSXIdentifier(path.node.name)) return false;
88
94
  const parent = path.parent;
@@ -119,10 +125,7 @@ const isReactNativeImport = (path, expectedImportedName) => {
119
125
  }
120
126
  return false;
121
127
  };
122
- const getViewAncestorClassification = (path) => {
123
- return classifyViewAncestors(path);
124
- };
125
- function classifyViewAncestors(path) {
128
+ const getAncestorClassification = (path) => {
126
129
  const context = {
127
130
  componentCache: /* @__PURE__ */ new WeakMap(),
128
131
  componentInProgress: /* @__PURE__ */ new WeakSet(),
@@ -139,7 +142,21 @@ function classifyViewAncestors(path) {
139
142
  ancestorPath = ancestorPath.parentPath;
140
143
  }
141
144
  return classification;
142
- }
145
+ };
146
+ const ancestorBailoutChecks = (path, dangerousOptimizationEnabled) => {
147
+ let classification;
148
+ const classify = () => classification != null ? classification : classification = getAncestorClassification(path);
149
+ return [
150
+ {
151
+ reason: "has Text ancestor",
152
+ shouldBail: () => classify() === "text"
153
+ },
154
+ {
155
+ reason: "has unresolved ancestor and dangerous optimization is disabled",
156
+ shouldBail: () => classify() === "unknown" && !dangerousOptimizationEnabled
157
+ }
158
+ ];
159
+ };
143
160
  function classifyJSXElementAsAncestor(path, context) {
144
161
  const openingElementName = path.node.openingElement.name;
145
162
  if (types.isJSXIdentifier(openingElementName)) {
@@ -667,18 +684,37 @@ function extractSelectableAndUpdateStyle(styleExpr) {
667
684
  }
668
685
  return void 0;
669
686
  }
670
- const isStringNode = (path, child) => {
671
- if (types.isJSXText(child) || types.isStringLiteral(child)) return true;
672
- if (types.isJSXExpressionContainer(child)) {
673
- const expression = child.expression;
674
- if (types.isIdentifier(expression)) {
675
- const binding = path.scope.getBinding(expression.name);
676
- if (binding && binding.path.node && types.isVariableDeclarator(binding.path.node)) {
677
- return !!binding.path.node.init && types.isStringLiteral(binding.path.node.init);
678
- }
679
- return false;
687
+ const isPrimitiveExpression = (path, expression, resolved = /* @__PURE__ */ new Set()) => {
688
+ if (types.isStringLiteral(expression) || types.isNumericLiteral(expression)) return true;
689
+ if (types.isTemplateLiteral(expression)) return true;
690
+ if (types.isBinaryExpression(expression)) {
691
+ const { left, right } = expression;
692
+ return types.isExpression(left) && isPrimitiveExpression(path, left, resolved) && isPrimitiveExpression(path, right, resolved);
693
+ }
694
+ if (types.isConditionalExpression(expression)) {
695
+ return isPrimitiveExpression(path, expression.consequent, resolved) && isPrimitiveExpression(path, expression.alternate, resolved);
696
+ }
697
+ if (types.isLogicalExpression(expression)) {
698
+ return isPrimitiveExpression(path, expression.left, resolved) && isPrimitiveExpression(path, expression.right, resolved);
699
+ }
700
+ if (types.isIdentifier(expression)) {
701
+ if (resolved.has(expression.name)) return false;
702
+ const binding = path.scope.getBinding(expression.name);
703
+ if (binding && binding.constant && binding.path.node && types.isVariableDeclarator(binding.path.node)) {
704
+ const init = binding.path.node.init;
705
+ return !!init && types.isExpression(init) && isPrimitiveExpression(path, init, new Set(resolved).add(expression.name));
680
706
  }
681
- if (types.isStringLiteral(expression)) return true;
707
+ return false;
708
+ }
709
+ return false;
710
+ };
711
+ const isPrimitiveChild = (path, child) => {
712
+ if (types.isJSXText(child)) return true;
713
+ if (types.isStringLiteral(child)) return true;
714
+ if (types.isJSXExpressionContainer(child)) {
715
+ const { expression } = child;
716
+ if (types.isJSXEmptyExpression(expression)) return false;
717
+ return isPrimitiveExpression(path, expression);
682
718
  }
683
719
  return false;
684
720
  };
@@ -717,6 +753,10 @@ const replaceWithNativeComponent = (path, parent, file, nativeComponentName) =>
717
753
  };
718
754
 
719
755
  const textBlacklistedProperties = /* @__PURE__ */ new Set([
756
+ // The `Text` wrapper translates `aria-hidden` into `accessibilityElementsHidden` /
757
+ // `importantForAccessibility`, which `processAccessibilityProps` does not yet handle. Passing it
758
+ // through would drop it, so bail. TODO: handle this in the runtime helper instead.
759
+ "aria-hidden",
720
760
  "id",
721
761
  "nativeID",
722
762
  "onLongPress",
@@ -734,18 +774,14 @@ const textBlacklistedProperties = /* @__PURE__ */ new Set([
734
774
  "selectionColor"
735
775
  // TODO: we can use react-native's internal `processColor` to process this at runtime
736
776
  ]);
737
- const textOptimizer = (path, logger) => {
777
+ const NORMALIZED_PROPERTIES = /* @__PURE__ */ new Set([...ACCESSIBILITY_PROPERTIES, "disabled"]);
778
+ const isNormalizedProperty = (attribute) => types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name) && NORMALIZED_PROPERTIES.has(attribute.name.name);
779
+ const textOptimizer = (path, logger, options, platform) => {
738
780
  if (!isValidJSXComponent(path, "Text")) return;
781
+ if (!isReactNativeImport(path, "Text")) return;
739
782
  const parent = path.parent;
740
- const skipReason = getFirstBailoutReason([
741
- {
742
- reason: "line is marked with @boost-ignore",
743
- shouldBail: () => isIgnoredLine(path)
744
- },
745
- {
746
- reason: "Text is not imported from react-native",
747
- shouldBail: () => !isReactNativeImport(path, "Text")
748
- },
783
+ const forced = isForcedLine(path);
784
+ const overridableChecks = [
749
785
  {
750
786
  reason: "contains blacklisted props",
751
787
  shouldBail: () => hasBlacklistedProperty(path, textBlacklistedProperties)
@@ -754,18 +790,31 @@ const textOptimizer = (path, logger) => {
754
790
  reason: "is a direct child of expo-router Link with asChild",
755
791
  shouldBail: () => hasExpoRouterLinkParentWithAsChild(path)
756
792
  },
793
+ // The local children check runs before the ancestor checks because it is cheap and prunes the
794
+ // common nested-element `Text` before the unbounded ancestor walk those checks trigger.
757
795
  {
758
- reason: "contains non-string children",
796
+ reason: "contains non-primitive children",
759
797
  shouldBail: () => hasInvalidChildren(path, parent)
798
+ },
799
+ ...ancestorBailoutChecks(path, (options == null ? void 0 : options.dangerouslyOptimizeTextWithUnknownAncestors) === true)
800
+ ];
801
+ if (forced) {
802
+ const overriddenReason = getFirstBailoutReason(overridableChecks);
803
+ if (overriddenReason) {
804
+ logger.forced({ component: "Text", path, reason: overriddenReason });
805
+ }
806
+ } else {
807
+ const skipReason = getFirstBailoutReason([
808
+ {
809
+ reason: "line is marked with @boost-ignore",
810
+ shouldBail: () => isIgnoredLine(path)
811
+ },
812
+ ...overridableChecks
813
+ ]);
814
+ if (skipReason) {
815
+ logger.skipped({ component: "Text", path, reason: skipReason });
816
+ return;
760
817
  }
761
- ]);
762
- if (skipReason) {
763
- logger.skipped({
764
- component: "Text",
765
- path,
766
- reason: skipReason
767
- });
768
- return;
769
818
  }
770
819
  const hub = path.hub;
771
820
  const file = typeof hub === "object" && hub !== null && "file" in hub ? hub.file : void 0;
@@ -779,18 +828,18 @@ const textOptimizer = (path, logger) => {
779
828
  fixNegativeNumberOfLines({ path, logger });
780
829
  addDefaultProperty(path, "allowFontScaling", types.booleanLiteral(true));
781
830
  addDefaultProperty(path, "ellipsizeMode", types.stringLiteral("tail"));
782
- processProps(path, file);
831
+ processProps(path, file, platform);
783
832
  replaceWithNativeComponent(path, parent, file, "NativeText");
784
833
  };
785
834
  function hasInvalidChildren(path, parent) {
786
835
  for (const attribute of path.node.attributes) {
787
836
  if (types.isJSXSpreadAttribute(attribute)) continue;
788
- if (types.isJSXIdentifier(attribute.name) && attribute.value && // For a "children" attribute, optimization is allowed only if it is a string
789
- attribute.name.name === "children" && !isStringNode(path, attribute.value)) {
837
+ if (types.isJSXIdentifier(attribute.name) && attribute.value && // For a "children" attribute, optimization is allowed only if it is a provable primitive
838
+ attribute.name.name === "children" && !isPrimitiveChild(path, attribute.value)) {
790
839
  return true;
791
840
  }
792
841
  }
793
- return !parent.children.every((child) => isStringNode(path, child));
842
+ return !parent.children.every((child) => isPrimitiveChild(path, child));
794
843
  }
795
844
  function fixNegativeNumberOfLines({ path, logger }) {
796
845
  for (const attribute of path.node.attributes) {
@@ -812,16 +861,15 @@ function fixNegativeNumberOfLines({ path, logger }) {
812
861
  }
813
862
  }
814
863
  }
815
- function processProps(path, file) {
864
+ function processProps(path, file, platform) {
816
865
  const currentAttributes = [...path.node.attributes];
817
866
  const { styleExpr, styleAttribute } = extractStyleAttribute(currentAttributes);
818
- const hasA11y = hasAccessibilityProperty(path, currentAttributes);
867
+ const shouldNormalize = hasAccessibilityProperty(path, currentAttributes) || currentAttributes.some(
868
+ (attribute) => types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name, { name: "disabled" })
869
+ );
819
870
  const spreadAttributes = [];
820
- if (hasA11y) {
821
- const accessibilityAttributes = currentAttributes.filter((attribute) => {
822
- if (!types.isJSXAttribute(attribute)) return false;
823
- return types.isJSXIdentifier(attribute.name) && ACCESSIBILITY_PROPERTIES.has(attribute.name.name);
824
- });
871
+ if (shouldNormalize) {
872
+ const normalizedAttributes = currentAttributes.filter((attribute) => isNormalizedProperty(attribute));
825
873
  const normalizeIdentifier = addFileImportHint({
826
874
  file,
827
875
  nameHint: "processAccessibilityProps",
@@ -829,7 +877,7 @@ function processProps(path, file) {
829
877
  importName: "processAccessibilityProps",
830
878
  moduleName: RUNTIME_MODULE_NAME
831
879
  });
832
- const accessibilityObject = buildPropertiesFromAttributes(accessibilityAttributes);
880
+ const accessibilityObject = buildPropertiesFromAttributes(normalizedAttributes);
833
881
  const accessibilityExpr = types.callExpression(types.identifier(normalizeIdentifier.name), [accessibilityObject]);
834
882
  spreadAttributes.push(types.jsxSpreadAttribute(accessibilityExpr));
835
883
  }
@@ -855,26 +903,45 @@ function processProps(path, file) {
855
903
  const remainingAttributes = [];
856
904
  for (const attribute of currentAttributes) {
857
905
  if (styleAttribute && attribute === styleAttribute) continue;
858
- if (hasA11y && types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name) && ACCESSIBILITY_PROPERTIES.has(attribute.name.name)) {
859
- continue;
860
- }
906
+ if (shouldNormalize && isNormalizedProperty(attribute)) continue;
861
907
  remainingAttributes.push(attribute);
862
908
  }
863
- path.node.attributes = [...spreadAttributes, selectableAttribute, ...remainingAttributes].filter(
909
+ const accessibleAttribute = shouldNormalize ? void 0 : buildAccessibleDefault(path, file, platform);
910
+ path.node.attributes = [...spreadAttributes, selectableAttribute, ...remainingAttributes, accessibleAttribute].filter(
864
911
  (attribute) => attribute !== void 0
865
912
  );
866
913
  }
914
+ function buildAccessibleDefault(path, file, platform) {
915
+ if (platform === "web") return void 0;
916
+ let value;
917
+ if (platform === "ios" || platform === "android") {
918
+ value = types.booleanLiteral(platform === "ios");
919
+ } else {
920
+ const accessibleIdentifier = addFileImportHint({
921
+ file,
922
+ nameHint: "getDefaultTextAccessible",
923
+ path,
924
+ importName: "getDefaultTextAccessible",
925
+ moduleName: RUNTIME_MODULE_NAME
926
+ });
927
+ value = types.callExpression(types.identifier(accessibleIdentifier.name), []);
928
+ }
929
+ return types.jsxAttribute(types.jsxIdentifier("accessible"), types.jsxExpressionContainer(value));
930
+ }
867
931
 
868
932
  const LOG_PREFIX = "[react-native-boost]";
869
933
  const ANSI_RESET = "\x1B[0m";
870
934
  const ANSI_GREEN = "\x1B[32m";
871
935
  const ANSI_YELLOW = "\x1B[33m";
872
936
  const ANSI_MAGENTA = "\x1B[35m";
937
+ const ANSI_RED = "\x1B[31m";
873
938
  const noopLogger = {
874
939
  optimized() {
875
940
  },
876
941
  skipped() {
877
942
  },
943
+ forced() {
944
+ },
878
945
  warning() {
879
946
  }
880
947
  };
@@ -888,6 +955,12 @@ const createLogger = ({ verbose, silent }) => {
888
955
  if (!verbose) return;
889
956
  writeLog("skipped", `Skipped ${payload.component} in ${formatPathLocation(payload.path)} (${payload.reason})`);
890
957
  },
958
+ forced(payload) {
959
+ writeLog(
960
+ "forced",
961
+ `Force-optimized ${payload.component} in ${formatPathLocation(payload.path)} (skipped bailout: ${payload.reason})`
962
+ );
963
+ },
891
964
  warning(payload) {
892
965
  const context = formatWarningContext(payload);
893
966
  const message = context.length > 0 ? `${context}: ${payload.message}` : payload.message;
@@ -916,6 +989,9 @@ function formatLevel(level) {
916
989
  if (level === "skipped") {
917
990
  return colorize("[skipped]", ANSI_YELLOW);
918
991
  }
992
+ if (level === "forced") {
993
+ return colorize("[forced]", ANSI_RED);
994
+ }
919
995
  return colorize("[warning]", ANSI_MAGENTA);
920
996
  }
921
997
  function colorize(value, colorCode) {
@@ -950,7 +1026,9 @@ function formatPathLocation(payloadPath) {
950
1026
  }
951
1027
 
952
1028
  const viewBlacklistedProperties = /* @__PURE__ */ new Set([
953
- // TODO: process a11y props at runtime
1029
+ // The `View` wrapper translates these into native props (e.g. `aria-*` → `accessibility*`,
1030
+ // `tabIndex` → `focusable`). The native host does not understand them, so passing them through
1031
+ // would silently drop them. TODO: process these at runtime instead of bailing.
954
1032
  "accessible",
955
1033
  "accessibilityLabel",
956
1034
  "accessibilityState",
@@ -958,51 +1036,47 @@ const viewBlacklistedProperties = /* @__PURE__ */ new Set([
958
1036
  "aria-checked",
959
1037
  "aria-disabled",
960
1038
  "aria-expanded",
1039
+ "aria-hidden",
961
1040
  "aria-label",
1041
+ "aria-labelledby",
1042
+ "aria-live",
962
1043
  "aria-selected",
1044
+ "aria-valuemax",
1045
+ "aria-valuemin",
1046
+ "aria-valuenow",
1047
+ "aria-valuetext",
963
1048
  "id",
964
1049
  "nativeID",
965
- "style"
966
- // TODO: process style at runtime
1050
+ "tabIndex"
967
1051
  ]);
968
1052
  const viewOptimizer = (path, logger, options) => {
969
1053
  if (!isValidJSXComponent(path, "View")) return;
970
- let ancestorClassification;
971
- const getAncestorClassification = () => {
972
- if (!ancestorClassification) {
973
- ancestorClassification = getViewAncestorClassification(path);
974
- }
975
- return ancestorClassification;
976
- };
977
- const skipReason = getFirstBailoutReason([
978
- {
979
- reason: "line is marked with @boost-ignore",
980
- shouldBail: () => isIgnoredLine(path)
981
- },
982
- {
983
- reason: "View is not imported from react-native",
984
- shouldBail: () => !isReactNativeImport(path, "View")
985
- },
1054
+ if (!isReactNativeImport(path, "View")) return;
1055
+ const forced = isForcedLine(path);
1056
+ const overridableChecks = [
986
1057
  {
987
1058
  reason: "contains blacklisted props",
988
1059
  shouldBail: () => hasBlacklistedProperty(path, viewBlacklistedProperties)
989
1060
  },
990
- {
991
- reason: "has Text ancestor",
992
- shouldBail: () => getAncestorClassification() === "text"
993
- },
994
- {
995
- reason: "has unresolved ancestor and dangerous optimization is disabled",
996
- shouldBail: () => getAncestorClassification() === "unknown" && (options == null ? void 0 : options.dangerouslyOptimizeViewWithUnknownAncestors) !== true
1061
+ ...ancestorBailoutChecks(path, (options == null ? void 0 : options.dangerouslyOptimizeViewWithUnknownAncestors) === true)
1062
+ ];
1063
+ if (forced) {
1064
+ const overriddenReason = getFirstBailoutReason(overridableChecks);
1065
+ if (overriddenReason) {
1066
+ logger.forced({ component: "View", path, reason: overriddenReason });
1067
+ }
1068
+ } else {
1069
+ const skipReason = getFirstBailoutReason([
1070
+ {
1071
+ reason: "line is marked with @boost-ignore",
1072
+ shouldBail: () => isIgnoredLine(path)
1073
+ },
1074
+ ...overridableChecks
1075
+ ]);
1076
+ if (skipReason) {
1077
+ logger.skipped({ component: "View", path, reason: skipReason });
1078
+ return;
997
1079
  }
998
- ]);
999
- if (skipReason) {
1000
- logger.skipped({
1001
- component: "View",
1002
- path,
1003
- reason: skipReason
1004
- });
1005
- return;
1006
1080
  }
1007
1081
  const hub = path.hub;
1008
1082
  const file = typeof hub === "object" && hub !== null && "file" in hub ? hub.file : void 0;
@@ -1019,6 +1093,7 @@ const viewOptimizer = (path, logger, options) => {
1019
1093
 
1020
1094
  var index = declare((api) => {
1021
1095
  api.assertVersion(7);
1096
+ const platform = api.caller((caller) => caller == null ? void 0 : caller.platform);
1022
1097
  return {
1023
1098
  name: "react-native-boost",
1024
1099
  visitor: {
@@ -1028,7 +1103,7 @@ var index = declare((api) => {
1028
1103
  const options = (_a = pluginState.opts) != null ? _a : {};
1029
1104
  const logger = getOrCreateLogger(pluginState, options);
1030
1105
  if (isIgnoredFile(path, (_b = options.ignores) != null ? _b : [])) return;
1031
- if (((_c = options.optimizations) == null ? void 0 : _c.text) !== false) textOptimizer(path, logger);
1106
+ if (((_c = options.optimizations) == null ? void 0 : _c.text) !== false) textOptimizer(path, logger, options, platform);
1032
1107
  if (((_d = options.optimizations) == null ? void 0 : _d.view) !== false) viewOptimizer(path, logger, options);
1033
1108
  }
1034
1109
  }