Webpack Config

One of Re.Pack key design goals was to give as much power and control to the developer — to you. This means that Re.Pack doesn't hide anything under its own proprietary configuration, but uses regular Webpack configuration.

Configuration path

By default, when running react-native webpack-start or react-native webpack-bundle, Re.Pack will look for Webpack configuration in the following places:

  1. <root>/webpack.config.js
  2. <root>/.webpack/webpack.config.js
  3. <root>/.webpack/webpackfile

Where <root> is your project's root directory.

INFO

The paths above are the same ones that normal Webpack CLI looks for Webpack configuration.

If you want to store Webpack config in different place, you can provide custom path:

  • react-native webpack-start --webpackConfig <path>
  • react-native webpack-bundle --webpackConfig <path>
  • webpack-cli -c <path>
INFO

If the <path> is relative, then it will be resolved based on root directory detected by React Native CLI, which usually should point to your project's root directory.

Webpack config template

Every project is different, has different structure, uses different dependencies, has different requirements, so we cannot know what Webpack configuration your project might needs. Instead we give use a sensible and well-documented Webpack template to use as a base and adjust some bits based on your project.

You can find the template inside Re.Pack's repository here: https://github.com/callstack/repack/blob/main/templates/webpack.config.js

or copy it from below:

templates/webpack.config.js
const path = require('path');
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
const ReactNative = require('@callstack/repack');

/**
 * More documentation, installation, usage, motivation and differences with Metro is available at:
 * https://github.com/callstack/repack/blob/main/README.md
 *
 * The API documentation for the functions and plugins used in this file is available at:
 * https://re-pack.netlify.app/
 */

/**
 * This is the Webpack configuration file for your React Native project.
 * It can be used in 2 ways:
 * - by running React Native Community 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 Community CLI most of the values from `getMode`, `getPlatform`, etc. will be filled in by React Native Community 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 Community CLI when Webpack is run from `react-native start` or `react-native bundle`.
 *
 * 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.
 */

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;

/**
 * Webpack configuration.
 */
module.exports = {
  mode,
  /**
   * This should be always `false`, since the Source Map configuration is done
   * by `SourceMapDevToolPlugin`.
   */
  devtool: false,
  context,
  /**
   * `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.
   */
  entry: [
    ...ReactNative.getInitializationEntries(reactNativePath, {
      hmr: devServer.hmr,
    }),
    entry,
  ],
  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.
     */
    ...ReactNative.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
   * `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 Community 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,
          },
        },
      }),
    ],
  },
  module: {
    /**
     * 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
     */
    rules: [
      {
        test: /\.[jt]sx?$/,
        include: [
          /node_modules(.*[/\\])+react/,
          /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.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:
       * ```
       * ReactNative.ASSET_EXTENSIONS.filter((ext) => ext !== 'svg')
       * ```
       */
      {
        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,
          },
        },
      },
    ],
  },
  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 Community 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 Community 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 Community 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`),
      },
    }),
  ],
};

We highly encourage to create recipes for adding or adjusting the support for other common use cases not included in the default template. We welcome any PR!

Re.Pack's config APIs

To make Webpack-produced bundle compatible and runnable by React Native, Re.Pack gives you a set of Webpack plugins:

Except for JavaScriptLooseModePlugin all of them are required — not including any of them might break your build on make the bundle crash at runtime.

Additionally to let you use React Native CLI in form of react-native webpack-start or react-native webpack-bundle commands, Re.Pack gives you helper functions to get arguments project information from React Native CLI and use them inside your Webpack config:

CAUTION

It entirely possible not to use the functions above, but it's highly recommended to leave them.

If you're planning on using Webpack CLI to run development server or bundle your application instead of relying on React Native CLI, then you might want to look into providing fallback values to each of these functions, for example:

const context = ReactNative.getContext({ fallback: __dirname });

Those fallback values are only used when Webpack compilation is created by Webpack CLI instead of React Native CLI.

CAUTION

It's highly recommended to use React Native CLI and react-native webpack-start or react-native webpack-bundle commands unless you have previous experience with Webpack and you know what you're doing.