npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

tsconfig-to-dual-package

v1.2.0

Published

A simple tool that add package.json({"type":"commonjs"/"module"}) to TypeScript outDir for dual package.

Downloads

15,719

Readme

tsconfig-to-dual-package

A Node.js dual package tool for TypeScript.

You can support CommonJS and ESModules in one package via this tool. This tool add package.json which is { "type": "module" } or { "type": "commonjs" } based on tsconfig's module and outDir option.

You can use this tool with tsc command.

$ tsc -p . && tsc -p ./tsconfig.cjs.json && tsconfig-to-dual-package # add "{ourDir}/package.json"

Install

Install with npm:

npm install tsconfig-to-dual-package --save-dev

Requirements: This tool depended on typescript package for parsing tsconfig.json file. It means that You need to install typescript as devDependencies in your project.

  • PeerDependency:
    • typescript: * (any version)
  • Node.js v16.17.0+

Usage

Usage
  $ tsconfig-to-dual-package [Option] <tsconfig.json>

Options
  --cwd                 [String] current working directory. Default: process.cwd()
  --debug               [Boolean] Enable debug output
  --help                [Boolean] show help

Examples
  # Find tsconfig*.json in cwd and convert to dual package
  $ tsconfig-to-dual-package
  # Convert specified tsconfig.json to dual package
  $ tsconfig-to-dual-package ./config/tsconfig.esm.json ./config/tsconfig.cjs.json

How it works

This tool adds package.json to tsconfig's outDir for dual package. Each generated package.json has type field that is commonjs or module.

You can see example repository in following:

For example, This project package.json is following:

{
  "name": "my-package",
  "version": "1.0.0",
  "type": "module",
  "main": "./lib/index.js",
  "types": "./lib/index.d.ts",
  "module": "./module/index.js",
  // Note: Normally same .js extension can not be used as dual package
  //      but this tool add custom `package.json` to each outDir(=lib/, module/) and resolve it.
  "exports": {
    ".": {
      "import": {
        "types": "./module/index.d.ts",
        "default": "./module/index.js"
      },
      "require": {
        "types": "./lib/index.d.ts",
        "default": "./lib/index.js"
      },
      "default": "./lib/index.js"
    }
  }
}

And, This project has tsconfig.json and tsconfig.cjs.json:

tsconfig.json: for ES Module

{
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "newLine": "LF",
    "outDir": "./module/",  // <= Output ESM to `module` directory
    "target": "ES2018",
    "strict": true,
  },
  "include": [
    "**/*"
  ]
}

tsconfig.cjs.json: for CommonJS

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "CommonJS",
    "moduleResolution": "Node",
    "outDir": "./cjs/" // <= Output CommonJS to `cjs` directory
  },
  "include": [
    "**/*"
  ]
}

Then, You can run tsconfig-to-dual-package after you compile both CommonJS and ES Module with following command:

{
  "scripts": {
    "build": "tsc -p ./tsconfig.json && tsc -p ./tsconfig.cjs.json && tsconfig-to-dual-package",
  }
}

tsconfig-to-dual-package command adds package.json to module and cjs directory.

As a result, you can publish both CommonJS and ESModule in a single package. It is called dual package.

- package.json          // { "type": "module" }
- index.ts              // Node.js treat this as ESModule
- tsconfig.json         // output to `module` directory
- tsconfig.cjs.json     // output to `cjs` directory
- cjs/
    - package.json      // { "type": "commonjs" }
    - index.js          // Node.js treat it as CommonJS module
- module/
    - package.json      // { "type": "module" }
    - index.js          // Node.js treat it as ESModule

For more details, please see Dual CommonJS/ES module packages in Node.js official document.

Limitation

This tool copy almost fields from package.json to generated {outDir}/package.json. However, it does not copy main, module, exports, types fields because it points invalid file path. It defined in OMIT_FIELDS constant.

Used by

Motivation

As a result, TypeScript and Node.js ESM support is conflicting. It is hard that you can support dual package with same .js extension.

Of course, you can use tsc-multi or Packemon to support dual packages. However, These are build tools. I want to use TypeScript compiler(tsc) directly.

tsconfig-to-dual-package do not touch TypeScript compiler(tsc) process. It just put package.json({ "type": "module" } or "{ "type": "commonjs" }) to outDir for each tsconfig.json after tsc compile source codes.

@aduh95 describe the mechanism in https://github.com/nodejs/node/issues/34515#issuecomment-664209714

For reference, the library-package/package.json contains:

{
	"name": "library-package",
	"version": "1.0.0",
	"main": "./index-cjs.js",
	"exports": {
		"import": "./index-esm.js",
		"require": "./index-cjs.js"
	},
	"type": "module"
}

Setting "type": "module" makes Node.js interpret all .js files as ESM, including index-cjs.js. When you remove it, all .js files will be interpreted as CJS, including index-esm.js. If you want to support both with .js extension, you should create two subfolders:

$ mkdir ./cjs ./esm
$ echo '{"type":"commonjs"}' > cjs/package.json
$ echo '{"type":"module"}' > esm/package.json
$ git mv index-cjs.js cjs/index.js
$ git mv index-esm.js esm/index.js

And then have your package exports point to those subfolders:

{
	"name": "library-package",
	"version": "1.0.0",
	"main": "./cjs/index.js",
	"exports": {
		"import": "./esm/index.js",
		"require": "./cjs/index.js"
	},
	"type": "module"
}

Also, Node.js documentation describe this behavior as follows

The nearest parent package.json is defined as the first package.json found when searching in the current folder, that folder's parent, and so on up until a node_modules folder or the volume root is reached.

// package.json
{
  "type": "module"
}
# In same folder as preceding package.json
node my-app.js # Runs as ES module

If the nearest parent package.json lacks a "type" field, or contains "type": "commonjs", .js files are treated as CommonJS. If the volume root is reached and no package.json is found, .js files are treated as CommonJS.

-- https://nodejs.org/api/packages.html#type

Pros and Cons

Pros

  • You can use TypeScript compiler(tsc) directly
    • No additional bundler, transpiler, build tool

Cons

FAQ

What should I do support dual package?

  • Example repository: tsconfig-to-dual-package-example
  • Pull Request: feat: support dual package by azu · Pull Request #2 · azu/tsconfig-to-dual-package-example
  • Steps:
    • Install tsconfig-to-dual-package: npm install --save-dev tsconfig-to-dual-package
    • Add "type": "module" to package.json via npm pkg set type=module
    • Add tsconfig.json and tsconfig.cjs.json
    • Create tsconfig.json and set it to use module: "esnext"
    • Create tsconfig.cjs.json and set it to use module: "commonjs"
    • Add tsconfig-to-dual-package to build script
      • "build": "tsc -p ./tsconfig.json && tsc -p ./tsconfig.cjs.json && tsconfig-to-dual-package"
    • Add "main"/"types"(for backward compatibility)/"files"/"exports" fields to package.json
      • "files": ["lib/", "module/"] (lib/ = cjs, module/ = esm)
      • "main"/"types"/"exports"
      {
        "main": "./lib/index.js",
        "types": "./lib/index.d.ts",
        "exports": {
          ".": {
            "import": {
              "types": "./module/index.d.ts",
              "default": "./module/index.js"
            },
            "require": {
              "types": "./lib/index.d.ts",
              "default": "./lib/index.js"
            },
            "default": "./module/index.js"
          },
          "./package.json": "./package.json"
        }
      }
    • Check Check Check
      • Lint
      • Test
        • use ts-node/esm instead of ts-node for testing
        • https://github.com/TypeStrong/ts-node#node-flags-and-other-tools
    • Publish!
      • npm publish
    • After Check!
      • publint
      • Load test via require/import

Is there a migration script?

It is not for everyone, but I wrote a migration script for TypeScript project.

Example Result:

Should I migrate to dual package?

  • If your package is a library, you should migrate to dual package if possible
    • Because dual package reduce interop issues between CJS and ESM
    • If your package is just logics, you can move to dual package
  • If your package is a Command Line Tool(CLI), you not need to migrate to dual package

References

Related

Changelog

See Releases page.

Running tests

Install devDependencies and Run npm test:

npm test

Contributing

Pull requests and stars are always welcome.

For bugs and feature requests, please create an issue.

  1. Fork it!
  2. Create your feature branch: git checkout -b my-new-feature
  3. Commit your changes: git commit -am 'Add some feature'
  4. Push to the branch: git push origin my-new-feature
  5. Submit a pull request :D

Author

License

MIT © azu