DEV Community

Hugo Di Francesco
Hugo Di Francesco

Posted on • Originally published at codewithhugo.com on

Writing an npm module with TypeScript and microbundle

For those looking to write a package and publish it to npm, TypeScript + microbundle is a low-friction way to build a high-quality library.

Why TypeScript?

TypeScript is a JavaScript superset that adds static types to it.

Its other features also follow the ECMAScript specification (current and future) quite closely.

For library authors this means you provide the consumers of your library, even those who don’t use TypeScript with more details around expected types and for some editors/IDEs that integrate with TypeScript (like Visual Studio Code) nicer auto-complete. TypeScript also serves as inline documentation that shouts at you when you pass something you shouldn’t, which will come handy when you rediscover your code a couple of months down the line.

Why microbundle?

Microbundle is a “zero-configuration bundler for tiny modules”. It’s a wrapper around rollup with sane defaults (including minification/compression), nice outputted size stats, multiple target formats (ES modules, CommonJS, UMD). Most of all in the scope of this post, it has TypeScript support out of the box (actually no configuration, not even a tsconfig.json).

It’s ridiculously easy to set up and allows library authors to focus on building a great library rather than setting up the plumbing to be able to ship a JavaScript library from ES6/TypeScript or other compile-to-JS tool 🙂.

Zero-config bundling

To start, we’ll have to create setup our package run npm init and complete all the prompts.

Next run: npm i --save-dev microbundle.

Let’s create a src and dist folder: mkdir src && mkdir dist

And add the first TypeScript file: touch src/index.ts.

Let’s add a class to the index.ts so we’re not just compiling empty files:
echo "export class MyMainClass {}" >> src/index.ts

Microbundle looks at the package.json "main" and "source" fields (compiled entry point and source entry point), in our case that’s dist/index.js (which doesn’t exist yet) and src/index.ts.

Let’s edit it to have the following in the package.json:

{
  "main": "dist/index.js",
  "source": "src/index.ts"
}
Enter fullscreen mode Exit fullscreen mode

This means microbundle knows how to compile our library now, run: npx microbundle (on versions of npm <5.x, you can also run ./node_modules/.bin/microbundle).

This will compile your src/index.ts to the dist folder. If you look at the contents of the dist folder, you’ll see how much work microbundle does for you:

$ ls dist
index.d.ts       index.js.map     index.m.js.map   index.umd.js.map
index.js         index.m.js       index.umd.js
Enter fullscreen mode Exit fullscreen mode

Let’s explain what all these are:

  • index.js is the CommonJS module. This is module type used by Node and it looks like const myModule = require('my-module')
  • index.m.js is the ECMAScript Module, as defined in ES6, it looks like import MyModule from 'my-module'
  • index.umd.js is the UMD module
  • index.d.ts is TypeScript type declaration file

Then there’s a matching .map file that maps back to the TypeScript source for each of the files.

Take a look inside index.js:

$ cat dist/index.js
var n=function(){return function(){}}();exports.MyMainClass=n;
//# sourceMappingURL=index.js.map
Enter fullscreen mode Exit fullscreen mode

Our class MyMainClass {} statement was compiled to its ES5 equivalent and the export to a CommonJS export.

index.d.ts is also interesting:

$ cat dist/index.d.ts
export declare class MyMainClass {
}
Enter fullscreen mode Exit fullscreen mode

This allows a TypeScript project to assign the correct type information back to the package… which is a roundabout way of doing since a TypeScript project should be able to just import the .ts file. The separate type declaration means that non-TypeScript projects can also understand the public API of the module (eg. code editors can do smart autocomplete on unseen npm packages).

microbundle can also watch for changes: npx microbundle watch.

For ease of use we can put the watch and build tasks in the package.json as npm scripts:

{
"scripts": {
"dev": "microbundle watch",
"build": "microbundle"
}
}
Enter fullscreen mode Exit fullscreen mode




Publish the module

By leveraging microbundle we can publish the module as a CommonJS module (standard npm module), but also as an ES Module and a UMD module, to do this follow the guide at https://github.com/developit/microbundle#specifying-builds-in-packagejson.

tl;dr

  • "main": "dist/index.js"
  • "umd:main": "dist/index.umd.js"
  • "module": "dist/index.m.js"
  • "source": "src/index.ts"

With this package.json you can publish to npm with npm publish.
And if you enjoyed using microbundle, definitely keep using it but also give it a star on GitHub and let Jason Miller aka @_developit know you’re a fan on Twitter.

Top comments (0)