How to publish a Typescript library on NPM?

Asked

Viewed 623 times

12

I set up the package.json with the properties main and types:

{
    "name": "idb2promise",
    "version": "0.0.5",
    "description": "TypeScript library to manage IndexedDB Storage",
    "main": "./dist/index.js",
    "types": "./dist/index.d.ts",
    "files": [ "./dist" ],
    "scripts": { [...] },
    "keywords": [ [...] ],
    "author": "Guilherme Costamilam",
    "license": "MIT",
    "repository": {
        "type": "git",
        "url": "git+https://github.com/Costamilam/IDB2Promise.git"
    },
    "bugs": {
        "url": "https://github.com/Costamilam/IDB2Promise/issues"
    },
    "homepage": "https://costamilam.github.io/IDB2Promise/",  
    "dependencies": { },
    "devDependencies": { [...] }
}

I set up the tsconfig.json with the property declaration as true:

{
    "compileOnSave": false,
    "compilerOptions": {
        "module": "commonjs",
        "moduleResolution": "node",
        "removeComments": true,
        "sourceMap": true,
        "target": "es5",
        "declaration": true,
        "declarationDir": "./dist",
        "outDir": "./dist"
    },
    "lib": [ "es2015.promise"],
    "include": [ "src/**/*.ts" ],
    "exclude": [ "src/test.ts" ]
}

In the .npmignore I put the Typescript code, the test and configuration files

src
coverage
test
karma.conf.js
rollup.config.js
tsconfig.json
test.tsconfig.json

And in the /src/index.ts export public parts

export * from './object-store';
export * from './intefaces/request';
export * from './intefaces/request-event';
export * from './intefaces/async-cursor';

I build it with tsc -b and public with command npm publish

The structure of the folder dist:

|- dist
    |- <file>.js/.map.js/.d.ts
    |- util
        |- <file>.js/.map.js/.d.ts
    |- interfaces
        |- <file>.js/.map.js/.d.ts

The /dist/index.d.ts is equal to /src/index.ts above

The archive /dist/index.js generated:

"use strict";
function __export(m) {
    for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(require("./object-store"));
//# sourceMappingURL=index.js.map

In another project, I install as dependency:

npm i idb2promise

When trying to import components

import { ObjectStore } from 'idb2promise';

I get the error

Cannot find module 'idb2promise'. ts(2307)

However, if I care using the relative path to the index, works as expected

import { ObjectStore } from './node_modules/idb2promise/dist/index';

How to import what is exported by index only by package name instead of relative path?

  • Take a look: https://cameronnokes.com/blog/the-30-second-guide-to-publishing-a-typescript-package-to-npm/

  • Is this your package? https://www.npmjs.com/package/idb2promise

  • @Marconi thanks for the link, I had already seen this, but I will relook. Yes that’s the same

  • 1

    @Marconi reviewed how it was done on the link and the repository on Github, but apparently I do the same thing, I don’t know why it’s not working. I didn’t know the npm link, will be of great help to test

2 answers

6


The error you are having when importing the module by Typescript:

Cannot find module 'idb2promise'. ts(2307)

It is noted that Typescript is trying to solve the module using the new module specification of Ecmascript 2015 (alias ES6), and its module, idb2promise does not export, in the package.json, a module that fits this new specification in the field module, exporting only the Commonjs module, in the field main.

In short, you will need to generate two "main" files that will import the rest of your library: one for Commonjs and one for the new specification.

The compiler of Typescript, tsc, However, there is no easy way to do this kind of thing. I went through a similar problem while migrating to Typescript from a simple library I made, yiq.

Basically, you must have three file types defined in your package.json:

  • main: The module that will be required by client that is using Commonjs, as in a Node.js environment;
  • module: The module that will be imported by client that is using the new specification of ES6 modules[EN];
  • types (or typings): Will be used by Typescript.

So, to make your life easier, instead of using the standard Typescript compiler, tsc, I suggest you use bundlers like the rollup.js, and use a plugin like rollup-plugin-typescript2 for integration with Typescript. I will leave below two options - the first that will use Rollup and the second, the tsc.

Compile using the Rollup

It is without doubt the easiest option and the one that I will prioritize most, since the bundler will do all the "hard work" for you. Just install the rollup and rollup-plugin-typescript2 and create a configuration file rollup.config.js:

import typescript from 'rollup-plugin-typescript2';
import pkg from './package.json';

export default {
  // O `entry` do seu pacote:
  input: 'src/idb2promise.ts',

  plugins: [
    typescript({
      rollupCommonJSResolveHack: true,
      clean: true
    })
  ],
  output: [
    { file: 'build/idb2promise.cjs.js', format: 'cjs' },
    { file: 'build/idb2promise.js', format: 'es' }

    // Note que o arquivo de declaração do TypeScript terá o nome
    // baseado no `input`. Nesse caso, será `idb2promise.d.ts`.
  ]
};

Note that we have set two output files in output. One of them will be the Commonjs module, which we will export in the main of package.json; and another that will be the ES6 specification module, which will be exported in the field main. The declaration will finally have the name based on input. In the example above, we will have idb2promise.d.ts, which will also be placed in the directory build.

Then we’ll have something like this on package.json:

{
  [...]

  "main": "build/lisdir-cjs.js",
  "module": "build/lisdir.mjs",
  "types": "build/lisdir.d.ts",
  "scripts": {
    "build": "rollup -c"
  }
  
  [...]
}

An example of this approach can be found here.

Compile using the tsc

Just create two Typescript files that will be used to export your library. For example, src/idb2promise.cjs.ts and src/idb2promise.ts. In the

To export the version that will support the new module specification of Ecmascript 2015, we will create the file src/idb2promise.ts:

//# src/idb2promise.ts

import { ib2promise } from './lib/idb2promise';

export default ib2promise;

export * from './lib/other-stuff';

And finally, to export a Commonjs module, we will create the file src/idb2promise.cjs.ts:

//# src/idb2promise.cjs.ts

import { idb2promise } from './lib/color-yiq';
import { otherStuff1, otherStuff2 } from './lib/other-stuff';

exports.otherStuff1 = otherStuff1;
exports.otherStuff2 = otherStuff2;

// Export "default":
export = colorYiq;

Note above that we use the statement export = to export the module using Commonjs. As per is included in the Typescript documentation, in English:

Typescript Supports export = to model the Traditional Commonjs and AMD workflow. The export = syntax specifies a single Object that is Exported from the module. This can be a class, interface, namespace, Function, or Enum.

And finally, just include in the package.json the three "export" files we created - ES6 compatible, Commonjs compliant and Typescript generated definitions.

An example of this approach can be found here.


P.S.: This answer is an extended version of a Stackoverflow Response in English. Although it once helped me, nowadays I consider Rollup a simpler option than using the tsc and the declaration export =. :)

  • Dude, you don’t need to publish with rollup, I’ve already published lib like this and no problem, it’s another problem there... if the library only has a "main" in package.json, it will import as commonjs. , which indicates that the library wants to be consumed as es-modules is the use of the "module" flag in package.json

  • 1

    At no time did I say I needed to use Rollup. It was just a recommendation. I’ve already published 3 libraries using Typescript, and so far using Rullup was the best way I could find. :)

2

import * as idb from 'idb2promise'

That worked.

Probably place esModuleInterop: true in your tsconfig solve the problem. Then you can also import via import idb from 'idb2promise' (import defailt syntax).

History

Basically, the module.exports of the commonjs and the export default es-modules are different things. The export default is literally an export named "default" name. Canonically, you should not be able to import commonjs with the default import syntax.

One day Abel’s people decided to do a little trick to use import nome from 'lib' also works with export global commonjs (the module.exports =). This little trick, on TS is activated with the esModuleInterop.

About how the webpack works:

  • It looks for the "module" key in package.json to import a lib as es-modules. es-modules allows "Tree-Shaking";
  • If not found, it looks for the key "main", in this case the lib will be interpreted as "commonjs";

If you want to publish es-module, the most correct would be to publish both. I recommend you try the TSDX for this, which is a ready rollup configuration.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.