腾讯课堂双十一活动需求:基于 Kbone 的 React 同构小程序实践

2024-06-15
来源:网络整理

1 背景

新增课程礼包是腾讯课堂双十一活动要求之一,该要求包括一个可在多终端(H5、小程序、APP)上使用的礼包课程领取页面。

小程序端我们可以使用web-view嵌入H5,但是该方案加载时间较长,且无法使用微信特有的能力(例如:获取微信用户绑定的手机号、沉浸式状态栏)。 由于已经支持同构,因此该页面我们尝试使用同构小程序做法。

最终的效果左边是H5,右边是小程序:

体验二维码

2 框架选择

目前大部分小程序构建方案都是采用静态编译的方式实现的,例如taro。

这种静态编译的方式,只能让我们使用 JSX 的语法来编写小程序代码,然后通过语法分析工具将代码翻译成小程序模板。由于 JSX 并不是一个模板语言,所以要将其翻译成小程序模板,就必须牺牲 JS 的部分动态特性,这也是这种方案在编写上存在诸多限制的原因。它的本质缺陷就在于语法分析是静态的,而 JS 是动态的。

另外该方案在实际运行时并不是“真正的”实现,因此后续功能无法与正式版同步。

至于它之所以能支持完整和JSX语法,是因为它引入了完整的JSX语法,并且针对底层的dom/bom接口,提供了一套轻量级的小程序适配层接口(-和-)。

因为通过提供适配器来模拟Web环境,所以我们可以在任何位置、以任何方式编写JSX,而不必担心某些新特性是否不受支持。

3——流程

从官方的例子来看,该代码用于构建小程序,其流程是基于的,在构建好Web端代码后,使用转换代码并将小程序相关的文件通过mp--追加到小程序项目中。

从上图可以看出,示例主要是从新建一个项目开始,通过 .mp..js 配置生成一个完整的小程序项目。但对于已有的小程序项目,我们其实并不需要 app.*、..json 等文件。同时更多时候我们希望在已有的 H5 项目中编写并复用代码,然后生成小程序页面输出到已有的小程序项目中。

4. 连接到现有项目

礼包课程领取页面主要涉及两个现有的项目:

为了优先保证H5能正常运行,我们将新页面的代码放入m-core项目中,然后添加.mp..js配置。由于同构生成的小程序页面依赖适配层库,为了避免原有小程序项目主包过大,我们需要构建生成分包页面。同时如上文所说,mp--会生成额外的小程序项目文件,所以我们要么在构建完成后移除这些文件,要么修改插件只生成必要的文件。我们暂时采用​​后者方案,相对灵活,并已反馈给微信同事,支持生成单页代码到指定目录。

4.1 构建配置

我们在--提供的.mp4.js基础上进行了修改,以支持项目中使用的、、条件编译、Tree等特性,以及与小程序代码的复用。

4.1.1

以下是H5与小程序代码转换的通用规则,以isMp进行区分。

{ test: /\.(ts|js)x?$/, use: [ 'thread-loader', { loader: 'babel-loader?cacheDirectory', options: isMp ? { configFile: false, // 避免babel加载babel.config.js presets: [ '@babel/preset-typescript', // 支持typescript '@babel/preset-react', // 支持react ], plugins: [ '@babel/plugin-proposal-class-properties', ] } : { configFile: path.resolve(rootDir, 'babel.config.js'), }, }, { loader: 'webpack-strip-block', options: { // 依据标记移除代码块 start: isMp ? 'strip-block--h5-only:begin' : 'strip-block--mp-only:begin', end: isMp ? 'strip-block--h5-only:end' : 'strip-block--mp-only:end', }, } ], include: [ path.resolve(rootDir, 'src'), path.resolve(rootDir, 'node_modules/@tencent'), ], sideEffects: !isMp, // 小程序开启tree shaking }

同构小程序使用的 - 配置和一般 H5 有些区别,对于小程序来说,我们可以省略 @/-env,因为小程序开发者工具本身提供了 ES6 到 ES5 的代码编译能力,以及增强编译能力。

至于插件,请不要使用@/--和@/---,这两个插件在h5中很常见,但是这里@/--会导致小程序开发者工具运行出错,并且@/---会影响Tree。

另外我们使用--,目的在于根据环境去除不需要的代码块(效果和一样,但是不能处理声明),和Tree配合使用。

4.1.2 树

由于对包大小有严格的限制,我们需要尽可能的减小包大小,Tree是一种代码优化技术,可以剔除无用的代码。

要在 中使用 Tree ,我们必须确保:

由上述情况可知,同构项目需要注意以下几点:

4.1.3 小程序代码复用

现有的-ke小程序项目使用了@/等npm库,实现了各种代码,如果在同构的代码中重新实现,显然是重新发明轮子,增加了小程序包的大小,所以我们需要复用-ke现有的代码。

对于 npm 包来说,由于引入了 -ke 小程序主包,同构代码只需要在构建小程序代码时,将 npm 包排除在输出代码之外,这样小程序运行时就会去主包中获取这些依赖。

开发小程序软件_小程序开发k_小程序开发序开发

对于 -ke 小程序实现的代码,首先我们需要找到目录并赋予它一个别名,例如。

resolve.alias.weappKeUtils = path.resolve(weappKeDir, 'utils');

然后我们在同构代码中引用小程序的代码模块。

import * as keRouter from 'weappKeUtils/router'; export function refresh() { if (process.env.isMiniprogram) { keRouter.refresh(); } else { window.location.reload(); } }

最后,代码模块以相对直接的方式对外依赖小程序。

externals: [ (context, request, callback) => { if (/^weappUtils/.test(request)) { // 通过commonjs引入小程序项目的utils,小程序模块定位只能使用相对路径 return callback(null, request.replace('weappUtils', '../../../utils'), 'commonjs'); } return callback(); } ],

4.2 代码编写 4.2.1 小程序、H5公众库适配

由于原来H5和小程序项目是分开的,暂时没有统一的模块管理,尤其是涉及到两端独有的API的库无法共享。

需要注意的是,有些库是有副作用的,除了方法之外,有些库可能还有一些自执行的逻辑,如果这些逻辑涉及到 wx API 或者 Web API,那么另一端就会抛出异常,所以在两端移除不相关的库是很重要的。

因此可以考虑抽出一个适配层,抹平两端公共库的差异,比如上报组件,统一路由跳转等。

比如由于同构代码在H5项目中,所以我们将小程序方法的输入参数和返回值与H5方法的输入参数和返回值对齐进行适配。

/* strip-block--h5-only:end */ request = function mpRequest(url: string, options: any = {}) { // ... } /* strip-block--h5-only:end */ /* strip-block--h5-only:begin */ request = require('assets/request').default; /* strip-block--h5-only:end */ export default request;

4.2.2 开放型回调函数

它有自己的事件系统,使用事件委托来监听事件。因此,如果我们在 JSX 中传入的事件不是支持的 DOM 事件(如,),那么我们传入的回调方法在 DOM 上是无法获取的。

在小程序中,对于一些开放型的设置,小程序支持设置回调来获取一些用户授权信息,比如设置了回调,我们从回调中获取解密用户手机号的参数,这些都不是 支持的回调函数。

因此这些方法需要手动绑定到 DOM 才能被检索和触发。我们可以封装另一个组件来处理这个特殊的回调:

processWxEvents = (fn: any) => { if (process.env.isMiniprogram) { Object.keys(this.props).forEach((k) => { if (k.indexOf('wxOn') === 0) { const eventName = `on${k.slice(4)}`; fn(eventName, this.props[k]); } }); } } componentDidMount() { this.processWxEvents(this.addEventToDom); } componentWillUnmount() { this.processWxEvents(this.removeEventFromDom); } render() { const { children, className } = this.props; return ( <wx-button {...this.props} {...(className ? { class: className } : null)} ref={this.buttonRef} > {children} wx-button> );

4.2.3 小程序组件类型属性

小程序组件有时候需要传递类型参数,比如 lazy-load 属性,可以直接用 JSX 编写

,属性也无法读取。可以使用类型转换来传递类型属性:

1} />

5 小程序同构页面优化 5.1 音量优化

腾讯课堂小程序原始大小约为,同构页面基本开发完成后,构建出来的页面分包大小在800k左右,页面甚至比主包还要大200k。

5.1.1 主要包内容分析:由于真正能引入Vue并且使用到的,所以最终的代码包也会完整包含这些库的代码。本次同构页面引入的+-dom压缩后大概120k。适配层组件:通过-和-两个npm包提供基本的dom/bom api。这两个包压缩后大概180k。业务wxss代码:压缩前大概350k。业务js代码:压缩前大概340k。

第1和第2部分属于第三方库,不易进一步优化,所以我们重点关注构建的业务代码。

5.1.2 简化 CSS

业务wxss代码达到350k明显不正常,经排查,转换后的wxss文件中含有两个“巨大的东西”:背景图片和。

背景图片是当前需求引入的,我们将背景图片上传到CDN,并在图片加载失败时设置背景颜色,避免将图片资源打包到CSS中。

在小程序中,全局样式中已经有设置了,就没必要再打包一个wxss到页面级别了。所以这里我们选择复用小程序通用样式,将H5和小程序分开处理。先把相关的css文件.css提取出来,在搭建H5的时候引入,在搭建小程序的时候移除。

import './index.css'; /* strip-block--h5-only:begin */ import './catefont.css'; /* strip-block--h5-only:end */

去掉背景图和重复样式()之后wxss只有90k左右(压缩前)。

5.1.3 通过前面的构建,实现了精简包与小程序的代码重用。通过前面的构建,实现了树。

经过最小化之后,js 只有大约 100k(压缩前)。

最终代码约420k,其中第三方库约300k,活动页面为子包,对主包大小无影响。

六,结论

目前实施的同构小程序效果还是不错的:

从上述开发实践来看,虽然 H5 与小程序已经实现了同构,但还是存在一些可以改进和优化的地方,比如其使用评论包来区分 H5 端与小程序端的依赖引入(),可能会自动修正依赖引入的顺序,导致评论包中的依赖乱序,影响程序的正常运行,因此这部分后续需要换一种方式进行优化。

分享