Skip to content

[2022-07-18]: Taro微信小程序分包小记 #21

@Lemonreds

Description

@Lemonreds

前言

项目使用Taro框架搭建,兼容H5钉钉端以及微信小程序端。代码随着迭代开发,小程序的代码体积逐渐变大,微信为了用户体验方面考虑,对代码包的体积有一定要求,如果过大则不允许上架。微信推荐的是使用分包方案来处理代码包过大的情况,即代码按照页面区分,分为主包和一个或者多个分包,其中总体积不超过20MB,主包的体积不超过2MB,就允许发布。此文记录了分包主要优化的方面以及遇到的问题。

分包配置

首先,需要改动路由配置,项目原有的路由配置文件如下:

  pages: [
    'pages/home/index', // 首页
    'pages/login/index', // 登录页面 用于token过期,重新登录
    'pages/consult/index', // 资讯
    'pages/consult_detail/index', // 资讯详情
    'pages/my-questionnaire/index', // 我的问卷
     // ....其他页面
  ],

我们将Tab下的四个页面放进主包,其他的页面放入分包(也可以使用多个分包,这里只分了一个包),分包后的路由配置:

 pages: [
    'pages/login/index', // 登录页面 用于token过期,重新登录
    'pages/home/index', // 首页
    'pages/my-library/index', // 学习中心
    'pages/study-calendar/index', // 学习日历
    'pages/person/index' // 我的
  ],
  subpackages: [
    {
      root: 'pages/module-common/',
      pages: [
        'consult/index', // 资讯
        'consult_detail/index', // 资讯详情
        'my-questionnaire/index', // 我的问卷
        'my-qa/index', // 我的提问
        // .... 其他页面
      ]
    }
  ],

可以看到,增加了subpackages配置,除了Tab的页面,全部放进来。同时,代码的文件目录也要做出改动
image.png
其中,module-common是我们自己定的分包命名,命名什么都是可以的。文件结构不允许全部放在pages下面,必须按照包名来分,否则微信会报错。

体积压缩

分包配置完成,但是我们还需要对代码体积进行分析,减少打包体积,目标是主包2MB以下。可以通过微信开发者工具,查看代码依赖分析,来看具体占体积较大的模块(注:应使用npm run build:weapp,生产会进行压缩)
image.png
上图是已经优化过后的代码包大小,可以看到,主包是1.87MB,分包是1.30MB,能达到微信的发布要求。接下来介绍下主要的优化工作,我们研究发现,代码包体积大的原因主要有以下几个方面:

静态资源

一般在assets目录下存放前端静态资源,以图片为主,图片是占据代码包大小的最大元凶,一张图片可能有700KB左右,所以我们首先的优化对象就是处理图片,推荐以下处理方法:

  1. 将体积稍大的图片上传到OSS,改成HTTP URL 引用资源,避免打包进来。
  2. 如果没有OSS,可以将图片进行压缩,前提是图片不要失真过度。
第三方库

我们还发现,某个业务页面的体积居然有500KB。要说明的是,我们的业务代码占总体代码是很少一部分的,代码体积的大头还是第三方库的使用。于是,当我们发现这个500KB的页面时,首先想到了这个页面是否引用了某个超大的第三方库,并将这个库的代码全部打进来了。

"crypto-js": "3.1.9-1",

答案是cryto-js这个库,用于加密。当使用最新版本时,会将其他无用的代码也打进来,压缩后的体积有400KB左右(例如,nodejs版的代码也打进来了),解决方案是回退到3.1.9-1的版本,重新打包后,页面从500KB变成50KB,可以接受。
另外,代码中如果使用了moment,建议使用dayjs来替换,dayjs的体积较小,api一致。如果一个项目既用moment,又用dayjs,在我看来是不可接受的。
微信的代码依赖分析图,可能并不是很明确,例如vendor.js文件,无法展示它具体包含了哪些第三库,这个时候可以考虑使用webpack-bundle-analyzer来分析是哪些库占用了较大的体积,这个插件要在配置文件的webpackchain加上。

打包了H5的代码

我们使用的是Taro来实现h5和小程序的多端统一。当然,有些功能是没可能做到一份代码,到处运行的。无法兼容多端的功能,我们会单独开发,h5一套代码,微信小程序一套代码。
例如,我们在h5中,有个pdf文件预览的功能,基于pdf.js。但是,pdf.js在微信小程序的环境是跑不了的,所以我们微信小程序的文件预览的功能是Webview内嵌h5页面。
分析打包时,发现微信小程序居然把pdf.js也打了进来,pdf.js本身就是一个很大的库,打包进无用的代码,是必须要进行优化的。

import { lazy, Suspense } from 'react';

let FileViewer;

if (process.env.TARO_ENV === 'h5') {
  FileViewer = lazy(() => import('./components/FileViewerH5'));
} else {
  FileViewer = lazy(() => import('./components/FileViewerTaro'));
}

const FilePreview = () => {
  return (
    <Suspense fallback={<span />}>
      <FileViewer />
    </Suspense>
  );
};

export default FilePreview;

解决上述问题的方法就是使用代码分割,这里使用的是react的Suspens、lazy以及异步import的方案,识别到伊布import后,webpack会自动帮我们按需加载,就不会将h5的代码打进来了。
还有一个例子是,我们的项目有视频播放的功能,在h5上使用的是video.js,在微信小程序上使用的是video标签,这部分也需要代码分割,按需引入,避免在微信上打包进了video.js。

import { forwardRef, lazy, Suspense } from 'react';

let Video;

if (process.env.TARO_ENV === 'h5') {
  Video = lazy(() => import('./VideoH5'));
} else {
  Video = lazy(() => import('./VideoTaro'));
}

function VideoWrapper(props) {
  return (
    <Suspense fallback={<span />}>
      <Video {...props} />
    </Suspense>
  );
}

export default forwardRef(VideoWrapper);

路由问题

我们进行分包的时候,业务代码基本写完了,页面的路由也都定好了。但是进行分包后,微信小程序和h5的路由都会发生变化,原有路由根本不能使用,例如:

 Taro.navigateTo({ url: `/pages/my-qa/index` });

分包后的路由:

Taro.navigateTo({ url: `/pages/module-common/my-qa/index` });

会发现,分包内的页面,要在路由上加载前缀,这个改动无疑是巨大的。我们不可能把代码里面的每一个url都手动去加上前缀,部分路由url,在后端也有存储,还要去改后端数据库里的url。考虑再三后,我们希望把改动降到最小,不改动原有的 navigateTo中的url,h5的路由保持不变,能接受改动微信小程序的路由。

h5的路由不变

image.pngh5路由保持不变使用的是 config.js中h5的router属性,来配置router的别名。即真实的路由是/pages/module-common/consult/index,但是/pages/consult/index相当于一个别名,也能找到页面。

单独处理小程序的路由

小程序没有像h5,可以配置“别名”,来映射路由。所以,我们期望的效果是在调用 Taro.navigateTo({ url: /pages/my-qa/index }); 时,能自动加上分包前缀,跳转到真正的页面。
Taro.navigateTo({ url: /pages/module-common/my-qa/index });
实现以上效果的做法是,在Taro的实例上定义一个navigateToPage方法,核心就是使用Object.defineProperties方法,所有的navigateTo都替换成navigateToPage方法,navigateToPage方法为跳转的url新增分包的前缀。
实现如下:

import Taro from '@tarojs/taro';

// 主包内的路由
const mainPkgRoutes = [
  'pages/home/index', // 首页
  'pages/login/index', // 登录页面 用于token过期,重新登录
  'pages/my-library/index', // 学习中心
  'pages/study-calendar/index', // 学习日历
  'pages/person/index' // 我的
];

// 分包的路由加上前缀
const beforeRoute = options => {
  if (process.env.TARO_ENV === 'weapp') {
    const { url } = options;
    const isMainPkgRoute = mainPkgRoutes.find(route => url.includes(route));
    if (isMainPkgRoute) {
      return options;
    }
    return {
      ...options,
      url: url.replace('/pages', '/pages/module-common')
    };
  }

  return options;
};


/**
 * @function 自定义navigaTo等路由跳转方法(用于处理微信分包)
 */
const initCustomTaro = () => {
  Object.defineProperties(Taro, {
    navigateToPage: {
      configurable: true,
      enumerable: true,
      get() {
        return options => Taro.navigateTo(beforeRoute(options));
      }
    },
    redirectToPage: {
      configurable: true,
      enumerable: true,
      get() {
        return options => Taro.redirectTo(beforeRoute(options));
      }
    },
    reLaunchPage: {
      configurable: true,
      enumerable: true,
      get() {
        return options => Taro.reLaunch(beforeRoute(options));
      }
    }
  });
};

export default initCustomTaro;

最后,在全局app.ts执行initCustomTaro进行初始化即可。再全局替换navigateTo为navigateToPage即可,就能自动带上分包前缀,避免改错改漏的情况发生。当然,如果在项目开始前,就定好分包策略,路由便不需要进行如此困难的改动了。

总结

本文记录了Taro框架下微信小程序进行分包的一些经验,包括如何进行页面拆分和配置、如何进行体积压缩、以及遇到的路由问题。

体积压缩:

  1. 处理图片,上传到OSS或者进行压缩。
  2. 使用第三方库时,要考虑其体积。如果仅仅是使用部分功能,可以按需加载,或者考虑用其他库来替代。
  3. 避免微信端打入了h5的代码。

路由问题:

  1. 建议在开发前,就定好微信的分包。
  2. 可以在Taro的实例上重新定义个路由跳转方法,用来增加分包前缀。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions