High-performance barrel file optimization for webpack and rspack, powered by Rust + NAPI.
barrel-loader rewrites index.ts/js/tsx/jsx barrel files into clean, explicit, deduplicated exports. It is designed for large codebases where deeply nested export * chains can slow builds and create noisy bundles.
- What this solves
- How it works
- Features
- Install
- Quick start
- Loader options
- Examples
- Behavior notes
- Debugging
- Build from source
- Architecture
- Troubleshooting
- Contributing
- License
In many projects, barrel files gradually become chains of wildcard exports:
// src/index.ts
export * from './components'
export * from './hooks'
// src/components/index.ts
export * from './button'
export * from './input'This creates a few common issues:
- Duplicate re-exports from different paths
- Harder-to-predict export surfaces
- Slower parsing and transform work in large projects
- Mixed value/type export handling edge cases
barrel-loader resolves these by parsing and flattening export graphs, then reconstructing a normalized barrel output.
Pipeline at build time:
- Parse exports (native Rust parser)
- Resolve nested barrels recursively
- Expand namespace exports when configured
- Remove duplicates
- Sort output deterministically
- Reconstruct the final barrel source
Core execution path is native (Rust + NAPI), with selective JavaScript handling around integration logic.
- Native Rust parsing and transforms via NAPI
- Webpack + rspack loader compatibility
- Recursive barrel resolution
- Namespace-to-named expansion support
- Type-aware export reconstruction
- Deterministic output ordering
- Verbose and debug logging modes
pnpm add @apec1/barrel-loader
# or
npm install @apec1/barrel-loaderRuntime requirements:
- Node.js >= 18
- macOS or Linux (Windows is not officially supported)
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\/index\.(ts|tsx|js|jsx)$/,
use: [
{
loader: '@apec1/barrel-loader',
options: {
verbose: false,
convertNamespaceToNamed: true
}
}
]
}
]
}
}// rspack.config.mjs
export default {
module: {
rules: [
{
test: /\/index\.(ts|tsx|js|jsx)$/,
use: [
{
loader: '@apec1/barrel-loader',
options: {
verbose: false,
convertNamespaceToNamed: true
}
}
]
}
]
}
}type BarrelLoaderOptions = {
resolveBarrelFiles?: boolean
removeDuplicates?: boolean
sort?: boolean
convertNamespaceToNamed?: boolean
verbose?: boolean
}| Option | Type | Default | Description |
|---|---|---|---|
resolveBarrelFiles |
boolean |
true |
Resolve nested barrel chains recursively |
removeDuplicates |
boolean |
true |
Deduplicate repeated exports |
sort |
boolean |
false |
Sort exports deterministically |
convertNamespaceToNamed |
boolean |
false |
Convert export * to explicit named exports when resolvable |
verbose |
boolean |
false |
Emit loader stage logs |
Input
// src/index.ts
export * from './ui'
// src/ui/index.ts
export * from './button'
export * from './input'Typical output intent
export { Button } from './ui/button'
export { Input } from './ui/input'With convertNamespaceToNamed: true:
// before
export * from './file'
// after (when analyzable)
export { fnA, fnB, fnC } from './file'// source
export * from './runtime'
export * from './types'
// output keeps value/type semantics distinct
export { run } from './runtime'
export type { User, Session } from './types'BARREL_LOADER_DEBUG=true pnpm buildThis enables detailed logs from parser/resolver stages and writes debug output files for transformed barrels.
More examples: docs/EXAMPLES.md
- The loader targets barrel-like entry files (
index.ts/js/tsx/jsx) in your rule configuration. - Current transforms are export-focused; direct declaration rewriting is intentionally limited.
- Native addon load failures can surface as fallback warnings, but build behavior depends on where the failure occurs.
Environment flags:
BARREL_LOADER_DEBUG=true→ deep debug logs +.debug.tsoutputs for transformed filesBARREL_LOADER_VERBOSE=true→ verbose parse/resolve logging
Example:
BARREL_LOADER_DEBUG=true BARREL_LOADER_VERBOSE=true pnpm build./setup.sh
./build.shpnpm build # Rust + TypeScript
pnpm build:rust # Rust only
pnpm build:ts # TypeScript bundles only
pnpm build:debug # Debug Rust build + TypeScript
pnpm version:bump patch # Bump patch in package.json + Cargo.toml
pnpm version:check # Ensure versions are in sync
pnpm release:verify # Version check + tests + full build
pnpm test # JS integration checks
pnpm test:rust # Rust tests
pnpm lint # biome + clippy
pnpm fmt # biome + rustfmtnative/barrel_loader_rs.nodedist/index.cjsdist/index.mjsdist/index.d.ts
src/lib.rs– top-level native modulesrc/types.rs– shared NAPI object shapessrc/rs_utils/parser/*– export parsingsrc/rs_utils/deduplication.rs– dedupe logicsrc/rs_utils/sorting.rs– deterministic sortsrc/rs_utils/reconstruction/*– source regeneration
src/barrel-loader.ts– loader orchestrationsrc/ts-utils/resolve-barrel.ts– recursive traversalsrc/ts-utils/resolve-utils.ts– export expansion helperssrc/ts-utils/native-addon.ts– native addon bridgesrc/index.ts– package entry and exports
rslibemits CJS + ESM bundles todist/- Native
.nodeaddon is copied tonative/anddist/during build
A full troubleshooting guide is available here:
It includes fixes for:
- Rust files accidentally parsed by rslib/rspack
- Native addon field mismatch errors (
exportTypevsexport_type) rslib: command not foundafter cleaning dependencies- pnpm side-effects cache vs rspack build cache confusion
- Native addon load failures and clean rebuild strategy
Contributions are welcome.
Recommended contribution workflow:
- Open an issue describing the problem/feature
- Add or update tests (
pnpm test,pnpm test:rust) - Keep docs in sync (
README,docs/*) - Run lint/format before opening PR
Release and publish details: PUBLISHING.md
MIT