How to import svg files in public directory with react vite svgr

1.3k Views Asked by At

How to use SVGR with React's 'public' folder assets?

I'm using react + vite + svgr and my svg files are located in the "public" directory. My project structure is

|
|- src
|- public
    |- icons
        |- svgs
            |- mySvg.svg
|- vite.config.ts
|- package.json

since public is outside of the src folder I get the error:

Assets in public directory cannot be imported from JavaScript.

If you intend to import that asset, put the file in the src directory, and use /src/assets/icons/svgs/mySvg.svg instead of /public/assets/icons/svgs/mySvg.svg.

Clarification:

  • The solution of converting to img element with url is not good as I need to change svg properties.
  • The solution of changing the folder from the public folder are not good in my case as I'd like to maintain the standard public folder as the folder for assets in order to receive built in supports from different libraries.
4

There are 4 best solutions below

0
Noy Oliel On BEST ANSWER

In the end, I couldn't find / get a proper solution, so I'll post my work around as the solution for future developers (in hopes someone can supply a better one or create a better one from my solution):

SVGR has CLI support features that can be used in order to create a script that will generate react component for each *.svg file in the public folder

In your package.json file add the "update-icons" script:

"scripts":
    "start": ...,
    ...
    "update-icons": "chmod u+x scripts/icons/update-icons.sh && scripts/icons/update-icons.sh", <--- add this line
    ...
...

create template files for your generated components and index files at a folder to your liking (e.g. "src/assets/templates"):

// example for component template in file named "componentTemplate.ts"

import { Template } from "@svgr/babel-plugin-transform-svg-component";

export const defaultTemplate: Template = (variables, { tpl }) => {
    return tpl`
import type { SVGProps, ReactElement } from "react";

${variables.interfaces};

export default function ${variables.componentName}(${variables.props}): ReactElement {
  return (
      ${variables.jsx}
  );
};

`;
};

export default defaultTemplate;


// example for index template in file named indexTemplate.ts

export const formatExportName = (name: string): string => {
    if (/[-]/g.test(name) && /^\d/.test(name)) {
        return `Svg${pascalCase(name)}`;
    }

    if (/^\d/.test(name)) {
        return `Svg${name}`;
    }

    return pascalCase(name);
};

const defaultIndexTemplate: IndexTemplate = (paths: FileInfo[]) => {
    const exportEntries = paths.map(({ path: filePath }) => {
        const basename = path.basename(filePath, path.extname(filePath));
        const exportName = formatExportName(basename);

        return `export { default as Svg${exportName} } from './${basename}'`;
});

    return exportEntries.join("\n");
};

create a shell script file named, for example "update-icons.sh", under some dedicated folder (e.g. "scripts/icons/").

In the file content add the following

#!/bin/bash

# configure below variables according to your project specific needs
baseFolder=$(pwd)
dest=$baseFolder/src/assets/generated
src=$baseFolder/public/assets
templatesPath=src/assets/templates # you template files
templates=( componentTemplate indexTemplate )
folders=( svgs ) # you can specify a list of folders here instead of generating all files in the public folder
BLUE="\033[0;34m" # this is used to make the console output prettier
NC="\033[0m" # this is used to make the console output prettier

rm -r "$dest" # remove existing files from the destination folder that contains the generated svg React components

# my project uses ESNext so I need to first transpile the related template files to CommonJS (for SVGR)
echo -e "${BLUE}Transpiling templates${NC}"
for template in "${templates[@]}"
do
  npx tsc --module commonjs --skipLibCheck true "./$templatesPath/$template.ts"
  cp "$baseFolder/$templatesPath/$template.js" "$baseFolder/$templatesPath/$template.cjs"
  rm "$baseFolder/$templatesPath/$template.js"
done
echo "Done"
echo ""

# this is the main part of the solution
echo -e "${BLUE}Generating React components${NC}"
for folder in "${folders[@]}"
do
      npx @svgr/cli --typescript --filename-case kebab --no-dimensions --index-template "./${templatesPath}/indexTemplate.cjs" --template "./${templatesPath}/componentTemplate.cjs" --out-dir "$dest/${folder}" "$src/${folder}"
done

# remove redundant CommonJS files
for template in "${templates[@]}"
do
  rm "$baseFolder/$templatesPath/$template.cjs"
done

Now just run "npm run update-icons" Hope this helps!

4
Salguod On

You need to Move your SVG files to the "src" directory under a suitable location, such as "src/assets/icons/svgs" (or any other appropriate location within the "src" directory).

2
Andrii Bezpryzvanyi On

The contents of the public folder are copied to dist unchanged. Try icons/svgs/mySvg.svg

1
Makuku On

I encountered a similar issue. Right after setting up the Vite project, I implemented path aliases. However, resolving from the public folder resulted in the same failure.

I managed to solve the problem by following the instructions in the Vite documentation:

import reactLogo from '@assets/react.svg'
const viteLogo = new URL('@public/vite.svg', import.meta.url).href

<div>
    <a href="https://vitejs.dev" target="_blank" rel="noreferrer">
      <img src={viteLogo} className="logo" alt="Vite logo" />
    </a>
    <a href="https://react.dev" target="_blank" rel="noreferrer">
      <img src={reactLogo} className="logo react" alt="React logo" />
    </a>

Asset import info

https://vitejs.dev/guide/assets.html#the-public-directory

Hope it helps