Ir al contenido

Plugins de Renderizado

Los plugins de renderizado de StudioCMS proporcionan una forma de extender y personalizar el proceso de renderizado de tu aplicación StudioCMS. Te permiten modificar cómo se renderiza el contenido en el frontend añadiendo componentes personalizados, envoltorios u otras modificaciones al pipeline de renderizado.

Para comenzar a crear un plugin de renderizado, necesitas definir un plugin de StudioCMS que registre tu tipo de página personalizada y su componente de renderizado asociado. A continuación, se muestra un ejemplo de cómo crear un plugin de renderizado simple que añade un tipo de página personalizada con un renderizador y un componente de editor.

mi-plugin.ts
import {
function definePlugin(options: StudioCMSPluginDef): StudioCMSPluginDef

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
);
// Define el Plugin de StudioCMS
export const
const miPlugin: () => {
readonly identifier: string;
readonly name: string;
readonly studiocmsMinimumVersion?: string | undefined | undefined;
readonly requires?: readonly string[] | undefined;
readonly hooks: {
"studiocms:astro-config"?: ((args: {
readonly logger: AstroIntegrationLogger;
readonly addIntegrations: (args: AstroIntegration | AstroIntegration[]) => Promise<void>;
}) => Promise<void>) | ((args: {
readonly logger: AstroIntegrationLogger;
readonly addIntegrations: (args: AstroIntegration | AstroIntegration[]) => Promise<void>;
}) => void) | undefined;
"studiocms:auth"?: ((args: {
readonly logger: AstroIntegrationLogger;
readonly setAuthService: (args: {
oAuthProvider: {
readonly name: string;
readonly formattedName: string;
readonly svg: string;
readonly endpointPath: string;
readonly requiredEnvVariables?: readonly string[] | undefined;
};
}) => Promise<void>;
}) => Promise<void>) | ((args: {
readonly logger: AstroIntegrationLogger;
readonly setAuthService: (args: {
oAuthProvider: {
readonly name: string;
readonly formattedName: string;
readonly svg: string;
readonly endpointPath: string;
readonly requiredEnvVariables?: readonly string[] | undefined;
};
}) => Promise<void>;
}) => void) | undefined;
"studiocms:dashboard"?: ((args: {
readonly logger: AstroIntegrationLogger;
readonly setDashboard: (args: {
translations: {
[x: string]: {
[x: string]: {
[x: string]: string;
};
};
};
dashboardGridItems?: readonly {
readonly name: string;
readonly span: 1 | 2 | 3;
readonly variant: "default" | "filled";
readonly requiresPermission?: "owner" | "admin" | "editor" | "visitor" | undefined;
readonly header?: {
readonly title: string;
readonly icon?: "heroicons:academic-cap" | "heroicons:academic-cap-16-solid" | "heroicons:academic-cap-20-solid" | "heroicons:academic-cap-solid" | "heroicons:adjustments-horizontal" | "heroicons:adjustments-horizontal-16-solid" | "heroicons:adjustments-horizontal-20-solid" | "heroicons:adjustments-horizontal-solid" | "heroicons:adjustments-vertical" | "heroicons:adjustments-vertical-16-solid" | "heroicons:adjustments-vertical-20-solid" | "heroicons:adjustments-vertical-solid" | "heroicons:archive-box" | "heroicons:archive-box-16-solid" | "heroicons:archive-box-20-solid" | "heroicons:archive-box-arrow-down" | "heroicons:archive-box-arrow-down-16-solid" | "heroicons:archive-box-arrow-down-20-solid" | "heroicons:archive-box-arrow-down-solid" | "heroicons:archive-box-solid" | "heroicons:archive-box-x-mark" | "heroicons:archive-box-x-mark-16-solid" | "heroicons:archive-box-x-mark-20-solid" | "heroicons:archive-box-x-mark-solid" | "heroicons:arrow-down" | "heroicons:arrow-down-16-solid" | "heroicons:arrow-down-20-solid" | "heroicons:arrow-down-circle" | "heroicons:arrow-down-circle-16-solid" | "heroicons:arrow-down-circle-20-solid" | "heroicons:arrow-down-circle-solid" | "heroicons:arrow-down-left" | "heroicons:arrow-down-left-16-solid" | "heroicons:arrow-down-left-20-solid" | "heroicons:arrow-down-left-solid" | "heroicons:arrow-down-on-square" | "heroicons:arrow-down-on-square-16-solid" | "heroicons:arrow-down-on-square-20-solid" | "heroicons:arrow-down-on-square-solid" | "heroicons:arrow-down-on-square-stack" | "heroicons:arrow-down-on-square-stack-16-solid" | "heroicons:arrow-down-on-square-stack-20-solid" | "heroicons:arrow-down-on-square-stack-solid" | "heroicons:arrow-down-right" | "heroicons:arrow-down-right-16-solid" | "heroicons:arrow-down-right-20-solid" | "heroicons:arrow-down-right-solid" | "heroicons:arrow-down-solid" | "heroicons:arrow-down-tray" | "heroicons:arrow-down-tray-16-solid" | "heroicons:arrow-down-tray-20-solid" | "heroicons:arrow-down-tray-solid" | "heroicons:arrow-left" | "heroicons:arrow-left-16-solid" | "heroicons:arrow-left-20-solid" | "heroicons:arrow-left-circle" | "heroicons:arrow-left-circle-16-solid" | "heroicons:arrow-left-circle-20-solid" | "heroicons:arrow-left-circle-solid" | "heroicons:arrow-left-end-on-rectangle" | "heroicons:arrow-left-end-on-rectangle-16-solid" | "heroicons:arrow-left-end-on-rectangle-20-solid" | "heroicons:arrow-left-end-on-rectangle-solid" | "heroicons:arrow-left-on-rectangle" | "heroicons:arrow-left-on-rectangle-20-solid" | "heroicons:arrow-left-on-rectangle-solid" ...
miPlugin
= () =>
function definePlugin(options: StudioCMSPluginDef): StudioCMSPluginDef

Defines a plugin for StudioCMS.

@paramoptions - The configuration options for the plugin.

@returnsThe plugin configuration.

definePlugin
({
identifier: string
identifier
: 'mi-plugin',
name: string
name
: 'Mi Plugin',
studiocmsMinimumVersion?: string | undefined
studiocmsMinimumVersion
: '0.1.0',
hooks: {
"studiocms:astro-config"?: ((args: {
readonly logger: AstroIntegrationLogger;
readonly addIntegrations: (args: AstroIntegration | AstroIntegration[]) => Promise<void>;
}) => Promise<void>) | ((args: {
readonly logger: AstroIntegrationLogger;
readonly addIntegrations: (args: AstroIntegration | AstroIntegration[]) => Promise<void>;
}) => void) | undefined;
... 5 more ...;
"studiocms:sitemap"?: ((args: {
readonly logger: AstroIntegrationLogger;
readonly setSitemap: (args: {
triggerSitemap?: boolean | undefined;
sitemaps?: readonly {
readonly pluginName: string;
readonly sitemapXMLEndpointPath: string;
}[] | undefined;
}) => Promise<void>;
}) => Promise<void>) | ((args: {
readonly logger: AstroIntegrationLogger;
readonly setSitemap: (args: {
triggerSitemap?: boolean | undefined;
sitemaps?: readonly {
readonly pluginName: string;
readonly sitemapXMLEndpointPath: string;
}[] | undefined;
}) => Promise<void>;
}) => void) | undefined;
}
hooks
: {
'studiocms:rendering': ({
setRendering: (args: {
pageTypes?: readonly {
readonly identifier: string;
readonly label: string;
readonly description?: string | undefined;
readonly fields?: readonly ({
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "checkbox";
readonly defaultChecked?: boolean | undefined;
readonly size?: "sm" | "md" | "lg" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "input";
readonly type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "textarea";
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "radio";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly defaultValue?: string | undefined;
readonly direction?: "horizontal" | "vertical" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "select";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly type?: "search" | "basic" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly fields: readonly ({
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "checkbox";
readonly defaultChecked?: boolean | undefined;
readonly size?: "sm" | "md" | "lg" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "input";
readonly type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "textarea";
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "radio";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly defaultValue?: string | undefined;
readonly direction?: "horizontal" | "vertical" | undefined;
} | {
readonly ...
setRendering
}) => {
setRendering: (args: {
pageTypes?: readonly {
readonly identifier: string;
readonly label: string;
readonly description?: string | undefined;
readonly fields?: readonly ({
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "checkbox";
readonly defaultChecked?: boolean | undefined;
readonly size?: "sm" | "md" | "lg" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "input";
readonly type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "textarea";
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "radio";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly defaultValue?: string | undefined;
readonly direction?: "horizontal" | "vertical" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "select";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly type?: "search" | "basic" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly fields: readonly ({
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "checkbox";
readonly defaultChecked?: boolean | undefined;
readonly size?: "sm" | "md" | "lg" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "input";
readonly type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "textarea";
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "radio";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly defaultValue?: string | undefined;
readonly direction?: "horizontal" | "vertical" | undefined;
} | {
readonly ...
setRendering
({
pageTypes?: readonly {
readonly identifier: string;
readonly label: string;
readonly description?: string | undefined;
readonly fields?: readonly ({
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "checkbox";
readonly defaultChecked?: boolean | undefined;
readonly size?: "sm" | "md" | "lg" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "input";
readonly type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "textarea";
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "radio";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly defaultValue?: string | undefined;
readonly direction?: "horizontal" | "vertical" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "select";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly type?: "search" | "basic" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly fields: readonly ({
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "checkbox";
readonly defaultChecked?: boolean | undefined;
readonly size?: "sm" | "md" | "lg" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "input";
readonly type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "textarea";
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "radio";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly defaultValue?: string | undefined;
readonly direction?: "horizontal" | "vertical" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "select";
readonly options: {
readonly value: string;
readonly label: string;
readonly ...
pageTypes
: [
{
identifier: string
identifier
: 'mi-tipo-de-pagina-personalizado',
label: string
label
: 'Mi Tipo de Página Personalizado',
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')
}
]
})
}
}
});

Para el componente renderizador, debes crear un archivo JavaScript o TypeScript (si cuentas con una etapa de compilación) que exporte un objeto acorde al tipo PluginRenderer. Este objeto debe incluir la lógica de renderizado para tu tipo de página personalizado.

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
: 'mi-renderizador-personalizado',
PluginRenderer.renderer?: GenericAsyncFn<string, string>
renderer
: async (
content: string
content
: string) => {
// La lógica de renderizado personalizada va aquí
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
;

Para el componente editor, necesitas crear un componente Astro que proporcione una interfaz de usuario para editar el contenido de tu tipo de página personalizada. Este componente recibirá el contenido actual como una propiedad y debería emitir actualizaciones al contenido a medida que el usuario realiza cambios. El <textarea> a continuación se utiliza en el formulario final de edición de página, permitiendo a los usuarios editar el contenido directamente. Por lo tanto, cualquier cambio realizado en cualquier componente editor personalizado debe actualizar el valor de este <textarea> para asegurar que el contenido se guarde correctamente.

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 quieres construir un frontend personalizado para tu proyecto StudioCMS, puedes usar el componente Renderizador de StudioCMS y el SDK para renderizar el contenido de StudioCMS usando tus plugins de renderizado personalizados.