Skip to content

@autotracer/plugin-babel-flow ​

Babel plugin for automatic function flow tracing (Next.js, CRA, Webpack).

Build-time plugin that automatically instruments functions to track function start/end, parameters, return values, and exceptions. Works with any Babel-based build system including Next.js, Create React App, and custom Webpack/Rollup setups.

Use @autotracer/plugin-babel-flow for build-time instrumentation and keep runtime startup in a separate app bootstrap module.

Set instrumentation selection in the Babel plugin config with include, exclude, mode, and pragma comments. Set outputMode in the Babel plugin config when your bootstrap imports @autotracer/flow or @autotracer/flow/runtime, because the plugin can inject that startup output-mode setting for you.

Lazy-load @autotracer/flow when you want tracing to start immediately. Lazy-load @autotracer/flow/runtime when you want dormant startup and later runtime control.

For browser-based runtime control, use the Dashboard workflow. When the dashboard is not available, use the lower-level commands in @autotracer/flow.

Installation ​

bash
# Install runtime and plugin
pnpm add @autotracer/flow
pnpm add -D @autotracer/plugin-babel-flow @babel/core

# Using npm
npm install @autotracer/flow
npm install --save-dev @autotracer/plugin-babel-flow @babel/core

# Using yarn
yarn add @autotracer/flow
yarn add -D @autotracer/plugin-babel-flow @babel/core

Note: @autotracer/logger installs automatically with @autotracer/flow. @babel/core is a peer dependency required by the Babel plugin.

For pnpm users: Add to .npmrc:

ini
# .npmrc
auto-install-peers=true

Public Surface ​

@autotracer/plugin-babel-flow exposes these public build-time surfaces:

  • default Babel plugin export used as "@autotracer/plugin-babel-flow" in Babel config
  • BabelPluginFlowConfig
  • DEFAULT_CONFIG, normalizeConfig, and shouldProcessFile for shared tooling
  • outputMode
  • logExceptions
  • exceptionLogLevel
  • tracerName
  • include
  • exclude
  • mode
  • prefix
  • Flow pragma comments

Runtime loading and browser control live in @autotracer/flow.

Use FlowTracer Babel Plugin Settings for the canonical per-setting reference.

Quick Start ​

Next.js ​

javascript
// babel.config.js or .babelrc
module.exports = {
  presets: ["next/babel"],
  plugins: [
    [
      "@autotracer/plugin-babel-flow",
      {
        include: {
          paths: ["**/src/**", "**/app/**"],
          functions: ["handle*", "on*", "fetch*"],
        },
      },
    ],
  ],
};
typescript
// app/AutoTracerFlowBootstrap.tsx or components/AutoTracerFlowBootstrap.tsx
"use client"; // App Router only

import { Suspense, lazy, type ReactNode } from 'react';

const isDevelopment = process.env.NODE_ENV === 'development';
const isInternalQa = process.env.NEXT_PUBLIC_INTERNAL_QA === 'true';

const AutoTracerFlowBoundary = lazy(async () => {
  if (isDevelopment || isInternalQa) {
    await import('@autotracer/flow/runtime');
  }

  return {
    default: ({ children }: { children: ReactNode }) => <>{children}</>,
  };
});

export function AutoTracerFlowBootstrap({
  children,
}: {
  children: ReactNode;
}) {
  return (
    <Suspense fallback={null}>
      <AutoTracerFlowBoundary>{children}</AutoTracerFlowBoundary>
    </Suspense>
  );
}

Create React App ​

javascript
// .babelrc or babel.config.js
{
  "plugins": [
    [
      "@autotracer/plugin-babel-flow",
      {
        "include": {
          "paths": ["src/**"],
          "functions": ["*"]
        }
      }
    ]
  ]
}
typescript
// src/index.tsx
const isDevelopment = process.env.NODE_ENV === 'development';
const isInternalQa = process.env.REACT_APP_INTERNAL_QA === 'true';

async function bootstrap(): Promise<void> {
  if (isDevelopment || isInternalQa) {
    await import('@autotracer/flow/runtime');
  }

  const ReactDOM = await import('react-dom/client');
  const { default: App } = await import('./App');

  ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
}

void bootstrap();

API Reference ​

Babel Plugin Options ​

javascript
{
  "plugins": [
    [
      "@autotracer/plugin-babel-flow",
      {
        // Options here
      }
    ]
  ]
}

BabelPluginFlowConfig ​

Configuration interface for the Babel plugin.

For exact setting behavior, use FlowTracer Babel Plugin Settings.

typescript
interface BabelPluginFlowConfig {
  /**
   * Sets the trace output format at startup.
  * When a processed module imports `@autotracer/flow` or `@autotracer/flow/runtime`,
  * the plugin injects startup code
    * so you do not need to call `globalThis.autoTracer.setOutputMode(...)` manually.
   * @default undefined
   */
  outputMode?: "devtools" | "copy-paste";

  /**
   * Include exception logging in catch blocks
   * @default true
   */
  logExceptions?: boolean;

  /**
   * Log level for exception logging
   * @default 'debug'
   */
  exceptionLogLevel?: "debug" | "warn" | "error";

  /**
    * Local tracer binding name used in injected code
   * @default '__flowTracer'
   */
  tracerName?: string;

  /**
   * Include configuration
   */
  include?: {
    /**
     * File path patterns (glob patterns)
     */
    paths?: string[];

    /**
     * Function name patterns (glob, regex, or exact strings)
     */
    functions?: Array<string | RegExp>;
  };

  /**
   * Exclude configuration
   */
  exclude?: {
    /**
     * File path patterns (glob patterns)
     */
    paths?: string[];

    /**
     * Function name patterns (glob, regex, or exact strings)
     */
    functions?: Array<string | RegExp>;
  };

  /**
   * Prefix prepended to every logged function name.
   *
   * Useful in islands or micro-frontend architectures where multiple independent
   * bundles share the same DevTools console, making it clear which app a log belongs to.
   *
   * Example: `prefix: "Island1"` logs `"Island1:processData"` instead of `"processData"`.
   *
   * **Filtering is not affected** — `include`/`exclude` patterns always match against
   * the original, un-prefixed function name.
   *
   * **Runtime start/stop is not affected** — the prefix is a build-time label baked
   * into the injected call; it has no runtime routing or gating effect.
   */
  prefix?: string;

  /**
   * Instrumentation mode.
   * - 'opt-in': Only instrument when `@trace` pragma is present on an eligible function
   * - 'opt-out': Instrument all eligible functions unless `@trace-disable` is present
   * @default 'opt-out'
   */
  mode?: 'opt-in' | 'opt-out';
}

Pragmas ​

Use line comments to control function-level instrumentation.

  • // @trace enables one eligible function.
  • // @trace-disable disables one eligible function.
  • include and exclude decide the eligible set before pragma signals are applied.

For exact placement rules, precedence, nested-function behavior, and examples, see Flow pragma comments.

Configuration ​

outputMode ​

Choose the trace output format FlowTracer starts with.

When a processed module imports @autotracer/flow or @autotracer/flow/runtime, the Babel plugin injects startup code that applies this format before instrumented user code usually runs.

If you omit outputMode, the Babel plugin does not inject startup output-mode code.

For exact behavior, use outputMode.

javascript
{
  "plugins": [
    [
      "@autotracer/plugin-babel-flow",
      {
        "outputMode": "copy-paste"
      }
    ]
  ]
}

logExceptions ​

Include exception logging in catch blocks.

For exact behavior, use logExceptions and exceptionLogLevel.

javascript
{
  "plugins": [
    [
      "@autotracer/plugin-babel-flow",
      {
        "logExceptions": true
      }
    ]
  ]
}

include / exclude ​

Selective function instrumentation.

By default, the plugin includes "**/*.{js,jsx,mjs,ts,tsx,mts}", excludes common noise paths such as tests, build outputs, coverage folders, and dependency folders, and applies no function-name filters.

Partial include and exclude objects use object-key merge. Each key you provide replaces the default for that key only. Arrays are never concatenated.

javascript
{
  "plugins": [
    [
      "@autotracer/plugin-babel-flow",
      {
        "include": {
          "paths": ["**/src/**", "**/app/**"],
          "functions": ["calculate*", "process*", "handle*", "fetch*"]
        },
        "exclude": {
          "functions": ["helper*", "internal*", "_*"]
        }
      }
    ]
  ]
}

This keeps the default path exclusions while adding function-name exclusions.

For exact behavior, use include, exclude, mode, prefix, and Flow pragma comments.

Pattern Examples:

  • '*' - All
  • 'handle*' - Starts with "handle"
  • '*Handler' - Ends with "Handler"
  • '*process*' - Contains "process"
  • ['on*', 'handle*'] - Multiple patterns

Features ​

Automatic Function Instrumentation ​

Wraps functions with tracing code during compilation:

javascript
// Original
function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.price, 0);
}

// Instrumented (simplified emitted shape)
function calculateTotal(items) {
  const __flowTracer = globalThis.__flowTracer;
  const _h = __flowTracer.enter("calculateTotal");
  __flowTracer.traceParameter("items", items);
  try {
    const _returnValue = items.reduce((sum, item) => sum + item.price, 0);
    __flowTracer.traceReturnValue(_returnValue);
    return _returnValue;
  } catch (e) {
    __flowTracer.traceException("calculateTotal", e, "debug");
    throw e;
  } finally {
    __flowTracer.exit(_h);
  }
}

Try/Catch/Finally Injection ​

Safely tracks exceptions without breaking error handling:

javascript
// Original with try/catch
function riskyOperation() {
  try {
    return dangerousCall();
  } catch (error) {
    console.error("Failed:", error);
    throw error;
  }
}

// Instrumented (simplified emitted shape)
function riskyOperation() {
  const __flowTracer = globalThis.__flowTracer;
  const _h = __flowTracer.enter("riskyOperation");
  try {
    try {
      const _returnValue = dangerousCall();
      __flowTracer.traceReturnValue(_returnValue);
      return _returnValue;
    } catch (error) {
      console.error("Failed:", error);
      throw error;
    }
  } catch (e) {
    __flowTracer.traceException("riskyOperation", e, "debug");
    throw e;
  } finally {
    __flowTracer.exit(_h);
  }
}

Async Function Support ​

Handles promises and async/await automatically:

javascript
// Original
async function fetchUser(id) {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

// Example output format:
// → fetchUser (async started)
// param id: "123"
// returned: Promise
// ← fetchUser (async completed, elapsed: 12.3ms)

Return Value Tracing ​

Captures all return statements:

javascript
function divide(a, b) {
  if (b === 0) return null;
  return a / b;
}

// Both return values traced

Dormant Mode Integration ​

Works with a bootstrap that lazy-loads @autotracer/flow/runtime:

javascript
// In your app
const isDevelopment = process.env.NODE_ENV === "development";

async function bootstrap() {
  if (isDevelopment) {
    await import("@autotracer/flow/runtime"); // Starts dormant
  }

  await import("./app");
}

void bootstrap();

In browser-based internal web apps, use the Dashboard when that workflow is mounted. For lower-level start, stop, and status commands, see @autotracer/flow.

Integration ​

Next.js Pages Router ​

javascript
// babel.config.js
module.exports = {
  presets: ["next/babel"],
  plugins: [
    [
      "@autotracer/plugin-babel-flow",
      {
        include: {
          paths: ["pages/**", "src/**"],
          functions: ["*"],
        },
      },
    ],
  ],
};
typescript
// pages/_app.tsx
import type { AppProps } from 'next/app';
import { Suspense, lazy, type ReactNode } from 'react';

const isDevelopment = process.env.NODE_ENV === 'development';
const isInternalQa = process.env.NEXT_PUBLIC_INTERNAL_QA === 'true';

const AutoTracerFlowBootstrap = lazy(async () => {
  if (isDevelopment || isInternalQa) {
    await import('@autotracer/flow/runtime');
  }

  return {
    default: ({ children }: { children: ReactNode }) => <>{children}</>,
  };
});

export default function App({ Component, pageProps }: AppProps) {
  return (
    <Suspense fallback={null}>
      <AutoTracerFlowBootstrap>
        <Component {...pageProps} />
      </AutoTracerFlowBootstrap>
    </Suspense>
  );
}

Next.js App Router ​

javascript
// babel.config.js
module.exports = {
  presets: ["next/babel"],
  plugins: [
    [
      "@autotracer/plugin-babel-flow",
      {
        include: {
          paths: ["app/**", "src/**"],
          functions: ["*"],
        },
      },
    ],
  ],
};
typescript
// app/layout.tsx
'use client';

import { Suspense, lazy, type ReactNode } from 'react';

const isDevelopment = process.env.NODE_ENV === 'development';
const isInternalQa = process.env.NEXT_PUBLIC_INTERNAL_QA === 'true';

const AutoTracerFlowBoundary = lazy(async () => {
  if (isDevelopment || isInternalQa) {
    await import('@autotracer/flow/runtime');
  }

  return {
    default: ({ children }: { children: ReactNode }) => <>{children}</>,
  };
});

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Suspense fallback={null}>
          <AutoTracerFlowBoundary>{children}</AutoTracerFlowBoundary>
        </Suspense>
      </body>
    </html>
  );
}

Create React App ​

javascript
// .babelrc
{
  "plugins": [
    [
      "@autotracer/plugin-babel-flow",
      {
        "include": {
          "paths": ["src/**"],
          "functions": ["*"]
        }
      }
    ]
  ]
}
typescript
// src/index.tsx
const isDevelopment = process.env.NODE_ENV === "development";
const isInternalQa = process.env.REACT_APP_INTERNAL_QA === "true";

async function bootstrap(): Promise<void> {
  if (isDevelopment || isInternalQa) {
    await import("@autotracer/flow/runtime");
  }

  await import("./app");
}

void bootstrap();

Custom Webpack ​

javascript
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        use: {
          loader: "babel-loader",
          options: {
            plugins: [
              [
                "@autotracer/plugin-babel-flow",
                {
                  include: {
                    paths: ["src/**"],
                    functions: ["*"],
                  },
                },
              ],
            ],
          },
        },
      },
    ],
  },
};

Custom Rollup ​

javascript
// rollup.config.js
import babel from "@rollup/plugin-babel";

export default {
  plugins: [
    babel({
      plugins: [
        [
          "@autotracer/plugin-babel-flow",
          {
            include: {
              paths: ["src/**"],
              functions: ["*"],
            },
          },
        ],
      ],
    }),
  ],
};

Requirements ​

  • Babel 7+
  • Node.js 18+
  • Modern browser with ES2020+ support
  • @babel/core (peer dependency)

Performance ​

  • Build-time only - All transformation during compilation
  • No plugin runtime logic - The plugin runs only during compilation
  • Selective targeting - Only instruments matched functions

When tracing is enabled, runtime overhead comes from the injected tracing hooks and the volume of console output/DevTools rendering.

TypeScript Support ​

Full TypeScript support with exported types:

typescript
import type { BabelPluginFlowConfig } from "@autotracer/plugin-babel-flow";

Examples ​

Basic Next.js ​

javascript
// babel.config.js
module.exports = {
  presets: ["next/babel"],
  plugins: [
    [
      "@autotracer/plugin-babel-flow",
      {
        include: {
          paths: ["app/**", "pages/**", "src/**"],
          functions: ["*"],
        },
      },
    ],
  ],
};

Event Handlers Only ​

javascript
{
  "plugins": [
    [
      "@autotracer/plugin-babel-flow",
      {
        "include": {
          "paths": ["src/**"],
          "functions": ["handle*", "on*"]
        }
      }
    ]
  ]
}

API Services ​

javascript
{
  "plugins": [
    [
      "@autotracer/plugin-babel-flow",
      {
        "include": {
          "paths": ["src/api/**", "src/services/**"],
          "functions": ["fetch*", "get*", "post*"]
        }
      }
    ]
  ]
}

Development Only ​

javascript
// babel.config.js
const plugins = [];

if (process.env.NODE_ENV === "development") {
  plugins.push([
    "@autotracer/plugin-babel-flow",
    {
      include: {
        paths: ["src/**"],
        functions: ["*"],
      },
    },
  ]);
}

module.exports = {
  presets: ["next/babel"],
  plugins,
};

Exclude Tests and Helpers ​

javascript
{
  "plugins": [
    [
      "@autotracer/plugin-babel-flow",
      {
        "include": {
          "paths": ["src/**"],
          "functions": ["*"]
        },
        "exclude": {
          "paths": ["**/*.test.*", "**/*.spec.*"],
          "functions": ["helper*", "internal*", "_*"]
        }
      }
    ]
  ]
}

Troubleshooting ​

Not Instrumenting ​

Check:

  1. File matches include.paths pattern (if configured)
  2. Function name matches include.functions pattern (if configured)
  3. File/function not in exclude patterns
  4. Babel configuration loaded correctly

Babel Configuration Not Loading ​

Ensure .babelrc or babel.config.js is in project root and properly formatted.

Import Errors ​

Ensure your app bootstrap lazy-loads @autotracer/flow when you want immediate startup, or lazy-loads @autotracer/flow/runtime when you want dormant startup and later runtime control:

typescript
await import("@autotracer/flow/runtime");

Next.js Specific ​

For Next.js, use babel.config.js (not .babelrc) and include next/babel preset.

See Also ​

Released under the MIT License.