Aller au contenu

Transformateurs de publication

Intervenez dans le pipeline de publication pour personnaliser le rendu et le déploiement des sites web.

Aperçu

Les transformateurs de publication sont cinq hooks qui s'exécutent pendant le processus de publication :

  1. renderComponent — Redéfinir le rendu HTML des composants GrapesJS
  2. renderCssRule — Redéfinir le rendu des règles CSS de GrapesJS
  3. transformFile — Modifier les fichiers (HTML, CSS, ressources) avant la publication
  4. transformPermalink — Réécrire les URL dans le HTML et le CSS
  5. transformPath — Changer l'emplacement de publication des fichiers sur le serveur

Les transformateurs sont enchaînés : chacun reçoit la sortie du précédent, ce qui permet à plusieurs transformateurs de se composer.

Prérequis

  • Compréhension de la configuration client
  • Familiarité avec le flux de publication
  • Connaissances de base en JavaScript

Interface PublicationTransformer

export interface PublicationTransformer {
  renderComponent?(
    component: Component,
    toHtml: () => string
  ): string | undefined

  renderCssRule?(
    rule: CssRule,
    initialRule: () => StyleProps
  ): StyleProps | undefined

  transformFile?(file: ClientSideFile): ClientSideFile

  transformPermalink?(
    link: string,
    type: ClientSideFileType,
    initiator: Initiator
  ): string

  transformPath?(
    path: string,
    type: ClientSideFileType
  ): string
}

Cas d'utilisation

Minifier le HTML et le CSS

const minifyHtml = require('html-minifier').minify
const postcss = require('postcss')
const cssnano = require('cssnano')

export default async function (config) {
  config.addPublicationTransformers({
    transformFile(file) {
      // Minify HTML
      if (file.type === 'html') {
        file.content = minifyHtml(file.content, {
          removeComments: true,
          collapseWhitespace: true,
          minifyCSS: true,
        })
      }

      // Minify CSS
      if (file.type === 'css') {
        const result = postcss([cssnano()]).process(file.content)
        file.content = result.css
      }

      return file
    },
  })
}

Ajouter du cache busting aux ressources

export default async function (config) {
  const assetHashes = new Map()

  config.addPublicationTransformers({
    transformFile(file) {
      // Generate hash for assets
      if (file.type === 'asset') {
        const crypto = require('crypto')
        const hash = crypto
          .createHash('md5')
          .update(file.content)
          .digest('hex')
          .slice(0, 8)

        const newName = file.name.replace(
          /(\.[^.]+)$/,
          `.${hash}$1`
        )
        assetHashes.set(file.name, newName)
        file.name = newName
      }

      return file
    },

    transformPermalink(link, type, initiator) {
      // Rewrite asset references in HTML/CSS
      if (type === 'asset') {
        const hashed = assetHashes.get(link)
        return hashed ? hashed : link
      }
      return link
    },
  })
}

Réécrire les URL pour un CDN

export default async function (config) {
  config.addPublicationTransformers({
    transformPermalink(link, type, initiator) {
      // Skip external URLs
      if (link.startsWith('http')) {
        return link
      }

      // Point assets to CDN
      if (type === 'asset') {
        return `https://cdn.example.com${link}`
      }

      // Keep HTML links local
      return link
    },
  })
}

Changer la structure des fichiers

export default async function (config) {
  config.addPublicationTransformers({
    transformPath(path, type) {
      // Move CSS to a different directory
      if (type === 'css') {
        return path.replace(/^\/css/, '/styles')
      }

      // Flatten file structure
      if (type === 'asset') {
        return '/' + path.split('/').pop()
      }

      return path
    },

    transformPermalink(link, type, initiator) {
      // Update references in HTML/CSS
      if (type === 'css') {
        link = link.replace(/^\/css/, '/styles')
      }

      if (type === 'asset' && initiator === 'css') {
        // Assets referenced in CSS: adjust relative path
        const newPath = '/' + link.split('/').pop()
        // Go up one level from CSS directory
        return '../' + newPath
      }

      return link
    },
  })
}

Supprimer les commentaires HTML en production

export default async function (config) {
  config.addPublicationTransformers({
    transformFile(file) {
      if (file.type === 'html' && process.env.NODE_ENV === 'production') {
        // Remove HTML comments
        file.content = file.content.replace(/<!--[\s\S]*?-->/g, '')
      }
      return file
    },
  })
}

Ajouter des scripts d'analytics

export default async function (config) {
  config.addPublicationTransformers({
    transformFile(file) {
      if (file.type === 'html') {
        const analyticsScript = `
          <script async src="https://analytics.example.com/track.js"></script>
          <script>
            window.trackingId = '${process.env.TRACKING_ID}'
          </script>
        `

        // Inject before closing </head>
        file.content = file.content.replace(
          /<\/head>/,
          analyticsScript + '</head>'
        )
      }
      return file
    },
  })
}

API des transformateurs

renderComponent

Interceptez le rendu des composants avant que GrapesJS ne les convertisse en HTML :

export default async function (config) {
  config.addPublicationTransformers({
    renderComponent(component, toHtml) {
      // Check component type
      if (component.get('type') === 'image') {
        // Custom logic for images
        const src = component.get('attributes').src
        console.log(`Rendering image: ${src}`)
      }

      // Call the default renderer
      return toHtml()
    },
  })
}

Retourner undefined utilise le rendu par défaut.

renderCssRule

Interceptez le rendu des règles CSS :

export default async function (config) {
  config.addPublicationTransformers({
    renderCssRule(rule, initialRule) {
      // Get the CSS rule
      const selector = rule.getSelector()
      const style = initialRule()

      // Transform the style
      if (selector.includes('button')) {
        style['cursor'] = 'pointer'
      }

      return style
    },
  })
}

Retourner undefined utilise les styles par défaut.

transformFile

Modifiez l'objet fichier (contenu, nom, type, chemin) :

export interface ClientSideFile {
  name: string                    // Filename
  type: ClientSideFileType        // 'html', 'css', 'asset'
  content: string | Buffer        // File content
  path?: string                   // File path on server
}

Exemple :

export default async function (config) {
  config.addPublicationTransformers({
    transformFile(file) {
      // Rename file
      if (file.type === 'css') {
        file.name = file.name + '.min'
      }

      // Transform content
      if (file.type === 'html') {
        file.content = file.content.toUpperCase()
      }

      return file
    },
  })
}

Réécrivez les URL référencées dans le HTML et le CSS :

export default async function (config) {
  config.addPublicationTransformers({
    transformPermalink(link, type, initiator) {
      // type: 'html', 'css', 'asset'
      // initiator: 'html' (in HTML file), 'css' (in CSS file)

      // Example: Make all links absolute
      if (!link.startsWith('http')) {
        return 'https://example.com' + (link.startsWith('/') ? link : '/' + link)
      }

      return link
    },
  })
}

transformPath

Changez l'emplacement de publication des fichiers :

export default async function (config) {
  config.addPublicationTransformers({
    transformPath(path, type) {
      // type: 'html', 'css', 'asset'

      // Example: Prefix all files with '/v1/'
      return '/v1' + (path.startsWith('/') ? path : '/' + path)
    },
  })
}

Enchaîner les transformateurs

Plusieurs transformateurs se composent :

export default async function (config) {
  // Transformer 1: Add hashes
  config.addPublicationTransformers({
    transformFile(file) {
      if (file.type === 'css') {
        file.name = file.name + '.hash'
      }
      return file
    },
  })

  // Transformer 2: Minify
  config.addPublicationTransformers({
    transformFile(file) {
      if (file.type === 'css') {
        file.content = minifyCSS(file.content)
      }
      return file
    },
  })
}

Les transformateurs s'exécutent dans l'ordre. La sortie du transformateur 1 devient l'entrée du transformateur 2.

Patrons courants

Transformations basées sur l'environnement

export default async function (config) {
  if (process.env.NODE_ENV === 'production') {
    config.addPublicationTransformers({
      transformFile(file) {
        // Minify in production only
        if (file.type === 'html') {
          file.content = minifyHtml(file.content)
        }
        return file
      },
    })
  }
}

Transformations conditionnelles basées sur le fichier

export default async function (config) {
  config.addPublicationTransformers({
    transformFile(file) {
      // Only transform specific files
      if (file.name === 'index.html') {
        // Special handling for homepage
        file.content = file.content.replace(/OLD_TEXT/g, 'NEW_TEXT')
      }
      return file
    },
  })
}

Journalisation et débogage

export default async function (config) {
  config.addPublicationTransformers({
    transformFile(file) {
      console.log(`Processing ${file.name} (${file.type})`)
      return file
    },

    transformPath(path, type) {
      console.log(`Path: ${path} → Type: ${type}`)
      return path
    },
  })
}

Considérations de performance

Les transformateurs s'exécutent à chaque publication. Optimisez :

// Cache expensive operations
const styleCache = new Map()

export default async function (config) {
  config.addPublicationTransformers({
    transformFile(file) {
      if (file.type === 'css') {
        // Check cache first
        if (styleCache.has(file.name)) {
          return { ...file, content: styleCache.get(file.name) }
        }

        // Expensive operation
        const processed = processCSS(file.content)
        styleCache.set(file.name, processed)
        file.content = processed
      }

      return file
    },
  })
}

Dépannage

Le transformateur n'est pas appliqué

Vérifiez :

  1. Le transformateur est ajouté dans la configuration client : config.addPublicationTransformers(...)
  2. La syntaxe est correcte (toutes les méthodes sont optionnelles)
  3. Pas d'erreurs dans la console du navigateur
  4. Le transformateur s'exécute sur les bons fichiers (vérifiez le paramètre type)

Ajoutez des journaux pour déboguer :

config.addPublicationTransformers({
  transformFile(file) {
    console.log('Transforming:', file.name, file.type)
    return file
  },
})

Les URL sont cassées après la transformation

Assurez-vous que transformPermalink correspond à transformPath :

config.addPublicationTransformers({
  transformPath(path, type) {
    if (type === 'css') {
      return '/styles/' + path.split('/').pop()
    }
    return path
  },

  transformPermalink(link, type, initiator) {
    // If CSS is in /styles/, update references
    if (type === 'css' && initiator === 'html') {
      return '/styles/' + link.split('/').pop()
    }
    return link
  },
})

Problèmes de performance

Si la publication est lente :

  1. Profilez avec les DevTools
  2. Mettez en cache les opérations coûteuses
  3. Minimisez les lectures/écritures de fichiers
  4. Évitez les opérations bloquantes

Voir aussi

Éditer cette page sur GitLab