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

refactor(mc-scripts): manually group some vendor chunks #3615

Merged
merged 12 commits into from
Oct 14, 2024

Conversation

tylermorrisford
Copy link
Contributor

@tylermorrisford tylermorrisford commented Sep 26, 2024

Summary

This PR revives another PR created by @emmenko to manually group some vendor chunks in the @commercetools-frontend/mc-scripts package. Doing so gives us a few extra files in the bundle but a significantly smaller vendor chunk.

Description

To reduce the main bundle chunk even more, we can instruct the bundlers to group certain vendor packages in specific groups. As a result, the main bundle will have a smaller size. Users (via browsers) will then only need to download the smaller main bundle when it gets updated (caching) as the other vendor chunks didn't change (unless via dependency updates).

I found this article that explains well how and why it's useful like that: https://www.codemzy.com/blog/react-bundle-size-webpack-code-splitting

Using this method to test, I rebuilt the account application before and after making these changes; this resulted in the main app chunk being reduced from ~660KB to ~412KB. Based on this I feel that all monorepo and satellite applications would benefit.

UPDATE
We had to change a bit the packages we wanted to create independent bundles for.
We need to bear in mind that splitting is done at the application level, so what we can split are their direct dependencies so it's not possible to split internals of (for instance) the application-shell package (that's something we can do separately and internally to that package).
We decided to create manual bundles for:

  • application-shell and application-shell-connectors
  • moment and moment-timezone
  • @commercetools-uikit/icons

Building the playground application, here is what biggest files originally looks like (webpack):
image

and this is how it looks like with the new manual bundles:
image

Also, this is the comparison when using vite.
BEFORE
image

AFTER
image

We're also adding new vite plugins that will help to analyze the built bundles using this env variables:

  • ANALYZE_BUNDLE
  • ANALYZE_BUNDLE_TREE

This is how they look like:
Screenshot 2024-10-08 at 18 31 02

Screenshot 2024-10-08 at 18 32 05

@tylermorrisford tylermorrisford self-assigned this Sep 26, 2024
@tylermorrisford tylermorrisford requested a review from a team as a code owner September 26, 2024 19:46
Copy link

changeset-bot bot commented Sep 26, 2024

🦋 Changeset detected

Latest commit: ce14413

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 36 packages
Name Type
@commercetools-frontend/mc-scripts Minor
@commercetools-frontend/sentry Minor
@commercetools-applications/merchant-center-template-starter-typescript Minor
@commercetools-applications/merchant-center-template-starter Minor
@commercetools-applications/merchant-center-custom-view-template-starter-typescript Minor
@commercetools-applications/merchant-center-custom-view-template-starter Minor
@commercetools-local/playground Minor
@commercetools-frontend/actions-global Minor
@commercetools-frontend/application-components Minor
@commercetools-frontend/application-shell-connectors Minor
@commercetools-frontend/application-shell Minor
@commercetools-frontend/i18n Minor
@commercetools-frontend/l10n Minor
@commercetools-frontend/permissions Minor
@commercetools-frontend/react-notifications Minor
@commercetools-frontend/codemod Minor
@commercetools-local/visual-testing-app Minor
@commercetools-website/components-playground Minor
@commercetools-frontend/cypress Minor
@commercetools-backend/eslint-config-node Minor
@commercetools-backend/express Minor
@commercetools-backend/loggers Minor
@commercetools-frontend/application-config Minor
@commercetools-frontend/assets Minor
@commercetools-frontend/babel-preset-mc-app Minor
@commercetools-frontend/browser-history Minor
@commercetools-frontend/constants Minor
@commercetools-frontend/create-mc-app Minor
@commercetools-frontend/eslint-config-mc-app Minor
@commercetools-frontend/jest-preset-mc-app Minor
@commercetools-frontend/jest-stylelint-runner Minor
@commercetools-frontend/mc-dev-authentication Minor
@commercetools-frontend/mc-html-template Minor
@commercetools-frontend/notifications Minor
@commercetools-frontend/sdk Minor
@commercetools-frontend/url-utils Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

vercel bot commented Sep 26, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
mc-app-kit-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback Oct 10, 2024 4:12pm
merchant-center-application-kit-components-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback Oct 10, 2024 4:12pm

@tylermorrisford tylermorrisford added the fe-chapter-rotation Tasks coming from frontend chapter work label Sep 26, 2024
Comment on lines 11 to 22
react: [
'react',
'react-dom',
'react-router',
'react-router-dom',
'react-intl',
'@reduxjs/toolkit',
'redux',
'react-redux',
'redux-logger',
'redux-thunk',
],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@emmenko the only change I made is to lump redux and react together; since redux is fairly small this seems fine to do.

@CarlosCortizasCT
Copy link
Contributor

CarlosCortizasCT commented Sep 30, 2024

Using this method to test, I rebuilt the account application before and after making these changes; this resulted in the main app chunk being reduced from ~660KB to ~412KB. Based on this I feel that all monorepo and satellite applications would benefit.

@tylermorrisford by default the applications are built using webpack but we also have "experimental" support for Vite.
Could you please also run that comparison by building the application using Vite so we also get those numbers?

ENABLE_EXPERIMENTAL_VITE_BUNDLER=true pnpm build

UPDATE: After some investigation, I think the problem is that we're trying to create bundles for dependencies that are transitive to the application. For instance, @apollo/client is not an application dependency, it is a @commercetools-frontend/application-shell internal dependency.
We're trying to control transitive dependencies form the application bundling which does not seem to be feasible.

We can, for instance, create a bundle for the application-shell packages, but @commercetools-frontend/application-shell is already quite big and I'm not sure we can do any bundling improvement from the application level. I believe we would need to look into that package in isolation and see if we can lazy load more stuff.
Having the bundles bundle visual diagram of that package can help us.

@CarlosCortizasCT
Copy link
Contributor

Building an application is failing for both webpack and vite bundlers.

To test it, you can get into the playground directory and run:

// Build with webpack
ENABLE_EXPERIMENTAL_VITE_BUNDLER=false pnpm build

// Build with Vite
ENABLE_EXPERIMENTAL_VITE_BUNDLER=true pnpm build

I think we need to investigate a little further what the updated configuration actually does.

@tylermorrisford
Copy link
Contributor Author

Two items I've discovered in testing the Vite build process by linking the my local version of app-kit with the monorepo and building the account application:

  1. Our Vite config seems to get tripped up on files wit .js extensions, updating those files to .jsx allowed the build to proceed.
  2. There is an export in packages-application/application-account/src/components/organizations/details/custom-extensions/custom-applications/configurations/_shared/form/default-raw-icon.js using !!raw-loader! which causes the build to fail, unless the entire export is added to build.rollupOptions.external.

@@ -181,7 +181,8 @@
"@typescript-eslint/parser": "^5.52.0"
},
"patchedDependencies": {
"[email protected]": "patches/[email protected]"
"[email protected]": "patches/[email protected]",
"[email protected]": "patches/[email protected]"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vite-bundle-analyzer has a rollup very flexible peer dependency (2.X -> 4.X) but it actually requires 3.X at least because of a typescript type usage.
Since we're using rollup 2.X in the repo (force by rollup), the alternative I took is to change the type usage (very simple change)

@@ -77,6 +86,10 @@ async function run() {
pluginSvgr(),
pluginDynamicBaseAssetsGlobals(),
pluginI18nMessageCompilation(),
process.env.ANALYZE_BUNDLE === 'true' &&
Copy link
Contributor

@CarlosCortizasCT CarlosCortizasCT Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This plugin create the bundles size visualization:
Screenshot 2024-10-08 at 18 31 02

@@ -77,6 +86,10 @@ async function run() {
pluginSvgr(),
pluginDynamicBaseAssetsGlobals(),
pluginI18nMessageCompilation(),
process.env.ANALYZE_BUNDLE === 'true' &&
analyzer({ defaultSizes: 'parsed', openAnalyzer: true }),
process.env.ANALYZE_BUNDLE_TREE === 'true' &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This plugin creates a bundles dependency visualization:
Screenshot 2024-10-08 at 18 32 05

],
};

const removeNonExistantDependencies = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just to make sure the packages we're defining in the manualChunks map actually exists in the application to build.
This is a safety measure so in case one of them are remove (eg: moment) the build does not fail but will only emit a warning about it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comment. I was fearful we'd remove something else but those we specify at first.

@CarlosCortizasCT CarlosCortizasCT requested review from a team October 10, 2024 09:51
Copy link
Member

@emmenko emmenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job, thanks for the contributions! 🙌

@@ -108,6 +109,7 @@
"thread-loader": "3.0.4",
"url": "^0.11.0",
"vite": "~4.5.3",
"vite-bundle-analyzer": "0.12.1",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if the patch will be used here when someone installs the mc-scripts package.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair question. Couldn't find documentation on it. I must say I assume it would be applied.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know either but it won't matter in this case: the change in the source code is about a Typescript type which is not generic in the rollup version we're using but the dependency code expects it to be.
So, the generated code is exactly the same (no typings go there).

manualChunks: Record<string, string[]>,
appDependencies: string[]
) => {
return Object.entries(manualChunks).reduce((acc, [chunkName, vendors]) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: let's use a more meaningful name

Suggested change
return Object.entries(manualChunks).reduce((acc, [chunkName, vendors]) => {
return Object.entries(manualChunks).reduce((chunkGroups, [chunkName, vendors]) => {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated here: f8f062c

Comment on lines 20 to 23
const existingVendors = vendors.filter((vendor) =>
appDependencies.includes(vendor)
);
if (existingVendors.length > 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const existingVendors = vendors.filter((vendor) =>
appDependencies.includes(vendor)
);
if (existingVendors.length > 0) {
const hasExistingVendors = vendors.some((vendor) =>
appDependencies.includes(vendor)
);
if (hasExistingVendors) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's picture this scenario:

const manualChunks = {
  'chunk-A': ['dep-a', 'dep-b'];
}

With the current code, if the app does not have 'dep-b' anymore, the manual chunk would still be created with dep-a. This would not work if we apply your suggestion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, nevermind then. I misread the code 🙈

Copy link
Contributor

@tdeekens tdeekens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the effort on this work!

- [vite-bundle-analyzer](https://github.com/KusStar/vite-bundle-visualizer)
- [rollup-plugin-visualizer](https://github.com/btd/rollup-plugin-visualizer) (tree visualization)

You can use them by setting this environment variables"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
You can use them by setting this environment variables"
You can use them by setting this environment variables:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated here: f8f062c

@@ -108,6 +109,7 @@
"thread-loader": "3.0.4",
"url": "^0.11.0",
"vite": "~4.5.3",
"vite-bundle-analyzer": "0.12.1",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair question. Couldn't find documentation on it. I must say I assume it would be applied.

],
};

const removeNonExistantDependencies = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comment. I was fearful we'd remove something else but those we specify at first.

@CarlosCortizasCT CarlosCortizasCT requested a review from a team October 10, 2024 15:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fe-chapter-rotation Tasks coming from frontend chapter work
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants