bf6-portal-bundler
v1.1.0
Published
Bundler for Battlefield 6 Portal mods
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>Arguments:
--entrypoint: Path to your main TypeScript entry file (e.g.,./src/index.ts)--outDir: Output directory wherebundle.tsandbundle.strings.jsonwill be written
Example:
bf6-portal-bundler --entrypoint ./src/index.ts --outDir ./distThis 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
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
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
