Skip to content

Implement module tree shaking #632

Closed

Description

@chang-ke

I'm implementing module level's tree shaking in here chang-ke#1 (it can shake lodash-es,react-native-svg now)hope given some suggestions

Activity

  1. chang-ke commented on Feb 19, 2021

    @chang-ke
    Author

    @cpojer Does metro interested in it?(then review it)

  2. changed the title [-][Feature]: support module tree shaking[/-] [+]Implement module tree shaking[/+] on Feb 19, 2021
  3. cpojer commented on Feb 19, 2021

    @cpojer
    Contributor

    I actually find this super interesting. What status is your PR in? How far along is the implementation and can you show some differences in bundle sizes?

  4. chang-ke commented on Feb 19, 2021

    @chang-ke
    Author

    I actually find this super interesting. What status is your PR in? How far along is the implementation and can you show some differences in bundle sizes?

    actually it used in my project now, but no test case. the differences of bundle sizes dependes on what npm package you used, I will give some cases and this pr has example project that you can get some real differences bundle sizes by running

  5. jsamr commented on Feb 26, 2021

    @jsamr

    Just a side note: I propose that tree-shaking for metro bundler follows webpack sideEffects package.json field, see https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free

  6. chang-ke commented on Mar 1, 2021

    @chang-ke
    Author

    Just a side note: I propose that tree-shaking for metro bundler follows webpack sideEffects package.json field, see https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free

    got it

  7. chang-ke commented on Mar 4, 2021

    @chang-ke
    Author

    I actually find this super interesting. What status is your PR in? How far along is the implementation and can you show some differences in bundle sizes?

    metro config
      /**
       * Metro configuration for React Native
       * https://github.com/facebook/react-native
       *
       * @format
       */
      
      const path = require('path');
      
      const { getDefaultConfig } = require('metro-config');
      
      module.exports = (async () => {
        const {
          resolver: { sourceExts, resolverMainFields },
        } = await getDefaultConfig();
        return {
        watchFolders: [path.resolve('../packages/')],
        transformer: {
          getTransformOptions: async () => ({
            transform: {
              experimentalImportSupport: false,
              inlineRequires: false,
            },
          }),
          experimentalTreeShaking: true,
          assetRegistryPath: require.resolve(
            'react-native/Libraries/Image/AssetRegistry',
          ),
          babelTransformerPath: require.resolve('../packages/metro-react-native-babel-transformer'),
        },
        serializer: {
          getPolyfills: require('react-native/rn-get-polyfills'),
          getModulesRunBeforeMainModule: () => [require.resolve(
            require.resolve('react-native/Libraries/Core/InitializeCore'),
          )]
        },
        resolver: {
          sourceExts: ['ios.js', 'android.js', ...sourceExts],
          blockList: /(website\/node_modules\/.*|.*\/__tests__\/.*)$/,
          resolverMainFields: ['react-native', 'module', ...resolverMainFields],
        },
      }})();

    common bundle (only react-native@0.64-rc2)

    index.js:

    /**
     * @format
    */
    import {AppRegistry} from 'react-native'

    bundle size: 658KB


    common bundle with react-native-svg

    index.js:

    /**
     * @format
     */
    import {AppRegistry} from 'react-native'
    import App from './App';
    import {name as appName} from './app.json';
    
    AppRegistry.registerComponent(appName, () => App);

    App.js:

    import React from 'react';
    import {SafeAreaView, StatusBar, Text} from 'react-native';
    import {Svg, Path} from 'react-native-svg';
    
    const App = () => {
      return (
        <>
          <StatusBar barStyle="dark-content" />
          <SafeAreaView>
            <Svg
              width="130"
              height="130"
              fill="blue"
              stroke="red"
              color="green"
              viewBox="-16 -16 544 544">
              <Path
                d="M318.37,85.45L422.53,190.11,158.89,455,54.79,350.38ZM501.56,60.2L455.11,13.53a45.93,45.93,0,0,0-65.11,0L345.51,58.24,449.66,162.9l51.9-52.15A35.8,35.8,0,0,0,501.56,60.2ZM0.29,497.49a11.88,11.88,0,0,0,14.34,14.17l116.06-28.28L26.59,378.72Z"
                strokeWidth="32"
              />
              <Path d="M0,0L512,512" stroke="currentColor" strokeWidth="32" />
            </Svg>
          </SafeAreaView>
        </>
      );
    };
    
    export default App;

    no tree-shaking bundle size: 1191KB

    tree-shaking bundle size: 700KB


    common bundle with lodash-es

    index.js:

    /**
     * @format
     */
    import {AppRegistry} from 'react-native'
    import App from './App';
    import {name as appName} from './app.json';
    
    AppRegistry.registerComponent(appName, () => App);

    App.js:

    import React from 'react';
    import {SafeAreaView, StatusBar, Text} from 'react-native';
    import {toString} from 'lodash-es';
    
    const App = () => {
      return (
        <>
          <StatusBar barStyle="dark-content" />
          <SafeAreaView>
            <Text>{toString([1, 2])}</Text>
          </SafeAreaView>
        </>
      );
    };
    
    export default App;

    no tree-shaking bundle size: 940KB

    tree-shaking bundle size: 661KB

  8. wilau2 commented on Mar 25, 2021

    @wilau2

    What is the next step here?

    I have to say I am very interested in this work.

    We are now at 7.10 mB in our project.

    @chang-ke could it be useful that we try your fix in our project?

    If you can give me the basics on how to use it in my project I could give another example of tree-shaking for our huge app.

  9. chang-ke commented on Mar 31, 2021

    @chang-ke
    Author

    What is the next step here?

    I have to say I am very interested in this work.

    We are now at 7.10 mB in our project.

    @chang-ke could it be useful that we try your fix in our project?

    If you can give me the basics on how to use it in my project I could give another example of tree-shaking for our huge app.

    you can clone this repo and run it by #632 (comment) 's metro config or https://github.com/chang-ke/metro/tree/feature/tree-shaking/examples.

  10. hannojg commented on Aug 31, 2022

    @hannojg

    May I ask why this has been closed?

    • Has it been merged?
    • Did the metro team decide on not supporting it at all?
    • Has there been some official correspondence between the metro team and the rnx-kit team, where the result is, that metro won't take care of it and if devs want tree shaking they should look into rnx-kit?

    Thanks in advance for your response!

  11. robhogan commented on Aug 31, 2022

    @robhogan
    Collaborator

    Hi @hannojg, this issue was closed by the author, no PR to Metro was ever opened. I guess they didn't have time to finish it, but the work (on their fork: chang-ke#1) looked interesting.

    Tree shaking isn't a priority within Meta right now but we're open to contributions - as this would be quite a complex feature, it'd be a good idea to open a discussion about approaches first.

  12. johnculviner commented on Nov 23, 2022

    @johnculviner

    Bump

    Wasn't a big reason why Hermes was created was to reduce app start time? Rather than creating an entirely new JavaScript engine using the example above treeshaking would cut 1/3 off the bundle size (and maybe about 1/3 off the start time too without Hermes - not sure if it would change anything WITH Hermes)

    I think doing this should be considered. I'm looking at my bundle with npx react-native-bundle-visualizer and seeing all sorts of things that really shouldn't be in there.

  13. russeg commented on Feb 6, 2023

    @russeg

    @johnculviner, the contributor said it was not a facebook priority. their needs comes first not the community.

  14. iketw commented on Mar 12, 2024

    @iketw

    @robhogan has FB reconsidered implementing tree-shaking support out of the box?

  15. robhogan commented on Mar 12, 2024

    @robhogan
    Collaborator

    @iketw we've explored it with interest! Because Metro already only bundles explicitly imported files/modules rather than packages, the gains from tree shaking (=identifying unused imports exports and eliminating them) are less than some bundlers, and as far as we've seen, not a big win in practice, especially under Hermes where the impact is only on a small fraction of download size vs a binary, with no runtime benefit. It would be a bigger win on web, but that hasn't been Metro's focus.

    We're absolutely open to explorations in this area though - I'd love to be able to say "Metro does tree shaking", but the fact is folks who've experimented with it on complex, real-world apps haven't yet found significant gains.

  16. kelset commented on Mar 13, 2024

    @kelset
    Contributor

    For what's worth, at Microsoft we have made a plugin for Metro to do treeshaking via esbuild (here) and it had led to good results in our complex real world apps (Office and Xbox to name a couple) for over two years (link to the first release). We have a whole guide around bundling enhanced by rnx-kit here.

  17. iketw commented on Mar 13, 2024

    @iketw

    For what's worth, at Microsoft we have made a plugin for Metro to do treeshaking via esbuild (here) and it had led to good results in our complex real world apps (Office and Xbox to name a couple) for over two years (link to the first release). We have a whole guide around bundling enhanced by rnx-kit here.

    @kelset thanks for your reply. Based on @robhogan's answer, it seems that the only thing missing for full tree-shaking is unused imports, is that what the MS's plugin completes in the cycle?

    Also, I'm building a RN SDK using native modules and JS code. If I use the plugin in my SDK's metro config, if the hosting app does not include the plugin, will it still tree-shake the dependencies inside my SDK?

  18. kelset commented on Mar 14, 2024

    @kelset
    Contributor

    @iketw :

    1. what our plugin for Metro does is leveraging esbuild's built-in treeshaker; you can find a more detailed explanation on how it works in this discussion.
    2. because our tool is a plugin for Metro, the treeshaking happens at the Metro bundle step level: which means that it's used when generating the jsbundle consumed by an app. So unless you use Metro to bundle your SDK that you then gets consumed by hosting apps, you won't get any treeshaking. So probably you'd want your hosting apps to add the plugin; to say it in another way, whoever does the bundling needs to add treeshaking.
  19. renchap commented on Mar 26, 2025

    @renchap

    we've explored it with interest! Because Metro already only bundles explicitly imported files/modules rather than packages, the gains from tree shaking (=identifying unused imports exports and eliminating them) are less than some bundlers, and as far as we've seen, not a big win in practice

    I am late to the discussion, but if you are using an icon library like Font Awesome, with imports like import { faShareSquare } from "@fortawesome/pro-solid-svg-icons", then tree-shaking is a really big win to app size.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions