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

refactor: migrate to Preact X #4156

Merged
merged 152 commits into from Oct 8, 2019
Merged

refactor: migrate to Preact X #4156

merged 152 commits into from Oct 8, 2019

Conversation

francoischalifour
Copy link
Member

@francoischalifour francoischalifour commented Oct 4, 2019

This updates InstantSearch.js to Preact X. This version was released as stable and will generate a lighter bundle and a more stable development environment. The challenging part of this migration was the way we tested some components.

This history is quite messy because I started this work a few months ago and we rebased the base branch in the meantime. Rebasing is now quite hard. I can spend some time rebasing later but not sure it's worth the trouble for now.

Why

We want to migrate to Preact X for the following reasons:

  • Recent and future-proof React public API
    • Fragment support: some component could not follow the InstantSearch.css spec because Fragments were not supported: additional divs were added.
  • Smaller bundle size
  • Less confusion in our setup (we used to use Preact components but test them with React)

Changes

unmountComponentAtNode is not exported by Preact anymore

We now rely on rendering null (which is the internal implementation in Preact) because unmountComponentAtNode is part of preact/compat which we don't need anymore.

onChange becomes onInput on input elements

This makes it hard to test with Enzyme. Besides Enzyme's mount doesn't mount children (it shallow-render them). Therefore I introduced preact-testing-library which correctly trigger DOM events that can be caught up in tests.

React is fully removed from the codebase

We used to depend on Preact for our components but React in the testing environment, which led to inconsistent results. We now use Preact everywhere and use the h pragma. This should make the setup easier and more consistent.

Preact Rheostat dependency is gone

Having Preact Rheostat as a dependency caused some troubles: hard to remove its proptypes in production, depends on other versions of Preact, unmaintained source. Besides, the version we had wasn't compatible with Preact X (it used string refs). The Rheostat component is now part of InstantSearch.js code and will be able to evolve with time. With this change, we're also in control of what goes into our library (I removed some unused part).

Snapshot format has changed

The mount function of Enzyme with the enzyme-adapter-preact-pure adapter now always returns arrays. Component snapshots have all been updated. Some were migrated using the snapshot version of preact-testing-library (we might want to use this version for new component tests).

Snapshot to Preact's render function have changed

We used to snapshot the whole call to the Preact render function but now only snapshot the props object. It'd be nice at some point to get rid of these snapshots.

Less usage of shouldComponentUpdate

shouldComponentUpdate acts differently in Preact X and cause issues where used like this:

shouldComponentUpdate(nextProps) {
  return !isEqual(this.props, nextProps);
}

I removed cases where it was used this way. The performance gains were minimal if not inexistent.

Remove usage of componentWillMount

componentWillMount is a deprecated lifecycle hook that was trivial to remove.

Browser support

Preact X supports IE11, like InstantSearch.js.

Skipped tests

Some tests don't work with the current setup and are skipped. I suspect this is because of underlying libraries not being updated yet. If we don't plan to touch the SearchBox component, we can wait for libraries to be updated and keep these skipped tests for now.

SearchBox autofocus

The test environment doesn't reflect the autofocus prop like it does in production.

SearchBox reset

The reset event doesn't seem to be caught by the test setup.

How to review

A lot of snapshots were updated so there are a lot of files to check. Reviewing components' behavior on the Storybook and on the examples will help make sure that everything behaves as expected.

Next steps

  • Migrate away from deprecated React lifecycles

Preview

Related

References

samouss and others added 30 commits May 22, 2019 09:21
* refactor(connectSearchBox): get rid of doSearch

* refactor(connectSearchBox): get rid of previousQuery

* refactor(connectSearchBox): avoid useless IIFE

* refactor(connectVoiceSearch): avoid useless IIFE

* refactor(connectVoiceSearch): get rid of previousQuery
* refactor(helper): replace mutateMe usage [PART-1] (#3792)

* refactor(helper): replace getQueryParameter usage [PART-2] (#3793)

* refactor(helper): update tests [PART-3] (#3794)

* refactor(helper): update listeners [PART-4] (#3796)

* refactor(helper): update page usage [PART-5] (#3797)

* refactor(helper): update query usage [PART-6] (#3804)
* test(connectHitsPerPage): calls the unmount function

* test(connectHitsPerPage): rename jsHelper -> algoliasearchHelper

* test(connectHitsPerPage): consistent names for render/unmount function

* feat(connectHitsPerPage): remove hitsPerPage on dispose

* feat(connectHitsPerpage): do not throw without unmount function
* test(connectAutocomplete): call unmount on dispose

* test(connectAutocomplete): detach the DerivedHelpers on dispose

* test(connectAutocomplete): rename jsHelper -> algoliasearchHelper

* feat(connectAutocomplete): remove query on dispose

* test(connectAutocomplete): remove only DerivedHelper created by autocomplete

* feat(connectAutocomplete): do not throw without unmount function

* feat(connectAutocomplete): remove TAG_PLACEHOLDER on dispose

* feat(connectAutocomplete): remove TAG_PLACEHOLDER on dispose only with `escapeHTML`

* test(connectAutocomplete): update test description
* test(connectHits): calls the unmount function

* test(connectHits): rename jsHelper -> algoliasearchHelper

* test(connectHits): consistent names for render/unmount function

* feat(connectHits): remove TAG_PLACEHOLDER on dispose

* feat(connectHits): do not throw without unmount function

* feat(connectHits): remove TAG_PLACEHOLDER only with `escapeHTML`

* test(connectHits): update test description
* test(connectInfiniteHits): calls the unmount function

* test(connectInfiniteHits): rename jsHelper -> algoliasearchHelper

* test(connectInfiniteHits): consistent names for render/unmount function

* feat(connectInfiniteHits): remove TAG_PLACEHOLDER on dispose

* feat(connectInfiniteHits): remove page on dispose

* test(connectInfiniteHits): do not throw without unmount function

* feat(connectInfiniteHits): remove TAG_PLACEHOLDER only with `escapeHTML`

* test(connectInfiniteHits): update test description
* test(connectPagination): calls the unmount function

* test(connectPagination): consistent names for render/unmount function

* test(connectPagination): rename jsHelper -> algoliasearchHelper

* feat(connectPagination): use noop for unmount

* feat(connectPagination): remove page on dispose
* test(connectSearchBox): calls the unmount function

* feat(connectSearchBox): use noop for unmount

* feat(connectSearchBox): remove query on dispose

* test(connectSearchBox): rename jsHelper -> algoliasearchHelper

* test(connectSearchBox): consistent names for render/unmount function
* test(connectVoiceSearch): scope dispose tests togheter

* test(connectVoiceSearch): do not throw without unmount function

* feat(connectVoiceSearch): remove query on dispose

* test(connectVoiceSearch): rename jsHelper -> algoliasearchHelper

* test(connectVoiceSearch): inline test setup
* test(connectAutocomplete): scope getConfiguration into its own describe

* feat(connectAutocomplete): mount a default query
* test(connectInfiniteHits): scope getConfiguration in its own describe

* test(connectInfiniteHits): mount a default page
* fix(InstantSearch): templatesConfig is required

* fix(InstantSearch): insightsClient is not optional

* test(mock): use typed mock for searchClient

* feat(types): export widget options

* test(mock): use typed mock for InstantSearch & widget options
* chore(storybook): move decorators into its own folder

* chore(storybook): add withLifecycle decorator

* chore(storybook): add lifecycle stories

* fix(withLifecycle): use Widget for the return type
* feat(index): add widget [PART-1] (#3892)

* feat(index): use widget [PART-2] (#3893)

* feat(index): add story [PART-3] (#3914)
* fix(InstantSearch): cancel scheduled render

* fix(InstantSearch): cancel scheduled search

* fix(InstantSearch): cancel scheduled stalled render

* test(utils): create controlled search client
* fix(index): prevent render without results

* refactor(InstantSearch): search stalled false by default

* docs: comments wording

Co-Authored-By: François Chalifour <francoischalifour@users.noreply.github.com>
* feat(defer): implement waitable callback

* fix(defer): recover from callback error

* test(defer): remove useless wait test
@francoischalifour francoischalifour requested a review from a team October 4, 2019 14:15
@ghost ghost requested review from tkrugg and yannickcr and removed request for a team October 4, 2019 14:15
@Haroenv
Copy link
Contributor

Haroenv commented Oct 4, 2019

You can update the dev bundle size, it's 10kb smaller than the limit

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.

Looks like this is a great change! Thanks 🙏

questions I couldn't tag:

  • why is src/types/utils.ts listed when it has no changes?

@francoischalifour
Copy link
Member Author

@Haroenv Good call about the utils.ts file, it an unused file that comes from a merge issue.

Copy link
Contributor

@yannickcr yannickcr left a comment

Choose a reason for hiding this comment

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

LGTM + @Haroenv comments.

This is a great migration 🚀
It is very nice to remove React from the tests 🙂

But it's a bit confusing to have some tests with enzyme and some tests with testing-library. What would be the cost of a future migration to only one solution for DOM testing?

@francoischalifour
Copy link
Member Author

@yannickcr I didn't want to migrate the files that didn't need to be updated but we should indeed at some point get rid of one solution (likely Enzyme). From my experience it's way easier to write tests with the Testing Library, except for the few problems that I mentioned and that I'm gonna write issues about on their respective projects.

@Haroenv @yannickcr Are you guys confident enough to include this in the first v4 release? (i.e. merging now)

@yannickcr
Copy link
Contributor

@francoischalifour Yes, my intent was not to migrate in the other files right now (this PR is already big 😋) but more about thinking for the future. I agree for Testing Library vs Enzyme 😇.

We are currently at 4.0.0-beta.1 so I suppose this can still be added to the release. I'm relatively confident but I would be more at ease with at least a beta and an RC before the final release.

@Haroenv
Copy link
Contributor

Haroenv commented Oct 7, 2019

I'm confident enough too once all comments have been solved

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.

Let's open an issue for the onReset test

@francoischalifour francoischalifour merged commit ef4e723 into next Oct 8, 2019
@francoischalifour francoischalifour deleted the chore/preact-x-next branch October 8, 2019 08:29
Haroenv pushed a commit that referenced this pull request Oct 23, 2019
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

4 participants