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
, но это альтернатива, которую я нашел, пока писал эту статью.
Извините за длинный пост, если вы все-таки зашли сюда, то вот вам картошка.