kristerkari / react-native-svg-transformer

Import SVG files in your React Native project the same way that you would in a Web application.
MIT License
1.61k stars 116 forks source link

react-native-web support #45

Open vitalyiegorov opened 5 years ago

vitalyiegorov commented 5 years ago

Hi @kristerkari,

Thank you for your plugin it is working great for React native projects,

We are trying to share our codebase across react-native and web(using react-native-web), our codebase is originally developed for React native CLI project, now we would like to add web support and using CRA with CRA-rewired but we are facing a problem:

According to CRA docs if you want to import SVG as react component you should use: import { ReactComponent as Logo } from './logo.svg';

but SVGR is making only export default SvgComponent so we cannot use this approach, to fix this we could add a small fix here which should not break anything:

var jsCode = svgr.sync(src, svgrConfig) + '\nexport const ReactComponent = SvgComponent;';

which gives us needed support to work in react-native and web projects(also definitions.d.ts should be updated accordingly).

Maybe we could make a PR for this, or what do you think?

Here is a quick fix for postinstall npm/yarn script:

"postinstall": "sed -e \"s:var jsCode = svgr.sync(src, svgrConfig);:var jsCode = svgr.sync(src, svgrConfig) + 'export const ReactComponent = SvgComponent;';:g\" -i.bak ./node_modules/react-native-svg-transformer/index.js"
kristerkari commented 5 years ago

Not sure what library/plugin CRA is using for the SVG images, but the idea with this transformer is that you can use SVGR in you Web projects, and the images and properties will work 100% the same between Web and React Native.

kristerkari commented 5 years ago

Can SVGR easily be added to CRA? That would fix the problem for you.

The fix that you are suggesting is a breaking change, so I'm not that keen on making it if it can be avoided.

vitalyiegorov commented 5 years ago

Why do you think that this is a breaking change? It would leave default export SvgComponent and same usage import Logo from './logo.svg'; unchanged, but also it will give ability to use named export: import {ReactComponent as Logo} from './logo.svg'; which is supported by default by CRA.

CRA uses SVGR but with some modifications, I am just trying to enable the simple bridge to use this plugin with react-native-web and CRA.

kristerkari commented 5 years ago

Why do you think that this is a breaking change? It would leave default export SvgComponent and same usage import Logo from './logo.svg'; unchanged

True, that would not be a breaking change, but the Typescript types will not be compatible with CRA as in CRA the default import seems to be a string, and the named import is a React component.

I need to look into the CRA docs a bit more to see if I'm looking at the correct things.

kristerkari commented 5 years ago

Actually, I'm not completely sure if the default import in CRA is the url or not, I tried to look at this, but I'm not sure: https://github.com/facebook/create-react-app/issues/3722

vitalyiegorov commented 5 years ago

@kristerkari Referring to CRA docs, you could use default export for SVG usage as image source:

import Logo from './logo.svg';
...
return <image src={Logo}/>

or use SVG as react component:

import {ReactComponent as Logo} from './logo.svg';
...
return <Logo/>
kristerkari commented 5 years ago

Yes exactly, and that's the problem: it works differently from the changes that you are suggesting. Which means that the default import would return a component in React Native, but a url in Web.

vitalyiegorov commented 5 years ago

Yes, but this transformer is not used in any CRA projects, they have their own configuration and transformer, its recommended to use this transformer only with React Native CLI projects, so this modification will influence only React Native projects which are using this transformer and thus adding named export won't break any existing React Native projects because the case with returning default as a string is not used in React Native projects, but this will give the ability to add react-native-web support to existing React Native CLI projects by changing default imports to named imports.

kristerkari commented 5 years ago

this modification will influence only React Native projects which are using this transformer and thus adding named export won't break any existing React Native projects because the case with returning default as a string is not used in React Native projects, but this will give the ability to add react-native-web support to existing React Native CLI projects by changing default imports to named imports.

I get that, but the change that you are suggesting also means that there would be types mismatch when sharing the same code between React Native and React Web with CRA and using Typescript or Flow.

I understand that many people are using CRA as their boilerplate, but what they do for Web doesn't make sense on the React Native side.

I use Typescript in most of my projects, and I'm not keen on making a change that only partially matches what CRA is doing, as all the existing users of this library are already using the default import for the React component.

I would suggest that you try to use @svgr/webpack directly instead, like it's set up in the demo project: https://github.com/kristerkari/react-native-svg-example/blob/master/webpack.config.js#L28-L38

vomchik commented 4 years ago

@vitalyiegorov You can set a custom template for SVGR.

// svgr.config.js

const isExportNamedDeclaration = item => item.type === "ExportNamedDeclaration";

const hasExportNamedDeclaration = (items) => {
  if (items === null) {
    return false;
  }

  if (items instanceof Array) {
    return !!(items && items.find(isExportNamedDeclaration))
  }

  return isExportNamedDeclaration(items) 
}

const getName = (component) => {
  if (component instanceof Object) {
    return component.name;
  }

  return component;
}

const customTemplate = (
  { template },
  opts,
  { imports, componentName, props, jsx, exports },
) => {
  const ReactComponent = hasExportNamedDeclaration(exports) 
    ? ''
    : 'export const ReactComponent = ' + getName(componentName);

  return template.ast`
    ${imports}
    function ${componentName}(${props}) {
      return ${jsx};
    }
    ${ReactComponent}
    ${exports}
  `
}

module.exports = {
  template: customTemplate
}

@kristerkari Maybe it will be useful, so let's add it to the README file.

calebmackdavenport commented 3 years ago

@vitalyiegorov You can set a custom template for SVGR.

// svgr.config.js

const isExportNamedDeclaration = item => item.type === "ExportNamedDeclaration";

const hasExportNamedDeclaration = (items) => {
  if (items === null) {
    return false;
  }

  if (items instanceof Array) {
    return !!(items && items.find(isExportNamedDeclaration))
  }

  return isExportNamedDeclaration(items) 
}

const getName = (component) => {
  if (component instanceof Object) {
    return component.name;
  }

  return component;
}

const customTemplate = (
  { template },
  opts,
  { imports, componentName, props, jsx, exports },
) => {
  const ReactComponent = hasExportNamedDeclaration(exports) 
    ? ''
    : 'export const ReactComponent = ' + getName(componentName);

  return template.ast`
    ${imports}
    function ${componentName}(${props}) {
      return ${jsx};
    }
    ${ReactComponent}
    ${exports}
  `
}

module.exports = {
  template: customTemplate
}

@kristerkari Maybe it will be useful, so let's add it to the README file.

Can you add this into the root of your project to handle the SVGs for web? Do you need to do anything else?

janlat commented 2 years ago

Since SVGR v6.0.0, switching to named exports is just a matter of adding exportType: 'named' to your svgrrc file