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

Add the option to provide a prefix to generated class names #1717

Merged
merged 14 commits into from
Oct 14, 2024

Conversation

guilhermehto
Copy link
Contributor

@guilhermehto guilhermehto commented Oct 9, 2024

What is this change?

This PR introduces a new configuration prop to @compiled/babel-plugin named classHashPrefix (feel free to suggest a better alternative) that allows users to define a prefix to be added to when generating class name hashes.

Why are we making this change?

We have an internal problem in a consumer of a remote module where it has its styles messed up due to the loss of specificity once the remote module is loaded. This is because both the wrapping application and the remote module use @compiled and due to the way we generate the class names, both end up generating a few with the same name.

How are we making this change?

This is the first time I ever work with a Babel plugin so I might be missing something, all feedback is appreciated. I tried to follow the code and tests to figure out where we were generating the hashes and hooked it up with the new config prop.

Lastly, I only worked off of tests on this, guidance on how to better test it are welcomed 😅

edit: I assume I'm probably missing a changeset on this pr done


PR checklist

Don't delete me!

I have...

  • Updated or added applicable tests
  • Updated the documentation in website/

Copy link

changeset-bot bot commented Oct 9, 2024

🦋 Changeset detected

Latest commit: 7ad0484

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 7 packages
Name Type
@compiled/parcel-transformer Minor
@compiled/webpack-loader Minor
@compiled/babel-plugin Minor
@compiled/css Minor
@compiled/parcel-config Patch
@compiled/babel-plugin-strip-runtime Minor
@compiled/parcel-optimizer Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

netlify bot commented Oct 9, 2024

Deploy Preview for compiled-css-in-js ready!

Name Link
🔨 Latest commit 7ad0484
🔍 Latest deploy log https://app.netlify.com/sites/compiled-css-in-js/deploys/670c360344a3f50008ef0edd
😎 Deploy Preview https://deploy-preview-1717--compiled-css-in-js.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link
Collaborator

@JakeLane JakeLane left a comment

Choose a reason for hiding this comment

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

How does this work with ax? That parses the classname and I don't think it'll work without changes.

Have a read of https://github.com/atlassian-labs/compiled/blob/master/packages/react/src/runtime/ax.ts#L30 and probably add some tests there

@@ -205,6 +205,36 @@ describe('babel plugin', () => {
expect(actual).toInclude('c_MyDiv');
});

it.only('should add a prefix to style hash hashPrefix is present', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
it.only('should add a prefix to style hash hashPrefix is present', () => {
it('should add a prefix to style hash hashPrefix is present', () => {

@guilhermehto
Copy link
Contributor Author

guilhermehto commented Oct 9, 2024

hey @JakeLane I was playing around with the code but I couldn't find any clear solutions besides adding the hash prefix into the hash of the group. This way groups would wind up with the same hash and ax should work as expected, I believe 🤔

Essentially

const group = hash(`${opts.atRule}${selectors}${node.prop}`).slice(0, 4);
// becomes
const group = hash(`${opts.hashPrefix}${opts.atRule}${selectors}${node.prop}`).slice(0, 4);

Seems too easy to be true though, am I potentially missing something?

edit: Obviously we'd lose the ability to check for the prefix when looking through the class names as they'd be hashed but that's not really why we're making this change to begin with so I think it's an acceptable tradeoff.

* Adds a defined prefix to the generated classes' hashes.
* Useful in micro frontend environments to avoid clashing/specificity issues.
*/
hashPrefix?: string;
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit

what about addClassPrefix? cc @dddlr @JakeLane

Copy link
Collaborator

Choose a reason for hiding this comment

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

yeah more descriptive naming is helpful

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Imo add suggests the expected value is a boolean and not a string that we're setting. Would classHashPrefix be better in this case?

Copy link
Collaborator

Choose a reason for hiding this comment

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

ye that's fine

@JakeLane
Copy link
Collaborator

hey @JakeLane I was playing around with the code but I couldn't find any clear solutions besides adding the hash prefix into the hash of the group. This way groups would wind up with the same hash and ax should work as expected, I believe 🤔

Essentially

const group = hash(`${opts.atRule}${selectors}${node.prop}`).slice(0, 4);
// becomes
const group = hash(`${opts.hashPrefix}${opts.atRule}${selectors}${node.prop}`).slice(0, 4);

Seems too easy to be true though, am I potentially missing something?

edit: Obviously we'd lose the ability to check for the prefix when looking through the class names as they'd be hashed but that's not really why we're making this change to begin with so I think it's an acceptable tradeoff.

I think this is fine. It might be good to validate VR test correctness manually; I don't think we have a way to change the babel config on VR tests right now.

@@ -93,6 +94,15 @@ Extensions that we should consider code. We use these to identify if a file shou
- Type: `string[]`
- Default: `['.js', '.jsx', '.ts', '.tsx']`

#### hashPrefix

Adds a prefix to the generated hashed css rule names.
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: mention what format hashPrefix is required to be

e.g. how it needs to match the /^[a-zA-Z\-][a-zA-Z\-_0-9]*$/ regex

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah yep good idea!

*/
const atomicClassName = (node: Declaration, opts: PluginOpts) => {
if (opts.hashPrefix && !isCssIdentifierValid(opts.hashPrefix)) {
Copy link
Collaborator

@dddlr dddlr Oct 10, 2024

Choose a reason for hiding this comment

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

nit: what if we move this check to the atomicifyRules function later in the file? we don't expect opts.hashPrefix to change during the build stage right

(not a strong preference, i don't mind either way)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

can do! Probably a better idea, we most likely don't wanna be running regex matches all willy nilly

*
*/
const isCssIdentifierValid = (value: string): boolean => {
const validCssIdentifierRegex = /^[a-zA-Z\-][a-zA-Z\-_0-9]*$/;
Copy link
Collaborator

Choose a reason for hiding this comment

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

where did you find this regex? it looks reasonable but it doesn't match with what i could easily find online e.g. https://stackoverflow.com/a/449000

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 made it myself as it was supposed to be in the middle of an already existing identifier. It's actually not 100% now that I look at it;

That said, since it's now being used in the hash and not as a standalone string it could be pretty much anything but I think it makes sense to continue to only accept identifiers to prevent any potential unwanted side effects. I'll refactor it

*/
const isCssIdentifierValid = (value: string): boolean => {
const validCssIdentifierRegex = /^[a-zA-Z\-_]+[a-zA-Z\-_0-9]*$/;
return Boolean(value.match(validCssIdentifierRegex));
Copy link
Collaborator

@dddlr dddlr Oct 11, 2024

Choose a reason for hiding this comment

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

oops sorry i just realised, can this be simplified to validCssIdentifierRegex.test(value) instead?

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test

@@ -21,10 +34,14 @@ interface PluginOpts {
*
* @param node CSS declaration
* @param opts AtomicifyOpts
*
* @throws Throws an error if `opts.classHashPrefix` contains invalid css class/id characters
Copy link
Collaborator

Choose a reason for hiding this comment

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

this @throws should be removed

{ classHashPrefix: 'myprefix' }
);

expect(actual).toInclude(hashedClassName);
Copy link
Collaborator

@liamqma liamqma Oct 13, 2024

Choose a reason for hiding this comment

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

should we check actual include classHashPrefix too?
All good. classHashPrefix is part of hash.

@dddlr dddlr merged commit 4fb5c6e into master Oct 14, 2024
13 checks passed
@dddlr dddlr deleted the feat/add-hash-prefix branch October 14, 2024 03:56
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.

5 participants