From 124c05f931de24b41d355bb0fdb0bda679cb77d4 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Fri, 3 Apr 2026 05:02:42 +0200 Subject: [PATCH 1/3] add gitbook-plugin-include-codeblock --- guide/book.json | 5 +- guide/package-lock.json | 578 ++++++++++++++++++++++++++++++++++++++++ guide/package.json | 1 + 3 files changed, 583 insertions(+), 1 deletion(-) diff --git a/guide/book.json b/guide/book.json index 80ff45f..ff195d6 100644 --- a/guide/book.json +++ b/guide/book.json @@ -1,3 +1,6 @@ { - "title": "Node.js package configuration guide" + "title": "Node.js package configuration guide", + "plugins": [ + "include-codeblock" + ] } diff --git a/guide/package-lock.json b/guide/package-lock.json index 40b8701..2872c7e 100644 --- a/guide/package-lock.json +++ b/guide/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "devDependencies": { + "gitbook-plugin-include-codeblock": "^3.2.3", "honkit": "^6.0.2" } }, @@ -192,6 +193,26 @@ "dev": true, "license": "MIT Expat" }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -375,6 +396,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha512-Ej37YKYbFUI8QiYlvj9YHb6/Z60dZyPJW0Cs8sFilMbd2lP0bw3ylAq9yJkK4lcTA2dID5fG8LjmJYbO7kWb7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/cheerio": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", @@ -603,6 +649,19 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-find-index": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -620,6 +679,43 @@ "dev": true, "license": "MIT" }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -814,6 +910,16 @@ "xtend": "~4.0.0" } }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -937,6 +1043,19 @@ "node": ">=8" } }, + "node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", @@ -1055,6 +1174,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gitbook-plugin-include-codeblock": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/gitbook-plugin-include-codeblock/-/gitbook-plugin-include-codeblock-3.2.3.tgz", + "integrity": "sha512-W2tXf++CeC3J/iX2+/+sd86U8u6OS92/wjuYuzGI7gm9WUPs83xlxcp3x5Q/6ttAcfwdy+LIuPO8kGxEQyQmjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^1.1.1", + "handlebars": "^4.0.5", + "language-map": "^1.1.1", + "meow": "^4.0.0" + }, + "bin": { + "include-codeblock": "bin/include-codeblock.js" + }, + "engines": { + "gitbook": "*" + } + }, "node_modules/gitbook-plugin-livereload": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/gitbook-plugin-livereload/-/gitbook-plugin-livereload-0.0.1.tgz", @@ -1152,6 +1290,28 @@ "dev": true, "license": "ISC" }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -1280,6 +1440,13 @@ "honkit": "bin/honkit.js" } }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true, + "license": "ISC" + }, "node_modules/html-entities": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.0.tgz", @@ -1411,6 +1578,16 @@ "node": ">=0.10.0" } }, + "node_modules/indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1440,6 +1617,13 @@ "node": "*" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1528,6 +1712,16 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -1564,6 +1758,13 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-defaults": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/json-schema-defaults/-/json-schema-defaults-0.1.1.tgz", @@ -1770,6 +1971,13 @@ "kramed": "bin/kramed" } }, + "node_modules/language-map": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/language-map/-/language-map-1.5.0.tgz", + "integrity": "sha512-n7gFZpe+DwEAX9cXVTw43i3wiudWDDtSn28RmdnS/HCPr284dQI/SztsamWanRr75oSlKSaGbV2nmWCTzGCoVg==", + "dev": true, + "license": "MIT" + }, "node_modules/livereload-js": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", @@ -1777,6 +1985,36 @@ "dev": true, "license": "MIT" }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -1784,6 +2022,20 @@ "dev": true, "license": "MIT" }, + "node_modules/loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/lru_map": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", @@ -1798,6 +2050,16 @@ "dev": true, "license": "MIT" }, + "node_modules/map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha512-TzQSV2DiMYgoF5RycneKVUzIa9bQsj/B3tTgsE3dOGqlzHnGIDaC7XBE7grnA+8kZPnfqSGFe95VHc2oc0VFUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", @@ -1812,6 +2074,27 @@ "dev": true, "license": "MIT" }, + "node_modules/meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -1848,6 +2131,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + }, + "engines": { + "node": ">= 4" + } + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -1878,6 +2175,13 @@ "dev": true, "license": "MIT" }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -1899,6 +2203,29 @@ } } }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2051,6 +2378,56 @@ "node": ">=0.10.0" } }, + "node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/parse5": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", @@ -2104,6 +2481,16 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -2121,6 +2508,19 @@ "dev": true, "license": "MIT" }, + "node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2134,6 +2534,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -2162,6 +2572,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha512-tRS7sTgyxMXtLum8L65daJnHUhfDUgboRdcWW2bR9vBfrj2+O5HSMbQOJfJJjIVSPFqbBCF37FpwWXGitDc5tA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -2186,6 +2606,35 @@ "node": ">= 0.8.0" } }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2199,6 +2648,20 @@ "node": ">=8.10.0" } }, + "node_modules/redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha512-XNwrTx77JQCEMXTeb8movBKuK75MgH0RZkujNuDKCezemx/voapl9i2gCSi8WWm8+ox5ycJi1gxF22fR7c0Ciw==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2364,6 +2827,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/slick": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz", @@ -2374,6 +2844,52 @@ "node": "*" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -2432,6 +2948,26 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -2513,6 +3049,16 @@ "dev": true, "license": "MIT" }, + "node_modules/trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha512-MTBWv3jhVjTU7XR3IQHllbiJs8sc75a80OEhB6or/q7pLTWgQ0bMGQXXYQSrSuXe6WiKWDZ5txXY5P59a/coVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/try-resolve": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/try-resolve/-/try-resolve-1.0.1.tgz", @@ -2528,6 +3074,20 @@ "dev": true, "license": "0BSD" }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/undici": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz", @@ -2565,6 +3125,17 @@ "node": ">=10" } }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/web-resource-inliner": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-6.0.1.tgz", @@ -2766,6 +3337,13 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/guide/package.json b/guide/package.json index 1b5cfa4..71f7a50 100644 --- a/guide/package.json +++ b/guide/package.json @@ -9,6 +9,7 @@ }, "license": "MIT", "devDependencies": { + "gitbook-plugin-include-codeblock": "^3.2.3", "honkit": "^6.0.2" } } From 0139b6bfdc236430b7dfb4da4069a966af044f91 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Fri, 3 Apr 2026 04:44:48 +0200 Subject: [PATCH 2/3] migrate to use codeblocks --- .../classic-commonjs/README.md | 42 +-- .../classic-commonjs/app.cjs | 2 + .../node_modules/my-logger/logger.js | 10 +- .../simple-esm/README.md | 41 +-- .../02-single-file-package/simple-esm/app.cjs | 2 + .../node_modules/my-logger/logger.js | 7 +- guide/03-multi-file-package/README.md | 66 +---- guide/03-multi-file-package/example/app.cjs | 9 + guide/03-multi-file-package/example/app.mjs | 7 + .../node_modules/my-logger/lib/utils.js | 6 + .../node_modules/my-logger/src/logger.js | 9 +- .../shipping-cjs-for-esm/README.md | 161 +++------- .../app-default-export.mjs | 3 + .../shipping-cjs-for-esm/app-dynamic-fail.mjs | 2 + .../app-dynamic-import.mjs | 6 +- .../app-dynamic-named-exports.mjs | 3 + .../app-import-exports-assignment.mjs | 3 + .../app-import-module-exports-assignment.mjs | 3 + .../app-import-non-existent-name.mjs | 2 + .../app-namespace-import.mjs | 5 +- .../app-namespace-named-exports.mjs | 3 + .../shipping-cjs-for-esm/app.cjs | 3 + .../node_modules/my-logger-dynamic/index.js | 8 +- .../my-logger-exports-assignment/index.js | 12 +- .../index.js | 13 +- .../my-logger-object-literal/index.js | 9 +- .../node_modules/my-logger/index.js | 9 +- .../shipping-cjs-for-esm/test.mjs | 13 + .../shipping-esm-for-cjs/README.md | 122 ++------ .../app-with-default-export.cjs | 5 +- .../app-with-default-export.mjs | 3 + .../shipping-esm-for-cjs/app.cjs | 3 + .../shipping-esm-for-cjs/app.mjs | 3 + .../shipping-esm-for-cjs/cycle-lazy/b.cjs | 3 + .../my-logger-with-default-export/index.js | 9 +- .../node_modules/my-logger/index.js | 9 +- .../migrating-exports/README.md | 275 ++++++------------ ...orting-default-from-named-only-partial.mjs | 4 +- .../app-requiring-default-export-partial.cjs | 2 +- .../node_modules/my-module/default-export.js | 5 + .../node_modules/my-module/named-only.js | 7 + .../node_modules/my-module/named-only.js | 5 + .../migrating-imports/README.md | 217 ++++---------- .../node_modules/my-module/import-dir.js | 2 +- .../my-module/import-undetectable-invalid.js | 2 +- .../after/node_modules/my-module/index.js | 17 +- .../my-module/load-without-extension.js | 2 +- .../before/node_modules/my-module/index.js | 11 +- .../my-module/load-without-extension.js | 3 +- 49 files changed, 475 insertions(+), 693 deletions(-) create mode 100644 guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-import-non-existent-name.mjs diff --git a/guide/02-single-file-package/classic-commonjs/README.md b/guide/02-single-file-package/classic-commonjs/README.md index 1cc1f5b..9572cec 100644 --- a/guide/02-single-file-package/classic-commonjs/README.md +++ b/guide/02-single-file-package/classic-commonjs/README.md @@ -8,27 +8,15 @@ This is the most traditional way of creating a package in Node.js, using the Com In this example, the `my-logger` package consists of a single file, logger.js, that implements and exports the `Logger` class. -```js -// logger.js -class Logger { - // ... Logger implementation -}; -exports.Logger = Logger; -``` +`logger.js`: + +[import:'logger_start,logger_end'](node_modules/my-logger/logger.js) The `package.json` file for this package looks like this: -```json -{ - "name": "my-logger", - "version": "1.0.0", - "type": "commonjs", - "exports": { - ".": "./logger.js", - "./package.json": "./package.json" - } -} -``` +`package.json`: + +[import](node_modules/my-logger/package.json) So the package structure is as follows: @@ -47,21 +35,15 @@ In addition, we have included an export for `./package.json` in the `"exports"` When users load this package, they can do so like this: -```js -// app.cjs -const { Logger } = require('my-logger'); -const logger = new Logger('debug'); -logger.error('This is an error message'); -``` +`app.cjs`: + +[import:'doc'](app.cjs) Or, a ESM consumer can load this package like this: -```js -// app.mjs -import { Logger } from 'my-logger'; -const logger = new Logger('debug'); -logger.error('This is an error message'); -``` +`app.mjs`: + +[import](app.mjs) Usually, a published package is placed in a `node_modules` directory of a project. When you use `require('my-logger')`, Node.js will start looking for a directory named `my-logger` in the nearest `node_modules` directory up until it reaches the root of the filesystem. You can refer to the [Node.js documentation](https://nodejs.org/api/modules.html#all-together) for more details about the module resolution algorithm. diff --git a/guide/02-single-file-package/classic-commonjs/app.cjs b/guide/02-single-file-package/classic-commonjs/app.cjs index 7ad9775..bcac8df 100644 --- a/guide/02-single-file-package/classic-commonjs/app.cjs +++ b/guide/02-single-file-package/classic-commonjs/app.cjs @@ -1,5 +1,7 @@ 'use strict'; +//! [doc] const { Logger } = require('my-logger'); const logger = new Logger('debug'); logger.error('This is an error message'); +//! [doc] diff --git a/guide/02-single-file-package/classic-commonjs/node_modules/my-logger/logger.js b/guide/02-single-file-package/classic-commonjs/node_modules/my-logger/logger.js index 631b448..84077a1 100644 --- a/guide/02-single-file-package/classic-commonjs/node_modules/my-logger/logger.js +++ b/guide/02-single-file-package/classic-commonjs/node_modules/my-logger/logger.js @@ -7,7 +7,10 @@ const LEVELS = { debug: 3 }; +//! [logger_start] class Logger { + // ... Logger implementation +//! [logger_start] constructor(level = 'info') { this.level = LEVELS[level]; } @@ -25,6 +28,7 @@ class Logger { warn(msg) { this.print('warn', msg); } info(msg) { this.print('info', msg); } debug(msg) { this.print('debug', msg); } -} - -module.exports = { Logger }; +//! [logger_end] +}; +exports.Logger = Logger; +//! [logger_end] diff --git a/guide/02-single-file-package/simple-esm/README.md b/guide/02-single-file-package/simple-esm/README.md index 4b904d2..d2fdbf2 100644 --- a/guide/02-single-file-package/simple-esm/README.md +++ b/guide/02-single-file-package/simple-esm/README.md @@ -8,26 +8,15 @@ This is the more modern way of creating a package in Node.js, using the ESM syst In this example, we have a package named `my-logger` that exports a `Logger` class. The package consists of a single file, `logger.js`, which contains the implementation of the `Logger` class. -```js -// logger.js -export class Logger { - // ... Logger implementation -}; -``` +`logger.js`: + +[import:'logger_start,logger_end'](node_modules/my-logger/logger.js) The `package.json` file for this package looks like this: -```json -{ - "name": "my-logger", - "version": "1.0.0", - "type": "module", - "exports": { - ".": "./logger.js", - "./package.json": "./package.json" - } -} -``` +`package.json`: + +[import](node_modules/my-logger/package.json) So the package structure is as follows: @@ -46,21 +35,15 @@ In addition, we have included an export for `./package.json` in the `"exports"` When users load this package, they can do so like this: -```js -// app.mjs -import { Logger } from 'my-logger'; -const logger = new Logger('debug'); -logger.error('This is an error message'); -``` +`app.mjs`: + +[import](app.mjs) Or, from Node.js 20 and above, CommonJS consumers can also load a ESM package like this: -```js -// app.cjs -const { Logger } = require('my-logger'); -const logger = new Logger('debug'); -logger.error('This is an error message'); -``` +`app.cjs`: + +[import:'doc'](app.cjs) Usually, a published package is placed in a `node_modules` directory of a project. When you use `import 'my-logger'`, Node.js will start looking for a directory named `my-logger` in the nearest `node_modules` directory up until it reaches the root of the filesystem. The module resolution algorithm for ESM is slightly different from the one used for loading CommonJS modules. You can refer to the [Node.js documentation](https://nodejs.org/api/esm.html#resolution-algorithm-specification) for more details. diff --git a/guide/02-single-file-package/simple-esm/app.cjs b/guide/02-single-file-package/simple-esm/app.cjs index 7ad9775..bcac8df 100644 --- a/guide/02-single-file-package/simple-esm/app.cjs +++ b/guide/02-single-file-package/simple-esm/app.cjs @@ -1,5 +1,7 @@ 'use strict'; +//! [doc] const { Logger } = require('my-logger'); const logger = new Logger('debug'); logger.error('This is an error message'); +//! [doc] diff --git a/guide/02-single-file-package/simple-esm/node_modules/my-logger/logger.js b/guide/02-single-file-package/simple-esm/node_modules/my-logger/logger.js index 721c2e4..da5c02f 100644 --- a/guide/02-single-file-package/simple-esm/node_modules/my-logger/logger.js +++ b/guide/02-single-file-package/simple-esm/node_modules/my-logger/logger.js @@ -5,7 +5,10 @@ const LEVELS = { debug: 3 }; +//! [logger_start] export class Logger { + // ... Logger implementation +//! [logger_start] constructor(level = 'info') { this.level = LEVELS[level]; } @@ -23,4 +26,6 @@ export class Logger { warn(msg) { this.print('warn', msg); } info(msg) { this.print('info', msg); } debug(msg) { this.print('debug', msg); } -} +//! [logger_end] +}; +//! [logger_end] diff --git a/guide/03-multi-file-package/README.md b/guide/03-multi-file-package/README.md index be49a5d..c123266 100644 --- a/guide/03-multi-file-package/README.md +++ b/guide/03-multi-file-package/README.md @@ -17,45 +17,23 @@ my-logger/ └── package.json ``` -```js -// lib/utils.js -export const LEVELS = { /* ... log levels ... */ }; -``` +`lib/utils.js`: -```js -// src/logger.js -import { LEVELS } from '../lib/utils.js'; -export class Logger { - // ... Logger implementation -}; -``` +[import:'abbrev,abbrev_end'](example/node_modules/my-logger/lib/utils.js) -```js -// index.js -export { Logger } from './src/logger.js'; -``` +`src/logger.js`: + +[import:'logger_start,logger_end'](example/node_modules/my-logger/src/logger.js) + +`index.js`: + +[import](example/node_modules/my-logger/index.js) The `package.json` file for this package would typically look like this: -```json -{ - "name": "my-logger", - "version": "1.0.0", - "type": "module", - "scripts": { - "test": "node --test" - }, - "exports": { - ".": "./index.js", - "./package.json": "./package.json" - }, - "files": [ - "lib/", - "src/", - "index.js" - ] -} -``` +`package.json`: + +[import](example/node_modules/my-logger/package.json) When this package is published, because `test` is not listed in `files`, this directory will be excluded in the published package. Here we use `npm pack` to verify it (though this convention is generally respected by most package managers): @@ -74,26 +52,14 @@ package/package.json In addition, when users load this package, they can only access `index.js` and `package.json`, but not the internal files. This allows maintainers to change the internal structure of the package without breaking users who may come to assume the internal files are part of the public API. -```js -// app.mjs -import { Logger } from 'my-logger'; -const logger = new Logger('debug'); -logger.error('This is an error message'); +`app.mjs`: -// This will throw ERR_PACKAGE_PATH_NOT_EXPORTED -await import('my-logger/lib/utils.js'); -``` +[import](example/app.mjs) This works similarly for CommonJS consumers (since the package is ESM, they will need to use Node.js 20 or above to load it from `require()`): -```js -// app.cjs -const { Logger } = require('my-logger'); -const logger = new Logger('debug'); -logger.error('This is an error message'); +`app.cjs`: -// This will throw ERR_PACKAGE_PATH_NOT_EXPORTED -require('my-logger/lib/utils.js'); -``` +[import:'doc'](example/app.cjs) You can find an example of this package on [GitHub](https://github.com/nodejs/package-examples/tree/main/guide/03-multi-file-package/example). diff --git a/guide/03-multi-file-package/example/app.cjs b/guide/03-multi-file-package/example/app.cjs index 7ad9775..06a5338 100644 --- a/guide/03-multi-file-package/example/app.cjs +++ b/guide/03-multi-file-package/example/app.cjs @@ -1,5 +1,14 @@ 'use strict'; +//! [doc] const { Logger } = require('my-logger'); const logger = new Logger('debug'); logger.error('This is an error message'); + +try { + // This will throw ERR_PACKAGE_PATH_NOT_EXPORTED + require('my-logger/lib/utils.js'); +} catch (e) { + console.error(e.code, e.message); +} +//! [doc] diff --git a/guide/03-multi-file-package/example/app.mjs b/guide/03-multi-file-package/example/app.mjs index 17f84d7..b08a850 100644 --- a/guide/03-multi-file-package/example/app.mjs +++ b/guide/03-multi-file-package/example/app.mjs @@ -1,3 +1,10 @@ import { Logger } from 'my-logger'; const logger = new Logger('debug'); logger.error('This is an error message'); + +try { + // This will throw ERR_PACKAGE_PATH_NOT_EXPORTED + await import('my-logger/lib/utils.js'); +} catch (e) { + console.error(e.code, e.message); +} diff --git a/guide/03-multi-file-package/example/node_modules/my-logger/lib/utils.js b/guide/03-multi-file-package/example/node_modules/my-logger/lib/utils.js index 685e427..62a6422 100644 --- a/guide/03-multi-file-package/example/node_modules/my-logger/lib/utils.js +++ b/guide/03-multi-file-package/example/node_modules/my-logger/lib/utils.js @@ -1,6 +1,12 @@ + +//! [abbrev] export const LEVELS = { + /* ... log levels ... */ +//! [abbrev] error: 0, warn: 1, info: 2, debug: 3 +//! [abbrev_end] }; +//! [abbrev_end] diff --git a/guide/03-multi-file-package/example/node_modules/my-logger/src/logger.js b/guide/03-multi-file-package/example/node_modules/my-logger/src/logger.js index 98ec610..55aad03 100644 --- a/guide/03-multi-file-package/example/node_modules/my-logger/src/logger.js +++ b/guide/03-multi-file-package/example/node_modules/my-logger/src/logger.js @@ -1,6 +1,9 @@ -import { LEVELS } from '../lib/utils.js'; +//! [logger_start] +import { LEVELS } from '../lib/utils.js'; export class Logger { + // ... Logger implementation +//! [logger_start] constructor(level = 'info') { this.level = LEVELS[level]; } @@ -18,4 +21,6 @@ export class Logger { warn(msg) { this.print('warn', msg); } info(msg) { this.print('info', msg); } debug(msg) { this.print('debug', msg); } -} +//! [logger_end] +}; +//! [logger_end] diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/README.md b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/README.md index 9911d71..4194f84 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/README.md +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/README.md @@ -14,29 +14,21 @@ When a CommonJS module is imported into an ESM module, its entire `module.export For example, consider a CommonJS package named `my-logger` that exports a `Logger` class: -```js -// node_modules/my-logger/index.js -class Logger { - // ... Logger implementation -}; -const defaultLogger = new Logger('debug'); -// This is equivalent to `export default defaultLogger` in ESM -module.exports = defaultLogger; -``` +`node_modules/my-logger/index.js`: + +[import:'logger_start,logger_end'](node_modules/my-logger/index.js) The `module.exports` object is returned to a CommonJS consumer using `require()`: -```js -// app.cjs -const defaultLogger = require('my-logger'); // Returns the `module.exports` object -``` +`app.cjs`: + +[import:'doc'](app.cjs) And to an ESM consumer using the [default import syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#default_import): -```js -// app-default-export.mjs -import defaultLogger from 'my-logger'; -``` +`app-default-export.mjs`: + +[import:'doc'](app-default-export.mjs) ### Dynamic `import()` and namespace import @@ -44,21 +36,15 @@ Unlike `require()`, which returns the `module.exports` object directly, the ECMA Node.js maps `module.exports` to the ESM `default` export. With dynamic `import()`, `module.exports` is available as the `default` property on the namespace object: -```js -// app-dynamic-import.mjs -// When dynamic `import()` is used, get the default export from the `default` property. -// By contrast, `require('my-logger')` returns `module.exports` directly. -const { default: defaultLogger } = await import('my-logger'); -``` +`app-dynamic-import.mjs`: + +[import:'doc'](app-dynamic-import.mjs) The same applies to the [namespace import syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#namespace_import): -```js -// app-namespace-import.mjs -import * as namespace from 'my-logger'; -// The `module.exports` object is only accessible via the `default` property. -const { default: defaultLogger } = namespace; -``` +`app-namespace-import.mjs`: + +[import:'doc'](app-namespace-import.mjs) ## Named imports from CommonJS in ESM @@ -66,126 +52,65 @@ In Node.js, a CommonJS module's exports can also be imported by name in ESM, if The ESM specification requires named imports to be checked. If a module imports an export by name from another module, but the providing module does not export that name, an error must be thrown before the code is executed. -```js -// app-import-non-existent-name.mjs -// Throws a SyntaxError before any code in my-logger or in this script is executed -import { DoesNotExist } from 'my-logger'; -``` +`app-import-non-existent-name.mjs`: + +[import](app-import-non-existent-name.mjs) Node.js statically analyzes CommonJS to detect export names. Because CommonJS exports can be dynamic, detection only works for common, static patterns. For example, assignments to `exports` with static names can be detected: -```js -// node_modules/my-logger-exports-assignment/index.js -class Logger { - // ... Logger implementation -}; +`node_modules/my-logger-exports-assignment/index.js`: -// This can be thought of as `export { Logger as Logger }` in ESM -exports.Logger = Logger; -// This can be thought of as `export { Logger as LoggerAlias }` in ESM -exports.LoggerAlias = Logger; -``` +[import:'logger_start,logger_end'](node_modules/my-logger-exports-assignment/index.js) An ESM consumer can load these exports by name: -```js -// app-import-exports-assignment.mjs -// The detected properties on the `module.exports` object from the -// CommonJS provider can be imported by name. -import { Logger, LoggerAlias } from 'my-logger-exports-assignment'; -``` +`app-import-exports-assignment.mjs`: + +[import:'doc'](app-import-exports-assignment.mjs) Assignment to `module.exports` followed by adding properties to it can also be detected: -```js -// node_modules/my-logger-module-exports-assignment/index.js -class Logger { - // ... Logger implementation -}; -const defaultLogger = new Logger('debug'); -// This can be thought of as: -// `export { defaultLogger as default, Logger, Logger as LoggerAlias }` -// in ESM. -module.exports = defaultLogger; -module.exports.Logger = Logger; -module.exports.LoggerAlias = Logger; -``` +`node_modules/my-logger-module-exports-assignment/index.js`: + +[import:'logger_start,logger_end'](node_modules/my-logger-module-exports-assignment/index.js) An ESM consumer can load these exports by name: -```js -// app-import-module-exports-assignment.mjs -// The detected properties on the `module.exports` object from the -// CommonJS provider can be imported by name. -import { Logger, LoggerAlias } from 'my-logger-module-exports-assignment'; -// The `module.exports` object from the CommonJS provider can be -// imported as if it's the default export. -import defaultLogger from 'my-logger-module-exports-assignment'; -``` +`app-import-module-exports-assignment.mjs`: + +[import:'doc'](app-import-module-exports-assignment.mjs) Or destructure after using namespace import: -```js -// app-namespace-named-exports.mjs -// The detected named exports on the `module.exports` object are properties on the -// module namespace object, while the `module.exports` object is in -// a property named `default`. -import * as namespace from 'my-logger-module-exports-assignment'; -const { Logger, LoggerAlias } = namespace; -const { default: defaultLogger } = namespace; -``` +`app-namespace-named-exports.mjs`: -Or with dynamic `import()`: +[import:'doc'](app-namespace-named-exports.mjs) -```js -// app-dynamic-named-exports.mjs -const namespace = await import('my-logger-module-exports-assignment'); -const { Logger, LoggerAlias } = namespace; -const { default: defaultLogger } = namespace; -``` +Or with dynamic `import()`: -Reassigned `module.exports` can be trickier. If reassigned to an object literal, its static properties are still available as named exports: +`app-dynamic-named-exports.mjs`: -```js -// node_modules/my-logger-object-literal/index.js -class Logger { - // ... Logger implementation -}; -module.exports = { - Logger, // This can be detected as a named export -}; -``` +[import:'doc'](app-dynamic-named-exports.mjs) -Dynamic naming defeats static detection. For example: +Reassigned `module.exports` can be trickier. If reassigned to an object literal, its static properties are still available as named exports: -```js -// node_modules/my-logger-dynamic/index.js -class Logger { - // ... Logger implementation -}; +`node_modules/my-logger-object-literal/index.js`: -module['exports'] = { Logger }; // ❌ non-dot access to module.exports +[import:'logger_start,logger_end'](node_modules/my-logger-object-literal/index.js) -const e = module.exports; -e.Logger = Logger; // ❌ assignment via an alias +Dynamic naming defeats static detection. For example: -const key = 'Logger'; -module.exports[key] = Logger; // ❌ assignment via a computed property +`node_modules/my-logger-dynamic/index.js`: -Object.defineProperty(module.exports, 'Logger', { // ❌ assignment via a property descriptor - enumerable: true, - get: () => Logger, -}); -``` +[import:'logger_start,logger_end'](node_modules/my-logger-dynamic/index.js) These cannot be imported by name in ESM. For example: -```js -// app-dynamic-fail.mjs -import { Logger } from 'my-logger-dynamic'; -``` +`app-dynamic-fail.mjs`: + +[import:'doc'](app-dynamic-fail.mjs) This throws: diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-default-export.mjs b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-default-export.mjs index 7690db1..68fc021 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-default-export.mjs +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-default-export.mjs @@ -1,2 +1,5 @@ + +//! [doc] import defaultLogger from 'my-logger'; +//! [doc] defaultLogger.log('Hello from ESM'); diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-dynamic-fail.mjs b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-dynamic-fail.mjs index 4146ca3..7db46f6 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-dynamic-fail.mjs +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-dynamic-fail.mjs @@ -1,2 +1,4 @@ // This should fail because Logger cannot be detected as a named export +//! [doc] import { Logger } from 'my-logger-dynamic'; +//! [doc] diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-dynamic-import.mjs b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-dynamic-import.mjs index a6fcd6f..9770a09 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-dynamic-import.mjs +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-dynamic-import.mjs @@ -1,3 +1,7 @@ -// Using dynamic import + +//! [doc] +// When dynamic `import()` is used, get the default export from the `default` property. +// By contrast, `require('my-logger')` returns `module.exports` directly. const { default: defaultLogger } = await import('my-logger'); +//! [doc] defaultLogger.log('Hello from dynamic import'); diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-dynamic-named-exports.mjs b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-dynamic-named-exports.mjs index bcf859e..70516fd 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-dynamic-named-exports.mjs +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-dynamic-named-exports.mjs @@ -1,6 +1,9 @@ + +//! [doc] const namespace = await import('my-logger-module-exports-assignment'); const { Logger, LoggerAlias } = namespace; const { default: defaultLogger } = namespace; +//! [doc] defaultLogger.log('Hello from dynamic import with named exports'); diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-import-exports-assignment.mjs b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-import-exports-assignment.mjs index d8fae53..71776bf 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-import-exports-assignment.mjs +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-import-exports-assignment.mjs @@ -1,6 +1,9 @@ + +//! [doc] // The detected properties on the `module.exports` object from the // CommonJS provider can be imported by name. import { Logger, LoggerAlias } from 'my-logger-exports-assignment'; +//! [doc] const logger = new Logger('info'); logger.log('Hello from Logger'); diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-import-module-exports-assignment.mjs b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-import-module-exports-assignment.mjs index 8bd4474..0c27ffe 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-import-module-exports-assignment.mjs +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-import-module-exports-assignment.mjs @@ -1,9 +1,12 @@ + +//! [doc] // The detected properties on the `module.exports` object from the // CommonJS provider can be imported by name. import { Logger, LoggerAlias } from 'my-logger-module-exports-assignment'; // The `module.exports` object from the CommonJS provider can be // imported as if it's the default export. import defaultLogger from 'my-logger-module-exports-assignment'; +//! [doc] defaultLogger.log('Hello from default logger'); diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-import-non-existent-name.mjs b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-import-non-existent-name.mjs new file mode 100644 index 0000000..f055e43 --- /dev/null +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-import-non-existent-name.mjs @@ -0,0 +1,2 @@ +// Throws a SyntaxError before any code in my-logger or in this script is executed +import { DoesNotExist } from 'my-logger'; diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-namespace-import.mjs b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-namespace-import.mjs index 52d77c5..7a4ad6e 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-namespace-import.mjs +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-namespace-import.mjs @@ -1,4 +1,7 @@ -// Using namespace import + +//! [doc] import * as namespace from 'my-logger'; +// The `module.exports` object is only accessible via the `default` property. const { default: defaultLogger } = namespace; +//! [doc] defaultLogger.log('Hello from namespace import'); diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-namespace-named-exports.mjs b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-namespace-named-exports.mjs index 9ff18dc..d4f6791 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-namespace-named-exports.mjs +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app-namespace-named-exports.mjs @@ -1,9 +1,12 @@ + +//! [doc] // The detected named exports on the `module.exports` object are properties on the // module namespace object, while the `module.exports` object is in // a property named `default`. import * as namespace from 'my-logger-module-exports-assignment'; const { Logger, LoggerAlias } = namespace; const { default: defaultLogger } = namespace; +//! [doc] defaultLogger.log('Hello from namespace with named exports'); diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app.cjs b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app.cjs index 2f2a546..6c6a561 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app.cjs +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/app.cjs @@ -1,2 +1,5 @@ + +//! [doc] const defaultLogger = require('my-logger'); // Returns the `module.exports` object +//! [doc] defaultLogger.log('Hello from CommonJS'); diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-dynamic/index.js b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-dynamic/index.js index b2e0d61..1b696ce 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-dynamic/index.js +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-dynamic/index.js @@ -1,4 +1,8 @@ + +//! [logger_start] class Logger { + // ... Logger implementation +//! [logger_start] constructor(level) { this.level = level; } @@ -6,7 +10,8 @@ class Logger { log(message) { console.log(`[${this.level}] ${message}`); } -} +//! [logger_end] +}; module['exports'] = { Logger }; // ❌ non-dot access to module.exports @@ -20,3 +25,4 @@ Object.defineProperty(module.exports, 'Logger', { // ❌ assignment via a prope enumerable: true, get: () => Logger, }); +//! [logger_end] diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-exports-assignment/index.js b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-exports-assignment/index.js index c16cee9..f5e3108 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-exports-assignment/index.js +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-exports-assignment/index.js @@ -1,4 +1,8 @@ + +//! [logger_start] class Logger { + // ... Logger implementation +//! [logger_start] constructor(level) { this.level = level; } @@ -6,9 +10,11 @@ class Logger { log(message) { console.log(`[${this.level}] ${message}`); } -} +//! [logger_end] +}; -// This can be thought of as if we are doing `export { Logger as Logger }` in ESM +// This can be thought of as `export { Logger as Logger }` in ESM exports.Logger = Logger; -// This can be thought of as if we are doing `export { Logger as LoggerAlias }` in ESM +// This can be thought of as `export { Logger as LoggerAlias }` in ESM exports.LoggerAlias = Logger; +//! [logger_end] diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-module-exports-assignment/index.js b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-module-exports-assignment/index.js index 676ebf2..9f8000c 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-module-exports-assignment/index.js +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-module-exports-assignment/index.js @@ -1,4 +1,8 @@ + +//! [logger_start] class Logger { + // ... Logger implementation +//! [logger_start] constructor(level) { this.level = level; } @@ -6,14 +10,13 @@ class Logger { log(message) { console.log(`[${this.level}] ${message}`); } -} - +//! [logger_end] +}; const defaultLogger = new Logger('debug'); -// Caveat: if module.exports is assigned, named exports in the form of -// exports.foo = bar; won't be detected. -// This can be thought of as if we are doing: +// This can be thought of as: // `export { defaultLogger as default, Logger, Logger as LoggerAlias }` // in ESM. module.exports = defaultLogger; module.exports.Logger = Logger; module.exports.LoggerAlias = Logger; +//! [logger_end] diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-object-literal/index.js b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-object-literal/index.js index 7b3b90b..e038194 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-object-literal/index.js +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger-object-literal/index.js @@ -1,4 +1,8 @@ + +//! [logger_start] class Logger { + // ... Logger implementation +//! [logger_start] constructor(level) { this.level = level; } @@ -6,8 +10,9 @@ class Logger { log(message) { console.log(`[${this.level}] ${message}`); } -} - +//! [logger_end] +}; module.exports = { Logger, // This can be detected as a named export }; +//! [logger_end] diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger/index.js b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger/index.js index b8fba25..1b14701 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger/index.js +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/node_modules/my-logger/index.js @@ -1,4 +1,8 @@ + +//! [logger_start] class Logger { + // ... Logger implementation +//! [logger_start] constructor(level) { this.level = level; } @@ -6,8 +10,9 @@ class Logger { log(message) { console.log(`[${this.level}] ${message}`); } -} - +//! [logger_end] +}; const defaultLogger = new Logger('debug'); // This is equivalent to `export default defaultLogger` in ESM module.exports = defaultLogger; +//! [logger_end] diff --git a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/test.mjs b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/test.mjs index b0dc682..6b8dfa3 100644 --- a/guide/04-cjs-esm-interop/shipping-cjs-for-esm/test.mjs +++ b/guide/04-cjs-esm-interop/shipping-cjs-for-esm/test.mjs @@ -138,4 +138,17 @@ describe('shipping-cjs-for-esm', () => { assert.notStrictEqual(result.status, 0); assert.match(result.stderr, /SyntaxError: Named export 'Logger' not found/); }); + + it('should fail when importing non-existent named export from my-logger', () => { + const result = spawnSync( + process.execPath, + ['app-import-non-existent-name.mjs'], + { + cwd: import.meta.dirname, + encoding: 'utf-8', + }, + ); + assert.notStrictEqual(result.status, 0); + assert.match(result.stderr, /SyntaxError: Named export 'DoesNotExist' not found/); + }); }); diff --git a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/README.md b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/README.md index a9d5b96..2133620 100644 --- a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/README.md +++ b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/README.md @@ -14,46 +14,21 @@ Examples in this chapter can be found [here](https://github.com/nodejs/package-e Consider the following ESM provider: -```js -// node_modules/my-logger/index.js -export class Logger { - // ... Logger implementation -}; -const logger = new Logger('debug'); -export default logger; -``` - -Dynamic `import()` returns the module namespace object: +`node_modules/my-logger/index.js`: -```js -// app.mjs -// The namespace object looks like { Logger: [class Logger], default: Logger {} } -const namespace = await import('my-logger'); +[import:'logger_start,logger_end'](node_modules/my-logger/index.js) -// The named export `Logger` is available by name on the namespace object. -const { Logger } = namespace; +Dynamic `import()` returns the module namespace object: -// The default export logger instance is available as the `default` property. -const { default: logger } = namespace; +`app.mjs`: -console.log(logger instanceof Logger); // true -``` +[import:'doc'](app.mjs) Likewise, `require()` returns the module namespace object: -```js -// app.cjs -// The namespace object looks like { Logger: [class Logger], default: Logger {} } -const namespace = require('my-logger'); - -// The named export `Logger` is available by name on the namespace object. -const { Logger } = namespace; +`app.cjs`: -// The default export logger instance is available as the `default` property. -const { default: logger } = namespace; - -console.log(logger instanceof Logger); // true -``` +[import:'doc'](app.cjs) ### Customize `require(esm)` via the `'module.exports'` export @@ -61,64 +36,44 @@ If the ESM has an export named `'module.exports'`, `require(esm)` returns that v Let's revisit the logger example and add a named export `'module.exports'` for the default logger instance: -```js -// node_modules/my-logger-with-default-export/index.js -export class Logger { - // ... Logger implementation -}; -const logger = new Logger('debug'); -export default logger; - -// Exposes the Logger class as a destructurable property on the default export. -logger.Logger = Logger; -export { logger as 'module.exports' }; -``` +`node_modules/my-logger-with-default-export/index.js`: + +[import:'logger_start,logger_end'](node_modules/my-logger-with-default-export/index.js) ESM consumers will see that the results from static or dynamic import are unchanged, but for CommonJS consumers, `require()` now returns the default `logger` instance, as specified by the `'module.exports'` export: -```js -// app-with-default-export.cjs -const { Logger } = require('my-logger-with-default-export'); // [class Logger] -// Logger {} - no need to destructure from `default` -const logger = require('my-logger-with-default-export'); -``` +`app-with-default-export.cjs`: + +[import:'doc'](app-with-default-export.cjs) This aligns CommonJS usage with the ESM equivalent: -```js -// app-with-default-export.mjs -import { Logger } from 'my-logger-with-default-export'; // [class Logger] -import logger from 'my-logger-with-default-export'; // Logger {} -``` +`app-with-default-export.mjs`: + +[import:'doc'](app-with-default-export.mjs) ## Limitations When shipping ESM for CommonJS via `require(esm)`, note: 1. **Synchronous graph only**: `require(esm)` is synchronous; When the ESM loaded by `require()` or any of its dependencies contain top‑level `await`, an [`ERR_REQUIRE_ASYNC_MODULE`](https://nodejs.org/api/errors.html#err_require_async_module) is thrown. -2. **No ESM↔CJS cycles**: To maintain [ECMAScript invariants](https://tc39.es/ecma262/#sec-innermoduleevaluation), cycles that cross the CommonJS/ESM boundary are unsupported and throw [`ERR_REQUIRE_CYCLE_MODULE`](https://nodejs.org/api/errors.html#err_require_cycle_module). +2. **No ESM <-> CJS cycles**: To maintain [ECMAScript invariants](https://tc39.es/ecma262/#sec-innermoduleevaluation), cycles that cross the CommonJS/ESM boundary are unsupported and throw [`ERR_REQUIRE_CYCLE_MODULE`](https://nodejs.org/api/errors.html#err_require_cycle_module). - ```js - // cycle-error/a.mjs - import './b.cjs'; - ``` +`cycle-error/a.mjs`: - ```js - // cycle-error/b.cjs - require('./a.mjs'); // Throws ERR_REQUIRE_CYCLE_MODULE - ``` +[import](cycle-error/a.mjs) - A typical workaround would be loading one side lazily (not at module load time). +`cycle-error/b.cjs`: - ```js - // cycle-lazy/b.cjs - let a; - function runOnlyWhenUsed() { - a = require('./a.mjs'); - } - ``` +[import](cycle-error/b.cjs) + +A typical workaround would be loading one side lazily (not at module load time). + +`cycle-lazy/b.cjs`: - Only cross‑boundary cycles fail; pure CommonJS or pure ESM cycles still work. +[import:'doc'](cycle-lazy/b.cjs) + +Only cross‑boundary cycles fail; pure CommonJS or pure ESM cycles still work. ## `require(esm)` feature detection @@ -128,24 +83,9 @@ When shipping ESM for CommonJS via `require(esm)`, note: In code, use [`process.features.require_module`](https://nodejs.org/api/process.html#processfeaturesrequire_module) to detect support. This helps when an ESM dependency is optional and the consumer code must be synchronous. -```js -// log-with-feature-detection.js -// Using the .js suffix for cross-environment support. -let logger; -// Suppose this function has to be synchronous for some reason. -function logError(msg) { - // If loaded as CJS by Node.js versions where require(esm) is supported, enhance it. - if (globalThis?.process?.features?.require_module) { - logger ??= new (require('my-logger').Logger)('error'); - logger.log(msg); - } else { - // In older Node.js versions, or in other environments like browsers, - // fall back to something less fancy. - console.error(msg); - } -} -logError('An error occurred'); -``` +`log-with-feature-detection.js`: + +[import](log-with-feature-detection.js) ## Useful `package.json` fields diff --git a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app-with-default-export.cjs b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app-with-default-export.cjs index 39b5e5f..49dcb79 100644 --- a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app-with-default-export.cjs +++ b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app-with-default-export.cjs @@ -1,6 +1,9 @@ + +//! [doc] const { Logger } = require('my-logger-with-default-export'); // [class Logger] -// Logger {} - no need to destruct from `default` +// Logger {} - no need to destructure from `default` const logger = require('my-logger-with-default-export'); +//! [doc] console.log(logger instanceof Logger); // true logger.log('Hello from CommonJS with module.exports'); diff --git a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app-with-default-export.mjs b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app-with-default-export.mjs index b6b4ed8..63d5dc3 100644 --- a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app-with-default-export.mjs +++ b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app-with-default-export.mjs @@ -1,5 +1,8 @@ + +//! [doc] import { Logger } from 'my-logger-with-default-export'; // [class Logger] import logger from 'my-logger-with-default-export'; // Logger {} +//! [doc] console.log(logger instanceof Logger); // true logger.log('Hello from ESM with module.exports'); diff --git a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app.cjs b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app.cjs index 731c608..40e92f5 100644 --- a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app.cjs +++ b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app.cjs @@ -1,3 +1,5 @@ + +//! [doc] // The namespace object looks like { Logger: [class Logger], default: Logger {} } const namespace = require('my-logger'); @@ -8,4 +10,5 @@ const { Logger } = namespace; const { default: logger } = namespace; console.log(logger instanceof Logger); // true +//! [doc] logger.log('Hello from CommonJS requiring ESM'); diff --git a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app.mjs b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app.mjs index 3782e93..8fed98d 100644 --- a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app.mjs +++ b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/app.mjs @@ -1,3 +1,5 @@ + +//! [doc] // The namespace object looks like { Logger: [class Logger], default: Logger {} } const namespace = await import('my-logger'); @@ -8,4 +10,5 @@ const { Logger } = namespace; const { default: logger } = namespace; console.log(logger instanceof Logger); // true +//! [doc] logger.log('Hello from ESM dynamic import'); diff --git a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/cycle-lazy/b.cjs b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/cycle-lazy/b.cjs index f79bd59..0fdc724 100644 --- a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/cycle-lazy/b.cjs +++ b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/cycle-lazy/b.cjs @@ -1,6 +1,9 @@ + +//! [doc] let a; function runOnlyWhenUsed() { a = require('./a.mjs'); +//! [doc] console.log('Loaded lazily:', a.getValue()); } diff --git a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/node_modules/my-logger-with-default-export/index.js b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/node_modules/my-logger-with-default-export/index.js index ea3372b..6ec4747 100644 --- a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/node_modules/my-logger-with-default-export/index.js +++ b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/node_modules/my-logger-with-default-export/index.js @@ -1,4 +1,8 @@ + +//! [logger_start] export class Logger { + // ... Logger implementation +//! [logger_start] constructor(level) { this.level = level; } @@ -6,11 +10,12 @@ export class Logger { log(message) { console.log(`[${this.level}] ${message}`); } -} - +//! [logger_end] +}; const logger = new Logger('debug'); export default logger; // Exposes the Logger class as a destructurable property on the default export. logger.Logger = Logger; export { logger as 'module.exports' }; +//! [logger_end] diff --git a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/node_modules/my-logger/index.js b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/node_modules/my-logger/index.js index ba752dc..4366bb1 100644 --- a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/node_modules/my-logger/index.js +++ b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/node_modules/my-logger/index.js @@ -1,4 +1,8 @@ + +//! [logger_start] export class Logger { + // ... Logger implementation +//! [logger_start] constructor(level) { this.level = level; } @@ -6,7 +10,8 @@ export class Logger { log(message) { console.log(`[${this.level}] ${message}`); } -} - +//! [logger_end] +}; const logger = new Logger('debug'); export default logger; +//! [logger_end] diff --git a/guide/05-cjs-esm-migration/migrating-exports/README.md b/guide/05-cjs-esm-migration/migrating-exports/README.md index 65b2253..8add1d3 100644 --- a/guide/05-cjs-esm-migration/migrating-exports/README.md +++ b/guide/05-cjs-esm-migration/migrating-exports/README.md @@ -21,67 +21,53 @@ In CommonJS, exports are typically done by writing to [the `module.exports` obje Static property assignments to `exports` or `module.exports` can be directly translated to [named exports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export#using_named_exports) in ESM. For example, if a CommonJS module contains: -```js -// before/node_modules/my-module/named-only.js -class Foo { /* ... */ } +`before/node_modules/my-module/named-only.js`: -exports.Foo = Foo; -exports.bar = 'bar'; -``` +[import:'main'](before/node_modules/my-module/named-only.js) This can be migrated to direct `export` statements: -```js -// after/node_modules/my-module/named-only.js -export class Foo { /* ... */ }; -export const bar = 'bar'; -``` +`after/node_modules/my-module/named-only.js`: + +[import:'main'](after/node_modules/my-module/named-only.js) Aliases can be migrated using the `export ... as ...` syntax: -```js -// before/node_modules/my-module/named-only.js -exports.FooAlias = Foo; -``` +`before/node_modules/my-module/named-only.js`: + +[import:'alias'](before/node_modules/my-module/named-only.js) -```js -// after/node_modules/my-module/named-only.js -export { Foo as FooAlias }; -``` +`after/node_modules/my-module/named-only.js`: + +[import:'alias'](after/node_modules/my-module/named-only.js) ### Migrating `module.exports = { foo, ... }` Some CommonJS modules provide named exports by reassigning `module.exports` to an object literal with static value properties. This can be migrated with the `export { ... }` syntax. For example: -```js -// before/node_modules/my-module/named-only-object-literal.js -class Baz { /* ... */ } -module.exports = { Baz }; -``` +`before/node_modules/my-module/named-only-object-literal.js`: + +[import](before/node_modules/my-module/named-only-object-literal.js) can be migrated to: -```js -// after/node_modules/my-module/named-only-object-literal.js -class Baz { /* ... */ } -export { Baz }; -``` +`after/node_modules/my-module/named-only-object-literal.js`: + +[import](after/node_modules/my-module/named-only-object-literal.js) ### Migrating `module.exports = notAnObjectLiteral` If `module.exports` is set to a value that is not an object literal, e.g. a function or a class, use the [`export default` syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export#using_the_default_export). For example: -```js -// before/node_modules/my-module/default-export.js -module.exports = function qux() {}; -``` +`before/node_modules/my-module/default-export.js`: + +[import](before/node_modules/my-module/default-export.js) can be migrated to ESM as follows: -```js -// after/node_modules/my-module/default-export.js -export default function qux() {}; -``` +`after/node_modules/my-module/default-export.js`: + +[import:'main'](after/node_modules/my-module/default-export.js) Note: in this case, additional care must be taken to if backward compatibilty for CommonJS consumers is needed. See the [maintaining backward compatibility section](#for-commonjs-consumers-if-moduleexports-was-reassigned-to-a-non-object-literal) for details. @@ -91,74 +77,57 @@ CommonJS modules can re-export from other modules by assigning properties from t For example, this CommonJS module: -```js -// before/node_modules/my-module/re-export-defaults.js -exports.foo = require('./foo.js'); -exports.bar = require('./bar.js'); -``` +`before/node_modules/my-module/re-export-defaults.js`: + +[import](before/node_modules/my-module/re-export-defaults.js) can be migrated to ESM like this: -```js -// after/node_modules/my-module/re-export-defaults.js -export { default as foo } from './foo.js'; -export { default as bar } from './bar.js'; -``` +`after/node_modules/my-module/re-export-defaults.js`: + +[import](after/node_modules/my-module/re-export-defaults.js) ### Re-exporting named exports from internal modules CommonJS modules can re-export selected named exports from another module: -```js -// before/node_modules/my-module/re-export-names.js -const { name1, name2 } = require('./names.js'); -exports.name1 = name1; -exports.name2 = name2; -``` +`before/node_modules/my-module/re-export-names.js`: + +[import](before/node_modules/my-module/re-export-names.js) this can be migrated to ESM with [`export ... from` statements](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export#using_export_from) too: -```js -// after/node_modules/my-module/re-export-names.js -export { name1, name2 } from './names.js'; -``` +`after/node_modules/my-module/re-export-names.js`: + +[import](after/node_modules/my-module/re-export-names.js) ### Re-exporting all exports from internal modules CommonJS modules may re-export all exports from another module: -```js -// before/node_modules/my-module/re-export-all.js -module.exports = require('./named-only-object-literal.js'); -``` +`before/node_modules/my-module/re-export-all.js`: + +[import](before/node_modules/my-module/re-export-all.js) To migrate to ESM, use `export * from` for named exports and add `export { default } from` for the default export omitted by `export * from`: -```js -// after/node_modules/my-module/re-export-all.js -export * from './named-only-object-literal.js'; -export { default } from './named-only-object-literal.js'; -``` +`after/node_modules/my-module/re-export-all.js`: + +[import](after/node_modules/my-module/re-export-all.js) ### Aggregating named exports from multiple modules If the CommonJS module aggregates exports from multiple internal modules: -```js -// before/node_modules/my-module/re-export-aggregate.js -module.exports = { - ...require('./debug-names.js'), - ...require('./log-names.js'), -}; -``` +`before/node_modules/my-module/re-export-aggregate.js`: + +[import](before/node_modules/my-module/re-export-aggregate.js) it can be migrated to ESM like this: -```js -// after/node_modules/my-module/re-export-aggregate.js -export * from './debug-names.js'; -export * from './log-names.js'; -``` +`after/node_modules/my-module/re-export-aggregate.js`: + +[import](after/node_modules/my-module/re-export-aggregate.js) ## Maintaining backward compatibility @@ -170,48 +139,31 @@ To maintain compatibility for ESM consumers, ESM migrated from CommonJS should a Consider the following CommonJS module: -```js -// before/node_modules/my-module/named-only.js -class Foo { /* ... */ } +`before/node_modules/my-module/named-only.js`: -exports.Foo = Foo; -exports.bar = 'bar'; -exports.FooAlias = Foo; -``` +[import:'main,alias'](before/node_modules/my-module/named-only.js) If only convert the named exports: -```js -// after/node_modules/my-module/named-only-partial.js -export class Foo { /* ... */ }; -export const bar = 'bar'; -export { Foo as FooAlias }; -``` +`after/node_modules/my-module/named-only-partial.js`: + +[import](after/node_modules/my-module/named-only-partial.js) the default export would be missing after migration, which would break ESM consumers that have been using the automatically added default export from the CommonJS external interface:: -```js -// after/app-importing-default-from-named-only-partial.mjs -// This used to be `module.exports` when the module was CommonJS, -// but after the migration, the default export is missing unless explicitly provided, -// so it would throw a SyntaxError. -import myModule from 'my-module/named-only-partial'; -``` +`after/app-importing-default-from-named-only-partial.mjs`: + +[import](after/app-importing-default-from-named-only-partial.mjs) To close this gap, provide a default export in the migrated ESM, which typically aggregates the named exports: -```js -// after/node_modules/my-module/named-only.js -export class Foo { /* ... */ }; -export const bar = 'bar'; -export { Foo as FooAlias }; -export default { Foo, bar, FooAlias: Foo }; // To be backward compatible with ESM consumers. -``` +`after/node_modules/my-module/named-only.js`: + +[import:'main,alias,compat'](after/node_modules/my-module/named-only.js) -```js -// after/app-importing-default-from-named-only.mjs -import myModule from 'my-module/named-only'; -``` +`after/app-importing-default-from-named-only.mjs`: + +[import](after/app-importing-default-from-named-only.mjs) ### If `module.exports` was reassigned to a non-object-literal @@ -219,47 +171,37 @@ If the CommonJS module previously reassigned `module.exports` to a value, unless For example, if the CommonJS module contained: -```js -// before/node_modules/my-module/default-export.js -module.exports = function qux() {}; -``` +`before/node_modules/my-module/default-export.js`: + +[import](before/node_modules/my-module/default-export.js) as mentioned before, this is typically migrated to ESM like below. -```js -// after/node_modules/my-module/default-export-partial.js -export default function qux() {}; -``` +`after/node_modules/my-module/default-export-partial.js`: + +[import](after/node_modules/my-module/default-export-partial.js) This is done so that ESM consumers can continue importing the function as the default export: -```js -// after/app-importing-default-export.mjs -import qux from 'my-module/default-export-partial'; // so this continues to work. -``` +`after/app-importing-default-export.mjs`: + +[import](after/app-importing-default-export.mjs) However, as discussed in [the ESM interoperability guide](../../04-cjs-esm-interop/shipping-esm-for-cjs/README.md#how-esm-exports-map-to-requireesm), per the ESM specification, the default export of that ESM would only be available as the `'default'` property on the module namespace object, which is returned by `require(esm)` directly by default: -```js -// after/app-requiring-default-export-partial.cjs -// The returned value is actually { default: [Function: qux] } -const qux = require('my-module/default-export-partial'); -qux(); // ⛔ Throws TypeError: qux is not a function -``` +`after/app-requiring-default-export-partial.cjs`: + +[import](after/app-requiring-default-export-partial.cjs) To address this disparity, Node.js recognizes a special `'module.exports'` named export for ESM. When provided, `require(esm)` returns its value directly instead of the module namespace object. -```js -// after/node_modules/my-module/default-export.js -export default function qux() {}; -export { qux as 'module.exports' }; // To be backward compatible with CommonJS consumers. -``` +`after/node_modules/my-module/default-export.js`: + +[import:'main,compat'](after/node_modules/my-module/default-export.js) -```js -// after/app-requiring-default-export.cjs -const qux = require('my-module/default-export'); // Returns the 'module.exports' named export. -qux(); // Now it works as expected. -``` +`after/app-requiring-default-export.cjs`: + +[import](after/app-requiring-default-export.cjs) ## Dynamic exports @@ -269,53 +211,22 @@ One typical approximation is to use a static export shape with `undefined` place For example, this CommonJS pattern: -```js -// before/node_modules/my-module/dynamic-exports.js -exports.initialize = function(type) { - if (type === 'foo') { - exports.foo = function() { /* ... */ }; - } else { - exports.bar = function() { /* ... */ }; - } -}; -// foo and bar are only added as exports when initialized. -``` +`before/node_modules/my-module/dynamic-exports.js`: + +[import](before/node_modules/my-module/dynamic-exports.js) cannot be directly migrated to ESM. However, it can be restructured if consumers do not require uninitialized exports to be absent from the export list: -```js -// after/node_modules/my-module/dynamic-exports.js -export let foo; -export let bar; -export function initialize(type) { - if (type === 'foo') { - foo = function() { /* ... */ }; - } else { - bar = function() { /* ... */ }; - } -} -// foo and bar will always be present as named exports, but may be undefined until initialized. -export default { foo, bar, initialize }; -``` - -```js -// before/app-using-dynamic-exports.mjs -import myModule from 'my-module/dynamic-exports'; -console.log('foo' in myModule); // false -console.log('bar' in myModule); // false -myModule.initialize('foo'); -console.log('foo' in myModule); // true -console.log('bar' in myModule); // false -``` - -```js -// after/app-using-dynamic-exports.mjs -import myModule from 'my-module/dynamic-exports'; -console.log('foo' in myModule); // true, even though it's undefined -console.log('bar' in myModule); // true, even though it's undefined -myModule.initialize('foo'); -console.log('foo' in myModule); // true -console.log('bar' in myModule); // true -``` +`after/node_modules/my-module/dynamic-exports.js`: + +[import](after/node_modules/my-module/dynamic-exports.js) + +`before/app-using-dynamic-exports.mjs`: + +[import](before/app-using-dynamic-exports.mjs) + +`after/app-using-dynamic-exports.mjs`: + +[import](after/app-using-dynamic-exports.mjs) diff --git a/guide/05-cjs-esm-migration/migrating-exports/after/app-importing-default-from-named-only-partial.mjs b/guide/05-cjs-esm-migration/migrating-exports/after/app-importing-default-from-named-only-partial.mjs index ee6c4cd..18bc8dd 100644 --- a/guide/05-cjs-esm-migration/migrating-exports/after/app-importing-default-from-named-only-partial.mjs +++ b/guide/05-cjs-esm-migration/migrating-exports/after/app-importing-default-from-named-only-partial.mjs @@ -1,2 +1,4 @@ -// This would break unless 'my-module' provides a default export during the migration +// This used to be `module.exports` when the module was CommonJS, +// but after the migration, the default export is missing unless explicitly provided, +// so it would throw a SyntaxError. import myModule from 'my-module/named-only-partial'; diff --git a/guide/05-cjs-esm-migration/migrating-exports/after/app-requiring-default-export-partial.cjs b/guide/05-cjs-esm-migration/migrating-exports/after/app-requiring-default-export-partial.cjs index 084058d..a9534db 100644 --- a/guide/05-cjs-esm-migration/migrating-exports/after/app-requiring-default-export-partial.cjs +++ b/guide/05-cjs-esm-migration/migrating-exports/after/app-requiring-default-export-partial.cjs @@ -1,3 +1,3 @@ // The returned value is actually { default: [Function: qux] } const qux = require('my-module/default-export-partial'); -qux(); // Throws TypeError: qux is not a function +qux(); // ⛔ Throws TypeError: qux is not a function diff --git a/guide/05-cjs-esm-migration/migrating-exports/after/node_modules/my-module/default-export.js b/guide/05-cjs-esm-migration/migrating-exports/after/node_modules/my-module/default-export.js index 7fa2664..02fcdfa 100644 --- a/guide/05-cjs-esm-migration/migrating-exports/after/node_modules/my-module/default-export.js +++ b/guide/05-cjs-esm-migration/migrating-exports/after/node_modules/my-module/default-export.js @@ -1,2 +1,7 @@ + +//! [main] export default function qux() {}; +//! [main] +//! [compat] export { qux as 'module.exports' }; // To be backward compatible with CommonJS consumers. +//! [compat] diff --git a/guide/05-cjs-esm-migration/migrating-exports/after/node_modules/my-module/named-only.js b/guide/05-cjs-esm-migration/migrating-exports/after/node_modules/my-module/named-only.js index 5bc8cf2..2f9d9d4 100644 --- a/guide/05-cjs-esm-migration/migrating-exports/after/node_modules/my-module/named-only.js +++ b/guide/05-cjs-esm-migration/migrating-exports/after/node_modules/my-module/named-only.js @@ -1,4 +1,11 @@ + +//! [main] export class Foo { /* ... */ }; export const bar = 'bar'; +//! [main] +//! [alias] export { Foo as FooAlias }; +//! [alias] +//! [compat] export default { Foo, bar, FooAlias: Foo }; // To be backward compatible with ESM consumers. +//! [compat] diff --git a/guide/05-cjs-esm-migration/migrating-exports/before/node_modules/my-module/named-only.js b/guide/05-cjs-esm-migration/migrating-exports/before/node_modules/my-module/named-only.js index 337bf60..ff89f2b 100644 --- a/guide/05-cjs-esm-migration/migrating-exports/before/node_modules/my-module/named-only.js +++ b/guide/05-cjs-esm-migration/migrating-exports/before/node_modules/my-module/named-only.js @@ -1,5 +1,10 @@ + +//! [main] class Foo { /* ... */ } exports.Foo = Foo; exports.bar = 'bar'; +//! [main] +//! [alias] exports.FooAlias = Foo; +//! [alias] diff --git a/guide/05-cjs-esm-migration/migrating-imports/README.md b/guide/05-cjs-esm-migration/migrating-imports/README.md index b9e83f4..6530cee 100644 --- a/guide/05-cjs-esm-migration/migrating-imports/README.md +++ b/guide/05-cjs-esm-migration/migrating-imports/README.md @@ -16,104 +16,77 @@ In most CommonJS modules, `require()` are done at the top-level without being gu If the original CommonJS module uses the entire exports object of the dependency, it's common to replace that with the `import defaultExport from 'module'` syntax. For example: -```js -// before/node_modules/my-module/index.js -const fs = require('fs'); // default export of Node.js built-in module -const pkg = require('pkg'); // default export of a third-party package -``` +`before/node_modules/my-module/index.js`: + +[import:'default_import'](before/node_modules/my-module/index.js) can be converted to: -```js -// after/node_modules/my-module/index.js -import fs from 'node:fs'; // default export of Node.js built-in module -import pkg from 'pkg'; // default export of a third-party package -``` +`after/node_modules/my-module/index.js`: + +[import:'default_import'](after/node_modules/my-module/index.js) ### Importing specific named exports If the original CommonJS module destructs from the result returned by `require()`, it's typical to migrate to `import { namedExport } from 'module'`. This helps with tree-shaking during bundling and allows Node.js to check for missing exports statically. For example: -```js -// before/node_modules/my-module/index.js -const { join } = require('path'); // Named export of Node.js built-in module -const { foo } = require('pkg'); // Named export of a third-party package -``` - -```js -// after/node_modules/my-module/index.js -import { join } from 'node:path'; // Named export of Node.js built-in module -import { foo } from 'pkg'; // Named export of a third-party package -``` + +`before/node_modules/my-module/index.js`: + +[import:'named_import'](before/node_modules/my-module/index.js) + +`after/node_modules/my-module/index.js`: + +[import:'named_import'](after/node_modules/my-module/index.js) When the provider is CommonJS, its exports can only be imported by name if the names are detectable for ESM imports. See the [CommonJS interoperability guide](../../04-cjs-esm-interop/shipping-cjs-for-esm/README.md#named-imports-from-commonjs-in-esm) for details. If the names are not exported in a detectable way, a typical workaround is to import the default export first, then destructure from it: -```js -// before/node_modules/my-module/index.js -// ✅ In CommonJS, this works, because the destructuring happens at run time. -const { bar } = require('cjs-pkg-with-undetectable-name'); -``` - -```js -// after/node_modules/my-module/import-undetectable-invalid.js -// ⛔ In ESM, this throws a SyntaxError, because named import validation happens -// at module linking time and needs to be static. -import { bar } from 'cjs-pkg-with-undetectable-name'; -``` - -```js -// after/node_modules/my-module/index.js -// ✅ To work around undetectable names from CJS, destructure -// from the default export, which is the `module.exports` object of the CommonJS module. -// CommonJS modules always have a default export, so this always works. -import cjsPkg from 'cjs-pkg-with-undetectable-name'; -const { bar } = cjsPkg; // The destructuring happens at run time again. -``` +`before/node_modules/my-module/index.js`: + +[import:'undetectable'](before/node_modules/my-module/index.js) + +`after/node_modules/my-module/import-undetectable-invalid.js`: + +[import](after/node_modules/my-module/import-undetectable-invalid.js) + +`after/node_modules/my-module/index.js`: + +[import:'undetectable'](after/node_modules/my-module/index.js) ## Include file extensions in import paths A CommonJS module may use `require()` to load from a path while omitting the file extension - in that case Node.js [would try to append different supported extensions to the path](https://nodejs.org/api/modules.html#file-modules) and load the first one that exists on the file system. For example: -```js -// before/node_modules/my-module/load-without-extension.js -const lib = require('./lib'); // If lib.js exists in the same directory, it will load ./lib.js -``` +`before/node_modules/my-module/load-without-extension.js`: + +[import](before/node_modules/my-module/load-without-extension.js) `import` in Node.js, however, [does not support extension probing](https://nodejs.org/api/esm.html#mandatory-file-extensions). In this case, the extension of a path must be fully specified during migration: -```js -// after/node_modules/my-module/load-without-extension.js -// ⛔ Throws ERR_MODULE_NOT_FOUND because it only attempts to load a file with the exact name './lib' -import lib from './lib'; -``` +`after/node_modules/my-module/load-without-extension.js`: -```js -// after/node_modules/my-module/index.js -// ✅ It would only work with a proper path specifying the extension -import lib from './lib.js'; -``` +[import](after/node_modules/my-module/load-without-extension.js) + +`after/node_modules/my-module/index.js`: + +[import:'ext_import'](after/node_modules/my-module/index.js) ## Directory imports are not supported A CommonJS module may use `require()` to load from a directory - in that case, Node.js would also [probe at different locations](https://nodejs.org/api/modules.html#folders-as-modules) to find the target module. For example: -```js -// before/node_modules/my-module/index.js -const utils = require('./utils-dir'); // If utils-dir/index.js exists, it will load ./utils-dir/index.js -``` +`before/node_modules/my-module/index.js`: + +[import:'dir_import'](before/node_modules/my-module/index.js) Similar to the extensionless case, `import` in Node.js does not support loading from directories either. The full path must also be explicitly specified during migration: -```js -// after/node_modules/my-module/import-dir.js -// ⛔ Throws ERR_UNSUPPORTED_DIR_IMPORT because import does not support loading directories -import utils from './utils-dir'; -``` +`after/node_modules/my-module/import-dir.js`: -```js -// after/node_modules/my-module/index.js -// ✅ It would only work with a proper path extended into the filename -import utils from './utils-dir/index.js' -``` +[import](after/node_modules/my-module/import-dir.js) + +`after/node_modules/my-module/index.js`: + +[import:'dir_import'](after/node_modules/my-module/index.js) ## Migrating from dynamic `require()` @@ -123,108 +96,40 @@ Sometimes, a module may have to load its dependencies conditionally or on-demand In this case, consider using `process.getBuiltinModule()` (available from Node.js v20.16.0+ / v22.3.0+). This is particularly handy if the module may be used in environments other than Node.js and it does not need to support older, end-of-life Node.js versions. For example: -```js -// before/node_modules/my-module/kernel-info.js -const isRunningAsCommonJSInNode = - (typeof module === 'object' && module.exports && typeof require === 'function'); - -function getKernelInfo() { // A synchronous API that has to remain synchronous - if (isRunningAsCommonJSInNode) { - // Running on Node.js as CommonJS, load the 'os' built-in via require(). - const os = require('os'); - return os.version(); - } else { - return 'unknown'; - } -} - -if (isRunningAsCommonJSInNode) { - module.exports = { getKernelInfo }; -} else { - // Other ways to export in non-CommonJS environments -} -``` +`before/node_modules/my-module/kernel-info.js`: + +[import](before/node_modules/my-module/kernel-info.js) Can be migrated to ESM like this: -```js -// after/node_modules/my-module/kernel-info.js -const isRunningOnNode = typeof process?.getBuiltinModule === 'function'; -function getKernelInfo() { // A synchronous API that has to remain synchronous - if (isRunningOnNode) { - // Running on Node.js as ESM, load the 'os' built-in via getBuiltinModule(). - const os = process.getBuiltinModule('os'); - return os.version(); - } else { - return 'unknown'; - } -} -export { getKernelInfo }; -``` +`after/node_modules/my-module/kernel-info.js`: + +[import](after/node_modules/my-module/kernel-info.js) ### If the dependency is not a built‑in and must be loaded synchronously In this case, a ESM module can still create a `require()` function using the `module.createRequire()` built-in. For example this: -```js -// before/node_modules/my-module/initialize-plugin-sync.js -function initializePluginsSync(plugins) { // Synchronous API that must remain synchronous. - const results = []; - for (const pluginName of plugins) { // - const plugin = require(`./sync-plugins/${pluginName}.js`); // dynamic require() - results.push(plugin.initialize()); // The plugin initialize() is synchronous - } - return results; -} -module.exports = { initializePluginsSync }; -``` +`before/node_modules/my-module/initialize-plugin-sync.js`: + +[import](before/node_modules/my-module/initialize-plugin-sync.js) can be migrated to ESM like this: -```js -// after/node_modules/my-module/initialize-plugin-sync.js -import { createRequire } from 'module'; -const require = createRequire(import.meta.url); - -function initializePluginsSync(plugins) { // Synchronous API that must remain synchronous. - const results = []; - for (const pluginName of plugins) { // - const plugin = require(`./sync-plugins/${pluginName}.js`); // dynamic require() - results.push(plugin.initialize()); // The plugin initialize() is synchronous - } - return results; -} -export { initializePluginsSync }; -``` +`after/node_modules/my-module/initialize-plugin-sync.js`: + +[import](after/node_modules/my-module/initialize-plugin-sync.js) ### If the dependency is not a built‑in and can be loaded asynchronously If it is acceptable to perform the dynamic loading asynchronously, the [dynamic `import()` expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) can be used. For example, if the CommonJS module previously looked like this: -```js -// before/node_modules/my-module/initialize-plugin-async.js -async function initializePlugins(plugins) { // Async API that can be async. - const results = []; - for (const pluginName of plugins) { - const plugin = require(`./async-plugins/${pluginName}.js`); // dynamic require() - results.push(await plugin.initialize()); // The plugin initialize() is already asynchronous - } - return results; -} -module.exports = { initializePlugins }; -``` +`before/node_modules/my-module/initialize-plugin-async.js`: + +[import](before/node_modules/my-module/initialize-plugin-async.js) It can be migrated to ESM like this: -```js -// after/node_modules/my-module/initialize-plugin-async.js -async function initializePlugins(plugins) { // Async API that can be async. - const results = []; - for (const pluginName of plugins) { - const plugin = await import(`./async-plugins/${pluginName}.js`); // require() -> await import() - results.push(await plugin.initialize()); // The plugin initialize() is already asynchronous - } - return results; -} -export { initializePlugins }; -``` +`after/node_modules/my-module/initialize-plugin-async.js`: + +[import](after/node_modules/my-module/initialize-plugin-async.js) diff --git a/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/import-dir.js b/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/import-dir.js index 282d6ed..996ac39 100644 --- a/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/import-dir.js +++ b/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/import-dir.js @@ -1,2 +1,2 @@ -// Throws ERR_UNSUPPORTED_DIR_IMPORT because import does not support loading directories +// ⛔ Throws ERR_UNSUPPORTED_DIR_IMPORT because import does not support loading directories import utils from './utils-dir'; diff --git a/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/import-undetectable-invalid.js b/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/import-undetectable-invalid.js index 4e604ca..0e1ef02 100644 --- a/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/import-undetectable-invalid.js +++ b/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/import-undetectable-invalid.js @@ -1,3 +1,3 @@ -// In ESM, this throws a SyntaxError, because named import validation happens +// ⛔ In ESM, this throws a SyntaxError, because named import validation happens // at module linking time and needs to be static. import { bar } from 'cjs-pkg-with-undetectable-name'; diff --git a/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/index.js b/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/index.js index 21e6b5b..c83f65a 100644 --- a/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/index.js +++ b/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/index.js @@ -1,19 +1,30 @@ + +//! [default_import] import fs from 'node:fs'; // default export of Node.js built-in module import pkg from 'pkg'; // default export of a third-party package +//! [default_import] +//! [named_import] import { join } from 'node:path'; // Named export of Node.js built-in module import { foo } from 'pkg'; // Named export of a third-party package +//! [named_import] -// To work around undetectable names from CJS, the ESM importer can destructure +//! [undetectable] +// ✅ To work around undetectable names from CJS, destructure // from the default export, which is the `module.exports` object of the CommonJS module. // CommonJS modules always have a default export, so this always works. import cjsPkg from 'cjs-pkg-with-undetectable-name'; const { bar } = cjsPkg; // The destructuring happens at run time again. +//! [undetectable] -// In ESM, the full path including filename must be specified +//! [dir_import] +// ✅ It would only work with a proper path extended into the filename import utils from './utils-dir/index.js'; +//! [dir_import] -// In ESM, the extension must be specified +//! [ext_import] +// ✅ It would only work with a proper path specifying the extension import lib from './lib.js'; +//! [ext_import] export { fs, pkg, join, foo, bar, utils, lib }; diff --git a/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/load-without-extension.js b/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/load-without-extension.js index cb0e04d..f091dcf 100644 --- a/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/load-without-extension.js +++ b/guide/05-cjs-esm-migration/migrating-imports/after/node_modules/my-module/load-without-extension.js @@ -1,2 +1,2 @@ -// Throws ERR_MODULE_NOT_FOUND because it only attempts to load a file with the exact name './lib' +// ⛔ Throws ERR_MODULE_NOT_FOUND because it only attempts to load a file with the exact name './lib' import lib from './lib'; diff --git a/guide/05-cjs-esm-migration/migrating-imports/before/node_modules/my-module/index.js b/guide/05-cjs-esm-migration/migrating-imports/before/node_modules/my-module/index.js index 4ef2186..a0674ef 100644 --- a/guide/05-cjs-esm-migration/migrating-imports/before/node_modules/my-module/index.js +++ b/guide/05-cjs-esm-migration/migrating-imports/before/node_modules/my-module/index.js @@ -1,13 +1,22 @@ + +//! [default_import] const fs = require('fs'); // default export of Node.js built-in module const pkg = require('pkg'); // default export of a third-party package +//! [default_import] +//! [named_import] const { join } = require('path'); // Named export of Node.js built-in module const { foo } = require('pkg'); // Named export of a third-party package +//! [named_import] +//! [dir_import] const utils = require('./utils-dir'); // If utils-dir/index.js exists, it will load ./utils-dir/index.js +//! [dir_import] -// In CommonJS, this works, because the destructuring happens at run time. +//! [undetectable] +// ✅ In CommonJS, this works, because the destructuring happens at run time. const { bar } = require('cjs-pkg-with-undetectable-name'); +//! [undetectable] const lib = require('./lib'); // If lib.js exists in the same directory, it will load ./lib.js diff --git a/guide/05-cjs-esm-migration/migrating-imports/before/node_modules/my-module/load-without-extension.js b/guide/05-cjs-esm-migration/migrating-imports/before/node_modules/my-module/load-without-extension.js index bf0e3c6..d97a485 100644 --- a/guide/05-cjs-esm-migration/migrating-imports/before/node_modules/my-module/load-without-extension.js +++ b/guide/05-cjs-esm-migration/migrating-imports/before/node_modules/my-module/load-without-extension.js @@ -1,2 +1 @@ -// If lib.js exists in the same directory, it will load ./lib.js -const lib = require('./lib'); +const lib = require('./lib'); // If lib.js exists in the same directory, it will load ./lib.js From 2f9754cabb5f1702c42f332235c31374ad1f9a67 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 6 Apr 2026 17:04:30 +0200 Subject: [PATCH 3/3] fixup! migrate to use codeblocks --- guide/04-cjs-esm-interop/shipping-esm-for-cjs/cycle-lazy/b.cjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/cycle-lazy/b.cjs b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/cycle-lazy/b.cjs index 0fdc724..5e7eb71 100644 --- a/guide/04-cjs-esm-interop/shipping-esm-for-cjs/cycle-lazy/b.cjs +++ b/guide/04-cjs-esm-interop/shipping-esm-for-cjs/cycle-lazy/b.cjs @@ -3,8 +3,7 @@ let a; function runOnlyWhenUsed() { a = require('./a.mjs'); -//! [doc] - console.log('Loaded lazily:', a.getValue()); } +//! [doc] exports.runOnlyWhenUsed = runOnlyWhenUsed;