Skip to content
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

Bundle Splitting via Webpack in @edx/frontend-build #173

Open
6 tasks
adamstankiewicz opened this issue Jul 20, 2023 · 4 comments
Open
6 tasks

Bundle Splitting via Webpack in @edx/frontend-build #173

adamstankiewicz opened this issue Jul 20, 2023 · 4 comments
Labels
enhancement Relates to new features or improvements to existing features epic Large unit of work, consisting of multiple tasks

Comments

@adamstankiewicz
Copy link
Member

adamstankiewicz commented Jul 20, 2023

Description

Context: Defining the performance KPI "Largest Contentful Paint"

An important KPI from frontend performance is the Core Web Vital called Largest Contentful Paint (LCP), which measures loading performance:

“time from when the page starts loading to when the largest text block or image element is rendered on the screen.”

The "best practice" for LCP is as follows:

  • Good. <= 2.5 s
  • Needs improvement. 2.5 s - 4 s
  • Poor. > 4s

Generally, I believe the majority of our edx.org MFEs fall in the "Poor" category for LCP.

About Webpack Production Builds

image

Webpack Bundle Analyzer

When running npm run build, a Webpack Bundle Analyzer report is generated: dist/report.html:

image

(Using standard Webpack configuration from @edx/frontend-build)

Finding performance bottlenecks for Open edX MFEs

Based on Chrome's DevTools' performance debugging tools, I've identified that one (large) bottleneck in terms of loading performance for at least a handful of Open edX MFEs is loading the JavaScript assets, and specifically large JS chunks for as vendor node_modules, from the network after the initial download of the index.html file.

The below screenshot utilizes Chrome's "Performance Insights", throttled to "Fast 3G" to simulate slower network speeds on poor network connections.

image

As shown above, the 542.[hash].js file takes over 12+ seconds to download on the simulated poor network speeds. In this time, users are shown a blank white screen with no indication of progress and is still before any network API calls have been made to any Django services.

An incremental step towards mitigate performance bottlenecks such as shown above is be to modify the webpack.prod.config.js configuration file in @edx/frontend-build to ensure Webpack is configured to appropriately split up vendor and application chunks according to more explicit rules that may improve performance and set the foundation for consuming MFEs to implement code splitting with React.lazy and Suspense.

For example, the following Webpack configuration will update the previous Webpack bundle report to contain significantly many more generated files:

image

The resulting Webpack bundle report:

image

Note that instead of all node_modules (vendor) getting consolidated into a single JS file, Webpack now splits out many more chunks when appropriate to ensure they are able to get loaded performantly by the browser.

Take for example, plotly.js. Gzipped, it's size is 1.03 MB. Currently, the MFE forces the user to download all 1.03 MB of plotly.js even if their user session never renders any component that uses plotly.js. That additional bandwidth is expensive for the user in terms of performance.

Instead, we could enable plotly.js to get split out (as seen in above screenshot) into it's own distinct bundle that may be code split by the consuming MFE utilizing React.lazy and Suspense, e.g.:

image

See this demo from React.

Tasks

@adamstankiewicz adamstankiewicz added the enhancement Relates to new features or improvements to existing features label Jul 20, 2023
@adamstankiewicz adamstankiewicz changed the title vendor Bundle Splitting via Webpack in @edx/frontend-build Bundle Splitting via Webpack in @edx/frontend-build Jul 20, 2023
@adamstankiewicz adamstankiewicz added the epic Large unit of work, consisting of multiple tasks label Jul 20, 2023
@Syed-Ali-Abbas-Zaidi
Copy link

Hi @adamstankiewicz, We did some RnD (on Account and Discussions MFE) on our end and the following are our findings:

  • Account MFE

    • Build Time (npm run build)
      • Without Bundle Splitting - 51 seconds
      • With Bundle Splitting - 35 seconds
    • Load Time
      • Without Bundle Splitting - 2.8 seconds
      • With Bundle Splitting - 2.9~2.8 seconds
  • Discussions MFE

    • Build Time (npm run build)
      • Without Bundle Splitting - 1 minute 20 seconds
      • With Bundle Splitting - 45 seconds
    • Load Time
      • Without Bundle Splitting - 5 seconds
      • With Bundle Splitting - 4.5 seconds

It seems like the bundle splitting didn't affect load time but the total build time got improved.

NOTE: We used the above given webpack configuration

@adamstankiewicz
Copy link
Member Author

adamstankiewicz commented Jul 25, 2023

@Syed-Ali-Abbas-Zaidi Thanks for the update!

Interesting about the improvement to build time. Regarding load time, I wouldn't expect too much of a change just with the above given Webpack configuration since the browser is still downloading all the same code; just a higher quantity of small files versus 1 larger file. That said, if/when MFEs start code splitting through dynamic imports and/or React.lazy and Suspense, these smaller, independent chunks could be dynamically loaded as the user interacts with the application.

A few follow-up questions/comments:

  1. [clarification] What methodology did you use to benchmark performance locally? For example, were you running Webpack Dev Server or serving the already-compiled MFE production bundle (e.g., through npm run serve, see docs)? I've noticed some discrepancies when doing performance benchmarks locally utilizing Webpack Dev Server vs. the production build directly.
  2. If a consumer wants to introduce code splitting today, can they do so without modifying the Webpack configuration? That is, are the proposed Webpack config changes a prerequisite for code splitting in a consuming MFE? For example, given the default Webpack configuration generates a single chunk for node_modules today and the consuming MFE opted to code split an import of one of these node_modules through a dynamic import and/or React.lazy and Suspense, will Webpack already support extracting that package out as its own chunk? Or do we need to introduce Webpack configuration changes as shown above to split out chunks before consumers can implement code splitting?
  3. [curious] Was there any exploration in what other permutations/options of generic Webpack configurations might be worth trying in support of bundle splitting for consumers?
  4. [suggestion] We might consider experimenting with a CI check for bundle size in our frontend repos (e.g., bundlewatch, bundlesize) CI check in GitHub to have better observability into ongoing changes to bundle size at the PR level. Example:

image

@adamstankiewicz
Copy link
Member Author

[suggestion] We might consider experimenting with a CI check for bundle size in our frontend repos (e.g., bundlewatch, bundlesize) CI check in GitHub to have better observability into ongoing changes to bundle size at the PR level.

[inform] Filed this GitHub issue related to bundlewatch/bundlesize: openedx/axim-engineering#837

@Syed-Ali-Abbas-Zaidi
Copy link

  1. We used npm run serve to benchmark the performance locally. (It works like a charm :) )
  2. Yes, the Consumer can introduce code-splitting without modifying the webpack config. Discussion MFE is already using Suspense and React.lazy. (Here is one of the example)
  3. Yes, I explored some other permutations too, but they were not that effective. I am currently working on this and will share it here If I find something interesting.
  4. Sure, We will explore this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Relates to new features or improvements to existing features epic Large unit of work, consisting of multiple tasks
Projects
Status: Backlog
Development

No branches or pull requests

2 participants