oblador / react-native-vector-icons

Customizable Icons for React Native with support for image source and full styling.
https://oblador.github.io/react-native-vector-icons/
MIT License
17.31k stars 2.12k forks source link

Using with Next.js #1447

Closed habovh closed 11 months ago

habovh commented 1 year ago

Environment

"react": "17.0.2",
"react-native": "0.65.1",
"react-native-vector-icons": "^9.1.0",
"react-native-web": "^0.18.1",

Description

I'm trying to setup a NextJS app with React Native Web and React Native Vector Icons. I've got the RN-web part working properly, but I can't get RN-vector-icons to work. As soon as a page includes a component coming from RN-vector-icon, I get the following error:

../app/node_modules/react-native-vector-icons/lib/create-icon-set.js
Module parse failed: Unexpected token (91:8)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| 
|       return (
>         <Text selectable={false} {...props}>
|           {glyph}
|           {children}

After some searching it appears I'm not the only one with the issue, yet I can't seem to find a solution. On Next.js, this issue mentions non-transpired code and suggests using a plugin to fix the issue. However it does not resolve the issue. Here are a few links/resources I used to try and figure out what's wrong in my setup:

Here's my next.config.js including the custom webpack configuration. I am quite certain that this can be fixed by configuring webpack further but I can't see how:

module.exports = withImages(withFonts({
  experimental: {
    externalDir: true
  },
  // This feature conflicts with next-images
  images: {
    disableStaticImages: true,
  },
  webpack: (config, options) => {
    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,
    ]

    if (options.isServer) {
      config.externals = ['react', 'react-native-web', ...config.externals];
    }
    config.resolve.alias['react'] = path.resolve(__dirname, '.', 'node_modules', 'react');
    config.resolve.alias['react-native-web'] = path.resolve(__dirname, '.', 'node_modules', 'react-native-web');

    config.module.rules.push({
      test: /\.ttf$/,
      loader: "url-loader", // or directly file-loader
      include: [
        // Not sure which one should work so I added both possible paths
        path.resolve(__dirname, "..", "app", "node_modules", "react-native-vector-icons"), // as reported by the error, imported from monorepo shared code package
        path.resolve(__dirname, ".", "node_modules", "react-native-vector-icons"), // from this package
      ],
    })

    return config;
  }
}));

And here's my Icon component:

import React from 'react'
import MaterialIcon from 'react-native-vector-icons/MaterialCommunityIcons'
import type { IconProps } from 'react-native-vector-icons/icon'

export const Icon = (props: IconProps) => (
  <MaterialIcon {...props} />
)

Note: when using the /dist version ('react-native-vector-icons/dist/MaterialCommunityIcons'), I'm getting another error: Error: Cannot find module 'react', so I believe it's best to use it like so?

Do you guys have any idea/guidance as to how I can get react-native-vector-icons working with my Next.js setup?

dennervidal commented 1 year ago

I'm having a similar issue with vitejs, like the < from <Text selectable={false} {...props}> on create-icon-set.js is not a expected token. I've tried all possible combinations, no success yet.

dootMaster commented 1 year ago

Can you try a different bundler or are you rooted to webpack?

dennervidal commented 1 year ago

I was able to resolve my issue. What I just did is import everything in react-native-vector-icons from dist folder. In my case, rollup wasn't transpiling code from the lib root (esm), so the correct way is to use already bundled (cjs) code.

mohamedabady commented 1 year ago

adding to @dennervidal i also needed to include all icons fonts in index.css with @font-face css specifier

@font-face { font-family: MaterialCommunityIcons; src: url('./node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf'); }

mohamedabady commented 1 year ago

don't forget to alias the import statement in package.json "alias": { ..., "react-native-vector-icons/MaterialCommunityIcons": "react-native-vector-icons/dist/MaterialCommunityIcons" }

bombillazo commented 1 year ago

Any update for support with next ?

cglacet commented 1 year ago

@habovh Did you found a clean solution to this?

cglacet commented 1 year ago

I use react-native-vector-icons v9.2.0

I feel like the error is not related to react-native-vector-icons in particular and that the issue is compiling JSX.

I managed to compile JSX using a combination of next-transpile-modules and next transpilePackages. I have no experience with this, but that seems extremely odd, from what I understand I should transpile with any of the two, not both.

If I use any of these two separately, I get the same kind of error as you did, but if I use both I have no error. To be more precise, if I only use the transpilePackages configuration:

/** @type {import('next').NextConfig} */
const configuration = {
    transpilePackages: modulesToTranspile,
}

module.exports = configuration;

I get the same error as you did.

On the other hand, if I use next-transpile-modules on its own:

const withTM = require('next-transpile-modules');

/** @type {import('next').NextConfig} */
const configuration = {
  // transpilePackages: modulesToTranspile,
}

module.exports = withTM(modulesToTranspile)(configuration);

I get some typescript error as soon as some typescript is encountered, for example on a line like this:

export type X = {};

I get the error:

Module parse failed: Unexpected token (19:7)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

Now, if I use both, things work:

const withTM = require('next-transpile-modules');

/** @type {import('next').NextConfig} */
const configuration = {
  transpilePackages: modulesToTranspile,
}

module.exports = withTM(modulesToTranspile)(configuration);

Strange.

Anyway, after doing all this, I got a page running and what seems to be a fontawsome5 icon, but it shows a square instead of the icon. Here is the generated HTML:

<html style="height: 100%">
    <head>
        <style>
            @font-face {
                font-family: "FontAwesome5_Brands";
                src: url(/_next/static/media/FontAwesome5_Brands.514cb523.ttf)
                    format("truetype");
            }

            @font-face {
                font-family: "FontAwesome5_Regular";
                src: url(/_next/static/media/FontAwesome5_Regular.71de5c0a.ttf)
                    format("truetype");
            }

            @font-face {
                font-family: "FontAwesome5_Solid";
                src: url(/_next/static/media/FontAwesome5_Solid.d1d5a4a8.ttf)
                    format("truetype");
            }
        </style>
    </head>
    <body style="height: 100%; overflow: hidden">
        <div id="__next">
            <div
                class="css-view-175oi2r r-alignItems-1awozwy r-flexGrow-16y2uox r-justifyContent-1777fci"
            >
                <h1
                    dir="auto"
                    role="heading"
                    class="css-text-1rynq56 r-alignItems-1awozwy r-fontSize-1x35g6 r-marginBottom-1peese0"
                >
                    React Native for Web &amp; Next.js
                    <div
                        dir="auto"
                        class="css-text-1rynq56 r-userSelect-lrvibr"
                        style="
                            font-size: 12px;
                            font-family: FontAwesome5_Brands;
                            font-weight: normal;
                            font-style: normal;
                        "
                    >
                         <!-- here is the square character that is not visible here, but shows on chrome -->
                    </div>
                </h1>
            </div>
        </div>
    </body>
</html>

Update

import Head from 'next/head';
import * as React from 'react';
import '../styles.css';

function MyApp({ Component, pageProps }) {
    return (
        <>
            <Head>
                <meta
                    name="viewport"
                    content="width=device-width, initial-scale=1"
                />
            </Head>
            <Component {...pageProps} />
        </>
    );
}

export default MyApp;

With the style.css file containing only my definitions for fontawsome font-face, for example, for the brands font:

@font-face {
    font-family: 'FontAwesome5_Brands';
    font-style: normal;
    font-weight: 400;
    font-display: block;
    src: url(./public/fonts/fa-brands-400.eot);
    src: url(./public/fonts/fa-brands-400.eot?#iefix)
            format('embedded-opentype'),
        url(./public/fonts/fa-brands-400.woff2) format('woff2'),
        url(./public/fonts/fa-brands-400.woff) format('woff'),
        url(./public/fonts/fa-brands-400.ttf) format('truetype'),
        url(./public/fonts/fa-brands-400.svg#fontawesome) format('svg');
}

Suspicious findings

I noticed that when I go in the "computed" style section in chrome inspector (at https://localhost:3000), I see something like:

"Rendered Fonts: Times—Local file(1 glyph)"

Which I different from what I see on fontawsome's cheatsheet:

"Rendered Fonts: Font Awesome 5 Brands Regular—Network resource(1 glyph)"

The font-familly name is different but its normal because this package generates FontAwesome5_Brands not Font Awesome 5 Brands like the cheatsheet use.

cglacet commented 1 year ago

Finally found a workaround. I stumbled upon this question: Self-Hosted FontAwesome Icons Not Rendering on OS X Sierra, so I tested it. I replaced my local file by the one hosted on fontawsome and it worked:

/* ./styles.css */
@font-face {
    font-family: 'FontAwesome5_Regular';
    src: url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-regular-400.eot);
    src: url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-regular-400.eot?#iefix)
            format('embedded-opentype'),
        url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-regular-400.woff2)
            format('woff2'),
        url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-regular-400.woff)
            format('woff'),
        url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-regular-400.ttf)
            format('truetype'),
        url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-regular-400.svg#fontawesome)
            format('svg');
}

@font-face {
    font-family: 'FontAwesome5_Solid';
    font-style: normal;
    font-weight: 900;
    font-display: block;
    src: url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-solid-900.eot);
    src: url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-solid-900.eot?#iefix)
            format('embedded-opentype'),
        url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-solid-900.woff2)
            format('woff2'),
        url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-solid-900.woff)
            format('woff'),
        url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-solid-900.ttf)
            format('truetype'),
        url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-solid-900.svg#fontawesome)
            format('svg');
}

@font-face {
    font-family: 'FontAwesome5_Brands';
    font-style: normal;
    font-weight: 400;
    font-display: block;
    src: url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-brands-400.eot);
    src: url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-brands-400.eot?#iefix)
            format('embedded-opentype'),
        url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-brands-400.woff2)
            format('woff2'),
        url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-brands-400.woff)
            format('woff'),
        url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-brands-400.ttf)
            format('truetype'),
        url(https://use.fontawesome.com/releases/v5.15.4/webfonts/fa-brands-400.svg#fontawesome)
            format('svg');
}

I kept everything else as described before, mainly, I kept the import "../styles.css" in ./pages/_app.tsx.

angelcgdev commented 1 year ago

I have the same ussue, any update about it?

haveamission commented 1 year ago

I have the same ussue, any update about it?

Same here too

jspizziri commented 1 year ago

Here's the solution that seems to be working for me. Effectively I just added a webpack loader for it:

// next.config.js

const nextConfig = {
  webpack: (config, options) => {
    ...
    // font loader for react-native-vector-icons
    config.module.rules.push({
      test: /\.(woff|woff2|ttf|eot|svg)$/,
      loader: 'file-loader',
      options: {
        esModule: false,
        name: '[name].[ext]',
        outputPath: 'static/media/fonts/',
        publicPath: '../assets/fonts/',
      },
    });
    ...
  },
};
filiptdz commented 11 months ago

If anyone's using the app router, this worked for me:

'use client';
import iconFont from 'react-native-vector-icons/Fonts/Fontisto.ttf';
const iconFontStyles = `@font-face {
  src: url(${iconFont});
  font-family: Fontisto;
}`;

const FontWrapper = ({ children }) => {
 useServerInsertedHTML(() => {
  return createElement('style', {
   dangerouslySetInnerHTML: {
    __html: iconFontStyles
   }
  });
 });

return children
}
yairopro commented 11 months ago

Why is this issue closed? 😭

There're only workarounds, hard to implements, and harder to understand. Still no documentation or smooth way to install. I encounter errors like Module parse failed: Unexpected token or You're importing a component that needs PureComponent. It only works in a Client Component... and so on by only trying to run next.js with this package.

And at the moment I don't have any other choice than to start using react-icons package even though your package react-native-vector-icons is so much easier to use. 😭

filiptdz commented 11 months ago

@yairopro I agree adding this information to the documentation would be nice, but it might be a tall order to try and cover all the edge cases of possible configurations for web projects. Specially considering that this project is focused on mobile, it might not be worth it to try and keep up with breaking changes on all the web frameworks. There'll always need to be a bit of interfacing between the framework you're using and this one to make it work, and it might be very specific to your configuration.

It's hard to say specifically, but reading from your message it seems like Module parse failed: Unexpected token could be an issue of not including the section to handle ttf files to the config.resolve.alias as outlined in this repo's docs and You're importing a component that needs PureComponent. It only works in a Client Component... probably means you're trying to import a component from this library on a NextJS server component. To solve this, read the section on the difference between server components and client components on NextJS' docs, but it's also not something this library can handle.

There are many examples on the comments of this issue and on the docs for the library of how to add CSS to make this library work, and how to load the font files using webpack. If porting that functionality to your specific framework doesn't make sense, you might need to familiarize yourself more with it, or it might be that the framework that's lacking documentation

achadee commented 8 months ago

Hey Guys if you add the vector icons to your transpile modules it should work,

here is mine as an example:

const nextConfig = {
  reactStrictMode: false,
  transpilePackages: [
    'react-native',
    'react-native-web',
    'solito',
    'moti',
    'app',
    'react-native-reanimated',
    'nativewind',
    'react-native-gesture-handler',
    'react-native-vector-icons',
  ],
}
rmarquois commented 6 months ago

In my case with Next JS 14 and @expo/next-adapter, I removed publicPath.

Here's the solution that seems to be working for me. Effectively I just added a webpack loader for it:

// next.config.js

const nextConfig = {
  webpack: (config, options) => {
    ...
    // font loader for react-native-vector-icons
    config.module.rules.push({
      test: /\.(woff|woff2|ttf|eot|svg)$/,
      loader: 'file-loader',
      options: {
        esModule: false,
        name: '[name].[ext]',
        outputPath: 'static/media/fonts/',
        publicPath: '../assets/fonts/', // <= I removed this
      },
    });
    ...
  },
};

I also added package @expo/vector-icons in transpilePackages like mentioned in previous response. It's also work for @expo-google-fonts package.