Использование vite-plugin-ssr с mdx-js, решение проблем с библиотекой ESM only, понимание конфигурации vite и написание плагинов vite

vite-plugin-ssr — это плагин vite, который позволяет нам создавать веб-сайты с рендерингом на стороне сервера, рендерингом на стороне клиента, одностраничными приложениями и генерацией статических сайтов. Этот плагин похож на Next.js, но предоставляет больше контроля над каждой страницей и для любого из ваших любимых frontend-фреймворков. Посетите веб-сайт, чтобы узнать, как использовать этот плагин.

В этом руководстве мы узнаем, как установить библиотеку mdx-js в проект vite для создания веб-сайтов на основе markdown и пререндерить их с помощью vite-plugin-ssr для генерации статических веб-сайтов.

В github-репо vite-plugin-ssr содержатся примеры проектов, которые вы можете клонировать и начать с них. Например, пример react-full уже содержит настройку для работы с библиотекой mdx-js. Цель этого руководства — показать, как решить некоторые проблемы, с которыми я столкнулся при использовании библиотеки mdx-js и функции пререндера vite-plugin-ssr.

Настройка проекта

Прежде всего, нам необходимо создать проект на основе vite + vite-plugin-ssr. Чтобы создать проект на основе vite-plugin-ssr, просто выполните следующие действия

npm init vite-plugin-ssr
Войти в полноэкранный режим Выйти из полноэкранного режима

Дайте проекту имя (я назвал его nn-blog) и выберите фронтенд-фреймворк (в данном примере react), который вы хотите использовать. После выполнения команды просто перейдите в папку проекта и установите все зависимости.

cd nn-blog
npm install
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Затем запустите сервер dev с помощью команды npm run dev. Поздравляем, вы только что установили проект на базе vite + vite-plugin-ssr. Установка поставляется с инициализированным git-репозиторием, так что вы можете начать изменять код. И вы заметите, как молниеносно быстро работает vite dev server.

Как только вы поймете концепции маршрутизации файловой системы в vite-plugin-ssr, создайте несколько страниц и поэкспериментируйте. Когда вы будете готовы, давайте начнем с добавления mdx-js.

Добавление mdx-js в проект vite

mdx-js — это библиотека, которая конвертирует содержимое markdown в jsx-совместимое содержимое, которое вы можете затем использовать с библиотеками на основе jsx, такими как react, preact, vue.

MDX позволяет вам использовать JSX в вашем контенте markdown. Вы можете импортировать компоненты, такие как интерактивные графики или оповещения, и встраивать их в свой контент.
vite использует rollup под капотом для сборки пакетов для производства. Поэтому для установки mdx-js в проект vite следует использовать @mdx-js/rollup, а для работы с пользовательскими MDX-компонентами можно использовать @mdx-js/react для проектов на базе react.

npm install @mdx-js/rollup @mdx-js/react
Вход в полноэкранный режим Выход из полноэкранного режима

После установки библиотек добавьте mdx-js в vite plugins в файл vite.config.js и настройте mdx plugin на использование @mdx-js/react в качестве proiderImportSource.

import react from '@vitejs/plugin-react'
import ssr from 'vite-plugin-ssr/plugin'
+import mdx from "@mdx-js/rollup"

export default {
- plugins: [react(), ssr()]
+ plugins: [react(), mdx({
+   providerImportSource: "@mdx-js/react"
+ }), ssr()],
}
Вход в полноэкранный режим Выход из полноэкранного режима

Решение проблемы 1 — require() модуля ES не поддерживается

Теперь после обновления vite.config.js при попытке запустить npm run dev мы получим эту непонятную ошибку

failed to load config from /workspace/example/nn-blog/vite.config.js
/workspace/example/nn-blog/vite.config.js:61509
undefined
            ^

Error [ERR_REQUIRE_ESM]: require() of ES Module /workspace/example/nn-blog/node_modules/@mdx-js/rollup/index.js from /workspace/example/nn-blog/vite.config.js not supported.
Войти в полноэкранный режим Выйти из полноэкранного режима

Эти проблемы возникают в следующем порядке.

  • Скрипт создает vite dev сервер с помощью vite.createServer.
  • Сервер vite dev сначала конвертирует vite.config.js в модуль CJS, а затем загружает конфиг из этого файла.
  • Поскольку модуль CJS пытается require("@mdx-js/rollup") плагин, который является модулем только для ESM, будет выдана ошибка.

Чтобы решить эту проблему, мы должны сообщить vite, чтобы он пропустил создание конфигурационного файла для CJS. Этого можно добиться, добавив

+ "type": "module",
}
Войти в полноэкранный режим Выйти из полноэкранного режима

в файл package.json.

Решение проблемы 2 — require() не определен в области видимости модуля ES

После того, как мы сообщили node о включении ES-модулей, мы не можем использовать синтаксис require в файлах .js. Именно это вы получите, если запустите npm run dev.

file:///workspace/example/nn-blog/server/index.js:1
const express = require('express')
                ^

ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension and '/workspace/example/nn-blog/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
Вход в полноэкранный режим Выйти из полноэкранного режима

К счастью, сама ошибка подсказала нам решение. Но сначала нужно перестать чесать голову и научиться читать эти строки, чтобы определить решение. Если вы внимательно посмотрите, то нам нужно просто переименовать наш файл index.js в index.cjs и ?.

Решение проблемы 3 — Невозможно найти модуль

node:internal/modules/cjs/loader:936
  throw err;
  ^

Error: Cannot find module '/workspace/example/nn-blog/server'
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)
    at Function.Module._load (node:internal/modules/cjs/loader:778:27)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47 {
  code: 'MODULE_NOT_FOUND',
  requireStack: []
}
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Подождите, куда пропал наш файл? Node говорит, что не может найти его, но он находится прямо в папке сервера.

Возможно, если вы достаточно терпеливы или талантливый ботаник, вы поймете, что node пытается загрузить модуль server, а не server/index.js. Файл /index.js появляется как часть последовательности загрузки модуля CJS в node. Итак, нам нужно добавить файл package.json со следующим значением

{
  "main": "index.cjs"
}
Войти в полноэкранный режим Выйти из полноэкранного режима

И ✨ поздравляем, теперь вы готовы к работе.

Добавление страницы с разметкой

Теперь перейдите в каталог pages и добавьте в него любое содержимое с расширением .md или .mdx. Например, для создания корня /naveennamani добавьте файл pages/naveennamani.page.mdx или pages/naveennamani/index.page.mdx или pages/index/naveennamani.page.mdx. (Для данного примера я предпочитаю последнее имя файла).

Как только вы создадите файл, добавьте в него любое содержимое в формате markdown, нажмите [localhost:3000/naveennamani] url, чтобы увидеть, как ваше содержимое в формате markdown преобразуется в html. Для использования компонентов react внутри ваших mdx-файлов просто импортируйте их и используйте.

# Hello world

import { Counter } from './Counter'

<Counter />
Вход в полноэкранный режим Выйти из полноэкранного режима

Это покажет заголовок с интерактивным счетчиком, который также отображается на главной странице.

Пререндеринг и придумывание новых проблем

Когда вы остановите dev-сервер и захотите создать свой удивительный сайт в виде статического контента, вы можете использовать функцию vite-plugin-ssr prerender. Просто добавьте следующий скрипт в package.json.

"scripts": {
   ...
   "prerender": "npm run build && vite-plugin-ssr prerender"
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Теперь, когда вы запустите npm run prerender, вы увидите, что папки distclient и distserver созданы и файлы сборки заполнены. Но пререндеринг не работает при

/workspace/example/nn-blog/dist/server/assets/naveennamani.page.04918628.js:4
var react = require("@mdx-js/react");
            ^

Error [ERR_REQUIRE_ESM]: require() of ES Module /workspace/example/nn-blog/node_modules/@mdx-js/react/index.js from /workspace/example/nn-blog/dist/server/assets/naveennamani.page.04918628.js not supported.
Вход в полноэкранный режим Выход из полноэкранного режима

Разве это не та же проблема, которую мы решали ранее? Да. Но почему снова? ?
На этот раз проблема возникает в следующем порядке.

  • Когда вы запускаете npm run build, он запускает vite build и vite build --ssr с первой командой, создающей активы для distclient и второй командой для distserver.
  • В то время как активы distclient — это все модули esm, выходные данные сборки distclient — это модули cjs.
  • Итак, снова @mdx-js/react, который является модулем только ESM, не удалось импортировать через require.

На этот раз мы можем генерировать модули ES вместо модулей CJS, настроив параметры сборки в vite.config.js следующим образом

  import react from '@vitejs/plugin-react'
  import ssr from 'vite-plugin-ssr/plugin'
  import mdx from "@mdx-js/rollup"
+ import { defineConfig } from 'vite'

+ export default defineConfig({
    plugins: [react(), mdx({
      providerImportSource: "@mdx-js/react"
    }), ssr()],
+   build: {
+     rollupOptions: {
+       output: {
+         format: "es"
+       }
+     }
+   }
+ })

Войти в полноэкранный режим Выйти из полноэкранного режима

Когда вы снова запустите npm run prerender, вы увидите, что папка distserver содержит файлы, которые являются ES модулями. Но вы все равно получаете эту сложную ошибку.

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/workspace/example/nn-blog/node_modules/react/jsx-runtime' imported from /workspace/example/nn-blog/dist/server/assets/index.page.0262694b.js
Did you mean to import react/jsx-runtime.js?
Войдите в полноэкранный режим Выход из полноэкранного режима

Написание плагина vite для решения наших проблем

На первый взгляд, ошибка похожа на орфографическую ошибку. Но если погуглить, то в официальном репо react (issue #20235) есть длинный список комментариев. Проблему можно просто решить, добавив расширение .js к импорту, но как сделать это автоматически?

Давайте напишем плагин vite, который будет делать это за нас. Написать vite-плагин очень просто, если следовать API плагина Vite.

Вот что получилось у меня.

export default function fix_ssr_esm_modules(replacements) {
  function transform(code, id, ssr) {
    if (ssr) // ssr is true when `vite build --ssr` is run
      return replacements.reduce((prevCode, { find, replacement }) => {
        return prevCode.replaceAll(find, replacement);
      }, code);
  }

  return { // configuration of our plugin used by vite
    name: "vite-plugin-fix-ssr-esm-modules",
    apply: "build", // execute only for build tasks
    enforce: "post", // execute after build finished
    transform: transform, // transformation function that returns transformed code
  };
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Теперь поместите код в файл fix_ssr_esm_modules.js, а затем импортируйте и используйте этот плагин в файле vite.config.js следующим образом.

+ import fix_ssr_esm_modules from "./fix_ssr_esm_imports.js";

export default defineConfig({
  plugins: [
    react(),
    mdx({
      providerImportSource: "@mdx-js/react",
    }),
    ssr(),
+   fix_ssr_esm_modules([
+     { find: "react/jsx-runtime", replacement: "react/jsx-runtime.js" },
+     { find: "react-dom/server", replacement: "react-dom/server.js" },
+   ]),
  ],
  build: {
    rollupOptions: {
      output: {
        format: "es",
      },
    },
  },
});
Вход в полноэкранный режим Выйти из полноэкранного режима

Плагин преобразует файлы сборки и заменяет импорт, заданный в качестве опций плагина.

Теперь вы можете запустить npm run prerender и обслуживать файлы в distclient статически, используя npx serve. Поздравляем ?, вы только что создали статический сайт с помощью vite-plugin-ssr.

Последний штрих

Финальная версия исходного кода проекта доступна на github naveennamani/vite-ssr-mdx.

Есть небольшая нестыковка с файлом server/index.js, но это альтернатива, которую я нашел, пока писал эту статью.

Извините за длинный пост, если вы все-таки зашли сюда, то вот вам картошка.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *