import hash from "@emotion/hash" import type { types } from "@babel/core" import { kebabCase } from "lodash" import type { BabelPlugin } from "./esbuild-babel" export const inlineCSSVariables = (): BabelPlugin<{ styles: { id: string; light: string; dark: string }[] }> => ({ types: t }) => ({ name: "inline CSS variables", visitor: { Program: { enter(_, state) { state.styles = [] }, exit(path, { styles }) { if (!styles.length) return const css = `body.light {${styles.map(s => `--${s.id}:${s.light}`).join(";")}}\n` + `body.dark {${styles.map(s => `--${s.id}:${s.dark}`).join(";")}}` path.node.body.unshift( t.importDeclaration([], t.stringLiteral(`data:text/css,${encodeURI(css)}`)), ) }, }, TaggedTemplateExpression(path, state) { function join(exp: types.Node): string[] | undefined { if (t.isIdentifier(exp)) return [exp.name] if (!t.isMemberExpression(exp) || !t.isIdentifier(exp.property)) return const prev = t.isIdentifier(exp.object) ? [exp.object.name] : join(exp.object) return prev ? [...prev, exp.property.name] : undefined } const { expressions: exps } = path.node.quasi for (const [i, exp] of exps.entries()) { if (t.isIdentifier(exp)) { if (exp.name === "DARK_MODE") { exps[i] = t.stringLiteral("body.dark &") } else if (exp.name === "LIGHT_MODE") { exps[i] = t.stringLiteral("body.light &") } continue } if ( t.isCallExpression(exp) && t.isIdentifier(exp.callee, { name: "color" }) && exp.arguments.length === 2 && t.isStringLiteral(exp.arguments[0]) && t.isStringLiteral(exp.arguments[1]) ) { const [light, dark] = (exp.arguments as babel.types.StringLiteral[]).map( arg => arg.value, ) const id = hash(`${light}-${dark}`) state.styles.push({ id, light, dark }) exps[i] = t.stringLiteral(`var(--${id})`) continue } let ids: string[] | undefined if ( t.isMemberExpression(exp) && t.isIdentifier(exp.property) && (ids = join(exp)) ) { const rest = ids.slice(1).join(".") if (ids[0] === "vars") { exps[i] = t.stringLiteral(`var(--${kebabCase(rest)})`) } if (ids[0] === "token") { exps[i] = t.stringLiteral(`var(--color-${kebabCase(rest)})`) } } } }, }, })