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

RFC/refactor: code-split ui package #234

Merged
merged 8 commits into from
Feb 29, 2024
Merged

RFC/refactor: code-split ui package #234

merged 8 commits into from
Feb 29, 2024

Conversation

tefkah
Copy link
Contributor

@tefkah tefkah commented Feb 22, 2024

  • refactor: split the code
  • 'chore: update @preconstruct/cli and typescript versions for compatibility
  • chore: update imports
  • fix: use correct styles.css imports

Issue(s) Resolved

I had a hunch that the ui package was not being tree shaken, which was right!

Currently, bc of the way the barrel file was structured, every dependency in ui was loaded anytime anything from ui was imported.

This meant that every single radix component and the whole uppy package were imported if you so much as used a button on a page.

Not good! While it makes "only" a difference of ~150kb rn, this would just keep going up the more packages we add to ui.

Test Plan

See if app still works

Screenshots (if applicable)

Notes/Context/Gotchas

The "RFC" here is mostly about: do you think this slightly less ergonomic way importing/exporting UI components is worth the tradeoff in bundle size? I think it is.

The biggest gain is had by not importing the uppy styles and everything on every page like previously.

Alternatives

So after doing I figured out that setting sideEffects: false in the ui package.json can produce similar results.

However I did not test this thoroughly and might have unintended side-effects (har har).
The uppy modules require importing some css, which is a side effect, so I'm not sure if this will effect it negatively.

If you are hesitant about this code-splitting approach, this might be a slightly easier way to achieve smaller bundles.

Here are some numbers (gzipped) for core (badly formatted sorry)

Route (app)                                Size     First Load JS
┌ λ /                                      399 B           178 kB
├ ○ /_not-found                            886 B           178 kB
├ λ /c/[communitySlug]                     168 B           177 kB
├ λ /c/[communitySlug]/integrations        2.38 kB         193 kB
├ λ /c/[communitySlug]/members             168 B           177 kB
├ λ /c/[communitySlug]/pubs                1.14 kB         351 kB
├ λ /c/[communitySlug]/pubs/[pubId]        309 B           350 kB
├ λ /c/[communitySlug]/search              168 B           177 kB
├ λ /c/[communitySlug]/settings            168 B           177 kB
├ λ /c/[communitySlug]/stages              10.6 kB         239 kB
├ λ /c/[communitySlug]/stages/[slug]       168 B           177 kB
├ λ /c/[communitySlug]/stages/manage       3.55 kB         219 kB
├ λ /c/[communitySlug]/types               5.17 kB         190 kB
├ ○ /confirm                               168 B           177 kB
├ λ /docs                                  499 kB          683 kB
├ ○ /forgot                                5.16 kB         214 kB
├ λ /login                                 5.51 kB         217 kB
├ λ /pubs/[pubId]                          398 B           178 kB
├ ○ /reset                                 5.3 kB          214 kB
├ λ /settings                              10.8 kB         229 kB
└ λ /signup                                5.35 kB         191 kB

As you can see, very similar number to this approach (see tables below)
Except for /c/[communitySlug]/pubs/*, no idea why things aren't being shaken there! Looking at the bundle analyzer it's including all the uppy stuff, which is weird bc I could not find it being imported anywhere.

Submissions benefits from this greatly as well, this is even better than in this PR.

Route (app)                              Size     First Load JS
┌ λ /                                    137 B            83 kB
├ λ /_not-found                          878 B          83.8 kB
├ λ /actions/submit                      4.19 kB         136 kB
└ λ /configure                           2.68 kB         120 kB

Evaluations not so much.

"Theory"

So, why does this work? Or rather, why did this not work before?

Dunno! Something something side effects.
Big discussion here: vercel/next.js#12557

Changes to preconstruct settings

The changes to preconstruct to make this work are

  1. Added an entry point for every importable component
    https://github.com/pubpub/v7/blob/46facaa434a1ba18e9ef5e0970912b8c55ef0ec2/packages/ui/package.json#L237-L265

  2. Added the preconstruct field to package.json with in extra all the non-compiled files you want to export (such as styles) and path aliases

https://github.com/pubpub/v7/blob/46facaa434a1ba18e9ef5e0970912b8c55ef0ec2/packages/ui/package.json#L216-L236

Importing

To import, you now need to import from ui/\<entrypoint\> instead of ui.

In most cases this is not a big deal, like here in the PubHeader
https://github.com/pubpub/v7/blob/46facaa434a1ba18e9ef5e0970912b8c55ef0ec2/core/app/c/%5BcommunitySlug%5D/pubs/PubHeader.tsx#L1-L6

But sometimes this leads to slightly more hairy imports like here in the StageForm, where you need to import everything from individual places

https://github.com/pubpub/v7/blob/46facaa434a1ba18e9ef5e0970912b8c55ef0ec2/core/app/c/%5BcommunitySlug%5D/stages/manage/StageForm.tsx#L3-L14

I think this is a fine tradeoff, auto import still works.

🆕 How to add a new component

  1. Create a file in src, something like src/component.tsx
  2. Add that file to package.json['entryPoints'] as component.tsx
  3. Add component to the package.json['files'] array
  4. In packages/ui, run pnpm preconstruct fix && pnpm preconstruct dev
  5. Everything should be good!

❌ Downsides

The three biggest downsides are

  1. More verbose import syntax
  2. More involved component adding process
  3. Way uglier file structure in packages/ui

We went over 1 & 2 above, I personally don't think they are dealbreakers.

I do really dislike the file structure, I think it might have something to do with how "bad" preconstruct is at dealing with types. As you can see in e.g. packages/ui/button/package.json, it just links to a number of .d.ts files in packages/ui/button/dist.

I think this has something to do with how preconstruct allows you to link back to your .ts files instead of your .d.ts files on Go to definition, but idk.

Preconstruct does have a newer experimental module output, which has a much more sensical folder structure, but having tried that for something else and it not working I was hesitant to do that here.

Supporting Docs

Changes are summarized in table below, using Next.js output size reporting (which was double checked with the output of @next/bundle-analyze)

Suprisingly, the pages seem to get somewhat larger, but the size of the first JS load is significantly reduced for a lot of pages.

All numbers are gzipped.

Core

Route (app) Size (Before) Size (After) Size Change First Load JS (Before) First Load JS (After) Load Change Net Load Change
λ / 396 B 399 B 0.76% 178 kB 178 kB 0.00% 0.00 kB
/_not-found 883 B 886 B 0.34% 178 kB 178 kB 0.00% 0.00 kB
λ /c/[communitySlug] 162 B 168 B 3.70% 177 kB 177 kB 0.00% 0.00 kB
λ /c/[communitySlug]/integrations 4.48 kB 3.9 kB -12.95% 348 kB 190 kB -45.40% -158.00 kB
λ /c/[communitySlug]/members 162 B 168 B 3.70% 177 kB 177 kB 0.00% 0.00 kB
λ /c/[communitySlug]/pubs 2.27 kB 2.38 kB 4.85% 349 kB 194 kB -44.41% -155.00 kB
λ /c/[communitySlug]/pubs/[pubId] 0.289 kB 7.16 kB 2377.52% 344 kB 204 kB -40.70% -140.00 kB
λ /c/[communitySlug]/search 162 B 168 B 3.70% 177 kB 177 kB 0.00% 0.00 kB
λ /c/[communitySlug]/settings 162 B 168 B 3.70% 177 kB 177 kB 0.00% 0.00 kB
λ /c/[communitySlug]/stages 7.2 kB 12.4 kB 72.22% 367 kB 237 kB -35.42% -130.00 kB
λ /c/[communitySlug]/stages/[slug] 161 B 168 B 4.35% 177 kB 177 kB 0.00% 0.00 kB
λ /c/[communitySlug]/stages/manage 3.86 kB 16.8 kB 335.23% 358 kB 216 kB -39.66% -142.00 kB
λ /c/[communitySlug]/types 4.15 kB 3.59 kB -13.49% 345 kB 188 kB -45.51% -157.00 kB
/confirm 162 B 168 B 3.70% 177 kB 177 kB 0.00% 0.00 kB
λ /docs 494 kB 499 kB 1.01% 683 kB 683 kB 0.00% 0.00 kB
/forgot 4.11 kB 2.98 kB -27.49% 369 kB 212 kB -42.55% -157.00 kB
λ /login 4.49 kB 3.33 kB -25.84% 372 kB 214 kB -42.47% -158.00 kB
λ /pubs/[pubId] 394 B 398 B 1.02% 178 kB 178 kB 0.00% 0.00 kB
/reset 4.24 kB 3.1 kB -26.89% 369 kB 212 kB -42.55% -157.00 kB
λ /settings 8.11 kB 8.49 kB 4.69% 375 kB 226 kB -39.73% -149.00 kB
λ /signup 4.33 kB 3.47 kB -19.86% 345 kB 188 kB -45.51% -157.00 kB

Submissionsa

Route (app) Size (Before) Size Change First Load JS (Before) First Load JS Change
λ / 141 B 141 B 177 kB 177 kB
λ /_not-found 881 B 881 B 178 kB 178 kB
λ /actions/submit 1.9 kB 15.1 kB 354 kB 228 kB
λ /configure 998 B 1.91 kB 353 kB 212 kB

Evaluations

Route (app) Size (Before) Size Change First Load JS (Before) First Load JS Change
λ / 142 B 141 B 177 kB 177 kB
λ /_not-found 885 B 881 B 178 kB 178 kB
λ /actions/evaluate 46.4 kB 44.2 kB 402 kB 361 kB
λ /actions/manage 4.02 kB 9.83 kB 358 kB 227 kB
λ /actions/respond 7.03 kB 8.76 kB 363 kB 209 kB
λ /configure 2.51 kB 21 kB 356 kB 238 kB

@tefkah tefkah changed the title refactor: code-split ui package RFC/refactor: code-split ui package Feb 22, 2024
@tefkah tefkah requested a review from 3mcd February 26, 2024 11:03
@tefkah tefkah requested review from kalilsn and qweliant February 29, 2024 13:37
@3mcd 3mcd merged commit 94e33c6 into main Feb 29, 2024
4 checks passed
@3mcd 3mcd deleted the tfk/ui-codesplitting branch February 29, 2024 15:41
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.

2 participants