babel-preset-expo 0.0.0-canary-20231122-1af9191

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 ADDED
@@ -0,0 +1,166 @@
1
+ # babel-preset-expo
2
+
3
+ This preset extends the default React Native preset (`metro-react-native-babel-preset`) and adds support for decorators, tree-shaking web packages, and loading font icons with optional native dependencies if they're installed.
4
+
5
+ You can use this preset in any React Native project as a drop-in replacement for `metro-react-native-babel-preset`. If your project isn't using native font loading or web support then this preset will only add support for decorators with `@babel/plugin-proposal-decorators` - this is mostly used for supporting legacy community libraries.
6
+
7
+ If you start your **web** project with `@expo/webpack-config` or `npx expo start` and your project doesn't contain a `babel.config.js` or a `.babelrc` then it will default to using `babel-preset-expo` for loading.
8
+
9
+ If you have problems with the code in this repository, please file issues & bug reports
10
+ at https://github.com/expo/expo. Thanks!
11
+
12
+ ## Expo Bundler Spec Compliance
13
+
14
+ A bundler must follow these requirements if they are to be considered spec compliant for use with a **universal React** (Expo) project.
15
+
16
+ ### Babel Loader
17
+
18
+ The babel loading mechanism must include the following properties on its `caller`.
19
+
20
+ #### platform
21
+
22
+ A `platform` property denoting the target platform. If the `platform` is not defined, it will default to using `web` when the `bundler` is `webpack` -- this is temporary and will throw an error in the future.
23
+
24
+ | Value | Description |
25
+ | --------- | ----------------------- |
26
+ | `ios` | Runs on iOS devices |
27
+ | `android` | Runs on Android devices |
28
+ | `web` | Runs in web browsers |
29
+
30
+ #### bundler
31
+
32
+ A `bundler` property denoting the name of the bundler that is being used to create the JavaScript bundle.
33
+ If the `bundler` is not defined, it will default to checking if a `babel-loader` is used, if so then `webpack` will be used, otherwise it will default to `metro`.
34
+
35
+ | Value | Description |
36
+ | --------- | -------------------------------- |
37
+ | `metro` | Bundling with [Metro][metro] |
38
+ | `webpack` | Bundling with [Webpack][webpack] |
39
+
40
+ [metro]: https://facebook.github.io/metro/
41
+ [webpack]: https://webpack.js.org/
42
+
43
+ ## Options
44
+
45
+ ### `reanimated`
46
+
47
+ `boolean`, defaults to `true`. Set `reanimated: false` to disable adding the `react-native-reanimated/plugin` when `react-native-reanimated` is installed.
48
+
49
+ ### [`jsxRuntime`](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx#runtime)
50
+
51
+ `classic | automatic`, defaults to `automatic`
52
+
53
+ - `automatic` automatically convert JSX to JS without the need to `import React from 'react'` in every file. Be sure to follow the rest of the [setup guide](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#how-to-upgrade-to-the-new-jsx-transform) after enabling this, otherwise ESLint and other tools will throw warnings.
54
+ - `classic` does not automatically import anything, React must imported into every file that uses JSX syntax.
55
+
56
+ ```js
57
+ [
58
+ 'babel-preset-expo',
59
+ {
60
+ jsxRuntime: 'classic',
61
+ },
62
+ ];
63
+ ```
64
+
65
+ This property is passed down to [`@babel/plugin-transform-react-jsx`](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx). This flag does nothing when `useTransformReactJSXExperimental` is set to `true` because `@babel/plugin-transform-react-jsx` is omitted.
66
+
67
+ ### [`jsxImportSource`](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx#importsource)
68
+
69
+ `string`, defaults to `react`
70
+
71
+ This option allows specifying a custom import source for importing functions.
72
+
73
+ ```js
74
+ [
75
+ 'babel-preset-expo',
76
+ {
77
+ jsxRuntime: 'automatic',
78
+ jsxImportSource: 'react',
79
+ },
80
+ ];
81
+ ```
82
+
83
+ This property is passed down to [`@babel/plugin-transform-react-jsx`](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx). This options does nothing when `jsxRuntime` is not set to `automatic`.
84
+
85
+ ### [`lazyImports`](https://babeljs.io/docs/en/babel-plugin-transform-modules-commonjs#lazy)
86
+
87
+ Changes Babel's compiled `import` statements to be lazily evaluated when their imported bindings are used for the first time.
88
+
89
+ _Note:_ this option has an effect only when the `disableImportExportTransform` option is set to `false`. On Android and iOS, `disableImportExportTransform` defaults to `false`, and on web it defaults to `true` to allow for tree shaking.
90
+
91
+ This can improve the initial load time of your app because evaluating dependencies up front is sometimes entirely un-necessary, particularly when the dependencies have no side effects.
92
+
93
+ The value of `lazyImports` has a few possible effects:
94
+
95
+ - `null` - [metro-react-native-babel-preset](https://github.com/facebook/metro/tree/master/packages/metro-react-native-babel-preset) will handle it. (Learn more about it here: https://github.com/facebook/metro/commit/23e3503dde5f914f3e642ef214f508d0a699851d)
96
+
97
+ - `false` - No lazy initialization of any imported module.
98
+
99
+ - `true` - Lazy-init all imported modules except local imports (e.g., `./foo`), certain Expo packages that have side effects, and the two cases mentioned [here](https://babeljs.io/docs/en/babel-plugin-transform-modules-commonjs#lazy).
100
+
101
+ - `Array<string>` - [babel-plugin-transform-modules-commonjs](https://babeljs.io/docs/en/babel-plugin-transform-modules-commonjs#lazy) will handle it.
102
+
103
+ - `(string) => boolean` - [babel-plugin-transform-modules-commonjs](https://babeljs.io/docs/en/babel-plugin-transform-modules-commonjs#lazy) will handle it.
104
+
105
+ If you choose to do this, you can also access the list of Expo packages that have side effects by using `const lazyImportsBlacklist = require('babel-preset-expo/lazy-imports-blacklist');` which returns a `Set`.
106
+
107
+ **default:** `null`
108
+
109
+ ```js
110
+ [
111
+ 'babel-preset-expo',
112
+ {
113
+ lazyImports: true
114
+ }
115
+ ],
116
+ ```
117
+
118
+ ### `disableImportExportTransform`
119
+
120
+ Enabling this option will allow your project to run with older JavaScript syntax (i.e. `module.exports`). This option will break tree shaking and increase your bundle size, but will eliminate the following error when `module.exports` is used:
121
+
122
+ > `TypeError: Cannot assign to read only property 'exports' of object '#<Object>'`
123
+
124
+ **default:** `false` when using Webpack. `true` otherwise.
125
+
126
+ ```js
127
+ [
128
+ 'babel-preset-expo',
129
+ {
130
+ disableImportExportTransform: true
131
+ }
132
+ ],
133
+ ```
134
+
135
+ ### `unstable_transformProfile`
136
+
137
+ Changes the engine preset in `metro-react-native-babel-preset` based on the JavaScript engine that is being targeted. In Expo SDK 50 and greater, this is automatically set based on the [`jsEngine`](https://docs.expo.dev/versions/latest/config/app/#jsengine) option in your `app.json`.
138
+
139
+ ### `enableBabelRuntime`
140
+
141
+ Passed to `metro-react-native-babel-preset`.
142
+
143
+ ### `disableFlowStripTypesTransform`
144
+
145
+ Passed to `metro-react-native-babel-preset`.
146
+
147
+ ## Platform-specific options
148
+
149
+ All options can be passed in the platform-specific objects `native` and `web` to provide different settings on different platforms. For example, if you'd like to only apply `disableImportExportTransform` on web, use the following:
150
+
151
+ ```js
152
+ [
153
+ 'babel-preset-expo',
154
+ {
155
+ // Default value:
156
+ disableImportExportTransform: false,
157
+
158
+ web: {
159
+ // Web-specific value:
160
+ disableImportExportTransform: true,
161
+ },
162
+ },
163
+ ];
164
+ ```
165
+
166
+ Platform-specific options have higher priority over top-level options.
@@ -0,0 +1,12 @@
1
+ export declare function hasModule(name: string): boolean;
2
+ /** Determine which bundler is being used. */
3
+ export declare function getBundler(caller: any): any;
4
+ export declare function getPlatform(caller: any): any;
5
+ export declare function getPossibleProjectRoot(caller: any): any;
6
+ export declare function getIsDev(caller: any): any;
7
+ export declare function getIsFastRefreshEnabled(caller: any): any;
8
+ export declare function getIsProd(caller: any): boolean;
9
+ export declare function getIsNodeModule(caller: any): boolean;
10
+ export declare function getBaseUrl(caller: any): string;
11
+ export declare function getIsServer(caller: any): any;
12
+ export declare function getInlineEnvVarsEnabled(caller: any): boolean;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getInlineEnvVarsEnabled = exports.getIsServer = exports.getBaseUrl = exports.getIsNodeModule = exports.getIsProd = exports.getIsFastRefreshEnabled = exports.getIsDev = exports.getPossibleProjectRoot = exports.getPlatform = exports.getBundler = exports.hasModule = void 0;
4
+ function hasModule(name) {
5
+ try {
6
+ return !!require.resolve(name);
7
+ }
8
+ catch (error) {
9
+ if (error.code === 'MODULE_NOT_FOUND' && error.message.includes(name)) {
10
+ return false;
11
+ }
12
+ throw error;
13
+ }
14
+ }
15
+ exports.hasModule = hasModule;
16
+ /** Determine which bundler is being used. */
17
+ function getBundler(caller) {
18
+ if (!caller)
19
+ return null;
20
+ if (caller.bundler)
21
+ return caller.bundler;
22
+ if (
23
+ // Known tools that use `webpack`-mode via `babel-loader`: `@expo/webpack-config`, Next.js <10
24
+ caller.name === 'babel-loader' ||
25
+ // NextJS 11 uses this custom caller name.
26
+ caller.name === 'next-babel-turbo-loader') {
27
+ return 'webpack';
28
+ }
29
+ // Assume anything else is Metro.
30
+ return 'metro';
31
+ }
32
+ exports.getBundler = getBundler;
33
+ function getPlatform(caller) {
34
+ if (!caller)
35
+ return null;
36
+ if (caller.platform)
37
+ return caller.platform;
38
+ const bundler = getBundler(caller);
39
+ if (bundler === 'webpack') {
40
+ return 'web';
41
+ }
42
+ // unknown
43
+ return caller.platform;
44
+ }
45
+ exports.getPlatform = getPlatform;
46
+ function getPossibleProjectRoot(caller) {
47
+ if (!caller)
48
+ return null;
49
+ if (caller.projectRoot)
50
+ return caller.projectRoot;
51
+ // unknown
52
+ return process.env.EXPO_PROJECT_ROOT;
53
+ }
54
+ exports.getPossibleProjectRoot = getPossibleProjectRoot;
55
+ function getIsDev(caller) {
56
+ if (caller?.isDev != null)
57
+ return caller.isDev;
58
+ // https://babeljs.io/docs/options#envname
59
+ return process.env.BABEL_ENV === 'development' || process.env.NODE_ENV === 'development';
60
+ }
61
+ exports.getIsDev = getIsDev;
62
+ function getIsFastRefreshEnabled(caller) {
63
+ if (!caller)
64
+ return false;
65
+ return caller.isHMREnabled && !caller.isServer && !caller.isNodeModule && getIsDev(caller);
66
+ }
67
+ exports.getIsFastRefreshEnabled = getIsFastRefreshEnabled;
68
+ function getIsProd(caller) {
69
+ if (caller?.isDev != null)
70
+ return caller.isDev === false;
71
+ // https://babeljs.io/docs/options#envname
72
+ return process.env.BABEL_ENV === 'production' || process.env.NODE_ENV === 'production';
73
+ }
74
+ exports.getIsProd = getIsProd;
75
+ function getIsNodeModule(caller) {
76
+ return caller?.isNodeModule ?? false;
77
+ }
78
+ exports.getIsNodeModule = getIsNodeModule;
79
+ function getBaseUrl(caller) {
80
+ return caller?.baseUrl ?? '';
81
+ }
82
+ exports.getBaseUrl = getBaseUrl;
83
+ function getIsServer(caller) {
84
+ return caller?.isServer ?? false;
85
+ }
86
+ exports.getIsServer = getIsServer;
87
+ function getInlineEnvVarsEnabled(caller) {
88
+ const isWebpack = getBundler(caller) === 'webpack';
89
+ const isDev = getIsDev(caller);
90
+ const isServer = getIsServer(caller);
91
+ const isNodeModule = getIsNodeModule(caller);
92
+ const preserveEnvVars = caller?.preserveEnvVars;
93
+ // Development env vars are added in the serializer to avoid caching issues in development.
94
+ // Servers have env vars left as-is to read from the environment.
95
+ return !isNodeModule && !isWebpack && !isDev && !isServer && !preserveEnvVars;
96
+ }
97
+ exports.getInlineEnvVarsEnabled = getInlineEnvVarsEnabled;
@@ -0,0 +1,9 @@
1
+ import { ConfigAPI } from '@babel/core';
2
+ export declare function expoInlineManifestPlugin(api: ConfigAPI & {
3
+ types: any;
4
+ }): {
5
+ name: string;
6
+ visitor: {
7
+ MemberExpression(path: any, state: any): void;
8
+ };
9
+ };
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.expoInlineManifestPlugin = void 0;
4
+ const config_1 = require("expo/config");
5
+ const common_1 = require("./common");
6
+ const debug = require('debug')('expo:babel:inline-manifest');
7
+ // Convert expo value to PWA value
8
+ function ensurePWAorientation(orientation) {
9
+ if (orientation) {
10
+ const webOrientation = orientation.toLowerCase();
11
+ if (webOrientation !== 'default') {
12
+ return webOrientation;
13
+ }
14
+ }
15
+ return undefined;
16
+ }
17
+ const RESTRICTED_MANIFEST_FIELDS = [
18
+ 'androidNavigationBar',
19
+ 'androidStatusBar',
20
+ 'privacy',
21
+ // Remove iOS and Android.
22
+ 'ios',
23
+ 'android',
24
+ // Hide internal / build values
25
+ 'plugins',
26
+ 'hooks',
27
+ '_internal',
28
+ // Remove metro-specific values
29
+ 'assetBundlePatterns',
30
+ ];
31
+ function getExpoConstantsManifest(projectRoot) {
32
+ const { exp } = getConfigMemo(projectRoot);
33
+ const manifest = applyWebDefaults(exp);
34
+ for (const field of RESTRICTED_MANIFEST_FIELDS) {
35
+ delete manifest[field];
36
+ }
37
+ return manifest;
38
+ }
39
+ function applyWebDefaults(appJSON) {
40
+ // For RN CLI support
41
+ const { web: webManifest = {}, splash = {}, ios = {}, android = {} } = appJSON;
42
+ // rn-cli apps use a displayName value as well.
43
+ const { appName, webName } = (0, config_1.getNameFromConfig)(appJSON);
44
+ const languageISOCode = webManifest.lang;
45
+ const primaryColor = appJSON.primaryColor;
46
+ const description = appJSON.description;
47
+ // The theme_color sets the color of the tool bar, and may be reflected in the app's preview in task switchers.
48
+ const webThemeColor = webManifest.themeColor || primaryColor;
49
+ const dir = webManifest.dir;
50
+ const shortName = webManifest.shortName || webName;
51
+ const display = webManifest.display;
52
+ const startUrl = webManifest.startUrl;
53
+ const { scope, crossorigin } = webManifest;
54
+ const barStyle = webManifest.barStyle;
55
+ const orientation = ensurePWAorientation(webManifest.orientation || appJSON.orientation);
56
+ /**
57
+ * **Splash screen background color**
58
+ * `https://developers.google.com/web/fundamentals/web-app-manifest/#splash-screen`
59
+ * The background_color should be the same color as the load page,
60
+ * to provide a smooth transition from the splash screen to your app.
61
+ */
62
+ const backgroundColor = webManifest.backgroundColor || splash.backgroundColor; // No default background color
63
+ return {
64
+ ...appJSON,
65
+ name: appName,
66
+ description,
67
+ primaryColor,
68
+ // Ensure these objects exist
69
+ ios: {
70
+ ...ios,
71
+ },
72
+ android: {
73
+ ...android,
74
+ },
75
+ web: {
76
+ ...webManifest,
77
+ meta: undefined,
78
+ build: undefined,
79
+ scope,
80
+ crossorigin,
81
+ description,
82
+ startUrl,
83
+ shortName,
84
+ display,
85
+ orientation,
86
+ dir,
87
+ barStyle,
88
+ backgroundColor,
89
+ themeColor: webThemeColor,
90
+ lang: languageISOCode,
91
+ name: webName,
92
+ },
93
+ };
94
+ }
95
+ function getExpoAppManifest(projectRoot) {
96
+ if (process.env.APP_MANIFEST) {
97
+ return process.env.APP_MANIFEST;
98
+ }
99
+ const exp = getExpoConstantsManifest(projectRoot);
100
+ debug('public manifest', exp);
101
+ return JSON.stringify(exp);
102
+ }
103
+ let config;
104
+ function getConfigMemo(projectRoot) {
105
+ if (!config) {
106
+ config = (0, config_1.getConfig)(projectRoot, {
107
+ isPublicConfig: true,
108
+ skipSDKVersionRequirement: true,
109
+ });
110
+ }
111
+ return config;
112
+ }
113
+ // Convert `process.env.APP_MANIFEST` to a modified web-specific variation of the app.json public manifest.
114
+ function expoInlineManifestPlugin(api) {
115
+ const { types: t } = api;
116
+ const platform = api.caller(common_1.getPlatform);
117
+ const possibleProjectRoot = api.caller(common_1.getPossibleProjectRoot);
118
+ return {
119
+ name: 'expo-inline-manifest-plugin',
120
+ visitor: {
121
+ MemberExpression(path, state) {
122
+ // Web-only feature, the native manifest is provided dynamically by the client.
123
+ if (platform !== 'web') {
124
+ return;
125
+ }
126
+ if (!t.isIdentifier(path.node.object, { name: 'process' }) ||
127
+ !t.isIdentifier(path.node.property, { name: 'env' })) {
128
+ return;
129
+ }
130
+ const parent = path.parentPath;
131
+ if (!t.isMemberExpression(parent.node)) {
132
+ return;
133
+ }
134
+ const projectRoot = possibleProjectRoot || state.file.opts.root || '';
135
+ if (
136
+ // Surfaces the `app.json` (config) as an environment variable which is then parsed by
137
+ // `expo-constants` https://docs.expo.dev/versions/latest/sdk/constants/
138
+ t.isIdentifier(parent.node.property, {
139
+ name: 'APP_MANIFEST',
140
+ }) &&
141
+ !parent.parentPath.isAssignmentExpression()) {
142
+ const manifest = getExpoAppManifest(projectRoot);
143
+ parent.replaceWith(t.stringLiteral(manifest));
144
+ }
145
+ },
146
+ },
147
+ };
148
+ }
149
+ exports.expoInlineManifestPlugin = expoInlineManifestPlugin;
@@ -0,0 +1,20 @@
1
+ import { ConfigAPI, types } from '@babel/core';
2
+ /**
3
+ * Inlines environment variables to configure the process:
4
+ *
5
+ * EXPO_PROJECT_ROOT
6
+ * EXPO_PUBLIC_USE_STATIC
7
+ * EXPO_ROUTER_ABS_APP_ROOT
8
+ * EXPO_ROUTER_APP_ROOT
9
+ * EXPO_ROUTER_IMPORT_MODE_IOS
10
+ * EXPO_ROUTER_IMPORT_MODE_ANDROID
11
+ * EXPO_ROUTER_IMPORT_MODE_WEB
12
+ */
13
+ export declare function expoRouterBabelPlugin(api: ConfigAPI & {
14
+ types: typeof types;
15
+ }): {
16
+ name: string;
17
+ visitor: {
18
+ MemberExpression(path: any, state: any): void;
19
+ };
20
+ };
@@ -0,0 +1,178 @@
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.expoRouterBabelPlugin = void 0;
7
+ const config_1 = require("expo/config");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const resolve_from_1 = __importDefault(require("resolve-from"));
11
+ const common_1 = require("./common");
12
+ const debug = require('debug')('expo:babel:router');
13
+ let config;
14
+ function getConfigMemo(projectRoot) {
15
+ if (!config || process.env._EXPO_INTERNAL_TESTING) {
16
+ config = (0, config_1.getConfig)(projectRoot);
17
+ }
18
+ return config;
19
+ }
20
+ function getExpoRouterImportMode(projectRoot, platform) {
21
+ const envVar = 'EXPO_ROUTER_IMPORT_MODE_' + platform.toUpperCase();
22
+ if (process.env[envVar]) {
23
+ return process.env[envVar];
24
+ }
25
+ const env = process.env.NODE_ENV || process.env.BABEL_ENV;
26
+ const { exp } = getConfigMemo(projectRoot);
27
+ let asyncRoutesSetting;
28
+ if (exp.extra?.router?.asyncRoutes) {
29
+ const asyncRoutes = exp.extra?.router?.asyncRoutes;
30
+ if (typeof asyncRoutes === 'string') {
31
+ asyncRoutesSetting = asyncRoutes;
32
+ }
33
+ else if (typeof asyncRoutes === 'object') {
34
+ asyncRoutesSetting = asyncRoutes[platform] ?? asyncRoutes.default;
35
+ }
36
+ }
37
+ let mode = [env, true].includes(asyncRoutesSetting) ? 'lazy' : 'sync';
38
+ // TODO: Production bundle splitting
39
+ if (env === 'production' && mode === 'lazy') {
40
+ throw new Error('Async routes are not supported in production yet. Set the `expo-router` Config Plugin prop `asyncRoutes` to `development`, `false`, or `undefined`.');
41
+ }
42
+ // NOTE: This is a temporary workaround for static rendering on web.
43
+ if (platform === 'web' && (exp.web || {}).output === 'static') {
44
+ mode = 'sync';
45
+ }
46
+ // Development
47
+ debug('Router import mode', mode);
48
+ process.env[envVar] = mode;
49
+ return mode;
50
+ }
51
+ function directoryExistsSync(file) {
52
+ return fs_1.default.statSync(file, { throwIfNoEntry: false })?.isDirectory() ?? false;
53
+ }
54
+ function getRouterDirectory(projectRoot) {
55
+ // more specific directories first
56
+ if (directoryExistsSync(path_1.default.join(projectRoot, 'src/app'))) {
57
+ // Log.log(chalk.gray('Using src/app as the root directory for Expo Router.'));
58
+ return './src/app';
59
+ }
60
+ // Log.debug('Using app as the root directory for Expo Router.');
61
+ return './app';
62
+ }
63
+ function getExpoRouterAppRoot(projectRoot) {
64
+ // Bump to v2 to prevent the CLI from setting the variable anymore.
65
+ // TODO: Bump to v3 to revert back to the CLI setting the variable again, but with custom value
66
+ // support.
67
+ if (process.env.EXPO_ROUTER_APP_ROOT_2) {
68
+ return process.env.EXPO_ROUTER_APP_ROOT_2;
69
+ }
70
+ const routerEntry = (0, resolve_from_1.default)(projectRoot, 'expo-router/entry');
71
+ // It doesn't matter if the app folder exists.
72
+ const appFolder = getExpoRouterAbsoluteAppRoot(projectRoot);
73
+ const appRoot = path_1.default.relative(path_1.default.dirname(routerEntry), appFolder);
74
+ debug('routerEntry', routerEntry, appFolder, appRoot);
75
+ process.env.EXPO_ROUTER_APP_ROOT_2 = appRoot;
76
+ return appRoot;
77
+ }
78
+ function getExpoRouterAbsoluteAppRoot(projectRoot) {
79
+ if (process.env.EXPO_ROUTER_ABS_APP_ROOT) {
80
+ return process.env.EXPO_ROUTER_ABS_APP_ROOT;
81
+ }
82
+ const { exp } = getConfigMemo(projectRoot);
83
+ const customSrc = exp.extra?.router?.unstable_src || getRouterDirectory(projectRoot);
84
+ const isAbsolute = customSrc.startsWith('/');
85
+ // It doesn't matter if the app folder exists.
86
+ const appFolder = isAbsolute ? customSrc : path_1.default.join(projectRoot, customSrc);
87
+ const appRoot = appFolder;
88
+ debug('absolute router entry', appFolder, appRoot);
89
+ process.env.EXPO_ROUTER_ABS_APP_ROOT = appFolder;
90
+ return appRoot;
91
+ }
92
+ // TODO: Strip the function `generateStaticParams` when bundling for node.js environments.
93
+ /**
94
+ * Inlines environment variables to configure the process:
95
+ *
96
+ * EXPO_PROJECT_ROOT
97
+ * EXPO_PUBLIC_USE_STATIC
98
+ * EXPO_ROUTER_ABS_APP_ROOT
99
+ * EXPO_ROUTER_APP_ROOT
100
+ * EXPO_ROUTER_IMPORT_MODE_IOS
101
+ * EXPO_ROUTER_IMPORT_MODE_ANDROID
102
+ * EXPO_ROUTER_IMPORT_MODE_WEB
103
+ */
104
+ function expoRouterBabelPlugin(api) {
105
+ const { types: t } = api;
106
+ const platform = api.caller(common_1.getPlatform);
107
+ const possibleProjectRoot = api.caller(common_1.getPossibleProjectRoot);
108
+ return {
109
+ name: 'expo-router',
110
+ visitor: {
111
+ // Convert `process.env.EXPO_ROUTER_APP_ROOT` to a string literal
112
+ MemberExpression(path, state) {
113
+ if (!t.isIdentifier(path.node.object, { name: 'process' }) ||
114
+ !t.isIdentifier(path.node.property, { name: 'env' })) {
115
+ return;
116
+ }
117
+ const parent = path.parentPath;
118
+ if (!t.isMemberExpression(parent.node)) {
119
+ return;
120
+ }
121
+ const projectRoot = possibleProjectRoot || state.file.opts.root || '';
122
+ // Used for log box and stuff
123
+ if (t.isIdentifier(parent.node.property, {
124
+ name: 'EXPO_PROJECT_ROOT',
125
+ }) &&
126
+ !parent.parentPath.isAssignmentExpression()) {
127
+ parent.replaceWith(t.stringLiteral(projectRoot));
128
+ }
129
+ else if (
130
+ // Enable static rendering
131
+ // TODO: Use a serializer or something to ensure this changes without
132
+ // needing to clear the cache.
133
+ t.isIdentifier(parent.node.property, {
134
+ name: 'EXPO_PUBLIC_USE_STATIC',
135
+ }) &&
136
+ !parent.parentPath.isAssignmentExpression()) {
137
+ if (platform === 'web') {
138
+ const isStatic = process.env.EXPO_PUBLIC_USE_STATIC === 'true' ||
139
+ process.env.EXPO_PUBLIC_USE_STATIC === '1';
140
+ parent.replaceWith(t.booleanLiteral(isStatic));
141
+ }
142
+ else {
143
+ parent.replaceWith(t.booleanLiteral(false));
144
+ }
145
+ }
146
+ else if (process.env.NODE_ENV !== 'test' &&
147
+ t.isIdentifier(parent.node.property, {
148
+ name: 'EXPO_ROUTER_ABS_APP_ROOT',
149
+ }) &&
150
+ !parent.parentPath.isAssignmentExpression()) {
151
+ parent.replaceWith(t.stringLiteral(getExpoRouterAbsoluteAppRoot(projectRoot)));
152
+ }
153
+ else if (
154
+ // Skip loading the app root in tests.
155
+ // This is handled by the testing-library utils
156
+ process.env.NODE_ENV !== 'test' &&
157
+ t.isIdentifier(parent.node.property, {
158
+ name: 'EXPO_ROUTER_APP_ROOT',
159
+ }) &&
160
+ !parent.parentPath.isAssignmentExpression()) {
161
+ parent.replaceWith(
162
+ // This is defined in Expo CLI when using Metro. It points to the relative path for the project app directory.
163
+ t.stringLiteral(getExpoRouterAppRoot(projectRoot)));
164
+ }
165
+ else if (
166
+ // Expose the app route import mode.
167
+ platform &&
168
+ t.isIdentifier(parent.node.property, {
169
+ name: 'EXPO_ROUTER_IMPORT_MODE_' + platform.toUpperCase(),
170
+ }) &&
171
+ !parent.parentPath.isAssignmentExpression()) {
172
+ parent.replaceWith(t.stringLiteral(getExpoRouterImportMode(projectRoot, platform)));
173
+ }
174
+ },
175
+ },
176
+ };
177
+ }
178
+ exports.expoRouterBabelPlugin = expoRouterBabelPlugin;
@@ -0,0 +1,24 @@
1
+ import { ConfigAPI, TransformOptions } from '@babel/core';
2
+ type BabelPresetExpoPlatformOptions = {
3
+ /** Enable or disable adding the Reanimated plugin by default. @default `true` */
4
+ reanimated?: boolean;
5
+ /** @deprecated Set `jsxRuntime: 'classic'` to disable automatic JSX handling. */
6
+ useTransformReactJSXExperimental?: boolean;
7
+ /** Change the policy for handling JSX in a file. Passed to `plugin-transform-react-jsx`. @default `'automatic'` */
8
+ jsxRuntime?: 'classic' | 'automatic';
9
+ /** Change the source module ID to use when importing an automatic JSX import. Only applied when `jsxRuntime` is `'automatic'` (default). Passed to `plugin-transform-react-jsx`. @default `'react'` */
10
+ jsxImportSource?: string;
11
+ lazyImports?: boolean;
12
+ disableImportExportTransform?: boolean;
13
+ disableFlowStripTypesTransform?: boolean;
14
+ enableBabelRuntime?: boolean;
15
+ unstable_transformProfile?: 'default' | 'hermes-stable' | 'hermes-canary';
16
+ };
17
+ export type BabelPresetExpoOptions = BabelPresetExpoPlatformOptions & {
18
+ /** Web-specific settings. */
19
+ web?: BabelPresetExpoPlatformOptions;
20
+ /** Native-specific settings. */
21
+ native?: BabelPresetExpoPlatformOptions;
22
+ };
23
+ declare function babelPresetExpo(api: ConfigAPI, options?: BabelPresetExpoOptions): TransformOptions;
24
+ export default babelPresetExpo;
package/build/index.js ADDED
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const common_1 = require("./common");
4
+ const expo_inline_manifest_plugin_1 = require("./expo-inline-manifest-plugin");
5
+ const expo_router_plugin_1 = require("./expo-router-plugin");
6
+ const inline_env_vars_1 = require("./inline-env-vars");
7
+ const lazyImports_1 = require("./lazyImports");
8
+ function getOptions(options, platform) {
9
+ const tag = platform === 'web' ? 'web' : 'native';
10
+ return {
11
+ ...options,
12
+ ...options[tag],
13
+ };
14
+ }
15
+ function babelPresetExpo(api, options = {}) {
16
+ const bundler = api.caller(common_1.getBundler);
17
+ const isWebpack = bundler === 'webpack';
18
+ let platform = api.caller((caller) => caller?.platform);
19
+ const engine = api.caller((caller) => caller?.engine) ?? 'default';
20
+ const isDev = api.caller(common_1.getIsDev);
21
+ const isFastRefreshEnabled = api.caller(common_1.getIsFastRefreshEnabled);
22
+ const baseUrl = api.caller(common_1.getBaseUrl);
23
+ // Unlike `isDev`, this will be `true` when the bundler is explicitly set to `production`,
24
+ // i.e. `false` when testing, development, or used with a bundler that doesn't specify the correct inputs.
25
+ const isProduction = api.caller(common_1.getIsProd);
26
+ const inlineEnvironmentVariables = api.caller(common_1.getInlineEnvVarsEnabled);
27
+ // If the `platform` prop is not defined then this must be a custom config that isn't
28
+ // defining a platform in the babel-loader. Currently this may happen with Next.js + Expo web.
29
+ if (!platform && isWebpack) {
30
+ platform = 'web';
31
+ }
32
+ const platformOptions = getOptions(options, platform);
33
+ if (platformOptions.disableImportExportTransform == null) {
34
+ if (platform === 'web') {
35
+ // Only disable import/export transform when Webpack is used because
36
+ // Metro does not support tree-shaking.
37
+ platformOptions.disableImportExportTransform = isWebpack;
38
+ }
39
+ else {
40
+ platformOptions.disableImportExportTransform = false;
41
+ }
42
+ }
43
+ if (platformOptions.unstable_transformProfile == null) {
44
+ platformOptions.unstable_transformProfile = engine === 'hermes' ? 'hermes-stable' : 'default';
45
+ }
46
+ // Note that if `options.lazyImports` is not set (i.e., `null` or `undefined`),
47
+ // `@react-native/babel-preset` will handle it.
48
+ const lazyImportsOption = platformOptions?.lazyImports;
49
+ const extraPlugins = [];
50
+ if (engine !== 'hermes') {
51
+ // `@react-native/babel-preset` configures this plugin with `{ loose: true }`, which breaks all
52
+ // getters and setters in spread objects. We need to add this plugin ourself without that option.
53
+ // @see https://github.com/expo/expo/pull/11960#issuecomment-887796455
54
+ extraPlugins.push([
55
+ require.resolve('@babel/plugin-proposal-object-rest-spread'),
56
+ { loose: false },
57
+ ]);
58
+ }
59
+ else {
60
+ // This is added back on hermes to ensure the react-jsx-dev plugin (`@babel/preset-react`) works as expected when
61
+ // JSX is used in a function body. This is technically not required in production, but we
62
+ // should retain the same behavior since it's hard to debug the differences.
63
+ extraPlugins.push(require('@babel/plugin-transform-parameters'));
64
+ }
65
+ if (isProduction && (0, common_1.hasModule)('metro-transform-plugins')) {
66
+ // Metro applies this plugin too but it does it after the imports have been transformed which breaks
67
+ // the plugin. Here, we'll apply it before the commonjs transform, in production, to ensure `Platform.OS`
68
+ // is replaced with a string literal and `__DEV__` is converted to a boolean.
69
+ // Applying early also means that web can be transformed before the `react-native-web` transform mutates the import.
70
+ extraPlugins.push([
71
+ require('metro-transform-plugins/src/inline-plugin.js'),
72
+ {
73
+ dev: isDev,
74
+ inlinePlatform: true,
75
+ platform,
76
+ },
77
+ ]);
78
+ }
79
+ if (platformOptions.useTransformReactJSXExperimental != null) {
80
+ throw new Error(`babel-preset-expo: The option 'useTransformReactJSXExperimental' has been removed in favor of { jsxRuntime: 'classic' }.`);
81
+ }
82
+ // Allow jest tests to redefine the environment variables.
83
+ if (process.env.NODE_ENV !== 'test') {
84
+ extraPlugins.push([
85
+ inline_env_vars_1.expoInlineTransformEnvVars,
86
+ {
87
+ // These values should not be prefixed with `EXPO_PUBLIC_`, so we don't
88
+ // squat user-defined environment variables.
89
+ EXPO_BASE_URL: baseUrl,
90
+ },
91
+ ]);
92
+ }
93
+ // Only apply in non-server, for metro-only, in production environments, when the user hasn't disabled the feature.
94
+ // Webpack uses DefinePlugin for environment variables.
95
+ // Development uses an uncached serializer.
96
+ // Servers read from the environment.
97
+ // Users who disable the feature may be using a different babel plugin.
98
+ if (inlineEnvironmentVariables) {
99
+ extraPlugins.push(inline_env_vars_1.expoInlineEnvVars);
100
+ }
101
+ if (platform === 'web') {
102
+ extraPlugins.push(require.resolve('babel-plugin-react-native-web'));
103
+ // Webpack uses the DefinePlugin to provide the manifest to `expo-constants`.
104
+ if (bundler !== 'webpack') {
105
+ extraPlugins.push(expo_inline_manifest_plugin_1.expoInlineManifestPlugin);
106
+ }
107
+ }
108
+ if ((0, common_1.hasModule)('expo-router')) {
109
+ extraPlugins.push(expo_router_plugin_1.expoRouterBabelPlugin);
110
+ }
111
+ if (isFastRefreshEnabled) {
112
+ extraPlugins.push([
113
+ require('react-refresh/babel'),
114
+ {
115
+ // We perform the env check to enable `isFastRefreshEnabled`.
116
+ skipEnvCheck: true,
117
+ },
118
+ ]);
119
+ }
120
+ return {
121
+ presets: [
122
+ [
123
+ // We use `require` here instead of directly using the package name because we want to
124
+ // specifically use the `@react-native/babel-preset` installed by this package (ex:
125
+ // `babel-preset-expo/node_modules/`). This way the preset will not change unintentionally.
126
+ // Reference: https://github.com/expo/expo/pull/4685#discussion_r307143920
127
+ require('@react-native/babel-preset'),
128
+ {
129
+ // Defaults to undefined, set to `true` to disable `@babel/plugin-transform-flow-strip-types`
130
+ disableFlowStripTypesTransform: platformOptions.disableFlowStripTypesTransform,
131
+ // Defaults to undefined, set to `false` to disable `@babel/plugin-transform-runtime`
132
+ enableBabelRuntime: platformOptions.enableBabelRuntime,
133
+ // This reduces the amount of transforms required, as Hermes supports many modern language features.
134
+ unstable_transformProfile: platformOptions.unstable_transformProfile,
135
+ // Set true to disable `@babel/plugin-transform-react-jsx` and
136
+ // the deprecated packages `@babel/plugin-transform-react-jsx-self`, and `@babel/plugin-transform-react-jsx-source`.
137
+ //
138
+ // Otherwise, you'll sometime get errors like the following (starting in Expo SDK 43, React Native 64, React 17):
139
+ //
140
+ // TransformError App.js: /path/to/App.js: Duplicate __self prop found. You are most likely using the deprecated transform-react-jsx-self Babel plugin.
141
+ // Both __source and __self are automatically set when using the automatic jsxRuntime. Please remove transform-react-jsx-source and transform-react-jsx-self from your Babel config.
142
+ useTransformReactJSXExperimental: true,
143
+ // This will never be used regardless because `useTransformReactJSXExperimental` is set to `true`.
144
+ // https://github.com/facebook/react-native/blob/a4a8695cec640e5cf12be36a0c871115fbce9c87/packages/react-native-babel-preset/src/configs/main.js#L151
145
+ withDevTools: false,
146
+ disableImportExportTransform: platformOptions.disableImportExportTransform,
147
+ lazyImportExportTransform: lazyImportsOption === true
148
+ ? (importModuleSpecifier) => {
149
+ // Do not lazy-initialize packages that are local imports (similar to `lazy: true`
150
+ // behavior) or are in the blacklist.
151
+ return !(importModuleSpecifier.includes('./') || lazyImports_1.lazyImports.has(importModuleSpecifier));
152
+ }
153
+ : // Pass the option directly to `@react-native/babel-preset`, which in turn
154
+ // passes it to `babel-plugin-transform-modules-commonjs`
155
+ lazyImportsOption,
156
+ },
157
+ ],
158
+ // React support with similar options to Metro.
159
+ // We override this logic outside of the metro preset so we can add support for
160
+ // React 17 automatic JSX transformations.
161
+ // The only known issue is the plugin `@babel/plugin-transform-react-display-name` will be run twice,
162
+ // once in the Metro plugin, and another time here.
163
+ [
164
+ require('@babel/preset-react'),
165
+ {
166
+ development: isDev,
167
+ // Defaults to `automatic`, pass in `classic` to disable auto JSX transformations.
168
+ runtime: platformOptions?.jsxRuntime || 'automatic',
169
+ ...(platformOptions &&
170
+ platformOptions.jsxRuntime !== 'classic' && {
171
+ importSource: (platformOptions && platformOptions.jsxImportSource) || 'react',
172
+ }),
173
+ // NOTE: Unexposed props:
174
+ // pragma?: string;
175
+ // pragmaFrag?: string;
176
+ // pure?: string;
177
+ // throwIfNamespace?: boolean;
178
+ // useBuiltIns?: boolean;
179
+ // useSpread?: boolean;
180
+ },
181
+ ],
182
+ ],
183
+ plugins: [
184
+ ...extraPlugins,
185
+ // TODO: Remove
186
+ [require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }],
187
+ require.resolve('@babel/plugin-transform-export-namespace-from'),
188
+ // Automatically add `react-native-reanimated/plugin` when the package is installed.
189
+ // TODO: Move to be a customTransformOption.
190
+ (0, common_1.hasModule)('react-native-reanimated') &&
191
+ platformOptions.reanimated !== false && [require.resolve('react-native-reanimated/plugin')],
192
+ ].filter(Boolean),
193
+ };
194
+ }
195
+ exports.default = babelPresetExpo;
196
+ module.exports = babelPresetExpo;
@@ -0,0 +1,13 @@
1
+ import { ConfigAPI, PluginObj, types } from '@babel/core';
2
+ export declare function expoInlineEnvVars(api: ConfigAPI & {
3
+ types: typeof types;
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;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.expoInlineTransformEnvVars = exports.expoInlineEnvVars = void 0;
4
+ const debug = require('debug')('expo:babel:env-vars');
5
+ function expoInlineEnvVars(api) {
6
+ const { types: t } = api;
7
+ function isFirstInAssign(path) {
8
+ return t.isAssignmentExpression(path.parent) && path.parent.left === path.node;
9
+ }
10
+ return {
11
+ name: 'expo-inline-production-environment-variables',
12
+ visitor: {
13
+ MemberExpression(path, state) {
14
+ const filename = state.filename;
15
+ if (path.get('object').matchesPattern('process.env')) {
16
+ // @ts-expect-error: missing types
17
+ const key = path.toComputedKey();
18
+ if (t.isStringLiteral(key) &&
19
+ !isFirstInAssign(path) &&
20
+ key.value.startsWith('EXPO_PUBLIC_')) {
21
+ debug('Inlining environment variable in %s: %s', filename, key.value);
22
+ path.replaceWith(t.valueToNode(process.env[key.value]));
23
+ }
24
+ }
25
+ },
26
+ },
27
+ };
28
+ }
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,2 @@
1
+ /** These Expo packages may have side-effects and should not be lazily initialized. */
2
+ export declare const lazyImports: Set<string>;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.lazyImports = void 0;
4
+ /** These Expo packages may have side-effects and should not be lazily initialized. */
5
+ exports.lazyImports = new Set(['expo', 'expo-asset', 'expo-task-manager']);
@@ -0,0 +1,6 @@
1
+ /**
2
+ * These Expo packages may have side-effects and should not be lazily initialized.
3
+ */
4
+ 'use strict';
5
+
6
+ module.exports = require('./build/lazyImports').lazyImports;
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "babel-preset-expo",
3
+ "version": "0.0.0-canary-20231122-1af9191",
4
+ "description": "The Babel preset for Expo projects",
5
+ "main": "build/index.js",
6
+ "files": [
7
+ "build",
8
+ "lazy-imports-blacklist.js"
9
+ ],
10
+ "scripts": {
11
+ "build": "expo-module build",
12
+ "clean": "expo-module clean",
13
+ "lint": "expo-module lint",
14
+ "test": "expo-module test",
15
+ "prepare": "expo-module prepare",
16
+ "prepublishOnly": "expo-module prepublishOnly",
17
+ "expo-module": "expo-module"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/expo/expo.git",
22
+ "directory": "packages/babel-preset-expo"
23
+ },
24
+ "keywords": [
25
+ "babel",
26
+ "babel-preset",
27
+ "expo",
28
+ "expo-web",
29
+ "react-native",
30
+ "react-native-web",
31
+ "metro",
32
+ "webpack"
33
+ ],
34
+ "author": "Expo <support@expo.dev>",
35
+ "license": "MIT",
36
+ "bugs": {
37
+ "url": "https://github.com/expo/expo/issues"
38
+ },
39
+ "homepage": "https://github.com/expo/expo/tree/main/packages/babel-preset-expo#readme",
40
+ "eslintConfig": {
41
+ "extends": "universe/node"
42
+ },
43
+ "dependencies": {
44
+ "@babel/plugin-proposal-decorators": "^7.12.9",
45
+ "@babel/plugin-proposal-object-rest-spread": "^7.12.13",
46
+ "@babel/plugin-transform-export-namespace-from": "^7.22.11",
47
+ "@babel/preset-env": "^7.20.0",
48
+ "@babel/preset-react": "^7.22.15",
49
+ "@babel/plugin-transform-parameters": "^7.22.15",
50
+ "@react-native/babel-preset": "^0.73.18",
51
+ "babel-plugin-react-native-web": "~0.18.10",
52
+ "react-refresh": "0.14.0"
53
+ },
54
+ "devDependencies": {
55
+ "@babel/core": "^7.20.0",
56
+ "jest": "^29.2.1"
57
+ },
58
+ "gitHead": "1af91912cdf13b6f7fa0a8207dc9f024132a9a72"
59
+ }