bf6-portal-bundler
v1.3.0
Published
Bundler for Battlefield 6 Portal mods
Maintainers
Readme
bf6-portal-bundler
A specialized bundler for Battlefield 6 Portal mods that combines TypeScript files and string resources into a single bundle compatible with the Portal runtime environment.
Overview
Battlefield 6 Portal requires mods to be uploaded as:
- A single TypeScript file (i.e.
bundle.ts) - A single strings JSON file (i.e.
bundle.strings.json)
The Portal runtime does not support:
- External npm dependencies (except the
modnamespace injected at runtime) - Multiple file imports
- Module resolution at runtime
This tool solves this limitation by:
- Walking the entire dependency graph from your entrypoint
- Resolving both local imports and
node_modulesdependencies - Flattening everything into a single TypeScript file
- Merging all
strings.jsonfiles into one - Removing all import statements (since everything is inlined)
Installation
Local Installation (Recommended)
Install as a dev dependency in your project:
npm install -D bf6-portal-bundlerThis allows you to use it in npm scripts and ensures all team members have the same version.
Global Installation
Install globally to use the bf6-portal-bundler command from any directory:
npm install -g bf6-portal-bundlerAfter global installation, you can run the bundler from any project without adding it as a dependency.
Usage
Command Line
bf6-portal-bundler --entrypoint <path> --outDir <path> [--removeComments] [--minify]Arguments:
--entrypoint: Path to your main TypeScript entry file (e.g.,./src/index.ts)--outDir: Output directory wherebundle.tsandbundle.strings.jsonwill be written--removeComments: (optional) Strip all comments from the bundle without minifying (see Comment removal)--minify: (optional) Apply rudimentary size reduction (see Minification)
Example:
bf6-portal-bundler --entrypoint ./src/index.ts --outDir ./dist
bf6-portal-bundler --entrypoint ./src/index.ts --outDir ./dist --removeComments
bf6-portal-bundler --entrypoint ./src/index.ts --outDir ./dist --minifyThis will generate:
./dist/bundle.ts- The flattened TypeScript bundle./dist/bundle.strings.json- The merged strings file
NPM Scripts
Add to your package.json:
{
"scripts": {
"build": "bf6-portal-bundler --entrypoint ./src/index.ts --outDir ./dist"
}
}Then run:
npm run buildHow It Works
1. Dependency Graph Traversal
The bundler performs a post-order depth-first traversal of your dependency graph:
- Starts from your entrypoint file
- Recursively follows all
importstatements - Resolves both relative imports (
./file) and node module imports (@community/pathfinder) - Visits each dependency before the file that imports it
- Builds an ordered list ensuring dependencies come first
Example:
src/index.ts
└─ imports → src/utils/helper.ts
└─ imports → node_modules/@community/pathfinder/index.tsOutput order:
node_modules/@community/pathfinder/index.ts(dependency)src/utils/helper.ts(depends on pathfinder)src/index.ts(depends on helper)
2. Import Resolution
The bundler resolves imports in this order:
Relative Imports (., ..):
- Resolved relative to the current file's directory
- Supports extensions:
.ts,/index.ts,.d.ts
Node Module Imports:
- Resolved from
node_modules/<package-name> - Looks for:
<package-name>.ts,<package-name>/index.ts,<package-name>.d.ts - Uses your project's
node_modulesdirectory (not the bundler's)
External Modules:
- The
modnamespace is ignored (provided by Portal runtime) - These imports are left as-is in the final bundle
3. Code Transformation
For each file in the dependency graph:
Removes all import statements:
import { ... } from "..."import "side-effect"import X = require("...")export * from "..."
Preserves all other code:
- Type definitions
- Classes, functions, variables
- Exports
- Comments (unless
--removeCommentsor--minifyis used; see Comment removal and Minification)
Adds source comments:
- Each file section is marked with
// --- SOURCE: <relative-path> ---
- Each file section is marked with
4. Strings Merging
The bundler automatically finds and merges all strings.json files:
- Discovery: Scans all directories containing TypeScript files in the bundle
- Collection: Finds all files ending with
strings.jsonin those directories - Validation: Checks for duplicate keys (build fails if found)
- Merging: Combines all strings into a single JSON object
Example:
src/
├─ index.ts
└─ strings.json → { "someKey": "someValue" }
src/utils/
├─ helper.ts
└─ strings.json → { "helper": { "key1": "value1", "key2": "value2" } }
node_modules/@community/pathfinder/
├─ index.ts
└─ strings.json → { "pathfinder": { "alpha": "This is Alpha", "beta": "This is Beta" } }Output:
{
"someKey": "someValue",
"helper": {
"key1": "value1",
"key2": "value2"
},
"pathfinder": {
"alpha": "This is Alpha",
"beta": "This is Beta"
}
}Features
✅ Supported Import Types
- Standard ES6 imports:
import { foo } from './bar' - Default imports:
import foo from './bar' - Namespace imports:
import * as foo from './bar' - Side-effect imports:
import './polyfills' - TypeScript require:
import foo = require('./bar') - Re-exports:
export * from './bar'
✅ Node Modules Support
You can use npm packages in your mod! The bundler will:
- Resolve packages from
node_modules - Bundle their TypeScript source files
- Include their
strings.jsonfiles
Note: Only packages with TypeScript source files (.ts) can be bundled. Compiled JavaScript packages won't work.
✅ Duplicate Detection
- Strings: Build fails if duplicate keys are found in
strings.jsonfiles - Files: Circular dependencies are handled (files are only processed once)
✅ Error Handling
- Clear error messages for missing files
- Warnings for unresolved imports
- Validation of JSON files
Minification (--minify)
The bundler supports a rudimentary size-reduction mode via the --minify flag. When enabled, it:
- Strips all comments from the bundled TypeScript (single-line, multi-line, and JSDoc)—same as
--removeComments. - Removes blank lines from the final output.
- Reformats the bundle with a compact Prettier configuration (e.g. wide line width, no semicolons, single quotes, no trailing commas) to reduce character count.
This is not a true minifier. The output remains valid, human-readable TypeScript, albeit a painful read. The BF6
Portal upload and validation system imposes strict requirements on uploaded bundles; the bundle must be valid TypeScript
that passes Portal’s checks. Aggressive minification (omitting types, variable shortening, name mangling, heavy
whitespace removal, or other transforms used by tools like Terser or esbuild’s minify) could break validation or runtime
behavior. The --minify option only applies safe, semantics-preserving reductions to help shrink bundle size while
keeping the result valid and compliant.
Comment removal (--removeComments)
You can strip all comments (single-line, multi-line, and JSDoc) from the bundle without minifying. Use
--removeComments when you want a smaller or cleaner bundle but prefer to keep the existing formatting and blank lines.
Comment stripping is forced when you use --minify.
Output Format
bundle.ts
// --- BUNDLED TYPESCRIPT OUTPUT ---
// @ts-nocheck
// --- SOURCE: node_modules/@community/pathfinder/index.ts ---
// [pathfinder code here]
// --- SOURCE: src/utils/helper.ts ---
// [helper code here]
// --- SOURCE: src/index.ts ---
// [main code here]bundle.strings.json
{
"someKey": "someValue",
"helper": {
"key1": "value1",
"key2": "value2"
},
"pathfinder": {
"alpha": "This is Alpha",
"beta": "This is Beta"
}
}Project Structure Example
my-mod/
├── package.json
├── src/
│ ├── index.ts # Entrypoint
│ ├── strings.json
│ ├── utils/
│ │ ├── helper.ts
│ │ └── strings.json
│ └── components/
│ └── widget.ts
├── node_modules/
│ └── @community/
│ └── pathfinder/
│ ├── index.ts
│ └── strings.json
└── dist/ # Generated by bundler
├── bundle.ts
└── bundle.strings.jsonLimitations
TypeScript Only: The bundler only processes
.tsfiles. JavaScript files innode_moduleswon't be included.No Dynamic Imports: Dynamic imports (
import()) are not supported.External Modules: Only the
modnamespace is recognized as external. All other imports must be resolvable.Circular Dependencies: While handled gracefully, complex circular dependencies may cause unexpected ordering.
Troubleshooting
"Could not resolve import"
- Check that the import path is correct
- For node modules, ensure the package has TypeScript source files
- Verify the package is installed in
node_modules
"Duplicate JSON Key Detected"
Two strings.json files contain the same key (at the same level). Rename one of the keys to resolve the conflict.
License
MIT
