MauroDataMapper / mdm-ui

Web front-end for the Mauro Data Mapper
Apache License 2.0
7 stars 5 forks source link

Reduce UI Bundle size by lazy-loading components #718

Open jamesrwelch opened 1 year ago

jamesrwelch commented 1 year ago

Instructions on how to do that kind of thing are here: https://developapa.com/angular-lazy-load/

abwilson23 commented 1 year ago

In short: Reducing bundle size via lazy loading will require a restructuring of the feature modules in the project.

The idea is to group a features logic and routes into a module so that it may be lazy loaded only when the route gets hit. The process for one such feature module would look something like:

  1. Identify the unshared functionality of the module along with it's routes. For example, the AdminModule would be a good place to start as it's routes and logic are primarily used in the Admin section of the site.
  2. Create an admin routes file (actually, one already exists for the Admin Module)
  3. Remove AdminModule references from the imports of other modules (this prevents the module from being eagerly loaded).
  4. Instead, add a route to the app.module.ts file that catches all /admin/* requests and use the loadChildren property of route to only then dynamically load the AdminModule. You can find an example of this code in the ui-router link below.

Additionally, lazy loading ag-grid and jodit would likely significantly reduce bundle size as they are the two largest packages that we currently use and are recommended starting places. Not the least of which because it might be easier to untangle their use from the rest of the src than it will be for our custom modules.

Note that we use the ui-router in mdm-ui rather than the standard angular router. Instructions for how lazy loading works in the ui-router can be found here: https://ui-router.github.io/guide/lazyloading#angular

The angular docs also have some good information re. lazy loading: https://angular.io/guide/lazy-loading-ngmodules

Finally, an interim step has been taken to use ag-grid modules instead of packages. The approach used is described in detail here: https://blog.ag-grid.com/minimising-bundle-size/. Unfortunately, we have not seen any real reduction in bundle size likely due to the fact that the community package is quite sparse to begin with so moving to modules doesn't save us much. The effect would be more noticeable if we were instead using the enterprise addition which comes loaded with many more features.

pjmonks commented 1 year ago

I have also been investigating the current bundle sizes based on what @abwilson23 has explained to me. Running the branch based on PR #800 I've generated this source map view (click to expand):

image

And created a table of these packages ordered by file size (descending):

prod_mdm-ui.txt

From this output, here are some observations I made:

Unnecessary packages

The following packages are pulled in:

Although our code does not use these, jquery and backbone seem to be dependencies of jointjs which is used by the diagram components. (underscore is a dependency of backbone). It should be tested if these really could be removed to reduce size.

lodash is a large library of functions but I've only found usage for 3 of them in the repo, yet importing the entire library is wasteful. My suggestions are to either:

  1. Remove lodash completely and rewrite the code that uses those 3 functions with equivalents, or
  2. Import just the functions needed instead of the top-level library - see https://www.blazemeter.com/blog/import-lodash-libraries

Large libraries

The two largest libraries used are ag-grid-community and jodit (with jodit-angular):

In both cases, @abwilson23 has suggested somehow restructuring our Angular modules to split these two areas off so they are lazy loaded when those pages are required. I agree with this - editing functionality should ideally only be pulled in when necessary (for instance, users with the "reader" privilege will never require the editor controls).

Possible duplicates

brace and code-mirror seem to both be packages for using syntax highlighted editor controls. brace is used for the Rule Representations and is definitely needed to support some of the more obscure language syntaxes. But perhaps code-mirror could be replaced to just use brace instead - the only occurance I've found of it is in the diagrams components related to Data Class Components, but I cannot get a test case available to see that working in the UI.

Another possible duplicate is ui-router, which takes up 124KB (1.7%) of the bundle size. Since Angular already provides a router package, would ui-router actually be necessary (apart from the sheer legacy of code)?

Correct modularisation

I agree with @abwilson23's analysis that, ultimately, a re-architecture of the the app modules which better support routed lazy-loading will be best long term. The above suggestions are only short-term measures which may hopefully reduce some of the total bundle size assuming there is only one large bundle to download.

jamesrwelch commented 1 year ago

Summarising a comment from Zulip: If we can remove unnecessary or duplicate packages for now, that would be great. It'll help reduce our package size a bit, make the code easier to maintain and reduce our susceptibility to security issues. Replacing components like Jodit will require a bit more thought, and probably a lot of code re-writing, so I'm inclined to leave those for now unless we can see a really persuasive argument. And correct modularisation is definitely in the long-term basket - I think there would be other improvements we'd want to make at the same time as a large-scale re-write.

abwilson23 commented 1 year ago

Just a note re. @pjmonks recommendations re. Lodash: I successfully tested switching to using lodash-es which allowed for individual function level imports, however there are several other packages that import lodash in full and so the optimization gains are not realized. In particular, there are many build warnings from angular about our use of CommonJS modules vs ECMAScript modules which are impacting our bundling optimization.

An investigation yielded this: moving away from CommonJS modules (the node default) and towards ECMAScript modules (a more modern alternative format) will allow for significantly better tree shaking to be performed because ECMAScript modules are statically loaded vs CommonJS's dynamic loading.