Search code examples
javascriptwebpackpreactemotion

Emotion.js with Preact SSR and RTL support


I have a preact SSR app and have used Emotion JS 10 for styling.

I wanted to add RTL support with this so have used createEmotion and createEmotionServer and used the resulting renderStylesToString to render the app.

However while creating createEmotion it requires the plugins to be used for which i added the stylis-rtl plugin but this would always apply the RTL styles while i want the RTL styles to be applied on the basis of each request.

I could not find any way to tell emotion to apply and get the styles for RTL on each request basis. They do have straight forward implementation for React 16 in which you can pass multiple caches for each request to the CacheProvider.

But i can't seem to figure this out for Preact.

One solution could be to serve different webpack builds for RTL but that would be unnecessary overhead.

EDIT 1: for anyone looking to implement something similar, this is my approach

Client Side as well as the css imports on the server:

import stylisRTL from 'stylis-rtl';

import createEmotion from 'create-emotion';

const {
  cx: cxRTL,
  injectGlobal: injectGlobalRTL,
  css: cssRTL,
  cache: cacheRTL,
  keyframes: keyframesRTL
} = createEmotion({
  key: 'c',
  stylisPlugins: [stylisRTL]
});
const {
  cx: cxLTR,
  injectGlobal: injectGlobalLTR,
  css: cssLTR,
  cache: cacheLTR,
  keyframes: keyframesLTR
} = createEmotion({
  key: 'c',
  stylisPlugins: []
});

const runForBoth = (rtlFn, ltrFn) => (...args) => {
//this would be ur store sent in html to check whether it is in rtl or ltr mode
  const isRTL = typeof window !== 'undefined' && window.__PRELOADED_STATE__.shell.RTL;
  let result;
  if (__BROWSER__) {
    if (isRTL) {
      result = rtlFn(...args);
    } else {
      result = ltrFn(...args);
    }
  } else {
    result = ltrFn(...args);
    rtlFn(...args);
  }

  return result;
};

export const cx = runForBoth(cxRTL, cxLTR);
export const injectGlobal = runForBoth(injectGlobalRTL, injectGlobalLTR);
export const css = runForBoth(cssRTL, cssLTR);
export const keyframes = runForBoth(keyframesRTL, keyframesLTR);

export const cacheEmotionLTR = cacheLTR;
export const cacheEmotionRTL = cacheRTL;

and for SSR:


const { renderStylesToString: renderStylesToStringLTR } = createEmotionServer(cacheEmotionLTR);
const { renderStylesToString: renderStylesToStringRTL } = createEmotionServer(cacheEmotionRTL);

i create 2 cache and based on request headers i decide which renderStylesToString to use

The one concern i have is runForBoth is technically wrong. But it is working as of now.

I did not want to change the way css Fn is imported as now i can just use this custom-emotion imports and alias it in webpack


Solution

  • If you want to make this per request then you should create 2 emotion instances (one with the plugin and one without it). Additionally, you would have to use correct instance for each request - the easiest way for this would be probably with context: put emotion in the context and use it from there instead of from import statement.