ui icon indicating copy to clipboard operation
ui copied to clipboard

Reduce Tailwind CSS bundle size for unused components

Open benjamincanac opened this issue 1 year ago • 27 comments

benjamincanac avatar Oct 27 '23 09:10 benjamincanac

Related to #802 do you want separate issues on reducing the unused colors (this could be an option parameter, even) and reducing bundle size for unused components?

jrutila avatar Oct 30 '23 06:10 jrutila

Removing unused colors is already done through Smart safelisting and I already answered here https://github.com/nuxt/ui/issues/802#issuecomment-1759280846 on how to select only desired colors, not sure why you want to create another issue.

benjamincanac avatar Oct 30 '23 09:10 benjamincanac

I read the docs and that's what nuxt/ui is trying to achieve. But it doesn't seem to work as intended so I created a bug: #889. We can continue this color discussion there and leave the ticket for the unused component thing.

jrutila avatar Oct 30 '23 14:10 jrutila

Sorry to jump in, but simply adding @nuxt/ui to an empty project creates an inline css of 243kb. Is that related to this issue or something that I'm missing from the documentation?

ronenteva avatar Oct 31 '23 21:10 ronenteva

It is related to this issue, the whole config is taken into account by Tailwind: https://github.com/nuxt/ui/blob/dev/src/runtime/ui.config.ts

benjamincanac avatar Oct 31 '23 21:10 benjamincanac

Is that ui.config file now available for Tailwind to do it's magic? Or does it do something more in nuxt/ui code? I am trying to scope the fix here. If nuxt/ui would know which components the dev has used in the code it could cherry-pick those definitions from the config file for Tailwind, right?

jrutila avatar Nov 01 '23 04:11 jrutila

Hello,

This is pretty serious affecting performance.

@Atinux can this pushed forward please?

Thanks!

divine avatar Nov 01 '23 11:11 divine

The build size is huge when using Static Site. Weirdly, NuxtUI is adding the tailwind styles inside a

raf202 avatar Nov 01 '23 13:11 raf202

@benjamincanac any timeline for this? It's labeled as enhancement but I'm sure for many this issue is a deal-breaker for using @nuxt/ui

ronenteva avatar Nov 07 '23 07:11 ronenteva

Hey! We're researching into this and hoping to come up with a breakthrough soon. 😄

ineshbose avatar Nov 07 '23 07:11 ineshbose

The build size is huge when using Static Site. Weirdly, NuxtUI is adding the tailwind styles inside a

@sigmaxf I think this is more of nuxt's features. Try to fiddle with https://nuxt.com/docs/guide/going-further/experimental-features#inlinessrstyles

jrutila avatar Nov 07 '23 13:11 jrutila

There should be a workaround available for this now, if someone would like to report back on this please.

See "Tailwind CSS bundle" section on release notes for https://github.com/nuxt/ui/releases/tag/v2.11.0.

ineshbose avatar Nov 23 '23 18:11 ineshbose

@ineshbose thanks! your help is highly appreciated!

I've tried excluding everything (on an empty project), and went down from 253kb to 185kb. Big improvement but still huge for production.

Just to understand the issue, is it not possible to load nuxt ui before cssnano?

Here's what I excluded, if it helps anyone:

      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/data/table.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/accordion.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/alert.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/avatar.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/avatarGroup.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/badge.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/button.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/buttonGroup.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/chip.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/dropdown.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/kbd.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/meter.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/meterGroup.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/progress.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/checkbox.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/radioGroup.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/radio.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/toggle.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/formGroup.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/select.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/textarea.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/selectMenu.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/range.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/input.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/layout/card.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/layout/container.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/layout/divider.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/layout/skeleton.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/navigation/breadcrumb.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/navigation/commandPalette.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/navigation/pagination.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/navigation/verticalNavigation.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/overlays/contextMenu.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/overlays/modal.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/overlays/notification.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/overlays/notifications.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/overlays/popover.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/overlays/slideover.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/overlays/tooltip.mjs')

ronenteva avatar Nov 24 '23 07:11 ronenteva

@ronenteva have you limited the amount of color entries in the final outcome? It has an impact, too. See #889

jrutila avatar Nov 24 '23 09:11 jrutila

@jrutila thanks! it's now down to 102kb

import colors from 'tailwindcss/colors';
import {resolve} from 'pathe';

export default {
  theme: {
    colors: {
      transparent: 'transparent',
      current: 'currentColor',
      black: colors.black,
      white: colors.white,
      gray: colors.gray,
      red: colors.red,
      green: colors.green
    }
  },
  content: {
    files: [
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/data/table.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/accordion.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/alert.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/avatar.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/avatarGroup.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/badge.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/button.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/buttonGroup.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/chip.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/dropdown.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/kbd.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/meter.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/meterGroup.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/elements/progress.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/checkbox.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/radioGroup.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/radio.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/toggle.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/formGroup.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/select.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/textarea.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/selectMenu.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/range.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/forms/input.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/layout/card.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/layout/container.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/layout/divider.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/layout/skeleton.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/navigation/breadcrumb.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/navigation/commandPalette.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/navigation/pagination.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/navigation/verticalNavigation.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/overlays/contextMenu.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/overlays/modal.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/overlays/notification.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/overlays/notifications.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/overlays/popover.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/overlays/slideover.mjs'),
      '!' + resolve('node_modules/@nuxt/ui/dist/runtime/ui.config/overlays/tooltip.mjs')
    ]
  }
};

ronenteva avatar Nov 24 '23 10:11 ronenteva

Just to understand the issue, is it not possible to load nuxt ui before cssnano?

cssnano should include all the styles from NuxtUI after build.

ineshbose avatar Nov 24 '23 13:11 ineshbose

Let me add another solution to this problem: @nuxtjs/critters

This should remove unused styles from your page, but few things to note - load this module BEFORE @nuxt/ui, and it only works for generate/prerendered pages.

ineshbose avatar Nov 24 '23 13:11 ineshbose

@ineshbose Will the critters solution work for ssr:false and npm run generate project?

ManasMadrecha avatar Dec 02 '23 04:12 ManasMadrecha

Will the critters solution work for ssr:false and npm run generate project?

It needs SSR enabled. Rest, you can exclude files from Tailwind content (a for-loop or even a Nuxt module would be quite elegant) for now.

ineshbose avatar Dec 09 '23 15:12 ineshbose

image How painful it is to look at this in 2024

iamrevolver avatar Feb 18 '24 20:02 iamrevolver

@benjamincanac @ineshbose I'm sorry to bump the issue but I want to make sure you guys understand that anyone who relies on SEO can't afford using @nuxt/ui until this is fully resolved.

ronenteva avatar Feb 24 '24 10:02 ronenteva

@ineshbose

I'm sorry to bump the issue but I want to make sure you guys understand that anyone who relies on SEO can't afford using @nuxt/ui until this is fully resolved.

Hey! Thanks for pinging - this issue is still in mind. Unfortunately content detection is a tricky part of Tailwind JIT and a component library based on Tailwind would have such limitations. We'll have to come up with some ground breaking stuff to have dynamic content based on import (contributions welcome) which all of TW community would benefit from, but till then we have a workaround available.

ineshbose avatar Feb 24 '24 10:02 ineshbose

Let me add another solution to this problem: @nuxtjs/critters

This should remove unused styles from your page, but few things to note - load this module BEFORE @nuxt/ui, and it only works for generate/prerendered pages.

Do you mind sharing an example of how to use critters to remove the unused styles? I couldn't get it to make any difference.

Another workaround is to use nuxt-purgecss and safelist all classes in the components in use.

ronenteva avatar Feb 26 '24 10:02 ronenteva

@ronenteva Excluding all of the config files did not do the trick?

benjamincanac avatar Feb 26 '24 14:02 benjamincanac

@benjamincanac I stopped using it at some point but I honestly can't remember what wasn't working. I'll give it another try and let you know.

Update: I tested again, adding @nuxt/ui to a clean project and excluding all components, still adds 114kb of inline style.

ronenteva avatar Feb 27 '24 03:02 ronenteva

If dark mode is not needed, you can save another 30k by setting:

nuxt.config.js

  colorMode: {
    preference: 'light'
  }

tailwind.config.js

  darkMode: []

ronenteva avatar Feb 27 '24 05:02 ronenteva

Providing a workaround:

  1. first, build you app and launch it.
  2. curl http://localhost:3000 (for example)
  3. copy the longest
  4. save it to a seperate css file and upload it to cdn
  5. in nuxt.config.js/ts, set tailwindcss:{cssPath:false}, and in app:{head:{link:{... import the css on cdn

significantly drop from ~700kb to ~160kb

(the 160kb is because another ui library I use.

yuzh2001 avatar Apr 27 '24 06:04 yuzh2001

Any updates on this? Or is there a way for nuxt ui to only bundle tailwind classes from used nuxt ui components? This defeats the purpose of tailwind.

ralph-burstsms avatar Jul 24 '24 03:07 ralph-burstsms

Any updates on this? Or is there a way for nuxt ui to only bundle tailwind classes from used nuxt ui components? This defeats the purpose of tailwind.

nope.

yuzh2001 avatar Jul 24 '24 03:07 yuzh2001

Any updates on this? Or is there a way for nuxt ui to only bundle tailwind classes from used nuxt ui components? This defeats the purpose of tailwind.

The key problem is that even if you only import the tailwind classes used, it's still a lot of classes considering how many tailwind classes are used in nuxt-ui. There is just no way around.

  1. SSR needs the css embed in the webpage itself rather than importing another css file.
  2. NuxtUI uses a bunch of tailwind classes.

I just gave up. There is little difference between 70kb and 700kb nowadays.

yuzh2001 avatar Jul 24 '24 03:07 yuzh2001