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

[HOLD for payment 2024-10-04] [$250] Avoid packaging WDYR and whole LODASH in JS bundle #48978

Closed
hurali97 opened this issue Sep 11, 2024 · 23 comments
Assignees
Labels
AutoAssignerNewDotQuality Used to assign quality issues to engineers Awaiting Payment Auto-added when associated PR is deployed to production Daily KSv2 NewFeature Something to build that is a new item.

Comments

@hurali97
Copy link
Contributor

hurali97 commented Sep 11, 2024

Problem

In our JS bundle we see that we have unrelated code bundled which is never used. This adds up to the bundle size which eventually leads to the slower loading time for the app. For example, why-did-you-render is present in the JS bundle which is a red flag, since it is installed as a dev dependency. The other case is with Lodash not being able to tree-shake the unused functions.

Apart from this, there are some libraries which are listed in dependencies whereas the right place for them is devDependencies. This doesn't change anything but it's a good practice to keep things where they belong. For example, @kie/act-js, @kie/mock-github, @types/mime-db and others.

To summarise, we have three action items that we will discuss in the solutions:

  • Avoid bundling why-did-you-render in the JS bundle
  • Allow Lodash to tree-shake the unused functions
  • Move the development libraries to devDependencies
  • And a little bonus at the end

Solution

Here's the bundle size as of the baseline ~19.18mb

Screenshot 2024-09-09 at 3 32 06 PM

Avoid bundling why-did-you-render in the JS bundle:

I couldn't figure out the root cause of it. When I comment out the whole wdyr.ts, then we don't have it bundled. I thought it
might be due to the import type {} from 'wdyr but it's not the case either. For some reasons, useWDYR flag is not respected.

Now, One of the solution we can apply is to ignore why-did-you-render if .env file is either of prod or staging. Below is how we can do this in webpack.common:

Diff
diff --git a/config/webpack/webpack.common.ts b/config/webpack/webpack.common.ts
index 33fd9131eca..2b2da879b88 100644
--- a/config/webpack/webpack.common.ts
+++ b/config/webpack/webpack.common.ts
@@ -128,6 +128,13 @@ const getCommonConfiguration = ({file = '.env', platform = 'web'}: Environment):
             resourceRegExp: /^\.\/locale$/,
             contextRegExp: /moment$/,
         }),
+        ...(file === '.env.production' || file === '.env.staging'
+            ? [
+                  new IgnorePlugin({
+                      resourceRegExp: /@welldone-software\/why-did-you-render/,
+                  }),
+              ]
+            : []),
         ...(platform === 'web' ? [new CustomVersionFilePlugin()] : []),
         new DefinePlugin({
             ...(platform === 'desktop' ? {} : {process: {env: {}}}),
This brings our bundle size to ~19.16mb giving us ~20kb reduction

Expensify Performance Board Screenshot


Allow Lodash to tree-shake the unused functions

We have lots of imports like import {} from 'lodash or import _ from 'lodash. These all doesn't allow web pack to tree shake Lodash. This brings in a ~110kb increase to the bundle size.

See Lodash size

Screenshot 2024-09-09 at 2 57 57 PM

One solution can be to refactor all these imports to named imports like import isEqual from 'lodash/isEqual'.

Otherwise, a better and simple solution with less changes is to use lodash-es and resolve lodash: lodash-es in from web pack. This avoids the need of a massive refactor. Since esmodules have better tree-shake support with web pack the syntax import {} from 'lodash works in terms of tree-shaking. However, we still have to refactor some imports like import _ from 'lodash' to the named ones.

Opting for the second solution brings us ~70kb reduction and the final bundle size is ~19.09mb

Screenshot 2024-09-09 at 3 17 29 PM

Irrespective of the gains, we see that the size of lodash and lodash-es is ~110kb. The reason for this is we have the whole imports or de-structured imports in expensify-common. Once we change those to named imports, everything falls into the place and lodash-es now takes ~50kb, which reduces the bundle size to ~19.04mb.

See bundle size

Screenshot 2024-09-10 at 4 56 50 PM


Move the development libraries to devDependencies

This is self explanatory. We just have to move the libraries which are listed in dependencies but their right place is devDependencies. This doesn't bring any improvement in the bundle size but it's good to place things where they belong.

See diff
diff --git a/package.json b/package.json
index 2f428fa0a14..d2f2f86e465 100644
--- a/package.json
+++ b/package.json
@@ -67,8 +67,6 @@
     "web:prod": "http-server ./dist --cors"
   },
   "dependencies": {
-    "@babel/plugin-proposal-private-methods": "^7.18.6",
-    "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
     "@dotlottie/react-player": "^1.6.3",
     "@expensify/react-native-live-markdown": "0.1.120",
     "@expo/metro-runtime": "~3.1.1",
@@ -77,14 +75,10 @@
     "@formatjs/intl-locale": "^4.0.0",
     "@formatjs/intl-numberformat": "^8.10.3",
     "@formatjs/intl-pluralrules": "^5.2.14",
-    "@fullstory/babel-plugin-annotate-react": "github:fullstorydev/fullstory-babel-plugin-annotate-react#ryanwang/react-native-web-demo",
-    "@fullstory/babel-plugin-react-native": "^1.2.1",
     "@fullstory/browser": "^2.0.3",
     "@fullstory/react-native": "^1.4.2",
     "@gorhom/portal": "^1.0.14",
     "@invertase/react-native-apple-authentication": "^2.2.2",
-    "@kie/act-js": "^2.6.2",
-    "@kie/mock-github": "2.0.1",
     "@onfido/react-native-sdk": "10.6.0",
     "@react-native-camera-roll/camera-roll": "7.4.0",
     "@react-native-clipboard/clipboard": "^1.13.2",
@@ -102,9 +96,7 @@
     "@react-ng/bounds-observer": "^0.2.1",
     "@rnmapbox/maps": "10.1.26",
     "@shopify/flash-list": "1.7.1",
-    "@types/mime-db": "^1.43.5",
     "@ua/react-native-airship": "19.2.1",
-    "@vue/preload-webpack-plugin": "^2.0.0",
     "awesome-phonenumber": "^5.4.0",
     "babel-polyfill": "^6.26.0",
     "canvas-size": "^1.2.6",
@@ -122,8 +114,6 @@
     "focus-trap-react": "^10.2.3",
     "htmlparser2": "^7.2.0",
     "idb-keyval": "^6.2.1",
-    "jest-expo": "51.0.3",
-    "jest-when": "^3.5.2",
     "lodash": "4.17.21",
     "lottie-react-native": "6.5.1",
     "mapbox-gl": "^2.15.0",
@@ -133,7 +123,6 @@
     "react": "18.3.1",
     "react-beautiful-dnd": "^13.1.1",
     "react-collapse": "^5.1.0",
-    "react-compiler-runtime": "file:./lib/react-compiler-runtime",
     "react-content-loader": "^7.0.0",
     "react-dom": "18.3.1",
     "react-error-boundary": "^4.0.11",
@@ -189,9 +178,7 @@
     "react-plaid-link": "3.3.2",
     "react-web-config": "^1.0.0",
     "react-webcam": "^7.1.1",
-    "react-window": "^1.8.9",
-    "semver": "^7.5.2",
-    "xlsx": "file:vendor/xlsx-0.20.3.tgz"
+    "react-window": "^1.8.9"
   },
   "devDependencies": {
     "@actions/core": "1.10.0",
@@ -200,6 +187,8 @@
     "@babel/parser": "^7.22.16",
     "@babel/plugin-proposal-class-properties": "^7.12.1",
     "@babel/plugin-proposal-export-namespace-from": "^7.18.9",
+    "@babel/plugin-proposal-private-methods": "^7.18.6",
+    "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
     "@babel/preset-env": "^7.20.0",
     "@babel/preset-flow": "^7.12.13",
     "@babel/preset-react": "^7.10.4",
@@ -210,7 +199,11 @@
     "@callstack/reassure-compare": "^1.0.0-rc.4",
     "@dword-design/eslint-plugin-import-alias": "^5.0.0",
     "@electron/notarize": "^2.1.0",
+    "@fullstory/babel-plugin-annotate-react": "github:fullstorydev/fullstory-babel-plugin-annotate-react#ryanwang/react-native-web-demo",
+    "@fullstory/babel-plugin-react-native": "^1.2.1",
     "@jest/globals": "^29.5.0",
+    "@kie/act-js": "^2.6.2",
+    "@kie/mock-github": "2.0.1",
     "@ngneat/falso": "^7.1.1",
     "@octokit/core": "4.0.4",
     "@octokit/plugin-paginate-rest": "3.1.0",
@@ -242,6 +235,7 @@
     "@types/js-yaml": "^4.0.5",
     "@types/lodash": "^4.14.195",
     "@types/mapbox-gl": "^2.7.13",
+    "@types/mime-db": "^1.43.5",
     "@types/node": "^20.11.5",
     "@types/pusher-js": "^5.1.0",
     "@types/react": "^18.2.6",
@@ -258,6 +252,7 @@
     "@typescript-eslint/eslint-plugin": "^7.13.1",
     "@typescript-eslint/parser": "^7.13.1",
     "@vercel/ncc": "0.38.1",
+    "@vue/preload-webpack-plugin": "^2.0.0",
     "@welldone-software/why-did-you-render": "7.0.1",
     "ajv-cli": "^5.0.0",
     "babel-jest": "29.4.1",
@@ -293,7 +288,9 @@
     "jest-circus": "29.4.1",
     "jest-cli": "29.4.1",
     "jest-environment-jsdom": "^29.4.1",
+    "jest-expo": "51.0.3",
     "jest-transformer-svg": "^2.0.1",
+    "jest-when": "^3.5.2",
     "link": "^2.1.1",
     "memfs": "^4.6.0",
     "onchange": "^7.1.0",
@@ -304,10 +301,12 @@
     "prettier": "^2.8.8",
     "pusher-js-mock": "^0.3.3",
     "react-compiler-healthcheck": "^0.0.0-experimental-ab3118d-20240725",
+    "react-compiler-runtime": "file:./lib/react-compiler-runtime",
     "react-is": "^18.3.1",
     "react-native-clean-project": "^4.0.0-alpha4.0",
     "react-test-renderer": "18.3.1",
     "reassure": "^1.0.0-rc.4",
+    "semver": "^7.5.2",
     "setimmediate": "^1.0.5",
     "shellcheck": "^1.1.0",
     "source-map": "^0.7.4",
@@ -325,6 +324,7 @@
     "webpack-cli": "^5.0.4",
     "webpack-dev-server": "^5.0.4",
     "webpack-merge": "^5.8.0",
+    "xlsx": "file:vendor/xlsx-0.20.3.tgz",
     "yaml": "^2.2.1"
   },
   "overrides": {

Bonus

This is something I found out while figuring out different solutions for tree-shaking Lodash. There's a plugin called babel-plugin-lodash which only picks the used code from lodash but it didn't work. However, while trying it out we have to set:

['@babel/preset-env', {targets: {node: 20}}]

Now, the lodash-plugin takes no effect on our bundle BUT this target setting reduces the bundle size to 18.4mb. This happens because we are now not targeting the oldest possible browser. This is also recommended in babel docs to specify the target for having reduced bundle size.

Screenshot 2024-09-11 at 4 47 54 PM

From my testing, setting to target: node, 20 doesn't break anything. We can do QA and see if there's something wrong and adjust the node version accordingly.

Final bundle size 18.4mb

Screenshot 2024-09-11 at 4 50 47 PM

Issue OwnerCurrent Issue Owner: @
Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~021843735541723310379
  • Upwork Job ID: 1843735541723310379
  • Last Price Increase: 2024-10-08
Issue OwnerCurrent Issue Owner: @sobitneupane
Copy link

melvin-bot bot commented Sep 16, 2024

Current assignee @mountiny is eligible for the AutoAssignerNewDotQuality assigner, not assigning anyone new.

@szymonrybczak
Copy link
Contributor

Hey I’m Szymon from Callstack - I will take this over from Hur!

Copy link

melvin-bot bot commented Sep 17, 2024

📣 @szymonrybczak! 📣
Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
Please follow these steps:

  1. Make sure you've read and understood the contributing guidelines.
  2. Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
  3. Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
  4. Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
    Screen Shot 2022-11-16 at 4 42 54 PM
    Format:
Contributor details
Your Expensify account email: <REPLACE EMAIL HERE>
Upwork Profile Link: <REPLACE LINK HERE>

Copy link

melvin-bot bot commented Sep 20, 2024

⚠️ Looks like this issue was linked to a Deploy Blocker here

If you are the assigned CME please investigate whether the linked PR caused a regression and leave a comment with the results.

If a regression has occurred and you are the assigned CM follow the instructions here.

If this regression could have been avoided please consider also proposing a recommendation to the PR checklist so that we can avoid it in the future.

@ishpaul777
Copy link
Contributor

ishpaul777 commented Sep 29, 2024

Just a Heads Up, Automation failed Deployed 27/9, Paydate 4/10

@mountiny mountiny changed the title Avoid packaging WDYR and whole LODASH in JS bundle [HOLD for payment 2024-10-04] Avoid packaging WDYR and whole LODASH in JS bundle Oct 1, 2024
@mountiny mountiny added the NewFeature Something to build that is a new item. label Oct 1, 2024
Copy link

melvin-bot bot commented Oct 1, 2024

Triggered auto assignment to @garrettmknight (NewFeature), see https://stackoverflowteams.com/c/expensify/questions/14418#:~:text=BugZero%20process%20steps%20for%20feature%20requests for more details. Please add this Feature request to a GH project, as outlined in the SO.

Copy link

melvin-bot bot commented Oct 1, 2024

⚠️ It looks like this issue is labelled as a New Feature but not tied to any GitHub Project. Keep in mind that all new features should be tied to GitHub Projects in order to properly track external CAP software time ⚠️

Copy link

melvin-bot bot commented Oct 1, 2024

Triggered auto assignment to Design team member for new feature review - @dannymcclain (NewFeature)

@mountiny mountiny added Awaiting Payment Auto-added when associated PR is deployed to production and removed Reviewing Has a PR in review labels Oct 1, 2024
@mountiny mountiny changed the title [HOLD for payment 2024-10-04] Avoid packaging WDYR and whole LODASH in JS bundle [HOLD for payment 2024-10-04] [$250] Avoid packaging WDYR and whole LODASH in JS bundle Oct 1, 2024
Copy link

melvin-bot bot commented Oct 1, 2024

⚠️ Could not update price automatically because there is no linked Upwork Job ID. The BZ team member will need to update the price manually in Upwork.

@mountiny
Copy link
Contributor

mountiny commented Oct 1, 2024

$250 to @ishpaul777

No regression testing required, this was performance improvement and we have included automated way to ensure we do not regress

@dannymcclain
Copy link
Contributor

Feels like I probably don't need to be on this one. Unassigning.

@dannymcclain dannymcclain removed their assignment Oct 1, 2024
Copy link

melvin-bot bot commented Oct 4, 2024

Payment Summary

Upwork Job

  • Contributor: @hurali97 is from an agency-contributor and not due payment
  • Contributor: @szymonrybczak is from an agency-contributor and not due payment
  • ROLE: @ishpaul777 paid $250 via Upwork (LINK)

BugZero Checklist (@garrettmknight)

  • I have verified the correct assignees and roles are listed above and updated the neccesary manual offers
  • I have verified that there are no duplicate or incorrect contracts on Upwork for this job (https://www.upwork.com/ab/applicants//hired)
  • I have paid out the Upwork contracts or cancelled the ones that are incorrect
  • I have verified the payment summary above is correct

@melvin-bot melvin-bot bot added Daily KSv2 and removed Weekly KSv2 labels Oct 8, 2024
@garrettmknight garrettmknight added the External Added to denote the issue can be worked on by a contributor label Oct 8, 2024
Copy link

melvin-bot bot commented Oct 8, 2024

Job added to Upwork: https://www.upwork.com/jobs/~021843735541723310379

@melvin-bot melvin-bot bot added the Help Wanted Apply this label when an issue is open to proposals by contributors label Oct 8, 2024
Copy link

melvin-bot bot commented Oct 8, 2024

Current assignee @ishpaul777 is eligible for the External assigner, not assigning anyone new.

@garrettmknight garrettmknight removed External Added to denote the issue can be worked on by a contributor Help Wanted Apply this label when an issue is open to proposals by contributors labels Oct 8, 2024
@garrettmknight
Copy link
Contributor

@ishpaul777 offer's out to you.

@garrettmknight
Copy link
Contributor

Paid!

@sobitneupane
Copy link
Contributor

I reviewed #49059 PR associated with this issue. I will need payment summary to request payment.

cc: @mountiny @garrettmknight

@garrettmknight
Copy link
Contributor

@mountiny can you add another payment summary for Sobit? I can't since I'm his approver in ND.

@mountiny
Copy link
Contributor

Sorry for missing that @sobitneupane

Given the testing and review of this PR, we should pay $250 to @sobitneupane as well

@garrettmknight
Copy link
Contributor

@sobitneupane request when you're ready!

Copy link

melvin-bot bot commented Oct 28, 2024

@garrettmknight @mountiny Be sure to fill out the Contact List!

@JmillsExpensify
Copy link

Payment summary:

@garrettmknight
Copy link
Contributor

$250 approved for @sobitneupane

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
AutoAssignerNewDotQuality Used to assign quality issues to engineers Awaiting Payment Auto-added when associated PR is deployed to production Daily KSv2 NewFeature Something to build that is a new item.
Projects
None yet
Development

No branches or pull requests

8 participants