npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fast-flow-transform

v0.0.3

Published

Native Hermes/FFT Flow stripping transform with webpack, rspack, rsbuild, Parcel, Vite, Rollup, Rolldown, and esbuild adapters

Downloads

1,055

Readme

fast-flow-transform

A programmatic Flow type stripper with webpack, rspack, rsbuild, Parcel, Vite, Rollup, Rolldown, and esbuild adapters.

Features

  • Native parse + transform + codegen + source map pipeline
  • Small programmatic API for one-shot transforms
  • CLI for transforming files with the same option set
  • Dedicated webpack, rspack, rsbuild, Parcel, Vite, Rollup, Rolldown, and esbuild entrypoints
  • Optional source map merging when you need emitted maps
  • FFT strips Flow syntax only. JSX stays in the output for the bundler's own JSX pipeline to handle.

Programmatic Usage

import transform from 'fast-flow-transform';

const result = await transform({
  filename: '/abs/path/input.js',
  source: 'const answer: number = 42;',
  sourcemap: true,
});

The programmatic API returns { code: string, map?: SourceMapLike }. map is present when source maps are enabled.

FFT always removes value imports that become empty after Flow stripping. This avoids preserving type-only imports as runtime side-effect imports.

Programmatic options:

| Property | Required | Type | Default | Description | | -------------------- | -------- | ----------------------------------------------- | --------------- | --------------------------------------------------------------------------------- | | source | Yes | string \| Buffer | n/a | Source text to transform | | format | No | 'compact' \| 'pretty' \| 'preserve' | 'compact' | Output mode. preserve is experimental; see Preserve Format | | dialect | No | 'flow' \| 'flow-detect' \| 'flow-unambiguous' | 'flow-detect' | Flow parsing mode | | comments | No | boolean | false | Preserves ordinary comments in any output mode | | sourcemap | No | boolean | true | Enables or disables FFT's emitted output source map | | filename | No | string | '<unknown>' | Used in diagnostics and in the emitted source map when you pass a path | | inputSourceMap | No | SourceMapLike \| null | none | Incoming source map from an earlier transform step to merge into FFT's output map | | reactRuntimeTarget | No | '18' \| '19' \| 18 \| 19 | '19' | Only affects Flow component lowering; normalized to '18' or '19' |

When you provide inputSourceMap, FFT merges it into the emitted map. The current merge path preserves source and name mappings, but does not yet retain upstream sourcesContent or custom metadata fields.

Flow enums always lower to the external flow-enums-runtime package. If your project uses Flow enums, install flow-enums-runtime as a dependency so the generated runtime imports resolve in your bundler and at runtime.

CLI Usage

fast-flow-transform src/input.js --out-file dist/output.js

The CLI reads the input file, calls the same transform(...) API exposed by the package, and:

  • writes transformed code to --out-file, or prints it to stdout when --out-file is absent
  • writes a source map to --source-map-file or --out-file.map when source map output is enabled

CLI arguments:

| Argument | Required | Value | Default | Description | | ---------------------------------- | -------- | ----------------------------------------- | ---------------- | ----------------------------------------------------------------------------------- | | <input-file> | Yes | path | n/a | Source file to transform | | --format <value> | No | compact, pretty, preserve | compact | Output mode. preserve is experimental; see Preserve Format | | --dialect <value> | No | flow, flow-detect, flow-unambiguous | flow-detect | Flow parsing mode | | --comments | No | flag | false | Preserve ordinary comments in the output | | --source-map / --no-source-map | No | flag | false | Enable or disable FFT's emitted output source map | | --out-file <path> | No | path | none | Write transformed code to a file | | --source-map-file <path> | No | path | <out-file>.map | Write the emitted output source map to a specific file and enable source map output | | --input-source-map <path> | No | path | none | Load an upstream source map JSON file to merge into FFT's emitted output map | | --react-runtime-target <n> | No | 18, 19 | 19 | Only affects Flow component lowering | | -h, --help | No | flag | n/a | Show help |

fast-flow-transform src/input.js \
  --out-file dist/output.js \
  --dialect flow-detect \
  --format preserve \
  --comments

Without --out-file, transformed code goes to stdout. Source maps are off by default unless you pass --source-map or --source-map-file.

FFT always removes value imports that become empty after Flow stripping. This avoids preserving type-only imports as runtime side-effect imports.

Preserve Format

format: 'preserve' and --format preserve are experimental.

This mode uses FFT's layout-preserving subtractive path instead of the normal reprinter. It tries to remove Flow syntax while keeping the original spacing, line structure, and comments as intact as possible. It supports source maps and works with comments: true.

Current limitations:

  • It only supports subtractive Flow stripping.
  • It does not yet support Flow component declarations.
  • It does not yet support Flow hook declarations.
  • It does not yet support Flow enum declarations.
  • It does not yet support Flow match statements or expressions.

Bundler Adapters

The adapter entrypoints expose the same canonical transform options where the host bundler makes sense of them: dialect, format, comments, reactRuntimeTarget, and sourcemap.

Bundler integrations do not all treat sourcemap the same way. webpack and rspack default to the loader context unless you override them, Parcel follows the asset's source-map setting, Rollup/Vite/Rolldown always ask FFT for maps so the bundler can compose them, and the esbuild adapter leaves final map emission to esbuild itself.

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.[jt]sx?$/,
        use: [
          {
            loader: 'swc-loader',
            options: {
              jsc: {
                parser: {
                  jsx: true,
                  syntax: 'ecmascript',
                },
                transform: {
                  react: {
                    runtime: 'classic',
                  },
                },
              },
            },
          },
          {
            loader: 'fast-flow-transform/webpack',
            options: {
              dialect: 'flow-detect',
            },
          },
        ],
      },
    ],
  },
};

If your config file is ESM, resolve the loader with createRequire(import.meta.url) and require.resolve(...) like the runnable example does.

Runnable example: examples/webpack

// rspack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.[jt]sx?$/,
        use: [
          {
            loader: 'builtin:swc-loader',
            options: {
              jsc: {
                parser: {
                  jsx: true,
                  syntax: 'ecmascript',
                },
                transform: {
                  react: {
                    runtime: 'classic',
                  },
                },
              },
            },
          },
          {
            loader: 'fast-flow-transform/rspack',
            options: {
              dialect: 'flow-detect',
            },
          },
        ],
      },
    ],
  },
};

If your config file is ESM, resolve the loader with createRequire(import.meta.url) and require.resolve(...) like the runnable example does.

Runnable example: examples/rspack

// rsbuild.config.ts
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import pluginFastFlowTransformRsbuild from 'fast-flow-transform/rsbuild';

export default defineConfig({
  plugins: [
    pluginReact({
      swcReactOptions: {
        runtime: 'classic',
      },
    }),
    pluginFastFlowTransformRsbuild({
      dialect: 'flow-detect',
      format: 'compact',
      sourcemap: true,
    }),
  ],
  source: {
    // Rsbuild skips most node_modules by default.
    // include: [{ not: /[\\/]core-js[\\/]/ }],
  },
});

If you need lower-level control, fast-flow-transform/rsbuild also exposes the named applyFastFlowTransformRsbuild helper for wiring directly inside your own tools.bundlerChain logic.

Runnable example: examples/rsbuild

Parcel expects transformers to be referenced from .parcelrc using either a Parcel-style plugin package name or a local file path. The recommended setup is to add a tiny local wrapper that imports fast-flow-transform/parcel.

{
  "extends": "@parcel/config-default",
  "transformers": {
    "*.{js,mjs,cjs,jsx}": ["./parcel-transformer.mjs", "..."],
  },
}
// parcel-transformer.mjs
import { createFastFlowTransformParcel } from 'fast-flow-transform/parcel';

export default createFastFlowTransformParcel({
  dialect: 'flow-detect',
  sourcemap: true,
});

If the defaults are good enough, your wrapper can re-export the package's default Parcel transformer instead:

export { default } from 'fast-flow-transform/parcel';

Runnable example: examples/parcel

// vite.config.ts
import { defineConfig, transformWithEsbuild } from 'vite';
import fastFlowTransformVite from 'fast-flow-transform/vite';

const JS_WITH_JSX = /\.[cm]?jsx?(?:[?#].*)?$/;

function jsxAfterFftPlugin() {
  return {
    enforce: 'pre',
    name: 'jsx-after-fft',
    async transform(code, id) {
      if (id.startsWith('\0') || !JS_WITH_JSX.test(id)) {
        return null;
      }

      return await transformWithEsbuild(code, id, {
        jsx: 'transform',
        loader: 'jsx',
        sourcemap: true,
      });
    },
  };
}

export default defineConfig({
  plugins: [
    fastFlowTransformVite({
      dialect: 'flow-detect',
    }),
    jsxAfterFftPlugin(),
  ],
});

FFT does not lower JSX itself. If your JSX already lives in .jsx or .tsx files, or another Vite plugin handles it, you may not need the extra transformWithEsbuild(...) pass shown above.

Runnable example: examples/vite

import fastFlowTransformRollup from 'fast-flow-transform/rollup';

export default {
  plugins: [
    fastFlowTransformRollup({
      dialect: 'flow-detect',
    }),
  ],
};

If your graph still contains JSX after Flow stripping, add your preferred Rollup JSX transform separately. FFT does not lower JSX.

Runnable example: examples/rollup

import fastFlowTransformRolldown from 'fast-flow-transform/rolldown';
import { defineConfig } from 'rolldown';

export default defineConfig({
  input: 'src/index.js',
  moduleTypes: {
    '.js': 'jsx',
  },
  output: {
    file: 'dist/out.js',
    format: 'cjs',
    sourcemap: true,
  },
  plugins: [
    fastFlowTransformRolldown({
      dialect: 'flow-detect',
    }),
  ],
  transform: {
    jsx: 'react',
  },
});

Rolldown already supports mixed ESM and CommonJS graphs natively, so you should not pair this adapter with @rollup/plugin-commonjs.

Runnable example: examples/rolldown

import { build } from 'esbuild';
import fastFlowTransformEsbuild from 'fast-flow-transform/esbuild';

await build({
  bundle: true,
  entryPoints: ['src/index.js'],
  outfile: 'dist/out.js',
  plugins: [
    fastFlowTransformEsbuild({
      dialect: 'flow-detect',
    }),
  ],
});

Runnable example: examples/esbuild

Native Binding Notes

FFT installs its native addon through platform-specific optional dependencies. If a package manager lockfile was created on a different platform, or install flags such as --ignore-optional / --no-optional were used, the matching binding package may be skipped.

If FFT reports that its native binding is missing, reinstall dependencies on the target machine without disabling optional dependencies. The supported manual override is NAPI_RS_NATIVE_LIBRARY_PATH.