tss-react
Version:
Type safe CSS-in-JS API heavily inspired by react-jss
101 lines (100 loc) • 5.42 kB
JavaScript
/* eslint-disable @typescript-eslint/ban-types */
import * as React from "react";
import { CacheProvider as DefaultCacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";
import { assert } from "../tools/assert";
/**
* @see <https://docs.tss-react.dev/ssr/next>
* This utility implements https://emotion.sh/docs/ssr#advanced-approach
* */
export function createEmotionSsrAdvancedApproach(
/** This is the options passed to createCache() from 'import createCache from "@emotion/cache"' */
options,
/**
* By default <CacheProvider /> from 'import { CacheProvider } from "@emotion/react"'
*
* NOTE: The actual expected type is `React.Provider<EmotionCache>` but we use `Function` because
* to make it work regardless of if you are using React 18 or React 19 type def.
*/
CacheProvider = DefaultCacheProvider) {
const { prepend, ...optionsWithoutPrependProp } = options;
const appPropName = `${options.key}EmotionCache`;
const insertionPointId = `${options.key}-emotion-cache-insertion-point`;
function augmentDocumentWithEmotionCache(Document) {
var _a;
let super_getInitialProps = (_a = Document.getInitialProps) === null || _a === void 0 ? void 0 : _a.bind(Document);
if (super_getInitialProps === undefined) {
import("next/document").then(({ default: DefaultDocument }) => (super_getInitialProps =
DefaultDocument.getInitialProps.bind(DefaultDocument)));
}
const createEmotionServerOrPr = import("@emotion/server/create-instance").then(({ default: createEmotionServer }) => createEmotionServer);
Document.getInitialProps = async (documentContext) => {
const cache = createCache(optionsWithoutPrependProp);
const createEmotionServer = createEmotionServerOrPr instanceof Promise
? await createEmotionServerOrPr
: createEmotionServerOrPr;
const emotionServer = createEmotionServer(cache);
const originalRenderPage = documentContext.renderPage;
documentContext.renderPage = ({ enhanceApp, ...params }) => originalRenderPage({
...params,
"enhanceApp": (App) => {
var _a;
const EnhancedApp = (_a = enhanceApp === null || enhanceApp === void 0 ? void 0 : enhanceApp(App)) !== null && _a !== void 0 ? _a : App;
return function EnhanceApp(props) {
return (React.createElement(EnhancedApp, { ...{ ...props, [appPropName]: cache } }));
};
}
});
assert(super_getInitialProps !== undefined, "Default document not yet loaded. Please submit an issue to the tss-react repo");
const initialProps = await super_getInitialProps(documentContext);
const emotionStyles = [
React.createElement("style", { id: insertionPointId, key: insertionPointId }),
...emotionServer
.extractCriticalToChunks(initialProps.html)
.styles.filter(({ css }) => css !== "")
.map(style => (React.createElement("style", { nonce: options.nonce, "data-emotion": `${style.key} ${style.ids.join(" ")}`, key: style.key, dangerouslySetInnerHTML: { "__html": style.css } })))
];
const otherStyles = React.Children.toArray(initialProps.styles);
return {
...initialProps,
"styles": prepend
? [...emotionStyles, ...otherStyles]
: [...otherStyles, ...emotionStyles]
};
};
}
function withAppEmotionCache(App) {
const createClientSideCache = (() => {
let cache = undefined;
return () => {
if (cache !== undefined) {
return cache;
}
return (cache = createCache({
...optionsWithoutPrependProp,
"insertionPoint": (() => {
// NOTE: Under normal circumstances we are on the client.
// It might not be the case though, see: https://github.com/garronej/tss-react/issues/124
const isBrowser = typeof document === "object" &&
typeof (document === null || document === void 0 ? void 0 : document.getElementById) === "function";
if (!isBrowser) {
return undefined;
}
const htmlElement = document.getElementById(insertionPointId);
assert(htmlElement !== null);
return htmlElement;
})()
}));
};
})();
function AppWithEmotionCache(props) {
const { [appPropName]: cache, ...rest } = props;
return (React.createElement(CacheProvider, { value: cache !== null && cache !== void 0 ? cache : createClientSideCache() },
React.createElement(App, { ...rest })));
}
Object.keys(App).forEach(staticMethod => (AppWithEmotionCache[staticMethod] = App[staticMethod]));
AppWithEmotionCache.displayName = AppWithEmotionCache.name;
return AppWithEmotionCache;
}
return { withAppEmotionCache, augmentDocumentWithEmotionCache };
}