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

React 18: Fix @automattic/global-styles package #77319

Merged
merged 12 commits into from
May 30, 2023

Conversation

arthur791004
Copy link
Contributor

@arthur791004 arthur791004 commented May 24, 2023

Related to #77048, #77046 -- this change fixes compatibility with WordPress packages after the react 18 update

Proposed Changes

  • Create a gutenberg-bridge folder inside the @automattic/global-styles package and
    • Unlock the private APIs we need and re-export them for Calypso to use.
    • Re-implement mergeBaseAndUserConfigs function as Gutenberg doesn't export it
    • Create withExperimentalBlockEditorProvider to wrap the component with ExperimentalBlockEditorProvider and eliminate the compatStyles. The reason is the compatStyles are not required on Calypso and it will cause warnings since some of compatStyles might not have an id so that we cannot use it as key when rendering.
  • Use the fixed version of @wordpress/private-apis to ensure we have the consistent __private symbol and avoid we're not able to unlock the private APIs due to the unequal private symbol. Or is dedupe enough?

There is a warning from Gutenberg below, but it looks like GB won't encounter this one 🤔

  • Warning: validateDOMNesting(...): <svg> cannot appear as a child of <head>.

Testing Instructions

Theme Showcase

  • Go to /themes/<your_site>
  • Ensure the style variation discs look good
  • Pick a theme with style variations
  • Ensure the style variations still look good on the theme details page

Design Picker

  • Go to /setup?siteSlug=<your_site>
  • Continue until you land on the Design Picker screen
  • Ensure the style variation discs look good
  • Pick a theme with style variations
  • Ensure the style variations still look good on the preview screen

Pattern Assembler

  • Go to /setup?siteSlug=<your_site>
  • Continue until you land on the Design Picker screen
  • Scroll down to the bottom
  • Pick the BCPA CTA (Start designing)
  • Select header/homepage/footer/colors/fonts
  • Ensure everything looks good as before

Pre-merge Checklist

  • Has the general commit checklist been followed? (PCYsg-hS-p2)
  • Have you written new tests for your changes?
  • Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
  • Have you checked for TypeScript, React or other console errors?
  • Have you used memoizing on expensive computations? More info in Memoizing with create-selector and Using memoizing selectors and Our Approach to Data
  • Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
  • For changes affecting Jetpack: Have we added the "[Status] Needs Privacy Updates" label if this pull request changes what data or activity we track or use (p4TIVU-ajp-p2)?

@matticbot
Copy link
Contributor

matticbot commented May 24, 2023

@arthur791004 arthur791004 self-assigned this May 24, 2023
@arthur791004 arthur791004 force-pushed the try-react-18-global-styles-pkg branch from ba2b3c3 to 4b85d57 Compare May 24, 2023 10:54
@matticbot matticbot added the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label May 24, 2023
@arthur791004 arthur791004 changed the title WIP React 18: Fix @automattic/global-styles package React 18: Fix @automattic/global-styles package May 24, 2023
@arthur791004 arthur791004 requested a review from a team May 24, 2023 11:12
@noahtallen noahtallen requested a review from a team May 24, 2023 18:27
@arthur791004 arthur791004 force-pushed the try-react-18-global-styles-pkg branch from 4b85d57 to 7c64d8e Compare May 25, 2023 03:51
Copy link
Member

@noahtallen noahtallen left a comment

Choose a reason for hiding this comment

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

Overall, this looks good to me. I really like the gutenberg-bridge approach, which should ideally make it easier to change things in the future. I tested all the features you mentioned in the PR, and they are all testing great for me! Do we use any of these in the editor via ETK?

One thing I'm worried about is that the unlock API package version is from a newer release than the other Gutenberg packages. However, the unlock package doesn't have dependencies on other packages in Gutenberg, so I think it will be ok.

I think that most of the CI failures are from the underlying React PR and not from this one.

},
[ setUserConfig ]
);
return [ !! true, userConfig, setConfig ];
Copy link
Member

Choose a reason for hiding this comment

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

What's the purpose of !! true here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To indicate the config is ready. We don't want to fetch the current user config from the server at the current stage, so it's always ready at the begining

Copy link
Member

Choose a reason for hiding this comment

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

I think Noah was suggesting that we could remove the !! boolean casting, since it's already a boolean.

import type { GlobalStylesObject } from '../types';

const { unlock } = __dangerousOptInToUnstableAPIsOnlyForCoreModules(
'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.',
Copy link
Member

Choose a reason for hiding this comment

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

I hadn't realized they added this message, but I'm definitely a fan since it forces us to be explicit about it 😅

Copy link
Member

Choose a reason for hiding this comment

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

Yup, I love it too 😉 Great tactics from a devex perspective. 👍

}
};

return mergeWith( {}, base, user, mergeTreesCustomizer );
Copy link
Member

Choose a reason for hiding this comment

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

We would like to avoid lodash if possible, but this could be a use case which is too difficult to replicate natively.

Copy link
Contributor Author

@arthur791004 arthur791004 May 26, 2023

Choose a reason for hiding this comment

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

Yes, we have to take time to see how to support mergeWith natively but I think it's not the goal of this PR. So, I'd prefer to do it later 🙂

Copy link
Member

Choose a reason for hiding this comment

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

I'd also love to not see new Lodash usage introduced.

We've been using deepmerge as a replacement for merge and mergeWith in Gutenberg and it mostly works well. In this case, it appears to be a simple scenario where we want to disable merging arrays and just have them overriden. This can be achieved with something like:

import deepmerge from 'deepmerge';
import { isPlainObject } from 'is-plain-object';

deepmerge( obj1, obj2, {
	isMergeableObject: isPlainObject,
} );

and if you want to avoid using is-plain-object for some reason, you could do something like (untested):

import deepmerge from 'deepmerge';

deepmerge( obj1, obj2, {
	isMergeableObject: ( value ) => ! Array.isArray( value ),
} );

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated! TIL: deepmerge 👍

// return user;
const { user, setUserConfig } = useContext( GlobalStylesContext );
useEffect( () => {
if ( ! enabled ) {
Copy link
Member

Choose a reason for hiding this comment

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

Note: this file changed on trunk, so it might need to be refreshed. (I resolved the merge conflict in the base PR to maintain compatibility with this branch)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rebased. Thanks for noting that!

@arthur791004 arthur791004 force-pushed the try-react-18-global-styles-pkg branch 2 times, most recently from 69a48b5 to ce2e393 Compare May 26, 2023 06:35
@arthur791004 arthur791004 force-pushed the try-react-18-global-styles-pkg branch from ce2e393 to 335e26e Compare May 26, 2023 06:38
}
};

return mergeWith( {}, base, user, mergeTreesCustomizer );
Copy link
Member

Choose a reason for hiding this comment

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

I'd also love to not see new Lodash usage introduced.

We've been using deepmerge as a replacement for merge and mergeWith in Gutenberg and it mostly works well. In this case, it appears to be a simple scenario where we want to disable merging arrays and just have them overriden. This can be achieved with something like:

import deepmerge from 'deepmerge';
import { isPlainObject } from 'is-plain-object';

deepmerge( obj1, obj2, {
	isMergeableObject: isPlainObject,
} );

and if you want to avoid using is-plain-object for some reason, you could do something like (untested):

import deepmerge from 'deepmerge';

deepmerge( obj1, obj2, {
	isMergeableObject: ( value ) => ! Array.isArray( value ),
} );

@@ -1,7 +1,6 @@
// import { GlobalStylesContext } from '@wordpress/edit-site/build-module/components/global-styles/context';
// import { mergeBaseAndUserConfigs } from '@wordpress/edit-site/build-module/components/global-styles/global-styles-provider';
import { isEmpty, mapValues } from 'lodash';
Copy link
Member

Choose a reason for hiding this comment

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

I bet those Lodash usages should be pretty straightforward to remove. Let me know if I can aid with suggestions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

Button: (
<Button
variant="link"
target="__blank"
Copy link
Member

Choose a reason for hiding this comment

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

I assume you meant:

Suggested change
target="__blank"
target="_blank"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed. But I found there are lots of __blank in our codebase 😂

Copy link
Member

Choose a reason for hiding this comment

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

Bummer. It's a good opportunity to fix them 😅 Fixed them all in #77484

Button: (
<Button
variant="link"
target="__blank"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
target="__blank"
target="_blank"

<ExternalLink href={ localizeUrl( 'https://wordpress.com/support/com-vs-org/' ) } />
<ExternalLink
href={ localizeUrl( 'https://wordpress.com/support/com-vs-org/' ) }
children={ null }
Copy link
Member

Choose a reason for hiding this comment

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

Why is that necessary? children will be undefined by default.

Copy link
Member

@noahtallen noahtallen May 26, 2023

Choose a reason for hiding this comment

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

The problem is that some components require children via TS, which is hard to support in some cases (like when interpolating)

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense, thanks.

Intuitively, I would suggest resolving this by altering the type to make the children prop optional, but a double-check at the ExternalLink code makes me think twice about it. Why do we have an external link without any text? Sounds like it could be an accessibility issue.

Copy link
Contributor Author

@arthur791004 arthur791004 May 29, 2023

Choose a reason for hiding this comment

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

Actually, it has as it used by createInterpolateElement function to replace the custom-defined tag with the specified component, and there is the text between the tag that is the text for the ExternalLink. For example,

// Input
createInterpolateElement(
  '{{a}}Learn more{{/a}}',
  { a: <ExternalLink /> }
);

// Output
<ExternalLink>Learn more</ExternalLink>

Copy link
Member

Choose a reason for hiding this comment

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

Cool! Then (sorry for going in circles 😅 ), I'd ask the following question: what do you feel is the best in this case - passing children={ null }, ignoring the TS rule, or fixing the ExternalLink type to support an optional children prop`? If you picked one of those, why did you prefer that choice?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the ExternalLink still needs the children so we should not make it optional. So, I'd prefer to pass children={ null } or even use ts-expect-error to bypass the rule and see whether we can fix this issue via createInterpolateElement as it should let TS know it will pass the children to that component. However, I'm not whether it's possible or not 😓

Copy link
Member

Choose a reason for hiding this comment

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

Sounds good, thanks for confirming, let's keep the children={ null } then 👍 No need to overcomplicate things.

Copy link
Member

Choose a reason for hiding this comment

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

I also asked about this in Slack a couple weeks ago (it's a broader issue uncovered in the react 18 update): p1684448743933079-slack-CK1QYS6P6

<ExternalLink href={ localizeUrl( 'https://wordpress.org/support/forums/' ) } />
<ExternalLink
href={ localizeUrl( 'https://wordpress.org/support/forums/' ) }
children={ null }
Copy link
Member

Choose a reason for hiding this comment

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

Why is that necessary? children will be undefined by default.

{} as GlobalStylesObject
)
);
}, [ globalStyles ] );
Copy link
Member

Choose a reason for hiding this comment

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

Is there a chance globalStyles will be a new object on every re-render and setUserConfig will be called every time? Should we memoize the config merging and call setUserConfig only if the memoized version changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We handle this case outside when using this hook.

Should we memoize the config merging and call setUserConfig only if the memoized version changes?

How could we detect the memoized version changes? If the globalStyles is a new object on every re-render, then the memoized version will be changed as well.

Copy link
Member

Choose a reason for hiding this comment

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

I was mostly thinking out loud and leaving this for your consideration, but if it's handled already, no need to worry about it 👍

import type { GlobalStylesObject } from '../types';

const { unlock } = __dangerousOptInToUnstableAPIsOnlyForCoreModules(
'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.',
Copy link
Member

Choose a reason for hiding this comment

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

Yup, I love it too 😉 Great tactics from a devex perspective. 👍

textAlign: 'center',
} }
>
{ translate( 'Default' ) }
Copy link
Member

Choose a reason for hiding this comment

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

Note that such simple strings often needs a context for the translators to know how to translate it in various languages.

@arthur791004 arthur791004 force-pushed the try-react-18-global-styles-pkg branch from 8fb2e94 to 5eef674 Compare May 29, 2023 07:22
@noahtallen noahtallen merged this pull request into try-react-18 May 30, 2023
@noahtallen noahtallen deleted the try-react-18-global-styles-pkg branch May 30, 2023 21:57
@noahtallen
Copy link
Member

I'll go ahead and merge this into the main React 18 PR so that we have all the fixes in one place.

@github-actions github-actions bot removed the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label May 30, 2023
@arthur791004
Copy link
Contributor Author

Thank you!

@noahtallen noahtallen restored the try-react-18-global-styles-pkg branch June 22, 2023 21:15
@noahtallen
Copy link
Member

I've been doing a lot more work on the React 18 PR, and in an effort to split out much as possible from that PR, I've pinned the WordPress packages to a lower version (versions from around December 2022). The benefit is that we won't need to bundle this fix into the React 18 PR, but we'll still need to land it in the PR after that when we update the WordPress packages to the current versions. So I restored the branch in anticipation of that

@noahtallen noahtallen mentioned this pull request Jun 23, 2023
25 tasks
@noahtallen
Copy link
Member

Reopened to merge onto the wordpress package updates: #78715

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.

4 participants