Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"You may need an appropriate loader to handle this file type" when using EvaIconsPack in react native web #729

Closed
1 of 2 tasks
willcrisis opened this issue Nov 13, 2019 · 8 comments · Fixed by #773
Closed
1 of 2 tasks
Assignees
Labels
🐛 Bug 🌐 Web react-native-web specific ❤️ Eva Icons eva-icons module-specific

Comments

@willcrisis
Copy link

Issue type

I'm submitting a ... (check one with "x")

  • bug report
  • feature request

Issue description

Current behavior:

After installing @ui-kitten/eva-icons package and trying to setup it with <IconRegistry icons={EvaIconsPack} />, when I try to open my expo project in a web browser, I get the following error:

 web  Failed to compile.
/Users/williankrause/des/abastecame/node_modules/@ui-kitten/eva-icons/evaIcon.component.js 21:16
Module parse failed: Unexpected token (21:16)
You may need an appropriate loader to handle this file type.
|         // @ts-ignore - Eva maps icon color to `tintColor`
|         const { tintColor } = props, restProps = __rest(props, ["tintColor"]);
>         return (<Icon fill={tintColor} {...restProps}/>);
|     }
| }

It works nice on Android/iOS tho.

Expected behavior:

My app would open without a compilation error

Steps to reproduce:

Related code:

import React, {
    useState,
    useCallback,
    useContext,
    createContext,
    FunctionComponent,
    PropsWithChildren
} from 'react';
import { light, dark, mapping } from '@eva-design/eva';
import { EvaIconsPack } from '@ui-kitten/eva-icons';
import {
    ApplicationProvider,
    IconRegistry,
    ThemeType
} from 'react-native-ui-kitten';

type ThemeContextType = {
    theme: ThemeType;
    changeTheme: () => void;
    isDarkThemeActive: boolean;
};

const ThemeContext = createContext<ThemeContextType>(null);

const ThemeContextProvider: FunctionComponent<PropsWithChildren<{}>> = ({
    children
}) => {
    const [theme, setTheme] = useState(light);
    const [isDarkThemeActive, setDarkThemeActive] = useState(false);

    const changeTheme = useCallback(() => {
        setTheme(isDarkThemeActive ? light : dark);
        setDarkThemeActive(currentValue => !currentValue);
    }, [isDarkThemeActive, setDarkThemeActive, setTheme]);

    return (
        <ThemeContext.Provider
            value={{
                theme,
                changeTheme,
                isDarkThemeActive
            }}
        >
            <IconRegistry icons={EvaIconsPack} /> {/* if I comment this and lines 10/13, it works  */}
            <ApplicationProvider mapping={mapping} theme={theme}>
                {children}
            </ApplicationProvider>
        </ThemeContext.Provider>
    );
};

export const useTheme = (): ThemeContextType => useContext(ThemeContext);

export default ThemeContextProvider;

Other information:

OS, device, package version
MacOS Mojave, Google Chrome

"@eva-design/eva": "^1.2.0",
"@ui-kitten/eva-icons": "^4.2.0",
"react-native-svg": "~9.9.2",
"react-native-ui-kitten": "^4.2.0",
@artyorsh artyorsh added ❤️ Eva Icons eva-icons module-specific 🌐 Web react-native-web specific labels Nov 13, 2019
@artyorsh artyorsh self-assigned this Nov 13, 2019
@artyorsh
Copy link
Collaborator

artyorsh commented Nov 13, 2019

Hi 👋 Thanks for reporting.

I've faced this issue myself recently but had no time to investigate yet.
However, it works perfectly when running with Expo. I guess that proper webpack configuration can solve this. Take a look at the default expo webpack config and compare it with the one you're using.

Also, you might be interested in how it's configured in our playground application (where it works properly)

Unfortunately, I'm not sure if I'm able to investigate this soon so let's keep it open to track the progress

@RooQI
Copy link

RooQI commented Nov 19, 2019

Hello.

You're right. In fact, I was able to run Kitten UI in the same configuration. I do not use EXPO. The point is to configure WEBPACK. However, there is another problem associated with the icons that I could only defeat manually. I do not know how relevant this is for you, however, launching under the web does not work on IE11 because of the Proxy. My company has clients working with Windows 7 and IE11 and this turned out to be relevant for me

createIconsMap.ts

import { IconProvider } from 'react-native-ui-kitten';
import { SvgProps } from 'react-native-svg';
import { findIconByName } from 'react-native-eva-icons/icons';
import { EvaIcon } from './evaIcon.component';
// @ts-ignore
// import Proxy from 'proxy-polyfill/src/proxy';

export const createIconsMap = (): { [key: string]: IconProvider<SvgProps> } => {
  return new Proxy({}, {
    get(target: {}, name: string): IconProvider<SvgProps> {
      console.log(name)
      return new EvaIcon(findIconByName(name));
    },
  });
}

Proxy-polyfill could not solve the problem. As a result, I removed the Proxy and downloaded the icons without it, and it worked. What do you think about IE11? Can I download icons without a Proxy by default?

Thanks.

@artyorsh
Copy link
Collaborator

artyorsh commented Nov 19, 2019

@RooQI Thanks for commenting.
Actually, @ui-kitten/eva-icons module relies on react-native-eva-icons. Same time react-native-eva-icons simply maps all icons from eva-icons package to react-native-svg elements (.svg files to react elements).

So you can do something like this. This is not the best approach, but this will make it work

export const createIconsMap = (): { [key: string]: IconProvider<SvgProps> } => {
  return {
    'activity': new EvaIcon(findIconByName('activity')),
    // Same for all needed icons. Note that the `activity` is the name you can use later
    // when using <Icon name='activity' />
  }
}

Btw, could you please share your webpack config (which has made this work in Chrome)?

@RooQI
Copy link

RooQI commented Nov 19, 2019

@artyorsh Yes, this is exactly the approach I applied and it worked. However, I had to go a long way and hack the source code to understand what the problem was. I think it would be a good idea to describe the solution to the IE11 problem in the documentation for those taking the first steps with Kitten UI.

Here is my webpack. Hope this works for you.

const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
  .BundleAnalyzerPlugin

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

const appIncludes = [
  resolveApp('src'),
  resolveApp('../core/src'),
  resolveApp('../components/src'),
  resolveApp('../../node_modules/react-native-keyboard-aware-scroll-view'),
  resolveApp('../../node_modules/react-native-ui-kitten'),
  resolveApp('../../node_modules/@ui-kitten/eva-icons'),
  resolveApp('../../node_modules/@react-navigation'),
  resolveApp('../../node_modules/react-navigation-drawer'),
  resolveApp('../../node_modules/react-native-eva-icons'),
  resolveApp('../../node_modules/react-native-calendars'),
  resolveApp('../../node_modules/react-native-reanimated'),
  resolveApp('../../node_modules/react-native-vector-icons/'),
  resolveApp('../../node_modules/react-native-gesture-handler/'),
  resolveApp('../../node_modules/react-native-autocomplete-input'),
]

module.exports = function override(config, env) {
  config.resolve.alias['deepmerge$'] = 'deepmerge/dist/umd.js'

  // 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] = null
  config.module.rules[2].oneOf[1].include = appIncludes
  config.module.rules[2].oneOf[1].options.plugins = [
    require.resolve('babel-plugin-react-native-web'),
    require.resolve('@babel/plugin-proposal-class-properties')
  ].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' }),
  )

  config.plugins.push(
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
      reportFilename: 'report.html',
    }),
  )

  return config
}

Thanks.

@RooQI
Copy link

RooQI commented Nov 19, 2019

@artyorsh And by the way, "Hello from Russia." You have a great product.

@artyorsh
Copy link
Collaborator

artyorsh commented Nov 19, 2019

@RooQI If you found my solution useful I can also suggest simplifying it. With the solution below you may only need to add the name of the icon into APP_ICONS array:

const APP_ICONS = [
  'activity',
  'heart',
  // ...
];

function createIconsMap() {
  return APP_ICONS.reduce((map, name) => ({
    ...map, [name]: new EvaIcon(findIconByName(name)),
  }), {});
}

Спасибо :)

@ghost
Copy link

ghost commented Dec 10, 2019

I ran into the same issue today, using aws-amplify and expo. Here is the webpack.config.js on my /node_modules/@expo/webpack-config/webpack/ folder:

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const webpack_pwa_manifest_plugin_1 = __importDefault(require("@expo/webpack-pwa-manifest-plugin"));
const webpack_1 = require("webpack");
// @ts-ignore
const webpack_deep_scope_plugin_1 = __importDefault(require("webpack-deep-scope-plugin"));
// @ts-ignore
const clean_webpack_plugin_1 = __importDefault(require("clean-webpack-plugin"));
// @ts-ignore
const ModuleNotFoundPlugin_1 = __importDefault(require("react-dev-utils/ModuleNotFoundPlugin"));
// @ts-ignore
const pnp_webpack_plugin_1 = __importDefault(require("pnp-webpack-plugin"));
const ModuleScopePlugin_1 = __importDefault(require("react-dev-utils/ModuleScopePlugin"));
const webpack_manifest_plugin_1 = __importDefault(require("webpack-manifest-plugin"));
const WatchMissingNodeModulesPlugin_1 = __importDefault(require("react-dev-utils/WatchMissingNodeModulesPlugin"));
// @ts-ignore
const mini_css_extract_plugin_1 = __importDefault(require("mini-css-extract-plugin"));
const copy_webpack_plugin_1 = __importDefault(require("copy-webpack-plugin"));
const getenv_1 = require("getenv");
const path_1 = __importDefault(require("path"));
const env_1 = require("./env");
const loaders_1 = require("./loaders");
const plugins_1 = require("./plugins");
const addons_1 = require("./addons");
const utils_1 = require("./utils");
const config_1 = require("@expo/config");
function getDevtool({ production, development }, { devtool }) {
    if (production) {
        // string or false
        if (devtool !== undefined) {
            // When big assets are involved sources maps can become expensive and cause your process to run out of memory.
            return devtool;
        }
        return 'source-map';
    }
    if (development) {
        return 'cheap-module-source-map';
    }
    return false;
}
function getOutput(locations, mode, publicPath) {
    const commonOutput = {
        sourceMapFilename: '[chunkhash].map',
        // We inferred the "public path" (such as / or /my-project) from homepage.
        // We use "/" in development.
        publicPath,
        // Build folder (default `web-build`)
        path: locations.production.folder,
    };
    if (mode === 'production') {
        commonOutput.filename = 'static/js/[name].[contenthash:8].js';
        // There are also additional JS chunk files if you use code splitting.
        commonOutput.chunkFilename = 'static/js/[name].[contenthash:8].chunk.js';
        // Point sourcemap entries to original disk location (format as URL on Windows)
        commonOutput.devtoolModuleFilenameTemplate = (info) => locations.absolute(info.absoluteResourcePath).replace(/\\/g, '/');
    }
    else {
        // Add comments that describe the file import/exports.
        // This will make it easier to debug.
        commonOutput.pathinfo = true;
        // Give the output bundle a constant name to prevent caching.
        // Also there are no actual files generated in dev.
        commonOutput.filename = 'static/js/bundle.js';
        // There are also additional JS chunk files if you use code splitting.
        commonOutput.chunkFilename = 'static/js/[name].chunk.js';
        // Point sourcemap entries to original disk location (format as URL on Windows)
        commonOutput.devtoolModuleFilenameTemplate = (info) => path_1.default.resolve(info.absoluteResourcePath).replace(/\\/g, '/');
    }
    return commonOutput;
}
function default_1(env, argv = {}) {
    return __awaiter(this, void 0, void 0, function* () {
        const config = env_1.getConfig(env);
        const mode = env_1.getMode(env);
        const isDev = mode === 'development';
        const isProd = mode === 'production';
        // Enables deep scope analysis in production mode.
        // Remove unused import/exports
        // override: `env.removeUnusedImportExports`
        const deepScopeAnalysisEnabled = utils_1.overrideWithPropertyOrConfig(env.removeUnusedImportExports, false
        // isProd
        );
        const locations = env.locations || (yield env_1.getPathsAsync(env.projectRoot));
        const { publicPath, publicUrl } = env_1.getPublicPaths(env);
        const { build: buildConfig = {} } = config.web;
        const { babel: babelAppConfig = {} } = buildConfig;
        const devtool = getDevtool({ production: isProd, development: isDev }, buildConfig);
        const babelProjectRoot = babelAppConfig.root || locations.root;
        const appEntry = [];
        // In solutions like Gatsby the main entry point doesn't need to be known.
        if (locations.appMain) {
            appEntry.push(locations.appMain);
        }
        else {
            throw new Error(`The entry point for your project couldn't be found. Please define it in the package.json main field`);
        }
        // Add a loose requirement on the ResizeObserver polyfill if it's installed...
        // Avoid `withEntry` as we don't need so much complexity with this config.
        const resizeObserverPolyfill = config_1.projectHasModule('resize-observer-polyfill/dist/ResizeObserver.global', env.projectRoot, config);
        if (resizeObserverPolyfill) {
            appEntry.unshift(resizeObserverPolyfill);
        }
        if (isDev) {
            // https://github.com/facebook/create-react-app/blob/e59e0920f3bef0c2ac47bbf6b4ff3092c8ff08fb/packages/react-scripts/config/webpack.config.js#L144
            // Include an alternative client for WebpackDevServer. A client's job is to
            // connect to WebpackDevServer by a socket and get notified about changes.
            // When you save a file, the client will either apply hot updates (in case
            // of CSS changes), or refresh the page (in case of JS changes). When you
            // make a syntax error, this client will display a syntax error overlay.
            // Note: instead of the default WebpackDevServer client, we use a custom one
            // to bring better experience for Create React App users. You can replace
            // the line below with these two lines if you prefer the stock client:
            // require.resolve('webpack-dev-server/client') + '?/',
            // require.resolve('webpack/hot/dev-server'),
            appEntry.unshift(require.resolve('react-dev-utils/webpackHotDevClient'));
        }
        let generatePWAImageAssets = !isDev;
        if (!isDev && typeof env.pwa !== 'undefined') {
            generatePWAImageAssets = env.pwa;
        }
        let webpackConfig = {
            mode,
            entry: {
                app: appEntry,
            },
            // https://webpack.js.org/configuration/other-options/#bail
            // Fail out on the first error instead of tolerating it.
            bail: isProd,
            devtool,
            context: __dirname,
            // configures where the build ends up
            output: getOutput(locations, mode, publicPath),
            plugins: [
                // Delete the build folder
                isProd &&
                    new clean_webpack_plugin_1.default([locations.production.folder], {
                        root: locations.root,
                        dry: false,
                        verbose: false,
                    }),
                // Copy the template files over
                isProd &&
                    new copy_webpack_plugin_1.default([
                        {
                            from: locations.template.folder,
                            to: locations.production.folder,
                            // We generate new versions of these based on the templates
                            ignore: [
                                'expo-service-worker.js',
                                'favicon.ico',
                                'serve.json',
                                'index.html',
                                'icon.png',
                                // We copy this over in `withWorkbox` as it must be part of the Webpack `entry` and have templates replaced.
                                'register-service-worker.js',
                            ],
                        },
                        {
                            from: locations.template.serveJson,
                            to: locations.production.serveJson,
                        },
                        {
                            from: locations.template.favicon,
                            to: locations.production.favicon,
                        },
                        {
                            from: locations.template.serviceWorker,
                            to: locations.production.serviceWorker,
                        },
                    ]),
                // Generate the `index.html`
                new plugins_1.ExpoHtmlWebpackPlugin(env),
                plugins_1.ExpoInterpolateHtmlPlugin.fromEnv(env, plugins_1.ExpoHtmlWebpackPlugin),
                new webpack_pwa_manifest_plugin_1.default(config, {
                    publicPath,
                    projectRoot: env.projectRoot,
                    noResources: !generatePWAImageAssets,
                    filename: locations.production.manifest,
                    HtmlWebpackPlugin: plugins_1.ExpoHtmlWebpackPlugin,
                }),
                // This gives some necessary context to module not found errors, such as
                // the requesting resource.
                new ModuleNotFoundPlugin_1.default(locations.root),
                new plugins_1.ExpoDefinePlugin({
                    mode,
                    publicUrl,
                    config,
                    productionManifestPath: locations.production.manifest,
                }),
                // This is necessary to emit hot updates (currently CSS only):
                isDev && new webpack_1.HotModuleReplacementPlugin(),
                // If you require a missing module and then `npm install` it, you still have
                // to restart the development server for Webpack to discover it. This plugin
                // makes the discovery automatic so you don't have to restart.
                // See https://github.com/facebook/create-react-app/issues/186
                isDev && new WatchMissingNodeModulesPlugin_1.default(locations.modules),
                isProd &&
                    new mini_css_extract_plugin_1.default({
                        // Options similar to the same options in webpackOptions.output
                        // both options are optional
                        filename: 'static/css/[name].[contenthash:8].css',
                        chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
                    }),
                // Generate a manifest file which contains a mapping of all asset filenames
                // to their corresponding output file so that tools can pick it up without
                // having to parse `index.html`.
                new webpack_manifest_plugin_1.default({
                    fileName: 'asset-manifest.json',
                    publicPath,
                }),
                deepScopeAnalysisEnabled && new webpack_deep_scope_plugin_1.default(),
                new plugins_1.ExpoProgressBarPlugin(),
            ].filter(Boolean),
            module: {
                strictExportPresence: false,
                rules: [
                    // Disable require.ensure because it breaks tree shaking.
                    { parser: { requireEnsure: false } },
                    {
                        oneOf: loaders_1.createAllLoaders(env),
                    },
                ].filter(Boolean),
            },
            resolveLoader: {
                plugins: [
                    // Also related to Plug'n'Play, but this time it tells Webpack to load its loaders
                    // from the current package.
                    pnp_webpack_plugin_1.default.moduleLoader(module),
                ],
            },
            resolve: {
                mainFields: ['browser', 'module', 'main'],
                extensions: env_1.getModuleFileExtensions('web'),
                plugins: [
                    // Adds support for installing with Plug'n'Play, leading to faster installs and adding
                    // guards against forgotten dependencies and such.
                    pnp_webpack_plugin_1.default,
                    // Prevents users from importing files from outside of node_modules/.
                    // This often causes confusion because we only process files within the root folder with babel.
                    // To fix this, we prevent you from importing files out of the root folder -- if you'd like to,
                    // please link the files into your node_modules/ and let module-resolution kick in.
                    // Make sure your source files are compiled, as they will not be processed in any way.
                    new ModuleScopePlugin_1.default(babelProjectRoot, [locations.packageJson]),
                ],
                symlinks: false,
            },
            // Turn off performance processing because we utilize
            // our own (CRA) hints via the FileSizeReporter
            // TODO: Bacon: Remove this higher value
            performance: getenv_1.boolish('CI', false) ? false : { maxAssetSize: 600000, maxEntrypointSize: 600000 },
        };
        if (isProd) {
            webpackConfig = addons_1.withCompression(addons_1.withOptimizations(webpackConfig), env);
        }
        return addons_1.withDevServer(addons_1.withReporting(addons_1.withNodeMocks(addons_1.withAlias(webpackConfig)), env), env, {
            allowedHost: argv.allowedHost,
            proxy: argv.proxy,
        });
    });
}
exports.default = default_1;
//# sourceMappingURL=webpack.config.js.map

Is there something wrong with that?

@artyorsh
Copy link
Collaborator

Got this fixed in 4.3.2 🎉
Upgrade

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐛 Bug 🌐 Web react-native-web specific ❤️ Eva Icons eva-icons module-specific
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants