
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Инструкции в текущей официальной документации не работают с Cloudflare Pages или Workers. Они также приводят к плохому UX, вызванному ошибкой FOUC (как показано далее). Мне потребовалось более 6 часов, чтобы понять и исправить эти проблемы.
Я пишу исправления ниже, чтобы другим не пришлось проходить через ту же боль (ссылка на пример репозитория внизу этого сообщения).
Если вы следуете инструкциям Usage with Remix в официальной документации и пытаетесь запустить сервер, вы получаете эту ошибку:
Это происходит потому, что createStylesServer в @mantine/remix использует @emotion/server под капотом в качестве поставщика стилей. По состоянию на 29 апреля 2023 года эта реализация полагается на функции узла, к которым у работников Cloudflare нет доступа.
Оказалось, что функции node не требуются injectStyles, поэтому исправление здесь заключается в том, чтобы переписать createStylesServer без них:
// TL;DR: Mantine doesnt work with cloudflare workers because @emotion/server
// doesn't. Below is a polyfill without the node bits to get it working
//
// from this github issue: https://github.com/emotion-js/emotion/issues/2446#issuecomment-1372440174
import createCache from "@emotion/cache"
function createExtractCriticalToChunks(cache) {
return function (html) {
const RGX = new RegExp(`${cache.key}-([a-zA-Z0-9-_]+)`, "gm")
const o = { html, styles: [] }
let match
const ids = {}
while ((match = RGX.exec(html)) !== null) {
if (ids[match[1]] === undefined) {
ids[match[1]] = true
}
}
const regularCssIds = []
let regularCss=""
Object.keys(cache.inserted).forEach((id) => {
if (
(ids[id] !== undefined || cache.registered[`${cache.key}-${id}`] === undefined) &&
cache.inserted[id] !== true
) {
if (cache.registered[`${cache.key}-${id}`]) {
regularCssIds.push(id)
regularCss += cache.inserted[id]
} else {
o.styles.push({
key: `${cache.key}-global`,
ids: [id],
css: cache.inserted[id],
})
}
}
})
o.styles.push({ key: cache.key, ids: regularCssIds, css: regularCss })
return o
}
}
function generateStyleTag(cssKey, ids, styles, nonceString) {
return `<style data-emotion="${cssKey} ${ids}"${nonceString}>${styles}</style>`
}
function createConstructStyleTagsFromChunks(cache, nonceString) {
return function (criticalData) {
let styleTagsString=""
criticalData.styles.forEach((item) => {
styleTagsString += generateStyleTag(item.key, item.ids.join(" "), item.css, nonceString)
})
return styleTagsString
}
}
function createEmotionServer(cache) {
if (cache.compat !== true) {
cache.compat = true
}
const nonceString = cache.nonce !== undefined ? ` nonce="${cache.nonce}"` : ""
return {
extractCriticalToChunks: createExtractCriticalToChunks(cache),
constructStyleTagsFromChunks: createConstructStyleTagsFromChunks(cache, nonceString),
}
}Затем мы можем написать собственную версию createStylesServer :
// mantine-polyfill.js
// this is important for later
export const cache = createCache({key: "mantine"})
// own version of mantine's createStylesServer with same functionality but
// using the above createEmotionServer
export function createStylesServer() {
return createEmotionServer(cache)
}Это исправляет ошибку, и страница теперь отображается:
// entry.server.tsx
import { renderToString } from "react-dom/server"
import { RemixServer } from "@remix-run/react"
import type { EntryContext } from "@remix-run/cloudflare"
import { injectStyles } from "@mantine/remix"
// file we created above
import { createStylesServer } from "./mantine-polyfill"
const server = createStylesServer()
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
let markup = renderToString(<RemixServer context = {remixContext} url = {request.url} />)
responseHeaders.set("Content-Type", "text/html")
// we expect an error below because our server doesn't have the node-related
// methods we removed (which are expected by injectStyles)
return new Response(
`<!DOCTYPE html>${injectStyles(
markup,
// @ts-expect-error
server
)}`,
{
status: responseStatusCode,
headers: responseHeaders,
}
)
}Теперь мы столкнулись со второй проблемой: FOUC (вспышка нестилизованного содержимого), когда клиент повторно увлажняется сервером. Это приводит к нестабильной работе пользователя:

Это происходит потому, что теги <style> из рендеринга на стороне сервера не совпадают с рендерингом на стороне клиента. Поэтому, когда происходит повторное увлажнение, клиенту приходится снова загружать стили.
Мы решили эту проблему, убедившись, что при рендеринге на сервере и на клиенте используется один и тот же кэш.
// entry.client.tsx
import { ClientProvider } from "@mantine/remix"
import { RemixBrowser } from "@remix-run/react"
import { startTransition, StrictMode } from "react"
import { hydrateRoot } from "react-dom/client"
import { cache } from "./mantine-polyfill"
// we pass in the cache to ClientProvider
function hydrate() {
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<ClientProvider emotionCache = {cache}>
<RemixBrowser />
</ClientProvider>
</StrictMode>
)
})
}
if (typeof requestIdleCallback === "function") {
requestIdleCallback(hydrate)
} else {
// Safari doesn't support requestIdleCallback
// https://caniuse.com/requestidlecallback
setTimeout(hydrate, 1)
}import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react"
import { MantineProvider, Navbar, AppShell, createEmotionCache } from "@mantine/core"
import { StylesPlaceholder } from "@mantine/remix"
import { IconBook, IconHome } from "@tabler/icons-react"
import type { LinksGroupProps } from "./components/navbar-links-group"
import { LinksGroup } from "./components/navbar-links-group"
import { Notifications } from "@mantine/notifications"
import { cache } from "./mantine-polyfill"
// ... other code
export default function App() {
// we pass in the same cache to MantineProvider using the emotionCache prop
return (
<html lang="en">
<head>
<StylesPlaceholder />
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<MantineProvider withGlobalStyles withNormalizeCSS emotionCache = {cache}>
<Notifications position="top-right" />
<AppShell padding="md" navbar = {<AppNavbar />}>
<Outlet />
</AppShell>
<ScrollRestoration />
<Scripts />
<LiveReload />
</MantineProvider>
</body>
</html>
)
}Теперь клиент прекрасно рендерит без вспышки.
Тем не менее, остается одна досадная проблема. Если мы проверим страницу, которая возвращается после первого GET на сервер, то увидим следующее:

Мы видим, что глобальные стили (включенные с помощью параметра withGlobalStyles в MantineProvider) отображаются внутри <body> вместо <head>. Это заняло у меня много времени, чтобы понять - это сводится к тому, что мы не рендерим Emotion CacheProvider при первом рендере сервера.
Мы исправили это, отредактировав наш entry.server.tsx следующим образом:
import { renderToString } from "react-dom/server"
import { RemixServer } from "@remix-run/react"
import type { EntryContext } from "@remix-run/cloudflare"
import { injectStyles } from "@mantine/remix"
// add import for the cache from earlier
import { createStylesServer, cache } from "./mantine-polyfill"
// import CacheProvider from emotion
import { CacheProvider } from "@emotion/react"
const server = createStylesServer()
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
// Wrap the server in the cache provider and set the cache
let markup = renderToString(
<CacheProvider value = {cache}>
<RemixServer context = {remixContext} url = {request.url} />
</CacheProvider>
)
responseHeaders.set("Content-Type", "text/html")
return new Response(
`<!DOCTYPE html>${injectStyles(
markup,
// @ts-expect-error
server
)}`,
{
status: responseStatusCode,
headers: responseHeaders,
}
)
}Теперь глобальные стили отображаются в <голове>, как мы и хотели:

Успех! Теперь у нас есть плавный рендеринг и супербыстрая производительность Remix, как мы и ожидали:

Точный код можно найти в этом репо: https://github.com/nastynaz/mantine-remix-cloudflare

20.08.2023 18:21
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в 2026-2027 годах? Или это полная лажа?".

20.08.2023 17:46
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.

19.08.2023 18:39
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в частности, магию поплавков и гибкость flexbox.
19.08.2023 17:22
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для чтения благодаря своей простоте. Кроме того, мы всегда хотим проверить самые последние возможности в наших проектах!

18.08.2023 20:33
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий их языку и культуре.

14.08.2023 14:49
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип предназначен для представления неделимого значения.