Như bạn có thể biết, Công cụ của Chrome cho nhà phát triển là một ứng dụng web được viết bằng HTML, CSS và JavaScript. Trong những năm qua, DevTools đã có nhiều tính năng hơn, thông minh hơn và hiểu biết hơn về nền tảng web rộng lớn hơn. Mặc dù DevTools đã mở rộng qua nhiều năm, nhưng kiến trúc của công cụ này phần lớn vẫn giống với kiến trúc ban đầu khi công cụ này vẫn là một phần của WebKit.
Bài đăng này là một phần trong một loạt bài đăng trên blog mô tả những thay đổi mà chúng tôi đang thực hiện đối với cấu trúc của DevTools và cách tạo cấu trúc đó. Chúng tôi sẽ giải thích cách hoạt động của DevTools trước đây, những lợi ích và hạn chế của công cụ này cũng như những việc chúng tôi đã làm để giảm thiểu các hạn chế này. Do đó, hãy cùng tìm hiểu sâu hơn về các hệ thống mô-đun, cách tải mã và cách chúng ta sử dụng các mô-đun JavaScript.
Ban đầu, không có gì
Mặc dù hiện tại, cảnh quan giao diện người dùng có nhiều hệ thống mô-đun với các công cụ được xây dựng xung quanh chúng, cũng như định dạng mô-đun JavaScript hiện đã được chuẩn hoá, nhưng không có công cụ nào trong số này tồn tại khi DevTools được xây dựng lần đầu tiên. DevTools được xây dựng dựa trên mã ban đầu được vận chuyển trong WebKit cách đây hơn 12 năm.
Lần đầu tiên đề cập đến hệ thống mô-đun trong DevTools là vào năm 2012: giới thiệu danh sách mô-đun có danh sách nguồn liên kết.
Đây là một phần của cơ sở hạ tầng Python được dùng để biên dịch và tạo DevTools.
Một thay đổi tiếp theo đã trích xuất tất cả các mô-đun vào một tệp frontend_modules.json
riêng biệt (commit) vào năm 2013, sau đó vào các tệp module.json
riêng biệt (commit) vào năm 2014.
Tệp module.json
mẫu:
{
"dependencies": [
"common"
],
"scripts": [
"StylePane.js",
"ElementsPanel.js"
]
}
Kể từ năm 2014, mẫu module.json
đã được sử dụng trong DevTools để chỉ định các mô-đun và tệp nguồn của mẫu đó.
Trong khi đó, hệ sinh thái web phát triển nhanh chóng và nhiều định dạng mô-đun được tạo ra, bao gồm UMD, CommonJS và cuối cùng là các mô-đun JavaScript được chuẩn hoá.
Tuy nhiên, Công cụ cho nhà phát triển sử dụng định dạng module.json
.
Mặc dù Công cụ cho nhà phát triển vẫn hoạt động, nhưng việc sử dụng hệ thống mô-đun không chuẩn hoá và độc đáo có một số nhược điểm:
- Định dạng
module.json
yêu cầu công cụ xây dựng tuỳ chỉnh, giống như các trình gói hiện đại. - Không có tính năng tích hợp IDE, điều này đòi hỏi phải có công cụ tuỳ chỉnh để tạo các tệp mà IDE hiện đại có thể hiểu được (tập lệnh ban đầu để tạo tệp jsconfig.json cho VS Code).
- Các hàm, lớp và đối tượng đều được đưa vào phạm vi toàn cục để có thể chia sẻ giữa các mô-đun.
- Các tệp phụ thuộc vào thứ tự, nghĩa là thứ tự liệt kê
sources
rất quan trọng. Không có gì đảm bảo rằng mã bạn dựa vào sẽ được tải, ngoài việc một người đã xác minh mã đó.
Nhìn chung, khi đánh giá trạng thái hiện tại của hệ thống mô-đun trong Công cụ cho nhà phát triển và các định dạng mô-đun khác (được sử dụng rộng rãi hơn), chúng tôi kết luận rằng mẫu module.json
đang tạo ra nhiều vấn đề hơn so với số lượng được giải quyết và đã đến lúc lên kế hoạch loại bỏ nó.
Lợi ích của tiêu chuẩn
Trong số các hệ thống mô-đun hiện có, chúng tôi đã chọn mô-đun JavaScript để di chuyển sang. Tại thời điểm đưa ra quyết định đó, các mô-đun JavaScript vẫn đang được vận chuyển sau một cờ trong Node.js và một lượng lớn gói có sẵn trên NPM không có gói mô-đun JavaScript mà chúng ta có thể sử dụng. Mặc dù vậy, chúng tôi kết luận rằng mô-đun JavaScript là lựa chọn tốt nhất.
Lợi ích chính của mô-đun JavaScript là đây là định dạng mô-đun được chuẩn hoá cho JavaScript.
Khi liệt kê các nhược điểm của module.json
(xem ở trên), chúng tôi nhận thấy rằng hầu hết các nhược điểm đó đều liên quan đến việc sử dụng định dạng mô-đun không chuẩn và riêng biệt.
Việc chọn định dạng mô-đun không chuẩn hoá có nghĩa là chúng ta phải tự đầu tư thời gian để tích hợp với các công cụ xây dựng và công cụ mà các nhà bảo trì của chúng ta sử dụng.
Những công cụ tích hợp này thường rắc rối và thiếu sự hỗ trợ cho các tính năng, đòi hỏi thêm thời gian bảo trì, nên đôi khi dẫn đến các lỗi nhỏ mà cuối cùng người dùng sẽ nhận được.
Vì các mô-đun JavaScript là tiêu chuẩn, nên các IDE như VS Code, trình kiểm tra loại như Closure Compiler/TypeScript và các công cụ xây dựng như Rollup/minifier sẽ có thể hiểu mã nguồn mà chúng ta đã viết.
Hơn nữa, khi một người duy trì mới tham gia nhóm DevTools, họ sẽ không phải mất thời gian tìm hiểu định dạng module.json
độc quyền, trong khi họ (có thể) đã quen thuộc với các mô-đun JavaScript.
Tất nhiên, khi DevTools được xây dựng ban đầu, không có lợi ích nào ở trên. Phải mất nhiều năm làm việc trong các nhóm tiêu chuẩn, triển khai thời gian chạy và nhà phát triển sử dụng các mô-đun JavaScript để đưa ra ý kiến phản hồi thì chúng tôi mới có được những thành quả như hiện tại. Nhưng khi các mô-đun JavaScript xuất hiện, chúng tôi có một lựa chọn: tiếp tục duy trì định dạng của riêng mình hoặc đầu tư vào việc di chuyển sang định dạng mới.
Chi phí của chiếc xe mới
Mặc dù các mô-đun JavaScript có nhiều lợi ích mà chúng ta muốn sử dụng, nhưng chúng ta vẫn ở trong thế giới module.json
không chuẩn.
Để khai thác các lợi ích của mô-đun JavaScript, chúng tôi phải đầu tư đáng kể vào việc dọn dẹp các khoản nợ kỹ thuật, thực hiện quá trình di chuyển có thể làm hỏng các tính năng và gây ra lỗi hồi quy.
Tại thời điểm này, câu hỏi không phải là "Chúng ta có muốn sử dụng mô-đun JavaScript không?" mà là "Chi phí để sử dụng mô-đun JavaScript là bao nhiêu?". Ở đây, chúng tôi phải cân bằng rủi ro gây lỗi cho người dùng bằng các lần hồi quy, chi phí mà các kỹ sư phải bỏ ra (một lượng lớn) thời gian di chuyển và trạng thái tạm thời tệ hơn mà chúng tôi sẽ làm việc.
Điểm cuối cùng đó hóa ra rất quan trọng. Mặc dù về lý thuyết, chúng ta có thể truy cập vào các mô-đun JavaScript, nhưng trong quá trình di chuyển, chúng ta sẽ phải kết thúc bằng mã phải tính đến cả mô-đun module.json
và JavaScript.
Điều này không chỉ khó đạt được về mặt kỹ thuật mà còn có nghĩa là tất cả kỹ sư làm việc trên DevTools đều cần biết cách làm việc trong môi trường này.
Họ sẽ phải liên tục tự hỏi "Đây là phần nào của cơ sở mã, là mô-đun module.json
hay JavaScript và làm cách nào để thay đổi?".
Xem trước: Chi phí tiềm ẩn để hướng dẫn các nhà bảo trì khác trong quá trình di chuyển lớn hơn chúng tôi dự kiến.
Sau khi phân tích chi phí, chúng tôi kết luận rằng vẫn nên di chuyển sang các mô-đun JavaScript. Do đó, các mục tiêu chính của chúng tôi là:
- Đảm bảo rằng việc sử dụng các mô-đun JavaScript mang lại lợi ích tối đa có thể.
- Đảm bảo rằng việc tích hợp với hệ thống dựa trên
module.json
hiện có là an toàn và không gây ảnh hưởng tiêu cực đến người dùng (lỗi hồi quy, người dùng khó chịu). - Hướng dẫn tất cả nhân viên bảo trì Công cụ cho nhà phát triển trong quá trình di chuyển, chủ yếu bằng các tính năng kiểm tra và cân bằng được tích hợp sẵn để ngăn chặn những sai sót vô tình.
Bảng tính, phép biến đổi và nợ kỹ thuật
Mặc dù mục tiêu đã rõ ràng, nhưng những hạn chế do định dạng module.json
áp đặt lại rất khó giải quyết.
Chúng tôi đã phải lặp lại nhiều lần, tạo nhiều nguyên mẫu và thay đổi cấu trúc trước khi phát triển một giải pháp mà chúng tôi cảm thấy hài lòng.
Chúng tôi đã viết tài liệu thiết kế với chiến lược di chuyển mà chúng tôi đã sử dụng.
Tài liệu thiết kế cũng liệt kê thời gian ước tính ban đầu của chúng tôi là: 2 đến 4 tuần.
Cảnh báo tiết lộ trước: phần quan trọng nhất của quá trình di chuyển mất 4 tháng và từ đầu đến cuối mất 7 tháng!
Tuy nhiên, kế hoạch ban đầu đã đứng vững trước thử thách của thời gian: chúng ta sẽ dạy môi trường thời gian chạy DevTools tải tất cả tệp được liệt kê trong mảng scripts
trong tệp module.json
theo cách cũ, trong khi tất cả tệp được liệt kê trong mảng modules
bằng mô-đun JavaScript nhập động.
Mọi tệp nằm trong mảng modules
đều có thể sử dụng tính năng nhập/xuất ES.
Ngoài ra, chúng ta sẽ thực hiện quá trình di chuyển trong 2 giai đoạn (cuối cùng, chúng ta sẽ chia giai đoạn cuối thành 2 giai đoạn phụ, xem bên dưới): giai đoạn export
và import
.
Trạng thái của mô-đun nào sẽ ở giai đoạn nào được theo dõi trong một bảng tính lớn:
Bạn có thể xem một đoạn mã của trang tính tiến trình tại đây.
export
-phase
Giai đoạn đầu tiên là thêm câu lệnh export
cho tất cả các ký hiệu dự kiến được chia sẻ giữa các mô-đun/tệp.
Quá trình chuyển đổi sẽ được tự động hoá bằng cách chạy một tập lệnh cho mỗi thư mục.
Do biểu tượng sau sẽ tồn tại trong thế giới module.json
:
Module.File1.exported = function() {
console.log('exported');
Module.File1.localFunctionInFile();
};
Module.File1.localFunctionInFile = function() {
console.log('Local');
};
(Ở đây, Module
là tên của mô-đun và File1
là tên của tệp. Trong cây nguồn của chúng ta, đó sẽ là front_end/module/file1.js
.)
Mã này sẽ được chuyển đổi thành:
export function exported() {
console.log('exported');
Module.File1.localFunctionInFile();
}
export function localFunctionInFile() {
console.log('Local');
}
/** Legacy export object */
Module.File1 = {
exported,
localFunctionInFile,
};
Ban đầu, chúng tôi cũng dự định viết lại các lệnh nhập cùng tệp trong giai đoạn này.
Ví dụ: trong ví dụ trên, chúng ta sẽ viết lại Module.File1.localFunctionInFile
thành localFunctionInFile
.
Tuy nhiên, chúng tôi nhận thấy việc tự động hoá và áp dụng sẽ dễ dàng hơn và an toàn hơn nếu chúng ta tách riêng hai phép biến đổi này.
Do đó, "di chuyển tất cả các ký hiệu trong cùng một tệp" sẽ trở thành giai đoạn phụ thứ hai của giai đoạn import
.
Vì việc thêm từ khoá export
vào tệp sẽ chuyển đổi tệp từ "tập lệnh" thành "mô-đun", nên nhiều cơ sở hạ tầng DevTools đã phải được cập nhật cho phù hợp.
Trong đó bao gồm thời gian chạy (với tính năng nhập động) và các công cụ như ESLint
để chạy ở chế độ mô-đun.
Một điều chúng tôi phát hiện được trong khi giải quyết các vấn đề này là các chương trình kiểm thử của chúng tôi đang chạy ở chế độ "bất cẩn".
Vì các mô-đun JavaScript ngụ ý rằng các tệp chạy ở chế độ "use strict"
, nên điều này cũng sẽ ảnh hưởng đến các bài kiểm thử của chúng ta.
Hóa ra, một lượng lớn các bài kiểm thử đã dựa vào sự cẩu thả này, bao gồm cả một bài kiểm thử sử dụng câu lệnh with
😱.
Cuối cùng, việc cập nhật thư mục đầu tiên để đưa vào câu lệnh export
mất khoảng một tuần và nhiều lần thử với các lần khởi động lại.
import
-phase
Sau khi tất cả các biểu tượng được xuất bằng câu lệnh export
và vẫn nằm trong phạm vi toàn cục (cũ), chúng tôi phải cập nhật tất cả các tệp tham chiếu đến các biểu tượng trên nhiều tệp để sử dụng tính năng nhập ES.
Mục tiêu cuối cùng là xoá tất cả "đối tượng xuất cũ", dọn dẹp phạm vi chung.
Quá trình chuyển đổi sẽ được tự động hoá bằng cách chạy một tập lệnh cho mỗi thư mục.
Ví dụ: đối với các ký hiệu sau đây tồn tại trong thế giới module.json
:
Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
SameModule.AnotherFile.moduleScoped();
Các giá trị này sẽ được chuyển đổi thành:
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();
Tuy nhiên, có một số điểm cần lưu ý với phương pháp này:
- Không phải biểu tượng nào cũng được đặt tên là
Module.File.symbolName
. Một số ký hiệu chỉ được đặt tên làModule.File
hoặc thậm chí làModule.CompletelyDifferentName
. Sự không nhất quán này có nghĩa là chúng ta phải tạo một mối liên kết nội bộ từ đối tượng toàn cục cũ đến đối tượng mới được nhập. - Đôi khi sẽ có xung đột giữa các tên mô-đunScoped.
Nổi bật nhất là chúng ta đã sử dụng mẫu khai báo một số loại
Events
nhất định, trong đó mỗi biểu tượng chỉ được đặt tên làEvents
. Điều này có nghĩa là nếu bạn đang theo dõi nhiều loại sự kiện được khai báo trong các tệp khác nhau, thì xung đột tên sẽ xảy ra trên câu lệnhimport
cho cácEvents
đó. - Hóa ra, có các phần phụ thuộc vòng tròn giữa các tệp.
Điều này không có vấn đề gì trong ngữ cảnh phạm vi toàn cục, vì việc sử dụng biểu tượng là sau khi tất cả mã được tải.
Tuy nhiên, nếu bạn yêu cầu
import
, thì phần phụ thuộc vòng tròn sẽ được thể hiện rõ ràng. Đây không phải là vấn đề ngay lập tức, trừ phi bạn có các lệnh gọi hàm hiệu ứng phụ trong mã phạm vi toàn cục mà Công cụ cho nhà phát triển cũng có. Nhìn chung, bạn cần phải thực hiện một số thao tác chỉnh sửa và tái cấu trúc để quá trình chuyển đổi diễn ra an toàn.
Một thế giới hoàn toàn mới với các mô-đun JavaScript
Vào tháng 2 năm 2020, 6 tháng sau khi bắt đầu vào tháng 9 năm 2019, lần dọn dẹp cuối cùng đã được thực hiện trong thư mục ui/
.
Đây là dấu hiệu kết thúc không chính thức của quá trình di chuyển.
Sau khi để bụi bẩn, chúng tôi chính thức đánh dấu quá trình di chuyển là đã hoàn tất vào ngày 5 tháng 3 năm 2020. 🎉
Giờ đây, tất cả các mô-đun trong DevTools đều sử dụng mô-đun JavaScript để chia sẻ mã.
Chúng ta vẫn đặt một số ký hiệu trong phạm vi toàn cục (trong tệp module-legacy.js
) cho các chương trình kiểm thử cũ hoặc để tích hợp với các phần khác của cấu trúc DevTools.
Các tính năng này sẽ bị xoá theo thời gian, nhưng chúng tôi không coi đó là rào cản cho quá trình phát triển trong tương lai.
Chúng tôi cũng có hướng dẫn về cách sử dụng các mô-đun JavaScript.
Thống kê
Theo ước tính thận trọng, số lượng thay đổi (CL) (viết tắt của danh sách thay đổi – thuật ngữ dùng trong Gerrit để biểu thị một thay đổi – tương tự như yêu cầu lấy dữ liệu trên GitHub) liên quan đến quá trình di chuyển này là khoảng 250 thay đổi, chủ yếu do 2 kỹ sư thực hiện. Chúng tôi không có số liệu thống kê chính xác về quy mô thay đổi, nhưng ước tính thận trọng về số dòng đã thay đổi (tính bằng tổng chênh lệch tuyệt đối giữa số lần chèn và xoá cho mỗi CL) là khoảng 30.000 (~20% tổng số mã giao diện người dùng của DevTools).
Tệp đầu tiên sử dụng export
được vận chuyển trong Chrome 79, phát hành bản ổn định vào tháng 12 năm 2019.
Thay đổi cuối cùng cần di chuyển sang import
được vận chuyển trong Chrome 83, phát hành bản ổn định vào tháng 5 năm 2020.
Chúng tôi nhận thấy một lỗi hồi quy đã được gửi đến Chrome phiên bản ổn định và được giới thiệu trong quá trình di chuyển này.
Tính năng tự động hoàn thành đoạn mã trong trình đơn lệnh bị lỗi do lệnh xuất default
không liên quan.
Chúng tôi đã gặp một số trường hợp hồi quy khác, nhưng các bộ kiểm thử tự động và người dùng Chrome Canary đã báo cáo những trường hợp này và chúng tôi đã khắc phục trước khi các trường hợp này có thể tiếp cận người dùng Chrome phiên bản ổn định.
Bạn có thể xem toàn bộ hành trình (không phải tất cả CL đều được đính kèm vào lỗi này, nhưng hầu hết đều được đính kèm) được ghi lại trên crbug.com/1006759.
Điều chúng tôi học được
- Các quyết định được đưa ra trước đây có thể ảnh hưởng lâu dài đến dự án của bạn. Mặc dù các mô-đun JavaScript (và các định dạng mô-đun khác) đã có từ khá lâu, nhưng DevTools không thể biện minh cho việc di chuyển. Việc quyết định khi nào nên và không nên di chuyển thật khó khăn và phải dựa trên những suy đoán có kiến thức.
- Thời gian ước tính ban đầu của chúng tôi là tính bằng tuần chứ không phải tháng. Điều này chủ yếu là do chúng tôi phát hiện nhiều vấn đề ngoài dự kiến hơn so với dự kiến trong quá trình phân tích chi phí ban đầu. Mặc dù kế hoạch di chuyển rất chắc chắn, nhưng nợ kỹ thuật (thường xuyên hơn chúng tôi mong muốn) lại là rào cản.
- Quá trình di chuyển mô-đun JavaScript bao gồm một lượng lớn công việc dọn dẹp nợ kỹ thuật (có vẻ như không liên quan). Việc di chuyển sang định dạng mô-đun chuẩn hoá hiện đại cho phép chúng tôi điều chỉnh lại các phương pháp hay nhất về lập trình cho phù hợp với hoạt động phát triển web hiện đại. Ví dụ: chúng tôi có thể thay thế trình gói Python tuỳ chỉnh bằng cấu hình Cuộn lên tối thiểu.
- Mặc dù có tác động lớn đến cơ sở mã của chúng tôi (~20% mã đã thay đổi), nhưng rất ít trường hợp hồi quy được báo cáo. Mặc dù gặp nhiều vấn đề khi di chuyển một số tệp đầu tiên, nhưng sau một thời gian, chúng tôi đã có một quy trình làm việc vững chắc, được tự động hoá một phần. Điều này có nghĩa là tác động tiêu cực của quá trình di chuyển này đối với người dùng ổn định là rất nhỏ.
- Việc giảng dạy chi tiết của một cuộc di cư cụ thể cho những người bảo trì khác là việc khó khăn và đôi khi không thể thực hiện được. Việc di chuyển ở quy mô này rất khó theo dõi và đòi hỏi nhiều kiến thức chuyên môn. Việc chuyển kiến thức chuyên môn đó cho những người khác làm việc trong cùng một cơ sở mã không phải là điều mong muốn đối với công việc họ đang làm. Việc biết nên chia sẻ thông tin gì và không nên chia sẻ thông tin gì là một nghệ thuật, nhưng cũng là một nghệ thuật cần thiết. Do đó, điều quan trọng là phải giảm số lượng di chuyển lớn hoặc ít nhất là không thực hiện chúng cùng một lúc.
Tải kênh xem trước xuống
Hãy cân nhắc sử dụng Chrome Canary, Dev hoặc Beta làm trình duyệt phát triển mặc định. Các kênh xem trước này cho phép bạn sử dụng các tính năng mới nhất của DevTools, kiểm thử các API nền tảng web tiên tiến và giúp bạn tìm thấy vấn đề trên trang web của mình trước khi người dùng phát hiện ra!
Liên hệ với nhóm Công cụ của Chrome cho nhà phát triển
Hãy sử dụng các lựa chọn sau để thảo luận về các tính năng, bản cập nhật mới hoặc bất kỳ nội dung nào khác liên quan đến Công cụ cho nhà phát triển.
- Gửi ý kiến phản hồi và yêu cầu về tính năng cho chúng tôi tại crbug.com.
- Báo cáo sự cố của Công cụ cho nhà phát triển bằng cách sử dụng biểu tượng Tuỳ chọn khác > Trợ giúp > Báo cáo sự cố của Công cụ cho nhà phát triển trong Công cụ cho nhà phát triển.
- Gửi tweet đến @ChromeDevTools.
- Để lại bình luận về Tính năng mới trong video trên YouTube trong Công cụ cho nhà phát triển hoặc Mẹo về công cụ cho nhà phát triển trong các video trên YouTube.