Trouble rendering "styled-components/native" with "react-native-web" in React Native (CLI) project for Web
Asked Answered
C

0

1

I've been working on a React Native (CLI) project for a few months, and it's almost ready for release on mobile. Now, I need to make the project run on browser as well, for that I'm trying to use "react-native-web". I've encountered difficulties getting "styled-components/native", "date-fns", and other packages to work with "react-native-web".

Following the steps outlined in this guide, I successfully configured webpack for a basic App.tsx with a Hello World message. Yet, challenges persist when incorporating trying to use styled components, even when I use "styled-components" import (without the native) by setting things like styled(View)instead of styled.View. It appears that webpack isn't cooperating as expected with the "styled-components/native" package.

Below is the code from my web/webpack.config.js:

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

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

const babelConfig = require('../babel.config');

const libAliases = {
  'styled-components/native': require.resolve('styled-components'),
  'react-native-text-input-mask': require.resolve('inputmask'),
  'lottie-react-native': require.resolve('lottie-web'),
};

babelConfig.plugins[0][1].alias = {
  'react-native$': require.resolve('react-native-web'),
  ...babelConfig.plugins[0][1].alias,
  ...libAliases,
};

// Babel loader configuration
const babelLoaderConfiguration = {
  test: /\.(tsx|jsx|ts|js)?$/,
  exclude: [
    {
      and: [
        // babel will exclude these from transpling
        path.resolve(appDirectory, 'node_modules'),
        path.resolve(appDirectory, 'ios'),
        path.resolve(appDirectory, 'android'),
      ],
      // whitelisted modules to be transpiled by babel
      not: [],
    },
  ],
  use: {
    loader: 'babel-loader',
    options: {
      cacheDirectory: true,
      // Presets and plugins imported from main babel.config.js in root dir
      presets: babelConfig.presets,
      plugins: ['react-native-web', ...(babelConfig.plugins || [])],
    },
  },
};

// Image loader configuration
const imageLoaderConfiguration = {
  test: /\.(gif|jpe?g|png|svg)$/,
  use: {
    loader: 'url-loader',
    options: {
      name: '[name].[ext]',
      esModule: false,
    },
  },
};

// File loader configuration
const fileLoaderConfiguration = {
  test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
  use: [
    {
      loader: 'file-loader',
      options: {
        name: '[name].[ext]',
        outputPath: 'fonts/',
      },
    },
  ],
};

module.exports = (argv) => {
  return {
    entry: path.resolve(appDirectory, 'index'),
    output: {
      clean: true,
      path: path.resolve(appDirectory, 'web/dist'),
      filename: '[name].[chunkhash].js',
      sourceMapFilename: '[name].[chunkhash].map',
      chunkFilename: '[id].[chunkhash].js',
    },
    resolve: {
      extensions: [
        '.web.js',
        '.js',
        '.web.ts',
        '.ts',
        '.web.jsx',
        '.jsx',
        '.web.tsx',
        '.tsx',
      ],
    },
    module: {
      rules: [
        babelLoaderConfiguration,
        imageLoaderConfiguration,
        fileLoaderConfiguration,
        {
          test: /\.js$/,
          include: [
            path.resolve(appDirectory, 'src'), // your source folder
            path.resolve(appDirectory, 'node_modules/date-fns'), // date-fns
            path.resolve(appDirectory, 'node_modules/@babel/runtime'), // @babel/runtime
          ],
          use: 'babel-loader',
        },
      ],
    },
    plugins: [
      // Fast refresh plugin
      new ReactRefreshWebpackPlugin(),

      // Plugin that takes public/index.html and injects script tags with the built bundles
      new HtmlWebpackPlugin({
        template: path.resolve(appDirectory, 'web/public/index.html'),
      }),

      // Defines __DEV__ and process.env as not being null
      new webpack.DefinePlugin({
        __DEV__: argv.mode !== 'production' || true,
        process: { env: {} },
      }),
    ],
    optimization: {
      // Split into vendor and main js files
      splitChunks: {
        cacheGroups: {
          commons: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendor',
            chunks: 'initial',
          },
        },
      },
    },
  };
};

And here's my babel.config.json (that remains intact since before react-native-web):

{
  "plugins": [
    [
      "module-resolver",
      {
        "root": ["./"],
        "alias": {
          "@assets": "./assets",
          "@components": "./src/components",
          "@context": "./src/context",
          "@lib": "./src/lib",
          "@utils": "./src/utils",
          "@src": "./src"
        }
      }
    ],
    "babel-plugin-styled-components",
    "module:react-native-dotenv"
  ],
  "presets": ["module:metro-react-native-babel-preset"],
  "env": {
    "production": {
      "plugins": ["react-native-paper/babel"]
    }
  }
}

And here's the current stack (for making the reading easier, I'm just listing the most relevant packages):

    "react": "18.2.0",
    "react-native": "0.72.6",
    "@react-navigation/native": "^6.1.9",
    "@react-navigation/bottom-tabs": "^6.5.11",
    "@react-navigation/native-stack": "^6.9.16",
    "axios": "^1.6.2",
    "react-hook-form": "^7.48.2",
    "@hookform/resolvers": "^3.3.2",
    "yup": "^1.3.2",
    "zustand": "^4.4.4",
    "styled-components": "^6.1.0",
    "@react-native-async-storage/async-storage": "^1.19.3",
    "date-fns": "^2.30.0",
    "react-native-vector-icons": "^10.0.1",
    "react-native-paper": "^5.11.1"

I've attempted aliasing and configuring babel, but I'm still facing challenges. Can someone help me troubleshoot this issue? Additionally, I'm considering whether Next.js might offer a solution by transpiling these packages. However, I lack clarity on the implementation process or if achieving the same outcome without Next.js is feasible.

I would greatly appreciate any guidance on effectively configuring my project to target both mobile and web platforms.

Thank you in advance for your assistance!


Edit 1

I forgot to add my output, but the thing is that I'm getting NO error whatsoever. It just refuses to render, without any verbosity.

App.tsx:

import React from 'react';
import { Dimensions, Text, View } from 'react-native';
import styled from 'styled-components';

const { height, width } = Dimensions.get('window');

const HelloWorldContainer = styled(View)`
  flex: 1;
  height: ${height}px;
  width: ${width}px;
  justify-content: center;
  align-items: center;
`;

const HelloWorldText = styled(Text)`
  font-size: 58px;
`;

const HelloWorld = () => {
  return (
    <HelloWorldContainer>
      <HelloWorldText>Hello World!</HelloWorldText>
    </HelloWorldContainer>
  );
};

export default HelloWorld;

The terminal output when running webpack-dev-server --config ./web/webpack.config.js --mode development:

> [email protected] web /Users/shibli/repositories/member
> webpack-dev-server --config ./web/webpack.config.js --mode development

<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:8080/
<i> [webpack-dev-server] On Your Network (IPv4): http://192.168.0.9:8080/
<i> [webpack-dev-server] On Your Network (IPv6): http://[fe80::1]:8080/
<i> [webpack-dev-server] Content not from webpack is served from '/Users/shibli/repositories/member/public' directory
<i> [webpack-dev-middleware] wait until bundle finished: /
asset vendor.ed39544ad714cff0e901.js 1.96 MiB [emitted] [immutable] (name: vendor) (id hint: commons)
asset main.9333044db6f8b6508335.js 54.2 KiB [emitted] [immutable] (name: main)
asset index.html 1.02 KiB [emitted]
Entrypoint main 2.01 MiB = vendor.ed39544ad714cff0e901.js 1.96 MiB main.9333044db6f8b6508335.js 54.2 KiB
runtime modules 31.2 KiB 15 modules
modules by path ./node_modules/ 1.58 MiB 460 modules
./index.js 1.62 KiB [built] [code generated]
./src/App.tsx 2.14 KiB [built] [code generated]
./app.json 57 bytes [built] [code generated]
webpack 5.90.3 compiled successfully in 21839 ms
<i> [webpack-dev-middleware] wait until bundle finished: /vendor.ed39544ad714cff0e901.js
<i> [webpack-dev-middleware] wait until bundle finished: /main.9333044db6f8b6508335.js
assets by status 1.96 MiB [cached] 1 asset
assets by info 55.1 KiB [immutable]
  asset main.90bdf61fc277eb649d96.js 54.2 KiB [emitted] [immutable] (name: main)
  asset main.3db06c133c551b7014f6.hot-update.js 849 bytes [emitted] [immutable] [hmr] (name: main)
  asset main.3db06c133c551b7014f6.hot-update.json 28 bytes [emitted] [immutable] [hmr]
asset index.html 1.02 KiB [emitted]
Entrypoint main 2.01 MiB = vendor.ed39544ad714cff0e901.js 1.96 MiB main.90bdf61fc277eb649d96.js 54.2 KiB main.3db06c133c551b7014f6.hot-update.js 849 bytes
cached modules 1.58 MiB [cached] 463 modules
runtime modules 31.2 KiB 15 modules
webpack 5.90.3 compiled successfully in 72 ms
assets by path *.js 1.95 MiB
  asset vendor.c259af1f674065728274.js 1.89 MiB [emitted] [immutable] (name: vendor) (id hint: commons)
  asset main.6433fab5e93948076c82.js 52.8 KiB [emitted] [immutable] (name: main)
  asset main.129784fe3c79354dbc8a.hot-update.js 3.74 KiB [emitted] [immutable] [hmr] (name: main)
asset index.html 1.02 KiB [emitted]
asset main.129784fe3c79354dbc8a.hot-update.json 419 bytes [emitted] [immutable] [hmr]
Entrypoint main 1.95 MiB = vendor.c259af1f674065728274.js 1.89 MiB main.6433fab5e93948076c82.js 52.8 KiB main.129784fe3c79354dbc8a.hot-update.js 3.74 KiB
cached modules 1.52 MiB [cached] 455 modules
runtime modules 30.6 KiB 12 modules
./src/App.tsx 1.9 KiB [built] [code generated]
webpack 5.90.3 compiled successfully in 106 ms

Then the page looks like it ignored styled-components: Hello world message at the top left, looking like not a single format was applied into it

But, if I change my App.tsx to work with StyleSheet from react-native, it does apply the styling: A page where the text "Hello World" is centered horizontally and vertically, clearly with a change on its font-size

import React from 'react';
import { Dimensions, Text, View, StyleSheet } from 'react-native';

const { height, width } = Dimensions.get('window');

const HelloWorld = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Hello World!</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    height: height,
    width: width,
    justifyContent: 'center',
    alignItems: 'center',
  },
  text: {
    fontSize: 58,
  },
});

export default HelloWorld;

Why don't I go for it and change every single style?

I can't change all the styling files throughout the project, because there are more than 100 of them and this change could mess their funcitonality.


Edit 2

Here's my minimal reproducible example (kinda minimal, you just clone it):

Correlation answered 5/3 at 22:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.