qwikifiers / qwik-ui

Qwik's official Headless and styled component library
https://qwikui.com
MIT License
551 stars 113 forks source link

πŸ“ƒ RFC: Dot notation components #700

Open thejackshelton opened 3 weeks ago

thejackshelton commented 3 weeks ago

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:

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:

import { Component } from '@qwik-ui/headless'
image

Drawbacks

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.

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 commented 3 weeks ago

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

I am a big fan of the Dot-notation.

cwoolum commented 3 weeks ago

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 commented 3 weeks ago

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 commented 3 weeks ago

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 commented 3 weeks ago

Edit: added one pro from @wmertens on Discord

Here are my thoughts πŸ‘‡

Pros:

Cons:

Neutral:

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 commented 3 weeks ago

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 commented 3 weeks ago

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 commented 3 weeks ago

@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 commented 3 weeks ago

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.