Skip to content
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

Support for generating SVG components without .defaultProps #129

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

Grohden
Copy link

@Grohden Grohden commented Dec 5, 2024

This should close #126

I've changed the code to:

  • Use inline 'defaultProps' in the generated SVG components
  • Use object.assign to merge the prop overrides (see linked issue)
  • Use an option (emitDeprecatedDefaultProps) that allows this package users to still keep emitting the defaultProp declarations

I've made the option to omit default props an opt-out, so it is a breaking change.. but up for the author to change that to a default false

@Grohden
Copy link
Author

Grohden commented Dec 5, 2024

Oh... btw I couldn't test (in a real project), ever since I've opened this issue we've migrated away from babel & this plugin

src/index.js Outdated
Comment on lines 162 to 173
if (!path.scope.hasBinding('object.assign/implementation')) {
const assignDeclaration = t.importDeclaration([
t.importDefaultSpecifier(t.identifier('objectAssign')),
], t.stringLiteral('object.assign/implementation'));

file.set('ensureObjectAssign', () => {
const [newPath] = path.unshiftContainer('body', assignDeclaration);
newPath.get('specifiers').forEach((specifier) => { path.scope.registerBinding('module', specifier); });
});
} else {
file.set('ensureObjectAssign', () => {});
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahhhh i think i misunderstood :-/ we definitely can't inject a package dependency into people's code (that we don't already know is there, like react)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh well... do we have any idea on how to do it without assign? 😟

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested letting just spread in the code, for some instances babel did transform it and used helper methods

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure we can produce spread and rely on babel transpiling it in user codebases

src/index.js Outdated
@@ -138,6 +158,20 @@ export default declare(({
if (typeof filename === 'undefined' && typeof opts.filename !== 'string') {
throw new TypeError('the "filename" option is required when transforming code');
}

if (!path.scope.hasBinding('object.assign/implementation')) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we did this, we'd want to use /polyfill and invoke that, so that we use the native version when present and compliant

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but like, I don't think we want to polyfill people envs right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

of course not :-) that's what "shim" does. "polyfill" is just a function that gets the best possible version.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, ok, I'll change the code to use shim instead.. I went with the impl one cuz I can just call it, for shim I'll have to either somehow inject this expr:

var objectAssignShim = require('object.assign/shim');
 // it probably needs to be separated for cases where we generate `import` syntax... 🤔 but if you think it is really safe to call require(...)(); I'm up for it
var objectAssign = objectAssignShim();

var Svg = function(overrides) {
   var props = objectAssign({}, DEFAULTS, overrides);
   // ....
}

or make it like this, which is the easy way to do it...

var objectAssignShim = require('object.assign/shim');

var Svg = function(overrides) {
   var props = objectAssignShim()({}, DEFAULTS, overrides);
   // ....
}

I'll make it like this ^, I don't really know the implications of that objectAssignShim() call on every render.. but I don't think it will be simple to inject that var objectAssign = objectAssignShim() at the top levels

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made the change

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry if i was unclear; shim is the one we DONT want to use. polyfill is the one we DO want to use, because it will be the native one when possible.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ohhh, sorry for misunderstanding, I thought polyfill would polyfill the env 😆 (now what I read bout shim makes more sense) Ill make the changes tomorrow

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made the change to use polyfill

src/index.js Outdated Show resolved Hide resolved
@ljharb ljharb marked this pull request as draft December 6, 2024 07:00
validateDefaultProps(result);
console.log('test/fixtures/test-multiple-svg.jsx', result.code);
console.log('test/fixtures/test-multiple-svg.jsx\n', result.code);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we know, I've thought it was clearer to read the outputs with a \n, but if you want I can revert this

src/index.js Outdated Show resolved Hide resolved
const defaultProps = SVG_DEFAULT_PROPS_CODE
? 'var props = objectAssign({}, SVG_DEFAULT_PROPS_CODE, overrides);'
: '';
const PROPS_NAME = SVG_DEFAULT_PROPS_CODE ? 'overrides' : 'props';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where does "overrides" come from? is that a react thing?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nop, thats just renamed props, I didn't want to change the body (SVG_CODE) so I made a little rename here, the final function will be

var SVG_NAME = function SVG_NAME(overrides) {
  var props = objectAssign({}, SVG_DEFAULT_PROPS_CODE, overrides);
  
  return SVG_CODE // which will probably ref `props`, which I didn't want to mess with so overrides is just a rename
} 

src/index.js Outdated
}) => {
const defaultProps = SVG_DEFAULT_PROPS_CODE
? 'var props = objectAssign({}, SVG_DEFAULT_PROPS_CODE, overrides);'
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, one implication of assign here is that just props will never be a stable ref, but I don't think anything in the SVG_CODE will ref just props alone so it should be ok...

ignorePattern,
caseSensitive,
filename: providedFilename,
emitDeprecatedDefaultProps = false,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ljharb I mentioned in the PR description, but JIC, this is a breaking change if this is default false... especially if someone reflects on the SVG default props

I think that since react went with the warnings this is a good thing (no warnings out of the box experience), but as a lib consumer I also don't love breaking changes 😞 though IMO, just reading the change log should do it 🤷

@ljharb
Copy link
Collaborator

ljharb commented Dec 7, 2024

To clarify, SVG_DEFAULT_PROPS_CODE is opts.SVG_DEFAULT_PROPS_CODE = t.objectExpression(defaultProps);, and so we can probably just remove that internal option. defaultProps is created from iterating over the actual SVG code, and reading the properties therein, so we could instead make like defaultEntries - an array of 2-tuples, property name and value, and then inside the component, iterate over that and mutate props.

However, a much much simpler approach is probably to just make this a class component and keep using defaultProps, since I'm pretty sure nothing about class components here would be deprecated?

@Grohden
Copy link
Author

Grohden commented Dec 7, 2024

@ljharb thats actually... a really good solution, just hope that whatever env people have supports classes 🤔 (~97% of web?)

Ill explore classes in this PR then

@ljharb
Copy link
Collaborator

ljharb commented Dec 7, 2024

ah, good point. I think we can make a class component without class syntax though.

@Grohden
Copy link
Author

Grohden commented Dec 14, 2024

@ljharb I've setup a nextjs project with babel to test this PR changes and essentially I've discovered that I can't use class components in nextjs server side

Error:   × You're importing a component that needs `PureComponent`. This React hook only works in a client component. To fix, mark the file (or its parent) with the `"use client"` directive.
   
    Learn more: https://nextjs.org/docs/app/api-reference/directives/use-client

Essentially, due to nextjs and SSR (and potentially any other SSR impl?) if we want to emit class components people should mark their files as client components, which is a pretty stupid requirement to be able to use inline SVGs... So I guess we should stick to the fn components idea

@Grohden
Copy link
Author

Grohden commented Dec 14, 2024

@ljharb made the change to polyfill and tested it in my mentioned nextjs project

About this comment I'm not really sure I want to mutate props, its unlikely that people will be relying on a props object after passing it to a component but 🤷 on top of that it will make things more complicated for me in this PR

@ljharb
Copy link
Collaborator

ljharb commented Dec 14, 2024

@Grohden class components can extend React.PureComponent though?

@Grohden
Copy link
Author

Grohden commented Dec 15, 2024

@ljharb yes (actually, isn't it the "only" way to make class component?), but will cause that problem I've mentioned on next and potentially any SSR solution out there 🤔

@ljharb
Copy link
Collaborator

ljharb commented Dec 15, 2024

No, class extends React.Component is a normal class component, and class extends React.PureComponent is a pure component. Functional components can't be "pure". SSR has worked fine for the better part of a decade, even before SFCs existed, so I don't think it's an SSR problem - but it indeed might be a next.js-specific one. Regardless of what we do here, can you file an issue on next? Any valid React component should be usable anywhere any kind of react component is accepted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

defaultProps is deprecated for fn components
2 participants