refine icon indicating copy to clipboard operation
refine copied to clipboard

[FEAT] Reduce bundle size in Next.js (with Material UI)

Open amirhhashemi opened this issue 2 years ago • 5 comments

Is your feature request related to a problem? Please describe.

The bundle size of my Refine app built with Next.js and Material UI is very big and the app is very slow:

┌   /_app                                  0 B             551 kB
├ λ /[[...refine]]                         249 B           551 kB
└ ○ /404                                   184 B           551 kB
+ First Load JS shared by all              551 kB
  ├ chunks/framework-305dbdef56d67aa9.js   45.4 kB
  ├ chunks/main-a8eaa706e962a5d6.js        27.9 kB
  ├ chunks/pages/_app-1e8eb2bf899090b0.js  476 kB
  └ chunks/webpack-44567bfec3a37904.js     1.05 kB

I think there is room for improvement.

Describe alternatives you've considered

No response

Additional context

No response

Describe the thing to improve

1.Out of curiosity, I removed the resource pages from the _app.tsx file to see the results. It significantly reduced the bundle size:

┌   /_app                                  0 B             292 kB
├ λ /[[...refine]]                         249 B           292 kB
└ ○ /404                                   184 B           292 kB
+ First Load JS shared by all              292 kB
  ├ chunks/framework-305dbdef56d67aa9.js   45.4 kB
  ├ chunks/main-a8eaa706e962a5d6.js        27.9 kB
  ├ chunks/pages/_app-432b4edb6ea69894.js  217 kB
  └ chunks/webpack-44567bfec3a37904.js     1.05 kB

The fact that I have to import all resource pages in the _app.tsx file to pass them to <Refine> has a significant effect on the bundle size and speed of my app. I wish there was a way to at least lazy load them.

  1. I have to import Material UI components using named imports:
import { Button } from "@pankod/refine-mui";

But the Material UI docs suggests to import the components using default imports to improve bundle size in dev mode:

import Button from "@pankod/refine-mui/Button";

This is not possible with @pankod/refine-mui because it only re-exports mui components with named export.

  1. Next.js recently introduced a new compiler option, modularizeImports that automatically converts named imports to default imports:
// before:
import { Delete } "@mui/icons-material"
// after:
import Delete "@mui/icons-material/Delete"

To enable it I just need to add this to my next.config.js:

module.exports = {
  modularizeImports: {
    "@mui/icons-material": {
      transform: "@mui/icons-material/{{member}}",
    },
  },
};

This is very handy for packages like @mui/icons-material. It significantly reduces the compile time in dev mode. It doesn't effect the production build because @mui/icons-material is side-effect free (at least this is what I've been told, I don't have deep knowledge about how bundles work). It also could be used with @mui/material but not your version of mui (@pankod/refine-mui/) so 1) we can't reduce compile time in dev mode with this option 2) it potentially effects the production build because @pankod/refine-mui is not side-effect free(?)

Again, I'm not very familiar with this topic. These are just my raw thoughts.

I'm willing to do anything I can do to help you on this because I'm currently blocked by this.

amirhhashemi avatar Jan 06 '23 09:01 amirhhashemi

I made some progress by dynamically importing resource pages:

import dynamic from "next/dynamic";

const ArticleList = dynamic(() =>
  import("../components/pages/articles/list").then((mod) => mod.ArticleList)
);

I think it's worth being documented. Also I get a Typescript error where I use the dynamically imported component that says:

1. Type 'ComponentType<IResourceComponentsProps<GetListResponse<Article>, { [key: string]: any; }, ILogData>>' is not assignable to type 'FunctionComponent<IResourceComponentsProps<any, any, ILogData>> | undefined'.
     Type 'ComponentClass<IResourceComponentsProps<GetListResponse<Article>, { [key: string]: any; }, ILogData>, any>' is not assignable to type 'FunctionComponent<IResourceComponentsProps<any, any, ILogData>>'.
       Type 'ComponentClass<IResourceComponentsProps<GetListResponse<Article>, { [key: string]: any; }, ILogData>, any>' provides no match for the signature '(props: IResourceComponentsProps<any, any, ILogData>, context?: any): ReactElement<any, any> | null'. [2322]

I would probably make a PR to fix those but I'm pretty buzy right now.

amirhhashemi avatar Jan 06 '23 15:01 amirhhashemi

Hey @ahhshm, sorry for the issue and thank you for sharing your progress here.

About the type incompatibility, that's an easy fix, don't know why but we've defined the action property types of the resources with React.FunctionalComponent rather than React.ComponentType 😅 We can deploy a fix for it very soon to resolve that.

Can you also share the bundle size change you get by using next/dynamic? 🙏

For the bundle size issue, I'll investigate this one to make refine packages comply with the bundlers to reduce the file size, goal is to make them all side effect free and a better way to pass resource components so we don't include them in everywhere of the app 🚀

aliemir avatar Jan 07 '23 13:01 aliemir

Hey @aliemir

We can deploy a fix for it very soon to resolve that.

That's great. Thank you.

Can you also share the bundle size change you get by using next/dynamic?

This is the bundle size after using next/dynamic

Route (pages)                              Size     First Load JS
┌   /_app                                  0 B             426 kB
├ λ /[[...refine]]                         249 B           426 kB
└ ○ /404                                   184 B           426 kB
+ First Load JS shared by all              426 kB
  ├ chunks/framework-305dbdef56d67aa9.js   45.4 kB
  ├ chunks/main-a8eaa706e962a5d6.js        27.9 kB
  ├ chunks/pages/_app-251183cf1f82c0cf.js  351 kB
  └ chunks/webpack-fdea33a90021617c.js     2.22 kB

It's about 130kb smaller and the app feels slightly faster. Still, it's very big compared to how relatively small my app is.

For the bundle size issue, I'll investigate this one to make refine packages comply with the bundlers to reduce the file size, goal is to make them all side effect free and a better way to pass resource components so we don't include them in everywhere of the app

Thank you. Looking forward to it.

amirhhashemi avatar Jan 07 '23 13:01 amirhhashemi

Hey @ahhshm , We've released the ComponentType fix (@pankod/[email protected])

omeraplak avatar Jan 09 '23 09:01 omeraplak

Hey @ahhshm and everyone interested in this issue. We're aware of the situation and will try to do some changes to reduce the bundle sizes in all of the packages and release but the major change will happen with the refine@4 since the main changes are going to be breaking ones and all the features/changes we want to do for v4 is not ready yet (including the bundle size related ones).

We'll try to share the roadmap and our target for the v4 and its release in the following days 🙏

aliemir avatar Jan 16 '23 13:01 aliemir

Hey @ahhshm , With refine v4, we removed re-exports and updated the routing structure. Now your refine apps have a much smaller bundle size! https://refine.dev/blog/refine-v4-announcement/#reduced-bundle-size

omeraplak avatar Mar 15 '23 18:03 omeraplak