تجميع وحزم ملفات MDX الخاصة بك وتبعياتها. سريع.
لديك سلسلة من ملفات MDX ومختلف ملفات TS/JS التي تستخدمها وتريد الحصول على نسخة مجمعة من هذه الملفات للتقييم في المتصفح.
هذه وظيفة غير متزامنة ستعمل على تجميع وتجميع ملفات MDX وتبعياتها. يستخدم MDX V3 و ESBUILD ، لذلك فهو سريع للغاية ويدعم ملفات typescript (لتبعيات ملفات MDX الخاصة بك).
يمكن أن تكون ملفات المصدر محلية ، في ريبو GitHub بعيد ، في CMS ، أو في أي مكان آخر ولا يهم. جميع mdx-bundler
تهتم به هو أن تقوم بتمرير جميع الملفات والرمز المصدري اللازمة وسيهتم بتجميع كل شيء لك.
يمكّنك MDX من الجمع بين بناء جملة Terse Markdown لمحتوىك مع قوة مكونات React. بالنسبة للمواقع الثقيلة للمحتوى ، يمكن أن تكون كتابة المحتوى باستخدام HTML مباشرة مطوّلة بشكل مزعج. غالبًا ما يحل الناس هذا باستخدام محرر WSYWIG ، ولكن في كثير من الأحيان يختصر أولئك الذين يخجلون من نية الكاتب إلى HTML. يفضل الكثير من الناس استخدام Markdown للتعبير عن مصدر المحتوى الخاص بهم وتوصل إلى HTML ليتم تقديمه.
المشكلة في استخدام Markdown للمحتوى الخاص بك هي إذا كنت ترغب في الحصول على بعض التفاعل مضمن في المحتوى الخاص بك ، فأنت محدود للغاية. تحتاج إما إلى إدراج عنصر يستهدف JavaScript (وهو غير مباشر بشكل مزعج) ، أو يمكنك استخدام iframe
أو شيء ما.
كما ذكرنا سابقًا ، تمكنك MDX من الجمع بين بناء جملة Terse Markdown لمحتوىك مع قوة مكونات React. حتى تتمكن من استيراد مكون React وجعله داخل التخفيض نفسه. إنه أفضل ما في العالمين.
next-mdx-remote
؟" mdx-bundler
في الواقع يحدد تبعيات ملفات MDX الخاصة بك. على سبيل المثال ، لن يعمل هذا مع next-mdx-remote
، ولكنه سيفعل مع mdx-bundler
:
--- العنوان: مثال بعد نشره: 2021-02-13Description: هذا بعض الوصف ---# Wahooimport Demo من '. إليك عرض ** أنيق **: <demo />
يختنق next-mdx-remote
على هذا الاستيراد لأنه ليس حزمًا ، إنه مجرد مترجم. mdx-bundler
هو مترجم MDX وحصين. هذا هو الفرق.
تهدف هذه الأدوات إلى تشغيل "في وقت البناء" ثم تقوم بنشر الإصدار المبني من ملفاتك. هذا يعني أنه إذا كان لديك بعض المحتوى في MDX وترغب في إجراء تغيير مطبعي ، فيجب عليك إعادة البناء وإعادة نشر الموقع بأكمله. هذا يعني أيضًا أن كل صفحة MDX التي تضيفها إلى موقعك ستزيد من أوقات البناء ، لذلك لا يتم قياس كل هذا بشكل جيد.
يمكن بالتأكيد استخدام mdx-bundler
في وقت الإنشاء ، ولكن يتم استخدامه بقوة أكبر كمحكم وقت التشغيل. تتمثل حالة الاستخدام الشائعة في الحصول على مسار لمحتوى MDX الخاص بك وعندما يأتي هذا الطلب ، يمكنك تحميل محتوى MDX وتسليم ذلك إلى mdx-bundler
للتجمع. هذا يعني أن mdx-bundler
قابلة للتطوير بلا حدود. لن يكون بناءك بعد الآن بغض النظر عن مقدار محتوى MDX لديك. أيضًا ، mdx-bundler
سريع جدًا ، ولكن لجعل هذا التجميع عند الطلب بشكل أسرع ، يمكنك استخدام رؤوس ذاكرة التخزين المؤقت المناسبة لتجنب إعادة التوطين غير الضروري.
تتطلب WebPack/Rollup/etc أيضًا أن تكون جميع ملفات MDX على نظام الملفات المحلي للعمل. إذا كنت ترغب في تخزين محتوى MDX الخاص بك في ريبو أو CMS منفصل ، فأنت محظوظ نوعًا ما أو تضطر إلى القيام ببعض الجمباز في وقت البناء للحصول على الملفات في مكانها للبناء.
باستخدام mdx-bundler
، لا يهم من أين يأتي محتوى MDX الخاص بك ، يمكنك تجميع الملفات من أي مكان ، فأنت مسؤول فقط عن الحصول على المحتوى في الذاكرة ثم تقوم بتسليم ذلك إلى mdx-bundler
للتجمع.
تماما. إنه يعمل مع أي من هذه الأدوات. اعتمادًا على ما إذا كان Meta-Framework يدعم عرضًا من جانب الخادم ، فسوف تنفذه بشكل مختلف. قد تقرر الذهاب مع نهج الوقت المبني (لجاتسبي/CRA) ، ولكن كما ذكرنا ، فإن القوة الحقيقية لـ mdx-bundler
تأتي في شكل تجميع عند الطلب. لذلك هو الأنسب لأطر SSR مثل Remix/Next.
ولم لا؟
يوفر Esbuild خدمة مكتوبة في Go تتفاعل معها. يمكن تشغيل مثيل واحد فقط من هذه الخدمة في وقت واحد ويجب أن يكون له نسخة متطابقة لحزمة NPM. إذا كان ذلك بمثابة اعتماد صعب ، فلن تتمكن إلا من استخدام إصدار ESBuild الذي يستخدمه MDX-Bundler.
تثبيت
الاستخدام
خيارات
عودة
الأنواع
استبدال المكون
Frontmatter و const
الوصول إلى الصادرات المسماة
تجميع الصورة
تجميع ملف.
مكونات مخصصة في ملفات المصب
القضايا المعروفة
إلهام
حلول أخرى
مشاكل
؟ البق
طلبات الميزة
المساهمين
رخصة
يتم توزيع هذه الوحدة عبر NPM التي يتم تجميعها بالعقدة ويجب تثبيتها كأحد dependencies
مشروعك:
npm install --save mdx-bundler esbuild
تتطلب واحدة من تبعيات MDX-Bundler إعداد GYP العمل لتتمكن من تثبيت GOYP GOYT بشكل صحيح.
استيراد {bundlemdx} من 'mdx-bundler'const mdxsource = `--- العنوان: مثال بعد نشره: 2021-02-13description: هذا بعض الوصف ---# wahooimport demo من'. * العرض التوضيحي: <demo />``. trim()const نتيجة = انتظار bundlemdx ({ المصدر: MDXSource ، الملفات: {'./demo.tsx': `استيراد * كرد فعل من 'React'Function Demo () {return <viv> demo neat! </viv>} تصدير العرض الافتراضي` ، } ،}) const {code ، frontmatter} = النتيجة
من هناك ، ترسل code
إلى عميلك ، ثم:
استيراد * كرد فعل من 'React'Import {getMdxComponent} من' mdx-bundler/client'function post ({code ، frontmatter}) { // من الجيد عمومًا أن تذكر هذه الدعوة إلى هذه الوظيفة // تجنب إعادة إنشاء المكون كل عرض. const component = react.usememo (() => getMdxComponent (رمز) ، [رمز]) return (<> <header> <h1> {frontmatter.title} </h1> <p> {frontmatter.description} </p> </header> <main> <component/> </main> </> )}
في النهاية ، يتم تقديم هذا (أساسًا):
<header> <h1> هذا هو العنوان </h1> <p> هذا بعض الوصف </p> </header> <main> <viv> <h1> wahoo </h1> <p> إليك <strong> neat </strong> التجريبي: </p> <viv> عرضًا أنيقًا! </div> </viv> </kain>
مصدر string
MDX الخاص بك.
لا يمكن تعيينه إذا تم تعيين file
الطريق إلى الملف على القرص الخاص بك مع MDX في. ربما تريد تعيين CWD أيضا.
لا يمكن تعيينه إذا تم تعيين source
تكوين files
هو كائن لجميع الملفات التي تجمعها. المفتاح هو المسار إلى الملف (نسبة إلى مصدر MDX) والقيمة هي سلسلة رمز مصدر الملف. يمكنك الحصول على هذه من نظام الملفات أو من قاعدة بيانات عن بعد. إذا لم يشير MDX الخاص بك إلى الملفات الأخرى (أو فقط يستورد الأشياء من node_modules
) ، فيمكنك حذف هذا تمامًا.
يتيح لك ذلك تعديل تكوين MDX المدمج (تم تمريره إلى @mdx-js/esbuild
). يمكن أن يكون هذا مفيدًا لتحديد probsplugins/rehypeplugins.
يتم تمرير الوظيفة MDXOptions الافتراضية و Frontmatter.
bundlemdx ({ المصدر: MDXSource ، mdxoptions (الخيارات ، Frontmatter) {// هذه هي الطريقة الموصى بها لإضافة الملحقات المخصصة/إعادة التأهيل: // قد يبدو بناء الجملة غريبًا ، ولكنه يحميك في حالة إضافة/إزالة // الإضافات في المستقبل. = [... (Options.RemarkPlugins ؟؟ []) ، MyRemarkPlugin] Options.rehypepeplugins = [... } ،})
يمكنك تخصيص أي من خيارات ESBuild مع خيار esbuildOptions
. يأخذ هذا وظيفة يتم تمريرها لخيارات ESBuild الافتراضية و Frontmatter وتتوقع إرجاع كائن خيارات.
bundlemdx ({ المصدر: MDXSource ، esBuildOptions (الخيارات ، Frontmatter) {Options.minify = poxioPtions.Target = ['ES2020' ، 'Chrome58' ، 'Firefox57' ، 'Safari11' ، 'Edge16' ، 'Node12' ،] } ،})
يمكن العثور على مزيد من المعلومات حول الخيارات المتاحة في وثائق ESBuild.
يوصى باستخدام هذه الميزة لتكوين target
لإخراجك المطلوب ، وإلا ، فإن الإعدادات الافتراضية لـ ESBUILD إلى esnext
وهذا يعني أنه لا يقوم بتجميع أي ميزات موحدة ، لذا فإن مستخدمي المتصفحات الأقدم سيواجهون أخطاء.
هذا يخبر Esbuild أن وحدة معينة متوفرة من الخارج. على سبيل المثال ، إذا كان ملف MDX الخاص بك يستخدم مكتبة D3 وكنت تستخدم بالفعل مكتبة D3 في تطبيقك ، فسوف ينتهي بك الأمر إلى شحن d3
إلى المستخدم مرتين (مرة واحدة لتطبيقك ومرة واحدة لمكون MDX). هذا أمر مضيء وسيكون من الأفضل لك مجرد إخبار Esbuild بعدم حزمة d3
ويمكنك نقله إلى المكون بنفسك عند الاتصال getMDXComponent
.
خيارات التكوين الخارجي العالمي: https://www.npmjs.com/package/@fal-works/esbuild-plugin-global-externals
هذا مثال:
. ! المصدر: MDXSource ، // ملاحظة: هذا * فقط * ضروري إذا كنت تريد مشاركة DEPS بين MDX الخاص بك // حزمة الملف والتطبيق المضيف. خلاف ذلك ، سيتم تجميع جميع النماذج. // لذلك سوف يعمل في كلتا الحالتين ، هذا مجرد تحسين لتجنب الإرسال // نسخ متعددة من نفس المكتبة للمستخدمين. Globals: {'Left-Pad': 'myleftpad'} ،})
// كود من جانب الخادم و/أو من جانب العميل يمكن تشغيله في المتصفح أو العقدة: استيراد * كرد فعل من 'React'import leftpad من' pad'iMport {getMdxComponent} من 'mdx-bundler/client'function mdxpage ({code}: {code: string}) { const component = react.usememo (() => getMdxComponent (result.code ، {myleftpad: leftpad}) ، [result.code ، leftpad] ، ) العودة (<Main> <component /> </kain> )}
سيسمح تعيين cwd
( دليل العمل الحالي ) إلى دليل ESBUILD بحل الواردات. يمكن أن يكون هذا الدليل هو الدليل الذي تمت قراءة محتوى MDX من أو دليل يجب تشغيله MDX خارج القرص.
المحتوى/الصفحات/demo.tsx
استيراد * كرد فعل من 'React'Function Demo () { إرجاع <div> demo neat! </viv>} تصدير العرض الافتراضي
src/build.ts
استيراد {bundlemdx} من 'mdx-bundler'const mdxsource = `--- العنوان: مثال بعد نشره: 2021-02-13description: هذا بعض الوصف ---# wahooimport demo من'. * العرض التوضيحي: <demo />``. trim()const نتيجة = انتظار bundlemdx ({ المصدر: MDXSource ، CWD: '/user/you/site/_content/pages' ،}) const {code ، frontmatter} = النتيجة
يتيح لك ذلك تكوين خيارات الرمادي.
يتم تمرير وظيفتك تكوين Matter Gray الحالي لتعديله. إرجاع كائن التكوين المعدل الخاص بك للمادة الرمادية.
bundlemdx ({ GrayMatterOptions: خيارات => {Options.excerpt = خيارات truereturn } ،})
يتيح لك ذلك تعيين دليل الإخراج للحزمة وعنوان URL العام على الدليل. إذا تم تعيين خيار واحد ، فيجب أن يكون الآخر كذلك.
لا تتم كتابة حزمة JavaScript إلى هذا الدليل ولا يزال يتم إرجاعها كسلسلة من bundleMDX
.
من الأفضل استخدام هذه الميزة مع التعديلات على mdxOptions
و esbuildOptions
. في المثال أدناه. تتم كتابة ملفات .png
على القرص ثم يتم تقديمها من /file/
.
يتيح لك ذلك تخزين الأصول باستخدام MDX الخاص بك ومن ثم إجراء معالجة Esbuild مثل أي شيء آخر.
يوصى بأن تحتوي كل حزمة على إدراكها bundleDirectory
بحيث لا تحدد الحزم المتعددة أصول بعضها البعض.
const {code} = في انتظار bundlemdx ({ ملف: '/path/to/site/content/file.mdx' ، CWD: '/path/to/site/content' ، bundledirectory: '/path/to/site/public/file' ، BundlePath: '/file/' ، mdxoptions: خيارات => {orpoicess.RemarkPlugins = [empromdscimages] خيارات الإرجاع } ، esBuildOptions: Options => {Options.loader = {... Options.loader ، '.png': 'file' ،} خيارات الإرجاع } ،})
bundleMDX
يعيد وعدًا لكائن مع الخصائص التالية.
code
- حزمة MDX الخاص بك string
.
frontmatter
- object
Frontmatter من الرمادي.
matter
- الكائن بأكمله الذي تم إرجاعه بواسطة Mray -Matter
mdx-bundler
Supplies تكمل الطباعة داخل الحزمة الخاصة بها.
يحتوي bundleMDX
على معلمة نوع واحد وهو نوع الأمامي. إنه افتراضي إلى {[key: string]: any}
ويجب أن يكون كائنًا. ثم يتم استخدام هذا لكتابة frontmatter
المُعاد وتم تمرير المواجهة إلى esbuildOptions
و mdxOptions
.
const {frontmatter} = bundlemdx <{title: string}> ({source})
يمر MDX Bundler على قدرة MDX على استبدال المكونات من خلال components
الدعامة على المكون الذي تم إرجاعه بواسطة getMDXComponent
.
إليك مثال يزيل علامات P من حول الصور.
استيراد * كرد فعل من 'React'Import {getMdxComponent} من الفقرة mdx-bundler/client'const: react.fc = props => { if (typeof props.children! == 'string' && props.children.type === 'img') {return <> {props.children} </> } إرجاع <p {... props} />} الدالة mdxpage ({code}: {code: string}) { const component = react.usememo (() => getMdxComponent (رمز) ، [رمز]) إرجاع (<Main> <مكونات المكون = {{p: paragraph}} /> </kain> )}
يمكنك الرجوع إلى Frontmatter Meta أو consts في محتوى MDX.
--- العنوان: مثال post --- تصدير const examplemage = 'https://example.com/image.jpg'# {frontmatter.title} <img src = {examplemage} alt = "صورة alt text"/>
يمكنك استخدام getMDXExport
بدلاً من getMDXComponent
لعلاج ملف MDX كوحدة بدلاً من مجرد مكون. يستغرق نفس الحجج التي يفعلها getMDXComponent
.
--- العنوان: مثال POST --- تصدير const toc = [{depth: 1 ، القيمة: 'title'}]# العنوان
استيراد * كرد فعل من 'React'Import {getMdxexport} من' mdx-bundler/client'function mdxpage ({code}: {code: string}) { const mdxexport = getMdxexport (رمز) console.log (mdxexport.toc) // [{depth: 1 ، value: 'title'}] const component = react.usememo (() => mdxexport.default ، [code]) العودة <component />}
مع CWD وملابس الإضافية الملحوظة ، يمكنك الحصول على صور في MDX!
هناك نوعان من اللصين في Esbuild يمكن استخدامه هنا. الأسهل هو dataurl
الذي يخرج الصور كقوانين بيانات مضمنة في الكود الذي تم إرجاعه.
استيراد {empromdximages} من 'empromm-mdx-images'const {code} = await bundlemdx ({ المصدر: MDXSource ، CWD: '/المستخدمين/أنت/الموقع/_content/pages "، mdxoptions: الخيارات => {{orpoys.remarkplugins = [... (Options.RemarkPlugins ؟؟ []) } ، esBuildOptions: Options => {Options.loader = {... Options.loader ، '.png': 'dataurl' ،} خيارات الإرجاع } ،})
يتطلب محمل file
المزيد من التكوين للعمل. من خلال محمل file
، يتم نسخ صورك إلى دليل الإخراج ، لذا يجب ضبط EsBuild على كتابة الملفات ويحتاج إلى معرفة مكان وضعها بالإضافة إلى عنوان URL للمجلد ليتم استخدامه في مصادر الصور.
كل مكالمة إلى
bundleMDX
معزولة عن الآخرين. إذا قمت بتعيين الدليل نفسه لكل شيء ، فسيقومbundleMDX
بتوضيح الصور دون سابق إنذار. نتيجة لذلك ، تحتاج كل حزمة إلى دليل الإخراج الخاص بها.
// للملف `_content/pages/about.mdx`const {code} = Await bundlemdx ({ المصدر: MDXSource ، CWD: '/المستخدمين/أنت/الموقع/_content/pages "، mdxoptions: الخيارات => {{orpoys.remarkplugins = [... (Options.RemarkPlugins ؟؟ []) } ، esBuildOptions: الخيارات => {// قم بتعيين `Outdir` على موقع عام لهذا bundle.options.outdir = '/usoRs/you/site/public/img/about'options.loader = {{options.loadserer ، // أخبر Esbuild استخدام محمل `file` لـ pngs '.png': بحيث يقوم esBuild بإخراج الملفات. } ،})
إذا كان ملف MDX الخاص بك على القرص الخاص بك ، فيمكنك حفظ بعض الوقت والرمز عن طريق جعل mdx-bundler
قراءة الملف لك. بدلاً من توفير سلسلة source
، يمكنك تعيين file
على مسار MDX على القرص. اضبط cwd
على مجلدها بحيث تعمل الواردات النسبية.
استيراد {bundlemdx} من 'mdx-bundler'const {code ، frontmatter} = await bundlemdx ({ ملف: '/USIT/YOU/SITE/CONTENT/FILE.MDX' ، CWD: '/المستخدمين/أنت/الموقع/المحتوى/' ،})
للتأكد من الوصول إلى المكونات المخصصة في ملفات MDX المصب ، يمكنك استخدام MDXProvider
من @mdx-js/react
لتمرير مكونات مخصصة إلى الواردات المتداخلة.
npm install --save @mdx-js/react
const globals = { '@mdx-js/react': {varname: 'mdxjsreact' ، اسمه exports: ['UsemdxComponents'] ، defaultexport: false ، } ،} ؛ const {code} = bundlemdx ({ مصدر، الكرات ، mdxoptions (الخيارات: سجل <سلسلة ، أي>) {return {... خيارات ، ProviderImportSource: '@mdx-js/react' ،} ؛ }}) ؛
من هناك ، ترسل code
إلى عميلك ، ثم:
استيراد {mdxprovider ، usemdxcomponents} من '@mdx-js/react' ؛ const mdx_global_config = { MDXJSREACT: {USEMDXCOMPONES ، } ،} ؛ تصدير const mdxComponent: React.fc <{ الكود: سلسلة ؛ FrontMatter: سجل <string ، any> ؛}> = ({code}) => { const component = usememo (() => getMdxComponent (رمز ، mdx_global_config) ، [رمز] ، ) ؛ return (<mdxprovider components = {{text: ({Kids}) => <p> {knids} </p>}}> <component/> </mdxprovider> ) ؛} ؛
نود أن يعمل هذا في عمال Cloudflare. لسوء الحظ ، لدى CloudFlares قيودًا تمنع mdx-bundler
من العمل في تلك البيئة:
لا يمكن للعمال تشغيل الثنائيات. يستخدم bundleMDX
esbuild
(ثنائي) لتجميع رمز MDX الخاص بك.
لا يمكن للعمال تشغيل eval
أو ما شابه. getMDXComponent
يقييم الرمز المجمع باستخدام new Function
.
أحد الحلول في هذا هو وضع الكود المتعلق بـ MDX-Bundler في بيئة مختلفة واستدعاء تلك البيئة من داخل عامل CloudFlare. IMO ، هذا يهزم الغرض من استخدام عمال CloudFlare. حل محتمل آخر هو استخدام WASM من داخل العامل. هناك esbuild-wasm
ولكن هناك بعض المشكلات مع هذه الحزمة الموضحة في هذا الرابط. ثم هناك wasm-jseval
، لكنني لم أستطع الحصول على ذلك لتشغيل الرمز الذي تم إخراجه من mdx-bundler
دون خطأ.
إذا كان شخص ما يرغب في البحث في هذا ، فسيكون ذلك ممتازًا ، لكن للأسف ، من غير المحتمل أن أعمل عليه.
يعتمد Esbuild على __dirname
للتعرف على مكان التنفيذ ، و next.js و webpack يمكن أن يكسروا هذا في بعض الأحيان ويجب إخبار esbuild يدويًا أينما نظرت.
ستؤدي إضافة الكود التالي قبل أن يشير bundleMDX
إلى Esbuild مباشرةً في المناسبة الصحيح لمنصة الخاصة بك.
استيراد مسار من 'path'if (process.platform ===' win32 ') { process.env.esbuild_binary_path = path.join (process.cwd () ، 'node_modules' ، 'esbuild' ، 'esbuild.exe' ، )} آخر { process.env.esbuild_binary_path = path.join (process.cwd () ، 'node_modules' ، 'esbuild' ، 'bin' ، 'esbuild' ، )}
يمكن العثور على مزيد من المعلومات حول هذه المشكلة في هذه المقالة.
بينما كنت أعيد كتابة Kentcdodds.com لإعادة التثبيت ، قررت أنني أردت الاحتفاظ بمشاركات المدونة الخاصة بي مثل MDX ، لكنني لم أكن أرغب في تجميعها جميعًا في وقت البناء أو مطالبة بإعادة النشر في كل مرة أقوم فيها بإصلاح المطبع المطبعي. لذلك صنعت هذا الذي يسمح لخادمتي بالتجميع عند الطلب.
هناك next-mdx-remote ولكنه أكثر من mdx-compiler أكثر من Bundler (لا يمكن تجميع MDX الخاص بك للاعتمادات). كما أنه يركز على Next.js في حين أن هذا هو لاأدري في الرمح.
تبحث للمساهمة؟ ابحث عن ملصق العدد الأول الجيد.
يرجى تقديم مشكلة عن الأخطاء أو الوثائق المفقودة أو السلوك غير المتوقع.
انظر الحشرات
يرجى تقديم مشكلة لاقتراح ميزات جديدة. التصويت على طلبات الميزة عن طريق إضافة؟. هذا يساعد المشرفين على إعطاء الأولوية لما يجب العمل عليه.
انظر طلبات الميزة
شكراً لهؤلاء الناس (مفتاح الرموز التعبيرية):
كينت سي دودز ؟ | بينويس ؟ ؟ | آدم لايكوك | تيتوس ؟ ؟ | كريستيان ميرفي ؟ | بيدرو دوارتي | إريك راسموسن |
عمر سيكس ؟ | جايل حامي | غابرييل لويكونو | سبنسر ميسكوفياك | كاسبر | أبوستولوس كريستودولو | Yordis Prieto |
Xoumi | ياسين | محمد "مو" مولازادا | يمكن رو | Hosenur Rahaman | Maciek Sitkowski | بريانغ |
فسيوساد | ستيفانبروبست | فلاد موروز |
يتبع هذا المشروع مواصفات جميع المساهمين. مساهمات من أي نوع ترحيب!
معهد ماساتشوستس للتكنولوجيا