API محیط برای فریمورکها
آزمایشی
Environment API در حال حاضر آزمایشی است. ما این APIها را در طول نسخه Vite 6 ثابت نگه میداریم تا اکوسیستم بتواند آن را آزمایش کند و بر روی آن توسعه دهد. برنامه ما این است که این APIهای جدید را در Vite 7 با تغییرات احتمالی نهایی کنیم.
منابع:
- بحث و گفتگو جایی که ما در حال جمعآوری نظرات درباره APIهای جدید هستیم.
- PR مربوط به Environment API جایی که API جدید پیادهسازی و بررسی شده است.
لطفاً نظرات و بازخوردهای خود را با ما به اشتراک بگذارید.
محیطها و فریمورکها
محیط ssr
و سایر محیطهای غیرکلاینت به طور پیشفرض در زمان توسعه از یک RunnableDevEnvironment
استفاده میکنند. در حالی که این نیاز دارد که رانتایم مشابه با سرور Vite باشد، این روش مشابه با ssrLoadModule
عمل میکند و به فریمورکها اجازه میدهد تا مهاجرت کرده و HMR را برای توسعه SSR خود فعال کنند. شما میتوانید هر محیط اجرایی را با استفاده از تابع isRunnableDevEnvironment
بررسی کنید.
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
فراهم میکند.
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 را پیادهسازی کنیم (جزئیات مربوط به مدیریت خطا در این مثال نادیده گرفته شده است).
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 نیز استفاده میشود:
// 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 را از بین میبرد.
// 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
روی ماژول کاربر، میتوان از پلاگین زیر استفاده کرد:
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
استفاده کنید. با این حال، دقت داشته باشید که پس از مرحله بیلد، این روش ممکن است همانند قبل کار نکند.
// 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
اجرا میشود. یک فریمورک یا کاربر میتواند با استفاده از تنظیمات زیر مشخص کند چگونه محیطها بیلد شوند:
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 مراجعه کنید.