react-native-builder-bob icon indicating copy to clipboard operation
react-native-builder-bob copied to clipboard

[Web] Tree Shaking

Open nandorojo opened this issue 3 years ago • 11 comments

Ask your Question

Hey there, thanks for this awesome package, it's seriously a life saver.

I'm experiencing an issue with my package, Dripsy, on web. It appears that importing dripsy is importing all of React Native Web, adding about 500kb more than I need to my site. I have sideEffects: false in my package.json file, but this seems to have no effect.

I was wondering if you have any suggestions for how to solve that. Should I try using the react-native-web babel plugin in my babel config for bob? I'm not that familiar with Babel and how it works, so I figured I'd ask. I'm using Expo web, and have dripsy in my transpile modules file, so I figured this would apply the RNW babel plugin to it. But it seems like it isn't.

Let me know if any suggestions come to mind. Thanks again!

nandorojo avatar Apr 07 '21 00:04 nandorojo

I switched my build scripts to expo-module-scripts, and that helped a lot. Seems like that's my answer.

nandorojo avatar Apr 07 '21 02:04 nandorojo

I'll reopen to propose changing to expo-module-scripts as the default build script. I've tried this for 2 different react native packages now, including Dripsy and react-native-safe-area-context. In both cases, I was able to reduce web bundle size by ~400kb, which is significant. In both cases, it took like 15 minutes to switch to expo-module prepare.

I'd be happy to submit a PR to Bob for this, just let me know.

nandorojo avatar Apr 07 '21 21:04 nandorojo

I don't see why whole react-native-web would be imported with bob's output and not with expo-module-scripts's output. What does it do internally?

You should be using react-native-web babel plugin in your app when building for web anyway but perhaps expo-build-scripts already includes it. Including it in the library's build scripts isn't safe since we're not building packages specifically for react-native-web.

satya164 avatar Apr 08 '21 02:04 satya164

Honestly, I'm not sure how it works. @evanbacon will know better, seems like it's some sort of webpack tree shaking optimization that expo-module does.

I refactored a few of my packages using expo-module prepare instead of bob build, though, and suddenly my Next.js app wasn't importing all of RNW, and my bundle size dropped. I wish I could offer more insight into why that is.

nandorojo avatar Apr 08 '21 03:04 nandorojo

Another instance of this is @react-navigation/native.

I use <NavigationContainer> in my pages/_app.tsx for Next.js.

This is without NavigationContainer:

Screen Shot 2021-04-08 at 11 44 43 AM

And this is with NavigationContainer:

Screen Shot 2021-04-08 at 11 45 36 AM

I could try forking @react-navigation/core to see if using expo-module prepare instead solves this.

Update: I tried forking and using expo-module prepare, but I couldn't get type checking to work, seems like expo module needs you to extend its tsconfig, and it pushes you to use its eslint config, so that raised some type errors.

Context

Here's a PR I did for react-native-safe-area-context as an example.

This is a related issue for Dripsy, where I solved it.

nandorojo avatar Apr 08 '21 15:04 nandorojo

But what does it do? bob compiles the files down to relatively modern ES5 so that's gonna increase bundle size due to generated code and polyfills, the code is also not minified so it'll be bigger (you'd minify in prod anyway). You can customize what gets compiled with a browserlist config or your custom babel config. does expo module scripts compile down? if not it'll be smaller but it won't work on slightly older browsers.

You're saying that with Bob your whole react native web gets included but I don't see why that would be since bob doesn't bundle any code, it just compiles individual files.

What does expo module scripts do?

satya164 avatar Apr 08 '21 16:04 satya164

Bob uses Babel and expo-module-scripts uses tsc to compile the files. But the generated code for both are almost the same:

Bob:

function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }

import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { useSafeAreaInsets } from './SafeAreaContext';
// prettier-ignore
const TOP = 0b1000, RIGHT = 0b0100, BOTTOM = 0b0010, LEFT = 0b0001, ALL = 0b1111;
/* eslint-disable no-bitwise */
const edgeBitmaskMap = {
  top: TOP,
  right: RIGHT,
  bottom: BOTTOM,
  left: LEFT
};
export function SafeAreaView(_ref) {
  ...
}
//# sourceMappingURL=SafeAreaView.js.map

expo-module-scripts:

import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { useSafeAreaInsets } from './SafeAreaContext';
// prettier-ignore
const TOP = 0b1000, RIGHT = 0b0100, BOTTOM = 0b0010, LEFT = 0b0001, ALL = 0b1111;
/* eslint-disable no-bitwise */
const edgeBitmaskMap = {
  top: TOP,
  right: RIGHT,
  bottom: BOTTOM,
  left: LEFT,
};
export function SafeAreaView({ style = {}, mode, edges, ...rest }) {
  ...
}
//# sourceMappingURL=SafeAreaView.js.map

dalcib avatar Apr 08 '21 19:04 dalcib

I'm still trying to get to the bottom do why this is happening. Could it be as simple as, compiling with TSC is better for preserving tree shaking for web?

It's my understanding that on iOS and Android, apps resolve to the package.jsonreact-native field, which typically point to the source directly. For web, the behavior is different.

This really isn't my area of expertise, but I've run multiple experiments on different libraries now. In each case, expo-module-scripts results in essentially no bundle size increase, while Bob's web output causes hundreds of KB increases.

https://github.com/th3rdwave/react-native-safe-area-context/pull/189

For example, NavigationContainer from React Navigation adds 700kb to my output: https://github.com/nandorojo/dripsy/issues/41#issuecomment-815936119

nandorojo avatar Sep 03 '21 13:09 nandorojo

On web, we preserve import/export syntax so we can transform react-native -> react-native-web. Generally I find that tools will transform import { View } from 'react-native' to const ReactNative = require('react-native'); ReactNative.View... meaning we can't use babel to ignore paths later.

EvanBacon avatar Dec 15 '21 00:12 EvanBacon

^I can confirm that bob exports es module and commonjs bundles in two separate directories. As long as the bundler picks es modules bundle path, transformers can get the import { View } from "react-native" (es modules) code.

To add some context here for reference, it seems like picking up CommonJS / transpiled code would be the issue:

image

I think using experimental.esmExternals (I forget the exact field name) from Next.js should solve this, assuming Bob's module output isn't using the default Babel behavior. I'll do some tests to confirm it's working.

nandorojo avatar Jun 14 '22 04:06 nandorojo

@nandorojo This should be addressed by #363 and then https://github.com/software-mansion/react-native-svg/pull/1993. Together those will fix the module output of react-native-svg and enable proper tree-shaking. I've attached the bundle analyzer output from my project.

Current: Screenshot 2023-02-24 at 6 06 26 PM

After changes: Screenshot 2023-02-24 at 6 23 36 PM

merrywhether avatar Feb 25 '23 02:02 merrywhether