
يحتوي TypeScript على دعم داخلي لبناء جملة JSX ويوفر برنامج التحويل البرمجي لـ TypeScript أدوات مفيدة لإعداد عملية تجميع JSX. بشكل أساسي ، هذا يجعل من الممكن كتابة DSL المكتوب باستخدام JSX. هذه المقالة سوف تناقش هذا بالضبط - كيفية كتابة DSL من ص باستخدام jsx. مهتمة ، أطلب القط.
→ مستودع مع مثال الجاهزة.
في هذه المقالة ، لن أعرض الإمكانيات بأمثلة متعلقة بالويب ، React ، وما شابه. مثال على ذلك ليس من الويب سيوضح أن قدرات JSX لا تقتصر على React ومكوناته وتوليد html في الحالة العامة. في هذه المقالة ، سأوضح كيفية تنفيذ DSL لإنشاء كائنات الرسائل لـ Slack .
إليك الكود الذي نأخذه كأساس. هذا هو مصنع رسالة صغيرة من نفس النوع:
interface Story { title: string link: string publishedAt: Date author: { name: string, avatarURL: string } } const template = (username: string, stories: Story[]) => ({ text: `:wave: ${username}, .`, attachments: stories.map(s => ({ title, color: '#000000', title_link: s.link, author_name: s.author.name, author_icon: s.author.avatarURL, text: ` _${s.publishedAt}_.` }) })
يبدو أنها تبدو جيدة ، ولكن هناك نقطة واحدة يمكن تحسينها بشكل كبير - إمكانية القراءة . على سبيل المثال ، انتبه إلى خاصية color
غير title_link
، أو title_link
بالعنوان ( title
و title_link
) ، أو title_link
السفلية في text
(النص الموجود داخل _
سيكون مائلًا ). كل هذا يمنعنا من فصل المحتوى عن التفاصيل الأسلوبية ، مما يجعل من الصعب العثور على ما هو مهم. ومع مثل هذه المشاكل ، ينبغي أن يساعد DSL.
هنا هو نفس المثال الذي كتب للتو في JSX:
const template = (username: string, stories: Story[]) => <message> :wave: ${username}, . {stories.map(s => <attachment color='#000000'> <author icon={s.author.avatarURL}>{s.author.name}</author> <title link={s.link}>{s.title}</title> <i>{s.publishedAt}</i>. </attachment> )} </message>
أفضل بكثير! كل شيء يجب أن يعيش معًا لديه تفاصيل ومحتوى موحد ، مفصولان بوضوح - جمال بكلمة واحدة.
كتابة DSL
تخصيص المشروع
تحتاج أولاً إلى تمكين JSX في المشروع وإخبار المترجم أننا لا نستخدم React ، وأن JSX الخاص بنا يحتاج إلى تجميع بطريقة مختلفة.
// tsconfig.json { "compilerOptions": { "jsx": "react", "jsxFactory": "Template.create" } }
"jsx": "react"
يتضمن "jsx": "react"
دعم JSX في المشروع ويقوم المترجم بترجمة جميع عناصر React.createElement
إلى مكالمات React.createElement
. "jsxFactory"
خيار "jsxFactory"
بتكوين المترجم لاستخدام مصنعنا لعناصر JSX.
بعد هذه الإعدادات البسيطة ، رمز النموذج:
import * as Template from './template' const JSX = <message>Text with <i>italic</i>.</message>
سوف تجميع في
const Template = require('./template'); const JSX = Template.create('message', null, 'Text with ', Template.create('i', null, 'italic'), '.');
وصف JSX العلامات
الآن وبعد أن تعرف المترجم ما الذي يجب ترجمة JSX إليه ، نحتاج إلى إعلان العلامات بأنفسهم. للقيام بذلك ، نستخدم واحدة من ميزات TypeScript الرائعة - وهي إعلانات مساحة الاسم المحلية. بالنسبة إلى حالة JSX ، تتوقع TypeScript أن يحتوي المشروع على مساحة اسم JSX
(لا يهم الموقع المحدد للملف) مع واجهة IntrinsicElements
التي يتم فيها وصف العلامات نفسها. المحول البرمجي catches لهم ويستخدمها للتحقق من النوع وتلميحات.
هنا أعلنا عن جميع علامات JSX الخاصة بـ DSL وكل سماتها. في الواقع ، اسم المفتاح في الواجهة هو اسم العلامة نفسها ، والتي ستكون متاحة في الكود. القيمة هي وصف للسمات المتاحة. قد لا تحتوي بعض العلامات ( i
في حالتنا) على أي سمات ، وقد يكون البعض الآخر اختياريًا أو ضروريًا.
المصنع نفسه - Template.create
لدينا مصنع من tsconfig.json
هو موضوع المحادثة. سيتم استخدامه في وقت التشغيل لإنشاء كائنات.
في أبسط الحالات ، قد يبدو مثل هذا:
type Kinds = keyof JSX.IntrinsicElements
من السهل كتابة العلامات التي تضيف أنماط فقط إلى النص بداخلها ( i
في حالتنا): يلتف المصنع ببساطة بمحتويات العلامة في سلسلة مع _
على كلا الجانبين. تبدأ المشاكل بعلامات معقدة. معظم الوقت قضيته معهم ، أبحث عن حل أنظف. ما هي المشكلة الفعلية؟
وهو أن المترجم يطبع نوع <message>Text</message>
على any
. الأمر الذي لم يقترب من خط المشترك الرقمي (DSL) المكتوب ، حسنًا ، حسنًا ، الجزء الثاني من المشكلة هو أن نوع كل العلامات سيكون واحدًا بعد المرور عبر المصنع - وهذا قيد JSX نفسه (في React ، يتم تحويل جميع العلامات إلى ReactElement).
الأدوية الجنيسة تذهب إلى الإنقاذ!
Element
إضافة Element
فقط وسيقوم برنامج التحويل البرمجي الآن بإخراج جميع علامات JSX إلى نوع Element
. هذا أيضًا سلوك مترجم قياسي - استخدم JSX.Element
كنوع لجميع العلامات.
يحتوي Element
على طريقة شائعة واحدة فقط - تحويلها إلى نوع كائن رسالة. لسوء الحظ ، لن تعمل دائمًا ، فقط على <message/>
ذات المستوى الأعلى ، وسيكون هذا في مهلة.
وتحت المفسد هو النسخة الكاملة من المصنع.
رمز المصنع نفسه import { flatten } from 'lodash' type Kinds = keyof JSX.IntrinsicElements
→ مستودع مع مثال الجاهزة.
بدلا من الاستنتاج
عندما أجريت هذه التجارب ، كان لدى فريق TypeScript فقط فهم القوة والقيود على ما فعلوه مع JSX. الآن قدراته أكبر ويمكن أن يكون المصنع أكثر نظافة. إذا كنت تريد البحث عن المستودع وتحسينه بمثال - Wellcome مع طلبات السحب.