Skip to content

Commit b8f6d96

Browse files
committed
modules: draft
1 parent 33b67f0 commit b8f6d96

File tree

32 files changed

+2152
-0
lines changed

32 files changed

+2152
-0
lines changed
Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
2+
# Modules, the introduction
3+
4+
As our application grows bigger, we want to split it into multiple files, so called 'modules'.
5+
A module usually contains a class or a library of useful functions.
6+
7+
For a long time, Javascript existed without a language-level module syntax. That wasn't a problem, because initially scripts were small and simple. So there was no need.
8+
9+
But eventually scripts became more and more complex, so the community invented a variety of ways to organize code into modules.
10+
11+
For instance:
12+
13+
- [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- one of the most ancient module systems, initially implemented by the library [require.js](http://requirejs.org/).
14+
- [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) -- the module system created for Node.JS server.
15+
- [UMD](https://github.com/umdjs/umd) -- one more module system, suggested as a universal one, compatible with AMD and CommonJS.
16+
17+
Now all these slowly become a part of history, but we still can find them in old scripts. The language-level module system appeared in the standard in 2015, gradually evolved since then, and is now supported by all major browsers and in Node.js.
18+
19+
## What is a module?
20+
21+
A module is just a file, a single script, as simple as that.
22+
23+
Directives `export` and `import` allow to interchange functionality between modules:
24+
25+
- `export` keyword labels variables and functions that should be accessible from outside the file.
26+
- `import` allows to import functionality from other modules.
27+
28+
For instance, if we have a file `sayHi.js` exporting a function:
29+
30+
```js
31+
// 📁 sayHi.js
32+
export function sayHi(user) {
33+
alert(`Hello, ${user}!`);
34+
}
35+
```
36+
37+
...Then another file may import and use it:
38+
39+
```js
40+
// 📁 main.js
41+
import {sayHi} from './sayHi.js';
42+
43+
alert(sayHi); // function...
44+
sayHi('John'); // Hello, John!
45+
```
46+
47+
## Modules in the browser
48+
49+
In this tutorial we concentrate on the language itself, but we use browser as the demo environment, so let's see how modules work in the browser.
50+
51+
To use modules, we must set the attribute `<script type="module">`, like this:
52+
53+
[codetabs src="say" height="140" current="index.html"]
54+
55+
The browser automatically fetches and evaluates imports, then runs the script.
56+
57+
Module scripts, have several important differences, that's why `type="module"` is required (skip this part if you're not interested in browser javascript):
58+
59+
60+
1. External scripts that are fetched from another domain require [CORS](mdn:Web/HTTP/CORS) headers. In other words, if a module script is fetched from another domain, the remote server must supply a header `Access-Control-Allow-Origin: *` (may use fetching domain instead of `*`) to indicate that the fetch is allowed.
61+
```html
62+
<!-- another-site.com must supply Access-Control-Allow-Origin -->
63+
<!-- otherwise, the script won't execute -->
64+
<script type="module" src="*!*http://another-site.com/their.js*/!*"></script>
65+
```
66+
67+
That ensures better security by default.
68+
69+
2. External scripts with same `src` run only once:
70+
```html
71+
<!-- the script my.js is fetched and executed only once -->
72+
<script type="module" src="my.js"></script>
73+
<script type="module" src="my.js"></script>
74+
```
75+
76+
3. Module scripts are *always* deferred, same effect as `defer` attribute (described in the chapter [](info:onload-ondomcontentloaded)), for both external and inline scripts.
77+
78+
In other words:
79+
- external module scripts `<script type="module" src="...">` don't block HTML processing.
80+
- module scripts wait until the HTML document is fully ready.
81+
- relative order is maintained: scripts that go first in the document, execute first.
82+
83+
As a side-effect, module scripts always see HTML elements below them.
84+
85+
For instance:
86+
87+
```html run
88+
<script type="module">
89+
*!*
90+
alert(button); // HTMLButtonElement: the script can 'see' elements below
91+
*/!*
92+
// as modules are deferred, the script runs after the whole page is loaded
93+
</script>
94+
95+
<script>
96+
*!*
97+
alert(button); // Error: button is undefined, the script can't see elements below
98+
*/!*
99+
// regular scripts run immediately, before the rest of the page is processed
100+
</script>
101+
102+
<button id="button">Button</button>
103+
```
104+
105+
So, when we're using modules, we should be aware that HTML-document can show up before the Javascript application is ready. Certain functionality may not work yet. We should put transparent overlays or "loading indicators", or otherwise ensure that the visitor won't be confused because of it.
106+
107+
4. Async attribute `<script async type="module">` is allowed on both inline and external scripts. Async scripts run immediately when imported modules are processed, independantly of other scripts or the HTML document.
108+
109+
For example, the script below has `async`, so it doesn't wait for anyone.
110+
111+
It performs the import (fetches `./analytics.js`) and runs when ready, even if HTML document is not finished yet, or if other scripts are still pending.
112+
113+
That's good for functionality that doesn't depend on anything, like counters, ads, document-level event listeners.
114+
115+
```html
116+
<!-- all dependencies are fetched (analytics.js), and the script runs -->
117+
<!-- doesn't wait for the document or other <script> tags -->
118+
<script *!*async*/!* type="module">
119+
import {counter} from './analytics.js';
120+
121+
counter.count();
122+
</script>
123+
```
124+
125+
Also, in the browser, in scripts (not in HTML), `import` must get either a relative or absolute URL. So-called "bare" modules (without path) are not allowed.
126+
127+
For instance, this `import` is invalid:
128+
```js
129+
import {sayHi} from 'sayHi'; // Error, "bare" module
130+
// must be './sayHi.js' or wherever the module is
131+
```
132+
133+
Certain environments, like Node.js or bundle tools allow bare modules, as they have own ways for finding modules and hooks to fine-tune them. But browsers do not support bare modules yet.
134+
135+
### Build tools
136+
137+
In real-life, browser modules are rarely used in their "raw" form. Usually, we bundle them together with a special tool such as [Webpack](https://webpack.js.org/) and deploy to the production server.
138+
139+
One of the benefits of using bundlers -- they give more control over how modules are resolved, allowing bare modules and much more, like CSS/HTML modules.
140+
141+
Build tools do the following:
142+
143+
1. Take a "main" module, the one intended to be put in `<script type="module">` in HTML.
144+
2. Analyze its dependencies: imports and then imports of imports etc.
145+
3. Build a single file with all modules (or multiple files, that's tunable), replacing native `import` calls with bundler functions, so that it works. "Special" module types like HTML/CSS modules are also supported.
146+
4. In the process, other transforms and optimizations may be applied:
147+
- Unreachable code removed.
148+
- Unused exports removed ("tree-shaking").
149+
- Development-specific statements like `console` and `debugger` removed.
150+
- Modern, bleeding-edge Javascript syntax may be transformed to older one with similar functionality using [Babel](https://babeljs.io/).
151+
- The resulting file is minified (spaces removed, variables replaced with shorter named etc).
152+
153+
That said, native modules are also usable. So we won't be using Webpack here: you can configure it later.
154+
155+
### Compatibility: "nomodule"
156+
157+
Old browsers do not understand `type="module"`. Scripts of the unknown type are just ignored. For them, it's possible to provide a fallback using `nomodule` attribute:
158+
159+
```html run
160+
<script type="module">
161+
alert("Runs in modern browsers");
162+
</script>
163+
164+
<script nomodule>
165+
alert("Modern browsers know both type=module and nomodule, so skip this")
166+
alert("Old browsers ignore script with unknown type=module, but execute this.");
167+
</script>
168+
```
169+
170+
If we use bundle tools, then as modules are bundled together, their `import/export` statements are replaced by special bundler calls, so the resulting build does not require `type="module"`, and we can put it into a regular script:
171+
172+
```html
173+
<!-- Assuming we got bundle.js from a tool like Webpack -->
174+
<script src="bundle.js"></script>
175+
```
176+
177+
178+
## Core module features
179+
180+
In the previous section we covered browser-specific features. Now it should be easy to start using modules in the browser.
181+
182+
There are other module features, not bound to the host environment, valid both for browser and Node.js.
183+
184+
### Always "use strict"
185+
186+
Modules always `use strict`. E.g. assigning to an undeclared variable will give an error.
187+
188+
```html run
189+
<script type="module">
190+
a = 5; // error
191+
</script>
192+
```
193+
194+
Here we can see it in the browser, but the same is true for any module.
195+
196+
### Module-level scope
197+
198+
Each module has its own top-level scope. In other words, top-level variables and functions from a module are not seen in other scripts. Modules are expected to `export` what they want to be accessible from outside.
199+
200+
Independant top-level scope exists for `<script type="module">` as well:
201+
202+
```html run
203+
<script type="module">
204+
// The variable is only visible in this module script
205+
let user = "John";
206+
</script>
207+
208+
<script type="module">
209+
*!*
210+
alert(user); // Error: user is not defined
211+
*/!*
212+
</script>
213+
```
214+
215+
If we really need to share the variable, we can explicitly assign it to `window.user`. But that's an exception requiring a good reason.
216+
217+
### A module code is evaluated only the first time when imported
218+
219+
If a same module is imported into multiple other places, it's code is executed only the first time, then exports are given to all importers.
220+
221+
That has important consequences. Let's see that on examples.
222+
223+
First, if executing a module code brings side-effects, like showing a message, then importing it multiple times will trigger it only once -- the first time:
224+
225+
```js
226+
// 📁 alert.js
227+
alert("Module is evaluated!");
228+
```
229+
230+
```js
231+
// Import the same module from different files
232+
233+
// 📁 1.js
234+
import `./alert.js`; // Module is evaluated!
235+
236+
// 📁 2.js
237+
import `./alert.js`; // (nothing)
238+
```
239+
240+
In practice, top-level module code is mostly used for initialization. We create data structures, pre-fill them, and if we want something to be reusable -- export it.
241+
242+
Now, a more advanced example.
243+
244+
Let's say, a module exports an object:
245+
246+
```js
247+
// 📁 admin.js
248+
export let admin = {
249+
name: "John"
250+
};
251+
```
252+
253+
If this module is imported from multiple files, the module is only evaluated the first time, `admin` object is created, and then passed to all further importers.
254+
255+
All importers get exactly the one and only `admin` object:
256+
257+
```js
258+
// 📁 1.js
259+
import {admin} from './admin.js';
260+
admin.name = "Pete";
261+
262+
// 📁 2.js
263+
import {admin} from './admin.js';
264+
alert(admin.name); // Pete
265+
266+
*!*
267+
// Both 1.js and 2.js imported the same object
268+
// Changes made in 1.js are visible in 2.js
269+
*/!*
270+
```
271+
272+
So, let's reiterate -- the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other modules will see that .
273+
274+
Such behavior is great for modules that require configuration. We can set required properties on the first import, and then in further imports it's ready.
275+
276+
For instance, `admin.js` module may provide certain functionality, but expect the credentials to come into the `admin` object from outside:
277+
278+
```js
279+
// 📁 admin.js
280+
export let admin = { };
281+
282+
export function sayHi() {
283+
alert(`Ready to serve, ${admin.name}!`);
284+
}
285+
```
286+
287+
Now, in `init.js`, the first script of our app, we set `admin.name`. Then everyone will see it, including calls made from inside `admin.js` itself:
288+
289+
```js
290+
// 📁 init.js
291+
import {admin} from './admin.js';
292+
admin.name = "Pete";
293+
```
294+
295+
```js
296+
// 📁 other.js
297+
import {admin, sayHi} from './admin.js';
298+
299+
alert(admin.name); // *!*Pete*/!*
300+
301+
sayHi(); // Ready to serve, *!*Pete*/!*!
302+
```
303+
304+
## Summary
305+
306+
To summarize, the core concepts are:
307+
308+
1. A module is a file. To make `import/export` work, browsers need `<script type="module">`, that implies several differences:
309+
- External scripts need CORS headers.
310+
- Duplicate external scripts are ignored.
311+
- Deferred by default.
312+
- Async works on inline scripts.
313+
2. Modules have their own, local top-level scope and interchange functionality via `import/export`.
314+
3. Modules always `use strict`.
315+
4. Module code is executed only once. Exports are created once and shared between importers.
316+
5. In production, we usually use bundlers such as [Webpack](https://webpack.js.org) to bundle modules together.
317+
318+
In the next chapter we'll see more examples of modules and `export/import` statements.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
</head>
6+
<body>
7+
8+
<script type="module">
9+
import weekdays from './weekdays.js';
10+
11+
alert(weekdays[0]);
12+
</script>
13+
14+
</body>
15+
</html>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];

0 commit comments

Comments
 (0)