Taro multi-terminal unified development framework: easy to control small program development, one set of code can run on multiple terminals

introduction

In today's growing mini program market, the number of mini programs is increasing rapidly. This is because mini programs have many advantages, such as lightweight, convenience and good user experience, attracting more and more developers and companies to join this field. With the popularity of mini programs, various industries have launched their own mini programs to meet the diverse needs of users.

However, precisely because of the diversity and rapid development of the mini program market, the API differences of each mini program client have become very significant. Different mini program platforms often customize and adjust APIs in order to meet their own special needs and functional positioning. This leads to differences in APIs between various mini program clients, and developers on different platforms need to develop and adapt to different APIs.

For developers, re-developing a set of mini-program applications for different platforms will become an endless nightmare. Developers need to be familiar with and master the API differences of each client, write a lot of repetitive code, and perform platform-specific debugging and adaptation work. This not only increases the development workload and time cost, but also easily leads to errors and compatibility issues.

In this context, the emergence of Taro provides developers with a solution. By providing a unified set of development frameworks and components, it enables developers to write a set of codes that can run on multiple small program platforms at the same time. Taro's compilation tools can convert developers' codes into codes required by different platforms, thereby enabling cross-platform development and adaptation, reducing developers' burden and improving development efficiency.

Taro is a multi-terminal unified development framework that follows React syntax specifications (ps: Vue syntax is also supported). Mainly used to build cross-platform applets, H5 and mobile applications. There are other multi-terminal frameworks on the market, including but not limited to:

Among the above, only uni-app supports small program scenarios, and it occupies half of the multi-terminal frameworks.

Taro protagonist

In summary, the main features and advantages of Taro refer to the official statement: "Using Taro, we can only write a set of code, and then use Taro's compilation tool to compile the source code separately and compile it on different terminals (WeChat applet, H5 , App side, etc.)."

Project address: Taro | Mini program cross-platform unified development solution

Compile once and run on multiple terminals

Here we need to explain the "compile-time configuration" mechanism. The official "one-time compilation" does not mean that a dist package can run on all platforms.

Instead, use the corresponding instructions to create a package suitable for the platform you want to run on.

for example:

微信小程序 编译命令 yarn build:weapp
百度小程序 编译命令 yarn build:swan
支付宝小程序 编译命令 yarn build:alipay
H5 编译命令 yarn build:h5
RN 编译命令 yarn build:rn --platform ios
……

So what we need to really care about is what Taro has done for the target platform. The following takes WeChat applet as an example:

The Taro framework has built-in corresponding compilers and construction tools in the @tarojs/plugin-platform-weapp WeChat mini program platform plug-in. Register the configuration items of the WeChat mini program platform here.

//taro-weapp/src/index.ts

//在此注册微信小程序平台
ctx.registerPlatform({
  name: 'weapp',
  useConfigName: 'mini',
  async fn ({ config }) {
    const program = new Weapp(ctx, config, options || {})
    await program.start()
  }
})

Predefine a Template class named WeChat applet, which inherits from UnRecursiveTemplate. Its main function is to handle template-related operations in the Taro framework and customize it according to specific needs.

//taro-weapp/src/template.ts

export class Template extends UnRecursiveTemplate {
  ...
  
  //构建wxs模板
  buildXsTemplate () {
    return '<wxs module="xs" src="./utils.wxs" />'
  }

  //创建小程序组件
  createMiniComponents (components): any {
    const result = super.createMiniComponents(components)

    // PageMeta & NavigationBar
    this.transferComponents['page-meta'] = result['page-meta']
    this.transferComponents['navigation-bar'] = result['navigation-bar']
    delete result['page-meta']
    delete result['navigation-bar']

    return result
  }

  //替换属性名称
  replacePropName (name: string, value: string, componentName: string, componentAlias) {
    ...
  }

  //构建wxs模板中与焦点相关的方法,根据插件选项判断是否启用键盘附件功能,并返回相应的字符串
  buildXSTepFocus (nn: string) {
    ...
  }

  //修改模板结果的方法,根据节点名称和插件选项对模板进行修改。
  modifyTemplateResult = (res: string, nodeName: string, _, children) => {
    ...
  }

  //构建页面模板的方法,根据基础路径和页面配置生成页面模板字符串。
  buildPageTemplate = (baseTempPath: string, page) => {
    ...
  }
}

Taro's compilation tool converts the code required by the corresponding platform according to the selected platform. Use ctx.applyPlugins to call the plug-in processing function of the corresponding platform, where the platform parameter specifies the corresponding platform:

//taro-cli/src/build.ts

...
await ctx.applyPlugins(hooks.ON_BUILD_START)
await ctx.applyPlugins({
  name: platform,
  opts: {
    config: {
      ...config,
      isWatch,
      mode: isProduction ? 'production' : 'development',
      blended,
      isBuildNativeComp,
      newBlended,
      async modifyWebpackChain (chain, webpack, data) {
        await ctx.applyPlugins({
          name: hooks.MODIFY_WEBPACK_CHAIN,
          initialVal: chain,
          opts: {
            chain,
            webpack,
            data
          }
        })
      },
...

In addition, the code conversion process also involves:

  • Syntax conversion: Taro supports development using JSX syntax similar to React. It converts JSX code into syntax supported by different platforms, such as WXML for small programs, components of React Native, etc.

  • Style conversion: Taro supports using CSS preprocessors to write styles, such as Sass, Less, etc. During the compilation process, Taro converts these style files into style sheets supported by different platforms, such as WXSS for mini programs, CSS for H5, etc.

During the compilation process, Taro also executes:

  • Static resource processing: Taro will process the static resource files in the project, such as pictures, fonts, etc., convert them into formats suitable for different platforms, and compress and optimize them.

  • File copy: Taro will directly copy some files that do not require compilation to the output directory, such as project configuration files, static pages, etc.

  • File merging and splitting: Taro will merge or split multiple files based on configuration and reference relationships in the code to improve code loading performance.

  • Code compression and obfuscation: Taro can compress and obfuscate the generated code to reduce file size and improve execution efficiency.

Cross-platform adaptation and difference handling

There are always some differences in the APIs of different platforms. How does Taro achieve API adaptation and differentiation?

Taro implements API adaptation and differentiation through mechanisms such as adaptation layer and conditional compilation.

It provides a unified set of API interfaces that developers can use in their code, and Taro will convert these APIs into specific implementations suitable for each platform during the compilation process.

for getLocation example.

If we want to use the positioning function, we only need to use the API provided by Taro in the project  getLocation :

Taro.getLocation().then(res => {
  console.log(res.latitude, res.longitude);
});

During the compilation process, Taro will convert this code into specific implementations suitable for different platforms based on the differences in the target platform.

For the WeChat applet, when converted to a WeChat applet  wx.getLocation, the original parameters and callback functions are retained:

wx.getLocation().then(res => {
  console.log(res.latitude, res.longitude);
});

For the Alipay applet, Taro will convert it into the Alipay applet  my.getLocation, and also retain the original parameters and callback functions:

my.getLocation().then(res => {
  console.log(res.latitude, res.longitude);
});

In this way, Taro converts the unified API into specific APIs supported by each platform according to the differences in the target platform during the compilation process. In this code, the processApis function receives a collection of APIs as parameters and processes each API in it:

//shared/native-apis.ts

function processApis (taro, global, config: IProcessApisIOptions = {}) {
  ...
  apis.forEach(key => {
    if (_needPromiseApis.has(key)) {
      const originKey = key
      taro[originKey] = (options: Record<string, any> | string = {}, ...args) => {
        let key = originKey

        // 第一个参数 options 为字符串,单独处理
        if (typeof options === 'string') {
          ...
        }

        // 改变 key 或 option 字段,如需要把支付宝标准的字段对齐微信标准的字段
        if (config.transformMeta) {
          ...
        }
    ...

        // 为页面跳转相关的 api 设置一个随机数作为路由参数。为了给 runtime 区分页面。
        setUniqueKeyToRoute(key, options)

        // Promise 化:将原本的异步回调形式转换为返回Promise对象的形式,使api的调用更加方便且符合现代JavaScript的异步处理方式。
        const p: any = new Promise((resolve, reject) => {
          obj.success = res => {
            config.modifyAsyncResult?.(key, res)
            options.success?.(res)
            if (key === 'connectSocket') {
              resolve(
                Promise.resolve().then(() => task ? Object.assign(task, res) : res)
              )
            } else {
              resolve(res)
            }
          }
          obj.fail = res => {
            options.fail?.(res)
            reject(res)
          }
          obj.complete = res => {
            options.complete?.(res)
          }
          if (args.length) {
            task = global[key](obj, ...args)
          } else {
            task = global[key](obj)
          }
        })

        // 给 promise 对象挂载属性
        if (['uploadFile', 'downloadFile'].includes(key)) {
          ...
        }
        return p
      }
    } else {
      ...
    }
  })
  ...
}

ps: Although Taro provides a unified set of API interfaces, some platforms may not support specific functions or features. It may be necessary to use conditional compilation to call platform-specific APIs to handle platform-specific differences.

Cross-platform UI component library

View When we use Taro to write multi-terminal projects, we need to use Taro components provided by Taro  .

Because these Taro components will be converted into corresponding native components or elements on different platforms.

For example, in the following code, we use the Image, View, and Text components provided by Taro to create a view:

import Taro from '@tarojs/taro';
import { View, Text, Image } from '@tarojs/components';

function MyComponent() {
  return (
    <View>
      <Text>Hello</Text>
      <Image src="path/to/image.png" />
    </View>
  );
}

During the compilation and generation process, Taro will convert the components into specific components suitable for each platform based on the differences of the target platform. For example, View components will be converted into components of WeChat applet  view . For H5, View components will be converted into  <div> elements.

In the WeChat mini program:

<view>
  <text>Hello</text>
  <image src="path/to/image.png"></image>
</view>

In H5:

<div>
  <span>Hello</span>
  <img src="path/to/image.png" />
</div>

In this way, we can use the same code to write the view, which is what the official means by writing only one set of code.

Through abstraction layer, platform adaptation, cross-platform compilation and other processes, Taro has actually paved the way for the implementation of multi-terminal component libraries. If you want to adapt your own multi-terminal component library like Taro-UI. Just use the basic components provided by Taro to build complex components.

reverse conversion

If you say that you have made a WeChat mini program before, and now your boss wants you to parallel port it to a mini program such as Alipay. If there is no time to refactor the code, reverse conversion may save the day. Reverse conversion, as the name suggests, is to convert a small program into a Taro project.

The relevant code is in the @tarojs/cli-convertor package, and the core logic is in parseAst, which generates an AST tree and traverses and processes the corresponding content:

//taro-cli-convertor/src/index.ts

parseAst ({ ast, sourceFilePath, outputFilePath, importStylePath, depComponents, imports = [] }: IParseAstOptions): {
    ast: t.File
    scriptFiles: Set<string>
  } {
    ...
    // 转换后js页面的所有自定义标签
    const scriptComponents: string[] = []
    ...
    traverse(ast, {
      Program: {
        enter (astPath) {
          astPath.traverse({
            //对类的遍历和判断
            ClassDeclaration (astPath){...},
           //表达式
            ClassExpression (astPath) {...},
            //导出
            ExportDefaultDeclaration (astPath) {...},
            //导入
            ImportDeclaration (astPath) {...},
            //调用
            CallExpression (astPath) {...},
            //检查节点的 object 属性是否为标识符 wx,如果是,则将 object 修改为标识符 Taro,并设置一个标志变量 needInsertImportTaro 为 true。这段代码可能是将 wx 替换为 Taro,以实现对 Taro 框架的兼容性处理。
            MemberExpression (astPath) {...},
            //检查节点的 property 属性是否为标识符 dataset,如果是,则将 object 修改为一个 getTarget 函数的调用表达式,传递了两个参数 object 和标识符 Taro。它还创建了一个导入语句,将 getTarget 函数引入,并将其赋值给一个对象模式。这段代码可能是对可选链式调用中的 dataset 属性进行处理,引入了 getTarget 函数来实现相应的转换。
            OptionalMemberExpression (astPath) {...},
            // 获取js界面所有用到的自定义标签,不重复
            JSXElement (astPath) {...},
            // 处理this.data.xx = XXX 的情况,因为此表达式在taro暂不支持, 转为setData
            // 将this.data.xx=XX 替换为 setData()
            AssignmentExpression (astPath) {...}
          })
        },
        exit (astPath) {...}
      },
    })
  ...
    return {
      ast,
      scriptFiles,
    }
  }

ps: Although the official tool provides reverse conversion, it still has limitations. Not all mini programs support reverse conversion. Currently, only WeChat mini programs support reverse conversion. And not all native APIs can be converted, so you need to pay attention. It is hoped that this function will continue to be expanded and improved in the future.

Performance optimization - Prerender

Why is Prerender needed? The official explanation is given:

 

Taro Next needs to go through the following steps when loading a page:

 

The framework (React/Nerv/Vue) renders the page into the virtual DOM

 

When Taro runs, it serializes the virtual DOM of the page into renderable data and uses setData() to drive page rendering.

 

The applet itself renders serialized data

 

Compared with native applet or compiled applet framework, steps 1 and 2 are redundant. If there is no performance problem in the business logic code of the page, most of the performance bottlenecks lie in setData() in step 2: Since the initial rendering is the entire virtual DOM tree of the page, the amount of data is relatively large, so setData() needs to pass a comparison Large data will cause a period of white screen when initializing the page. This situation usually occurs when the number of wxml nodes for initial rendering of the page is relatively large or the performance of the user's machine is low.

The working principle of Taro pre-rendering is to use server-side rendering (SSR) technology during the build phase to render page components into static HTML files and save them in the static file directory. Then, when the client requests the page, the pre-rendered static HTML is returned directly instead of dynamically generating the page.

Improve first load speed, improve user experience, and optimize search engine indexing by rendering pages as static HTML files during the build phase.

How to use:

//config/index.js 或 /config/dev.js 或 /config/prod.js

const config = {
  ...
  mini: {
    prerender: {
      match: 'pages/shop/**', // 所有以 `pages/shop/` 开头的页面都参与 prerender
      include: ['pages/any/way/index'], // `pages/any/way/index` 也会参与 prerender
      exclude: ['pages/shop/index/index'] // `pages/shop/index/index` 不用参与 prerender
    }
  }
};

module.exports = config

For more usage details, please see the official website documentation.

Summarize

After the above superficial analysis, we can have a preliminary understanding of Taro's entire operating mechanism. Here’s a summary of how it works:

  1. Code transformation and conditional compilation: Taro generates code suitable for the target platform by applying code transformation and conditional compilation to the source code. This allows us to write applications for multiple platforms using one set of code.

  2. Abstraction layer and platform adaptation layer: Taro provides an abstraction layer and platform adaptation layer to handle the code conversion process and ensure the compatibility of the API on different platforms. This allows us to develop on different platforms using the same API.

  3. Taro custom components and multi-terminal adaptability: Taro’s built-in components naturally adapt to the framework, which means we can build component libraries that are suitable for multiple platforms, such as Taro UI. This improves development efficiency and enables cross-platform consistency.

  4. Reverse conversion: Reverse conversion is a reverse idea that attempts to achieve cross-platform by converting existing applications into Taro code. However, reverse conversion suffers from instability and limitations, and has limited benefits for maintainers.

  5. Prerender as a performance optimization option: Taro provides prerender technology as a performance optimization option. Pre-rendering can generate static HTML pages during the build process to improve first-time load speed and optimize search engine indexing. This is an effective means of performance optimization.

Guess you like

Origin blog.csdn.net/we2006mo/article/details/135359033