-
-
Notifications
You must be signed in to change notification settings - Fork 27k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
"SVG as a component" doesn't quite work in 2.x #3856
Comments
OK, I have a different idea that can maybe solve both this and help #3722 in the future. My main concern with #3722 is the build performance: we don't want to spend time building something we don't use. But the loader doesn't know what needs to be built if it's not directly in the "request" (of which the named import isn't a part). And the same exact problem is what's causing this bug. I thought: what if we gave webpack more information without the user knowing about it? We could have an extra Babel transform that takes import logoUrl, { ReactComponent as Logo } from './logo.svg'; and turns it into // generated code
import logoUrl from './logo.svg';
import Logo from 'svgr-loader!./logo.svg'; (or similar) The important part is this happens behind the scenes so the user doesn’t need to know about loaders. It does couple our Babel preset and webpack a little bit but it could be an opt-in option so people who use it without webpack won’t get this. This way we can both have an expressive way to import what you need without webpack syntax, and a way for the loaders to know what exactly to generate. Inside CSS we’d always get the filenames (since we wouldn‘t transform it with Babel). I won’t have time to work on this but @iansu maybe you’d be interested to try? |
Any chance you could help us understand the best path forward here? It seems like we could avoid a hacky solution if |
@gaearon Not sure if this is solvable with the current CSS Pipeline and CJS (it's overdue to update some parts there anyways (e.g CSS Loader (
|
'Tree-shaking'/Dedupe works (so far), since the CSS is 💯 ESM then |
It's also possible to add custom plugin.js export default postcss.plugin('name', (options = {}) => (css, result) => {
// Get Imports && Exports somehow...
// CSS Import
result.messages.push({
type: 'import' // required (Message Type)
name: `CSS__IMPORT__${idx}` // Can be anything
url: 'path/to/file' // Can be anything
import () { // required (Message Stringifier)
return `import ${this.name} from '${this.url}';`
}
})
// CSS Export
result.messages.push({
type: 'export', // required (Message Type)
name: `${name}`, // Can be anything
value: 'Custom Export', // Can be anything
export () { // required (Message Stringifier)
return `export const ${this.name} = ${this.value};`
}
})
}) |
It sounds like the Babel transform (either via a plugin or a macro) might be the best way forward. It also seems to be the favored solution in #3722 for implementing this for other file types. |
I think let's start with a userland solution based on import toReactComponent from 'svgr/macro';
const Logo = toReactComponent('./icon.svg');
<Logo /> If that works we can revert the new feature, and instead document this as a supported way. |
@kentcdodds @threepointone Maybe you can give a rough idea of how to implement this macro? |
I guess it'll be similar to this: #1792 (comment) |
You got it @gaearon 👍 I think that's a good start to the solution for many problems like this. So much for babel-plugin-macros being "experimental" 😉 😅 |
You can get the SVG URL ( // import X__IMPORT from '${url}'
HarmonyImportSideEffectsDependency {
name: 'X__IMPORT',
request: '${url}',
module: { <= file-loader
...
}
}
Nope But I see you want to go with |
The main reason that I'm thinking of going with Babel is because the loaders still have to produce the build output (even if it ends up treeshaken). Seems like wasted effort when it's not trivial. The Babel solution neatly avoids this. |
(Still, thanks for all the context!) |
I'll take a crack at an implementation after work hours, think this is important enough to exist before 2.0 launches |
(I can sense the excitement in @threepointone's tone!) |
I'm experimenting with adding a macro to |
Relevant code is in |
You'll find resources for learning about ASTs in the author docs:
I hope that's helpful! |
Just noticed this:
Should be:
|
Thanks. That was the last thing I added before I committed. It builds but I guess it will blow up when it tries to throw an exception. I've read the babel handbook before. Just re-familiarizing myself with everything now. Thanks for the links! |
I just realized one major shortcoming of this approach is that we're effectively inlining SVGs. If you include the same SVG in your code twice it will end up inlined in the bundle twice. 😞 |
Yup, that’s definitely an issue when using a macro as a ‘loader’, but can be worked around - write the generated component to a separate js file in the src directory, and import/require it from the original file. |
Yes, that's true. One way to get around this would be to put it in a module and import that module anywhere you like. Not quite as nice as a webpack solution. |
(We should probably make a helper to make macros-as-loaders simpler) |
@FWeinb Do I understand correctly that you intend to work on this, and then send a PR to incorporate your Babel plugin into our monorepo? It's important for us to be able to quickly tweak it. |
@gaearon |
That works for me. I'll get an initial PR ready for the SVG use case and then we can take it from there. |
Thanks! |
I was in holiday, I try to catch up. Babel seems a good idea, Webpack does not provide as much flexibility. @iansu do you need help? |
In my several projects, there are two kinds of SVG. One is icon-based which are exported from Adobe illustrator and the other is image-based (e.g., Logo) which are exported from Sketch.app. I need to setup different configs of Logo componet: import one svg file with default config:import toReactComponent from 'svgr.macro';
const Logo = toReactComponent('./logo.svg');
↓ ↓ ↓ ↓ ↓ ↓
const Logo = props => <svg width={116} height={28} viewBox="0 0 116 28" {...props}>
<g fill="none" fillRule="evenodd">
... Icon componets: glob multiple svg files with custom config:const { DoneBlack, Autorenew } = toReactComponent(
'./material/*.svg',
{ icon: true, replaceAttrValues: ['#61DAFB=currentColor'] },
);
↓ ↓ ↓ ↓ ↓ ↓
const {
DoneBlack,
Autorenew
} = {
"Autorenew": props => <svg height="1em" viewBox="0 0 24 24" width="1em" {...props}>
...
</svg>,
"DoneBlack": props => <svg height="1em" viewBox="0 0 24 24" width="1em" {...props}>
...
</svg>
}; ReferencesGitHub: https://github.com/evenchange4/svgr.macro cc @iansu Do you still continue working on your branch into svgr repository? |
@evenchange4 we discourage using babel macros for svgs because they'll inflate the bundle size (if you import the same svg across multiple files). Do you have a solution for this besides importing and exporting out of a separate file that's used everywhere? edit: this solution is still very cool & thank you for sharing |
I feel like it's okay for people who know what they're doing, but we won't be suggesting this as a default solution. It's cool that @evenchange4 made something flexible for his use case though :-) |
Why not using Webpack issuer? @ppozniak had the same problem (gregberge/svgr#43) and it looks like |
@neoziro But the Webpack issuer only provides the filename from which the
Knowing that the request was made from |
@FWeinb This would allow us to bypass the SVG loader if the request came from anything other than a |
Yes, this solves this specific problem but doesn't necessarily address the larger issue of allowing multiple named imports for a variety of file types. |
the issuer solution only works for production config though, i think it's because of style-loader the issuer become .js? |
more info about last comment: i think it's because somehow the loaders are cached in dev, if i remove the import in the js file, and import svg from css that is also imported in that file it works. so i think the babel plugin to transform the imports to webpack syntax still the best right now. |
Guys, can someone help clarify what's the current status with this?
|
Hi @GuyKedem1 I believe what you need is already implemented in the next branch. You can follow instructions here to install it: #3815 |
We merged #3718 and released the first 2.x alpha, but quickly discovered a few issues:
[Object object]
in URLs.toString()
here (which would just return the URL), it looks like the resulting JS bundle contains the React component as soon as CSS imports it. It's my impression that webpack CSS pipeline still relies on CommonJS and therefore can't tree shake the component.I'm not sure if there's any solution even possible here 😢 We probably need to revert the feature.
Just in case, pinging @neoziro @iansu for more ideas.
The text was updated successfully, but these errors were encountered: