Rehberler

DevTools sözleşmesi

useDevelopment(true) ile açılan __MOTIF_DEVTOOLS__ nesnesi; yollar, olaylar, ağaç, bileşen içeriği, reaktivite izi, sayaçlar, router ve hata kayıtları, yazma işlemleri.

Çekirdek bir DevTools arayüzü içermez; arayüzün okuyup yazabileceği tek bir nesne sunar. Bu sayfa o nesneyi anlatır. DevTools arayüzü yazanlar içindir. Paketinin kendi durumunu DevTools'a açmak isteyen paket yazarı Paketler için DevTools sayfasını okur; bu sayfa onun üstünde durduğu zemindir.

Açma

DevTools yalnız app.useDevelopment(true) ile açılır. Bu çağrı geliştirme modunu da açar. useDevelopment(false) ikisini birden kapatır. Adres çubuğundaki bir parametre ya da elle yazılan bir global DevTools'u açmaz.

Açıkken globalThis.__MOTIF_DEVTOOLS__ sözleşme nesnesini tutar; kapanınca nesne kaldırılır. Kapalıyken çekirdek hiçbir şey kaydetmez ve hiçbir olay üretmez. Sayaçlar ve kayıtlar DevTools'un açıldığı andan itibaren tutulur.

Modül düzeyindeki store'lar (export const cart = reactive({ … })) import sırasında, yani useDevelopment(true) çağrısından önce oluşur. @motifx/compiler Vite eklentisiyle geliştirirken, değeri @motifx/core'un reactive, useModel, createSignal, createComputed ya da createLazyComputed çağrısından gelen her üst düzey const ya da let, değişkeninin adıyla adlandırılır; .ts, .tsx, .js ve .jsx dosyalarının hepsinde. Bu store'lar getStores()'ta name (cart) ve file (src/stores/cart.ts, Vite köküne göre) ile listelenir. Üretim derlemesine hiçbir şey eklenmez.

Bir bileşen ya da servis alanında tutulan erken store'lar getStores() çağrısındaki taramayla listeye girer; yalnız bir kapanış içinde tutulan ve derleyicinin adlandırmadığı store'lar görünmez.

import type { MotifDevtools } from "@motifx/core/devtools";

app.useDevelopment(true);
const devtools = (globalThis as any).__MOTIF_DEVTOOLS__ as MotifDevtools;
app.useDevelopment(true);
const devtools = globalThis.__MOTIF_DEVTOOLS__;

version sözleşmenin sürümüdür, bugün 2. Bir alanın anlamı değiştiğinde artar; yeni alan eklenmesi sürümü değiştirmez. coreVersion uygulamanın çalıştığı @motifx/core paketinin sürümüdür ('1.2.0').

Yollar

Bileşenlerin kimlik numarası yoktur. Ağaç bir ormandır: [] bütün kökleri tutan sanal Application düğümüdür, yolun ilk sayısı kökü seçer, kalanı o kökten aşağı controls indeksleridir. Kökler sırayla run()'ın kurduğu uygulama kabuğu ([0]), oluşturulmuşsa sayfa katmanı app.layer (Layer) ve app.windows ile açılmış ya da bağlanmış her pencerenin gövdesini saran host bileşeni (Window); pencereler app.windows.list sırasındadır. [0, 2] kabuğun üçüncü çocuğu, [1, 0] katmanın ilk çocuğudur. Hiçbir kökün altında olmayan bir bileşenin yolu null'dur.

Yol, bileşenin listedeki sırasına bağlıdır. Öncesine bir bileşen eklenince ya da liste yeniden sıralanınca yol değişir. Bu yüzden ağaçtaki her ekleme, çıkarma ve taşıma tree:changed olayıyla bildirilir; olay, yeniden okunması gereken üst bileşenlerin o anki yollarını taşır. Katman oluştuğunda ve bir pencere açılıp kapandığında kök listesi değişir; bu da aynı olayda [] yoluyla bildirilir.

Olaylar

on(fn) bir dinleyici ekler ve dinleyiciyi kaldıran bir fonksiyon döndürür. Her olay { type, payload } biçimindedir. Bileşen olaylarının yükünde her zaman { path, type, tag } bulunur; hiçbir kökün altında olmayan bileşende path null'dur.

Olay Yük
tree:changed { parents }: değişen üst bileşenlerin yolları; kök listesi değiştiğinde []. Aynı turdaki değişiklikler tek olayda gelir.
documents:changed Uygulamaya bağlı belgelerin listesi değişti: {}. Liste getDocuments() ile yeniden okunur.
component:built duration: çocuklar dahil build süresi (ms).
component:visibility visible; yalnız görünürlük değişince.
component:wait waiting; yalnız bekleme durumu değişince.
component:disposing Bileşen bilgisi.
component:disposed duration; path, dispose başladığı andaki yoldur.
reactivity:flush Her reaktivite turunun sonunda bir kez: { id, duration, runs, triggers, effects, operations }; bkz. Operation'lar.
operation:started Bir iş başladı (bkz. Operation'lar): { id, kind, name?, data?, parent? }.
operation:finished İş bitti: { id, duration, error? }; async işte promise sonuçlandığında.
stores:changed Yeni store kaydedildiğinde, aynı turda kaç store oluşursa oluşsun bir kez: {}. Liste getStores() ile yeniden okunur.
scopes:changed Bir DI kapsamı açıldığında, onaylandığında ya da kapandığında, tur başına bir kez: {}. Liste getScopes() ile yeniden okunur.
service:created Bir servis örneği oluştu: { scope, token, lifetime, source, instance }. scope getScopes()'taki numaradır, source class, factory ya da value; transient her çözülüşte yayar.
service:disposed Bir kapsam örneklerinden birini kapatmaya başladı: { scope, token, lifetime, instance }.
route:navigation-start { uri }.
route:navigation-end { uri, requested, outcome, reason?, params, duration }; her gezinme bir sonuçla kapanır.
warning { code, message, details }; her gelişte.
error:caught { context, error }; her gelişte.
extension:registered Bir paket kayıt oldu (bkz. Paketler): { name }.
extension:disposed Kaydı kaldırıldı: { name }.
extension:changed Paket arayüzden yeniden okumasını istiyor: { name, source? }; source yoksa panelleri, varsa o kaynağı.
extension:reveal Paket arayüzden bir şey göstermesini istiyor: { name, target }; target { panel, route?, params?, state? } biçimindedir.
ext:<ad>:<tür> Bir paketin yaydığı olay (bkz. Paketler).

Bileşenin sayfaya girdiği an için olay yoktur. Bir bileşenin sayfada olup olmadığı ağaçtaki connected alanından okunur.

Ağaç

tag elemanın etiket adıdır; fragment için #fragment, metin için #text.

Bileşen içeriği

inspect(path) tek bir bileşenin içini düz veri olarak döndürür. Okuma sırasında bileşende hiçbir iç nesne yaratılmaz ve kullanıcı kodu çağrılmaz.

Alan İçerik
props Her prop'un önizlemesi. Fonksiyon { kind: 'function', name } olarak gösterilir, çağrılmaz. Dizi { kind: 'array', length }, nesne { kind: 'object', type, keys }, bileşen { kind: 'component', path, type }, DOM düğümü { kind: 'node', tag }, signal ya da computed değerinin önizlemesiyle { kind: 'signal', type: 'signal' \| 'computed', value, stale? }; stale: true güncel değerini henüz hesaplamamış bir computed'i gösterir ve getter'ı çalıştırılmaz.
fields Bileşenin kendi alanları (örneğin sınıf alanı state = reactive(…)), props ile aynı önizleme biçiminde; çerçevenin her bileşene yazdığı iç alanlar listelenmez. Getter olan alan { kind: 'getter' } olarak görünür ve çağrılmaz.
functionProps Yalnız fonksiyon bileşeninin kökünde bulunur: fonksiyona verilen prop'lar, props ile aynı önizleme biçiminde.
bindings { kind, property?, member?, updateMode?, active }; kind: property, list, conditional, class, attr, style.
events { name, type, dom, handler, modifiers }; dom: false bileşen olayıdır.
hooks { name, method, handlers }: sınıf metodu olarak mı tanımlı, kaç handler bağlı.
classes { static, reactive: [{ id, classes }] }; reaktif kaynakların o anki sınıf kümesi.
attributes { name, value, reactive, held?, heldValue? }; value bileşenin kendi değeridir. Üstünde attr.hold ile bir değer tutulan öznitelikte held: true olur ve heldValue elemanda görünen değeri verir.
services { scope, requested }: bileşenin getService ile istediği servisler ve DI kapsamı.
effects Bkz. Reaktivite izi.
extensions Bu bileşen için veri veren paketlerin çıktısı.
window Yalnız pencere kökünde: { kind, title, open, components }; kind popup, pip ya da attached, components penceredeki bileşen sayısı.

readValue(path, source, keys), props, functionProps ya da fields (source) içindeki bir değerin bir düzeyini açar: keys değere giden yoldur (['stat', 'tags']). Sonuç aynı önizleme biçiminde { ok: true, value: { value, entries: [{ key, value, getter? }], total } } olur; en fazla 100 alan döner, total tam sayıyı verir. Getter ve fonksiyonlar hiç çağrılmaz: getter getter: true olarak görünür, getter'dan geçen yol { ok: false, error: 'getter' } döndürür. Dizi, düz nesne, sınıf örneği, Map (anahtarlar metin olarak) ve Set (sıra numarasıyla) açılabilir; signal ya da computed'den geçen yol değerinin içine devam eder. readStore(id, keys) aynı biçimde getStores()'taki bir store'un içini açar; signal ve computed'de değerin içi açılır.

getServices() uygulamaya kayıtlı servisleri { token, lifetime, created } olarak listeler.

getScopes() DI kapsamlarını ve her birinde oluşmuş servisleri listeler:

Alan İçerik
id, parent Kapsamın numarası ve üst kapsamının numarası; kökte parent null'dır.
kind, name root, navigation (her gezinmede bir tane) ya da user (createScope(name) ile açılan) ve kapsamın adı.
current Güncel sayfanın scoped servisleri aldığı kapsam.
pending Henüz kurulan, onaylanmamış gezinme kapsamı.
pins Kapsamı güncel sayfanın dışında hâlâ tutan bileşen sayısı, örneğin keepAlive ile saklanan bir sayfa ya da serbest bırakılması süren ayrılan sayfa. Önceki gezinme kapsamı bu sayı sıfırın üstündeyken yaşar.
services { token, lifetime, instance, creating? }: kökte singleton'lar, her kapsamda scoped servisler, props ile aynı önizleme biçiminde; async servis kurulurken creating: true.
transients enableAutoDisposeTransients() açıkken kapsamın kapatmak için tuttuğu transient örnekleri.

createScope ile açılan kapsam kullanıldığı sürece listelenir, dispose() ile listeden çıkar. scopes:changed olayı listenin ne zaman yeniden okunacağını söyler.

readService(scope, token, keys) bir servis örneğinin bir düzeyini readValue ile aynı biçimde açar: scope getScopes()'taki numara, token o kapsamın services listesinde görünen addır. Singleton'lar kök kapsamdan, scoped servisler onları oluşturan kapsamdan okunur. Okuma servisi asla oluşturmaz: henüz oluşmamış servis { ok: false, error: 'no service' } döndürür. Yalnız örneğin kendi alanları listelenir; sınıftaki getter'lar ve #özel alanlar listelenmez.

Reaktivite izi

inspect(path).effects, bileşenin sahip olduğu effect'leri listeler. Bir kayıt şöyledir:

const effect = {
  kind: "binding:textContent", runs: 4, lastDuration: 0.1,
  lastTrigger: { target: { kind: "object", type: "Object", keys: 7 }, key: "title" },
  deps: [{ target: { kind: "object", type: "Object", keys: 7 }, key: "title" }],
};
const effect = {
  kind: "binding:textContent", runs: 4, lastDuration: 0.1,
  lastTrigger: { target: { kind: "object", type: "Object", keys: 7 }, key: "title" },
  deps: [{ target: { kind: "object", type: "Object", keys: 7 }, key: "title" }],
};

kind effect'in nereden geldiğini söyler: binding:<özellik>, list, when, method, ternary, watch, switch, conditional, class, attr:<ad>, style, virtual-data, computed. Doğrudan effect() ile oluşturulanlar effect olarak görünür ve sahipsizdir.

reactivity:flush olayı her turda bir kez gelir. triggers o turu başlatan değişiklikleri sayılarıyla, effects çalışan effect'leri sahip bileşenlerinin yollarıyla taşır. Değişen nesne getStores()'taki bir store'a aitse tetikleyici store: { id, path } alanını da taşır: path store'un kökünden değişen nesneye giden anahtarlardır (stats[1].value değişikliği { path: ['stats', '1'] } ve key: 'value' olarak gelir). Signal ve computed'de anahtar value'dur. Bir turda aynı değere yapılan bin değişiklik yine tek olaydır.

Her flush'ın artan bir id'si vardır. Bir operation içinde gerçekleşen tetikleyici operationId taşır; operations, o turun tetikleyicilerinin ait olduğu operation kimliklerini listeler. Aynı değere farklı operation'larda yapılan değişiklikler ayrı sayılır.

Sayaçlar

getCounters() tek çağrıda şu alanları taşıyan bir nesne döndürür:

Alan Anlamı
components.live Yaşayan bileşen sayısı
components.inTree Uygulama ağacındaki bileşen sayısı
components.notInTree Yaşayan ama ağaçta olmayan bileşen sayısı
components.byType Bileşen türüne göre canlı sayı ({ NoteRow: 12 })
bindings Ağaçtaki bağ sayısı
listeners.dom / listeners.component DOM dinleyicileri / bileşen olayı dinleyicileri
effects.live / effects.owned Yaşayan effect'ler / bunlardan bir bileşene ait olanlar
reactivity.runs Effect çalışma sayısı
reactivity.flushes / reactivity.flushRuns Tur sayısı / turlarda çalışan effect toplamı
reactivity.lastFlushDuration / reactivity.maxFlushDuration Son turun / en uzun turun süresi (ms)

Alanların hepsi sayıdır; byType tür adından sayıya bir nesnedir.

live bileşen kurulunca artar, dispose olunca azalır. inTree, bindings ve listeners çağrı anında ağaç dolaşılarak sayılır. notInTree, yaşayan ama ağaçta olmayan bileşenlerdir; sayfadan çıkıldığı halde düşmüyorsa o bileşenler bir yerde tutuluyordur. Sayaçlar DevTools her açıldığında sıfırdan başlar.

Router, hatalar ve uyarılar

Yazma işlemleri

Her yazma, uygulama kodunun da kullandığı public API'den geçer. Sonuç { ok: true, value? } ya da { ok: false, error } olur; işlemler hata fırlatmaz.

İşlem Ne yapar
setClass(path, name, on) class.add / class.remove.
setAttribute(path, name, value) attr.add; value: null siler. on… ile başlayan adlar yazılmaz.
setVisible(path, visible) motif.show() / motif.hide(); geçişler beklenir. Kökler gizlenmez: tek sayılı yol ok: false döner.
writeModel(path, value) bindings.writeModel(value); model bağlaması yoksa ok: false.
setProp(path, keys, value) props üzerinde anahtar yoluna yazar, ardından setState() ile bağlamaları yeniden çalıştırır.
navigate(uri) router.navigate(uri).
setStoreValue(id, keys, value) getStores()'taki kimliğiyle bir reactive ya da signal kaydına yazar; signal'de keys boşsa değerin kendisini değiştirir. Computed yazılamaz. Reaktivite normal çalışır.
callExtension(name, panel, command, args) Bir paketin kaydettiği komutu çalıştırır (bkz. Paketler); yalnız capabilities'i write izni veren bir panelden. Komutun dönüş değeri beklenir ve value olur.

__proto__, prototype ve constructor anahtarları yazılmaz.

Sonraki adım

Kendi durumunu DevTools'ta gösteren paket registerDevtools ile kaydolur: Paketler için DevTools. Dışa aktarılan bütün sembollerin özeti için API Referansı bölümüne geçin.