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

The global style should be at the top #2803

Closed
u3u opened this issue Jul 2, 2022 · 7 comments
Closed

The global style should be at the top #2803

u3u opened this issue Jul 2, 2022 · 7 comments

Comments

@u3u
Copy link

u3u commented Jul 2, 2022

Current behavior:

When there is more than one emotion cache I want the global style to always be at the top and then be overridden in order
The prepend option can only be set to the top or bottom, and the insertionPoint option needs to be passed HTMLElement which does not support SSR

image

image

I can adjust the order of ServerStyles here, but there's no way to override the mantine style directly

MyDocument.getInitialProps = async (ctx: DocumentContext) => {
  const initialProps = await Document.getInitialProps(ctx)
  const server = createEmotionServer(cache)
  return {
    ...initialProps,
    styles: (
      <>
        {initialProps.styles}
        <ServerStyles html={initialProps.html} server={stylesServer} />
        <ServerStyles html={initialProps.html} server={server} />
      </>
    ),
  }
}

To reproduce:

https://github.com/u3u/emotion-issue-2803

Expected behavior:

image

Environment information:

  • react version: 18.2.0
  • @emotion/react version: 11.9.3
@srmagura
Copy link
Contributor

srmagura commented Jul 2, 2022

Thanks for the report. I don't see how to solve this... the expected behavior shows the mantine styles being inserted in the middle of the styles for the main Emotion cache.

Somewhat similar issue: #2790

@Andarist
Copy link
Member

Andarist commented Jul 4, 2022

You can use this patch to work around your issue:

patch for the repro
diff --git a/pages/_app.tsx b/pages/_app.tsx
index f6f61f5..2da5a2e 100755
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -1,18 +1,46 @@
-import { cache } from '@emotion/css'
 import { CacheProvider } from '@emotion/react'
 import { MantineProvider } from '@mantine/core'
+import createCache from '@emotion/cache'
 import type { AppProps } from 'next/app'
 import { GlobalStyles } from 'styles'
 import { mantine } from 'theme'
+import React from 'react'
 
-function MyApp({ Component, pageProps }: AppProps) {
+const createMyEmotionCache = () => {
+  const insertionPoint = typeof document !== 'undefined'
+    ? (document.querySelector('meta[name="emotion-insertion-point"]')! as HTMLElement)
+    : undefined
+
+  const cache = createCache({
+    key: 'uthreeu',
+    insertionPoint
+  })
+  cache.compat = true;
+
+  return cache;
+}
+
+const cache = createMyEmotionCache()
+
+const wrapOnClient = (children: React.ReactNode) => {
+  if (typeof document === 'undefined') {
+    return children;
+  }
   return (
     <MantineProvider {...mantine}>
       <CacheProvider value={cache}>
-        <GlobalStyles />
-        <Component {...pageProps} />
+        {children}
       </CacheProvider>
     </MantineProvider>
+  );
+};
+
+function MyApp({ Component, pageProps }: AppProps) {
+  return wrapOnClient(
+    <>
+      <GlobalStyles />
+      <Component {...pageProps} />
+    </>
   )
 }
 
diff --git a/pages/_document.tsx b/pages/_document.tsx
index 424a765..de94e2f 100644
--- a/pages/_document.tsx
+++ b/pages/_document.tsx
@@ -1,4 +1,6 @@
-import { cache } from '@emotion/css'
+import { CacheProvider } from '@emotion/react'
+import { MantineProvider } from '@mantine/core'
+import createCache from '@emotion/cache'
 import createEmotionServer from '@emotion/server/create-instance'
 import { ServerStyles, createStylesServer } from '@mantine/next'
 import Document, {
@@ -8,13 +10,15 @@ import Document, {
   NextScript,
   DocumentContext,
 } from 'next/document'
+import { mantine } from 'theme'
 
-const stylesServer = createStylesServer()
-
+const stylesServer = createStylesServer(mantine.emotionOptions)
 export function MyDocument() {
   return (
     <Html>
-      <Head />
+      <Head>
+        <meta name="emotion-insertion-point" content=""></meta>
+      </Head>
       <body>
         <Main />
         <NextScript />
@@ -23,16 +27,46 @@ export function MyDocument() {
   )
 }
 
+const createMyEmotionCache = () => {
+  const insertionPoint = typeof document !== 'undefined'
+    ? (document.querySelector('meta[name="emotion-insertion-point"]')! as HTMLElement)
+    : undefined
+
+  const cache = createCache({
+    key: 'uthreeu',
+    insertionPoint
+  })
+  cache.compat = true;
+
+  return cache;
+}
+
 MyDocument.getInitialProps = async (ctx: DocumentContext) => {
+  const cache = createMyEmotionCache()
+  const originalRenderPage = ctx.renderPage
+
+  ctx.renderPage = () =>
+    originalRenderPage({
+      // eslint-disable-next-line react/display-name
+      enhanceApp: (App) => (props) =>
+        (
+          <MantineProvider {...mantine}>
+            <CacheProvider value={cache}>
+              <App {...props} />
+            </CacheProvider>
+          </MantineProvider>
+        ),
+    });
   const initialProps = await Document.getInitialProps(ctx)
+
   const server = createEmotionServer(cache)
   return {
     ...initialProps,
     styles: (
       <>
         {initialProps.styles}
-        <ServerStyles html={initialProps.html} server={stylesServer} />
         <ServerStyles html={initialProps.html} server={server} />
+        <ServerStyles html={initialProps.html} server={stylesServer} />
       </>
     ),
   }
diff --git a/theme/mantine.ts b/theme/mantine.ts
index 1ef6ce0..79fbde4 100644
--- a/theme/mantine.ts
+++ b/theme/mantine.ts
@@ -24,3 +24,8 @@ export const styles: MantineProviderProps['styles'] = {
     filled: tw`bg-primary-500 hocus:(bg-primary-400) active:(bg-primary-600)`,
   } as Record<ButtonStylesNames, CSSObject>,
 }
+
+export const emotionOptions = {
+  key: 'mantine',
+  prepend: false,
+}

@u3u
Copy link
Author

u3u commented Jul 9, 2022

Disable JavaScript Server-side rendering

image

image

@Andarist Thanks for the solution, but to make it easier to override styles I'd like to be able to have my own non-global styles after the mantine styles

Client-side rendering

image

I don't quite understand, this doesn't seem to solve the problem, and insertionPoint only works on client-side rendering. Even if it were possible, the results of server-side and client-side rendering would be inconsistent, right?

If I just put the mantine style at the end I would just adjust the order of the ServerStyles in the _document and set the emotionOptions.prepend of mantine to false and it would have the same effect

@Andarist
Copy link
Member

Andarist commented Jul 9, 2022

@Andarist Thanks for the solution, but to make it easier to override styles I'd like to be able to have my own non-global styles after the mantine styles

Since global styles are not scoped anyway you could use 2 separate caches for this - one cache for global styles with insertionPoint before Mantine styles and one for scoped styles with insertionPoint after Mantine styles. Since Mantine is also using Emotion under the hood - you'd probably have to introduce an insertionPoint for it too (between those other two).

I don't quite understand, this doesn't seem to solve the problem, and insertionPoint only works on client-side rendering. Even if it were possible, the results of server-side and client-side rendering would be inconsistent, right?

With extractCritical, that you are using, you can just "manually" insert those rendered styles at the correct location (right after insertionPoint). You would have to implement this manually~ without the help of the ServerStyles "helper" that you are using.

Since those extracted styles are rendered into <head/> during SSR anyway - you won't have SSR mismatches as your client-side React won't even see your <head/>.

@u3u
Copy link
Author

u3u commented Jul 9, 2022

Disable JavaScript Server-side rendering

image

Client-side rendering

image

image

@Andarist I got it, thanks! I've considered extracting global styles to a separate cache before, so that server-side rendering only requires adjusting the ServerStyles order, and client-side rendering only requires setting the insertionPoint of the Mantine!
It seems that cache.compat = true is the main point, can you elaborate on this?

My solution: u3u/emotion-issue-2803@2a302de

git patch
From 2a302de6435057db2f68e8c49203911ed07cc04f Mon Sep 17 00:00:00 2001
From: qwq <qwq@qwq.cat>
Date: Sat, 9 Jul 2022 22:18:56 +0800
Subject: [PATCH] feat: add solution

---
 pages/_app.tsx      | 15 +++++++++------
 pages/_document.tsx | 11 +++++++----
 pages/index.tsx     |  2 +-
 styles/emotion.ts   | 11 +++++++++++
 styles/index.ts     |  1 +
 theme/mantine.ts    |  9 +++++++++
 6 files changed, 38 insertions(+), 11 deletions(-)
 create mode 100644 styles/emotion.ts

diff --git a/pages/_app.tsx b/pages/_app.tsx
index f6f61f5..599057c 100755
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -1,4 +1,5 @@
 import { cache } from '@emotion/css'
+import { globalEmotionCache } from 'styles'
 import { CacheProvider } from '@emotion/react'
 import { MantineProvider } from '@mantine/core'
 import type { AppProps } from 'next/app'
@@ -7,12 +8,14 @@ import { mantine } from 'theme'
 
 function MyApp({ Component, pageProps }: AppProps) {
   return (
-    <MantineProvider {...mantine}>
-      <CacheProvider value={cache}>
-        <GlobalStyles />
-        <Component {...pageProps} />
-      </CacheProvider>
-    </MantineProvider>
+    <CacheProvider value={globalEmotionCache}>
+      <GlobalStyles />
+      <MantineProvider {...mantine}>
+        <CacheProvider value={cache}>
+          <Component {...pageProps} />
+        </CacheProvider>
+      </MantineProvider>
+    </CacheProvider>
   )
 }
 
diff --git a/pages/_document.tsx b/pages/_document.tsx
index 1721b8d..bc25e4b 100644
--- a/pages/_document.tsx
+++ b/pages/_document.tsx
@@ -1,9 +1,10 @@
 import { cache } from '@emotion/css'
+import { globalEmotionCache } from 'styles'
 import createEmotionServer from '@emotion/server/create-instance'
 import { ServerStyles, createStylesServer } from '@mantine/next'
 import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document'
 
-const stylesServer = createStylesServer()
+const mantineEmotionServer = createStylesServer()
 
 export function MyDocument() {
   return (
@@ -19,14 +20,16 @@ export function MyDocument() {
 
 MyDocument.getInitialProps = async (ctx: DocumentContext) => {
   const initialProps = await Document.getInitialProps(ctx)
-  const server = createEmotionServer(cache)
+  const globalEmotionServer = createEmotionServer(globalEmotionCache)
+  const cssEmotionServer = createEmotionServer(cache)
   return {
     ...initialProps,
     styles: (
       <>
         {initialProps.styles}
-        <ServerStyles html={initialProps.html} server={stylesServer} />
-        <ServerStyles html={initialProps.html} server={server} />
+        <ServerStyles html={initialProps.html} server={globalEmotionServer} />
+        <ServerStyles html={initialProps.html} server={mantineEmotionServer} />
+        <ServerStyles html={initialProps.html} server={cssEmotionServer} />
       </>
     ),
   }
diff --git a/pages/index.tsx b/pages/index.tsx
index 1b63c99..a50d178 100755
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -27,7 +27,7 @@ const IndexPage: NextPage = () => {
         opened
         withArrow
       >
-        <Button tw="shadow">Button</Button>
+        <Button tw="px-4 shadow">Button</Button>
       </Tooltip>
     </Root>
   )
diff --git a/styles/emotion.ts b/styles/emotion.ts
new file mode 100644
index 0000000..17f797f
--- /dev/null
+++ b/styles/emotion.ts
@@ -0,0 +1,11 @@
+import createCache, { Options } from '@emotion/cache'
+
+export const createEmotionCache = (options: Options) => {
+  const cache = createCache(options)
+  cache.compat = true
+  return cache
+}
+
+export const globalEmotionCache = createEmotionCache({ key: 'global' })
+
+export const twEmotionCache = createEmotionCache({ key: 'tw' })
diff --git a/styles/index.ts b/styles/index.ts
index 0cec958..dd95c10 100644
--- a/styles/index.ts
+++ b/styles/index.ts
@@ -1 +1,2 @@
+export * from './emotion'
 export * from './GlobalStyles'
diff --git a/theme/mantine.ts b/theme/mantine.ts
index 4c56b6d..2e33de2 100644
--- a/theme/mantine.ts
+++ b/theme/mantine.ts
@@ -1,5 +1,6 @@
 import type { Tuple, CSSObject, MantineProviderProps, ButtonStylesNames } from '@mantine/core'
 import tw, { theme as _theme } from 'twin.macro'
+import { last } from 'remeda'
 
 const primary = Object.values(_theme`colors.primary`) as Tuple<string, 10>
 
@@ -19,3 +20,11 @@ export const styles: MantineProviderProps['styles'] = {
     filled: tw`bg-primary-500 hocus:(bg-primary-400) active:(bg-primary-600)`,
   } as Record<ButtonStylesNames, CSSObject>,
 }
+
+export const emotionOptions: MantineProviderProps['emotionOptions'] = {
+  key: 'mantine',
+  insertionPoint:
+    typeof document !== 'undefined'
+      ? last([...document.querySelectorAll<HTMLElement>('style[data-emotion*="global"]')])
+      : undefined,
+}
-- 
2.36.1

@Andarist
Copy link
Member

It seems that cache.compat = true is the main point, can you elaborate on this?

This is automatically set to true by createEmotionServer. It makes our APIs not to render styles "inline" during SSR - they are just put into the cache as it's expected that they will be extracted using APIs like extractCritical etc. It also turns off the infamous "unsafe pseudo-selectors" warnings.

With your latest patch - is it already working as you want? Or do you need further assistance?

@u3u
Copy link
Author

u3u commented Jul 10, 2022

This is automatically set to true by createEmotionServer. It makes our APIs not to render styles "inline" during SSR - they are just put into the cache as it's expected that they will be extracted using APIs like extractCritical etc. It also turns off the infamous "unsafe pseudo-selectors" warnings.

With your latest patch - is it already working as you want? Or do you need further assistance?

@Andarist Thank you! This problem has been solved 😁

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