gcanti/fp-ts

Add exports field to package.json in order to support moduleResolution node12/nodenext

viell-dev opened this issue ยท 11 comments

๐Ÿš€ Feature request

Current Behavior

When using "moduleResolution": "NodeNext", in your tsconfig it expects an "exports": ... field in package.json in order to resolve the package.

Otherwise:
Cannot find module 'fp-ts/Either' or its corresponding type declarations.ts(2307)

Desired Behavior

Importing to work.

Suggested Solution

Shove this in package.json:

  "exports": {
    ".": {
      "import": {
        "types": "./es6/index.d.ts",
        "default": "./es6/index.js"
      },
      "require": {
        "types": "./lib/index.d.ts",
        "default": "./lib/index.js"
      }
    },
    "./*": {
      "import": {
        "types": "./es6/*.d.ts",
        "default": "./es6/*.js"
      },
      "require": {
        "types": "./lib/*.d.ts",
        "default": "./lib/*.js"
      }
    },
    "./es6/*": {
      "import": {
        "types": "./es6/*.d.ts",
        "default": "./es6/*.js"
      }
    },
    "./lib/*": {
      "require": {
        "types": "./lib/*.d.ts",
        "default": "./lib/*.js"
      }
    }
  },

Tested it locally by putting that above "main" like in the example as seen in the TS 4.7 link below, seems to do the job.
The "./es6/*" or "./lib/*" parts are only needed for backwards-compatibility with the old import paths.

Who does this impact? Who is this for?

People wanting to use fp-ts with the new ECMAScript Module Support added with TypeScript 4.7.

Describe alternatives you've considered

Not using the new ECMAScript Module support feature. ๐Ÿคทโ€โ™‚๏ธ
Most NPM packages aren't updated to support this yet.

Additional context

TS 4.7 link: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#esm-nodejs
Node.js link: https://nodejs.org/api/packages.html#package-entry-points

Your environment

Software Version(s)
fp-ts 2.12.1
TypeScript 4.7.3

The above suggestion is mostly correct, but there's a caveat with the file extensions. Depending on the the value of the type field in package.json, Node will treat .js files either as CJS or ESM, while it will always treat .cjs file as the former and .mjs files as the latter.

To make Node happy we have to either:

  1. Rename ./es6/index.js to ./es6/index.mjs and ./es6/*.js to ./es6/*.mjs
  2. Add type: module and rename ./lib/index.js to ./lib/index.cjs and ./lib/*.js to ./lib/*.cjs
  3. Add a package.json inside ./es6 with type: module (and for good measure do the equivalent in ./lib)

There's also the option of dropping CJS completely, adding type: module in package.json, and living a happier life.

I don't think the suggestion by @andreavaccari is currently applicable, since fp-ts isn't building ECMAScript Modules.

My suggestion would allow a user using ESM to load the ES6 version of fp-ts without issue. fp-ts does not need to use ESM itself to be compatible.

Though, to be fair, I don't think there is much of a difference between ES6 and ESM files. I believe it's just the internal import statements requiring extensions. And the package.json stuff with type: module. But I feel that's another issue entirely.

Edit:
See https://github.com/viell-dev/fp-ts for a test fork.
Only change there is the addition of the exports section in package.json.

Updated my test fork to 2.12.3. (viell-dev/fp-ts)

Also I saw that #1525 also talks about exports stuff.
Not sure what that means for 2.x ESM support.
Is it planned for 3.x and thus not a concern for 2.x?

@viell-dev in v3 I'm using

{
  "exports": {
    ".": {
      "require": "./lib/index.js",
      "import": "./es6/index.js"
    },
    "./*": {
      "require": "./lib/*.js",
      "import": "./es6/*.js"
    }
  },
  "types": "index.d.ts",
  "typesVersions": {
    "*": {
      "*": [
        "./types/*"
      ]
    }
  },
  "sideEffects": false
}

So for backwards-compatibility in v2 would be

{
  "exports": {
    ".": {
      "require": "./lib/index.js",
      "import": "./es6/index.js"
    },
    "./*": {
      "require": "./lib/*.js",
      "import": "./es6/*.js"
    },
    "./es6/*": {
      "import": "./es6/*.js"
    },
    "./lib/*": {
      "require": "./lib/*.js"
    }
  },
  "types": "index.d.ts",
  "typesVersions": {
    "*": {
      "*": [
        "./types/*"
      ]
    }
  },
  "sideEffects": false
}

Hi all, I've just released https://github.com/gcanti/fp-ts/releases/tag/2.13.0-rc.2 (install with npm i fp-ts@rc), could you please try it out and let me know if everything is ok?

@gcanti I tried it and I'm getting: "Could not find a declaration file for module 'fp-ts/Option'."

After testing locally I've concluded that you will need to have the types sections under exports as well.

e.g.

{
  "exports": {
    ".": {
      "require": "./lib/index.js",
      "import": "./es6/index.js",
      "types": "./types/index.d.ts"
    },
    "./*": {
      "require": "./lib/*.js",
      "import": "./es6/*.js",
      "types": "./types/*.d.ts"
    },
    "./es6/*": {
      "import": "./es6/*.js",
      "types": "./types/*.d.ts"
    },
    "./lib/*": {
      "require": "./lib/*.js",
      "types": "./types/*.d.ts"
    }
  },
}

Ok, thank you, going to release 2.13.0-rc.3 with the fix asap

going to release 2.13.0-rc.3 with the fix asap

done

Looks great. ๐Ÿ‘๐Ÿป

Nice, thank you for your help @viell-dev

I saw that the changes got reverted in 2.13.1 due to #1799 and similar issues. So I'm using a fork for my use-cases since I have need of fp-ts in projects where the types don't work without the exports field.