pd4d10 / vite-plugin-svgr

Vite plugin to transform SVGs into React components
MIT License
563 stars 54 forks source link

Dynamic SVG imports? #72

Open wmonecke opened 1 year ago

wmonecke commented 1 year ago

Is there a way to import svgs dynamically?

I would love to be able to do something like this:

dynamic import hook

import { FunctionComponent } from 'preact';
import { useEffect, useRef, useState } from 'preact/hooks';

type Props = {
  name: string;
};

export const useDynamicSVGImport = ({ name }: Props) => {
  const ImportedIconRef = useRef<
    FunctionComponent<SVGSVGElement> | undefined
  >();

  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<Error>();

  useEffect(() => {
    setLoading(true);

    const importIcon = async (): Promise<void> => {
      try {
        const imported = await import(`../../assets/icons/${name}.svg`).then(
          module => module.default,
        );

        // logs: /src/assets/icons/spinner.svg if I pass name = spinner
        console.log('imported', imported); 

        ImportedIconRef.current = imported;

      } catch (err) {
        console.log(err);

        setError(err);
      } finally {
        setLoading(false);
      }
    };

    importIcon();
  }, [name]);

  return {
    loading,
    error,
    SvgIcon: ImportedIconRef.current,
  };
};

Note: If I pass SvgIcon to the src of an img tag it renders the icon properly so it is being imported.

icon.tsx

import { FunctionComponent } from 'preact';

import { useDynamicSVGImport } from '~/hooks/icons/use-dynamic-import';

type Props = {
  name: string;
  className?: string;
  isOutlineIcon?: boolean;
};

export const Icon: FunctionComponent<Props> = ({
  name,
  className,
  isOutlineIcon = false,
  ...restProps
}) => {
  const { error, loading, SvgIcon } = useDynamicSVGImport({ name });

  if (error || !SvgIcon || loading) {
    return null;
  }

  return (
    <SvgIcon
      className={`
        fill-slate-400
        ${isOutlineIcon ? '' : 'fill-current'}
        ${className ? className : 'h-5 w-5 text-lightBlack'}`}
      {...restProps}
    />
  );
};

This is currently not working.

How can I convert the imported file into a component?

    const imported = await import(`../../assets/icons/${name}.svg`).then(
          module => module.default,
        );
flydev-fr commented 1 year ago

@wmonecke you could take a look on this example on stackblitz and answer on stackoverflow to check if it fit your needs.

pd4d10 commented 1 year ago

Would Vite glob import help?

GideonMax commented 11 months ago

aren't you supposed to import with ?react for example: import Logo from "./logo.svg?react"

nogess commented 5 months ago

有没有办法动态导入 svgs?

我很想能够做这样的事情:

动态导入钩子

import { FunctionComponent } from 'preact';
import { useEffect, useRef, useState } from 'preact/hooks';

type Props = {
  name: string;
};

export const useDynamicSVGImport = ({ name }: Props) => {
  const ImportedIconRef = useRef<
    FunctionComponent<SVGSVGElement> | undefined
  >();

  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<Error>();

  useEffect(() => {
    setLoading(true);

    const importIcon = async (): Promise<void> => {
      try {
        const imported = await import(`../../assets/icons/${name}.svg`).then(
          module => module.default,
        );

        // logs: /src/assets/icons/spinner.svg if I pass name = spinner
        console.log('imported', imported); 

        ImportedIconRef.current = imported;

      } catch (err) {
        console.log(err);

        setError(err);
      } finally {
        setLoading(false);
      }
    };

    importIcon();
  }, [name]);

  return {
    loading,
    error,
    SvgIcon: ImportedIconRef.current,
  };
};

注意:如果我传递SvgIconsrc标签,img它会正确呈现图标,以便被导入。

图标.tsx

import { FunctionComponent } from 'preact';

import { useDynamicSVGImport } from '~/hooks/icons/use-dynamic-import';

type Props = {
  name: string;
  className?: string;
  isOutlineIcon?: boolean;
};

export const Icon: FunctionComponent<Props> = ({
  name,
  className,
  isOutlineIcon = false,
  ...restProps
}) => {
  const { error, loading, SvgIcon } = useDynamicSVGImport({ name });

  if (error || !SvgIcon || loading) {
    return null;
  }

  return (
    <SvgIcon
      className={`
        fill-slate-400
        ${isOutlineIcon ? '' : 'fill-current'}
        ${className ? className : 'h-5 w-5 text-lightBlack'}`}
      {...restProps}
    />
  );
};

目前这不起作用。

如何将导入的文件转换为组件?

    const imported = await import(`../../assets/icons/${name}.svg`).then(
          module => module.default,
        );

Need to downgrade the plugin to version 2.x, the latest 4.x version doesn't work with dynamic introduction

A-Rhman commented 2 months ago

You need to add '?react' to import path like: const imported = await import(../../assets/icons/${name}.svg?react).then( module => module.default, );

If you're using Typescript; Make sure you have this line in vite-env.d.ts: /// <reference types="vite-plugin-svgr/client" />

i-xiao-zi commented 1 month ago

my solution

// dynamic.tsx
import React from 'react';

const Dynamic: React.FC<React.SVGAttributes<SVGSVGElement> & {icon: string}> = (props) => {
  const {icon, ...svgProps} = props;
  if(icon.length === 0) return (<></>);
  const Icon = React.lazy(async () => {
    let component = await import(`./assets/non_data.svg?react`);
    try {
      component = await import(`./assets/${icon}.svg?react`);
    } catch (_) { /* empty */ }
    return component;
  });
  return (
    <React.Suspense>
      <Icon { ...svgProps } />
    </React.Suspense>
  );
}

export default Dynamic;
// App.tsx
import Dynamic from './dynamic'
function App() {
  return (
      <span style={{ color: 'blue' }}><Dynamic icon="power" fill="currentColor" /></span>
}

export default App