المحتويات
التصيير على الخادم
يوفّر Carats تصييرًا تلقائيًا على الخادم (SSR) عبر حزمة @carats/ssr.
نقطة دخول الخادم
تُعرَّف نقطة دخول الخادم في src/server/entrypoint.ts باستخدام defineServerEntry من @carats/ssr. وهي تربط الأوجه المشتركة وتسجّل الدوال الخادمية وتُظهر دالتي render وgetServerProps اللتين يستخدمهما مُلحق الخادم.
import { defineServerEntry } from '@carats/ssr';
import facets from '../client/facets.cara';
export default defineServerEntry(facets);
ما تكشفه نقطة الدخول
الكائن الذي يُعيده `defineServerEntry` هو ما يقوده مُلحق الخادم:
- `render(req)` يحلّ الوجه للمسار المطلوب، ويشغّل مكوّن الصفحة، ويُعيد `html` المُصيَّر مع وسوم `head` المجموعة.
- `getServerProps(req)` يحلّ الدالة الخادمية للمسار المطلوب ويُعيد قيمتها، أو `undefined` عندما لا يكون للمسار دالة خادمية.
- `facets` هو كائن الأوجه المشترك، و`culets` هو السجل الذي يبنيه culet وseat.
هذا هو سبب وجوب أن يكون التصدير الافتراضي هو نقطة الدخول نفسها: مُلحق Express يستدعي هذه الدوال لكل طلب.
تعريف الدوال الخادمية
تُعرَّف الدوال الخادمية بدالة culet من @carats/ssr. عرِّف واحدة فقط عندما يحتاج مسار فعلًا خاصية من الخادم، وفقط عندما يمكن جلب البيانات بطلب GET واحد. يتلقى المعالج الطلب كاملًا بما فيه معاملات المسار والكوكيز والترويسات والاستعلام والطريقة.
import { culet } from '@carats/ssr';
culet<User>('/profile', (req) => {
return {
id: '1',
name: 'Alexander Whitmore',
username: 'awhitmore',
email: 'a.whitmore@vault.io',
phone: '+1 (212) 555-0193',
website: 'whitmore.capital',
};
});
معامل النوع (generic) اختياري لكنه يستحق الإبقاء: فهو ما يجعل القيمة المُعادة تطابق خصائص الصفحة التي تستهلكه.
تثبيت الدوال الخادمية
يجب تسجيل الدالة الخادمية المُنشأة في ملفها الخاص بدالة seat في نقطة دخول الخادم. والتثبيت لا يُلَفّ الدالة الخادمية ولا يُنشئها من جديد؛ بل يُعيد الدالة نفسها ليُستخدَم في السطر.
import { culet, seat } from '@carats/ssr';
import getTradeData from './culets/trade';
seat(getTradeData);
الدالة الخادمية المصرَّح عنها في السطر داخل نقطة الدخول تُسجّل نفسها، لذا لا تحتاج إلى استدعاء seat. أما الدالة الخادمية المستوردة من src/server/culets فقد أُنشئت بـ culet() في وحدتها الخاصة ويجب تثبيتها مرة واحدة. لا تُلَفّ دالة خادمية مثبَّتة باستدعاء culet() آخر، لأن ذلك سيُسجِّل المعالج نفسه تحت مسار ثانٍ.
التكامل مع Express
Carats مدمج مع Express عبر @carats/express. وتُعيد الدالة المُصدَّرة carats قيمة Router يُصيّر الصفحات ويحلّ خصائص الخادم ويقدّم أصول العميل المبنية، لذا تُركَّب بـ app.use. وتتطلب هذه الحزمة Express 5.
نقطة دخول الخادم
import { culet, defineServerEntry, seat } from '@carats/ssr';
import facets from '../client/facets.cara';
import getTradeData from './culets/trade';
seat(getTradeData);
culet<User>('/profile', () => {
return {
id: '1',
name: 'Alexander Whitmore',
username: 'awhitmore',
email: 'a.whitmore@vault.io',
phone: '+1 (212) 555-0193',
website: 'whitmore.capital',
};
});
export default defineServerEntry(facets);
تطبيق Express
import { carats } from '@carats/express'
import express from 'express'
const app = express()
const port = process.env.PORT || 5173
app.use(carats())
const server = app.listen(port, () => {
console.log(`Server started at http://localhost:${port}`)
})
export default server;
رتّب الوسائط البرمجية بحيث يعمل `carats()` بعد المسارات ومعالجات الملفات الثابتة التي لا ينبغي له ابتلاعها. يُعيد المُلحق قيمة Express `Router`، ولهذا يُركَّب بـ `app.use` بدل تسجيله كمسار.
إعدادات Vite للخادم
يُبنى حزمة الخادم من src/server/entrypoint.ts إلى dist/server. ويُعطَّل publicDir لأن الأصول الثابتة تخص بناء العميل.
import { defineConfig } from 'vite'
import path from 'path'
export default defineConfig({
publicDir: false,
build: {
ssr: path.resolve(__dirname, 'src/server/entrypoint.ts'),
outDir: path.resolve(__dirname, 'dist/server'),
emptyOutDir: true,
minify: true,
rollupOptions: {
treeshake: true
}
}
})
مثال كامل لنقطة دخول الخادم
// src/server/culets/trade.ts
import { culet } from '@carats/ssr';
export default culet<TradeData>('/trade/:symbol', async (req) => {
const symbol = req.params.symbol;
if (!symbol) {
throw new Error('Symbol is required');
}
return {
symbol: symbol.toUpperCase(),
price: '$67,234.18',
change24h: '+2.34%',
volume: '$28.4B',
high24h: '$68,102.50',
low24h: '$65,890.00',
orderBook: { bids: [], asks: [] },
};
});
// src/server/entrypoint.ts
import { culet, defineServerEntry, seat } from '@carats/ssr';
import facets from '../client/facets.cara';
import getTradeData from './culets/trade';
seat(getTradeData);
culet<User>('/profile', () => {
return {
id: '1',
name: 'Alexander Whitmore',
username: 'awhitmore',
email: 'a.whitmore@vault.io',
phone: '+1 (212) 555-0193',
website: 'whitmore.capital',
};
});
export default defineServerEntry(facets);
لاحظ أن مسار الوجه ومسار الدالة الخادمية سلسلتان متطابقتان تمامًا، بما في ذلك المعامل :symbol. والاختلاف بينهما هو السبب الأكثر شيوعًا لإصيارة صفحة مُبرَزة دون بيانات.
تعريفات الأنواع
import { CaratsRequest } from '@carats/core';
import { Facets } from '@carats/render';
interface CaratsServerEntry {
render: (req: CaratsRequest<never>) => Promise<{
html?: string;
head?: string;
}>;
getServerProps: <T = any>(req: CaratsRequest<never>) => Promise<T> | T;
facets: Facets;
culets: Record<string, Culet>;
}
type CuletArgs = Omit<CaratsRequest, 'data'> & {
params: Record<string, string>;
};
type Culet<T = any> = (request: CuletArgs) => T;
declare const seat: <T extends Culet>(f: T) => T;
declare function culet<T = any>(route: string, culet: Culet<T>): Culet<T>;
declare function defineServerEntry(facets: Facets): CaratsServerEntry;