Skip to content

Tree-shaking/Code splitting doesn't with react-aria-components #60246

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

Closed
samcx opened this issue Jan 5, 2024 Discussed in #60206 · 14 comments · Fixed by #62238
Closed

Tree-shaking/Code splitting doesn't with react-aria-components #60246

samcx opened this issue Jan 5, 2024 Discussed in #60206 · 14 comments · Fixed by #62238
Labels
bug Issue was opened via the bug report template. locked

Comments

@samcx
Copy link
Member

samcx commented Jan 5, 2024

Discussed in #60206

Originally posted by IonelLupu January 4, 2024

Summary

We started to use react-aria-components in our NextJS project and we encountered something strange: when using just a simple <Button> from react-aria-components, the entire 140kb+ react-aria-components package is imported in the app as seen in the next-bundle-analyzer report below:

image

At first, I thought this is a problem with how the files are exported from react-aria-components.

I don't think this is the case, because a Vite React project shows it only imports under 40kb of react-aria-components packages as seen in the vite-bundle-visualizer report below:

image

Is next-bundle-analyzer outputting wrong data or is there a problem with the NextJS builder?

Following this issue, I came across other two opened issues:

Reading these I noticed that using 'use client' in every page does bring the react-aria-components package size down to ~40kb:
image

image

You might thing the issue is solved, but nooo.

As you will notice in the NextJS repos I created to replicate the issue and I listed below, the "/" page is using a simple <Button> and the "/other-page" is using a more complex <Select> component. Still, the "/" page that is using only a <Button> contains all of the modules for the <Select> component EVEN IF the "/" page is not using the <Select> component at all. 🤯

Here is another example where I have a page with a <Table> from react-aria-components (I didn't add this page in the repo, but you get the idea). The bundle size goes crazy to around ~140kb:
image

In order to see this yourself, clone the NextJS repo linked below. From there, check these scenarios:

Scenario 1: run npm run build. In the "client" bundle analyzer browser tab, after you select the "app/page" file, you will see the bundle have around 36kb. This contains both the Button and Select modules:
image

Scenario 2: delete the entire "other-page" folder and run npm run build again. In the "client" bundle analyzer opened tab, after you select the "app/page" file. Notice the bundle size for react-aria going down to around ~15kb. It contains only the modules for . Below are some screenshots for both the "app/page" page and "app/other-page/page" page:

image

image

Scenario 3: remove the 'use client' from the "app/page.tsx" file and run npm run build again. In the "client" bundle analyzer opened tab, after you select the "app/page" file. Notice the bundle size going up to ~140kb for react-aria. It contains the ENTIRE "react-aria-components" library
image

Using modularizeImports doesn't work for react-aria-components, but I think this is a react-aria-components issue:
image

Also, using "sideEffect": false doesn't do anything either.


I don't know what happens, but it seems NextJS is summing up all the used imports and uses that FOR EVERY SINGLE PAGE that uses react-aria-components.

It should split the react-aria-component library based on every component used for that particular page.

Here are the repos you can use to see the difference:

Additional information

$ next info  

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.3.0: Fri Dec  1 03:17:00 PST 2023; root:xnu-10002.80.11~58/RELEASE_ARM64_T6000
Binaries:
  Node: 18.18.2
  npm: 9.8.1
  Yarn: 1.22.21
  pnpm: N/A
Relevant Packages:
  next: 14.0.5-canary.37
  eslint-config-next: 14.0.4
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.3.3
Next.js Config:
  output: N/A

Example

https://github.com/IonelLupu/react-aria-button-bundle-size-nextjs

@IonelLupu
Copy link

@samcx any news about this?

@samcx
Copy link
Member Author

samcx commented Jan 10, 2024

@IonelLupu I haven't been able to yet! Will let you know when I dig into this :blob_bowing:

@IonelLupu
Copy link

@samcx I can help with this. Let me know some quick steps to begin with: where to look (folders, files).

It might be something with the webpack terser where it doesn't remove the dead code from node_module files. But not sure

@samcx
Copy link
Member Author

samcx commented Jan 25, 2024

@IonelLupu Thanks for assisting! I was able to replicate the large bundles with your :repro:.

Even tried using optimizePackageImports to no success. We may have an issue with our Server Component bundling. We'll be taking a deeper look at this!

@IonelLupu
Copy link

@samcx let me know if I can help with a PR.

We may have an issue with our Server Component bundling

Based on its name ('Server Component bundling'), it seems that resolving this will only address the bundle size for server components. Is there a "Client Component bundling" that needs to be fixed too? Because that has issues as well. It's also explained in the issue above here (although, in not a great way as it lacks some words. Sorry 😅 ), but I also explained it here: adobe/react-spectrum#5639.

Long story short: if you use, for example, a Button on page A (let's say 10kb in size) and then a Table and DatePicker on page B (let's say these have a total of 80kb), you would expect the difference in bundle size between page A and page B to be around 70kb, right?

In reality, it's not like that. Now, both page A and page B now have a bundle size of 90kb: the combined value. Not sure if this is also a NextJS issue or a react-aria-components issue

@samcx
Copy link
Member Author

samcx commented Feb 6, 2024

@IonelLupu Apologies for the late response!

We're still taking a look at this. We can confirm that there is an issue with bundling when you import a Client Component (from the other thread, I see that the react-aria-components package is using use client) into a Server Component—this shouldn't be happening! I can also confirm this only occurs in that scenario. A package shouldn't be limited to use server to have proper bundling.

Will circle back when we have more updates on this.

@devongovett
Copy link

FYI, we decided to remove "use client" from react-aria-components and instead import "client-only" to enforce that it is only usable from a client component. In our case, the components all require some kind of event handler so weren't really usable from a server component anyway. Adding "client-only" improves the error message, and moves the responsibility of adding "use client" to the consumer. See adobe/react-spectrum#5826 for more explanation. I guess this is still a bundling issue, but our change side steps it for now.

@samcx
Copy link
Member Author

samcx commented Feb 7, 2024

@devongovett Thank you for the clarification!

@IonelLupu
Copy link

thank you @devongovett . We were able to shave almost 20% of bundle size. 🥳

One caveat though: we also use buttons inside <Link> elements. The Button has no javascript events attached to it. It's there just for design purposes and it functions as a link to other pages. The workaround was to mark our custom Button component as a client component using use client because the <Link> was in a server component.

@federicocappellotto97
Copy link

Also noticed this in our company's project, the bundle jumps when we import any of react-aria-components.
We're using "next": "^14.1.0" and pages directory.
These are the sizes of our two pages, one is empty, one has a form with only a text input field.
Screenshot 2024-02-19 at 09 59 40

@IonelLupu
Copy link

@federicocappellotto97 did you use the latest version of react-aria-components. They kinda fix the problem

@federicocappellotto97
Copy link

Yes, we're using:
"react-aria": "^3.32.1", "react-aria-components": "^1.1.1",

@huozhi
Copy link
Member

huozhi commented Feb 20, 2024

We're cooking an optimization solution (PR #62238) for the cases like react-aria-components import, after this being landed nextjs will be able to do tree-shaking optimization to the direct imports from client components, if there's no shared component as barrel file in between.

Sharing some testing result:

If we compare the react-aria-components the reproduction from #60246, you'll see the result being optimized a lot, 134KB being tree-shaked out

After

Route (app)                              Size     First Load JS
┌ ○ /                                    324 B           127 kB
├ ○ /_not-found                          872 B          86.5 kB
└ ○ /other-page                          174 B           127 kB

Before

Route (app)                              Size     First Load JS
┌ ○ /                                    325 B           261 kB
├ ○ /_not-found                          870 B          86.5 kB
└ ○ /other-page                          176 B           261 kB

huozhi added a commit that referenced this issue Feb 20, 2024
…onent module (#62238)

### What & Why

This PR helps fixes a long time tree-shaking issue that if you're import
some identifiers from client components, the whole client component is
being included in the client chunk. Because we're using import eager
mode in webpack to include all the client component modules that make
sure they're present in SSR entry and browser entry when they're
transformed to client reference on RSC layer.

But the way we collect client components is a bit "aggressive" where
contains some spaces to optimize.

### How

We change the collected client components from simpliy collecting it
resolved module request, into collecting both the imported identifiers
(by server components) and the module request. And when we inserted the
used client components imports into the SSR and Client entry, leverage
webpack magic comments `"webpackExports"` to only contain the used
exports in the bundle. Thank you webpack for this nice feature : )

Along the way we also fixed an issue that when you only used default
export, the `default` export itself should also be proxied when the
bundle is in ESM.

#### Notice

There's a limitation yet that it can't work with barrel file, if you
have a shared component `index.js` to re-export the changes several
client components there and you only partially import few from
`index.js` it won't work. For the cases that the node_modules package
contain a barrel file importing multiple client components, please use
[optimizePackageImports](https://nextjs.org/docs/app/api-reference/next-config-js/optimizePackageImports)
config for now. We'll have follow up optimizations

### Testing Result

If we compare the `react-aria-components` the reproduction from #60246,
you'll see the result being optimized a lot:

#### After vs Before

134KB being tree-shaked out 🤯 
```
Route (app)                              Size     First Load JS
┌ ○ /                                    324 B           127 kB
├ ○ /_not-found                          872 B          86.5 kB
└ ○ /other-page                          174 B           127 kB
```

```
Route (app)                              Size     First Load JS
┌ ○ /                                    325 B           261 kB
├ ○ /_not-found                          870 B          86.5 kB
└ ○ /other-page                          176 B           261 kB
```

Fixes #60246
Related report: adobe/react-spectrum#5639
Closes NEXT-2527
phase 1 of NEXT-1799

---------

Co-authored-by: Shu Ding <g@shud.in>
Copy link
Contributor

github-actions bot commented Mar 6, 2024

This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot added the locked label Mar 6, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 6, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Issue was opened via the bug report template. locked
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants