Skip to main content

Making TypeScript npm Packages

If you've landed here, I can only assume you're like me and see packages as the highest form of sophistication in software development.  In that same vein, I bet at some point in the past you've wished you could start applying DRY principles to your client-side efforts.  I know for myself, I don't enjoy writing the same application bootstrap code constantly and so recently, I was motivated to codify it.

This body of understanding has taken me quite some while to figure out, hopefully what I share here is helpful enough to get you up to speed.  No post is complete without some kind of example, so throughout I'm going to reference a package I've just finished putting together called protoculture.

Briefly described, protoculture encapsulates all the common bootstrap and conventions I've been using while developing TypeScript apps that use React and Redux.  Honestly, I've already gotten a lot of benefit out of putting this package together, but nothing about this post will be coupled to its objective.

TypeScript

Based on the title, you shouldn't be surprised.  The first thing I'm going to tell anyone looking to tame their front end code is to stop writing it in plain ES.  Although with that said, much of this post focuses on the generic npm ecosystem, so if you are still committed to ES, I'm sure there are still plenty of useful tidbits here.

Getting into things, make sure you understand TypeScript itself and have an appreciation for authoring code in it.  This should start with really getting a handle on tsconfig.json which even took me a bit to appreciate fully.
Once you're in the swing of things, try to look for opportunities to make use of things like enums and generics. Explore TypeScript and find ways make your code more expressive and easier to consume.

With regards to IDE support, they're in a bit of a weird space currently and can sometimes get in the way. Especially Visual Studio which still trips on its own solution and project models.
Before you go too far writing any TypeScript code, throw a tsconfig.json in your projects early and play by the rules.
If you really need a suggestion for an editor, look at Atom, Visual Studio Code or WebStorm, they are all quite strong.

Building TypeScript

Building your final outputs in the JavaScript space has got to be one of the most challenging things to gain a good mental model for.  It's going to have to happen if we want to produce a package, so you'll have to commit to at least appreciating all the objectives.

The community has produced great tools like JSPM and WebPack which take care of module loading and bundling. Briefly explained:
  • Module loading is the convention where anything processing your code will go through a series of steps and specific locations to resolve your imports to actual JS code.
  • Bundling is the process you go through to produce your final output. This usually ends up being one or several .js files that you reference from script tags.
Believe it or not, for the sake of this blog post, we won't have to think about bundling!  It makes sense that this is not in the picture as when we're making a library, we're only going to put the code in a state where it can be consumed by other projects. Not deliver it to web browsers for execution.

To accomplish this, what I've resorted to doing when producing packages backed by TypeScript code is I get tsc to produce its output as UMD modules.  This ensures that anyone looking to use your package can do so, regardless of what module system they're using inside of their project.

If someone using your package is authoring their code in plain JS, they'll be on their way. Indeed one of the nice things about this process is that while your code will be written in TypeScript, consumers of your package won't be obligated to do the same.
If anyone intends on consuming your package from another TypeScript project however, this is only going to be half the story.  I'll address the other half once a few other things have been established.

Docker

This won't come as a surprise to people who know me, but I will containerize anything that can be. The benefits I've observed in doing this cannot be understated and if for whatever reason you've missed it up until now, it has become eye-rollingly easy to get started using Docker for Windows and/or Docker for Mac.

My usage of docker in this scenario is simple.  If I can orchestrate my tooling and processes through a container, then anyone can run them.  Not only will we all then be using the same tools and have the same experience, but the process will run anywhere.  Including my build server!

It might be piling on a bit too much too early if you're really starting from scratch learning all of this. But I do suggest circling back afterwards and containerizing as a stretch goal.

npm

Goes without saying, you need to be authoring a package.json to contain all your dependencies and metadata.

This one is straightforward and most of your existing understandings of this file as a consumer are going to carry you. The main things to shore up will be to do with making sure your package is ready for others:
  • The version string as it is committed in source control will never change and should stay permanently fixed at something like 0.0.0-development.
    Bear in mind that publishing your package will require that this version number be different to any prior version of your package that has been published. But I'll explain how that process works shortly.
  • Be a good FOSS citizen and take the time to choose and appreciate a proper SPDX license identifier for your package.  All the cool kids are doing Apache 2 lately.
  • Identify the entrypoints of your package.  There are two types of entrypoints to know about for our purposes, I'll go into detail about what I've chosen shortly.
    • main - is standard according to npm and describes the script that will be returned when your root package is imported.
    • types - is used by TypeScript as a convention to indicate typing information for the entrypoint script.
Another file you're going to want to know about is .npmignore which is used to mask off files from being included during packaging.  You'll want this so that you don't include more than what is necessary when pushing your package to npm.

Yarn

Yarn is a recently-released although long in development drop-in replacement for npm. If you use server-side tooling like NuGet, composer or bundler, then you can basically think of this as bringing the quality of dependency management up to community expectations in one strike.

It's designed to be fully compatible with npm, all you need to do is start using it and remember to commit your lockfile to source control.

TypeScript and npm Revisited

While going over npm, I mentioned two entrypoint properties in package.json.  The files they point to are clear when you read them and the respective documentation for the properties does a good enough job explaining things.
What wasn't immediately apparent when I was putting all of this together was how to produce these two files and what exactly they would correspond to.  Especially when producing a library.

Libraries are usually just inert functionality, waiting to be integrated.  There is no entrypoint and nothing to invoke at a top level.

Nevertheless, consumers of your package are going to want some notion of the touchpoints you are exposing.  With that in mind, your index actually ends up looking kind of like a header file!  When typescript goes to build your UMD modules, index.js will just be the JS equivalent of those re-exports and index.d.ts will export the corresponding type information.  Spend a good amount of time understanding this as I think once understood, the motivations behind everything else gets clearer.

Something else recently improved in this space is the introduction of @types packages for TypeScript.  These packages are produced either by package maintainers or the community to allow people authoring TypeScript to get type hinting for packages not authored in TypeScript.

Before @types, there was an external tool called Typings that would install these dependencies, much like npm would install its own packages. I spent a bit of time using this prior and have to say @types is a massive improvement. Especially when introduced alongside Yarn.  Everything blends in nicely with the node_modules directory and at long last the ecosystem seems to be stabilizing around some standard expectations.

Semantic Release

There's a lot of cool going on here, but this is a really nice one and my hat goes off to the maintainers of this project.

I mentioned earlier that you shouldn't put any real version numbers inside of your package.json file. This is for good reason, as I'm sure it's obvious that managing that by hand would become a burden. It also makes continuous delivery quite difficult.

Semantic release is an npm-based dev-time tool that shoulders the burden of coming up with a proper version number and publishing npm packages with them.  It's extremely easy to get started using it, especially if you're using any CI platforms that are popular with the community.

Continuous Integration and Delivery

One skill that I think people don't always spend enough time advancing is build and deployment. There are a number of very valid reasons for this, ranging from comfort and confidence to not having the infrastructure available to tinker with.

If you're looking for a quick way to upgrade your skills, I can think of no better a teacher than producing open source packages.  The purpose of the package is immaterial, you can seriously take the most menial of things that you deal with and turn them into packages.

What I'd like to first say is that Travis CI is free for open source projects.  Just go make yourself an account there and poke around the interface. It integrates fully with github and they have done such a good job coming up with a simple and intuitive experience that it can guide you into learning exactly what kind of objectives to seek out when configuring build flows.
Second, based on the publicly visible build information for protoculture you can see what goes on just by examining the logs. Configuring builds in Travis CI is done using a simple .yml file.  No surprises or undocumented gotchas.

In fact, if this is something you are thinking about taking on, you're now probably itching to read more about what I've done with Docker!

Summary

I've talked about a lot of different moving parts in this post and I think that's really what motivated me to author it. Creating packages for npm is not trivial - and automating it can be daunting.  Adding a transpiled language like TypeScript doesn't help matters as you are removing yourself by one degree from the flow in the hopes of obtaining other structural advantages.

Up until recently I only had a rough blueprint of what the best practices were in the JavaScript space. There is a lot of cynicism and humour out there targeting node, npm and browser development. Much of it is a caricature of legitimate gripes, but most of the time I see through it as a way to avoid some very rewarding effort.
Being able to reuse code on the client side has given me a level of confidence similar to what I enjoy during server side development.  Going forward, I know my abstractions will be much better suited for deployment and reuse because of the great tools made by the community.

I hope this post has helped you and as always if I think of any improvements or if anyone has a correction for me, I'll be sure to make edits.

Comments

Popular posts from this blog

Laravel Project Architecture: The Missing Guide

At my job, we've been doing a lot of learning and development using Taylor Otwell 's Laravel 4 PHP framework.  As we've become more familiar with it, we've had to come up with better ways to structure our projects outside of what the documentation indicates and the default distribution that is provided. If you've been working with Laravel 4 for any amount of time or come with experience from another framework and are just getting started, you've probably noticed that there are a lot of different ways to cut up your projects. Choice is nice, but sometimes it can be paralysing or misleading. Concrete Advice This post is done in such a way that you can just skim the headings, but if you want a detailed explanation in each section, feel free to read in where necessary. While I can't say the entirety of my advice is in practice throughout the community, I can say that we are starting to use it, and to very good effect at my job.  Especially consider...

Building .NET Core Nuget Packages

My last blog post was on building and publishing npm packages, specifically for typescript projects. In my opinion, packages are an important fundamental unit in software development. They sit at the confluence of technical competence and collaboration, and represent something the software community should be proud of. Most of the time, you're going to be creating software packages once you're comfortable with some essential pillars: Coding Project structure Software architecture Building Delivery Community Licensing After I got my npm package up and running, my next task was to do the same thing with my C# libraries. Similar to protoculture, I have made a library called praxis .  This is done by leveraging the services and tooling known in the .NET ecosystem as nuget. In this case, praxis abstracts many of the concepts and functionality I require when producing server projects. It builds on top of ASP.NET Core, so in that sense you can almost think of it as ...

Laravel is Good, Facades Aren't

I've been working on some Laravel 4 based packages lately which inevitably results in me also looking at other packages. I've noticed sometimes that people use facades at times that give me pause. The most troubling being from inside their model classes. A quick google turned up this video which assures people "there's still an instance behind everything, we're fine" .  Everything mentioned in the video is true except that there is a very glaring omission. Scope What usually goes out the door at the start of a long series of mishaps in software design is scope . When the desire to obtain a solution is stronger than the desire to consider the implications of a firm approach, mistakes are sure to follow.  Sacrifices like this are made due to the assumption of a high cost to developing carefully. What really is happening however is a false dilemma , being responded to with a convenience decision . It's very easy to write model code like ...