Hi everyone ( first post on a forum, sorry for mistakes ), I'm building a library for handle my custom design system logic; I've searched a lot but it seems like a tailwind limitation, I have many components like TypographyDisplay that has props like size or fontColor that needs to be dynamically rendered basing on how the component is instantiated. I'm using Qwik framework but I found that problem in Next.JS too and I'm going to have a terrible headhache: I don't want to send all the class possibile combination in the css bundle, if I instantiate like this "<TypographyDisplay fontColor = "primary">Something</TypographyDisplay>" i only want to send .text-primary not all the theme fontColor combinations in bundle :( I'm using SSR to avoid those things, I've seen that I can purge files but I don't know how to purge SSR generated HTML...Qwik is also a resumable framework so it sends pure HTML to client, I find that TailwindCSS behaviour so frustrating, it should simply understand what class are going to be rendered in that occasion and make bundle on-the-fly...is not the JIT mode (that should be used as default) do something like this? Have I to switch to another CSS technology or use some meta-utils like twin.macro? thanks for the time spent.
package.json
{
"name": "my-qwik-basic-starter",
"description": "App with Routing built-in (recommended)",
"engines": {
"node": ">=15.0.0"
},
"private": true,
"scripts": {
"build": "qwik build",
"build.client": "vite build",
"build.preview": "vite build --ssr src/entry.preview.tsx",
"build.types": "tsc --incremental --noEmit",
"deploy": "echo 'Run \"npm run qwik add\" to install a server adapter'",
"dev": "vite --mode ssr",
"dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
"fmt": "prettier --write .",
"fmt.check": "prettier --check .",
"lint": "eslint \"src/**/*.ts*\"",
"preview": "qwik build preview && vite preview --host",
"start": "vite --host --mode ssr",
"qwik": "qwik"
},
"devDependencies": {
"@builder.io/qwik": "0.17.5",
"@builder.io/qwik-city": "0.1.0",
"@types/eslint": "8.21.0",
"@types/node": "^18.11.19",
"@types/node-fetch": "latest",
"@typescript-eslint/eslint-plugin": "5.51.0",
"@typescript-eslint/parser": "5.51.0",
"autoprefixer": "10.4.11",
"eslint": "8.33.0",
"eslint-plugin-qwik": "0.17.5",
"node-fetch": "3.3.0",
"postcss": "^8.4.16",
"prettier": "2.8.3",
"tailwindcss": "^3.1.8",
"typescript": "4.9.5",
"undici": "5.18.0",
"vite": "4.1.1",
"vite-tsconfig-paths": "3.5.0"
},
"dependencies": {
"@builder.io/sdk-qwik": "^0.1.7",
"dotenv": "^16.0.3"
}
}
tailwind.config.js
/** @type {import('tailwindcss').Config} */
const MDSysTypefaceDisplayLarge = require( "./src/designTokens/materialDesign/md.sys.typescale.display-large" ) ;
const MDSysTypefaceDisplayMedium = require( "./src/designTokens/materialDesign/md.sys.typescale.display-medium" ) ;
const MDSysTypefaceDisplaySmall = require( "./src/designTokens/materialDesign/md.sys.typescale.display-small" ) ;
// etc.
module.exports = {
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
theme: {
fontFamily : {
"display-large" : MDSysTypefaceDisplayLarge.font ,
"display-medium" : MDSysTypefaceDisplayMedium.font ,
"display-small" : MDSysTypefaceDisplaySmall.font ,
// etc.
} ,
fontSize : {
"display-large" : [ MDSysTypefaceDisplayLarge.size + "px" , {
lineHeight : MDSysTypefaceDisplayLarge.lineHeight + "px" ,
letterSpacing : MDSysTypefaceDisplayLarge.tracking + "px" ,
fontWeight : MDSysTypefaceDisplayLarge.weight
} ] ,
"display-medium" : [ MDSysTypefaceDisplayMedium.size + "px" , {
lineHeight : MDSysTypefaceDisplayMedium.lineHeight + "px" ,
letterSpacing : MDSysTypefaceDisplayMedium.tracking + "px" ,
fontWeight : MDSysTypefaceDisplayMedium.weight
} ] ,
"display-small" : [ MDSysTypefaceDisplaySmall.size + "px" , {
lineHeight : MDSysTypefaceDisplaySmall.lineHeight + "px" ,
letterSpacing : MDSysTypefaceDisplaySmall.tracking + "px" ,
fontWeight : MDSysTypefaceDisplaySmall.weight
} ] ,
// etc.
} ,
extend: {},
},
plugins: [],
};
UITypographyHeadingDisplay.tailwindcss.ts
import type { IUITypographyHeadingDisplay } from "../.." ;
import { EUIComponentColors, EUIComponentSizes } from "~/components/enums" ;
import { UITypographyHeadingTailwindCSSStylingStrategyHandler } from "~/components/ui/typography/headings" ;
export interface IUITypographyHeadingDisplayTailwindCSSStylingStrategyProps extends IUITypographyHeadingDisplay {}
export type IUITypographyTailwindCSSStylingStrategy = ( args : IUITypographyHeadingDisplayTailwindCSSStylingStrategyProps ) => string[] ;
export const UITypographyHeadingDisplayTailwindCSSStylingStrategyHandler : IUITypographyTailwindCSSStylingStrategy = ( {
size = EUIComponentSizes.MEDIUM ,
fontColor = EUIComponentColors.ON_SURFACE ,
isBold = false ,
isItalic = false ,
} ) => {
let _classNames : string[] = UITypographyHeadingTailwindCSSStylingStrategyHandler( {
fontColor ,
isBold ,
isItalic ,
} ) ;
const fontFaceClassName =
size === EUIComponentSizes.LARGE ? "font-display-large" :
size === EUIComponentSizes.MEDIUM ? "font-display-medium" :
size === EUIComponentSizes.SMALL ? "font-display-small" :
""
;
;
if ( fontFaceClassName !== "" ) _classNames = [
..._classNames ,
fontFaceClassName ,
] ;
const fontSizeClassName =
size === EUIComponentSizes.LARGE ? "text-display-large" :
size === EUIComponentSizes.MEDIUM ? "text-display-medium" :
size === EUIComponentSizes.SMALL ? "text-display-small" :
""
;
if ( fontSizeClassName !== "" ) _classNames = [
..._classNames ,
fontSizeClassName
] ;
return _classNames ;
} ;
UITypographyDisplay.tsx
import { component$ } from "@builder.io/qwik" ;
import type { IUITypographyHeadingDisplay } from "../" ;
import { UITypographyHeadingDisplayTailwindCSSStylingStrategyHandler } from "../" ;
import { QwikHTMLTypographyHeading } from "~/components/html/typography/concretes/heading" ;
import { QwikHTMLTypographyBoldDecorator } from "~/components/html/typography/decorators/bold" ;
import { QwikHTMLTypographyItalicDecorator } from "~/components/html/typography/decorators/italic" ;
import {
EUIComponentSizes ,
EUIComponentColors ,
} from "~/components/enums" ;
import { EUITypographyHeadingLevels } from "~/components/enums/typography/headings" ;
export interface IUITypographyHeadingDisplayQwikComponentProps extends IUITypographyHeadingDisplay {}
export const QwikUITypographyHeadingDisplay = component$( ( {
id = undefined ,
onClick = undefined ,
onChange = undefined ,
size = EUIComponentSizes.MEDIUM ,
fontColor = EUIComponentColors.ON_SURFACE ,
isBold = false ,
isItalic = false ,
HeadingLevel = EUITypographyHeadingLevels.H1 ,
copy = "" ,
} : IUITypographyHeadingDisplayQwikComponentProps ) => {
const _classNames = UITypographyHeadingDisplayTailwindCSSStylingStrategyHandler( {
size ,
fontColor ,
isBold ,
isItalic
} ) ;
const _QwikHTMLTypographyHeadingProps = {
id ,
onClick ,
onChange ,
classNames : _classNames.join( " " ) ,
HeadingLevel ,
} ;
return (
<QwikHTMLTypographyBoldDecorator isBold = { isBold } >
<QwikHTMLTypographyItalicDecorator isItalic = { isItalic } >
<QwikHTMLTypographyHeading { ..._QwikHTMLTypographyHeadingProps } >
{ copy }
</QwikHTMLTypographyHeading>
</QwikHTMLTypographyItalicDecorator>
</QwikHTMLTypographyBoldDecorator>
) ;
} ) ;
QwikHTMLTypographyHeading
import {
component$ ,
Slot ,
} from "@builder.io/qwik" ;
import type { IHTMLTypographyHeading } from ".." ;
import { EUITypographyHeadingLevels } from "~/components/enums/typography/headings" ;
export interface IHTMLTypographyHeadingQwikComponentProps extends IHTMLTypographyHeading {}
export const QwikHTMLTypographyHeading = component$( ( {
classNames = undefined ,
HeadingLevel = EUITypographyHeadingLevels.H1 ,
} : IHTMLTypographyHeadingQwikComponentProps ) => {
return (
<HeadingLevel class = { classNames } >
<Slot />
</HeadingLevel>
) ;
} ) ;
tried to set mode:"jit" but obv nothing changes
class = text-${color}
was the first attempt and obv not working without commenting classnames or adding them to safelist, far i've understand to use full classnames but laaarge bundle size if someone use a complex design system
ps: I've tried to extrapolate classNames into an external object and that object in an external file too and accessing it like colorStyles[color] but same result with dirty enumerator usage, I've tried to put the logic into QwikHTMLTypographyHeading class property but same result...
I want only to compile css classes with ssr pros like knowing the classname of a used component to have correct bundle size...
I find that TailwindCSS behaviour so frustrating, it should simply understand what class are going to be rendered in that occasion and make bundle on-the-fly...is not the JIT mode (that should be used as default) do something like this?
Tailwind is really not that type of tool, you can suggest as a new feature, but pretty sure this was already requested or there a plugin for that.
What I would suggest here is CSS custom properties for every class you might want to be customizable. Your color example can have the problem completely avoid by simply doing:
<div class="text-[var(--customColor, black)]"></div>
this allow customization either on the client or on the server without bothering with some fancy plugin to figure out the variants.
This also allow you to predefine the variants for color by setting the --customColor and not the tailwind classes.
Not sure I understood correctly but hope this helps.
Edit: Answering your comment.
You don't set the classes and you don't change the custom property name, the class is always "text-[var(--customColor, black)]"
You set the --customColor either by:
This way you don't need to define maps you just dynamically set whatever value. And if you need maps the mapping is directly the style value nothing to do with tailwind.
Tailwind is not for that use case. Maybe styled components would fit you better here, which allow this kind of dynamic behavior but with the burden of having styling possibly evaluated at runtime and at every render.