Geliştirici Araçları mimarisi yenileme: JavaScript modüllerine geçiş

Tim van der Lippe
Tim van der Lippe

Bildiğiniz gibi Chrome Geliştirici Araçları; HTML, CSS ve JavaScript kullanılarak yazılmış bir web uygulamasıdır. Yıllar içinde DevTools, web platformu hakkında daha fazla özellik ve bilgi sahibi oldu. DevTools yıllar içinde genişlese de mimarisi, hâlâ WebKit'in bir parçası olduğu zamanki orijinal mimariye benzemektedir.

Bu gönderi, Geliştirici Araçları mimarisinde yaptığımız değişiklikleri ve nasıl oluşturulduğunu açıklayan bir dizi blog yayınının bir parçasıdır. Geliştirici Araçları'nın geçmişte nasıl çalıştığını, avantajları ile sınırlamalarının neler olduğunu ve bu sınırlamaları gidermek için neler yaptığımızı açıklayacağız. Şimdi modül sistemlerinin, kodun nasıl yükleneceğinin ve JavaScript modüllerini nasıl kullandığımızın ayrıntılarına dalalım.

Başlangıçta hiçbir çözüm yoktu

Mevcut ön uç ortamında, araçların yerleşik olduğu çeşitli modül sistemleri ve şu anda standart hale getirilmiş JavaScript modülü biçimi bulunuyor olsa da DevTools ilk geliştirildiğinde bunlardan hiçbiri yoktu. Geliştirici Araçları, ilk olarak 12 yıldan uzun bir süre önce WebKit'te kullanıma sunulan kod temel alınarak geliştirilmiştir.

DevTools'da modül sisteminden ilk kez 2012'de bahsedildi: Modüller listesinin ve ilişkili bir kaynak listesinin kullanıma sunulması. Bu, o zamanlar Geliştirici Araçları'nı derlemek ve derlemek için kullanılan Python altyapısının bir parçasıydı. Yapılan bir takip değişikliği sonucunda tüm modüller 2013'te ayrı bir frontend_modules.json dosyası (taahhüt) olarak çıkartıldı ve 2014'te ayrı module.json dosyaları (taahhüt) oluşturuldu.

Örnek module.json dosyası:

{
  "dependencies": [
    "common"
  ],
  "scripts": [
    "StylePane.js",
    "ElementsPanel.js"
  ]
}

2014'ten beri Geliştirici Araçları'nda modüllerini ve kaynak dosyalarını belirtmek için module.json kalıbı kullanılmaktadır. Bu arada web ekosistemi de hızla gelişti ve UMD, CommonJS ve nihayetinde standart hale gelen JavaScript modüllerini de içeren birçok modül biçimi oluşturuldu. Ancak Geliştirici Araçları, module.json biçimini kullanmaya devam etti.

Geliştirici Araçları çalışmaya devam etse de standart dışı ve benzersiz bir modül sistemi kullanmanın bazı olumsuz yönleri vardı:

  1. module.json biçimi, modern paketleyicilere benzer şekilde özel derleme araçları gerektiriyordu.
  2. IDE entegrasyonu yoktu. Bunun için, modern IDE'lerin anlayabileceği dosyalar oluşturmak için özel araçlar gerekiyordu (VS Code için jsconfig.json dosyaları oluşturmak üzere kullanılan orijinal komut dosyası).
  3. Modüller arasında paylaşımı mümkün kılmak için işlevler, sınıflar ve nesneler global kapsama yerleştirildi.
  4. Dosyalar sıraya bağlıydı. Yani, sources dosyasının listelenme sırası önemliydi. Güvendiğiniz kodun yükleneceğinin garantisi yoktu, ancak bir insan tarafından doğrulanmıştı.

Sonuç olarak, modül sisteminin Geliştirici Araçları'ndaki ve diğer (daha yaygın olarak kullanılan) modül biçimlerindeki mevcut durumunu değerlendirirken, module.json kalıbının çözdüğünden daha fazla sorun oluşturduğu sonucuna vardık ve bundan nasıl uzaklaşacağımızı planlamanın zamanı geldi.

Standartların faydaları

Mevcut modül sistemlerinin dışında, taşınmak üzere JavaScript modüllerini seçtik. Bu kararın alındığı tarihte, JavaScript modülleri Node.js'de bir bayrakla teslim edilmeye devam ediyordu ve NPM'de bulunan büyük miktarlarda paket, kullanabileceğimiz bir JavaScript modülü paketi içermiyordu. Buna rağmen, JavaScript modüllerinin en iyi seçenek olduğu sonucuna vardık.

JavaScript modüllerinin birincil avantajı, JavaScript için standartlaştırılmış modül biçimi olmasıdır. module.json yönteminin dezavantajlarını (yukarıya bakın) listelediğimizde neredeyse hepsinin standart dışı ve benzersiz bir modül biçimi kullanmayla ilgili olduğunu fark ettik.

Standart dışı bir modül biçimi seçmek, derleme araçları ve bakım sorumlularımızın kullandığı araçlarla entegrasyon oluşturmak için kendimize zaman ayırmamız gerektiği anlamına geliyor.

Bu entegrasyonlar çoğu zaman kırılgan oluyordu ve özellikler için destek içermiyordu. Ek bakım için gereken süre bazen, nihayetinde kullanıcılara gönderilecek küçük hatalara yol açıyordu.

JavaScript modülleri standart olduğu için VS Code gibi IDE'lerin, Closure Compiler/TypeScript gibi tür denetleyicilerin ve Rollup/minifier gibi derleme araçlarının yazdığımız kaynak kodu anlayabileceği anlamına geliyordu. Dahası, Geliştirici Araçları ekibine yeni bir bakımcı katıldığında özel module.json biçimini öğrenmek için zaman harcamak zorunda kalmazdı ancak JavaScript modüllerine zaten aşina olmalıydı (muhtemelen).

DevTools ilk geliştirildiğinde elbette yukarıdaki avantajlardan hiçbiri mevcut değildi. Şu anda oldukları noktaya gelmek için standart grupları, çalışma zamanı uygulamaları ve JavaScript modüllerini kullanan geliştiricilerle geri bildirim sağlayan geliştiriciler yıllarca çalıştı. Ancak JavaScript modülleri kullanıma sunulduğunda bir seçim yapmak zorundaydık: kendi biçimimizi korumayı sürdürmek veya yeni biçime geçiş için yatırım yapmak.

Çünkü pırıl pırıl yeni

JavaScript modüllerinden yararlanmak istediğimiz birçok avantaj olsa da, standart dışı module.json dünyasında kaldık. JavaScript modüllerinin avantajlarından faydalanmak, teknik borçları temizlemek ve özellikleri bozabilecek ve regresyon hatalarına yol açabilecek bir taşıma işlemi gerçekleştirmek için önemli ölçüde yatırım yapmamız gerekti.

Bu noktada akla "JavaScript modülleri kullanmak istiyor muyuz?" değil, "JavaScript modüllerini kullanmak ne kadar pahalı?" sorusu geliyordu. Bu noktada, kullanıcılarımızın memnuniyetini geride bırakma riski, taşıma işlemi için çok fazla zaman harcayan mühendislerin maliyeti ve çalışacağımız daha kötü geçici durum arasında denge kurmamız gerekiyordu.

Bu son noktanın çok önemli olduğu ortaya çıktı. Teoride JavaScript modüllerine erişebilsek de, taşıma sırasında hem module.json hem de JavaScript modüllerini dikkate almamız gereken bir kod elde ettik. Bunu başarmak teknik açıdan zor olmakla kalmadı, aynı zamanda Geliştirici Araçları'nda çalışan tüm mühendislerin bu ortamda nasıl çalışacaklarını bilmesi gerektiği anlamına da geliyordu. Kendilerine sürekli olarak "Kod tabanının bu kısmında, module.json veya JavaScript modülleri mi ve nasıl değişiklik yaparım?" diye sormaları gerekir.

Tanıtım: Diğer işletmecilerimize taşıma işleminde rehberlik etmenin gizli maliyeti, beklediğimizden daha büyüktü.

Maliyet analizinin ardından, JavaScript modüllerine geçmenin yine de yararlı olduğuna karar verdik. Bu nedenle ana hedeflerimiz şunlardı:

  1. JavaScript modüllerini kullanmanın avantajlardan mümkün olan en iyi şekilde yararlandığından emin olun.
  2. module.json tabanlı mevcut sistemle entegrasyonun güvenli olduğundan ve kullanıcılar üzerinde olumsuz etki (regresyon hataları, kullanıcıların hayal kırıklığı) yaşamadığından emin olun.
  3. Tüm Geliştirici Araçları yöneticilerine geçiş süreci boyunca rehberlik edin. Öncelikle, yanlışlıkla yapılabilecek hataları önlemek için yerleşik denetimler ve denge yöntemlerinden yararlanın.

Elektronik tablolar, dönüşümler ve teknik borç

Hedef çok açık olsa da module.json biçimin getirdiği sınırlamaların üstesinden gelmek zordu. Rahat olduğumuz bir çözüm geliştirebilmemiz için birkaç yineleme, prototip ve mimari değişiklik gerekti. Oluşturduğumuz taşıma stratejisiyle birlikte bir tasarım dokümanı hazırladık. Tasarım dokümanında ayrıca ilk süre tahminimiz de belirtilmiş: 2-4 hafta.

Spoiler uyarısı: Taşımanın en yoğun kısmı 4 ay sürdü ve baştan sona 7 ay sürdü.

Ancak ilk plan zamana meydan okuyordu: DevTools çalışma zamanına module.json dosyasındaki scripts dizisinde listelenen tüm dosyaları eski yöntemle yükleyecekken, modules dizisinde listelenen tüm dosyaların JavaScript modülleri dinamik içe aktarma özelliğiyle yüklenmesini öğretiyorduk. modules dizisinde yer alacak dosyalar ES içe/dışa aktarma işlemlerini kullanabilir.

Ayrıca, taşıma işlemini 2 aşamada gerçekleştireceğiz (sonunda son aşamayı 2 alt aşamaya ayırdık; aşağıya bakın): export ve import aşamaları. Büyük bir e-tabloda izlenen aşamanın hangi modülün içinde olacağı durumu:

JavaScript modülleri taşıma e-tablosu

İlerleme sayfasının snippet'ine buradan ulaşabilirsiniz.

export aşamalı

İlk aşamada, modüller/dosyalar arasında paylaşılması gereken tüm semboller için export ifadeleri eklenecekti. Dönüşüm, klasör başına bir komut dosyası çalıştırılarak otomatik hale getirilir. Aşağıdaki simge de module.json dünyasında mevcut olur:

Module.File1.exported = function() {
  console.log('exported');
  Module.File1.localFunctionInFile();
};
Module.File1.localFunctionInFile = function() {
  console.log('Local');
};

(Burada, Module modülün adı ve File1 dosyanın adıdır. Kaynak ağacımızda, bu front_end/module/file1.js olacaktır.)

Bu değer şuna dönüştürülür:

export function exported() {
  console.log('exported');
  Module.File1.localFunctionInFile();
}
export function localFunctionInFile() {
  console.log('Local');
}

/** Legacy export object */
Module.File1 = {
  exported,
  localFunctionInFile,
};

Başta, bu aşamada aynı dosyaları içe aktarmayı da yeniden yazmayı planlıyorduk. Örneğin, yukarıdaki örnekte Module.File1.localFunctionInFile değerini localFunctionInFile olarak yeniden yazarız. Ancak bu iki dönüşümü ayırmamız halinde otomatikleştirmenin daha kolay ve uygulamanın daha güvenli olacağını fark ettik. Bu nedenle, "aynı dosyadaki tüm simgeleri taşıma", import aşamasının ikinci alt aşaması olur.

export anahtar kelimesinin bir dosyaya eklenmesi dosyayı "script"ten "modül"e dönüştürdüğü için DevTools altyapısının çoğunun güncellenmesi gerekti. Bu, çalışma zamanını (dinamik içe aktarma özellikli) ve aynı zamanda modül modunda çalışacak ESLint gibi araçları içeriyordu.

Bu sorunlarla ilgili çalışmalarımızı sürdürürken yaptığımız keşiflerden biri, testlerimizin "dikkatsiz" modda çalıştırıldığıydı. JavaScript modülleri dosyaların "use strict" modunda çalıştığını belirttiğinden bu durum da testlerimizi etkiler. Anlaşıldı ki, with ifadesi içeren bir test de dahil olmak üzere önemsiz miktarda test bu özensizliğe dayanıyordu 😳.

Sonunda, ilk klasörün export ifadelerini içerecek şekilde güncellenmesi yaklaşık bir hafta ve yeniden alanlarla birden fazla deneme aldı.

import aşamalı

Tüm simgeler export ifadeleri kullanılarak dışa aktarıldıktan ve genel kapsamda (eski) kaldıktan sonra, ES içe aktarmalarını kullanmak için dosyalar arası simgelerle ilgili tüm referansları güncellememiz gerekti. Nihai hedef, tüm "eski dışa aktarma nesnelerini" kaldırarak global kapsamı temizlemek olacaktır. Dönüşüm, klasör başına bir komut dosyası çalıştırılarak otomatik hale getirilir.

Örneğin, module.json dünyasında var olan aşağıdaki simgeler için:

Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
SameModule.AnotherFile.moduleScoped();

Bu değerler şuna dönüştürülür:

import * as Module from '../module/Module.js';
import * as AnotherModule from '../another_module/AnotherModule.js';

import {moduleScoped} from './AnotherFile.js';

Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
moduleScoped();

Ancak, bu yaklaşımda dikkat edilmesi gereken bazı noktalar vardır:

  1. Her sembol Module.File.symbolName olarak adlandırılmadı. Bazı semboller yalnızca Module.File, hatta Module.CompletelyDifferentName olarak adlandırılmıştır. Bu tutarsızlık, eski global nesneden yeni içe aktarılan nesneye bir iç eşleme oluşturmamız gerektiği anlamına geliyordu.
  2. Bazen modeScoped adları arasında çakışmalar olabilir. En belirgin şekilde, belirli Events türlerini tanımlamak için bir kalıp kullandık. Burada her sembol yalnızca Events olarak adlandırıldı. Yani, farklı dosyalarda tanımlanan birden fazla etkinlik türünü dinliyorsanız söz konusu Events için import ifadesinde bir ad çakışması oluyordu.
  3. Ama ortaya çıktığı gibi, dosyalar arasında döngüsel bağımlılıklar vardı. Simgenin kullanımı tüm kod yüklendikten sonra yapıldığı için bu, global kapsam bağlamında sorun teşkil etmiyordu. Bununla birlikte, bir import öğesine ihtiyacınız varsa döngüsel bağımlılık açıkça belirtilir. Geliştirici Araçları'nda da bulunan global kapsam kodunuzda yan etki işlevi çağrıları bulunmuyorsa bu durum pek sorun yaratmaz. Sonuç olarak, dönüşümün güvenli olması için bazı ameliyatlar ve yeniden düzenleme yapılması gerekiyordu.

JavaScript modülleriyle yepyeni bir dünya

Şubat 2020'de, Eylül 2019'un başlangıcından 6 ay sonra son temizlik işlemleri ui/ klasöründe gerçekleştirildi. Bu, taşımanın resmî olmayan sona ermesine neden oluyordu. Tozun hafifletilmesinin ardından, taşıma işlemini 5 Mart 2020'de tamamlandı olarak resmi olarak işaretledik. 🎉

Artık Geliştirici Araçları'ndaki tüm modüller, kod paylaşmak için JavaScript modüllerini kullanıyor. Eski testlerimiz veya DevTools mimarisinin diğer bölümleriyle entegrasyon için global kapsama (module-legacy.js dosyalarına) bazı simgeler eklemeye devam ediyoruz. Bunlar zaman içinde kaldırılacaktır, ancak bunları gelecekteki geliştirmeler için engel teşkil etmiyoruz. Ayrıca, JavaScript modüllerinin kullanımı ile ilgili bir stil rehberimiz de bulunmaktadır.

İstatistikler

Bu taşıma işlemine dahil olan CL (değişiklik listesinin kısaltması; Gerrit'te bir değişikliği temsil eden, GitHub pull isteğine benzer terim) sayısıyla ilgili konservatif tahminler, genellikle 2 mühendis tarafından gerçekleştirilen 250 CL civarındadır. Yapılan değişikliklerin boyutuna ilişkin kesin bir istatistiğimiz yok ancak satırların konservatif tahmini değişimi (her CL için ekleme ve silme işlemleri arasındaki mutlak farkın toplamı olarak hesaplanır) yaklaşık 30.000 (Tüm Geliştirici Araçları ön uç kodunun yaklaşık% 20'si) olur.

export kullanan ilk dosya, Chrome 79 sürümünde gönderildi ve Aralık 2019'da kararlı sürüm olarak yayınlandı. import sürümüne geçişle ilgili son değişiklik, Mayıs 2020'de mevcut ürün sürümüne yayınlanan Chrome 83 sürümünde sunulmuştur.

Chrome'un mevcut ürün sürümüne gönderilen ve bu taşıma kapsamında kullanıma sunulan bir regresyon olduğunu biliyoruz. Komut menüsündeki snippet'lerin otomatik tamamlama özelliği, harici default dışa aktarma işlemi nedeniyle bozuldu. Başka regresyonlarımız da oldu, ancak otomatik test paketlerimiz ve Chrome Canary kullanıcıları bunları bildirdi ve Chrome'un kararlı kullanıcılarına ulaşmadan önce bu sorunları düzelttik.

Yolculuğun tamamını crbug.com/1006759 adresinde günlüğe görebilirsiniz (tüm CL'ler bu hataya eklenmemiş olsa da çoğu).

Öğrendiklerimiz

  1. Geçmişte verilen kararlar projeniz üzerinde uzun süreli bir etki yaratabilir. JavaScript modülleri (ve diğer modül biçimleri) uzun süredir kullanımdaydı, ancak DevTools taşımaya uygun bir konumda değildi. Bilgiye dayalı tahminlere dayanarak, ne zaman taşınmayacağına ve ne zaman taşınmayacağına karar vermek zordur.
  2. İlk süre tahminlerimiz aylar yerine haftalar halindeydi. Bu durum büyük ölçüde, başlangıçtaki maliyet analizimizde beklediğimizden daha fazla beklenmedik sorunla karşılaşmış olmamızdan kaynaklanmaktadır. Geçiş planı sağlam olmasına rağmen, teknik borçlar engel teşkil ediyordu (istediğimizden daha fazlaydı).
  3. JavaScript modüllerinin taşınması sürecinde, çok sayıda (görünüşte alakasız gibi) teknik borç temizlikleri gerçekleştirildi. Modern ve standartlaştırılmış bir modül biçimine geçiş, en iyi kodlama uygulamalarımızı modern web geliştirmesiyle uyumlu hale getirmemizi sağladı. Örneğin, özel Python paketleyicimizi minimal bir Rollup yapılandırmasıyla değiştirebildik.
  4. Kod tabanımız üzerindeki büyük etkisine rağmen (kodun yaklaşık% 20'si değişmişti) çok az sayıda regresyon bildirildi. İlk birkaç dosyayı taşırken sayısız sorunla karşılaştık. Ancak bir süre sonra sağlam, kısmen otomatik bir iş akışımız oldu. Dolayısıyla kararlı kullanıcılarımız üzerindeki olumsuz kullanıcı etkisi bu geçiş sürecinde minimum düzeydeydi.
  5. Belirli bir göç hareketinin inceliklerini diğer yöneticilere öğretmek zor ve bazen imkansızdır. Bu ölçekteki taşıma işlemlerini takip etmek zordur ve çok fazla alan bilgisi gerektirir. Söz konusu alan bilgisinin aynı kod tabanında çalışan diğer kullanıcılara aktarılması, bu kişilerin yaptıkları iş için tercih edilen bir durum değildir. Neyi paylaşacağınızı ve hangi ayrıntıları paylaşmayacağınızı bilmek sanat olsa da gerekli bir iştir. Bu nedenle, büyük taşıma işlemlerinin sayısını azaltmak veya en azından bunları aynı anda gerçekleştirmemek son derece önemlidir.

Önizleme kanallarını indirme

Varsayılan geliştirme tarayıcınız olarak Chrome Canary, Yeni geliştirilenler veya Beta'yı kullanmayı düşünün. Bu önizleme kanallarıyla Geliştirici Araçları'nın en yeni özelliklerine erişebilir, son teknoloji ürünü web platformu API'lerini test edebilir ve sitenizdeki sorunları kullanıcılarınızdan önce tespit edebilirsiniz!

Chrome Geliştirici Araçları ekibiyle iletişim kurma

Yayındaki yeni özellikler ve değişiklikler ya da Geliştirici Araçları ile ilgili diğer konular hakkında konuşmak için aşağıdaki seçenekleri kullanın.

  • crbug.com adresinden öneri veya geri bildirim gönderin.
  • Geliştirici Araçları'nda, Diğer seçenekler   Diğer > Yardım > Geliştirici Araçları sorunu bildir'i kullanarak Geliştirici Araçları sorunlarını bildirin.
  • @ChromeDevTools adresine tweet gönderin.
  • Geliştirici Araçları'ndaki YouTube videoları veya Geliştirici Araçları İpuçları YouTube videolarındaki yenilikler hakkındaki görüşlerinizi bizimle paylaşın.