-
Notifications
You must be signed in to change notification settings - Fork 2
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
Rewrite of the codemod #2
Conversation
Here's an updated version of the Explorer: https://astexplorer.net/#/gist/7bc4771564a12c9f93c4904b3934aa1c/b8ce61a62c97c913866b42426b24762c4c1d0c83 |
Dude. Thank you so much! That was incredible! I was actually planning to set aside time to write up a part of this for you to have a read and possibly learn from. Looks like you did amazing stuff already! Can I participate in this PR and help with the remaining tasks? Thanks again for the PR! |
I think I'd like to try continuing with global styles and keyframes. But if you want you can maybe look into improving which properties are actual css properties and which are not here. e.g Also one could attempt to fail the codemod if it encounters |
@danielberndt I'll look at that. Thanks for your contributions so far! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great stuff! Thanks especially for the tests! They sure help!
I think the implementation needs some docs and now would be a good time to add it so that all of us (especially newcomers) have an idea of what's going on, in case someone wants to learn about codemods.
Knowledge sharing ftw!
Also, I've noticed that changing the ImportDefaultSpecifier
in the AST explorer outright removes the default import if it is not gl
. 🤔
Since default imports/export specifiers are not dependent on the actual identifiers, I think we can preserve them.
From that explorer link too, removing a css
prop from any component results in an error.
index.js
Outdated
module.exports = function(babel) { | ||
const {types: t} = babel; | ||
|
||
const fixContentProp = args => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the expected value of args
?
Can we add a bit of documentation here so it's easier for people to understand? I'd like this project to be a bit educational as well: it helped you, so it ought to help others interested in AST transformation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes you're, right, I'll do a more descriptive pass soon :)
index.js
Outdated
const newAttrs = attrs.filter(attr => { | ||
const {value, name, type} = attr; | ||
if (type === "JSXSpreadAttribute") return true; | ||
if (name.name === "css") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can do better than name.name
here in order to allow developers not as experienced with this stuff to understand what's happening and more clearly describe our intent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't come up with attr.name.name
. That's part of the AST API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know that, but I'm suggesting aliasing the destructured name on line 51 in order to provide an identifier that is a little more understandable.
index.js
Outdated
); | ||
appendToCss.push({ | ||
name: t.identifier(name.name), | ||
value: value.type === "JSXExpressionContainer" ? value.expression : value, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would the t.isJSXExpressionContainer
function work for this validation instead? I think it's more future proof because it could handle more imperative validations if needed in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably applies to type === "XXX"
checks, of which I did quite a lot.
index.js
Outdated
if (appendToCss.length > 0) { | ||
if (!cssAttr) { | ||
cssAttr = t.jsxAttribute( | ||
t.jsxIdentifier("css"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Afaik, <span css={}
is invalid in intrinsic JSX elements. The valid alternative is <span style={}
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checked out this part of the emotion docs!
<span css={styles}/>
will be processed by the emotion babel plugin and turned into <span className={...}/>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔
Yes, but is it safe to assume that everyone has the babel plugin? I'd rather the codemod give users something that works with base emotion out of the box.
We could even add a plugin option for users to opt-in to babel-plugin-specific syntax sugar like this. I know @kentcdodds wanted this and I agree that this would be a really nice feature to add to this codemod.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So what would you suggest for code like:
<glamorous.Span css={{":hover": {color: "red"}}}/>
I guess we could do something like:
// opts.withPlugin === true
<span css={{":hover": {color: "red"}}}/>
// opts.withPlugin === false
import {css} from emotion
<span className={css({":hover": {color: "red"}})}/>
But for the sake of keeping this PR small, I'd suggest to assume that the plugin is installed for now. We can introduce the withPlugin
flag in a separate PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great suggestion.
Philosophically, for keeping the PR small, I'd remove the functionality around CSS props and other plugin-related transformations from this PR altogether and include them all in a more encapsulated PR.
This means that the existing transformations around this functionality ought not be removed, but moved to another PR so this one is a little leaner and simpler, even to review and collaborate on.
index.js
Outdated
); | ||
newAttrs.push(cssAttr); | ||
} else if (cssAttr.value.expression.type !== "ObjectExpression") { | ||
// turn <span css={obj} .../> into <span css={{...obj}} .../> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the intent here is to turn <span css={obj()} into <span css={{ ...obj() }}
? The current description of spreading an object into an object seems to be a bit of a no-op. 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is needed to transform e.g.
<gl.Span css={redStyle} marginTop={5}/>
into
<span css={{...redStyle, marginTop: 5}}/>
So the comment doesn't tell the whole story yet, I'll fix it!
Ah, have a look at this test to understand why I did this. Previously a reference to glamorous was needed for One side effect is that if glamorous was imported and never used, the import will disappear due to the codemod. That's something I'd be fine with. |
Okay, I just added the discussed improvements. (and added eslint) If you're happy with the changes, I'd suggest to work on all the additional suggestions above (and in the code) in separate PRs :) |
Thanks for the documentation adjustments and destructured aliases! Would you agree with me that it is a far more welcoming to readers of the source code now? I certainly think so. Great job! I'll have another read through it tomorrow and possibly approve and merge it if we get the separation of PR concerns right. One extra nit I found was that we're writing multiline comments with It has been a real pleasure collaborating with you on this so far. 😄 |
Re separating the css property stuff into a separate PR: Regarding multiline comments. To me it's mostly a matter of organical growth. Most comments start off as a single line comment, then I add a half-sentence, it gets too long, so I break it into two lines without changing the type of comment. It's a mixture of lazyness and lack of IDE-features I guess. |
index.js
Outdated
) | ||
); | ||
} | ||
const styledVisitor = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we document this a little better?
name
, oldName
, etc. seem a little ambiguous.
package.json
Outdated
@@ -5,5 +5,27 @@ | |||
"main": "index.js", | |||
"repository": "https://github.com/tejasq/babel-plugin-glamorous-to-emotion", | |||
"author": "Tejas Kumar <[email protected]>", | |||
"license": "MIT" | |||
"license": "MIT", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should add a "collaborators"
here since we're buddies now! 😄
Great stuff! Let's add a little more explicit naming and docs and then we're good to merge. 🚀 🔥 |
Alright, I just added some clarifying comments for the core part of the transform step :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is awesome. Readable, tested, wonderful. Thanks for your contribution!
]), | ||
// only if the traversal below wants to know the newName, | ||
// we're gonna add the default import | ||
const getNewName = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this work on line 61 if it's defined here? 🤔 Surely I've missed something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whatever you put as a second argument when calling a traverse step, will be available as state
in the second parameter of each Visitor.
I pass the getNewName
parameter here
Check this part of kentcdodds best practices for more information!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! I missed the part where it was being passed in on line 44! Makes sense!
Ah sorry, didn't expect your review to be that quick, I just made a small change to how the "contributors" field is set up. And... this seems to have dismissed your review. |
]), | ||
// only if the traversal below wants to know the newName, | ||
// we're gonna add the default import | ||
const getNewName = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! I missed the part where it was being passed in on line 44! Makes sense!
Hi there!
I eventually found some time to dig into AST transformations, and I had the desire to dig deep 😅
This PR is more or less a complete rewrite, supporting all the old-features, and including many new ones. Just check the
__tests__/__fixtures__
folder for some examples.It also allows you to run tests via
npm install && npm test -- --watch
for a fast feedback loop while developing.I haven't tested it on a real code base yet, so consider this more of an WIP...
Here's what this PR should fix from my list:
import g from "glamorous"
the codemod expects import glamorous from "glamorous"<glamorous.div marginTop={5}/>
to<div css={{marginTop: 5}}/>
filterProps/forwardProps
toshouldForwardProps
(the latter expects a function), most other options (e.g. withProps) are also not directly translatableforwardProps: ["innerRef"]
does not work with emotion