Tìm hiểu cách sử dụng các tính năng WebAssembly mới nhất trong khi vẫn hỗ trợ người dùng trên tất cả trình duyệt.
WebAssembly 1.0 được phát hành cách đây 4 năm, nhưng quá trình phát triển vẫn chưa dừng lại ở đó. Các tính năng mới được thêm vào thông qua quy trình chuẩn hoá đề xuất. Thông thường, với các tính năng mới trên web, thứ tự và tiến trình triển khai 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 khác thêm các nguyên hàm 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 và khả năng tích hợp với phần còn lại của web.
Bạn có thể xem danh sách đầy đủ các đề xuất và giai đoạn tương ứng của các đề xuất đó 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 rằng người dùng của tất cả trình duyệt đều có thể sử dụng ứng dụng của bạn, bạn cần xác định những tính năng mà bạn muốn sử dụng. 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, ở 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.
Chọn và nhóm các tính năng
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: Hỗ trợ luồng và SIMD, không hỗ trợ xử lý ngoại lệ.
- Safari: Hỗ trợ luồng, không hỗ trợ SIMD và xử lý ngoại lệ.
- 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 cách 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 hàm trong mô-đun. Tuy nhiên, cơ chế như vậy tự nó 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 thời điểm đó, phương pháp này vẫn là cách duy nhất để tạo và tải mã bằng các tính năng WebAssembly mới trên tất cả trình duyệt.