Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(module): reduce css bundle size by fixing safelist regex #2005

Merged
merged 5 commits into from
Aug 2, 2024

Conversation

yuzh2001
Copy link
Contributor

@yuzh2001 yuzh2001 commented Aug 1, 2024

πŸ”— Linked issue

Resolves #877

❓ Type of change

  • πŸ“– Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • πŸ‘Œ Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

πŸ“š Description

TLDR: By replacing all safelist rules, the bundle size shrink from 250kb to 80kb.

The bundle size problem has bothered community a long time. Even a cleanest page with only importing UCard and UContainer (as in /playground) would generate a ~250kb pagesize which heavily effect the SEO. The reason for using Nuxt is to enjoy the rich feature it provides in SSR and boosts SEO performance. Therefore, it's needed to fix this bug.

However, it appears to be the result of having so many components. The solution would be to manually exclude some components. However, after taking a look at the bundled css, it appears that the biggest part is color definitions.
Tailwindcss should be able to purge all useless colors by default. Therefore, there must be some bugs in Nuxt-ui safelisting.

As mentioned, nuxt-ui safelists a lot of colors, this could be the problem. However, only a few color definition won't take up 250kb.

Looking into the bundled files, the true reason lies:

.bg-primary-100{--tw-bg-opacity:1;background-color:rgb(var(--color-primary-100)/var(--tw-bg-opacity))}.bg-primary-100/0{background-color:rgb(var(--color-primary-100)/0)}.bg-primary-100/10{background-color:rgb(var(--color-primary-100)/.1)}.bg-primary-100/100{background-color:rgb(var(--color-primary-100)/1)}.bg-primary-100/15{background-color:rgb(var(--color-primary-100)/.15)}.bg-primary-100/20{background-color:rgb(var(--color-primary-100)/.2)}.bg-primary-100/25{background-color:rgb(var(--color-primary-100)/.25)}.bg-primary-100/30{background-color:rgb(var(--color-primary-100)/.3)}.bg-primary-100/35{background-color:rgb(var(--color-primary-100)/.35)}.bg-primary-100/40{background-color:rgb(var(--color-primary-100)/.4)}.bg-primary-100/45{background-color:rgb(var(--color-primary-100)/.45)}.bg-primary-100/5{background-color:rgb(var(--color-primary-100)/.05)}.bg-primary-100/50{background-color:rgb(var(--color-primary-100)/.5)}.bg-primary-100/55{background-color:rgb(var(--color-primary-100)/.55)}.bg-primary-100/60{background-color:rgb(var(--color-primary-100)/.6)}.bg-primary-100/65{background-color:rgb(var(--color-primary-100)/.65)}.bg-primary-100/70{background-color:rgb(var(--color-primary-100)/.7)}.bg-primary-100/75{background-color:rgb(var(--color-primary-100)/.75)}.bg-primary-100/80{background-color:rgb(var(--color-primary-100)/.8)}.bg-primary-100/85{background-color:rgb(var(--color-primary-100)/.85)}.bg-primary-100/90{background-color:rgb(var(--color-primary-100)/.9)}.bg-primary-100/95{background-color:rgb(var(--color-primary-100)/.95)}.bg-primary-400{--tw-bg-opacity:1;background-color:rgb(var(--color-primary-400)/var(--tw-bg-opacity))}.bg-primary-400/0{background-color:rgb(var(--color-primary-400)/0)}.bg-primary-400/10{background-color:rgb(var(--color-primary-400)/.1)}.bg-primary-400/100{background-color:rgb(var(--color-primary-400)/1)}

Each color breakpoint generates ten different opacity classes!

src/runtime/utils/colors.ts:20-210 defines the safelist regexp. Here lies the bugs.

As an untold configuration, the regexp should not be defined without ^ at the beginning and $ at the end.
tailwindcss/Add support for alpha values in safelist

  alert: (colorsAsRegex) => [{
    pattern: RegExp(`bg-(${colorsAsRegex})-50`)
  },

should be fixed to be:

  alert: (colorsAsRegex) => [{
    pattern: RegExp(`^bg-(${colorsAsRegex})-50$`)
  },

By replacing all safelist rules, the bundle size shrink from 250kb to 80kb.

Checks:

  • All colors behave normal.
  • Docs behave normal, hot replacement of theme color works fine.

πŸ“ Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@yuzh2001
Copy link
Contributor Author

yuzh2001 commented Aug 2, 2024

(base) ➜  bundle_test git:(dev) βœ— wget https://ui-3sq8udeao-nuxt-js.vercel.app/
--2024-08-02 08:50:04--  https://ui-3sq8udeao-nuxt-js.vercel.app/
Resolving ui-3sq8udeao-nuxt-js.vercel.app (ui-3sq8udeao-nuxt-js.vercel.app)... 240.0.8.37
Connecting to ui-3sq8udeao-nuxt-js.vercel.app (ui-3sq8udeao-nuxt-js.vercel.app)|240.0.8.37|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 420265 (410K) [text/html]
Saving to: 'index.html'

index.html                              100%[=============================================================================>] 410.42K  1.88MB/s    in 0.2s    

2024-08-02 08:50:05 (1.88 MB/s) - 'index.html' saved [420265/420265]

(base) ➜  bundle_test git:(dev) βœ— wget https://ui.nuxt.com
--2024-08-02 08:50:13--  https://ui.nuxt.com/
Resolving ui.nuxt.com (ui.nuxt.com)... 240.0.7.129
Connecting to ui.nuxt.com (ui.nuxt.com)|240.0.7.129|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: 'index.html.1'

index.html.1                                [  <=>                                                                         ]   1.25M  3.71MB/s    in 0.3s    

2024-08-02 08:50:14 (3.71 MB/s) - 'index.html.1' saved [1316486]

from 1.25M to 410K.

@yuzh2001
Copy link
Contributor Author

yuzh2001 commented Aug 2, 2024

Further work may be to completely change the color system. Make the choices static and hard-code all different themes.

Additionally, user may change appConfig.ui.colors to a smaller array to shrink size more.

@benjamincanac benjamincanac changed the title fix: reduce css bundle size by correcting the configuration of tailwindcss safelist. fix(module): reduce css bundle size by fixing tailwindcss safelist config Aug 2, 2024
@benjamincanac
Copy link
Member

Thanks a lot @yuzh2001! It never occurred to me that the generated colors with opacity modifiers were caused by the regex syntax πŸ˜…

@yuzh2001
Copy link
Contributor Author

yuzh2001 commented Aug 2, 2024

Thanks a lot @yuzh2001! It never occurred to me that the generated colors with opacity modifiers were caused by the regex syntax πŸ˜…

so happy to make contributions! nuxi ui is an excellent project with best design. Enjoy using it and appreciate your hard work. cant be happier to be a part of it.

@benjamincanac
Copy link
Member

benjamincanac commented Aug 2, 2024

Just to clarify your previous comment, I might have misunderstood but how is appConfig.ui.colors going to shrink the bundle size? If you talk about this:

safelist: generateSafelist(${JSON.stringify(moduleOptions.safelistColors || [])}, ${JSON.stringify(nuxt.options.appConfig.ui.colors)}),

It doesn't take user's app.config.ts into account there and by default the safelistColors in nuxt.config.ts only defaults to ['primary'] so the only way would be to remove some colors from tailwind.config.ts I guess.

@yuzh2001
Copy link
Contributor Author

yuzh2001 commented Aug 2, 2024

I may be wrong since I didn't test it.

the code here: generateSafelist(${JSON.stringify(moduleOptions.safelistColors || [])}, ${JSON.stringify(nuxt.options.appConfig.ui.colors)}),

the second parameters accept an array for colors. these colors will then be used to ...safelistByComponent['notification'](colorsAsRegex(globalColors)),, since export const generateSafelist = (colors: string[], globalColors: string[]) => { the function definition is that second argument is globalColor.

the main part of color definition is the color safelist. and the default globalColors is a long array
safelist: generateSafelist(["primary"], ["red","orange","amber","yellow","lime","green","emerald","teal","cyan","sky","blue","indigo","violet","purple","fuchsia","pink","rose","primary"]),, therefore, by changing the appConfig.ui.colors, it should shrink the array, therefore shrink the output css.

for example, I only use 'primary', 'red' and 'yellow'.

to be clear, I haven't test above assumption. It may be wrong.(But I do think it's promising since if I choose green to be my primary color, I wouldn't use blue that often.)


I've change my configuration to be:
ui: { colors: ['red', 'orange', 'green', 'blue', 'indigo', 'primary'],

I don't know if this will work. I guess it will work.()

sorry for the uncertainty.

@yuzh2001
Copy link
Contributor Author

yuzh2001 commented Aug 2, 2024

I did question that whether the arguments are passed by the wrong order. But I'm not that certain.

@benjamincanac
Copy link
Member

It won't work as we don't have access to user's app.config at this point. The colors from nuxt.options.appConfig.ui.colors are coming from https://github.com/nuxt/ui/blob/dev/src/tailwind.ts#L29.

This is the main reason why we can't implement something like #1883.

@benjamincanac
Copy link
Member

To go back to your PR, I just tried a simple app.vue with a <UButton color="red" /> and it seems modifiers are not safelisted (no effect on hover for example) but it works when using safelistColors: ['red'] which is weird because it's using the same safelist.

There might be a conflict with the new syntax and this code: https://github.com/nuxt/ui/blob/dev/src/runtime/utils/colors.ts#L321-L330

@yuzh2001
Copy link
Contributor Author

yuzh2001 commented Aug 2, 2024

To go back to your PR, I just tried a simple app.vue with a <UButton color="red" /> and it seems modifiers are not safelisted (no effect on hover for example) but it works when using safelistColors: ['red'] which is weird because it's using the same safelist.

There might be a conflict with the new syntax and this code: https://github.com/nuxt/ui/blob/dev/src/runtime/utils/colors.ts#L321-L330

I did ignore this problem, sorry.

Tried to update the code you mentioned. Adding a <Ubutton color="red" />, it seems ok to me.

Please let me know if there are any more errors.

@yuzh2001
Copy link
Contributor Author

yuzh2001 commented Aug 2, 2024

It won't work as we don't have access to user's app.config at this point. The colors from nuxt.options.appConfig.ui.colors are coming from https://github.com/nuxt/ui/blob/dev/src/tailwind.ts#L29.

This is the main reason why we can't implement something like #1883.

cool. My assumption is false then. sorry for the irresponsible guess TAT

@yuzh2001
Copy link
Contributor Author

yuzh2001 commented Aug 2, 2024

The idea is that since extract focus on extracting the real classnames used, it should not consider ^ and $. therefore, let's remove em.

@benjamincanac benjamincanac changed the title fix(module): reduce css bundle size by fixing tailwindcss safelist config fix(module): reduce css bundle size by safelist regex Aug 2, 2024
@benjamincanac benjamincanac changed the title fix(module): reduce css bundle size by safelist regex fix(module): reduce css bundle size by fixing safelist regex Aug 2, 2024
@benjamincanac
Copy link
Member

It seems to fix the issue! Thanks a lot 😊

@benjamincanac benjamincanac merged commit 8ac9ca4 into nuxt:dev Aug 2, 2024
1 check passed
davestewart added a commit to davestewart/nuxt-ui that referenced this pull request Aug 2, 2024
* dev:
  fix(module): reduce css bundle size by fixing safelist regex (nuxt#2005)
  fix(useFormGroup): app config default input size (nuxt#2011)
binhtranhuu pushed a commit to binhtranhuu/nuxt-ui that referenced this pull request Aug 3, 2024
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
@muhammedjenos
Copy link

Is there any documentation available how to implement this?

@yuzh2001
Copy link
Contributor Author

yuzh2001 commented Sep 2, 2024

Is there any documentation available how to implement this?

Above fix is implemented by the nuxt-ui framework. There should be no further configuration needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Reduce Tailwind CSS bundle size for unused components
3 participants