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

📃 RFC: Dot notation components #700

Open
thejackshelton opened this issue Apr 12, 2024 · 11 comments
Open

📃 RFC: Dot notation components #700

thejackshelton opened this issue Apr 12, 2024 · 11 comments

Comments

@thejackshelton
Copy link
Contributor

thejackshelton commented Apr 12, 2024

Summary

Most headless ui libraries with a component based approach will use a dot notation component, also known as a namespaced component.

The current Qwik UI API does not use namespaced components. This RFC intends to discuss whether we should:

  • Adopt namespaced components
  • Deprecate the previous syntax, keep the current syntax, or support both syntaxes

Example

import { Collapsible } from '@qwik-ui/headless'; 

<Collapsible.Root>
        <Collapsible.Trigger>Collapsible Trigger</Collapsible.Trigger>
        <Collapsible.Content>Content</Collapsible.Content>
</Collapsible.Root>

Our current syntax is something like:

import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@qwik-ui/headless'; 

<Collapsible>
   <CollapsibleTrigger>Collapsible Trigger</CollapsibleTrigger>
   <CollapsibleContent>Content</CollapsibleContent>
</Collapsible>

Motivation

Namespaced components offer a couple advantages:

  • Easy to import
import { Component } from '@qwik-ui/headless'
  • Typescript Autocompletion
image
  • More legible. I believe that <Modal.Description /> is easier to follow than <ModalDescription />

Drawbacks

  • There are potential tree shaking issues involved.

It would appear that this syntax does in-fact properly tree shake. It also does not wake up the framework or any other shenanigans from first glance.

here is a demo of the behavior:
https://github.com/thejackshelton/qwikui-select-city

Side note: you might notice core is being executed on page load here with the select component, this is a separate bug that I have since fixed.

  • The current components API would change.

We would have to change the API of each component to meet this new convention. This means a potential breaking change. Because Qwik UI is still pre 1.0, an API change here could be warranted.

Migration Strategy

A potential migration strategy could be exporting both our current syntax, and the new one:

export * from "./select-inline";
export { Select as Root } from './select-inline'

Then the consumer could choose to use either <Select /> or <Select.Root />

I'm not sure this is worth the effort, as it leads to potential tree shaking issues.

FAQs

So how do you get a namespaced component?

Having a lower level barrel file, for example:

in the component file

export { SelectTrigger as Trigger } from './select-trigger';

in the library export

export * as Select from '@components/select'

This becomes:

<Select.Trigger />
@GregOnNet
Copy link
Contributor

Hey,

we discussed this notation while creating the Modal component.

Back that time TreeShaking was the main reason why we switched from Dot-notation to the current version.

Another argument was that Dot-notation might make the developer that the provided components cannot be replaced with own implementations.

Nevertheless Dot-notation is easier to understand.
You get auto-completion.
There is one single API entry-point.

Question

  • Is Tree-shaking really a problem
  • From my understanding more is can end up in the compiled bundle, but does this mean it is also loaded by the client? In Qwik we have these cool Code-Splitting mechanisms.... Maybe there is more in the bundle, but does this mean that all this code is also loaded? 🤔

I am a big fan of the Dot-notation.

@cwoolum
Copy link
Contributor

cwoolum commented Apr 13, 2024

I think the dot notation makes discovery much better. I really like the idea of supporting both syntaxes so more advanced users can only import the components they need for the most finely tuned bundle.

@thejackshelton
Copy link
Contributor Author

Hey,

we discussed this notation while creating the Modal component.

Back that time TreeShaking was the main reason why we switched from Dot-notation to the current version.

Another argument was that Dot-notation might make the developer that the provided components cannot be replaced with own implementations.

Nevertheless Dot-notation is easier to understand. You get auto-completion. There is one single API entry-point.

Question

  • Is Tree-shaking really a problem
  • From my understanding more is can end up in the compiled bundle, but does this mean it is also loaded by the client? In Qwik we have these cool Code-Splitting mechanisms.... Maybe there is more in the bundle, but does this mean that all this code is also loaded? 🤔

I am a big fan of the Dot-notation.

Yeah, so I don't think it's a problem for us. I tested via a new Qwik project, and it seemed to tree shake out each of the "parts" just great.

Qwik's code splitting seems to do the trick here. It also did not execute those parts until interaction.

@thejackshelton
Copy link
Contributor Author

thejackshelton commented Apr 13, 2024

I think the dot notation makes discovery much better. I really like the idea of supporting both syntaxes so more advanced users can only import the components they need for the most finely tuned bundle.

Yeah it wouldn't be a breaking change at that point either. I guess the tradeoff here would be double the exports 😬 . Not sure if we'd run into any bundle problems there or not.

But @cwoolum I believe both solutions give you a fine-grained bundle since it properly tree shakes each component. We are exporting each exact piece for example:
export { SelectRoot as Root } from './select'

and then export * as Select from './components/select'

The current export is:

in the component
export * from './select'

in the lib
export * from './components/select'

@maiieul
Copy link
Contributor

maiieul commented Apr 13, 2024

Edit: added one pro from @wmertens on Discord

Here are my thoughts 👇

Pros:

  • Smaller import line -> import { Collapsible } from '@qwik-ui/headless'; read nicely and this will make the styled-components copy/paste code MUCH MUCH cleaner
  • (from Woot): [with the dot notation], you can effectively namespace the components, so e.g. you don't need separate SelectListItem and RadioListItem exports.

Cons:

  • Will result in a breaking change (I don't think it's a good idea to have a migration strategy for such a big change as it could induce a big maintenance burden for us... Qwik UI is early and that breaking change shouldn't hurt too many users.. If we are to do it, the sooner the better).

Neutral:

  • No more namespace issues with preserveModules (for those who don't know, preserveModules potentially solves some tree-shaking issues we've been observing in the past) if we use <Modal.Description /> for headless and <ModalDescription /> for styled.. But I'm thinking <Modal.Description /> for styled could be cool as well for the autocomplete 😅.. So yeah neutral..
  • <Modal.Description /> reads the same as <ModalDescription /> to me.
  • There's only a slightly easier discovery thanks to typescript autocomplete on <Modal.Description />. I also have autocomplete with <ModalDescription /> (when I start writing Modal, vscode suggests me all the headless modal components) -> So kind of neutral too..
  • Although I haven't detected tree-shaking issues, there might be some tree-shaking issues hidden somewhere, but at least we'll know for sure 😁.. So also neutral to me..

Conclusion:

It's two small pros for a rather small con (since breaking change at this point shouldn't be that big of a deal), but what's missing in my analysis is the feeling of the dot syntax. I believe copy/pasting a styled component that imports a headless component with the dot syntax will be slightly less scary and feel cleaner overall. Also better namespacing means easier maintenance. So I would lean towards dot syntax.

Bonus:
This article explains the tradeoffs well https://medium.com/@skovy/using-component-dot-notation-with-typescript-to-create-a-set-of-components-b0b2aad4892b

@wmertens
Copy link
Contributor

Also, you export all the helper components a component uses, even if that is duplication. That's easier for discovery and costs nothing.

I think in this case where main and helper components are so tightly coupled, this is a great syntax. 👍

@GrandSchtroumpf
Copy link

My main concern with the namespace is component reusability. For example you might want to have the same Option component for Listbox, Combobox, Select, ...
Should the user prefix the Option with the current namespace?
Is it possible to reexport the Option as part of each namespace without increasing the bundle size ?

@maiieul
Copy link
Contributor

maiieul commented Apr 16, 2024

@GrandSchtroumpf my gut tells me that it reads more nicely to have Listbox.Trigger, Combobox.Trigger, Select.Trigger even though it's the same Triggercomponent under the hood for all of them. The advantage is that you can instantly know visually that you're dealing with a Listbox sub-component and not a custom component of yours.

@thejackshelton
Copy link
Contributor Author

thejackshelton commented Apr 16, 2024

My main concern with the namespace is component reusability. For example you might want to have the same Option component for Listbox, Combobox, Select, ... Should the user prefix the Option with the current namespace? Is it possible to reexport the Option as part of each namespace without increasing the bundle size ?

Hm... if it's intended to be a reusable component, I don't believe it would be a namespaced component to begin with. For example, VisuallyHidden is going to remain a regular component.

The tricky part with something like a reusable listbox or option component is context.

However, what you are saying is how I believe React Aria does it. For example:

<Select>
  <Label>Favorite Animal</Label>
  <Button>
    <SelectValue />
    <span aria-hidden="true"></span>
  </Button>
  <Popover>
    <ListBox>
      <ListBoxItem>Aardvark</ListBoxItem>
      <ListBoxItem>Cat</ListBoxItem>
      <ListBoxItem>Dog</ListBoxItem>
      <ListBoxItem>Kangaroo</ListBoxItem>
      <ListBoxItem>Panda</ListBoxItem>
      <ListBoxItem>Snake</ListBoxItem>
    </ListBox>
  </Popover>
</Select>

This is their select component API.

Every single component is able to be reusable. I believe their value proposition though is more tailored towards using their hooks, and then their own state management library, but I am still unsure how they are able to reuse everything 😅, and what the tradeoffs of that would be.

And maybe that's the direction we should go! I am still unsure of that. We have a Qwik UI meeting tomorrow if you'd like to help us take a stab at a much more reusable approach.

@shairez shairez changed the title RFC: Dot notation components 📃 RFC: Dot notation components Apr 17, 2024
@gparlakov
Copy link
Contributor

How many components are already using the dot notation vs the non-dot-notation ? If there's 10% dot notation - would it make sense to undertake such an enormous piece of work? And vice versa - if only 10% are not - make them all dot notation...

@thejackshelton
Copy link
Contributor Author

thejackshelton commented May 13, 2024

How many components are already using the dot notation vs the non-dot-notation ? If there's 10% dot notation - would it make sense to undertake such an enormous piece of work? And vice versa - if only 10% are not - make them all dot notation...

There's some components where it would be odd to do so, like <Label />. Dot notation works really great when you have "pieces" or sub-components.

As for what you mentioned first, the library is already moved to dot notation, we will be releasing 0.4 once the styled kit has fully moved over. 🎨

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

No branches or pull requests

7 participants