title: 第三方工具

概述

如何利用好第三方工具提升使用 Taro 的开发体验是很多社区内开发者共有的问题,比方说如何利用 Jest 测试或者使用 StoryBook 编写组件库示例等等,都需要借助 Taro-H5 相关的能力。

基础配置

正常使用 Taro 时,cli 会帮助我们完成编译配置并对 ast 做出一定的修改,如果使用第三方工具,那么我们需要对 webpack 和 babel 相关的配置做出一定的修改。

Webpack

Taro-H5 中使用到的 API 实际上并不在 @tarojs/taro 的入口文件之下,如果想要使用需要在 Webpack 中配置解析入口和别名如下:

```js title=”webpack.config.js” module.exports = { // … resolve: { mainFields: [‘main:h5’, ‘browser’, ‘module’, ‘jsnext:main’, ‘main’], alias: { ‘@tarojs/taro’: ‘@tarojs/taro-h5’ }, }, // … }

  1. ### Babel
  2. Taro-H5 实际并没有在 Taro 对象上挂载所有的 API,这是为了避免不必要的 API 占用包体的大小,那么为了兼容小程序的 API 使用方法就需要对开发者的代码在编译前做出一些调整,在使用第三方工具时,也需要通过引入 `babel-plugin-transform-taroapi` 依赖完成这一操作。
  3. ## 示例
  4. ### StoryBook
  5. `StoryBook: 6.4.13` 为例,在 Taro 中使用需要在 StoryBook 安装完成之后,更新以下配置:
  6. ```js title=".storybook/main.js"
  7. const webpack = require('webpack');
  8. const path = require('path')
  9. module.exports = {
  10. // ...
  11. babel: options => ({
  12. ...options,
  13. plugins: [
  14. ...options.plugins,
  15. [require('babel-plugin-transform-taroapi').default, {
  16. apis: require(require.resolve('@tarojs/taro-h5/dist/taroApis', { basedir: path.resolve(__dirname, '..') })),
  17. packageName: '@tarojs/taro'
  18. }],
  19. ]
  20. }),
  21. webpackFinal: config => ({
  22. ...config,
  23. resolve: {
  24. ...config.resolve,
  25. mainFields: ['main:h5', 'browser', 'module', 'jsnext:main', 'main'],
  26. alias: {
  27. ...config.resolve.alias,
  28. '@tarojs/taro': '@tarojs/taro-h5',
  29. ['@tarojs/components$']: '@tarojs/components/dist-h5/react',
  30. },
  31. },
  32. plugins: [
  33. ...config.plugins,
  34. new webpack.DefinePlugin({
  35. 'process.env.TARO_ENV': JSON.stringify('h5'),
  36. ENABLE_INNER_HTML: JSON.stringify(false),
  37. ENABLE_ADJACENT_HTML: JSON.stringify(false),
  38. ENABLE_SIZE_APIS: JSON.stringify(false),
  39. ENABLE_TEMPLATE_CONTENT: JSON.stringify(false),
  40. ENABLE_CLONE_NODE: JSON.stringify(false),
  41. ENABLE_CONTAINS: JSON.stringify(false),
  42. ENABLE_MUTATION_OBSERVER: JSON.stringify(false),
  43. }),
  44. ]
  45. })
  46. // ...
  47. }

:::caution 请注意 该方法不适用 pxTransform 方法,如果需要使用请先调用自行调用 initPxTransform 初始化配置 (目前 Taro 使用 webpack4 构建项目,无法在 StoryBook 中直接引用 @tarojs/webpack-runner 提供的方法引入所有配置,等升级到 webpack5 之后会提供替代解决方案)。 目前解决办法是在.storybook/preview.js中预先执行initPxTransform并载入相关样式。 :::

```js title=”.storybook/preview.js” import { DecoratorFn } from ‘@storybook/react’;

import { defineCustomElements, applyPolyfills } from ‘@tarojs/components/loader’; import Taro from ‘@tarojs/taro’;

import ‘@tarojs/components/dist/taro-components/taro-components.css’;

export const decorators = [ (Story) => { applyPolyfills().then(function () { defineCustomElements(window); });

  1. Taro.initPxTransform({
  2. designWidth: 750,
  3. deviceRatio: {
  4. '640': 2.34 / 2,
  5. '750': 1,
  6. '828': 1.81 / 2,
  7. },
  8. });
  9. return <Story />;

}, ];

//…

  1. :::解决storybook中渲染结果与设计稿大小不一致的问题(以designWidth: 750px为例)
  2. :::
  3. ```html title=".storybook/preview-body.html"
  4. <style>
  5. html {
  6. font-size: 23.4375px;
  7. }
  8. </style>

Jest

使用 Jest 测试也是类似,需要添加配置如下

```js title=”jest” module.exports = { // … globals: { // … window: true, ENABLEINNERHTML: true, ENABLEADJACENTHTML: true, ENABLESIZEAPIS: true, ENABLE_TEMPLATE_CONTENT: true, ENABLE_CLONE_NODE: true, ENABLE_CONTAINS: true, ENABLE_MUTATION_OBSERVER: true, }, moduleNameMapper: { // … ‘@tarojs/taro’: ‘@tarojs/taro-h5’, // ‘@tarojs/components’: ‘@tarojs/components/dist-h5/react’, // ‘@tarojs/plugin-framework-react/dist/runtime’: ‘/__mocks/taro-framework’, // ‘@tarojs/plugin-framework-vue2/dist/runtime’: ‘/__mocks/taro-framework’, // ‘@tarojs/plugin-framework-vue3/dist/runtime’: ‘/__mocks/taro-framework’, } }

  1. :::caution 请注意
  2. 该方法不适用路由跳转和部分生命周期测试。
  3. :::
  4. #### TabBar
  5. 如果项目需要测试 TabBar 相关的逻辑,需要将应用完成初始化,参看方法如下:
  6. ```js title="__tests__/tab-bar.test.js"
  7. import * as Taro from '@tarojs/taro-h5'
  8. import { buildApp } from './utils'
  9. describe('tabbar', () => {
  10. beforeEach(() => {
  11. jest.resetAllMocks()
  12. buildApp()
  13. })
  14. it('should be able to set/removeTabBarBadge', done => {
  15. Taro.eventCenter.once('__taroSetTabBarBadge', res => res.successHandler({
  16. errMsg: 'setTabBarBadge:ok'
  17. }))
  18. Taro.eventCenter.once('__taroRemoveTabBarBadge', res => res.successHandler({
  19. errMsg: 'removeTabBarBadge:ok'
  20. }))
  21. Taro.setTabBarBadge({
  22. index: 0,
  23. text: 'text'
  24. }).then(res => {
  25. expect(res.errMsg).toBe('setTabBarBadge:ok')
  26. Taro.removeTabBarBadge({
  27. index: 0
  28. }).then(res => {
  29. expect(res.errMsg).toBe('removeTabBarBadge:ok')
  30. done()
  31. })
  32. })
  33. })
  34. })

```js title=”tests/utils.js” import { createReactApp } from ‘@tarojs/plugin-framework-react/dist/runtime’ import { createRouter } from ‘@tarojs/router’ import React, { Component } from ‘react’ import ReactDOM from ‘react-test-renderer’

const appConfig: any = { pages: [ ‘pages/index/index’, ‘pages/about/index’ ], window: { backgroundTextStyle: ‘light’, navigationBarBackgroundColor: ‘#fff’, navigationBarTitleText: ‘WeChat’, navigationBarTextStyle: ‘black’ }, tabBar: { color: ‘#333’, selectedColor: ‘#409EFF’, backgroundColor: ‘#fff’, borderStyle: ‘black’, list: [{ pagePath: ‘/pages/index/index’, text: ‘首页’ }, { pagePath: ‘/pages/about/about’, text: ‘关于’ }], mode: ‘hash’, basename: ‘/test/app’, customRoutes: { ‘/pages/about/index’: ‘/about’ } }, router: { mode: ‘hash’ } }

export function buildApp () { const config: any = { …appConfig } class App extends Component { render () { return this.props.children } } config.routes = [ config.pages?.map(path => ({ path, load: () => null })) ] const inst = createReactApp(App, React, ReactDOM, config) createRouter(inst, config, ‘React’) }

  1. ```js title="__mocks__/taro-framework.js"
  2. const App = {}
  3. export function createReactApp () { return { ...App } }
  4. export function createVueApp () { return { ...App } }
  5. export function createVue3App () { return { ...App } }

Hooks

一些诸如 useDidShowuseDidHide 等等依赖于生命周期的 Hooks 并不会通过 Taro-H5 提供,使用它们需要提供 Mock 方法并挂在到 taro 对象上(可以参考 @tarojs/plugin-framework-react/dist/api-loader 中的方法注入),测试时如果需要触发钩子,则可以通过 Taro.eventCenter 来模拟。

svg-sprite-loader

部分项目希望在 H5 使用 SVG sprites,为此需要使用 svg-sprite-loader 覆盖 taro 提供的 loader

具体用法在这里并不会详细展开,可以参考官方的文档,在这里只说明和 Taro 相关的问题

js title="config/index.js" // ... webpackChain(chain) { chain.merge({ module: { rule: { // 覆盖 Taro 默认的图片加载配置 'image': { test: /\.(png|jpe?g|gif|bpm|webp)(\?.*)?$/, use: [ { loader: 'url-loader', options: { name: path.resolve(__dirname, 'images/[name].[ext]'), }, }, ], }, // 使用 svg-sprite-loader 的配置 'svg-loader': { test: /.svg$/, use: [ { loader: 'svg-sprite-loader', options: {}, }, { loader: 'svgo-loader', options: {}, }, ], }, }, }, }); }, imageUrlLoaderOption: { limit: 5000, exclude: [path.resolve(__dirname, '../src/images/icons')], name: 'static/images/[name].[hash:8].[ext]', } // ...

:::caution 请注意 另外使用 svg-sprite-loader 依旧需要引入图片,避免被 tree shaking 抖动掉可以改用动态导入,参考 Issue 9569。 :::

NextJS

社区 @SyMind 大佬提供了编译 NextJS 应用的插件 tarojs-plugin-platform-nextjs,用于支持 Web 端支持 SSR 能力,可以根据项目需要自行选择。