babel-preset-expo 10.0.2 → 11.0.1

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
@@ -42,6 +42,24 @@ If the `bundler` is not defined, it will default to checking if a `babel-loader`
42
42
 
43
43
  ## Options
44
44
 
45
+ ### `minifyTypeofWindow`
46
+
47
+ Set `minifyTypeofWindow: false` to preserve the `typeof window` check in your code, e.g. `if (typeof window === 'undefined')` -> `if (true)` in servers. This is useful when you're using libraries that mock the window object on native or in the server.
48
+
49
+ ```js
50
+ [
51
+ 'babel-preset-expo',
52
+ {
53
+ // If your native app doesn't polyfill `window` then setting this to `false` can reduce bundle size.
54
+ native: {
55
+ minifyTypeofWindow: true,
56
+ },
57
+ },
58
+ ];
59
+ ```
60
+
61
+ Defaults to `false` for server environments and web, `true` for native platforms to support legacy browser polyfills.
62
+
45
63
  ### `reanimated`
46
64
 
47
65
  `boolean`, defaults to `true`. Set `reanimated: false` to disable adding the `react-native-reanimated/plugin` when `react-native-reanimated` is installed.
@@ -0,0 +1 @@
1
+ export declare function reactClientReferencesPlugin(): babel.PluginObj;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.reactClientReferencesPlugin = void 0;
7
+ /**
8
+ * Copyright © 2024 650 Industries.
9
+ */
10
+ const core_1 = require("@babel/core");
11
+ const url_1 = __importDefault(require("url"));
12
+ function reactClientReferencesPlugin() {
13
+ return {
14
+ name: 'expo-client-references',
15
+ visitor: {
16
+ Program(path, state) {
17
+ const isUseClient = path.node.directives.some((directive) => directive.value.value === 'use client');
18
+ // TODO: use server can be added to scopes inside of the file. https://github.com/facebook/react/blob/29fbf6f62625c4262035f931681c7b7822ca9843/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js#L55
19
+ const isUseServer = path.node.directives.some((directive) => directive.value.value === 'use server');
20
+ if (isUseClient && isUseServer) {
21
+ throw path.buildCodeFrameError("It's not possible to have both `use client` and `use server` directives in the same file.");
22
+ }
23
+ const filePath = state.file.opts.filename;
24
+ if (!filePath) {
25
+ // This can happen in tests or systems that use Babel standalone.
26
+ throw new Error('[Babel] Expected a filename to be set in the state');
27
+ }
28
+ const outputKey = url_1.default.pathToFileURL(filePath).href;
29
+ // File starts with "use client" directive.
30
+ if (!isUseClient && !isUseServer) {
31
+ // Do nothing for code that isn't marked as a client component.
32
+ return;
33
+ }
34
+ // Clear the body
35
+ if (isUseClient) {
36
+ path.node.body = [];
37
+ path.node.directives = [];
38
+ path.pushContainer('body', core_1.template.ast `module.exports = require("react-server-dom-webpack/server").createClientModuleProxy(${JSON.stringify(outputKey)});`);
39
+ }
40
+ else {
41
+ path.pushContainer('body', core_1.template.ast `
42
+ ;(() => {
43
+ if (typeof module.exports === 'function') {
44
+ require('react-server-dom-webpack/server').registerServerReference(module.exports, ${JSON.stringify(outputKey)}, null);
45
+ } else {
46
+ for (const key in module.exports) {
47
+ if (typeof module.exports[key] === 'function') {
48
+ require('react-server-dom-webpack/server').registerServerReference(module.exports[key], ${JSON.stringify(outputKey)}, key);
49
+ }
50
+ }
51
+ }
52
+ })()`);
53
+ }
54
+ },
55
+ },
56
+ };
57
+ }
58
+ exports.reactClientReferencesPlugin = reactClientReferencesPlugin;
package/build/common.d.ts CHANGED
@@ -3,6 +3,8 @@ export declare function hasModule(name: string): boolean;
3
3
  export declare function getBundler(caller: any): any;
4
4
  export declare function getPlatform(caller: any): any;
5
5
  export declare function getPossibleProjectRoot(caller: any): any;
6
+ /** If bundling for a react-server target. */
7
+ export declare function getIsReactServer(caller: any): boolean;
6
8
  export declare function getIsDev(caller: any): any;
7
9
  export declare function getIsFastRefreshEnabled(caller: any): any;
8
10
  export declare function getIsProd(caller: any): boolean;
package/build/common.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getAsyncRoutes = exports.getInlineEnvVarsEnabled = exports.getExpoRouterAbsoluteAppRoot = exports.getIsServer = exports.getBaseUrl = exports.getIsNodeModule = exports.getIsProd = exports.getIsFastRefreshEnabled = exports.getIsDev = exports.getPossibleProjectRoot = exports.getPlatform = exports.getBundler = exports.hasModule = void 0;
6
+ exports.getAsyncRoutes = exports.getInlineEnvVarsEnabled = exports.getExpoRouterAbsoluteAppRoot = exports.getIsServer = exports.getBaseUrl = exports.getIsNodeModule = exports.getIsProd = exports.getIsFastRefreshEnabled = exports.getIsDev = exports.getIsReactServer = exports.getPossibleProjectRoot = exports.getPlatform = exports.getBundler = exports.hasModule = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  function hasModule(name) {
9
9
  try {
@@ -56,6 +56,11 @@ function getPossibleProjectRoot(caller) {
56
56
  return process.env.EXPO_PROJECT_ROOT;
57
57
  }
58
58
  exports.getPossibleProjectRoot = getPossibleProjectRoot;
59
+ /** If bundling for a react-server target. */
60
+ function getIsReactServer(caller) {
61
+ return caller?.isReactServer ?? false;
62
+ }
63
+ exports.getIsReactServer = getIsReactServer;
59
64
  function getIsDev(caller) {
60
65
  if (caller?.isDev != null)
61
66
  return caller.isDev;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copyright © 2024 650 Industries.
3
+ * Copyright (c) 2016 Formidable
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ import * as t from '@babel/types';
9
+ declare const plugin: ({ types }: {
10
+ types: typeof t;
11
+ }) => babel.PluginObj;
12
+ export default plugin;
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright © 2024 650 Industries.
4
+ * Copyright (c) 2016 Formidable
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || function (mod) {
26
+ if (mod && mod.__esModule) return mod;
27
+ var result = {};
28
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
29
+ __setModuleDefault(result, mod);
30
+ return result;
31
+ };
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ const t = __importStar(require("@babel/types"));
34
+ /**
35
+ * Replace a node with a given value. If the replacement results in a BinaryExpression, it will be
36
+ * evaluated. For example, if the result of the replacement is `var x = "production" === "production"`
37
+ * The evaluation will make a second replacement resulting in `var x = true`
38
+ */
39
+ function replaceAndEvaluateNode(nodePath, replacement) {
40
+ nodePath.replaceWith(t.valueToNode(replacement));
41
+ if (nodePath.parentPath && nodePath.parentPath.isBinaryExpression()) {
42
+ const result = nodePath.parentPath.evaluate();
43
+ if (result.confident) {
44
+ nodePath.parentPath.replaceWith(t.valueToNode(result.value));
45
+ }
46
+ }
47
+ }
48
+ /**
49
+ * Checks if the given identifier is an ES module import
50
+ * @param {babelNode} identifierNodePath The node to check
51
+ * @return {boolean} Indicates if the provided node is an import specifier or references one
52
+ */
53
+ const isImportIdentifier = (identifierNodePath) => {
54
+ if (identifierNodePath.container &&
55
+ !Array.isArray(identifierNodePath.container) &&
56
+ 'type' in identifierNodePath.container) {
57
+ return (identifierNodePath.container.type === 'ImportDefaultSpecifier' ||
58
+ identifierNodePath.container.type === 'ImportSpecifier');
59
+ }
60
+ return false;
61
+ };
62
+ const memberExpressionComparator = (nodePath, value) => nodePath.matchesPattern(value);
63
+ const identifierComparator = (nodePath, value) => 'name' in nodePath.node && nodePath.node.name === value;
64
+ const unaryExpressionComparator = (nodePath, value) => {
65
+ if ('argument' in nodePath.node && nodePath.node.argument && 'name' in nodePath.node?.argument) {
66
+ return nodePath.node.argument.name === value;
67
+ }
68
+ return false;
69
+ };
70
+ const isLeftHandSideOfAssignmentExpression = (node, parent) => t.isAssignmentExpression(parent) && parent.left === node;
71
+ const TYPEOF_PREFIX = 'typeof ';
72
+ const plugin = ({ types }) => {
73
+ const processNode = (replacements, nodePath, comparator) => {
74
+ const replacementKey = Object.keys(replacements).find((value) => comparator(nodePath, value));
75
+ if (typeof replacementKey === 'string' &&
76
+ replacements != null &&
77
+ replacementKey in replacements) {
78
+ replaceAndEvaluateNode(nodePath, replacements[replacementKey]);
79
+ }
80
+ };
81
+ return {
82
+ name: 'expo-define-globals',
83
+ visitor: {
84
+ // process.env.NODE_ENV;
85
+ MemberExpression(nodePath, state) {
86
+ if (
87
+ // Prevent rewriting if the member expression is on the left-hand side of an assignment
88
+ isLeftHandSideOfAssignmentExpression(nodePath.node, nodePath.parent)) {
89
+ return;
90
+ }
91
+ const replacements = state.opts;
92
+ assertOptions(replacements);
93
+ processNode(replacements, nodePath, memberExpressionComparator);
94
+ },
95
+ // const x = { version: VERSION };
96
+ Identifier(nodePath, state) {
97
+ const binding = nodePath.scope?.getBinding(nodePath.node.name);
98
+ if (binding ||
99
+ // Don't transform import identifiers. This is meant to mimic webpack's
100
+ // DefinePlugin behavior.
101
+ isImportIdentifier(nodePath) ||
102
+ // Do not transform Object keys / properties unless they are computed like {[key]: value}
103
+ (nodePath.key === 'key' &&
104
+ nodePath.parent &&
105
+ 'computed' in nodePath.parent &&
106
+ nodePath.parent.computed === false) ||
107
+ (nodePath.key === 'property' &&
108
+ nodePath.parent &&
109
+ 'computed' in nodePath.parent &&
110
+ nodePath.parent.computed === false)) {
111
+ return;
112
+ }
113
+ const replacements = state.opts;
114
+ assertOptions(replacements);
115
+ processNode(replacements, nodePath, identifierComparator);
116
+ },
117
+ // typeof window
118
+ UnaryExpression(nodePath, state) {
119
+ if (nodePath.node.operator !== 'typeof') {
120
+ return;
121
+ }
122
+ const replacements = state.opts;
123
+ assertOptions(replacements);
124
+ const typeofValues = {};
125
+ Object.keys(replacements).forEach((key) => {
126
+ if (key.substring(0, TYPEOF_PREFIX.length) === TYPEOF_PREFIX) {
127
+ typeofValues[key.substring(TYPEOF_PREFIX.length)] = replacements[key];
128
+ }
129
+ });
130
+ processNode(typeofValues, nodePath, unaryExpressionComparator);
131
+ },
132
+ },
133
+ };
134
+ };
135
+ function assertOptions(opts) {
136
+ if (opts == null || typeof opts !== 'object') {
137
+ throw new Error('define plugin expects an object as options');
138
+ }
139
+ }
140
+ exports.default = plugin;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Copyright © 2024 650 Industries.
3
+ */
4
+ import { ConfigAPI, types } from '@babel/core';
5
+ /** Prevent importing certain known imports in given environments. This is for sanity to ensure a module never accidentally gets imported unexpectedly. */
6
+ export declare function environmentRestrictedImportsPlugin(api: ConfigAPI & {
7
+ types: typeof types;
8
+ }): babel.PluginObj;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.environmentRestrictedImportsPlugin = void 0;
4
+ const common_1 = require("./common");
5
+ const FORBIDDEN_CLIENT_IMPORTS = ['server-only'];
6
+ const FORBIDDEN_REACT_SERVER_IMPORTS = ['client-only'];
7
+ /** Prevent importing certain known imports in given environments. This is for sanity to ensure a module never accidentally gets imported unexpectedly. */
8
+ function environmentRestrictedImportsPlugin(api) {
9
+ const { types: t } = api;
10
+ const isReactServer = api.caller(common_1.getIsReactServer);
11
+ const forbiddenPackages = isReactServer
12
+ ? FORBIDDEN_REACT_SERVER_IMPORTS
13
+ : FORBIDDEN_CLIENT_IMPORTS;
14
+ function checkSource(source, path) {
15
+ forbiddenPackages.forEach((forbiddenImport) => {
16
+ if (source === forbiddenImport) {
17
+ if (isReactServer) {
18
+ throw path.buildCodeFrameError(`Importing '${forbiddenImport}' module is not allowed in a React server bundle. Add the "use client" directive to this file or one of the parent modules to allow importing this module.`);
19
+ }
20
+ else {
21
+ throw path.buildCodeFrameError(`Importing '${forbiddenImport}' module is not allowed in a client component.`);
22
+ }
23
+ }
24
+ });
25
+ }
26
+ return {
27
+ name: 'expo-environment-restricted-imports-plugin',
28
+ visitor: {
29
+ ImportDeclaration(path) {
30
+ checkSource(path.node.source.value, path);
31
+ },
32
+ ExportAllDeclaration(path) {
33
+ if (path.node.source) {
34
+ checkSource(path.node.source.value, path);
35
+ }
36
+ },
37
+ ExportNamedDeclaration(path) {
38
+ if (path.node.source) {
39
+ checkSource(path.node.source.value, path);
40
+ }
41
+ },
42
+ CallExpression(path) {
43
+ if ((('name' in path.node.callee && path.node.callee.name === 'require') ||
44
+ (t.isMemberExpression(path.node.callee) &&
45
+ 'name' in path.node.callee.property &&
46
+ ['resolveWeak', 'importAll', 'importDefault'].includes(path.node.callee.property.name))) &&
47
+ path.node.arguments.length > 0 &&
48
+ t.isStringLiteral(path.node.arguments[0])) {
49
+ checkSource(path.node.arguments[0].value, path);
50
+ }
51
+ // Handle dynamic import() syntax
52
+ else if (path.node.callee.type === 'Import' &&
53
+ path.node.arguments.length > 0 &&
54
+ t.isStringLiteral(path.node.arguments[0])) {
55
+ checkSource(path.node.arguments[0].value, path);
56
+ }
57
+ },
58
+ },
59
+ };
60
+ }
61
+ exports.environmentRestrictedImportsPlugin = environmentRestrictedImportsPlugin;
package/build/index.d.ts CHANGED
@@ -13,6 +13,8 @@ type BabelPresetExpoPlatformOptions = {
13
13
  disableFlowStripTypesTransform?: boolean;
14
14
  enableBabelRuntime?: boolean;
15
15
  unstable_transformProfile?: 'default' | 'hermes-stable' | 'hermes-canary';
16
+ /** Enable `typeof window` runtime checks. The default behavior is to minify `typeof window` on web clients to `"object"` and `"undefined"` on servers. */
17
+ minifyTypeofWindow?: boolean;
16
18
  };
17
19
  export type BabelPresetExpoOptions = BabelPresetExpoPlatformOptions & {
18
20
  /** Web-specific settings. */
package/build/index.js CHANGED
@@ -1,10 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const client_module_proxy_plugin_1 = require("./client-module-proxy-plugin");
3
4
  const common_1 = require("./common");
5
+ const environment_restricted_imports_1 = require("./environment-restricted-imports");
4
6
  const expo_inline_manifest_plugin_1 = require("./expo-inline-manifest-plugin");
5
7
  const expo_router_plugin_1 = require("./expo-router-plugin");
6
8
  const inline_env_vars_1 = require("./inline-env-vars");
7
9
  const lazyImports_1 = require("./lazyImports");
10
+ const restricted_react_api_plugin_1 = require("./restricted-react-api-plugin");
8
11
  function getOptions(options, platform) {
9
12
  const tag = platform === 'web' ? 'web' : 'native';
10
13
  return {
@@ -18,9 +21,12 @@ function babelPresetExpo(api, options = {}) {
18
21
  let platform = api.caller((caller) => caller?.platform);
19
22
  const engine = api.caller((caller) => caller?.engine) ?? 'default';
20
23
  const isDev = api.caller(common_1.getIsDev);
24
+ const isServer = api.caller(common_1.getIsServer);
25
+ const isReactServer = api.caller(common_1.getIsReactServer);
21
26
  const isFastRefreshEnabled = api.caller(common_1.getIsFastRefreshEnabled);
22
27
  const baseUrl = api.caller(common_1.getBaseUrl);
23
28
  const supportsStaticESM = api.caller((caller) => caller?.supportsStaticESM);
29
+ const isServerEnv = isServer || isReactServer;
24
30
  // Unlike `isDev`, this will be `true` when the bundler is explicitly set to `production`,
25
31
  // i.e. `false` when testing, development, or used with a bundler that doesn't specify the correct inputs.
26
32
  const isProduction = api.caller(common_1.getIsProd);
@@ -31,6 +37,9 @@ function babelPresetExpo(api, options = {}) {
31
37
  platform = 'web';
32
38
  }
33
39
  const platformOptions = getOptions(options, platform);
40
+ if (platformOptions.useTransformReactJSXExperimental != null) {
41
+ throw new Error(`babel-preset-expo: The option 'useTransformReactJSXExperimental' has been removed in favor of { jsxRuntime: 'classic' }.`);
42
+ }
34
43
  if (platformOptions.disableImportExportTransform == null) {
35
44
  if (platform === 'web') {
36
45
  // Only disable import/export transform when Webpack is used because
@@ -52,27 +61,39 @@ function babelPresetExpo(api, options = {}) {
52
61
  // `@react-native/babel-preset` configures this plugin with `{ loose: true }`, which breaks all
53
62
  // getters and setters in spread objects. We need to add this plugin ourself without that option.
54
63
  // @see https://github.com/expo/expo/pull/11960#issuecomment-887796455
55
- extraPlugins.push([
56
- require.resolve('@babel/plugin-transform-object-rest-spread'),
57
- { loose: false },
58
- ]);
64
+ extraPlugins.push([require('@babel/plugin-transform-object-rest-spread'), { loose: false }]);
59
65
  }
60
- else {
66
+ else if (!isServerEnv) {
61
67
  // This is added back on hermes to ensure the react-jsx-dev plugin (`@babel/preset-react`) works as expected when
62
68
  // JSX is used in a function body. This is technically not required in production, but we
63
69
  // should retain the same behavior since it's hard to debug the differences.
64
70
  extraPlugins.push(require('@babel/plugin-transform-parameters'));
65
71
  }
66
- if (isProduction && (0, common_1.hasModule)('metro-transform-plugins')) {
67
- // Metro applies this plugin too but it does it after the imports have been transformed which breaks
68
- // the plugin. Here, we'll apply it before the commonjs transform, in production, to ensure `Platform.OS`
69
- // is replaced with a string literal and `__DEV__` is converted to a boolean.
70
- // Applying early also means that web can be transformed before the `react-native-web` transform mutates the import.
72
+ const inlines = {
73
+ 'process.env.EXPO_OS': platform,
74
+ // 'typeof document': isServerEnv ? 'undefined' : 'object',
75
+ };
76
+ // `typeof window` is left in place for native + client environments.
77
+ const minifyTypeofWindow = (platformOptions.minifyTypeofWindow ?? isServerEnv) || platform === 'web';
78
+ if (minifyTypeofWindow !== false) {
79
+ // This nets out slightly faster in development when considering the cost of bundling server dependencies.
80
+ inlines['typeof window'] = isServerEnv ? 'undefined' : 'object';
81
+ }
82
+ if (isProduction) {
83
+ inlines['process.env.NODE_ENV'] = 'production';
84
+ inlines['__DEV__'] = false;
85
+ inlines['Platform.OS'] = platform;
86
+ }
87
+ if (process.env.NODE_ENV !== 'test') {
88
+ inlines['process.env.EXPO_BASE_URL'] = baseUrl;
89
+ }
90
+ extraPlugins.push([require('./define-plugin'), inlines]);
91
+ if (isProduction) {
92
+ // Metro applies a version of this plugin too but it does it after the Platform modules have been transformed to CJS, this breaks the transform.
93
+ // Here, we'll apply it before the commonjs transform, in production only, to ensure `Platform.OS` is replaced with a string literal.
71
94
  extraPlugins.push([
72
- require('metro-transform-plugins/src/inline-plugin.js'),
95
+ require('./minify-platform-select-plugin'),
73
96
  {
74
- dev: isDev,
75
- inlinePlatform: true,
76
97
  platform,
77
98
  },
78
99
  ]);
@@ -80,17 +101,6 @@ function babelPresetExpo(api, options = {}) {
80
101
  if (platformOptions.useTransformReactJSXExperimental != null) {
81
102
  throw new Error(`babel-preset-expo: The option 'useTransformReactJSXExperimental' has been removed in favor of { jsxRuntime: 'classic' }.`);
82
103
  }
83
- // Allow jest tests to redefine the environment variables.
84
- if (process.env.NODE_ENV !== 'test') {
85
- extraPlugins.push([
86
- inline_env_vars_1.expoInlineTransformEnvVars,
87
- {
88
- // These values should not be prefixed with `EXPO_PUBLIC_`, so we don't
89
- // squat user-defined environment variables.
90
- EXPO_BASE_URL: baseUrl,
91
- },
92
- ]);
93
- }
94
104
  // Only apply in non-server, for metro-only, in production environments, when the user hasn't disabled the feature.
95
105
  // Webpack uses DefinePlugin for environment variables.
96
106
  // Development uses an uncached serializer.
@@ -100,7 +110,7 @@ function babelPresetExpo(api, options = {}) {
100
110
  extraPlugins.push(inline_env_vars_1.expoInlineEnvVars);
101
111
  }
102
112
  if (platform === 'web') {
103
- extraPlugins.push(require.resolve('babel-plugin-react-native-web'));
113
+ extraPlugins.push(require('babel-plugin-react-native-web'));
104
114
  // Webpack uses the DefinePlugin to provide the manifest to `expo-constants`.
105
115
  if (bundler !== 'webpack') {
106
116
  extraPlugins.push(expo_inline_manifest_plugin_1.expoInlineManifestPlugin);
@@ -109,6 +119,14 @@ function babelPresetExpo(api, options = {}) {
109
119
  if ((0, common_1.hasModule)('expo-router')) {
110
120
  extraPlugins.push(expo_router_plugin_1.expoRouterBabelPlugin);
111
121
  }
122
+ // Ensure these only run when the user opts-in to bundling for a react server to prevent unexpected behavior for
123
+ // users who are bundling using the client-only system.
124
+ if (isReactServer) {
125
+ extraPlugins.push(client_module_proxy_plugin_1.reactClientReferencesPlugin);
126
+ extraPlugins.push(restricted_react_api_plugin_1.environmentRestrictedReactAPIsPlugin);
127
+ }
128
+ // This plugin is fine to run whenever as the server-only imports were introduced as part of RSC and shouldn't be used in any client code.
129
+ extraPlugins.push(environment_restricted_imports_1.environmentRestrictedImportsPlugin);
112
130
  if (isFastRefreshEnabled) {
113
131
  extraPlugins.push([
114
132
  require('react-refresh/babel'),
@@ -118,6 +136,8 @@ function babelPresetExpo(api, options = {}) {
118
136
  },
119
137
  ]);
120
138
  }
139
+ // Use the simpler babel preset for web and server environments (both web and native SSR).
140
+ const isModernEngine = platform === 'web' || isServerEnv;
121
141
  return {
122
142
  presets: [
123
143
  [
@@ -125,7 +145,7 @@ function babelPresetExpo(api, options = {}) {
125
145
  // specifically use the `@react-native/babel-preset` installed by this package (ex:
126
146
  // `babel-preset-expo/node_modules/`). This way the preset will not change unintentionally.
127
147
  // Reference: https://github.com/expo/expo/pull/4685#discussion_r307143920
128
- require('@react-native/babel-preset'),
148
+ isModernEngine ? require('./web-preset') : require('@react-native/babel-preset'),
129
149
  {
130
150
  // Defaults to undefined, set to `true` to disable `@babel/plugin-transform-flow-strip-types`
131
151
  disableFlowStripTypesTransform: platformOptions.disableFlowStripTypesTransform,
@@ -184,12 +204,12 @@ function babelPresetExpo(api, options = {}) {
184
204
  plugins: [
185
205
  ...extraPlugins,
186
206
  // TODO: Remove
187
- [require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }],
188
- require.resolve('@babel/plugin-transform-export-namespace-from'),
207
+ [require('@babel/plugin-proposal-decorators'), { legacy: true }],
208
+ require('@babel/plugin-transform-export-namespace-from'),
189
209
  // Automatically add `react-native-reanimated/plugin` when the package is installed.
190
210
  // TODO: Move to be a customTransformOption.
191
211
  (0, common_1.hasModule)('react-native-reanimated') &&
192
- platformOptions.reanimated !== false && [require.resolve('react-native-reanimated/plugin')],
212
+ platformOptions.reanimated !== false && [require('react-native-reanimated/plugin')],
193
213
  ].filter(Boolean),
194
214
  };
195
215
  }
@@ -2,12 +2,3 @@ import { ConfigAPI, PluginObj, types } from '@babel/core';
2
2
  export declare function expoInlineEnvVars(api: ConfigAPI & {
3
3
  types: typeof types;
4
4
  }): PluginObj;
5
- /**
6
- * Given a set of options like `{ EXPO_BASE_URL: '/' }`, inline the values into the bundle.
7
- * This is used for build settings that are always available and not configurable at runtime.
8
- *
9
- * Webpack uses DefinePlugin for similar functionality.
10
- */
11
- export declare function expoInlineTransformEnvVars(api: ConfigAPI & {
12
- types: typeof types;
13
- }): PluginObj;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.expoInlineTransformEnvVars = exports.expoInlineEnvVars = void 0;
3
+ exports.expoInlineEnvVars = void 0;
4
4
  const debug = require('debug')('expo:babel:env-vars');
5
5
  function expoInlineEnvVars(api) {
6
6
  const { types: t } = api;
@@ -27,34 +27,3 @@ function expoInlineEnvVars(api) {
27
27
  };
28
28
  }
29
29
  exports.expoInlineEnvVars = expoInlineEnvVars;
30
- /**
31
- * Given a set of options like `{ EXPO_BASE_URL: '/' }`, inline the values into the bundle.
32
- * This is used for build settings that are always available and not configurable at runtime.
33
- *
34
- * Webpack uses DefinePlugin for similar functionality.
35
- */
36
- function expoInlineTransformEnvVars(api) {
37
- const { types: t } = api;
38
- function isFirstInAssign(path) {
39
- return t.isAssignmentExpression(path.parent) && path.parent.left === path.node;
40
- }
41
- return {
42
- name: 'expo-inline-transform-environment-variables',
43
- visitor: {
44
- MemberExpression(path, state) {
45
- const options = state.opts;
46
- if (path.get('object').matchesPattern('process.env')) {
47
- // @ts-expect-error: missing types
48
- const key = path.toComputedKey();
49
- if (t.isStringLiteral(key) &&
50
- !isFirstInAssign(path) &&
51
- options[key.value] !== undefined) {
52
- debug('Inlining transform setting in %s: %s', state.filename || '[unknown file]', key.value);
53
- path.replaceWith(t.valueToNode(options[key.value]));
54
- }
55
- }
56
- },
57
- },
58
- };
59
- }
60
- exports.expoInlineTransformEnvVars = expoInlineTransformEnvVars;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Copyright © 2024 650 Industries.
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ import { ConfigAPI, types } from '@babel/core';
9
+ export default function minifyPlatformSelectPlugin({ types: t, }: ConfigAPI & {
10
+ types: typeof types;
11
+ }): babel.PluginObj;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright © 2024 650 Industries.
4
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ const core_1 = require("@babel/core");
11
+ function minifyPlatformSelectPlugin({ types: t, }) {
12
+ return {
13
+ visitor: {
14
+ CallExpression(path, state) {
15
+ const node = path.node;
16
+ const arg = node.arguments[0];
17
+ const opts = state.opts;
18
+ if (isPlatformSelect(path) && core_1.types.isObjectExpression(arg)) {
19
+ if (hasStaticProperties(arg)) {
20
+ let fallback;
21
+ if (opts.platform === 'web') {
22
+ fallback = () => findProperty(arg, 'default', () => t.identifier('undefined'));
23
+ }
24
+ else {
25
+ fallback = () => findProperty(arg, 'native', () => findProperty(arg, 'default', () => t.identifier('undefined')));
26
+ }
27
+ path.replaceWith(findProperty(arg, opts.platform, fallback));
28
+ }
29
+ }
30
+ },
31
+ },
32
+ };
33
+ }
34
+ exports.default = minifyPlatformSelectPlugin;
35
+ function isPlatformSelect(path) {
36
+ return (core_1.types.isMemberExpression(path.node.callee) &&
37
+ core_1.types.isIdentifier(path.node.callee.object) &&
38
+ core_1.types.isIdentifier(path.node.callee.property) &&
39
+ path.node.callee.object.name === 'Platform' &&
40
+ path.node.callee.property.name === 'select' &&
41
+ core_1.types.isObjectExpression(path.node.arguments[0]));
42
+ }
43
+ function findProperty(objectExpression, key, fallback) {
44
+ let value = null;
45
+ for (const p of objectExpression.properties) {
46
+ if (!core_1.types.isObjectProperty(p) && !core_1.types.isObjectMethod(p)) {
47
+ continue;
48
+ }
49
+ if ((core_1.types.isIdentifier(p.key) && p.key.name === key) ||
50
+ (core_1.types.isStringLiteral(p.key) && p.key.value === key)) {
51
+ if (core_1.types.isObjectProperty(p)) {
52
+ value = p.value;
53
+ break;
54
+ }
55
+ else if (core_1.types.isObjectMethod(p)) {
56
+ value = core_1.types.toExpression(p);
57
+ break;
58
+ }
59
+ }
60
+ }
61
+ return value ?? fallback();
62
+ }
63
+ function hasStaticProperties(objectExpression) {
64
+ return objectExpression.properties.every((p) => {
65
+ if (('computed' in p && p.computed) || core_1.types.isSpreadElement(p)) {
66
+ return false;
67
+ }
68
+ if (core_1.types.isObjectMethod(p) && p.kind !== 'method') {
69
+ return false;
70
+ }
71
+ return core_1.types.isIdentifier(p.key) || core_1.types.isStringLiteral(p.key);
72
+ });
73
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Copyright © 2024 650 Industries.
3
+ */
4
+ import { ConfigAPI, types } from '@babel/core';
5
+ export declare function environmentRestrictedReactAPIsPlugin(api: ConfigAPI & {
6
+ types: typeof types;
7
+ }): babel.PluginObj;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.environmentRestrictedReactAPIsPlugin = void 0;
4
+ const INVALID_SERVER_REACT_DOM_APIS = [
5
+ 'findDOMNode',
6
+ 'flushSync',
7
+ 'unstable_batchedUpdates',
8
+ 'useFormStatus',
9
+ 'useFormState',
10
+ ];
11
+ // From the React docs: https://github.com/vercel/next.js/blob/d43a387d271263f2c1c4da6b9db826e382fc489c/packages/next-swc/crates/next-custom-transforms/src/transforms/react_server_components.rs#L665-L681
12
+ const INVALID_SERVER_REACT_APIS = [
13
+ 'Component',
14
+ 'createContext',
15
+ 'createFactory',
16
+ 'PureComponent',
17
+ 'useDeferredValue',
18
+ 'useEffect',
19
+ 'useImperativeHandle',
20
+ 'useInsertionEffect',
21
+ 'useLayoutEffect',
22
+ 'useReducer',
23
+ 'useRef',
24
+ 'useState',
25
+ 'useSyncExternalStore',
26
+ 'useTransition',
27
+ 'useOptimistic',
28
+ ];
29
+ function isNodeModule(path) {
30
+ return path != null && /[\\/]node_modules[\\/]/.test(path);
31
+ }
32
+ // Restricts imports from `react` and `react-dom` when using React Server Components.
33
+ const FORBIDDEN_IMPORTS = {
34
+ react: INVALID_SERVER_REACT_APIS,
35
+ 'react-dom': INVALID_SERVER_REACT_DOM_APIS,
36
+ };
37
+ function environmentRestrictedReactAPIsPlugin(api) {
38
+ const { types: t } = api;
39
+ return {
40
+ name: 'expo-environment-restricted-react-api-plugin',
41
+ visitor: {
42
+ ImportDeclaration(path, state) {
43
+ // Skip node_modules
44
+ if (isNodeModule(state.file.opts.filename)) {
45
+ return;
46
+ }
47
+ const sourceValue = path.node.source.value;
48
+ const forbiddenList = FORBIDDEN_IMPORTS[sourceValue];
49
+ if (forbiddenList) {
50
+ path.node.specifiers.forEach((specifier) => {
51
+ if (t.isImportSpecifier(specifier)) {
52
+ const importName = t.isStringLiteral(specifier.imported)
53
+ ? specifier.imported.value
54
+ : specifier.imported.name;
55
+ // Check for both named and namespace imports
56
+ const isForbidden = forbiddenList.includes(importName);
57
+ if (isForbidden) {
58
+ if (['Component', 'PureComponent'].includes(importName)) {
59
+ // Add special handling for `Component` since it is different to a function API.
60
+ throw path.buildCodeFrameError(`Client-only "${sourceValue}" API "${importName}" cannot be imported in a React server component. Add the "use client" directive to the top of this file or one of the parent files to enable running this stateful code on a user's device.`);
61
+ }
62
+ else {
63
+ const forbiddenImports = path.scope.getData('forbiddenImports') ?? new Map();
64
+ if (!forbiddenImports.has(sourceValue))
65
+ forbiddenImports.set(sourceValue, new Set());
66
+ forbiddenImports.get(sourceValue).add(importName);
67
+ path.scope.setData('forbiddenImports', forbiddenImports);
68
+ }
69
+ }
70
+ }
71
+ else {
72
+ const importName = t.isStringLiteral(specifier.local)
73
+ ? specifier.local
74
+ : specifier.local.name;
75
+ // Save namespace import for later checks in MemberExpression
76
+ path.scope.setData('importedNamespace', { [importName]: sourceValue });
77
+ }
78
+ });
79
+ }
80
+ },
81
+ // Match against `var _useState = useState(0),`
82
+ VariableDeclarator(path) {
83
+ const importedSpecifiers = path.scope.getData('forbiddenImports');
84
+ if (!importedSpecifiers)
85
+ return;
86
+ importedSpecifiers.forEach((forbiddenApis, importName) => {
87
+ if (t.isCallExpression(path.node.init) && t.isIdentifier(path.node.init.callee)) {
88
+ if (forbiddenApis.has(path.node.init.callee.name)) {
89
+ throw path.buildCodeFrameError(`Client-only "useState" API cannot be used in a React server component. Add the "use client" directive to the top of this file or one of the parent files to enable running this stateful code on a user's device.`);
90
+ }
91
+ }
92
+ });
93
+ },
94
+ MemberExpression(path) {
95
+ const importedNamespaces = path.scope.getData('importedNamespace') || {};
96
+ Object.keys(importedNamespaces).forEach((namespace) => {
97
+ const library = importedNamespaces[namespace];
98
+ const forbiddenList = FORBIDDEN_IMPORTS[library];
99
+ const objectName = t.isIdentifier(path.node.object) ? path.node.object.name : null;
100
+ if (objectName === namespace &&
101
+ forbiddenList &&
102
+ t.isIdentifier(path.node.property) &&
103
+ forbiddenList.includes(path.node.property.name)) {
104
+ // Throw a special error for class components since it's not always clear why they cannot be used in RSC.
105
+ // e.g. https://x.com/Baconbrix/status/1749223042440392806?s=20
106
+ if (path.node.property.name === 'Component') {
107
+ throw path.buildCodeFrameError(`Class components cannot be used in a React server component due to their ability to contain stateful and interactive APIs that cannot be statically evaluated in non-interactive environments such as a server or at build-time. Migrate to a function component, or add the "use client" directive to the top of this file or one of the parent files to render this class component on a user's device.`);
108
+ }
109
+ throw path.buildCodeFrameError(`Client-only "${namespace}" API "${path.node.property.name}" cannot be used in a React server component. Add the "use client" directive to the top of this file or one of the parent files to enable running this stateful code on a user's device.`);
110
+ }
111
+ });
112
+ },
113
+ },
114
+ };
115
+ }
116
+ exports.environmentRestrictedReactAPIsPlugin = environmentRestrictedReactAPIsPlugin;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Copyright © 2024 650 Industries.
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * A fork of `@react-native/babel-preset` but with everything unrelated to web/ssr removed.
9
+ * https://github.com/facebook/react-native/blob/2af1da42ff517232f1309efed7565fe9ddbbac77/packages/react-native-babel-preset/src/configs/main.js#L1
10
+ */
11
+ export {};
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright © 2024 650 Industries.
4
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ *
9
+ * A fork of `@react-native/babel-preset` but with everything unrelated to web/ssr removed.
10
+ * https://github.com/facebook/react-native/blob/2af1da42ff517232f1309efed7565fe9ddbbac77/packages/react-native-babel-preset/src/configs/main.js#L1
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ // use `this.foo = bar` instead of `this.defineProperty('foo', ...)`
14
+ const loose = true;
15
+ const defaultPlugins = [
16
+ [require('@babel/plugin-syntax-flow')],
17
+ [require('babel-plugin-transform-flow-enums')],
18
+ [require('@babel/plugin-transform-private-methods'), { loose }],
19
+ [require('@babel/plugin-transform-private-property-in-object'), { loose }],
20
+ // [require('@babel/plugin-syntax-dynamic-import')],
21
+ [require('@babel/plugin-syntax-export-default-from')],
22
+ // [require('@babel/plugin-syntax-nullish-coalescing-operator')],
23
+ // [require('@babel/plugin-syntax-optional-chaining')],
24
+ // [require('@babel/plugin-transform-unicode-regex')],
25
+ ];
26
+ module.exports = function (babel, options) {
27
+ const extraPlugins = [];
28
+ // NOTE: We also remove `@react-native/babel-plugin-codegen` since it doesn't seem needed on web.
29
+ if (!options || !options.disableImportExportTransform) {
30
+ extraPlugins.push([require('@babel/plugin-proposal-export-default-from')], [
31
+ require('@babel/plugin-transform-modules-commonjs'),
32
+ {
33
+ strict: false,
34
+ strictMode: false,
35
+ lazy: options.lazyImportExportTransform,
36
+ allowTopLevelThis: true, // dont rewrite global `this` -> `undefined`
37
+ },
38
+ ]);
39
+ }
40
+ if (!options || options.enableBabelRuntime !== false) {
41
+ // Allows configuring a specific runtime version to optimize output
42
+ const isVersion = typeof options?.enableBabelRuntime === 'string';
43
+ extraPlugins.push([
44
+ require('@babel/plugin-transform-runtime'),
45
+ {
46
+ helpers: true,
47
+ regenerator: false,
48
+ ...(isVersion && { version: options.enableBabelRuntime }),
49
+ },
50
+ ]);
51
+ }
52
+ return {
53
+ comments: false,
54
+ compact: true,
55
+ presets: [
56
+ // TypeScript support
57
+ [require('@babel/preset-typescript'), { allowNamespaces: true }],
58
+ ],
59
+ overrides: [
60
+ // the flow strip types plugin must go BEFORE class properties!
61
+ // there'll be a test case that fails if you don't.
62
+ {
63
+ plugins: [require('@babel/plugin-transform-flow-strip-types')],
64
+ },
65
+ {
66
+ plugins: defaultPlugins,
67
+ },
68
+ {
69
+ plugins: extraPlugins,
70
+ },
71
+ ],
72
+ };
73
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "babel-preset-expo",
3
- "version": "10.0.2",
3
+ "version": "11.0.1",
4
4
  "description": "The Babel preset for Expo projects",
5
5
  "main": "build/index.js",
6
6
  "files": [
@@ -42,13 +42,13 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@babel/plugin-proposal-decorators": "^7.12.9",
45
- "@babel/plugin-transform-object-rest-spread": "^7.12.13",
46
45
  "@babel/plugin-transform-export-namespace-from": "^7.22.11",
47
- "@babel/preset-env": "^7.20.0",
48
- "@babel/preset-react": "^7.22.15",
46
+ "@babel/plugin-transform-object-rest-spread": "^7.12.13",
49
47
  "@babel/plugin-transform-parameters": "^7.22.15",
50
- "@react-native/babel-preset": "^0.73.18",
51
- "babel-plugin-react-native-web": "~0.18.10",
48
+ "@babel/preset-typescript": "^7.23.0",
49
+ "@babel/preset-react": "^7.22.15",
50
+ "@react-native/babel-preset": "~0.74.81",
51
+ "babel-plugin-react-native-web": "~0.19.10",
52
52
  "react-refresh": "0.14.0"
53
53
  },
54
54
  "devDependencies": {
@@ -56,5 +56,5 @@
56
56
  "expo-module-scripts": "^3.3.0",
57
57
  "jest": "^29.2.1"
58
58
  },
59
- "gitHead": "88a8226609b870c0635c39da43ac8306c4dc7031"
59
+ "gitHead": "0897aeadb926491a457bcd67d83360956994ee82"
60
60
  }