Skip to content

@rnx-kit/metro-serializer: Serializer did not return expected format using Expo #3674

@timostroehlein

Description

@timostroehlein

What happened?

I'm using the @rnx-kit/metro-serializer package within my metro config, which extends the @expo/metro-config. When running the bundler using npx expo start everything works as expected. But if I try to run npx expo export --platform <android|ios> I get the following error

Error: Serializer did not return expected format. The project copy of `expo/metro-config` may be out of date. Error: Unexpected token 'v', "var __BUND"... is not valid JSON`

Once I remove the MetroSerializer, the npx expo export command works as expected. I think this is happening due to Expo also requiring a custom serializer. One idea I had was to run both serializers and merge the results, not sure whether this will even work.

customSerializer: async (entryPoint, preModules, graph, options) => {
  const expoSerializer = await config.serializer.customSerializer(
    entryPoint,
    preModules,
    graph,
    options,
  );
  const metroSerializer = await MetroSerializer([
    CyclicDependencies({
      includeNodeModules: false,
      linesOfContext: 1,
      throwOnError: process.env.CI === 'true',
    }),
    DuplicateDependencies({
      // List of packages that should be ignored for duplicates check
      ignoredModules: [
        'isarray',
        'duplexify',
        'url',
        'safe-buffer',
        'string_decoder',
        'readable-stream',
        'entities',
        'escape-string-regexp',
        'tslib',
        'buffer',
        'punycode',
        'promise',
        'scheduler',
        '@wry/trie',
        'ansi-styles',
        'color-convert',
        'color-name',
        'prop-types',
        'base-64',
        'inherits',
        'util',
        'color',
        'qs',
        'deepmerge',
        'events',
        'uuid',
        'nanoid',
        'fuse.js',
        'hash-base',
        'bn.js',
      ],
      throwOnError: process.env.CI === 'true',
    }),
  ])(entryPoint, preModules, graph, options);
  return metroSerializer;
},

Here is my full metro config

/* eslint-disable no-console */
/**
 * Metro configuration for React Native
 * https://github.com/facebook/react-native
 *
 * @format
 */
const { getSentryExpoConfig } = require('@sentry/react-native/metro');
const path = require('path');
const projectRoot = __dirname;
const monorepoRoot = path.resolve(projectRoot, '../..');
const config = getSentryExpoConfig(projectRoot);
const {
  wrapWithReanimatedMetroConfig,
} = require('react-native-reanimated/metro-config');
const {
  makeMetroConfig,
  resolveUniqueModule,
  exclusionList,
} = require('@rnx-kit/metro-config');
const { MetroSerializer } = require('@rnx-kit/metro-serializer');
const {
  CyclicDependencies,
} = require('@rnx-kit/metro-plugin-cyclic-dependencies-detector');
const {
  DuplicateDependencies,
} = require('@rnx-kit/metro-plugin-duplicates-checker');

const env = process.env.ENV;
if (env) console.log(`Starting Metro Bundler with ${env} environment`);

let sourceExts = config.resolver.sourceExts;
// Used for mock and env file splitting
sourceExts = [
  `${env}.ts`,
  `${env}.tsx`,
  `${env}.js`,
  `${env}.jsx`,
  ...sourceExts,
];
if (process.env.E2E) {
  sourceExts = ['e2e.ts', ...sourceExts];
}

// List of packages that should be resolved to a single instance
const singleInstancePackages = [
  '@callstack/react-theme-provider',
  '@smithy/util-hex-encoding',
  '@smithy/is-array-buffer',
  '@smithy/util-buffer-from',
  '@smithy/util-utf8',
  '@smithy/types',
  'react-is',
  '@sentry-internal/replay',
  '@react-native/normalize-colors',
  'use-latest-callback',
  '@sentry/core',
  '@sentry/react',
  '@sentry/browser',
  '@sentry-internal/browser-utils',
  '@sentry-internal/replay-canvas',
  '@sentry-internal/feedback',
  'create-hash',
  'ripemd160',
  '@storybook/core',
  'storybook',
  'fast-deep-equal',
];

const { extraNodeModules, blockList } = singleInstancePackages.reduce(
  (acc, curr) => {
    const [pkgPath, pkgExcludePattern] = resolveUniqueModule(curr);
    const blockListArr = [...acc.blockListArr, pkgExcludePattern];
    const blockList = exclusionList(blockListArr);
    return {
      extraNodeModules: { ...acc.extraNodeModules, pkgPath },
      blockListArr,
      blockList,
    };
  },
  { extraNodeModules: {}, blockListArr: [], blockList: new RegExp('') },
);

let finalConfig = wrapWithReanimatedMetroConfig(
  makeMetroConfig({
    ...config,
    watchFolders: [monorepoRoot],
    resolver: {
      ...config.resolver,
      extraNodeModules: {
        ...require('node-libs-react-native'), // Make all core node modules available
        tls: require.resolve('react-native-tcp-socket'),
        ...config.resolver.extraNodeModules,
        ...extraNodeModules,
      },
      blockList: [blockList, config.resolver.blockList, /.*\/db.devices.json/],
      nodeModulesPaths: [
        path.resolve(projectRoot, 'node_modules'),
        path.resolve(monorepoRoot, 'node_modules'),
      ],
      sourceExts: sourceExts,
      resolverMainFields: ['react-native', 'browser', 'main'],
      unstable_enableSymlinks: true,
      unstable_enablePackageExports: true,
      unstable_conditionNames: ['browser', 'require', 'react-native'], // axios package won't work without 'browser': https://github.com/facebook/react-native/issues/38025
    },
    serializer: {
      ...config.serializer,
      // Integration of rnx-kit plugins
      // https://microsoft.github.io/rnx-kit/docs/tools/metro-serializer
      customSerializer: MetroSerializer([
        CyclicDependencies({
          includeNodeModules: false,
          linesOfContext: 1,
          throwOnError: process.env.CI === 'true',
        }),
        DuplicateDependencies({
          // List of packages that should be ignored for duplicates check
          ignoredModules: [
            'isarray',
            'duplexify',
            'url',
            'safe-buffer',
            'string_decoder',
            'readable-stream',
            'entities',
            'escape-string-regexp',
            'tslib',
            'buffer',
            'punycode',
            'promise',
            'scheduler',
            '@wry/trie',
            'ansi-styles',
            'color-convert',
            'color-name',
            'prop-types',
            'base-64',
            'inherits',
            'util',
            'color',
            'qs',
            'deepmerge',
            'events',
            'uuid',
            'nanoid',
            'fuse.js',
            'hash-base',
            'bn.js',
          ],
          throwOnError: process.env.CI === 'true',
        }),
      ]),
    },
  }),
);

if (env === 'storybook') {
  const withStorybook = require('@storybook/react-native/metro/withStorybook');
  finalConfig = withStorybook(config, {
    // set to false to disable storybook specific settings
    // you can use an env variable to toggle this
    enabled: true,
    // path to your storybook config folder
    configPath: path.resolve(__dirname, './.storybook'),
  });
}

module.exports = finalConfig;

Affected Package

@rnx-kit/metro-serializer

Version

2.0.0

Which platforms are you seeing this issue on?

  • Android
  • iOS
  • macOS
  • Windows

System Information

System:
  OS: macOS 15.5
  CPU: (12) arm64 Apple M2 Max
  Memory: 895.63 MB / 32.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 22.15.0
    path: ~/.nvm/versions/node/v22.15.0/bin/node
  Yarn:
    version: 4.7.0
    path: ~/.nvm/versions/node/v22.15.0/bin/yarn
  npm:
    version: 11.3.0
    path: ~/.nvm/versions/node/v22.15.0/bin/npm
  Watchman:
    version: 2025.03.10.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.16.2
    path: /Users/aw17732/.gem/ruby/3.1.0/bin/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 24.5
      - iOS 18.5
      - macOS 15.5
      - tvOS 18.5
      - visionOS 2.5
      - watchOS 11.5
  Android SDK:
    API Levels:
      - "31"
      - "32"
      - "33"
      - "34"
      - "35"
      - "36"
    Build Tools:
      - 30.0.3
      - 31.0.0
      - 33.0.0
      - 33.0.1
      - 34.0.0
      - 35.0.0
      - 35.0.1
      - 36.0.0
    System Images:
      - android-33 | Wear OS 4 ARM 64 v8a
      - android-33 | Google Play ARM 64 v8a
      - android-34 | Google APIs ARM 64 v8a
      - android-34 | Google Play ARM 64 v8a
      - android-35 | Google APIs ARM 64 v8a
      - android-36 | Google APIs ARM 64 v8a
      - android-36 | Google Play ARM 64 v8a
    Android NDK: Not Found
IDEs:
  Android Studio: 2025.1 AI-251.25410.109.2511.13752376
  Xcode:
    version: 16.4/16F6
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.10
    path: /Users/aw17732/.sdkman/candidates/java/current/bin/javac
  Ruby:
    version: 3.1.7
    path: /opt/homebrew/opt/[email protected]/bin/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react: Not Found
  react-native: Not Found
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true

Steps to Reproduce

  1. Create a default Expo metro config based on the docs https://docs.expo.dev/guides/customizing-metro/
  2. Add the custom metro serializer to the config customSerializer: MetroSerializer([])
  3. Run npx expo export --platform android

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions