小程式最佳化:第三方SDK過大解決方案

一杯龍井解千愁發表於2023-12-25

【前言】

小程式開發中,有時會遇到下面這種情況,專案目錄中存放過大的js包,會被警告影響手機端效能,同時讓開發編譯啟動變得很慢。慢是其次,單是影響效能這一點,就需要解決一下。

 

 

【雲資源】

將專案js包放入公司的oss、obs之類的雲端儲存上,透過https連結來訪問。

https連結不能使用node的require載入,會拋錯,但是可以透過其他兩種方式進行訪問:

  1. request請求

  2. fileSystemManager檔案管理器

 

方法1簡單,但是不可取,原因如下:

1) 獲得的檔案資訊,沒有較好的儲存方法,既不能存在store中,也不能存在local中,不僅是資料儲存格式問題,更重要的同樣會造成效能缺點,總不能解決了一個問題,又創造新的問題

2) 由於1)描述的那樣,如果不能儲存檔案,那麼每次需要依賴這個檔案時,就需要每次請求介面,這就造成了資源浪費

 

故此,基於以上兩點,只能負重前行,選擇較為麻煩和讓人頭疼的方案2(fileSystemManager檔案管理器),雖然麻煩,但是卻可以一勞永逸,並且可以抽離邏輯封裝成一套方法,可以在以後複用。

 

 

【前期準備】

實現方案前,有幾個注意事項:

1. 首先要將存放雲資源的oss或obs的域名配置在白名單中,這裡就需要配置request合法域名和downloadFile合法域名。

 

2. 需要勾選“不校驗合法域名...”,不勾選的話,真機會遇到意想不到的問題。

 

 

3. 將js檔案轉成json檔案,如果沒辦法,就自行抽離js包,拆出一個json檔案,因為微信fileSystemManager不支援讀取js檔案,js檔案會變成string文字,但是支援json。

當然,如果你覺得可以使用JavaScript 直譯器來破這個局,那麼你又一次要碰壁了,微信官方對此做了限制,禁止eval5、estime、evil-eval等動態執行程式碼的js直譯器。

原文地址:關於禁止小程式JavaScript直譯器使用規範要求

 

 

【方案思考】

fileSystemManager,它是getFileSystemManager返回的物件,給我們暴露出了多個方法,下面為部分截圖

原文地址:FileSystemManager

 

為保證效能和可靠性,這裡我們採用下面這種方案:

首次:下載 + 儲存 + 讀取,非首次:直接讀取

流程圖如下:

 

 

 

【方法封裝】

我這裡提供了兩種封裝寫法:

  1. 鏈式寫法,方便回撥處理,不需要回撥可以採用寫法2
  2. 解耦式寫法,降低了函式顆粒度,每個方法獨立,更加靈活,可以單獨使用某一個函式

 

千言萬語,前面的鋪墊已完成,直接上程式碼:

鏈式寫法

/**
 * 讀取靜態資源,如果沒有該資源,則觸發下載,並儲存至使用者本地
 * 注意:此方法為鏈式呼叫,如果需要單獨的方法,可選擇下面拆分過的獨立方法
 * @param {String} name 檔名 -必傳
 * @param {String} url 檔案路徑 -必傳
 * @param {Function} callback 是否需回撥操作 -非必傳
 */
export async function getSystemFileInfoToChain(name = 'jn', url, callback = null) {
  if (!url) {
    return;
  }
  const fileManager = uni.getFileSystemManager();
  // 使用者檔案路徑
  let filePath = `${uni.env.USER_DATA_PATH}/${name}`;
  // 讀取靜態資源,如果沒有該資源,則觸發下載
  try {
    let res = await fileManager.readFileSync(filePath, 'utf-8');
    const resData = res && JSON.parse(res);
    let resFile = await fileManager.readFileSync(url, 'utf-8');
    console.log('🆒 fileManager 111', resData, name, url, resFile);
    if (callback && typeof callback === 'function') {
      callback(resData);
    }
    // 暴露出檔案內容
    return resData;
  } catch (err) {
    console.log('getSystemFileInfoToChain err', err);
    // 下載檔案至本地
    try {
      const res = await uni.downloadFile({
        url
      });
      let tempFilePath = res?.[1]?.tempFilePath;
      console.log('🆒 fileManager 222', tempFilePath);
      if (!tempFilePath) {
        return;
      }
      // 儲存檔案至本地
      try {
        await fileManager.saveFileSync(tempFilePath, filePath);
        console.log('🆒 fileManager 333', url);
        // 再次觸發,獲得需要的資料
        getSystemFileInfoToChain(name, url, callback);
      } catch (err) {
        console.log('saveFileSync err', err);
      }
    } catch (err) {
      console.log('downloadFile err', err);
    }
  }
}

  

解耦式寫法

/**
 * 讀取靜態資源,如果沒有該資源,則觸發下載,並儲存至使用者本地
 * @param {String} name 檔名 -必傳
 * @param {String} url 檔案路徑 -必傳
 * @param {Boolean} next 是否下一步(如果讀取失敗,則下載檔案至本地) -非必傳
 */
export async function getSystemFileInfo(name = 'jn', url, next = false) {
  const fileManager = uni.getFileSystemManager();
  // 使用者檔案路徑
  let filePath = `${uni.env.USER_DATA_PATH}/${name}`;
  try {
    let res = await fileManager.readFileSync(filePath, 'utf-8');
    const resData = res && JSON.parse(res);
    console.log('🆒 fileManager 111', resData);
    // 暴露出檔案內容
    return resData;
  } catch (err) {
    // 允許下一步(下載檔案),且有url,進行下載儲存
    next && url && onDownloadFile(name, url, next);
    console.log('readFileSync err', err);
  }
  // console.log('🆒 fileManager', fileManager, uni.env.USER_DATA_PATH);
}

/** 
 * 下載檔案至本地
 * @param {String} name 檔名 -必傳
 * @param {String} url 檔案路徑 -必傳
 * @param {Boolean} next 是否下一步(檔案內容讀取) -非必傳
 */
export async function onDownloadFile(name = 'jn', url, next = false) {
  if (!url) {
    return;
  }
  try {
    const res = await uni.downloadFile({
      url
    });
    let tempFilePath = res?.[1]?.tempFilePath;
    console.log('🆒 fileManager 222', tempFilePath);
    onSaveFile({ name, url }, tempFilePath, next);
  } catch (err) {
    console.log('downloadFile err', err);
  }
}

/**
 * 儲存檔案至本地
 * @param {String} params[0].name 檔名 -必傳
 * @param {String} params[0].url 檔案路徑 -非必傳
 * @param {String} tempFilePath 臨時檔案路徑 -必傳
 * @param {Boolean} next 是否下一步(檔案內容讀取) -非必傳
 */
export async function onSaveFile({
  name = 'jn',
  url
}, tempFilePath, next = false) {
  if (!tempFilePath) {
    return;
  }
  const fileManager = uni.getFileSystemManager();
  // 使用者檔案路徑
  let filePath = `${uni.env.USER_DATA_PATH}/${name}`;
  try {
    await fileManager.saveFileSync(tempFilePath, filePath);
    // 允許下一步(檔案內容讀取),且有url,進行檔案內容讀取
    next && url && getSystemFileInfo(name, url, false);
    console.log('🆒 fileManager 333', url);
  } catch (err) {
    console.log('saveFileSync err', err);
  }
}

 

到此,就封裝完成了,後面使用看具體場景,來選擇鏈式、解耦式寫法。

相關文章