Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I tell if a particular module is a CommonJS module or an ES6 module?

I have an Azure Function (which uses NodeJS) from which I'm trying to consume an npm module published by another team. After npm adding the module in question, I try to use it like this:

import * as Model from "@thing/app-model";
...
const appModel: Model.TheModel = new Model.TheModel([]);

However, when I try to run the Azure Function, I get this error:

[error] Worker was unable to load function pump: 'SyntaxError: Unexpected token export'

The results related to that error message that I could find online say that this error usually indicates I'm trying to consume an ES6 module, something that Node doesn't support (instead I should be consuming a CommonJS module). The challenge is that I thought the module WAS a CommonJS module. The tsconfig.json for the project that is producing the module has this line in the compilerOptions:

"module": "commonjs"

In order to verify or rule out the ES6 vs CommonJS problem, if I look at the imported module in my Azure Function's node_modules directory, is there a way to tell by looking at it if it's an ES6 module or a CommonJS module?

Thanks!

like image 871
Auth Infant Avatar asked Jul 09 '19 21:07

Auth Infant


2 Answers

Find the npm package in your node_modules folder the open the package.json.

The package may support CommonJs and/or ES6, so you may have combination.

You should see something like this:

  "main": "dist/index.js",
  "module": "dist/my-module.esm.js",

The main usually points to the CommonJs version and module points to the ESM version.

This is a general case and isn't always true. Sometimes the file ended can show you if it supports both, e.g. index.cjs.js, index.esm.js

like image 95
peter.swallow Avatar answered Nov 15 '22 00:11

peter.swallow



Modules in Node are very different than they were only a Month ago, and even more so than 18-20 months ago. As of JUNE 2022 this is the most current means for inspecting a TS-Node package, and inferring the module-type of the package.

The inspected package can be, cjs, esm or both (cjs & esm)

Its now possible to use TypeScript to build a CJS module, and an ESM module from one TS-Node code-base (or project if you will). In-fact, if your going to maintain both module types, its almost a necessity to have a transpiler (unfortunately I can't go further into detail, as such a topic extends past the scope of the original question).


It Should be Noted: Knowing how to infer a module type from a TS-Node package, is close to the same as knowing how to configure TS-Node packages as different mod0ule-types.


The Module-type of a TS-Node Package can be Inferred from 2 Files

  1. The package.json
  2. The tsconfig.json
In the package.json File

    ...the "type" property is where the module type is defined. Its important to note, that this is where Node.js will also infer what the modules type is, and how it should be expected to resolve modules with-in the project. TypeScript isn't present during Runtime, where Node.js is the Runtime Environment. Because Node.js is the RTE, its a bit more concrete to look at the package.json, and infer the module type from it, however, the story is far from over. JavaScript Modules make for a complex topic now.

Before I move on, lets look at a package.json for an ESM configuration.

ESM Configured package.json
// FILE: "./package.json"

  {
    "name": "foo-pkg-bar"
    "version": "1.23.4",
    "type": "module", // <-- You're Looking for this K/V pair
    "license": "MIT",
    "desc": "some description here...", 
    /* 
        ...rest of the JSON-file's properties... 
    */
  }

When the module is an ES-Module or (ESM), it will have its package.json property set to "module".

In a CommonJS Module (or CJS Module) the configuration isn't required to be explicit. This means that you can have the "type" property completely left out of the package.json file, and in that very common situation the module-type will default to CJS. The "type" property can be configured to implicitly declare the module-type as CJS though, which in that case the "type" property will look like this:

{
   name: "foobar-foofoo-head",
   version: 1.23.4,
   type: "commonjs"
}

Multiple Package.json Files

Often times there can be, and in dual-module-typed packages (packages that are built to support both ESM & CJS environments), there is going to be, more than a single package.json file. In a dual-module-typed package, the ESM build will have a package.json file that has the single property added to it as shown bellow.

// package.json for the ESM build
{
  "type": "module"
}

CJS will also have its own type package.json file, and it will also have a single property added to it (see below).

// package.json for the CJS build
{
  "type": "commonjs"
}

The rest of the package.json file will be in the ROOT-package.json file. In such a situation the file-structure will look somthing like this.


├── build
│   ├── cjs
│   │   ├── lib
│   │   │   ├── project-stuff.d.ts
│   │   │   └── project-stuff.js
│   │   ├── main.cjs
│   │   ├── main.d.cts
│   │   └── package.json        // <-- CJS "package.json"
│   └── esm
│       ├── lib
│       │   ├── project-stuff.d.ts
│       │   └── project-stuff.js
│       ├── main.d.mts
│       ├── main.mjs
│       └── package.json        // <-- ESM "package.json"
├── package.json                // <-- ROOT "package.json"
├── package-lock.json
├── src
│   ├── lib
│   │   └── project-stuff.ts
│   ├── main.cts
│   └── main.mts
├── tsconfig.base.json
├── tsconfig.cjs.json
└── tsconfig.esm.json


The above is actually from the project I built to teach myself how to trans-pile to both module types.

You probably noticed the many tsconfig.json files?

Honestly, if you see tsconfig files with that naming convention, you don't need to inspect any further, it definitely can be used as either a CJS, or an ESM module (assuming its a working package).

Inspecting the TSConfig for the Package's Module-type

The TSConfig can also be used to infer which module-type any given TS/Node project is. Open the TSConfig, and look for the Module & Module resolution properties.

TS v4.7, eased support for ESM in Node/TS projects

The support for ESM in Node packages that are trans-piled using TypeScript was eased by the addition of valid values that can be passed to the tsconfig.json files configuration properties "module" and "moduleResolution"

"module" & module resolution can now accept the new values
  1. node12 (no top level await),
  2. node16 (ESM standard implemented for the LTS node16 version) &
  3. nodenext(most current ESM standard)

If you see the module & moduleResolution properties set to these values then the project is being transpiled as an ES-Module.


CJS & ESM

Modules can be both, CJS & ESM. If a package is being trans piled into a CJS build & ESM build, you should see two separate directories for the builds.

There should be separate package.json files for each build, that take advantage of the cascading file structure that package.json files are able to be placed in (e.g. a tree-like hierarchy).

  • There should be a base package.json file.
  • One package.json file should have its "type" property set to module
  • while the other package.json file should have its "type" property set to commonjs

this is a sign that the package, not only supports both ESM & CJS, but is both ESM & CJS


You can, once again, Infer the Module-type by Inspecting the tsconfig.json files.

There will also be multiple TSConfig files. The thing about the TS Configuration files, is that there is not any one way that they will be named, or structurally set up. From what I see, most use a tsconfig.base, then extend it to two other tsconfig.*.json files.

Something like this:

  • There will be the tsconfig.base.json file (I have seen this also named tsconfig-base.json as well)
  • There should be a tsconfig.*.json file for CJS
  • Then there should be a tsconfig.*.json file for ESM as well.

The naming conventions are not static, there highly configurable, and customizable (which is what we all love about TS right?). Getting use to this can be difficult for some. It was hard for me at first.

The key is to read each TSConfig, and see what the module & moduleResolution properties are set to. If you see a tsconfig with the module property set to "commonjs", and another tsconfig has its module property set to NodeNext, that means the project trans-piles to both ESM & CJS module types.

like image 27
j D3V Avatar answered Nov 15 '22 01:11

j D3V