Publishing TS Libraries for Fun and Profit

Rate this content
Bookmark

Publishing libraries to NPM is easy - just `tsc && npm publish` and you're done, right?

Whoops, you forgot proper ESM compat. And a user is asking for a UMD build. And it doesn't work in Webpack 4. And `moduleResolution: "node16"` can't find the types.

Publishing libraries today is _complicated_. We'll take a look at the many problems and questions you should consider when publishing a package, and some hard-earned possible answers to those questions.type

Mark Erikson
Mark Erikson
31 min
21 Sep, 2023

Comments

Sign in or register to post your comment.

Video Summary and Transcription

Mark Erickson discusses the complexities of publishing TypeScript libraries, including considerations like build artifact file formats, package exports, and different user environments. He shares his experiences with ESM support and interop with other module formats, and the challenges faced in migrating Redux to TypeScript. Erickson highlights the importance of understanding file formats and module types, and the insights gained from discussions with the TypeScript team. He also emphasizes the need for better tools and documentation in the ecosystem for publishing and maintaining TypeScript libraries.

1. Introduction to Publishing TypeScript Libraries

Short description:

Hi, my name is Mark Erickson and today I am very excited to talk to you about publishing TypeScript libraries for fun and profit. Publishing packages is not as simple as running TSC and npm publish. There are many considerations to keep in mind, such as build artifact file formats, package exports, different user environments, bundler behavior differences, and more. Maintaining Redux and other libraries has given me insight into the complexities of the process, including the challenges of ESM support and interop with other module formats.

♪ Hi, my name is Mark Erickson, and today I am very excited to talk to you about publishing TypeScript libraries for fun and profit. Mostly excited? Somewhat excited? Okay, look, it's been a really difficult year. There hasn't been a whole lot of fun. And actually, trust me, there has not been any profit at all. We're gonna go through the details.

A couple quick things about myself. I am a senior front-end engineer at Replay, where we're building a time-traveling debugger for JavaScript. Please check it out. I will answer questions pretty much anywhere there is a text box on the Internet. I collect all kinds of interesting links. I write extremely long blog posts. I am a Redux maintainer, but most people know me as that guy with the Simpsons avatar.

So, publishing packages is really simple, right? You just run TSC and npm publish, and you're done. Thank you. Oh boy, wow, I wish it were that easy. This would be a whole lot shorter talk, if it was. Earlier this year I got kind of annoyed and published a tweet where I listed some of the things you have to keep in mind when publishing packages stay. Build artifact file formats, whether to bundle or keep individual JavaScript files, package exports, WebPack 4, Typescript module resolution, different user environments, bundler behavior differences, Node ESM versus CGS, whether to bundle your TypeScript types, and now React's use client. There's no guides. Everybody's borrowing from everybody else, and it's a miracle this ecosystem works at all.

So, how did I get involved in this process? Well, I've been maintaining Redux for the last several years, and as of the start of this year, I maintained five different libraries. Redux core, React Redux, Redux Slunk, Reselect, and Redux Toolkit. And each of these had a somewhat different build setup, but in general, there was a mixture of ESM, CommonJS, and UMD build artifacts. Everything used a .js extension. Everything was being compiled to ES5 for IE11 compatibility. Most of the packages used rollup plus babble, except Redux Toolkit, which used es-build. None of the packages defined the exports field in package.json, and there were a variety of different folders being used for the build output.

So, what does ESM support even mean, anyway? And the problem here is that the ES2015 language spec defined the syntax for importing and exporting, and some of the expected behaviors, but it didn't define how runtime environments, like the browser or node, are actually supposed to handle loading these, or how they're supposed to interop with other module formats like CommonJS. Now, most of us have been writing ESM syntax for years, but when you publish a library, you normally convert it to CommonJS before you publish. And you also usually compile your syntax to ES5, so it works in IE11, unfortunately.

2. Understanding File Formats and Module Types

Short description:

So packaged JSON has different fields that tools look for to find the right file. Node took years to add support for ES modules. There's a new field called exports, but it's a breaking change. Node understands file type through file extension or the type module field. We decided to modernize Redux packages and encountered import issues in Node-ESM environment. We migrated Redux to TypeScript but didn't ship it. We wanted modern build output and smaller bundle sizes.

So packaged JSON has a bunch of different fields that different tools look for to try to find the right file. Node looks at the main field for CommonJS files, bundlers often look at the module field for ESM, CDNs and tools like unpackage look for different keys, TypeScript looks for its TypeScript types, and all these different tools have different expectations. On top of that, it took Node years to add decent support for ES modules because they were trying to figure out how it would work with CommonJS.

So there's a relatively new field called exports, and it's supposed to be the fully definitive one-stop shop for where you tell tools how to find your different entry points and different file formats. So you can find a whole bunch of different entry points, you can have nested conditions, like here's where to find an ESM file versus a CommonJS file, you can define conditions like development and production. But the problem is that adding exports is really a breaking change for your package, which means you can only do it in a major version.

So how does Node understand whether a given file is ESM or CommonJS? There's two different ways. One is it now allows you to use a .mjs or a .cjs file extension to declare what type of module it is, or you can add the type module field at the top level, or type CommonJS, and every file with a .js extension will be treated as if it's that type of module. So at the start of the year, we decided to modernize all the Redux packages. We had gotten some bug reports that you couldn't import them properly in a Node-ESM environment. We'd actually migrated the Redux score to Typescript back in 2019 and then never actually shipped it. Version 4 worked fine and we had concerns about shipping a new major version. And we wanted to modernize all the build output and ship modern JS syntax for smaller bundle sizes.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Levelling up Monorepos with npm Workspaces
DevOps.js Conf 2022DevOps.js Conf 2022
33 min
Levelling up Monorepos with npm Workspaces
Top Content
Learn more about how to leverage the default features of npm workspaces to help you manage your monorepo project while also checking out some of the new npm cli features.
Webpack in 5 Years?
JSNation 2022JSNation 2022
26 min
Webpack in 5 Years?
Top Content
What can we learn from the last 10 years for the next 5 years? Is there a future for Webpack? What do we need to do now?
Stop Writing Your Routes
Vue.js London 2023Vue.js London 2023
30 min
Stop Writing Your Routes
The more you keep working on an application, the more complicated its routing becomes, and the easier it is to make a mistake. ""Was the route named users or was it user?"", ""Did it have an id param or was it userId?"". If only TypeScript could tell you what are the possible names and params. If only you didn't have to write a single route anymore and let a plugin do it for you. In this talk we will go through what it took to bring automatically typed routes for Vue Router.
TypeScript and the Database: Who Owns the Types?
TypeScript Congress 2022TypeScript Congress 2022
27 min
TypeScript and the Database: Who Owns the Types?
Top Content
We all love writing types in TypeScript, but we often find ourselves having to write types in another language as well: SQL. This talk will present the choose-your-own-adventure story that you face when combining TypeScript and SQL and will walk you through the tradeoffs between the various options. Combined poorly, TypeScript and SQL can be duplicative and a source of headaches, but done well they can complement one another by addressing each other's weaknesses.
Lessons from Maintaining TypeScript Libraries
TypeScript Congress 2022TypeScript Congress 2022
30 min
Lessons from Maintaining TypeScript Libraries
Maintaining widely-used JS libraries is already complicated, and TypeScript adds an additional set of challenges.

Join Redux maintainer Mark Erikson for a look at some of the unique problems TS library maintainers face, and how the Redux team has handled those problems. We'll cover:

- Tradeoffs of different ways to define TS types for a library
- How to target different versions of TS, and considerations for determining the supported version range
- Migrating existing JS libraries to TS
- Differences between writing "app" types and "library" types
- Managing and versioning public types APIs
- Tips and tricks used by types from the Redux libraries
- TS limitations and possible language-level improvements
TypeScript Performance: Going Beyond the Surface
TypeScript Congress 2023TypeScript Congress 2023
34 min
TypeScript Performance: Going Beyond the Surface
Do you ever find yourself pondering how to identify and address performance issues in TypeScript to maximize the effectiveness of your code? If so, join us for a talk on the performance of TypeScript and the techniques you can use to get the most out of your code. We'll delve into various ways to debug performance, explore how to leverage the power of the TypeScript compiler to detect potential performance issues and use the profiling tools available to track down the underlying bottlenecks.

Workshops on related topic

Finding, Hacking and fixing your NodeJS Vulnerabilities with Snyk
JSNation 2022JSNation 2022
99 min
Finding, Hacking and fixing your NodeJS Vulnerabilities with Snyk
WorkshopFree
Matthew Salmon
Matthew Salmon
npm and security, how much do you know about your dependencies?Hack-along, live hacking of a vulnerable Node app https://github.com/snyk-labs/nodejs-goof, Vulnerabilities from both Open source and written code. Encouraged to download the application and hack along with us.Fixing the issues and an introduction to Snyk with a demo.Open questions.
Build Web3 apps with React
React Summit 2022React Summit 2022
51 min
Build Web3 apps with React
WorkshopFree
Shain Dholakiya
Shain Dholakiya
The workshop is designed to help Web2 developers start building for Web3 using the Hyperverse. The Hyperverse is an open marketplace of community-built, audited, easy to discover smart modules. Our goal - to make it easy for React developers to build Web3 apps without writing a single line of smart contract code. Think “npm for smart contracts.”
Learn more about the Hyperverse here.
We will go over all the blockchain/crypto basics you need to know to start building on the Hyperverse, so you do not need to have any previous knowledge about the Web3 space. You just need to have React experience.