Migrating from Bower to NPM/Yarn

As we all know, Bower is on its way out, although Bower is still being maintained, Bower maintainers recommend not using Bower anymore and use the combination Yarn + Webpack. Webpack is not a dependency manager so we won’t analyze it, and there’s plenty of information about it now everywhere in the web (I have a couple of posts pending about it though).

Bower

Some of most important characteristics of Bower are:

  • Tight git coupling, meaning
    • All modules need to be git repositories and
    • Module versions are based on git tags
  • Lightweight registry with small JSONs pointing to where the git repository is and some metadata

[image of how bower works]

As a very quick refresher, this is what you need to publish a new module to the Bower registry:

git init
git add .
git commit -m "First commit"
git tag v1.0.0
git push origin master --tags
bower register my-module <git-endpoint>

# Then, somewhere else:
bower install my-module

We will see later some other ways to consume bower modules that will help us to give backward compatibility for people still consuming our NPM package with Bower.

NPM / Yarn

First, we need to clarify that both NPM and Yarn are NPM clients, and having said that, I could rephrase the options to npm-cli and yarn.

  • Both work with NPM packages and they differ on how they install the packages, how fast/secure/deterministic they are installing them,
  • And on the other tools they provide, like a way to see packages licenses, find out which packages are using a particular package, etc.

I will not go in depth about the differences between the 2 because I think people have already explained it several times and with great quality. These are some useful articles if you want to read more about its comparison:

I personally prefer Yarn because I found it more consistent and fast installing packages.

Migrating your Bower module

Step 1: Moving your dependencies from bower.json to package.json

This step should be easy, if you are using packages with some popularity then they should provide a package.json with their dependencies defined. In that case you just run

yarn add my-bower-dep
# or 
npm install --save my-bower-dep

You can use the alternative ways to reference a package with npm, like:

$ npm install https://github.com/indexzero/forever/tarball/v0.5.6

$ npm install git+ssh://git@github.com:npm/npm.git#v1.0.27

$ npm install git+ssh://git@github.com:npm/npm#semver:^5.0

$ npm install git+https://isaacs@github.com/npm/npm.git

$ npm install git://github.com/npm/npm.git#v1.0.27

$ npm install mygithubuser/myproject

$ npm install github:mygithubuser/myproject

Check more in this documentation link.

Now, a problem may araise if your dependency doesn’t have a package.json defined in its root’s directory. You can either create a pull request for it in your dependency’s repo or use something like napa.

napa’s configuration is pretty simple, taken from its documentation, one clean opproach is like this:

{
  "scripts": {
    "install": "napa"
  },
  "napa": {
    "foo": "username/repo#v1.2.3",
    "bar": "username/bar#some-branch",
    "baz": "username/baz#347259472813400c7a982690acaa516292a8be40",
    "qoo": "https://example.com/downloads/release.tar.gz",
    "fuz": "git+https://yourcompany.com/repos/project.git",
    "goo": "git+ssh://yourcompany.com/repos/project.git"
  }
}

With these approaches you should have your dependencies covered and defined in your package.json.

Step 2: Ignoring the dist/, build/ or whatever directory

With bower you were obliged to push your distribution files in the repository because the bower install was a checkout of the specified version/tag. With NPM, you can generate the distribution files during your publication phase and never push it to git.

npm run build
npm publish

Step 3: bower.json > ignore -> .npmignore (or package.json > files)

With the ignore object in the bower.json we could tell bower to ignore files when being downloaded from our consumers.

With NPM, we can do the same with the .npmignore file or via the package.json’s files object.

I prefer the positive approach where I say what files I want in the package, and not have to maintain the .npmignore every time I add a new configuration file or whatever. It’s safer too, if by any change you forget to ignore a file with some kind of sensitive information then your are screwed.

Step 4: Publishing your package to NPM (all versions if needed)

The step is super simple, change your package.json’s version property and just npm publish or yarn publish.

Some useful references, npm version, npm publish, yarn version, yarn publish.

If you want to publish packages for every tag you have, then repeat this process going back in your history and publishing the package. You have to understand though that your package.json can’t be “injected” in your git history. You can branch from the tag and push the package.json or just add it, publish the package, discard it and forget about it.

Remember that you can run npm publish --tag <tag> to not only publish your package but also create a git tag for your repository. So you can keep the consistency with your previous git tags. Anyway in my opinion is a good practice to tag released versions.

Step 5: Other useful information

What about bower’s flat installation?

If you see repeated nested dependencies inside node_modules, don’t panic, it’s ok, with Yarn there’s a way out:

yarn install --flat --prod

Yarn provides the yarn install --flat command that provides that funcionality. Just remember to divide correctly your dependencies from your devDependencies in your package.json and yarn will do the work for you, prompting you to chose a version if needed, Bower’ style. So you can be sure that you’ll have only 1 version of your dependencies in the browser and no conflicts between your dependencies.

Provide backward compatibility

Do you have consumers that will still consume you via Bower?

  • You can leave your bower.json if you want, and decide between being responsible of maintaining it or put a big notice in your readme saying that you’ll leave it as backward compatibility until the next version or something like that.
  • If your package is already registered in the bower registry, then just by creating tags for your releases you make your module downloadeable by bower. But if you don’t have a bower.json then your consumers will not download your dependencies (and they will have to add them in their bower.json). Also your distribution files are no longer versioned, even if they download your repo at a tag, they don’t have what they want! So what do you do?

While you maintain your bower.json versioned, generate the distribution files as always and publish the npm package with the bower.json.

Your consumers will have to consume you like:

dependencies: {
	"awesome-now-npm-only-dep": "https://registry.npmjs.org/ember/-/ember-1.0.3.tgz"
}

I put ember there just as an example, awesome-now-npm-only-dep would make the url too long :p

Bower will download that .tgz in their bower_components and if a bower.json is present, continue downloading the transitive dependencies. So the cost for you in this approach is leaving the bower.json in your repo and choose a policy for it: maintain it forever or until the version you choose.

And remember, even after you remove it, they still can download your package using the tag and add your dependencies as theirs (definitely not something I would do but…you never know).

Ok, I think this is enough information for you to have something working :) Bye!

Blog Logo

Tomas Alabes

Software Engineer, author, blogger and obsessive learner, from Argentina living in Silicon Valley


Published

Image

Tomas Alabes' Blog

My personal site's blog

Back to Overview