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: Use store instance as param for select and dispatch #26655

Merged
merged 13 commits into from
Nov 17, 2020

Conversation

gziolo
Copy link
Member

@gziolo gziolo commented Nov 3, 2020

Description

There are other packages that register a data store and provide various services for the app

For the record, there is a convention that a package should declare another package as a NPM dependency even if it merely uses a store registered by it, without doing any import. @youknowriad did a PR that added some missing ones: #23517.

To be clear, all packages that use a given datastore should import it in the entry point by convention. It's something that could be improved because if it's missed it might indeed cause errors. At the moment, there is no simple way to validate it happens. Example:

import '@wordpress/core-data';
import '@wordpress/block-editor';
import '@wordpress/editor';
import '@wordpress/keyboard-shortcuts';
import '@wordpress/reusable-blocks';
import '@wordpress/viewport';
import '@wordpress/notices';

I started a quick PR that could help to mitigate the issue with import statements for the packages that expose stores used in the package. In the long run, it isn't solid enough, which I was able to confirm by only testing one package @wordpress/viewport. I propose that we expose the store object from every package and use it as a param that gets passed to select and dispatch calls rather than hardcoded strings. To make it backward compatible we can inject toString method that returns the name of the registered store and cast the store object when passing to select and dispatch calls.

This PR also resolves #14150 where it was proposed to use consistent, accurate naming for store keys, I picked key for now, but I'm happy to update to storeName as originally proposed.

My recommendation would be storeName, merely for alignment with the similarly-structured blockName naming.

@wordpress/viewport package

Based on the fact that @wordpress/viewport is no longer referenced in the codebase, it looks like @wordpress/viewport as a package is no longer necessary and it probably should be replaced with something else. Let's ignore this fact when discussing this package and focus on how we can ensure that data stores are properly imported by packages that use them.

How has this been tested?

New unit tests were added.

Types of changes

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR.

@gziolo gziolo added [Type] Technical Prototype Offers a technical exploration into an idea as an example of what's possible [Package] Data /packages/data [Package] Viewport /packages/viewport labels Nov 3, 2020
@gziolo gziolo self-assigned this Nov 3, 2020
@github-actions
Copy link

github-actions bot commented Nov 3, 2020

Size Change: +449 B (0%)

Total Size: 1.19 MB

Filename Size Change
build/annotations/index.js 3.8 kB +30 B (0%)
build/block-directory/index.js 8.72 kB +8 B (0%)
build/block-editor/index.js 133 kB -191 B (0%)
build/block-library/index.js 147 kB -4 B (0%)
build/blocks/index.js 48 kB +25 B (0%)
build/components/index.js 171 kB +257 B (0%)
build/core-data/index.js 14.8 kB +8 B (0%)
build/data/index.js 8.8 kB +61 B (0%)
build/edit-navigation/index.js 11.2 kB +11 B (0%)
build/edit-post/index.js 306 kB +18 B (0%)
build/edit-site/index.js 23.3 kB +26 B (0%)
build/edit-widgets/index.js 26.4 kB +44 B (0%)
build/editor/index.js 42.5 kB +28 B (0%)
build/keyboard-shortcuts/index.js 2.54 kB +22 B (0%)
build/notices/index.js 1.81 kB +23 B (1%)
build/nux/index.js 3.42 kB +25 B (0%)
build/reusable-blocks/index.js 3.06 kB +16 B (0%)
build/rich-text/index.js 13.3 kB +22 B (0%)
build/viewport/index.js 1.86 kB +20 B (1%)
ℹ️ View Unchanged
Filename Size Change
build/a11y/index.js 1.14 kB 0 B
build/api-fetch/index.js 3.42 kB 0 B
build/autop/index.js 2.84 kB 0 B
build/blob/index.js 664 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.3 kB 0 B
build/block-editor/style.css 11.3 kB 0 B
build/block-library/editor-rtl.css 8.91 kB 0 B
build/block-library/editor.css 8.92 kB 0 B
build/block-library/style-rtl.css 8.1 kB 0 B
build/block-library/style.css 8.1 kB 0 B
build/block-library/theme-rtl.css 792 B 0 B
build/block-library/theme.css 793 B 0 B
build/block-serialization-default-parser/index.js 1.87 kB 0 B
build/block-serialization-spec-parser/index.js 3.06 kB 0 B
build/components/style-rtl.css 15.3 kB 0 B
build/components/style.css 15.3 kB 0 B
build/compose/index.js 9.9 kB 0 B
build/data-controls/index.js 821 B 0 B
build/date/index.js 11.2 kB 0 B
build/deprecated/index.js 768 B 0 B
build/dom-ready/index.js 571 B 0 B
build/dom/index.js 4.92 kB 0 B
build/edit-navigation/style-rtl.css 881 B 0 B
build/edit-navigation/style.css 885 B 0 B
build/edit-post/style-rtl.css 6.51 kB 0 B
build/edit-post/style.css 6.49 kB 0 B
build/edit-site/style-rtl.css 3.98 kB 0 B
build/edit-site/style.css 3.98 kB 0 B
build/edit-widgets/style-rtl.css 3.16 kB 0 B
build/edit-widgets/style.css 3.16 kB 0 B
build/editor/editor-styles-rtl.css 476 B 0 B
build/editor/editor-styles.css 478 B 0 B
build/editor/style-rtl.css 3.85 kB 0 B
build/editor/style.css 3.85 kB 0 B
build/element/index.js 4.62 kB 0 B
build/escape-html/index.js 735 B 0 B
build/format-library/index.js 6.86 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 2.16 kB 0 B
build/html-entities/index.js 623 B 0 B
build/i18n/index.js 3.57 kB 0 B
build/is-shallow-equal/index.js 698 B 0 B
build/keycodes/index.js 1.94 kB 0 B
build/list-reusable-blocks/index.js 3.1 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/media-utils/index.js 5.32 kB 0 B
build/nux/style-rtl.css 671 B 0 B
build/nux/style.css 668 B 0 B
build/plugins/index.js 2.56 kB 0 B
build/primitives/index.js 1.43 kB 0 B
build/priority-queue/index.js 790 B 0 B
build/redux-routine/index.js 2.84 kB 0 B
build/server-side-render/index.js 2.77 kB 0 B
build/shortcode/index.js 1.69 kB 0 B
build/token-list/index.js 1.27 kB 0 B
build/url/index.js 4.05 kB 0 B
build/warning/index.js 1.14 kB 0 B
build/wordcount/index.js 1.22 kB 0 B

compressed-size-action

@gziolo
Copy link
Member Author

gziolo commented Nov 3, 2020

I went too far with removing @wordpress/viewport from dependencies of 2 packages, will fix it later :)

@ellatrix
Copy link
Member

ellatrix commented Nov 3, 2020

I propose that we expose the store object from every package and use it as a param that gets passed to select and dispatch calls rather than hardcoded strings.

I like that. Never understood why we have the hardcoded keys instead of imports.

@mcsf
Copy link
Contributor

mcsf commented Nov 17, 2020

I'm a bit late to the party, but for me too the main thing on my mind was:

My other idea would be to expose the constant or simple object instead for start

So, nothing in the API would change for third-party consumers of select/dispatch, but in core Gutenberg we would expect all references to be select( someStoreName ). It should be easy to write an ESLint rule to prevent the use of literal strings.

Finally, just a side comment on:

The problem with the current proposal though is that it leaks the store implementation as a public API (redux store).
I think the solution is that we need a new "store definition" object that could be something like { name: "something", __internal } where __internal can have any shape but is clearly marked as an internal API and would be used by the registerStore call to actually instantiate the store and register it.

I still prefer the simple string-based approach, but for me this question of "store objects" is exactly the kind of problem that ESNext's Symbol would solve:

const storeSymbol = Symbol( 'core/foo' );
assert( storeSymbol !== Symbol( 'core/foo' ) );
assert( storeSymbol.description === 'core/foo' );

function select( storeName ) {
  if ( typeof storeName === 'symbol' ) {
    storeName = storeName.description;
  }
  /* ... */
}

@youknowriad youknowriad added [Type] New API New API to be used by plugin developers or package users. Needs Dev Note Requires a developer note for a major WordPress release cycle and removed [Type] Technical Prototype Offers a technical exploration into an idea as an example of what's possible labels Nov 17, 2020
@youknowriad
Copy link
Contributor

for me the symbol type is the perfect thing to use when you want something to be "internal" to check against without a way for third-party devs to hack around it.

While that was the original idea of the __internal thing, this changed a bit in #26996 after some discussions with @jsnajdr
Instead now we have a well defined interface for stores { name, instantiate: (registry) => store } third-party devs could use to build their own store implementation (think the atomic one for instance) while at the same time leaving the internals (redux and such) hidden inside the "instantiate" function.

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.

this is looking good for me. I think we need a bit more documentation on this.


_Returns_

- (unknown type): Store Object.
Copy link
Member Author

Choose a reason for hiding this comment

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

It's a bit unfortunate that it isn't able to properly pick up the TS type.

} );

expect( store.getState() ).toBe( 'OK' );
await registry.dispatch( STORE_NAME ).update();
Copy link
Member Author

Choose a reason for hiding this comment

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

This test needs to be updated, to work with the store definition.

Copy link
Contributor

Choose a reason for hiding this comment

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

I left this is as because it was using the redux store directly to access state.

@youknowriad youknowriad merged commit 5c3a32d into master Nov 17, 2020
@youknowriad youknowriad deleted the try/data-store-as-param branch November 17, 2020 23:01
@github-actions github-actions bot added this to the Gutenberg 9.5 milestone Nov 17, 2020
@jsnajdr
Copy link
Member

jsnajdr commented Nov 18, 2020

I think we need a bit more documentation on this.

Another related task would be to do the work required by the peerDependencies change in #26954. Identify which packages have breaking or non-breaking API changes, or new stricter requirements on peer packages, and then document them properly and decide what version bumps they need.

The data package adds a new API without breaking changes, so it deserves a minor version bump on next release.

Then, some other packages, like annotations, started to rely on the new data API, and would need a major bump. In a sense, they support only on the latest version of the underlying "operating system" and won't run on older versions.

The annotations package also adds a new named export (store) that wasn't there before. That would warrant a minor version bump if the package didn't already have a major one for a different reason.

The core-data package removes the storeConfig export (breaking change that warrants a major bump) and add a new store export (minor bump).

And so on for all other affected packages...

@gziolo
Copy link
Member Author

gziolo commented Nov 18, 2020

@jsnajdr, thanks for bringing up all those issues, I'm taking care of all documentation updates in #27061.

peerDependencies changes proposed need to be addressed separately as it creates some maintainance challenges as discussed in #26954.

@jsnajdr
Copy link
Member

jsnajdr commented Nov 18, 2020

thanks for bringing up all those issues, I'm taking care of all documentation updates

Thanks! 🎉

peerDependencies changes proposed need to be addressed separately as it creates some maintainance challenges as discussed

Even if the peerDependencies proposal was never landed, this data refactoring is a good opportunity to do a sort of "clinical trial" for it 🙂 I.e., use a real-world API change to verify how the required maintenance work really looks like.

@gziolo
Copy link
Member Author

gziolo commented Nov 19, 2020

The good news is that storeConfig in @wordpress/core-data was exposed in the package 9 days ago in #26661. So we can safely remove it without any mentions in the CHANGELOG as it was never published to npm 😃

I'll double-check if we can also get rid of storeConfig from @wordpress/block-editor and @wordpress/editor packages without any backward compatibility issues.

Update:
In both cases @wordpress/block-editor and @wordpress/editorstoreConfig was exposed 2 years ago in #15989. We should be able to refactor usage to use store definition instead:

const newRegistry = createRegistry(
{
'core/block-editor': blockEditorStoreConfig,
},
registry
);
newRegistry.registerStore( 'core/editor', storeConfig );

@youknowriad, what do you think?

@gziolo
Copy link
Member Author

gziolo commented Nov 19, 2020

I also opened a follow up that is meant to track refactoring of existing usage of hardcoded store keys: #27088.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] Data /packages/data [Package] Viewport /packages/viewport [Type] New API New API to be used by plugin developers or package users.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Data: Use consistent, accurate naming for store keys
7 participants