Tìm hiểu cách sử dụng các tính năng mới nhất của WebAssembly trong khi 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 thêm vào thông qua quy trình tiêu chuẩn hoá đề xuất. Như thường lệ với các tính năng mới trên web, thứ tự và tiến trình triển khai chú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 của mình bị bỏ sót. 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 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 nguyên gốc 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 web.
Bạn có thể tìm thấy danh sách đầy đủ các đề xuất và các giai đoạn tương ứng trong kho lưu trữ chính thức hoặc theo dõi trạng thái triển khai trong các công cụ trên trang lộ trình tính năng chính thức.
Để đảm bảo rằng người dùng của tất cả các trình duyệt đều có thể sử dụng ứng dụng của bạn, bạn cần tìm ra những tính năng bạn muốn sử dụng. Sau đó, hãy chia chúng thành nhiều nhóm dựa trên khả năng hỗ trợ 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, ở 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.
Các tính năng chọn và nhóm
Hãy cùng tìm hiểu các bước đó bằng cách chọn một nhóm tính năng tuỳ ý làm ví dụ. Giả sử tôi đã xác định được mình muốn sử dụng SIMD, luồng và xử lý ngoại lệ trong thư viện vì lý do kích thước và hiệu suất. Sau đây là Hỗ trợ trình duyệt của các dịch vụ này:
![Bảng thể hiện khả năng trình duyệt hỗ trợ các tính năng đã chọn.](https://web.developers.google.cn/static/articles/webassembly-feature-detection/image/a-table-showing-browser-s-d37cca41e05a3.png?authuser=5&hl=vi)
Bạn có thể chia các trình duyệt thành các nhóm thuần tập sau để đảm bảo rằng mỗi người dùng đều có được trải nghiệm tối ưu hoá nhất:
- Các trình duyệt dựa trên Chrome: Xử lý luồng, SIMD và ngoại lệ đều được hỗ trợ.
- Firefox: Thread và SIMD được hỗ trợ, không hỗ trợ xử lý ngoại lệ.
- Safari: Luồng được hỗ trợ, xử lý SIMD và ngoại lệ thì không.
- Các trình duyệt khác: giả sử chỉ hỗ trợ WebAssembly cơ sở.
Bảng chi tiết này được chia theo phần hỗ trợ tính năng trong phiên bản mới nhất của mỗi trình duyệt. Các trình duyệt hiện đại luôn tự động cập nhật và luôn cập nhật lâu dài, 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 làm 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ả cho những người dùng có trình duyệt lỗi thời.
Biên dịch cho các bộ tính năng khác nhau
WebAssembly không có phương pháp tích hợp sẵn để 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 thành Wasm riêng 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, và bạn cần tham khảo tài liệu về 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 thư viện C++ gồm một tệp trong ví dụ sau và trình bày cách biên dịch thư viện đó bằng Emscripten.
Tôi sẽ dùng SIMD thông qua mô phỏng SSE2, các luồng qua chức năng hỗ trợ thư viện Pthreads và chọn giữa Xử lý ngoại lệ cấu hình 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__
để lựa chọn có điều kiện giữa phương thức triển khai song song (chuỗi và SIMD) của cùng một hàm và phương thức triển khai nối tiếp tại thời điểm biên dịch. Mã sẽ 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
}
Việc xử lý ngoại lệ không cần lệnh #ifdef
vì bạn có thể sử dụng lệnh này theo cách tương tự trong 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.
Đang tải đúng gói
Sau khi đã tạo các gói cho tất cả nhóm thuần tập 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 được hỗ trợ trong trình duyệt hiện tại. Bạn có thể làm việc đó bằng thư viện wasm-feature-detect. Bằng cách kết hợp gói 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
Các từ cuối cùng
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 tính năng có thể sẽ không còn duy trì được. Để khắc phục 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 trong thực tế, bỏ qua các trình duyệt ít phổ biến hơn và để họ quay lại nhóm thuần tập 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 đến sự cân bằng hợp lý giữa tính năng nâng cao tăng dần và hiệu suất trong thời gian chạy.
Trong tương lai, WebAssembly có thể có được phương pháp tích hợp sẵn để phát hiện các tính năng được hỗ trợ và chuyển đổi giữa các phương thức triển khai khác nhau của cùng một chức năng trong mô-đun. Tuy nhiên, bản thân cơ chế như vậy sẽ là một tính năng sau MVP mà bạn cần phát hiện và tải theo điều kiện bằng cách sử dụng phương pháp 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.