Création de plugins¶
Écrivez des plugins personnalisés pour étendre Silex avec de nouvelles fonctionnalités.
Aperçu¶
Un plugin Silex est un module JavaScript qui suit le patron de plugin : il exporte un objet avec une fonction init. Les plugins peuvent être côté client (extensions de l'éditeur), côté serveur (connecteurs et routes), ou les deux.
Structure d'un plugin :
// my-plugin.js
export default {
name: 'MyPlugin',
async init(editor, options) {
console.log('Plugin initialized with options:', options)
},
}
Les plugins reçoivent :
- Plugin client : (editor, options) — l'instance de l'éditeur GrapesJS
- Plugin serveur : (config, options) — l'instance de ServerConfig
Licence : Les plugins ne sont pas considérés comme des oeuvres dérivées de Silex au regard de la licence AGPL. Vous pouvez utiliser n'importe quelle licence pour vos plugins.
Contribuer au coeur de Silex : Si vous souhaitez corriger des bugs ou ajouter des fonctionnalités à Silex lui-même (pas seulement des plugins), consultez le guide de contribution pour les instructions d'installation et les premières tâches à réaliser.
Prérequis¶
- Connaissances en JavaScript/TypeScript
- Compréhension de la configuration client et de la configuration serveur
- Connaissances de base de npm pour le packaging
Plugins côté client¶
Étendez l'éditeur GrapesJS avec une interface personnalisée, des blocs ou des commandes.
Structure de base¶
// plugins/my-editor-plugin.js
export default {
name: 'MyEditorPlugin',
async init(editor, options) {
console.log('Editor plugin initialized')
// Access the editor
const canvas = editor.Canvas
const blocks = editor.Blocks
const commands = editor.Commands
// Add a custom block
blocks.add('my-block', {
label: 'My Custom Block',
content: '<div class="my-block">Custom content</div>',
category: 'Custom',
media: '<svg>...</svg>',
attributes: {
class: 'gjs-fonts gjs-f-h1',
},
})
// Add a custom command
commands.add('my-command', {
run(editor) {
console.log('Command executed')
},
stop(editor) {
console.log('Command stopped')
},
})
// Listen to editor events
editor.on('component:create', (component) => {
console.log('Component created:', component)
})
},
}
Ajouter des panneaux d'interface¶
Ajoutez un panneau personnalisé à l'éditeur :
export default {
name: 'MyPanel',
async init(editor, options) {
const pnm = editor.Panels
// Create a new panel
pnm.addPanel({
id: 'my-panel',
buttons: [
{
id: 'my-button',
label: 'My Button',
command: 'my-command',
className: 'btn-icon-blank',
},
],
})
// Add button to existing toolbar
const topPanel = pnm.getPanel('options')
topPanel.buttons.add([
{
id: 'another-button',
label: 'Another Button',
active: false,
togglable: true,
command: 'another-command',
},
])
},
}
Interagir avec les éléments sélectionnés¶
export default {
name: 'ElementModifier',
async init(editor, options) {
editor.Commands.add('bold-selected', {
run(editor) {
const selected = editor.getSelected()
if (selected) {
selected.addClasses('font-weight-bold')
}
},
})
},
}
Chargement dans la configuration client¶
// client-config.js
import myEditorPlugin from './plugins/my-editor-plugin'
export default async function (config) {
config.grapesJsConfig.plugins = [
...config.grapesJsConfig.plugins,
myEditorPlugin,
]
config.grapesJsConfig.pluginsOpts = {
...config.grapesJsConfig.pluginsOpts,
MyEditorPlugin: {
customOption: 'value',
},
}
}
Plugins côté serveur¶
Étendez le backend avec des connecteurs, des routes ou des écouteurs d'événements.
Structure de base¶
// plugins/my-server-plugin.js
module.exports = {
name: 'MyServerPlugin',
async init(config, options) {
console.log('Server plugin initialized with options:', options)
// Add routes to the Express app
// config.app is available after plugins are loaded
},
}
Ajouter des connecteurs¶
Créez un connecteur de stockage ou d'hébergement personnalisé :
// plugins/my-connector.js
const { Connector, StorageConnector, ConnectorType } = require('@silexlabs/silex-plugins')
class MyStorageConnector extends StorageConnector {
connectorId = 'my-storage'
displayName = 'My Storage'
connectorType = ConnectorType.STORAGE
color = '#ff0000'
background = '#ffeeee'
icon = 'data:image/svg+xml,...'
async isLoggedIn(session) {
// Check if user is logged in
return !!session.myStorageToken
}
async getOAuthUrl(session) {
// Return OAuth login URL
return `https://my-service.com/oauth?client_id=${this.options.clientId}`
}
async listWebsites(session) {
// Return array of websites
return []
}
async createWebsite(session, data) {
// Create a new website
return 'website-id'
}
// ... implement other StorageConnector methods
}
module.exports = {
name: 'MyStorageConnectorPlugin',
async init(config, options) {
config.addStorageConnector(
new MyStorageConnector(config, {
clientId: options.clientId,
clientSecret: options.clientSecret,
})
)
},
}
Ajouter des routes Express¶
module.exports = {
name: 'MyApiPlugin',
async init(config, options) {
// Note: config.addRoutes is called after plugin init
// Store the route setup for later
const originalAddRoutes = config.addRoutes.bind(config)
config.addRoutes = async (app) => {
await originalAddRoutes(app)
// Add custom routes
app.get('/api/my-endpoint', (req, res) => {
res.json({ message: 'Hello from my plugin' })
})
app.post('/api/my-endpoint', (req, res) => {
const data = req.body
// Process data
res.json({ success: true })
})
}
},
}
Écouter les événements du serveur¶
const { ServerEvent } = require('@silexlabs/silex/dist/server/events')
module.exports = {
name: 'MyLoggerPlugin',
async init(config, options) {
config.on(ServerEvent.STARTUP_END, () => {
console.log(`Silex is running at ${config.url}`)
})
config.on(ServerEvent.PUBLISH_START, async (publicationData) => {
console.log(`Publishing: ${publicationData.siteSettings.name}`)
})
config.on(ServerEvent.PUBLISH_END, async (error) => {
if (error) {
console.error(`Publication failed: ${error.message}`)
} else {
console.log('Publication succeeded')
}
})
},
}
Chargement dans la configuration serveur¶
// .silex.js
const myServerPlugin = require('./plugins/my-server-plugin')
module.exports = async function (config) {
await config.addPlugin(myServerPlugin, {
apiKey: process.env.MY_API_KEY,
debug: config.debug,
})
}
Transformateurs de publication¶
Personnalisez la compilation et la publication des sites web. Consultez Transformateurs de publication pour plus de détails.
Exemple de plugin de transformateur de publication :
// plugins/minify-plugin.js
export default {
name: 'MinifyPlugin',
async init(config, options) {
config.addPublicationTransformers({
transformFile(file) {
// Minify HTML files
if (file.type === 'html') {
// Use a minification library
const minified = minifyHtml(file.content)
return { ...file, content: minified }
}
return file
},
transformPath(path, type) {
// Change .css to .min.css
if (type === 'css') {
return path.replace(/\.css$/, '.min.css')
}
return path
},
})
},
}
Chargez dans la configuration client :
import minifyPlugin from './plugins/minify-plugin'
export default async function (config) {
config.grapesJsConfig.plugins.push(minifyPlugin)
}
Plugins full-stack¶
Les plugins peuvent s'exécuter à la fois côté client et serveur :
// plugins/full-stack-plugin.js
// Export two different modules
module.exports.client = {
name: 'MyPlugin-Client',
async init(editor, options) {
// Client-side code
editor.Commands.add('my-command', { /* ... */ })
},
}
module.exports.server = {
name: 'MyPlugin-Server',
async init(config, options) {
// Server-side code
config.addStorageConnector(/* ... */)
},
}
Chargez séparément :
// client-config.js
import { client } from './plugins/full-stack-plugin'
config.grapesJsConfig.plugins.push(client)
// .silex.js
const { server } = require('./plugins/full-stack-plugin')
await config.addPlugin(server, {})
Packager des plugins¶
Pour la publication sur npm¶
Créez une structure de paquet :
my-silex-plugin/
├── package.json
├── dist/
│ ├── client.js
│ ├── server.js
│ └── index.js
├── src/
│ ├── client.js
│ ├── server.js
│ └── index.js
└── README.md
package.json :
{
"name": "my-silex-plugin",
"version": "1.0.0",
"main": "dist/index.js",
"files": ["dist"],
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
},
"peerDependencies": {
"@silexlabs/silex": "^3.0.0"
}
}
Publiez sur npm :
Les utilisateurs installent avec :
Pour un usage privé¶
Conservez les plugins dans le répertoire de votre instance Silex :
my-silex/
├── .silex.js
├── client-config.js
└── plugins/
├── my-storage-connector.js
├── my-editor-plugin.js
└── my-transformer.js
Chargez avec des chemins relatifs :
// .silex.js
const myConnector = require('./plugins/my-storage-connector')
await config.addPlugin(myConnector, {})
// client-config.js
import myEditorPlugin from './plugins/my-editor-plugin'
Support TypeScript¶
Écrivez des plugins en TypeScript pour un meilleur support IDE :
// plugins/my-plugin.ts
import { Editor, EditorConfig } from 'grapesjs'
interface MyPluginOptions {
apiKey: string
debug?: boolean
}
export default {
name: 'MyPlugin',
async init(editor: Editor, options: MyPluginOptions) {
// Fully typed
editor.Blocks.add('my-block', { /* ... */ })
},
}
Compilez en JavaScript :
Tester les plugins¶
Testez avec Jest ou un autre framework de test :
// my-plugin.test.js
describe('MyPlugin', () => {
it('should initialize', async () => {
const mockEditor = {
Commands: { add: jest.fn() },
Blocks: { add: jest.fn() },
}
const plugin = require('./my-plugin')
await plugin.init(mockEditor, { })
expect(mockEditor.Commands.add).toHaveBeenCalled()
})
})
Dépannage¶
Le plugin ne s'initialise pas¶
Vérifiez :
- Le plugin est chargé dans le fichier de configuration
- Le plugin exporte la bonne structure avec
nameetinit - Pas d'erreurs de syntaxe dans le code du plugin
- Les options sont passées correctement
Activez le débogage :
export default {
name: 'MyPlugin',
async init(editor, options) {
console.log('Plugin init called with:', options)
},
}
Conflits du plugin avec GrapesJS¶
Certains plugins modifient le comportement de base de GrapesJS. En cas de conflits :
- Changez l'ordre de chargement
- Vérifiez le code source du plugin pour les surcharges
- Utilisez un plugin différent ou corrigez les conflits
Les routes du plugin serveur ne sont pas accessibles¶
Assurez-vous que les routes sont ajoutées dans config.addRoutes :
config.addRoutes = async (app) => {
await originalAddRoutes(app)
app.get('/my-route', (req, res) => { /* ... */ })
}
Les routes ajoutées en dehors de cette fonction peuvent ne pas fonctionner.
Voir aussi¶
- Utiliser des plugins existants
- Configuration client et plugins — API de configuration client
- Configuration du serveur — API de configuration serveur
- GrapesJS plugin development — Documentation officielle