I recently decided to publish [an NPM module](https://www.npmjs.com/package/unstated-viewer). I wrote it in [TypeScript](https://www.typescriptlang.org/) and went with [Rollup](https://rollupjs.org) for bundling. It was my first time publishing a module and my first experience with Rollup, so I’d like to write a tutorial on how to do it, both as a reference for myself and for anyone who may find this useful. Let’s get started! ### Why TypeScript and Rollup I went with TypeScript for several reasons. It’s what I use at work, so I’m not only familiar with it, but it’s always good to hone my skills, especially since I’m not necessarily a TypeScript expert (yet). I’m also a fan of types for a number of reasons, and compile-time errors are invaluable. [I also believe all JS libraries should be authored in TypeScript](https://staltz.com/all-js-libraries-should-be-authored-in-typescript.html). As for Rollup, I mostly went with it because I had never used it and have heard good things about it. Not only that, but I know that a number of open-source projects use it over Webpack. I also read [this great post](https://medium.com/webpack/webpack-and-rollup-the-same-but-different-a41ad427058c) about the differences between the two and why Rollup is better for building libraries. ### Prerequisites I’m going to be using `yarn`, but you can use `npm` if you wish! First, let’s create our `package.json` inside the project directory. Fill out everything as you wish, or just hit `Enter` at every step if you don’t care yet. You can always edit these later. $ yarn init First, let’s install TypeScript and Rollup inside the project directory, as well as a plugin to allow Rollup to compile TypeScript as part of its bundling process. $ yarn --dev add typescript rollup rollup-plugin-typescript2 **Note:** The original `rollup-plugin-typescript` appears to be unmaintained, which is why we’re using this one instead. At this point, your `package.json` should look _something_ like this: { "name": "some-project", "version": "1.0.0", "main": "index.js", "author": "John Doe <jdoe[@example.com](mailto:grardb@gmail.com)\>", "license": "MIT", "devDependencies": { "rollup": "^0.62.0", "rollup-plugin-typescript2": "^0.15.1", "typescript": "^2.9.2" } } Let’s add a few extra lines to the file and replace the default for `main`: { "name": "some-project", "version": "1.0.0", "main": "dist/index.js", "module": "dist/index.es.js", "files": \["dist"\], "types": "dist/index.d.ts", "scripts": { "build": "rollup -c", "watch": "rollup -cw" }, "author": "John Doe <jdoe[@example.com](mailto:grardb@gmail.com)\>", "license": "MIT", "devDependencies": { "rollup": "^0.62.0", "rollup-plugin-typescript2": "^0.15.1", "typescript": "^2.9.2" } } Here’s what’s going on: * `main` and `module` point to the bundled JavaScript so that the library consumer may import the module. `main` is for the CommonJS module, and `module` is for the ES module. You don’t need to understand the details of that for now, though! * `files` lets `npm`/`yarn` know what to publish, or rather what gets installed inside a user’s `node_modules` when they install your module. For now, let’s just do `dist`, which will contain our bundled JS. * `types` points to our TypeScript declaration file. This will get compiled automatically for us (more on that below). * `scripts` just has some handy Rollup scripts, which you can use during development. `yarn run build` will bundle the module once, while `yarn run watch` will build it every time a file changes. Now we’re ready for some configuration! ### TypeScript configuration In order to configure TypeScript, we need to create a `tsconfig.json` file. There are [more options](https://www.typescriptlang.org/docs/handbook/compiler-options.html) [than we can go over](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html), but the following ones will be good enough for now. I encourage you to dive deeper into the options if you find yourself unsatisfied with how something is working! Copy the following into `tsconfig.json` in the root directory of your project. { "compilerOptions": { "declaration": true, "declarationDir": "./dist", "module": "es6", "noImplicitAny": true, "outDir": "./dist", "target": "es5" }, "include": \[ "src/\*\*/\*" \], "exclude": \["node\_modules"\] } What’s going on: * `"declaration": true` is there to enable automatic generating of TypeScript’s [declaration files](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html). These are helpful for TypeScript users who are using your module, since they’ll have access to all the types of your module’s methods, variables, etc. * `declarationDir` and `outDir` specify where the compiled code will go. I’m using `./dist`, but you may choose another name such as `./lib`. If you choose a different directory name, make sure to use that name in your `package.json` as well! * `"noImplicitAny": true` forces us to be a bit stricter with types. By default, TypeScript allows you to get away with not assigning types to variables whose types cannot be [inferred](https://en.wikipedia.org/wiki/Type_inference). This option makes it so that if we want something to use the type `any`, it must be done explicitly. * `target` allows us to specify which version of JavaScript to compile our TypeScript to. In this case, I’m choosing ES5. * `include` allows us to specify where TypeScript should look for `.ts`, `.d.ts`, and `.tsx` files to compile. * `exclude` allows us to specify directories for TypeScript to ignore when it comes to compiling. ### Rollup configuration Next, we’ll configure Rollup. In the root directory of your project, create a file called `rollup.config.js`. Next, copy this into there: import typescript from 'rollup-plugin-typescript2' import pkg from './package.json' export default { input: 'src/index.ts', output: \[ { file: pkg.main, format: 'cjs', }, { file: pkg.module, format: 'es', }, \], external: \[ ...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {}), \], plugins: \[ typescript({ typescript: require('typescript'), }), \], } The first thing to note is that we’re importing `package.json`. This isn’t necessary, but it’s a handy way to make sure you aren’t duplicating certain information, such as the filename of your bundle. Here’s a breakdown of what’s going on: * `input` tells Rollup where to look for code to bundle. This is similar to Webpack’s `entry`. * `output` is where our bundle gets stored. As stated earlier, we’re going to bundle both CommonJS (`cjs`) and ES (`es`) modules. [Rollup’s documentation](https://rollupjs.org/guide/en) has a pretty good explanation of the differences if you’re interested. * `external` is what we use to tell Rollup what modules to exclude from our bundle. Since `pkg.dependencies` will get installed by the module consumer’s `yarn` or `npm`, and since `pkg.peerDependencies` are expected to be installed by the consumer, we can safely exclude those from the bundle. * The `plugins` section is a bit weird. What we’re doing there is making `rollup-plugin-typescript2` use the locally-installed TypeScript. By default, it uses a version that likely isn’t up-to-date. There are a bunch of other plugins we could install, but this is fine for now! ### Publishing to NPM In order to publish the module, we first need to log into NPM. If you don’t have an account, [go create one now](https://www.npmjs.com/signup). You’ll only need to do this once on your machine. For some reason, `yarn login` didn’t work for me, so I’m going to use `npm login` for this: $ npm login Username: yourusername Password: Email: (this IS public) [email@example.com](mailto:grardb@gmail.com) Logged in as yourusername on [https://registry.npmjs.org/](https://registry.npmjs.org/). Next, let’s create a tiny module to publish. Create a `src` directory, and inside there, an `index.ts` file: // src/index.ts export const greet = () => console.log('Hello, world!') After that, run the `build` command to compile the TypeScript: $ yarn run build yarn run v1.3.2 $ rollup -c src/index.ts → dist/index.js, dist/index.es.js... created dist/index.js, dist/index.es.js in 522ms ✨ Done in 0.91s. You’ll notice a `dist` folder was created with three files in it. Feel free to check those out! Finally, we can publish the module to NPM: $ yarn publish yarn publish v1.3.2 \[1/4\] Bumping version... info Current version: 0.0.1 question New version: \[2/4\] Logging in... \[3/4\] Publishing... success Published. \[4/4\] Revoking token... info Not revoking login token, specified via config file. ✨ Done in 1.85s. And that’s it! You’ve successfully published a module to NPM using TypeScript and Rollup. To verify that everything looks good, create another directory somewhere else on your computer and run `yarn init`, then `yarn add your-package-name`. If you navigate to `node_modules/your-package-name`, you should see the `dist` folder with all the compiled files in there! You can then import your module like so: import { greet } from 'your-package-name' ### Developing locally As you can imagine, it would be pretty annoying and hacky to publish your package every time you wanted to test it out. While writing automated tests helps a lot in this regard, sometimes you’ll need to actually use your module to make sure that it works properly before publishing it. For this, we can use `yarn link` (or `npm link`, of course). First, navigate to your module’s project directory and run the following: $ yarn link Next, navigate to the project where you’d like to consume this module. Make sure that the module is not installed via `yarn` or `npm`, then run: $ yarn link your-package-name This will create a [symbolic link](https://en.wikipedia.org/wiki/Symbolic_link) to your package folder inside your project’s `node_modules` directory. Essentially, you’ll be able to use the local version of your package the same way you’d be able to use it if you had it published and downloaded/installed via `yarn add` or `npm install`! ### Thanks for reading If you’ve made it this far, congratulations on publishing something to NPM! I hope you enjoyed this tutorial. If I made any mistakes or if anything needs clarification, please don’t hesitate to [reach out](https://twitter.com/grardb). I’d like to contribute more to NPM going forward, so I’ll try to keep this tutorial as up-to-date as I can.