Tìm hiểu cách sử dụng các tính năng mới nhất của WebAssembly trong khi vẫn hỗ trợ người dùng trên tất cả các trình duyệt.
WebAssembly 1.0 đã được phát hành cách đây 4 năm, nhưng sự phát triển không dừng lại ở đó. Các tính năng mới được bổ sung thông qua quy trình chuẩn hoá đề xuất. Như thường lệ với các tính năng mới trên web, thứ tự triển khai và tiến trình của những tính năng đó có thể khác nhau đáng kể giữa các công cụ. Nếu muốn sử dụng các tính năng mới đó, bạn cần đảm bảo rằng không có người dùng nào bị bỏ lại. Trong bài viết này, bạn sẽ tìm hiểu một phương pháp để đạt được điều này.
Một số tính năng mới cải thiện kích thước mã bằng cách thêm các hướng dẫn mới cho các thao tác phổ biến, một số tính năng bổ sung hiệu suất mạnh mẽ, và một số tính năng khác cải thiện trải nghiệm của nhà phát triển cũng như khả năng tích hợp với phần còn lại của môi trường web.
Bạn có thể xem danh sách đầy đủ các đề xuất và giai đoạn tương ứng của chúng trong kho lưu trữ chính thức hoặc theo dõi trạng thái triển khai của các đề xuất đó trong công cụ trên trang lộ trình tính năng chính thức.
Để đảm bảo người dùng thuộc tất cả các trình duyệt có thể sử dụng ứng dụng của bạn, bạn cần phải tìm hiểu xem mình muốn sử dụng tính năng nào. Sau đó, hãy chia các thành phần đó thành các nhóm dựa trên khả năng hỗ trợ của trình duyệt. Sau đó, hãy biên dịch cơ sở mã riêng cho từng nhóm đó. Cuối cùng, về phía trình duyệt, bạn cần phát hiện các tính năng được hỗ trợ và tải gói JavaScript và Wasm tương ứng.
Tính năng chọn và nhóm
Hãy cùng tìm hiểu từng bước bằng cách chọn một số tập hợp tính năng tuỳ ý làm ví dụ. Giả sử tôi đã xác định rằng tôi muốn sử dụng SIMD, luồng và xử lý ngoại lệ trong thư viện của mình vì lý do kích thước và hiệu suất. Dịch vụ hỗ trợ trình duyệt của họ như sau:
Bạn có thể chia trình duyệt thành các nhóm thuần tập sau đây để đảm bảo rằng mỗi người dùng đều có được trải nghiệm được tối ưu hoá nhất:
- Trình duyệt dựa trên Chrome: Tất cả luồng, SIMD và xử lý ngoại lệ đều được hỗ trợ.
- Firefox: Thread và SIMD được hỗ trợ, nhưng không hỗ trợ xử lý ngoại lệ.
- Safari: Các luồng được hỗ trợ, SIMD và xử lý ngoại lệ thì không.
- Các trình duyệt khác: giả định chỉ hỗ trợ WebAssembly cơ sở.
Bảng chi tiết này được chia theo tính năng hỗ trợ trong phiên bản mới nhất của từng trình duyệt. Các trình duyệt hiện đại luôn cập nhật và tự động cập nhật, vì vậy, trong hầu hết trường hợp, bạn chỉ cần lo lắng về bản phát hành mới nhất. Tuy nhiên, miễn là bạn đưa WebAssembly cơ sở vào nhóm thuần tập dự phòng, bạn vẫn có thể cung cấp một ứng dụng hoạt động ngay cả đối với những người dùng có trình duyệt đã lỗi thời.
Biên dịch cho nhiều nhóm tính năng
WebAssembly không có cách tích hợp để phát hiện các tính năng được hỗ trợ trong thời gian chạy, do đó, tất cả hướng dẫn trong mô-đun phải được hỗ trợ trên mục tiêu. Do đó, bạn cần biên dịch mã nguồn vào Wasm riêng biệt cho từng nhóm tính năng.
Mỗi chuỗi công cụ và hệ thống xây dựng đều khác nhau, bạn cần tham khảo tài liệu của trình biên dịch của riêng mình để biết cách điều chỉnh các tính năng đó. Để đơn giản, tôi sẽ sử dụng một thư viện C++ gồm một tệp duy nhất trong ví dụ sau và hướng dẫn cách biên dịch thư viện đó bằng Emscripten.
Tôi sẽ sử dụng SIMD thông qua tính năng mô phỏng SSE2, luồng thông qua tính năng hỗ trợ thư viện Pthreads và chọn giữa xử lý ngoại lệ Wasm và triển khai JavaScript dự phòng:
# First bundle: threads + SIMD + Wasm exceptions
$ emcc main.cpp -o main.threads-simd-exceptions.mjs -pthread -msimd128 -msse2 -fwasm-exceptions
# Second bundle: threads + SIMD + JS exceptions fallback
$ emcc main.cpp -o main.threads-simd.mjs -pthread -msimd128 -msse2 -fexceptions
# Third bundle: threads + JS exception fallback
$ emcc main.cpp -o main.threads.mjs -pthread -fexceptions
# Fourth bundle: basic Wasm with JS exceptions fallback
$ emcc main.cpp -o main.basic.mjs -fexceptions
Bản thân mã C++ có thể sử dụng #ifdef __EMSCRIPTEN_PTHREADS__
và #ifdef __SSE2__
để chọn có điều kiện giữa các phương thức triển khai song song (luồng và SIMD) của cùng một hàm và các phương thức triển khai tuần tự tại thời điểm biên dịch. Mã sẽ có dạng như sau:
void process_data(std::vector<int>& some_input) {
#ifdef __EMSCRIPTEN_PTHREADS__
#ifdef __SSE2__
// …implementation using threads and SIMD for max speed
#else
// …implementation using threads but not SIMD
#endif
#else
// …fallback implementation for browsers without those features
#endif
}
Phương thức xử lý ngoại lệ không cần lệnh #ifdef
vì có thể sử dụng phương thức này theo cách tương tự từ C++ bất kể phương thức triển khai cơ bản được chọn thông qua cờ biên dịch.
Tải đúng gói
Sau khi tạo gói cho tất cả nhóm thuần tập theo tính năng, bạn cần tải đúng gói từ ứng dụng JavaScript chính. Để làm việc đó, trước tiên, hãy phát hiện những tính năng nào được hỗ trợ trong trình duyệt hiện tại. Bạn có thể thực hiện việc đó bằng thư viện wasm-feature-detect. Bằng cách kết hợp tính năng này với tính năng nhập động, bạn có thể tải gói được tối ưu hoá nhất trong bất kỳ trình duyệt nào:
import { simd, threads, exceptions } from 'https://unpkg.com/wasm-feature-detect?module';
let initModule;
if (await threads()) {
if (await simd()) {
if (await exceptions()) {
initModule = import('./main.threads-simd-exceptions.mjs');
} else {
initModule = import('./main.threads-simd.mjs');
}
} else {
initModule = import('./main.threads.mjs');
}
} else {
initModule = import('./main.basic.mjs');
}
const Module = await initModule();
// now you can use `Module` Emscripten object like you normally would
Lời kết
Trong bài đăng này, tôi đã hướng dẫn cách chọn, tạo và chuyển đổi giữa các gói cho các nhóm tính năng khác nhau.
Khi số lượng tính năng tăng lên, số lượng nhóm thuần tập tính năng có thể trở nên không thể duy trì. Để giảm thiểu vấn đề này, bạn có thể chọn nhóm thuần tập tính năng dựa trên dữ liệu người dùng thực tế, bỏ qua các trình duyệt ít phổ biến hơn và để chúng quay lại các nhóm thuần tập ít tối ưu hơn một chút. Miễn là ứng dụng của bạn vẫn hoạt động cho tất cả người dùng, phương pháp này có thể mang lại sự cân bằng hợp lý giữa tính năng cải tiến dần và hiệu suất trong thời gian chạy.
Trong tương lai, WebAssembly có thể có một phương pháp tích hợp để phát hiện các tính năng được hỗ trợ và chuyển đổi giữa các cách triển khai khác nhau của cùng một chức năng trong mô-đun. Tuy nhiên, cơ chế đó sẽ là một tính năng sau MVP mà bạn cần phát hiện và tải có điều kiện bằng phương pháp nêu trên. Cho đến lúc đó, phương pháp này vẫn là cách duy nhất để xây dựng và tải mã bằng các tính năng WebAssembly mới trên tất cả trình duyệt.