-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Proposal: Name components and Props once #1959
Comments
I’m with you. The components are exported for consumption as named components, so it doesn’t make sense to have a bunch default exporting of them outside of components/index.ts. There are a bunch of shared names of subcomponents like Item, but as long as those remain statics on the base component it shouldn’t be an issue. |
I've been wondering about the best convention for exporting components within a library context (such as // src/components/Button/Button.tsx
export function Button () {}
export namespace Button {
export interface Props {}
}
// src/components/Button/index.ts
export {Button} from './Button';
// src/components/index.ts
export {Button} from './Button';
// src/components/ResourceList/ResourceList.tsx
import {Button} from '../Button'; There are rumors about the I'm very curious to hear your thoughts on this convention since your experiences in component library development would be much greater than my own. |
I am unfamiliar with namespaces. A cursory search suggests that they were born from a world prior to es modules where global variables were common, given this comment by Dan, the product manager for TS:
The ability to export a single |
Thanks for the response @BPScott, appreciate your take on it. If you're ever in Melbourne let me buy you a coffee! ☕️ |
Two other thoughts that make me lean towards exports over namespaces: There's currently an issue where eslint reports the use of a namespace as a redeclaration linting error: import React from 'react';
export declare namespace Button {
export interface Props {
x: string;
}
}
// The below line has an eslint error saying `'Button' is already defined (no-redeclare)`
export function Button(props: Button.Props) {
return <React.Fragment>{props.x}</React.Fragment>;
} For us, changing to this method of exporting props would be a breaking change for us as currently we export For the sake of completeness there is a 3rd approach - React's types exposes // Button.tsx
import React from 'react';
interface Props {
x: string;
}
export function Button(props: Props) {
return <p>props.x</p>;
}
// Some file that consumes Button
import React from 'react';
import {Button} from './Button';
// this type is equal to the Props defined in button
type ButtonProps = React.ComponentPropsWithoutRef<typeof Button>; |
I hadn't seen export class Button extends React.Component<Button.Props, Button.State> {}
export namespace Button {
export enum Type {
NORMAL = "normal",
DESTRUCTIVE = "destructive",
}
export interface Props {
type: Button.Type;
}
export interface State {}
}
const jsx = (
<Button type={Button.Type.DESTRUCTIVE} />
); However I understand your reasoning and think I'll end up joining the |
I’m in favor of this 👍 |
The way we're doing it right now does feel a bit convoluted, and I agree with your changes, but I think we can go further. I wonder if we're not just creating these problems for ourselves by using index files for every component. The issues you're pointing out seem especially bad because we're exporting everything twice. I think we can skip that step and just have a single index file for all components. For example, if we did something like this: // ./components/Button/Button.tsx
export Button;
export ButtonProps;
// ./components/index.ts
export {Button, ButtonProps} from './Button/Button.tsx'; we'd have something simpler than what we have right now, and just as functional. |
Killing index files would help avoid some indirection but they might be tougher to remove from a social perspective. The shopify convention is that index files are the public API of a component. The strict-component boundaries linting rule helps enforce this by complaining if you try and do Removing it would mean that the I'd be open to the idea of keeping the Component index files (e.g. Tangentially related to all this I've been playing with "What if we could use babel for our build" and currently the babel and rollup combo doesn't like it when we reexport Props (as babel acts on a per-file JS basis and it can't find exports of emit-less values). So killing off explict reexports of all that stuff and moving to exporting We could say individual components will export *, but |
The social reasons you mention don’t seem like strong reasons to me: if you don’t want something to be public, then don’t export it in the first place. I can be convinced otherwise if you showed me a good use case, like exporting something for tests that can't be tested otherwise. The webpack reason is solid though. This isn't an urgent problem or anything, just something I thought to question based on a few conversations I've had with the web foundations team. I'm ok with it if we keep it as is. |
Chatted with @amrocha, we're gonna punt on changing to use |
Closed by #2058 |
Default exports are low-key annoying as their name is "default" so every time you import them you have to give them a name. Instead of naming something once and using it consistently everywhere (and having TS shout at us if we misname it) we have to make sure we use the same name manually in every file (JS doesn't care but its would be confusing for us to have two names for the same concept).
I would like to propose that we stop using default exports and instead use named exports for everything. Additionally we should name
Props
based upon the component to avoid needing to rename props when used in other components to avoid naming clashes. This would save on export renaming, and would lead to simpler usage in sister components and reexporting in component indexes.We can help enforce this by enabling the
import/no-default-export
eslint rule.Further reading from someone else who came to the same conclusion: https://humanwhocodes.com/blog/2019/01/stop-using-default-exports-javascript-module/
The current world
Currently in our components we export the component as the default and its props as
Props
in bothsrc/components/ComponentName/index.js
andsrc/components/ComponentName/ComponentName.js
. We then reexport with a different name in the components index. Additionally in the cases where components are used by other components we have to rename the imports as they would conflict with existingThe new world
Use named exports and prefix the Props with the name of the component to avoid clashes (e.g. The Props in the
Button
component becomesButtonProps
).This way there is no need to rename exports when using them
Sounds neat, how would we do this?
Update the Props explorer stuff in styleguide to make it look for
ComponentNameProps
in addition toProps
when hunting for interfaces (the real fix is to infer the interface from the exported component function but that's an LOT more work)Go rename exports and Props
Enable the
import/no-default-export
eslint rule to check we haven't missed any default exports. I've got a little script 40 line script that can hunt for default exports and ones namedProps
too.Save this as test.js and run with `node test.js`
The text was updated successfully, but these errors were encountered: