Skip to content

Commit 62776d6

Browse files
Kevin SmithKevin Smith
authored andcommitted
Initial commit
0 parents  commit 62776d6

File tree

2 files changed

+202
-0
lines changed

2 files changed

+202
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.DS_Store
2+
_*
3+
node_modules

README.md

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# A Modest Proposal for ES6 Modules in Node.js
2+
3+
## Guiding Principles
4+
5+
- The solution must be 100% backward-compatible.
6+
- In the far future, developers should be able to write Node programs and libraries without knowledge of the CommonJS module system.
7+
- Module resolution rules should be reasonably compatible with the module resolution rules used by browsers.
8+
- The ability to import a legacy package is important for adoption.
9+
10+
## Design Summary
11+
12+
**1. There is no change to the behavior of `require`. It cannot be used to import ES6 modules.**
13+
14+
This ensures 100% backward-compatibility, while still allowing some freedom of design.
15+
16+
**2. The only folder entry point for ES6 modules is "default.js". "package.json" files are not used for resolving ES6 module paths.**
17+
18+
A distinct entry point file name ("default.js") allows us to detect when a user is attempting to import from a legacy package or a folder containing legacy modules.
19+
20+
**3. When `import`ing a file path, file extensions are not automatically appended.**
21+
22+
The default resolution algorithm used by web browsers will not automatically append file extensions.
23+
24+
**4. When `import`ing a directory, if a "default.js" file cannot be found, the algorithm will attempt to find an entry point using legacy `require` rules, by consulting "package.json" and looking for "index.*" files.**
25+
26+
This provides users with the ability to `import` from legacy packages.
27+
28+
**5. `require.import(modulePath)` synchronously imports an ES6 module.**
29+
30+
This allows old-style modules to `import` from new-style modules.
31+
32+
## Use Cases
33+
34+
### Existing modules
35+
36+
Since there is no change to the behavior of `require`, there is no change to the behavior of existing modules and packages.
37+
38+
### Supporting `import` for old-style packages
39+
40+
If a "default.js" file does not exist in the package root, then it will be loaded as an old-style module with no further changes. It just works.
41+
42+
### Supporting `require` for ES6 packages
43+
44+
Since `require` cannot be directly used to import ES6 modules, we need to provide an old-style "index.js" entry point if we want to allow consumers to `require` our package:
45+
46+
```
47+
src/
48+
[ES6 modules]
49+
default.js -> src/default.js
50+
index.js
51+
```
52+
53+
The purpose of the "index.js" file will be to map the ES6 module into an old-style module and can be as simple as:
54+
55+
```js
56+
// [index.js]
57+
module.exports = require.import('./src/default.js');
58+
```
59+
60+
### Distributing both transpiled and native ES6 modules
61+
62+
In this usage scenario, a package is authored in ES6 modules and transpiled to old-style modules using a compiler like Babel. A typical directory layout for such a project is:
63+
64+
```
65+
lib/
66+
[Transpiled modules]
67+
src/
68+
[ES6 modules]
69+
index.js -> lib/index.js
70+
```
71+
72+
Users that `require` the package will load the transpiled version of the code. If we want to allow `import`ing of this package, we can add a "default.js" file.
73+
74+
```
75+
lib/
76+
[Transpiled modules]
77+
src/
78+
[ES6 modules]
79+
index.js -> lib/index.js
80+
default.js -> src/index.js
81+
```
82+
83+
We might also want our transpiler to rename "default.js" source files to "index.js".
84+
85+
```
86+
lib/
87+
[Transpiled modules]
88+
src/
89+
[ES6 modules]
90+
index.js -> lib/index.js
91+
default.js -> src/default.js
92+
```
93+
94+
### Gradually migrating a project to ES modules
95+
96+
In this scenario, a user has a large project and wants to convert old-style modules to new style modules gradually.
97+
98+
**Option 1: Using a transpiler**
99+
100+
The project uses a transpiler to convert all code to old-style modules. Old-style modules are distributed to consumers. When all modules have been migrated, the transpiler can be removed.
101+
102+
**Option 2: Replacing require sites**
103+
104+
When converting an old-style module to the ES module syntax, use a script to update all internal modules which reference the converted module. The script would change occurrences of:
105+
106+
```js
107+
var someModule = require('./some-module');
108+
```
109+
110+
to:
111+
112+
```js
113+
var someModule = require.import('./some-module.js').default;
114+
```
115+
116+
### Deep-linking into a package
117+
118+
A common practice with old-style packages is to allow the user to `require` individual modules within the package source:
119+
120+
```js
121+
// Loads node_modules/foo/bar.js
122+
var deepModule = require('foo/bar');
123+
```
124+
125+
If the package author wants to support both `require`ing and `import`ing into a nested module, they can do so by creating a folder for each "deep link", which contains both an old-style and new-style entry point:
126+
127+
```
128+
bar/
129+
index.js (Entry point for require)
130+
default.js (Entry point for import)
131+
```
132+
133+
## Why "default.js"?
134+
135+
- "default.html" is frequently used as a folder entry point for web servers.
136+
- The word "default" has a special, and similar meaning in ES6 modules.
137+
- Despite "default" being a common English word, "default.js" is not widely used as a file name.
138+
139+
In a random sampling of 25,000 NPM packages (10% of the total number of packages), "default.js" was only found one time in a package root. This particular "default.js" file was already an ES6 module. As a filename, "default.js" was found only 174 times. By contrast, "index.js" was found 22,607 times, and in the package root 10,282 times.
140+
141+
## Running Modules from the Command Line
142+
143+
When a user executes
144+
145+
```sh
146+
$ node my-module.js
147+
```
148+
149+
from the command line, there is absolutely no way for Node to tell whether "my-module.js" is a legacy CJS module or an ES6 module. In the interest of backward compatibility, Node should probably attempt to load the file as a CJS module, and fallback to ES6 if there is a syntax error indicating the presence of `import` declarations. As people move away from CJS modules in general, future Node versions can assume that the file is an ES6 module.
150+
151+
## Lookup Algorithm Psuedo-Code
152+
153+
### LOAD_MODULE(X, Y, T)
154+
155+
Loads _X_ from a module at path _Y_. _T_ is either "require" or "import".
156+
157+
1. If X is a core module, then
158+
1. return the core module
159+
1. STOP
160+
1. If X begins with './' or '/' or '../'
161+
1. LOAD_AS_FILE(Y + X, T)
162+
1. LOAD_AS_DIRECTORY(Y + X, T)
163+
1. LOAD_NODE_MODULES(X, dirname(Y), T)
164+
1. THROW "not found"
165+
166+
### LOAD_AS_FILE(X, T)
167+
168+
1. If T is "import",
169+
1. If X is a file, then
170+
1. If extname(X) is ".js", load X as ES6 module text. STOP
171+
1. If extname(X) is ".json", parse X to a JavaScript Object. STOP
172+
1. If extname(X) is ".node", load X as binary addon. STOP
173+
1. THROW "not found"
174+
1. Else,
175+
1. Assert: T is "require"
176+
1. If X is a file, load X as CJS module text. STOP
177+
1. If X.js is a file, load X.js as CJS module text. STOP
178+
1. If X.json is a file, parse X.json to a JavaScript Object. STOP
179+
1. If X.node is a file, load X.node as binary addon. STOP
180+
181+
### LOAD_AS_DIRECTORY(X, T)
182+
183+
1. If T is "import",
184+
1. If X/default.js is a file, load X/default.js as ES6 module text. STOP
185+
1. NOTE: If X/default.js is not a file, then fallback to legacy behavior
186+
1. If X/package.json is a file,
187+
1. Parse X/package.json, and look for "main" field.
188+
1. let M = X + (json main field)
189+
1. LOAD_AS_FILE(M, "require")
190+
1. If X/index.js is a file, load X/index.js as JavaScript text. STOP
191+
1. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
192+
1. If X/index.node is a file, load X/index.node as binary addon. STOP
193+
194+
### LOAD_NODE_MODULES(X, START, T)
195+
196+
1. let DIRS=NODE_MODULES_PATHS(START)
197+
2. for each DIR in DIRS:
198+
1. LOAD_AS_FILE(DIR/X, T)
199+
1. LOAD_AS_DIRECTORY(DIR/X, T)

0 commit comments

Comments
 (0)