@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.
Recommended Setup
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
# 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/coreNote: @autotracer/logger installs automatically with @autotracer/flow. @babel/core is a peer dependency required by the Babel plugin.
For pnpm users: Add to .npmrc:
# .npmrc
auto-install-peers=truePublic Surface
@autotracer/plugin-babel-flow exposes these public build-time surfaces:
- default Babel plugin export used as
"@autotracer/plugin-babel-flow"in Babel config BabelPluginFlowConfigDEFAULT_CONFIG,normalizeConfig, andshouldProcessFilefor shared toolingoutputModelogExceptionsexceptionLogLeveltracerNameincludeexcludemodeprefix- 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
// babel.config.js or .babelrc
module.exports = {
presets: ["next/babel"],
plugins: [
[
"@autotracer/plugin-babel-flow",
{
include: {
paths: ["**/src/**", "**/app/**"],
functions: ["handle*", "on*", "fetch*"],
},
},
],
],
};// 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
// .babelrc or babel.config.js
{
"plugins": [
[
"@autotracer/plugin-babel-flow",
{
"include": {
"paths": ["src/**"],
"functions": ["*"]
}
}
]
]
}// 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
{
"plugins": [
[
"@autotracer/plugin-babel-flow",
{
// Options here
}
]
]
}BabelPluginFlowConfig
Configuration interface for the Babel plugin.
For exact setting behavior, use FlowTracer Babel Plugin Settings.
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.
// @traceenables one eligible function.// @trace-disabledisables one eligible function.includeandexcludedecide 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.
{
"plugins": [
[
"@autotracer/plugin-babel-flow",
{
"outputMode": "copy-paste"
}
]
]
}logExceptions
Include exception logging in catch blocks.
For exact behavior, use logExceptions and exceptionLogLevel.
{
"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.
{
"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:
// 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:
// 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:
// 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:
function divide(a, b) {
if (b === 0) return null;
return a / b;
}
// Both return values tracedDormant Mode Integration
Works with a bootstrap that lazy-loads @autotracer/flow/runtime:
// 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
// babel.config.js
module.exports = {
presets: ["next/babel"],
plugins: [
[
"@autotracer/plugin-babel-flow",
{
include: {
paths: ["pages/**", "src/**"],
functions: ["*"],
},
},
],
],
};// 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
// babel.config.js
module.exports = {
presets: ["next/babel"],
plugins: [
[
"@autotracer/plugin-babel-flow",
{
include: {
paths: ["app/**", "src/**"],
functions: ["*"],
},
},
],
],
};// 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
// .babelrc
{
"plugins": [
[
"@autotracer/plugin-babel-flow",
{
"include": {
"paths": ["src/**"],
"functions": ["*"]
}
}
]
]
}// 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
// 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
// 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:
import type { BabelPluginFlowConfig } from "@autotracer/plugin-babel-flow";Examples
Basic Next.js
// babel.config.js
module.exports = {
presets: ["next/babel"],
plugins: [
[
"@autotracer/plugin-babel-flow",
{
include: {
paths: ["app/**", "pages/**", "src/**"],
functions: ["*"],
},
},
],
],
};Event Handlers Only
{
"plugins": [
[
"@autotracer/plugin-babel-flow",
{
"include": {
"paths": ["src/**"],
"functions": ["handle*", "on*"]
}
}
]
]
}API Services
{
"plugins": [
[
"@autotracer/plugin-babel-flow",
{
"include": {
"paths": ["src/api/**", "src/services/**"],
"functions": ["fetch*", "get*", "post*"]
}
}
]
]
}Development Only
// 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
{
"plugins": [
[
"@autotracer/plugin-babel-flow",
{
"include": {
"paths": ["src/**"],
"functions": ["*"]
},
"exclude": {
"paths": ["**/*.test.*", "**/*.spec.*"],
"functions": ["helper*", "internal*", "_*"]
}
}
]
]
}Troubleshooting
Not Instrumenting
Check:
- File matches
include.pathspattern (if configured) - Function name matches
include.functionspattern (if configured) - File/function not in
excludepatterns - 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:
await import("@autotracer/flow/runtime");Next.js Specific
For Next.js, use babel.config.js (not .babelrc) and include next/babel preset.
Related Packages
- @autotracer/flow - Runtime library (required)
- @autotracer/logger - Internal shared logger utility used by
@autotracer/flow - @autotracer/plugin-vite-flow - Vite plugin alternative
- @autotracer/react18 - React component tracer (complementary)