DEV Community

Cover image for Consuming CJS in a Typescript module
Louis
Louis

Posted on

Consuming CJS in a Typescript module

At Plasmo we leverage Typescript for our web projects.
CJS project like humanparser would have example like this:

const human = require('humanparser');
const fullName = 'Mr. William R. Hearst, III';
const attrs = human.parseName(fullName);

console.log(attrs);
Enter fullscreen mode Exit fullscreen mode

The require statement is how we import the humanparser module into a CJS codebase. This statement can be translated into TypeScript/ESM 2 ways:

import * as humanParser from 'humanparser'
Enter fullscreen mode Exit fullscreen mode

OR

import humanParser from 'humanparser'
Enter fullscreen mode Exit fullscreen mode

Which is it?

The answer lies in the source code of the humanparser module itself. This is an artifact from the evolution of ES, from ES3-> ES5 -> ES6 and beyond (now ESM). There were a couple of ways a module could be exported.

The first, commonly used during the transition between ES3 -> ES5 (early nodejs days), was to assign an entry function or object into module.exports global:

module.exports = stuff
Enter fullscreen mode Exit fullscreen mode

In this example, the module.exports global was used to bridge into the require statement. Because module.exports represent "everything" that was exported from this module, we must use the import all statement:

import * as humanParser from 'humanparser'
Enter fullscreen mode Exit fullscreen mode

The * represents the module.exports object.

Another way, which occurred during the transition between ES5->ES6, is to export a default property as the entry for your module:

module.exports = {
    default : stuff
}
Enter fullscreen mode Exit fullscreen mode

This assigned stuff into the defaults property exported by the module. The reason why this was done is because in ES6, when doing:

import humanParser from 'humanparser'
Enter fullscreen mode Exit fullscreen mode

The above code is actually importing the default props exported by a module. (The export statement in ES6 would be: export default stuff). The import statement above is doing something like this in equivalent CJS code:

const humanParser = require('humanparser').default
Enter fullscreen mode Exit fullscreen mode

Now, back to humanparser's source code, their export statement looks like this:

const parser = module.exports = {};
parser.parseName = function(){}
parser.getFullestName = (str) => {}
parser.parseAddress = (str) => {}
Enter fullscreen mode Exit fullscreen mode

Because there's no default prop being exported, we can import the parser two ways, either importing the whole object:

import * as parser from "humanparser"
Enter fullscreen mode Exit fullscreen mode

OR, importing the props that was exported separately:

import {parseName, parseAddress} from "humanparser"
Enter fullscreen mode Exit fullscreen mode

Personally, I like to import the named export, it helps with code intelligent, and I don't have to deal with the import all module namespace issue.


Back Story

Back in the olden day, pre-2015 to be exact, where angular.js were still trendy, react was still the new kid in the block, and people were comparing Corodva phonegap to react native, there was a transition from ES3/ES5 (commonJS or CJS) to ES6 (ES2015, or ESM, or MJS - modulejs). I suspect whoever named the extension was a huge fan of the late pop king.

Cautions

If you are looking at new projects, be cautious of pure ESM module. They will only allow you to import them with:

import mod from "module-name"
Enter fullscreen mode Exit fullscreen mode

The tricky part is that, your project must also be a module - i.e, it cannot be compiled into common js (which converted all import statement into cjs require call). It must be an MJS/ecmascript module file (with the .mjs extension) OR that your package.json have specified the module property. Otherwise, it would simply fail to compile, and you won't be able to import it using require because ESM code looks like this:

export default stuff
Enter fullscreen mode Exit fullscreen mode

Instead of this:

module.exports = stuff
Enter fullscreen mode Exit fullscreen mode

In the CJS example, module.exports are bridged into the require statement for importing in other modules. Meanwhile, in the ESM example, the stuff is being exported by the export statement , and thus can only be imported via the import statement.

The dilemma here is that if you're using TypeScript and was hoping that it would play well with ESM only module, you are in for some sour candy - it does not. Typescript compiler doesn't yet care if a module it imported is ESM or not - it converts all to CJS import unless you configured it properly. You would also need to tell nodejs to use an experimental flag when running your compiled code: https://nodejs.org/api/esm.html#customizing-esm-specifier-resolution-algorithm

P.s: this post is a brain-dump. If it's useful, you're welcome. If not, here's the MIT license for this otherwise unreadable post:

Copyright 2022 Lā¤ā˜®šŸ¤š

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Enter fullscreen mode Exit fullscreen mode

Discussion (0)