metro icon indicating copy to clipboard operation
metro copied to clipboard

inlineRequires: true not working on default exports/imports

Open robrechtme opened this issue 2 years ago • 9 comments

Do you want to request a feature or report a bug? Bug

What is the current behavior?

When setting inlineRequires to true, I'd expect all imports to be transformed to inline requires. However, it seems like this is not working for default exports. Check out the following example:

// metro.config.js
module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    }),
  },
};
// defaultFn.js
console.log('defaultFn.js imported');

export default function defaultFn() {
  console.log('defaultFn executed');
}
// namedFn.js
console.log('namedFn.js imported');

export function namedFn() {
  console.log('namedFn executed');
}
// index.js
import React from 'react';
import { AppRegistry, Button, SafeAreaView } from 'react-native';
import { name } from './app.json';
import { namedFn } from './namedFn';
import defaultFn from './defaultFn';

function App() {
  const onPress = () => {
    namedFn();
    defaultFn();
  };

  return (
    <SafeAreaView>
      <Button title="Click me" onPress={onPress} />
    </SafeAreaView>
  );
}

AppRegistry.registerComponent(name, () => App);

When launching the app, I immediately get the following logs:

defaultFn.js imported

After pressing the button, the following is logged:

namedFn.js imported
namedFn executed
defaultFn executed

What is the expected behavior?

I would expect the defaultFn.js imported log to also occur only after pressing the button. The log indicates that the defaultFn.js file is imported and parsed, and inlineRequires: true is not behaving correctly? Or is this by design?

Please provide your exact Metro configuration and mention your Metro, node, yarn/npm version and operating system.

  • react-native 0.70.6
  • node 16.16.0
  • npm 8.16.0
  • MacOS

robrechtme avatar Dec 20 '22 16:12 robrechtme

Thanks @robrechtme! This is a known issue. experimentalImportSupport: true solves this, but the option is currently undocumented and experimental (as reflected in its name). We're looking into what it would involve to make it the default behaviour in Metro.

motiz88 avatar Dec 29 '22 12:12 motiz88

Hi @motiz88

Are you sure? Changing experimentalImportSupport has no effect on my example.

I'm using [email protected] which resolves metro to:

    metro-react-native-babel-transformer "0.72.3"
    metro-runtime "0.72.3"
    metro-source-map "0.72.3"

robrechtme avatar Dec 29 '22 13:12 robrechtme

Hi @motiz88,

I'm seeing the same as @robrechtme.

Both experimentalImportSupport and inlineRequires are set to true, but the default exports are never transformed to inline requires. I also verify this by setting a console.log() top-level in a component file.

When default exporting the component, the console.log() is always executed immediately (so no inline require happens). But when "regularly" exporting the component, the console.log() is only executed when rendered.

This is rather annoying, since most of our components are exported as defaults.

I even converted all our default exports to regular exports, just to be able to profit from this feature (hundreds of components) 😅

bitcrumb avatar Dec 29 '22 20:12 bitcrumb

@robrechtme (and @bitcrumb): Is there a babel.config.js file in your project? One quirk of the current implementation of experimentalImportSupport: true is that it depends on disabling Babel's ES Module support, which you may need to do manually at the moment.

motiz88 avatar Dec 29 '22 21:12 motiz88

There is in ours, but it's rather limited:

module.exports = function babelConfig(api) {
  api.cache.using(() => process.env.NODE_ENV);

  const presets = ['module:metro-react-native-babel-preset'];

  const plugins = ['react-native-reanimated/plugin'];

  if (!(api.env('development') || api.env('test'))) {
    plugins.push('transform-remove-console');
  }

  plugins.push([
    require.resolve('babel-plugin-module-resolver'),
    {
      root: ['./src/'],
      alias: {
...
      },
    },
  ]);

  return {
    presets,
    plugins,
  };
};

How exactly do I disable Babel ES Module support?

bitcrumb avatar Dec 29 '22 21:12 bitcrumb

👀

bitcrumb avatar Feb 03 '23 13:02 bitcrumb

I could reproduce this in a fresh RN app with npx react-native init, there the babel config is:

// babel.config.js
module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
};

robrechtme avatar Feb 08 '23 08:02 robrechtme

How fix it via custom resolver?

aemre avatar Feb 17 '23 11:02 aemre

I even converted all our default exports to regular exports, just to be able to profit from this feature (hundreds of components) 😅

Hey @bitcrumb , did you measure improvements? We also often use default export, I'm wondering how beneficial it is to switch to a named export.

Rag0n avatar Feb 19 '23 12:02 Rag0n