background

In a more mature WeChat small procedures, the behavior of each page to statistics, such as statistics page PV, UV, the elements on the page click events such as monitor, and report to our own data statistics on the server, and can be found on the Internet for most of the implementation scheme is buried by manual way, this way the efficiency is low, To a page to add a statistical logic, more intrusive code. In addition, the online solutions are all based on native micro programs of wechat, which are too inadequate for the projects developed by Taro. Therefore, this article is compiled through a period of research and experiment. Use Taro to tease out how to be non-intrusive or low-intrusive using Taro (just call a method in app.tsx to listen on all page life cycles).

Making project

Taro-track welcomes star. If you have any questions, please mention ISSUES for discussion

Train of thought

We want to achieve non-intrusive or low-intrusive monitoring page declaration cycle function, for the original micro channel small program, we can refer to an existing solution online: small program from manual buried point to automatic buried point.

Its realization principle is as follows: Through the Page method of proxy wechat applets, wrap a wrapper function outside the lifecycle hook function passed in by the user, implement the logic of unified data reporting in the wrapper function, and then call the user-defined declaration cycle hook function, so that the user can encode without perception. All data collection and reporting operations can be performed within the Wrapper function.

However, the above scheme is only applicable to native wechat applet. In the project based on Taro, all units in the Taro program are components instead of Pages. After repeated experiments, The Page method is never called during the implementation of the project. Through the agent wechat native Page method this way is not feasible.

So, since everything is a Component in Taro, can we implement similar logic through proxy Component? The idea worked, but because the Component lifecycle hook is different from the Page lifecycle hook, we needed to transform it.

The specific implementation

//// core/wx-tools.ts

/** * Get wechat native Page *@returns {WechatMiniprogram.Page.Constructor}* /
export function getWxPage() :WechatMiniprogram.Page.Constructor  {
  return Page;
}

/** * Rewrite wechat native Page *@param newPage* /
export function overrideWxPage(newPage: any) :void {
  Page = newPage;
}

/** * Get wechat native App *@returns {WechatMiniprogram.App.Constructor}* /
export function getWxApp() :WechatMiniprogram.App.Constructor {
  return App;
}

/** * Rewrite wechat native App *@param newApp* /
export function overrideWxApp(newApp: any) :void {
  App = newApp;
}

/** * Get wechat native Component *@returns {WechatMiniprogram.Component.Constructor}* /
export function getWxComponent() :WechatMiniprogram.Component.Constructor {
  return Component;
}

/** * Rewrite wechat native Component *@param newComponent* /
export function overrideWxComponent(newComponent: any) :void {
  Component = newComponent;
}

Copy the code
//// overrideWxPage.ts
import { getWxComponent, getWxPage, overrideWxComponent, overrideWxPage } from '@kiner/core/es';

// Lifecycle hooks for the proxy are required, including the Page and Component hooks
const proxyMethods = [
  "onShow"."onHide"."onReady"."onLoad"."onUnload"."created"."attached"."ready"."moved"."detached",];// Trigger the initialization argument in the hook callback
exportinterface OverrideWechatPageInitOptions { __route__? : string __isPage__? : boolean [key:string]: any }// Trigger hook is the type of callback function called
export type OverrideWechatPageHooksCb = (method: string, options: OverrideWechatPageInitOptions) = >void;

// Store all callback functions
const pageHooksCbs: OverrideWechatPageHooksCb[] = [];

export class OverrideWechatPage {
  // wechat native Page method
  private readonly wechatOriginalPage: WechatMiniprogram.Page.Constructor;
  // wechat native Component method
  private readonly wechatOriginalComponent: WechatMiniprogram.Component.Constructor;
  // Whether to use the taro framework
  private readonly isTaro = true;

  public constructor(isTaro:boolean=true) {
    this.isTaro = true;
    // Based on the need to be compatible with toutiao and Baidu applets in the future, all the operations of the native wechat applets are removed to a separate module for maintenance.
    // If you want to be compatible with other applets in the future, you just need to dynamically specify the API switch inside a block of the cover
    // Save the original wechat Page object so that we can restore it when we destroy it
    this.wechatOriginalPage = getWxPage();
    // Save the wechat original Component object so that we can restore it when we destroy it
    this.wechatOriginalComponent = getWxComponent();
  }

  public initialize(pageHooksCb: OverrideWechatPageHooksCb): void {
    const _Page = getWxPage();
    const _Component = getWxComponent();
    // The callback functions are queued and called in turn when the native lifecycle hooks are triggered
    pageHooksCbs.push(pageHooksCb);

    console.info('original Page object', pageHooksCbs, this.wechatOriginalPage);

    const self = this;

    Filter the hook functions that need to be propped based on whether to use the Taro framework
    // If you use Taro, you need the lifecycle hook of the proxy component. If you use native applet, you need the lifecycle hook of the proxy Page
    const needProxyMethods = proxyMethods.filter(item= >this.isTaro? ! item.startsWith('on'):item.startsWith('on'));

    / * * * implementation agent Page | Component logic *@param {OverrideWechatPageInitOptions} options
     * @returns {string}* /
    const wrapper = function(options: OverrideWechatPageInitOptions){


      needProxyMethods.forEach(methodName= >{
        // Cache user-defined lifecycle hooks
        const _originalHooks = options[methodName];
        const wrapperMethod = function (. args: any[]) {
          // Trigger page life cycle callbacks in turn
          pageHooksCbs.forEach((fn: OverrideWechatPageHooksCb) = >fn(methodName, options));
          // Execute the hook function if the user has defined the lifecycle hook
          return _originalHooks&&_originalHooks.call(this. args); };// Rewrite options to override the original hook function with a new wrapper functionoptions = { ... options, [methodName]:wrapperMethod }; });// Initialize with new options
      let res = "";
      if(self.isTaro){
        res = _Component(options);
      }else{
        _Page(options);
      }

      Since everything is a component in Taro, we need to know whether the current component is a page component or a generic component
      // The native Component of wechat applets executes the constructor and returns the path of the current Component directly, such as pages/index/index
      // Therefore, we can store this path in our wrapper so that we can externally determine if the current component is a page component
      options.__router__ = wrapper.__route__ = res;
      options.__isPage__ = res.startsWith('pages/');

      console.info('Rewrite wechat applet Page object', options, res);

      return res;
    };

    wrapper.__route__ = ' ';
    wrapper.__isPage__ = false;


    / / rewrite WeChat native Page | Component
    if(this.isTaro){
      overrideWxComponent(wrapper);
    }else{ overrideWxPage(wrapper); }}/** * reset the wechat native method */
  public destroy(): void {
    overrideWxPage(this.wechatOriginalPage);
    overrideWxComponent(this.wechatOriginalComponent); }}Copy the code
//// entry.ts

/** * Initializes wechat applets lifecycle listening *@param {string | undefined} BaseUrl Sends logs to the server. The default is production service *@param {TransporterType} The transporter uses elK or Console *@param {string | undefined} AppVersion Current applet version *@param {string | undefined} AppName Name of the current applets *@param {boolean} ShowLog Indicates whether logs are successfully sent *@param {number} PstInterval Interval for reporting applets - PST events. The default value is 5000 x@param ExtraData Parameters, which are not directly available in the SDK, such as appID, etc. *@param {{[p: string]: string}} extraData
 */
export function initAppletLifecycleListener(
    {baseUrl,
      isTaro,
      transporter,
      appVersion,
      appName,
      showLog = false,
      pstInterval = 5000
    }: InitAppletLifecycleOption,
    extraData: { [key: string]: string } = {}
  ){
  const logger = Logger.create('initAppletLifecycleListener');
  const logStyle = 'background: green; color: #FFFFFF; padding: 5px 10px; ';
  const tpr = initTransporter(transporter, {
    baseUrl: baseUrl,
    query: {
      app_name: appName,
      app_version: appVersion,
      ev_type: 'client_ub'}});let timer = null;


  const overrideWechatPage = new OverrideWechatPage(isTaro);

  const prevUrl = getWxCurrentHref();

  // Listen for onLoad and onReady on the page
  overrideWechatPage.initialize(async (methodName: string, options) => {
    if(! options.__isPage__) {return;
    }

    // const hooksName = CompAndPageHookMap[methodName];

    console.log(`dolphin-wx/entry:${methodName}-${CompAndPageHookMap[methodName]}`);
    const openTime = Date.now();
    const baseExtFields = getBaseExtFields(extraData);
    const baseFields = await getBaseFields(extraData);

    const extraExt = extraData.ext || {};

    function sendPv() {
      const now = Date.now();
      const sendData = {
        ev: 'applet-pv'. baseFields, ... extraData,time: now,
        ext: {... baseExtFields, ... extraExt,time: now - openTime
        }
      };

      tpr.send(sendData, () = > showLog && logger.info('%capplet- PV reporting success:', logStyle, sendData));
    }

    function sendPst() {
      const now = Date.now();
      const sendPstData = {
        ev: 'applet-pst'. baseFields, ... extraData,time: now,
        ext: {... baseExtFields, ... extraExt,time: now - openTime,
          url: getWxCurrentHref()
        }
      };
      tpr.send(sendPstData, () = > showLog && logger.info('%capplet- PST reporting success:', logStyle, sendPstData));
    }


    function sendPvOut() {
      const now = Date.now();
      const sendPvOutData = {
        ev: 'applet-pvout'. baseFields, ... extraData,time: now,
        pl: baseFields.url,
        ext: {... baseExtFields, ... extraExt,time: now - openTime,
          url: baseFields.url
        }
      };
      tpr.send(sendPvOutData, () = > showLog && logger.info('%capplet-pvout reporting success:', logStyle, sendPvOutData));
    }


    // console.log(`dolphin-wx/entry[${methodName}]`);
    switch (methodName) {
      case proxyWxLifeHooks.onReady:
      case proxyWxLifeHooks.ready:
        // If the current URL is different from the cached URL when onLoad or Attached is triggered, a page jump occurs and PVOut is triggered
        if(prevUrl&&prevUrl! ==getWxCurrentHref()){ sendPvOut(); } sendPv(); timer =setInterval(() = > {

          sendPst();

        }, pstInterval);
        break;
      case proxyWxLifeHooks.onUnload:
      case proxyWxLifeHooks.detached:
        sendPvOut();
        break; }}); }Copy the code

Final effect

Recently, some friends asked me whether this method is realistic and feasible. Once again, after my practical application in the company’s project, it has been proved to be feasible. Although it is not quite perfect, it can completely achieve our goal. Note: Since Taro is a third-party framework, version updates cannot be controlled. Therefore, we are only compatible with certain versions, such as 2.1.5. Other versions have not been compatible yet