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
Maintainers
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.jsThe 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-fileis absent - writes a source map to
--source-map-fileor--out-file.mapwhen 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 \
--commentsWithout --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
componentdeclarations. - It does not yet support Flow
hookdeclarations. - It does not yet support Flow
enumdeclarations. - It does not yet support Flow
matchstatements 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.
