7

I've setup monorepo project with react-native and react-native-web. I am sharing the same codebase for Android, iOS and Web. after installed react-native-vector-icons I've run the code in all three platforms and it works fine in Android and iOS but not in Web. In the web it looks like below:

enter image description here

I've set up Webpack as per the description here

below is my Webpack config config-overrides.js:

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

const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);

// our packages that will now be included in the CRA build step
const appIncludes = [
    resolveApp('src'),
    resolveApp('../components/src'),
];
//below is the config for react-native-vector-icons
const ttf = {
    test: /\.ttf$/,
    loader: "file-loader",
    include: path.resolve(__dirname, "../../node_modules/react-native-vector-icons"),
};
module.exports = function override(config, env) {
    // allow importing from outside of src folder
    config.resolve.plugins = config.resolve.plugins.filter(
        plugin => plugin.constructor.name !== 'ModuleScopePlugin'
    );
    config.module.rules[0].include = appIncludes;
    config.module.rules[1] = ttf; //add the react-native-vector-icons here
    config.module.rules[2].oneOf[1].include = appIncludes;
    config.module.rules[2].oneOf[1].options.plugins = [
        require.resolve('babel-plugin-react-native-web'),
    ].concat(config.module.rules[2].oneOf[1].options.plugins);
    config.module.rules = config.module.rules.filter(Boolean);
    config.plugins.push(
        new webpack.DefinePlugin({__DEV__: env !== 'production'})
    );
    return config
};

Below is the use of react-native-vector-icons in my App.js file

import Icon from 'react-native-vector-icons/dist/FontAwesome';

<Icon name="glass" size={24}/>

I don't know why the icons are not loading or what I missed to configure. Please help me. Thank you in advance.

Rutvik Bhatt
  • 3,185
  • 1
  • 16
  • 28

5 Answers5

7

Okay. So lets assume you use nextjs and react-native-web. I will give you a step by step breakdown on how you can make it work on web.

install all these packages yarn add react-native-vector-icons react-native-svg next-compose-plugins next-transpile-modules

Update your next.config.js

const withPlugins = require('next-compose-plugins');
const withTM = require('next-transpile-modules')(['react-native-vector-icons', 'react-native-svg']);

module.exports = withPlugins([withTM], {
  images: {
    disableStaticImages: true,
  },
  typescript: {
    ignoreBuildErrors: true,
  },
  webpack: (config) => {
    config.resolve.alias = {
      ...(config.resolve.alias || {}),
      // Transform all direct `react-native` imports to `react-native-web`
      'react-native$': 'react-native-web',
    }
    config.resolve.extensions = [
      '.web.js',
      '.web.ts',
      '.web.tsx',
      ...config.resolve.extensions,
    ]
    config.module.rules.push(
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        use: [ 'url-loader?limit=10000', 'img-loader' ]
      },
    );
    return config
  },
});

Go to node_modules/react-native-vector-icons/Fonts and copy all the fonts you need and paste them in your public folder, and just the way you add custom fonts to the project, you need to include all those fonts like so in your style.css

@font-face {
    src: url(Ionicons.ttf);
    font-family: "Ionicons";
}

@font-face {
    src: url(FontAwesome.ttf);
    font-family: "FontAwesome";
}

@font-face {
    src: url(MaterialIcons.ttf);
    font-family: "MaterialIcons";
}

@font-face {
    src: url(Feather.ttf);
    font-family: "Feather";
}

@font-face {
    src: url(MaterialCommunityIcons.ttf);
    font-family: "MaterialCommunityIcons";
}

In your _document.js file, make sure you include your style.css file like so

        <Head>
          <link href="/fonts/style.css" rel="stylesheet"/>
        </Head>

And finally import it import Icon from 'react-native-vector-icons/MaterialCommunityIcons';

And set it <Icon name="google-play" size={30} color="#900" />

Voila!!!

Jeremiah
  • 350
  • 4
  • 12
  • 1
    This is the only thing that worked for me on NextJs with react-native-web :) – mocode10 Apr 13 '22 at 18:51
  • 2
    For Web, if perfomance is important for SEO, it's strongly discouraged to use a lot of Icon Fonts. Find one Icon Font, like MaterialCommunityIcons that covers most needs, and go with it. – KeitelDOG Mar 01 '23 at 17:37
  • 1
    @KeitelDOG Yep, this is what was KILLING my SEO - Google was giving me a total performance score of 27. Got rid of the fonts, 68. Still bad, but 40% difference is nothing to sneeze at – Steven Matthews Jun 30 '23 at 00:18
  • @StevenMatthews I had the same problem even if my Web App was not in production yet. I even hack the `MaterialCommunityIcons.json` glyphmaps in `next.config.js` to alias it to my custom JSON glyphmaps, that uses only the icons info that I use. `'./glyphmaps/MaterialCommunityIcons.json'` aliased to a custom one like `{ "account": 983044, "alert": 983078 }`. Original `MaterialCommunityIcons` is 190Kb with 6,596 icon references, while custom one is 1Kb with 55 icon references that I used in my App (I add them there on the go). If you want more info just tell me. – KeitelDOG Jun 30 '23 at 16:55
  • @KeitelDOG I just extracted all of the images from the TTF as SVG images and going to try using that - if it doesn't work I'd definitely be open to trying your method, because yeah, the size is absolutely insane - I was following a guide from my UI framework and it encouraged loading ALL the vector icon fonts - my SEO rating was 27! Twenty seven! – Steven Matthews Jun 30 '23 at 20:38
  • @StevenMatthews cool approach. There are 2 sides using vector icons, 1 is that it loads all font icons, and 2 is that is loads all icon references (numbers). So your approach is to select only the font icons you are using. And if you do it with SVG, then you have full control on both sides. Only you might lose some flexibility. TTF does not block rendering, icons are rendered as square placeholders. But Glythmaps are injected directly in main chunk codes, while more than 90-99% of them won't even be used. – KeitelDOG Jun 30 '23 at 21:09
  • @KeitelDOG What do you put in your next.config.js to do it? – Steven Matthews Jul 01 '23 at 02:43
  • I added aliases: `const chunkAlias = { './glyphmaps/MaterialCommunityIcons.json': path.resolve( __dirname, './glyphmaps/MaterialCommunityIcons.json', ), };` and I created a directory `glyphmaps/` and added my custom `glyphmaps/MaterialCommunityIcons.json` in it. So that when Web Pack is resolving the full Material Icon glyphmaps, it replaces it with my custom one. And I added it under react-native alias `'react-native$': 'react-native-web', ...chunkAlias,`. – KeitelDOG Jul 01 '23 at 03:25
  • If you want a clearer version, I will add it in an answer or a File Sharing. But this solution requires you to add each new icon reference in the custom JSON files. Not painful from my experience so far. – KeitelDOG Jul 01 '23 at 03:28
  • @StevenMatthews the path of the original glyphmaps JSON is `node_modules/react-native-vector-icons/glyphmaps/MaterialCommunityIcons.json` . Use it to find your icons and copy each reference in your custom glyphmaps. A bit of work for the first time. – KeitelDOG Jul 01 '23 at 04:46
  • @KeitelDOG Hrm, perhaps I didn't do it right - I'm not seeing a performance gain. I may ask a question and link you so you can write an answer in more depth if you'd like. Otherwise I'll just go with the SVG method I mentioned above - I only have about 50 icons I need, so either way is pretty equally easy – Steven Matthews Jul 02 '23 at 05:46
  • No Worry, I will add an Answer about web performance on it that discusses many approaches overtime. – KeitelDOG Jul 03 '23 at 18:12
  • @StevenMatthews see if it makes any difference following the answer: https://stackoverflow.com/a/76607450/5565544 . Also I saw there were Python script where you could delete many TTF fonts programmatically, or using bash commands that will use a text file as source of fonts to remove. This way you could reduce font size from 1.1Mb to like just 50kb or less. I will try it and let people know. – KeitelDOG Jul 03 '23 at 19:03
3

After a lot of research finally, I found the solution as mentioned below:

add the TTF files and your custom font file into the public directory 
Rutvik Bhatt
  • 3,185
  • 1
  • 16
  • 28
1

I just came across this but different vector icon issue from react-native-paper.

In that case to display the icon properly, the custom font family has to be defined in global ccs pointing to the ttf file.

https://github.com/oblador/react-native-vector-icons#web-with-webpack

CallMeLaNN
  • 8,328
  • 7
  • 59
  • 74
0

I added react native web to my existing RN app.

Following are the things I did.

  • Created a Public folder and added the index.html file to it. ( For proper structure)

  • Updated the path to the index.html file in webpack.config.js

    plugins: [
    new HtmlWebpackPlugin({
        template: path.join(__dirname, 'public/index.html'),
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.DefinePlugin({
        // See: https://github.com/necolas/react-native-web/issues/349
        __DEV__: JSON.stringify(true),
    }),
    

    ],

  • Added react-native-vector-icon package to compileNodeModules array in webpack.config.js

     const compileNodeModules = [
       // Add every react-native package that needs compiling
       // 'react-native-gesture-handler',
     'react-native-vector-icons',
      ].map((moduleName) => path.resolve(appDirectory, 
     `node_modules/${moduleName}`));
    
  • Created a Fonts folder inside public folder.

  • Copied all the the required fonts from node_modules/react-native-vector-icon/Fonts to the Public/Fonts

  • Added styles to index.html in the styles tag or you can create individual styles files for that.

      @font-face {
         src: url(Fonts/Ionicons.ttf);
         font-family: "Ionicons";
      }
    
  • And it worked. Thank you @Jeremiah your answer helped me.

AmitSingh
  • 21
  • 4
0

My answer is to complete @Jeremiah answer already in this thread: https://stackoverflow.com/a/69426231/5565544 , in order to address some web performance issue. There are many techniques to decrease size, like decreasing glyphmaps, editing TTF fonts file, or converting TTF to SVG or just use other SVG sources. Each add some complexity in code maintaining. I will only address the Glyphmaps Hack for now.

Glyphmaps

React Native Vector Icons has its fonts in node_modules/react-native-vector-icons/fonts, and its glyphmaps in node_modules/react-native-vector-icons/glyphmaps. Glyphmap allows package to reference the icon character code with icon name. Like { "account": 983044, "alert": 983078 }. In the package, according to version node_modules/react-native-vector-icons/glyphmaps/MaterialCommunityIcons.json has a size of 190kb, with 6,596 references. And if you are using a heavy font to have more icon options, you can still make Webpack to replace that glyphmap with a custom one, that would only have 50, 100 references and size like 1kb or 2kb max.

I suggest to use @next/bundle-analyzer to see the size of each package in your main chunk, you will be amazed on how many module parts are present that you don't even use. To replace Glyphmap, use webpack Alias in Next Config:

const path = require('path');
const withPlugins = require('next-compose-plugins');
const withTM = require('next-transpile-modules')(['react-native-vector-icons', 'react-native-svg']);
// optional to see package and chunk sizes
// const withBundleAnalyzer = require('@next/bundle-analyzer')({
//   enabled: process.env.ANALYZE === 'true',
// });

module.exports = withPlugins([withTM], {
  images: {
    disableStaticImages: true,
  },
  typescript: {
    ignoreBuildErrors: true,
  },
  webpack: (config) => {
    const chunkAlias = {
      './glyphmaps/MaterialCommunityIcons.json': path.resolve(
        __dirname,
        './glyphmaps/MaterialCommunityIcons.json',
      ),
    };

    config.resolve.alias = {
      ...(config.resolve.alias || {}),
      // Transform all direct `react-native` imports to `react-native-web`
      'react-native$': 'react-native-web',
      ...chunkAlias,
    }
    config.resolve.extensions = [
      '.web.js',
      '.web.ts',
      '.web.tsx',
      ...config.resolve.extensions,
    ]
    config.module.rules.push(
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        use: [ 'url-loader?limit=10000', 'img-loader' ]
      },
    );
    return config
  },
});

And now create a custom file for Glyphmap and put it anywhere like <root-project>/glyphmaps/MaterialCommunityIcons.json. If you change it, then change accordingly to match the alias. And now, the only pain is that you have to add manually each reference you use in the App by looking at corresponding package glyphmap. Hopefully they are not a lot (I use only 55 for now). Painful for the first time, and baby work afterwards:

{
  "account": 983044,
  "alert": 983078,
  "at": 983141,
  "cancel": 984890,
  "close": 983382,
  "comment": 983418,
  "dots-vertical": 983513,
  "history": 983770,
  "home": 983772,
  "menu": 983900,
  "reload": 984147,
  "share-outline": 985394,
  "soccer": 984248,
  "thumb-down": 984337,
  "thumb-down-outline": 984338,
  "thumb-up": 984339,
  "thumb-up-outline": 984340,
  "view-dashboard": 984430,
  "web": 984479,
}
KeitelDOG
  • 4,750
  • 4
  • 18
  • 33