Editorial cover graphic with a diff-tick motif in gold on cream.

The GSAP module was loading twice on half our pages

A small library dependency fix, and how to tell when Drupal is attaching the same asset twice.

The friction

Any page that used a scroll-triggered component and a component with a simple hover tween loaded gsap.min.js twice: two <script> tags, two separate GSAP instances registered on window, and the second one silently winning, which meant plugins registered against the first instance stopped working with no error.

Why

gsap.libraries.yml defined two libraries against the same physical file, for what seemed like a reasonable reason at the time. A lean gsap/core handled components that only need basic tweens, and a fuller gsap/scrolltrigger covered anything scroll-driven:

gsap.libraries.yml
core:
js:
js/gsap.min.js: {}
scrolltrigger:
js:
js/gsap.min.js: {}
js/ScrollTrigger.min.js: {}

Drupal’s library system dedupes by machine name and resolves dependencies correctly within a single library, but core and scrolltrigger are two different libraries that happen to reference the same file. Nothing about Drupal’s asset system is supposed to notice that two separately-declared libraries point at identical paths; that’s not a case it dedupes, because in the general case it isn’t safe to assume it should. A component depending on gsap/core and another depending on gsap/scrolltrigger on the same page got gsap.min.js attached twice, once from each library.

The fix

scrolltrigger:
dependencies:
- gsap/core
js:
js/gsap.min.js: {}
js/ScrollTrigger.min.js: {}

scrolltrigger now declares gsap/core as a dependency instead of re-listing the same file, so Drupal’s own dependency resolution (which does dedupe within a resolved dependency graph) only ever attaches gsap.min.js once, however many components on the page ask for it. The obvious fix would have been to merge the two libraries back into one, but that reintroduces ScrollTrigger.min.js on every page that only needed a basic tween, which was the entire reason they were split in the first place.

Confirming it

ScrollTrigger.getAll().length before the fix returned inconsistent counts depending on component order on the page. After the fix, it’s deterministic, and the network panel shows one request for gsap.min.js regardless of how many components use it.

Improve custom style definition DX using YamlDiscovery is the tracking issue for the broader Canvas/GSAP work this fix shipped alongside.