Skip to content

Commit

Permalink
feat(cli): transform based on engine (#24672)
Browse files Browse the repository at this point in the history
# Why

- Reduce the amount of Babel transformations based on the jsEngine for a
particular platform.
- Hermes supports more modern language features than the default React
Native target, so we don't have to transpile as much. This is an
unstable feature that seems reasonable enough to turn on by default.
- Users can overwrite the default by using the same
`unstable_transformProfile` as before.
- Web will also use this newer transform as the majority of modern
browsers support more features than Hermes.

The following will now be disabled when using Hermes:

```
@babel/plugin-transform-computed-properties
@babel/plugin-transform-parameters
@babel/plugin-transform-shorthand-properties
@babel/plugin-proposal-optional-catch-binding
@babel/plugin-transform-function-name
@babel/plugin-transform-literals
@babel/plugin-proposal-numeric-separator
@babel/plugin-transform-sticky-regex
@babel/plugin-transform-spread
@babel/plugin-proposal-object-rest-spread
@babel/plugin-proposal-optional-chaining
@babel/plugin-proposal-nullish-coalescing-operator
@babel/plugin-transform-runtime -> regenerator
```

And `@babel/plugin-transform-named-capturing-groups-regex` will be
enabled.

Expressed as a diff:

<img width="862" alt="Screenshot 2023-09-28 at 6 03 17 PM"
src="https://github.com/expo/expo/assets/9664363/d90f0d9c-5b14-4e4e-9820-b88de9bbd6c8">


<!--
Please describe the motivation for this PR, and link to relevant GitHub
issues, forums posts, or feature requests.
-->

# How

- Add an engine flag to the URL to trigger a cache invalidation
per-platform if the app.json settings change.
- Pass the flag to babel where we can toggle on Hermes mode.

# Test Plan

- Tested in sandbox, also wrote unit tests for the syntax.

---------

Co-authored-by: Expo Bot <34669131+expo-bot@users.noreply.github.com>
  • Loading branch information
EvanBacon and expo-bot committed Sep 29, 2023
1 parent 2863253 commit 2dac7b9
Show file tree
Hide file tree
Showing 15 changed files with 259 additions and 18 deletions.
2 changes: 2 additions & 0 deletions packages/@expo/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### 🎉 New features

- Automatically optimize transformations based on Hermes usage. ([#24672](https://github.com/expo/expo/pull/24672) by [@EvanBacon](https://github.com/EvanBacon))

### 🐛 Bug fixes

- Resolve browser shims with mismatched extensions. ([#24671](https://github.com/expo/expo/pull/24671) by [@EvanBacon](https://github.com/EvanBacon))
Expand Down
2 changes: 1 addition & 1 deletion packages/@expo/cli/e2e/__tests__/start-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ describe('server', () => {

// URLs
expect(manifest.launchAsset.url).toBe(
'http://127.0.0.1:8081/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&hot=false'
'http://127.0.0.1:8081/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&hot=false&transform.engine=hermes'
);
expect(manifest.extra.expoGo.debuggerHost).toBe('127.0.0.1:8081');
expect(manifest.extra.expoGo.mainModuleName).toBe('node_modules/expo/AppEntry');
Expand Down
12 changes: 11 additions & 1 deletion packages/@expo/cli/src/start/server/getStaticRenderFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type StaticRenderOptions = {
minify?: boolean;
platform?: string;
environment?: 'node';
engine?: 'hermes';
};

const moveStaticRenderFunction = memoize(async (projectRoot: string, requiredModuleId: string) => {
Expand Down Expand Up @@ -100,7 +101,13 @@ export async function createMetroEndpointAsync(
projectRoot: string,
devServerUrl: string,
absoluteFilePath: string,
{ dev = false, platform = 'web', minify = false, environment }: StaticRenderOptions = {}
{
dev = false,
platform = 'web',
minify = false,
environment,
engine = 'hermes',
}: StaticRenderOptions = {}
): Promise<string> {
const root = getMetroServerRoot(projectRoot);
const safeOtherFile = await ensureFileInRootDirectory(projectRoot, absoluteFilePath);
Expand All @@ -112,6 +119,9 @@ export async function createMetroEndpointAsync(
if (environment) {
url += `&resolver.environment=${environment}&transform.environment=${environment}`;
}
if (engine) {
url += `&transform.engine=${engine}`;
}
return url;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { resolveGoogleServicesFile, resolveManifestAssets } from './resolveAsset
import { resolveAbsoluteEntryPoint } from './resolveEntryPoint';
import { parsePlatformHeader, RuntimePlatform } from './resolvePlatform';
import { ServerHeaders, ServerNext, ServerRequest, ServerResponse } from './server.types';
import { isEnableHermesManaged } from '../../../export/exportHermes';
import * as Log from '../../../log';
import { env } from '../../../utils/env';
import { stripExtension } from '../../../utils/url';
Expand Down Expand Up @@ -84,6 +85,7 @@ export function createBundleUrlPath({
serializerOutput,
serializerIncludeMaps,
lazy,
engine,
}: {
platform: string;
mainModuleName: string;
Expand All @@ -93,6 +95,7 @@ export function createBundleUrlPath({
serializerOutput?: 'static';
serializerIncludeMaps?: boolean;
lazy?: boolean;
engine?: 'hermes';
}): string {
const queryParams = new URLSearchParams({
platform: encodeURIComponent(platform),
Expand All @@ -108,6 +111,11 @@ export function createBundleUrlPath({
if (minify) {
queryParams.append('minify', String(minify));
}

if (engine) {
queryParams.append('transform.engine', engine);
}

if (environment) {
queryParams.append('resolver.environment', environment);
queryParams.append('transform.environment', environment);
Expand Down Expand Up @@ -191,6 +199,8 @@ export abstract class ManifestMiddleware<
// Read from headers
const mainModuleName = this.resolveMainModuleName(projectConfig, platform);

const isHermesEnabled = isEnableHermesManaged(projectConfig.exp, platform);

// Create the manifest and set fields within it
const expoGoConfig = this.getExpoGoConfig({
mainModuleName,
Expand All @@ -203,6 +213,7 @@ export abstract class ManifestMiddleware<
platform,
mainModuleName,
hostname,
engine: isHermesEnabled ? 'hermes' : undefined,
});

// Resolve all assets and set them on the manifest as URLs
Expand Down Expand Up @@ -253,17 +264,20 @@ export abstract class ManifestMiddleware<
platform,
mainModuleName,
hostname,
engine,
}: {
platform: string;
hostname?: string | null;
mainModuleName: string;
engine?: 'hermes';
}): string {
const path = createBundleUrlPath({
mode: this.options.mode ?? 'development',
minify: this.options.minify,
platform,
mainModuleName,
lazy: shouldEnableAsyncImports(this.projectRoot),
engine,
});

return (
Expand All @@ -275,12 +289,14 @@ export abstract class ManifestMiddleware<
);
}

public _getBundleUrlPath({
private _getBundleUrlPath({
platform,
mainModuleName,
engine,
}: {
platform: string;
mainModuleName: string;
engine?: 'hermes';
}): string {
const queryParams = new URLSearchParams({
platform: encodeURIComponent(platform),
Expand All @@ -291,7 +307,9 @@ export abstract class ManifestMiddleware<
if (shouldEnableAsyncImports(this.projectRoot)) {
queryParams.append('lazy', String(true));
}

if (engine) {
queryParams.append('transform.engine', String(engine));
}
if (this.options.minify) {
queryParams.append('minify', String(this.options.minify));
}
Expand Down Expand Up @@ -362,6 +380,8 @@ export abstract class ManifestMiddleware<
return this._getBundleUrlPath({
platform,
mainModuleName,
// Hermes doesn't support more modern JS features than most, if not all, modern browser.
engine: 'hermes',
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ describe('checkBrowserRequestAsync', () => {
// NOTE(EvanBacon): Browsers won't pass the `expo-platform` header so we need to
// provide the `platform=web` query parameter in order for the multi-platform dev server
// to return the correct bundle.
'/index.bundle?platform=web&dev=true&hot=false',
'/index.bundle?platform=web&dev=true&hot=false&transform.engine=hermes',
],
});
expect(res.setHeader).toBeCalledWith('Content-Type', 'text/html');
Expand Down
2 changes: 2 additions & 0 deletions packages/@expo/metro-config/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### 🎉 New features

- Automatically optimize transformations based on Hermes usage. ([#24672](https://github.com/expo/expo/pull/24672) by [@EvanBacon](https://github.com/EvanBacon))

### 🐛 Bug fixes

### 💡 Others
Expand Down
7 changes: 5 additions & 2 deletions packages/@expo/metro-config/build/babel-transformer.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/@expo/metro-config/build/babel-transformer.js.map

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions packages/@expo/metro-config/src/babel-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ const transform: BabelTransformer['transform'] = ({
// Empower the babel preset to know the env it's bundling for.
// Metro automatically updates the cache to account for the custom transform options.
isServer: options.customTransformOptions?.environment === 'node',
// Pass the engine to babel so we can automatically transpile for the correct
// target environment.
engine: options.customTransformOptions?.engine,
},
ast: true,

Expand Down
2 changes: 2 additions & 0 deletions packages/babel-preset-expo/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

### 🎉 New features

- Automatically optimize transformations based on Hermes usage. ([#24672](https://github.com/expo/expo/pull/24672) by [@EvanBacon](https://github.com/EvanBacon))

### 🐛 Bug fixes

### 💡 Others
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-preset-expo/build/index.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions packages/babel-preset-expo/build/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,59 @@ exports[`metro uses the platform's react-native import 1`] = `
var _reactNative=require("react-native");"
`;
exports[`metro+hermes aliases @expo/vector-icons 1`] = `
"
require("@expo/vector-icons");
require("@expo/vector-icons");
imposter.require('react-native-vector-icons');
imposter.import('react-native-vector-icons');"
`;
exports[`metro+hermes composes with babel-plugin-module-resolver 1`] = `
"
require("react-native");
require("@expo/vector-icons");"
`;
exports[`metro+hermes supports automatic JSX runtime 1`] = `
"Object.defineProperty(exports,"__esModule",{value:true});exports.default=App;
var _reactNative=require("react-native");var _jsxRuntime=require("react/jsx-runtime");
function App(){
return(0,_jsxRuntime.jsx)(_reactNative.View,{children:(0,_jsxRuntime.jsx)(_reactNative.Text,{children:"Hello World"})});
}"
`;
exports[`metro+hermes supports classic JSX runtime 1`] = `
"Object.defineProperty(exports,"__esModule",{value:true});exports.default=App;
var _reactNative=require("react-native");
function App(){
return React.createElement(_reactNative.View,null,React.createElement(_reactNative.Text,null,"Hello World"));
}"
`;
exports[`metro+hermes supports disabling reanimated 1`] = `
"
function someWorklet(greeting){
'worklet';
console.log("Hey I'm running on the UI thread");
}"
`;
exports[`metro+hermes supports reanimated worklets 1`] = `
"var _worklet_2797951844470_init_data={code:"function someWorklet(greeting) {\\n console.log(\\"Hey I'm running on the UI thread\\");\\n}",location:"[mock]/worklet.js",sourceMap:"{\\"version\\":3,\\"mappings\\":\\"AAAA;EAAAA;AACA\\",\\"names\\":[\\"console\\"],\\"sources\\":[\\"[mock]/worklet.js\\"]}"};var
someWorklet=function(){var _e=[new global.Error(),1,-27];var _f=function(greeting){
console.log("Hey I'm running on the UI thread");
};_f._closure={};_f.__initData=_worklet_2797951844470_init_data;_f.__workletHash=2797951844470;_f.__stackDetails=_e;_f.__version="3.3.0";return _f;}();"
`;
exports[`metro+hermes transpiles non-standard exports 1`] = `"Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _default=_interopRequireWildcard(require("./Animated"));exports.default=_default;function _getRequireWildcardCache(nodeInterop){if(typeof WeakMap!=="function")return null;var cacheBabelInterop=new WeakMap();var cacheNodeInterop=new WeakMap();return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop;})(nodeInterop);}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule){return obj;}if(obj===null||typeof obj!=="object"&&typeof obj!=="function"){return{default:obj};}var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj)){return cache.get(obj);}var newObj={};var hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj){if(key!=="default"&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;if(desc&&(desc.get||desc.set)){Object.defineProperty(newObj,key,desc);}else{newObj[key]=obj[key];}}}newObj.default=obj;if(cache){cache.set(obj,newObj);}return newObj;}"`;
exports[`metro+hermes uses the platform's react-native import 1`] = `
"
var _reactNative=require("react-native");"
`;
exports[`webpack aliases @expo/vector-icons 1`] = `
"
import"@expo/vector-icons";
Expand Down

0 comments on commit 2dac7b9

Please sign in to comment.