-
-
Notifications
You must be signed in to change notification settings - Fork 298
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
Stateless Components #28
Conversation
describe('Stateless Function Components with React.createElement', () => { | ||
it('accepts simple arrow function components', () => { | ||
var def = parse( | ||
'var Foo = () => React.creatElement("div", null);' |
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.
Looks good so far. One remark is actually covered by a typo in this test :D (note the missing e
in React.creatElement
.
Seems like it accepts any function which calls another function as react component.
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.
ha, well, the issue is that I accidentally deleted the check to ensure that I was calling createElement
in the visitor below so basically everything is matched 😇
I’ve gotten a bit stuck on trying to duplicate isReactCreateClassCall
to isReactCreateElementCall
which handles ensuring the correct createElement method is called and it looks up to the root import React bit to ensure it’s the real react.
in src/utils/resolveToValue.js
path.scope.lookup(node.name)
is always returning undefined.
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.
In your test? You'd also have to add var React = require('react');
to the test code.
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.
Yeah, I’ve done that to each...well, technically I’ve done import React from 'react';
or import R from 'react';
.
I’m bouncing for dinner, but should have the next push sometime tomorrow with the next set of questions :)
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.
👍 Really appreciate your effort :)
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.
Oh! Found my sticking point. Since I’m calling recast.visit(node, {})
in the lcontainsJSXElementOrReactCreateElementCall
function I’m losing all of recasts internal state
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.
Managed to get through this. Passing the root path
instead of path.node
didn’t lose state.
I notice you have
|
I attribute it to bad design decisions at the beginning ;) Everything kind of expects that the result of the resolver is a single AST node and extracts data from there. I guess we could introduce some new interface, Handlers shouldn't necessarily what kind of definition they are dealing with. That custom logic could be implemented in the |
I’m currently working on extending the All of my API transformation projects have terrible design decisions. And be “terrible” I mean “no”. |
:) |
Well....I think this is actually working now.. 😮 |
hmmm....not quite working, but close. |
55c2629
to
c480dc5
Compare
Alright, fixed my final commit. This is now working for stateless function components in my current codebase as expected :) |
c480dc5
to
fdabdfd
Compare
dbbe6b2
to
e4c867c
Compare
@fkling I probably have until EOD to finish this up then will have to bow out for a while. I want to prioritize supported use cases versus not supported use cases. This is the known issues ordered by what I think the priority should be. What do you think?
const NotAComponent = () => {
const Component = () => React.createElement('div', null);
const AlsoAComponent = () => React.createElement('div', null);
return {Component, AlsoAComponent};
}
const bag = {
Component() { return React.createElement('div', null); },
AlsoAComponent() { return React.createElement('div', null); }
};
bag.Components.propTypes = {
/**
* Hey I'm a string
*/
foo: PropTypes.string.isRequired
}; |
Also e4c867c is of note for the |
recast.visit(ast, { | ||
visitFunctionDeclaration: statelessVisitor, | ||
visitFunctionExpression: statelessVisitor, | ||
visitProperty: statelessVisitor, |
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 don't think we need this, do we? What case does this capture which isn't captured otherwise?
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 added this as part of supporting some of the permutations also being added to gaearon/babel-plugin-react-transform; namely this format: https://github.com/gaearon/babel-plugin-react-transform/pull/34/files#diff-9d6f6ff3fa6b5b63c09190bd7a2efb95R15
Honestly I’m not sure if that is captured or handled correctly yet. 😇
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 this should be handled by the other visitors since methods are just represented as function expressions in the AST.
On Oct 7, 2015, at 12:51 PM, Dustan Kasten <[email protected]mailto:[email protected]> wrote:
In src/resolver/findAllComponentDefinitions.jshttps://github.com//pull/28#discussion_r41437429:
recast.visit(ast, {
- visitFunctionDeclaration: statelessVisitor,
- visitFunctionExpression: statelessVisitor,
- visitProperty: statelessVisitor,
I added this as part of supporting some of the permutations also being added to gaearon/babel-plugin-react-transform; namely this format: https://github.com/gaearon/babel-plugin-react-transform/pull/34/files#diff-9d6f6ff3fa6b5b63c09190bd7a2efb95R15
Honestly I’m not sure if that is captured or handled correctly yet. [:innocent:]
—
Reply to this email directly or view it on GitHubhttps://github.com//pull/28/files#r41437429.
This is pretty awesome work, thank you so much! Regarding priorities:
Definitely that one. Not sure how tricky this one is though.
I wonder what the use cases are for doing something like this. This would be covered by finding all component definitions anyway though, wouldn't it? I was wondering about this following case: function Foo() {
return <div />;
}
Object.assign(Foo, {
displayName: 'Foo',
propTypes: { ... }
}); Do you think that would be a common use case? Or are |
I have a couple ideas to try. The first is just to filter on all ReturnStatement’s in the body of a possible stateless component and detect if it’s a ReactElement. Though I’m not certain if these will be considered valid components off-hand. +@spicyj if I need to account for those. I don’t think they are at current.. const IsThisAComponent => null;
const IsThisAComponent => 'string';
const IsThisAComponent => 7;
const IsThisAComponent => [<div key="first" />, <div key="last" />]; The weakness is this will definitely fail for the following permutations: const Component(props) {
if (Math.random() > 0.5) return <div />;
else return <span />;
}
const Component(props) {
return (Math.random() > 0.5) ? <div /> : <span />;
} Alternatively I could do some scope crawling and ensure that var HOC = (options) => {
const Component = (props) => <div />
Component.propTypes = {};
return {Component};
} Sadly, I think that to be totally accurate we’ll need to track all possible return values and ensure each is a const truthyFunc = () => <div />;
const falsyFunc = () => <span />;
const Component = (props) => props ? truthyFunc() : falsyFunc(); |
@iamdustan Those aren't currently valid components but will be (at least the one with |
thanks @spicyj. I’ll punt that until 0.15 :) |
In the worst case we can also introduce a doclet, like |
@fkling this now supports a pretty wide array of use cases. It’s not quite as bullet proof as I’d like, but this is all I have within me for now. Let me know what you think and if you want to block on anything. |
}; | ||
return helpers.comp(); | ||
}; | ||
`; |
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 function for this isn’t quite recursive enough. While all of these resolve as expected, the following two cases do not resolve correctly yet:
const ComponentG = function(props) {
var nested = {
helpers: {
comp() { return <div>{props.children}</div>; }
}
};
return nested.helpers.comp();
};
const ComponentF = function(props) {
var other = () => <div>{props.children}</div>;
var helpers = {
comp: other
};
return helpers.comp();
};
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 works like a champ.
@fkling I’m good here unless you come up with additional code examples that fail. A thorough review to undo the cobwebs I’ve created over the past week could likely help clean up the code in here. |
@iamdustan: I'll try to get to it tomorrow or Wednesday! |
Another thought I had while adding so many use cases was: do we even want to support all the possible ways to do stateless components for this? The idea of these stateless function components is for simplicity. Documenting only a subset of these could help encourage folks to keep things simple. |
Yep, good point. My answer to this is: no. We probably already don't support every possible way to create a React component and that's OK IMO. Not sure I can get to it this week (will be away tomorrow for the rest of the week) and there is another issue I'd like to address before releasing a new major release (and I think this should be a major release because it could pickup components that haven't been considered as such). |
👍 can’t slow me down! https://github.com/iamdustan/react-docgen/tree/stateless-components-dist :) |
@@ -34,7 +35,18 @@ export default function findAllReactCreateClassCalls( | |||
return false; | |||
} | |||
|
|||
function statelessVisitor(path) { | |||
if (isStatelessComponent(path)) { | |||
// TODO: normalizeStatelessDefinition to pick up propTypes |
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 done right? (because we use a different way to resolve this 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.
Why yes I do believe it is!
👍 |
Friendly ping for review. |
Sorry, I thought I would be able to get something else in, but don't really have much time. I'm not so sure anymore this requires a major version bump. In the worst case it just finds more components. A minor version bumpv should suffice an which case I can get this in now. What do you think? |
I’ve been using it without issues since the PR was opened so it feels generally safe to me. It shouldn’t cause any regressions on |
Woohoo! Made it. Thanks! |
Starts support for Stateless Component detection and documentation. Currently this adds an
isStatelessComponent
util method. I’ll start working on working this in to the resolvers now, but wanted early review on this to see if I missed anything.