Aller au contenu

Modules d’extension de moteur de rendu

Les modules d’extension de moteur de rendu de StudioCMS offrent un moyen d’étendre et de personnaliser le processus de rendu de votre application StudioCMS. Ils vous permettent de modifier la façon dont le contenu est rendu côté client en ajoutant des composants personnalisés, des enveloppes ou d’autres modifications au pipeline de rendu.

Création d’un module d’extension de moteur de rendu

Section intitulée « Création d’un module d’extension de moteur de rendu »

Pour créer un module d’extension de moteur de rendu, vous devez définir un module d’extension pour StudioCMS qui enregistre votre type de page personnalisé et le composant associé à son moteur de rendu. Vous trouverez ci-dessous un exemple de création d’un module d’extension de moteur de rendu simple qui ajoute un type de page personnalisé avec un moteur de rendu et un composant d’édition.

mon-module-extension.ts
import {
function definePlugin(options: StudioCMSPlugin): StudioCMSPlugin

Defines a plugin for StudioCMS.

@paramoptions - The configuration options for the plugin.

@returnsThe plugin configuration.

definePlugin
} from 'studiocms/plugins';
import {
const createResolver: (_base: string) => {
resolve: (...path: Array<string>) => string;
}

Allows resolving paths relatively to the integration folder easily. Call it like this:

@param_base - The location you want to create relative references from. import.meta.url is usually what you'll want.

@seehttps://astro-integration-kit.netlify.app/core/create-resolver/

@example

const { resolve } = createResolver(import.meta.url);
const pluginPath = resolve("./plugin.ts");

This way, you do not have to add your plugin to your package.json exports.

createResolver
} from 'astro-integration-kit';
import {
(alias) interface AstroIntegration
import AstroIntegration
AstroIntegration
} from 'astro';
const {
const resolve: (...path: Array<string>) => string
resolve
} =
function createResolver(_base: string): {
resolve: (...path: Array<string>) => string;
}

Allows resolving paths relatively to the integration folder easily. Call it like this:

@param_base - The location you want to create relative references from. import.meta.url is usually what you'll want.

@seehttps://astro-integration-kit.netlify.app/core/create-resolver/

@example

const { resolve } = createResolver(import.meta.url);
const pluginPath = resolve("./plugin.ts");

This way, you do not have to add your plugin to your package.json exports.

createResolver
(import.

The type of import.meta.

If you need to declare that a given property exists on import.meta, this type may be augmented via interface merging.

meta
.
ImportMeta.url: string

The absolute file: URL of the module.

This is defined exactly the same as it is in browsers providing the URL of the current module file.

This enables useful patterns such as relative file loading:

import { readFileSync } from 'node:fs';
const buffer = readFileSync(new URL('./data.proto', import.meta.url));

url
);
// Définit le module d’extension pour StudioCMS
export const
const myPlugin: () => StudioCMSPlugin
myPlugin
= () =>
function definePlugin(options: StudioCMSPlugin): StudioCMSPlugin

Defines a plugin for StudioCMS.

@paramoptions - The configuration options for the plugin.

@returnsThe plugin configuration.

definePlugin
({
StudioCMSPlugin.identifier: string

The identifier of the plugin, usually the package name.

identifier
: 'mon-module-extension',
StudioCMSPlugin.name: string

The name of the plugin, displayed in the StudioCMS Dashboard.

name
: 'Mon module d’extension',
StudioCMSPlugin.studiocmsMinimumVersion: string

The minimum version of StudioCMS required for this plugin to function correctly. This is used to ensure compatibility between the plugin and the StudioCMS core. It should be a semantic version string (e.g., "1.0.0"). If the plugin is not compatible with the current version of StudioCMS, it should not be loaded. This is a required field.

@example "1.0.0"

studiocmsMinimumVersion
: '0.1.0',
StudioCMSPlugin.hooks: {
'studiocms:astro-config'?: PluginHook<{
logger: AstroIntegrationLogger;
addIntegrations: (args_0: AstroIntegration | AstroIntegration[]) => void;
}>;
... 5 more ...;
'studiocms:sitemap'?: PluginHook<...>;
} & Partial<...>
hooks
: {
'studiocms:rendering': ({
setRendering: (args_0: {
pageTypes?: {
label: string;
identifier: string;
fields?: ({
name: string;
label: string;
input: "checkbox";
required?: boolean | undefined;
readOnly?: boolean | undefined;
color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
defaultChecked?: boolean | undefined;
size?: "sm" | "md" | "lg" | undefined;
} | {
name: string;
label: string;
input: "input";
type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined;
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
} | {
name: string;
label: string;
input: "textarea";
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
} | {
options: {
value: string;
label: string;
disabled?: boolean | undefined;
}[];
name: string;
label: string;
input: "radio";
required?: boolean | undefined;
readOnly?: boolean | undefined;
color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
defaultValue?: string | undefined;
direction?: "horizontal" | "vertical" | undefined;
} | {
options: {
value: string;
label: string;
disabled?: boolean | undefined;
}[];
name: string;
label: string;
input: "select";
type?: "search" | "basic" | undefined;
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
} | {
name: string;
label: string;
input: "row";
fields: ({
name: string;
label: string;
input: "checkbox";
required?: boolean | undefined;
readOnly?: boolean | undefined;
color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
defaultChecked?: boolean | undefined;
size?: "sm" | "md" | "lg" | undefined;
} | {
name: string;
label: string;
input: "input";
type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined;
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
} | {
name: string;
label: string;
input: "textarea";
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
} | {
options: {
value: string;
label: string;
disabled?: boolean | undefined;
}[];
name: string;
label: string;
input: "radio";
required?: boolean | undefined;
readOnly?: boolean | undefined;
color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
defaultValue?: string | undefined;
direction?: "horizontal" | "vertical" | undefined;
} | {
options: {
value: string;
label: string;
disabled?: boolean | undefined;
}[];
name: string;
label: string;
input: "select";
type?: "search" | "basic" | undefined;
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
})[];
required?: boolean | undefined;
readOnly?: boolean | undefined;
alignCenter?: boolean | undefined;
gapSize?: "sm" | "md" | "lg" | undefined;
})[] | undefined;
description?: string | undefined;
pageContentComponent?: string | undefined;
rendererComponent?: string | undefined;
apiEndpoint?: string | undefined;
}[] | undefined;
augments?: ({
type: "component";
components ...
setRendering
}) => {
setRendering: (args_0: {
pageTypes?: {
label: string;
identifier: string;
fields?: ({
name: string;
label: string;
input: "checkbox";
required?: boolean | undefined;
readOnly?: boolean | undefined;
color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
defaultChecked?: boolean | undefined;
size?: "sm" | "md" | "lg" | undefined;
} | {
name: string;
label: string;
input: "input";
type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined;
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
} | {
name: string;
label: string;
input: "textarea";
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
} | {
options: {
value: string;
label: string;
disabled?: boolean | undefined;
}[];
name: string;
label: string;
input: "radio";
required?: boolean | undefined;
readOnly?: boolean | undefined;
color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
defaultValue?: string | undefined;
direction?: "horizontal" | "vertical" | undefined;
} | {
options: {
value: string;
label: string;
disabled?: boolean | undefined;
}[];
name: string;
label: string;
input: "select";
type?: "search" | "basic" | undefined;
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
} | {
name: string;
label: string;
input: "row";
fields: ({
name: string;
label: string;
input: "checkbox";
required?: boolean | undefined;
readOnly?: boolean | undefined;
color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
defaultChecked?: boolean | undefined;
size?: "sm" | "md" | "lg" | undefined;
} | {
name: string;
label: string;
input: "input";
type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined;
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
} | {
name: string;
label: string;
input: "textarea";
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
} | {
options: {
value: string;
label: string;
disabled?: boolean | undefined;
}[];
name: string;
label: string;
input: "radio";
required?: boolean | undefined;
readOnly?: boolean | undefined;
color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
defaultValue?: string | undefined;
direction?: "horizontal" | "vertical" | undefined;
} | {
options: {
value: string;
label: string;
disabled?: boolean | undefined;
}[];
name: string;
label: string;
input: "select";
type?: "search" | "basic" | undefined;
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
})[];
required?: boolean | undefined;
readOnly?: boolean | undefined;
alignCenter?: boolean | undefined;
gapSize?: "sm" | "md" | "lg" | undefined;
})[] | undefined;
description?: string | undefined;
pageContentComponent?: string | undefined;
rendererComponent?: string | undefined;
apiEndpoint?: string | undefined;
}[] | undefined;
augments?: ({
type: "component";
components ...
setRendering
({
pageTypes?: {
label: string;
identifier: string;
fields?: ({
name: string;
label: string;
input: "checkbox";
required?: boolean | undefined;
readOnly?: boolean | undefined;
color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
defaultChecked?: boolean | undefined;
size?: "sm" | "md" | "lg" | undefined;
} | {
name: string;
label: string;
input: "input";
type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined;
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
} | {
name: string;
label: string;
input: "textarea";
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
} | {
options: {
value: string;
label: string;
disabled?: boolean | undefined;
}[];
name: string;
label: string;
input: "radio";
required?: boolean | undefined;
readOnly?: boolean | undefined;
color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
defaultValue?: string | undefined;
direction?: "horizontal" | "vertical" | undefined;
} | {
options: {
value: string;
label: string;
disabled?: boolean | undefined;
}[];
name: string;
label: string;
input: "select";
type?: "search" | "basic" | undefined;
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
} | {
name: string;
label: string;
input: "row";
fields: ({
name: string;
label: string;
input: "checkbox";
required?: boolean | undefined;
readOnly?: boolean | undefined;
color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
defaultChecked?: boolean | undefined;
size?: "sm" | "md" | "lg" | undefined;
} | {
name: string;
label: string;
input: "input";
type?: "number" | "password" | "text" | "email" | "tel" | "url" | "search" | undefined;
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
} | {
name: string;
label: string;
input: "textarea";
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
} | {
options: {
value: string;
label: string;
disabled?: boolean | undefined;
}[];
name: string;
label: string;
input: "radio";
required?: boolean | undefined;
readOnly?: boolean | undefined;
color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
defaultValue?: string | undefined;
direction?: "horizontal" | "vertical" | undefined;
} | {
options: {
value: string;
label: string;
disabled?: boolean | undefined;
}[];
name: string;
label: string;
input: "select";
type?: "search" | "basic" | undefined;
required?: boolean | undefined;
readOnly?: boolean | undefined;
placeholder?: string | undefined;
defaultValue?: string | undefined;
})[];
required?: boolean | undefined;
readOnly?: boolean | undefined;
alignCenter?: boolean | undefined;
gapSize?: "sm" | "md" | "lg" | undefined;
})[] | undefined;
description?: string | undefined;
pageContentComponent?: string | undefined;
rendererComponent?: string | undefined;
apiEndpoint?: string | undefined;
}[] | undefined
pageTypes
: [
{
identifier: string
identifier
: 'mon-type-page-personnalise',
label: string
label
: 'Mon type de page personnalisé',
rendererComponent?: string | undefined
rendererComponent
:
const resolve: (...path: Array<string>) => string
resolve
('./components/render.js'),
pageContentComponent?: string | undefined
pageContentComponent
:
const resolve: (...path: Array<string>) => string
resolve
('./components/Editor.astro')
}
]
})
}
}
});

Pour le composant de moteur de rendu, vous devez créer un fichier JavaScript ou TypeScript (si vous avez une étape de compilation) qui exporte un objet conforme au type PluginRenderer. Cet objet doit inclure la logique de rendu de votre type de page personnalisé.

components/render.js
import type {
(alias) interface PluginRenderer
import PluginRenderer

Represents a plugin renderer with optional sanitization and rendering logic.

@propertyname - The unique name of the plugin renderer.

@propertysanitizeOpts - Optional configuration for content sanitization.

@propertyrenderer - Optional asynchronous function to render content as a string.

PluginRenderer
} from 'studiocms/types';
const
const render: {
name: string;
renderer: (content: string) => Promise<string>;
sanitizeOpts: {};
}
render
= {
PluginRenderer.name: string
name
: 'mon-moteur-rendu-personnalise',
PluginRenderer.renderer?: GenericAsyncFn<string, string>
renderer
: async (
content: string
content
: string) => {
// La logique de rendu personnalisée se trouve ici
return
content: string
content
;
},
PluginRenderer.sanitizeOpts?: SanitizeOptions
sanitizeOpts
: {},
} satisfies
(alias) interface PluginRenderer
import PluginRenderer

Represents a plugin renderer with optional sanitization and rendering logic.

@propertyname - The unique name of the plugin renderer.

@propertysanitizeOpts - Optional configuration for content sanitization.

@propertyrenderer - Optional asynchronous function to render content as a string.

PluginRenderer
;
export default
const render: {
name: string;
renderer: (content: string) => Promise<string>;
sanitizeOpts: {};
}
render
;

Pour le composant d’édition, vous devez créer un composant Astro qui fournit une interface utilisateur pour modifier le contenu de votre type de page personnalisé. Ce composant recevra le contenu actuel en tant que propriété et devra émettre des mises à jour du contenu au fur et à mesure que l’utilisateur effectue des modifications. La zone de texte <textarea> ci-dessous est utilisée dans le formulaire d’édition de la page finale, permettant aux utilisateurs de modifier directement le contenu. Par conséquent, toute modification effectuée dans un composant d’édition personnalisé doit mettre à jour la valeur de ce champ <textarea> afin de garantir que le contenu soit correctement enregistré.

components/Editor.astro
---
import type { PluginPageTypeEditorProps } from 'studiocms/types';
interface Props extends PluginPageTypeEditorProps {}
const { content } = Astro.props;
---
<div class="editor-container">
<textarea id="page-content" name="page-content">{content}</textarea>
</div>

Si vous souhaitez créer un frontend personnalisé pour votre projet StudioCMS, vous pouvez utiliser le composant Renderer et le SDK de StudioCMS pour afficher le contenu de StudioCMS à l’aide de vos modules d’extension de rendu personnalisés.