Pembaruan arsitektur DevTools: bermigrasi ke modul JavaScript

Tim van der Lippe
Tim van der Lippe

Seperti yang mungkin Anda ketahui, Chrome DevTools adalah aplikasi web yang ditulis menggunakan HTML, CSS, dan JavaScript. Selama bertahun-tahun, DevTools menjadi lebih kaya fitur, lebih cerdas, dan memiliki pengetahuan tentang platform web yang lebih luas. Meskipun DevTools telah berkembang selama bertahun-tahun, arsitekturnya sebagian besar menyerupai arsitektur asli ketika masih menjadi bagian dari WebKit.

Postingan ini adalah bagian dari serangkaian postingan blog yang menjelaskan perubahan yang kami lakukan pada arsitektur DevTools dan cara pembuatannya. Kami akan menjelaskan bagaimana cara kerja DevTools secara historis, apa manfaat dan keterbatasannya, serta apa yang telah kami lakukan untuk mengatasi keterbatasan ini. Oleh karena itu, mari kita pelajari lebih dalam sistem modul, cara memuat kode dan bagaimana kita akhirnya menggunakan modul JavaScript.

Pada awalnya, tidak ada

Meskipun lanskap frontend saat ini memiliki berbagai sistem modul dengan alat yang dibangun di sekitarnya, serta format modul JavaScript yang sekarang distandardisasi, tidak satu pun dari sistem ini yang ada saat DevTools pertama kali di-build. DevTools dibuat di atas kode yang awalnya dikirimkan di WebKit lebih dari 12 tahun yang lalu.

Penyebutan sistem modul di DevTools pertama kali berasal dari tahun 2012: pengantaran daftar modul dengan daftar sumber terkait. Ini adalah bagian dari infrastruktur Python yang digunakan saat itu untuk mengompilasi dan membangun DevTools. Perubahan lanjutan mengekstrak semua modul ke dalam file frontend_modules.json terpisah (commit) pada tahun 2013, lalu menjadi file module.json terpisah (commit) pada tahun 2014.

Contoh file module.json:

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

Sejak 2014, pola module.json telah digunakan di DevTools untuk menentukan modul dan file sumbernya. Sementara itu, ekosistem web berkembang dengan cepat dan beberapa format modul dibuat, termasuk UMD, CommonJS, dan modul JavaScript yang pada akhirnya distandardisasi. Namun, DevTools macet dengan format module.json.

Sementara DevTools tetap berfungsi, ada beberapa kelemahan menggunakan sistem modul yang unik dan tidak standar:

  1. Format module.json memerlukan alat build kustom, yang mirip dengan pemaket modern.
  2. Tidak ada integrasi IDE, yang memerlukan alat kustom untuk menghasilkan file yang dapat dipahami oleh IDE modern (skrip asli untuk menghasilkan file jsconfig.json untuk VS Code).
  3. Fungsi, class, dan objek ditempatkan pada cakupan global untuk memungkinkan berbagi antar-modul.
  4. File bergantung pada urutan, yang berarti urutan sources yang dicantumkan adalah penting. Tidak ada jaminan bahwa kode yang Anda andalkan akan dimuat, selain bahwa kode tersebut telah diverifikasi oleh manusia.

Secara keseluruhan, saat mengevaluasi status sistem modul saat ini di DevTools dan format modul lainnya (yang lebih banyak digunakan), kita menyimpulkan bahwa pola module.json menciptakan lebih banyak masalah daripada yang dipecahkan, dan sekarang saatnya merencanakan untuk meninggalkannya.

Manfaat standar

Dari sistem modul yang ada, kami memilih modul JavaScript sebagai tujuan migrasi. Pada saat keputusan tersebut, modul JavaScript masih dikirim di belakang flag di Node.js dan sejumlah besar paket yang tersedia di NPM tidak memiliki paket modul JavaScript yang dapat kita gunakan. Meskipun demikian, kami menyimpulkan bahwa modul JavaScript adalah opsi terbaik.

Manfaat utama modul JavaScript adalah format modul standar untuk JavaScript. Ketika kami mencantumkan kekurangan module.json (lihat di atas), kami menyadari bahwa hampir semuanya terkait dengan penggunaan format modul yang unik dan tidak standar.

Memilih format modul yang tidak terstandardisasi berarti kami harus menginvestasikan waktu untuk membangun integrasi dengan alat dan alat build yang digunakan pengelola kami.

Integrasi ini sering kali rapuh dan tidak memiliki dukungan untuk fitur, yang membutuhkan waktu pemeliharaan tambahan, yang terkadang menyebabkan bug kecil yang pada akhirnya akan dikirimkan kepada pengguna.

Karena modul JavaScript merupakan standar, ini berarti IDE seperti VS Code, pemeriksa jenis seperti Closure Compiler/TypeScript dan alat build seperti Rollup/minifiers akan dapat memahami kode sumber yang kita tulis. Selain itu, ketika pengelola baru akan bergabung dengan tim DevTools, mereka tidak perlu menghabiskan waktu untuk mempelajari format module.json eksklusif, sedangkan mereka (kemungkinan) sudah terbiasa dengan modul JavaScript.

Tentu saja, ketika DevTools awalnya dibuat, tidak ada satu pun manfaat di atas yang tersedia. Dibutuhkan waktu bertahun-tahun pekerjaan dalam grup standar, implementasi runtime, dan developer menggunakan modul JavaScript yang memberikan masukan untuk mencapai titik yang ada sekarang. Namun, saat modul JavaScript tersedia, kami harus memilih: terus mempertahankan format kami sendiri, atau berinvestasi dalam bermigrasi ke format baru.

Biaya teknologi baru

Meskipun modul JavaScript memiliki banyak manfaat yang ingin kami gunakan, kita tetap berada di dunia module.json non-standar. Setelah menuai manfaat modul JavaScript, kami harus berinvestasi secara signifikan untuk menghapus utang teknis, melakukan migrasi yang berpotensi merusak fitur, dan menimbulkan bug regresi.

Pada tahap ini, pertanyaannya bukan "Apakah kita ingin menggunakan modul JavaScript?", tetapi pertanyaan tentang "Seberapa mahal penggunaan modul JavaScript?". Di sini, kami harus menyeimbangkan risiko pecahnya pengguna dengan regresi, biaya engineer yang menghabiskan (banyak waktu) waktu untuk bermigrasi, dan kondisi sementara yang lebih buruk yang akan menjadi tempat kerja kami.

Poin terakhir itu ternyata sangat penting. Meskipun secara teori kita dapat membuka modul JavaScript, selama migrasi, kita akan mendapatkan kode yang harus memperhitungkan baik modul module.json maupun JavaScript. Tidak hanya sulit untuk dicapai secara teknis ini, ini juga berarti bahwa semua engineer yang bekerja di DevTools perlu mengetahui cara bekerja di lingkungan ini. Mereka harus terus bertanya pada diri sendiri "Untuk bagian codebase ini, apakah ini modul module.json atau JavaScript dan bagaimana cara membuat perubahan?".

Cuplikan: Biaya tersembunyi membimbing rekan pengelola melalui migrasi lebih besar dari yang kami perkirakan.

Setelah analisis biaya, kami menyimpulkan bahwa migrasi ke modul JavaScript masih bermanfaat. Oleh karena itu, sasaran utama kami adalah sebagai berikut:

  1. Pastikan penggunaan modul JavaScript menuai manfaatnya semaksimal mungkin.
  2. Pastikan integrasi dengan sistem berbasis module.json yang ada aman dan tidak menimbulkan dampak negatif bagi pengguna (bug regresi, rasa frustrasi pengguna).
  3. Memandu semua pengelola DevTools selama migrasi, terutama dengan pemeriksaan dan keseimbangan bawaan untuk mencegah kesalahan yang tidak disengaja.

Spreadsheet, transformasi, dan utang teknis

Meskipun tujuannya jelas, batasan yang diberlakukan oleh format module.json terbukti sulit untuk diselesaikan. Dibutuhkan beberapa iterasi, prototipe, dan perubahan arsitektur sebelum kami mengembangkan solusi yang sesuai dengan keinginan kami. Kami menulis dokumen desain berisi strategi migrasi yang akhirnya kami buat. Dokumen desain juga mencantumkan perkiraan waktu awal kami: 2-4 minggu.

Peringatan {i>spoiler<i}: bagian paling intensif dari migrasi membutuhkan waktu 4 bulan dan dari awal hingga akhir membutuhkan waktu 7 bulan!

Namun, rencana awal telah diuji: kami akan mengajari runtime DevTools untuk memuat semua file yang tercantum dalam array scripts di file module.json menggunakan cara lama, sementara semua file yang tercantum dalam array modules dengan impor dinamis modul JavaScript. File apa pun yang berada dalam array modules akan dapat menggunakan impor/ekspor ES.

Selain itu, kami akan melakukan migrasi dalam 2 fase (akhirnya kami membagi fase terakhir menjadi 2 sub-fase, lihat di bawah): fase export dan import. Status modul akan berada di fase mana dilacak dalam {i>spreadsheet<i} besar:

Spreadsheet migrasi modul JavaScript

Cuplikan sheet progres tersedia untuk publik di sini.

export fase

Fase pertama adalah menambahkan pernyataan export untuk semua simbol yang seharusnya dibagikan di antara modul/file. Transformasi akan dilakukan secara otomatis, dengan menjalankan skrip per folder. Dengan mempertimbangkan simbol berikut akan ada di dunia module.json:

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

(Di sini, Module adalah nama modul dan File1 adalah nama filenya. Dalam sourcetree kita, itu akan menjadi front_end/module/file1.js.)

Ini akan diubah menjadi berikut:

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

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

Awalnya, rencana kami adalah menulis ulang impor file yang sama selama fase ini juga. Misalnya, dalam contoh di atas, kami akan menulis ulang Module.File1.localFunctionInFile menjadi localFunctionInFile. Namun, kami menyadari bahwa proses otomatisasi akan lebih mudah dan lebih aman jika kami memisahkan kedua transformasi ini. Oleh karena itu, "migrasikan semua simbol dalam file yang sama" akan menjadi sub-fase kedua dari fase import.

Karena penambahan kata kunci export dalam file akan mengubah file dari "skrip" menjadi "modul", banyak infrastruktur DevTools harus diupdate sebagaimana mestinya. Ini termasuk runtime (dengan impor dinamis), serta alat seperti ESLint untuk dijalankan dalam mode modul.

Satu penemuan yang kami temukan saat mengatasi masalah ini adalah bahwa pengujian kami berjalan dalam mode "ceroboh". Karena modul JavaScript menyiratkan bahwa file berjalan dalam mode "use strict", hal ini juga akan memengaruhi pengujian kami. Ternyata, pengujian yang sangat berat mengandalkan kecerobohan ini, termasuk pengujian yang menggunakan pernyataan with 😱.

Pada akhirnya, memperbarui folder pertama agar menyertakan pernyataan export memerlukan waktu sekitar satu minggu dan beberapa upaya pengiriman ulang.

import fase

Setelah semua simbol diekspor menggunakan pernyataan export dan tetap berada dalam cakupan global (lama), kita harus memperbarui semua referensi ke simbol lintas-file agar dapat menggunakan impor ES. Tujuan akhirnya adalah menghapus semua "objek ekspor lama", membersihkan cakupan global. Transformasi akan dilakukan secara otomatis, dengan menjalankan skrip per folder.

Misalnya, untuk simbol berikut yang ada di lingkup module.json:

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

Parameter tersebut akan diubah menjadi:

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();

Namun, ada beberapa peringatan terkait pendekatan ini:

  1. Tidak semua simbol diberi nama Module.File.symbolName. Beberapa simbol hanya diberi nama Module.File atau bahkan Module.CompletelyDifferentName. Inkonsistensi ini berarti kami harus membuat pemetaan internal dari objek global lama ke objek yang diimpor yang baru.
  2. Terkadang akan ada bentrokan antara nama moduleScoped. Yang paling jelas, kami menggunakan pola deklarasi jenis Events tertentu, dengan setiap simbol hanya diberi nama Events. Artinya, jika Anda memproses beberapa jenis peristiwa yang dideklarasikan dalam file yang berbeda, konflik nama akan terjadi pada pernyataan import untuk Events tersebut.
  3. Ternyata, ada dependensi sirkular di antara file. Hal ini tidak menjadi masalah dalam konteks cakupan global, karena penggunaan simbol terjadi setelah semua kode dimuat. Namun, jika Anda memerlukan import, dependensi sirkular akan dijadikan eksplisit. Ini bukan masalah secara langsung, kecuali Anda memiliki panggilan fungsi efek samping dalam kode cakupan global, yang juga dimiliki DevTools. Secara keseluruhan, diperlukan beberapa operasi dan pemfaktoran ulang untuk membuat transformasi yang aman.

Dunia yang benar-benar baru dengan modul JavaScript

Pada Februari 2020, 6 bulan setelah dimulai pada bulan September 2019, pembersihan terakhir dilakukan di folder ui/. Hal ini menandai berakhirnya migrasi secara tidak resmi. Setelah semuanya teratasi, kami secara resmi menandai migrasi sebagai selesai pada 5 Maret 2020. 🎉

Sekarang, semua modul di DevTools menggunakan modul JavaScript untuk berbagi kode. Kita masih menempatkan beberapa simbol pada cakupan global (dalam file module-legacy.js) untuk pengujian lama atau untuk mengintegrasikan dengan bagian lain dari arsitektur DevTools. Fitur ini akan dihapus seiring waktu, tetapi kami tidak menganggapnya sebagai penghambat pengembangan di masa mendatang. Kami juga memiliki panduan gaya untuk penggunaan modul JavaScript.

Statistik

Perkiraan konservatif untuk jumlah CL (singkatan dari changelist - istilah yang digunakan di Gerrit yang mewakili perubahan - mirip dengan permintaan pull GitHub) yang terlibat dalam migrasi ini adalah sekitar 250 CL, sebagian besar dilakukan oleh 2 engineer. Kami tidak memiliki statistik definitif tentang ukuran perubahan yang dibuat, tetapi perkiraan baris yang berubah secara konservatif (dihitung sebagai jumlah perbedaan absolut antara penyisipan dan penghapusan untuk setiap CL) adalah sekitar 30.000 (~20% dari semua kode frontend DevTools).

File pertama yang menggunakan export yang dikirimkan di Chrome 79, dirilis ke stabil pada Desember 2019. Perubahan terakhir untuk migrasi ke import yang dikirimkan di Chrome 83, dirilis ke stabil pada Mei 2020.

Kami mengetahui satu regresi yang dikirim ke Chrome versi stabil dan yang diperkenalkan sebagai bagian dari migrasi ini. Pelengkapan otomatis cuplikan di menu perintah pecah karena ekspor default yang tidak relevan. Kami telah mengalami beberapa regresi lain, tetapi rangkaian pengujian otomatis dan pengguna Chrome Canary melaporkan masalah tersebut dan kami memperbaikinya sebelum fitur tersebut dapat menjangkau pengguna Chrome yang stabil.

Anda dapat melihat perjalanan lengkapnya (tidak semua CL dilampirkan ke bug ini, tetapi sebagian besarnya) dicatat di crbug.com/1006759.

Yang kami pelajari

  1. Keputusan yang diambil di masa lalu dapat berdampak jangka panjang pada proyek Anda. Meskipun modul JavaScript (dan format modul lainnya) tersedia untuk beberapa waktu, DevTools tidak dapat menjadi dasar migrasi. Memutuskan kapan waktunya untuk migrasi adalah hal yang sulit dan berdasarkan perkiraan yang telah matang.
  2. Perkiraan waktu awal kami adalah dalam hitungan minggu, bukan bulan. Hal ini sebagian besar berasal dari fakta bahwa kami menemukan lebih banyak masalah tak terduga daripada yang kami perkirakan dalam analisis biaya awal. Meskipun rencana migrasi solid, utang teknis (lebih sering daripada yang kami inginkan) adalah penghambat.
  3. Migrasi modul JavaScript mencakup sejumlah besar pembersihan utang teknis (yang tampaknya tidak terkait). Migrasi ke format modul berstandar modern memungkinkan kami menyesuaikan praktik terbaik coding dengan pengembangan web modern. Misalnya, kita dapat mengganti pemaket Python kustom dengan konfigurasi Rollup minimal.
  4. Meskipun dampaknya besar pada codebase kami (~20% kode berubah), sangat sedikit regresi yang dilaporkan. Meskipun kami memang menemui banyak masalah saat memigrasikan beberapa file pertama, setelah beberapa saat kami memiliki alur kerja yang solid dan sebagian otomatis. Artinya, dampak negatif terhadap pengguna stabil kami minimal untuk migrasi ini.
  5. Mengajarkan seluk-beluk migrasi tertentu kepada sesama pengelola adalah hal yang sulit dan terkadang tidak mungkin dilakukan. Migrasi dalam skala ini sulit diikuti dan membutuhkan banyak pengetahuan domain. Mentransfer pengetahuan domain tersebut ke orang lain yang bekerja di codebase yang sama tidak diinginkan untuk pekerjaan yang mereka lakukan. Mengetahui apa yang harus dibagikan dan detail apa yang tidak boleh dibagikan adalah seni, tetapi sebuah hal yang penting. Oleh karena itu, penting untuk mengurangi jumlah migrasi besar, atau setidaknya tidak melakukannya secara bersamaan.

Mendownload saluran pratinjau

Pertimbangkan untuk menggunakan Chrome Canary, Dev, atau Beta sebagai browser pengembangan default Anda. Saluran pratinjau ini memberi Anda akses ke fitur DevTools terbaru, menguji API platform web tercanggih, dan menemukan masalah di situs Anda sebelum pengguna melakukannya.

Menghubungi tim Chrome DevTools

Gunakan opsi berikut untuk membahas fitur dan perubahan baru di postingan, atau hal lain yang berkaitan dengan DevTools.

  • Kirim saran atau masukan kepada kami melalui crbug.com.
  • Laporkan masalah DevTools menggunakan Opsi lainnya   Lainnya   > Bantuan > Laporkan masalah DevTools di DevTools.
  • Tweet di @ChromeDevTools.
  • Berikan komentar di video YouTube Apa yang baru di DevTools atau video YouTube Tips DevTools.