انتقل إلى المحتوى
في هذه الصفحة

ngrx-graph: التحليل الاستاتيكي ورسم المخططات البيانية لتطبيقات NgRx

ngrx-graph: التحليل الاستاتيكي ورسم المخططات البيانية لتطبيقات NgRxصورة منشأة بالذكاء الاصطناعي

إذا قمت يوماً بتتبع تدفق حدث (Action) عبر كود NgRx واسع النطاق، متسائلاً أي Effect يستمع إليه، وأي Reducer يعالجه، وما إذا كان هناك Effect آخر يطلق حركياً حدثاً آخر في السلسلة، فأنت تعرف حجم المعاناة. التتبع اليدوي يعمل حتى يصل إلى حائط مسدود. استدعاء واحد مفقود لـ dispatch() مدفون داخل mergeMap كفيل بتدمير تصورك الذهني للكود.

أداة ngrx-graph هي أداة سطر أوامر (CLI) تحول تلك الفوضى إلى صور واضحة. تقوم بفحص مشروع Angular NgRx، وبناء تمثيل هيكلي بصيغة JSON لكل Component و Action و Effect و Reducer، ثم توليد مخططات بصيغة DOT و SVG تجعل تدفق الأحداث مرئياً بنظرة واحدة.

المشكلة: تتبع أحداث NgRx يدوياً

إطار NgRx قوي للغاية، حيث يوفر حاوية حالة متوقعة مع فصل واضح للمسؤوليات: تصف الأحداث (Actions) ما الذي حدث، وتتعامل التأثيرات (Effects) مع الآثار الجانبية، بينما تقرر المخفضات (Reducers) كيف تتغير الحالة. من الناحية النظرية، تدفق البيانات نقي ومنظم.

أما عملياً، فتتراكم في تطبيقات NgRx الكبيرة عشرات (وأحياناً مئات) الأحداث. تقوم الـ Effects بربط الأحداث ببعضها. تطلق المكونات أحداثاً تحفز تأثيراً يطلق أحداثاً أخرى. تستمع الـ Reducers لأنواع أفعال محددة. وفي مكان ما في المنتصف، يتم تعريف اسم مستعار للحدث عبر تصدير حزمة (barrel export) وإعادة تصديره باسم مختلف.

إليك كيف تبدو جلسة تصحيح الأخطاء (Debugging) التقليدية:

  1. تجد استدعاء dispatch(SomeAction) داخل Component.
  2. تبحث عن مكان معالجة SomeAction: ربما في Effect، وربما في Reducer.
  3. يقوم الـ Effect بعمل ما ثم يطلق AnotherAction.
  4. تكرر نفس العملية مع AnotherAction.
  5. عند المستوى الثالث من التعقيد، تفقد التتبع تماماً.

هذه ليست مشكلة نظرية. في مشاريع NgRx الفعلية، تمتد سلاسل الأحداث روتينياً عبر 3 إلى 5 مستويات من التوجيه غير المباشر. الـ Effects تركّب التأثيرات فوق بعضها، والأحداث المتداخلة (أحداث يتم إنشاؤها عبر إطلاق أحداث أخرى) تضيف طبقة جديدة من التعقيد. الإجهاد الذهني واقعي ويؤدي إلى أخطاء: استدعاءات مفقودة، حالة غير محدثة، وتكرار منطق البرمجة.

ما تحتاجه حقاً هو خريطة. ليست خريطة رسمها شخص ما مرة واحدة ونسي تحديثها، بل خريطة مولدة آلياً ومبنية من الكود المصدري الفعلي في كل مرة.

بدون ngrx-graph: 15 دقيقة تنقل بين 8 تبويبات في VS Code لتتبع ofType() و barrel exports واستدعاءات dispatch().

مع ngrx-graph: نفّذ الأمر npx ngrx-graph "LoadUsers" --svg وافحص مسار التنفيذ الكامل في ثانيتين.

الهندسة المعمارية للنظام

أداة ngrx-graph هي CLI مبنية على Node.js باستخدام oclif. تعمل سلسلة المعالجة عبر ثلاث مراحل:

  1. الراسم (Scanning): تقوم TypeScript Compiler API بتحليل ملفات المصدر، واستخراج تعاريف الأحداث، وسلاسل الـ Effects، ومعالجات الـ Reducers، واستدعاءات التمرير في المكونات عبر المرور على AST.
  2. ملف JSON الوسيط: يتم تحويل جميع نتائج الفحص إلى صيغة ngrx-graph.json المعيارية، مما يفصل مرحلة الفحص عن مرحلة الرسم.
  3. توليد المخطط (Graph Generation): يتم تغذية ملف JSON لتوليد صيغة Graphviz DOT، مع خيار تحويلها إلى SVG عبر أمر dot الأصلي أو عبر مكتبة WASM بديلة.
ملفات المصدر --> راسمات AST --> ngrx-graph.json --> مولّد DOT/SVG
     |                |                 |                   |
  ملفات *.ts     عمال p-limit      صيغة معيارية      Graphviz أو viz.js

كل مرحلة قابلة للاستخدام بشكل مستقل. يمكنك التوقف بعد مرحلة JSON لمعاينة مخرجات الفحص، كما يمكنك إعادة استخدام ملفات DOT مع أي أداة متوافقة مع Graphviz. تحافظ هذه المعمارية على فصل المسؤوليات بحيث لا تؤثر تحسينات الفحص على عملية الرسم والعكس صحيح.

التثبيت والاستخدام السريع

قم بالتثبيت بشكل عام أو استخدم npx مباشرة:

npm install -g ngrx-graph
# أو
npx ngrx-graph ...

مسارات العمل الشائعة

# إنشاء JSON فقط: معاينة ما يكتشفه الفاحص
ngrx-graph -d ./src --out ./out
# ينتج: ./out/ngrx-graph.json

# مخطط مركز لحدث معين
ngrx-graph "LoadUsers" -d ./src --out ./out --svg
# ينتج: ./out/LoadUsers.dot + ./out/LoadUsers.svg

# مخطط كامل للمشروع
ngrx-graph -a -d ./src --out ./out --dot --svg
# ينتج: ./out/all.dot + ./out/all.svg

# إجبار إعادة الفحص (تجاهل ذاكرة التخزين المؤقت)
ngrx-graph -d ./src --out ./out -f

الدليل الكامل للوسوم (Flags)

الخيارالمختصرالوصف
--dir-dالدليل المراد فحصه (الافتراضي: مجلد العمل الحالي)
--out-oمجلد المخرجات لملف ngrx-graph.json
--json-jالفحص وكتابة ملف JSON فقط دون إنشاء DOT/SVG
--dotإنشاء ملفات DOT (لكل حدث + المخطط المجمع)
--svg-sإنشاء ملفات SVG من ملفات DOT
--vizتفضيل viz.js (WASM) لإنشاء SVG عند عدم توفر أمر dot
--all-aإنشاء الملف المجمع all.dot فقط
--concurrency-cعدد العمال المتوازيين لتحليل الملفات (الافتراضي: عدد المعالجات - 2)
--force-fإجبار إعادة الفحص وتجاهل JSON المخزن مؤقتاً
--verbose-vتفعيل سجلات التفاصيل الكاملة

نصائح للمشاريع الكبيرة

بالنسبة للمشاريع التي تحتوي على آلاف الملفات، فإن بعض الممارسات تجعل الفارق كبيراً بين فحص يستغرق ثانيتين وأخر يستغرق 20 ثانية:

  • ابدأ بملف JSON: قم بتشغيل ngrx-graph -d ./src --json أولاً لمعاينة المخرجات، وراجع ngrx-graph.json قبل توليد المخططات.
  • الاعتماد على التخزين المؤقت: العمليات اللاحقة تعيد استخدام ملف JSON الحالي. لا تستخدم --force إلا بعد إجراء تعديلات جوهرية على الكود.
  • ضبط التوازي: في بيئات CI ذات المعالجات المحدودة، قم بتخفيض التوازي باستخدام --concurrency 2 لتجنب استهلاك الموارد.
  • استخدام --viz بدون تثبيت Graphviz: يستخدم الخيار --viz محرك viz.js (محرك WASM/JS) بدلاً من أداة dot النظامية.
  • ركز مخططاتك: الخيار --all ينتج مخطط المشروع كاملاً. لتتبع حدث معين، مرر اسم الحدث دائماً للحصول على مخطط فرعي مركز. المخططات الكاملة التي تحتوي على أكثر من 500 عقدة تكون دقيقة تقنياً لكنها غير صالحة للقراءة عملياً.

تحت الغطاء: حل مشكلات AST والمخططات البيانية المعقدة

بناء مخطط بياني موثوق لأحداث NgRx من خلال التحليل الاستاتيكي تطلب حل ثلاث مشكلات هندسية غير تافهة. كل مشكلة منها أفشلت الطرق التقليدية وتطلبت حلاً خوارزمياً مخصصاً.

التحدي 1: التحليل الاستاتيكي لسلاسل RxJS في الـ Effects

تعتبر الـ Effects الجزء الأصعب في الرسم البياني. يظهر الـ Effect النموذج كالتالي:

loadUsers$ = createEffect(() =>
  this.actions$.pipe(
    ofType(loadUsers),
    switchMap(() =>
      this.userService.getAll().pipe(
        map(users => loadUsersSuccess({ users }))
      )
    )
  )
);

يجب على الفاحص تحديد أمرين: الأحداث التي تدخل عبر ofType()، والأحداث التي تخرج عبر dispatch() أو return داخل الدوال المرتدة. الصعوبة تكمن في أن مشغلات RxJS هي دوال مرتدة متداخلة بشكل عميق. الحدث المُطلق داخل map() أو mergeMap() ليس مجاوراً لنداء ofType()، بل يقع في مستويات أعمق من التداخل.

الحل هو التتبع العودي لشجرة AST عبر عقيدات CallExpression. ابتداءً من جسم كل createEffect()، يتعرف الفاحص على استدعاءات pipe()، ويمر على حُجج المشغلات، ويطابق نداءات ofType لاستخراج الأحداث المدخلة:

// مركب تتبع عقيدات AST لمدخلات الـ Effect
if (ts.isCallExpression(node) && node.expression.getText() === "ofType") {
  const inputAction = node.arguments[0]?.getText();
  // تسجيل الحدث المدخل...
}

عندما يواجه المتبع المشغلات map أو mergeMap أو switchMap أو concatMap أو exhaustMap، فإنه ينزل إلى جسم الدالة المرتدة، ويبحث عن استدعاءات dispatch(SomeAction) وتعبيرات return SomeAction لتسجيل كل منها كحدث مخرج.

تتعامل هذه الطريقة مع سلاسل المشغلات المتداخلة بلا حدود: السلسلة pipe(switchMap(() => pipe(map(X), tap(() => dispatch(Y))))) يتم حلها بشكل صحيح لأن المتبع لا يهتم بعمق التداخل بل يتبع السلسلة بنيوياً.

يتعامل الفاحص أيضاً مع النمط الشائع حيث يطلق الـ Effect حدثاً بشكل شرطي. كل استدعاء لـ dispatch() داخل جسم المشغل، بغض النظر عن موقعه في فروع if/else يتم التقاطه كمخرج محتمل. هذا التصميم محافظ بطبعه: إظهار حدث قد يتم إطلاقه أفضل من تفويت حدث يتم إطلاقه بالفعل.

التحدي 2: حل الأسماء المستعارة في ملفات التجميع (Barrel Files)

تنظم مشاريع Angular الأحداث عادة في ملفات مخصصة وتستخرجها عبر ملفات تجميع (index.ts). تظهر المشكلة عندما تستخدم التصديرات أسماء مستعارة:

// actions/user.actions.ts
export const loadUsers = createAction('[Users] Load');
export const loadUsersSuccess = createAction('[Users] Load Success');
// index.ts
export { loadUsers, loadUsersSuccess as usersLoaded } from "./actions/user.actions";
// user.effects.ts
import { usersLoaded } from "../";

يشير الـ Effect إلى usersLoaded بينما الاسم المعياري هو loadUsersSuccess. الفاحص البسيط يتعامل معهما كحدثين غير مرتبطين، مما يؤدي إلى تجزئة المخطط البياني إلى مخططات فرعية منفصلة وتعطيل الخريطة الكاملة.

تحل ngrx-graph هذه المشكلة عبر جدول رموز معياري في الذاكرة يتم بناؤه أثناء مرحلة الفحص:

  1. فحص التصريحات: التمرير الأول يحدد جميع استدعاءات createAction() ويسجل أسماءها المعيارية ومواقع ملفاتها المصدرية.
  2. فحص إعادة التصدير: التمرير الثاني يمر على جميع ملفات index.ts ويحلل أنماط export { X as Y } و export { X }. ولكل إعادة تصدير، يربط الاسم المستعار (Y) بالاسم المعياري الأصلي (X).
  3. الحل: عندما يواجه الفاحص اسم حدث مستورد، فإنه يتحقق من جدول الرموز. إذا كان الاسم مستعاراً، فإنه يرجعه إلى اسمه المعياري قبل تسجيله في المخطط.

يتعامل هذا النهج ذو التمريرات الثلاثة مع إعادة التصدير متعددة المستويات (A يعيد التصدير من B، وB يعيد التصدير من C) وأنماط الأسماء المستعارة المختلطة. يشكل جدول الرموز المصدر الوحيد للحقيقة لهوية الحدث في كامل الكود.

التحدي 3: تصفية الضوضاء وإمكانية الوصول في المخطط البياني

مخطط NgRx كاملاً لمشروع يحتوي على مئات الأحداث ليس مفيداً؛ إذ يجعل عدد العقد تخطيطات DOT غير صالحة للقراءة، ومعظم العقد تكون غير متعلقة بالمسألة التي تفحصها. المشكلة الهندسة تكمن في استخراج مخطط فرعي ذو معنى من مخطط موجه كبير.

[ Disconnected Action ]   [ Angular Component ]
           |                        |
           x               ( Target Action )  <-- BFS Root
                              /        \
                     [ Effect 1 ]    [ Reducer ]
                          |
                  [ Output Action ]

تمثل ngrx-graph تدفق الحالة كمخطط موجه G=(V,E) حيث النقاط هي الـ Components و Actions و Effects و Reducers، والروابط تمثل علاقات التمرير والمعالجة. عندما يطلب المستخدم مخططاً مركزاً لحدث معين A، تقوم الأداة بتشغيل البحث بالأولية العريضة (BFS) بالاتجاهين:

التتبع الأمامي: انطلاقاً من A، يتم تتبع الروابط لاكتشاف جميع الأحداث التي تطلقها التأثيرات التي تستمع إلى A، ثم الأحداث الناتجة عن التأثيرات التي تستمع لـ تلك الأحداث وهكذا. هذا التقاط كامل للسلسلة التالية.

التتبع الخلفي: انطلاقاً من A، يتم تتبع الروابط بشكل عكسي لمعرفة المكونات التي تطلق A والتأثيرات التي تنتج A. هذا التقاط للسياق السابق.

اتحاد التتبعين ينتج أصغر مخطط فرعي يحتوي على A وكل ما يمكن الوصول إليه منه أو الوصول إليه منه. الأحداث والتأثيرات والمخفضات غير الممكن الوصول إليها يتم استبعادها. النتيجة هي مخطط مركز وواضح يظهر التدفق المرتبط بالحدث بشكل دقيق.

يستخدم تنفيذ BFS تمثيل قائمة التجاور لتحقيق تعقيد تتبع بمقدار O(V+E). بالنسبة للمشاريع التي تحتوي على أكثر من 500 حدث و200 Effect، يتكون المخطط المركز عادة من 10 إلى 30 عقدة فقط، مما يجعله مقروءاً فورياً.

التقنيات المستخدمة والقرارات المعمارية

تم اختيار كل مكتبة في ngrx-graph بناءً على قيود محددة:

المكونالخيارالسببالبديل المتروك
تحليل ASTTypeScript Compiler API (ts.createSourceFile)دقة كاملة لملفات TS/TSX. يتعامل مع الأنواع المتقدمة والأنماط التي يزيلها Babel. لا اختلاف في الإعدادات بين المحلل ومشروع tsconfig.Babel: أسرع لكنه يفقد معلومات الأنماط البرمجية. Regex: ينكسر عند الدوال المتداخلة والواردات المستعارة.
تقييد التوازيp-limitيمنع استهلاك المعالج والذاكرة في المشاريع التي تحتوي آلاف الملفات. عدد عمال max(1, CPU_COUNT - 2) يضمن استجابة الجهاز أثناء الفحص.Promise.all العادي: التوازي المفتوح يؤدي لنفاذ الذاكرة (OOM) في المشاريع ذات +10k ملف. التتابع: بطيء جداً للمشاريع الكبيرة.
إطار عمل CLIoclifهيكل احترافي لأدوات سطر الأوامر: تحليل الوسوم، توليد التعليمات، أدوات الاختبار. مجرب بكثافة في Salesforce.Commander/yargs: أخف وزناً لكنه يفتقر لتنسيق التعليمات المدمج وهياكل الاختبار.
رسم المخطط (الرئيسي)Graphviz dot أصليأسرع مسار للرسم. ينتج تخطيطات عالية الجودة جاهزة والنشر. متوفر في معظم بيئات CI.لا يوجد: هذا هو التنفيذ المرجعي.
رسم المخطط (البديل)@hpcc-js/wasm (viz.js)محرك WASM بدون أي اعتماديات خارجية. عند عدم تثبيت dot (شائع في بيئات Docker أو CI) يستخدم الخيار --viz النسخة المترجمة للتمكين المباشر.Puppeteer/Chrome بدون واجهة: ثقيل، بطيء، وغالباً ما ينكسر في بيئات CI.
صيغة المخرجاتGraphviz DOTصيغة معيارية مدعومة من كل أدوات الرسم. ملفات DOT نصية، قابلة للمقارنة، والتحويل لـ SVG يتطلب خطوة واحدة.صيغة JSON مخصصة: تتطلب محرك رسم مخصص وتفقد التوافقية مع الأدوات الأخرى.

استراتيجية الرسم ثنائية الطبقات (dot الأصلي أولاً، ثم WASM كبديل) تعني أن ngrx-graph تعمل في كل مكان دون إجبار المستخدمين على تثبيت حزم نظام إضافية.

أمثلة واقعية والحالات الخاصة

يحتوي المستودع على أربع حالات توضيحية تعكس سيناريوهات متدرجة التعقيد.

الحالة 1: تدفق الأحداث الأساسي

إعداد بسيط: component واحد يطلق action1، و effect يستمع له ويطلق action2 و action3، و reducer يعالج action3.

npx ngrx-graph action1

يعرض المخطط المولّد التدفق الكامل: FirstComponent -> action1 -> effect1$ -> action2, action3 -> firstReducer. ترى بوضوح المكون الذي بدأ السلسلة وأين انتهت. في المشاريع المؤسسية، هذا هو خط الأساس: التحقق من أن تدفق الأحداث لميزة ما مكتمل من البداية إلى النهاية.

أما التركيز على action3 فيكشف عن شريحة أضيق: فقط العقد التي تؤثر على هذا الحدث المحدد أو تتأثر به.

الحالة 2: الأحداث المتداخلة

يمكن للأحداث أن تحمل أحداثاً أخرى داخل بياناتها (payload). تعكس الحالة الثانية nestedAction1 (الذي يحتوي على action1 و action2 كـ payload) و nestedAction2. يميّز المخطط الأحداث المتداخلة بلون سماوي فاتح، مما يجعل نمط التركيب واضحاً بصرياً.

هذا مهم في مشاريع NgRx الكبيرة حيث يُعدّ تركيب الأحداث نمطاً شائعاً لتنسيق سير العمل متعدد الخطوات. دون تمييز بصري، يسهل تفويت الأحداث المتداخلة أثناء مراجعة الكود.

الحالة 3: تصفية الوصول في المخططات المنفصلة

عندما تكون الأحداث غير مرتبطة (action2 موجود لكن لا يمكن الوصول إليه من action1 عبر أي سلسلة تأثيرات)، يقوم المخطط المركز باستبعاده بشكل صحيح. بدون تصفية إمكانية الوصول، ستكون المخططات المركزة أكثر ضجيجاً من نفعها. هذا أمر حاسم في المشاريع المؤسسية حيث تتعايش مئات الأحداث بينما تشارك مجموعة فرعية فقط في أي تدفق معين. تضمن خوارزمية BFS أن المخرج هو المخطط الفرعي الأصغر دون عقد معلقة أو روابط غير ذات صلة. الفرق بين مخطط مركز ومخطط مجمع في مشروع يضم 300 حدث هو الفرق بين خريطة مقروءة وجدار من الحبر.

الحالة 4: الأسماء المستعارة وإعادة التصدير

تُعيد المشاريع الحقيقية تصدير الأحداث عبر ملفات تجميع:

// index.ts
export { actionB, actionA as exportedActionA } from "./case4.actions";

تستورد الـ Effects الاسم المستعار:

import { exportedActionA } from "./index";

تقوم ngrx-graph بإرجاع هذه الأسماء المستعارة إلى أسماء الأحداث الأصلية، حتى يظهر المخطط التدفق المعياري الحقيقي بدلاً من الانكسار عند حدود إعادة التصدير. في المعماريات أحادية المستودع (monorepo) مع مكتبات أحداث مشتركة، تمنع إعادة حل الأسماء المستعارة المخطط من التجزؤ إلى مخططات فرعية منفصلة.

حالة خاصة: الإطلاق الشرطي

تطلق الـ Effects الأحداث أحياناً بشكل شرطي:

loadUsers$ = createEffect(() =>
  this.actions$.pipe(
    ofType(loadUsers),
    switchMap(() =>
      this.userService.getAll().pipe(
        map(users => users.length > 0
          ? loadUsersSuccess({ users })
          : loadUsersEmpty()
        )
      )
    )
  )
);

يظهر كل من loadUsersSuccess و loadUsersEmpty كـ مخرجات. يلتقط الفاحص كل مسار إطلاق محتمل لضمان عدم إسقاط أي حدث من المخطط.

حالة خاصة: إعادة التصدير متعدد المستويات

في المشاريع الكبيرة، قد تمر الأحداث عبر ملفات تجميع متعددة:

core/actions.ts  -->  core/index.ts  -->  feature/index.ts  -->  feature.effects.ts

يقوم جدول الرموز المعياري بحل سلاسل الأسماء المستعارة في أي عمق، ويظهر المخطط دائماً اسم الحدث الأصلي.

لماذا تتفوق أداة CLI على أدوات الفحص في المتصفح

توجد عدة أدوات لتجسيم حالة NgRx، لكنها تخدم احتياجات مختلفة:

  • ngrx-visualizer (من Google): أداة مستندة إلى الويب للاستكشاف التفاعلي. تتطلب بناء المخطط يدوياً في المتصفح. تعمل بشكل جيد للمشاريع الصغيرة، وتعد أقل عملية لخطوط تجميع CI/CD أو المشاريع الكبيرة.
  • ngrx-graph: أداة موجهة لسطر الأوامر مخصصة للأتمتة. تقوم بتحليل الكود المصدري الفعلي عبر AST، وتتعامل مع الأسماء المستعارة والأحداث المتداخلة، وتدعم المخططات المركزة مع تصفية الوصول، وتنتج ملفات DOT/SVG معيارية للدمج في خطوط التوثيق.

الميزة التنافسية الأساسية هي مخططات الأحداث المركزة. معظم أدوات التجسيم تعوض مخطط الحالة كاملاً دفعة واحدة، مما ينتج شبكة عقد غير صالحة للقراءة في المشاريع الكبيرة. بينما تتبع ngrx-graph حدثاً واحداً عبر التدفق الكامل، مظهرة العقد الممكن الوصول إليها فقط، وهو ما يمثل القيمة الحقيقية لتصحيح الأخطاء.

أين تحقق ngrx-graph أعلى قيمة في الإنتاج

مراجعة الكود (Code Review): قم بتوليد مخطط مركز لحدث جديد للتحقق من وجود التدفق الكامل. يظهر معالج الـ Reducer المفقود أو اشتراك الـ Effect غير المكتمل بوضوح بصري فوراً.

تهيئة الموظفين الجدد (Onboarding): يفحص أعضاء الفريق الجدد مخطط المشروع كاملاً لفهم معمارية إدارة الحالة دون الحاجة لقراءة كل ملف. إنه مخطط معمارية حقيقي ومباشر.

تصحيح مشاكل الإنتاج: عند تتبع bug يعود لحالة قديمة أو غير محدثة، قم بتوليد مخطط لسلسلة الأحداث المشتبه بها. ستلاحظ سريعاً ما إذا كان الـ Reducer يفتقد لمعالج أو إذا كان الـ Effect يطلق حدثاً غير متوقع.

إعادة الهيكلة (Refactoring): قبل إعادة تسمية حدث أو إزالته، قم بتوليد مخططه المركز لرؤية كل اعتمادية متعلقة به للوقاية من الانكسارات المفاجئة.

جربها الآن

npx ngrx-graph -d ./src --out ./out --svg

أمر واحد. مخطط واحد. يتوقف مخزن NgRx لديك عن كونه صندوقاً أسود.

الروابط:

المساهمات مرحب بها دائماً. قم بفتح Issue إذا حدث انكسار ما، أو Pull Request إذا أردت إصلاحه.