[PM-6355] Speed up CLI startup by ~35% by lazy-loading `DOMParser`
Type of change
- [ ] Bug fix
- [ ] New feature development
- [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [ ] Other
Objective
Bitwarden's CLI takes ~2.5s to run bw --version on my laptop. When poking around in bw.ts, I noticed that an instance of JSDOM was created every time the CLI starts up, even though it's rarely used. If that polyfill is lazily loaded, the CLI starts up ~500ms faster in all circumstances. When running bw --version, thats ~37% faster!
There's quite a few places to speed up the CLI, and I'll be looking into solutions for them in the near future. In a quickly hacked together version, I got about 2x startup time just by lazy-loading dependencies!
Benchmark 1: node new/bw.js --version
Time (mean ± σ): 1.772 s ± 0.018 s [User: 1.861 s, System: 0.212 s]
Range (min … max): 1.752 s … 1.802 s 10 runs
Benchmark 2: node old/bw.js --version
Time (mean ± σ): 2.439 s ± 0.013 s [User: 2.587 s, System: 0.254 s]
Range (min … max): 2.423 s … 2.466 s 10 runs
Summary
node new/bw.js --version ran
1.38 ± 0.02 times faster than node old/bw.js --version
Code changes
-
apps/cli/src/bw.ts:
- I replaced
import * as jsdomwithimport { JSDOM }, as the CLI never uses anything else from jsdom, although it's only used for typing now. - I replaced the old
DOMParserpolyfill (which just immediately created a JSDOM instance) with a lazy-loaded one (which callsnew JSDOMonly whenglobal.DOMParseris accessed).
- I replaced
Before you submit
- Please add unit tests where it makes sense to do so (encouraged but not required)
- If this change requires a documentation update - notify the documentation team
- If this change has particular deployment requirements - notify the DevOps team
- Ensure that all UI additions follow WCAG AA requirements
Thank you for your contribution! We've added this to our internal Community PR board for review. ID: PM-6355
Checkmarx One – Scan Summary & Details – 817940c7-31fe-468b-bea8-8d4aa589292d
No New Or Fixed Issues Found
I've been shifting around dependencies for a while, and I've gotten the startup time ~3.6x faster. All tests are passing and no dependencies/logic have been changed.
I have some questions about the code style/normal practices for Bitwarden:
- Is there a reason that
import typecalls are not used when something is only imported as a type? - Are
require()orimport()statements preferred when dynamically importing modules?tsconfig.eslint.jsonhasmodule: commonjs, so TypeScript complains when usingimport(), but@typescript-eslint/no-var-requirescomplains whenrequire()is used.
Benchmark (with all changes)
Benchmark 1: node build/bw.js --version
Time (mean ± σ): 369.4 ms ± 29.2 ms [User: 364.7 ms, System: 59.0 ms]
Range (min … max): 358.2 ms … 452.4 ms 10 runs
Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.
Benchmark 2: node build.bak/bw.js --version
Time (mean ± σ): 968.1 ms ± 31.4 ms [User: 1019.7 ms, System: 115.7 ms]
Range (min … max): 949.4 ms … 1053.8 ms 10 runs
Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.
Summary
node build/bw.js --version ran
3.62 ± 0.22 times faster than node build.bak/bw.js --version
Flamegraphs
Before
After
Related to https://github.com/bitwarden/cli/pull/530