RFC/refactor: code-split ui package #234
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 fromui
was imported.This meant that every single
radix
component and the wholeuppy
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 theui
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)
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 theuppy
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.
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
settingsThe changes to preconstruct to make this work are
Added an entry point for every importable component
https://github.com/pubpub/v7/blob/46facaa434a1ba18e9ef5e0970912b8c55ef0ec2/packages/ui/package.json#L237-L265
Added the
preconstruct
field topackage.json
with inextra
all the non-compiled files you want to export (such as styles) and path aliaseshttps://github.com/pubpub/v7/blob/46facaa434a1ba18e9ef5e0970912b8c55ef0ec2/packages/ui/package.json#L216-L236
Importing
To import, you now need to import from
ui/\<entrypoint\>
instead ofui
.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 placeshttps://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
src
, something likesrc/component.tsx
package.json['entryPoints']
ascomponent.tsx
component
to thepackage.json['files']
arraypackages/ui
, runpnpm preconstruct fix && pnpm preconstruct dev
❌ Downsides
The three biggest downsides are
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 inpackages/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 onGo 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
Submissionsa
Evaluations