API محیط برای پلاگینها
آزمایشی
رابط Environment API هنوز در مرحلهی آزمایشی (experimental) هست. با این حال، ما تلاش میکنیم بین نسخههای اصلی (major) پایداری این APIها را حفظ کنیم تا جامعهی توسعهدهندگان بتوانند با آنها کار کرده و تجربیات خود را بر اساس آنها توسعه دهند. ما قصد داریم این APIهای جدید را در یکی از نسخههای اصلی آینده به حالت پایدار (stable) برسانیم. البته ممکن است در این فرآیند تغییرات شکننده (breaking changes) نیز اعمال شود، اما این کار زمانی انجام خواهد شد که پروژهها و کتابخانههای وابسته فرصت کافی برای آزمایش و ارزیابی این قابلیتهای جدید را داشته باشند.
منابع:
- بحث و گفتگو جایی که ما در حال جمعآوری نظرات درباره APIهای جدید هستیم.
- PR مربوط به Environment API جایی که API جدید پیادهسازی و بررسی شده است.
لطفاً نظرات و بازخوردهای خود را با ما به اشتراک بگذارید.
دسترسی به محیط فعلی در هوکها
از آنجا که تا نسخه Vite 6 فقط دو محیط وجود داشت (client و ssr)، یک بولین ssr برای شناسایی محیط فعلی در APIهای Vite کافی بود. هوکهای پلاگین یک بولین ssr را در آخرین پارامتر گزینهها دریافت میکردند و API ها انتظار داشتند که یک پارامتر ssr اختیاری برای ارتباط صحیح ماژولها با محیط مناسب ارائه شود (برای مثال server.moduleGraph.getModuleByUrl(url, { ssr })).
با معرفی محیطهای قابل پیکربندی، اکنون یک روش یکنواخت برای دسترسی به گزینهها و نمونههای محیط در پلاگینها وجود دارد. هوکهای پلاگین اکنون this.environment را در کانتکست خود ارائه میدهند و APIهایی که قبلاً به یک بولین ssr نیاز داشتند، اکنون به محیط مناسب محدود شدهاند (برای مثال environment.moduleGraph.getModuleByUrl(url)).
سرور Vite یک مسیر پردازش پلاگین مشترک دارد، اما زمانی که یک ماژول پردازش میشود، همیشه در کانتکست یک محیط مشخص انجام میشود. نمونه environment در کانتکست پلاگین در دسترس است.
یک پلاگین میتواند از نمونه environment برای تغییر نحوه پردازش یک ماژول بر اساس پیکربندی محیط استفاده کند (که میتوان از طریق environment.config به آن دسترسی داشت).
transform(code, id) {
console.log(this.environment.config.resolve.conditions)
}ثبت محیطهای جدید با استفاده از هوکها
پلاگینها میتوانند محیطهای جدیدی را در هوک config اضافه کنند (برای مثال، برای داشتن یک گراف ماژول جداگانه برای RSC):
config(config: UserConfig) {
config.environments.rsc ??= {}
}یک آبجکت خالی برای ثبت محیط کافی است، زیرا مقادیر پیشفرض از تنظیمات محیط از ریشه گرفته میشوند.
پیکربندی محیط با استفاده از هوکها
در زمان اجرای هوک config، لیست کامل محیطها هنوز مشخص نیست و محیطها میتوانند تحت تأثیر مقادیر پیشفرض از تنظیمات محیط در سطح ریشه یا به صورت صریح از طریق رکورد config.environments قرار گیرند. پلاگینها باید مقادیر پیشفرض را با استفاده از هوک config تنظیم کنند. برای پیکربندی هر محیط، میتوانند از هوک جدید configEnvironment استفاده کنند. این هوک برای هر محیط با تنظیمات جزئی اضافهشده آن، شامل مقادیر پیشفرض نهایی، فراخوانی میشود.
configEnvironment(name: string, options: EnvironmentOptions) {
if (name === 'rsc') {
options.resolve.conditions = // ...هوک hotUpdate
- تایپ:
(this: { environment: DevEnvironment }, options: HotUpdateOptions) => Array<EnvironmentModuleNode> | void | Promise<Array<EnvironmentModuleNode> | void> - همچنین ببینید: HMR API
هوک hotUpdate به پلاگینها اجازه میدهد تا مدیریت بهروزرسانی HMR سفارشی را برای یک محیط خاص انجام دهند. زمانی که یک فایل تغییر میکند، الگوریتم HMR برای هر محیط به ترتیب موجود در server.environments به صورت سری اجرا میشود، بنابراین هوک hotUpdate چندین بار فراخوانی خواهد شد. این هوک یک آبجکت کانتکست با امضای زیر دریافت میکند:
interface HotUpdateOptions {
type: 'create' | 'update' | 'delete'
file: string
timestamp: number
modules: Array<EnvironmentModuleNode>
read: () => string | Promise<string>
server: ViteDevServer
}this.environmentمحیط اجرایی ماژول است که در آن بهروزرسانی فایل در حال پردازش است.modulesآرایهای از ماژولها در این محیط است که تحت تأثیر فایل تغییر یافته قرار گرفتهاند. این آرایه شامل چندین ماژول است، زیرا یک فایل ممکن است به چندین ماژول سرو شده نگاشت شود (مثلاً در Vue SFCها).readیک تابع خواندن غیرهمزمان است که محتوای فایل را برمیگرداند. این تابع ارائه شده است زیرا در برخی سیستمها، ممکن است callback تغییر فایل خیلی سریع اجرا شود، قبل از اینکه ویرایشگر فایل را بهطور کامل بهروزرسانی کند، و در این حالت استفاده مستقیم ازfs.readFileمحتوای خالی برمیگرداند. تابعreadاین رفتار را نرمالسازی میکند.
این هوک میتواند:
لیست ماژولهای تحت تأثیر را فیلتر کرده و محدود کند تا HMR دقیقتر انجام شود.
یک آرایه خالی برگرداند و بارگذاری کامل را انجام دهد:
jshotUpdate({ modules, timestamp }) { if (this.environment.name !== 'client') return // Invalidate modules manually const invalidatedModules = new Set() for (const mod of modules) { this.environment.moduleGraph.invalidateModule( mod, invalidatedModules, timestamp, true ) } this.environment.hot.send({ type: 'full-reload' }) return [] }آرایه خالی برگردانید و مدیریت کامل HMR سفارشی را با ارسال رویدادهای سفارشی به کلاینت انجام دهید:
jshotUpdate() { if (this.environment.name !== 'client') return this.environment.hot.send({ type: 'custom', event: 'special-update', data: {} }) return [] }کد کلاینت باید با استفاده از HMR API هندلر مربوطه را ثبت کند (این کار میتواند توسط هوک
transformهمان پلاگین انجام شود):jsif (import.meta.hot) { import.meta.hot.on('special-update', (data) => { // perform custom update }) }
وضعیت وابسته به محیط (Per-environment State) در پلاگینها
از آنجا که یک نمونهی یکسان از پلاگین ممکن است در محیطهای مختلف (مثلاً کلاینت و SSR) استفاده شود، وضعیت (state) مربوط به پلاگین باید با استفاده از this.environment تفکیک شود. این همان الگویی است که پیشتر نیز در اکوسیستم استفاده میشده؛ یعنی برای نگهداری وضعیت ماژولها به صورت مجزا برای SSR و کلاینت، از مقدار بولی ssr به عنوان کلید استفاده میشده تا از قاطی شدن وضعیت ماژولهای SSR و کلاینت جلوگیری شود. در این حالت میتوان از ساختاری مانند Map<Environment, State> برای نگهداری وضعیت مستقل هر محیط استفاده کرد. نکته: برای حفظ سازگاری با نسخههای قبلی، متدهای buildStart و buildEnd به صورت پیشفرض فقط برای محیط کلاینت فراخوانی میشوند، مگر اینکه گزینهی perEnvironmentStartEndDuringDev: true فعال شده باشد.
function PerEnvironmentCountTransformedModulesPlugin() {
const state = new Map<Environment, { count: number }>()
return {
name: 'count-transformed-modules',
perEnvironmentStartEndDuringDev: true,
buildStart() {
state.set(this.environment, { count: 0 })
},
transform(id) {
state.get(this.environment).count++
},
buildEnd() {
console.log(this.environment.name, state.get(this.environment).count)
}
}
}پلاگینهای مخصوص هر محیط
یک پلاگین میتواند مشخص کند که در کدام محیطها باید اعمال شود، با استفاده از تابع applyToEnvironment.
const UnoCssPlugin = () => {
// مشترک سراسری state
return {
buildStart() {
// WeakMap<Environment, Data> مقداردهی وضعیت مخصوص هر محیط با
// this.environment با استفاده از
},
configureServer() {
// استفاده از هوکهای سراسری به صورت معمول
},
applyToEnvironment(environment) {
// برگردانید true اگر این پلاگین باید در این محیط فعال باشد، مقدار
// یا یک پلاگین جدید برای جایگزینی آن برگردانید
// اگر این هوک استفاده نشود، پلاگین در همه محیطها فعال خواهد بود
},
resolveId(id, importer) {
// فقط برای محیطهایی که این پلاگین در آنها اعمال میشود، فراخوانی میشود
},
}
}اگر یک پلاگین از محیط آگاه نباشد و دارای وضعیتی باشد که بر اساس محیط فعلی کلیدگذاری نشده است، هوک applyToEnvironment امکان تبدیل آن به یک پلاگین مخصوص هر محیط را بهسادگی فراهم میکند.
import { nonShareablePlugin } from 'non-shareable-plugin'
export default defineConfig({
plugins: [
{
name: 'per-environment-plugin',
applyToEnvironment(environment) {
return nonShareablePlugin({ outputName: environment.name })
},
},
],
})Vite یک تابع کمکی به نام perEnvironmentPlugin ارائه میدهد تا مواردی که نیازی به هوکهای دیگر ندارند، سادهتر کند:
import { nonShareablePlugin } from 'non-shareable-plugin'
export default defineConfig({
plugins: [
perEnvironmentPlugin('per-environment-plugin', (environment) =>
nonShareablePlugin({ outputName: environment.name }),
),
],
})هوک applyToEnvironment در زمان پیکربندی فراخوانی میشود، و در حال حاضر بعد از اجرای configResolved انجام میگیرد. دلیل این ترتیب، وجود پروژههایی در اکوسیستم است که پلاگینها را در این مرحله تغییر میدهند. با این حال، ممکن است در آینده فرآیند تشخیص پلاگینهای محیطی (environment plugins resolution) به قبل از configResolved منتقل شود. این تغییر به منظور بهبود ساختار و پیشبینیپذیری فرایند پیکربندی در نظر گرفته شده است.
محیط در هوکهای بیلد
مشابه حالت توسعه، هوکهای پلاگین در زمان بیلد نیز نمونه محیط را دریافت میکنند و جایگزین بولین ssr میشوند. این موضوع برای هوکهایی مانند renderChunk ، generateBundle و سایر هوکهای مختص بیلد نیز کار میکند.
پلاگینهای مشترک در زمان بیلد
قبل از Vite 6، مسیر اجرا پلاگینها در حالت توسعه و بیلد به صورت متفاوت عمل میکرد:
- در زمان توسعه: پلاگینها مشترک بودند.
- در زمان بیلد: پلاگینها برای هر محیط جداگانه بودند (در فرآیندهای مختلف:
vite buildسپسvite build --ssr).
این موضوع باعث میشد فریمورکها برای به اشتراکگذاری وضعیت بین بیلد client و بیلد ssr از فایلهای مانیفست نوشتهشده در فایل سیستم استفاده کنند. در Vite 6، اکنون تمام محیطها در یک فرآیند واحد بیلد میشوند، بنابراین مسیر اجرا پلاگینها و ارتباط بین محیطها میتواند با حالت توسعه هماهنگ شود.
در یکی از نسخههای اصلی آینده، ممکن است به یک هماهنگی کامل برسیم:
- در هر دو حالت توسعه و بیلد: پلاگینها مشترک خواهند بود، با فیلتر کردن مخصوص هر محیط.
همچنین یک نمونه مشترک از ResolvedConfig در زمان بیلد وجود خواهد داشت که امکان کش کردن در سطح کل فرآیند بیلد برنامه را فراهم میکند، مشابه کاری که در زمان توسعه با WeakMap<ResolvedConfig, CachedData> انجام میدهیم.
برای Vite 6، ما نیاز داریم یک گام کوچکتر برداریم تا سازگاری با نسخههای قبلی حفظ شود. پلاگینهای اکوسیستم در حال حاضر از config.build به جای environment.config.build برای دسترسی به تنظیمات استفاده میکنند، بنابراین به صورت پیشفرض باید یک ResolvedConfig جدید برای هر محیط ایجاد کنیم. یک پروژه میتواند با تنظیم builder.sharedConfigBuild روی true، به اشتراکگذاری کامل تنظیمات و مسیر اجرا پلاگینها را فعال کند.
این گزینه در ابتدا فقط برای یک زیرمجموعه کوچک از پروژهها کار خواهد کرد، بنابراین نویسندگان پلاگین میتوانند برای یک پلاگین خاص با تنظیم فلگ sharedDuringBuild روی true، اشتراکگذاری آن را فعال کنند. این امکان به راحتی به اشتراکگذاری وضعیت برای پلاگینهای معمولی را فراهم میکند:
function myPlugin() {
// بین تمام محیطها در حالت توسعه و بیلد state اشتراکگذاری
const sharedState = ...
return {
name: 'shared-plugin',
transform(code, id) { ... },
// فعالسازی یک نمونه مشترک برای تمام محیطها در زمان بیلد
sharedDuringBuild: true,
}
}