背景

京东购物小程序作为京东小程序业务流量的主要入口,承载着许多的活动和页面,而很多的活动在小程序开展的同时,也会在京东 APP 端进行同步的 H5 端页面的投放。这时候,一个相同的活动,需要同时开发原生小程序页面和H5页面的难题又摆在了前端程序员的面前。

幸运的是,我们有 Taro,一个开放式跨端跨框架解决方案。可以帮助我们很好地解决这种跨端开发的问题。但不幸的是,Taro 并没有提供一套完整的将项目作为独立分包运行在小程序中的解决方案。因此,本篇文章将介绍如何通过一套合适的混合开发实践方案,解决 Taro 项目作为独立分包后出现的一些问题

目录

  • 背景
  • 整体流程
  • 应用过程
    • 准备合适的开发环境
    • 将 Taro 项目作为独立分包进行编译打包
    • 引入 @tarojs/plugin-indie 插件,保证 Taro 前置逻辑优先执行
    • 引入 @tarojs/plugin-mv 插件,自动化挪动打包后的文件
    • 引入公共方法、公共基类和公共组件
      • 引入公共方法
      • 引入公共组件
      • 引入页面公共基类
  • 存在问题
  • 后续

整体流程

总的来说,若要使用 Taro 3 将项目作为独立分包运行在京东购物小程序,我们需要完成以下四个步骤:

  1. 准备开发环境,下载正确的 Taro 版本
  2. 安装 Taro 混合编译插件,解决独立分包的运行时逻辑问题
  3. 调用 Taro 提供的混合编译命令,对 Taro 项目进行打包
  4. 挪动打包后 Taro 文件到主购小程序目录下

那么接下来,我们将对每个步骤进行详细的说明,告诉大家怎么做,以及为什么要这样做。

应用过程

准备合适的开发环境

首先我们需要全局安装 Taro 3,并保证全局和项目下的 Taro 的版本高于3.1.4,这里我们以新建的Taro 3.2.6项目为例:

yarn global add @tarojs/cli@3.2.6

taro init

之后我们在项目中用React语法写入简单的 hello word 代码,并在代码中留出一个Button组件来为将来调用京东购物小程序的公共跳转方法做准备。

// src/pages/index/index.jsx

import { Component } from 'react'
import { View, Text, Button } from '@tarojs/components' import './index.scss' export default class Index extends Component {
handleButtonClick () {
// 调用京东购物小程序的公共跳转方法
console.log('trigger click')
} render () {
return (
<View className='index'>
<Text>Hello world!</Text>
<Button onClick={this.handleButtonClick.bind(this)} >点击跳转到主购首页</Button>
</View>
)
}
}

俗话说得好,有竟者事竟成,在开始编码前,我们来简单地定几个小目标:

  • 成功地将 Taro 项目 Hello world 在京东购物小程序的分包路由下跑通
  • 引入京东购物小程序的公共组件 nav-bar 并能正常使用
  • 引入公共方法 navigator.goto 并能正常使用
  • 引入公共基类 JDPage 并能正常使用

将 Taro 项目作为独立分包进行编译打包

在将 Taro 项目打包进主购小程序时,我们很快就遇到了第一个难题:Taro 项目下默认的命令打包出来的文件是一整个小程序,如何打包成一个单独的分包?

幸运的是,在3.1.4版本后的 Taro,提供了混合开发的功能,意思为可以让原生项目和 Taro 打包出来的文件混合使用,只需要在打包时加入 --blended 命令即可。

cross-env NODE_ENV=production taro build --type weapp --blended

blended 中文翻译是混合的意思,在加入了这个命令后,Taro 会在构建出来的 app.js 文件中导出 taroApp,我们可以通过引入这个变量来在原生项目下的 app.js 调用 Taro 项目 app 的 onShow、onHide 等生命周期。

// 必须引用 Taro 项目的入口文件
const taroApp = require('./taro/app.js').taroApp App({
onShow () {
// 可选,调用 Taro 项目 app 的 onShow 生命周期
taroApp.onShow()
}, onHide () {
// 可选,调用 Taro 项目 app 的 onHide 生命周期
taroApp.onHide()
}
})

如果单纯地使用 blended 命令,即使我们不需要调用 onShow、onHide 这些生命周期,我们也需要在原生项目下的 app.js 里引入Taro项目的入口文件,因为在执行我们的小程序页面时,我们需要提前初始化一些运行时的逻辑,因此要保证 Taro 项目下的 app.js 文件里的逻辑能优先执行。

理想很丰满,现实很骨感,由于我们需要将 Taro 项目作为单独的分包打包到主购项目中,因此这种直接在原生项目的 app.js 中引入的方式只适用于主包内的页面,而不适用于分包。

引入 @tarojs/plugin-indie 插件,保证 Taro 前置逻辑优先执行

要解决混合开发在分包模式下不适用的问题,我们需要引入另外一个 Taro 插件 @tarojs/plugin-indie

首先我们先在 Taro 项目中对该插件进行安装

yarn add --dev @tarojs/plugin-indie

之后我们在 Taro 的配置项文件中对该插件进行引入

// config/index.js
const config = {
// ...
plugins: [
'@tarojs/plugin-indie'
]
// ...
}

查看该插件的源码,我们可以发现该插件处理的逻辑非常简单,就是在编译代码时,对每个页面下的 js chunk 文件内容进行调整,在这些 js 文件的开头加上 require("../../app"),并增加对应 modulesourceMap 映射。在进行了这样的处理后,便能保证每次进入 Taro 项目下的小程序页面时,都能优先执行 Taro 打包出来的运行时文件了。

引入 @tarojs/plugin-mv 插件,自动化挪动打包后的文件

到目前为止,我们已经可以成功打包出能独立分包的 Taro 小程序文件了,接下来,我们需要将打包出来的 dist 目录下的文件挪到主购项目中。

手动挪动?no,一个优秀的程序员应该想尽办法在开发过程中“偷懒”。

因此我们会自定义一个 Taro 插件,在 Taro 打包完成的时候,自动地将打包后的文件移动到主购项目中。

// plugin-mv/index.js
const fs = require('fs-extra')
const path = require('path') export default (ctx, options) => {
ctx.onBuildFinish(() => {
const blended = ctx.runOpts.blended || ctx.runOpts.options.blended if (!blended) return console.log('编译结束!') const rootPath = path.resolve(__dirname, '../..')
const miniappPath = path.join(rootPath, 'wxapp')
const outputPath = path.resolve(__dirname, '../dist') // testMini是你在京东购物小程序项目下的路由文件夹
const destPath = path.join(miniappPath, `./pages/testMini`) if (fs.existsSync(destPath)) {
fs.removeSync(destPath)
}
fs.copySync(outputPath, destPath) console.log('拷贝结束!')
})
}

在配置文件中加入这个自定义插件:

// config/index.js
const path = require('path') const config = {
// ...
plugins: [
'@tarojs/plugin-indie',
path.join(process.cwd(), '/plugin-mv/index.js')
]
// ...
}

重新执行cross-env NODE_ENV=production taro build --type weapp --blended打包命令,即可将 Taro 项目打包并拷贝到京东购物小程序项目对应的路由文件夹中。

至此,我们便可在开发者工具打开主购小程序项目,在 app.json 上添加对应的页面路由,并条件编译该路由,即可顺利地在开发者工具上看到 Hello World 字样。

引入公共方法、公共基类和公共组件

在日常的主购项目开发中,我们经常需要用到主购原生项目下封装的一些公共模块和方法,那么,通过混合编译打包过来的 Taro 项目是否也能通过某种办法顺利引用这些方法和模块呢?

答案是可以的。

引入公共方法

先简单说一下思路,更改 webpack 的配置项,通过 externals 配置处理公共方法和公共模块的引入,保留这些引入的语句,并将引入方式设置成 commonjs 相对路径的方式,详细代码如下所示:

const config = {
// ...
mini: {
// ...
webpackChain (chain) {
chain.merge({
externals: [
(context, request, callback) => {
const externalDirs = ['@common', '@api', '@libs']
const externalDir = externalDirs.find(dir => request.startsWith(dir)) if (process.env.NODE_ENV === 'production' && externalDir) {
const res = request.replace(externalDir, `../../../../${externalDir.substr(1)}`) return callback(null, `commonjs ${res}`)
} callback()
},
],
})
}
// ...
}
// ...
}

通过这样的处理之后,我们就可以顺利地在代码中通过 @common/*@api/*@libs/* 来引入原生项目下的 common/*api/*libs/* 了。

// src/pages/index/index.jsx

import { Component } from 'react'
import { View, Text, Button } from '@tarojs/components' import * as navigator from '@common/navigator.js' import './index.scss' export default class Index extends Component {
handleButtonClick () {
// 调用京东购物小程序的公共跳转方法
console.log('trigger click')
// 利用公共方法跳转京东购物小程序首页
navigator.goto('/pages/index/index')
} render () {
return (
<View className='index'>
<Text>Hello world!</Text>
<Button onClick={this.handleButtonClick.bind(this)} >点击跳转到主购首页</Button>
</View>
)
}
}

能看到引入的公共方法在打包后的小程序页面中也能顺利跑通了

引入公共组件

公共组件的引入更加简单,Taro 默认有提供引入公共组件的功能,但是如果是在混合开发模式下打包后,会发现公共组件的引用路径无法对应上,打包后页面配置的 json 文件引用的是以 Taro 打包出来的 dist 文件夹为小程序根目录,所以引入的路径也是以这个根目录为基础进行引用的,因此我们需要利用 Taro 的 alias 配置项来对路径进行一定的调整:

// pages/index/index.config.js
export default {
navigationBarTitleText: '首页',
navigationStyle: 'custom',
usingComponents: {
'nav-bar': '@components/nav-bar/nav-bar',
}
}
// config/index.js
const path = require('path') const config = {
// ...
alias: {
'@components': path.resolve(__dirname, '../../../components'),
}
// ...
}

接着我们在代码中直接对公共组件进行使用,并且无需引入:

// src/pages/index/index.jsx

import { Component } from 'react'
import { View, Text, Button } from '@tarojs/components' import * as navigator from '@common/navigator.js' import './index.scss' export default class Index extends Component {
handleButtonClick () {
// 调用京东购物小程序的公共跳转方法
console.log('trigger click')
// 利用公共方法跳转京东购物小程序首页
navigator.goto('/pages/index/index')
} render () {
return (
<View className='index'>
{/* 公共组件直接引入,无需引用 */}
<nav-bar
navBarData={{
title: '测试公共组件导航栏',
capsuleType: 'miniReturn',
backgroundValue: 'rgba(0, 255, 0, 1)'
}}
/>
<Text>Hello world!</Text>
<Button onClick={this.handleButtonClick.bind(this)} >点击跳转到主购首页</Button>
</View>
)
}
}

这样打包出来的 index.json 文件中 usingComponents 里的路径就能完美匹配原生小程序下的公共组件文件了,我们也由此能看到公共导航栏组件 nav-bar 在项目中的正常使用和运行了:

引入页面公共基类

在京东购物小程序,每一个原生页面在初始化的时候,基本都会引入一个 JDPage 基类,并用这个基类来修饰原本的 Page 实例,会给 Page 实例上原本的生命周期里添加一些埋点上报和参数传递等方法。

而我们在使用 Taro 进行混合编译开发时,再去单独地实现一遍这些方法显然是一种很愚蠢的做法,所以我们需要想办法在 Taro 项目里进行类似的操作,去引入 JDPage 这个基类。

首先第一步,我们需要在编译后的 JS 文件里,找到 Page 实例的定义位置,这里我们会使用正则匹配,去匹配这个 Page 实例在代码中定义的位置:

const pageRegx = /(Page)(\(Object.*createPageConfig.*?\{\}\)\))/

找到 Page 实例中,将 Page 实例转换成我们需要的 JDPage 基类,这些步骤我们都可以将他们写在我们之前自制 Taro 插件 plugin-mv 中去完成:

const isWeapp = process.env.TARO_ENV === 'weapp'
const jsReg = /pages\/(.*)\/index\.js$/
const pageRegx = /(Page)(\(Object.*createPageConfig.*?\{\}\)\))/ export default (ctx, options) => {
ctx.modifyBuildAssets(({ assets }) => {
Object.keys(assets).forEach(filename => {
const isPageJs = jsReg.test(filename) if (!isWeapp || !isPageJs) return const replaceFn = (match, p1, p2) => {
return `new (require('../../../../../bases/page.js').JDPage)${p2}`
} if (
!assets[filename]._value &&
assets[filename].children
) {
assets[filename].children.forEach(child => {
const isContentValid = pageRegx.test(child._value) if (!isContentValid) return child._value = child._value.replace(pageRegx, replaceFn)
})
} else {
assets[filename]._value = assets[filename]._value.replace(pageRegx, replaceFn)
}
})
})
}

经过插件处理之后,打包出来的页面 JS 里的 Page 都会被替换成 JDPage,也就拥有了基类的一些基础能力了。

至此,我们的 Taro 项目就基本已经打通了京东购物小程序的混合开发流程了。在能使用 Taro 无痛地开发京东购物小程序原生页面之余,还为之后的双端甚至多端运行打下了结实的基础。

存在问题

在使用 Taro 进行京东购物小程序原生页面的混合开发时,会发现 Taro 在一些公共样式和公共方法的处理上面,存在着以下一些兼容问题:

  1. Taro 会将多个页面的公共样式进行提取,放置于 common.wxss 文件中,但打包后的 app.wxss 文件却没有对这些公共样式进行引入,因此会导致页面的公共样式丢失。解决办法也很简单,只要在插件对 app.wxss 文件进行调整,添加对 common.wxss 的引入即可:
const wxssReg = /pages\/(.*)\/index\.wxss$/
function insertContentIntoFile (assets, filename, content) {
const { children, _value } = assets[filename]
if (children) {
children.unshift(content)
} else {
assets[filename]._value = `${content}${_value}`
}
}
export default (ctx, options) => {
ctx.modifyBuildAssets(({ assets }) => {
Object.keys(assets).forEach(filename => {
const isPageWxss = wxssReg.test(filename) // ... if (isPageWxss) {
insertContentIntoFile(assets, filename, "@import '../../common.wxss';\n")
}
}
})
}
  1. 使用 Taro 打包后的 app.js 文件里会存在部分对京东购物小程序公共方法的引用,该部分内容使用的是和页面 JS 同一个相对路径进行引用的,因此会存在引用路径错误的问题,解决办法也很简单,对 app.js 里的引用路径进行调整即可:
const appReg = /app\.js$/
const replaceList = ['common', 'api', 'libs']
export default (ctx, options) => {
ctx.modifyBuildAssets(({ assets }) => {
Object.keys(assets).forEach(filename => {
const isAppJS = appReg.test(filename)
const handleAppJsReplace = (item) => {
replaceList.forEach(name => {
item = item.replace(new RegExp(`../../../../../${name}`, 'g'), `'../../../${name}`)
})
}
if (isAppJS) {
if (
!assets[filename]._value &&
assets[filename].children
) {
assets[filename].children.forEach(child => {
replaceList.forEach(name => {
const value = child._value ? child._value : child handleAppJsReplace(value)
})
})
} else {
handleAppJsReplace(assets[filename]._value)
}
}
}
})
}

后续

本篇文章主要是讲述了 Taro 项目在京东购物小程序端的应用方式和开发方式,暂无涉及 H5 部分的内容。之后计划输出一份 Taro 项目在 H5 端的开发指南,并讲述 Taro 在多端开发中的性能优化方式。

欢迎关注凹凸实验室博客:aotu.io

或者关注凹凸实验室公众号(AOTULabs),不定时推送文章:

京东购物小程序 | Taro3 项目分包实践的更多相关文章

  1. python学习day4--python基础--购物小程序

    '''购物小程序:用户启动时先输入工资用户启动程序后打印商品列表允许用户选择购买商品允许用户不断购买各种商品购买时检测余额是否够,如果够直接扣款,否则打印余额不足允许用户主动退出程序,退出时打印已购商 ...

  2. 微信小程序mpvue项目使用WuxWeapp前端UI组件

    前言:这是一篇简单粗暴的使用指南 在最近的小程序项目里前端UI框架最后选择使用WuxWeapp,这篇文章记录一下如何在小程序mpvue项目中使用该UI组件. 步骤一:下载源码 (地址在这里)主要是里面 ...

  3. 微信小程序学习二 微信小程序的项目结构

    进来之后可以看到五个文件和两个文件夹,一般新建的小程序项目都是这种格式,但有些可能会不一样,不用担心,因为我们所要关注的文件是不会变的 pages 小程序的页面放置文件夹,每一个页面(page)包含四 ...

  4. 小程序demo项目实践

    今天开始做一个简单的小程序,做的过程中势必会有一些知识经验需要记录 项目初始化 首先创建好一个小程序项目,在app.wxss里面将自带的css样式统统去除,加上自己写的初始化样式 小程序目前不支持*号 ...

  5. 微信小程序开发入门与实践

    基础知识---- MINA 框架 为方便微信小程序开发,微信为小程序提供了 MINA 框架,这套框架集成了大量的原生组件以及 API.通过这套框架,我们可以方便快捷的完成相关的小程序开发工作. MIN ...

  6. 微信小程序从入门到实践(一)-设置底部导航栏

    微信小程序最多能加5个导航图标.因为我们只有两个默认页面,这里我们就添加两个导航图标 先看我们要达到的就是这么一个效果 接下来开始实践: (1)准备工作 找几个图标,将上述起好名字的图标 保存到 小程 ...

  7. 微信小程序上手项目

    小程序刚发布的时候何其风光,可能大家习惯性的对微信给予了过高的期待,加上一开始小程序的功能确实很孱弱,扫了很多人的兴. 经过最开始的热闹和喧嚣,如今微信小程序热度大减,但随着不断迭代,如今小程序的功能 ...

  8. 微信小程序< 3 > ~ 微信小程序开源项目合集

    简介 移动开发者想学习微信小程序需要学习一点HTML ,CSS和JS才能够比较快速的上手,参考自己学习Android学习过程,阅读源码是一个很好的方式,所以才收集了一些WeApp的开源项目. awes ...

  9. KNY三人组对YiSmile小程序的项目总结

    设想和目标 1.我们的小程序要解决什么问题? 针对于本校学生,服务于本校学生.由于丢东西,找东西的事情每天都在上演,空间说说,朋友圈,官方QQ,信息比较冗杂,没有一个固定的平台来专门提供学生.此外,教 ...

随机推荐

  1. 65.QT-UDP组播实现多人共享桌面(同时支持收发显示)

    这里我们只是简单学习下通过udp组播如何共享桌面demo.帧率上面比较低,毕竟没有用推流,只是简单的将图片发送到组播地址,而加入组播地址的客户端去取数据显示而已. 主要是为了学习UDP知识而写的,真的 ...

  2. cmake使用笔记,一些常用的命令

    我的工程目录如下: │ CMakeLists.txt ├─cmake_tutorial │ CMakeLists.txt │ cmake_tutorial.cpp │ cmake_tutorial.h ...

  3. 关于Mysql事务,你必须知道的几个知识点!

    Transaction事务 上期我们讲到了jpa的常用操作,查询.更新.删除等,但是如果在操作数据库事务时发生异常,数据会回滚吗?下面我们来看个例子 UserController新增如下代码: @Ge ...

  4. SpringCloud-OAuth2(四):改造篇

    本片主要讲SpringCloud Oauth2篇的实战改造,如动态权限.集成JWT.更改默认url.数据库加载client信息等改造. 同时,这应该也是我这系列博客的完结篇. 关于Oauth2,我也想 ...

  5. Java核心API-日期时间

    java.util.Date Date类用来表示时间点. 时间是用距离一个固定时间点的毫秒数表示的,这个时间点就是纪元. UTC时间是为表示这个纪元的科学标准时间,从1970年1月1日0时开始.另一种 ...

  6. ClickHouse学习系列之七【系统命令介绍】

    背景  前面介绍了ClickHouse相关的系列文章,该系列文章包括了安装.权限管理.副本分片.配置说明等.这次介绍一些ClickHouse相关的系统命令,如重载配置文件.关闭服务和进程.停止和启动后 ...

  7. .NET 云原生架构师训练营(设计原则与模式)--学习笔记

    在复杂系统的架构设计中引入设计原则与模式,能够极大降低复杂系统开发.和维护的成本 目录 几个问题 为什么要学习设计模式 优良架构设计的具体指标 理解复杂系统 面向对象思想(指导复杂系统的分析.设计.实 ...

  8. Docker学不会?不妨看看这篇文章

    大家好,我是辰哥! 上一篇文章(2300+字!在不同系统上安装Docker!)教大家如何在系统上安装docker,今天咱们来学习docker的基本使用. 辰哥将在本文里详细介绍docker的各种使用命 ...

  9. SpringCloud:Zuul路由配置超时问题

    测试访问时长 修改下业务类,增加sleep休眠时长,以此查看Zuul的熔断 @GetMapping("/test1") public Object test1() { try { ...

  10. Jquery Validate动态添加和删除校验规则

    以下面文本框为例, <input type="text"name="username" id="username"/> 我们要让 ...