Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(babel-preset-expo): auto add reanimated babel plugin when available #23798

Merged
merged 8 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 0 additions & 30 deletions docs/pages/versions/unversioned/sdk/reanimated.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,6 @@ import PlatformsSection from '~/components/plugins/PlatformsSection';

<APIInstallSection href="https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation" />

After the installation completes, you must also add the Babel plugin to **babel.config.js**:

```js babel.config.js
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: ['react-native-reanimated/plugin'],
};
};
```

After you add the Babel plugin, restart your development server and clear the bundler cache: `npx expo start --clear`.

> If you load other Babel plugins, the Reanimated plugin has to be the last item in the plugins array.

### Web support

**For web,** you'll have to install the [`@babel/plugin-proposal-export-namespace-from`](https://babeljs.io/docs/en/babel-plugin-proposal-export-namespace-from#installation) Babel plugin and update the **babel.config.js** to load it:

{/* prettier-ignore */}
```js babel.config.js
plugins: [
'@babel/plugin-proposal-export-namespace-from',
'react-native-reanimated/plugin',
],
```

After you add the Babel plugin, restart your development server and clear the bundler cache: `npx expo start --clear`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably add something here to indicate that you can remove the reanimated babel plugin that we automatically add, and link to the babel-preset-expo readme or docs for that


## Usage

You should refer to the [react-native-reanimated docs](https://docs.swmansion.com/react-native-reanimated/docs/) for more information on the API and its usage. But the following example (courtesy of that repo) is a quick way to get started.
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 invalidate cache when `react-native-reanimated` version changes or is added. ([#23798](https://github.com/expo/expo/pull/23798) by [@EvanBacon](https://github.com/EvanBacon))

### 🐛 Bug fixes

### 💡 Others
Expand Down
26 changes: 15 additions & 11 deletions packages/@expo/metro-config/build/ExpoMetroConfig.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/ExpoMetroConfig.js.map

Large diffs are not rendered by default.

29 changes: 17 additions & 12 deletions packages/@expo/metro-config/src/ExpoMetroConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,11 @@ export function getDefaultConfig(
// Add support for cjs (without platform extensions).
sourceExts.push('cjs');

const reanimatedVersion = getPkgVersion(projectRoot, 'react-native-reanimated');

let sassVersion: string | null = null;
if (options.isCSSEnabled) {
sassVersion = getSassVersion(projectRoot);
sassVersion = getPkgVersion(projectRoot, 'sass');
// Enable SCSS by default so we can provide a better error message
// when sass isn't installed.
sourceExts.push('scss', 'sass', 'css');
Expand All @@ -114,6 +116,7 @@ export function getDefaultConfig(
console.log(`- Exotic: ${isExotic}`);
console.log(`- Env Files: ${envFiles}`);
console.log(`- Sass: ${sassVersion}`);
console.log(`- Reanimated: ${reanimatedVersion}`);
console.log();
}
const {
Expand Down Expand Up @@ -186,6 +189,8 @@ export function getDefaultConfig(
? stableHash(JSON.stringify(pkg.browserslist)).toString('hex')
: null,
sassVersion,
// Ensure invalidation when the version changes due to the Babel plugin.
reanimatedVersion,

// `require.context` support
unstable_allowRequireContext: true,
Expand Down Expand Up @@ -222,17 +227,17 @@ export { MetroConfig, INTERNAL_CALLSITES_REGEX };
// re-export for legacy cases.
export const EXPO_DEBUG = env.EXPO_DEBUG;

function getSassVersion(projectRoot: string): string | null {
const sassPkg = resolveFrom.silent(projectRoot, 'sass');
if (!sassPkg) return null;
const sassPkgJson = findUpPackageJson(sassPkg);
if (!sassPkgJson) return null;
const pkg = JsonFile.read(sassPkgJson);

debug('sass package.json:', sassPkgJson);
const sassVersion = pkg.version;
if (typeof sassVersion === 'string') {
return sassVersion;
function getPkgVersion(projectRoot: string, pkgName: string): string | null {
const targetPkg = resolveFrom.silent(projectRoot, pkgName);
if (!targetPkg) return null;
const targetPkgJson = findUpPackageJson(targetPkg);
if (!targetPkgJson) return null;
const pkg = JsonFile.read(targetPkgJson);

debug(`${pkgName} package.json:`, targetPkgJson);
const pkgVersion = pkg.version;
if (typeof pkgVersion === 'string') {
return pkgVersion;
}

return null;
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 add `react-native-reanimated/plugin` when available. ([#23798](https://github.com/expo/expo/pull/23798) by [@EvanBacon](https://github.com/EvanBacon))

### 🐛 Bug fixes

### 💡 Others
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,13 @@ return React.createElement(_reactNative.View,null,React.createElement(_reactNati
}"
`;

exports[`metro 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:"/Users/evanbacon/Documents/GitHub/expo/packages/babel-preset-expo/__tests__/samples/worklet.js",sourceMap:"{\\"version\\":3,\\"mappings\\":\\"AAAA,oBAASA,CAAYC,UAAQ;EAE3BC,OAAO,CAACC,GAAG,CAAC,kCAAkC,CAAC;AACjD\\",\\"names\\":[\\"someWorklet\\",\\"greeting\\",\\"console\\",\\"log\\"],"sources":["[mock]/worklet.js"]}"};var someWorklet=function(){var _e=[new global.Error(),1,-27];var _f=function _f(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 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 _getRequireWildcardCache(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 uses the platform's react-native import 1`] = `
Expand Down Expand Up @@ -312,6 +319,13 @@ return React.createElement(View,null,React.createElement(Text,null,"Hello World"
}"
`;

exports[`webpack 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:"/Users/evanbacon/Documents/GitHub/expo/packages/babel-preset-expo/__tests__/samples/worklet.js",sourceMap:"{\\"version\\":3,\\"mappings\\":\\"AAAA,oBAASA,CAAYC,UAAQ;EAE3BC,OAAO,CAACC,GAAG,CAAC,kCAAkC,CAAC;AACjD\\",\\"names\\":[\\"someWorklet\\",\\"greeting\\",\\"console\\",\\"log\\"],"sources":["[mock]/worklet.js"]}"};var someWorklet=function(){var _e=[new global.Error(),1,-27];var _f=function _f(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[`webpack transpiles non-standard exports 1`] = `
"import*as _default from"./Animated";export{_default as
default};"
Expand Down
35 changes: 35 additions & 0 deletions packages/babel-preset-expo/__tests__/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,41 @@ export default function App() {
expect(code).toMatchSnapshot();
});

it(`supports reanimated worklets`, () => {
expect(require.resolve('react-native-reanimated/plugin')).toBeDefined();

const samplesPath = path.resolve(__dirname, 'samples/worklet.js');

const options = {
babelrc: false,
presets: [[preset, { jsxRuntime: 'automatic' }]],
// Make the snapshot easier to read
retainLines: true,
caller,
};

function stablePaths(src) {
return src.replace(
/\\"sources\\":\[\\".*\/babel-preset-expo\/__tests__\/samples\/worklet\.js\\"\]}/,
'"sources":["[mock]/worklet.js"]}'
);
}

const code = stablePaths(babel.transformFileSync(samplesPath, options).code);

expect(code).toMatchSnapshot();

expect(
stablePaths(
babel.transformFileSync(samplesPath, {
...options,
// Test that duplicate plugins make no difference
plugins: [require.resolve('react-native-reanimated/plugin')],
}).code
)
).toBe(code);
});

it(`supports classic JSX runtime`, () => {
const options = {
babelrc: false,
Expand Down
4 changes: 4 additions & 0 deletions packages/babel-preset-expo/__tests__/samples/worklet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function someWorklet(greeting) {
'worklet';
console.log("Hey I'm running on the UI thread");
}
3 changes: 3 additions & 0 deletions packages/babel-preset-expo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ module.exports = function (api, options = {}) {
platform === 'web' && [require.resolve('babel-plugin-react-native-web')],
isWebpack && platform !== 'web' && [require.resolve('./plugins/disable-ambiguous-requires')],
require.resolve('@babel/plugin-proposal-export-namespace-from'),

// Automatically add `react-native-reanimated/plugin` when the package is installed.
hasModule('react-native-reanimated') && [require.resolve('react-native-reanimated/plugin')],
].filter(Boolean),
};
};
Expand Down