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

Tim van der Lippe
Tim van der Lippe

Chrome Geliştirici Araçları'nın HTML, CSS ve JavaScript kullanılarak yazılmış bir web uygulaması olduğunu biliyorsunuzdur. DevTools, yıllar içinde daha fazla özellik, daha akıllı ve daha geniş web platformu hakkında daha bilgili hale geldi. Geliştirici Araçları yıllar içinde genişlemiş olsa da mimarisi, hâlâ WebKit'in bir parçası olduğu zamanki orijinal mimariye büyük ölçüde benziyor.

Bu yayın, DevTools'un 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. DevTools'un geçmişte nasıl çalıştığını, avantajlarını ve sınırlamalarını ve bu sınırlamaları hafifletmek için neler yaptığımızı açıklayacağız. Bu nedenle modül sistemlerini, kodun nasıl yüklendiğini ve JavaScript modüllerini nasıl kullandığımızı inceleyelim.

Başlangıçta hiçbir sorun yoktu

Mevcut ön uç ortamında, etrafında araçlar bulunan çeşitli modül sistemleri ve artık standartlaştırılmış JavaScript modülleri biçimi mevcut olsa da DevTools ilk oluşturulduğunda bunların hiçbiri yoktu. DevTools, ilk olarak 12 yıldan uzun bir süre önce WebKit'te kullanıma sunulan kodun üzerine inşa edilmiştir.

Geliştirici Araçları'nda bir modül sisteminden ilk bahsedildiğinde 2012 yılından bahsedildi: İlişkilendirilmiş kaynak listesinin bulunduğu bir modül listesinin kullanıma sunulması. Bu, o zamanlar DevTools'u derlemek ve oluşturmak için kullanılan Python altyapısının bir parçasıydı. Bir takip değişikliği, tüm modülleri 2013'te ayrı bir frontend_modules.json dosyasına (commit) ve ardından 2014'te ayrı module.json dosyalarına (commit) ayırdı.

Örnek bir module.json dosyası:

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

2014'ten beri DevTools'ta modüllerini ve kaynak dosyalarını belirtmek için module.json kalıbı kullanılmaktadır. Bu sırada web ekosistemi hızla gelişti ve UMD, CommonJS ve nihayetinde standartlaştırılmış JavaScript modülleri dahil olmak üzere birden fazla 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ı dezavantajları vardı:

  1. module.json biçimi, modern paketleyicilere benzer şekilde özel derleme araçları gerektiriyordu.
  2. IDE entegrasyonu yoktu. Bu nedenle, modern IDE'lerin anlayabileceği dosyalar oluşturmak için özel araçlar gerekiyordu (VS Code için jsconfig.json dosyaları oluşturmaya yönelik orijinal komut dosyası).
  3. Modüller arasında paylaşımı mümkün kılmak için işlevler, sınıflar ve nesnelerin tümü genel kapsama alındı.
  4. Dosyalar sıraya bağlıydı. Yani sources öğelerinin listelenme sırası önemliydi. Yalnızca bir kişi tarafından doğrulanmış olması dışında, kullandığınız kodun yükleneceğine dair bir garanti bulunmuyordu.

DevTools'daki modül sisteminin ve diğer (daha yaygın kullanılan) modül biçimlerinin mevcut durumunu değerlendirdiğimizde, module.json kalıbının çözdüğünden daha fazla soruna yol açtığına ve bu kalıptan geçiş planlama zamanının geldiğine karar verdik.

Standartların avantajları

Mevcut modül sistemleri arasından, taşınacak modül olarak JavaScript modüllerini seçtik. Bu karar alındığında JavaScript modülleri hâlâ Node.js'de bir işaretin arkasından yayınlanıyordu ve NPM'de bulunan çok sayıda pakette kullanabileceğimiz bir JavaScript modülü paketi yoktu. Buna rağmen, en iyi seçeneğin JavaScript modülleri 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 aracının dezavantajlarını listelediğimizde (yukarıya bakın) neredeyse hepsinin standart dışı ve benzersiz bir modül biçimi kullanmakla ilgili olduğunu fark ettik.

Standart olmayan bir modül biçimi seçmek, derleme araçlarıyla ve bakım ekibimizin kullandığı araçlarla entegrasyon oluşturmak için zaman ayırmamız gerektiği anlamına gelir.

Bu entegrasyonlar genellikle kararsızdı ve özellikler için destekten yoksundu. Bu nedenle ek bakım süresi gerektiriyordu ve bazen kullanıcılara gönderilecek küçük hatalara yol açıyordu.

JavaScript modüllerinin standart olması, VS Code gibi IDE'lerin, Closure Compiler/TypeScript gibi yazım denetleyicileri ve Rollup/küçültücüler gibi derleme araçlarının yazdığımız kaynak kodunu anlayabilmesi anlamına geliyordu. Ayrıca, DevTools ekibine yeni katılan bir geliştiricinin, özel bir module.json biçimini öğrenmek için zaman harcaması gerekmez. Bunun yerine, muhtemelen JavaScript modülleri hakkında bilgi sahibidir.

Elbette, DevTools ilk oluşturulduğunda yukarıdaki avantajlardan hiçbiri mevcut değildi. Standartlar grupları, çalışma zamanı uygulamaları ve JavaScript modüllerini kullanan geliştiricilerin geri bildirimleriyle bugünkü noktaya ulaşmak için yıllarca çalışma yapıldı. Ancak, JavaScript modülleri kullanıma sunulduğunda ya kendi biçimimizi korumayı ya da yeni biçime geçmek için yatırım yapmayı seçme şansına sahip olduk.

Yeni cihazın maliyeti

JavaScript modüllerinin yararlanmak istediğimiz birçok avantajı olmasına rağmen standart olmayan module.json dünyasında kaldık. JavaScript modüllerinin avantajlarından yararlanmak için teknik borcu temizlemeye ve özellikleri bozabilecek ve geriye dönük hatalara neden olabilecek bir taşıma işlemi gerçekleştirmeye önemli ölçüde yatırım yapmamız gerekiyordu.

Bu noktada, "JavaScript modüllerini kullanmak istiyor muyuz?" sorusu değil, "JavaScript modüllerini kullanmak ne kadar pahalı?" sorusuydu. Bu noktada, kullanıcılarımızın regresyonlarla kırılması riskini, mühendislerin geçişe oldukça fazla zaman harcamalarının (çok fazla) maliyeti ile çalışacağımız geçici olarak daha kötü olan durumu dengelemek zorundaydık.

Son noktanın çok önemli olduğu ortaya çıktı. Teorik olarak JavaScript modüllerine erişebileceksek de, taşıma sırasında module.json ve JavaScript modüllerinin ikisini dikkate almamız gereken bir kodla karşılaşırız. Bunun başarılması teknik açıdan zor olmasının yanı sıra Geliştirici Araçları üzerinde çalışan tüm mühendislerin bu ortamda nasıl çalışacaklarını da bilmeleri gerektiği anlamına geliyordu. Kendilerine sürekli şu soruyu sormaları gerekir: "Kod tabanının bu kısmı için module.json mı yoksa JavaScript modülleri mi kullanılıyor ve nasıl değişiklik yaparım?".

Önizleme: Bakım uzmanlarımıza geçiş sürecinde rehberlik etmenin gizli maliyeti beklediğimizden daha büyüktü.

Maliyet analizinin ardından, JavaScript modüllerine geçişin yine de değerli olduğu sonucuna vardık. Bu nedenle, ana hedeflerimiz şunlardı:

  1. JavaScript modüllerinin kullanımından mümkün olduğunca fazla yararlanın.
  2. Mevcut module.json tabanlı sistemle entegrasyonun güvenli olduğundan ve kullanıcılar üzerinde olumsuz bir etkisi (gerileme hataları, kullanıcıların canını sıkacak durumlar) olmadığından emin olun.
  3. Tüm DevTools geliştiricilerinin taşıma işleminde yol gösterici olması için, öncelikle yanlışlıkla yapılan hataları önlemek amacıyla yerleşik denetim ve dengeler sunar.

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

Hedef açıkça belli olsa da module.json biçiminin getirdiği kısıtlamaların geçici bir çözüm üretmesi zor olduğu anlaşıldı. Kendimizi rahat hissettiğimiz bir çözüm geliştirmeden önce birkaç iterasyon, prototip ve mimari değişiklik yaptık. Sonunda edindiğimiz taşıma stratejisini içeren bir tasarım belgesi hazırladık. Tasarım dokümanında ilk süre tahminimiz de (2-4 hafta) belirtilmişti.

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 karşı koydu: DevTools çalışma zamanında, scripts dizisinde listelenen tüm dosyaları module.json dosyasında eski yöntemi kullanarak, modules dizisinde listelenen tüm dosyaları ise JavaScript modülleri dinamik içe aktarma ile yüklemeyi öğretecektik. modules dizisinde bulunan tüm dosyalar ES içe aktarma/dışa aktarma işlemlerini kullanabilir.

Ayrıca taşıma işlemini 2 aşamada gerçekleştiririz (son aşamayı 2 alt aşamaya ayırırız, aşağıya bakın): export ve import aşamaları. Hangi modülün hangi aşamada olacağı büyük bir e-tabloda izleniyordu:

JavaScript modüllerini taşıma e-tablosu

İlerleme sayfasının bir snippet'ini buradan inceleyebilirsiniz.

export-aşama

İlk aşamada, modüller/dosyalar arasında paylaşılması gereken tüm semboller için export-ifadeleri eklenir. Dönüşüm, her klasör için bir komut dosyası çalıştırılarak otomatik hale gelir. module.json dünyasında aşağıdaki simge 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ı, File1 ise dosyanın adıdır. Kaynak ağacımızda bu değer front_end/module/file1.js olacaktır.)

Bu, aşağıdaki gibi 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ştaki planımız, bu aşamada aynı dosya içe aktarma işlemlerini de yeniden yazmaktı. Örneğin, yukarıdaki örnekte Module.File1.localFunctionInFile yerine localFunctionInFile yazıyoruz. Ancak bu iki dönüşümü ayırmamız halinde otomatikleştirmenin daha kolay ve daha güvenli olacağını fark ettik. Bu nedenle, "aynı dosyadaki tüm sembolleri taşıma" işlemi, import aşamasının ikinci alt aşaması olur.

Bir dosyaya export anahtar kelimesi eklendiğinde dosya "komut dosyası" olmaktan "modül"e dönüştüğü için DevTools altyapısının büyük bir kısmının buna göre güncellenmesi gerekiyordu. Buna çalışma zamanı (dinamik içe aktarma ile) ve aynı zamanda modül modunda çalışan ESLint gibi araçlar da dahil edildi.

Bu sorunlar üzerinde çalışırken testlerimizin "sloppy" modunda çalıştığını tespit ettik. JavaScript modülleri, dosyaların "use strict" modunda çalıştığını ima ettiğinden bu durum testlerimizi de etkiler. Sonuçta ortaya çıktığı gibi, önemli düzeyde bir test yöntemi de bu dengesizliği dikkate alıyordu. Hatta with ifadesini kullanan bir test de yer alıyordu 😅.

Sonuç olarak, ilk klasörü export ifadeleri içerecek şekilde güncellemek yaklaşık bir hafta ve yeniden yüklemeyle birden fazla deneme aldı.

import-aşama

Tüm simgeler hem export-ifadeleri kullanılarak dışa aktarıldıktan hem de genel kapsamda (eski) kaldıktan sonra, ES içe aktarma işlemlerini kullanmak için tüm dosyalar arası simge referanslarını güncellememiz gerekti. Nihai hedef, tüm "eski dışa aktarma nesnelerini" kaldırarak global kapsamı temizlemektir. Dönüşüm, her klasör için bir komut dosyası çalıştırılarak otomatik hale gelir.

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

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

Bu resimler ş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şımın bazı sakıncaları vardı:

  1. Her simge Module.File.symbolName olarak adlandırılmamıştır. Bazı simgeler yalnızca Module.File veya hatta Module.CompletelyDifferentName olarak adlandırılıyordu. Bu tutarsızlık, eski genel nesneden içe aktarılan yeni nesneye dahili bir eşleme oluşturmamız gerektiği anlamına geliyordu.
  2. Bazen moduleScoped adları arasında çakışmalar olabilir. En belirgin olarak, her sembolün yalnızca Events olarak adlandırıldığı belirli Events türlerini beyan etme kalıbı kullandık. Bu, farklı dosyalarda tanımlanan birden fazla etkinlik türünü dinliyorsanız bu Events için import-ifadesinde bir ad çakışması yaşanacağı anlamına geliyordu.
  3. Dosyalar arasında döngüsel bağımlılık olduğu ortaya çıktı. Simgenin kullanımı tüm kod yüklendikten sonra olduğundan bu, global kapsam bağlamında sorunsuzdu. Ancak bir import'e ihtiyacınız varsa döngüsel bağımlılık açıkça belirtilir. DevTools'ta da olduğu gibi, genel kapsamlı kodunuzda yan etki işlevi çağrılarınız yoksa bu hemen bir sorun oluşturmaz. Sonuç olarak, dönüşümü güvenli hale getirmek için bazı düzenlemeler ve yeniden yapılandırmalar yapmak gerekti.

JavaScript modülleriyle yepyeni bir dünya

Eylül 2019'da başlayan işlemden 6 ay sonra, Şubat 2020'de ui/ klasöründe son temizleme işlemleri gerçekleştirildi. Bu, taşıma işleminin gayrı resmi olarak sona erdiğini işaret ediyordu. Havanın düzelmesinin ardından, taşıma işlemini 5 Mart 2020'de tamamlandı şeklinde resmi olarak işaretledik. 🎉

Artık DevTools'daki tüm modüller kod paylaşmak için JavaScript modüllerini kullanıyor. Eski testlerimiz için veya DevTools mimarisinin diğer bölümleriyle entegrasyon sağlamak amacıyla bazı sembolleri hâlâ global kapsama (module-legacy.js dosyalarına) yerleştiriyoruz. Bu özellikler zaman içinde kaldırılacak olsa da gelecekteki geliştirmeleri engellemeyecektir. Ayrıca JavaScript modüllerini kullanmamızla ilgili bir stil kılavuzumuzu da inceleyebilirsiniz.

İstatistikler

Bu taşıma işleminde yer alan CL (değişiklik listesi kısaltması - Gerrit'te bir değişikliği temsil eden terim - GitHub çekme isteğine benzer) sayısına dair muhafazakar tahminler çoğunlukla 2 mühendis tarafından gerçekleştirilen 250 CL civarındadır. Yapılan değişikliklerin boyutu hakkında kesin istatistiklerimiz yok ancak değişen satır sayısıyla ilgili muhafazakar bir tahmin (her CL için eklemeler ve silme işlemleri arasındaki mutlak farkın toplamı olarak hesaplanır) yaklaşık 30.000'dir (tüm DevTools ön uç kodunun yaklaşık %20'si).

export kullanan ilk dosya Chrome 79'da gönderildi ve Aralık 2019'da kararlı kanalında yayınlandı. import'e geçişle ilgili son değişiklik, Mayıs 2020'de kararlı sürümde yayınlanan Chrome 83'te kullanıma sunulmuştur.

Chrome kararlılığına gönderilmiş ve bu taşıma kapsamında kullanıma sunulan bir regresyondan haberdarız. Bilinmeyen bir default dışa aktarma nedeniyle komut menüsündeki snippet'lerin otomatik olarak tamamlanması bozuldu. Başka birkaç regresyonla da karşılaştık. Ancak otomatik test paketlerimiz ve Chrome Canary kullanıcılarımız bu sorunları bildirdi ve Chrome'un kararlı kullanıcılarına ulaşmadan önce bu sorunları düzelttik.

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

Öğrendiklerimiz

  1. Geçmişte alınan kararlar projeniz üzerinde uzun süreli bir etki yaratabilir. JavaScript modülleri (ve diğer modül biçimleri) uzun süredir kullanılsa da DevTools, taşıma işlemini haklı çıkaracak durumda değildi. Ne zaman geçiş yapacağınıza ve ne zaman yapmayacağınıza karar vermek zordur ve tahminlere dayanır.
  2. İlk zaman tahminlerimiz ay yerine hafta olarak yapılmıştı. Bu durum büyük ölçüde, ilk maliyet analizimizde beklediğimizden daha fazla beklenmedik sorun saptamış olmamızdan kaynaklanmaktadır. Geçiş planı sağlam olsa da teknik borç (istediğimizden çok daha fazla) engel teşkil etti.
  3. JavaScript modülleri taşıma işlemi, çok sayıda (ilgili görünmeyen) teknik borç temizlemesini içeriyordu. Modern ve standartlaştırılmış bir modül biçimine geçiş, kodlamayla ilgili en iyi uygulamalarımızı modern web geliştirmeyle yeniden uyumlu hale getirmemize olanak tanıdı. Örneğin, özel Python paketleyicimizi minimum bir Rollup yapılandırmasıyla değiştirebildik.
  4. Kod tabanımızdaki büyük etkiye rağmen (kodun yaklaşık% 20'si değişti) çok az sayıda gerileme bildirildi. İlk birkaç dosyayı taşırken çok sayıda sorun yaşadık ancak bir süre sonra sağlam ve kısmen otomatik bir iş akışına sahip olduk. Bu, bu taşıma işleminde sabit kullanıcılarımız üzerindeki olumsuz etkiyi en aza indirdiği anlamına geliyor.
  5. Bu tür bir taşımanın inceliklerini, bakıcılara öğretmek zor, bazen de imkansızdır. Bu ölçekteki taşıma işlemlerini takip etmek zordur ve çok fazla alan bilgisi gerekir. Bu alan bilgisini, aynı kod tabanında çalışan diğer kişilere aktarmak, yaptıkları iş açısından kendi başına istenilen bir durum değildir. Neleri paylaşacağınızı ve hangi ayrıntıları paylaşmayacağınızı bilmek bir sanattır ancak gerekli bir sanattır. Bu nedenle, büyük taşıma işlemlerinin sayısını azaltmak veya en azından bu işlemleri aynı anda gerçekleştirmemek son derece önemlidir.

Önizleme kanallarını indirme

Chrome Canary, Yeni geliştirilenler veya Beta'yı varsayılan geliştirme tarayıcınız olarak kullanabilirsiniz. Bu önizleme kanalları en yeni Geliştirici Araçları özelliklerine erişmenizi, son teknoloji web platformu API'lerini test etmenizi ve sitenizdeki sorunları kullanıcılarınızdan önce bulmanızı sağlar.

Chrome Geliştirici Araçları Ekibi ile iletişime geçme

Yeni özellikler, güncellemeler veya Geliştirici Araçları ile ilgili başka herhangi bir konu hakkında konuşmak için aşağıdaki seçenekleri kullanın.