Como usar Vite para desarrollar bloques y assets en WordPress en WordPress

Contents

Introducción

Este tutorial explica, con todo lujo de detalles, cómo usar Vite para desarrollar bloques (Gutenberg) y gestionar assets en WordPress. Cubre la configuración del entorno, la integración con el servidor de desarrollo de Vite (HMR), la generación de builds optimizados y la forma correcta de encolar scripts y estilos desde PHP usando el manifiesto generado por Vite. Se mostrará código real y plantillas tanto para el frontend/editor como para el servidor PHP de WordPress.

Conceptos clave

  • Vite: herramienta moderna de bundling y servidor de desarrollo con HMR nativo para ESM.
  • Dev server / HMR: permite ver cambios en caliente durante el desarrollo sin build manual.
  • Externals: declarar paquetes de WordPress (por ejemplo @wordpress/blocks) como externos para no incluirlos en el bundle y usar las APIs globales que WP ya carga.
  • Manifest: Vite puede generar un manifest (manifest.json) con los nombres con hash para que PHP encole correctamente los assets en producción.
  • Integración PHP: en desarrollo se apunta al servidor de Vite en producción se encolan los archivos construidos leyendo manifest.json.

Estructura de proyecto recomendada

my-plugin/
├─ src/
│  ├─ index.js         (entrada del bloque/editor)
│  ├─ editor.css
│  └─ style.css        (estilos front)
├─ vite.config.js
├─ package.json
├─ dist/               (salida de build)
├─ manifest.json       (generado por Vite en producción)
└─ my-plugin.php       (plugin principal que registra bloques y assets)

Instalación y dependencias

Instalar Vite y dependencias mínimas. Ejemplo con uso de PostCSS/Tailwind opcional:

npm init -y
npm install --save-dev vite @vitejs/plugin-react # si usas React si usas @wordpress/element no necesitas react directo
# Además instala herramientas CSS si las usas:
npm install --save-dev postcss autoprefixer sass
# Opcionalmente usa tailwind
npm install --save-dev tailwindcss

Ejemplo de scripts en package.json:

{
  scripts: {
    dev: vite,
    build: vite build,
    preview: vite preview
  }
}

Configuración recomendada de Vite

Vite debe configurarse para no bundlear los paquetes de WordPress (se declaran como externos). Además hay que habilitar la generación del manifiesto en producción.

// vite.config.js
import { defineConfig } from vite
import path from path

const wpExternals = [
  @wordpress/blocks,
  @wordpress/components,
  @wordpress/element,
  @wordpress/i18n,
  @wordpress/editor,
  @wordpress/block-editor,
  @wordpress/data
]

export default defineConfig(({ mode }) => ({
  root: path.resolve(__dirname, src),
  base: mode === development ? / : /wp-content/plugins/my-plugin/dist/,
  build: {
    outDir: path.resolve(__dirname, dist),
    emptyOutDir: true,
    manifest: true,
    rollupOptions: {
      input: {
        editor: path.resolve(__dirname, src/index.js),
        style: path.resolve(__dirname, src/style.css)
      },
      external: wpExternals,
      output: {
        // Mapear externals a los globals de WordPress (no siempre necesario en ESM, pero evita bundling)
        globals: {
          @wordpress/blocks: wp.blocks,
          @wordpress/components: wp.components,
          @wordpress/element: wp.element,
          @wordpress/i18n: wp.i18n,
          @wordpress/editor: wp.editor,
          @wordpress/block-editor: wp.blockEditor,
          @wordpress/data: wp.data
        }
      }
    },
    assetsInlineLimit: 4096
  },
  resolve: {
    alias: {
      @: path.resolve(__dirname, src)
    }
  },
  server: {
    port: 5173,
    strictPort: false,
    hmr: {
      protocol: ws
    }
  }
}))

Ejemplo de bloque: código JS y CSS

Ejemplo sencillo de registro de bloque usando las APIs oficiales de WordPress. Importamos desde @wordpress/ para usar las dependencias externas (no serán bundleadas).

// src/index.js
import { registerBlockType } from @wordpress/blocks
import { __ } from @wordpress/i18n
import { useBlockProps } from @wordpress/block-editor
import ./editor.css
import ./style.css

registerBlockType(mi-plugin/mi-bloque, {
  apiVersion: 2,
  title: __(Mi Bloque con Vite, mi-plugin),
  icon: smiley,
  category: widgets,
  edit: () => {
    const blockProps = useBlockProps()
    return (
      

Bloque en desarrollo con Vite (Editor)

) }, save: () => { const blockProps = useBlockProps.save() return (

Bloque compilado por Vite (Frontend)

) } })
/ src/editor.css /
.wp-block-mi-plugin-mi-bloque {
  border: 1px dashed #7f7f7f
  padding: 12px
}
/ src/style.css /
.wp-block-mi-plugin-mi-bloque {
  background: #f7f7f7
  padding: 10px
  border-radius: 4px
}

Encolar assets en desarrollo (dev server de Vite)

Durante el desarrollo conviene apuntar directamente al servidor de Vite para aprovechar HMR. Una forma robusta es añadir una condición en tu plugin para cuando WP_DEBUG esté activo y el servidor de Vite esté accesible.

// my-plugin.php (fragmento)
function mi_plugin_enqueue_dev() {
    if ( ! defined( WP_DEBUG )  ! WP_DEBUG ) {
        return
    }

    vite_url = http://localhost:5173
    // Script de entrada en src: /src/index.js -> el server de Vite sirve ESM, por eso inyectamos un tag module
    add_action( admin_enqueue_scripts, function() use ( vite_url ) {
        echo 
    })

    // Para frontend durante desarrollo (si necesitas estilos o preview)
    add_action( wp_enqueue_scripts, function() use ( vite_url ) {
        echo 
    })
}
add_action( init, mi_plugin_enqueue_dev )

Notas:

  • Usamos una inyección directa del script type=module apuntando al servidor Vite: así HMR funciona de forma nativa.
  • Otra alternativa es usar script_loader_tag para convertir una cola de WordPress en type=module, pero la inyección directa es más simple y fiable en la fase de desarrollo.

Encolar assets en producción (build) usando manifest.json

Cuando ejecutas vite build se genera la carpeta dist y, si habilitaste manifest: true, un manifest.json con todos los nombres con hash. El siguiente ejemplo lee el manifest y encola scripts y estilos para editor y frontend.

// my-plugin.php (fragmento)
function mi_plugin_get_manifest() {
    manifest_path = plugin_dir_path( __FILE__ ) . dist/manifest.json
    if ( ! file_exists( manifest_path ) ) {
        return null
    }
    manifest = json_decode( file_get_contents( manifest_path ), true )
    return manifest ?: null
}

function mi_plugin_register_assets() {
    manifest = mi_plugin_get_manifest()
    dist_url = plugin_dir_url( __FILE__ ) . dist/

    if ( ! manifest ) {
        return
    }

    // Registrar y encolar editor script
    if ( isset( manifest[editor.js] ) ) {
        editor = manifest[editor.js]
        wp_register_script(
            mi-plugin-editor,
            dist_url . editor[file],
            [ wp-blocks, wp-i18n, wp-element, wp-editor ], // dependencias de WP
            filemtime( plugin_dir_path( __FILE__ ) . dist/ . editor[file] ),
            true
        )

        // Si el manifest incluye imports (chunks), regístralos también
        if ( ! empty( editor[imports] ) ) {
            foreach ( editor[imports] as import ) {
                if ( isset( manifest[ import . .js ] ) ) {
                    imp = manifest[ import . .js ]
                    wp_register_script(
                        mi-plugin- . sanitize_title( import ),
                        dist_url . imp[file],
                        [],
                        filemtime( plugin_dir_path( __FILE__ ) . dist/ . imp[file] ),
                        true
                    )
                }
            }
        }
    }

    // Registrar estilos si existen
    if ( isset( manifest[style.css] ) ) {
        style = manifest[style.css]
        wp_register_style(
            mi-plugin-style,
            dist_url . style[file],
            [],
            filemtime( plugin_dir_path( __FILE__ ) . dist/ . style[file] )
        )
    }

    // Registrar el block type: enlazar el handle del editor y estilo
    register_block_type( plugin_dir_path( __FILE__ ) . block.json, [
        editor_script => mi-plugin-editor,
        style => mi-plugin-style
    ] )
}
add_action( init, mi_plugin_register_assets )

Observaciones prácticas:

  • Si tu build genera varios chunks, puedes optimizar la lectura del manifest para encolar todos los imports correctamente. El ejemplo anterior muestra una idea básica.
  • Usa filemtime para cache-busting en el parámetro de versión.

Externals: por qué y cómo

Al desarrollar para WordPress es recomendable no bundlear las librerías que WP ya provee (p. ej. @wordpress/). Así reduces el tamaño del bundle y evitas conflictos. En la configuración de Vite (rollupOptions.external) se listan los paquetes que no deseas empaquetar. En tu código importa desde @wordpress/… y deja que WordPress los provea en tiempo de ejecución.

// fragmento de vite.config.js (resumen)
rollupOptions: {
  external: [
    @wordpress/blocks,
    @wordpress/i18n,
    @wordpress/element,
    @wordpress/components
  ],
  output: {
    globals: {
      @wordpress/blocks: wp.blocks,
      @wordpress/i18n: wp.i18n,
      @wordpress/element: wp.element,
      @wordpress/components: wp.components
    }
  }
}

HMR dentro del editor Gutenberg

Para que HMR funcione dentro del editor debes asegurarte de que el editor cargue el bundle desde el dev server (type=module). Con la inyección del script como vimos más arriba, Vite provee HMR y las actualizaciones aparecerán tanto en el editor como (si cargas el script en frontend) en el site preview.

Si trabajas con React y JSX, usa el plugin react de Vite o @vitejs/plugin-react-refresh para mantener HMR con componentes React. Si prefieres la API de WordPress (@wordpress/element), HMR es compatible con ESM también y Vite recargará módulos según corresponda.

Soporte para CSS, SASS y Tailwind

  • Importa tus CSS/SCSS directamente desde los módulos JS: import ./editor.css Vite procesará CSS y generará archivos CSS separados en el build.
  • Para SCSS necesitas instalar sass y configurar los preprocessors en Vite si hace falta.
  • Para Tailwind, configura postcss.config.js y tailwind.config.js y luego importa tu archivo CSS que contiene las directivas @tailwind.

Buenas prácticas y recomendaciones

  • Usa @wordpress/element en lugar de React directo si quieres aprovechar las dependencias que WP expone.
  • Desarrolla con WP_DEBUG activado y con el dev server apuntando solo en build de producción encola los assets compilados.
  • Mantén externals sincronizados: si importas un paquete desde @wordpress/ asegúrate de listarlo en external para evitar bundling.
  • Evita cargar assets de desarrollo en entornos de producción: controla mediante constantes (WP_DEBUG, WP_ENV) o mediante opciones del plugin.
  • Si tu plugin es reutilizable, empaqueta el manifest.json y los archivos dist correctamente para su despliegue.

Depuración de problemas comunes

  1. HMR no funciona en editor: comprueba que el script se inyecta como type=module y que la URL del dev server es accesible desde el navegador (CORS / firewall).
  2. Paquetes duplicados o errores de React: evita bundlear react/react-dom y usa @wordpress/element o marca react como external y mapea el global apropiadamente.
  3. En producción no aparecen estilos: revisa manifest.json para ver si los CSS se generaron con otro nombre y encolaste el fichero incorrecto.
  4. Rutas incorrectas en producción: ajusta la opción base en vite.config.js para que coincida con la URL real donde se servirán los assets.

Resumen final

Integrar Vite con el desarrollo de bloques de WordPress ofrece mejoras sustanciales en flujo de trabajo: recarga instantánea, builds rápidos y mejor experiencia de desarrollo. Claves: declarar externals para las APIs de WordPress, usar el dev server para HMR en desarrollo y leer manifest.json en producción para encolar los assets correctos. Sigue las prácticas mostradas en este artículo y tendrás un entorno moderno y eficiente para construir bloques Gutenberg con Vite.

Referencias útiles: https://vitejs.dev/, https://developer.wordpress.org/block-editor/



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 *