Discovery.js Guide: البداية السريعة

سيرشدك هذا الدليل الإرشادي التالي إلى عملية إنشاء حل يستند إلى مشروع Discovery.js . هدفنا هو إنشاء مفتش node_modules NPM ، أي واجهة لفحص بنية node_modules .



ملاحظة: لا يزال Discovery.js في مرحلة مبكرة من التطوير ، لذا بمرور الوقت ، سوف يسهل شيء ما ويصبح أكثر فائدة. إذا كانت لديك أفكار حول كيفية تحسين شيء ما ، فاكتب لنا .

ملخص


ستجد أدناه نظرة عامة على المفاهيم الأساسية لـ Discovery.js. يمكنك معرفة الكود اليدوي بالكامل في مستودع التخزين على GitHub ، أو يمكنك تجربة كيفية عمله على الإنترنت .


الظروف الأولية


بادئ ذي بدء ، نحن بحاجة إلى اختيار مشروع للتحليل. يمكن أن يكون هذا مشروعًا تم إنشاؤه حديثًا أو مشروعًا قائمًا ، والشيء الرئيسي هو أنه يحتوي على node_modules (هدف تحليلنا).


أولاً ، قم بتثبيت حزمة discoveryjs الأساسية وأدوات وحدة التحكم الخاصة بها:


 npm install @discoveryjs/discovery @discoveryjs/cli 

بعد ذلك ، قم بتشغيل خادم Discovery.js:


 > npx discovery No config is used Models are not defined (model free mode is enabled) Init common routes ... OK Server listen on http://localhost:8123 

إذا قمت بفتح http://localhost:8123 في المستعرض ، يمكنك رؤية ما يلي:


اكتشاف دون التكوين


هذا وضع بدون نموذج ، أي وضع عند عدم تكوين أي شيء. لكن الآن ، باستخدام زر "تحميل البيانات" ، يمكنك تحديد أي ملف JSON ، أو ببساطة اسحبه إلى الصفحة وبدء التحليل.


ومع ذلك ، نحن بحاجة إلى شيء محدد. على وجه الخصوص ، نحتاج إلى الحصول على عرض لبنية node_modules . للقيام بذلك ، أضف التكوين.


إضافة التكوين


كما قد تلاحظ ، No config is used عرض الرسالة " No config is used عند بدء تشغيل الخادم. لنقم بإنشاء ملف تكوين .discoveryrc.js بالمحتويات التالية:


 module.exports = { name: 'Node modules structure', data() { return { hello: 'world' }; } }; 

ملاحظة: إذا قمت بإنشاء ملف في دليل العمل الحالي (أي ، في جذر المشروع) ، فلن يكون هناك شيء آخر مطلوب. وإلا ، فأنت بحاجة إلى تمرير المسار إلى ملف التكوين باستخدام الخيار --config ، أو تعيين المسار في package.json :


 { ... "discovery": "path/to/discovery/config.js", ... } 

أعد تشغيل الخادم بحيث يتم تطبيق التكوين:


 > npx discovery Load config from .discoveryrc.js Init single model default Define default routes ... OK Cache: DISABLED Init common routes ... OK Server listen on http://localhost:8123 

كما ترون ، يتم الآن استخدام الملف الذي أنشأناه. يتم تطبيق النموذج الافتراضي الموضح من قبلنا (يمكن أن يعمل Discovery في وضع العديد من الطرز ، وسوف نتحدث عن هذه الميزة في الأدلة التالية). دعونا نرى ما الذي تغير في المتصفح:


مع التكوين الأساسي


ما يمكن رؤيته هنا:


  • يستخدم name كعنوان للصفحة ؛
  • يتم عرض نتيجة استدعاء طريقة data باعتبارها المحتوى الرئيسي للصفحة.

ملاحظة: يجب أن تقوم طريقة data بإرجاع البيانات أو الوعد ، الذي يحل للبيانات.

مصنوعة الإعدادات الأساسية ، يمكنك المضي قدما.


السياق


دعونا نلقي نظرة على صفحة التقرير المخصص (انقر فوق Make report ):


تقرير الصفحة


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


إعادة صفحة البداية


لاحظ كيف يتم تعريف الرأس: "h1:#.name" . هذا هو رأس المستوى الأول مع محتويات #.name ، وهو طلب Jora . يشير # إلى سياق الطلب. لعرض محتوياته ، ببساطة أدخل # في محرر الاستعلام واستخدم العرض الافتراضي:


قيم السياق


أنت الآن تعرف كيفية الحصول على معرّف الصفحة الحالية ومعلماتها والقيم المفيدة الأخرى.


جمع البيانات


الآن نستخدم كعب روتين في المشروع بدلاً من بيانات حقيقية ، لكننا نحتاج إلى بيانات حقيقية. للقيام بذلك ، قم بإنشاء وحدة نمطية وتغيير قيمة data في التكوين (بالمناسبة ، بعد هذه التغييرات ، لا يلزم إعادة تشغيل الخادم):


 module.exports = { name: 'Node modules structure', data: require('./collect-node-modules-data') }; 

محتويات collect-node-modules-data.js :


 const path = require('path'); const scanFs = require('@discoveryjs/scan-fs'); module.exports = function() { const packages = []; return scanFs({ include: ['node_modules'], rules: [{ test: /\/package.json$/, extract: (file, content) => { const pkg = JSON.parse(content); if (pkg.name && pkg.version) { packages.push({ name: pkg.name, version: pkg.version, path: path.dirname(file.filename), dependencies: pkg.dependencies }); } } }] }).then(() => packages); }; 

لقد استخدمت حزمة @discoveryjs/scan-fs ، الأمر الذي يبسط عملية مسح نظام الملفات. تم توضيح مثال لاستخدام الحزمة في الملف التمهيدي لها ، وقد أخذت هذا المثال كأساس وتم الانتهاء منه حسب الحاجة. الآن لدينا بعض المعلومات حول محتويات node_modules :


البيانات التي تم جمعها


ما تحتاجه! وعلى الرغم من حقيقة أن هذا JSON عادي ، يمكننا بالفعل تحليله واستخلاص بعض الاستنتاجات. على سبيل المثال ، باستخدام الإطار المنبثق لهيكل البيانات ، يمكنك معرفة عدد الحزم ومعرفة عدد منها يحتوي على أكثر من مثيل فعلي واحد (بسبب اختلافات الإصدار أو مشاكل في إلغاء البيانات المكررة الخاصة بهم).


بحوث هيكل البيانات


على الرغم من حقيقة أن لدينا بالفعل بعض البيانات ، نحن بحاجة إلى مزيد من التفاصيل. على سبيل المثال ، سيكون من الجيد معرفة الحالة المادية التي تحل كل من التبعيات المعلنة لوحدة معينة. ومع ذلك ، فإن العمل على تحسين استخراج البيانات يتجاوز نطاق هذا الدليل. لذلك ، @discoveryjs/node-modules بحزمة @discoveryjs/node-modules (والتي تستند أيضًا إلى @discoveryjs/scan-fs ) لاسترداد البيانات والحصول على التفاصيل اللازمة حول الحزم. نتيجة لذلك ، collect-node-modules-data.js تبسيط عملية collect-node-modules-data.js بشكل كبير:


 const fetchNodeModules = require('@discoveryjs/node-modules'); module.exports = function() { return fetchNodeModules(); }; 

الآن المعلومات حول node_modules تبدو كما يلي:


بنية بيانات جديدة


النصي التحضير


كما قد تلاحظ ، تحتوي بعض الكائنات التي تصف الحزم على وحدات تبعية - قائمة deps . كل تبعية لها مجال تم resolved وقيمته هي الإشارة إلى مثيل مادي للحزمة. مثل هذا الرابط هو قيمة path إحدى الحزم ، إنه فريد من نوعه. لحل ارتباط الحزمة ، تحتاج إلى استخدام رمز إضافي (على سبيل المثال ، #.data.pick(<path=resolved>) ). وبالطبع ، سيكون أكثر ملاءمة إذا تم حل هذه الروابط بالفعل في مراجع كائن.


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


 module.exports = { ... prepare: __dirname + '/prepare.js', // :   ,    ... }; 

تحديد prepare.js :


 discovery.setPrepare(function(data) { //  -  data /   discovery }); 

في هذه الوحدة التعليمية ، حددنا وظيفة prepare لمثيل الاستكشاف. يتم استدعاء هذه الوظيفة في كل مرة قبل تطبيق البيانات على مثيل الاستكشاف. هذا مكان جيد للسماح بالقيم في مراجع الكائنات:


 discovery.setPrepare(function(data) { const packageIndex = data.reduce((map, pkg) => map.set(pkg.path, pkg), new Map()); data.forEach(pkg => pkg.deps.forEach(dep => dep.resolved = packageIndex.get(dep.resolved) ) ); }); 

هنا أنشأنا فهرس حزمة حيث المفتاح هو قيمة path الحزمة (فريدة من نوعها). بعد ذلك ، نتصفح جميع الحزم وتبعياتها ، وفي التبعيات نستبدل القيمة التي تم resolved بالإشارة إلى كائن الحزمة. النتيجة:


تحويل deps.resved


الآن أصبح من الأسهل بكثير إجراء استعلامات الرسم البياني التبعية. هذه هي الطريقة التي يمكنك بها الحصول على مجموعة من التبعيات (مثل التبعيات ، تبعيات التبعية ، وما إلى ذلك) لحزمة معينة:


مثال على مجموعة التبعية


قصة نجاح غير متوقعة: أثناء دراسة البيانات أثناء كتابة الدليل ، اكتشفت مشكلة في @discoveryjs/cli (باستخدام الاستعلام .[deps.[not resolved]] ) ، والتي كان لها خطأ مطبعي في peerDependencies. تم إصلاح المشكلة على الفور. الحالة مثال جيد على كيفية مساعدة هذه الأدوات.

ربما حان الوقت لإظهار على صفحة البداية العديد من الأرقام والحزم مع يأخذ.


تخصيص صفحة البداية


أولاً ، نحتاج إلى إنشاء وحدة نمطية للصفحة ، على سبيل المثال ، pages/default.js . نحن نستخدم الوضع default ، لأن هذا هو المعرف لصفحة البداية ، والتي يمكننا تجاوزها (في Discovery.js ، يمكنك تجاوز الكثير). لنبدأ بشيء بسيط ، على سبيل المثال:


 discovery.page.define('default', [ 'h1:#.name', 'text:"Hello world!"' ]); 

الآن في التكوين تحتاج إلى توصيل وحدة الصفحة:


 module.exports = { name: 'Node modules structure', data: require('./collect-node-modules-data'), view: { assets: [ 'pages/default.js' //     ] } }; 

تحقق في المتصفح:


تجاوز صفحة البداية


إنه يعمل!


الآن دعونا نحصل على بعض العدادات. للقيام بذلك ، قم بإجراء تغييرات على pages/default.js :


 discovery.page.define('default', [ 'h1:#.name', { view: 'inline-list', item: 'indicator', data: `[ { label: 'Package entries', value: size() }, { label: 'Unique packages', value: name.size() }, { label: 'Dup packages', value: group(<name>).[value.size() > 1].size() } ]` } ]); 

هنا نحدد قائمة مضمنة من المؤشرات. قيمة data هي استعلام Jora الذي ينشئ مجموعة من السجلات. يتم استخدام قائمة الحزم (جذر البيانات) كأساس للاستعلامات ، لذلك نحصل على طول القائمة ( size() ) ، وعدد أسماء الحزمة الفريدة ( name.size() ) وعدد أسماء الحزم التي لها مكررة ( group(<name>).[value.size() > 1].size() ).


إضافة مؤشرات إلى صفحة البداية


ليس سيئا ومع ذلك ، سيكون من الأفضل أن يكون لديك ، بالإضافة إلى الأرقام ، روابط للعينات المقابلة:


 discovery.page.define('default', [ 'h1:#.name', { view: 'inline-list', data: [ { label: 'Package entries', value: '' }, { label: 'Unique packages', value: 'name' }, { label: 'Dup packages', value: 'group(<name>).[value.size() > 1]' } ], item: `indicator:{ label, value: value.query(#.data, #).size(), href: pageLink('report', { query: value, title: label }) }` } ]); 

بادئ ذي بدء ، قمنا بتغيير قيمة data ، والآن هي مجموعة منتظمة مع بعض الكائنات. أيضًا ، تمت إزالة طريقة size() من طلبات القيمة.


بالإضافة إلى ذلك ، تمت إضافة استعلام فرعي إلى عرض indicator . تنشئ هذه الأنواع من الاستعلامات كائنًا جديدًا لكل عنصر يتم فيه احتساب value و href . بالنسبة value ، يتم تنفيذ استعلام باستخدام طريقة query() ، والتي يتم نقل البيانات إليها من السياق ، ثم يتم تطبيق طريقة size() على نتيجة الاستعلام. بالنسبة إلى href ، يتم استخدام طريقة pageLink() ، والتي تنشئ رابطًا إلى صفحة التقرير مع طلب محدد ورأس. بعد كل هذه التغييرات ، أصبحت المؤشرات قابلة للنقر (لاحظ أن قيمها أصبحت زرقاء) وأكثر وظيفية.


مؤشرات قابلة للنقر


لجعل صفحة البداية أكثر فائدة ، أضف جدولًا به حزم تحتوي على نسخ مكررة.


 discovery.page.define('default', [ // ...      'h2:"Packages with more than one physical instance"', { view: 'table', data: ` group(<name>) .[value.size() > 1] .sort(<value.size()>) .reverse() `, cols: [ { header: 'Name', content: 'text:key' }, { header: 'Version & Location', content: { view: 'list', data: 'value.sort(<version>)', item: [ 'badge:version', 'text:path' ] } } ] } ]); 

يستخدم الجدول نفس البيانات مثل مؤشر Dup packages . تم فرز قائمة الحزم حسب حجم المجموعة بترتيب عكسي. ترتبط بقية الإعدادات بالأعمدة (بالمناسبة ، عادة لا تحتاج إلى تكوين). بالنسبة لعمود Version & Location ، حددنا قائمة متداخلة (مرتبة حسب الإصدار) ، يكون فيها كل عنصر زوجًا من رقم الإصدار والمسار إلى المثيل.


جدول مضاف مع حزم تحتوي على أكثر من مثيل فعلي واحد


حزمة الصفحة


الآن لدينا فقط لمحة عامة عن الحزم. لكن سيكون من المفيد الحصول على صفحة تحتوي على تفاصيل حول حزمة معينة. للقيام بذلك ، قم بإنشاء pages/package.js وحدة نمطية جديدة pages/package.js وتحديد صفحة جديدة:


 discovery.page.define('package', { view: 'context', data: `{ name: #.id, instances: .[name = #.id] }`, content: [ 'h1:name', 'table:instances' ] }); 

في هذه الوحدة ، قمنا بتعريف الصفحة package المعرف. تم استخدام مكون context كتمثيل أولي. هذا مكون غير مرئي يساعدك على تحديد بيانات التعيينات المتداخلة. لاحظ أننا استخدمنا #.id للحصول على اسم الحزمة ، والتي يتم استردادها من عنوان URL مثل http://localhost:8123/#package:{id} .


لا تنسَ تضمين الوحدة النمطية الجديدة في التكوين:


 module.exports = { ... view: { assets: [ 'pages/default.js', 'pages/package.js' //   ] } }; 

النتيجة في المتصفح:


حزمة صفحة مثال


ليست مؤثرة جدا ، ولكن الآن. سننشئ تعيينات أكثر تعقيدًا في أدلة لاحقة.


لوحة جانبية


نظرًا لأن لدينا بالفعل صفحة حزمة ، سيكون من الجيد الحصول على قائمة بجميع الحزم. للقيام بذلك ، يمكنك تحديد عرض خاص - sidebar ، والذي يتم عرضه إذا تم تعريفه (غير معرف بشكل افتراضي). قم بإنشاء views/sidebar.js وحدة نمطية جديدة views/sidebar.js :


 discovery.view.define('sidebar', { view: 'list', data: 'name.sort()', item: 'link:{ text: $, href: pageLink("package") }' }); 

الآن لدينا قائمة بجميع الحزم:


الشريط الجانبي


انها تبدو جيدة. ولكن مع مرشح سيكون أفضل. نحن نوسع تعريف sidebar :


 discovery.view.define('sidebar', { view: 'content-filter', content: { view: 'list', data: 'name.[no #.filter or $~=#.filter].sort()', item: { view: 'link', data: '{ text: $, href: pageLink("package"), match: #.filter }', content: 'text-match' } } }); 

قمنا هنا بلف القائمة في مكون content-filter الذي يحول قيمة الإدخال في حقل الإدخال إلى تعبيرات عادية (أو null إذا كان الحقل فارغًا) وحفظه كقيمة filter في السياق (يمكن تغيير name خيار name ). أيضًا ، لتصفية البيانات للقائمة ، استخدمنا #.filter . أخيرًا ، طبقنا تعيين الارتباط لتمييز الأجزاء المتطابقة مع مطابقة text-match . النتيجة:


قائمة التصفية


إذا كنت لا تحب التصميم الافتراضي ، يمكنك تخصيص الأنماط كما تشاء. لنفترض أنك تريد تغيير عرض الشريط الجانبي ، لذلك تحتاج إلى إنشاء ملف نمط (على سبيل المثال ، views/sidebar.css ):


 .discovery-sidebar { width: 300px; } 

وإضافة رابط إلى هذا الملف في التكوين ، وكذلك إلى وحدات جافا سكريبت:


 module.exports = { ... view: { assets: [ ... 'views/sidebar.css', //  assets    *.css  'views/sidebar.js' ] } }; 

الرابط الآلي


الفصل الأخير من هذا الدليل مخصص للروابط. في وقت سابق ، باستخدام طريقة pageLink() ، أنشأنا رابطًا إلى صفحة الحزمة. ولكن بالإضافة إلى الرابط ، يجب عليك أيضًا تعيين نص الارتباط. ولكن كيف يمكننا أن نجعل الأمر أسهل؟


لتبسيط عمل الروابط ، نحتاج إلى تحديد قاعدة لإنشاء الروابط. من الأفضل القيام بذلك في البرنامج النصي prepare :


 discovery.setPrepare(function(data) { ... const packageIndex = data.reduce( (map, item) => map .set(item, item) // key is item itself .set(item.name, item), // and `name` value new Map() ); discovery.addEntityResolver(value => { value = packageIndex.get(value) || packageIndex.get(value.name); if (value) { return { type: 'package', id: value.name, name: value.name }; } }); }); 

أضفنا خريطة جديدة (فهرس) للحزم واستخدمناها في أداة تحليل الكيانات. يحاول محلل الكيان ، إن أمكن ، تحويل القيمة التي تم تمريرها إليها إلى واصف كيان. يحتوي واصف:


  • type - نوع الكيان
  • id - هو مرجع فريد لمثيل الكيان المستخدم في الارتباطات كمعرّف
  • name - يستخدم كنص ارتباط

أخيرًا ، تحتاج إلى تعيين هذا النوع إلى صفحة معينة (يجب أن يؤدي الارتباط إلى مكان ما ، أليس كذلك؟).


 discovery.page.define('package', { ... }, { resolveLink: 'package' //    `package`    }); 

والنتيجة الأولى لهذه التغييرات هي أن بعض القيم في عرض struct يتم تمييزها الآن برابط إلى صفحة الحزمة:


روابط السيارات في الهيكل


والآن يمكنك أيضًا تطبيق مكون auto-link على كائن أو اسم الحزمة:


باستخدام الرابط التلقائي


وكمثال ، يمكنك إعادة صياغة الشريط الجانبي قليلاً:


  //   item: { view: 'link', data: '{ text: $, href: pageLink("package"), match: #.filter }', content: 'text-match' }, //   `auto-link` item: { view: 'auto-link', content: 'text-match:{ text, match: #.filter }' } 

استنتاج


لديك الآن فهم أساسي للمفاهيم الأساسية لـ Discovery.js . في الأدلة التالية سوف نلقي نظرة فاحصة على الموضوعات التي تمت تغطيتها.


يمكنك الاطلاع على شفرة المصدر الكاملة للدليل في مستودع التخزين على GitHub أو تجربة كيفية عمله على الإنترنت .


اتبع @ js_discovery على Twitter لمواكبة آخر الأخبار!

Source: https://habr.com/ru/post/ar469115/


All Articles