- Вопрос или проблема
- Ответ или решение
- Можете ли вы установить ./server.ts в качестве точки входа для сборки SSR?
- 1. Структура проекта и начальная конфигурация
- 2. Переименование server.js в server.ts
- 3. Настройка сборки Vite
- 4. Импортирование точки входа сервера
- 5. Управление путями и сборкой
- 6. Оптимизация скриптов в package.json
- Заключение
Вопрос или проблема
Я создал новый проект vite с использованием pnpm create vite
. Чтобы это можно было воспроизвести, вот мои выборы при запросе:
- Выберите фреймворк: Другие
- Выберите вариант: create-vite-extra
- Выберите шаблон: ssr-vue
- Выберите вариант: Не потоковый
- Выберите вариант: TypeScript
Я хотел использовать TypeScript для сгенерированного ./server.js
, а не только для кода в ./src
, поэтому я “конвертировал” (т.е. временно переименовал) server.js
в server.ts
.
На этом этапе я понял, что мне придется самостоятельно собирать ./server.ts
, и чтобы vite собирал так называемую точку входа на стороне сервера ./src/entry-server.ts
.
Я использую vite, чтобы избежать ужасов конфигурации сборки TypeScript, поэтому я задаюсь вопросом, возможно ли установить ./server.ts
в качестве точки входа для сборки SSR? Это, и при этом иметь работающий SSR.
Часть, где я застрял, заключается в том, как на самом деле импортировать ./src/entry-server.ts
, теперь, когда ./server.ts
является моей точкой входа (т.е. render = (await import('./dist/server/entry-server.js')).render
больше не работает, теперь когда entry-server.js
больше не собирается).
Я нашел, как сделать работающий вариант. Вот что я сделал, на случай, если это будет полезно кому-то еще:
./vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path';
import { PORT, STATIC_URL } from './src/lib/env';
export default defineConfig(({ command, mode }) => {
const buildTarget = process.env.BUILD_TARGET;
// Общая конфигурация, используемая как для разработки, так и для сборки.
const baseConfig = {
plugins: [vue()],
server: {
host: '127.0.0.1',
port: PORT,
hmr: {
host: '127.0.0.1',
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
};
// Опции, специфичные для сборки
if (buildTarget === 'entry-client') {
return {
...baseConfig,
base: STATIC_URL,
build: {
outDir: 'dist/client'
}
};
} else if (buildTarget === 'entry-server') {
return {
...baseConfig,
base: STATIC_URL,
build: {
manifest: true,
ssr: 'src/entry-server.ts',
outDir: 'dist/server'
}
};
} else if (buildTarget === 'node-server') {
return {
...baseConfig,
publicDir: false,
base: STATIC_URL,
build: {
ssr: 'server.ts',
outDir: 'dist/node',
}
};
} else {
return baseConfig;
}
});
Секция скриптов package.json
:
"scripts": {
"build:ssr-entry-client": "BUILD_TARGET=entry-client vite build",
"build:ssr-entry-server": "BUILD_TARGET=entry-server vite build",
"build:ssr-node-server": "BUILD_TARGET=node-server vite build",
"build:ssr": "npm run build:ssr-entry-client && npm run build:ssr-entry-server && npm run build:ssr-node-server",
"dev": "nodemon --exec tsx server.ts --watch server.ts --watch src --ext ts,js --ignore dist --ignore node_modules",
"prod": "pnpm build:ssr && NODE_ENV=production node dist/node/server.js"
},
И наконец, в моем server.ts есть утилита resolve для абсолютного разрешения путей:
// Определить абсолютный путь к /frontend
function getRootDir(): string {
const currentPath = process.cwd();
const frontendIndex = currentPath.indexOf('/frontend');
return currentPath.slice(0, frontendIndex + '/frontend'.length);
}
const rootDir = getRootDir();
function resolve(p: string): string {
return path.resolve(rootDir, p);
}
// Обернуть создание сервера, чтобы избежать ожидания на верхнем уровне.
async function createServer() {
// Кэшированная страница
const templateHtml = isProduction
? await fs.readFile(resolve('dist/client/index.html'), 'utf-8')
: ''
...
Побочный эффект использования моей утилиты resolve
для динамической загрузки/импортов файлов заключается в том, что импорт import render = (await import(resolve('dist/server/entry-server.js'))).render
(заметьте, он теперь обернут в resolve
), что было для меня проблемой в вопросе, теперь непрозрачно для vite, поэтому для сборки node-server
vite не генерирует ресурсы для entry-server.js
и его транзитивных зависимостей. На данный момент это кажется мне счастливым случайностью; я уже построил все это в dist/server
.
Ответ или решение
Можете ли вы установить ./server.ts в качестве точки входа для сборки SSR?
Вопрос о том, можно ли установить файл ./server.ts
в качестве точки входа для сборки серверного рендеринга (SSR) в проекте, созданном с использованием Vite, действительно актуален и заслуживает подробного рассмотрения. Давайте разобьем этот процесс на несколько ключевых этапов.
1. Структура проекта и начальная конфигурация
Вы упомянули, что начали свой проект с помощью команды pnpm create vite
, выбрав опцию Others
, шаблон ssr-vue
и TypeScript. Это дает вам хорошую основу для работы, так как Vite уже реализует необходимые настройки для работы с SSR.
2. Переименование server.js
в server.ts
Переименование server.js
в server.ts
позволяет вам использовать возможности TypeScript для всего кода, а не только для компонентов в ./src
. Однако, с изменением точки входа на server.ts
, ваша сборка Vite требует некоторой доработки.
3. Настройка сборки Vite
В вашем конфигурационном файле vite.config.ts
вы правильно подходите к системе сборки. Обратите внимание на секцию, отвечающую за различные цели сборки (BUILD_TARGET
). Вам необходимо установить ssr
в значение server.ts
при сборке для node-server
.
Вот ключевые моменты, которые стоит учесть:
else if (buildTarget === 'node-server') {
return {
...baseConfig,
publicDir: false,
base: STATIC_URL,
build: {
ssr: 'server.ts',
outDir: 'dist/node',
}
};
}
4. Импортирование точки входа сервера
Теперь, когда вы установили ./server.ts
как точку входа, в нем вам нужно корректно импортировать ./src/entry-server.ts
. Использование resolve
в ваших динамических импортируемых файлах, как вы уже сделали, является хорошей практикой. Например:
import render = (await import(resolve('dist/server/entry-server.js'))).render;
Это позволяет Vite правильно обрабатывать зависимости.
5. Управление путями и сборкой
Вы создали утилиту для получения абсолютного пути к директории вашего проекта. Это полезно для предотвращения проблем с относительными путями, когда вы работаете в разных средах (разработка, тестирование, продакшен). Ваш код для функции resolve
позволяет избежать путаницы с путями:
function resolve(p: string): string {
return path.resolve(rootDir, p);
}
6. Оптимизация скриптов в package.json
Ваши скрипты для команды сборки также выглядят хорошо структурированными. Использование переменной окружения BUILD_TARGET
помогает организовать сборку для различных целей — entry-client
, entry-server
и node-server
.
Заключение
С точки зрения архитектуры, ваш подход к тому, чтобы использовать server.ts
в качестве точки входа для SSR, вполне обоснован. Вы правильно настроили Vite и обеспечили возможность импортировать необходимые модули. Aliasing и использование утилиты resolve
помогут вам избежать проблем с путями и зависимостями.
Ваше решение по использованию Vite для компиляции TypeScript, включая как файл сервера, так и клиентские модули, обеспечивает правильное структурирование и гибкость в разработке. Таким образом, установка ./server.ts
как точки входа для SSR сборки – это не только возможно, но и является удобным и практическим выбором в контексте вашего проекта.