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

Data: Build the basic data controls into every store #25362

Merged
merged 7 commits into from
Oct 1, 2020

Conversation

jsnajdr
Copy link
Member

@jsnajdr jsnajdr commented Sep 16, 2020

In the @wordpress/block-editor package, replaces the home-grown SELECT control with SYNC_SELECT from @wordpress/data-controls.

There is one thing in this PR I don't like very much: the @wordpress/block-editor becomes indirectly dependent on @wordpress/api-fetch, although it never uses that code. The controls object exported from @wordpress/data-controls contains the API_FETCH field. Although we don't use it, it's there, and it's not even possible to tree-shake it away. It's a field of an exported object, not a standalone export.

How bad is that?

@jsnajdr jsnajdr added [Type] Code Quality Issues or PRs that relate to code quality [Package] Block editor /packages/block-editor labels Sep 16, 2020
@jsnajdr jsnajdr self-assigned this Sep 16, 2020
@github-actions
Copy link

github-actions bot commented Sep 16, 2020

Size Change: -580 B (0%)

Total Size: 1.18 MB

Filename Size Change
build/block-directory/index.js 8.55 kB -57 B (0%)
build/block-editor/index.js 129 kB -112 B (0%)
build/block-library/index.js 135 kB -15 B (0%)
build/block-serialization-default-parser/index.js 1.78 kB +1 B
build/blocks/index.js 47.5 kB +1 B
build/components/index.js 169 kB -7 B (0%)
build/compose/index.js 9.42 kB +2 B (0%)
build/data-controls/index.js 685 B -585 B (85%) 🏆
build/data/index.js 8.6 kB +189 B (2%)
build/edit-site/index.js 20.4 kB -1 B
build/edit-widgets/index.js 21.1 kB +3 B (0%)
build/editor/index.js 45.5 kB +2 B (0%)
build/element/index.js 4.44 kB +1 B
build/escape-html/index.js 734 B +1 B
build/format-library/index.js 7.49 kB -3 B (0%)
build/is-shallow-equal/index.js 710 B +1 B
build/media-utils/index.js 5.12 kB -1 B
build/plugins/index.js 2.44 kB -1 B
build/priority-queue/index.js 790 B +1 B
build/server-side-render/index.js 2.6 kB -2 B (0%)
build/shortcode/index.js 1.7 kB +1 B
build/url/index.js 4.06 kB +2 B (0%)
build/warning/index.js 1.13 kB -1 B
ℹ️ View Unchanged
Filename Size Change
build/a11y/index.js 1.14 kB 0 B
build/annotations/index.js 3.52 kB 0 B
build/api-fetch/index.js 3.35 kB 0 B
build/autop/index.js 2.72 kB 0 B
build/blob/index.js 620 B 0 B
build/block-directory/style-rtl.css 943 B 0 B
build/block-directory/style.css 942 B 0 B
build/block-editor/style-rtl.css 11 kB 0 B
build/block-editor/style.css 11 kB 0 B
build/block-library/editor-rtl.css 8.6 kB 0 B
build/block-library/editor.css 8.6 kB 0 B
build/block-library/style-rtl.css 7.65 kB 0 B
build/block-library/style.css 7.65 kB 0 B
build/block-library/theme-rtl.css 741 B 0 B
build/block-library/theme.css 741 B 0 B
build/block-serialization-spec-parser/index.js 3.1 kB 0 B
build/components/style-rtl.css 15.4 kB 0 B
build/components/style.css 15.4 kB 0 B
build/core-data/index.js 12 kB 0 B
build/date/index.js 31.9 kB 0 B
build/deprecated/index.js 772 B 0 B
build/dom-ready/index.js 568 B 0 B
build/dom/index.js 4.42 kB 0 B
build/edit-navigation/index.js 10.7 kB 0 B
build/edit-navigation/style-rtl.css 868 B 0 B
build/edit-navigation/style.css 871 B 0 B
build/edit-post/index.js 306 kB 0 B
build/edit-post/style-rtl.css 6.25 kB 0 B
build/edit-post/style.css 6.24 kB 0 B
build/edit-site/style-rtl.css 3.78 kB 0 B
build/edit-site/style.css 3.78 kB 0 B
build/edit-widgets/style-rtl.css 3 kB 0 B
build/edit-widgets/style.css 3 kB 0 B
build/editor/editor-styles-rtl.css 492 B 0 B
build/editor/editor-styles.css 493 B 0 B
build/editor/style-rtl.css 3.83 kB 0 B
build/editor/style.css 3.82 kB 0 B
build/format-library/style-rtl.css 547 B 0 B
build/format-library/style.css 548 B 0 B
build/hooks/index.js 1.74 kB 0 B
build/html-entities/index.js 621 B 0 B
build/i18n/index.js 3.54 kB 0 B
build/keyboard-shortcuts/index.js 2.39 kB 0 B
build/keycodes/index.js 1.85 kB 0 B
build/list-reusable-blocks/index.js 3.02 kB 0 B
build/list-reusable-blocks/style-rtl.css 476 B 0 B
build/list-reusable-blocks/style.css 476 B 0 B
build/notices/index.js 1.69 kB 0 B
build/nux/index.js 3.27 kB 0 B
build/nux/style-rtl.css 671 B 0 B
build/nux/style.css 668 B 0 B
build/primitives/index.js 1.34 kB 0 B
build/redux-routine/index.js 2.85 kB 0 B
build/rich-text/index.js 13 kB 0 B
build/token-list/index.js 1.24 kB 0 B
build/viewport/index.js 1.74 kB 0 B
build/wordcount/index.js 1.17 kB 0 B

compressed-size-action

@youknowriad
Copy link
Contributor

i'm not sure about this PR tbh, I think the "syncSelect" control is small enough that duplication is probably better than having the ap-fetch dependency there.

@jsnajdr
Copy link
Member Author

jsnajdr commented Sep 16, 2020

i'm not sure about this PR tbh, I think the "syncSelect" control is small enough that duplication is probably better than having the ap-fetch dependency there.

I tend to agree that the pros don't outweight the cons here. It shows, though, that there might be something slightly wrong with the data-controls package. Maybe it's putting together things that don't belong together?

The select/dispatch controls are so close to the data stores so that they could be part of the data package itself. And I would go even further: make them built-in in each store by default 🙂 Just like rungen has plenty of builtin controls like all or fork and then the user can register additional ones.

@youknowriad
Copy link
Contributor

I tend to agree that the pros don't outweight the cons here. It shows, though, that there might be something slightly wrong with the data-controls package. Maybe it's putting together things that don't belong together?

The select/dispatch controls are so close to the data stores so that they could be part of the data package itself. And I would go even further: make them built-in in each store by default

I agree here 💯

cc @nerrad

@nerrad
Copy link
Contributor

nerrad commented Sep 16, 2020

Sounds very similar to the evolution of resolvers moving into the wp.data package. I'm not opposed to this being built-in to wp.data by default. Built-in controls would also get rid of the gotcha folks encounter with not defining a controls object when registering a store and that resulting in resolvers not working.

Would we deprecate the data-controls package all-together?

@jsnajdr
Copy link
Member Author

jsnajdr commented Sep 21, 2020

Would we deprecate the data-controls package all-together?

That's one of the options: it would be deprecated, and implemented as a thin wrapper around the builtin data controls.

Of course, we'd need to find a new home for the apiFetch control. Maybe the api-fetch package itself? The control code is self-contained without any dependencies: a control creator that returns a { type: 'API_FETCH' } object and a handler that executes apiFetch.

We could also fix the unfortunate naming of the select control discussed in #25235, namely making the select control do the sync select rather than resolveSelect.

There's one thing that's not great about moving the controls to the @wordpress/data package: how are we going to export them? The select and dispatch exports are already taken by the defaultRegisty.select and defaultRegistry.dispatch bindings. We'd need to find some less-than-optimal names for them.

This is where the @wordpress/data-controls package provides value: it makes the imports ergonomic:

// import controls with nice names from dedicated package
import { select, dispatch } from '@wordpress/data-controls';

// and use them in my control
export function *myControl() {
  yield select( 'core', 'foo' );
  yield dispatch( 'core', bar() );
}

@youknowriad
Copy link
Contributor

Maybe we can export theme as "controls" variable, it's not tree-shakable but we have just three right?

We intend to register some controls created by `createRegistryControl` as
built-ins with every store. In order to do that, we need to break a dependency
cycle where registry creation depends on store registration which depends
on creating controls which depends on default registry.

The solution is to remove the `defaultRegistry` binding, which is there
only to satisfy a typechecker anyway, and doesn't have any runtime impact.
Instead of shipping them in a separate `data-controls` package and require
the store author to register them, build them in into every store.
The implementation and the unit test have been moved to the `@wordpress/data`
package. The `data-controls` package now exposes only legacy aliases.
…rols

Updates unit tests of actions (in `block-directory`, `edit-site` and `core-data`)
that inspect the internal properties (like `type`) of controls that the action
generator yields.

A test that verifies if the action _behaves_ correctly wouldn't need to be changed
like that.
@jsnajdr jsnajdr force-pushed the use/data-controls-in-block-editor branch from 66cf456 to c2afe8b Compare September 24, 2020 13:26
@jsnajdr jsnajdr changed the title Block Editor: use syncSelect control from data-controls instead of a package-local impl Data: build the basic data controls into every store Sep 24, 2020
@jsnajdr jsnajdr changed the title Data: build the basic data controls into every store Data: Build the basic data controls into every store Sep 24, 2020
@jsnajdr jsnajdr added [Package] Data /packages/data [Package] Data Controls /packages/data-controls labels Sep 24, 2020
@jsnajdr
Copy link
Member Author

jsnajdr commented Sep 24, 2020

I completely reworked this PR by moving the data controls (select, resolveSelect, dispatch) into the base data package and registering them by default with every store.

I also wrote a brand new unit test for the controls. I didn't like the original data-controls unit tests. In my view, they focused too much on internals (like working with selector.hasResolver flags) and were too far from how a real-world usage would look like. The new tests register some small example stores with controls, and verify the desired behavior by dispatching actions and calling selectors and checking if the results are as expected. There is zero mocking of anything.

The data-controls package now only exports aliases for backward compatibility.

The last commit migrates the block-editor package, just like the original PR, but this time without the unwanted dependency on api-fetch.

args,
};
}
export const dispatch = dataControls.dispatch;
Copy link
Contributor

Choose a reason for hiding this comment

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

do you think we should deprecate these?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, deprecation would discourage folks from adding new usages of data-controls. I guess the data-controls wrappers should use the @wordpress/deprecated helper?

Copy link
Member Author

Choose a reason for hiding this comment

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

@youknowriad I updated the data-controls wrappers to warn about deprecation using the @wordpress/deprecated package, in 51f7fc5.

But now I have problems with unit tests that I don't know how to solve: every unit tests uses the jest-console plugin that checks that the code didn't produce any console messages. But the deprecated() function logs the deprecation messages with console.warn(), so many unit tests for code that still uses the deprecated controls now fail.

Is there any established way how to get out of this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, we have some asserters to aknowledge that warnings will be thrown. expect( console ).toHaveWarned();

That said, when we introduce deprecations, we do it after refactoring the Core usage of this API. Do we still have existing places where these APIs are used on Gutenberg?

Copy link
Member Author

Choose a reason for hiding this comment

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

Do we still have existing places where these APIs are used on Gutenberg?

Yes, the data-controls are still used in many stores. It will take some 5+ PRs to migrate them one by one.

At this moment, it seems to me the best option is to mark the data-controls as deprecated only after that migration is finished. It shouldn't take long after this PR is merged.

Copy link
Contributor

Choose a reason for hiding this comment

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

it seems to me the best option is to mark the data-controls as deprecated only after that migration is finished. It shouldn't take long after this PR is merged.

👍

*
* @type {WPDataRegistry}
*/
selector.registry = defaultRegistry;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this some useless leftover?

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 described the motivation for this in the commit's commit message:

We intend to register some controls created by createRegistryControl as built-ins with every store. In order to do that, we need to break a dependency cycle where registry creation depends on store registration which depends on creating controls which depends on default registry.

The solution is to remove the defaultRegistry binding, which is there only to satisfy a typechecker anyway, and doesn't have any runtime impact.

Another solution would be to break up the factory.js module and put createRegistrySelector and createRegistryControl into separate modules. createRegistrySelector has the offending defaultRegistry dependency, while createRegistryControl is needed to create a store and therefore introduces a cyclic dependency.

However, to make the API really type-safe, we'd need to rethink how we create the selectors and introduce types like UnregisteredRegistrySelector and RegisteredRegistrySelector. Right now, the usage is like:

const selector = createRegistrySelector( ( select ) => ( state ) => { select( 'core' ) /* ... */ } );

// at this moment the selector is in a weird state where it's either unbound or bound to `defaultRegistry`
// it's not entirely certain whether the following call should crash or not and what `value` is supposed to be
const value = selector( state ); 

// only after this registration is the `selector` registered and has 100% defined behavior
customRegistry.registerStore( 'mystore', { reducer, selectors:  { selector } } );

// this second call can have an entirely different result from the first one, although the `selector`
// value is still the same and has the same type. The `selector` properties have been mutated during
// `registerStore`.
const value = selector( state );

I.e., the code is not safe and I don't immediately see how introducing types could prevent the unsafe usages.

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 i understand everything here but I agree that depending on the default registry should be avoided anyway. The default registry only exists because it was there first. If we'd start over, we'd probably avoid it entirely.

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 i understand everything here but I agree that depending on the default registry should be avoided anyway.

The tl;dr is that the default registry reference started introducing dependency cycles 🙂

Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 1, 2020

Thanks for the review @youknowriad 👍 I'll check if unit tests are indeed green after the deprecations have been commented out, and then will merge the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] Block editor /packages/block-editor [Package] Data Controls /packages/data-controls [Package] Data /packages/data [Type] Code Quality Issues or PRs that relate to code quality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants