multiformats / js-multiformats

Multiformats interface (multihash, multicodec, multibase and CID)
Other
228 stars 52 forks source link

Support esm/typescript with "NodeNext" module resolution #229

Closed honungsburk closed 7 months ago

honungsburk commented 1 year ago

I have a project using the following tsconfig.js file

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "NodeNext",
    "lib": ["ESNext"],
    "noImplicitReturns": true,
    "noUnusedLocals": false,
    "outDir": "dist",
    "sourceMap": true,
    "strict": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
  },
  "compileOnSave": true,
  "include": [
    "./src",
    "bin",
  ],
  "exclude": ["node_modules"],
  "ts-node": {
    "transpileOnly": true
  }
}

I can not import this library because typescript can not find the d.ts file.

Example that doesn't work:

import multiformats from "multiformats";

This can be fixed by adding explicit types in the package.json file:

  "exports": {
    ".": {
      "types": "./types/src/index.d.ts",
      "browser": "./esm/src/index.js",
      "require": "./cjs/src/index.js",
      "import": "./esm/src/index.js"
    },
    "./cid": {
      "types": "./types/src/cid.d.ts",
      "browser": "./esm/src/cid.js",
      "require": "./cjs/src/cid.js",
      "import": "./esm/src/cid.js"
    },
    "./basics": {
      "types": "./types/src/basics.d.ts",
      "browser": "./esm/src/basics.js",
      "require": "./cjs/src/basics.js",
      "import": "./esm/src/basics.js"
    },
    "./block": {
      "types": "./types/src/block.d.ts",
      "browser": "./esm/src/block.js",
      "require": "./cjs/src/block.js",
      "import": "./esm/src/block.js"
    },
    "./traversal": {
      "types": "./types/src/traversal.d.ts",
      "browser": "./esm/src/traversal.js",
      "require": "./cjs/src/traversal.js",
      "import": "./esm/src/traversal.js"
    },
    "./bases/identity": {
      "types": "./types/src/bases/identity.d.ts",
      "browser": "./esm/src/bases/identity.js",
      "require": "./cjs/src/bases/identity.js",
      "import": "./esm/src/bases/identity.js"
    },
    "./bases/base2": {
      "types": "./types/src/bases/base2.d.ts",
      "browser": "./esm/src/bases/base2.js",
      "require": "./cjs/src/bases/base2.js",
      "import": "./esm/src/bases/base2.js"
    },
    "./bases/base8": {
      "types": "./types/src/bases/base8.d.ts",
      "browser": "./esm/src/bases/base8.js",
      "require": "./cjs/src/bases/base8.js",
      "import": "./esm/src/bases/base8.js"
    },
    "./bases/base10": {
      "types": "./types/src/bases/base10.d.ts",
      "browser": "./esm/src/bases/base10.js",
      "require": "./cjs/src/bases/base10.js",
      "import": "./esm/src/bases/base10.js"
    },
    "./bases/base16": {
      "types": "./types/src/bases/base16.d.ts",
      "browser": "./esm/src/bases/base16.js",
      "require": "./cjs/src/bases/base16.js",
      "import": "./esm/src/bases/base16.js"
    },
    "./bases/base32": {
      "types": "./types/src/bases/base32.d.ts",
      "browser": "./esm/src/bases/base32.js",
      "require": "./cjs/src/bases/base32.js",
      "import": "./esm/src/bases/base32.js"
    },
    "./bases/base36": {
      "types": "./types/src/bases/base36.d.ts",
      "browser": "./esm/src/bases/base36.js",
      "require": "./cjs/src/bases/base36.js",
      "import": "./esm/src/bases/base36.js"
    },
    "./bases/base58": {
      "types": "./types/src/bases/base58.d.ts",
      "browser": "./esm/src/bases/base58.js",
      "require": "./cjs/src/bases/base58.js",
      "import": "./esm/src/bases/base58.js"
    },
    "./bases/base64": {
      "types": "./types/src/bases/base64.d.ts",
      "browser": "./esm/src/bases/base64.js",
      "require": "./cjs/src/bases/base64.js",
      "import": "./esm/src/bases/base64.js"
    },
    "./bases/base256emoji": {
      "types": "./types/src/bases/base256emoji.d.ts",
      "browser": "./esm/src/bases/base256emoji.js",
      "require": "./cjs/src/bases/base256emoji.js",
      "import": "./esm/src/bases/base256emoji.js"
    },
    "./hashes/hasher": {
      "types": "./types/src/hashes/hasher.d.ts",
      "browser": "./esm/src/hashes/hasher.js",
      "require": "./cjs/src/hashes/hasher.js",
      "import": "./esm/src/hashes/hasher.js"
    },
    "./hashes/digest": {
      "types": "./types/src/hashes/digest.d.ts",
      "browser": "./esm/src/hashes/digest.js",
      "require": "./cjs/src/hashes/digest.js",
      "import": "./esm/src/hashes/digest.js"
    },
    "./hashes/sha2": {
      "types": "./types/src/hashes/sha2.d.ts",
      "browser": "./esm/src/hashes/sha2-browser.js",
      "require": "./cjs/src/hashes/sha2.js",
      "import": "./esm/src/hashes/sha2.js"
    },
    "./hashes/identity": {
      "types": "./types/src/hashes/identity.d.ts",
      "browser": "./esm/src/hashes/identity.js",
      "require": "./cjs/src/hashes/identity.js",
      "import": "./esm/src/hashes/identity.js"
    },
    "./codecs/json": {
      "types": "./types/src/codecs/json.d.ts",
      "browser": "./esm/src/codecs/json.js",
      "require": "./cjs/src/codecs/json.js",
      "import": "./esm/src/codecs/json.js"
    },
    "./codecs/raw": {
      "types": "./types/src/codecs/raw.d.ts",
      "browser": "./esm/src/codecs/raw.js",
      "require": "./cjs/src/codecs/raw.js",
      "import": "./esm/src/codecs/raw.js"
    }
  },
rvagg commented 1 year ago

I'm pretty sure we've been publishing with "types" for a long time, and it's persisted even after the bit v10 upgrade, e.g.: https://unpkg.com/browse/multiformats@11.0.0/package.json

What version are you using? Can you try using a v10 or v11?

rvagg commented 1 year ago

I'd like to close this issue out if we don't get further feedback. If this is still broken in the latest multiformats could someone please post details of their stack and some error messages that demonstrate the failure. it could very well be a specific compile toolchain that's not picking up the exports properly.

pablomendezroyo commented 1 year ago

Hey! having a similar issue. Bundling my app with webpack The error:

Error: Cannot find module 'multiformats/bases/base58'
Require stack:
- /usr/src/app/index.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1039:15)
    at Module._load (node:internal/modules/cjs/loader:885:27)
    at Module.require (node:internal/modules/cjs/loader:1105:19)
    at require (node:internal/modules/cjs/helpers:103:18)
    at 13558 (/usr/src/app/index.js:278996:32)
    at __webpack_require__ (/usr/src/app/index.js:363577:42)
    at /usr/src/app/index.js:363681:37
    at Object.<anonymous> (/usr/src/app/index.js:363683:12)
    at Module._compile (node:internal/modules/cjs/loader:1218:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1272:10) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/usr/src/app/index.js' ]
}
Node.js v19.2.0
rvagg commented 1 year ago

require - we've removed CJS compatibility from multiformats, you'll have to consume it as an ESM module from now on, Webpack should be able to handle it but you'll have to tell it to compile ESM

pablomendezroyo commented 1 year ago

Im not using CJS in my setup, it is a module and my webpack config supports ESM. The only library that is failing is js-multiformats

rvagg commented 1 year ago

@achingbrain we're probably going to need your expertise on this one - any thoughts on where to look for the problem here? Are you seeing this kind of thing reported elsewhere where we've gone ESM-only?

achingbrain commented 1 year ago

@honungsburk

import multiformats from "multiformats";

This won't work because there's no default export in src/index.js, it needs to be:

import * as multiformats from "multiformats";

Or better yet, just import what you need:

import { base64urlpad } from "multiformats/bases/base64";

I can't replicate your problem - here's a gist of what I tried based on the file contents shared in your original post https://gist.github.com/achingbrain/dce4b1242406d295fed05399d9d85484

The only thing I changed was the import style as mentioned above and adding "Dom" to the "lib" field in tsconfig.json to allow use of console.

I think the problem is somewhere else - your webpack config maybe?


@pablomendezroyo can you share a link to your project or a small repo with a reproduction?

From the stack trace it's going through the node cjs loader which as @rvagg points out means something is being required somewhere.

This normally means you're compiling something to CJS, even though you may be authoring in what looks like ESM or TypeScript but it's impossible to tell without more information.

BigLep commented 1 year ago

Closing since haven't heard back in a couple of weeks.

pablomendezroyo commented 1 year ago

Sorry for the delay in the response.

Bundling webpack with:

    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.1"

My webpack config

import path from "path";
import webpack from "webpack";
import { fileURLToPath } from "url";

const { NODE_ENV = "production" } = process.env;

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const paths = {
  // Source files
  src: path.resolve(__dirname, "./src"),
  // Production build files
  build: path.resolve(__dirname, "./build")
};

export default {
  entry: paths.src + "/index.ts",
  mode: NODE_ENV,
  output: {
    path: paths.build,
    filename: "index.js",
    publicPath: ""
  },
  // externals: [/node_modules/, "bufferutil", "utf-8-validate"],
  externalsPresets: { node: true },
  target: "node19.2",
  // externals: [nodeExternals()],
  resolve: {
    extensions: [".ts", ".js"],
    extensionAlias: {
      ".js": [".ts", ".js"],
      ".cjs": [".cts", ".cjs"],
      ".mjs": [".mts", ".mjs"]
    },
    fallback: {
      electron: false
    }
  },
  stats: {
    errors: true,
    errorDetails: true
  },
  module: {
    rules: [
      {
        test: /\.m?js/
      },
      {
        test: /\.([cm]?ts|tsx)$/,
        loader: "ts-loader",
        exclude: [/node_modules/]
      },
      {
        test: /\.node$/,
        loader: "node-loader"
      }
    ]
  },
  plugins: [
    new webpack.ProvidePlugin({
      WebSocket: "ws",
      fetch: ["node-fetch", "default"]
    })
  ],
  optimization: {
    // Minimization does not provide great disk space savings, but reduces debug capacity
    minimize: false
  },
  devtool: "source-map"
};

Bundle result

webpack 5.75.0 compiled with 42 errors and 15 warnings in 38633 ms

Most of the errors look like (all of them from multiformats library)

ERROR in ./node_modules/multiformats/src/link/interface.ts 3:12
Module parse failed: Unexpected token (3:12)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| /* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
| /* eslint-disable no-use-before-define */
> import type { MultihashDigest } from '../hashes/interface.js'
| import type { MultibaseEncoder, MultibaseDecoder, Multibase } from '../bases/interface.js'
| import type { Phantom, ByteView } from '../block/interface.js'
 @ ./node_modules/multiformats/src/cid.js 8:0-42 11:0-35 11:0-35
 @ ./src/api/middlewares/ethForward/utils/decodeContentHash.ts 1:0-39 16:20-30 24:20-30
 @ ./src/api/middlewares/ethForward/utils/index.ts 2:0-39 2:0-39
 @ ./src/api/middlewares/ethForward/resolveDomain.ts 6:0-92 74:13-20 75:19-36 80:13-20 81:19-32 86:13-20 87:19-32
 @ ./src/api/middlewares/ethForward/index.ts 3:0-60 6:26-48
 @ ./src/api/middlewares/index.ts 3:0-64 3:0-64
 @ ./src/index.ts 23:0-108 32:23-33 33:28-53 34:26-49
achingbrain commented 7 months ago

This module has been refactored as TypeScript and works for me locally with the NodeNext module resolution strategy - please re-open if you're still having trouble.