TL;DR?
NPM 🥊 | Composer 👊 |
---|---|
npm install (libraries go in node_modules, executables in node_modules/.bin) |
composer install (libraries go in vendor, executables in vendor/bin) |
npm install --production |
composer install --no-dev |
npm install --package-lock-only |
composer update --lock |
npm install <package> |
composer require <package> |
npm install <package> --save-dev |
composer require --dev <package> |
npm install -g <package> |
composer global require <package> |
npm update |
composer update |
npm update <package> |
composer update <package> |
npm outdated |
composer outdated |
npm run <script-name> |
composer run-script <script-name> |
The Complete Picture
As developers, we’re used to saying, “It works on my machine.” A decade ago, this phrase was reserved for snarky conversations with your friendly quality assurance person or the product owner who is testing a feature.
As our technology tooling has become more advanced (read: more complex), I’ve seen it come up as inter-developer conversation, frequently between a frontend and backend engineers:
Composer and Node package manager (npm) are the de-facto package managers for PHP and JavaScript. We use them every day at WebDevStudios. Let’s help each other get to know their similarities and their differences, as both tools can do unexpected things that you may not realize.
Init
While initializing a project isn’t the most common command, it’s useful to cover it first; in case you want to try to initialize a Composer or npm project in an empty directory.
$ composer init
Composer will interactively ask you some questions about your PHP project including: package name, description, package type, etc. Newer versions of Composer will prompt you to add the vendor
directory to your .gitignore
file. You should do this!
You can also just create your own composer.json
file. Here’s an example minimal composer.json
:
{ "name": "webdevstudios/composer-vs-npm", "description": "php side of composer-vs-npm", "license": "GPL-3.0-or-later" }
npm has the same command:
$ npm init
It will also interactively ask you a few questions about your JavaScript project including: package name, description, license, etc. After gathering this info, it will create a package.json
file.
You can instead create a package.json
file yourself. A minimal package.json
is just {}
, but npm will warn you if you don’t have these fields:
{ "name": "@webdevstudios/composer-vs-npm", "description": "js side of composer-vs-npm", "license": "GPL-3.0-or-later", "repository": "https://github.com/WebDevStudios/composer-vs-npm" }
Init Best Practices
For the npm package name, it’s a good idea to add a scope using the @ symbol, your organization name, and a slash before the package name. While it’s not required like Composer’s vendor name, it will help group your packages together.
npm does not offer to create an entry in .gitignore
for the node_modules
directory, but you should.
You can validate your composer.json
file format by running:
$ composer validate
I recommend not including version
in your composer.json
. It’s just one more thing that developers will forget or ignore. Let your version numbers be managed through tagging in your version control software.
npm requires a version
entry in package.json
only if you’re publishing your package. If you’re publishing your package, make sure keeping this up to date is part of your workflow. Otherwise, I suggest to omit it.
Both Composer and npm use the same license strings. A license entry is not strictly required, but both will print a warning, if you don’t include it.
Saving Dependencies
I’m getting a little ahead of myself because we’re going to talk about npm install
before the Install section below, but just bear with me. In Composer land, telling it that your project relies on a library is done by requiring it.
$ composer require <vendor>/<name>
This will instruct Composer to add an entry to the require
section of your composer.json
file, install the package (usually in a vendor
directory), and create and/or update a composer.lock
file with info about the version it installed.
npm does the same thing, but with their install
command:
$ npm install <package>
This will instruct npm to add an entry to the dependencies
section of your package.json
file, install the package (in a node_modules
directory), and create and/or update a package-lock.json
file with info about the version it installed.
Saving Options
You may have PHP or JavaScript dependencies that are only needed for development, and aren’t needed to use your project when it’s deployed. You can specify that those packages are installed as development dependencies:
$ composer require --dev <vendor>/<name>
$ npm install <name> --save-dev
Both Composer and npm can require specific versions of a package:
$ composer require <vendor>/<name>:<version>
$ npm install <name>@<version>
If you omit the version number, they’ll both specify the latest stable version with a carat (^) pre-pended. If you require lodash
via npm, it will specify "lodash": "^4.17.20"
in your package.json
file. This means if you were to tell npm to update Lodash, it would upgrade to newer versions up to the next major version—5.0.0. If you ever get unexpected results from version numbers, you can use these excellent semantic versioning (semver) tools to test:
Install
As the “Dad Joke Dog” meme explains above, the most common command to run is install
.
$ composer install
Running this in a folder that has a composer.json
file will instruct composer to read that file (along with composer.lock
if present) and install any requirements specified.
If the composer.lock
file is present, it will install the exact versions specified by that file, even if a newer version exists. If there’s no composer.lock
file, composer will install the latest packages according to the version constraints in composer.json
and create a composer.lock
file with those versions.
Usually packages will go into a vendor
directory, next to the composer.json
file.
$ npm install
Running this in a folder that has a package.json
file will instruct npm to read that file (along with package-lock.json
if present) and install any requirements specified.
If the package-lock.json
file is present, npm (similarly) will install the exact versions specified by that file, even if a newer version exists. If there’s no package-lock.json
file, npm will install the latest packages according to the version constraints in package.json
and create a package-lock.json
file with the exact versions.
Installing without development dependencies
To install packages without the development dependencies, like you would for a production environment, run either of these commands:
$ composer install --no-dev
$ npm install --production
This will skip installing require-dev
or devDependencies
for Composer or npm (respectively).
Update
This is one of those sections where I like to ask:
Are you sure you want to do this?
$ composer update
$ npm update
I like to ask are you sure because when you don’t supply any arguments to update
, Composer and npm have the potential to make a lot of changes that you may not have intended. Both will use the version constraints to update your specified dependencies (including dev dependencies) and it will update the child dependencies of the packages, too. So, if your version constraints are fairly “loose,” a lot of stuff is going to get updated, possibly some things you didn’t intend.
A more focused approach would be to upgrade one specific package, not all of them. Like this:
$ composer update composer/installers
$ npm update lodash
If you’re curious what is out of date, and what would be updated if you ran the update
command without any arguments, you can run an outdated
report instead:
$ composer outdated
$ npm outdated
Other useful commands
If you change something in your composer.json
file that is unrelated to the version constraints—like reordering the required packages—Composer will complain that your composer.lock
file is not up to date. To update just the content-hash
part of the composer.lock
file without updating any package versions, run:
$ composer update --lock
You can do the same thing with npm:
$ npm install --package-lock-only
Conclusion
As you can see, there are several commands that do the same thing and look the same in both Composer and npm. However, there are also commands that do the same thing but look very different.
It’s good to make sure you know what that command is going to do before you do it, so you don’t have to undo things you didn’t want to happen. 😬
Are there any other commands you use often that I missed? Let us know in the comments.