Skip to content

Bundle: only boot once when the script is added after page load - #3349

Open
goastler wants to merge 4 commits into
mainfrom
fix/bundle-boot-once
Open

goastler wants to merge 4 commits into
mainfrom
fix/bundle-boot-once

Conversation

@goastler

@goastler goastler commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

What changed

  • The bundle now starts up as soon as its script runs, instead of waiting for the script's load event (which fires immediately afterwards anyway).
  • When the script is loaded a second time on the same page, the second copy only renders procaptcha elements that don't have a widget yet, and leaves window.procaptcha pointing at the first copy.

Why

  • Scripts added after the page loaded (Google Tag Manager, apps that load the captcha on demand) hit both startup paths, so every widget was mounted twice and onload ran twice.
  • A tag manager firing the script twice re-mounted every widget the same way.
  • If the script tag couldn't be found (bundle served under another filename) and the page was still loading, nothing rendered at all.

Effect

  • One widget per element and one onload call per script, however the script is added.
  • Single-page apps that add the script again on each route still get the new route's widgets rendered and their onload called.
  • reset() and remove() keep working after a second load.

Tests

  • boot.unit.test.ts: after-load, still-loading, script-tag-not-found, implicit plus onload, and second-copy cases. 5 of the 10 fail on the previous code; all 93 bundle tests pass now.
  • Checked in headless Chrome that a script's load event fires straight after it runs for normal, async, defer, module and injected scripts.

A script injected after the page loaded runs with document.readyState
already complete and then gets its own load event, so both boot triggers
fired: implicit widgets were mounted twice (the first copy orphaned but
still running) and the onload callback ran twice.
The script's load event fires straight after the script runs, so boot no
longer waits for it. That removes the double trigger outright and lets
widgets render when the script tag can't be found while the page is
still loading.

Implicit render marks each element it takes, so a second copy of the
bundle on the same page only renders new elements, and it leaves the
first copy's window.procaptcha in place so reset() and remove() still
reach the existing widgets.
@HughParry
HughParry marked this pull request as ready for review October 26, 2026 17:07
…m/prosopo/captcha into fix/bundle-boot-once

# Conflicts:
#	packages/procaptcha-bundle/src/index.ts

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants