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!
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
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.
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.
package.json
tsconfig.json
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.
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"
}
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
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).
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.
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 valuesnode12
(no top level await),node16
(ESM standard implemented for the LTS node16 version) &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.
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).
package.json
file.package.json
file should have its "type"
property set to module
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
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:
tsconfig-base.json
as well)tsconfig.*.json
file for CJS
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With