Интеграция с файловой системой

Для полноценной интеграции Remark и Rehype с файловой системой используются стандартные модули Node.js, такие как fs и path, а также сторонние библиотеки для удобного обхода каталогов и обработки асинхронных операций. Основная задача — читать исходные Markdown-файлы, преобразовывать их в дерево AST (Abstract Syntax Tree), модифицировать или анализировать структуру, а затем сохранять результат в файл или отправлять дальше по конвейеру обработки.


Чтение и запись файлов

Для работы с файловой системой применяется модуль fs/promises, позволяющий использовать промисы вместо колбэков:

import fs from 'fs/promises';
import path from 'path';
import {remark} from 'remark';
import remarkHtml from 'remark-html';

const inputFilePath = path.join(process.cwd(), 'example.md');
const outputFilePath = path.join(process.cwd(), 'example.html');

async function processFile() {
    const markdown = await fs.readFile(inputFilePath, 'utf-8');
    const processed = await remark().use(remarkHtml).process(markdown);
    await fs.writeFile(outputFilePath, processed.toString(), 'utf-8');
}

processFile();

Ключевые моменты:

  • fs.readFile и fs.writeFile используются для асинхронного чтения и записи файлов.
  • remark().use(remarkHtml).process преобразует Markdown в HTML через AST.
  • path.join обеспечивает корректную работу с путями на разных ОС.

Обход каталогов

Для обработки сразу нескольких файлов удобно использовать рекурсивный обход каталогов. В Node.js можно применить fs.readdir с опцией { withFileTypes: true }:

async function getMarkdownFiles(dir) {
    const entries = await fs.readdir(dir, { withFileTypes: true });
    const files = [];

    for (const entry of entries) {
        const fullPath = path.join(dir, entry.name);
        if (entry.isDirectory()) {
            files.push(...await getMarkdownFiles(fullPath));
        } else if (entry.isFile() && entry.name.endsWith('.md')) {
            files.push(fullPath);
        }
    }

    return files;
}

Особенности:

  • Проверка entry.isDirectory() позволяет рекурсивно обойти подкаталоги.
  • Фильтрация по расширению .md выделяет только Markdown-файлы.
  • Результат — массив абсолютных путей к Markdown-документам.

Интеграция с плагинами Remark и Rehype

Remark и Rehype используют систему плагинов для анализа, модификации и генерации контента. Плагины могут обрабатывать как синтаксис Markdown, так и дерево HTML:

import remarkSlug from 'remark-slug';
import remarkAutolinkHeadings from 'remark-autolink-headings';
import rehypePrettyCode from 'rehype-pretty-code';

async function transformMarkdown(markdown) {
    const result = await remark()
        .use(remarkSlug) // Добавляет идентификаторы к заголовкам
        .use(remarkAutolinkHeadings, {beh * avior: 'wrap'}) // Оборачивает заголовки ссылками
        .use(require('remark-rehype')) // Преобразует AST Markdown в HTML AST
        .use(rehypePrettyCode, {theme: 'one-dark'}) // Подсветка кода
        .process(markdown);

    return result.toString();
}

Ключевые аспекты:

  • remark-rehype выполняет мост между Markdown AST и HTML AST, что позволяет применять Rehype-плагины.
  • Плагины могут модифицировать дерево до генерации финального HTML.
  • Асинхронный вызов process позволяет использовать плагины с промисами.

Массовая обработка файлов

Объединение обхода файлов и трансформации позволяет создавать полноценные конвейеры:

async function buildSite(contentDir, outputDir) {
    const files = await getMarkdownFiles(contentDir);

    for (const file of files) {
        const markdown = await fs.readFile(file, 'utf-8');
        const html = await transformMarkdown(markdown);

        const relativePath = path.relative(contentDir, file);
        const outputPath = path.join(outputDir, relativePath.replace(/\.md$/, '.html'));
        await fs.mkdir(path.dirname(outputPath), { recursive: true });
        await fs.writeFile(outputPath, html, 'utf-8');
    }
}

Особенности:

  • path.relative и replace помогают сохранить структуру каталогов при генерации HTML.
  • fs.mkdir с { recursive: true } создаёт нужные подкаталоги автоматически.
  • Обработка происходит асинхронно для каждого файла, что повышает производительность.

Асинхронные плагины и потоковая обработка

Для больших сайтов целесообразно использовать потоковую обработку файлов с асинхронными плагинами. Remark поддерживает плагины, возвращающие промисы:

import remarkParse from 'remark-parse';
import remarkStringify from 'remark-stringify';

const asyncPlugin = () => async (tree) => {
    // Пример асинхронной обработки узлов AST
    for (const node of tree.children) {
        if (node.type === 'code') {
            node.value = await fetchSyntaxHighlight(node.value);
        }
    }
};

await remark()
    .use(remarkParse)
    .use(asyncPlugin)
    .use(remarkStringify)
    .process(markdown);

Особенности:

  • Плагины могут выполнять сетевые запросы, асинхронные преобразования или сложную логику.
  • AST Markdown предоставляет полный доступ к содержимому для модификации.

Работа с фронтматтером

Markdown-файлы часто содержат фронтматтер YAML для метаданных. Remark поддерживает плагины для его парсинга:

import remarkFrontmatter from 'remark-frontmatter';
import yaml from 'js-yaml';

const metadataPlugin = () => (tree, file) => {
    const fmNode = tree.children.find(node => node.type === 'yaml');
    if (fmNode) {
        file.data.meta = yaml.load(fmNode.value);
    }
};

await remark()
    .use(remarkFrontmatter, ['yaml'])
    .use(metadataPlugin)
    .process(markdown);

Особенности:

  • Доступ к метаданным позволяет формировать динамическую навигацию и страницы.
  • file.data используется для хранения информации вне основного AST.

Практическая архитектура проекта

Для крупных проектов интеграция с файловой системой обычно строится по следующей схеме:

  1. Сканирование каталогов для обнаружения исходных Markdown.
  2. Чтение файлов и извлечение фронтматтера.
  3. Преобразование через Remark с использованием плагинов (структурные изменения, ссылки, подсветка кода).
  4. Преобразование через Rehype для финального HTML или JSX.
  5. Сохранение результатов в соответствующих подкаталогах.
  6. Кэширование и оптимизация: сохранение промежуточных AST для ускорения повторной сборки.

Эта архитектура позволяет строить сложные статические сайты, документацию, блог-платформы и системы генерации контента с полной интеграцией Remark и Rehype с файловой системой.