Skip to content

API محیط برای فریم‌ورک‌ها

آزمایشی

Environment API در حال حاضر آزمایشی است. ما این APIها را در طول نسخه Vite 6 ثابت نگه می‌داریم تا اکوسیستم بتواند آن را آزمایش کند و بر روی آن توسعه دهد. برنامه ما این است که این APIهای جدید را در Vite 7 با تغییرات احتمالی نهایی کنیم.

منابع:

لطفاً نظرات و بازخوردهای خود را با ما به اشتراک بگذارید.

محیط‌ها و فریم‌ورک‌ها

محیط ssr و سایر محیط‌های غیرکلاینت به طور پیش‌فرض در زمان توسعه از یک RunnableDevEnvironment استفاده می‌کنند. در حالی که این نیاز دارد که رانتایم مشابه با سرور Vite باشد، این روش مشابه با ssrLoadModule عمل می‌کند و به فریم‌ورک‌ها اجازه می‌دهد تا مهاجرت کرده و HMR را برای توسعه SSR خود فعال کنند. شما می‌توانید هر محیط اجرایی را با استفاده از تابع isRunnableDevEnvironment بررسی کنید.

ts
export class RunnableDevEnvironment extends DevEnvironment {
  public readonly runner: ModuleRunner
}

class ModuleRunner {
  /**
   * برای اجرا URL
   * می‌تواند مسیر فایل، مسیر سرور، یا شناسه‌ای نسبی به ریشه را بپذیرد
   * ssrLoadModule یک ماژول نمونه‌سازی‌شده را برمی‌گرداند مشابه
   */
  public async import(url: string): Promise<Record<string, any>>
  /**
   * ModuleRunner سایر متدهای
   */
}

if (isRunnableDevEnvironment(server.environments.ssr)) {
  await server.environments.ssr.runner.import('/entry-point.js')
}

هشدار

runner زمانی که برای اولین بار به آن دسترسی پیدا کنید، بلافاصله مقداردهی می‌شود. توجه داشته باشید که وقتی runner با فراخوانی process.setSourceMapsEnabled ساخته می‌شود یا در صورت عدم دسترسی، با جایگزین کردن Error.prepareStackTrace ، Vite از پشتیبانی سورس مپ استفاده می‌کند.

فریم‌ورک‌هایی که از طریق Fetch API با محیط اجرای خود ارتباط برقرار می‌کنند، می‌توانند از FetchableDevEnvironment استفاده کنند. این کلاس روشی استاندارد برای مدیریت درخواست‌ها از طریق متد handleRequest فراهم می‌کند.

ts
import {
  createServer,
  createFetchableDevEnvironment,
  isFetchableDevEnvironment,
} from 'vite'

const server = await createServer({
  server: { middlewareMode: true },
  appType: 'custom',
  environments: {
    custom: {
      dev: {
        createEnvironment(name, config) {
          return createFetchableDevEnvironment(name, config, {
            handleRequest(request: Request): Promise<Response> | Response {
              // handle Request and return a Response
            },
          })
        },
      },
    },
  },
})

// Any consumer of the environment API can now call `dispatchFetch`
if (isFetchableDevEnvironment(server.environments.custom)) {
  const response: Response = await server.environments.custom.dispatchFetch(
    new Request('/request-to-handle'),
  )
}

محیط پیش‌فرض RunnableDevEnvironment

با توجه به سرور Vite که مطابق راهنمای راه‌اندازی SSR در حالت میان‌افزار (middleware) پیکربندی شده، بیایید با استفاده از API محیط، میان‌افزار SSR را پیاده‌سازی کنیم (جزئیات مربوط به مدیریت خطا در این مثال نادیده گرفته شده است).

js
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { createServer } from 'vite'

const __dirname = path.dirname(fileURLToPath(import.meta.url))

const server = await createServer({
  server: { middlewareMode: true },
  appType: 'custom',
  environments: {
    server: {
// اجرا شده است Vite به طور پیش‌فرض، ماژول‌ها در همان فرآیندی اجرا می‌شوند که سرور
    },
  },
})

// تبدیل کنید RunnableDevEnvironment نیاز داشته باشید آن را به TypeScript شاید در
// استفاده کنید runner برای بررسی دسترسی به isRunnableDevEnvironment یا از
const environment = server.environments.node

app.use('*', async (req, res, next) => {
  const url = req.originalUrl

  // 1. index.html خواندن فایل
  const indexHtmlPath = path.resolve(__dirname, 'index.html')
  let template = fs.readFileSync(indexHtmlPath, 'utf-8')

  // 2. Vite به HTML تبدیل‌های مربوط به
  //    را اعمال می‌کند Vite را تزریق می‌کند و همچنین تبدیل‌های پلاگین‌های HMR Client
  //    @vitejs/plugin-react پیش‌درآمد از
  template = await server.transformIndexHtml(url, template)

  // 3. را ESM کد import(url) ماژول ورودی سرور را بارگیری می‌کند. متد
  //    به صورت خودکار تبدیل می‌کند و نیاز به باندل ندارد Node.js برای استفاده در
  //    را فراهم می‌کند HMR همچنین پشتیبانی کامل از
  const { render } = await environment.runner.import('/src/entry-server.js')

  // 4. از render برنامه را رندر می‌کند. فرض بر این است که تابع HTML محتوای
  //    فریم‌ورک استفاده می‌کند SSR های مربوط به API از entry-server.js
  //    ReactDOMServer.renderToString() مانند
  const appHtml = await render(url)

  // 5. محتوای رندر شده را وارد قالب می‌کند
  const html = template.replace(`<!--ssr-outlet-->`, appHtml)

  // 6. نهایی را برمی‌گرداند HTML محتوای
  res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
})

SSR بدون وابستگی به Runtime مشخص

از آنجا که RunnableDevEnvironment فقط در همان رانتایم سرور Vite کد را اجرا می‌کند، این رانتایم باید قادر به اجرای سرور Vite باشد (رانتایمی سازگار با Node.js). به این معناست که برای حذف وابستگی به رانتایم مشخص، باید از DevEnvironment به صورت خام استفاده کنید.

پیشنهاد FetchableDevEnvironment

در طرح اولیه، متدی به نام run در کلاس DevEnvironment پیشنهاد شد که با استفاده از گزینه transport، امکان ایمپورت در بخش اجراکننده (runner) را فراهم می‌کرد. در آزمایش‌ها مشخص شد که این API به اندازه کافی فراگیر نیست تا توصیه شود. هم‌اکنون منتظر بازخورد در مورد پیشنهاد FetchableDevEnvironment هستیم.

در RunnableDevEnvironment متد runner.import وجود دارد که مقدار ماژول را برمی‌گرداند، اما در DevEnvironment خام در دسترس نیست و نیاز دارد کد استفاده‌کننده از APIهای Vite و ماژول‌های کاربر از یکدیگر جدا باشند.

مثال زیر، کد از ماژول کاربر در همان جایی استفاده می‌کند که از APIهای Vite نیز استفاده می‌شود:

ts
// code using the Vite's APIs
import { createServer } from 'vite'

const server = createServer()
const ssrEnvironment = server.environment.ssr
const input = {}

const { createHandler } = await ssrEnvironment.runner.import('./entry.js')
const handler = createHandler(input)
const response = handler(new Request('/'))

// -------------------------------------
// ./entrypoint.js
export function createHandler(input) {
  return function handler(req) {
    return new Response('hello')
  }
}

اگر کد شما می‌تواند در همان محیط اجرایی ماژول‌های کاربر اجرا شود (یعنی به APIهای مخصوص Node.js وابسته نیست)، می‌توانید از یک ماژول مجازی استفاده کنید. این روش نیاز به دسترسی به مقدار از طریق APIهای Vite را از بین می‌برد.

ts
// Vite های API کد با استفاده از
import { createServer } from 'vite'

const server = createServer({
  plugins: [
    // `virtual:entrypoint` پلاگین برای رسیدگی به مسیر مجازی
    {
      name: 'virtual-module',
      /* پیاده‌سازی پلاگین */
    },
  ],
})
const ssrEnvironment = server.environment.ssr
const input = {}

// از توابعی که توسط فکتوری‌های هر محیط فراهم می‌شوند استفاده می‌کند
// بررسی می‌کند هر فکتوری محیط چه امکاناتی ارائه می‌دهد
if (ssrEnvironment instanceof RunnableDevEnvironment) {
  ssrEnvironment.runner.import('virtual:entrypoint')
} else if (ssrEnvironment instanceof CustomDevEnvironment) {
  ssrEnvironment.runEntrypoint('virtual:entrypoint')
} else {
  throw new Error(`Unsupported runtime for ${ssrEnvironment.name}`)
}

// -------------------------------------
// virtual:entrypoint
const { createHandler } = await import('./entrypoint.js')
const handler = createHandler(input)
const response = handler(new Request('/'))

// -------------------------------------
// ./entrypoint.js
export function createHandler(input) {
  return function handler(req) {
    return new Response('hello')
  }
}

برای مثال، برای فراخوانی transformIndexHtml روی ماژول کاربر، می‌توان از پلاگین زیر استفاده کرد:

ts
function vitePluginVirtualIndexHtml(): Plugin {
  let server: ViteDevServer | undefined
  return {
    name: vitePluginVirtualIndexHtml.name,
    configureServer(server_) {
      server = server_
    },
    resolveId(source) {
      return source === 'virtual:index-html' ? '\0' + source : undefined
    },
    async load(id) {
      if (id === '\0' + 'virtual:index-html') {
        let html: string
        if (server) {
          this.addWatchFile('index.html')
          html = fs.readFileSync('index.html', 'utf-8')
          html = await server.transformIndexHtml('/', html)
        } else {
          html = fs.readFileSync('dist/client/index.html', 'utf-8')
        }
        return `export default ${JSON.stringify(html)}`
      }
      return
    },
  }
}

اگر کد شما به APIهای Node.js نیاز دارد، می‌توانید برای ارتباط با کدی که از APIهای Vite در ماژول‌های کاربر استفاده می‌کند، از hot.send استفاده کنید. با این حال، دقت داشته باشید که پس از مرحله بیلد، این روش ممکن است همانند قبل کار نکند.

ts
// Vite های API کد با استفاده از
import { createServer } from 'vite'

const server = createServer({
  plugins: [
    // `virtual:entrypoint` پلاگین برای رسیدگی به مسیر مجازی
    {
      name: 'virtual-module',
      /* پیاده‌سازی پلاگین */
    },
  ],
})
const ssrEnvironment = server.environment.ssr
const input = {}

// از توابعی که توسط فکتوری‌های هر محیط فراهم می‌شوند استفاده می‌کند
// بررسی می‌کند هر فکتوری محیط چه امکاناتی ارائه می‌دهد
if (ssrEnvironment instanceof RunnableDevEnvironment) {
  ssrEnvironment.runner.import('virtual:entrypoint')
} else if (ssrEnvironment instanceof CustomDevEnvironment) {
  ssrEnvironment.runEntrypoint('virtual:entrypoint')
} else {
  throw new Error(`Unsupported runtime for ${ssrEnvironment.name}`)
}

const req = new Request('/')

const uniqueId = 'a-unique-id'
ssrEnvironment.send('request', serialize({ req, uniqueId }))
const response = await new Promise((resolve) => {
  ssrEnvironment.on('response', (data) => {
    data = deserialize(data)
    if (data.uniqueId === uniqueId) {
      resolve(data.res)
    }
  })
})

// -------------------------------------
// virtual:entrypoint
const { createHandler } = await import('./entrypoint.js')
const handler = createHandler(input)

import.meta.hot.on('request', (data) => {
  const { req, uniqueId } = deserialize(data)
  const res = handler(req)
  import.meta.hot.send('response', serialize({ res: res, uniqueId }))
})

const response = handler(new Request('/'))

// -------------------------------------
// ./entrypoint.js
export function createHandler(input) {
  return function handler(req) {
    return new Response('hello')
  }
}

محیط‌ها در زمان بیلد

در خط فرمان (CLI)، فراخوانی vite build و vite build --ssr همچنان به‌خاطر سازگاری با نسخه‌های قبلی فقط محیط کلاینت و محیط SSR را بیلد می‌کند.

زمانی که builder تعریف شده باشد (یا وقتی از vite build --app استفاده می‌کنید)، vite build برای بیلد کل اپلیکیشن فعال می‌شود. این کار در نسخه مهم بعدی به صورت پیش‌فرض خواهد بود. یک نمونه از ViteBuilder (معادل بیلدی ViteDevServer) ایجاد می‌شود تا تمام محیط‌های پیکربندی‌شده را برای پروداکشن بیلد کند. به صورت پیش‌فرض، بیلد محیط‌ها به صورت سری و بر اساس ترتیب رکورد environments اجرا می‌شود. یک فریم‌ورک یا کاربر می‌تواند با استفاده از تنظیمات زیر مشخص کند چگونه محیط‌ها بیلد شوند:

js
export default {
  builder: {
    buildApp: async (builder) => {
      const environments = Object.values(builder.environments)
      return Promise.all(
        environments.map((environment) => builder.build(environment)),
      )
    },
  },
}

کد بدون وابستگی مستقیم به محیط

اغلب اوقات، نمونه محیط فعلی به عنوان بخشی از کانتکست کدی که اجرا می‌شود در دسترس است، بنابراین نیاز به دسترسی مستقیم از طریق server.environments معمولاً کم است. به عنوان مثال، در هوک‌های پلاگین، محیط به عنوان بخشی از PluginContext در دسترس قرار می‌گیرد و می‌توانید با this.environment به آن دسترسی داشته باشید. برای آشنایی با نحوه ساخت پلاگین‌های آگاه به محیط، به Environment API for Plugins مراجعه کنید.

تحت مجوز MIT منتشر شده. (dev)