背景

京东购物小程序作为京东小程序业务流量的主要入口,承载着许多的活动和页面,而很多的活动在小程序开展的同时,也会在京东 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. C#调用JAVA(一)制作jar包

    1 //AndroidJavaClass类可以理解为某一个类 2 //AndroidJavaObject类可以理解为某一个类中的一个对象 打开AS,创建一个空项目 输入项目名,包名,本地路径以及语言之 ...

  2. mysql字符集utf8和utf8mb4区别

    1.起因 公司游戏项目上线第一天,出现单个区服异常宕机的问题,根据日志排查下来,连接数据的时候报错,后面排查是因为有玩家插入Emoji 等表情导致无法存储如数据库,数据库字符集编码为utf8,后续改成 ...

  3. 温故知新,CSharp遇见异步编程(Async/Await),聊聊异步编程最佳做法

    什么是异步编程(Async/Await) Async/Await本质上是通过编译器实现的语法糖,它让我们能够轻松的写出简洁.易懂.易维护的异步代码. Async/Await是C# 5引入的关键字,用以 ...

  4. oracle查询用户所在表为空的表名

    1 select * from user_tables where num_rows = 0 查询之后发现查询的不全,查询之后发现num_rows为空 select * from user_table ...

  5. NoSql非关系型数据库之MongoDB应用(三):MongoDB在项目中的初步应用

    业精于勤,荒于嬉:行成于思,毁于随. 我们可以结合相关的IDE做一个简单的增删改查了,实现MongoDB在项目中的初步应用. 前提是安装了MongoDB服务和MongoDB可视化工具,没有安装的可以点 ...

  6. Java实验项目三——面向对象定义职工类和日期类

    Program:按照如下要求设计类: (1)设计一个日期类,用于记录年.月.日,并提供对日期处理的常用方法. (2)设计一个职工类,该职工类至少具有下面的属性:职工号,姓名,性别,生日,工作部门,参加 ...

  7. php弱类型比较

    前言:今天XCTF题目中出现了弱类型比较,特别过来记录一下, 0x01: == 是弱类型比较,两个不同类型比较时,会自动转换成相同类型后再比较值 ===是强比较,需要比较值和类型 0x02: 看下图案 ...

  8. 2012年第三届蓝桥杯C/C++程序设计本科B组省赛 取球博弈

    2012年第三届蓝桥杯C/C++程序设计本科B组省赛 取球博弈 题目描述 **取球博弈 今盒子里有n个小球,A.B两人轮流从盒中取球,每个人都可以看到另一个人取了多少个,也可以看到盒中还剩下多少个,并 ...

  9. 解决ionic5多个模态关闭一个其他不显示的问题

    ionic5 modal使用过程中,在模态窗中打开另外一个模态窗,浏览器中显示正常,但是andorid8系统真机调试时,关闭最上层模态窗,上级模态窗DOM中存在,但是不显示. 原因是android版本 ...

  10. css--filter(滤镜) 属性

    前言 前段时间找工作面试官问到一个问题,你如何将一个网页整体置灰?面试遇到这样的问题,一下束手无策,之前没有接触过这样的需求,因此没有回答上来,面试结束我才知道了这是考查对 CSS3 的新属性的了解. ...