Author: Energetically intelligent front end team Ademily

preface

The latest specification of the Node.js package module provides the ability to avoid exposing specific files and apis in favor of code specification and repository dependency governance. The students of the intelligent front end team made a detailed study of the latest rules of Node package module. This article brings you the latest Node package module import and export rules, I hope you can handle this situation in your work.

Original text: nodejs.org/api/package…

Package module history changes:

version change
v14.13.0 new"exports"Mode ("exports"Patterns)
v14.6.0 new"imports"field
V13.7.0, v12.16.0 Conditional exports no longer use Unflag Conditional exports
V13.6.0, v12.16.0 Second, Unflag self-referencing a package using its name.
v12.7.0 The introduction of"exports"thispackage.jsonNew field, as a classic field"main"A more powerful substitute for
v12.0.0 Through the newpackage.json"type"Field, so that.jsFiles are treated as ES modules.

Introduce a.

A package is a folder tree described by package.json. The package consists of the folder containing package.json and all subfolders, up to the next folder containing another package.json, or the node_modules folder.

This page shows you how to write package.json. There is also a package.json field defined by Node.js at the bottom of the page for reference.

2. Judge the module system

When passed to Node as initial input, or referenced by an import statement in the ES module code, Node.js treats the following as ESM modules:

  • A file ending in.mjs

  • A.js file whose top-level “type” value is “module” for the nearest parent package.json

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

Node.js treats all other forms of input as CommonJS, such as.js files whose most recent parent package.json does not have a “type” field, or starts Node without passing the –input-type flag. The CommonJS schema is retained for backward compatibility. While Node.js currently supports both CommonJS and ESM modules, it’s best to be as specific as possible about which one to use.

Node.js treats node as CommonJS when it is started as initial input, or when it is referenced with an import statement in the ESM module code:

  • Files ending in.cjs

  • A.js file with the top-level field “type” value of “commonjs” in the latest parent package.json

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

Package authors should include the “type” field in package.json, even if all source code is CommonJS. Specifying the “type” of the package protects the package in the future if the default node.js type changes, and also makes it easier for build tools and loaders to figure out how to parse the files inside the package.

1. Package. json and file extension

In a package, the package.json “type” field defines how Node.js should parse.js files. If package.json does not have a “type” field, the.js file will be treated as a CommonJS module.

When package.json has a “type” value of “module”, Node.js will parse the.js file in that package to use ESM module syntax.

The “type” field applies not only to initialization entry files (node my-app.js), but also to files referenced by import statements and import() expressions.

// my-app.js is treated as an ES module because "type" in package.json is "module" import './startup/init.js'. // Import 'commonjs-package' as ES module; // import 'commonjs-package' as ES module; // Import 'commonjs-package' as ES module; // Because./node_modules/commonjs-package/package.json lacks the "type" field or contains "type":"commonjs", So the file loads import './node_modules/commonjs-package/index.js' as CommonJS. // Because./node_modules/commonjs-package/package.json lacks the "type" field or contains "type":"commonjs", the file is loaded as commonjsCopy the code

Files ending in.mjs are always loaded as ESM modules and are not affected by the most recent parent package.json.

Similarly, files ending in.cjs are always loaded as CommonJS and are not affected by the most recent parent package.json.

The import '. / legacy - file. CJS '. Import 'commonjs-package/ SRC /index.mjs'; import 'commonjs-package/ SRC /index.mjs'; // Load as an ES module, because.mjs is always loaded as an ES moduleCopy the code

The.mjs and.cjs extensions allow us to use two modes in a package:

  • In a package of “type”: “module”, Node.js can interpret specific files with the suffix.cjs as CommonJS (because both.js and.mjs files are treated as ES modules in the “module” package).

  • In a package with “type”: “commonjs”, Node.js can interpret specific files with the suffix.mjs as ES modules (because in the “commonjs” package, both.js and.cjs files are treated as commonjs).

2. – input – type logo

When strings are passed to Node as an –eval (or -e) argument or via STDIN, they are treated as ESM modules once the –input-type=module flag is set.

node --input-type=module --eval "import { sep } from 'path'; console.log(sep);"

echo "import { sep } from 'path'; console.log(sep);" | node --input-type=module
Copy the code

Also, we have –input-type=commonjs to explicitly run string input as commonJS. If –input-type is not specified, the default is CommonJS mode.

Iii. Package Entry Points

In the package.json file, there are two fields that define the package entry: “main” and “exports”. All versions of Node.js support the “main” field, but its functionality is limited: it only defines the main entrance to the package.

The “exports” field is an alternative to “main” in that it defines the main entrance to the package and also closes the package to prevent access to other undefined content. This closure allows module authors to define common interfaces for their packages.

If both “exports” and “main” are defined, the “exports” field takes precedence over “main”. Exports “are not unique to ESM modules or CommonJS;” Exports “would override “main” if it existed. Therefore, **”main”** cannot be used as a CommonJS downgrade, but it can be used as a demotion for older Versions of Node.js that do not support the **”exports”** field.

Using Conditional exports in “exports” can define different entries for each environment, including whether the package is referenced by require or import. For information on how to support both CommonJS and ESM modules in one package, see the section “Dual CommonJS/ESM Module Packages”.

Note: Using the “exports” field prevents package users from using other undefined entry points, including package.json (e.g., require(‘your-package/package.json’). This is likely to be a major change.

To make the introduction of “exports” non-destructive, make sure that every previously supported entry is exported. It is best to specify each entry explicitly so that the package has a clear public API definition. For example, a project that previously exported main, lib, feature and package.json could be written like this:

{
  "name": "my-mod",
  "exports": {
    ".": "./lib/index.js",
    "./lib": "./lib/index.js",
    "./lib/index": "./lib/index.js",
    "./lib/index.js": "./lib/index.js",
    "./feature": "./feature/index.js",
    "./feature/index.js": "./feature/index.js",
    "./package.json": "./package.json"
  }
}
Copy the code

Alternatively, a project can choose to export the entire folder:

{
  "name": "my-mod",
  "exports": {
    ".": "./lib/index.js",
    "./lib": "./lib/index.js",
    "./lib/*": "./lib/*.js",
    "./feature": "./feature/index.js",
    "./feature/*": "./feature/*.js",
    "./package.json": "./package.json"
  }
}
Copy the code

As a last resort, the “./*”: “./*” can be used to export the entire package root directory, and the “close” function will not work. This approach of exposing all files within a package provides potential utility convenience at the expense of disabling closure. Because the ES M module loader in Node.js enforces the full path and exports the root directory rather than explicitly defining the entry, the former is much less expressive than our previous examples. Not only does this lose closure, but the caller can’t directly use import feature from ‘my-mod/feature’ because they need to write the full path import feature from ‘my-mod/feature/index.js.

1. Main entry point export

To set the main entry of the package, it is best to define both “exports” and “main” in package.json.

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

When the “exports” field is defined, all child paths are closed and not open to users. For example, require(‘ PKG /subpath.js’) raises ERR_PACKAGE_PATH_NOT_EXPORTED errors.

This closure of exports not only provides a more reliable interface guarantee for the tool, but also guarantees the upgrade of processing packages. This is not strictly closed, because requiring any absolute path directly, such as require(‘/path/to/node_modules/ PKG /subpath.js’) would still load subpath.js.

2. Subpath exports

When using the “exports” field, you can treat the main entry as a “.” path and then construct a custom path:

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

Currently only subpaths defined in “exports” can be imported:

import submodule from 'es-module-package/submodule'; // node_modules/es-module-package/ SRC /submodule.js.Copy the code

Error when importing other subpaths:

import submodule from 'es-module-package/private-module.js'; / / wrong ERR_PACKAGE_PATH_NOT_EXPORTEDCopy the code

3. Subpath imports

In addition to the “exports” field, you can define internal package import mappings that only apply to importing specified content inside a package. Entries defined in the imports field must always start with a # to ensure that they are distinct from the externally specified contents of the package. For example, the imports field can also do conditional exports for internal modules:

// package.json { "imports": { "#dep": { "node": "dep-node-native", "default": "./dep-polyfill.js" } }, "dependencies": {"dep-node-native": "^1.0.0"}}Copy the code

As the example above shows, import ‘#dep’ does not fetch the external package dep-Node-native when in a non-Node environment, but the file./dep-polyfill.js. Unlike the “Exports” field, the “imports” field allows external packages to be mapped. The imports field resolves similarly to the Exports field.

4. Subpath Patterns

For packages with a small number of exports or imports, we recommend explicitly listing each subpath entry. But for packages with large quantum paths, this can lead to package.json bloatiness and maintenance issues. For this case with a large number of paths, we can use the subpath export mode instead:

// ./node_modules/es-module-package/package.json
{
  "exports": {
    "./features/*": "./src/features/*.js"
  },
  "imports": {
    "#internal/*": "./src/internal/*.js"
  }
}
Copy the code

Left-sided matching patterns must all end with *. All * instances on the right are replaced with values on the left, including whether it contains the/delimiter.

import featureX from 'es-module-package/features/x'; / / load/node_modules/es - module - package/SRC/features/x.j s. import featureY from 'es-module-package/features/y/y'; / / load/node_modules/es - module - package/SRC/features/y/y.j s. import internalZ from '#internal/z'; / / load/node_modules/es - module - package/SRC/internal/z.j s.Copy the code

This is a straightforward static replacement without any special treatment for file extensions. In the example above, PKG /features/x.json will be resolved in the map as./ SRC /features/x.json.js. Whether the properties of exports can remain statically enumerable depends on how the export mode is set, such as ** on the right side of the export mode or listing a long list of files. Since the node_moduels path is prohibited from being written in exports’ target, pattern matching can only write to files of the package itself.

5. Subpath Folder Mappings

Stability: 0 – Obsolete. Use subpath mode instead. Since it has been abandoned, the translation will not be put here.

6. Exports Grammar Sugar

When “.” is the only export, the “exports” field provides syntactic sugar for this case. When “.” exports an array or string value that has been degraded, the “exports” field can be set directly to that value.

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

Can be rewritten as:

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

7. Conditional exports

Conditional exports provide a way to map to different paths based on specific conditions. Conditional exports are supported for both CommonJS and ES module imports. For example, a package that wanted to provide different ES module exports for require() and import could be written as:

// package.json
{
  "main": "./main-require.cjs",
  "exports": {
    "import": "./main-module.js",
    "require": "./main-require.cjs"
  },
  "type": "module"
}
Copy the code

Out of the box, Node.js supports the following situations:

  • “Import” – This condition matches when packages are loaded via import or import(), or through any top-level import or parsing operation of the ECMAScript module loader. This applies regardless of the module format of the object file. “Import” is always mutually exclusive with “require”.

  • “Require” – matches when the package is loaded through require(). The referenced file should be able to be loaded with require(), although this condition is independent of the module format of the target file. Expected formats include CommonJS, JSON, and native plug-ins, but not the ES module, as require() does not support them. “Require” is always mutually exclusive with “import”.

  • “Node” – Matches any Node.js environment. This can be a CommonJS or ESM module file. This condition should always be followed by “import” or “require”.

  • “Default” – Default downgrade condition. It can be a CommonJS or ESM module file. This condition should always come last.

In “exports” objects, the key order is important. In the process of conditional matching, the entry with higher order has higher priority. As a general rule, these conditions should be ordered from most special to least special.

Other conditions such as “browser”, “electron”, “deno”, “react-native”, etc., are not available for Node.js and will therefore be ignored. They can be used at the discretion of runtimes or tools other than Node.js. There may be more restrictions, definitions, or guidance on condition names in the future.

Using the “import” and “require” conditions can cause some harm, as explained further in the CommonJS/ES dual module package section. Subpaths can also be derived using conditions as follows:

{
  "main": "./main.js",
  "exports": {
    ".": "./main.js",
    "./feature": {
      "node": "./feature-node.js",
      "default": "./feature.js"
    }
  }
}
Copy the code

The above code shows that there are different implementations of require(‘ PKG /feature’) and import(‘ PKG /feature’) in Node.js and other JS environments.

When using an environment branch, include “default” as much as possible. Providing “default” ensures that any unknown JS environment can use this generic implementation, and avoids unknown JS environments having to pretend to be existing ones in order to support conditional exports. For this reason, using “node” and “default” conditional branches is usually preferable to using “node” and “browser” conditional branches.

It was believed that there should be Nested conditions.

In addition to direct mapping, Node.js also supports nested conditional objects. For example, define a dual-mode entry that is only available to Node.js and is not available to browsers

{
  "main": "./main.js",
  "exports": {
    "node": {
      "import": "./feature-node.mjs",
      "require": "./feature-node.cjs"
    },
    "default": "./feature.mjs",
  }
}
Copy the code

Conditions continue to match flattened conditions in sequence. If a nested condition does not have any mappings, it continues to check the remaining conditions of the parent condition. In this way, nested conditions behave like nested JavaScript if statements.

9. Resolving User Conditions

When running Node.js, you can add custom conditions using the –conditions flag:

node --conditions=development main.js
Copy the code

When the existing “node”, “default”, “import”, and “require” conditions are properly resolved, the above code resolves the “development” condition in package imports and exports. You can add as many custom conditions as you want, just keep adding the repeated –conditions.

Self-referencing a package using its name

Inside the package, the value defined by “exports” in package.json can be used as the name of the reference inside the package. Suppose package.json looks like this:

// package.json
{
  "name": "a-package",
  "exports": {
    ".": "./main.mjs",
    "./foo": "./foo.js"
  }
}
Copy the code

Then, any module of _ in the package can be referenced within the package:

// ./a-module.mjs
import { something } from 'a-package'; // Imports "something" from ./main.mjs.
Copy the code

Self-referencing is only available if package.json has “exports” and only imports allowed by that “exports” (in package.json) are allowed. So as defined in the package.json example above, the following code generates a runtime error:

/ /. / another - module. MJS. // Imports "another" from./ m.js. It failed because // the "exports" field in "package.json" // did not provide an export named "./ m.js ". import { another } from 'a-package/m.mjs';Copy the code

Self-referencing is also possible when using require, both in ES modules and in CommonJS modules. Such as:

// ./a-module.js
const { something } = require('a-package/foo'); // Loads from ./foo.js.
Copy the code

Dual CommonJS/ES Module Packages

Before Node.js supported ESM modules, it was a common pattern for package authors to put source code for both CommonJS and ESM modules in packages as follows: Specify the CommonJS entry in the “main” field of package.json and the ES module entry in the “module” field. This allows Node.js to run the CommonJS entry normally, while other packaging build tools, etc., use the ES module entry, because Node.js ignores (and still ignores) the top-level “module” field.

Node.js can now run ESM module entries, and a package can contain both CommonJS and ESM module entries (either through a separate specification such as “PKG” and “PKG/ES-Module”, or by placing both on the same specification via “conditional export”). Unlike the case where “module” is only used by the packager, or where ESM module files are converted to CommonJS before node.js is executed, the files referenced by the ESM module entry are used directly as ESM modules.

Dual Package hazard

When an application uses a package that provides both CommonJS and ES module sources, some bugs may occur if both versions of the package are loaded. This potential risk comes from the fact that: PkgInstance created by const pkgInstance = require(‘ PKG ‘) versus pkgInstance created by import pkgInstance from ‘PKG’ (or an alternate primary path like ‘PKG /module’) PkgInstance is not the same. The effect of two versions of the same package being loaded in the same runtime environment is known as the “double package hazard.” While it is unlikely that an application or package intentionally loads two versions directly, it is common for an application to load one version while the application’s dependencies load the other. Because Node.js supports intermixing of CommonJS and ES modules, this hazard can occur and lead to unexpected behavior.

If the package primarily exports a constructor, then a comparison of instanceof instances created by two versions returns false. Also, if you export an object, attributes added for one version of the object (such as pkginstance.foo = 3) do not exist on the other version. This is different from how import and require statements work in a full CommonJS or ESM module environment, respectively, and can be surprising to consumers. This is also different from the behavior you’re familiar with when compiling with tools like Babel or ESM.

Minimizing hazards when Writing dual packages while avoiding

First, the dangers described in the previous section occur when a package contains both CommonJS and ESM module sources, and both sources are used by Node.js, either through a separate main entry or export path. In contrast, if you write a package, any version of Node.js will only receive CommonJS sources, and any individual ESM module sources in the package will only be used in other environments, such as browsers. Such a package can be used by any version of Node.js because the import can refer to CommonJS files, but it doesn’t provide any advantage of using ES module syntax.

A package may also switch from CommonJS to ESM module syntax in a major release update. One disadvantage of this is that the latest version of the package can only be used in node.js versions that support ESM modules.

Each pattern requires trade-offs, but there are two ways to do this:

  1. Packages can pass throughrequireimportTwo ways to use
  2. Packages can be used in current Node.js or older versions of Node.js that lack ESM module support
  3. The main entry to the package, for example'pkg'Can pass throughrequireParsing to CommonJS file can also be passedimportParse to ESM module files. (Likewise, the export path is the same, for example'pkg/feature' )
  4. Packages provide named exports, for exampleimport { name } from 'pkg'Rather thanimport pkg from 'pkg'; pkg.name
  5. Packages may be used in other ESM module environments, such as browser environments
  6. The double packet hazard described in the previous section should be avoided or minimized
Approach #1: Use an ES Module wrapper

Write the package in CommonJS or translate the ESM module source code into CommonJS, and create an ESM module wrapper file with a named export. Using conditional export, use ESM module wrapper for import and CommonJS entry for require.

// ./node_modules/pkg/package.json
{
  "type": "module",
  "main": "./index.cjs",
  "exports": {
    "import": "./wrapper.mjs",
    "require": "./index.cjs"
  }
}
Copy the code

The above example uses the explicit extensions.mjs and.cjs. Type “: “module” will treat.js files as ES modules, just as “type”: “commonjs” will treat.js files as commonjs.

// ./node_modules/pkg/index.cjs
exports.name = 'value';


// ./node_modules/pkg/wrapper.mjs
import cjsModule from './index.cjs';
export const name = cjsModule.name;
Copy the code

In the example above, name in import {name} from ‘PKG’ is the same thing as name in const {name} = require(‘ PKG ‘). So === returns true when comparing two names, avoiding the harm caused by different specifiers.

If the module does not simply name a list of exports but contains unique function or object exports, such as module.exports = function () {… }, or if you want to support import PKG from ‘PKG’ in the wrapper, the wrapper will be written to export other named values as well as the default:

import cjsModule from './index.cjs';
export const name = cjsModule.name;
export default cjsModule;
Copy the code

This approach is suitable for any of the following use cases:

  • The package is currently written in CommonJS, and the authors do not want to refactor it into ESM module syntax, but want to provide named exports for ESM module consumers

  • This package has other packages that depend on it, and end users may install this package along with other packages that depend on it. For example, the Utilities package is used directly in the application, while the utilities-Plus package adds some functionality to utilities. Because the wrapper exports the underlying CommonJS file, this works regardless of whether utilities-Plus is written in CommonJS or ES module syntax.

  • Internal state is stored in the package, and the package author does not intend to refactor the package to isolate its state management. See the next section, “Method #2.”

Another way to do this without requiring conditional exports from the user is to add an export, such as “./module”, pointing to a package with a full SYNtactic version of the ES module. If the consumer is confident that the CommonJS version will not be loaded anywhere in the application, such as through dependencies; Or if the CommonJS version can be loaded without affecting the ES module version (for example, because packages are stateless), it can be used by importing ‘PKG /module’.

// ./node_modules/pkg/package.json
{
  "type": "module",
  "main": "./index.cjs",
  "exports": {
    ".": "./index.cjs",
    "./module": "./wrapper.mjs"
  }
}
Copy the code
Approach #2: Isolate State

The package.json file can directly define separate entries for CommonJS and ESM modules:

// ./node_modules/pkg/package.json
{
  "type": "module",
  "main": "./index.cjs",
  "exports": {
    "import": "./index.mjs",
    "require": "./index.cjs"
  }
}
Copy the code

If the CommonJS and ESM module versions of the package are equivalent, for example, because one is a translation of the other; This can be done if the state management of the package is carefully isolated (or if the package is stateless).

State is an issue because both CommonJS and ESM module versions of the package can be used within the application; For example, a user’s application code might import the ESM module version, and one of the application’s dependencies require the CommonJS version. If this happens, both versions are loaded in memory, so there are two different states. This is likely to produce bugs that are difficult to resolve.

In addition to writing a stateless package (if JavaScript Math is a package, it would be stateless since all its methods are static), there are ways to isolate state so that it is shared between CommonJS and ESM module instances that might be loaded:

1. If possible, package all states in an instantiated object. For example, JavaScript Date needs to be instantiated to contain state; If it were a package, it would be used like this:

import Date from 'date'; const someDate = new Date(); // someDate contains state; The Date the statelessCopy the code

The new keyword is not required; package functions can also return a new object or modify an incoming object to preserve the package’s external state.

2. Isolate the status of one or more CommonJS files shared in the CommonJS and ESM module versions of the package. For example, if the entry to CommonJS and ESM modules is index.cjs and index.mjs respectively:

// ./node_modules/pkg/index.cjs
const state = require('./state.cjs');
module.exports.state = state;


// ./node_modules/pkg/index.mjs
import state from './state.cjs';
export {
  state
};
Copy the code

Even if the PKG is used in an application as require and import, respectively (for example, import in application code and require in other dependent modules), each reference to the PKG will contain the same state; And changing the state from either module system applies to both modules.

Any plug-ins related to package singletons need to be applied to CommonJS and ESM module singletons, respectively. This approach applies to any of the following use cases:

  • Packages are currently written in ESM module syntax, and package authors want to use that version where that syntax is supported

  • A package is stateless, or its state can be relatively easily isolated

  • Packages are less likely to be relied on by other public packages; Or, if there are dependencies, the package is stateless, or its state does not need to be shared between dependencies or across the application.

Even in isolation, additional code may need to be executed between CommonJS and ESM versions of the package. As with the previous approach, another way to do this without requiring the consumer to do a conditional export is to add an export. For example, “./module” to point to the full ESM version of the package:

// ./node_modules/pkg/package.json
{
  "type": "module",
  "main": "./index.cjs",
  "exports": {
    ".": "./index.cjs",
    "./module": "./index.mjs"
  }
}
Copy the code

5. The Node. Jspackage.jsonField definition

This section describes the fields used by Node.js at runtime. Other tools (such as NPM) use additional fields that are ignored by Node.js and won’t be explained here. The following fields in package.json are used by Node.js:

  • “Name” – relevant when using named imports within a package. Also used by the package manager as the name of the package.

  • “Main” – if no exports are specified, or in older Node.js versions that cannot use exports, “main” is the default module for loading packages.

  • “Type” – This field determines whether the.js file will be loaded as CommonJS or ES module.

  • “Exports” – package exports and conditional exports. When “exports” has a value, it limits which submodules can be loaded.

  • “Imports” – Imports of a package for use by the modules within the package themselves.

1. “name”

Historical changes:

version change
V13.1.0, v12.16.0 This attribute is added in versions V13.1.0 and V12.16.0
V13.6.0, v12.16.0 remove--experimental-resolve-selfoptions
  • Type: <string>
{ "name": "package-name" }
Copy the code

The “Name” field defines the name of the package. Publishing to NPM requires a name that meets certain requirements.

“Name” can be used with the “exports” field for self-reference.

2. “main”

Added this attribute in V0.4.0

  • Type: <string>
{ "main": "./main.js" }
Copy the code

When the package directory is loaded through require(), it is defined using the “main” field. The value of main” is a path.

require('./path/to/directory'); / / this will be resolved as the/path/to/directory/main js.Copy the code

When a package has an “exports” field, “exports” takes precedence over the “main” field when importing the package by name.

3. “type”

Historical changes:

version change
V13.2.0, v12.17.0 No longer use--experimental-modulesmark
v12.0.0 This property is added in v12.0.0
  • Type: <string>

The “type” field defines the module format node.js will use for all.js files that have this package.json as their closest parent.

When the top-level field “type” of the most recent parent package.json is “module”, files ending in.js will be loaded as ES modules.

Most recent parent package.json: is defined as the first package.json found when searching in the current folder, or the first package.json found in the parent package of that folder, and so on, until you reach the node_modules folder or the root directory.

// package.json
{
  "type": "module"
}
Copy the code
Nodemy-app. js # Run as an ESM module because package.json defines the "module" typeCopy the code

If the most recent parent package.json lacks the “type” field, or contains “type”: “commonjs”, the.js file will be treated as commonjs. If package.json is not found until the root directory, the.js file is treated as CommonJS.

If the latest parent package.json contains “type”: “module”, the.js file will be treated as an ES module when it is imported:

// my-app.js, which is part of the previous example import './startup.js'; // This will be loaded as an ES module due to package.json.Copy the code

Regardless of the value of the “type” field,.mjs files are always treated as ES modules and.cjs files are always treated as CommonJS.

4. “exports”

Historical changes:

version change
v14.13.0 Additional supportexportsmodel
V13.7.0, v12.16.0 Implements the logical order of conditional exports
V13.7.0, v12.16.0 remove--experimental-conditional-exportsoptions
V13.2.0, v12.16.0 Implement conditional export
v12.7.0 This attribute is added in v12.7.0
  • Type: <Object> | <string> | <string[]>
{ "exports": "./index.js" }
Copy the code

The “exports” field defines the entry of packages, which can be found via node_modules or imported by self-reference. Node.js 12+ started supporting “exports” as an alternative to “main”, which both supports defining subpath exports and conditional exports and also blocks internal unexported modules.

Conditional exports can also be used in “exports” to define different package entries in each environment, including whether the package is imported via require or import.

All paths defined in “exports” must be relative path urls starting with./.

5. “imports”

This attribute is added in version 14.6.0

  • Type: <Object>
// package.json { "imports": { "#dep": { "node": "dep-node-native", "default": "./dep-polyfill.js" } }, "dependencies": {"dep-node-native": "^1.0.0"}}Copy the code

The entry in the imports field must be a string beginning with #.

Import mapping allows external packages to be mapped. This field defines the subpath import of the current package.

Appendix: Webpack function verification

Webpack also checks the above node.js supported features:

The following is a browser validation using Webpack:

The repO for the above test cases can be found at: github.com/zEmily/webp…

7. To summarize

I looked at several popular libraries and found that they were not using exports new features. Perhaps the package’s authors don’t want to do disruptive iterations right now, but expect new features to trickle down in the future.

Although the external package is not used in exports, we can use it internally first. For example, when writing generic lib packages, use exports to expose only the intended interface, rather than making all package contents freely accessible to outsiders. If the package is only used in the Node environment, you can use the “conditional export” mentioned above to restrict it and avoid unnecessary bugs.