tailwindlabs / discuss

A place to ask questions, get help, or share what you've built with Tailwind CSS.
MIT License
171 stars 9 forks source link

Use @apply, then remove utilities/unused classes #150

Open ernsheong opened 6 years ago

ernsheong commented 6 years ago

Hi all,

I understand that @apply can only be used to import classes that it can "see".

I come from the perspective of web components, particularly using the Shadow DOM.

Hence I would like to generate classes from @tailwind utilities using @apply, and then subsequently remove the utilities file after the class generation, as I want to lose the weight, and also not let the browser do more parsing work than it needs to. Otherwise each Shadow DOM component that I ship would contain an exact duplicate of all of Tailwind's CSS.

I guess a corollary to this question is that is there a way for Tailwind (or some other tool) to parse the DOM and remove unused classes?

Is there a way to do the above?

adamwathan commented 6 years ago

I personally use Purgecss like we talk about in the documentation:

https://tailwindcss.com/docs/controlling-file-size#removing-unused-css-with-purgecss

evenfrost commented 6 years ago

Actually, I've been struggling with @apply thing for a while, though otherwise having great times with Tailwind. We're using Tailwind + Vue for remaking UI of a pretty large project (was Angular 1.x + Semantic UI), and this is the issue (not an issue probably but rather workflow) I'd be happy to see worked out (and I'm all ready to assist with).

I hope @adamwathan can guide me better if this is not the right place to discuss it. So, we're using Vue's single file components and adding @import "tailwindcss/utilities"; to each <style> block where we need to use @apply. This adds whole utilities CSS to every Vue component. Later on, we remove redundant styles with PurgeCSS (along with OptimizeCssAssetsPlugin and stuff). All works great, but our webpack build is taking ages, and yet growing. First build time is about 2-2.5 mins, subsequent builds are about 10 secs, and we have just 30-40 components. I've removed CSS part from webpack for testing purposes, and it took about 6 secs for full build. So, I assume this is because utilities being imported in every component. Importing utilities file into just entry file doesn't help, @apply errors start showing up.

I'm following all the issues like https://github.com/tailwindcss/tailwindcss/issues/449, https://github.com/tailwindcss/tailwindcss/issues/231 and https://github.com/tailwindcss/tailwindcss/pull/169#issuecomment-343549111, but all these are closed, and as for now I don't see a right place to discuss this problem.

I'd be happy to hear your suggestions about it Adam, I really love Tailwind CSS and overall your awesome work with Steve on Refactoring UI. Thank you!

adamwathan commented 6 years ago

I'm planning to work on making @apply work without having the CSS that you are trying to apply exist in the same CSS tree next month, but I'm worried the performance isn't really going to get better.

The reason for this is that right now the way people have their Vue projects like yours configured is that PostCSS runs all of your plugins completely independently for every single <style> block in your project. Tailwind has to spin up and run from scratch on every single component, as does autoprefixer, etc.

This means that even if I make it possible to @apply classes that aren't actually defined in that CSS tree, Tailwind still needs to generate those classes silently, then look up the matching class. So I fear that even after I get this working, Tailwind still needs to do the same amount of work for every single <style> block as it does right now, and because each block is processed completely independently, I'm not sure if I can even do any sort of intelligent caching or anything.

I'm personally still not convinced this wouldn't be better solved using a Webpack configuration. I don't have enough Webpack experience to suggest how to do this myself, but it seems to me that the real problem is that if you have 100 components that all have <style> blocks, PostCSS runs 100 times. It makes way more sense to me for PostCSS to run on a single CSS tree once; that being all of the styles from your whole project combined into one tree, then being run through PostCSS before those styles are inserted into the document.

If someone could figure out how to do that, we wouldn't need to change Tailwind at all, everything would just work.

The other thing worth mentioning is that in general, I'm not sure what the benefit of even using @apply is inside of a Vue component. The point of that feature is to avoid duplication in your templates, but if you have already componentized something at the Vue level, there's no downside to just using the utilities directly in your markup.

Can you give an example of how you are using @apply inside of a Vue component and why it's better than just using the utilities directly in the HTML?

evenfrost commented 6 years ago

Thank you for detailed answer, I see the whole problem now.

As for why we're using @apply instead of raw utility classes in markup, I think, it can be boiled down to a few points:

  1. Pseudo-selectors, pseudo-attributes, adjacent selectors etc. can be styled only with @apply, inlining just doesn't work here.
  2. It makes layout much more clean and readable, as I can understand which part of layout belongs to which part of component. Same for dynamic classes mapping via :class directive and, probably, some other cases.
  3. I can style vendor components (e.g. vue-multiselect) with @apply rules, following the same style guide from tailwind.config.js we've fine-tuned for our project.

Probably this is not the best example, but here is one of our components using selectors for some more-advanced-than-usual layout stuff:

<template>
  <div class="segment-placeholder">
    <div class="segment-placeholder-icon">
      <slot
        name="icon"
      ></slot>
    </div>

    <div
      v-if="title"
      class="segment-placeholder-title"
    >{{ title }}</div>
  </div>
</template>

<script lang="ts">
  import Vue from 'vue';

  export default Vue.extend({
    props: {
      title: {
        type: String,
        default: '',
      },
    },
  });
</script>

<style lang="postcss" scoped>
  @import "@/styles/utilities.pcss";

  .segment-placeholder {
    @apply .text-grey .text-center .mt-12;

    & .segment-placeholder-icon {
      & + .segment-placeholder-title {
        @apply .mt-4;
      }
    }

    &:not(:last-child) {
      @apply .mb-12;
    }

    &:last-child {
      @apply .mb-6;
    }
  }
</style>
evenfrost commented 6 years ago

May be related: https://github.com/vuejs/vue-loader/issues/328.

ernsheong commented 6 years ago

I'm planning to work on making @apply work without having the CSS that you are trying to apply exist in the same CSS tree

@adamwathan I think that would be a great start!

evenfrost commented 6 years ago

Hi @adamwathan, just wondering if there is any update on this? I'm currently stuck with our dev bundle being 45 MB with 200 .vue files due to utilities inclusion for using @apply in most components. We also have workflow for removing duplicate styles and optimizing the bundle size with purgecss-webpack-plugin, optimize-css-assets-webpack-plugin, cssnano and folks but this way our dev build takes 10 minutes for first call and about 50s for every change, which is pretty daunting.

adamwathan commented 6 years ago

@evenfrost I released v0.6.2 (but install v0.6.3, v0.6.2 had a major bug) today which includes support for this behind an experimental flag:

https://github.com/tailwindcss/tailwindcss/releases/tag/v0.6.2#allow-applying-classes-that-arent-defined-but-would-be-generated

Please test it out and let me know what you think.

ernsheong commented 6 years ago

FWIW the interim approach I took was to split up my utilities into multiple small JS files and include them as needed into my web components. No the most DRY but removes the bloat.

adamwathan commented 6 years ago

@ernsheong Try that new shadowLookup option and let me know how it works for you.

evenfrost commented 6 years ago

@adamwathan sorry for the delay, all working great! With shadowLookup we reduced our dev bundle size from 50 MB to 5 MB, and build times are greatly improved as well. :tada:

mattkoch614 commented 5 years ago

@adamwathan Will the shadowLookup option still work in the 1.x releases (beta, etc.)?

adamwathan commented 5 years ago

That is all enabled by default since I think 0.6 or 0.7, you don't need the experiments config at all anymore 👍🏻