Skip to content

Collapse: match triggers with matches() instead of a document query - #42975

Open
njarrar wants to merge 1 commit into
twbs:v5-devfrom
njarrar:collapse-trigger-matches
Open

njarrar wants to merge 1 commit into
twbs:v5-devfrom
njarrar:collapse-trigger-matches

Conversation

@njarrar

@njarrar njarrar commented Oct 2, 2026 •

Copy link
Copy Markdown

Requested by Najeeb · project thread

Description

When a Collapse builds its trigger list, it runs SelectorEngine.find(selector) (a document-wide querySelectorAll) for every [data-bs-toggle="collapse"] on the page, only to check whether the result contains its own element. This change uses this._element.matches(selector) instead, which answers the same question without querying the document.

Motivation & Context

On pages with many collapses (long FAQs, accordions, tables with expandable rows), initialization cost grows quickly. Time to initialize every collapse on a page with N collapses, headless Chromium 141, one run each:

N before after
100 84 ms 39 ms
500 6.7 s 2.7 s
1000 52.9 s 18.6 s

Benchmark: N pairs of <button data-bs-toggle="collapse" data-bs-target="#cI"> and <div class="collapse" id="cI">, then Collapse.getOrCreateInstance(el, { toggle: false }) on each, timed with performance.now().

Type of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Refactoring (non-breaking change)
  • Breaking change (fix or feature that would change existing functionality)

Checklist

  • I have read the contributing guidelines
  • My code follows the code style of the project (using npm run lint)
  • My change introduces changes to the documentation
  • I have updated the documentation accordingly
  • I have added tests to cover my changes
  • All new and existing tests passed

Existing trigger tests cover this path; npm run js-test-karma: 809 of 809 pass.

When building its trigger list, each Collapse ran a document-wide
`querySelectorAll` for every `[data-bs-toggle="collapse"]` on the page
just to check whether the result contained its own element. Use
`this._element.matches(selector)` instead, which gives the same answer
without querying the document.

Time to initialize every collapse on a page with N collapses
(headless Chromium 141, one run each):

  N=100:   84 ms -> 39 ms
  N=500:  6.7 s  -> 2.7 s
  N=1000: 52.9 s -> 18.6 s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RCK6vggtNiub9nuny85at8
@njarrar
njarrar requested a review from a team as a code owner October 2, 2026 15:45
@julien-deramond julien-deramond added js JavaScript or TypeScript sources and plugins v5 v5, the v5-dev branch needs-tests The PR needs tests labels Oct 3, 2026
@julien-deramond julien-deramond added performance Speed, size or rendering cost refactoring Internal change, same behaviour labels Oct 10, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

js JavaScript or TypeScript sources and plugins needs-tests The PR needs tests performance Speed, size or rendering cost refactoring Internal change, same behaviour v5 v5, the v5-dev branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants