airbnb / babel-plugin-inline-react-svg

A babel plugin that optimizes and inlines SVGs for your React Components.
MIT License
474 stars 92 forks source link

Dynamically import svgs #51

Open mick-feller opened 5 years ago

mick-feller commented 5 years ago

So we have a library with all our svg icons in it.

I'm looking for a way to import all these svgs dynamically and display them in our styleguide (storybook) to give a visual representation of which icons we have.

I was trying something like this but no luck:

const reqSvgs = require.context('./svgdir', true, /\.svg$/);
reqSvgs.keys().map((filename) => {
      return (
        <div className="icon">
            {reqSvgs(filename)}
        </div>
      );
})

But no luck, anyone any idea how i can make this work?

chaance commented 5 years ago

@mick-feller Have you tried the dynamic import syntax? https://reactjs.org/docs/code-splitting.html#import? This is untested, but I think it should look something like this:

const reqSvgs = require.context('./svgdir', true, /\.svg$/);
reqSvgs.keys().map((filename) => {
  import(`./svgdir/${filename}`)
    .then(Icon =>
      <div key={filename} className="icon">
        <Icon />
      </div>
    )
    .catch(/* fallback */)
})
yairEO commented 5 years ago

@chancestrickland - How would you use each specific Icon in JSX when implementing your method?

Doing import(`./svgdir/${filename}`) gives:

Module {default: "static/media/spinner.aabcf541.svg", __esModule: true, Symbol(Symbol.toStringTag): "Module"}
yairEO commented 5 years ago

This does not work:

let Foo;

// I've placed a single svg file in the folder
const reqSvgs = require.context('./icons', true, /\.svg$/);
reqSvgs.keys().map((filename) => {
    filename = filename.replace('./','')
    let name = filename.replace(/\.[^.]*$/,'')

    import(`./icons/${filename}`)
        .then(Icon => {
            Foo = () => <Icon />;
        })
})

const Icon = ({type}) => {
    return <Foo/> // assume this will be dynamic using "type"
}

export default Icon

ERROR:

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. Check the render method of Foo.

yairEO commented 5 years ago

I now understand require.context is a webpack-only thing and I do not want webpack in my code, since I don't have an app, just a single standalone <Icon> component

AwsmOli commented 5 years ago

This will give you require.context for rollup: https://www.npmjs.com/package/rollup-plugin-require-context

jp1987 commented 2 years ago

@yairEO @mick-feller did you ever fix this?

I'm trying to import inline:

import(`../Icons/${name}.svg`).then(icon => {
   console.log(icon);
})

Which returns the module.

Webpack config:

{
  test: /\.svg$/,
  use: ['babel-loader']
}

And running version 2.0.1

mick-feller commented 2 years ago

@jp1987 we actually moved away from this plugin and went with @svgr/webpack, the webpack piece is what we setup in storybook, but you can obviously convert this to regular webpack.

config.module.rules.push({
    test: /\.svg?$/,
    oneOf: [
      {
        use: [
          {
            loader: '@svgr/webpack',
            options: {
              prettier: false,
              svgo: true,
              svgoConfig: {
                removeViewBox: false
              },
              titleProp: true,
            },
          },
        ]
      }
    ]
  })

And then in our code we actually do this to generate an object of all our SVG's in a folder:

const getSVGS = () => {
  const context = require.context('../../icons', true, /\.svg$/);
  const obj = {};
  context.keys().forEach((key) => {
    obj[key] = context(key).default;
  });
  return obj;
};