Automattic / wp-calypso

The JavaScript and API powered WordPress.com
https://developer.wordpress.com
GNU General Public License v2.0
12.39k stars 1.98k forks source link

Task: Migrate wp-data stores from registerStore to createReduxStore #74399

Open noahtallen opened 1 year ago

noahtallen commented 1 year ago

Details

The main reason we should do this is that registerStore is deprecated. But the main benefit we get is the new approach to using stores gives us much better typescript integration. Previously, one would have to declare types to override the entire wp data package.

This is actually impossible to do now that the package provides its own typedefs, so we've had to rely on the temporary solution of casting to selector types like (select( 'automattic/foo' ) as FooSelectors).selector(). This is very clunky, and we also lost type info for dispatch calls. (See this comment for more info.)

As a result, we should migrate stores to use the "new" format (it's been available for a long time actually, though it wasn't supported by the WordPress versions we support at first.) And ideally, also start using stores via imports instead of via string names!

This will restore type data we lost in the upgrade, and also make types much easier to work with for WordPress data.

This is all made possible by our recent WordPress package updates done in #73890.

Checklist

A store is registered in each of these files. See PRs for example migrations.

Editing Toolkit Plugin

Calypso data-stores

Payments related

On hold:

These two persist a single option to help keep a modal close. Could probably be migrated to wp preferences.

These data stores persist several options. Since these persist much more of the store, it'd be harder to migrate them to wp preferences.

Address this after double registration issue is solved:

tyxla commented 1 year ago

Thanks for opening an issue, @noahtallen!

I and the rest of @Automattic/team-calypso-frameworks would love to aid with those.

Perhaps @sirbrillig and the rest of @Automattic/shilling folks could aid with the checkout-related ones?

sirbrillig commented 1 year ago

I'm curious: in my testing, while passing the store to useSelect does correctly provide types, passing the store to useDispatch always seems to return any. Is that expected or am I doing something wrong?

tyxla commented 1 year ago

I'm curious: in my testing, while passing the store to useSelect does correctly provide types, passing the store to useDispatch always seems to return any. Is that expected or am I doing something wrong?

This is expected, @sirbrillig, because useDispatch types were added later in https://github.com/WordPress/gutenberg/pull/43528.

We'll improve the situation with useDispatch types as soon as we upgrade to a newer version of the @wordpress packages, which is on our radar.

sirbrillig commented 1 year ago

Sounds good! I just wanted to make sure I was doing it right!

tyxla commented 1 year ago

Many of the usages of those stores are in Gutenboarding, which I found to be unused. See #74475 where I explain in detail and suggest its removal.

Landing that will make part of the work refactoring those stores easier, since some may end up unused.

noahtallen commented 1 year ago

I ran into an issue with block-editor-nux (#74454). Essentially, with the way the wp data persistence plugin works, it only works if you use registerStore and not the other methods. However, there has been a lot of work and deprecation around the plugin layer in general, so there's not a straightforward solution. Hopefully we can have a conversation about that in core here: https://github.com/WordPress/gutenberg/issues/11449

noahtallen commented 1 year ago

In this PR, there are suggestions that using the old registration approach for data stores improves tree shaking. Thoughts?

tyxla commented 1 year ago

In this PR, there are suggestions that using the old registration approach for data stores improves tree shaking. Thoughts?

I'm not concerned about this for a few reasons:

noahtallen commented 1 year ago

Nice explanation, thanks! One related thought I had is that importing anything from @automattic/data-stores probably registers every single store in the package, given that the file import in the top-level index file of that package will cause every store to register. This didn't happen before. I don't immediately have any concerns about it, though.

tyxla commented 1 year ago

Nice explanation, thanks! One related thought I had is that importing anything from @automattic/data-stores probably registers every single store in the package, given that the file import in the top-level index file of that package will cause every store to register. This didn't happen before. I don't immediately have any concerns about it, though.

That's a valid one, indeed. I don't very much like the idea of re-exporting everything through the main package anyway, especially given that we have side effects. One way to beat that would be to not use a primary index and re-export there, but rather to import from the specific stores only, using bare module specifier resolution:


import { AutomatedTransferEligibility } from '@automattic/data-stores/automated-transfer-eligibility';
jsnajdr commented 1 year ago

importing anything from @automattic/data-stores probably registers every single store in the package

The every-store-registration shouldn't be happening. The data-stores package has the sideEffects: false flag enabled, and when there are re-exports like this in data-stores/index.js:

import * as Foo from './foo';
import * as Bar from './bar';

export { Foo, Bar };

Then webpack will load ./foo only if its exports are actually imported anywhere. Reexports are "pass-through", i.e., if some useFoo.js file imports Foo:

import { Foo } from 'data-stores';

webpack will not consider the intermediate index.js file, and will treat it as "useFoo.js is importing Foo from data-stores/foo.js". Cut out the middleman!

The caveat is that the Foo store will be registered only if Foo is imported anywhere. In other words, you are kind of forced to use the new store description convention:

import { Foo } from 'data-stores';

registry.select( Foo.store ).getFoo();
noahtallen commented 1 year ago

In other words, you are kind of forced to use the new store description convention

Nice, thanks for the explanation. This side effect stuff can get tricky :)

We are migrating how these stores are used at the same time. Previously, you still needed to import Foo store and register it somewhere in the app. In most cases, the consumer also re-exported the store key and then used that to access the stores. So it's not a massive change -- just removing one or two layers of indirection