-
Notifications
You must be signed in to change notification settings - Fork 78
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
Increase webpack performance by up to 7x 🚀🚀🚀🌑 #5099
Merged
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
purfectliterature
added
Enhancement
Performance
Dependencies
Pull requests that update a dependency file
JavaScript
Pull requests that update JavaScript code
labels
Sep 14, 2022
purfectliterature
force-pushed
the
phillmont/webpack-to-the-moon
branch
from
September 14, 2022 05:01
1eca7a5
to
25b988a
Compare
ekowidianto
approved these changes
Sep 15, 2022
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome work and thanks for this!! @purfectliterature
use foreman start -f Procfile.profile to profile webpack builds on dev
ekowidianto
force-pushed
the
phillmont/webpack-to-the-moon
branch
from
September 15, 2022 00:27
25b988a
to
d6387aa
Compare
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
Dependencies
Pull requests that update a dependency file
Enhancement
JavaScript
Pull requests that update JavaScript code
Performance
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Profiling environment
@babel-core
development
foreman start
.foreman start -f Procfile.profile
command that uses webpack's built-inProgressPlugin
. See 6d8967b.Optimization results
babel-loader
+fork-ts-checker-webpack-plugin
cacheDirectory
** after building once.
So, what's the main bottleneck?
TL;DR:
ts-loader
's built-in typechecking is extremely 🐌 slow 🐌. [+100% 🚀]Setting
ts-loader
totranspileOnly: true
and usingfork-ts-checker-webpack-plugin
for typechecking on a separate process yielded almost similar build times as the one with onlybabel-loader
.Interestingly, we have
@babel/preset-typescript
, butbabel-loader
is set to only run on .js(x) files, so essentially this preset did nothing. As of Babel 7, Babel can already natively handle TypeScript files, so technically usingbabel-loader
only is sufficient for our project. As such,ts-loader
is removed in favour for a leaner webpack build process. However, we do lose typechecking because Babel is only a transpiler; typechecking is done bytsc
.Microsoft outlined ways to circumvent this caveat, but the author ultimately decided to integrate
babel-loader
withfork-ts-checker-webpack-plugin
to bring back true TypeScript typechecking, although it increased fresh builds by ~8-10s. The author believed typechecking is very crucial to our codebase, and the tradeoff is worth it. Anyway, the typechecking gets slow when there are many files to be checked, but that would not be the case in HMRs, thus rebuilds are still (mostly) unaffected byfork-ts-checker-webpack-plugin
.But, that's not enough!
Enabling Babel caching [+60% 🚀]
cacheDirectory
is an option inbabel-loader
that allows for caching non-changing Babel-transpiled codes for future webpack builds. According to their report and the author's tests, webpack builds after 1-2 builds sped up by up to 60%.cacheCompression
is disabled to further speed up Babel's transpilation.Babel 🌳 tree-shaking for MUI and lodash exports [+10% 🚀]
The author noticed during webpack profiling that there was many babel transpilations done on all
@mui/icons-material
, some@mui/material-ui
components, and lodash modules. This is mainly caused by named imports, e.g.,import { Something } from 'something';
. Path imports, e.g.,import Something from 'something/Something';
is actually faster because not the whole library's modules are transpiled.Note that this is different from webpack's tree-shaking, because webpack's tree-shaking is more focused on production bundle size, i.e., by removing unused exports (
usedExports: true
). Babel tree-shaking happens when we start webpack, so the lack of it contributed to long startup times.The author applied
babel-plugin-import
to convert all MUI named imports to path imports as of 093f8d2 andbabel-plugin-lodash
to do the same for lodash as of42d307d.
lodash-webpack-plugin
was not installed, despite the advertised performance benefits, because it might lead to some unexpected behaviours of lodash functions, although so far we only useisNumber
inscribing.js
anddebounce
inform/fields/TextField.jsx
.We might consider lodash-es for smaller build sizes, people say.
Limiting
css-loader
,postcss-loader
, andsass-loader
[+2% 🚀]css-loader
now only works onrc-slider/assets/index.css
because apparently it is used byVideoPlayerSlider.jsx
.postcss-loader
now only works ontheme/index.css
that contains Tailwind's default directives, and custom utilities in the future, if any.sass-loader
now excludesnode_modules
.Additional webpack
development
optimisations [+1% 🚀]The author removed webpack output path info to relieve garbage collection and disabled additional webpack chunk clean-ups in development.
Takeaways and additional notes
⏱️ A new command to profile webpack
There is now a separate webpack configuration for profiling,
webpack.profile.js
, that uses webpack's built-inProgressPlugin
. There is a new command to run Coursemology on profile mode. This command will runyarn build:profile
on client. See 6d8967b.Developers can now monitor the webpack build progress and see the time taken for plugins, loaders, etc. to process what files in our codebase. Note that running webpack on profile mode will be slower than on normal development, notably because of console outputs. The results you obtain may be an overestimation of what the
development
build times will be(, which is good anyway).ℹ️ CSS and Sass loading should be on whitelist
We are lucky that only one CSS file needs to be transpiled, except Tailwind's
index.css
. Since we are transitioning to Tailwind, we should only use Tailwind's utilities, and if there are custom utilities needed, define them only inindex.css
.If for some reasons we need to add CSS files, we should add it to
include:
incss-loader
's options inwebpack.common.js
. The author did not do the same forsass-loader
because we are still amidst React pages migration, and we might still have some Sass files around.❗ Please do NOT add more loose CSS or Sass files moving forward.
ℹ️ Use evaluatable translations identifier for
intl.formatMessage
Do not do
intl.formatMessage({ ...translations.something })
, which is weird 👿. You may face an error like the following:The author fixed this issue as of f924c1a. This issue was not caught because
react-intl
's translations extractor was handled bybabel-plugin-formatjs
, and our TypeScript files were transpiled byts-loader
.ℹ️ Think twice before installing 📦 big packages and importing them 🤔
The big devils in our codebase is moment, lodash, Immutable.js, and MUI. MUI and lodash have been handled properly by tree-shaking above. The problem with these packages for development build startups is named imports. Essentially, if there is a way to optimize them, e.g., with Babel plugins, tree-shaking, etc., then sure, use named imports for best DX. Otherwise, for small, one-time-good-one things, consider path imports. Violate the abstraction principle, the author could not care less, so long HMR times are kept fast.
ℹ️ Consider using
DllPlugin
for webpackWe should move our vendors from chunk-splitting to DllPlugins. Dll-ing is basically moving chunks/parts of code that do not change very often so that they need only be built once and not anymore. This is good for vendor libraries or our own stable libraries. If we move our vendors to Dlls, then our startups will be astronomically faster because we need not repeatedly build recorderjs, fabric, etc. The author did not do this because no time leh.
🤔 OkAy, CoOl, bUt WhAT AbOuT ✨ esbuild ✨?
The author hereby declares his love ❤️ for Vue.js and Evan You, and so esbuild is cool, too. The author did not have time to test esbuild, but here are some pointers that he wishes to extend:
babel-loader
toesbuild-loader
.esbuild-loader
can help us with TypeScript and polyfills, and typechecking is handled separately anyway.esbuild-loader
. We will lose MUI and lodash tree-shaking (should be a way, somehow), but most importantly,react-intl
processing withbabel-plugin-formatjs
. See Support the esbuild plug-in system? evanw/esbuild#111.If we are going to try
esbuild-loader
, which the author very much like to, we need to:esbuild-loader
for MUI and lodash.react-intl
to other (better) libraries, e.g., i18next, that has esbuild support or no reliance on Babel.Thanks for coming this far; hope you enjoy this PR 👉👈. Maybe I should start a dev.to blog.