TypeScript has become a very popular JavaScript language for a reason. Its type system and compiler catch bugs at compile time before your software runs, and the additional code editor capabilities make it a very efficient environment for developers.

But what happens when you want to write a library or package in TypeScript and distribute it in JavaScript so your end users don’t have to compile your code manually? How can we write using modern JavaScript features, such as the ES module, while still getting all the benefits of TypeScript?

This article aims to address all of these issues and provide you with a setup that lets you write and share TypeScript libraries with confidence and provides an easy experience for wrapper users.

Full code for this article: github.com/dunizb/Code…


An introduction to

The first thing we need to do is set up a new program. In this tutorial, we will create a basic math package — not one that serves any practical purpose — because it will allow us to demonstrate all the TypeScript we need without deviating from the actual functionality of the package.

First, create an empty directory and run NPM init-y to create a new project. This will create your package.json and give you an empty project to work with:

$ mkdir maths-package
$ cd maths-package
$ npm init -y
Copy the code

Now we can add our first and most important dependency: TypeScript!

$ npm install --save-dev typescript
Copy the code

After installing TypeScript, you can initialize a TypeScript project by running TSC –init. TSC, short for TypeScript compiler, is TypeScript’s command-line utility.

To make sure you’re running the TypeScript compiler we just installed locally, prefix the command with NPX. NPX is a great tool that will look for the commands you provide in the node_modules folder, so by prefixing the commands we make sure we’re using the local version and not any of the other global versions of TypeScript you might already have installed.

$ npx tsc --init
Copy the code

This will create a tsconfig.json file that is responsible for configuring our TypeScript project. You’ll see that the file has hundreds of options, most of which are commented out (TypeScript supports comments in tsconfig.json files). I have reduced the file to an enable-only setting like this:

{
  "compilerOptions": {
    "target": "es5"."module": "commonjs"."strict": true."esModuleInterop": true."forceConsistentCasingInFileNames": true}}Copy the code

We need to make some changes to this configuration to enable us to distribute packages using the ES module, so let’s look at these options now.

configurationtsconfig.jsonoptions

If you’re looking for a complete list of all possible tsConfig options, you can find this handy reference on the TypeScript web site.

Let’s start with Target, which defines the level of JavaScript support you will provide code with in the browser. If you must use a set of older browsers that may not have all the latest and most powerful features, you can set it to ES2015. TypeScript even supports ES3 if you really need maximum browser coverage.

We’ll use ES2015 for this module here, but you can always change it accordingly. For example, if I build a quick helper project for myself and only care about cutting-edge browsers, I’m happy to set it to ES2020.

Select module system

Next, we must decide on the module system to be used for the project. Note that this is not the module system we’re writing, but the module system TypeScript’s compiler uses when it outputs code.

One thing I like to do when releasing modules is to release two versions:

  • A modern version of the ES module comes with it so that bundling tools can subtly tree-shake unused code, so browsers that support the ES module need only import files
  • Use a version of the CommonJS module (which you’ll be used to if you work in Node)requireCode), so older build tools and Node.js environments can easily run this code

We’ll show you how to bundle two times with different options later, but for now, let’s configure TypeScript to output ES modules. We can do this by setting the Module Settings to ES2020.

Your tsconfig.json file should now look like this:

{
  "compilerOptions": {
    "target": "ES2015"."module": "ES2020"."strict": true."esModuleInterop": true."forceConsistentCasingInFileNames": true}}Copy the code

Write some code

Before we talk about bundling code, we need to write some code! Let’s create two small modules that both export functions and provide a master entry file for modules that export all code.

I like to put all TypeScript code in the SRC directory because it means we can point the TypeScript compiler directly to it, so I’ll create SRC /add.ts using the following code:

export const add = (x: number, y:number) :number= > {
  return x + y;
}
Copy the code

I’ll also create SRC/sum.ts:

export const subtract = (x: number, y:number) :number= > {
  return x - y;
}
Copy the code

Finally, SRC /index.ts will import all of our API methods and export them again:

import { add } from './add.js'
import { subtract } from './subtract.js'
export {
  add,
  subtract
}
Copy the code

This means that users can get our functionality by importing only what they need, or by getting everything.

import { add } from 'maths-package';

import * as MathsPackage from 'maths-package';
Copy the code

Notice that in SRC /index.ts, my import contains the file extension. You don’t need to do this if you just want to support Node.js and build tools (such as Webpack), but you do need file extensions if you want to support browsers that support the ES module.

Compile using TypeScript

Let’s see if we can get TypeScript to compile our code. We need to make some adjustments to the tsconfig.json file before we can do the following:

{
  "compilerOptions": {
    "target": "ES2015"."module": "ES2020"."strict": true."esModuleInterop": true."forceConsistentCasingInFileNames": true."outDir": "./lib",},"include": [
    "./src"]}Copy the code

We made two changes:

  • compilerOptions.outDirThis tells TypeScript to compile our code into a directory. In this case, I’ve told it to name the directorylib, but you can name it as needed.
  • includeTell TypeScript which files we want to include during compilation. In our case, all of our code is locatedsrcDirectory, so I pass it in. That’s why I like to keep all the TS source files in one folder, which makes configuration very easy

Let’s give it a try and see what happens! I’ve found that adjusting my TypeScript configuration works best for me by adjusting, compiling, examining the output, and then adjusting again. Don’t be afraid to experiment with these Settings and see how they affect the end result.

To compile TypeScript, we run TSC and tell it where tsconfig.json is using the -p flag (short for “project”) :

npx tsc -p tsconfig.json
Copy the code

If you have any type of error or configuration problem, it will be shown here. If not, you should see nothing — but be aware that you have a new lib directory with files in it! TypeScript compiles without merging any files together. Instead, it converts each module into the corresponding JavaScript.

Let’s take a look at the three output files:

// lib/add.js
export const add = (x, y) = > {
    return x + y;
};

// lib/subtract.js
export const subtract = (x, y) = > {
    return x - y;
};

// lib/index.js
import { add } from './add.js';
import { subtract } from './subtract.js';
export { add, subtract };
Copy the code

They look very similar to our input, but without the type comments we added. This is to be expected: we write our code in the ES module and tell TypeScript to output it this way as well. If we use any JavaScript features that are newer than ES2015, TypeScript converts them to ES2015 friendly syntax, but in our case, we didn’t use it, so TypeScript largely just keeps everything.

The module can now be published to NPM for use by other users, but we have two problems to solve:

  • We don’t publish any type of information in our code. This isn’t disruptive to our users, but it’s a missed opportunity: people who use typescript-enabled editors or write applications in TypeScript will have a better experience if we also publish our type information.
  • Node does not yet support the ES module out of the box. It’s also good to release CommonJS versions so Node doesn’t need any extra work. ES module support will be available in Node 13 and later, but it will take a while to catch up with the ecosystem.

Release Type Definition

We can solve the type information problem by requiring TypeScript to emit a declaration file when writing code. This file ends with.d.ts, which will contain type information about our code. Think of it as source code, which contains only types, except that it contains no types or implementations.

Let’s add “declaration”: true (in the “compilerOptions” section) to tsconfig.json and run NPX TSC -p tsconfig.json again.

Tip: I want to add a script to my package.json file to compile, so there is no need to type the following:

 "scripts": {
    "tsc": "tsc -p tsconfig.json"
  }
Copy the code

I can then run NPM run TSC to compile my code.

You should now see an equivalent add.d.ts file next to each JavaScript file (such as add.js), as follows:

// lib/add.d.ts
export declare const add: (x: number, y: number) = > number;
Copy the code

As a result, the TypeScript compiler will now be able to select all of these types when users use our modules.

Published to the CommonJS

The final piece of the puzzle is to also configure TypeScript to output versions of code that use CommonJS. To do this, we can make two tsconfig.json files, one for the ES module and one for CommonJS. However, instead of copying everything, we can have the CommonJS configuration extend our default Settings and override modules Settings.

Let’s create tsconfig-cjs.json:

{
  "extends": "./tsconfig.json"."compilerOptions": {
    "module": "CommonJS"."outDir": "./lib/cjs"}},Copy the code

What matters is the first line, which means that this configuration inherits all the Settings of tsconfig.json by default. This is important because you don’t need to synchronize Settings across multiple JSON files.

Then overwrite the Settings that need to be changed. I update the module accordingly and then update the outDir Settings to lib/ CJS so that we can output to subfolders in lib.

At this point, I also updated the TSC script in package.json:

"scripts": {
  "tsc": "tsc -p tsconfig.json && tsc -p tsconfig-cjs.json"
}
Copy the code

Now, when we run NPM run TSC, we will compile twice, and our lib directory will look like this:

This is a bit messy, let’s update the ESM output to lib/ ESM by updating the outDir option in tsconfig

Next, we’ll set the Module property. This is the property that should link to the ES module version of our package. Tools that support this feature will be able to use this version of the package. Therefore, it should be set to./lib/esm/index.js.

Next, we add files Entry to package.json. Here we define all the files that should be included when publishing the module. I like to use this approach to explicitly define the files to be pushed to NPM in the final module.

This allows us to reduce the size of the module. For example, instead of publishing SRC files, we publish lib directories. If you provide a directory in Files Entry, all of its files and subdirectories are included by default, so you don’t have to list them all.

Tip: To see what files will be included in the module, run NPX PKgFiles to get the list.

Now, our package.json contains the following three additional fields:

"main": "./lib/cjs/index.js",
  "module": "./lib/esm/index.js",
  "files": [
    "lib/"
  ],
Copy the code

There is one last step. Since we are publishing the lib directory, we need to make sure it is up to date when we run NPM publish. There is a section in the NPM documentation on how to do this — we can use the prepublishOnly script. When we run NPM publish, the script will automatically run for us:

"scripts": {
  "tsc": "tsc -p tsconfig.json && tsc -p tsconfig-cjs.json",
  "prepublish": "npm run tsc"
},
Copy the code

Note that there is also a script called prePublish, which makes the selection a little confusing. The NPM documentation mentions this: PrePublish is not recommended and prepublishOnly should be used if you only want to run code at publish time.

Thus, running NPM publish will run our TypeScript compiler and publish the module online! I posted the package under @jackfranklin/ms-package-for-blog-post, and while I don’t recommend you use it, you can browse the file and check it out.

The end of the

That’s it! I hope this tutorial has shown you that getting started with TypeScript and running TypeScript isn’t as difficult as it first seems, and that with a few tweaks, you can make TypeScript output in as many formats as you might need without too much hassle.


This article is first published in the wechat public number “front-end foreign language selection”, attention is to send a gift package, you can save a lot of money!