directory

  • preface
  • Node support for CJS and ESM
      1. We can solve it with suffixes
      1. Resolve the problem by using the Type field
      1. – input – type logo
    • doubt
  • The module field is off
  • Disadvantages of the main field
  • Exports of Kings
      1. Scope of the package
      1. Pattern of subpaths
      1. Conditional export
  • In duplicate
  • conclusion
  • reference

preface

Node has a very core knowledge point — module. In the era when front-end modularization has not really come, the solution given by Node is CommonJS for CJS.

Later, ECMAScript adopted the JS modular system, thus opening up the situation of CJS and ESM co-existence. With the popularity of modularization today, have you ever wondered why most packages can be used through BOTH CJS and ESM?

So let’s look at this principle.

The ESM and CJS:

  • Esm-ecmascript module
  • CJS – CommonJS

Node support for CJS and ESM

Node supports CJS by default, which we all know, and then supports ESM, so what changes have been made to Node?

1. We can solve it with suffixes

  • The ESM to.mjsAt the end.
  • CJS in.cjsAt the end.

2. Use the Type field to resolve the problem

  • CJS in.jsEnd of file, and most recent parentpackage.jsonMiddle-top-level field"type"A value of"commonjs".
  • The ESM to.jsEnd of file, and most recent parentpackage.jsonThe top of the"type"A value of"module".

3. --input-typemark

  • Pass the flag –input-type=commonjs as an argument to –eval or –print, or to Node via STDIN.

  • Pass a string marked –input-type=module as an argument to –eval or through STDIN to Node

doubt

However, normally a package can only support one type of module, which is either ESM or CJS.

But you notice that most packages can be used with require and import. How is that?

The module field is off

We support ESM by using Node’s native support for CJS to support require syntax, and by using Webpack and other packaging tools to identify the module field of package.json. It’s also tree-shaking relative to require.

Disadvantages of the main field

  1. The primary disadvantage of the main field is that it does not support both formats.
  2. Files inside package cannot be isolated and can be referenced at will, for example, I referenced Chalkpackage.jsonThe file can import the relative pathnode_modules/chalk/package.json.

The new Module from Exports and the packaged tool-supported module is similar, but Exports has Node’s native support and is even more powerful.

Exports of Kings

Exports have three important functions:

  1. Scope package.
  2. Subpath mode
  3. Conditional export

Exports has other features that are not the focus of today’s article, so I’ll skip them.

1. Scope packages

Exports and main fields are mutually exclusive. If you define both “exports” and “main”, “exports” will override “main” in Node that supports “exports” (version v12.7.0 or higher). Otherwise, “main” takes effect.

So we can use exports by simply copying the main field and changing it to exports, like this:

{
  "main": "./index.js"."exports": "./index.js"
}
Copy the code

It is important to note that if the exports field is valid, you cannot refer to unexported files in the package, unlike the main field, which is the scoped package.

We’ll take a look at the Calculator demo we created in the previous article using the workspaces field described in the new way to replace NPM Link debugging.

Export default (STR) => STR; export default (STR) => STR; , the current folder directory

. ├ ─ ─ packages │ ├ ─ ─ divide │ │ ├ ─ ─ index. The js │ │ └ ─ ─ package. The json │ ├ ─ ─ minus │ │ ├ ─ ─ subpath. Js │ │ ├ ─ ─ index. The ts │ │ └ ─ ─ package. Json │ ├ ─ ─ plus │ │ ├ ─ ─ index. The js │ │ └ ─ ─ package. The json │ └ ─ ─ times │ ├ ─ ─ index. The js │ └ ─ ─ package. The jsonCopy the code

Minus’s package.json folder now looks like this:

{
    "main": "index.js"
}
Copy the code

We can refer to the files in the package whenever we want, now we can introduce subpath.js in the root directory index.js:

import subpath from "minus/subpath.js";

console.log(subpath("Hi JavaScript"));
Copy the code

However, we did not work with exports:

{
    "main": "index.js",
    "exports": "./index.js"
}
Copy the code

When the “exports” field is defined and all child paths are closed, debug raises the error ERR_PACKAGE_PATH_NOT_EXPORTED.

By the way, the default installation package of NPM should really learn from PNPM and do the package sealing function.

2. Subpath pattern

Okay, subpath closure is great, but sometimes we just want one feature of a package, like Lodash giving us the ability to import on demand.

This requires a subpath pattern, which is essentially a path map.

As an example, let’s do path mapping under exports mode.

"exports": {
    ".": "./index.js",
    "./subpath.js": "./subpath.js"
},
Copy the code

The ERR_PACKAGE_PATH_NOT_EXPORTED error is gone when debugging the code.

3. Export conditions

Conditional export is very simple.. Represents the current directory.

"exports": {
    ".": {
        "import": "./index.mjs"."require": "./index.cjs"}},Copy the code

When you use this package, Node will resolve the corresponding module specification depending on the user or downstream package environment. Now I can import it in projects that support import environments, or require it in projects that support require.

In duplicate

Since the package needs to support two modularity, the problem is that it is impossible for us to write the code in two parts. We have to use packaging tools, Webapck and Rollup, but their configuration is too complicated. After you finish the environment, the inspiration and mood for writing the code will probably be lost. Today we introduce a small and beautiful tool called TSUP.

Tsup is also perfect for using Typescript with zero configuration:

$ tsup src/index.ts
Copy the code

You can then publish dist/index.js in your project root directory.

Of course, our focus is on dual-format modules, so support dual-format with just a flag:

$ tsup src/index.ts --format cjs,esm
Copy the code

Both dist/index.js and dist/index.mjs are generated together, which is very Nice.

Here’s a copy of the preferred template for package.json:

{
  "name": "calculator"."main": "./dist/index.js"."module": "./dist/index.mjs"."types": "./dist/index.d.ts"."exports": {
    ".": {
      "require": "./dist/index.js"."import": "./dist/index.mjs"."types": "./dist/index.d.ts"}},"scripts": {
    "build": "tsup src/index.ts --format cjs,esm --dts --clean"."watch": "npm run build -- --watch src"."prepublishOnly": "npm run build"}}Copy the code

Having said that, I also highly recommend trying out the surprisingly fast Esbuild.

conclusion

Today, looking back at modularity and recognizing the coexistence of CJS and ESM, Node has moved with The Times to support dual packages, and to compensate for the potential abuse of unexported packages, Node has improved its functionality along the way.

I don’t see any open source projects using this feature yet, but I’m sure you’ll see it in major open source projects in the future.

reference

  • publish-esm-and-cjs
  • Node latest Module import/export specification