背景

京东购物小程序作为京东小程序业务流量的主要入口,承载着许多的活动和页面,而很多的活动在小程序开展的同时,也会在京东 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项目为例:

  1. yarn global add @tarojs/cli@3.2.6
  2. taro init

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

  1. // src/pages/index/index.jsx
  2. import { Component } from 'react'
  3. import { View, Text, Button } from '@tarojs/components'
  4. import './index.scss'
  5. export default class Index extends Component {
  6. handleButtonClick () {
  7. // 调用京东购物小程序的公共跳转方法
  8. console.log('trigger click')
  9. }
  10. render () {
  11. return (
  12. <View className='index'>
  13. <Text>Hello world!</Text>
  14. <Button onClick={this.handleButtonClick.bind(this)} >点击跳转到主购首页</Button>
  15. </View>
  16. )
  17. }
  18. }

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

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

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

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

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

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

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

  1. // 必须引用 Taro 项目的入口文件
  2. const taroApp = require('./taro/app.js').taroApp
  3. App({
  4. onShow () {
  5. // 可选,调用 Taro 项目 app 的 onShow 生命周期
  6. taroApp.onShow()
  7. },
  8. onHide () {
  9. // 可选,调用 Taro 项目 app 的 onHide 生命周期
  10. taroApp.onHide()
  11. }
  12. })

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

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

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

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

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

  1. yarn add --dev @tarojs/plugin-indie

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

  1. // config/index.js
  2. const config = {
  3. // ...
  4. plugins: [
  5. '@tarojs/plugin-indie'
  6. ]
  7. // ...
  8. }

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

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

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

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

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

  1. // plugin-mv/index.js
  2. const fs = require('fs-extra')
  3. const path = require('path')
  4. export default (ctx, options) => {
  5. ctx.onBuildFinish(() => {
  6. const blended = ctx.runOpts.blended || ctx.runOpts.options.blended
  7. if (!blended) return
  8. console.log('编译结束!')
  9. const rootPath = path.resolve(__dirname, '../..')
  10. const miniappPath = path.join(rootPath, 'wxapp')
  11. const outputPath = path.resolve(__dirname, '../dist')
  12. // testMini是你在京东购物小程序项目下的路由文件夹
  13. const destPath = path.join(miniappPath, `./pages/testMini`)
  14. if (fs.existsSync(destPath)) {
  15. fs.removeSync(destPath)
  16. }
  17. fs.copySync(outputPath, destPath)
  18. console.log('拷贝结束!')
  19. })
  20. }

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

  1. // config/index.js
  2. const path = require('path')
  3. const config = {
  4. // ...
  5. plugins: [
  6. '@tarojs/plugin-indie',
  7. path.join(process.cwd(), '/plugin-mv/index.js')
  8. ]
  9. // ...
  10. }

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

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

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

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

答案是可以的。

引入公共方法

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

  1. const config = {
  2. // ...
  3. mini: {
  4. // ...
  5. webpackChain (chain) {
  6. chain.merge({
  7. externals: [
  8. (context, request, callback) => {
  9. const externalDirs = ['@common', '@api', '@libs']
  10. const externalDir = externalDirs.find(dir => request.startsWith(dir))
  11. if (process.env.NODE_ENV === 'production' && externalDir) {
  12. const res = request.replace(externalDir, `../../../../${externalDir.substr(1)}`)
  13. return callback(null, `commonjs ${res}`)
  14. }
  15. callback()
  16. },
  17. ],
  18. })
  19. }
  20. // ...
  21. }
  22. // ...
  23. }

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

  1. // src/pages/index/index.jsx
  2. import { Component } from 'react'
  3. import { View, Text, Button } from '@tarojs/components'
  4. import * as navigator from '@common/navigator.js'
  5. import './index.scss'
  6. export default class Index extends Component {
  7. handleButtonClick () {
  8. // 调用京东购物小程序的公共跳转方法
  9. console.log('trigger click')
  10. // 利用公共方法跳转京东购物小程序首页
  11. navigator.goto('/pages/index/index')
  12. }
  13. render () {
  14. return (
  15. <View className='index'>
  16. <Text>Hello world!</Text>
  17. <Button onClick={this.handleButtonClick.bind(this)} >点击跳转到主购首页</Button>
  18. </View>
  19. )
  20. }
  21. }

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

引入公共组件

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

  1. // pages/index/index.config.js
  2. export default {
  3. navigationBarTitleText: '首页',
  4. navigationStyle: 'custom',
  5. usingComponents: {
  6. 'nav-bar': '@components/nav-bar/nav-bar',
  7. }
  8. }
  1. // config/index.js
  2. const path = require('path')
  3. const config = {
  4. // ...
  5. alias: {
  6. '@components': path.resolve(__dirname, '../../../components'),
  7. }
  8. // ...
  9. }

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

  1. // src/pages/index/index.jsx
  2. import { Component } from 'react'
  3. import { View, Text, Button } from '@tarojs/components'
  4. import * as navigator from '@common/navigator.js'
  5. import './index.scss'
  6. export default class Index extends Component {
  7. handleButtonClick () {
  8. // 调用京东购物小程序的公共跳转方法
  9. console.log('trigger click')
  10. // 利用公共方法跳转京东购物小程序首页
  11. navigator.goto('/pages/index/index')
  12. }
  13. render () {
  14. return (
  15. <View className='index'>
  16. {/* 公共组件直接引入,无需引用 */}
  17. <nav-bar
  18. navBarData={{
  19. title: '测试公共组件导航栏',
  20. capsuleType: 'miniReturn',
  21. backgroundValue: 'rgba(0, 255, 0, 1)'
  22. }}
  23. />
  24. <Text>Hello world!</Text>
  25. <Button onClick={this.handleButtonClick.bind(this)} >点击跳转到主购首页</Button>
  26. </View>
  27. )
  28. }
  29. }

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

引入页面公共基类

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

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

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

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

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

  1. const isWeapp = process.env.TARO_ENV === 'weapp'
  2. const jsReg = /pages\/(.*)\/index\.js$/
  3. const pageRegx = /(Page)(\(Object.*createPageConfig.*?\{\}\)\))/
  4. export default (ctx, options) => {
  5. ctx.modifyBuildAssets(({ assets }) => {
  6. Object.keys(assets).forEach(filename => {
  7. const isPageJs = jsReg.test(filename)
  8. if (!isWeapp || !isPageJs) return
  9. const replaceFn = (match, p1, p2) => {
  10. return `new (require('../../../../../bases/page.js').JDPage)${p2}`
  11. }
  12. if (
  13. !assets[filename]._value &&
  14. assets[filename].children
  15. ) {
  16. assets[filename].children.forEach(child => {
  17. const isContentValid = pageRegx.test(child._value)
  18. if (!isContentValid) return
  19. child._value = child._value.replace(pageRegx, replaceFn)
  20. })
  21. } else {
  22. assets[filename]._value = assets[filename]._value.replace(pageRegx, replaceFn)
  23. }
  24. })
  25. })
  26. }

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

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

存在问题

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

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

后续

本篇文章主要是讲述了 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. Mysql8关于hashjoin的代码处理方式

    Mysql8关于hashjoin的代码处理方式 目录 Mysql8关于hashjoin的代码处理方式 1 表的Schema如下所示: 2 HashJoin代码实现 3 总结 1 表的Schema如下所 ...

  2. 用vue ui创建的项目怎么关闭eslint校验

    在Vue Cli的控制面板找到配置-ESLint configuration,然后关闭保存时检查就可以了

  3. JavaScript 沙盒模式

    微前端已经成为前端领域比较火爆的话题,在技术方面,微前端有一个始终绕不过去的话题就是前端沙箱 什么是沙箱 Sandboxie(又叫沙箱.沙盘)即是一个虚拟系统程序,允许你在沙盘环境中运行浏览器或其他程 ...

  4. 2、SpringBoot整合之SpringBoot整合servlet

    SpringBoot整合servlet 一.创建SpringBoot项目,仅选择Web模块即可 二.在POM文件中添加依赖 <!-- 添加servlet依赖模块 --> <depen ...

  5. testt

    一级标题 二级标题 三级标题 四级标题 l 1

  6. 双向链表(DoubleLinkList)

    双向链表 有关链表的知识可以点击我上篇文章这里就不再赘述LinkedList 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱.所以,从双向链表中的任意一个 ...

  7. C语言:位运算符

    异或        ^     两个二进制位相同结果为0:不相同结果为1              1^1=0    1^0=1   0^1=1  0^0=1 按位或    |      两个二进制位 ...

  8. css颜色介绍和背景设置

    现在美丽网页的设计图中颜色五花八门的,网页模块中漂亮背景图也很多,网页中颜色和背景设置必不可少,接下来我们就先学颜色是如何表达的,要知其然,知其所以然. 颜色表达形式 1.RGB:rgb( red, ...

  9. lombok之@Data

    在实体类的编写过程中,常常需要应用大量的get.set方法,需要写大量的重复代码,即有的工具有自动生成功能,当时也会使实体类中产生大量冗余代码,使得代码变,springboot为我们提供了相应注解可以 ...

  10. 在Springboot + Mybaitis-plus 项目中利用Jackson实现json对java多态的(反)序列化

    Jackson允许配置多态类型处理,当JSON面对的转换对象是一个接口.抽象类或者一个基类的时候,可以通过一定配置实现JSON的转换.在实际项目中,Controller层接收入参以及在Dao层将对象以 ...