babel-preset-expo 0.0.1-canary-20240222-c729a13 → 0.0.1-canary-20240305-e60019e

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.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Copyright © 2024 650 Industries.
3
+ */
4
+ import { ConfigAPI, types } from '@babel/core';
5
+ export declare function reactClientReferencesPlugin(api: ConfigAPI & {
6
+ types: typeof types;
7
+ }): babel.PluginObj;
@@ -0,0 +1,98 @@
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
+ const url_1 = __importDefault(require("url"));
8
+ function reactClientReferencesPlugin(api) {
9
+ const { types: t } = api;
10
+ const reactServerAdapter = 'react-server-dom-webpack/server';
11
+ return {
12
+ name: 'expo-client-references',
13
+ visitor: {
14
+ Program(path, state) {
15
+ const isUseClient = path.node.directives.some((directive) => directive.value.value === 'use client');
16
+ // 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
17
+ const isUseServer = path.node.directives.some((directive) => directive.value.value === 'use server');
18
+ if (isUseClient && isUseServer) {
19
+ throw path.buildCodeFrameError("It's not possible to have both `use client` and `use server` directives in the same file.");
20
+ }
21
+ const filePath = state.file.opts.filename;
22
+ if (!filePath) {
23
+ // This can happen in tests or systems that use Babel standalone.
24
+ throw new Error('[Babel] Expected a filename to be set in the state');
25
+ }
26
+ const outputKey = url_1.default.pathToFileURL(filePath).href;
27
+ // File starts with "use client" directive.
28
+ if (!isUseClient && !isUseServer) {
29
+ // Do nothing for code that isn't marked as a client component.
30
+ return;
31
+ }
32
+ // Clear the body
33
+ if (isUseClient) {
34
+ path.node.body = [];
35
+ path.node.directives = [];
36
+ // Inject the following:
37
+ //
38
+ // module.exports = require('react-server-dom-webpack/server').createClientModuleProxy(`${outputKey}#${filePath}`)
39
+ // TODO: Use `require.resolveWeak` instead of `filePath` to avoid leaking the file path.
40
+ // module.exports = require('react-server-dom-webpack/server').createClientModuleProxy(`${outputKey}#${require.resolveWeak(filePath)}`)
41
+ path.pushContainer('body', t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.identifier('module'), t.identifier('exports')), t.callExpression(t.memberExpression(t.callExpression(t.identifier('require'), [
42
+ t.stringLiteral(reactServerAdapter),
43
+ ]), t.identifier('createClientModuleProxy')),
44
+ // `${outputKey}#${require.resolveWeak(filePath)}`
45
+ [t.stringLiteral(outputKey)]))));
46
+ }
47
+ else {
48
+ // Inject the following:
49
+ //
50
+ // ;(() => {
51
+ // const { registerServerReference } = require('react-server-dom-webpack/server');
52
+ // if (typeof module.exports === 'function') registerServerReference(module.exports, moduleId, null);
53
+ // else {
54
+ // for (const key in module.exports) {
55
+ // if (typeof module.exports[key] === 'function') {
56
+ // registerServerReference(module.exports[key], moduleId, key);
57
+ // }
58
+ // }
59
+ // }
60
+ // })()
61
+ const mmexp = t.memberExpression(t.callExpression(t.identifier('require'), [t.stringLiteral(reactServerAdapter)]), t.identifier('registerServerReference'));
62
+ // Create the loop body
63
+ const loopBody = t.blockStatement([
64
+ t.ifStatement(t.binaryExpression('===', t.unaryExpression('typeof', t.memberExpression(t.memberExpression(t.identifier('module'), t.identifier('exports')), t.identifier('key'), true)), t.stringLiteral('function')), t.expressionStatement(t.callExpression(mmexp, [
65
+ t.memberExpression(t.memberExpression(t.identifier('module'), t.identifier('exports')), t.identifier('key'), true),
66
+ t.stringLiteral(outputKey),
67
+ t.identifier('key'),
68
+ ]))),
69
+ ]);
70
+ // Create the for-in loop
71
+ const forInStatement = t.forInStatement(t.variableDeclaration('const', [t.variableDeclarator(t.identifier('key'))]), t.memberExpression(t.identifier('module'), t.identifier('exports')), loopBody);
72
+ path.pushContainer('body', t.expressionStatement(t.callExpression(t.arrowFunctionExpression([], t.blockStatement([
73
+ t.ifStatement(t.binaryExpression('===', t.unaryExpression('typeof', t.memberExpression(t.identifier('module'), t.identifier('exports'))), t.stringLiteral('function')),
74
+ // registerServerReference(module.exports, moduleId, null);
75
+ t.blockStatement([
76
+ t.expressionStatement(t.callExpression(mmexp, [
77
+ t.memberExpression(t.identifier('module'), t.identifier('exports')),
78
+ t.stringLiteral(outputKey),
79
+ t.nullLiteral(),
80
+ ])),
81
+ ]),
82
+ // Else
83
+ t.blockStatement([
84
+ // for (const key in module.exports) {
85
+ // if (typeof module.exports[key] === 'function') {
86
+ // registerServerReference(module.exports[key], moduleId, key);
87
+ // }
88
+ // }
89
+ forInStatement,
90
+ ])),
91
+ ])), [])));
92
+ //
93
+ }
94
+ },
95
+ },
96
+ };
97
+ }
98
+ 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,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.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,6 +21,7 @@ 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 isReactServer = api.caller(common_1.getIsReactServer);
21
25
  const isFastRefreshEnabled = api.caller(common_1.getIsFastRefreshEnabled);
22
26
  const baseUrl = api.caller(common_1.getBaseUrl);
23
27
  const supportsStaticESM = api.caller((caller) => caller?.supportsStaticESM);
@@ -106,6 +110,14 @@ function babelPresetExpo(api, options = {}) {
106
110
  if ((0, common_1.hasModule)('expo-router')) {
107
111
  extraPlugins.push(expo_router_plugin_1.expoRouterBabelPlugin);
108
112
  }
113
+ // Ensure these only run when the user opts-in to bundling for a react server to prevent unexpected behavior for
114
+ // users who are bundling using the client-only system.
115
+ if (isReactServer) {
116
+ extraPlugins.push(client_module_proxy_plugin_1.reactClientReferencesPlugin);
117
+ extraPlugins.push(restricted_react_api_plugin_1.environmentRestrictedReactAPIsPlugin);
118
+ }
119
+ // 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.
120
+ extraPlugins.push(environment_restricted_imports_1.environmentRestrictedImportsPlugin);
109
121
  if (isFastRefreshEnabled) {
110
122
  extraPlugins.push([
111
123
  require('react-refresh/babel'),
@@ -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,94 @@
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
+ // Add special handling for `Component` since it is different to a function API.
59
+ 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.`);
60
+ }
61
+ }
62
+ else {
63
+ const importName = t.isStringLiteral(specifier.local)
64
+ ? specifier.local
65
+ : specifier.local.name;
66
+ // Save namespace import for later checks in MemberExpression
67
+ path.scope.setData('importedNamespace', { [importName]: sourceValue });
68
+ }
69
+ });
70
+ }
71
+ },
72
+ MemberExpression(path) {
73
+ const importedNamespaces = path.scope.getData('importedNamespace') || {};
74
+ Object.keys(importedNamespaces).forEach((namespace) => {
75
+ const library = importedNamespaces[namespace];
76
+ const forbiddenList = FORBIDDEN_IMPORTS[library];
77
+ const objectName = t.isIdentifier(path.node.object) ? path.node.object.name : null;
78
+ if (objectName === namespace &&
79
+ forbiddenList &&
80
+ t.isIdentifier(path.node.property) &&
81
+ forbiddenList.includes(path.node.property.name)) {
82
+ // Throw a special error for class components since it's not always clear why they cannot be used in RSC.
83
+ // e.g. https://x.com/Baconbrix/status/1749223042440392806?s=20
84
+ if (path.node.property.name === 'Component') {
85
+ 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.`);
86
+ }
87
+ 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.`);
88
+ }
89
+ });
90
+ },
91
+ },
92
+ };
93
+ }
94
+ exports.environmentRestrictedReactAPIsPlugin = environmentRestrictedReactAPIsPlugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "babel-preset-expo",
3
- "version": "0.0.1-canary-20240222-c729a13",
3
+ "version": "0.0.1-canary-20240305-e60019e",
4
4
  "description": "The Babel preset for Expo projects",
5
5
  "main": "build/index.js",
6
6
  "files": [
@@ -53,8 +53,8 @@
53
53
  },
54
54
  "devDependencies": {
55
55
  "@babel/core": "^7.20.0",
56
- "expo-module-scripts": "0.0.1-canary-20240222-c729a13",
56
+ "expo-module-scripts": "0.0.1-canary-20240305-e60019e",
57
57
  "jest": "^29.2.1"
58
58
  },
59
- "gitHead": "c729a13f936926743c13c021f569a22921455e49"
59
+ "gitHead": "e60019e11a6d46e330b57b18c64468a58d589875"
60
60
  }