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

feat(horizontal-slider): change renderer implementation to VDOM #28

Merged
merged 32 commits into from Jun 8, 2021

Conversation

shortcuts
Copy link
Member

Follow up of the previous PR: algolia/ui-components#27

This is a rework of the rendering system of the Horizontal Slider UI components to an agnostic virtual DOM implementation.

Problems

Here's a list of the problems we have with the current architecture:

  • Bundle size. The JavaScript package embeds the complete preact/compat package which exports the whole React API. (We now only rely on preact and preact/hooks.)
  • Not future-proof. The current React/Preact aliasing is not going to allow us to bring support for Vue, React Native, and all VDOM-based framework.
  • Bridge between JavaScript packages and UI components. The bridge between UI components like Horizontal Slider and the Recommendations component is challenging because we're not in control of the view prop rendering framework.
  • Tooling. The tooling is difficult to get working because we have to alias the dependencies in all our toolchain (Parcel, Jest, Rollup, Babel).

Architecture

Current

The previous packages main entry point was react-horizontal-slider, where the logic and the rendering happened. The js-horizontal-slider package relied on react-horizontal-slider with an alias from react to preact/compat.

New

We now have these packages:

packages
├── horizontal-slider-js
├── horizontal-slider-react
├── horizontal-slider-theme
└── horizontal-slider-vdom

horizontal-slider-js

horizontal-slider-js is an implementation of horizontal-slider-vdom with Preact (similar front-end framework that we use in Autocomplete, InstantSearch and the recommendations-js package). It specifies horizontal-slider-vdom with the Preact renderer.

API

  • horizontalSlider

horizontal-slider-react

horizontal-slider-react is an implementation of horizontal-slider-vdom with React.

API

  • HorizontalSlider

horizontal-slider-theme

Package that hosts the HorizontalSlider component default theme.

horizontal-slider-vdom

horizontal-slider-vdom is an agnostic package that allows to build components given a renderer. A renderer is composed of { createElement, Fragment }. It's purely the UI part and creates uncontrolled components, lacking any state and lifecycle functionalities.

API

  • createHorizontalSliderComponent
  • generateHorizontalSliderId: the function to initialize the sliderIdRef ref with.
  • updateNavigationButtonsProps: the function to wrap in an effect, which will update the current value of the refs.

Changes

container

The container props for the recommendations-js and horizontal-slider-js packages are now optional. When undefined, we will directly return the component instead of rendering it in its container.

This allows us to:

  • Leverage the view prop of the recommendations-js components with the HorizontaSlider.
  • Render a fallbackComponent from the recommendations-js library in the same container as its parent.

Usage

frequentlyBoughtTogether({
  container: '#frequentlyBoughtTogether',
  searchClient,
  indexName,
  // ...
  fallbackComponent() {
    return relatedProducts({
      searchClient,
      indexName,
      view: horizontalSlider,
      // ...
    });
  },
});

Other packages in the future

When the time comes, we'll be able to create horizontal-slider-vue with the same strategy as horizontal-slider-js and horizontal-slider-react. We could also expose this component for React Native in the future.

Next steps

  • Deprecate the previous packages
  • Update rollup bundle config

Copy link
Member

@francoischalifour francoischalifour left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good—love the optional container pattern!

I've got a concern about the ref compatibility with a future Vue package though.

examples/js-demo/index.html Outdated Show resolved Hide resolved
packages/horizontal-slider-js/README.md Outdated Show resolved Hide resolved
packages/horizontal-slider-vdom/src/index.ts Outdated Show resolved Hide resolved
Comment on lines +1 to +3
type Ref<TElement> = {
current: TElement;
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering how that will play out with Vue because their refs are strings.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we will be in control of the Vue implementation, we could define a fallback type for it

@@ -0,0 +1,36 @@
export type Pragma = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Next component that we create we might want have a shared/common package for these types.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I can already see multiple use cases for a shared package!

packages/recommendations-js/README.md Outdated Show resolved Hide resolved
packages/recommendations-js/README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
package.json Show resolved Hide resolved
@@ -1,13 +1,13 @@
# `@algolia/ui-components-js-horizontal-slider`
# `@algolia/ui-components-horizontal-slider-js`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

much better name, alternative would have been js-ui-components- horizontal-slider, but this format works well

nextButtonRef,
previousButtonRef,
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this happen on every change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this call will set update the hidden boolean to hide/show our arrows, yes. Did you meant to add the concerned refs to the dependencies?

environment = window,
...props
}: HorizontalSliderProps<TObject> & EnvironmentProps) {
const children = <HorizontalSlider {...props} />;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be called children or component (it's only one element, maybe child)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We choosed children to keep it consistent with the return of our recommendations components

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

component also makes sense to me. Both are fine IMO.

Comment on lines +30 to +33
listRef={listRef}
nextButtonRef={nextButtonRef}
previousButtonRef={previousButtonRef}
sliderIdRef={sliderIdRef}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should be a single refs object?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably not due to the diff annoyances that causes

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this is easier to understand as single refs, what would your implementation looks like?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refs={{ nextButton: nextButtonRef, previousButton: previousButtonRef, sliderId: sliderIdRef }} or something similar

let lastHorizontalSliderId = 0;

export function generateHorizontalSliderId() {
return `uic-horizontal-slider-${lastHorizontalSliderId++}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this generate stable ids for use in server side rendering?

Copy link
Member Author

@shortcuts shortcuts Jun 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same logic as we used in autocomplete but I didn't tried it yet with SSR!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this to be SSR-safe, we need to accept an id prop in all our components. You would need to generate the id on the server and pass it to the client. (Autocomplete allows this.)

We've planed to add this later. But as it is now, it's not SSR-safe.

(Other interesting resources could be the new React's useOpaqueIdentifier.)

Comment on lines +51 to +53
<pre>
<code>{JSON.stringify(item)}</code>
</pre>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you use it without preact (document.createElement) too, or does that not make sense?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be either VDOM or HTML strings (see algolia/ui-components#27 (comment)).

Co-authored-by: Haroen Viaene <hello@haroen.me>
Base automatically changed from feat/vdom to next June 8, 2021 09:14
Copy link
Contributor

@Haroenv Haroenv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all good for me

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

Successfully merging this pull request may close these issues.

None yet

3 participants