Como encolar scripts ES modules y usar import dinámico en WP en WordPress

Este artículo explica en detalle cómo encolar scripts ES Modules en WordPress y cómo aprovechar el import dinámico para carga condicional, code-splitting y optimización. Incluye ejemplos prácticos (PHP y JavaScript), consideraciones sobre atributos type=module, nomodule, CORS, y buenas prácticas de versionado y localización de datos para usar desde los módulos.

Contents

Conceptos clave

  • ES Modules: scripts que usan import/export, ejecutados en modo estricto y con ámbito propio (no pollutan el global).
  • type=module: atributo necesario en la etiqueta ltscriptgt para indicar al navegador que trate el archivo como módulo.
  • import dinámico: la función import() devuelve una promesa y permite cargar módulos bajo demanda.
  • nomodule: atributo para fallback en navegadores antiguos que no soportan módulos.
  • CORS y crossorigin: cuando un módulo importa recursos desde otro origen, el navegador exige CORS la etiqueta script puede requerir crossorigin.

Por qué WordPress no añade type=module automáticamente

wp_enqueue_script y wp_register_script generan etiquetas ltscript src=…gt por defecto sin el atributo type. Para transformar la etiqueta y añadir type=module (o nomodule), hay que usar el filtro script_loader_tag. Además, cuando necesitas atributos extra como crossorigin o construir un fallback nomodule, también se usa ese filtro.

Encolar un módulo ES (ejemplo básico)

En este ejemplo registramos y encolamos el script, y después usamos script_loader_tag para inyectar el atributo type=module. También añadimos un inline script antes del módulo para exponer datos (p. ej. URLs, nonce) en window, ya que los módulos no comparten automáticamente el scope global.

 plugin_dir_url( __FILE__ ),
        nonce   => wp_create_nonce( myplugin-nonce ),
    )
    // Añadir inline script ANTES del módulo para que esté disponible en scope global
    wp_add_inline_script( handle, window.__MYPLUGIN__ =  . wp_json_encode( data ) . , before )

    wp_enqueue_script( handle )
}
add_action( wp_enqueue_scripts, myplugin_enqueue_module )

/
  Añadir type=module para el manejador concreto.
 /
function myplugin_add_module_attribute( tag, handle, src ) {
    if ( myplugin-main === handle ) {
        // Si necesitas crossorigin usa: 
        tag = 
    }
    return tag
}
add_filter( script_loader_tag, myplugin_add_module_attribute, 10, 3 )
?>

Notas sobre el ejemplo

  • wp_add_inline_script con la posición before garantiza que la variable en window esté disponible para el módulo cuando se ejecute.
  • Los módulos son defer por defecto: no es necesario añadir el atributo defer.
  • Usar filemtime para versionado ayuda al cache busting en desarrollo/despliegue.

Fallback para navegadores antiguos (nomodule)

Si necesitas servir un paquete transpilado para navegadores que no soportan módulos, encola un script con nomodule. Importante: no uses el mismo archivo para ambos el navegador ignorará la etiqueta nomodule si ya soporta módulos.


    }
    if ( myplugin-legacy === handle ) {
        return 
    }
    return tag
}
add_filter( script_loader_tag, myplugin_module_and_nomodule_attributes, 10, 3 )
?>

Import dinámico: ejemplos y patrones

El import dinámico permite cargar funcionalidades solo cuando se necesitan. La función importa el módulo y devuelve una promesa con las exportaciones.

Ejemplo simple en main.js (módulo)

// dist/main.js (type=module)
document.querySelector(#load-feature).addEventListener(click, async () => {
    try {
        // Carga condicional
        const module = await import(./features/feature-a.js)
        // Si el módulo exporta una función por defecto:
        module.default()
    } catch (err) {
        console.error(Fallo al cargar feature-a:, err)
    }
})

Uso con URL absoluta desde PHP (nuevo URL basado en window variable)

// En el inline script PHP se expuso window.__MYPLUGIN__.baseUrl
(async () => {
    const base = window.__MYPLUGIN__.baseUrl
    // Construir la URL absoluta para el import (recomendado evitar rutas relativas desde HTML si se sirve desde subcarpetas)
    const moduleUrl = new URL(./dist/features/feature-a.js, base).href
    const { default: initFeature } = await import(moduleUrl)
    initFeature()
})()

Uso de import.meta.url para rutas relativas dentro de un módulo

// modules/entry.js (módulo)
const base = new URL(., import.meta.url).href
const other = new URL(./helper.js, base).href

export async function init() {
    const { helper } = await import(other)
    helper()
}

Aspectos de CORS y atributo crossorigin

Si tu módulo importa recursos desde otro dominio (por ejemplo CDN), el navegador comprueba CORS. Si la respuesta no tiene las cabeceras CORS correctas, la importación fallará. Para que la petición del módulo incluya credenciales o sea anónima, la etiqueta ltscriptgt puede necesitar crossorigin. Puedes añadirlo desde el filtro script_loader_tag.


    }
    return tag
}
add_filter( script_loader_tag, myplugin_add_crossorigin, 10, 3 )
?>

Localización de datos: pasar información de PHP a módulos

Los módulos no acceden al scope global del HTML de la misma forma que scripts clásicos, pero sí pueden leer propiedades en window. La técnica recomendada es añadir un inline script antes del módulo (usando wp_add_inline_script before) que ponga los datos necesarios en window. Evita exponer secretos sensibles.


Errores comunes y cómo evitarlos

  • No añadir type=module: el navegador tratará el archivo como script clásico y fallarán las sentencias import/export.
  • Usar el mismo archivo para module y nomodule: el comportamiento puede ser imprevisible genera dos bundles distintos (uno moderno, otro transpíldo).
  • Rutas relativas incorrectas: cuando encolas desde un plugin/theme la ruta relativa del import dentro del módulo debe resolverse correctamente usar import.meta.url o una base URL desde PHP ayuda.
  • CORS: importar desde otro origen sin cabeceras CORS válidas fallará.
  • Política de CSP: si tu sitio usa Content-Security-Policy que restringe scripts/inline scripts, ajusta para permitir el inline si usas wp_add_inline_script (o evita inline y usa otros mecanismos).

Tabla rápida de atributos y comportamiento

Atributo Qué hace Uso típico
type=module Indica que el script es un módulo ES permite import/export y import() Scripts modernos y code-splitting
nomodule Indica fallback para navegadores sin soporte de módulos Encolado de bundle legacy
crossorigin Controla la forma de compartir credenciales y habilita CORS para importaciones entre orígenes CDN / recursos de terceros

Buenas prácticas y checklist antes del despliegue

  • Transpila y genera un bundle legacy si necesitas soporte para navegadores antiguos mantén el bundle moderno para navegadores actuales.
  • Usa versionado (filemtime o hash) para evitar problemas de caché al desplegar.
  • Evita inline scripts con datos sensibles cualquier valor expuesto en window puede ser visto por el cliente.
  • Prueba import dinámico en varios navegadores (desktop y mobile) y verifica los headers CORS si importas de otro origen.
  • Para plugins/temas con muchas dependencias, considera un sistema de build (Webpack/Rollup/Vite) que emita bundles optimizados y configurables para módulos y legacy.
  • Si necesitas depuración en producción, asegúrate de mapear sourcemaps o tener builds no minificados en entornos de staging.

Resumen final

Encolar ES Modules en WordPress implica registrar/enqueuar el script y modificar la etiqueta mediante script_loader_tag para añadir type=module (y opcionalmente crossorigin o nomodule). El import dinámico es ideal para cargar funcionalidades bajo demanda, mejorar rendimiento y aplicar code-splitting. Usa wp_add_inline_script before para exponer datos necesarios en window, y cuida CORS, versionado y compatibilidad con navegadores antiguos.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *