Внедрение скриптов и стилей

Remark и Rehype — это мощные инструменты для работы с Markdown и HTML в экосистеме JavaScript. Remark отвечает за обработку Markdown, а Rehype — за HTML, что позволяет гибко интегрировать скрипты и стили в генерируемый контент.


Подключение Remark и Rehype

Для начала необходимо установить пакеты:

npm install remark remark-html rehype rehype-stringify

Remark выполняет разбор Markdown в AST (Abstract Syntax Tree), а Rehype превращает этот AST в HTML. Встраивание скриптов и стилей чаще всего происходит на этапе Rehype.

import {remark} from 'remark';
import remarkHtml from 'remark-html';
import {rehype} from 'rehype';
import rehypeStringify from 'rehype-stringify';

const markdown = `
# Заголовок
Пример текста с кастомным стилем и скриптом
`;

const html = await remark()
  .use(remarkHtml)
  .process(markdown);

console.log(String(html));

Внедрение inline-стилей

Для добавления стилей прямо в HTML-элементы используется Rehype-плагин rehype-raw для обработки уже сгенерированного HTML и модификации дерева DOM.

import rehypeRaw from 'rehype-raw';
import rehypeAddClasses from 'rehype-add-classes';

const htmlWithStyles = await rehype()
  .data('settings', {fragment: true})
  .use(rehypeRaw)
  .use(rehypeAddClasses, {h1: 'main-title', p: 'text-body'})
  .use(rehypeStringify)
  .process(String(html));

console.log(String(htmlWithStyles));

Здесь rehypeAddClasses позволяет добавить CSS-классы к определённым тегам. Это удобно для систематизации стилей и интеграции с CSS-фреймворками.


Внедрение внешних CSS и JS

Для подключения внешних ресурсов можно динамически создавать элементы link и script в дереве HTML:

import {visit} from 'unist-util-visit';

function addScriptsAndStyles() {
  return (tree) => {
    visit(tree, 'root', (node) => {
      node.children.unshift({
        type: 'element',
        tagName: 'link',
        properties: {rel: 'stylesheet', href: '/styles.css'},
        children: [],
      });
      node.children.push({
        type: 'element',
        tagName: 'script',
        properties: {src: '/script.js', defer: true},
        children: [],
      });
    });
  };
}

const enhancedHtml = await rehype()
  .data('settings', {fragment: true})
  .use(rehypeRaw)
  .use(addScriptsAndStyles)
  .use(rehypeStringify)
  .process(String(html));

console.log(String(enhancedHtml));
  • visit из unist-util-visit позволяет обходить дерево и модифицировать его узлы.
  • link и script можно вставлять в начало или конец документа для корректной загрузки ресурсов.

Inline-скрипты с безопасностью

Inline-скрипты можно вставлять через AST:

function addInlineScript(scriptContent) {
  return (tree) => {
    visit(tree, 'root', (node) => {
      node.children.push({
        type: 'element',
        tagName: 'script',
        properties: {},
        children: [{type: 'text', value: scriptContent}],
      });
    });
  };
}

const scriptContent = `
console.log('Hello from inline script');
`;

const htmlWithInlineScript = await rehype()
  .data('settings', {fragment: true})
  .use(rehypeRaw)
  .use(addInlineScript, scriptContent)
  .use(rehypeStringify)
  .process(String(html));

Важно: inline-скрипты увеличивают риск XSS-атак, поэтому рекомендуется фильтровать контент или использовать CSP (Content Security Policy).


Модификация существующих элементов

Rehype позволяет не только добавлять новые скрипты и стили, но и модифицировать существующие элементы:

function modifyExistingStyles() {
  return (tree) => {
    visit(tree, 'element', (node) => {
      if (node.tagName === 'p') {
        node.properties.style = 'color: red; font-weight: bold;';
      }
    });
  };
}

const htmlModified = await rehype()
  .data('settings', {fragment: true})
  .use(rehypeRaw)
  .use(modifyExistingStyles)
  .use(rehypeStringify)
  .process(String(html));
  • Этот подход эффективен для генерации динамически стилизованных страниц.
  • Применяется для изменения классов, атрибутов или стилей конкретных тегов.

Интеграция с системами сборки

Remark и Rehype отлично работают в связке с Webpack, Vite или Astro:

import remarkGfm from 'remark-gfm';
import rehypePrism from '@mapbox/rehype-prism';

const processedHtml = await remark()
  .use(remarkGfm)
  .use(remarkHtml)
  .use(() => rehype().use(rehypePrism))
  .process(markdown);
  • remarkGfm добавляет поддержку GitHub Flavored Markdown.
  • rehypePrism позволяет подсвечивать код, при этом можно внедрять собственные скрипты для интерактивности.

Советы по производительности

  • Минимизировать количество inline-скриптов и стилей, особенно при большом контенте.
  • Использовать fragment: true для генерации частей HTML, а не полного документа, если планируется дальнейшая интеграция.
  • Применять кеширование сгенерированного HTML при больших объёмах Markdown.

Remark и Rehype предоставляют гибкий и мощный инструментальный набор для внедрения скриптов и стилей. Работа с AST позволяет точно контролировать содержимое, обеспечивая высокую степень кастомизации HTML и безопасное управление ресурсами.