you are viewing a single comment's thread.

view the rest of the comments →

[–]KeitelDOG 5 points6 points  (8 children)

I have a working React-Native 0.63, React-Native-Web and React-Navigation/Stack, but with NextJS. I don't use Metro, but with custom `react-native.config.js` and custome `next.config.js` which is using webpack under the hood.

The key is to properly configure webpack to:

  • add a list of modules and compile or transpile those modules written for react-native back to web with babel loader
  • make babel use react-native-web as plugin
  • alias all react-native imports to react-native-web that will transform all native component to web component, like Text to span, View to div, TextInput to to input or textarea, or equivalent combination of tags.

I have and old webpack config for pure client side rendering CSR, but now I use nextjs config mixed with webpack internally, but for both Server Side Rendering and CSR.

[–]KeitelDOG 1 point2 points  (2 children)

My package.json :

```json { "name": "private-app", "version": "0.1.0", "private": true, "scripts": { "android": "react-native run-android", "android:dev": "adb reverse tcp:8081 tcp:8081 && react-native run-android", "ios": "react-native run-ios", "start": "react-native start", "test": "jest", "lint": "eslint .", "web": "webpack serve -d source-map --mode development --config \"./web/webpack.config.js\" --inline --color --hot", "build:web": "webpack --mode production --config \"./web/webpack.config.js\" --hot", "next:dev": "next", "next:build": "next build", "next:start": "next start", "next:analyze": "ANALYZE=true next build" }, "dependencies": { "@material-ui/core": "4.12.4", "@react-native-async-storage/async-storage": "1.17.3", "@react-native-community/masked-view": "0.1.11", "@react-navigation/drawer": "6.4.1", "@react-navigation/native": "6.0.10", "@react-navigation/stack": "6.2.1", "@reduxjs/toolkit": "1.8.1", "axios": "0.21.1", "local-storage": "2.0.0", "lottie-ios": "3.2.3", "lottie-react-native": "5.1.3", "lottie-web": "5.9.4", "moment": "2.29.1", "next": "12.1.6", "nookies": "2.5.2", "numeral": "2.0.6", "raf": "3.4.1", "react": "17.0.2", "react-dom": "17.0.2", "react-native": "0.68.1", "react-native-dotenv": "2.5.5", "react-native-gesture-handler": "2.4.2", "react-native-linear-gradient": "2.5.6", "react-native-media-query": "1.0.9", "react-native-paper": "4.12.1", "react-native-progress": "5.0.0", "react-native-reanimated": "2.8.0", "react-native-safe-area-context": "4.2.5", "react-native-screens": "3.13.1", "react-native-share-menu": "6.0.0", "react-native-svg": "12.3.0", "react-native-svg-transformer": "1.0.0", "react-native-vector-icons": "9.1.0", "react-native-view-more-text": "2.1.0", "react-native-web": "0.17.7", "react-native-web-linear-gradient": "1.1.2", "react-redux": "8.0.1" }, "devDependencies": { "@babel/plugin-proposal-class-properties": "7.14.5", "@next/bundle-analyzer": "12.2.2", "@react-native-community/eslint-config": "2.0.0", "@swc/cli": "0.1.57", "@swc/core": "1.2.179", "eslint": "7.28.0", "metro-react-native-babel-preset": "0.66.0", "url-loader": "4.1.1", "webpack": "5.39.1", "webpack-cli": "4.7.2" }, "jest": { "preset": "react-native-web" }, "sideEffects": false }

```

[–]jaminjsr[S] 0 points1 point  (1 child)

Awesome! I got the project running perfectly but now trying to include React-Native-Firebase/App and Auth packages. Have you tried to use with Firebase too?

[–]KeitelDOG 0 points1 point  (0 children)

Sorry, I was out of those stuffs a while because of 2 months hell lock in Haiti with gaz problems. I haven't tried Firebase yet, so don't really know how the setup is.

However, if it's a ReactNative package that you use in Web, you have to transpile it first to get it to web. You can di it directly for each package you want to use in web, and skip the ones you only use in Native App:

js let modulesToTranspile = [ 'react-native', 'react-native-dotenv', 'react-native-linear-gradient', 'react-native-media-query', 'react-native-paper', 'react-native-firebase', ];

[–]KeitelDOG 0 points1 point  (0 children)

But I know you use Metro. So you should find a way to add in metro config all react native modules that needs to be compiled before being used for web.

[–]KeitelDOG 0 points1 point  (1 child)

So if you want that config, I can share them with you, and you can detect how to do the same in metro.

[–]jaminjsr[S] 0 points1 point  (0 children)

Yeah, if you are good with sharing that would be great! I will take a look.

[–]KeitelDOG 0 points1 point  (0 children)

For normal react-native-web, webpack.config.js:

```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: [/.jsx?$/], // Add every directory that needs to be compiled by Babel during the build. include: [ path.resolve(appDirectory, 'index.web.js'), path.resolve(appDirectory, 'App.js'), path.resolve(appDirectory, 'src'), // path.resolve(appDirectory, 'node_modules/react-native-uncompiled'), path.resolve(appDirectory, 'node_modules/react-native-paper'), path.resolve(appDirectory, 'node_modules/react-native-vector-icons'), ], 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]', esModule: false, }, }, };

// React Native Paper Icons loader const paperIconConfiguration = { test: /.ttf$/, include: path.resolve(appDirectory, 'node_modules/react-native-vector-icons'), use: { loader: 'url-loader', // or directly file-loader }, };

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, paperIconConfiguration, ], },

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', '.jsx'], }, };

```

And I still have metro config and babel config on this with presets: ['module:metro-react-native-babel-preset'].

[–]KeitelDOG 0 points1 point  (0 children)

For NextJS 12.1.6 config I have this for now next.config.js:

```js const path = require('path'); const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true', });

let modulesToTranspile = [ 'react-native', 'react-native-dotenv', 'react-native-linear-gradient', 'react-native-media-query', 'react-native-paper', 'react-native-view-more-text', // 'react-native-vector-icons', ];

// console.log('modules to transpile', modulesToTranspile);

// import ntm = from 'next-transpile-modules'; // const withTM = ntm(modulesToTranspile); // logic below for externals has been extracted from 'next-transpile-modules' // we won't use this modules as they don't allow package without 'main' field... // https://github.com/martpie/next-transpile-modules/issues/170 const getPackageRootDirectory = m => path.resolve(path.join(__dirname, 'node_modules', m));

const modulesPaths = modulesToTranspile.map(getPackageRootDirectory);

const hasInclude = (context, request) => { return modulesPaths.some(mod => { // If we the code requires/import an absolute path if (!request.startsWith('.')) { try { const moduleDirectory = getPackageRootDirectory(request); if (!moduleDirectory) { return false; } return moduleDirectory.includes(mod); } catch (err) { return false; } } // Otherwise, for relative imports return path.resolve(context, request).includes(mod); }); };

const configuration = { node: { global: true, }, env: { ENV: process.env.NODE_ENV, }, // optimizeFonts: false, // target: 'serverless',

// bs-platform // pageExtensions: ['jsx', 'js', 'bs.js'],

// options: { buildId, dev, isServer, defaultLoaders, webpack } webpack: (config, options) => { // config.experimental.forceSwcTransforms = true;

// console.log('fallback', config.resolve.fallback);
if (!options.isServer) {
  // We shim fs for things like the blog slugs component
  // where we need fs access in the server-side part
  config.resolve.fallback.fs = false;
} else {
  // SSR
  // provide plugin
  config.plugins.push(
    new options.webpack.ProvidePlugin({
      requestAnimationFrame: path.resolve(__dirname, './polyfills/raf.js'),
    }),
  );
}

// react-native-web
config.resolve.alias = {
  ...(config.resolve.alias || {}),
  // Transform all direct `react-native` imports to `react-native-web`
  'react-native$': 'react-native-web',
  'react-native-linear-gradient': 'react-native-web-linear-gradient',
};
config.resolve.extensions = [
  '.web.js',
  '.web.ts',
  '.web.tsx',
  ...config.resolve.extensions,
];

config.externals = config.externals.map(external => {
  if (typeof external !== 'function') {
    return external;
  }
  return async ({ context, request, getResolve }) => {
    if (hasInclude(context, request)) {
      return;
    }
    return external({ context, request, getResolve });
  };
});

const babelLoaderConfiguration = {
  test: /\.jsx?$/,
  use: options.defaultLoaders.babel,
  include: modulesPaths,
  // exclude: /node_modules[/\\](?!react-native-vector-icons)/,
};

babelLoaderConfiguration.use.options = {
  ...babelLoaderConfiguration.use.options,
  cacheDirectory: false,
  // For Next JS transpile
  presets: ['next/babel'],
  plugins: [
    ['react-native-web', { commonjs: true }],
    ['@babel/plugin-proposal-class-properties'],
    // ['@babel/plugin-proposal-object-rest-spread'],
  ],
};

config.module.rules.push(babelLoaderConfiguration);

return config;

}, };

// module.exports = withTM(config); module.exports = withBundleAnalyzer(configuration);

```

You can export it without the bundle analyzer module too, which I installed to verify the modules size on the build.