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

How can I wrap all the emotion styling with one @layer? #3134

Open
MSanbira opened this issue Nov 30, 2023 · 3 comments
Open

How can I wrap all the emotion styling with one @layer? #3134

MSanbira opened this issue Nov 30, 2023 · 3 comments

Comments

@MSanbira
Copy link

MSanbira commented Nov 30, 2023

Hi, I'm working with the latest version of MUI and implemented it alongside vanilla CSS organized with @layers.

I would love to wrap all the generated emotion CSS with @layer framework { styles} and don't know how.
I tried having an observer on the HTML's head node and manually adding the strings but it only works in my local environment.

I tried to look around and saw some mention of supporting @layer but I didn't understand how and to what extent.

I would love some help if you can 🙏
thanks

@jamesarosen
Copy link

jamesarosen commented Feb 12, 2024

Here's my solution:

import createCache, { type StylisPlugin } from '@emotion/cache';
import { CacheProvider } from '@emotion/react';
import { ChakraProvider, ChakraProviderProps } from '@chakra-ui/react';
import { type FC } from 'react';

const wrapInLayer: (layerName: string) => StylisPlugin = (layerName) => (node) => {
  // if we're not at the root of a <style> tag, leave the tree intact
  if (node.parent) return;

  // if we're at the root, replace node with `@layer layerName { node }`
  const child = { ...node, parent: node, root: node };
  Object.assign(node, {
    children: [child],
    length: 6,
    parent: null,
    props: [layerName],
    return: '',
    root: null,
    type: '@layer',
    value: `@layer ${layerName}`,
  });
};

const emotionCache = createCache({
  key: 'emotion-css-cache',
  prepend: true,
  stylisPlugins: [wrapInLayer('chakra')],
});

/**
 * A wrapper for the out-of-the-box ChakraProvider that puts all the Emotion
 * CSS at the top of `<head>` and wrapped in a `@layer chakra` so our own CSS
 * can override Chakra.
 */
const ChakraProviderWithLayer: FC<ChakraProviderProps> = ({ children, ...rest }) => {
  return (
    <CacheProvider value={emotionCache}>
      <ChakraProvider {...rest}>
        {children}
      </ChakraProvider>
    </CacheProvider>
  );
};

export { ChakraProviderWithLayer as ChakraProvider };

You can use it just like you'd use the out-of-the-box <ChakraProvider>. And, of course, if you're not using Chakra, you can omit that part of the code and just use the emotionCache wherever you currently use it.

Caveat: this doesn't work for things wrapped in a @media query. I haven't yet figured out how to get those to nest within the @layer properly. I think the nesting is supposed to be @media { @layer { ... } }, but I can't get the @layer to show up when I insert it between the @media node and its children.

Fix

The problem was input CSS like this:

h1 {
  color: red;
  @media screen and (min-width: 30em) { color: blue; }
}

which Stylis transforms into

h1 { color: red; }
@media screen and (min-width: 30em) { h1 { color: blue; } }

The solution is to check for root instead of parent because Stylis attaches a root property to all elements except the roots in the transformed AST.

Thus, the solution is

// if we're not at the root of a <style> tag, leave the tree intact
if (node.root) return;

// rest of code as-is

@MSanbira
Copy link
Author

thank you very much, ill try and implement it to see if it works 🙏

@tpict
Copy link

tpict commented Mar 4, 2024

Thanks @jamesarosen, this works really well. I think there's a typo: Object.assign(element... ought to be Object.assign(node...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants