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:
- Must work in browser environment with Webpack (reached ✔️)
- 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 ✔️).
- Must work in NodeJS environment.
- 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?
CodePudding user response:
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, asnpm run Rebuild distributable
wouldn't work. You have to either quote likenpm run 'Rebuild distributable'
or escape likenpm run Rebuild\ distributable
- if you have a
"files"
entry inpackage.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.