关于前端:京东购物小程序-Taro3-项目分包实践

29次阅读

共计 8699 个字符,预计需要花费 22 分钟才能阅读完成。

背景

京东购物小程序作为京东小程序业务流量的次要入口,承载着许多的流动和页面,而很多的流动在小程序发展的同时,也会在京东 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")
       }
     }
      })
    }
  2. 应用 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 在多端开发中的性能优化形式。

正文完
 0