6

I was getting this error when import svg in React/Typescript/Webpack 5 project:

Cannot find module '../path' or its corresponding type declarations.

then I added:

declare module '*.svg' {
    import * as React from 'react';

    export const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement> & { title?: string }>;

    const src: string;
    export default src;
}

to the custom.d.ts file. It works if I use the svg as an image src. But I need to use the svg as an ReactElement because I need to change its content based on user clicks.

I tried to import svg as

import DislikeIcon from '../../media/icons/dislike.svg';

const Component = () => (<> <DislikeIcon /> </>)

then I got this error:

<data:image/svg... /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.

then I tried:

import { ReactComponent as DislikeIcon } from '../../media/icons/dislike.svg';

and got:

Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

My webpack.config.js file

module.exports = {
    ...
    module: {
        rules: [
            {
                test: /\.[jt]sx?$/,
                use: ['babel-loader'],
                exclude: /node_modules/,
            },
            {
                test: /\.scss$/,
                exclude: /node_modules/,
                use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader'],
            },
            {
                test: /\.svg$/,
                loader: 'url-loader',
            },
            {
                test: /\.(?:ico|gif|png|jpg|jpeg)$/i,
                type: 'asset/resource',
            },
            {
                test: /\.(woff(2)?|eot|ttf|otf)$/,
                type: 'asset/inline',
            },
        ],
    },
    ...
};

my custom.d.ts file:

declare module '*.png' {
    const value: string;
    export = value;
}
declare module '*.jpg' {
    const value: string;
    export = value;
}
declare module '*.svg' {
    import * as React from 'react';

    export const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement> & { title?: string }>;

    const src: string;
    export default src;
}

my tsconfig.json file:

{
    "compilerOptions": {
      "target": "ES5",
      "module": "ESNext",
      "moduleResolution": "node",
      "lib": [
        "DOM",
        "ESNext"
      ],
      "jsx": "react-jsx",
      "noEmit": true,
      "isolatedModules": true,
      "esModuleInterop": true,
      "strict": true,
      "skipLibCheck": true,
      "forceConsistentCasingInFileNames": true,
      "resolveJsonModule": true,
      "allowJs": true,
      "checkJs": true,
    },
    "include": ["src/**/*", "./custom.d.ts"],
    "exclude": ["node_modules", "build"],
  }

I searched a lot for an answer, but everyone says something similar. This is taking me a time I don't have. Does anyone have any suggestion?

Thank you for you attention!

Evry
  • 73
  • 1
  • 7

1 Answers1

0

I found the solution for this here: https://duncanleung.com/typescript-module-declearation-svg-img-assets/ With thanks to (Duncan Leung) The solution was to create a ./src/@types/assets/index.d.ts TypeScript module declaration file for media assets.

One main gotcha about TypeScript module declaration files is in how they are included in tsconfig.json using the typeRoots property.

The property typeRoots defines the types folder where type declarations will be contained, but the index.d.ts module declaration files must be in a subfolder since each subfolder under typeRoots is considered a "package" and is added to your project.

We were incorrectly trying to place the .svg module declarations under ./src/@types/index.d.ts.

Moving the index.d.ts for .svg files to it's own ./src/@types/assets subfolder allowed TypeScript to correctly recognize the .svg module declarations.

./src/@types/assets/index.d.ts

declare module "\*.svg" {
  import React = require("react");
  export const ReactComponent: React.SFC<React.SVGProps<SVGSVGElement>>;
  const src: string;
  export default src;
}

declare module "\*.jpg" {
  const content: string;
  export default content;
}

declare module "\*.png" {
  const content: string;
  export default content;
}

declare module "\*.json" {
  const content: string;
  export default content;
}

Here is an example tsconfig.json:

tsconfig.json

{
  "compileOnSave": false,
  "compilerOptions": {
    "target": "es5",
    "module": "esnext",
    "types": ["node"],
    "moduleResolution": "node",
    "esModuleInterop": true,
    "typeRoots": ["./src/@types", "./node_modules/@types"],
    "lib": ["dom", "es2015", "es2017"],
    "jsx": "react",
    "sourceMap": true,
    "strict": true,
    "resolveJsonModule": true,
    "noUnusedLocals": true,
    "noImplicitAny": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "allowSyntheticDefaultImports": true,
    "downlevelIteration": true,
    "baseUrl": "./",
    "paths": {
      "~/*": ["src/*"],
      "@turn/styled": ["src/styled"]
    }
  },
  "include": ["./src/**/*", "./test-utils/**/*", "./__mocks__/**/*"],
  "exclude": ["node_modules"]
}
Kell
  • 3,252
  • 20
  • 19