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

This talk has been presented at TypeScript Congress 2023, check out the latest edition of this Tech Conference.

FAQ

Mark Erickson is a senior front-end engineer at Replay, a Redux maintainer, and a writer known for his long blog posts and Simpsons avatar.

Mark Erickson maintains several libraries including Redux core, React Redux, Redux Thunk, Reselect, and Redux Toolkit.

Mark Erickson faced challenges such as handling different build artifact formats, package exports, WebPack 4 issues, TypeScript module resolution, and ensuring compatibility with various user environments and bundler behaviors.

The 'exports' field in package.json is used to define different entry points for ESM and CommonJS files, and it helps tools find the correct file formats. Adding this field is considered a breaking change and can only be done in a major version update.

Node determines if a file is ESM or CommonJS by using file extensions like .mjs or .cjs, or by setting the 'type' field to 'module' or 'commonjs' in package.json, which applies to all .js files.

'Are The Types Wrong?' is a tool that helps verify how TypeScript interprets different entry points and type definitions in a package. It was developed by Andrew Branch as part of his work on TypeScript's module support.

Universal Module Definition (UMD) is a module format that can be used as an AMD file, a CommonJS file, or a global script tag. It is considered somewhat legacy and not always necessary in modern packages.

TSUp is a wrapper around ESBuild aimed at simplifying the build process for TypeScript libraries. It can also generate bundled TypeScript definition files.

Mark Erickson encountered issues with WebPack 4 such as lack of support for the 'exports' field, and inability to parse ES 2018 object spread syntax and optional chaining syntax. WebPack 4 also chokes on .mjs files in the main field.

React Server Components are a new technology in React that complicate the job of library maintainers by requiring additional build artifacts and causing more bug reports. There has been limited communication from the React team on how these changes impact the ecosystem.

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
NPM workspaces help manage multiple nested packages within a single top-level package, improving since the release of NPM CLI 7.0. You can easily add dependencies to workspaces and handle duplications. Running scripts and orchestration in a monorepo is made easier with NPM workspaces. The npm pkg command is useful for setting and retrieving keys and values from package.json files. NPM workspaces offer benefits compared to Lerna and future plans include better workspace linking and adding missing features.
Webpack in 5 Years?
JSNation 2022JSNation 2022
26 min
Webpack in 5 Years?
Top Content
In the last 10 years, Webpack has shaped the way we develop web applications by introducing code splitting, co-locating style sheets and assets with JavaScript modules, and enabling bundling for server-side processing. Webpack's flexibility and large plugin system have also contributed to innovation in the ecosystem. The initial configuration for Webpack can be overwhelming, but it is necessary due to the complexity of modern web applications. In larger scale applications, there are performance problems in Webpack due to issues with garbage collection, leveraging multiple CPUs, and architectural limitations. Fixing problems in Webpack has trade-offs, but a rewrite could optimize architecture and fix performance issues.
Stop Writing Your Routes
Vue.js London 2023Vue.js London 2023
30 min
Stop Writing Your Routes
Designing APIs is a challenge, and it's important to consider the language used and different versions of the API. API ergonomics focus on ease of use and trade-offs. Routing is a misunderstood aspect of API design, and file-based routing can simplify it. Unplugging View Router provides typed routes and eliminates the need to pass routes when creating the router. Data loading and handling can be improved with data loaders and predictable routes. Handling protected routes and index and ID files are also discussed.
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
The Talk discusses the use of TypeScript and SQL together in software development. It explores different approaches, such as using an ORM like TypeORM or a schema generator like pg2ts. Query builders like connects JS and tools like PGTyped are also discussed. The benefits and trade-offs of using TypeScript and SQL are highlighted, emphasizing the importance of finding a middle ground approach.
TypeScript Performance: Going Beyond the Surface
TypeScript Congress 2023TypeScript Congress 2023
34 min
TypeScript Performance: Going Beyond the Surface
Top Content
Today's Talk provides an overview of TypeScript performance and tools to address performance issues. It covers the compiler process, including the parser, binder, checker, and transformers steps. The Talk emphasizes the importance of keeping TypeScript up to date for better performance. It also discusses strategies for optimizing TypeScript compilation and debugging, analyzing build performance using trace files, and improving performance by simplifying types and avoiding overloading union types.
Lessons from Maintaining TypeScript Libraries
TypeScript Congress 2022TypeScript Congress 2022
30 min
Lessons from Maintaining TypeScript Libraries
Top Content
Mark Erickson, a Senior Frontend Engineer at Replay, discusses JavaScript libraries and their support for TypeScript, including migration, versioning, and debugging. He also explores the challenges of supporting multiple TypeScript versions and designing APIs for use with TypeScript. Additionally, he shares advanced Redux type tricks and insights into maintaining a TypeScript library. The poll results reveal the widespread usage of TypeScript among developers, with many gradually migrating their codebases. Lastly, he provides tips for upgrading TypeScript and verifying functionality.

Workshops on related topic

Mastering advanced concepts in TypeScript
React Summit US 2023React Summit US 2023
132 min
Mastering advanced concepts in TypeScript
Top Content
Featured WorkshopFree
Jiri Lojda
Jiri Lojda
TypeScript is not just types and interfaces. Join this workshop to master more advanced features of TypeScript that will make your code bullet-proof. We will cover conditional types and infer notation, template strings and how to map over union types and object/array properties. Each topic will be demonstrated on a sample application that was written with basic types or no types at all and we will together improve the code so you get more familiar with each feature and can bring this new knowledge directly into your projects.
You will learn:- - What are conditional types and infer notation- What are template strings- How to map over union types and object/array properties.
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.