⚠️ Don't try this at home: Add Brainf**k support to your JS app today!
Source: Dev.to
Dear Mods of dev.to, please bear with me for this one.
This post will have a few profanities, as you might guess from the title. However, these profanities are names of packages and actual technical terms. None of them is meant to offend anyone. I’ll try to censor most of them where sensible.
And here we go again. Stuff no one should probably do in production, because it’s either completely and utterly useless, will confuse the living heck out of everyone having to maintain it, or make everyone scream “No” and “Please don’t”.
Esoteric programming languages it is this time
Specifically, Brainf**k, a dialect of Ook! that sadly can’t be understood by Orang Utans. And no, I refuse to believe that it’s actually the other way around.
Brainf**k is notoriously hard to read. Mostly because its commands are single characters that move the stack pointer by 1, increase or decrease the value it points at by 1, and loop as long as the current stack element the pointer is pointing to is greater than 0.
I’ve recently written a post about using and creating unplugins where I mentioned adding Brainf**k support and thought to myself:
So here we are. If you ever wanted to build half your app in a cumbersome, convoluted, unreadable way, the time is now! And you can even open‑source it and let others do the same, too!
Creating the Unplugin boilerplate
Straightforward. We also need another dependency called “hirnf**k”, a library that transpiles Brainfk code to JS. For the non‑German‑speaking folks, the name of the package translates to “brainfk”, so there’s that.
# Oops – typo!
$ px degit unplugin/unplugin-starter unplugin-bf
bash: px: command not found
Whoops. Typo. Yes, this already started badly.
$ npx degit unplugin/unplugin-starter unplugin-bf
$ cd unplugin-bf/ && npm i -s hirnfick
Much better.
This creates an unplugin and installs our source‑to‑source compiler. We’ll implement the compilation in the unplugin’s src/index.ts file.
Looking at hirnf**k’s documentation
It includes an ESM example we can slightly modify:
import hirnfick from "https://jspm.dev/hirnfick";
const helloWorldBF =
"++++++++[>++++[>++>+++>+++>++>+>->>+[>.>---." +
"+++++++..+++.>>.>+.>++.";
try {
const helloJs = hirnfick.compileToJsWeb(helloWorldBF);
const runHello = new Function(`${helloJs} return main().output.trim();`);
console.log(runHello());
} catch (err) {
console.error(`Error: ${err.message}`);
}
Perfect, right? It creates an executable function and even contains a “Hello World” in Brainf**k already!
Implementing everything
It’s surprisingly straightforward. Luckily, the unplugin starter already gives us most of the boilerplate; we only need to fill in the gaps.
Starter code
import type { UnpluginFactory } from "unplugin";
import type { Options } from "./types";
import { createUnplugin } from "unplugin";
export const unpluginFactory: UnpluginFactory = (options) => ({
name: "unplugin-starter",
transformInclude(id) {
return id.endsWith("main.ts");
},
transform(code) {
return code.replace("__UNPLUGIN__", `Hello Unplugin! ${options}`);
},
});
export const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory);
export default unplugin;
Adjusting the boilerplate
- Change the file filter – we want
.bffiles instead ofmain.ts. - Rename the unplugin –
unplugin-bf. - Remove unused options.
// ...
export const unpluginFactory: UnpluginFactory = () => ({
name: "unplugin-bf",
transformInclude(id) {
return id.endsWith(".bf");
},
transform(code) {
return code;
},
});
Adding the actual compilation
Now we copy/paste hirnf**k’s example into the transform function and tweak it a bit:
import type { UnpluginFactory } from "unplugin";
import type { Options } from "./types";
import hirnfick from "hirnfick";
import { createUnplugin } from "unplugin";
export const unpluginFactory: UnpluginFactory = () => ({
name: "unplugin-bf",
transformInclude(id) {
return id.endsWith(".bf");
},
transform(code) {
const transformed = hirnfick.compileToJsWeb(code);
const wrapped = `export default new Function(\`${transformed} return main().output.trim();\`)`;
return wrapped;
},
});
export const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory);
export default unplugin;
And believe it or not, we’re done. We now have Brainf**k support in our JS apps via our lovely unplugin.
How it works
When Vite/Rollup (or any bundler that supports unplugins) processes a file, it:
- Checks the file name using
transformInclude. Only files ending with.bfare passed through. - Passes the file contents (
code) to thetransformfunction. - Compiles the Brainfk source** to JavaScript using
hirnfick.compileToJsWeb. - Wraps the generated JS in a
new Functionthat returns the trimmed output of the Brainf**k program. - Exports the function as the default export of the module, so you can import and run it like any other JS module.
That’s it – a tiny, self‑contained bridge from an esoteric language to modern JavaScript. Enjoy!
How the Brainf**k → JavaScript Compilation Works
The unplugin takes the contents of a .bf file (a string) and wraps it in a JavaScript module that exports a default function.
That function runs the compiled Brainf**k code and returns whatever the . command would print, with all whitespace trimmed.
Minimal Example
Brainf**k source:
+-.
The compiled JavaScript looks like this:
export default new Function(`let position = 0;
const cells = [0];
let output = '';
function putchar() {
output += String.fromCharCode(cells[position]);
}
function main() {
if (cells[position] 0) {
cells[position] -= 1;
}
putchar(String.fromCharCode(cells[position]));
return { cells, output };
}
return main().output.trim();`)
You can see how each Brainf**k instruction maps to JavaScript:
| Brainf**k | JavaScript equivalent |
|---|---|
+ | cells[position] += 1; |
- | cells[position] -= 1; |
. | putchar(String.fromCharCode(cells[position])); |
Trying It Out
The unplugin‑starter repository already contains a playground that sets everything up for you.
-
Create a Brainfk file**
hello-world.bf++++++++[>++++[>++>+++>+++>++>+>->>+[>.>---.+++++++..+++.>>.>+.>++. -
Import and run it in
main.tsimport HelloWorld from './hello-world.bf' document.body.innerHTML = HelloWorld()VSCode will complain because it doesn’t know that .bf files export JavaScript, but the unplugin handles it at build time. (A future VSCode extension could silence those warnings.)
-
Start the playground
cd playground npm i && npm run devThis launches a dev server (usually at
http://localhost:3000). Opening it in a browser shows the output:
It works!
⚠️ Warning: Do not use Brainf**k in production code. It’s just a fun experiment.
Extending the Compiler
If you’re feeling adventurous, you can add extra features:
- Input support via the
,command. - Source maps (good luck!).
- Ook! language support.
Thanks!
I hope you enjoyed this article as much as I enjoyed writing it. If you did, please leave a ❤️.
I write tech articles in my free time and love coffee. If you’d like to support my work, you can:
- Buy me a coffee ☕
- Donate via PayPal
You can also follow me on Bluesky 🦋.

