微信购物入口「京喜」首页跨端开发与优化实践
Taro 是一套遵循 React 语法规范的多端开发解决方案。
现如今市面上端的形态多种多样,Web、React-Native、微信小程序等各种端大行其道,当业务要求同时在不同的端都要求有所表现的时候,针对不同的端去编写多套代码的成本显然非常高,这时候只编写一套代码就能够适配到多端的能力就显得极为需要。
使用 Taro,我们可以只书写一套代码,再通过 Taro 的编译工具,将源代码分别编译出可以在不同端(微信/百度/支付宝/字节跳动/QQ 小程序、快应用、H5、React-Native 等)运行的代码。
选它有两个原因,一来是 Taro 已经成熟,内部和外部都有大量实践,内部有京东 7FRESH、京东到家等,外部有淘票票、猫眼试用等多个案例,可以放心投入到业务开发;二来团队成员都拥有使用 Taro 来开发内部组件库的经验,对业务快速完成有保障。
开发实录
由于首页改版的开发排期并不充裕,因此充分地复用已有基础能力(比如像请求、上报、跳转等必不可少的公共类库),能大量减少我们重复的工作量。话虽如此,但在三端统一开发过程中,我们仍遇到不少问题同时也带来解决方案,以下我们一一阐述。
H5 篇
我们所有的页面都依赖现有业务的全局公共头尾及搜索栏等组件,这就不可避免的需要将 Taro 开发流程融入到现有开发和发布流程中去。同时公共组件都是通过 SSI 的方式引入和维护的,为了能在运行 npm run dev:h5
时预览到完整的页面效果,需要对 index.html
模版中的 SSI 语法进行解析,index.html
模版文件代码结构大致如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>京喜</title>
<!--#include virtual="/sinclude/common/head_inc.shtml"-->
</head>
<body>
<div id="m_common_header" style="display:none;"></div>
<!--S 搜索框-->
<div id="search_block" class="search_block"></div>
<div id="smartboxBlock" style="display:none;"></div>
<!--E 搜索框-->
<div id="app" class="wx_wrap"></div>
<!--#include virtual="/sinclude/common/foot.shtml"-->
</body>
</html>
可以看到模版中存在很多类似 <!--#include virtual="..." -->
格式的代码,这些就是通过 SSI 方式引入的 H5 公共组件,它的 virtual
属性指向的文件不存在于本地而是存在于服务器上的,所以我们遇到的第一个问题就是在本地解析这些文件,确保能预览到完整的页面效果,不然开发调试起来就非常的低效。好在 Taro 有暴露出 webpack 的配置,我们可以通过引入自定义加载器(这里就叫 ssi-loader
)来解析这些代码的路径,然后请求服务器上的文件内容并进行替换即可,要实现这个功能只需在项目的 config/dev.js
中加入如下代码即可:
module.exports = {
h5: {
webpackChain(chain, webpack) {
chain.merge({
module: {
rule: {
ssiLoader: {
test: /\.html/,
use: [
{
loader: 'html-loader'
},
{
loader: 'ssi-loader',
options: {
locations: {
include: 'https://wqs.jd.com'
}
}
}
]
}
}
}
})
}
}
}
这样就解决了本地开发调试难点,然后开开心心的进行页面开发。
当页面开发完成之后,接下来遇到的问题就是如何将前端资源部署到测试和生产环境。由于现有开发和发布流程都是基于内部已有的平台,我们临时定制一套也不太现实,所以需要将它融入到 Taro 的流程中去,这里我们引入了 gulp
来整合各种构建和发布等操作,只要构建出符合发布平台规范的目录即可利用它的静态资源构建、版本控制及服务器发布等能力,这样我们就打通了整个开发和发布流程。
这套拼凑起来的流程还存在不少的问题,对于新接手的同学有一点小繁琐,有着不少改善的空间,这也是接下来的重点工作方向。另外 Taro 的 H5 端之前是基于 SPA 模式,对于有着多页开发需求的项目来说不太友好,当时反馈给 Taro 团队负责 H5 的同学,很快得到了响应,目前 Taro 已支持 H5 多页开发模式,支持非常迅速。
小程序篇
由于开发完 H5 版之后,对应的业务逻辑就已经处理完了,接下来只需要处理小程序下的一些特殊逻辑(比如分享、前端测速上报等)即可,差异比较大的就是开发和发布流程。
这里讲一下如何在一个原生小程序项目中使用 Taro 进行开发,因为我们的 Taro 项目跟已有的原生小程序项目是独立的两个项目,所以需要将 Taro 项目的小程序代码编译到已有的原生小程序项目目录下,第一步要做的就是调整 Taro 配置 config/index.js
,指定编译输出目录以及禁用 app 文件输出防止覆盖已有文件。
const config = {
// 自定义输出根目录
outputRoot: process.argv[3] === 'weapp' ? '../.temp' : 'dist',
// 不输出 app.js 和 app.json 文件
weapp: {
appOutput: false
}
}
由于京喜以前是主购小程序的一个栏目,后面独立成了独立的小程序,但是核心购物流程还是复用的主购小程序,所以这让情况变得更加复杂。这里还是通过 gulp
来进行繁琐的目录文件处理,比如我们的小程序页面和组件都需要继承主购小程序的 JDPage
和 JDComponent
基类,所以在进行文件复制之前需要进行代码替换,代码如下:
// WEAPP
const basePath = `../.temp`
const destPaths = [`${basePath}/pages/index/`, `${basePath}/pages/components/`]
const destFiles = destPaths.map(item => `${item}**/*.js`)
/*
* 基类替换
*/
function replaceBaseComponent (files) {
return (
gulp
.src(files || destFiles, { base: basePath })
.pipe(
replace(
/\b(Page|Component)(\(require\(['"](.*? "'"")\/npm\/)(.*)(createComponent.*)/,
function(match, p1, p2, p3, p4, p5) {
const type =
(p5 || '').indexOf('true') != -1 ||
(p5 || '').indexOf('!0') != -1
? 'Page'
: 'Component'
if (type == 'Page') p5 = p5.replace('))', '), true)') // 新:page.js基类要多传一个参数
const reservedParts = p2 + p4 + p5
// const type = p1
// const reservedParts = p2
const rootPath = p3
const clsName = type == 'Page' ? 'JDPage' : 'JDComponent'
const baseFile = type == 'Page' ? 'page.taro.js' : 'component.js'
console.log(
`🌝 Replace with \`${clsName}\` successfully: ${this.file.path.replace(
/.*?wxapp\//,
'wxapp/'
)}`
)
return `new (require("${rootPath}/bases/${baseFile}").${clsName})${reservedParts}`
}
)
)
.pipe(gulp.dest(basePath))
)
}
// 基类替换
gulp.task('replace-base-component', () => replaceBaseComponent())
还有很多类似这样的骚操作,虽然比较麻烦,但是只需要处理一次,后续也很少改动。
RN 篇
对于 RN 开发,也是第一次将它落地到实际的业务项目中,所以大部分时候都是伴随着各种未知的坑不断前行,所以这里也友情提示一下,对于从未使用过的技术,还是需要一些耐心的,遇到问题勤查勤问。
由于京喜 APP 是复用京东技术中台的基础框架和 JDReact 引擎,所以整个的开发和部署都是遵循 JDReact 已有的流程,画了一张大致的流程图如下:
JDReact 平台是在 Facebook ReactNative 开源框架基础上,进行了深度二次开发和功能扩展。不仅打通了 Android/iOS/Web 三端平台,而且对京东移动端基础业务能力进行了 SDK 级别的封装,提供了统一、易于开发的 API。业务开发者可以通过 JDReact SDK 平台进行快速京东业务开发,并且不依赖发版就能无缝集成到客户端(android/iOS)或者转换成 Web 页面进行线上部署,真正实现了一次开发,快速部署三端。
由于京喜 APP 的 JDReact 模块都是独立的 git 仓库,所以需要调整我们 Taro 项目配置 config/index.js
的编译输出路径如下:
rn: {
outPath: '../jdreact-jsbundle-jdreactpingouindex'
}
这样,当我们运行 yarn run dev:rn
进行本地开发时,文件自动编译到了 JDReact 项目,接下来我们就可以用模拟器或者真机来进行预览调试了。当我们在进行本地开发调试的时候,最高效的方式还是推荐用 Taro 官方提供的 taro-native-shell
原生 React Native 壳子来启动我们的项目,详细的配置参照该项目的 README 进行配置即可。
由于 React Native 官方提供的 Remote Debugger 功能非常弱,推荐使用 React Native Debugger 来进行本地 RN 调试,提供了更为丰富的功能,基本接近 H5 和小程序的调试体验。
这样我们就拥有了一个正常的开发调试环境,接下来就可以进行高效的开发了,由于我们前面在 H5 和小程序版本阶段已经完成了绝大部分的业务逻辑开发,所以针对 RN 版本的主要工作集中在 iOS 和安卓不同机型的样式和交互适配上。
在样式适配这块,不得不提下 Taro 针对我们常见的场景提供了一些最佳实践,可以作为布局参考:
- 固定尺寸(按钮、文字大小、间距):写 PX / Px / pX
- 保持宽高比(比如 banner 图片):
Image
组件处理 - 间距固定,内容自适应(比如产品卡片宽度):使用
flex
布局 - 按屏幕等比缩放:使用 px 单位,编译时处理(
scalePx2dp
动态计算)
Taro RN 最佳实践集锦
在实际开发过程中也遇到不少兼容性问题,这里整理出来以供大家参考:
-
文本要用
<Text>
标签包起来,因为 RN 没有textNode
的概念; -
使用 Swiper 时在外面包一个 View,否则设置
margin
后会导致安卓下高度异常; -
Cannot read property 'x' of undefined
,Swiper 底层使用的 react-native-swiper 导致的问题,Disable Remote JS Debug 就不会出现。 -
图片默认尺寸不对,RN 不会自动帮助设置图片尺寸,而是交给开发者自己处理,故意这样设计的;
-
Image 组件上不可以设置 onClick
-
实现基线对齐:
vertical-align: baseline
,用<Text>
把需要基线对齐的组件包住即可。<Text> <Text style={{ fontSize: 20 }}>abc</Text> <Text style={{ fontSize: 40 }}>123</Text> </Text>
-
尽量避免使用
line-height
,在安卓和 iOS 下表现不一致,而且即使设置为与fontSize
相同也会导致裁剪; -
android 调试生产环境的 bundle,摇手机,选 Dev Setting,取消勾选第一项 Dev 即可;
-
iOS 调试生产环境的 bundle,
AppDelegate.m
中增加一行语句关闭 dev 即可:[[RCTBundleURLProvider sharedSettings] setEnableDev:false]; // 找到这行,并在它的上面增加上面这行 jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
-
<Text>
与<View>
支持的 style 属性不相同。> [Text Style Props](https://facebook.github.io/react-native/docs/text-style-props "Text Style Props") & [View Style Props](https://facebook.github.io/
react-native/docs/view-style-props)
-
render 方法中不要返回空字符串
下面的代码在 android 下会报错(empty_string 内容为空字符串)
<View> {empty_string && <Text></Text>} </View>
因为 `empty_string && <Text>
[转载]原文链接:https://segmentfault.com/a/1190000021189368
本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。
转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]