Skip to content

perf: use cheerio/slim to reduce module load time - #4114

Draft
metalwarrior665 wants to merge 2 commits into
masterfrom
claude/crawlee-v4-import-perf-1wsvos
Draft

perf: use cheerio/slim to reduce module load time#4114
metalwarrior665 wants to merge 2 commits into
masterfrom
claude/crawlee-v4-import-perf-1wsvos

Conversation

@metalwarrior665

@metalwarrior665 metalwarrior665 commented Sep 8, 2026

Copy link
Copy Markdown
Member

This was pure regression. v3 Actor loads modules in 422 ms vs 605 ms of v4. I will do more testing once this is merged to identify other potential issues but that this was the biggest.

AI summary

Switch from the full cheerio entrypoint to the slim variant to improve startup performance.

The slim entrypoint excludes cheerio's parse5 and undici dependencies, which are only needed for cheerio.load(string) and cheerio.fromURL(). Since cheerio-crawler always passes a pre-parsed htmlparser2 document to load(), this code path is never reached. Importing the unused dependencies adds ~100ms of overhead on every module load.

Changes:

  • Import from cheerio/slim instead of cheerio in cheerio-crawler
  • Added explanatory comment documenting why the slim variant is appropriate for this use case

https://claude.ai/code/session_01J5TMh6KxDLgUrjMHtwK8ui

`cheerio`'s main entrypoint statically imports `undici` (for
`cheerio.fromURL()`), `parse5`, `encoding-sniffer` and `whatwg-encoding`.
`CheerioCrawler` parses the body with `htmlparser2`'s `parseDocument()` and
only hands the resulting document to `cheerio.load()`, so none of that code
is ever reachable - but it is compiled on every process start. `undici`
alone pulls in ~1.5 MB of CommonJS plus a chunk of Node's internal modules.

Switching to the `cheerio/slim` entrypoint (same `load()`, htmlparser2-only)
cuts `import { CheerioCrawler } from '@crawlee/cheerio'` from ~385 ms to
~306 ms locally, which closes almost the whole import-time gap against v3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5TMh6KxDLgUrjMHtwK8ui
`htmlToText()`, `extractMicrodata()` and `parseOpenGraph()` all accept either a
raw HTML string or an existing `CheerioAPI`, but awaited `import('cheerio')`
before checking which one they got. Callers passing a `$` they already have -
the common path inside a `CheerioCrawler` handler - paid ~85 ms of module
compilation for a `load()` that was never called. Moving the import into the
string branch drops that to ~2 ms.

`parseHandlesFromHtml()` always parses a string, but does so with
`{ xml: { decodeEntities: true } }`. A truthy `xml` option sets
`_useHtmlParser2`, so cheerio already parses and serializes it with htmlparser2
and dom-serializer instead of parse5 - the slim entrypoint is byte-identical
here and skips the parse5 + undici imports.

The remaining `await import('cheerio')` sites (`parseWithCheerio` /
`waitForSelector` on HttpCrawler, JSDOMCrawler, Playwright and Puppeteer) call
`load()` on an HTML string with no options, so they genuinely use parse5.
Switching those to slim would change the parse tree - htmlparser2 does not
imply `<html>/<head>/<body>`, does no `<table>` foster parenting, and serializes
void SVG elements differently - so they are left alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5TMh6KxDLgUrjMHtwK8ui
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants