基於WebAssembly開發網頁端
來源 https://zhuanlan.zhihu.com/p/162082688
序言
Qt for WebAssembly,是Qt在2018年釋出的技術,於5.12加入到Qt,官方對此技術介紹如下:
https://www.qt.io/blog/2018/05/22/qt-for-webassembly
簡單的說,這是一個讓Qt程式可以直接跑在web中的一個方法,具體流程如下:
使用Emscripten作為platfrom靜態編譯Qt工程,把整個工程和Qt環境打包編譯成wasm可執行檔案,配合html套殼一起載入到瀏覽器中,然後瀏覽器會提供一個虛擬化環境執行wasm,程式執行起來後所有的圖形結果透過一個canvas輸出。
相比之前WebGL技術這樣的遠端執行技術,這一次WebAssembly是真的把Qt程式跑在了瀏覽器本地上,實現了效能,效果的保證。總體靠譜得多。
關於WebAssembly詳細描述和資料,這裡不再累述,請直接參考Qt官方文件:
如果你想真的透過WebAssembly開發程式,我也建議務必看完以下連結裡所有資料,這會幫你節約很多時間
https://wiki.qt.io/Qt_for_WebAssembly
https://www.qt.io/cn/blog/2019/01/18/getting-started-qt-webassembly
https://www.youtube.com/watch?v=W3WC-VpKdGQ&t=1319s
作為一個面世2年的技術,這項技術已經被多位前輩介紹,但是大多都是草草帶過沒有涉及很多細節,因此本文會相對整體介紹,看完本文後可以構建一個比較滿意的工程。
環境準備
系統:Ubuntu 18.04 64bit
注:我是在虛擬機器裡面安裝的是原版系統,並且進行了常規更新(update/upgrade),我建議為了避免奇怪的環境問題,儘量使用虛擬機器安裝環境,雖然Qt支援Windows和macOS下使用WebAssembly,但是我不建議在這兩個系統下操作。
注2:請不要過多擔心繫統問題,因為編譯後的產物例如wasm、html,都是跨平臺的,無所謂是透過哪個系統編譯出來的。
Qt原始碼:qt-everywhere-src-5.14.2.tar.xz
原始碼下載地址:http://download.qt.io/archive/qt/5.14/5.14.2/single/
環境配置引數
sudo apt-get install vim git
sudo apt-get install gcc g++ make libgl1-mesa-dev libglu1-mesa-dev libssl1.0-dev
sudo apt-get install libssl-dev
sudo apt-get install libfontconfig1-dev libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libxi-dev libxrender-dev libxcb1-dev libx11-xcb-dev libxcb-glx0-dev libxkbcommon-x11-dev libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev libxcb-sync0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-randr0-dev libxcb-render-util0-dev
sudo apt-get install python
sudo apt-get install openjdk-8-jdk
Emscripten配置
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
git pull
./emsdk install sdk-fastcomp-1.38.30-64bit
./emsdk activate sdk-fastcomp-1.38.30-64bit
source ./emsdk_env.sh
em++ --version
注:Emscripten和Qt是有版本對應關係,版本不匹配可能導致編譯失敗以及執行時奇怪問題,具體對應關係請參考本文開頭髮的參考連結。我使用的Qt5.14.2
配套Emscripten1.38.27
或Emscripten1.38.30
注2:source ./emsdk_env.sh是每次開啟終端後必須執行的,他會初始化Emscripten環境
Qt編譯引數
xz -d ./qt-everywhere-src-5.14.2.tar.xz
tar -xvf ./qt-everywhere-src-5.14.2.tar
cd qt-everywhere-src-5.14.2
./configure -prefix ~/Develop/qt5.14.2_web -confirm-license -opensource -xplatform wasm-emscripten -nomake tools -nomake examples -nomake tests -skip qt3d -skip qtcharts -skip qtdatavis3d -skip qtdoc -skip qtgamepad -skip qtlocation -skip qtlottie -skip qtmacextras -skip qtmultimedia -skip qtnetworkauth -skip qtpurchasing -skip qtquick3d -skip qtremoteobjects -skip qtscxml -skip qtsensors -skip qtserialbus -skip qtserialport -skip qtspeech -skip qtsvg -skip qttools -skip qtvirtualkeyboard -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebglplugin -skip qtwebview -skip qtwinextras -no-feature-accessibility -no-feature-appstore-compliant -no-feature-big_codecs -no-feature-calendarwidget -no-feature-colordialog -no-feature-d3d12 -no-feature-filesystemwatcher -no-feature-fontcombobox -no-feature-fontdialog -no-feature-ftp -no-feature-imageformat_xbm -no-feature-lcdnumber -no-feature-library -no-feature-multiprocess -no-feature-pdf -no-feature-printdialog -no-feature-printpreviewdialog -no-feature-printpreviewwidget -no-feature-qml-debug -no-feature-qml-devtools -no-feature-qml-worker-script -no-feature-qml-xml-http-request -no-feature-quick-designer -no-feature-sha3-fast -no-feature-sharedmemory -no-feature-socks5 -no-feature-udpsocket -no-feature-whatsthis -no-feature-wizard -no-feature-xml-schema -no-feature-xmlstreamwriter
make -j6
make install
注:我的配置引數特別長,因為我對Qt進行了裁剪,這個裁剪能縮減大約1mb的wasm檔案體積,如果你覺得沒必要可以去掉-no-feature
開頭的那批引數
注2:相比編譯桌面版Qt,WebAssembly版的Qt程式HTTPS請求依託瀏覽器環境,因此這裡不需要配置ssl相關引數。也就是說編譯時不需要配置ssl環境就可以用HTTPS
使用
若一切成功,依次執行qmake -v
和em++ -v
應該可以看到以下資訊
編譯工程
和編譯普通Qt工程一樣,切換到原始碼目錄,直接執行這2個命令就行了
qmake
make -j3
但是因為WebAssembly和靜態編譯原因,編譯會特別慢,一個簡單的工程會需要編譯幾分鐘,這裡耐心等待就行了
編譯好後,我們一共需要從工程目錄中提取5個檔案,如下:
注:我的工程名為test
qtloader.js
qtlogo.svg
test.html
test.js
test.wasm
其中test.js
和test.wasm
是對應Qt工程的主要產物,每次Qt工程重新編譯後都要更新這2個檔案,這兩個檔案也不建議進行二次修改,直接用就行了。
如果要對頁面進行美化,直接修改test.html
就行了
因為不支援直接從檔案開啟,所以如果要對頁面進行訪問,最簡單的方法就是用python開啟一個web伺服器
python3 -m http.server
然後在瀏覽器(例如Ubuntu自帶的火狐)中直接開啟http://127.0.0.1:8000/test.html
就能看到程式了
我這裡有一個已經掛在網上的WebAssembly程式,可以直接開啟看下效果
https://web.jasonserver.com:10035/test/test.html
最佳化
截止上一步,一個簡單的web程式就構建完成了,但是我們還有幾步完善過程要走
中文字型裁剪
目前Qt for WebAssembly這邊有一個缺陷就是無法使用瀏覽器或者說客戶宿主機的字型,簡單的數字和英文Qt有處理可以顯示但是中文全部是小方塊。根據靜態編譯的思路我們也不太會把整個中文字型都打包到資原始檔裡,畢竟中文字型基本都10mb+,就算打包進去載入速度也會被大幅度拖慢。因此我們需要對字型進行裁剪,我使用的是一個線上裁剪工具,配合常用2000字漢字表。
https://font-subset.disidu.com
https://wenku.baidu.com/view/3d2bd02b453610661ed9f40a.html
注:如果百度不讓你複製,一個最簡單的破解方法就是開啟頁面後禁用頁面JS,然後再複製,就行了
我裁剪了SourceHanSansCN-Medium
字型,裁剪前10mb+,裁剪後才300多kb,打包進資原始檔完全可以接受
然後如果要在Qt裡支援這個字型,在main函式里增加一行程式碼就行了
QFontDatabase::addApplicationFont( ":/SourceHanSansCN-Medium.subset.otf" );
壓縮
為什麼要壓縮,因為wasm檔案真的有點大。我這裡一個簡單工程編譯出來就有18.2mb,這個對於一個web而言有點大了,在實際網路環境中可能需要10秒左右才能載入完成。
至於如何壓縮,這個東西就有點坑了,最初我不熟悉web的壓縮體系看了半天沒看懂,只看官方說可以壓縮卻沒說怎麼壓縮,最後才知道這個是WebServer自帶功能,不需要開發者手動壓縮
比如說我們可以使用nginx部署web,在nginx配置檔案中配置開啟glib壓縮,我的配置如下:
gzip on;
gzip_min_length 32k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 3;
gzip_types text/plain text/css text/xml application/xml application/json text/javascript application/javascript application/octet-stream;
然後這個壓縮就全自動的完成了。
大致流程就是瀏覽器在請求資原始檔的時候會說明自己支援的壓縮方式,然後伺服器如果可以匹配到這個壓縮方式就會壓縮好資料再返回給瀏覽器。
可以看到開啟glib壓縮後,18.2mb下降到了7.3mb,還是比較可觀的,縮減了幾倍的網路傳輸消耗
如果追求更高壓縮率可以使用brotli,本文不再介紹。注意這個壓縮方法不是每個瀏覽器都支援,比如說Safari就不支援
ico
ico這個東西很多地方都用得到,web這裡需要一個favicon.ico建議還是準備下
ico建議弄多解析度版本,推薦工具gfie
我在html中配置ico方式為:
<link rel="shortcut icon" type="image/ico" href="favicon.ico">
<link rel="apple-touch-icon" href="favicon.ico">
<link rel="icon" type="image/ico" href="favicon.ico">
PWA
現在Web已經可以做的和應用程式一樣,在桌面有一個小圖示,進入後只有本身的頁面沒有瀏覽器框架,這依託於PWA標準
具體參考如下:
https://developer.mozilla.org/zh-CN/docs/Web/Manifest
簡單的說這裡只要一個manifest.json
配置檔案,我的配置如下:
{
"name": "Test",
"short_name": "Test",
"start_url": "test.html",
"display": "standalone",
"background_color": "#000e27",
"theme_color": "#000e27",
"description": "Qt for WebAssembly Test",
"icons": [{
"src": "favicon.ico",
"sizes": "256x256 64x64 48x48 32x32 24x24 16x16",
"type": "image/x-icon"
}],
"related_applications": [{
"platform": "web"
}]
}
iOS狀態列修改
雖然我們有了PWA,但是iOS這邊還是有一些私有標準需要單獨寫html相容,我的配置如下:
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-touch-fullscreen" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
問題
目前來說Qt for WebAssembly還是有一些問題,這作為一個成長中的框架和平臺我們是接受的,在我測試中我注意到幾個主要問題,還是有必要單獨說下,這些問題理論上在將來都會慢慢解決,希望能儘快了。
多執行緒
目前多執行緒支援是幾乎沒有的。為什麼說幾乎沒有是因為在一些組合下還是可以用多執行緒,但是限制特別多,例如要挑瀏覽器,以及對引數有特別要求。這對於web這樣一個高相容性的設計多少有些背道而馳。
移動端相容性
我嘗試了幾個安卓手機,自帶的瀏覽器以及一些第三方瀏覽器都不支援WebAssembly技術,因此根本無法開啟頁面。但是微信裡面自帶瀏覽器卻可以開啟。總的來說在安卓上相容性比較差。
但是iOS沒有這個問題,基本上所有瀏覽器都支援。
High-DPI
在桌面瀏覽器平臺High-DPI縮放沒有問題,但是在iOS上和部分安卓上縮放是異常的,具體的bug我已經反饋給Qt,可以透過Qt的bug系統來追蹤這個問題修復情況。這個問題會導致在iOS下消耗大量無意義的資源進而導致卡頓。老裝置上甚至無法正常載入頁面。
https://bugreports.qt.io/browse/QTBUG-85662
注:示例工程也一起傳到這裡了,需要的可以直接去下載
@2x和@3x圖片渲染異常
我在Bug系統裡也找到了這個反饋,截止發文還是沒修復。因此目前程式中無法使用@2x和@3x的圖片
https://bugreports.qt.io/browse/QTBUG-79378
HTTPS
若要部署到https到web server上面,可能會報錯
Application exit (TypeError: undefined is not an object (evaluating'handle[name]'))
我不清楚這是Qt到bug還是什麼原因,但是我看Qt的線上示例也有這個問題。要避免這個問題需要修改js檔案,找到projectname.js裡找到這一行
functionBody+=" var rv = handle[name]("argsList");\n";
改成
functionBody+=" if(handle){var rv = handle[name]("+argsList+");}\n";
這個問題也提交Qt了,可以到這裡追蹤問題修復
https://bugreports.qt.io/browse/QTBUG-85736
============= End