Metro: Implement module tree shaking

Created on 1 Feb 2021  路  8Comments  路  Source: facebook/metro

I'm implementing module level's tree shaking in here https://github.com/chang-ke/metro/pull/1 锛坕t can shake lodash-es,react-native-svg now锛塰ope given some suggestions

Most helpful comment

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 [email protected])

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

All 8 comments

@cpojer Does metro interested in it?锛坱hen review it)

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?

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

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

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

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 [email protected])

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

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.

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 https://github.com/facebook/metro/issues/632#issuecomment-790253097 's metro config or https://github.com/chang-ke/metro/tree/feature/tree-shaking/examples.

Was this page helpful?
0 / 5 - 0 ratings