Trong 2 năm qua, nhóm kỹ thuật của Goodnotes đã làm việc trong một dự án nhằm mang ứng dụng ghi chú thành công trên iPad sang các nền tảng khác. Nghiên cứu điển hình này đề cập đến việc ứng dụng iPad năm 2022 đã được xuất bản trên web, ChromeOS, Android và Windows nhờ các công nghệ web, cũng như sử dụng lại WebAssembly cùng một mã Swift mà nhóm chúng tôi đã xây dựng trong hơn 10 năm.
Lý do Goodnotes đã có mặt trên web, Android và Windows
Năm 2021, Goodnotes chỉ được cung cấp dưới dạng một ứng dụng dành cho iOS và iPad. Nhóm kỹ thuật tại Goodnotes đã chấp nhận một thách thức lớn về kỹ thuật: tạo một phiên bản mới của Goodnotes nhưng là dành cho các hệ điều hành và nền tảng khác. Sản phẩm phải hoàn toàn tương thích và hiển thị các ghi chú giống như ứng dụng iOS. Mọi ghi chú được chụp trên tệp PDF hoặc bất kỳ hình ảnh nào được đính kèm đều phải tương đương và hiển thị cùng một nét vẽ mà ứng dụng iOS hiển thị. Mọi nét vẽ được thêm vào phải tương đương với nét mà người dùng iOS có thể tạo, độc lập với công cụ mà người dùng đang sử dụng (ví dụ: bút, bút đánh dấu, bút máy, hình dạng hoặc tẩy).
Dựa trên các yêu cầu và kinh nghiệm của nhóm kỹ thuật, đội ngũ này đã nhanh chóng kết luận rằng việc sử dụng lại cơ sở mã Swift là phương án hành động tốt nhất, vì mã này đã được viết và thử nghiệm kỹ lưỡng qua nhiều năm. Nhưng tại sao không chỉ chuyển ứng dụng iOS/iPad đã có sang một nền tảng hoặc công nghệ khác như Flutter hoặc Compose Multiplatform? Để chuyển sang một nền tảng mới, bạn cần viết lại Goodnotes. Làm như vậy có thể bắt đầu một cuộc đua phát triển giữa ứng dụng iOS đã triển khai và một ứng dụng cần được tạo từ không có ứng dụng mới nào, hoặc liên quan đến việc ngừng phát triển mới trên ứng dụng hiện có trong khi cơ sở mã mới đã bắt kịp. Nếu Goodnotes có thể sử dụng lại mã Swift, thì nhóm có thể hưởng lợi từ các tính năng mới do nhóm iOS triển khai trong khi nhóm đa nền tảng đang nghiên cứu các nguyên tắc cơ bản về ứng dụng và tiếp cận các tính năng tương đương.
Sản phẩm này đã giải quyết được một số thách thức thú vị cho iOS khi thêm các tính năng như:
- Kết xuất ghi chú.
- Đồng bộ hoá tài liệu và ghi chú.
- Giải quyết xung đột cho các ghi chú bằng Các loại dữ liệu được sao chép không có xung đột.
- Phân tích dữ liệu để đánh giá mô hình AI.
- Tìm kiếm nội dung và lập chỉ mục tài liệu.
- Trải nghiệm cuộn tuỳ chỉnh và ảnh động.
- Xem quy trình triển khai mô hình cho tất cả các lớp giao diện người dùng.
Tất cả các tính năng này sẽ dễ triển khai hơn cho các nền tảng khác nếu nhóm kỹ thuật có thể lấy được cơ sở mã iOS đã sử dụng cho các ứng dụng iOS và iPad và thực thi những mã này như một phần của dự án Goodnotes có thể được gửi dưới dạng Windows, Android hoặc ứng dụng web.
Nhóm công nghệ của Goodnotes
May mắn là đã có cách để sử dụng lại mã Swift hiện có trên web—WebAssembly (Wasm). Goodnotes đã xây dựng một nguyên mẫu bằng Wasm với dự án nguồn mở và do cộng đồng duy trì SwiftWasm. Với SwiftOncem, nhóm Goodnotes có thể tạo tệp nhị phân Wasm bằng tất cả mã Swift đã triển khai. Tệp nhị phân này có thể được đưa vào một trang web được phân phối dưới dạng Ứng dụng web tiến bộ dành cho Android, Windows, ChromeOS và mọi hệ điều hành khác.
Mục tiêu là phát hành Goodnotes dưới dạng một ứng dụng web tiến bộ (PWA) và có thể đăng những ghi chú này trên cửa hàng của mọi nền tảng. Ngoài Swift, ngôn ngữ lập trình đã được sử dụng cho iOS và WebAssembly dùng để thực thi mã Swift trên web, dự án này cũng sử dụng các công nghệ sau:
- TypeScript: Ngôn ngữ lập trình được dùng thường xuyên nhất trong các công nghệ web.
- React và webpack: Khung và trình đóng gói phổ biến nhất cho web.
- PWA và trình chạy dịch vụ: Trình hỗ trợ rất lớn cho dự án này vì nhóm có thể chuyển ứng dụng của chúng tôi dưới dạng một ứng dụng ngoại tuyến hoạt động như mọi ứng dụng iOS khác và bạn có thể cài đặt ứng dụng đó từ cửa hàng hoặc chính trình duyệt.
- PWABuilder: Lưu ý chính của dự án dùng để gói PWA vào một tệp nhị phân gốc của Windows để nhóm có thể phân phối ứng dụng của chúng tôi từ Microsoft Store.
- Hoạt động đáng tin cậy trên web: Công nghệ Android quan trọng nhất mà công ty sử dụng để phân phối PWA dưới dạng một ứng dụng gốc nâng cao.
Hình sau đây cho thấy những gì được triển khai bằng TypeScript và React cổ điển, cũng như những gì được triển khai bằng SwiftOncem và vanilla JavaScript, Swift và WebAssembly. Phần này của dự án sử dụng JSKit, một thư viện khả năng tương tác với JavaScript dành cho Swift và WebAssembly mà nhóm sử dụng để xử lý DOM trong màn hình trình chỉnh sửa từ mã Swift khi cần hoặc thậm chí là sử dụng một số API dành riêng cho trình duyệt.
Tại sao nên sử dụng Wasm và web?
Mặc dù Wasm không được Apple chính thức hỗ trợ, nhưng sau đây là những lý do khiến nhóm kỹ thuật của Goodnotes cho rằng phương pháp này là quyết định sáng suốt nhất:
- Việc sử dụng lại hơn 100 nghìn dòng mã.
- Khả năng tiếp tục phát triển sản phẩm cốt lõi, đồng thời đóng góp cho các ứng dụng đa nền tảng.
- Sức mạnh của việc tiếp cận mọi nền tảng càng sớm càng tốt bằng cách sử dụng quy trình phát triển lặp lại.
- Có quyền kiểm soát để hiển thị cùng một tài liệu mà không cần sao chép toàn bộ logic nghiệp vụ, đồng thời tạo ra sự khác biệt trong các cách triển khai của chúng tôi.
- Hưởng lợi từ tất cả các điểm cải tiến về hiệu suất được thực hiện trên mọi nền tảng cùng một lúc (và tất cả các bản sửa lỗi được triển khai trên mọi nền tảng).
Việc sử dụng lại hơn 100 nghìn dòng mã và logic nghiệp vụ triển khai quy trình kết xuất của chúng tôi là các yếu tố cơ bản. Đồng thời, việc giúp mã Swift tương thích với các chuỗi công cụ khác cho phép họ sử dụng lại mã này trên nhiều nền tảng trong tương lai nếu cần.
Phát triển sản phẩm lặp lại
Đội ngũ này đã áp dụng một phương pháp lặp đi lặp lại để cung cấp một sản phẩm cho người dùng nhanh nhất có thể. Lưu ý bắt đầu bằng phiên bản chỉ đọc của sản phẩm, trong đó người dùng có thể lấy bất kỳ tài liệu chia sẻ nào và đọc trên bất kỳ nền tảng nào. Chỉ với một đường liên kết, họ có thể truy cập và đọc chính những ghi chú mà họ đã viết trên iPad. Giai đoạn tiếp theo đã bổ sung các tính năng chỉnh sửa, để làm cho các phiên bản trên nhiều nền tảng tương đương với phiên bản iOS.
Phiên bản đầu tiên của sản phẩm chỉ đọc mất 6 tháng để phát triển, 9 tháng tiếp theo dành riêng cho nhóm các tính năng chỉnh sửa đầu tiên và màn hình giao diện người dùng, nơi bạn có thể xem tất cả tài liệu bạn đã tạo hoặc người nào đó chia sẻ với bạn. Ngoài ra, các tính năng mới của nền tảng iOS có thể dễ dàng chuyển sang dự án đa nền tảng nhờ Chuỗi công cụ SwiftOncem. Ví dụ: một loại bút mới đã được tạo ra và dễ dàng triển khai trên nhiều nền tảng bằng cách sử dụng lại hàng nghìn dòng mã.
Việc xây dựng dự án này là một trải nghiệm tuyệt vời và Goodnotes đã học được rất nhiều điều từ dự án này. Đó là lý do những phần sau sẽ tập trung vào các điểm kỹ thuật thú vị về việc phát triển web cũng như cách sử dụng WebAssembly cũng như các ngôn ngữ như Swift.
Trở ngại ban đầu
Việc thực hiện dự án này là vô cùng khó khăn từ nhiều góc nhìn. Trở ngại đầu tiên mà nhóm phát hiện thấy có liên quan đến chuỗi công cụ SwiftOncem. Chuỗi công cụ này đóng vai trò hỗ trợ rất lớn cho đội ngũ, nhưng không phải đoạn mã iOS nào cũng tương thích với Wasm. Ví dụ: mã liên quan đến IO hoặc giao diện người dùng (như việc triển khai khung hiển thị, ứng dụng API hoặc quyền truy cập vào cơ sở dữ liệu) không thể sử dụng lại. Vì vậy, đội ngũ cần bắt đầu tái cấu trúc các phần cụ thể của ứng dụng để có thể sử dụng lại chúng trong giải pháp trên nhiều nền tảng. Hầu hết các PR mà nhóm đã tạo đều được tái cấu trúc thành các phần phụ thuộc trừu tượng. Nhờ đó, sau này nhóm có thể thay thế chúng bằng tính năng chèn phần phụ thuộc hoặc các chiến lược tương tự khác. Ban đầu, mã iOS kết hợp logic nghiệp vụ thô có thể được triển khai trong Wasm với mã chịu trách nhiệm về đầu vào/đầu ra và giao diện người dùng không thể triển khai trong Wasm vì Wasm không hỗ trợ. Vì vậy, mã IO và giao diện người dùng cần được triển khai lại trong TypeScript sau khi logic nghiệp vụ Swift đã sẵn sàng để sử dụng lại giữa các nền tảng.
Đã giải quyết vấn đề về hiệu suất
Sau khi Goodnotes bắt đầu xây dựng trình chỉnh sửa, nhóm này đã xác định được một số vấn đề về trải nghiệm chỉnh sửa và những hạn chế đầy thách thức về công nghệ đã được đưa vào lộ trình của chúng tôi. Vấn đề đầu tiên liên quan đến hiệu suất. JavaScript là một ngôn ngữ đơn luồng. Tức là có một ngăn xếp lệnh gọi và một vùng nhớ khối xếp bộ nhớ. Hàm này thực thi mã theo thứ tự và phải hoàn tất việc thực thi một đoạn mã trước khi chuyển sang phần mã tiếp theo. Hàm này đồng bộ, nhưng đôi khi có thể gây hại. Ví dụ: nếu một hàm mất một chút thời gian để thực thi hoặc phải chờ một hàm nào đó, thì hàm đó sẽ đóng băng mọi thứ trong thời gian chờ đợi. Và đây chính xác là những gì các kỹ sư phải giải quyết. Việc đánh giá một số đường dẫn cụ thể trong cơ sở mã của chúng tôi liên quan đến lớp hiển thị hoặc các thuật toán phức tạp khác là vấn đề đối với nhóm, vì các thuật toán này đồng bộ và việc thực thi các đường dẫn đó đang chặn luồng chính. Nhóm Goodnotes đã viết lại các thành phần này để giúp chúng hoạt động nhanh hơn và tái cấu trúc một số thành phần để khiến chúng không đồng bộ. Họ cũng đã giới thiệu một chiến lược lợi nhuận để ứng dụng có thể dừng quá trình thực thi thuật toán và tiếp tục sau đó, cho phép trình duyệt cập nhật giao diện người dùng và tránh hiện tượng rớt khung hình. Đây không phải là vấn đề đối với ứng dụng iOS vì ứng dụng này có thể sử dụng các luồng và đánh giá những thuật toán này ở chế độ nền trong khi luồng iOS chính cập nhật giao diện người dùng.
Một giải pháp khác mà nhóm kỹ thuật phải giải quyết là di chuyển giao diện người dùng dựa trên các phần tử HTML đi kèm với DOM sang giao diện người dùng tài liệu dựa trên canvas toàn màn hình. Dự án bắt đầu hiển thị tất cả ghi chú và nội dung liên quan đến một tài liệu trong cấu trúc DOM bằng cách sử dụng các phần tử HTML như mọi trang web khác, nhưng đến một thời điểm nào đó, chúng tôi đã chuyển sang canvas toàn màn hình để cải thiện hiệu suất trên các thiết bị cấp thấp bằng cách giảm thời gian trình duyệt xử lý các bản cập nhật DOM.
Nhóm kỹ thuật đã xác định những thay đổi sau đây là những điều có thể đã giảm thiểu một số vấn đề gặp phải, nếu họ đã thực hiện những thay đổi này vào đầu dự án.
- Giảm tải luồng chính nhiều hơn bằng cách sử dụng trình thực thi web thường xuyên cho các thuật toán nặng.
- Sử dụng thư viện tương tác JS-Swift đã xuất và các hàm đã nhập thay cho thư viện tương tác JS-Swift từ đầu để có thể giảm tác động của việc thoát khỏi ngữ cảnh hoạt động. Thư viện tương tác JavaScript này rất hữu ích trong việc truy cập vào DOM hoặc trình duyệt, nhưng lại chậm hơn các hàm gốc được xuất từ Wasm.
- Đảm bảo mã nguồn cho phép sử dụng
OffscreenCanvas
để ứng dụng có thể giảm tải luồng chính và di chuyển mọi hoạt động sử dụng Canvas API sang một trình chạy web nhằm tối đa hoá hiệu suất của ứng dụng khi viết ghi chú. - Di chuyển tất cả quá trình thực thi liên quan đến Wasm sang một trình thực thi web hoặc thậm chí một nhóm trình thực thi web để ứng dụng có thể giảm khối lượng công việc của luồng chính.
Trình chỉnh sửa văn bản
Một vấn đề thú vị khác liên quan đến một công cụ cụ thể là trình soạn thảo văn bản.
Việc triển khai iOS cho công cụ này dựa trên NSAttributedString
, một bộ công cụ nhỏ sử dụng RTF nâng cao. Tuy nhiên, cách triển khai này không tương thích với SwiftOncem nên
nhóm nhiều nền tảng buộc phải tạo
một trình phân tích cú pháp tuỳ chỉnh dựa trên ngữ pháp RTF
và sau đó triển khai trải nghiệm chỉnh sửa bằng cách chuyển đổi RTF thành HTML và
ngược lại. Trong khi đó, nhóm iOS đã bắt đầu triển khai công cụ mới này để thay thế việc sử dụng RTF bằng mô hình tuỳ chỉnh để ứng dụng có thể trình bày văn bản được tạo kiểu theo cách thân thiện với tất cả nền tảng dùng chung một mã Swift.
Thử thách này là một trong những điểm thú vị nhất trong lộ trình của dự án vì được giải quyết đi lặp lại dựa trên nhu cầu của người dùng. Đây là một vấn đề kỹ thuật được giải quyết bằng cách tiếp cận tập trung vào người dùng, trong đó nhóm cần viết lại một phần mã để có thể hiển thị văn bản, từ đó cho phép chỉnh sửa văn bản trong bản phát hành thứ hai.
Bản phát hành lặp lại
Quá trình phát triển của dự án này trong 2 năm qua thật đáng kinh ngạc. Nhóm đã bắt đầu làm việc với phiên bản chỉ có thể đọc của dự án và nhiều tháng sau đó đã ra mắt một phiên bản hoàn toàn mới với nhiều khả năng chỉnh sửa. Để thường xuyên phát hành các thay đổi về mã trong quá trình sản xuất, nhóm đã quyết định sử dụng rộng rãi cờ tính năng. Đối với mỗi bản phát hành, nhóm có thể bật các tính năng mới cũng như phát hành các thay đổi về mã triển khai các tính năng mới mà người dùng sẽ thấy vài tuần sau đó. Tuy nhiên, có một số điểm mà nhóm này cho rằng họ có thể cải thiện! Họ cho rằng việc giới thiệu hệ thống gắn cờ tính năng linh hoạt sẽ giúp đẩy nhanh tiến độ, vì hệ thống này sẽ giúp bạn không cần triển khai lại để thay đổi các giá trị của cờ. Việc này sẽ mang đến cho Goodnotes linh hoạt hơn, đồng thời đẩy nhanh quá trình triển khai tính năng mới vì Goodnotes sẽ không cần liên kết quá trình triển khai dự án với bản phát hành sản phẩm.
Công việc khi không có mạng
Một trong những tính năng chính mà nhóm phụ trách đã thực hiện là hỗ trợ ngoại tuyến. Khả năng chỉnh sửa và sửa đổi tài liệu là một tính năng mà bạn mong muốn ở mọi ứng dụng tương tự. Tuy nhiên, đây không phải là một tính năng đơn giản vì Goodnotes hỗ trợ hoạt động cộng tác. Điều này có nghĩa là tất cả thay đổi do người dùng khác nhau thực hiện trên các thiết bị khác nhau sẽ được thực hiện trên từng thiết bị mà không yêu cầu người dùng giải quyết bất kỳ xung đột nào. Goodnotes đã giải quyết vấn đề này từ lâu bằng cách sử dụng nâng cao CRDT. Nhờ các Loại dữ liệu được sao chép không có xung đột này, Goodnotes có thể kết hợp mọi thay đổi do người dùng bất kỳ thực hiện trên một tài liệu bất kỳ rồi hợp nhất các thay đổi mà không có xung đột hợp nhất nào. Việc sử dụng IndexedDB và bộ nhớ còn trống cho các trình duyệt web là yếu tố thúc đẩy rất lớn cho trải nghiệm cộng tác ngoại tuyến trên web.
Ngoài ra, việc mở ứng dụng web Goodnotes sẽ dẫn đến chi phí tải xuống trước ban đầu là khoảng 40 MB do kích thước tệp nhị phân của Wasm. Ban đầu, nhóm Goodnotes chỉ dựa vào bộ nhớ đệm thông thường của trình duyệt cho gói ứng dụng và hầu hết các điểm cuối API mà họ sử dụng, nhưng sau đó, nhóm dịch vụ này có thể thu lợi từ bộ nhớ đệm và trình chạy dịch vụ đáng tin cậy hơn trước đó. Ban đầu, nhóm tránh né nhiệm vụ này do mức độ phức tạp được giả định của nó, nhưng cuối cùng, họ nhận ra rằng Workbox khiến nó bớt đáng sợ.
Các đề xuất khi dùng Swift trên web
Nếu có một ứng dụng iOS có rất nhiều mã mà bạn muốn dùng lại, hãy chuẩn bị sẵn sàng vì bạn sắp bắt đầu một hành trình đáng kinh ngạc. Có một số mẹo mà bạn có thể thấy thú vị trước khi bắt đầu.
- Kiểm tra xem bạn muốn sử dụng lại mã nào. Nếu logic nghiệp vụ của ứng dụng được triển khai ở phía máy chủ, thì có thể bạn muốn sử dụng lại mã giao diện người dùng và Wasm sẽ không giúp bạn trong trường hợp này. Nhóm nghiên cứu đã xem xét sơ bộ Tokamak, một khung tương thích với SwiftUI để xây dựng các ứng dụng trình duyệt bằng WebAssembly, nhưng chưa hoàn thiện để đáp ứng nhu cầu của ứng dụng. Tuy nhiên, nếu ứng dụng của bạn có logic kinh doanh hoặc các thuật toán mạnh mẽ được triển khai trong mã ứng dụng, thì Wasm là người bạn tốt nhất của bạn.
- Đảm bảo cơ sở mã Swift đã sẵn sàng. Các mẫu thiết kế phần mềm cho lớp giao diện người dùng hoặc các cấu trúc cụ thể tạo ra sự phân tách chặt chẽ giữa logic giao diện người dùng và logic nghiệp vụ sẽ thực sự hữu ích vì bạn sẽ không thể sử dụng lại cách triển khai lớp giao diện người dùng. Kiến trúc sạch hoặc nguyên tắc kiến trúc lục giác cũng sẽ là yếu tố cơ bản, vì bạn sẽ phải chèn và cung cấp các phần phụ thuộc cho tất cả mã liên quan đến IO. Điều này sẽ dễ dàng hơn nếu bạn tuân theo các cấu trúc này, trong đó chi tiết triển khai được xác định là trừu tượng và nguyên tắc đảo ngược phần phụ thuộc được sử dụng nhiều.
- Wasm không cung cấp mã giao diện người dùng. Do đó, hãy quyết định khung giao diện người dùng mà bạn muốn sử dụng cho web.
- JSKit sẽ giúp bạn tích hợp mã Swift với JavaScript, nhưng xin lưu ý rằng nếu bạn có một đường dẫn nóng, việc vượt qua cầu nối JS–Swift có thể gây tốn kém và bạn sẽ cần phải thay thế mã đó bằng các hàm đã xuất. Bạn có thể tìm hiểu thêm về cách thức hoạt động của JSKit trong tài liệu chính thức và bài đăng Tra cứu thành viên động trong Swift – một tiện ích ẩn danh!.
- Việc bạn có thể sử dụng lại cấu trúc của mình hay không sẽ phụ thuộc vào cấu trúc mà ứng dụng của bạn theo dõi và thư viện cơ chế thực thi mã không đồng bộ mà bạn sử dụng. Các mẫu như MVVP hoặc cấu trúc thành phần kết hợp sẽ giúp bạn sử dụng lại các mô hình chế độ xem và một phần của logic giao diện người dùng mà không cần ghép phương thức triển khai với các phần phụ thuộc UIKit mà bạn không sử dụng được với Wasm. RXSwift và các thư viện khác có thể không tương thích với Wasm. Vì vậy, hãy lưu ý đến điều này vì bạn sẽ phải sử dụng OpenCombine, không đồng bộ/chờ và phát trực tuyến trong mã Swift của Goodnotes.
- Nén tệp nhị phân Wasm bằng gzip hoặc brotli. Xin lưu ý rằng kích thước của tệp nhị phân sẽ khá lớn đối với các ứng dụng web cổ điển.
- Ngay cả khi bạn có thể sử dụng Wasm mà không có PWA, hãy đảm bảo ít nhất bạn có một trình chạy dịch vụ, ngay cả khi ứng dụng web của bạn không có tệp kê khai hoặc bạn không muốn người dùng cài đặt ứng dụng này. Trình chạy dịch vụ sẽ lưu và phân phát tệp nhị phân Wasm miễn phí và tất cả tài nguyên ứng dụng, do đó, người dùng không cần tải xuống mỗi khi mở dự án của bạn.
- Xin lưu ý rằng việc tuyển dụng có thể khó hơn dự kiến. Có thể bạn cần thuê những nhà phát triển web giỏi có một số kinh nghiệm về Swift hoặc các nhà phát triển Swift vững mạnh có một số kinh nghiệm trên web. Nếu bạn có thể tìm được các kỹ sư tổng quát hoá có kiến thức về cả hai nền tảng thì thật tuyệt vời
Kết luận
Việc xây dựng một dự án web bằng một nhóm công nghệ phức tạp trong khi xử lý một sản phẩm đầy thách thức là một trải nghiệm tuyệt vời. Sẽ rất khó khăn, nhưng hoàn toàn xứng đáng. Lưu ý sẽ không bao giờ phát hành được phiên bản nào cho Windows, Android, ChromeOS và web trong khi xử lý các tính năng mới cho ứng dụng iOS nếu không sử dụng phương pháp này. Nhờ có nhóm công nghệ này và đội ngũ kỹ thuật của Goodnotes, giờ đây, Goodnotes có mặt ở khắp mọi nơi và cả nhóm đã sẵn sàng tiếp tục chinh phục những thử thách tiếp theo! Nếu muốn biết thêm về dự án này, bạn có thể xem bài phát biểu mà nhóm Goodnotes đưa ra tại NSSpanish 2023. Hãy nhớ dùng thử Ghi chú hay cho web!