DEV Community

Cover image for When Composer Met pnpm: The Birth of Pomposer
HichemTech
HichemTech

Posted on

When Composer Met pnpm: The Birth of Pomposer

Pomposer logo

It was a typical Saturday night, coding away while the rest of the world was probably out having fun or sleeping. Yep, that's me, and if you're reading this, it's probably you too. I was deep into one of my side projects, building an open-source JavaScript tool (you'll probably see it soon, stay tuned! 😉), when I casually hit pnpm install. And, whoosh! Installation completed instantly. No surprise there, right? That's pnpm doing its magic—sharing packages globally, optimizing storage, and making installs lightning fast.

Right after that, I switched gears to a Composer project (my little side project had both backend and frontend). Out of habit, I typed composer install and waited. And waited some more. Composer downloaded everything fresh, even packages I've installed dozens of times. That's when the idea hit me, why doesn't Composer have pnpm's awesome feature?

Alert! 🚨 My brain had officially woken up.

If you know me, you know I love building things out of pure curiosity and the moment's need. Remember my previous adventures like ts-runtime-picker, or when I almost rewrote half of Laravel just to fix my personal issue? Yeah, I get carried away sometimes, but hey, it's all part of the fun (you should check out those stories sometime).

Anyway, back to my Saturday night realization. I decided right then and there, why not build "pnpm for Composer"? Wait, if npm has pnpm, then Composer should have… Pomposer! 😄 Yes, I actually loved that name and ran with it immediately.

Terminal opened:

mkdir pomposer && composer init
Enter fullscreen mode Exit fullscreen mode

Then came the staring at my blank sketchpad. How do we make this happen?

First obvious idea: install packages once in a global folder, let's call it ~/.pomposer-store. Easy right? I quickly tried creating a dedicated folder for each dependency, running composer require individually:

cd ~/.pomposer-store
mkdir monolog/monolog && cd monolog/monolog
composer require monolog/monolog
Enter fullscreen mode Exit fullscreen mode

Yay! Wait… no. 🤔 It installed sub-dependencies too. That's not what I wanted at all. Dependency hell was looming. And to be honest, it was frustrating. How do we get Composer to just install a single package?

I tried different strategies, maybe I could parse the composer.lock file manually and selectively install dependencies? Nah, we don't always have the composer.lock. What about using Composer's internal APIs? Nope, Composer stubbornly refuses to install just one package without dragging along its friends.

Next idea: cloning repositories directly from GitHub? It sounded brilliant at first, but then I realized not every package was on GitHub :"), and managing different git references became messy super fast.

I spent several hours, probably way too many cups of coffee deep, contemplating how Composer works under the hood. After all that caffeine-fueled brainstorming, it finally struck me.. what if we just download packages directly from Packagist as ZIP files? Direct, simple, no Composer command involved:

$url = "https://repo.packagist.org/p2/monolog/monolog.json";
$json = json_decode(file_get_contents($url), true);
$zipUrl = $json['packages']['monolog/monolog'][0]['dist']['url'];
file_put_contents("monolog.zip", file_get_contents($zipUrl));
Enter fullscreen mode Exit fullscreen mode

Wow! It worked perfectly. Packages downloaded separately, no dependency mess. But then, another problem—how do we handle autoloading? Composer generates nice autoload files for us, but now we're doing it manually.

Another round of brainstorming commenced. Regex parsing adventures, many error messages, and a lot more coffee later, the solution slowly emerged—I could read each package's composer.json to generate a unified autoloader dynamically. It wasn't ideal, but it worked:

// autoload_psr4.php
return [
  'Monolog\\' => '~/.pomposer-store/monolog/monolog/src/',
  // other packages
];
Enter fullscreen mode Exit fullscreen mode

And voilà! It worked. 😍 Here's a working example:

Create a simple project with a composer.json:

{
  "require": {
    "monolog/monolog": "^3.9",
    "hichemtab-tech/namecrement": "^1.1"
  }
}
Enter fullscreen mode Exit fullscreen mode

Run Pomposer:

pomposer install
Enter fullscreen mode Exit fullscreen mode

And a quick test file:

<?php
require_once 'vendor/autoload.php';

use HichemTabTech\Namecrement\Namecrement;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$existing = ['file', 'file (1)', 'file (2)'];
$newName = Namecrement::namecrement('file', $existing);

echo "Next unique file name after the list of existing files:\n";
echo "Existing files: " . implode(', ', $existing) . "\n";
echo "New file name: " . $newName . "\n\n";

$log = new Logger('pomposer');
$log->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG));
$log->info('Pomposer is alive! 🚀');
Enter fullscreen mode Exit fullscreen mode

Running php index.php outputs:

Next unique file name after the list of existing files:
Existing files: file, file (1), file (2)
New file name: file (3)

[2025-07-06 20:31:22] pomposer.INFO: Pomposer is alive! 🚀 []
Enter fullscreen mode Exit fullscreen mode

Yes, Pomposer lives! But hold on, we're still in super-early beta. It has no Composer plugin support, no dev dependencies yet, no scripts or fancy features—it's just a proof-of-concept.

But hey, it works—and that's pretty cool, right? 😅

Building Pomposer has been a fantastic challenge filled with both frustrating and satisfying moments. It's not just another Composer wrapper—it's a completely new take on package management inspired by pnpm. And yes, it's rough, but that's the beauty of open source.

If this excites you and you'd like to see Pomposer evolve, contribute, or share ideas, please join us on GitHub. I can't wait to hear your thoughts and ideas!

Top comments (1)

Collapse
 
xwero profile image
david duymelinck

'Monolog\' => '~/.pomposer-store/monolog/monolog/src/',

Shouldn't that be ~/.pomposer-store/monolog/monolog/3.9/src/? Otherwise there is no use for storing the different package versions in separate directories?

The next question is how does pomposer handle multiple versions of the same package in a project? Isn't that why Composer has the dependency mess. You can't change the library namespace for the libraries that have different package versions.

While I think it is a great idea, I think there are some unmovable walls that are going to make it impossible to achieve what you have in mind in more challenging situations.