Instalación y registro de plugins
Instalar GSAP por npm o por etiqueta script, registrar plugins correctamente, y entender por qué el registro existe y qué se rompe exactamente cuando falta.
Instalar GSAP es npm install gsap y no hay más. Lo que sí tiene sustancia es el registro de plugins: una llamada que parece burocracia, que en desarrollo se puede omitir sin consecuencias, y cuya ausencia produce el bug más desconcertante del ecosistema —funciona perfectamente en local y falla solo en el build de producción, con un mensaje que no señala al problema real—. Entender por qué existe gsap.registerPlugin te ahorra esa tarde.
- Instalar GSAP y sus plugins desde npm o desde una etiqueta
script. - Explicar qué hace
gsap.registerPluginy por qué protege del tree shaking. - Diagnosticar el fallo que solo aparece en producción y arreglarlo.
- Centralizar el registro en un módulo único para no repetirlo.
Las dos formas de traer los ficheros
Con un empaquetador, todo viene del paquete público:
npm install gsap
Ese único paquete trae el núcleo y todos los plugins. No hay paquetes adicionales que instalar ni registros alternativos que configurar. Cada plugin se importa de su propia ruta dentro del paquete:
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { Flip } from 'gsap/Flip';
import { CustomEase } from 'gsap/CustomEase';
Las rutas usan el nombre exacto del plugin, y los tres de SVG llevan el sufijo Plugin en el nombre del módulo: gsap/MorphSVGPlugin, gsap/DrawSVGPlugin, gsap/MotionPathPlugin. Los eases que no están en el núcleo —rough, slow y expoScale— vienen juntos en gsap/EasePack.
Si tu herramienta de construcción no entiende módulos ES, el paquete incluye una copia en formato universal bajo dist:
import { gsap } from 'gsap/dist/gsap';
import { ScrollTrigger } from 'gsap/dist/ScrollTrigger';
Sin empaquetador, con etiquetas script, el orden importa: el núcleo primero y los plugins después.
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/ScrollTrigger.min.js"></script>
<script>
gsap.registerPlugin(ScrollTrigger);
gsap.to('.caja', { x: 200, duration: 1 });
</script>
Con etiquetas script, GSAP intenta autorregistrar los plugins que detecte cargados, así que el registerPlugin es técnicamente opcional en ese escenario. Escríbelo igualmente: cuesta una línea y hace que el código sea portable si algún día ese proyecto pasa a tener empaquetador.
Por qué existe el registro
gsap.registerPlugin hace dos cosas.
La primera es funcional: le dice al núcleo que existe un plugin con ciertos nombres de propiedad reservados, de modo que cuando alguien escriba scrollTrigger: {...} o morphSVG: "#otra" dentro de un objeto vars, el motor sepa que eso no es una propiedad que animar sino una instrucción para el plugin. Sin registro, GSAP ve una propiedad desconocida en un objeto y hace lo que hace con cualquier propiedad desconocida: intenta animarla, no puede, y la ignora en silencio.
La segunda es defensiva, y es la que produce el bug famoso. Los empaquetadores modernos hacen tree shaking: eliminan del bundle final cualquier módulo importado cuyo contenido no se use en ningún sitio. Un plugin de GSAP se importa pero nunca se referencia por su nombre en el código de aplicación: tú escribes scrollTrigger: {...} como una cadena dentro de un objeto, no ScrollTrigger.algo(). Para el analizador estático del empaquetador, esa importación no se usa, así que la elimina.
En desarrollo el tree shaking no se aplica, y por eso todo funciona. En el build de producción el plugin desaparece del bundle, GSAP no lo encuentra, y la animación se ejecuta ignorando la opción del plugin. El síntoma es característico: la animación sí ocurre, pero se dispara de inmediato en vez de con el scroll, o interpola mal, o no fija nada. Nadie relaciona eso con una importación eliminada.
Pasar el plugin como argumento a registerPlugin crea una referencia real que el analizador ve, y la importación sobrevive.
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { SplitText } from 'gsap/SplitText';
// Esta linea no es burocracia: es lo que impide que el
// empaquetador borre las dos importaciones de arriba.
gsap.registerPlugin(ScrollTrigger, SplitText);
Si una animación con scrollTrigger funciona en npm run dev y en producción se dispara toda de golpe al cargar la página, no busques en el CSS ni en el orden de los scripts: te falta el registerPlugin. Es tan consistente que sirve de diagnóstico. Registrar un plugin dos veces no tiene ningún efecto negativo, así que ante la duda, regístralo.
Centralizar el registro
Repetir las importaciones y el registro en cada componente es ruidoso y da lugar a que alguien se olvide en uno. El patrón recomendado es un módulo único que importa, registra y reexporta:
// src/lib/gsap.js
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { Flip } from 'gsap/Flip';
import { CustomEase } from 'gsap/CustomEase';
gsap.registerPlugin(ScrollTrigger, Flip, CustomEase);
// Las curvas propias del proyecto, definidas una sola vez.
CustomEase.create('salida', '0.16, 1, 0.3, 1');
export { gsap, ScrollTrigger, Flip, CustomEase };
// En cualquier componente:
import { gsap } from '../lib/gsap.js';
gsap.to('.panel', { y: 0, autoAlpha: 1, ease: 'salida', duration: 0.6 });
Ese módulo se convierte en el único sitio donde se decide qué plugins entran en el bundle, lo cual es exactamente lo que quieres para poder auditarlo. Y como el registro ocurre en la evaluación del módulo, sucede una sola vez por muchas veces que se importe.
En proyectos con renderizado en servidor hay un matiz: varios plugins tocan el DOM al registrarse o al construirse. La importación en sí es segura, pero la creación de animaciones no lo es. La forma limpia es que el módulo de arriba solo importe y registre —eso no toca el DOM— y que todas las llamadas a gsap vivan en código que solo se ejecuta en el cliente.
Comprobar que ha ido bien
Dos comprobaciones que cuestan diez segundos y evitan media hora:
import { gsap } from './lib/gsap.js';
// 1. Version instalada.
console.log(gsap.version);
// 2. Un plugin registrado responde a su nombre de propiedad.
// Si el plugin falta, esta llamada no dispara nada al hacer scroll.
console.log(typeof ScrollTrigger.getAll === 'function');
Y una comprobación que merece la pena tener en el proyecto: en el build de producción, buscar el nombre del plugin dentro del bundle generado. Si ScrollTrigger no aparece como cadena en ningún fichero de salida, es que se ha caído.
# Tras construir, buscar la huella del plugin en la salida.
grep -rl "ScrollTrigger" dist/ | head
registerPlugin protege la importación del plugin, pero no protege los nombres de ease. Los eases que no están en el núcleo —rough, slow, expoScale— viven en EasePack, y las curvas que defines con CustomEase.create se registran por nombre en una tabla interna. Si escribes ease: 'rough' sin haber importado EasePack, GSAP no encuentra ese ease y cae silenciosamente al ease por defecto, que es power1.out. No hay error, no hay aviso: la animación funciona y se siente distinta. Lo mismo pasa con un CustomEase cuyo módulo de definición no llegó a ejecutarse porque nadie lo importó, que es fácil que ocurra si defines las curvas en un fichero que solo se importa desde un componente que se carga bajo demanda. La regla: los eases con nombre propio se crean en el mismo módulo central donde registras los plugins, y ese módulo se importa desde la entrada de la aplicación, no desde una rama perezosa del árbol.
Provoca el bug a propósito para reconocerlo: monta un proyecto mínimo con un empaquetador, una animación con scrollTrigger, y sin la llamada a registerPlugin. Comprueba que funciona en desarrollo, construye para producción, y observa el fallo. Después añade el registro y verifica en el bundle generado que el nombre del plugin ahora sí aparece.