zanaptak / TypedCssClasses

A CSS class type provider for F# web development. Bring external stylesheet classes into your F# code as design-time discoverable compiler-verified properties.
MIT License
166 stars 8 forks source link

An example for working with the latest tailwind #15

Closed Darkle closed 11 months ago

Darkle commented 11 months ago

Hi, I thought I might post my approach to using the latest tailwind css with TypedCssClasses since things have changed in the latest version of tailwind. I thought others might benefit from this.

So the latest tailwind version isn't distributed as a complete file anymore. It now has a "just-in-time" compilation to help prevent shipping all the tailwind classes in order to minimize the .css file size. This of course is a problem for TypedCssClasses as it needs to read the classes from a .css file.

My approach is to use javascript in the tailwind config file to dynamically get all the tailwind classes from the .fs files and add it to the tailwind config safelist property - the safelist property makes sure to include the classes listed, which means TypedCssClasses can then pick it up in the file the tailwind cli outputs.

Here is my tailwind.config.cjs file:

const fs = require('fs')
const path = require('path')

// Taken from: https://learnwithparam.com/blog/get-all-files-in-a-folder-using-nodejs/
// Recursive function to get files
function getFilesRecursive(dir, files = []) {
  const fileList = fs.readdirSync(dir)
  for (const file of fileList) {
    const name = path.join(dir, file)
    if (fs.statSync(name).isDirectory()) {
      getFilesRecursive(name, files)
    } else {
      files.push(name)
    }
  }
  return files
}

function prePopulateSafeList() {
  // fsFolder is where all my .fs files live
  const fsFolder = path.join(__dirname, 'fs')
  const files = getFilesRecursive(fsFolder)

  const allClassesUsedIncDupes = files.flatMap(filePath =>
    fs
      .readFileSync(filePath)
      .toString()
      // 'tw.' comes from one of my .fs files: type tw = CssClasses<"public/css/build.css", Naming.Verbatim>
      .split('tw.')
      .map(str => {
        if (str.startsWith('``')) {
          return str.slice(0, str.lastIndexOf('``'))
        }
        // This is for ones like tw.flex where there is no `` in it. \s is a whitespace character including Space, Tab, Newlines.
        return str.slice(0, /\s/.exec(str)?.index)
      })
  )

  const allClassesUsedDeduped = [...new Set(allClassesUsedIncDupes)]

  return allClassesUsedDeduped
}

const tailwindConfig = {
  content: [<your stuff here>],
  safelist: [...prePopulateSafeList()],
  theme: { },
  variants: {
    extend: {},
  },
  plugins: [],
}

module.exports = tailwindConfig

Note: tw. comes from one of my .fs files: type tw = CssClasses<"public/css/build.css", Naming.Verbatim>

The only problem with this approach is that when I add new tailwind classes to my .fs files, the tailwind.config.cjs is already cached and wont run again. I get around that by using a watcher program to re-run the tailwind cli, which will in-turn re-load its config file. The only small down side is that it is slightly less performant than running tailwind --watch as it adds anywhere from 100-200 milliseconds to the build time as you are re-running the tailwind cli from scratch.

I chose nodemon as the watcher program, but you could use another watcher program:

nodemon --ext fs,css  --watch fs --watch css --exec "tailwindcss -i ./css/index.css -o ./public/css/build.css"

All up it may take 1-3 seconds for vscode to be updated when you add a tailwind class that has not yet been added to the css. This isn't as bad as it sounds as once you get going, you'll find you don't add many new tailwind classes that you have never used before.

Also, you may need to save a second time in vscode after a second to remove the red squiggly line.

I hope all that makes sense. :-)

kerams commented 9 months ago

Can you not point the provider to https://cdn.tailwindcss.com/3.3.5 (even though you then won't have access to your custom styles based on tailwind)?

Darkle commented 9 months ago

Yeah that's definitely a simpler option if you have no custom styles.

Darkle commented 6 months ago

So this is weird, but I have recently found that if I just change the content to:

content: ['./public/**/*.{mjs,html}', './fs/**/*.fs'],

it works fine on its own.