Skip to content

Commit

Permalink
🌲 Reduce bundle size on Web by 80% (#3278)
Browse files Browse the repository at this point in the history
## Description

<!--
Description and motivation for this PR.

Inlude Fixes #<number> if this is fixing some issue.

Fixes # .
-->

Fixes #2843.

Reduces Reanimated's bundle size on Web from `85kb` to `17kb`.

## Changes

Zero user-facing changes are in this PR. It is strictly file organization changes to optimize Reanimated for tree shaking. After extensive testing, this PR reduced Reanimated's bundle size on Web by almost 80%.

In `package.json`, you'll notice that there is a new `sideEffects` array. This is a way to tell Webpack which files need to run global code rather than as standalone modules. I looked through every file that runs on Web which edits `global` at the top-level and found 2 files. Ideally, we could move all side effects into a single file rather than colocating them with other code that may go unused. But for now, this works. (Update: I moved some into their own file)

More on Webpack tree shaking and the `sideEffects` field can be found here: https://webpack.js.org/guides/tree-shaking/


## Screenshots / GIFs

I created a fresh [repo](https://github.com/nandorojo/reanimated-tree-shaking) to fix tree shaking for Reanimated. The playground tests it by building in a Next.js app (the most common framework for Web).

Prior to this PR, this imported _all_ code from Reanimated:

```ts
import Animated from 'react-native-reanimated'
```

This resulted in an absolutely massive impact on the Web bundle size, approximately 85kb, including tons of unused code (such as layout animations).

This PR reduces the Reanimated overhead down to about `17kb`. In the future, I may be able to investigate further improvements by seeing where that size is actually coming from. But this PR is a massive step forward, since it provides no changes to the user.

I wrote about my findings [here](nandorojo/reanimated-tree-shaking#1). There are plenty of detailed screenshots there.

### Before

Massive bundle size on Web, nearly unusable.

### After

It's now very optimized for tree shaking.

## Checklist

- [x] Included code example that can be used to test this change
- [x] Updated TS types
- [ ] Added TS types tests
- [ ] Added unit / integration tests
- [ ] Updated documentation
- [x] Ensured that CI passes
  • Loading branch information
nandorojo committed Jun 28, 2022
1 parent c73c15f commit d04720c
Show file tree
Hide file tree
Showing 26 changed files with 161 additions and 96 deletions.
4 changes: 3 additions & 1 deletion .eslintrc.js
Expand Up @@ -14,7 +14,9 @@ module.exports = {
},
settings: {
'import/resolver': {
'babel-module': {},
'babel-module': {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
},
rules: {
Expand Down
2 changes: 1 addition & 1 deletion Example/babel.config.js
Expand Up @@ -23,7 +23,7 @@ module.exports = (api) => {
'module-resolver',
{
alias: {
'react-native-reanimated': '../src/Animated',
'react-native-reanimated': '../src/index',
react: './node_modules/react',
'react-native': './node_modules/react-native',
'@babel': './node_modules/@babel',
Expand Down
1 change: 1 addition & 0 deletions Example/package.json
Expand Up @@ -21,6 +21,7 @@
"e2e:tests": "yarn e2e:android && yarn e2e:ios"
},
"dependencies": {
"@babel/plugin-proposal-export-namespace-from": "^7.17.12",
"@fortawesome/fontawesome-svg-core": "^1.2.28",
"@fortawesome/free-solid-svg-icons": "^5.13.0",
"@fortawesome/react-native-fontawesome": "^0.2.3",
Expand Down
13 changes: 13 additions & 0 deletions Example/yarn.lock
Expand Up @@ -858,6 +858,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5"
integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==

"@babel/helper-plugin-utils@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96"
integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==

"@babel/helper-plugin-utils@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670"
Expand Down Expand Up @@ -1350,6 +1355,14 @@
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/plugin-syntax-export-namespace-from" "^7.8.3"

"@babel/plugin-proposal-export-namespace-from@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.17.12.tgz#b22864ccd662db9606edb2287ea5fd1709f05378"
integrity sha512-j7Ye5EWdwoXOpRmo5QmRyHPsDIe6+u70ZYZrd7uz+ebPYFKfRcLcNu3Ro0vOlJ5zuv8rU7xa+GttNiRzX56snQ==
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
"@babel/plugin-syntax-export-namespace-from" "^7.8.3"

"@babel/plugin-proposal-json-strings@^7.12.13", "@babel/plugin-proposal-json-strings@^7.14.2":
version "7.14.2"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.2.tgz#830b4e2426a782e8b2878fbfe2cba85b70cbf98c"
Expand Down
1 change: 1 addition & 0 deletions FabricExample/package.json
Expand Up @@ -13,6 +13,7 @@
"postinstall": "patch-package && echo 73fa47c9c033e2f1bf10ebab9ac58f02c94ffff9 > ./node_modules/react-native/sdks/.hermesversion"
},
"dependencies": {
"@babel/plugin-proposal-export-namespace-from": "^7.17.12",
"@react-navigation/native": "^6.0.10",
"@react-navigation/native-stack": "^6.6.2",
"react": "18.0.0",
Expand Down
2 changes: 1 addition & 1 deletion TVOSExample/babel.config.js
Expand Up @@ -23,7 +23,7 @@ module.exports = api => {
'module-resolver',
{
alias: {
'react-native-reanimated': '../src/Animated',
'react-native-reanimated': '../src/index',
react: './node_modules/react',
'react-native': './node_modules/react-native',
'@babel': './node_modules/@babel',
Expand Down
1 change: 1 addition & 0 deletions TVOSExample/package.json
Expand Up @@ -11,6 +11,7 @@
"lint": "eslint ."
},
"dependencies": {
"@babel/plugin-proposal-export-namespace-from": "^7.17.12",
"glob-to-regexp": "^0.4.1",
"lodash.isequal": "^4.5.0",
"react": "17.0.2",
Expand Down
20 changes: 20 additions & 0 deletions TVOSExample/yarn.lock
Expand Up @@ -195,6 +195,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5"
integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==

"@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.8.3":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96"
integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==

"@babel/helper-remap-async-to-generator@^7.16.8":
version "7.16.8"
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3"
Expand Down Expand Up @@ -295,6 +300,14 @@
"@babel/helper-plugin-utils" "^7.16.7"
"@babel/plugin-syntax-export-default-from" "^7.16.7"

"@babel/plugin-proposal-export-namespace-from@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.17.12.tgz#b22864ccd662db9606edb2287ea5fd1709f05378"
integrity sha512-j7Ye5EWdwoXOpRmo5QmRyHPsDIe6+u70ZYZrd7uz+ebPYFKfRcLcNu3Ro0vOlJ5zuv8rU7xa+GttNiRzX56snQ==
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
"@babel/plugin-syntax-export-namespace-from" "^7.8.3"

"@babel/plugin-proposal-nullish-coalescing-operator@^7.0.0", "@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz#141fc20b6857e59459d430c850a0011e36561d99"
Expand Down Expand Up @@ -366,6 +379,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.16.7"

"@babel/plugin-syntax-export-namespace-from@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a"
integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"

"@babel/plugin-syntax-flow@^7.0.0", "@babel/plugin-syntax-flow@^7.16.7", "@babel/plugin-syntax-flow@^7.2.0":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.16.7.tgz#202b147e5892b8452bbb0bb269c7ed2539ab8832"
Expand Down
2 changes: 1 addition & 1 deletion __tests__/hooks.useSharedValue.test.js
@@ -1,6 +1,6 @@
import React from 'react';
import renderer from 'react-test-renderer';
import Animated, { useSharedValue } from '../src/Animated';
import Animated, { useSharedValue } from '../src';

jest.mock('../src/ReanimatedEventEmitter');
jest.mock('../src/ReanimatedModule');
Expand Down
16 changes: 11 additions & 5 deletions package.json
Expand Up @@ -34,10 +34,10 @@
"clean:deep": "cd android && rm -rf .cxx .gradle build && cd ../Example/android && rm -rf .gradle build app/build && cd ../.. && yarn clean",
"reset:deep": "yarn clean:deep && yarn setup"
},
"main": "lib/Animated.js",
"module": "lib/Animated",
"react-native": "src/Animated",
"source": "src/Animated",
"main": "lib/index.js",
"module": "lib/index",
"react-native": "src/index",
"source": "src/index",
"types": "react-native-reanimated.d.ts",
"files": [
"Common/",
Expand Down Expand Up @@ -76,6 +76,7 @@
},
"homepage": "https://github.com/software-mansion/react-native-reanimated#readme",
"dependencies": {
"@babel/plugin-proposal-export-namespace-from": "^7.17.12",
"@babel/plugin-transform-object-assign": "^7.16.7",
"@babel/preset-typescript": "^7.16.7",
"@types/invariant": "^2.2.35",
Expand Down Expand Up @@ -158,5 +159,10 @@
"commonjs",
"module"
]
}
},
"sideEffects": [
"./lib/reanimated2/core",
"./lib/reanimated2/js-reanimated/global",
"./lib/index"
]
}
11 changes: 11 additions & 0 deletions plugin.js
Expand Up @@ -759,6 +759,16 @@ function isPossibleOptimization(fun) {
return flags;
}

const pluginProposalExportNamespaceFrom =
require('@babel/plugin-proposal-export-namespace-from').default;
const apiMock = {
assertVersion: () => {
// do nothing.
},
};
const ExportNamedDeclarationFn =
pluginProposalExportNamespaceFrom(apiMock).visitor.ExportNamedDeclaration;

module.exports = function ({ types: t }) {
return {
pre() {
Expand All @@ -781,6 +791,7 @@ module.exports = function ({ types: t }) {
processIfGestureHandlerEventCallbackFunctionNode(t, path, state);
},
},
ExportNamedDeclaration: ExportNamedDeclarationFn,
},
};
};
18 changes: 0 additions & 18 deletions src/Animated.js

This file was deleted.

11 changes: 11 additions & 0 deletions src/Animated.ts
@@ -0,0 +1,11 @@
export { default as createAnimatedComponent } from './createAnimatedComponent';
export {
addWhitelistedNativeProps,
addWhitelistedUIProps,
} from './ConfigHelper';

export { default as Text } from './reanimated2/component/Text';
export { default as View } from './reanimated2/component/View';
export { default as ScrollView } from './reanimated2/component/ScrollView';
export { default as Image } from './reanimated2/component/Image';
export { default as FlatList } from './reanimated2/component/FlatList';
1 change: 0 additions & 1 deletion src/index.js

This file was deleted.

5 changes: 5 additions & 0 deletions src/index.ts
@@ -0,0 +1,5 @@
// tree-shaken side effects
import './reanimated2/js-reanimated/global';

export * from './reanimated2';
export * as default from './Animated';
8 changes: 3 additions & 5 deletions src/reanimated2/component/FlatList.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import { FlatList, FlatListProps, LayoutChangeEvent } from 'react-native';
import WrappedComponents from './WrappedComponents';
import ReanimatedView from './View';
import createAnimatedComponent from '../../createAnimatedComponent';
import { ILayoutAnimationBuilder } from '../layoutReanimation/animationBuilder/commonTypes';

Expand All @@ -15,11 +15,9 @@ interface AnimatedFlatListProps {
const createCellRenderer = (itemLayoutAnimation?: ILayoutAnimationBuilder) => {
const cellRenderer = (props: AnimatedFlatListProps) => {
return (
<WrappedComponents.View
layout={itemLayoutAnimation}
onLayout={props.onLayout}>
<ReanimatedView layout={itemLayoutAnimation} onLayout={props.onLayout}>
{props.children}
</WrappedComponents.View>
</ReanimatedView>
);
};

Expand Down
6 changes: 6 additions & 0 deletions src/reanimated2/component/Image.ts
@@ -0,0 +1,6 @@
import { Image } from 'react-native';
import createAnimatedComponent from '../../createAnimatedComponent';

const AnimatedImage = createAnimatedComponent(Image as any);

export default AnimatedImage;
6 changes: 6 additions & 0 deletions src/reanimated2/component/ScrollView.ts
@@ -0,0 +1,6 @@
import { ScrollView } from 'react-native';
import createAnimatedComponent from '../../createAnimatedComponent';

const AnimatedScrollView = createAnimatedComponent(ScrollView);

export default AnimatedScrollView;
6 changes: 6 additions & 0 deletions src/reanimated2/component/Text.ts
@@ -0,0 +1,6 @@
import { Text } from 'react-native';
import createAnimatedComponent from '../../createAnimatedComponent';

const AnimatedText = createAnimatedComponent(Text as any);

export default AnimatedText;
6 changes: 6 additions & 0 deletions src/reanimated2/component/View.ts
@@ -0,0 +1,6 @@
import { View } from 'react-native';
import createAnimatedComponent from '../../createAnimatedComponent';

const AnimatedView = createAnimatedComponent(View);

export default AnimatedView;
11 changes: 0 additions & 11 deletions src/reanimated2/component/WrappedComponents.ts

This file was deleted.

9 changes: 0 additions & 9 deletions src/reanimated2/component/index.ts

This file was deleted.

38 changes: 38 additions & 0 deletions src/reanimated2/js-reanimated/global.ts
@@ -0,0 +1,38 @@
// In order to keep bundle size down, we treat this file as a polyfill for Web.

import { shouldBeUseWeb } from '../PlatformChecker';

if (shouldBeUseWeb()) {
global._frameTimestamp = null;
global._setGlobalConsole = (_val) => {
// noop
};
global._measure = () => {
console.warn(
"[Reanimated] You can't use `measure` with Chrome Debugger or with web version"
);
return {
x: 0,
y: 0,
width: 0,
height: 0,
pageX: 0,
pageY: 0,
};
};
global._scrollTo = () => {
console.warn(
"[Reanimated] You can't use `scrollTo` with Chrome Debugger or with web version"
);
};
global._dispatchCommand = () => {
console.warn(
"[Reanimated] You can't use `scrollTo` or `dispatchCommand` methods with Chrome Debugger or with web version"
);
};
global._setGestureState = () => {
console.warn(
"[Reanimated] You can't use `setGestureState` with Chrome Debugger or with web version"
);
};
}
36 changes: 0 additions & 36 deletions src/reanimated2/js-reanimated/index.ts
@@ -1,5 +1,4 @@
import JSReanimated from './JSReanimated';
import { shouldBeUseWeb } from '../PlatformChecker';
import { AnimatedStyle, StyleProps } from '../commonTypes';

const reanimatedJS = new JSReanimated();
Expand All @@ -13,41 +12,6 @@ interface JSReanimatedComponent {
};
}

if (shouldBeUseWeb()) {
global._frameTimestamp = null;
global._setGlobalConsole = (_val) => {
// noop
};
global._measure = () => {
console.warn(
"[Reanimated] You can't use `measure` with Chrome Debugger or with web version"
);
return {
x: 0,
y: 0,
width: 0,
height: 0,
pageX: 0,
pageY: 0,
};
};
global._scrollTo = () => {
console.warn(
"[Reanimated] You can't use `scrollTo` with Chrome Debugger or with web version"
);
};
global._dispatchCommand = () => {
console.warn(
"[Reanimated] You can't use `scrollTo` or `dispatchCommand` methods with Chrome Debugger or with web version"
);
};
global._setGestureState = () => {
console.warn(
"[Reanimated] You can't use `setGestureState` with Chrome Debugger or with web version"
);
};
}

export const _updatePropsJS = (
updates: StyleProps | AnimatedStyle,
viewRef: { _component?: JSReanimatedComponent }
Expand Down

0 comments on commit d04720c

Please sign in to comment.