Re.Pack 2.x to 3.x

This guide shows how to upgrade from @callstack/repack@2.x to @callstack/repack@3.x.

INFO

If you're upgrading from version 1.x, we recommended you start with fresh Webpack config from our templates and reapply any changes you've added in the old config to the new one.

Dependencies

First, you need to update dependencies in your project's package.json:

yarn add --dev @callstack/repack@^3.0.0
# or
npm install --save-dev @callstack/repack@^3.0.0
TIP

It's also recommended to update Webpack to the latest version. In case of any trouble, take a look at Compatibility with Webpack table.

Webpack config

Every project is different and we have no way of knowing what your Webpack config looks like, so we cannot pin point every possible change you might need to do. Instead we provide a diff of our Webpack config template, which you can use as a base to figure out what you need to change.

INFO

The diff uses CJS template (webpack.config.cjs), since ESM was not supported in Re.Pack 2.x, so the diff between 2 CJS templates will easier to read.

Below is the diff of template/webpack.config.js between Re.Pack 2.x and 3.x:

diff --git a/templates/webpack.config.js b/templates/webpack.config.cjs
index 6ff525d..af917c4 100644
--- a/templates/webpack.config.js
+++ b/templates/webpack.config.cjs
@@ -1,7 +1,6 @@
 const path = require('path');
-const webpack = require('webpack');
 const TerserPlugin = require('terser-webpack-plugin');
-const ReactNative = require('@callstack/repack');
+const Repack = require('@callstack/repack');

 /**
  * More documentation, installation, usage, motivation and differences with Metro is available at:
@@ -12,292 +11,223 @@ const ReactNative = require('@callstack/repack');
  */

 /**
- * This is the Webpack configuration file for your React Native project.
- * It can be used in 2 ways:
- * - by running React Native CLI eg: `npx react-native start` or `npx react-native bundle`
- * - by running Webpack CLI eg: `PLATFORM=(ios|android) npx webpack-cli -c webpack.config.js`
- *
- * Depending on which option you chose the output might be different, since when running with
- * React Native CLI most of the values from `getMode`, `getPlatform`, etc. will be filled in by React Native CLI.
- * However, when running with Webpack CLI, you might want to tweak `fallback` values to your liking.
- *
- * Please refer to the API documentation for list of options, plugins and their descriptions.
- */
-
-/**
- * Get options from React Native CLI when Webpack is run from `react-native start` or `react-native bundle`.
+ * Webpack configuration.
+ * You can also export a static object or a function returning a Promise.
  *
- * If you run Webpack using Webpack CLI, the values from `fallback` will be used - use it
- * to specify your values, if the defaults don't suit your project.
+ * @param env Environment options passed from either Webpack CLI or React Native CLI
+ *            when running with `react-native start/bundle`.
  */
+module.exports = (env) => {
+  const {
+    mode = 'development',
+    context = __dirname,
+    entry = './index.js',
+    platform,
+    minimize = mode === 'production',
+    devServer = undefined,
+    bundleFilename = undefined,
+    sourceMapFilename = undefined,
+    assetsPath = undefined,
+    reactNativePath = require.resolve('react-native'),
+  } = env;

-const mode = ReactNative.getMode({ fallback: 'development' });
-const dev = mode === 'development';
-const context = ReactNative.getContext();
-const entry = ReactNative.getEntry();
-const platform = ReactNative.getPlatform({ fallback: process.env.PLATFORM });
-const minimize = ReactNative.isMinimizeEnabled({ fallback: !dev });
-const devServer = ReactNative.getDevServerOptions();
-const reactNativePath = ReactNative.getReactNativePath();
-
-/**
- * Depending on your Babel configuration you might want to keep it.
- * If you don't use `env` in your Babel config, you can remove it.
- *
- * Keep in mind that if you remove it you should set `BABEL_ENV` or `NODE_ENV`
- * to `development` or `production`. Otherwise your production code might be compiled with
- * in development mode by Babel.
- */
-process.env.BABEL_ENV = mode;
+  if (!platform) {
+    throw new Error('Missing platform');
+  }

-/**
- * Webpack configuration.
- */
-module.exports = {
-  mode,
   /**
-   * This should be always `false`, since the Source Map configuration is done
-   * by `SourceMapDevToolPlugin`.
+   * Using Module Federation might require disabling hmr.
+   * Uncomment below to set `devServer.hmr` to `false`.
+   *
+   * Keep in mind that `devServer` object is not available
+   * when running `webpack-bundle` command. Be sure
+   * to check its value to avoid accessing undefined value,
+   * otherwise an error might occur.
    */
-  devtool: false,
-  context,
+  // if (devServer) {
+  //   devServer.hmr = false;
+  // }
+
   /**
-   * `getInitializationEntries` will return necessary entries with setup and initialization code.
-   * If you don't want to use Hot Module Replacement, set `hmr` option to `false`. By default,
-   * HMR will be enabled in development mode.
+   * Depending on your Babel configuration you might want to keep it.
+   * If you don't use `env` in your Babel config, you can remove it.
+   *
+   * Keep in mind that if you remove it you should set `BABEL_ENV` or `NODE_ENV`
+   * to `development` or `production`. Otherwise your production code might be compiled with
+   * in development mode by Babel.
    */
-  entry: [
-    ...ReactNative.getInitializationEntries(reactNativePath, {
-      hmr: devServer.hmr,
-    }),
-    entry,
-  ],
-  resolve: {
+  process.env.BABEL_ENV = mode;
+
+  return {
+    mode,
     /**
-     * `getResolveOptions` returns additional resolution configuration for React Native.
-     * If it's removed, you won't be able to use `<file>.<platform>.<ext>` (eg: `file.ios.js`)
-     * convention and some 3rd-party libraries that specify `react-native` field
-     * in their `package.json` might not work correctly.
+     * This should be always `false`, since the Source Map configuration is done
+     * by `SourceMapDevToolPlugin`.
      */
-    ...ReactNative.getResolveOptions(platform),
-
+    devtool: false,
+    context,
     /**
-     * Uncomment this to ensure all `react-native*` imports will resolve to the same React Native
-     * dependency. You might need it when using workspaces/monorepos or unconventional project
-     * structure. For simple/typical project you won't need it.
+     * `getInitializationEntries` will return necessary entries with setup and initialization code.
+     * If you don't want to use Hot Module Replacement, set `hmr` option to `false`. By default,
+     * HMR will be enabled in development mode.
      */
-    // alias: {
-    //   'react-native': reactNativePath,
-    // },
-  },
-  /**
-   * Configures output.
-   * It's recommended to leave it as it is unless you know what you're doing.
-   * By default Webpack will emit files into the directory specified under `path`. In order for the
-   * React Native app use them when bundling the `.ipa`/`.apk`, they need to be copied over with
-   * `ReactNative.OutputPlugin`, which is configured by default.
-   */
-  output: {
-    clean: true,
-    path: path.join(__dirname, 'build', platform),
-    filename: 'index.bundle',
-    chunkFilename: '[name].chunk.bundle',
-    publicPath: ReactNative.getPublicPath(devServer),
-  },
-  /**
-   * Configures optimization of the built bundle.
-   */
-  optimization: {
-    /** Enables minification based on values passed from React Native CLI or from fallback. */
-    minimize,
-    /** Configure minimizer to process the bundle. */
-    minimizer: [
-      new TerserPlugin({
-        test: /\.(js)?bundle(\?.*)?$/i,
-        /**
-         * Prevents emitting text file with comments, licenses etc.
-         * If you want to gather in-file licenses, feel free to remove this line or configure it
-         * differently.
-         */
-        extractComments: false,
-        terserOptions: {
-          format: {
-            comments: false,
-          },
-        },
+    entry: [
+      ...Repack.getInitializationEntries(reactNativePath, {
+        hmr: devServer && devServer.hmr,
       }),
+      entry,
     ],
-  },
-  module: {
+    resolve: {
+      /**
+       * `getResolveOptions` returns additional resolution configuration for React Native.
+       * If it's removed, you won't be able to use `<file>.<platform>.<ext>` (eg: `file.ios.js`)
+       * convention and some 3rd-party libraries that specify `react-native` field
+       * in their `package.json` might not work correctly.
+       */
+      ...Repack.getResolveOptions(platform),
+
+      /**
+       * Uncomment this to ensure all `react-native*` imports will resolve to the same React Native
+       * dependency. You might need it when using workspaces/monorepos or unconventional project
+       * structure. For simple/typical project you won't need it.
+       */
+      // alias: {
+      //   'react-native': reactNativePath,
+      // },
+    },
+    /**
+     * Configures output.
+     * It's recommended to leave it as it is unless you know what you're doing.
+     * By default Webpack will emit files into the directory specified under `path`. In order for the
+     * React Native app use them when bundling the `.ipa`/`.apk`, they need to be copied over with
+     * `Repack.OutputPlugin`, which is configured by default inside `Repack.RepackPlugin`.
+     */
+    output: {
+      clean: true,
+      path: path.join(dirname, 'build/generated', platform),
+      filename: 'index.bundle',
+      chunkFilename: '[name].chunk.bundle',
+      publicPath: Repack.getPublicPath({ platform, devServer }),
+    },
     /**
-     * This rule will process all React Native related dependencies with Babel.
-     * If you have a 3rd-party dependency that you need to transpile, you can add it to the
-     * `include` list.
-     *
-     * You can also enable persistent caching with `cacheDirectory` - please refer to:
-     * https://github.com/babel/babel-loader#options
+     * Configures optimization of the built bundle.
      */
-    rules: [
-      {
-        test: /\.[jt]sx?$/,
-        include: [
-          /node_modules(.*[/\\])+react\//,
-          /node_modules(.*[/\\])+react-native/,
-          /node_modules(.*[/\\])+@react-native/,
-          /node_modules(.*[/\\])+@react-navigation/,
-          /node_modules(.*[/\\])+@react-native-community/,
-          /node_modules(.*[/\\])+@expo/,
-          /node_modules(.*[/\\])+pretty-format/,
-          /node_modules(.*[/\\])+metro/,
-          /node_modules(.*[/\\])+abort-controller/,
-          /node_modules(.*[/\\])+@callstack[/\\]repack/,
-        ],
-        use: 'babel-loader',
-      },
+    optimization: {
+      /** Enables minification based on values passed from React Native CLI or from fallback. */
+      minimize,
+      /** Configure minimizer to process the bundle. */
+      minimizer: [
+        new TerserPlugin({
+          test: /\.(js)?bundle(\?.*)?$/i,
+          /**
+           * Prevents emitting text file with comments, licenses etc.
+           * If you want to gather in-file licenses, feel free to remove this line or configure it
+           * differently.
+           */
+          extractComments: false,
+          terserOptions: {
+            format: {
+              comments: false,
+            },
+          },
+        }),
+      ],
+      chunkIds: 'named',
+    },
+    module: {
       /**
-       * Here you can adjust loader that will process your files.
+       * This rule will process all React Native related dependencies with Babel.
+       * If you have a 3rd-party dependency that you need to transpile, you can add it to the
+       * `include` list.
        *
        * You can also enable persistent caching with `cacheDirectory` - please refer to:
        * https://github.com/babel/babel-loader#options
        */
-      {
-        test: /\.[jt]sx?$/,
-        exclude: /node_modules/,
-        use: {
-          loader: 'babel-loader',
-          options: {
-            /** Add React Refresh transform only when HMR is enabled. */
-            plugins: devServer.hmr ? ['module:react-refresh/babel'] : undefined,
+      rules: [
+        {
+          test: /\.[jt]sx?$/,
+          include: [
+            /node_modules(.*[/\\])+react\//,
+            /node_modules(.*[/\\])+react-native/,
+            /node_modules(.*[/\\])+@react-native/,
+            /node_modules(.*[/\\])+@react-navigation/,
+            /node_modules(.*[/\\])+@react-native-community/,
+            /node_modules(.*[/\\])+@expo/,
+            /node_modules(.*[/\\])+pretty-format/,
+            /node_modules(.*[/\\])+metro/,
+            /node_modules(.*[/\\])+abort-controller/,
+            /node_modules(.*[/\\])+@callstack\/repack/,
+          ],
+          use: 'babel-loader',
+        },
+        /**
+         * Here you can adjust loader that will process your files.
+         *
+         * You can also enable persistent caching with `cacheDirectory` - please refer to:
+         * https://github.com/babel/babel-loader#options
+         */
+        {
+          test: /\.[jt]sx?$/,
+          exclude: /node_modules/,
+          use: {
+            loader: 'babel-loader',
+            options: {
+              /** Add React Refresh transform only when HMR is enabled. */
+              plugins:
+                devServer && devServer.hmr
+                  ? ['module:react-refresh/babel']
+                  : undefined,
+            },
           },
         },
-      },
+        /**
+         * This loader handles all static assets (images, video, audio and others), so that you can
+         * use (reference) them inside your application.
+         *
+         * If you wan to handle specific asset type manually, filter out the extension
+         * from `ASSET_EXTENSIONS`, for example:
+         * ```
+         * Repack.ASSET_EXTENSIONS.filter((ext) => ext !== 'svg')
+         * ```
+         */
+        {
+          test: Repack.getAssetExtensionsRegExp(Repack.ASSET_EXTENSIONS),
+          use: {
+            loader: '@callstack/repack/assets-loader',
+            options: {
+              platform,
+              devServerEnabled: Boolean(devServer),
+              /**
+               * Defines which assets are scalable - which assets can have
+               * scale suffixes: `@1x`, `@2x` and so on.
+               * By default all images are scalable.
+               */
+              scalableAssetExtensions: Repack.SCALABLE_ASSETS,
+            },
+          },
+        },
+      ],
+    },
+    plugins: [
       /**
-       * This loader handles all static assets (images, video, audio and others), so that you can
-       * use (reference) them inside your application.
+       * Configure other required and additional plugins to make the bundle
+       * work in React Native and provide good development experience with
+       * sensible defaults.
        *
-       * If you wan to handle specific asset type manually, filter out the extension
-       * from `ASSET_EXTENSIONS`, for example:
-       * ```
-       * ReactNative.ASSET_EXTENSIONS.filter((ext) => ext !== 'svg')
-       * ```
+       * `Repack.RepackPlugin` provides some degree of customization, but if you
+       * need more control, you can replace `Repack.RepackPlugin` with plugins
+       * from `Repack.plugins`.
        */
-      {
-        test: ReactNative.getAssetExtensionsRegExp(
-          ReactNative.ASSET_EXTENSIONS
-        ),
-        use: {
-          loader: '@callstack/repack/assets-loader',
-          options: {
-            platform,
-            devServerEnabled: devServer.enabled,
-            /**
-             * Defines which assets are scalable - which assets can have
-             * scale suffixes: `@1x`, `@2x` and so on.
-             * By default all images are scalable.
-             */
-            scalableAssetExtensions: ReactNative.SCALABLE_ASSETS,
-          },
+      new Repack.RepackPlugin({
+        context,
+        mode,
+        platform,
+        devServer,
+        output: {
+          bundleFilename,
+          sourceMapFilename,
+          assetsPath,
         },
-      },
+      }),
     ],
-  },
-  plugins: [
-    /**
-     * Various libraries like React and React rely on `process.env.NODE_ENV` / `__DEV__`
-     * to distinguish between production and development
-     */
-    new webpack.DefinePlugin({
-      __DEV__: JSON.stringify(dev),
-    }),
-
-    /**
-     * This plugin makes sure the resolution for assets like images works with scales,
-     * for example: `image@1x.png`, `image@2x.png`.
-     */
-    new ReactNative.AssetsResolverPlugin({
-      platform,
-    }),
-
-    /**
-     * React Native environment (globals and APIs that are available inside JS) differ greatly
-     * from Web or Node.js. This plugin ensures everything is setup correctly so that features
-     * like Hot Module Replacement will work correctly.
-     */
-    new ReactNative.TargetPlugin(),
-
-    /**
-     * By default Webpack will emit files into `output.path` directory (eg: `<root>/build/ios`),
-     * but in order to for the React Native application to include those files (or a subset of those)
-     * they need to be copied over to correct output directories supplied from React Native CLI
-     * when bundling the code (with `webpack-bundle` command).
-     * All remote chunks will be placed under `remoteChunksOutput` directory (eg: `<root>/build/<platform>/remote` by default).
-     * In development mode (when development server is running), this plugin is a no-op.
-     */
-    new ReactNative.OutputPlugin({
-      platform,
-      devServerEnabled: devServer.enabled,
-      remoteChunksOutput: path.join(__dirname, 'build', platform, 'remote'),
-    }),
-
-    /**
-     * Runs development server when running with React Native CLI start command or if `devServer`
-     * was provided as s `fallback`.
-     */
-    new ReactNative.DevServerPlugin({
-      platform,
-      ...devServer,
-    }),
-
-    /**
-     * Configures Source Maps for the main bundle based on CLI options received from
-     * React Native CLI or fallback value..
-     * It's recommended to leave the default values, unless you know what you're doing.
-     * Wrong options might cause symbolication of stack trace inside React Native app
-     * to fail - the app will still work, but you might not get Source Map support.
-     */
-    new webpack.SourceMapDevToolPlugin({
-      test: /\.(js)?bundle$/,
-      exclude: /\.chunk\.(js)?bundle$/,
-      filename: '[file].map',
-      append: `//# sourceMappingURL=[url]?platform=${platform}`,
-      /**
-       * Uncomment for faster builds but less accurate Source Maps
-       */
-      // columns: false,
-    }),
-
-    /**
-     * Configures Source Maps for any additional chunks.
-     * It's recommended to leave the default values, unless you know what you're doing.
-     * Wrong options might cause symbolication of stack trace inside React Native app
-     * to fail - the app will still work, but you might not get Source Map support.
-     */
-    new webpack.SourceMapDevToolPlugin({
-      test: /\.(js)?bundle$/,
-      include: /\.chunk\.(js)?bundle$/,
-      filename: '[file].map',
-      append: `//# sourceMappingURL=[url]?platform=${platform}`,
-      /**
-       * Uncomment for faster builds but less accurate Source Maps
-       */
-      // columns: false,
-    }),
-
-    /**
-     * Logs messages and progress.
-     * It's recommended to always have this plugin, otherwise it might be difficult
-     * to figure out what's going on when bundling or running development server.
-     */
-    new ReactNative.LoggerPlugin({
-      platform,
-      devServerEnabled: devServer.enabled,
-      output: {
-        console: true,
-        /**
-         * Uncomment for having logs stored in a file to this specific compilation.
-         * Compilation for each platform gets it's own log file.
-         */
-        // file: path.join(__dirname, `${mode}.${platform}.log`),
-      },
-    }),
-  ],
+  };
 };
TIP

After applying these changes to your Webpack config, try bundling your application or running it.

If you have build errors try adjusting Webpack config based on what the errors are saying. After addressing build errors, you should be able to run the application.

CAUTION

If you are facing runtime errors or crashes, please open an issue!

ChunkManager

We renamed ChunkManager to ScriptManager, changing its underlying logic. You can find details here. To migrate you'll need to:

  • replace ChunkManager.configure(...) with ScriptManager.shared.addResolver(async (scriptId, caller)=> {...})
  • take function passe to resolveRemoteChunks config option in ChunkManager and use it in ScriptManager.shared.addResolver(...)
  • rename chunkId and parentChunkIdto scriptId and caller- adjust naming convention in your project to avoid mistakes in the future
  • replace ChunkManager.loadChunk with ScriptManager.loadScript
TIP

To adjust resolve implementation you might also use exposed utlities from Script:

  • Script.getDevSeverURL(scriptId)
  • Script.getRemoteURL(url)
  • Script.getFileSystemURL(scriptId)

See examples here

TIP

To migrate from V2 where Module Federtion was implemented please keep in mind that Fedrated utilities have set of helpers to work with Module Federation

  • Federated.createURLResolver({containers: Record<string,string>}) - resolver to be used in ScriptManager.shared.addResolver
  • Federated.importModule - handle loading dynamic containers

See examples here and here

TIP

Read more about support of Module Federation out of the box here