boopathi / react-svg-loader

A loader for webpack, rollup, babel that loads svg as a React Component
MIT License
638 stars 86 forks source link

Compatibility with preact #290

Open jakub-g opened 5 years ago

jakub-g commented 5 years ago

Hello, I was trying to use the loader with preact (bundling with webpack) and here's what I've found for other interested people.

I used following webpack conf:

 rules: [
   ...
   {
      test: /\.svg$/,
      loader: 'react-svg-loader',
    }
  ]

Outside of the box, the module does not work with preact because of a hard dependency on react. When trying to compile the app, there's an error like this:

ERROR in ./app/icons/XXX.svg (./node_modules/react-svg-loader/lib/loader.js!./app/icons/XXX.svg) Module not found: Error: Can't resolve 'react' in 'C:\git\<repo>\app\icons\'

I managed to fix it by installing preact-compat and aliasing it as react in webpack config:

{
  ...
  resolve: {
    ...
    alias: {
      react: 'preact-compat',
  }
}

This works but is not ideal, as preact-compat is 13kB (5 kB gzipped) which seems wasteful because the only thing necessary for the module to work is React.createElement which should be generally what preact.h does.

I will try to find out if there's some way to have a build-time replacement of this instead of runtime dependency on preact-compat. If anyone knows how to do it, please let me know.

jakub-g commented 5 years ago

Alright so for the future visitors, I managed to make this loader compatible with preact v8 without using preact-compat as follows:

  1. webpack config:
  return {
    rules: [
      {
        test: /\.svg$/,
        use: [
          // Note the loaders are applied in reverse order!
          // 2. Transform JSX to JS.
          // Basically copied the existing babel config for JSX files here.
          {
            loader: 'babel-loader',
            options: {
              presets: [['env', presetEnvLegacySettings]],
              plugins: [
                  ['transform-object-rest-spread', { useBuiltIns: true }],
                  ['transform-react-jsx', { pragma: 'h' }],
              ]
            },
          },
          // 1. Load SVG and transform it to JSX
          {
            loader: 'react-svg-loader',
            options: {
              jsx: true,
            },
          },
        ],
      },
  1. Modify babel-plugin-react-svg:

https://github.com/boopathi/react-svg-loader/blob/fa8b061f7378658b6bb5ee108589e497d56971a9/packages/babel-plugin-react-svg/src/index.ts#L152-L155

-      path.node.body.unshift(t.importDeclaration([t.importDefaultSpecifier(t.identifier("React"))], t.stringLiteral("react")));
+      path.node.body.unshift(t.importDeclaration([t.importSpecifier(t.identifier("h"), t.identifier("h"))], t.stringLiteral("preact")));
  1. Alternatively in step 1, do not define babel-loader + options: { jsx: true,}, but instead modify react-svg-core as follows:

https://github.com/boopathi/react-svg-loader/blob/fa8b061f7378658b6bb5ee108589e497d56971a9/packages/react-svg-core/src/index.ts#L23-L25

-    presets: [jsx ? void 0 : "@babel/preset-react"].filter(Boolean),
+    presets: jsx ? [] : [["@babel/preset-react", {pragma: 'h'}]],

Another thing I tried but it didn't quite work: without passing { jsx: true } to the plugin and add babel-loader, you could m

but this still requires preact-compat to work (it is required but not used, but somehow webpack doesn't discard it).