Можешь ли ты установить ./server.ts в качестве точки входа для сборки SSR?

Вопрос или проблема

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

Оцените материал
Добавить комментарий

Капча загружается...