devextreme-angular icon indicating copy to clipboard operation
devextreme-angular copied to clipboard

Large Bundle Size - Angular

Open anikets43 opened this issue 6 years ago • 63 comments

I tried compiling the angular 6 project with and without devexteme-angular. I just used simple customer grid in the app.component and build the project using ng build --configuration production. main.*.js bundle seems to be too large for such a small demo app. I did set AOT, buildOptimizer and other configurations for the prod.

Versions: "devextreme": "^17.2.7", "devextreme-angular": "^17.2.7"

Here's the screenshot image

Please let me know if I'm missing anything in the configuration or any optimization strategy needs to be done.

Cheers, Aniket

anikets43 avatar Jun 19 '18 07:06 anikets43

Check issue 592. dxvladislavvolkov has posted a solution at the bottom of the thread.

hakimio avatar Jun 19 '18 09:06 hakimio

Thanks @hakimio Unfortunately doesn't make any difference even after referencing only the used module; bundle size remains same.

import { DxDataGridModule } from 'devextreme-angular/ui/data-grid';

anikets43 avatar Jun 19 '18 10:06 anikets43

Did you replace all the places where you are importing something from "devextreme-angular"? It worked well for me.

hakimio avatar Jun 19 '18 11:06 hakimio

@hakimio This is just a demo app containing a datagrid, so only imported in the app.module.ts.

Nowhere else it's required.

anikets43 avatar Jun 19 '18 11:06 anikets43

imports directly from the data-grid file does make no sense, bundle size still huge

import { DxDataGridModule} from 'devextreme-angular/ui/data-grid';
import { DxTemplateModule } from 'devextreme-angular/core/template';

// chunk {2} 2.e1aa563238c7e83d49a9.js, 2.e1aa563238c7e83d49a9.js.map () 2.24 MB  [rendered]

and

import { DxDataGridModule, DxTemplateModule } from 'devextreme-angular';

// chunk {2} 2.e1aa563238c7e83d49a9.js, 2.e1aa563238c7e83d49a9.js.map () 2.24 MB  [rendered]

Angular v6.0.5 Angular CLI v6.0.7

devextreme v18.1.4 devextreme-angular v18.1.4

default23 avatar Jul 03 '18 13:07 default23

@default23 It does make sense if you want to reduce your bundle size. See the comment I linked to in my previous post. 2MB is the normal size for DevExtreme + DevExtreme-Angular (comment from one of DevExtreme devs). If you are using gzip compression on your http server, the size should be around 4 times less than the number you get when you run "ng build --prod". For example, in my case 2.37MB "main.js" file when transferred with gzip only weighs 584KB. Also, if initial load time is so important for you, you can use server side rendering.

hakimio avatar Jul 03 '18 13:07 hakimio

@hakimio is all of your library code using in the data-grid? Why u shouldn't use tree-shaking?

import { DxButtonModule } from 'devextreme-angular/ui/button';
import { DxTextAreaModule } from 'devextreme-angular/ui/text-area';
import { DxSelectBoxModule } from 'devextreme-angular/ui/select-box';
import { DxDateBoxModule } from 'devextreme-angular/ui/date-box';
import { DxDropDownBoxModule } from 'devextreme-angular/ui/drop-down-box';
import { DxMenuModule } from 'devextreme-angular/ui/menu';
import { DxPopupModule } from 'devextreme-angular/ui/popup';
import { DxTextBoxModule } from 'devextreme-angular/ui/text-box';
import { DxCheckBoxModule } from 'devextreme-angular/ui/check-box';
import { DxLoadPanelModule } from 'devextreme-angular/ui/load-panel';
import { DxPopoverModule } from 'devextreme-angular/ui/popover';
import { DxValidatorModule } from 'devextreme-angular/ui/validator';
import { DxValidationSummaryModule } from 'devextreme-angular/ui/validation-summary';
import { DxTreeListModule } from 'devextreme-angular/ui/tree-list';
import { DxDataGridModule } from 'devextreme-angular/ui/data-grid';
import { DxTemplateModule } from 'devextreme-angular/core/template';

// chunk {2} 2.c465b33c04f3f453e371.js, 2.c465b33c04f3f453e371.js.map () 2.47 MB  [rendered]

default23 avatar Jul 04 '18 12:07 default23

Having the same issue. image I just used time picker but full bundle has been imported, there should be tree-shaking or at least, devextreme angular should provide custom webpack config for it.

webcat12345 avatar Dec 25 '18 17:12 webcat12345

@webcat12345 same here. Why do we have every widgets in the bundle ? I'm using only 5 widgets from "devextreme-angular" (with the correct imports).

b-veiga-fi avatar May 17 '19 09:05 b-veiga-fi

my final decision was just don't use this library. Its optimization is bad.

webcat12345 avatar May 17 '19 09:05 webcat12345

@webcat12345 same here. Why do we have every widgets in the bundle ? I'm using only 5 widgets from "devextreme-angular" (with the correct imports).

Most likely because you are importing modules and components wrong way. Don't do the following:

import { DxButtonModule } from 'devextreme-angular';

Do this instead:

import { DxButtonModule } from 'devextreme-angular/ui/button';

And this applies both to modules and components:

import { DxButtonComponent } from 'devextreme-angular/ui/button';

If you do that it should only pull required modules.

hakimio avatar May 17 '19 09:05 hakimio

my final decision was just don't use this library. Its optimization is bad.

It's your loss. Should have read the README. DevExtreme is an awesome UI component library.

hakimio avatar May 17 '19 09:05 hakimio

@hakimio this is exactly what I am doing. My imports : image and my bundle: image

b-veiga-fi avatar May 17 '19 09:05 b-veiga-fi

Maybe some of the components you are using are importing other components. For example, data grid has a lot of dependencies.

hakimio avatar May 17 '19 09:05 hakimio

I can't remember exactly but I think I used this way to import only for a timepicker. Let me walk through Git history if I was wrong.

webcat12345 avatar May 17 '19 09:05 webcat12345

Also, here are few recommendations to improve initial app load time:

  • Use gzip compression
  • Set "cache-control" and "expires" headers:
cache-control: max-age=31536000
expires: Sat, 16 May 2020 09:38:43 GMT
  • Enable http/2 protocol
  • Use CDN (AWS CloudFront, Azure CDN, Cloudflare, etc)
  • If you are running your own http server, use nginx instead of Apache since it's much better for serving static files.

hakimio avatar May 17 '19 09:05 hakimio

I tried to gzip compression actually, and http/2 protocol couldn't be used because we were using Heroku. And from my understanding even if we import libraries without targetting the specific destination, it should be handled by tree-shaking automatically, isn't it? Readme says that exactly here, but obviously it didn't work. Or is this feature introduced recently?

My last attempt was made 6 months ago. :) And Doc should introduce recommended way like this

webcat12345 avatar May 17 '19 09:05 webcat12345

And from my understanding even if we import libraries without targetting the specific destination, it should be handled by tree-shaking automatically, isn't it?

It should but with DevExtreme Angular it never did work that way and it doesn't work right now. That section of the readme should be fixed. Even in all official examples where they are using Angular CLI, they are still importing using specific modules / long paths. Otherwise you get a huge bundle.

hakimio avatar May 17 '19 10:05 hakimio

That document update is quite important for these guys otherwise they could lose users and I can certainly say that they could write it on medium or twitter which is the worst case (I think I have read about it on medium 6 months ago or not sure), for me I was able to reduce 2.0 MB by removing dev extreme from the project and the entire team was happy with it. :D Looking forward to updated documentation!

webcat12345 avatar May 17 '19 10:05 webcat12345

FYI - I created a new project with one page, and implemented this widget: https://js.devexpress.com/Demos/WidgetsGallery/Demo/DataGrid/Overview/Angular/Light/

I ran two tests, one using the long path, one using the short path imports.

In both cases main.js came out at 2.43MB using: ng build --project=myproj --configuration=production

configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "apps/myproj/src/environments/environment.ts",
                  "with": "apps/myproj/src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                }
              ]
            }

Reviewing the stats.json I can see that what is imported is (plausibly) needed for all the various bits (it's a very full featured grid).

IMHO: Tree shaking I guess is either working, or the library imports are using short paths and pulling in everything. I couldn't verify using short paths making a difference to my build with prod settings as above.

Mine is for an internal app, meaning that size is (ahem) less of a priority - I've now got to go down the PWA route and see if that helps. The usual things to improve server configs will help, sure - but I totally see webcat12345's point, serving this over 3G to a tablet would be less than great where your trying to ensure that only the absolute necessities are downloaded.

StevenFewster avatar May 22 '19 09:05 StevenFewster

@StevenFewster Try to make the same test with textbox or some other smaller component. And no, Tree Shaking is not supported according to one of the developers. See issue #353.

hakimio avatar May 22 '19 09:05 hakimio

Absolutely right - I guess if using DataGrid (Pivot too, probably) you're going to have to accept a large bundle size. DxButtonGrid came in at 858kB for main.js, and unaffected by full/partial path.

StevenFewster avatar May 22 '19 10:05 StevenFewster

there are some infos in the roadmap https://js.devexpress.com/Roadmap/#Performance

image

Brandinga avatar May 27 '19 15:05 Brandinga

I am also using the DxDataGrid. It doesn't matter how I import the Modules/Components, my bundle size does not change. Main.js is 2.9mb and Vendor.js is 5.4 mb.

I've tried this: import { DevExtremeModule } from 'devextreme-angular'; -- in app.module only

this: import { (ControlModule) } from 'devextreme-angular'; -- in all modules that contain the specified control

this: import { (ControlModule) } from 'devextreme-angular/ui/(control name)'; -- in all modules that contain the specified control

Main and Vendor files remains the same. Yes, I am using a production build. With a dev build, Main.js is 3.8mb and Vendor.js is 16.5mb.

OmniAndy avatar Jun 26 '19 16:06 OmniAndy

@OmniAndy you have to import everything from "devextreme-angular" library using long paths ("devextreme-angular/ui/(control name)" or similar) from specific sub-folders, no matter if it's UI module, component or "DxServerTransferStateModule". If you have import {SomeThing} from 'devextreme-angular' anywhere in your code, it will result in a huge bundle. Normally DevExtreme Angular app (JS + CSS) weighs 2 to 3MB. Try to build devextreme-angular-template example project to see how much the bundle should weigh when everything is imported correctly.

hakimio avatar Jun 27 '19 12:06 hakimio

@hakimio ... like I said. I tried that. Bundle was the same exact size. I think people are right when they say Data-Grid has so many dependencies that it ends up importing everything anyway.

OmniAndy avatar Jun 27 '19 13:06 OmniAndy

@OmniAndy we also use the data grid (and many other widgets), but we don't generate separate vendor.js - everything's included in main.js. The output looks like this:

$ ng build --prod

Date: 2019-06-27T13:16:32.868Z
Hash: cd5d65433cfd386e034b
Time: 42819ms

chunk {0} runtime.a5dd35324ddfd942bef1.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} main.3b629f44655bed6ddbac.js (main) 2.82 MB [initial] [rendered]
chunk {2} polyfills.1cd3466dbc8de4b5c45c.js (polyfills) 41 kB [initial] [rendered]
chunk {3} styles.0fbb3dafd701930074f1.css (styles) 638 kB [initial] [rendered]

Done in 55.03s.

EDIT: after gzip the file sizes look like this:

main.js      705 KB
styles.css    80.9 KB

hakimio avatar Jun 27 '19 13:06 hakimio

@hakimio How do you not use a vendor file? Is that the "vendorChunk" flag in angular.json?

OmniAndy avatar Jun 27 '19 13:06 OmniAndy

@OmniAndy here is the config we use:

            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [{
                "type": "initial",
                "maximumWarning": "4mb",
                "maximumError": "6mb"
              }]
            }

Also, make sure you are using --prod flag when building your app.

hakimio avatar Jun 27 '19 13:06 hakimio

What helped me was take a look at your Component import not anly at your Module import, some of my Component imports where also from devextreme-angular/ui/all..

frankiDotNet avatar Jul 23 '19 08:07 frankiDotNet