background

Sindre Sorhus, the NPM wheel guy, undertakes the maintenance of the bottom wheel of a sizeable part of the community, and his every move will profoundly affect the community’s tens of thousands of top tools.

Sindre Sorhu, who now hates CJS and wants to fully embrace Pure ESM, has forcibly migrated almost all of the wheels he maintains to pure ESM versions in recent months. This move is publicly available as follows:

  1. Big version change: A big version of Breaking change has been made, so if you are writing with CJS, you need to install the previous version (for example, ^3.0.0 now, you need to install ^2.0.0).

  2. Community shock: caused many top-level tool links to be broken, such as NextJS, and quickly released alpha fixed ESM version in a matter of days.

  3. Force sweeping the ESM: While esM is known to be the future of JavaScript, writing CJS in nodeJS is an ancient legacy, and Sindre Sorhus believes that pure ESM will be the future and CJS will be abandoned, even though typescript conversions are powerful.

Let’s look at some of the angles.

How do I migrate pure ESM

Since the ESM is the future of JavaScript, the ultimate question we need to ask everyone is how to migrate.

Following a recent change to pure ESM for command line tool king Execa, the community has become increasingly critical of to Pure ESM.

Sindre Sorhus himself has published a recommended technique for migrating Pure ESM: ESM-package.md

I’ll take a detailed look at Sindre Sorhus’s advice on how to convert CJS tools to ESM.

JavaScript writing tools

For CJS tools originally written in JS, you need to do the following:

  1. Add “type”: “module” to your package.json: So node knows to run your tool using the ESM loader (CJS by default)

  2. Replace “main”: “index.js” with “exports” in package.json: “. / index. Js “: This step is to limit the export scope of ESM, to prevent implicit hack imports from causing undefined behavior, of course exports can also be specified for object specific scope (details can be learned after the search engine search). The use of exports was also recommended in TS 4.5 to prevent implicit imports (although its Nodenext module conversion was temporarily suspended until the next release).

  3. In the package. The json limit nodejs running version number: engines used in the fields such as “node” : “^ 12.20.0 | | ^ 14.13.1 | | > = 16.0.0”.

  4. Remove ‘use strict’ from all code; : We do not need this strict declaration in the ESM world.

  5. Convert all CJS imports and exports in the code require()/module.exports to esM import/export.

  6. Import files using the full relative file path, e.g. Import x from ‘./index.js’, where filename and extension are specified.

  7. Convert all.d.ts files to ESM export formats: For popular toolkits written by the community using JS, the official or community will help maintain a types package such as @types/*. These auxiliary format declaration files will also be migrated to ESM.

  8. It is recommended to use the Node :* protocol to import the built-in modules of Node: Import path from ‘path’ → import path from ‘node:path’ This explicitly restricts telling Node I’m importing a built-in module (because sometimes NPM’s public package is also named the same as node’s built-in module).

Tools written in Typescript

For CJS tools originally written by TS, the migration method is roughly the same as for JS tools:

  1. Adding a Module indicates: as shown in step 1 of the js tool.

  2. Limit ESM exports: As above, same as step 2 for tools written with JS.

  3. Limit the Node Engines version: As shown in step 3 of the js tool.

  4. Use strict imports: as above, same as step 6 for tools written with JS.

  5. Delete namespace usage and use export: This means that the pure.d.ts namespace type method has been removed from the ESM register. It is believed that there are many novice users who simply use.d.ts to store type images, resulting in problems that cannot be copied, cannot be exported, and sometimes the IDE will not recognize other problems.

  6. Change tsconfig.json compilation target to ES format, module: “ES2020” (future TS 4.6+ will support new Node module option to better handle to ESM conversion).

In summary, it seems that a lot of the changes are minor issues, but the only thing that’s a lot of work is that you have to use an absolutely specified file import, so instead of being able to import./index.js as a default, you now have to write./index.js without dropping the file name. Even in typescript, it must be./index.js.

Talk about the cost to pure

The cost of migrating absolute imports

Let’s talk about the most expensive, which is migrating from implicit default imports such as./ to absolute imports, which seems easy in a JS-written library, as long as the files are lined up to manually replace the changes.

As for the TS libraries, typescript plans to add a node12 / nodenext module option in plan 4.5 to facilitate esM migration:

// tsconfig.json

{
  "compilerOptions": {
    "module": "node12" // or nodenext
    
    // moduleResolution will be set by the node indicator of the Module, no need to display the Settings
    // "moduleResolution": "node12" // or nodenext}}Copy the code

This feature is currently available in TS 4.6-dev. There are two places you can go back to:

  • Json official refer: module-node12 / nodenext

  • ECMAScript Module Support in Node.js TypeScript 4.5 Beta node12 / nodenext

Node12 / nodenext will automatically convert the absolute path to node12 / nodenext.

import { valueOfPi } from "./constants";
export const twoPi = valueOfPi * 2;
Copy the code

Specifying “module”: “node12” yields:

"use strict";
Object.defineProperty(exports."__esModule", { value: true });
exports.twoPi = void 0;
// ↓ Note the path change of this line!
const constants_js_1 = require("./constants.js");
exports.twoPi = constants_js_1.valueOfPi * 2;
Copy the code

You can see that the introduction of constants is automatically suffixed.

In TS 4.6-dev, suffixes are not automatically added. Instead, when you build, you will be warned to use an absolute path indicator where the suffix is not introduced.

Here are the prerequisites that trigger this alarm:

  1. Specify the ESM format in package.json: “type”: “module”.

  2. Node12 / nodenext node conversions need to be enabled: for example, “module”: “node12”. ModuleResolution is no longer specified.

When you meet the above two criteria, you will get an alert when the TSC build does not use an absolute path import.

Is it reasonable to use import x from ‘./index.js’ in ts to import an index.ts? The.js suffix is now compatible with TS 4.5, and no errors will be reported.

An example of an alarm:

As a matter of fact, since TS 4.6 is ready for ESM-compliant conversions, we can migrate to Pure ESM with the help of typescript 4.6+, and it is very useful to have TS support for absolute path migration.

To sum up, the cost of absolute path migration is there, but not particularly high.

Cost of migrating CJS built-in variables

We believe that the cost has been psychological preparation, well known, that is:

  1. Transformation from require to import

  2. Conversion from __dirname / __filename to import.meta.url

As for import.meta. Url migration, there is currently a community practice of hacking: using CJS export __dirname to hack

Of course, this hack is an unconventional method, and the conventional migration method should be:

import path from 'node:path'
import { fileURLToPath } from 'node:url'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
Copy the code

Import.meta. Url is converted to the absolute path to file using the built-in tool and then converted to the folder name using path.dirname.

Some additional information to note is as follows:

  1. Import.meta. url is an es2020 (ES11) behavior, but is also supported by ESNext, which you may want to keep in mind when using it elsewhere to compile as an ESM module.

  2. Do not use new URL(import.meta.url). Pathname, which can cause inconsistency and errors on different platforms. See url.fileurltopath (URL) for details.

What does enhanced resolve now use to guarantee packet addressing success? There is currently a wheel that the community can try: import-meta-resolve, which is a polyfill of import.meta-. Resolve and can help you with the guided packet addressing behavior.

In conclusion, the cost of migrating an ESM is detailed and requires a lot of knowledge.

But are we not mindlessly embracing change, well CJS need not, to migrate ESM? Look at community reviews before moving.

General comments on community evaluation to ESM

Let’s briefly summarize the ridicule of current community users in the process of ESM migration:

ts-node

Ts-node does not support ESM well because it requires injection of a specific ESM loader, which is still experimental in NodeJS. So far you can also see the warning for running the ESM Loader.

ts-node-dev

Does not support the esm

next.js

Nextjs was rushed out with a new beta version of ESM by Sindre Sorhus, but it still had a lot of problems with the ESM tools.

Jest

Jest is unable to identify libraries that lack the main field, and the surrounding tools support esM poorly or barely work.

And so on…

There are plenty of arguments for and against the ESM. Supporters of no are enumerating reasons why it should not be done now.

conclusion

So we’re not the big brother of the community, nor are we deep users, so what should we do from the perspective of pure “simple use practitioners”?

Here are a few suggestions.

Always believe in Typscript

Sindre Sorhus fired the first shot of the reform, ts is also crazy to follow, based on the historical changes we made to TS, you never know how well TS will do, I believe that TS will gradually lead to esM specification reform and migration after 4.6, It’s all “smooth and guided” and you can always trust Typescript.

Be a CJS/ESM double

Since exports have a higher recognition priority than main, we can polyfill it into ESM format (such as ESM-compatible __dirname) with the help of translation and substitution mechanisms of some tools on the basis of our CJS writing. This results in both CJS and ESM artifacts in just one CJS script, similar to the way we used the app-type third-party library with main and Module entries in our WebPack project.

At present, there are some esbuild ecological tools in the community to do these practices, but they are still immature. There are many corner cases, which may be a possible direction of TS development in the future.

The downgrading of mental agility

Since Wheel brother is going to sweep ESM, we should not upgrade the big version of 😅, you upgrade you upgrade, I will go to github release before installing the package to check whether the current version supports CJS, whether pure ESM is ok, if yes, downgrade a big version to install.

But this is really tired.

In fact, nodeJS ‘native support for CJS and the legacy of history show that there is a long way to go to migrate ESM. We don’t resent someone trying to cause change, because if we let you do it, you don’t have that much influence in the community. Therefore, the change ultimately needs more and more community leaders to step up and stand up. For the rest of us, keep tracking and embrace change.

other

About typescript 4.5: Announcing typescript 4.5