-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Description
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-renderin the JS bundle - Allow
Lodashto tree-shake the unused functions - Move the development libraries to
devDependencies - And a little bonus at the end
Solution
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: {}}}),
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.
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.
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.
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.
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.
Issue Owner
Current 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






