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

SVGR dynamic import with Webpack (& React Lazy?) #724

Open
jameswilson opened this issue May 20, 2022 · 10 comments
Open

SVGR dynamic import with Webpack (& React Lazy?) #724

jameswilson opened this issue May 20, 2022 · 10 comments

Comments

@jameswilson
Copy link

jameswilson commented May 20, 2022

💬 Questions and Help

Thank you for this helpful project!

I'm trying to dynamically import SVGs based on a string filename, but I'm a little lost.

Is something like this possible using webpack import?

import React, { Suspense } from "react";

const Logo = ({ brand }) => {
  const Svg = React.lazy(() => import(`../logos/${brand}.svg`));

  return (
    <Suspense fallback={<>{/* loader */}</>}>
      <Svg className={brand} />
    </Suspense>
  );
};

When I run the webpack build, I get same error for every SVG in the folder.

ERROR in ./src/logos/somebrand.svg 1:0
Module parse failed: Unexpected token (1:0)
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

webpack.config.js setup following docs:

  return {
    module: {
      rules: [    
        {
          test: /\.svg$/i,
          issuer: /\.[jt]sx?$/,
          use: ['@svgr/webpack'],
        },
      ],
    },
  };

I've tried adding 'file-loader', to the use to no avail. Apologies if I'm off track in some obvious way, I'm fairly new to SVGR & React. I read somewhere that SVGR creates a mapping during build, so if there is a simpler way to just load the right SVG dynamically based on a string, an example would be great to have in docs. My apologies if this is documented already, I searched, scanned, and couldn't find anything relevant.

@julianklumpers
Copy link

Same issue with me

@gregberge
Copy link
Owner

Hi, I am very surprised that it does not work. I think it is a webpack configuration issue more than a SVGR one. By looking at your example I have no idea why it does not work. Maybe someone else could guess.

@stale
Copy link

stale bot commented Oct 1, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Oct 1, 2022
@stale stale bot closed this as completed Oct 16, 2022
@gregberge gregberge reopened this Oct 17, 2022
@stale stale bot removed the wontfix label Oct 17, 2022
@huazhuangnan
Copy link

@jameswilson @julianklumpers
webpack.config.js remove issuer: /\.[jt]sx?$/,

@Matt-Tranzact
Copy link

Matt-Tranzact commented Dec 28, 2022

How about for rollup, is there any way to load svg dynamically similar to webpack?

@stale
Copy link

stale bot commented May 21, 2023

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label May 21, 2023
@johnschult
Copy link

johnschult commented Aug 1, 2023

@jameswilson @julianklumpers webpack.config.js remove issuer: /\.[jt]sx?$/,

If anyone is finding this issue because they used the SVGR NextJS docs to setup webpack, removing the issuer line fixes the error below:

Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type,
currently no loaders are configured to process this file. 

FWIW I am using Next 13 with app router and until I removed that line it failed.

@nachten
Copy link

nachten commented Oct 6, 2023

I'm facing a different problem, hopefully someone can help me. I have an UI library with an Icon component:

import clsx from 'clsx';
import styles from './Icon.module.scss';
import { icons } from './icons';
import { Suspense, useMemo } from 'react';

export type IconName = keyof typeof icons;

export type IconProps = React.HTMLAttributes<HTMLDivElement> & {
    icon: IconName;
    disabled?: boolean;
};

export const Icon: React.FC<IconProps> = ({ icon, disabled = false, ...props }: IconProps) => {
    const SvgIcon = useMemo(() => icons[icon], [icon]);
    const classNames = clsx(styles.root, disabled ? styles.disabled : '', props.className);

    if (!SvgIcon) return null;

    return (
        <div
            {...props}
            className={classNames}
        >
            <Suspense fallback={null}>
                <SvgIcon style={{ width: '100%', height: '100%' }} />
            </Suspense>
        </div>
    );
};

export default Icon;

The content of my icons.ts looks like this

import React from 'react';

const lazy = (componentImportFn: Function) =>
    React.lazy(async () => {
        let obj = await componentImportFn();
        return typeof obj.default === 'function' ? obj : obj.default;
    });

export const icons = {
    Icon1: lazy(async () => import('./assets/ico-icon1.svg')),
    Icon2: lazy(async () => import('./assets/ico-icon2.svg')),
    Icon3: lazy(async () => import('./assets/ico-icon3.svg')),
    Icon4: lazy(async () => import('./assets/ico-icon4.svg')),
}

And my NextJs config looks like this as described here

/** @type {import('next').NextConfig} */

const nextConfig = {
    output: 'export',
    distDir: 'build-next-static',
    swcMinify: true,
    reactStrictMode: true,
    webpack(config) {
        // Grab the existing rule that handles SVG imports
        const fileLoaderRule = config.module.rules.find((rule) => rule.test?.test?.('.svg'));

        config.module.rules.push(
            // Reapply the existing rule, but only for svg imports ending in ?url
            {
                ...fileLoaderRule,
                test: /\.svg$/i,
                resourceQuery: /url/, // *.svg?url
            },
            // Convert all other *.svg imports to React components
            {
                test: /\.svg$/i,
                // issuer: /\.[jt]sx?$/,
                resourceQuery: { not: /url/ }, // exclude if *.svg?url
                use: ['@svgr/webpack'],
            }
        );

        // Modify the file loader rule to ignore *.svg, since we have it handled now.
        fileLoaderRule.exclude = /\.svg$/i;

        return config;
    },
};

module.exports = nextConfig;

And when calling the Icon button in a nextjs page e.g.

import { Icon } from 'ui';

export default function Page() {
    return (
        <>
            <Icon icon="Icon1" />
        </>
    );
}

I'm getting the following error from nextjs

Unhandled Runtime Error
Error: Unsupported Server Component type: undefined

Can someone push me in a direction or better have a solution for me ?

@stale stale bot removed the wontfix label Oct 6, 2023
@epavletic
Copy link

I have a similar problem, although with a slightly different error:

So, usage looks something like this (we’re using @svgr/webpack to transform svg’s on the fly):

const logos = {
  brand1: lazy(() => import('path/to/brand1logo.svg')),
  brand2: lazy(() => import('path/to/brand2logo.svg')),
};

const LogoComponent = ({ brand }) => {
  const Logo = logos[brand];

  return (
    <Suspense fallback={null}>
      <Logo />
    </Suspense>
  )
}

…and what I’m getting in the console is the following:

react-dom.development.js:17733 Uncaught Error: Element type is invalid. Received a promise that resolves to: /path/to/logo.svg. Lazy element type must resolve to a class or function.

It feels like we’re getting back something that isn’t a valid component, so React does not know what to do with it. In my head, I’d figure that SVGR would do the transformation from svg → react component before the promise resolves (or, at least before React sinks its teeth in it), but perhaps that’s not the case? Any ideas?

@epavletic
Copy link

Relevant addendum: We’re still on webpack 4.x.x, which I’m beginning to suspect might be part of the problem…

In another greenfield project where we’re using Next, we’re able to do the above just fine (just using Next’s dynamic() instead of lazy/Suspense). We had to move away from using variables as part of the dynamic import path though (../logos/${brand}.svg) – never got that to work, regardless of which setup we introduced it to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants