diff --git a/.changeset/weak-cooks-compare.md b/.changeset/weak-cooks-compare.md new file mode 100644 index 0000000000..8056ad0abe --- /dev/null +++ b/.changeset/weak-cooks-compare.md @@ -0,0 +1,5 @@ +--- +'@emotion/react': minor +--- + +Automatic labeling at runtime is now an opt-in feature. Define `globalThis.EMOTION_RUNTIME_AUTO_LABEL = true` before Emotion gets initialized to enable it. diff --git a/docs/labels.mdx b/docs/labels.mdx index 6e7a1c628c..dbdf9c2b57 100644 --- a/docs/labels.mdx +++ b/docs/labels.mdx @@ -2,7 +2,7 @@ title: 'Labels' --- -Emotion adds a css property called `label`, the value of it is appended to the end of the class name, so it's more readable than a hash. `@emotion/babel-plugin` adds these labels automatically based on the variable name and other information, so they don't need to be manually specified. +Emotion adds a CSS property called `label` which is appended to the generated class name to make it more readable. `@emotion/babel-plugin` adds these labels automatically based on the variable name and other information, so they don't need to be manually specified. ```jsx // @live @@ -29,3 +29,16 @@ render( ) ``` + +## Automatic Labeling at Runtime + +If you are not using `@emotion/babel-plugin`, you can still get automatic labels in development by setting the following global flag: + +```js +globalThis.EMOTION_RUNTIME_AUTO_LABEL = true +``` + +This feature is opt-in because: + +- If you use server-side rendering and test your site in Safari, you may get spurious hydration warnings because the label computed on the server does not match the label computed in Safari. +- This feature may degrade performance if the number of elements using the `css` prop is very large. diff --git a/packages/react/__tests__/css.js b/packages/react/__tests__/css.js index 2d599f4df0..1c8579a553 100644 --- a/packages/react/__tests__/css.js +++ b/packages/react/__tests__/css.js @@ -10,6 +10,10 @@ import createCache from '@emotion/cache' console.error = jest.fn() console.warn = jest.fn() +beforeEach(() => { + delete globalThis.EMOTION_RUNTIME_AUTO_LABEL +}) + afterEach(() => { jest.clearAllMocks() safeQuerySelector('body').innerHTML = '' @@ -185,7 +189,27 @@ test('speedy option from a custom cache is inherited for styles', () = expect(safeQuerySelector('body style').textContent).toEqual('') }) +it('does not autoLabel without babel or EMOTION_RUNTIME_AUTO_LABEL', () => { + let SomeComp = props => { + return ( +
+ something +
+ ) + } + const tree = renderer.create() + + expect(tree.toJSON().props.className).toMatch(/css-[^-]+/) +}) + test('autoLabel without babel', () => { + globalThis.EMOTION_RUNTIME_AUTO_LABEL = true + let SomeComp = props => { return (
{ }) test('autoLabel without babel (sanitized)', () => { + globalThis.EMOTION_RUNTIME_AUTO_LABEL = true + let SomeComp$ = props => { return (
diff --git a/packages/react/__tests__/rehydration.js b/packages/react/__tests__/rehydration.js index 113a96e2c2..ae9cdc85c4 100644 --- a/packages/react/__tests__/rehydration.js +++ b/packages/react/__tests__/rehydration.js @@ -53,8 +53,8 @@ beforeEach(() => { test("cache created in render doesn't cause a hydration mismatch", () => { safeQuerySelector('body').innerHTML = [ '
', - '', - '
Hello world!
', + '', + '
Hello world!
', '
' ].join('') @@ -141,7 +141,7 @@ test('initializing another Emotion instance should not move already moved styles data-s="" > - .stl-1pdkrhd-App{color:hotpink;} + .stl-168r6j{color:hotpink;}
@@ -189,7 +189,7 @@ test('initializing another Emotion instance should not move already moved styles data-s="" > - .stl-1pdkrhd-App{color:hotpink;} + .stl-168r6j{color:hotpink;}
diff --git a/packages/react/src/emotion-element.js b/packages/react/src/emotion-element.js index 211dbac662..2c2b7cc3cd 100644 --- a/packages/react/src/emotion-element.js +++ b/packages/react/src/emotion-element.js @@ -40,10 +40,16 @@ export const createEmotionProps = ( newProps[typePropName] = type - // For performance, only call getLabelFromStackTrace in development and when - // the label hasn't already been computed + // Runtime labeling is an opt-in feature because: + // - It causes hydration warnings when using Safari and SSR + // - It can degrade performance if there are a huge number of elements + // + // Even if the flag is set, we still don't compute the label if it has already + // been determined by the Babel plugin. if ( process.env.NODE_ENV !== 'production' && + typeof globalThis !== 'undefined' && + !!globalThis.EMOTION_RUNTIME_AUTO_LABEL && !!props.css && (typeof props.css !== 'object' || typeof props.css.name !== 'string' ||