3

Like lodash, the library @yamato-daiwa/es-extensions suggests the common functions for both BrowserJS and NodeJS, but unlike lodash it has single main file index.js with re-exports.

Requirements

This library:

  1. Must work in browser environment with Webpack (reached ✔️)
  2. Must make available the Webpack's tree shaking (by other words, the cutting off of the unused functionality on production building) for BrowserJS where each kilobyte on count (reached ✔️).
  3. Must work in NodeJS environment.
  4. Must work with ts-node

Main problem

The conflicting point is the modules type. To make the Webpack's tree shaking available, the ES modules are required, but currently NodeJS supports CommonJS modules only, and in ts-node the ES modules support is limited (especially in the library exporting case).

What we have is:

Modules type BrowserJS (Webpack) Tree shaking NodeJS ts-node
CommonJS Yes No Yes Yes
ES20XX Yes Yes No Limited

Because the tree shaking is critical for BroswerJS, it's already been decided to distribute the library by ES2020 modules. But this way, the support for NodeJS and ts-node will be lost.

Even if to build the NodeJS application with Webpack where it's not recommended to bundle the NodeJS libraries (webpack node modules externals is being used to exclude them), application will crush if don't add the @yamato-daiwa/es-extensions with it's ES modules to excluding of webpack node modules externals.

Repro

In this repro, npm run "Webpack:ProductionBuild" will build the files BrowserJS.js and NodeJS.js for appropriate environment. Because the source code using isUndefined function of "@yamato-daiwa/es-extensions" library only, in BrowserJS.js must not be any other functionality (Webpack's tree shaking):

(()=>{"use strict";console.log(!1),console.log("End of the script")})();

Well, it was the cleared challenge. The subject of this question isNodeJS.js:

(()=>{"use strict";const e=require("@yamato-daiwa/es-extensions");console.log((0,e.isUndefined)("ALPHA")),console.log("End of the script")})();

If we try to run it, we will have the error:

C:\Users\***\WebpackDoesNotCuttOfTheUnusedFunctionality\ConsumingProject\node_modules\@yamato-dai
wa\es-extensions\Distributable\index.js:6
export { default as formatNumberWith4KetaKanji } from "./Numbers/formatNumberWith4KetaKanji";

Now let's try to run it via ts-node (npm run "TSNode test"):

> ts-node index.ts

(node:18148) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
C:\Users\***\WebpackDoesNotCuttOfTheUnusedFunctionality\ConsumingProject\index.ts:1
import { isUndefined } from "@yamato-daiwa/es-extensions";

Does in mean "you must change the library"?

Note

This question has been asked before TypeScript 4.5 release. Maybe its new functionality will solve these problems?

Pedro A
  • 3,989
  • 3
  • 32
  • 56
Takeshi Tokugawa YD
  • 670
  • 5
  • 40
  • 124
  • 1
    `currently NodeJS supports CommonJS modules only` is wrong. – morganney Oct 11 '21 at 01:26
  • @morganney, Thank you for the comment. Well, I can't know everything in up-date state. May I ask you to add the details about this moment in your answer? – Takeshi Tokugawa YD Oct 11 '21 at 02:46
  • How about `umd` module type? It supports every javascript environments. – h-sifat Oct 11 '21 at 03:36
  • @h-sifat, will three shaking available with UMD? – Takeshi Tokugawa YD Oct 11 '21 at 05:27
  • I'm not sure but try setting `library: { type: "umd" }` in the `output` object of webpack configuration and use `es6` module syntax in your code. See if it works. – h-sifat Oct 11 '21 at 06:03
  • @h-sifat From the [Webpack documentation](https://webpack.js.org/guides/tree-shaking/): "It relies on the static structure of ES2015 module syntax". Thus, dead-code elimination will not work with UMD. – Takeshi Tokugawa YD Oct 11 '21 at 08:51
  • But you're still using `es6 import & export` syntax in your source code. You're library bundle will use `umd` not your source code! If the output bundle doesn't use `umd` then your library won't support every environment! – h-sifat Oct 11 '21 at 13:49
  • 1
    @h-sifat, Hmm, you gave me an idea. What if library could be distributed by CommonJS modules and `sideEffects: false` only matters? I'll check this hypothesis on weekend. Thanks for idea! – Takeshi Tokugawa YD Oct 12 '21 at 13:11
  • @h-sifat, my hypothesis was wrong... CommonJS modules has not been eliminated even with `sideEffects: false`. Well, I' sorry, but I has not time to experiment with UMD. I'll follow Eric Haynes's solution. Thank you for the comments! – Takeshi Tokugawa YD Oct 16 '21 at 04:05

1 Answers1

3

The "main" entry in a package.json file should always be in commonjs format. The "module" entry should always be es modules. Right now, you have "main" pointing to es modules, which will not be resolved properly (your ts-node error, for example).

Generally, if you want to give consumers the option, you would create 2 builds in the distributable.

(you'll need to remove the comments in these json files)

// tsconfig-esm.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "ES2020",

    // es module build target
    "outDir": "Distributable/esm"
  }
}
// tsconfig-cjs.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "commonjs",

    // commonjs build target
    "outDir": "Distributable/cjs",

    // the other one will build types, so not needed here
    "declaration": false
  }
}

Then, in your package.json:

"main": "./Distributable/cjs/index.js",
"module": "./Distributable/esm/index.js",
"types": "./Distributable/esm/index.d.ts",
"scripts": {
  "build": "rimraf Distributable && tsc -p tsconfig-cjs.json && tsc -p tsconfig-esm.json",
  "Lint": "eslint Source Tests"
},
"files": [
  "Distributable"
],
"devDependencies": {
  "rimraf": "^3.0.2",

Note:

  • you can call your scripts whatever you like, but build is idiomatic. The spaces would drive me nuts, as npm run Rebuild distributable wouldn't work. You have to either quote like npm run 'Rebuild distributable' or escape like npm run Rebuild\ distributable
  • if you have a "files" entry in package.json, you no longer need .npmignore. This means "package only the files specified", which is much safer than having to remember to add every new file to an ignore list.
  • I added the rimraf dependency, which is a common tool for deleting the dist directory in a platform-independent way. del-cli only works for windows.
Eric Haynes
  • 5,126
  • 2
  • 29
  • 36