Open filipsobol opened 2 weeks ago
[!IMPORTANT]
This comment shows progress (from top to bottom) of how each change improved the bundle size. The bundles are minified, but not gzipped.
Name | Version | Vite | esbuild | webpack |
---|---|---|---|---|
Baseline | 0.0.0-nightly-20240426.0 | 1377 kb | 1412 kb | 1411 kb |
#__PURE__ before mixins |
0.0.0-nightly-20240509.0 | 1374 kb | 1396 kb | 1411 kb |
Bumping target to ES2022 | 0.0.0-nightly-20240510.0 | 679 kb | 1295 kb | 1296 kb |
#__PURE__ in commercial |
0.0.0-nightly-20240515.0 | 685 kb | 1081 kb | 1159 kb |
Name | Version | Vite | esbuild | webpack |
---|---|---|---|---|
Baseline | 0.0.0-nightly-20240426.0 | 1819 kb | 1779 kb | 1793 kb |
#__PURE__ before mixins |
0.0.0-nightly-20240509.0 | 1820 kb | 1773 kb | 1794 kb |
Bumping target to ES2022 | 0.0.0-nightly-20240510.0 | 1537 kb | 1715 kb | 1734 kb |
#__PURE__ in commercial |
0.0.0-nightly-20240515.0 | 1556 kb | 1608 kb | 1693 kb |
When working on the new installation methods (NIM), we noticed that the bundle size in the consuming projects was larger than we expected. Investigation showed that there was a lot of code in various core and commercial packages that was not properly tree-shaken.
Although we noticed this while working on NIM, this is not a new problem. Because in the old installation methods the plugins are imported directly from their respective packages, it was much less likely that the code that wasn't properly tree-shaken would end up unused. However, in NIM, all core and commercial packages are imported from the
ckeditor5
andckeditor5-premium-feature
packages. This means that pulling code from one plugin will also pull side-effects from all other plugins in these packages.How significant is this issue? When using the
0.0.0-nightly-20240426.0
build, the following imports will result in a bundle size of 1390.65 kB in Rollup and esbuild (minified, not gzipped):In a perfect world, the bundle size should be 0.0 kB because we didn't import anything specific. However, about 550 kB of that 1390.65 kB are essential packages (
engine
,ui
,core
,utils
,watchdog
) that are used by every plugin. While we could try to make this part tree-shakable, that's not the main focus of this task. The main focus now is to make the other 840 kB tree-shakable.Our initial tests show that we can reduce the bundle size to 738 kB (or 182 kB minzipped) without major code changes, with the potential to reduce the size by another 150 kB. This would be a 58% reduction from the current state.
Problems
Below are the main problems that we identified.
Static methods and fields
In our codebase, we make heavy use of static methods and fields. While support for static public methods was introduced in ES2015, support for static private methods and static fields (public and private) was added in ES2022.
Because support for all four combinations has long been available in TypeScript, it and other bundlers had to transpile them to support the older browser. Unfortunately, these workarounds make the output code less tree-shakable. For a simple example, consider the following code:
Since public static methods are well supported, the
public static method()
is transpiled tostatic method()
. However, thepublic static field
is transpiled to code that cannot be tree-shaken, and the output varies between transpilers. See the following links for TypeScript, esbuild, and SWC outputs:Change the output target to ES2022 to see how the workarounds disappears to something that can be easily analyzed and tree-shaken.
We can't change the target in the entire codebase because that would break support for webpack 4 (that's why we only target ES2019 for now), but we can change the target only in our new CLI tool to only affect new installation methods.
Before we can do that, however, we need to fix the problem described in #14703, because part of our code is not valid and only works because of the transpilation.
Mixins
We use mixins in our codebase to share code between classes. However, the current implementation of mixins in TypeScript is not tree-shakable. Consider the following code:
When statically analyzing the code, it's impossible for bundlers to know what
ObservableMixin()
will return and if it contains side effects. This makes (some) classes that extend mixins not tree-shakable.This can be easily fixed by adding the
#__PURE__
comment to all mixin calls, like this:Alternatively (or even preferably), we could try annotating the mixin functions with
#__NO_SIDE_EFFECTS__
, but we need to make sure that this comment works when using webpack, Vite, and esbuild.Other problems
mix
.is
methods right, we used some workarounds which are not tree-shakable. We should move theis
implementations to static methods without breaking the API / types.turndown
andmarked
makes the bundlers unable to tree-shake them. We should refactor the code to fix it.How to debug
(One time) Setup repository
external
folder in the root of the repository.external
folder.external/ckeditor5-dev/packages/ckeditor5-dev-build-tools/src/config.ts
file and add theexperimentalLogSideEffects: true
property to the object returned from thegetRollupConfig
function.external/ckeditor5-dev/packages/ckeditor5-dev-build-tools/src/build.ts
file and addpreserveModules: true
to the object passed to thebuild.write
in thebuild
function (not thegenerateUmdBuild
function).yarn reinstall
in the root of theckeditor5
repository. This should install all dependencies and build the changes we made in theckeditor5-dev
repository.(One time) Setup Verdaccio
.npmrc
file and addregistry=http://localhost:4873/
to it.npm adduser --registry http://localhost:4873/
. Make sure to name the userckeditor
(this is required by one of our internal scripts).(One time) Setup test repository
vite
directory create the.npmrc
file and addregistry=http://localhost:4873/
to it.npm install
in thevite
directory.vite/src/main.ts
to:Test changes
In the
ckeditor5
repository:git add .
).npm run release:prepare-packages -- --nightly
in the root of theckeditor5
repository (you can comment out some of the steps in thescripts/release/preparepackages.js
file to speed up the process).npm run release:publish-packages -- --nightly
to publish the changes to Verdaccio.git restore .
).In the
new-installation-methods
repository:vite
directory.npm run build
to see the changes in the bundle size.meta.json
file to the esbuild analyzer for a more detailed analysis.