|
| 1 | +title: 使用Taro3将项目作为独立分包运行在京东购物小程序中 |
| 2 | +subtitle: 介绍如何把一个Taro3项目作为独立分包运行在京东购物小程序,并顺利地进行开发、调试和上线等操作。 |
| 3 | +cover: https://img12.360buyimg.com/img/s900x383_jfs/t1/198722/1/1593/173438/610a3741E9707add5/8bd57c1599217905.jpg |
| 4 | +categories: Web开发 |
| 5 | +tags: |
| 6 | + - Taro |
| 7 | + - 混合开发 |
| 8 | + - 京东购物小程序 |
| 9 | +author: |
| 10 | + nick: 小屁 |
| 11 | + github_name: xuanzebin |
| 12 | +date: 2021-08-04 14:21:00 |
| 13 | +wechat: |
| 14 | + share_cover: https://img12.360buyimg.com/img/s900x543_jfs/t1/178574/28/17638/256141/610a3741Ecf5abc01/b586bc488042b570.jpg |
| 15 | + share_title: 使用Taro3将项目作为独立分包运行在京东购物小程序中 |
| 16 | + share_desc: 介绍如何把一个Taro3项目作为独立分包运行在京东购物小程序,并顺利地进行开发、调试和上线等操作。 |
| 17 | +--- |
| 18 | +## 目录 |
| 19 | +- 整体流程 |
| 20 | +- 应用过程 |
| 21 | + - 准备合适的开发环境 |
| 22 | + - 将Taro项目作为独立分包进行编译打包 |
| 23 | + - 引入@tarojs/plugin-indie插件,保证Taro前置逻辑优先执行 |
| 24 | + - 引入@tarojs/plugin-mv插件,自动化挪动打包后的文件 |
| 25 | + - 引入公共方法、公共基类和公共组件 |
| 26 | + - 引入公共方法 |
| 27 | + - 引入公共组件 |
| 28 | + - 引入页面公共基类 |
| 29 | +- 存在问题 |
| 30 | +- 后续 |
| 31 | + |
| 32 | + |
| 33 | + |
| 34 | +## 整体流程 |
| 35 | +总的来说,若要使用Taro3将项目作为独立分包运行在京东购物小程序,我们需要完成以下四个步骤: |
| 36 | +1. **准备开发环境**,下载正确的Taro版本 |
| 37 | +2. 安装**Taro混合编译插件**,解决独立分包的运行时逻辑问题 |
| 38 | +3. 调用Taro提供的**混合编译命令**,对Taro项目进行打包 |
| 39 | +4. **挪动打包后Taro文件**到主购小程序目录下 |
| 40 | + |
| 41 | +那么接下来,我们将对每个步骤进行详细的说明,告诉大家怎么做,以及为什么要这样做。 |
| 42 | + |
| 43 | +## 应用过程 |
| 44 | + |
| 45 | +### 准备合适的开发环境 |
| 46 | +首先我们需要全局安装Taro3,并保证**全局和项目下的Taro的版本高于`3.1.4`**,这里我们以新建的`Taro 3.2.6`项目为例: |
| 47 | +```shell |
| 48 | +yarn global add @tarojs/cli@3.2.6 |
| 49 | + |
| 50 | +taro init |
| 51 | +``` |
| 52 | + |
| 53 | +之后我们在项目中用`React`语法写入简单的hello word代码,并在代码中留出一个`Button`组件来为将来调用京购小程序的公共跳转方法做准备。 |
| 54 | + |
| 55 | + |
| 56 | +俗话说得好,有竟者事竟成,在开始编码前,我们来简单地定几个小目标: |
| 57 | +- **成功地将Taro项目Hello world在京购小程序的分包路由下跑通** |
| 58 | +- **引入京购小程序的公共组件nav-bar并能正常使用** |
| 59 | +- **引入公共方法navigator.goto并能正常使用** |
| 60 | +- **引入公共基类JDPage并能正常使用** |
| 61 | + |
| 62 | +### 将Taro项目作为独立分包进行编译打包 |
| 63 | +在将Taro项目打包进主购小程序时,我们很快就遇到了第一个难题:Taro项目下默认的命令打包出来的文件是一整个小程序,**如何打包成一个单独的分包?** |
| 64 | + |
| 65 | +幸运的是,在`3.1.4`版本后的Taro,提供了混合开发的功能,意思为可以让原生项目和Taro打包出来的文件混合使用,只需要**在打包时加入`--blended`命令**即可。 |
| 66 | + |
| 67 | +``` |
| 68 | +cross-env NODE_ENV=production taro build --type weapp --blended |
| 69 | +``` |
| 70 | + |
| 71 | +`blended`中文翻译是混合的意思,在加入了这个命令后,Taro会在构建出来的`app.js`文件中导出`taroApp`,我们可以通过引入这个变量来在原生项目下的`app.js`调用Taro项目app的onShow、onHide等生命周期。 |
| 72 | + |
| 73 | +```javascript |
| 74 | +// 必须引用 Taro 项目的入口文件 |
| 75 | +const taroApp = require('./taro/app.js').taroApp |
| 76 | + |
| 77 | +App({ |
| 78 | + onShow () { |
| 79 | + // 可选,调用 Taro 项目 app 的 onShow 生命周期 |
| 80 | + taroApp.onShow() |
| 81 | + }, |
| 82 | + |
| 83 | + onHide () { |
| 84 | + // 可选,调用 Taro 项目 app 的 onHide 生命周期 |
| 85 | + taroApp.onHide() |
| 86 | + } |
| 87 | +}) |
| 88 | +``` |
| 89 | + |
| 90 | +如果单纯地使用`blended`命令,即使我们不需要调用onShow、onHide这些生命周期,我们也需要**在原生项目下的`app.js`里引入Taro项目的入口文件**,因为在执行我们的小程序页面时,我们需要提前初始化一些运行时的逻辑,因此要保证Taro项目下的`app.js`文件里的逻辑能优先执行。 |
| 91 | + |
| 92 | +理想很丰满,现实很骨感,由于我们需要将Taro项目作为单独的分包打包到主购项目中,因此这种直接在原生项目的app.js中引入的方式**只适用于主包内的页面,而不适用于分包。** |
| 93 | + |
| 94 | +### 引入@tarojs/plugin-indie插件,保证Taro前置逻辑优先执行 |
| 95 | +要解决混合开发在分包模式下不适用的问题,我们需要引入另外一个Taro插件`@tarojs/plugin-indie`。 |
| 96 | + |
| 97 | +首先我们先在Taro项目中对该插件进行安装 |
| 98 | +```shell |
| 99 | +yarn add --dev @tarojs/plugin-indie |
| 100 | +``` |
| 101 | + |
| 102 | +之后我们在Taro的配置项文件中对该插件进行引入 |
| 103 | +```javascript |
| 104 | +// config/index.js |
| 105 | +const config = { |
| 106 | + // ... |
| 107 | + plugins: [ |
| 108 | + '@tarojs/plugin-indie' |
| 109 | + ] |
| 110 | + // ... |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +查看该插件的源码,我们可以发现该插件处理的逻辑非常简单,就是在编译代码时,对每个页面下的js chunk文件内容进行调整,在这些js文件的开头加上`require("../../app")`,并增加对应module的sourceMap映射。在进行了这样的处理后,便能保证**每次进入Taro项目下的小程序页面时,都能优先执行Taro打包出来的运行时文件了。** |
| 115 | + |
| 116 | +### 引入@tarojs/plugin-mv插件,自动化挪动打包后的文件 |
| 117 | +到目前为止,我们已经可以成功打包出能独立分包的Taro小程序文件了,接下来,我们需要将打包出来的dist目录下的文件挪到主购项目中。 |
| 118 | + |
| 119 | +手动挪动?no,一个优秀的程序员应该想尽办法在开发过程中“偷懒”。 |
| 120 | +因此我们会自定义一个Taro插件,在Taro打包完成的时候,**自动地将打包后的文件移动到主购项目中。** |
| 121 | +```javascript |
| 122 | +// plugin-mv/index.js |
| 123 | +const fs = require('fs-extra') |
| 124 | +const path = require('path') |
| 125 | + |
| 126 | +export default (ctx, options) => { |
| 127 | + ctx.onBuildFinish(() => { |
| 128 | + const blended = ctx.runOpts.blended || ctx.runOpts.options.blended |
| 129 | + |
| 130 | + if (!blended) return |
| 131 | + |
| 132 | + console.log('编译结束!') |
| 133 | + |
| 134 | + const rootPath = path.resolve(__dirname, '../..') |
| 135 | + const miniappPath = path.join(rootPath, 'wxapp') |
| 136 | + const outputPath = path.resolve(__dirname, '../dist') |
| 137 | + |
| 138 | + // testMini是你在京购项目下的路由文件夹 |
| 139 | + const destPath = path.join(miniappPath, `./pages/testMini`) |
| 140 | + |
| 141 | + if (fs.existsSync(destPath)) { |
| 142 | + fs.removeSync(destPath) |
| 143 | + } |
| 144 | + fs.copySync(outputPath, destPath) |
| 145 | + |
| 146 | + console.log('拷贝结束!') |
| 147 | + }) |
| 148 | +} |
| 149 | +``` |
| 150 | + |
| 151 | +在配置文件中加入这个自定义插件: |
| 152 | +```javascript |
| 153 | +// config/index.js |
| 154 | +const path = require('path') |
| 155 | + |
| 156 | +const config = { |
| 157 | + // ... |
| 158 | + plugins: [ |
| 159 | + '@tarojs/plugin-indie', |
| 160 | + path.join(process.cwd(), '/plugin-mv/index.js') |
| 161 | + ] |
| 162 | + // ... |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +重新执行`cross-env NODE_ENV=production taro build --type weapp --blended`打包命令,即可将Taro项目打包并拷贝到京购项目对应的路由文件夹中。 |
| 167 | + |
| 168 | +至此,我们便可在开发者工具打开主购小程序项目,在app.json上添加对应的页面路由,并条件编译该路由,即可顺利地在开发者工具上看到`Hello World`字样。 |
| 169 | + |
| 170 | + |
| 171 | + |
| 172 | +### 引入公共方法、公共基类和公共组件 |
| 173 | +在日常的主购项目开发中,我们经常需要用到主购原生项目下封装的一些公共模块和方法,那么,通过混合编译打包过来的Taro项目是否也有方法,让Taro打包的小程序页面也能顺利引用这些方法和模块呢? |
| 174 | + |
| 175 | +答案是有的。 |
| 176 | + |
| 177 | +#### 引入公共方法 |
| 178 | +先简单说一下思路,更改webpack的配置项,**通过externals配置处理公共方法和公共模块的引入**,保留这些引入的语句,并将引入方式设置成commonjs相对路径的方式,详细代码如下所示: |
| 179 | + |
| 180 | +```javascript |
| 181 | +const config = { |
| 182 | + // ... |
| 183 | + mini: { |
| 184 | + // ... |
| 185 | + webpackChain (chain) { |
| 186 | + chain.merge({ |
| 187 | + externals: [ |
| 188 | + (context, request, callback) => { |
| 189 | + const externalDirs = ['@common', '@api', '@libs'] |
| 190 | + const externalDir = externalDirs.find(dir => request.startsWith(dir)) |
| 191 | + |
| 192 | + if (process.env.NODE_ENV === 'production' && externalDir) { |
| 193 | + const res = request.replace(externalDir, `../../../../${externalDir.substr(1)}`) |
| 194 | + |
| 195 | + return callback(null, `commonjs ${res}`) |
| 196 | + } |
| 197 | + |
| 198 | + callback() |
| 199 | + }, |
| 200 | + ], |
| 201 | + }) |
| 202 | + } |
| 203 | + // ... |
| 204 | + } |
| 205 | + // ... |
| 206 | +} |
| 207 | +``` |
| 208 | + |
| 209 | +通过这样的处理之后,我们就可以顺利地在代码中通过`@common/*`、`@api/*`和`@libs/*`来引入原生项目下的`common/*`、`api/*`和`libs/*`了。 |
| 210 | + |
| 211 | + |
| 212 | + |
| 213 | +能看到引入的公共方法在打包后的小程序页面中也能顺利跑通了 |
| 214 | + |
| 215 | + |
| 216 | + |
| 217 | +#### 引入公共组件 |
| 218 | +公共组件的引入更加简单,Taro默认有提供引入公共组件的功能,但是如果是在混合开发模式下打包后,会发现公共组件的引用路径无法对应上,打包后页面配置的json文件引用的是以Taro打包出来的dist文件夹为小程序根目录,所以引入的路径也是以这个根目录为基础进行引用的,因此我们**需要利用webpack的alias配置项来对路径进行一定的调整:** |
| 219 | +```javascript |
| 220 | +// pages/index/index.config.js |
| 221 | +export default { |
| 222 | + navigationBarTitleText: '首页', |
| 223 | + navigationStyle: 'custom', |
| 224 | + usingComponents: { |
| 225 | + 'nav-bar': '@components/nav-bar/nav-bar', |
| 226 | + } |
| 227 | +} |
| 228 | +``` |
| 229 | +```javascript |
| 230 | +// config/index.js |
| 231 | +const path = require('path') |
| 232 | + |
| 233 | +const config = { |
| 234 | + // ... |
| 235 | + alias: { |
| 236 | + '@components': path.resolve(__dirname, '../../../components'), |
| 237 | + } |
| 238 | + // ... |
| 239 | +} |
| 240 | +``` |
| 241 | + |
| 242 | +接着我们在代码中直接对公共组件进行使用,并且无需引入: |
| 243 | + |
| 244 | + |
| 245 | + |
| 246 | +这样打包出来的`index.json`文件中`usingComponents`里的路径就能完美匹配原生小程序下的公共组件文件了,我们也由此能看到公共导航栏组件`nav-bar`在项目中的正常使用和运行了: |
| 247 | + |
| 248 | + |
| 249 | + |
| 250 | +#### 引入页面公共基类 |
| 251 | +在京东购物小程序,每一个原生页面在初始化的时候,基本都会引入一个JDPage基类,并用这个基类来修饰原本的Page实例,会**给Page实例上原本的生命周期里添加一些埋点上报和参数传递等方法。** |
| 252 | + |
| 253 | +而我们在使用Taro进行混合编译开发时,再去单独地实现一遍这些方法显然是一种很愚蠢的做法,所以我们需要想办法在Taro项目里进行类似的操作,去引入JDPage这个基类。 |
| 254 | + |
| 255 | +首先第一步,我们需要在编译后的JS文件里,找到Page实例的定义位置,这里我们会**使用正则匹配**,去匹配这个Page实例在代码中定义的位置: |
| 256 | +```javascript |
| 257 | +const pageRegx = /(Page)(\(Object.*createPageConfig.*?\{\}\)\))/ |
| 258 | +``` |
| 259 | + |
| 260 | +找到Page实例中,将Page实例转换成我们需要的JDPage基类,这些步骤我们都可以将他们写在我们之前自制Taro插件`plugin-mv`中去完成: |
| 261 | + |
| 262 | +```javascript |
| 263 | +const isWeapp = process.env.TARO_ENV === 'weapp' |
| 264 | +const jsReg = /pages\/(.*)\/index\.js$/ |
| 265 | +const pageRegx = /(Page)(\(Object.*createPageConfig.*?\{\}\)\))/ |
| 266 | + |
| 267 | +export default (ctx, options) => { |
| 268 | + ctx.modifyBuildAssets(({ assets }) => { |
| 269 | + Object.keys(assets).forEach(filename => { |
| 270 | + const isPageJs = jsReg.test(filename) |
| 271 | + |
| 272 | + if (!isWeapp || !isPageJs) return |
| 273 | + |
| 274 | + const replaceFn = (match, p1, p2) => { |
| 275 | + return `new (require('../../../../../bases/page.js').JDPage)${p2}` |
| 276 | + } |
| 277 | + |
| 278 | + if ( |
| 279 | + !assets[filename]._value && |
| 280 | + assets[filename].children |
| 281 | + ) { |
| 282 | + assets[filename].children.forEach(child => { |
| 283 | + const isContentValid = pageRegx.test(child._value) |
| 284 | + |
| 285 | + if (!isContentValid) return |
| 286 | + |
| 287 | + child._value = child._value.replace(pageRegx, replaceFn) |
| 288 | + }) |
| 289 | + } else { |
| 290 | + assets[filename]._value = assets[filename]._value.replace(pageRegx, replaceFn) |
| 291 | + } |
| 292 | + }) |
| 293 | + }) |
| 294 | +} |
| 295 | +``` |
| 296 | + |
| 297 | +经过插件处理之后,打包出来的页面JS里的Page都会被替换成JDPage,也就拥有了基类的一些基础能力了。 |
| 298 | + |
| 299 | +至此,我们的Taro项目就基本已经打通了京购小程序的混合开发流程了。**在能使用Taro无痛地开发京购小程序原生页面之余,还为之后的双端甚至多端运行打下了结实的基础。** |
| 300 | + |
| 301 | +## 存在问题 |
| 302 | +在使用Taro进行京购小程序原生页面的混合开发时,会发现Taro在一些公共样式和公共方法的处理上面,存在着以下一些兼容问题: |
| 303 | +1. Taro会将多个页面的公共样式进行提取,放置于`common.wxss`文件中,但打包后的`app.wxss`文件却没有对这些公共样式进行引入,因此会导致页面的公共样式丢失。解决办法也很简单,只要在插件对`app.wxss`文件进行调整,添加对`common.wxss`的引入即可: |
| 304 | +```javascript |
| 305 | +const wxssReg = /pages\/(.*)\/index\.wxss$/ |
| 306 | +function insertContentIntoFile (assets, filename, content) { |
| 307 | + const { children, _value } = assets[filename] |
| 308 | + if (children) { |
| 309 | + children.unshift(content) |
| 310 | + } else { |
| 311 | + assets[filename]._value = `${content}${_value}` |
| 312 | + } |
| 313 | +} |
| 314 | +export default (ctx, options) => { |
| 315 | + ctx.modifyBuildAssets(({ assets }) => { |
| 316 | + Object.keys(assets).forEach(filename => { |
| 317 | + const isPageWxss = wxssReg.test(filename) |
| 318 | + |
| 319 | + // ... |
| 320 | + |
| 321 | + if (isPageWxss) { |
| 322 | + insertContentIntoFile(assets, filename, "@import '../../common.wxss';\n") |
| 323 | + } |
| 324 | + } |
| 325 | + }) |
| 326 | +} |
| 327 | +``` |
| 328 | +2. 使用Taro打包后的`app.js`文件里会存在部分对京购小程序公共方法的引用,该部分内容使用的是和页面JS同一个相对路径进行引用的,因此会存在引用路径错误的问题,解决办法也很简单,对`app.js`里的引用路径进行调整即可: |
| 329 | +```javascript |
| 330 | +const appReg = /app\.js$/ |
| 331 | +const replaceList = ['common', 'api', 'libs'] |
| 332 | +export default (ctx, options) => { |
| 333 | + ctx.modifyBuildAssets(({ assets }) => { |
| 334 | + Object.keys(assets).forEach(filename => { |
| 335 | + const isAppJS = appReg.test(filename) |
| 336 | + const handleAppJsReplace = (item) => { |
| 337 | + replaceList.forEach(name => { |
| 338 | + item = item.replace(new RegExp(`../../../../../${name}`, 'g'), `'../../../${name}`) |
| 339 | + }) |
| 340 | + } |
| 341 | + if (isAppJS) { |
| 342 | + if ( |
| 343 | + !assets[filename]._value && |
| 344 | + assets[filename].children |
| 345 | + ) { |
| 346 | + assets[filename].children.forEach(child => { |
| 347 | + replaceList.forEach(name => { |
| 348 | + const value = child._value ? child._value : child |
| 349 | + |
| 350 | + handleAppJsReplace(value) |
| 351 | + }) |
| 352 | + }) |
| 353 | + } else { |
| 354 | + handleAppJsReplace(assets[filename]._value) |
| 355 | + } |
| 356 | + } |
| 357 | + } |
| 358 | + }) |
| 359 | +} |
| 360 | +``` |
| 361 | +
|
| 362 | +## 后续 |
| 363 | +本篇文章主要是讲述了Taro项目在京购小程序端的应用方式和开发方式,暂无涉及H5部分的内容。之后计划输出一份Taro项目在H5端的开发指南,并讲述Taro在多端开发中的性能优化方式。 |
0 commit comments