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

Using svgr with webpack and TypeScript #546

Closed
mutsys opened this issue Mar 14, 2021 · 10 comments
Closed

Using svgr with webpack and TypeScript #546

mutsys opened this issue Mar 14, 2021 · 10 comments

Comments

@mutsys
Copy link

mutsys commented Mar 14, 2021

This isn't really a question or request for help, rather a suggestion that the docs be updated a bit to help others out so they don't spend quite as much time fumbling around as I did.

Setting up svgr as a webpack loader is wonderfully easy and works right out of the box as described in the docs. However, if you are using TypeScript, you must an ambient type definition in order for the TypeScript compiler to understand what to do with svg files. The typical example looks like this:

declare module "*.svg" {
    const svg: string;
    export default svg;
}

And this allows you to get the basics working:

import { FC } from "react";
import Logo from "./logo.svg";

const MyLogo: FC = () => ( <Logo/> );

However, if you want to add props to the Logo component, none of the expected props will be accepted. This is because the ambient type declaration doesn't adequately describe the component created by svgr. In order to have the component typed correctly, you need the following ambient type definition instead:

declare module "*.svg" {
    import React from "react";
    const SVG: React.VFC<React.SVGProps<SVGSVGElement>>;
    export default SVG;
}

Now, the TypeScript compiler understands that what svgr hands off to you via the import statement is a React component that generates an <svg> element with the full range of props available to you.

import { CSSProperties, FC } from "react";
import Logo from "./logo.svg";

const logoStyle: CSSProperties = {
    fill: "#CF4532",
    width: "100px"
};

const MyLogo: FC = () => ( <Logo  style={ logoStyle }/> );

One of those things that isn't immediately obvious and probably should be explicitly stated in the docs.

@open-collective-bot
Copy link

Hey @mutsys 👋,
Thank you for opening an issue. We'll get back to you as soon as we can.
Please, consider supporting us on Open Collective. We give a special attention to issues opened by backers.
If you use SVGR at work, you can also ask your company to sponsor us ❤️.

@chrisabrams
Copy link

This isn't really a question or request for help, rather a suggestion that the docs be updated a bit to help others out so they don't spend quite as much time fumbling around as I did.

Setting up svgr as a webpack loader is wonderfully easy and works right out of the box as described in the docs. However, if you are using TypeScript, you must an ambient type definition in order for the TypeScript compiler to understand what to do with svg files. The typical example looks like this:

declare module "*.svg" {
    const svg: string;
    export default svg;
}

And this allows you to get the basics working:

import { FC } from "react";
import Logo from "./logo.svg";

const MyLogo: FC = () => ( <Logo/> );

However, if you want to add props to the Logo component, none of the expected props will be accepted. This is because the ambient type declaration doesn't adequately describe the component created by svgr. In order to have the component typed correctly, you need the following ambient type definition instead:

declare module "*.svg" {
    import React from "react";
    const SVG: React.VFC<React.SVGProps<SVGSVGElement>>;
    export default SVG;
}

Now, the TypeScript compiler understands that what svgr hands off to you via the import statement is a React component that generates an <svg> element with the full range of props available to you.

import { CSSProperties, FC } from "react";
import Logo from "./logo.svg";

const logoStyle: CSSProperties = {
    fill: "#CF4532",
    width: "100px"
};

const MyLogo: FC = () => ( <Logo  style={ logoStyle }/> );

One of those things that isn't immediately obvious and probably should be explicitly stated in the docs.

@mutsys where are you putting the declaration file?

@mutsys
Copy link
Author

mutsys commented Mar 19, 2021

I typically have my ambient types defined in a fie named declarations.d.ts at the root of my project along with a tsconfig.json that looks something like this:

{
    "include": [
        ...
    ],
    "exclude": [
        ...
    ],
    "compilerOptions": {
        ...
    },
    "files": [
        "declarations.d.ts"
    ]
}

It should be possible to export the definition from svgr itself to save everyone the trouble.

@flo-sch
Copy link

flo-sch commented Apr 28, 2021

I don't think SVGR should provide typings affecting the global *.svg namespace, especially just for the sake of comfort 😕

I am using it in projects where I load some svg with svgr and some differently (background-image, file-loader or whatever).
For those I adjusted the typings to only type svg in a specific path with svgr React component exports:

// typings.d.ts
declare module 'src/assets/svgr/*.svg' {
  import React from "react";
  const SVG: React.VFC<React.SVGProps<SVGSVGElement>>;
  export default SVG;
}

Anyway, my point is that it is possible to opt-in for SVGR types (worth documenting probably?), but it would be more difficult to opt-out if SVGR was to force those types for all svg assets.

Unless there are more elegant ways in TS to avoid "polluting" global namespaces, but this is a bit above my TS knowledges atm 🤷

@mutsys
Copy link
Author

mutsys commented May 11, 2021

@flo-sch yeah, good point. absolutely agree that no one should be coerced/forced into any particular configuration by surprise. then just how to make it easier for newcomers to opt-in to the best configuration for them from among the available options?

@Jackbennett
Copy link

Isn't the webpack typescript option supposed to kick out the definition so I don't need to include a custom.d.ts in every repo with an svg?

use: [
  {
    loader: "@svgr/webpack",
    options: {
      typescript: true,
      ext: "tsx",
    }
},
...

But I haven't had any success getting this to work with webpack in storybook.

@doug-lessen
Copy link

Isn't the webpack typescript option supposed to kick out the definition so I don't need to include a custom.d.ts in every repo with an svg?

use: [
  {
    loader: "@svgr/webpack",
    options: {
      typescript: true,
      ext: "tsx",
    }
},
...

But I haven't had any success getting this to work with webpack in storybook.

I am facing the same challenge trying to get this to play nice with webpack and storybook too!

@mutsys
Copy link
Author

mutsys commented Jul 21, 2021

this is where i was running into trouble as well. i think storybook itself is stomping on something here. i can get things to work where my app is fine but storybook doesn't work or storybook works and my app doesn't. i have had trouble finding a config that works well for both.

Isn't the webpack typescript option supposed to kick out the definition so I don't need to include a custom.d.ts in every repo with an svg?

use: [
  {
    loader: "@svgr/webpack",
    options: {
      typescript: true,
      ext: "tsx",
    }
},
...
  
But I haven't had any success getting this to work with webpack in storybook.

I am facing the same challenge trying to get this to play nice with webpack and storybook too!

@gregberge
Copy link
Owner

#573 should fix it.

@abdessamadely
Copy link

abdessamadely commented Feb 26, 2022

Hello, for me I didn't have to use any .d.ts types, I'm using Nextjs and this is how I solved the issues:

import dynamic from 'next/dynamic';
import { FC, SVGAttributes } from 'react';

interface Props extends SVGAttributes<SVGElement> {
  ext?: 'svg';
  icon: string;
}

export const Icon: FC<Props> = ({ icon, ext = 'svg', ...props }) => {
  const Icon = dynamic(() => import(`./${ext}/${icon}.svg`));
  return <Icon {...props} />;
};

this is a component located at @/components/icons/svg/index.tsx and it loads *.svg files dynamically from: components/icons/svg directory.

then you can use it as a simple components with all SVG props + {ext:"for directory name", icon:"svg file name"}

for usage with React, which doesn't have the next/dynamic package, I think you can replace it with require

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

7 participants