京东购物小程序做为京东小程序业务流量的主要入口,承载着许多的活动和页面,而不少的活动在小程序开展的同时,也会在京东 APP 端进行同步的 H5 端页面的投放。这时候,一个相同的活动,须要同时开发原生小程序页面和H5页面的难题又摆在了前端程序员的面前。 幸运的是,咱们有 Taro,一个开放式跨端跨框架解决方案。能够帮助咱们很好地解决这种跨端开发的问题。但不幸的是,Taro 并无提供一套完整的将项目做为独立分包运行在小程序中的解决方案。所以,本篇文章将介绍如何经过一套合适的混合开发实践方案,解决 Taro 项目做为独立分包后出现的一些问题。javascript
总的来讲,若要使用 Taro 3 将项目做为独立分包运行在京东购物小程序,咱们须要完成如下四个步骤:css
那么接下来,咱们将对每一个步骤进行详细的说明,告诉你们怎么作,以及为何要这样作。前端
首先咱们须要全局安装 Taro 3,并保证全局和项目下的 Taro 的版本高于3.1.4
,这里咱们以新建的Taro 3.2.6
项目为例:java
yarn global add @tarojs/cli@3.2.6
taro init
复制代码
以后咱们在项目中用React
语法写入简单的 hello word
代码,并在代码中留出一个Button
组件来为未来调用京东购物小程序的公共跳转方法作准备。react
// 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>
)
}
}
复制代码
俗话说得好,有竟者事竟成,在开始编码前,咱们来简单地定几个小目标:webpack
在将 Taro 项目打包进主购小程序时,咱们很快就遇到了第一个难题:Taro 项目下默认的命令打包出来的文件是一整个小程序,如何打包成一个单独的分包?程序员
幸运的是,在3.1.4
版本后的 Taro,提供了混合开发的功能,意思为可让原生项目和 Taro 打包出来的文件混合使用,只须要在打包时加入 --blended
命令便可。web
cross-env NODE_ENV=production taro build --type weapp --blended
复制代码
blended
中文翻译是混合的意思,在加入了这个命令后,Taro 会在构建出来的 app.js
文件中导出 taroApp
,咱们能够经过引入这个变量来在原生项目下的 app.js
调用 Taro 项目 app 的 onShow、onHide 等生命周期。shell
// 必须引用 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
文件里的逻辑能优先执行。json
理想很丰满,现实很骨感,因为咱们须要将 Taro 项目做为单独的分包打包到主购项目中,所以这种直接在原生项目的 app.js 中引入的方式只适用于主包内的页面,而不适用于分包。
要解决混合开发在分包模式下不适用的问题,咱们须要引入另一个 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")
,并增长对应 module
的 sourceMap
映射。在进行了这样的处理后,便能保证每次进入 Taro 项目下的小程序页面时,都能优先执行 Taro 打包出来的运行时文件了。
到目前为止,咱们已经能够成功打包出能独立分包的 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 在一些公共样式和公共方法的处理上面,存在着如下一些兼容问题:
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")
}
}
})
}
复制代码
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 在多端开发中的性能优化方式。