43081j / postcss-lit

PostCSS syntax for extracting CSS from template literals inside JS/TS files
84 stars 6 forks source link

Can you provide a working example of postcss-lit with tailwindcss? #56

Open flipkickmedia opened 8 months ago

flipkickmedia commented 8 months ago

Ive been trying to get tailwindcss working with lit and postcss-lit seems to offer a way to be able to do this but the examples on the internet fail for me.

If you could show an example of how to get this setup Id appreciate it.

william-kerr commented 8 months ago

After failing to get postcss-lit working with rollup to transform css within static styles = css`` (which vite uses in case that's relevant), I have also come to the conclusion in this comment: https://github.com/43081j/postcss-lit/issues/23#issuecomment-1848389575

As a side note, I think there might be a fundamental problem with using Tailwind CSS with web components or Lit. In a typical tailwind.config.js file, you manage css purging with content: ['./src/*/...

  1. Let's say that you wanted to build 25 small chunks of js corresponding to 25 separate small web components that could be lazy loaded.
  2. Let's also say that html in web component 1 just contains px-1, component 2 contains px-2, component 3 contains px-3...

What you'll find when you look in the bundled file for a component, is that its css contains px-1, px-2, px-3... even though the component is only using one of those classes in its html.

If we just bundle all components into one js bundle and share the Tailwind css to prevent deploying duplicate Tailwind css, then we aren't taking advantage of the scalable nature of lazy loading many small independent web components.

After thinking about things, if it's hard to use PostCSS syntax inside of a Lit file, it might also be hard to purge Tailwind css for similar reasons. It seems like just using PostCSS (not Tailwind, so no purging) in css files imported into Lit components makes the most sense for now.

william-kerr commented 8 months ago

I got inspired by: https://github.com/mwmcode/rollup-plugin-lit-tailwindcss/blob/main/src/index.js

ChatGPT helped me come up with the following simple concept.

vite.config.ts:

import postcss from 'postcss'
import tailwindcss from 'tailwindcss'
import { defineConfig } from 'vite'
import tsconfigPaths from 'vite-tsconfig-paths'

function viteLitTailwind() {
  return {
    name: 'vite-lit-tailwind',
    async transform(code, id) {
      if (!id.endsWith('.ts')) return null

      const match = code.match(/css`([\s\S]*?@tailwind\s+\w+;[\s\S]*?)`/)
      if (match) {
        try {
          const result = await postcss([
            tailwindcss({
              content: [id]
            })
          ]).process(match[1], { from: undefined, to: undefined })
          if (result.css) {
            return code.replace(match[0], `css\`${result.css.replace(/`/g, '\\`')}\``)
          }
        } catch (error) {
          this.error('Error processing with Tailwind CSS: ', error)
        }
      }

      return null
    }
  }
}

export default defineConfig({
  plugins: [tsconfigPaths(), viteLitTailwind()]
})

lit-web-component.ts:

  render() {
    return html`
      <p class="px-2">Hello World</p>
    `
  }

  static styles = css`
    @tailwind utilities;
  `

It runs tailwindcss for each Lit component that contains a Tailwind directive (ex. @tailwind utilities;) in its first css``, passing in the TypeScript filename (ex. lit-web-component.ts) for purging based on just that file alone. In order to keep components light, it probably makes sense to abandon @tailwind base and components directives and only use utilities, to keep css to a minimum in each component. Since the vite plugin code is simple, you can keep adding more functionality whenever you run into a bug or limitation.

Here's how I could imagine using this:

  1. Use Tailwind CSS only inside of html in Lit components, and prefer to do most styling that way.
  2. If you need to write custom css, stick it in an external css file that's processed with PostCSS and autoprefixer, and import it into your Lit component.
43081j commented 8 months ago

in the projects i've used tailwind in, we basically split the styles out in all non-trivial cases

i.e. we have this:

// my-element.ts
class MyElement extends LitElement {
  static styles = [commonStyles];
}

// my-other-element.ts
class MyOtherElement extends LitElement {
  static styles = [
    commonStyles,
    css`some local styles that don't use tailwind`
  ];
}

// common-styles.ts
export const commonStyles = css`
  /* some tailwind mixins */
`;

the point being to share stylesheets across elements often. although tbh we have mostly moved into css files now

william-kerr commented 8 months ago

in the projects i've used tailwind in, we basically split the styles out in all non-trivial cases

i.e. we have this:

// my-element.ts
class MyElement extends LitElement {
  static styles = [commonStyles];
}

// my-other-element.ts
class MyOtherElement extends LitElement {
  static styles = [
    commonStyles,
    css`some local styles that don't use tailwind`
  ];
}

// common-styles.ts
export const commonStyles = css`
  /* some tailwind mixins */
`;

the point being to share stylesheets across elements often. although tbh we have mostly moved into css files now

Regarding just Tailwind though, you either have the Tailwind directives (ex. @tailwind/utilities;) mentioned once in your code base and use a purging glob like content: ['./src/**/*.ts'] and end up with a large amount of Tailwind css swapped out in place of that single @tailwind/utilities; which is shared by all components, or you actually put @tailwind/utilities; inside many components and have a purging glob like content: ['./src/lit-web-component.ts'] swap out just the Tailwind css used in that glob. I think if someone was going to have 1,000 components and use lazy loading, eventually they might find that the shared Tailwind css swapped out in place of their single @tailwind/utilities; was large, and their initial page load for the entry point would take longer to load, which is similar to the situation we are in currently with front-end monoliths.