Panduan ini dan panduan berikut akan memandu Anda melalui proses membuat solusi berdasarkan proyek Discovery.js . Tujuan kami adalah membuat inspektur untuk dependensi NPM, yaitu antarmuka untuk memeriksa struktur node_modules
.

Catatan: Discovery.js berada pada tahap awal pengembangan, jadi seiring waktu, sesuatu akan menyederhanakan dan menjadi lebih bermanfaat. Jika Anda memiliki ide tentang cara meningkatkan sesuatu, tulis kepada kami .
Anotasi
Di bawah ini Anda akan menemukan gambaran umum konsep-konsep kunci dari Discovery.js. Anda dapat mempelajari seluruh kode manual dalam repositori di GitHub , atau Anda dapat mencoba cara kerjanya secara online .
Kondisi awal
Pertama-tama, kita perlu memilih proyek untuk dianalisis. Ini bisa berupa proyek yang baru dibuat atau yang sudah ada, yang utamanya adalah berisi node_modules
(objek analisis kami).
Pertama, instal paket inti discoveryjs
dan alat konsolnya:
npm install @discoveryjs/discovery @discoveryjs/cli
Selanjutnya, luncurkan server 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
Jika Anda membuka http://localhost:8123
di browser, Anda dapat melihat yang berikut:

Ini adalah mode tanpa model, yaitu mode ketika tidak ada yang dikonfigurasi. Tetapi sekarang, dengan menggunakan tombol "Muat data", Anda dapat memilih file JSON apa saja, atau cukup seret ke halaman dan mulai analisis.
Namun, kami membutuhkan sesuatu yang spesifik. Secara khusus, kita perlu melihat struktur node_modules
. Untuk melakukan ini, tambahkan konfigurasi.
Tambahkan konfigurasi
Seperti yang mungkin Anda perhatikan, pesan No config is used
ditampilkan ketika server mulai. Mari kita membuat file konfigurasi .discoveryrc.js
dengan konten berikut:
module.exports = { name: 'Node modules structure', data() { return { hello: 'world' }; } };
Catatan: jika Anda membuat file di direktori kerja saat ini (yaitu, di root proyek), maka tidak ada lagi yang diperlukan. Jika tidak, Anda perlu meneruskan path ke file konfigurasi menggunakan opsi --config
, atau mengatur path di package.json
:
{ ... "discovery": "path/to/discovery/config.js", ... }
Mulai ulang server sehingga konfigurasi diterapkan:
> 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
Seperti yang Anda lihat, sekarang file yang kami buat digunakan. Dan model default yang dijelaskan oleh kami diterapkan (Discovery dapat bekerja dalam mode banyak model, kami akan berbicara tentang fitur ini dalam manual berikut). Mari kita lihat apa yang telah berubah di browser:

Apa yang bisa dilihat di sini:
name
digunakan sebagai judul halaman;- hasil pemanggilan metode
data
ditampilkan sebagai konten utama halaman.
Catatan: metode data
harus mengembalikan data atau Janji, yang memutuskan untuk data.
Pengaturan dasar dibuat, Anda dapat melanjutkan.
Konteks
Mari kita lihat halaman laporan khusus (klik Make report
):

Pada pandangan pertama, ini tidak jauh berbeda dari halaman awal ... Tetapi di sini Anda dapat mengubah segalanya! Misalnya, kami dapat dengan mudah membuat ulang tampilan halaman awal:

Perhatikan bagaimana tajuk didefinisikan: "h1:#.name"
. Ini adalah tajuk tingkat pertama dengan isi #.name
, yang merupakan permintaan Jora . #
mengacu pada konteks permintaan. Untuk melihat isinya, cukup masukkan #
di editor kueri dan gunakan tampilan default:

Sekarang Anda tahu cara mendapatkan ID halaman saat ini, parameternya, dan nilai berguna lainnya.
Pengumpulan data
Sekarang kami menggunakan rintisan dalam proyek bukan data nyata, tetapi kami membutuhkan data nyata. Untuk melakukan ini, buat modul dan ubah nilai data
dalam konfigurasi (omong-omong, setelah perubahan ini tidak perlu me-restart server):
module.exports = { name: 'Node modules structure', data: require('./collect-node-modules-data') };
Isi dari 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); };
Saya menggunakan paket @discoveryjs/scan-fs
, yang menyederhanakan pemindaian sistem file. Contoh menggunakan paket dijelaskan dalam readme-nya, saya mengambil contoh ini sebagai dasar dan diselesaikan sesuai kebutuhan. Sekarang kami memiliki beberapa informasi tentang isi node_modules
:

Apa yang kamu butuhkan! Dan terlepas dari kenyataan bahwa ini adalah JSON biasa, kita sudah dapat menganalisisnya dan menarik beberapa kesimpulan. Misalnya, menggunakan popup struktur data, Anda dapat mengetahui jumlah paket dan mencari tahu berapa banyak dari mereka yang memiliki lebih dari satu contoh fisik (karena perbedaan dalam versi atau masalah dengan deduplikasi mereka).

Terlepas dari kenyataan bahwa kami sudah memiliki beberapa data, kami membutuhkan lebih banyak detail. Sebagai contoh, akan menyenangkan untuk mengetahui instance fisik mana yang menyelesaikan setiap dependensi yang dideklarasikan dari modul tertentu. Namun, upaya meningkatkan ekstraksi data berada di luar cakupan panduan ini. Oleh karena itu, kami akan menggantinya dengan paket @discoveryjs/node-modules
(yang juga didasarkan pada @discoveryjs/scan-fs
) untuk mengambil data dan mendapatkan detail yang diperlukan tentang paket. Akibatnya, collect-node-modules-data.js
sangat disederhanakan:
const fetchNodeModules = require('@discoveryjs/node-modules'); module.exports = function() { return fetchNodeModules(); };
Sekarang informasi tentang node_modules
terlihat seperti ini:

Script persiapan
Seperti yang mungkin Anda perhatikan, beberapa objek yang menggambarkan paket berisi deps
- daftar dependensi. Setiap ketergantungan memiliki bidang yang resolved
yang nilainya merupakan referensi ke instance fisik paket. Tautan semacam itu adalah nilai path
salah satu paket, unik. Untuk menyelesaikan tautan ke paket, Anda perlu menggunakan kode tambahan (misalnya, #.data.pick(<path=resolved>)
). Dan tentu saja, akan jauh lebih nyaman jika tautan seperti itu sudah dipecahkan menjadi referensi objek.
Sayangnya, pada tahap pengumpulan data, kami tidak dapat menyelesaikan tautan, karena ini akan menyebabkan koneksi sirkuler, yang akan menciptakan masalah mentransfer data tersebut dalam bentuk JSON. Namun, ada solusinya: ini naskah prepare
khusus. Ini didefinisikan dalam konfigurasi dan dipanggil setiap kali data baru ditugaskan ke instance Discovery. Mari kita mulai dengan konfigurasi:
module.exports = { ... prepare: __dirname + '/prepare.js',
Tentukan prepare.js
:
discovery.setPrepare(function(data) {
Dalam modul ini, kami mendefinisikan fungsi prepare
untuk instance Discovery. Fungsi ini dipanggil setiap kali sebelum menerapkan data ke instance Discovery. Ini adalah tempat yang baik untuk memungkinkan nilai dalam referensi objek:
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) ) ); });
Di sini kami telah membuat indeks paket di mana kuncinya adalah nilai path
paket (unik). Kemudian kita pergi melalui semua paket dan dependensinya, dan dalam dependensi kita mengganti nilai yang resolved
dengan referensi ke objek paket. Hasil:

Sekarang jauh lebih mudah untuk membuat query grafik dependensi. Ini adalah bagaimana Anda bisa mendapatkan sekelompok dependensi (mis. Dependensi, dependensi dependensi, dll.) Untuk paket tertentu:

Sebuah kisah sukses yang tak terduga: ketika mempelajari data selama penulisan manual, saya menemukan masalah di @discoveryjs/cli
(menggunakan kueri .[deps.[not resolved]]
), yang memiliki kesalahan ketik pada peerDependencies. Masalahnya segera diperbaiki . Kasing merupakan contoh yang baik tentang bagaimana alat tersebut membantu.
Mungkin sudah waktunya untuk menunjukkan pada halaman awal beberapa nomor dan paket dengan memakan waktu.
Kustomisasi Halaman Awal
Pertama, kita perlu membuat modul halaman, misalnya, pages/default.js
. Kami menggunakan default
, karena ini adalah pengidentifikasi untuk halaman awal, yang dapat kami timpa (dalam Discovery.js, Anda dapat menimpa banyak). Mari kita mulai dengan sesuatu yang sederhana, misalnya:
discovery.page.define('default', [ 'h1:#.name', 'text:"Hello world!"' ]);
Sekarang dalam konfigurasi Anda perlu menghubungkan modul halaman:
module.exports = { name: 'Node modules structure', data: require('./collect-node-modules-data'), view: { assets: [ 'pages/default.js'
Periksa di browser:

Itu berhasil!
Sekarang mari kita cari beberapa counter. Untuk melakukan ini, buat perubahan pada 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() } ]` } ]);
Di sini kita mendefinisikan daftar indikator inline. Nilai data
adalah kueri Jora yang membuat array rekaman. Daftar paket (akar data) digunakan sebagai dasar untuk kueri, jadi kami mendapatkan panjang daftar ( size()
), jumlah nama paket unik ( name.size()
) dan jumlah nama paket yang memiliki duplikat ( group(<name>).[value.size() > 1].size()
).

Tidak buruk. Namun demikian, akan lebih baik untuk memiliki, selain angka, tautan ke sampel yang sesuai:
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 }) }` } ]);
Pertama-tama, kami mengubah nilai data
, sekarang ini adalah array reguler dengan beberapa objek. Selain itu, metode size()
telah dihapus dari permintaan nilai.
Selain itu, subquery telah ditambahkan ke tampilan indicator
. Jenis kueri ini membuat objek baru untuk setiap elemen di mana value
dan href
dihitung. Untuk value
, kueri dieksekusi menggunakan metode query()
, yang datanya ditransfer dari konteks, dan kemudian metode size()
diterapkan pada hasil kueri. Untuk href
, metode pageLink()
digunakan, yang menghasilkan tautan ke halaman laporan dengan permintaan dan tajuk tertentu. Setelah semua perubahan ini, indikator menjadi dapat diklik (perhatikan bahwa nilainya telah menjadi biru) dan lebih fungsional.

Untuk membuat halaman awal lebih bermanfaat, tambahkan tabel dengan paket yang memiliki duplikat.
discovery.page.define('default', [
Tabel ini menggunakan data yang sama dengan indikator Dup packages
. Daftar paket diurutkan berdasarkan ukuran grup dalam urutan terbalik. Sisa pengaturan terkait dengan kolom (omong-omong, biasanya mereka tidak perlu disetel). Untuk kolom Version & Location
, kami mendefinisikan daftar bersarang (diurutkan berdasarkan versi), di mana setiap elemen adalah pasangan nomor versi dan jalur ke instance.

Halaman Paket
Sekarang kami hanya memiliki gambaran umum tentang paket. Tetapi akan bermanfaat untuk memiliki halaman dengan detail tentang paket tertentu. Untuk melakukan ini, buat pages/package.js
modul baru pages/package.js
dan tentukan halaman baru:
discovery.page.define('package', { view: 'context', data: `{ name: #.id, instances: .[name = #.id] }`, content: [ 'h1:name', 'table:instances' ] });
Dalam modul ini, kami mendefinisikan halaman dengan package
pengenal. Komponen context
digunakan sebagai representasi awal. Ini adalah komponen non-visual yang membantu Anda menentukan data untuk pemetaan bersarang. Perhatikan bahwa kami menggunakan #.id
untuk mendapatkan nama paket, yang diambil dari URL seperti ini http://localhost:8123/#package:{id}
.
Jangan lupa untuk memasukkan modul baru dalam konfigurasi:
module.exports = { ... view: { assets: [ 'pages/default.js', 'pages/package.js'
Hasil dalam browser:

Tidak terlalu mengesankan, tetapi untuk saat ini. Kami akan membuat pemetaan yang lebih kompleks di manual selanjutnya.
Panel samping
Karena kita sudah memiliki halaman paket, alangkah baiknya memiliki daftar semua paket. Untuk melakukan ini, Anda dapat menentukan tampilan khusus - sidebar
, yang ditampilkan jika sudah ditentukan (tidak ditentukan secara default). Buat views/sidebar.js
modul baru views/sidebar.js
:
discovery.view.define('sidebar', { view: 'list', data: 'name.sort()', item: 'link:{ text: $, href: pageLink("package") }' });
Sekarang kami memiliki daftar semua paket:

Itu terlihat bagus. Tetapi dengan filter itu akan lebih baik. Kami memperluas definisi 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' } } });
Di sini kami membungkus daftar dalam komponen content-filter
yang mengubah nilai input dalam bidang input ke ekspresi reguler (atau null
jika bidang kosong) dan menyimpannya sebagai nilai filter
dalam konteks (nama dapat diubah dengan opsi name
). Juga, untuk memfilter data untuk daftar, kami menggunakan #.filter
. Akhirnya, kami menerapkan pemetaan tautan untuk menyorot bagian yang cocok dengan text-match
. Hasil:

Jika Anda tidak menyukai desain default, Anda dapat menyesuaikan gaya sesuai keinginan. Katakanlah Anda ingin mengubah lebar bilah sisi, untuk ini Anda perlu membuat file gaya (katakanlah, views/sidebar.css
):
.discovery-sidebar { width: 300px; }
Dan tambahkan tautan ke file ini dalam konfigurasi, serta ke modul JavaScript:
module.exports = { ... view: { assets: [ ... 'views/sidebar.css',
Tautan Otomatis
Bab terakhir dari panduan ini dikhususkan untuk tautan. Sebelumnya, menggunakan metode pageLink()
, kami membuat tautan ke halaman paket. Namun selain tautan, Anda juga harus mengatur teks tautan. Tetapi bagaimana kita membuatnya lebih mudah?
Untuk menyederhanakan pekerjaan tautan, kita perlu mendefinisikan aturan untuk menghasilkan tautan. Ini paling baik dilakukan dalam skrip prepare
:
discovery.setPrepare(function(data) { ... const packageIndex = data.reduce( (map, item) => map .set(item, item)
Kami menambahkan peta baru (indeks) paket dan menggunakannya untuk resolver entitas. Penyelesai entitas mencoba, jika mungkin, untuk mengubah nilai yang diteruskan ke entitas descriptor. Deskriptor berisi:
type
- tipe entitasid
- referensi unik ke instance entitas yang digunakan dalam tautan sebagai IDname
- digunakan sebagai teks tautan
Terakhir, Anda perlu menetapkan jenis ini ke halaman tertentu (tautannya mengarah ke suatu tempat, bukan?).
discovery.page.define('package', { ... }, { resolveLink: 'package'
Konsekuensi pertama dari perubahan ini adalah bahwa beberapa nilai dalam tampilan struct
sekarang ditandai dengan tautan ke halaman paket:

Dan sekarang Anda juga dapat menerapkan komponen auto-link
ke objek atau nama paket:

Dan, sebagai contoh, Anda dapat sedikit mengolah ulang bilah samping:
Kesimpulan
Anda sekarang memiliki pemahaman dasar tentang konsep-konsep kunci dari Discovery.js . Dalam panduan berikut kami akan melihat lebih dekat topik yang dibahas.
Anda dapat melihat seluruh kode sumber panduan dalam repositori di GitHub atau mencoba cara kerjanya secara online .
Ikuti @js_discovery di Twitter untuk mengikuti berita terbaru!