⚠️ Don't try this at home: Add Brainf**k support to your JS app today!

Published: (January 17, 2026 at 02:00 PM EST)
5 min read
Source: Dev.to

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:

Meme of Bilbo Baggins asking

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

  1. Change the file filter – we want .bf files instead of main.ts.
  2. Rename the unpluginunplugin-bf.
  3. 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:

  1. Checks the file name using transformInclude. Only files ending with .bf are passed through.
  2. Passes the file contents (code) to the transform function.
  3. Compiles the Brainfk source** to JavaScript using hirnfick.compileToJsWeb.
  4. Wraps the generated JS in a new Function that returns the trimmed output of the Brainf**k program.
  5. 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**kJavaScript 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.

  1. Create a Brainfk file**

    hello-world.bf
    ++++++++[>++++[>++>+++>+++>++>+>->>+[>.>---.+++++++..+++.>>.>+.>++.
  2. Import and run it in main.ts

    import 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.)

  3. Start the playground

    cd playground
    npm i && npm run dev

    This launches a dev server (usually at http://localhost:3000). Opening it in a browser shows the output:

    Hello World 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:

You can also follow me on Bluesky 🦋.

Buy me a coffee button

Back to Blog

Related posts

Read more »

jQuery 4.0.0 Released

Article URL: https://blog.jquery.com/2026/01/17/jquery-4-0-0/ Comments URL: https://news.ycombinator.com/item?id=46664755 Points: 16 Comments: 1...

jQuery 4

Article URL: https://blog.jquery.com/2026/01/17/jquery-4-0-0/ Comments URL: https://news.ycombinator.com/item?id=46664755 Points: 235 Comments: 60...