Skip to content

Commit 4d1c2b8

Browse files
authored
2025/4/11
1 parent fdafbb1 commit 4d1c2b8

File tree

1 file changed

+226
-1
lines changed

1 file changed

+226
-1
lines changed

README.md

Lines changed: 226 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,235 @@
5555

5656

5757
> [!NOTE]
58-
> Last updated 2025/04/06
58+
> updated 2025/04/11
5959
60+
### Different Types of Imports and Exports
6061

62+
> an overview of how different imports and exports behave
6163
64+
```javascript
65+
// These give you a live reference:
66+
import { thing } from './module.js';
67+
import { thing as otherName } from './module.js';
68+
import * as module from './module.js';
69+
const module = await import('./module.js');
70+
71+
// This assigns the current value:
72+
let { thing } = await import('./module.js');
73+
```
74+
75+
## The Special Case of `export default`
76+
77+
The `export default` syntax has different semantics than named exports.
78+
79+
```javascript
80+
// module.js
81+
let thing = 'initial';
82+
export { thing };
83+
export default thing;
84+
setTimeout(() => {
85+
thing = 'changed';
86+
}, 500);
87+
```
88+
89+
> Export Default Behavior
90+
91+
92+
### Why the Difference?
93+
94+
The reason for this behavior is that `export default` allows exporting values directly:
95+
96+
```javascript
97+
export default 'hello!'; // This works
98+
export { 'hello!' as thing }; // This doesn't work
99+
```
100+
101+
## The Third Way: `export { thing as default }`
102+
103+
There's another way to export a default that behaves differently:
104+
105+
```javascript
106+
// module.js
107+
let thing = 'initial';
108+
export { thing, thing as default };
109+
setTimeout(() => {
110+
thing = 'changed';
111+
}, 500);
112+
```
113+
114+
This method maintains the live reference, unlike `export default thing`.
115+
116+
### Special Case: `export default function`
117+
118+
Functions get special treatment:
119+
120+
```javascript
121+
// module.js
122+
export default function thing() {}
123+
setTimeout(() => {
124+
thing = 'changed';
125+
}, 500);
126+
127+
// main.js
128+
import thing from './module.js';
129+
setTimeout(() => {
130+
console.log(thing); // "changed"
131+
}, 1000);
132+
```
133+
134+
> Function Export Behavior
135+
136+
137+
## Circular Dependencies and Hoisting
138+
139+
### Function Hoisting Behavior
140+
141+
```javascript
142+
thisWorks(); // This runs fine
143+
function thisWorks() {
144+
console.log('yep, it does');
145+
}
146+
```
147+
148+
> [!NOTE]
149+
> Function declarations are hoisted, but other declarations aren't:
150+
151+
```javascript
152+
assignedFunction(); // Doesn't work
153+
new SomeClass(); // Doesn't work
154+
155+
const assignedFunction = function() {
156+
console.log('nope');
157+
};
158+
class SomeClass {}
159+
```
160+
161+
### Circular Dependencies Example
162+
163+
```javascript
164+
// main.js
165+
import { foo } from './module.js';
166+
foo();
167+
export function hello() {
168+
console.log('hello');
169+
}
170+
```
171+
172+
```javascript
173+
// module.js
174+
import { hello } from './main.js';
175+
hello();
176+
export function foo() {
177+
console.log('foo');
178+
}
179+
```
180+
181+
This works due to hoisting. However, changing to arrow functions breaks it:
182+
183+
```javascript
184+
// main.js
185+
import { foo } from './module.js';
186+
foo();
187+
export const hello = () => console.log('hello');
188+
```
189+
190+
```javascript
191+
// module.js
192+
import { hello } from './main.js';
193+
hello();
194+
export const foo = () => console.log('foo');
195+
```
196+
197+
## Summary
198+
199+
The complete behavior overview:
200+
201+
```javascript
202+
// Live references:
203+
import { thing } from './module.js';
204+
import { thing as otherName } from './module.js';
205+
import * as module from './module.js';
206+
const module = await import('./module.js');
207+
208+
// Value copy:
209+
let { thing } = await import('./module.js');
210+
211+
// Live reference exports:
212+
export { thing };
213+
export { thing as otherName };
214+
export { thing as default };
215+
export default function thing() {}
216+
217+
// Value exports:
218+
export default thing;
219+
export default 'hello!';
220+
```
221+
222+
_Note: The original article was written by Jake Archibald, with contributions from the V8 team members Toon Verwaest, Marja Hölttä, and Mathias Bynens, as well as Dave Herman and Daniel Ehrenberg._
223+
224+
### Best Practices
225+
226+
1. Avoid circular dependencies whenever possible
227+
2. Be aware of the difference between value exports and reference exports
228+
3. Consider using `export { thing as default }` instead of `export default thing` when you need to maintain live bindings
229+
4. Remember that `export default function` is a special case that maintains references
230+
231+
232+
```mermaid
233+
graph TD
234+
subgraph "Export Types"
235+
A["Named Export<br>export { thing }"] -->|"Live Reference"| D["Import { thing }"]
236+
A -->|"Live Reference"| E["Import { thing as otherName }"]
237+
A -->|"Live Reference"| F["Import * as module"]
238+
A -->|"Live Reference"| G["await import('./module.js')"]
239+
240+
B["Default Export<br>export default thing"] -->|"Value Copy"| H["Import default"]
241+
242+
C["Function Export<br>export default function(){}"] -->|"Live Reference"| I["Import default function"]
243+
244+
J["Named as Default<br>export { thing as default }"] -->|"Live Reference"| K["Import default"]
245+
end
246+
247+
subgraph "Import Types"
248+
D
249+
E
250+
F
251+
G -->|"Value Copy"| L["let { thing } = await import()"]
252+
G -->|"Live Reference"| M["const module = await import()"]
253+
H
254+
I
255+
K
256+
end
257+
258+
subgraph "Circular Dependencies"
259+
N["Function Declaration<br>export function thing(){}"] -->|"Works due to hoisting"| O["Circular Import"]
260+
P["Arrow Function<br>export const thing = ()=>{}"] -->|"Fails - no hoisting"| Q["Circular Import Fails"]
261+
end
262+
263+
style A fill:#d4f1f9,stroke:#333
264+
style B fill:#ffdfba,stroke:#333
265+
style C fill:#ffdfba,stroke:#333,stroke-dasharray: 5 5
266+
style J fill:#d4f1f9,stroke:#333,stroke-dasharray: 5 5
267+
style N fill:#d8f8e1,stroke:#333
268+
style P fill:#ffbaba,stroke:#333
269+
```
270+
271+
#### Named exports (blue boxes) create live references when imported through any import syntax
272+
273+
Regular named exports maintain live bindings to the original variables
274+
The special export { thing as default } syntax also maintains live binding
275+
276+
277+
#### Default exports (orange boxes) behave differently:
278+
279+
Regular export default thing creates a value copy (not a reference)
280+
The special case export default function() {} maintains a live reference
281+
282+
283+
#### Circular dependencies behavior:
284+
285+
Function declarations (green box) work in circular dependencies due to hoisting
286+
Arrow functions and const declarations (red box) fail in circular dependencies
62287

63288
<a name="es6-exports--imports-cheat-sheet"></a>
64289
### ES6 exports / imports cheat sheet

0 commit comments

Comments
 (0)