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(js): change renderer implementation to virtual DOM #381

Merged
merged 16 commits into from
Jan 21, 2021

Conversation

francoischalifour
Copy link
Member

@francoischalifour francoischalifour commented Dec 15, 2020

This is a complete rework of the rendering system of Autocomplete from a DOM implementation to an agnostic virtual DOM implementation.

Problems

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

  • Performance. Until now, we relied solely on DOM manipulation to re-render the panel on each state change (note: a state change also happens on an item hover because the item becomes active).
  • Scrolling. Since hovering an item triggers a state change, we had UI flashes when the dropdown had a scroll bar because we used to reset the whole panel. This made advanced panel rendering impossible to use.
  • Debugging. We couldn't easily inspect the panel with DevTools even with the debug mode because it triggered a state change and you lost the DOM reference of the element you wanted to inspect.
  • Plugins. The rendering of our plugins was coupled with the DOM and we couldn't leverage users' framework preference.

Templates

Templates used to accept a string in return, or void. Users were allowed to trigger side effects when returning void (e.g., appending to the root element and attaching events).

We don't pass the root element anymore because it's now transparent; you need to return a VNode that contains this logic and that the library appends to its parent.

autocomplete({
  // ...
  getSources({ query }) {
    return [
      {
        // ...
        templates: {
          header({ items }) {
            // A string is a valid VNode
            return `Query Suggestions ${items.length}`;
          },
          item({ item }) {
            // A Preact component compiles to `preact.createElement`
            return <Item {...item} />;
          },
          footer({ pragma, items }) {
            // `pragma` defaults to `preact.createElement`
            return pragma('div', {
              // You can still render HTML:
              dangerouslySetInnerHTML: {
                __html: `<div>items: ${items.length}</div>`,
              },
            });
          },
        },
      },
    ];
  },
});

How do I render HTML?

We now internally manipulate virtual nodes, which isn't compatible with HTML. There are still two ways to write HTML.

Using dangerouslySetInnerHTML

pragma('div', {
  dangerouslySetInnerHTML: {
    __html: `<div>Items: ${items.length}</div>`,
  },
});

Using the htm library

The htm library allows to create HTML elements from virtual DOM.

See example →

API

We introduced two new options: pragma and pragmaFrag (names taken from @babel/plugin-transform-react-jsx). These two options default to preact.createElement and preact.Fragment.

These two pragmas are passed to the template functions so that users (and plugin authors) can leverage the pragmas passed to the Autocomplete instance, making the rendering environment-agnostic.

Impact on plugins

Until now, plugins were manipulating DOM elements, no matter the frontend framework you were using. This new element API allows plugins to be framework agnostic by using the template-provided pragma and pragmaFrag params.

Bundle size impact

Since we now embed Preact in Autocomplete JS, the gzipped UMD bundle size increased from 10KB to 13KB. This is not a great impact because we already embed Preact in InstantSearch.js. Besides, it'll allow community to build better and more interactive templates.

Future

Perhaps that this API is actually enough to unlock our React, Vue and Angular implementations. These framework-based flavors could only be syntax sugar on top of Autocomplete JS.

Next steps

  • Leverage this API to render the search box too.
  • The Autocomplete JS with Preact bindings could be exported at another endpoint so that users don't embed Preact if they don't need it.

@codesandbox-ci
Copy link

codesandbox-ci bot commented Dec 15, 2020

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 4a3d11c:

Sandbox Source
@algolia/js-example Configuration
@algolia/react-renderer-example Configuration
Autocomplete with HTM PR

@Haroenv
Copy link
Contributor

Haroenv commented Dec 15, 2020

I think pragma should be called h or createElement, wdyt?

@francoischalifour
Copy link
Member Author

@Haroenv I initially called it createElement, but when I introduced Fragment, the two were distinct enough to make me reconsider the naming. Conceptually we're close to what @babel/plugin-transform-react-jsx is doing so I figured pragma* might make more sense? I'm not too sure.

@Haroenv
Copy link
Contributor

Haroenv commented Dec 15, 2020

Those are indeed the name of the options, but not really what you interact with as a user, which is h, createElement or jsx

@francoischalifour
Copy link
Member Author

[...] but not really what you interact with as a user [...]

Yeah that makes perfect sense.

Wdyt about keeping pragma and pragmaFrag as options and to pass createElement and Fragment in the templates? Otherwise going fully with createElement and Fragment?

@eunjae-lee
Copy link
Contributor

going fully with createElement and Fragment

I'm in favor of going fully.

});
},
templates: {
item({ item }) {
Copy link
Contributor

Choose a reason for hiding this comment

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

what does an example using a provided pragma / createElement look like (without using jsx directly)?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure whether the main example should be using jsx, since it might confuse people unfamiliar with it (Shopify, magento...)

Copy link
Member Author

Choose a reason for hiding this comment

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

what does an example using a provided pragma / createElement look like (without using jsx directly)?

You can see in the plugins implementations.

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'm not sure whether the main example should be using jsx, since it might confuse people unfamiliar with it (Shopify, magento...)

The main example is used as a playground. We'll provide code samples for people relying on HTML.

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 created this sandbox to demo how you can use Autocomplete with HTML.

packages/autocomplete-js/src/getDefaultOptions.ts Outdated Show resolved Hide resolved
packages/autocomplete-js/src/getDefaultOptions.ts Outdated Show resolved Hide resolved
packages/autocomplete-js/src/types/AutocompleteOptions.ts Outdated Show resolved Hide resolved
packages/autocomplete-js/src/types/AutocompleteRenderer.ts Outdated Show resolved Hide resolved
@francoischalifour
Copy link
Member Author

francoischalifour commented Jan 11, 2021

Since 3a9d69b, the two options are renamed renderer.createElement and renderer.Fragment.

Copy link
Contributor

@eunjae-lee eunjae-lee 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 to me

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.

🙌

packages/autocomplete-js/src/getDefaultOptions.ts Outdated Show resolved Hide resolved
@francoischalifour francoischalifour merged commit 5a1efc2 into next Jan 21, 2021
@francoischalifour francoischalifour deleted the feat/vdom branch January 21, 2021 13:47
francoischalifour added a commit that referenced this pull request Jan 21, 2021
* feat(js): change renderer implementation to virtual DOM

* feat(highlighting): revamp highlighting system to VDOM (#399)
francoischalifour added a commit that referenced this pull request Feb 1, 2021
* feat(js): change renderer implementation to virtual DOM

* feat(highlighting): revamp highlighting system to VDOM (#399)
Meschreiber pushed a commit that referenced this pull request Feb 4, 2021
* docs(website): bootstrap new doc structure

* docs(website): add doc for `createAlgoliaInsightsPlugin`

* docs(website): add draft sections

* docs: rewrite "Integrating keyboard navigation" (#389)

* feat: add intro

* Apply suggestions from code review

Co-authored-by: Sarah Dayan <[email protected]>

* fix: remove conflicting sentence

* docs: rewrite "Accessing data with Context" (#390)

* docs: rewrote "Controlling behavior with State" (#391)

* fix: bold and underline text

* docs: rewrite "Populating autocomplete with Sources" (#396)

* docs: write "Displaying items with Templates" (#397)

* fix(core): update query conditions

* chore(test): add tests for getEnvironmentProps onTouchMove (#398)

* feat(emptyStates): implements empty source template and renderEmpty method (#395)

* Implements `empty` template and `renderEmpty` method

* Add wait function to `test/utils` folder

Co-authored-by: François Chalifour <[email protected]>

* Add tests for `onActive` and `onSelect` on plugins (#400)

* feat(js): change renderer implementation to virtual DOM (#381)

* feat(js): change renderer implementation to virtual DOM

* feat(highlighting): revamp highlighting system to VDOM (#399)

* fix: fix code example (#403)

* docs: switch core usage with autocomplete-js (#401)

* docs: write "Basic configuration options" (#402)

* docs: write "help and discussion" page (#394)

* docs: add help page

* Apply suggestions from code review

Co-authored-by: François Chalifour <[email protected]>

Co-authored-by: François Chalifour <[email protected]>

* feat: put in a placeholder input

* fix: update links

* fix: links

* fix: merge conflict

* fix: merge conflict

* feat: add AutocompleteExample

* feat: add AutocompleteItem

* fix: remove htm package

* fix: remove htm package

* fix: rename AutocompleteItem -> AutocompleteDocSearchItem

* fix(website): use React renderer in `AutocompleteExample`

* fix(website): support content record types

Co-authored-by: François Chalifour <[email protected]>
Co-authored-by: Sarah Dayan <[email protected]>
Co-authored-by: Yannick Croissant <[email protected]>
Co-authored-by: Clément Vannicatte <[email protected]>
Co-authored-by: François Chalifour <[email protected]>
Meschreiber pushed a commit that referenced this pull request Feb 4, 2021
* docs(website): bootstrap new doc structure

* docs(website): add doc for `createAlgoliaInsightsPlugin`

* docs(website): add draft sections

* docs: rewrite "Integrating keyboard navigation" (#389)

* docs: rewrite "Accessing data with Context" (#390)

* docs: rewrote "Controlling behavior with State" (#391)

* docs: rewrite "Populating autocomplete with Sources" (#396)

* docs: write "Displaying items with Templates" (#397)

* fix(core): update query conditions

* chore(test): add tests for getEnvironmentProps onTouchMove (#398)

* feat(emptyStates): implements empty source template and renderEmpty method (#395)

* Implements `empty` template and `renderEmpty` method

* Add wait function to `test/utils` folder

Co-authored-by: François Chalifour <[email protected]>

* Add tests for `onActive` and `onSelect` on plugins (#400)

* feat(js): change renderer implementation to virtual DOM (#381)

* feat(js): change renderer implementation to virtual DOM

* feat(highlighting): revamp highlighting system to VDOM (#399)

* fix: fix code example (#403)

* docs: switch core usage with autocomplete-js (#401)

* docs: write "Basic configuration options" (#402)

* docs: write "help and discussion" page (#394)

* docs: add help page

* Apply suggestions from code review

Co-authored-by: François Chalifour <[email protected]>

Co-authored-by: François Chalifour <[email protected]>

* feat: getting started first draft

* Apply suggestions from code review

Co-authored-by: François Chalifour <[email protected]>

* feat: update code example

* fix: copy-edit

* feat: add placeholders

* fix: remove unnecessary code snippets

* fix: links

* fix: add record example

* fix: rename titles to be more user focused

* fix: correct URL

* fix: update links

* Apply suggestions from code review

Co-authored-by: Sarah Dayan <[email protected]>

* fix: update installation headers and add note

* fix: remove headless installation and add 3rd party caveat

* Update packages/website/docs/getting-started.md

Co-authored-by: François Chalifour <[email protected]>

* fix: remove CDNJS

* Update packages/website/docs/getting-started.md

Co-authored-by: François Chalifour <[email protected]>

* Update packages/website/docs/getting-started.md

* Update packages/website/docs/getting-started.md

Co-authored-by: Sarah Dayan <[email protected]>

* feat: add AutocompleteExample

* fix: merge docs-1

Co-authored-by: François Chalifour <[email protected]>
Co-authored-by: Sarah Dayan <[email protected]>
Co-authored-by: Yannick Croissant <[email protected]>
Co-authored-by: Clément Vannicatte <[email protected]>
Co-authored-by: François Chalifour <[email protected]>
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.

3 participants