Skip to content

Commit 421aa8e

Browse files
committed
Revision 2
- fix nits and spelling errors - rewite the intro again for clarity - change future to present tense - rewrite 'Motivation' to be more concise - remove implementation specifics - add FAQ covering ESM-based configs - add reference to the implementation PR
1 parent 44cc3ad commit 421aa8e

File tree

1 file changed

+43
-64
lines changed

1 file changed

+43
-64
lines changed

designs/2019-esm-compatibilty/README.md

+43-64
Original file line numberDiff line numberDiff line change
@@ -6,93 +6,69 @@
66

77
## Summary
88

9-
ES module support is rolling out in the latest version of Node. One change included in that update is the ability to define a package as an ES module by default. That means all `.js` files will be evaluated as ES rather than CommonJS modules by default.
9+
**This Issue**
1010

11-
This breakes the existing functionality for ESLint JS configs (ie which are CJS). The fix for this is to use the alternative `.cjs` extension to explicitly signal that the config file is a CommonJS module. This RFC addresses this issue and outlines the specifics required to add support for the `.cjs` extension.
11+
Node now supports ES modules. ESLint configurations that use the CommonJS format (i.e., `.eslintrc`, `.eslintrc.js`) are not compatible with ES module based packages.
1212

13-
## Motivation
13+
**The Impact**
14+
15+
This applies to ES module based packages (i.e., packages using `"type": "module"`) using a CommonJS configuration (i.e., `.eslintrc`, `.eslintrc.js`).
1416

15-
The new ES module standard is finally rolling out but, there already exists a massive ecosystem of packages build as CommonJS modules (incl ESLint).
17+
**The Fix**
1618

17-
In the majority case, changing a package to be interpreted as an ES module by default is as simple as adding `"type": "module"` to `package.json`.
19+
Node provides an 'escape hatch' via the `.cjs` extension. The extension explicitly signals to Node that the file should *always* evaluate as CommonJS. Support for the `.cjs` needs to be added to ESLint, and ESLint users building ES module based packages need to be notified.
1820

19-
In excepitonal edge cases it may require extra work to establish interoperability across package boundaries. The way ESLint uses `.js` config files is one of those edge cases.
21+
## Motivation
2022

21-
The goal of this RFC is to explore the different options for addressing this issue.
23+
ES modules are here. ESLint is ubiquitous in the JS ecosystem. With some minor adjustments, ESLint can be made to be compatible with the new format.
2224

2325
## Detailed Design
2426

2527
To understand the design outlined here requires some background info into the mechanics by which Node modules are loaded.
2628

2729
### Jargon
2830

29-
- Package - A NPM package (ie contains `package.json`)
31+
- Package - A NPM package (i.e., contains `package.json`)
3032
- Module - A JS file
3133
- CJS - A CommonJS module
32-
- ESM - A Standard ECMAScript module
34+
- ESM - A standard ECMAScript module
3335
- Consumer - A package that uses/depends on this package
3436
- Dependent - Package(s) that this package needs to work
3537
- Boundary - The demarcation between package, consumer, dependent
3638

3739
### A Crash Course on Package Boundaries
3840

39-
Historically, NPM packages have come in a variety of different package formats (ie IIFE, AMD, UMD, CJS). Prior to an actual spec, NPM settled on CJS as it's de-facto module format. CJS isn't going anywhere, the addition of ESM is additive.
41+
Historically, NPM packages have come in a variety of different package formats (i.e., IIFE, AMD, UMD, CJS). Prior to an actual spec, NPM settled on CJS as it's de-facto module format. CJS isn't going anywhere, the addition of ESM is additive.
4042

4143
By default, all NPM packages are CJS-based. That means all `.js` files will be read as CJS modules. To include an ES module in a CJS package, it must have the `.mjs` extension.
4244

43-
In addition, if `package.json` contains `"type": "module"` then the package is ESM-based. Meaning, all `.js` files contained within will be treated as ESM. To include a CJS module in a ESM-based package, it must have the `.cjs` extension.
45+
If, `package.json` contains `"type": "module"` then the package is ESM-based. Meaning, all `.js` files contained within will be treated as ESM. To include a CJS module in a ESM-based package, it must have the `.cjs` extension.
4446

45-
This configuration option does *not* affect consumers or dependents. Their functionality depends entirely on their own configuration. Assuming all packages follow this rule, there should be no issues with interoperability between CJS/ESM.
47+
This configuration option does *not* affect consumers or dependents. Whatever the configuration, it applies to all modules within the package boundary. Assuming packages only ever directly import within their package boundary, there should be no issues with interoperability between CJS/ESM.
4648

4749
### The Scenario
4850

49-
A user is creating a new package. They want it to be ESM for Browser <-> Node compatibility so they configure their package to be ESM-based.
51+
A user is creating a new package. They prefer ESM for Browser <-> Node compatibility so they configure their package to be ESM-based.
5052

51-
The user adds `eslint` as a devDependency, w/ a typical NPM script to lint the package contents.
53+
The user adds `eslint` as a devDependency and a typical NPM script to lint the package's contents.
5254

53-
The user defines `.eslintrc.js` outlining the ruleset they prefer to use within their package.
55+
The user defines `.eslintrc.js` outlining the rule set they prefer to use within their package.
5456

5557
### The Issue
5658

57-
The user pacakge is ESM-based, all `.js` files within are read as ESM.
59+
The user package is ESM-based, all `.js` files within are read as ESM.
5860

5961
ESLint is CJS-based, it loads all files within it's package boundary as CJS.
6062

61-
The configuration file is defined as a CJS module (ie `module.exports`), but has the `.js` syntax so requiring it throws an error. ESLint, by design reaches across the package boundary to load the configuration but the user has inadvertently signaled to Node that it uses the wrong module type.
63+
The configuration file is defined as a CJS module (i.e., `module.exports`), but has a `.js` extension syntax so requiring it throws an error. ESLint, by design reaches across the package boundary to load the user-defined configuration but the user has inadvertently signaled to Node to load it with the wrong module loader.
6264

6365
### The Fix
6466

65-
Rename `.eslintrc.js -> .eslintrc.cjs`
66-
67-
There is no documentation outlining how to define a ESM-based configuration. As such, it can be assumed that any JS-based ESLint config will always be a CJS module.
68-
69-
Therefore, the `.cjs` extension needs to be assiciated w/ JS-based configrations.
70-
71-
*/lib/cli-engine/config-array-factory.js*
67+
Add support for the `.cjs` file extension
7268

73-
```diff
74-
function loadConfigFile(filePath) {
75-
switch (path.extname(filePath)) {
76-
case ".js":
77-
+ case ".cjs:
78-
return loadJSConfigFile(filePath);
69+
*Note: In Node, `.cjs` will always load as a CommonJS module, irrespective of package configuration*
7970

80-
case ".json":
81-
if (path.basename(filePath) === "package.json") {
82-
return loadPackageJSONConfigFile(filePath);
83-
}
84-
return loadJSONConfigFile(filePath);
85-
86-
case ".yaml":
87-
case ".yml":
88-
return loadYAMLConfigFile(filePath);
89-
90-
default:
91-
return loadLegacyConfigFile(filePath);
92-
}
93-
}
94-
95-
```
71+
### Usage
9672

9773
If a user:
9874

@@ -113,45 +89,43 @@ None. The change has no negative functionality or performance impacts.
11389

11490
## People
11591

116-
Some devs within the Node ecosystem are strongly opposed to supporting `"type": "module"` based packages at all.
92+
Some developers within the Node ecosystem are strongly opposed to supporting `"type": "module"` at all.
11793

11894
## Backwards Compatibility Analysis
11995

12096
*tl;dr: Backward compatibility will be unaffected*
12197

12298
### Story 1 - ESLint Compatibility
12399

124-
This change is additive. It associates `.cjs` files with the existing Javascript configuration loader.
100+
This change is additive. It associates `.cjs` files with JS configurations.
125101

126-
The actual semantics of loading the CommonJS config do not change at all. The `.cjs` extension specifically exists as a escape hatch for this use case (ie loading a CommonJS file that exists in a ESM-based package).
102+
The existing semantics of loading CommonJS configurations for CommonJS-based packages do not change.
127103

128-
### Story 2 - Node Compatiblity
104+
### Story 2 - Node Compatibility
129105

130-
ESM support rolls out (as a non-experimental feature) w/ Node 13. This includes: the ability to define a package as a ESM; and backward interoperability w/ CJS in ES modules by using the `.cjs` file extension.
106+
In Node, ES module and `.cjs` support roll out together.
131107

132-
This change *only* impacts users who define their package as an ES module. Without Node 13+ that isn't possible.
108+
This change *only* impacts users who define their package as an ES module.
133109

134-
Existing packages that use the default (ie CommonJS) will be unaffected.
110+
Existing packages that use the default (i.e., CommonJS module resolution) will be unaffected.
135111

136112
## Alternatives
137113

138114
### Alternative 1 - Suppress the error
139115

140116
**[PR#12333](https://github.com/eslint/eslint/pull/12333)**
141117

142-
There is some debate among the node/modules group surrounding whether the error that causes this issue should be removed altogether.
118+
The fencing exists to prevent users from creating dual-mode (i.e., both ESM/CJS) packages as interoperability between the two format can cause issues. In the case of ESLint, loading a simple config file from a user-defined package should not cause any issues or side-effects.
143119

144-
The fencing exists to prevent users from creating dual-mode (ie both ESM/CJS) packages as interoperability between the two formats has issues. In the case of ESLint, loading a simple config file from a user-defined package should not cause any issues or side-effecs.
145-
146-
Eating the exception is viable solution. Although, this may become dead code if the error is removed from Node's core.
120+
Eating the exception is viable solution.
147121

148122
### Alternative 2 - Support dynamic `import()`
149123

150124
Instead of using 'import-fresh' to require the config, import it in a cross-format-compatible way using dynamic `import()`.
151125

152-
There has been and will continue to be a lot of talk about using this approach as the CJS/ESM interop story is fleshed out.
126+
There has been and will continue to be a lot of talk about using this approach as the CJS/ESM interoperability story is fleshed out.
153127

154-
For now it presents too many unknowns and -- even if it didn't -- adding `import()` to ESLint's core would force Node 13+ as a hard constraint.
128+
For now it presents too many unknowns.
155129

156130
## Open Questions
157131

@@ -179,17 +153,22 @@ For now it presents too many unknowns and -- even if it didn't -- adding `import
179153

180154
> Why doesn't compatibility w/ ESM-based packages 'just work'?
181155
182-
ESLint reaches across the package boundary to retrieve the user-defined configuration. If the consumer package is ESM-based, the `.js` config file will be seen as an ES module, and trying to `require()` it will throw an error.
156+
ESLint reaches across the package boundary to retrieve the user-defined configuration. If the consumer package is ESM-based, the `.js` config file will be seen as an ES module. When the ES module loader encounters `module.exports` (i.e., the CommonJS module specifier) it will throw an error.
157+
158+
> What does this interoperability issue affect?
159+
160+
Only ESM-based packages (i.e., packages with `"type": "module"` defined in package.json).
183161

184-
> What does this interop issue affect?
162+
> What about 3rd-party linting tools that provide their own built-in rule set for ESLint (ex [StandardJS](https://standardjs.com/))?
185163
186-
Only ESM-based packages (ie `"type": "module"`).
164+
They aren't impacted by this change. 3rd-party modules define both their code and the ESLint rule set used within the same package boundary.
187165

188-
> What about 3rd-party linting tools that provide their own built-in ruleset for ESLint (ex [StandardJS](https://standardjs.com/))?
166+
> What about support for ES module configurations
189167
190-
They aren't impacted by this change. 3rd-party modules define both their code and the ESLint ruleset used within the same package boundary.
168+
The scope of this RFC is limited to only ES module compatibility concerns. If there is a desire to add support for ESM-based configurations, it will need to be addressed in another RFC.
191169

192170
## Related Discussions
193171

194172
- [Issue #12321](https://github.com/eslint/eslint/pull/12321)
173+
- [PR #12321](https://github.com/eslint/eslint/pull/12321)
195174
- [Transition Path Problems for Tooling - node/modules](https://github.com/nodejs/modules/issues/388)

0 commit comments

Comments
 (0)