ben-rogerson / twin.macro

๐Ÿฆนโ€โ™‚๏ธ Twin blends the magic of Tailwind with the flexibility of css-in-js (emotion, styled-components, solid-styled-components, stitches and goober) at build time.
MIT License
7.87k stars 184 forks source link

Support tailwind plugins (e.g. custom-forms) #7

Closed lukasluecke closed 4 years ago

lukasluecke commented 4 years ago

I tried to use https://tailwindcss-custom-forms.netlify.com but seems like it can't find the new classes.

โœ• โ€œform-inputโ€ was not found in the Tailwind config.

ben-rogerson commented 4 years ago

Hey Lukas Twin only converts Tailwind classes to object styles and plugins aren't supported... yet. It's a feature I really want to add and it's on the TODO list. Cheers

lukasluecke commented 4 years ago

Yeah that's what I figured as well, no worries. If there's anything I can do to help let me know ๐Ÿ™‚

vladferix commented 4 years ago

I would also vote for this feature, cause this seems to be the thing preventing me to migrate from tailwind.macro to twin

ben-rogerson commented 4 years ago

@vladferix Twin stemmed from tailwind.macro so there shouldn't be a difference in the way it handles plugins. If you've tailwind.macro loads the plugin correctly (and doesn't in twin) could you please let me know the plugin so I can look into it further?

vladferix commented 4 years ago

@ben-rogerson in my tailwind.config I write something like this

plugins: [
    require("tailwindcss-transitions")(),
    require("tailwindcss-transforms")(),
    function({ addUtilities }) {
      const newUtilities = {
        ".type-sm": {
          fontSize: fontSize.sm,
          fontWeight: fontWeight.medium,
          lineHeight: lineHeight.tight
        }
      }
      addUtilities(newUtilities);
    }
]

I got the error:

 โœ• โ€œtype-smโ€ was not found in the Tailwind config.

  Learn more: https://www.npmjs.com/package/twin.macro
     at /node_modules/twin.macro/macro.js:2194:13
     at Array.reduce (<anonymous>)
     at getStyles (/node_modules/twin.macro/macro.js:2170:33)
     at /node_modules/twin.macro/macro.js:2349:38
     at Array.forEach (<anonymous>)
     at twinMacro (/node_modules/twin.macro/macro.js:2339:22)
     at macroWrapper (/node_modules/babel-plugin-macros/dist/index.js:63:12)
     at applyMacros (/node_modules/babel-plugin-macros/dist/index.js:230:14)
     at ImportDeclaration (/node_modules/babel-plugin-macros/dist/index.js:114:28)
     at NodePath._call (/node_modules/@babel/core/node_modules/@babel/traverse/lib/path/context.js:55:20)

The external plugins are complaining in the same fashion In tailwind.macro the following structure is fully working

ben-rogerson commented 4 years ago

that's interesting, let me do some digging and I'll get back to you

tsaiDavid commented 4 years ago

@ben-rogerson - just wanted to point out this would be useful in allow folks to utilize TailwindUI specific styles too!

https://www.notion.so/Tailwind-UI-Documentation-f9083ed0e2694690ac89253e88afb2b6

ben-rogerson commented 4 years ago

@tsaiDavid Expect a Twin release within the next few days - I'm aiming to have plugin support in there ๐Ÿ‘

ben-rogerson commented 4 years ago

Okay guys, we now have support for custom utilities in your plugin section in twin.macro@1.0.0-alpha.5. Full support for Tailwind plugins will take a lot more time to get working. Read the release notes

zcei commented 4 years ago

Hej @ben-rogerson I should have read that those last messages earlier.

After solving this tiny number bug in #8 I was a bit hooked and discovered that twin.macro does not use tailwindcss to actually do it's job. I think that's fine in the beginning, but parity with the tool it suggest to use would be nice.

So I had a bit of time during the weekeend and spiked potential solutions. The first attempt was rather easy, but involved copying quite a bit from tailwind, which is not desirable from a feature parity point of view.

So for now I came up with two potential solutions, and I saw you using one of them partially by now.

Both of them have in common to use the PostCSS AST, which is the right format for us to get all the knowledge we might need. Tailwind itself proposes itself to be a PostCSS plugin and while this macro doesn't neceessarily need to support other postcss-* plugins, we should use what Tailwind plugins use under the hood and is consumed by: postcss

1. Simple but heavy

Basically we just create a small document just inserting taiwind.

const template = postcssJs.parse({
  '@tailwind base': true,
  '@tailwind components': true,
  '@tailwind utilities': true,
  // potentiall @tailwind screens, but it auto-injected by tailwind
})

Then use processTailwindFeatures on this. This basically provides with a full, denormalized, exhaustive set of CSS, still in its reified AST form.

This plugin could built a lookup map like here or just simple rule walkers to find the rules we're interested in.

By walking the parent, or even using another (potentially selfmade?) plugin we could then extract media queries (I'm really shit when it comes to css, are there any other @rules we need to account for?) and if the rule is nested in one generate the JS accordingly.

Advantages

Disadvantages

2. Tailwind where tailwind is due

This solution focusses more on Tailwind and might even yield a few PRs for Tailwind to export / refactor things if needed.

It still relies heavily on the tailwindcss package but pickes only the good parts we want.

We would start with having the processing the plugins like you already did.

(Here we could optimize in the future by having Tailwinds plugins actually lazy rendering, or exposing components definitions better. But nothing we need to care about right now)

Now that we have everything in the object as needed there is just something to understand from a Tailwind perspective:

utilities are wrapped in variant atRules. Components don't get auto-generated variants but may be wrapped and configured.

Overall you can think about it like displayed here, just in CSS/AST:

plugin(function({ addComponents }) {
  addComponents({
    '@variants responsive, hover': {
      '.btn': {
        padding: '.5rem 1rem !important',
        borderRadius: '.25rem !important',
        fontWeight: '600 !important',
      },
      // ...
    }
  })
})

This makes it really convenient to stop here in the "normalized" set of rules and further work on it. (We might want to replace theme() calls in values).

This is done once at startup of the macro and would ideally persist (cache somehow or can we rely on Node runtime?)

The resulting AST as CSS looks like this:

@variants responsive {
  .tw-italic {
    font-style: italic;
  }
  .tw-not-italic {
    font-style: normal;
  }
}

@variants responsive, hover, focus {
  .tw-text-transparent {
    color: transparent;
  }
  .tw-text-black {
    color: #000;
  }
  .tw-text-white {
    color: #fff;
  }
  .tw-text-gray-100 {
    color: #f7fafc;
  }
  /* ... */

  .tw-text-red-100 {
    color: #fff5f5;
  }
  /* ... */
}

Now to the macro "runtime" does the following:

When resolving a set of classes to apply like sm:bg-green md:bg-yellow xl:bg-red sm:hover:text-light we can resolve this to it'a variants. responsive is special in the @variants and will resolve to the config.screens keys as accepted prefix values.

e.g.:

`sm:hover:text-light` 
// => { utility: 'text-light', variants: ['sm', 'hover'] }

Bby looking on the @variant atRules in the AST for the utility we can find out whether all of the class names resolve to allowed versions (we can even expand specificity by ignoring the order of variant params, except for responsive. By design it should always be outer-most.)

(We can then use the information from the variant generator functions if needed to build an optimized subset of CSS by using the utility value as the only rule input to wrap. This may be postponed to when it's needed.)

What we end upt with are CSS rules & declarations we can then expose via postcss-js as the output of the macro.

Advantages

Disadvantages


After one of those is done, we have all the ulities the tailwind.config.js (plus default config) defined.

Yes, this means you don't need the duplication within ./config anymore! :tada:)

This also means deactivating not used utilites in corePlugins will work as expected, didn't research whether that's currently the case with this macro.

But in the latest state of knowledge it looks like you generate the processed plugins over and over again, containing the defaults all the time, and never make use that default rules are plugin based. Every change to these has to be manually reflected by you in your config folder and maybe even code, right? (No complain here, just trying to remember all of my selling points :laughing: )

Oh, and btw I tested this with custom-forms over the weekend to make sure external plugins are working. Didn't yell and saw properly prefixed, and properly non-varianted, form classes in the output.

And just one more thing, it would need its own ticket: having the AST it's a breeze to get us TypeScript typings (which even helps vanilla JS users when they're for example in VS Code).

lukasluecke commented 4 years ago

@zcei Sounds awesome! Thanks for your work on this ๐Ÿš€

I had some of the same ideas regarding actually "running" tailwindcss but no time to work on it. Out of your proposed solutions I would be in favor of the 2nd one, if there is no issue with any of the most popular plugins (i.e. your 2nd "disadvantage" doesn't affect them).

zcei commented 4 years ago

@lukasluecke I just looked on NPM and there seem to be quite some plugins. Do you have a list, or would be willing to create one, that I can turn into e2e dependencies? I have no clue which ones are really important to the ecosystem.

For context, I'm really not a design or CSS experienced person at all. This is why I like the rise of style systems so much: I don't need to know all of this as long as I can express simple intents of "this should look like something darkish blue and should be three items per row" ๐Ÿ™ƒ

My area of interest is more on backend and language transform side of thing, so I thought I could be of help here. I just need proper input - usually that's @axe312ger job, feel free to chime in ๐Ÿ˜‰

ben-rogerson commented 4 years ago

Hey zcei, thanks for looking into this issue further. I'd prefer the second option more also and I'm really interested in the idea of removing the need for the ./config files and letting Twin rely more upon tailwindcss for parsing the tailwind config. You mentioned you tried the idea with custom-forms - would you have any code to take a look at?

zcei commented 4 years ago

@ben-rogerson it's the infamous weekend coding night messy state, but I think I could get a rough versions together towards the weekend.

Scope would be using tailwindcss core plugins instead of the config (but without inlined media queries for now, just as a stretch goal) and custom-forms as en example of a community plugin.

Those two things are basically solved together in there and kinda go hand in hand, so I think it would take more time separating this from each other.

adrianjacob commented 4 years ago

Great work on getting 'addUtilities' live in Alpha 5 :)

Is there an eta for 'addComponents'?

vladferix commented 4 years ago

Yes, now it is working pretty awesome in our project, looking forward to further support!

ben-rogerson commented 4 years ago

Is there an eta for 'addComponents'?

Sorry no eta yet - I'll keep you posted

fvanwijk commented 4 years ago

Since plugin support is still WIP, could you update the docs on what is supported and what not? Maybe also add a list of "to do" items including links to GH issues.

All variants, all the time

Looks a bit misleading to me, since addVariants is not supported yet ;) At least, that is what I found out when I tried to use the dark-mode plugin

ben-rogerson commented 4 years ago

Yeah, I'll add some more details shortly about the current plugin support. For the list of todos, take a look at GitHub Projects.

ben-rogerson commented 4 years ago

So I've been making progress on plugin support, shouldn't be too long now. Here's the summary:

ben-rogerson commented 4 years ago

v1.4.0 is up with support for addComponents ๐ŸŽ‰ I've also started listing plugins and showing twin support.

I'll close this topic for now as addVariant isn't going to be supported anytime soon.

skoshy commented 3 years ago

@ben-rogerson What's the technical reason for not being able to support addVariant?

Also I know twin adds in many more bartenura, but what should the workaround be if we'd like to use some new custom variants to use with twin?

ben-rogerson commented 3 years ago

@ben-rogerson What's the technical reason for not being able to support addVariant?

It's been a while since I looked at this, but I remember there being issues due to the differences in tailwind who generates all the styles ahead of time vs twin that generates the css after you use a class. I think it's probably worth another look on my part to see if I can come up with another solution.

The only workaround at the moment would be to generate the classes with the variants in a tailwind plugin or use a javascript function that adds the variant(s) with vanilla css instead. If you'd like some help with this, perhaps it's best to open a new discussion including some examples of what you'd like to do here.

ben-rogerson commented 3 years ago

Just a quick note to anyone interested here - I've opened up a discussion about adding addVariant support.