Yandex Metrica Tag

repository·main·Indexed 42 days ago

https://github.com/yandex/metrica-tag

A modular analytics collection script (@metrica/tag v0.0.1) that allows developers to customize script size and feature sets. It features a task-based asynchronous execution model to prevent UI blocking, a modular provider-sender-middleware-transport request pipeline, and a configurable build system via features.json.

Tokens
21K
Snippets
73
Records
94
Agent score
54%

What's inside Yandex Metrica Tag

  1. Overview of async utility modules

    main

    The async module provides several utilities for managing asynchronous execution:

    • async.ts: Provides runAsync for deferred execution in the next tick.
    • executor.ts: Provides executeIterator to add tasks to a shared queue.
    • helpers.ts: Provides iterateTaskWithConstraints, which wraps executeIterator.
    • iterator.ts: A collection of iterators.
    • task.ts: Defines the core task interface and logic.
  2. How Global Storage and Mappings work

    main

    The Metrica tag architecture relies on a global lifecycle:

    • GlobalStorage: Created automatically when the script is parsed. It is shared by all counters on a page, contains common data, and facilitates inter-counter communication (e.g., via semaphores for actions that should only run once per page).
    • Mappings: Define the relationship between providers and their execution chain. Specifically, they map which Sender, Middleware, and Transport combination should be used with each Provider.
  3. Understand the API communication structure

    main

    The api folder defines the communication layer with the server. The architecture is organized by routes, where each subfolder (excluding common) represents a specific server route.

    Key architectural rules:

    • Route Isolation: Code outside the api folder should import variables from the specific route folder they need to interact with, rather than from common.
    • Common Definitions: The common folder is reserved for shared parameters used by multiple routes. Only route folders should import from common.
    • Exceptions: Very common constants (e.g., TRANSPORT_ID_BR_KEY) may be used by the transport layer regardless of the specific route being called.
  4. Understand browserInfo and telemetry serialization

    main

    The API uses internal key-value storages that are serialized into single strings to be sent as URL parameters. This minimizes the number of parameters in a request.

    • browserInfo: Serialized into the browser-info URL parameter. It stores data related to the page, user, and visit.
    • telemetry: Serialized into the t URL parameter. It stores logging and debugging information.
  5. Configure features in the build

    main
    The Metrica tag build is modular. You can control the script size and capabilities by enabling or disabling features in the features.json file. By default, all features are included unless they are explicitly marked with "disabled": true. This allows you to balance analytics quality (amount of data collected) against script size.
  6. Understand the Metrica request flow

    main

    When a request is made, it follows a specific pipeline through four main stages:

    1. Provider: Gathers basic information from the page/browser, subscribes to user or DOM events, parses event data, and triggers a sender.
    2. Sender: Sets basic request information, defines the backend endpoint, and establishes common parameters.
    3. Middlewares: Appends additional useful information to the request data.
    4. Transport: Iterates through available transports until the request is successfully delivered to the backend.
  7. How the Task abstraction works

    main

    A task is the fundamental unit of execution in this module, acting as a functional alternative to a Promise. Unlike standard Promise objects which reside in the MicroTaskQueue (potentially blocking UI rendering and user events), tasks are executed using setDefer. This places them in the MacroTaskQueue, allowing them to run alongside other browser events and ensuring they do not degrade the website's responsiveness or interface performance.

    A task is initialized with an execution function of the type: (reject, resolve) => void.

  8. Install and build Metrica tag

    main

    To use Metrica tag, you must first install the dependencies and build the script. The project requires Node.js. The build output is a single file located at _build/public/watch.js.

    # Install Node.js version if using nvm
    nvm install
    
    # Install dependencies
    npm ci
    
    # Build the production script (uglified)
    npm run build
    
    # Build the raw script (for debugging, no uglification)
    npm run build:raw
  9. How to initialize a Metrica counter

    main

    A Metrica counter can be initialized in two ways:

    1. Direct Constructor Call: Using the new keyword with the counter ID.
    2. Proxy Stack Call: Using the ym function with the init command.

    Upon initialization, the system parses options, runs prioritized providers, executes the first hit (pageview) to retrieve counter settings, registers JS API methods, and finally runs asynchronous providers.

    // Method 1: Direct constructor
    new Ya.Metrika2(XXXXXX);
    
    // Method 2: Via proxy stack
    ym(XXXXXX, "init", {});
  10. Write tests using Mocha, Sinon, and Chai

    main

    The project uses mocha for the test runner, sinon for stubs, and chai for assertions. When using sinon to stub functions, it is recommended to use a sandbox and provide proper TypeScript types for the stubs to ensure a good developer experience.

    import * as sinon from 'sinon';
    import defer from 'src/utils/defer/defer';
    
    describe('test', () => {
        const sandbox = sinon.createSandbox();
        let setDeferStub: sinon.SinonStub<
            Parameters<typeof defer.setDefer>,
            ReturnType<typeof defer.setDefer>
        >
    
        beforeEach(() => {
            setDeferStub = sandbox.stub(defer, 'setDefer');
        });
    });
  11. Add a new provider feature

    main

    To add a new provider, follow these steps:

    1. Register the feature: Add an entry to features.json. Use the weight field to control initialization order (lower weight = earlier initialization). For example, a proxy feature that must run last should have a high weight.

    2. Generate feature set: Run npm run features to update the build configuration.

    3. Implement the provider: Create a folder in src/providers. The index.ts file must export an initProvider function. This function must use the feature flag (e.g., flags.YOUR_FEATURE_NAME) to ensure that if the feature is disabled, the code is removed by Rollup during the build process.

    4. Write logic and tests: Place the provider logic in separate files within the provider folder and add tests in the __tests__ folder using .spec.ts extensions.

    // Example index.ts for a new provider in src/providers/myProvider
    export const initProvider = () => {
        if (flags.SOME_NEW_FEATURE) {
            // Required code to register the provider
            providersSync.push(someNewProvider);
    
            // Optional setup
            addMiddlewareForProvider(SOME_NEW_PROVIDER, watchSyncFlags(), 1);
            providerMap[SOME_NEW_PROVIDER] = useSenderWatch;
            nameMap[SOME_NEW_PROVIDER] = fullList;
        }
    };
  12. Overview of Metrika Tag

    main
    Metrika tag is a library used to initialize and manage Metrika counters. It provides a robust mechanism for handling counter initialization, managing asynchronous providers, decorating methods with error handling and telemetry, and ensuring proper cleanup via a destruction lifecycle. It is designed to work within a global namespace (typically window.ya) and supports features like Turbo parameters and stack proxying.