DEV Community

Mike Hamilton
Mike Hamilton

Posted on

React Native Web platform specific web.js extension

I came across a great medium posts about creating a multiplatform (iOS/Android/Web) React Native project, which I highly recommend and can be found here. It's really great and should be added to the official react-native-web documentation, in my opinion!

However, there was one missing piece, and that was how to add the proper web.js extension. Out of the box, React Native is configured to automatically import files based on the platform being used. For example, you could have a Button.ios.js and a Button.android.js, and when you do import Button from './components/Button, React Native will be smart enough to pick the proper extension for the platform being built.

The medium article mentioned above doesn't cover properly adding the web.js extension, but it's really quite easy after understanding what is actually happening.

In the article, it has you pull in the react-scripts dependency, and then modify your package.json so you can do something like yarn web or npm run web and your web project will be built. What is actually happening is that react-scripts is a package from the create-react-app project. It does a lot of magic under the hood, but what it's doing for us in this case is pulling in webpack as a dependency so you can build your React Native Web project. React Native uses the metro bundler by default for ios and android projects.

So now that we know that webpack is being used under the hood, we can add a directory in the root of our project called web (to match the existing ios and android folders) that will hold the web specific config files. Once that is done, simply create a new file in web/webpack.config.js that contains the following

// web/webpack.config.js

const path = require('path');
const webpack = require('webpack');

const appDirectory = path.resolve(__dirname, '../');

// This is needed for webpack to compile JavaScript.
// Many OSS React Native packages are not compiled to ES5 before being
// published. If you depend on uncompiled packages they may cause webpack build
// errors. To fix this webpack can be configured to compile to the necessary
// `node_module`.
const babelLoaderConfiguration = {
  test: /\.js$/,
  // Add every directory that needs to be compiled by Babel during the build.
  include: [
    path.resolve(appDirectory, 'index.web.js'),
    path.resolve(appDirectory, 'src'),
    path.resolve(appDirectory, 'node_modules/react-native-uncompiled'),
  ],
  use: {
    loader: 'babel-loader',
    options: {
      cacheDirectory: true,
      // The 'metro-react-native-babel-preset' preset is recommended to match React Native's packager
      presets: ['module:metro-react-native-babel-preset'],
      // Re-write paths to import only the modules needed by the app
      plugins: ['react-native-web'],
    },
  },
};

// This is needed for webpack to import static images in JavaScript files.
const imageLoaderConfiguration = {
  test: /\.(gif|jpe?g|png|svg)$/,
  use: {
    loader: 'url-loader',
    options: {
      name: '[name].[ext]',
    },
  },
};

module.exports = {
  entry: [
    // load any web API polyfills
    // path.resolve(appDirectory, 'polyfills-web.js'),
    // your web-specific entry file
    path.resolve(appDirectory, 'index.web.js'),
  ],

  // configures where the build ends up
  output: {
    filename: 'bundle.web.js',
    path: path.resolve(appDirectory, 'dist'),
  },

  // ...the rest of your config

  module: {
    rules: [babelLoaderConfiguration, imageLoaderConfiguration],
  },

  resolve: {
    // This will only alias the exact import "react-native"
    alias: {
      'react-native$': 'react-native-web',
    },
    // If you're working on a multi-platform React Native app, web-specific
    // module implementations should be written in files using the extension
    // `.web.js`.
    extensions: ['.web.js', '.js'],
  },
};
Enter fullscreen mode Exit fullscreen mode

It's quite verbose, but in the end we are providing webpack a config file that adds extensions: ['.web.js', '.js'], to provide support for the new web.js extension. This webpack config was taken from the react-native-web documentation, and you can see that there's a // ...the rest of your config section, so if you have any webpack specific changes you should add them there. I haven't done a ton of testing, but with a new project created using the medium article, this webpack config seems to work.

Now we should be good to go, you can use file.js and it will apply to all platforms, file.native.js to apply to both iOS and Android projects (but not web) or file.web.js for only the web!

Oldest comments (2)

Collapse
 
seanmclem profile image
Seanmclem

Awesome description. Very helpful thanks. I'm using expo lately and using web as a target, but also developing universally for android/ios. I'll neet some platform specific code eventually and it's good to see how to do the web files vs native

Collapse
 
gorbypark profile image
Mike Hamilton • Edited

I've found that I don't use iOS or Android specific files very much, as any minor differences can be handled using if (Platform.OS === "ios") { ... } within the same file. However, when making a multi-platform app that targets phones (small screens) and the web (sometimes big screens), there are so many differences that it's easier to have separate files rather than dozens of if statements that make the code hard to follow.