ItsJonQ / g2

✨ An experimental reimagining of WordPress components
http://g2-components.com/
MIT License
105 stars 12 forks source link

WP/Components with G2 Adapters #75

Open ItsJonQ opened 3 years ago

ItsJonQ commented 3 years ago

Going with out Context System based integration strategy, we need to create a series of "adapters" to bridge the props/markup between the current WordPress components UI and the G2 components UI.

Using the Typography Design Tools as the potential G2 launch candidate/vehicle.. I've identified a list of components we need to pay attention to.

Note: (...) indicates sub-components

β˜€οΈ Required G2 Components

πŸŒ” Existing WP Component equivalents

If we want to bring the G2 Typography tools experience (in it's entirety), we'll need to ensure the above listed components (under πŸŒ”) are πŸ’― adapted.

ItsJonQ commented 3 years ago

Had a wonderful chat with @griffbrad today!

One of the things we talked about was this ContextSystem x Adapter strategy. Brad had pointed out that these mechanics are similar to that of "feature flagging".

I thought that was a wonderful way to look at it.

Yes! Indeed. ContextSystem x Adapters enables use to "feature flag" our way to safely integrating G2 in Gutenberg πŸŽ‰

ItsJonQ commented 3 years ago

πŸ“‚ File/Code convention idea

Let's use the Button component as an example. Within Gutenberg, in the button/ component directory, we can add a next.js file:

/button
  |- index.js
  \- next.js

All of the logic and G2 Component "rigging" can happen in that file.

For example:

// components/src/button/next.js
import { withNextComponent as withNext } from '@wordpress/ui.context';
import { Button } from '@wordpress/ui.components';

function adapter(props) { ... }

export function withNextComponent(current) {
  // current - Current WP Button
  // Button - The G2 Component Button
  // 'WPComponentsButton' - Namespace to register into the Context System
  // adapter - Adapter to bridge the props between current Button -> G2 Button

  return withNext(current, Button, 'WPComponentsButton', adapter);
}

Finally, in the current Button index.js file, we just need to import this withNextComponent HOC and export the wrapped component.

import { withNextComponent } from './next';

function Button (props) { ... }

export default withNextComponent(Button);

With this technique, there's a clear division between:

  1. Existing Button code
  2. Bridge code (between old/new button)
  3. New Button code (lives in the G2 Components package)
ItsJonQ commented 3 years ago

Chatted with @griffbrad today! In addition to the technical implementation, @griffbrad had suggested the idea having guides for developers. Developers who have existing 3rd party block code, and devs who want to build something brand new.

I don't think we necessarily need to write a details walkthrough/guide, at least not in the beginning. However, I think we should have a ~good~ solid sense of what the migration path would be like for both core and 3rd party devs.


Expanding a bit on the Context/HOC strategy above.

It seems like the current plan is to add the G2 packages into Gutenberg as separate packages (e.g. @wordpress/ui.components ... maybe).

This would certainly make it clearer which parts of the system you're working in (e.g. @wordpress/data).

The question is, should we globally export and expose the G2 component code? So that @wordpress/ui.components/Button exports to window.wp.ui-components.Button.

I would say no.

I propose that G2 Components is surfaced throughout the project, as well as globally (window), via @wordpress/components.

There will be a (long) period where we'll have 2 component codebases.

Overtime, as we've completely migrated over to G2 Components, we'll be able to move the @wordpress/ui.components code into @wordpress/components, and move the current code into (maybe) into legacy.js files - similar to how G2 code exists in next.js files in the current integration proposal (above).

In short, G2 Components should ultimately become @wordpress/components. We're just working out the details on how to do it :)

And these details should define and influence how we approach best practices and developer guidelines/tutorials.

youknowriad commented 3 years ago

It seems like the current plan is to add the G2 packages into Gutenberg as separate packages (e.g. @wordpress/ui.components ... maybe).

Why a separate package? I'd personally prefer a folder in the same package instead. This ensures it's organized properly and at the same time only exposed internally and not externally (through wp.*)

button example

In the Button example above, I believe it means we keep both components working right? How does one consumer switches? Also can't we instead remove the legacy component and have a "prop adapter" instead (rewriting props, triggering deprecations for deprecated props) but still use the G2 components for both usage?

ItsJonQ commented 3 years ago

Why a separate package?

At the moment, G2 Components has about 8 primary packages (about 6 if you exclude packages that are basically dependency aliases):

I'd personally prefer a folder in the same package instead.

Just double checking. Is your suggestion for all of these packages to be moved directly into @wordpress/components?


For context, these packages do very different things (not unlike how many of the current @wordpress/* packages are split/organized).

It's also valuable to be able to use/share styles from certain G2 packages (like utils, state or styles) in code beyond the components package. Based on my experience with (current) Gutenberg and more recent experience with G2 Components, I'm very certain things that fall into the "Design Tools" category will benefit greatly from this shared code.

(e.g. Performant state management/syncing, unit parsing/serializing)


In the Button example above, I believe it means we keep both components working right?

Correct!


How does one consumer switches?

With this strategy, there are two primary ways to render the new Button.

Inline version prop:

<Button version="next">...</Button>

Or, with a ContextSystemProvider:

<ContextSystemProvider value={{ WPComponentsButton: { version: 'next' }}>
  ...
  <Button />
  ...
</ContextSystemProvider>

(Note: We can change the version prop to anything we want. That's just the prop I've chosen for my initial tests)


Also can't we instead remove the legacy component

Unfortunately not, mostly for style conflict reasons.

For example, the Button. It's being used in many (many) places, some of which have custom styling (e.g. Toolbar).

Replacing the current Button with the G2 Components Button won't break (JS) component rendering (assuming all props are adapted), but it would break CSS styling rendering.

Having the feature-flag like control of the ContextSystemProvider allows us to incrementally upgrade things while (hopefully) reducing unexpected side-effects (again, mostly from CSS related conflicts).

That being said, I'm 100% open to alternative strategies. This incremental context-based "chunking" migration strategy was something I've used in the past (with great success).


Hope this helps! Really appreciate your thoughts @youknowriad ! ❀️

youknowriad commented 3 years ago

Just double checking. Is your suggestion for all of these packages to be moved directly into @wordpress/components?

I see, I think it seems there's value in these packages being separate (maybe not all, we'll have to see). The important thing though is that they stay as "bundled modules" (like @wordpress/icons and @wordpress/interface right now) to avoid having them leak as public APIs in WordPress.

for the components one (I guess the highest level one), I think we should avoid the temporary package and just use a folder in @wordpress/components to start with.

Inline version prop:

πŸ‘Œ I wonder if we should mark this prop as unstable, since it's only useful during the migration.

Unfortunately not, mostly for style conflict reasons.

So you're saying, once we migrate all buttons usage in our codebase, we can remove the old implementation. that seems reasonable.

ItsJonQ commented 3 years ago

πŸ“ Modules

The important thing though is that they stay as "bundled modules" (like @wordpress/icons and @wordpress/interface right now) to avoid having them leak as public APIs in WordPress

Wonderful! icons and interface are the ones that come to mind for how I'd imagine these G2 packages working - especially icons as those bits of code can be accessed in many other parts of the Gutenberg codebase.

πŸ’« Combined Component Package

for the components one (I guess the highest level one), I think we should avoid the temporary package and just use a folder in @wordpress/components to start with

I'm open to that. Although, I think there's value in separating the newer G2 Components code in same way. If all of it goes into @wordpress/components, I feel like they should live in a directory labelled __next (or something similar).

Perhaps like this:

src/
β”œβ”€β”€ button/
β”‚   β”œβ”€β”€ __next/
β”‚   └── index.js
β”œβ”€β”€ card/
β”‚   β”œβ”€β”€ __next/
β”‚   β”œβ”€β”€ (other files)
β”‚   └── index.js
β”œβ”€β”€ hstack/
β”‚   └── index.js
└── etc...

I feel like the structure above can make code easier to find (since the G2 code is scoped locally to it's named directory), while encapsulating and signifying the "next" version of code that's being worked on.

In this above example, I've included HStack (one of the new layout-based primitive components), specifically because it currently doesn't exist within @wordpress/components.

For cases like this, I think it would be okay organize the files without using a __next/ directory.

βš›οΈ Version Prop

πŸ‘Œ I wonder if we should mark this prop as unstable, since it's only useful during the migration.

I'm happy with that :). I think using __unstableVersion (or something similar) is a good signifier that stuff is being updated.

🧹 Cleaning up the Old Implementation

So you're saying, once we migrate all buttons usage in our codebase, we can remove the old implementation.

That's my hope πŸ™ . That these G2 Components eventually become @wordpress/components. Since we can migrate things incrementally, that means that we can also remove the old implementation incrementally as well - rather than waiting for the day were we remove all of the old code in one-shot (which, we can do if that's a better strategy).

Once all of the previous implementation is removed, all that remains of it are the (now) legacy component APIs.

At that point, we can decide if we want to deprecate these component APIs in favour of newer ones that make sense to the ecosystem.

ItsJonQ commented 3 years ago

cc'ing @saramarcondes

ItsJonQ commented 3 years ago

Related: https://github.com/ItsJonQ/g2/issues/123