如何把Electron做成一個Runtime,讓多個應用共享同一個Electron

這個問題涉及到很多知識,而且要想把這個Runtime做好很繞。

下面我就說一下我的思路:
(以下內容以Windows平台為基礎,Mac平台和Linux平台還得去調查一下,才能確定是否可行)

首先,我們先區分三類用戶:

  1. Runtime建設者(就是我們)
  2. Runtime使用者(就是使用Runtime的開發者)
  3. 最終用戶(就是使用Runtime開發者開發的應用的那些用戶)

接下來我們就以Runtime建設者的視角來審視這項工作

首先我們要為Runtime使用者提供一個專有的打包工具,我們就叫它:打包工具。這個打包工具還內置了幾個可執行程式,我們給他們起個名字,分別叫:

  • 最終安裝程式
  • 最終執行程式
  • 最終卸載程式

好,我們一個一個聊他們的職責

打包工具的職責
按Runtime使用者的要求修改最終執行程式的圖標、應用簽名、版本、版權、文件名等資源資訊;
按Runtime使用者的要求修改最終卸載程式的圖標、應用簽名、版本、版權、文件名等資源資訊;
把最終執行程式、最終卸載程式與Runtime使用者開發好的HTML/CSS/JS等靜態文件放到一起,壓縮成一個壓縮包,我們叫他:資源文件
把這個資源文件以資源的形式封裝到最終安裝程式中;
按Runtime使用者的要求修改這個最終安裝程式的圖標、應用簽名、版本、版權、文件名等資源資訊;
(修改資源的程式碼,後文有介紹)

這幾個工作完成之後,Runtime使用者就可以把這個最終安裝程式分發給最終用戶了。

最終安裝程式的職責
這個最終安裝程式在最終用戶的電腦上運行時,會完成以下工作:

檢查最終用戶的註冊表,看其是否安裝了我們的Electron Runtime
如果沒有安裝,則下載Electron的發行版,釋放到一個特定目錄下,並在註冊表記下來。
在這個特定目錄下記錄當前應用的資訊(卸載當前應用時要用到);
把自身的資源釋放到最終用戶指定的目錄內,也就是前文說的資源文件
解壓縮資源文件得到最終執行程式、最終卸載程式和Runtime使用者開發的HTML/CSS/JS等靜態文件
寫註冊表記錄最終卸載程式的位置,這樣用戶就可以在控制面板里卸載我們的程式了。
按最終用戶的要求,創建開始菜單圖標、桌面圖標,這些圖標均指向最終執行程式
(讀取資源的程式碼,後文有介紹)

如果最終用戶工作在沒有網路的環境下,那麼我們也可以允許Runtime使用者把Electron Runtime打包到最終安裝程式內,這是打包工具的職責。

如果擔心Electron官方提供的下載地址速度慢,可以考慮使用中國鏡像地址:​npmmirror.com/mirrors/electron/

最終執行程式的職責
檢查用戶註冊表,找到Electron Runtime的放置路徑
啟動Electron Runtime並把當前應用的入口程式當做參數傳給Electron.exe,應用入口程式就是Runtime使用者開發HTML/CSS/JS等靜態文件之一,
electron.exe path/to/entry.js
最終卸載程式的職責
刪除安裝目錄下的文件
刪除註冊表的卸載程式資訊
刪除Electron Runtime所在目錄下的應用程式資訊,如果發現沒有別的應用在依賴Electron Runtime了,那麼就把Electron Runtime所在目錄也刪掉。

 

把一個文件作為資源寫入一個可執行程式的程式碼如下所示:

HANDLE hFile;
DWORD dwFileSize,dwBytesRead;
LPBYTE lpBuffer;
char szFile[MAX_PATH+1] = {0};
::GetDlgItemText(hwnd,EditId,szFile,MAX_PATH);
hFile = CreateFile(szFile,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
dwFileSize = GetFileSize(hFile, NULL);
lpBuffer = new BYTE[dwFileSize];
ReadFile(hFile, lpBuffer, dwFileSize, &dwBytesRead, NULL);
HANDLE hResource = BeginUpdateResource(szFilePath, FALSE);
UpdateResource(hResource,RT_RCDATA,MAKEINTRESOURCE(EditId),MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),(LPVOID)lpBuffer,dwFileSize);
EndUpdateResource(hResource, FALSE);
delete [] lpBuffer;
CloseHandle(hFile);
return 1;

可執行程式讀取自身資源,把資源寫到指定路徑下的程式碼如下:

HMODULE hInstance = ::GetModuleHandle(NULL);
TCHAR szFilePath[MAX_PATH + 1];
GetPath(szFilePath,resourceName,hInstance);
HRSRC hResID = ::FindResource(hInstance,resourceID,RT_RCDATA);
HGLOBAL hRes = ::LoadResource(hInstance,hResID);
LPVOID pRes = ::LockResource(hRes);
DWORD dwResSize = ::SizeofResource(hInstance,hResID);
if(!dwResSize) return 0;
HANDLE hResFile = CreateFile(szFilePath,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
DWORD dwWritten = 0;
WriteFile(hResFile,pRes,dwResSize,&dwWritten,NULL);
CloseHandle(hResFile);
if(dwResSize == dwWritten) return 1;
return 0;

這兩段程式碼是從我的一個項目中摘抄出來的,僅供參考。

遺留的問題
我們並沒有考慮多Electron版本共存的問題;
此方案高度依賴Windows API,跨平台實現差異肯定會比較大;
應用程式啟動後,任務欄的圖標是Electron Runtime的圖標,而非Runtime使用者指定的圖標(這是有解決辦法的);