Hey there,
I wonder if there's a way to preserve the comments that are being stripped out of my template (I think by Babel, but not entirely sure).
I use flow, and the // flow comment at the beginning of the file is mandatory to have flow validations in the file.
Thanks a bunch!
@neoziro any info on this?
It seems like the comments are stripped out when calling template.smart, because the ast that is generated later is already without comments.
Following babel.template docs I tried adding {generatorOpts: { comments: false }} to template.smart, but to no avail.
I'm forced to go back to the old svgr package if I can't find a way to keep the comments :sadpanda:, which is a bummer cause the new one is super fast!
Hmm I tested things and it seems to be a babel issue, can you add a new issue on Babel?
@neoziro I've found a workaround/fix, but I think there might also be a bug on babel-template's side.
I'll create a PR for you to review with an updated doc on how to correctly use templates and I'll try to make it easier for folks to navigate and understand templating with svgr.
Great feature, but it took me a lot of time digging into source code, tests and issues to actually understand what the hell was going on 😂
Awesome! Thank you, your help is welcome! Just for curiosity what is your workaround?
In the documentation, whenever a custom template is mentioned or used, the syntax template.ast`...` is used, but that's not what we should be using here.
As per babel-template's docs on AST: "If no placeholders are in use and you just want a simple way to parse a string into an AST, you can use the .ast version of the template."
The interface of the template function (same API and return value as template.smart) is:
template(code, [opts])
And the output of the template function is "[...] a function which is invoked with an optional object of replacements."
With all that in mind, this is my functioning version of the template
module.exports = ({ template }, opts, { componentName, jsx }) => {
const code = `
// @flow
import * as React from 'react';
import { withTheme } from 'styled-components';
type PropsType = {
theme: *,
className?: string,
};
const COMPONENT_NAME = (props: PropsType) => {
const { theme, className } = props;
return JSX;
};
export default withTheme(COMPONENT_NAME);
`;
const flowTpl = template.smart(code, {
plugins: ['flow'],
preserveComments: true,
});
return flowTpl({
COMPONENT_NAME: componentName,
JSX: jsx,
});
};
.ast short versiontemplate.smart(code, opts)I was thinking I could add a template section that explains all that, and link it in the Advanced menu, instead of putting it inside CLI or Node, etc...
What do you think?
To be honest, I don't even understand how the current flow/typescript works, because template.ast`...` seems to ignore everything except the plugins option.
You might have some more insights on this, but it seems to work by accident, like maybe plugins is set globally on the template object or something like that (haven't checked the src for babel-template on this)
E.g. from the docs for TS
```js
const typeScriptTpl = template.smart({
plugins: ['typescript'], // failing to add this will make the parsing fail
preserveComments: true, // adding this the same as my code above, but completely ignored here
})
return typeScriptTpl.ast`
import * as React from 'react';
const ${componentName} = (props: React.SVGProps<SVGSVGElement>) => ${jsx};
export default ${componentName};
`
@dmatteo Is there a way to preserve line breaks?
@donifer I tried using the parser option retainLines, but without success. The docs themselves state
[...] it is only a best-effort, and is not guaranteed in all cases with all plugins.
I guess a slightly hacky way would be to use replacement strings with newlines in it, e.g. (I haven't actually tested this)
module.exports = ({ template }, opts, { componentName, jsx }) => {
const code = `
// @flow
import * as React from 'react';
NEWLINE
NEWLINE
NEWLINE
const COMPONENT_NAME = (props) => {
const { theme, className } = props;
return JSX;
};
export default COMPONENT_NAME;
`;
const flowTpl = template.smart(code, {
plugins: ['flow'],
preserveComments: true,
});
return flowTpl({
COMPONENT_NAME: componentName,
JSX: jsx,
NEWLINE: '\n',
});
};
@dmatteo Works perfectly, thank you so much!
Template in previous version was much better. Babel template is an uncontrollable shit.
now now, let's not get carried away with the judgement :)
@TrySound I agree we should bring back the old system in the new version.
Oh well, I won't bother documenting this then 👍
@neoziro @dmatteo
A quick thought on this decision. My initial thought when using this was that for webpack I would use normal @svgr/webpack loader, but my SSR would use something like:
require("@babel/register")({
"presets": [
["@babel/env", {
useBuiltIns: "usage"
}],
"@babel/react",
"@babel/typescript",
"@svgr/babel-preset",
],
extensions: ['.js', '.jsx', '.ts', '.tsx', '.svg']
});
From what I gather though, the plugin @svgr/babel-plugin-transform-svg-component (or whatever) isn't set up to accept real babel file inputs, instead meant to be used via svgr();. On the other hand, according to you, this support is part of the roadmap (#252). If you were to switch back to a non-babel-style template language, would it cause issues with supporting isomorphic transformations?
would it cause issues with supporting isomorphic transformations
What do you mean by "supporting isomorphic transformations"?
I'm referring to transformations from both Build-time and Run-time entrypoints. E.g., @svgr/webpack (webpack build-time) vs @babel/register (babel runtime)
so, is there no way to do this with just the template.ast method?
ok, here's what I did:
function template({ template }, opts, { imports, componentName, props, jsx, exports }) {
const typeScriptTpl = template.smart({ plugins: ['typescript'] });
const viewBox = jsx.openingElement.attributes.find((att) => att.name.name === 'viewBox');
const fill = jsx.openingElement.attributes.find((att) => att.name.name === 'fill');
const stroke = jsx.openingElement.attributes.find((att) => att.name.name === 'stroke');
const viewBoxValue = `${viewBox.value.value}`;
const fillValue = fill ? `fill: "${fill.value.value}",` : '';
const strokeValue = stroke ? `stroke: "${stroke.value.value}",` : '';
let addedProps = `{ width: "1em", height: "1em", viewBox: "${viewBoxValue}", ${fillValue} ${strokeValue} ...props }`;
const comment = `// this file is generated from the root of the admin repo
// see https://github.com/tocktix/admin/wiki/Svg-Icons-Components in order to add a new file`;
return typeScriptTpl.ast`
${comment}
${imports}
import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';
const ${componentName} = (props: SvgIconProps) => React.createElement(SvgIcon,
${addedProps}, ${jsx.children});
${exports};
`;
}
module.exports = template;
Most helpful comment
In the documentation, whenever a custom template is mentioned or used, the syntax
template.ast`...`is used, but that's not what we should be using here.As per babel-template's docs on AST:
"If no placeholders are in use and you just want a simple way to parse a string into an AST, you can use the .ast version of the template."The interface of the template function (same API and return value as
template.smart) is:And the output of the
templatefunction is "[...] a function which is invoked with an optional object of replacements."With all that in mind, this is my functioning version of the template
Notable differences
.astshort versiontemplate.smart(code, opts)I was thinking I could add a
templatesection that explains all that, and link it in the Advanced menu, instead of putting it inside CLI or Node, etc...What do you think?