1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
|
---
title: Модули JavaScript
slug: Web/JavaScript/Guide/Modules
translation_of: Web/JavaScript/Guide/Modules
---
<div>{{JSSidebar("JavaScript Guide")}}{{Previous("Web/JavaScript/Guide/Meta_programming")}}</div>
<div>Это руководство содержит всю необходимую информацию для начала работы с модулями JavaScript.</div>
<h2 id="Модули_история_вопроса">Модули: история вопроса</h2>
<p>Сначала программы на JavaScript были небольшими — в прежние времена они использовались для изолированных задач, добавляя при необходимости немного интерактивности веб-страницам, так что большие скрипты в основном не требовались. Прошло несколько лет, и вот мы уже видим полномасштабные приложения, работающие в браузерах и содержащие массу кода на JavaScript; кроме того, язык стал использоваться и в других контекстах (например, <a href="/en-US/docs/Glossary/Node.js">Node.js</a>).</p>
<p>Таким образом, в последние годы появились причины на то, чтобы подумать о механизмах деления программ на JavaScript на отдельные модули, которые можно импортировать по мере необходимости. Node.js включал такую возможность уже давно, кроме того, некоторые библиотеки и фреймворки JavaScript разрешали использование модулей (например, <a href="https://en.wikipedia.org/wiki/CommonJS">CommonJS</a> и основанные на <a href="https://github.com/amdjs/amdjs-api/blob/master/AMD.md">AMD</a> системы модулей типа <a href="https://requirejs.org/">RequireJS</a>, а позднее также <a href="https://webpack.github.io/">Webpack</a> и <a href="https://babeljs.io/">Babel</a>).</p>
<p>К счастью, современные браузеры стали сами поддерживать функциональность модулей, о чем и рассказывает эта статья. Этому можно только порадоваться — браузеры могут оптимизировать загрузку модулей, что было бы гораздо эффективнее использования библиотеки, и взять на себя обработку на стороне клиента и прочие накладные расходы.</p>
<h2 id="Поддержка_в_браузерах">Поддержка в браузерах</h2>
<p>Встроенная обработка модулей JavaScript связана с функциями {{JSxRef("Statements/import", "import")}} и {{JSxRef("Statements/export", "export")}}, которые поддерживаются браузерами следующим образом:</p>
<h3 id="import">import</h3>
<p>{{Compat("javascript.statements.import")}}</p>
<h3 id="export">export</h3>
<p>{{Compat("javascript.statements.export")}}</p>
<h2 id="Пример_использования_модулей">Пример использования модулей</h2>
<p>Для того, чтобы продемонстрировать использование модулей, мы создали <a href="https://github.com/mdn/js-examples/tree/master/modules">простой набор примеров</a>, который вы можете найти на GitHub.
В этих примерах мы создаём элемент <a href="/en-US/docs/Web/HTML/Element/canvas" title="Используйте HTML <canvas> элемент вместе с Canvas API или с WebGL API для отрисовки графики и анимаций."><code><canvas></code></a>
на веб-странице и затем рисуем различные фигуры на нём (и выводим информацию об этом).</p>
<p>Примеры довольно тривиальны, но они намеренно сделаны простыми для ясной демонстрации модулей.</p>
<div class="blockIndicator note">
<p><strong>Примечание</strong>: Если вы хотите скачать примеры и запустить их локально, вам нужно будет запустить их через локальный веб-сервер.</p>
</div>
<h2 id="Базовая_структура_примера">Базовая структура примера</h2>
<p>В первом примере (см. директорию <a href="https://github.com/mdn/js-examples/tree/master/modules/basic-modules">basic-modules</a>) у нас следующая структура файлов:</p>
<pre class="notranslate">index.html
main.js
modules/
canvas.js
square.js</pre>
<div class="blockIndicator note">
<p><strong>Примечание</strong>: Все примеры в этом руководстве в основном имеют одинаковую структуру.</p>
</div>
<p>Давайте разберём два модуля из директории modules:</p>
<ul>
<li><code>canvas.js</code> — содержит функции, связанные с настройкой canvas:
<ul>
<li><code>create()</code> — создаёт холст заданной ширины <code>width</code> и высоты <code>height</code> внутри <a href="/en-US/docs/Web/HTML/Element/div" title="Элемент разделения контента (<div>) — это общий контейнер для потокового содержимого. Он не влияет на контент или макет, пока не будет стилизован с помощью CSS."><code><div></code></a>-обертки с указанным <code>id</code> и помещённой в родителя <code>parent</code>. Результатом выполнения функции будет объект, содержащий 2D-контекст холста и id обертки.</li>
<li><code>createReportList()</code> — создаёт неупорядоченный список, добавленный внутри указанного элемента-обёртки, который можно использовать для вывода данных отчёта. Возвращает id списка.</li>
</ul>
</li>
<li><code>square.js</code> — содержит:
<ul>
<li><code>name</code> — переменная со строковым значением 'square'.</li>
<li><code>draw()</code> — функция, рисующая квадрат на указанном холсте с заданными размером, положением и цветом. Возвращает объект, содержащий размер, положение и цвет квадрата.</li>
<li><code>reportArea()</code> — функция, которая выводит посчитанную площадь квадрата в указанный список отчета.</li>
<li><code>reportPerimeter()</code> — функция, которая выводи посчитанный периметр квадрата в указанный список отчета.</li>
</ul>
</li>
</ul>
<h2 id="Взгляд_со_стороны_—_.mjs_против_.js">Взгляд со стороны — <code>.mjs</code> против <code>.js</code></h2>
<p>В этой статье мы используем расширение <code>.js</code> для файлов наших модулей, но в других источниках вы можете встретить расширение <code>.mjs</code>. Например, <a href="https://v8.dev/features/modules#mjs">в документации движка V8 используется <code>.mjs</code></a>. Причины следующие:</p>
<ul>
<li>Это полезно для ясности, то есть дает понять, какие файлы являются модулями, а какие — обычными JavaScript-файлами.</li>
<li>Это гарантирует, что файлы вашего модуля будут проанализированы как модуль средами выполнения, такими как <a href="https://nodejs.org/api/esm.html#esm_enabling">Node.js</a>, и инструментами сборки, такими как <a href="https://babeljs.io/docs/en/options#sourcetype">Babel</a>.</li>
</ul>
<p>Тем не менее, мы решили продолжать использовать <code>.js</code>, по крайней мере на данным момент. Чтобы модули корректно работали в браузере, вам нужно убедиться, что ваш сервер отдаёт их с заголовком <code>Content-Type</code>, который содержит JavaScript MIME type такой как <code>text/javascript</code>. В противном случае вы получете ошибку проверки MIME type — "The server responded with a non-JavaScript MIME type", и браузер не сможет запустить ваш JavaScript. Большинство серверов уже имеют правильный тип для <code>.js</code>-файлов, но ещё не имеют нужного MIME type для <code>.mjs</code>-файлов. Серверы, которые уже отдают <code>.mjs</code> файлы корректно, включают в себя <a href="https://pages.github.com/">GitHub Pages</a>и <code><a href="https://github.com/http-party/http-server#readme">http-сервер</a></code> для Node.js.</p>
<p>Это нормально, если вы уже используете такую среду или ещё нет, но знаете, что делать, и имеете нужные доступы (то есть вы можете настроить свой сервер, чтобы он устанавливал корректный <code><a href="/en-US/docs/Web/HTTP/Headers/Content-Type">Content-Type</a></code>-заголовок для <code>.mjs</code>-файлов).
Однако это может вызвать путаницу, если вы не контролируете сервер, с которого отдаются файлы, или публикуете файлы для общего пользования, как мы здесь.</p>
<p>В целях обучения и переносимости на разные платформы мы решили остановится на <code>.js</code>.</p>
<p>Если вы действительно видите ценность и ясность использования <code>.mjs</code> для модулей по сравнению с использованием <code>.js</code> для обычных JavaScript-файлов,
но не хотите столкнуться с проблемой описанной выше, вы должны всегда использовать <code>.mjs</code> во время разработки
и конвертировать их в <code>.js</code> во время сборки.</p>
<p>Также стоит отметить, что:</p>
<ul>
<li>Некоторые инструменты могут никогда не добавить поддержку <code>.mjs</code>, например, <a href="https://www.typescriptlang.org/">TypeScript</a>.</li>
<li><code><script type="module"></code> атрибут используется для обозначения того, что файл является модулем. Вы увидите примеры использования данного атрибута ниже.</li>
</ul>
<h2 id="Экспорт_функционала_модуля">Экспорт функционала модуля</h2>
<p>Первое, что нужно сделать, чтобы получить доступ к функционалу модуля, — экспортировать его. Это делается с помощью инструкции {{JSxRef("Statements/export", "export")}}.</p>
<p>Самый простой способ использовать экспорт — поместить конструкцию <code>export</code> перед любыми элементами, которые вы хотите экспортировать из модуля, например:</p>
<pre class="brush: js; notranslate">export const name = 'square';
export function draw(ctx, length, x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, length, length);
return {
length: length,
x: x,
y: y,
color: color
};
}</pre>
<p>Вы можете экспортировать <code>var</code>-, <code>let</code>-, <code>const</code>-переменные, и — как мы увидим позже — классы.
Они должны быть в верхней области видимости, вы не можете использовать <code>export</code> внутри функции, например.</p>
<p>Более удобный способ экспорта всех элементов, которые вы хотите экспортировать,— использовать одну конструкцию <code>export</code> в конце файла модуля, где указать переменные, функции, классы, который вы хотите экспортировать, через запятую в фигурных скобках. Например:</p>
<pre class="brush: js; notranslate">export { name, draw, reportArea, reportPerimeter };</pre>
<h2 id="Импорт_функционала_в_ваш_скрипт">Импорт функционала в ваш скрипт</h2>
<p>После того, как вы экспортировали некоторые функции из своего модуля, вам необходимо импортировать их в свой скрипт, чтобы иметь возможность использовать их. Самый простой способ сделать это:</p>
<pre class="brush: js; notranslate">import { name, draw, reportArea, reportPerimeter } from './modules/square.js';</pre>
<p>Используйте конструкцию {{JSxRef("Statements/import", "import")}}, за которой следует разделенный запятыми список функций, которые вы хотите импортировать, заключённый в фигурные скобки, за которым следует ключевое слово from, за которым следует путь к файлу модуля — путь относительно корня сайта, который для нашего примера <code>basic-modules</code> будет равен <code>/js-examples/modules/basic-modules</code>.</p>
<p>Однако, мы написали путь немного иначе — мы используем (<code>.</code>) синтаксис, означающий "текущую директорию", за которым следует путь к файлу, который мы пытаемся найти.
Это намного лучше, чем каждый раз записывать весь относительный путь, поскольку он короче и делает URL-адрес переносимым - пример все равно будет работать, если вы переместите его в другое место в иерархии сайта.</p>
<p>Так например:</p>
<pre class="notranslate">/js-examples/modules/basic-modules/modules/square.js</pre>
<p>становится</p>
<pre class="notranslate">./modules/square.js</pre>
<p>Вы можете найти подобные строки кода в файле <code><a href="https://github.com/mdn/js-examples/blob/master/modules/basic-modules/main.js">main.js</a></code>.</p>
<div class="blockIndicator note">
<p><strong>Примечание</strong>: В некоторых модульных системах вы можете опустить расширение файла и начальные <code>/</code>, <code>./</code>, or <code>../</code> (например <code>'modules/square'</code>). Это не работает в нативных JavaScript-модулях.</p>
</div>
<p>После того, как вы импортировали функции в свой скрипт, вы можете использовать их так же, как если бы они были определены в этом же файле.
Следующий пример можно найти в <code>main.js</code>, сразу за строками импорта:</p>
<pre class="brush: js; notranslate">let myCanvas = create('myCanvas', document.body, 480, 320);
let reportList = createReportList(myCanvas.id);
let square1 = draw(myCanvas.ctx, 50, 50, 100, 'blue');
reportArea(square1.length, reportList);
reportPerimeter(square1.length, reportList);
</pre>
<div class="blockIndicator note">
<p><strong>Примечание: </strong>
Хотя импортированные функции доступны в файле, они доступны только для чтения.
Вы не можете изменить импортированную переменную, но вы всё равно можете изменять свойства у <code>const</code>-переменных.
Кроме того, переменные импортируется как "live bindings" -
это означает, что они могут меняться по значению, даже если вы не можете изменить привязку, в отличие от <code>const</code>.</p>
</div>
<h2 id="Добавление_модуля_на_HTML-страницу">Добавление модуля на HTML-страницу</h2>
<p>Далее нам необходимо подключить модуль <code>main.js</code> на нашу HTML-страницу. Это очень похоже на то, как мы подключаем обычный скрипт на страницу, с некоторыми заметными отличиями.</p>
<p>Прежде всего, вам нужно добавить <code>type="module"</code> в <a href="/en-US/docs/Web/HTML/Element/script" title="The HTML <script> элемент используется для встраивания исполняемого кода или ссылки на него; обычно используется для встраивания JavaScript кода или ссылки на него."><code><script></code></a>-элемент, чтобы объявить, что скрипт является модулем. Чтобы подключить модуль <code>main.js</code>, нужно написать следующее:</p>
<pre class="brush: html; no-line-numbers notranslate"><script type="module" src="main.js"></script></pre>
<p>Вы также можете встроить скрипт модуля непосредственно в HTML-файл, поместив JavaScript-код внутрь <code><script></code>-элемента:</p>
<pre class="brush: js notranslate"><script type="module">
/* код JavaScript модуля */
</script></pre>
<p>Скрипт, в который вы импортируете функционал модуля, в основном действует как модуль верхнего уровня.
Если вы упустите это, то Firefox, например, выдаст ошибку "SyntaxError: import declarations may only appear at top level of a module".</p>
<p>Вы можете использовать <code>import</code> и <code>export</code> инструкции только внутри модулей, внутри обычных скриптов они работать не будут.</p>
<h2 id="Другие_отличия_модулей_от_обычных_скриптов">Другие отличия модулей от обычных скриптов</h2>
<ul>
<li>Вы должны быть осторожны во время локального тестирование — если вы попытаетесь загрузить файл HTML локально (то есть по <code>file://</code> URL), вы столкнётесь с ошибками CORS из-за требований безопасности JavaScript-модулей. Вам нужно проводить тестирование через сервер.</li>
<li>Также обратите внимание, что вы можете столкнуться с отличным от обычных файлов поведением кода в модулях. Это происходит из-за того, что модули используют {{JSxRef("Strict_mode", "strict mode", "", 1)}} автоматически.</li>
<li>Нет необходимости использовать атрибут <code>defer</code> (см. <a href="/en-US/docs/Web/HTML/Element/script#Attributes" title="The HTML <script> элемент используется для встраивания исполняемого кода или ссылки на него; обычно используется для встраивания JavaScript кода или ссылки на него."> атрибуты <code><script></code> элемента</a>) при загрузке модуля, модули являются deferred по умолчанию.</li>
<li>Модули выполняются только один раз, даже если на них есть ссылки в нескольких <code><script></code> тэгах.</li>
<li>И последнее, но не менее важное: давайте проясним это — функции модуля импортируются в область видимости одного скрипта, они недоступны в глобальной области видимости.
Следовательно, вы сможете получить доступ к импортированному функционалу только в скрипте, в который он импортирован, и, например, вы не сможете получить к нему доступ из консоли JavaScript в DevTools.
Вы по-прежнему будете получать синтаксические ошибки в DevTools, но вы не сможете использовать некоторые методы отладки, которые, возможно, ожидали использовать.</li>
</ul>
<h2 id="Экпорт_по_умолчанию_против_именнованого_экспорта">Экпорт по умолчанию против именнованого экспорта</h2>
<p>Экспорты функций и переменных, которые мы использовали в примерах выше являются <strong>именованными экспортами</strong> — каждый элемент (будь то функция или <code>const</code>-переменная, например) упоминается по имени при экспорте, и это имя также используется для ссылки на него при импорте.</p>
<p>Существует также тип экспорта, который называется <strong>экспорт по умолчанию</strong> —
это сделано для того, чтобы упростить использование экпортируемого функционала сторонним модулем,
а также помогает модулям JavaScript взаимодействовать с существующими модульными системами CommonJS и AMD (это хорошо объясняется в статье <a href="https://hacks.mozilla.org/2015/08/es6-in-depth-modules/">ES6 в деталях: Модули</a> от Джейсон Орендорфа — ищите по ключевому слову «Default exports»).</p>
<p>Давайте посмотрим на пример, и мы объясним, как это работает.
В модуле <code>square.js</code> из нашего примера вы можете найти функцию <code>randomSquare()</code>, которая создаёт квардрат случайного цвета и размера со случайными координатами. Мы хотим экпортировать эту функции по умолчанию, поэтому в конце файла пишем следующее:</p>
<pre class="brush: js; notranslate">export default randomSquare;</pre>
<p>Обратите внимание на отсутствие фигурных скобок.</p>
<p>Кстати, можно было бы определить функцию как анонимную и добавить к ней <code>export default</code>:</p>
<pre class="brush: js; notranslate">export default function(ctx) {
...
}</pre>
<p>В нашем файле <code>main.js</code> мы импортируем функцию по умолчанию, используя эту строку:</p>
<pre class="brush: js; notranslate">import randomSquare from './modules/square.js';</pre>
<p>Снова обратите внимание на отсутствие фигурных скобок.
Такой синтакис допустим, поскольку для каждого модуля разрешен только один экспорт по умолчанию, и мы знаем, что это <code>randomSquare</code>.
Вышеупомянутая строка является сокращением для:</p>
<pre class="brush: js; notranslate">import {default as randomSquare} from './modules/square.js';</pre>
<div class="blockIndicator note">
<p><strong>Примечание</strong>: «as» синтаксис для переименования экспортируемых элементов поясняется ниже в разделе <a href="#Переименование_импорта_и_экмпорта">Переименование импорта и экмпорта</a>.</p>
</div>
<h2 id="Как_избежать_конфликтов_имён">Как избежать конфликтов имён</h2>
<p>Пока что наши модули для рисования фигур на холсте работают нормально.
Но что произойдёт, если мы попытаемся добавить модуль, который занимается рисованием другой фигуры, например круга или треугольника?
С этими фигурами, вероятно, тоже будут связаны такие функции, как <code>draw()</code>, <code>reportArea()</code> и т.д.;
если бы мы попытались импортировать разные функции с одним и тем же именем в один и тот же файл модуля верхнего уровня, мы бы столкнулись с конфликтами и ошибками.</p>
<p>К счастью, есть несколько способов обойти это. Мы рассмотрим их в следующих разделах.</p>
<h2 id="Переименование_импорта_и_экспорта">Переименование импорта и экспорта</h2>
<p>Внутри фигурных скобок инструкций <code>import</code> и <code>export</code> вы можете использовать ключевое слово <code>as</code> вместе с новым именем функционала, чтобы задать ему новое имя, которое вы будете использовать для него внутри модуля верхнего уровня.</p>
<p>Так, например, оба следующих элемента будут выполнять одну и ту же работу, хотя и немного по-разному:</p>
<pre class="brush: js; notranslate">// внутри module.js
export {
function1 as newFunctionName,
function2 as anotherNewFunctionName
};
// внутри main.js
import { newFunctionName, anotherNewFunctionName } from './modules/module.js';</pre>
<pre class="brush: js; notranslate">// внутри module.js
export { function1, function2 };
// внутри main.js
import { function1 as newFunctionName,
function2 as anotherNewFunctionName } from './modules/module.js';</pre>
<p>Давайте посмотрим на реальный пример. В нашей <a href="https://github.com/mdn/js-examples/tree/master/modules/renaming">renaming</a> директории
вы увидите ту же модульную систему, что и в предыдущем примере,
за исключением того, что мы добавили модули <code>circle.js</code> и <code>triangle.js</code> для рисования кругов и треугольников и создания отчетов по ним.</p>
<p>Внутри каждого из этих модулей у нас есть функции с одинаковыми именами, которые экспортируются, и поэтому у каждого из них есть один и тот же оператор <code>export</code> внизу файла:</p>
<pre class="brush: js; notranslate">export { name, draw, reportArea, reportPerimeter };</pre>
<p>Если бы в <code>main.js</code> при их импорте мы попытались использовать</p>
<pre class="brush: js; notranslate">import { name, draw, reportArea, reportPerimeter } from './modules/square.js';
import { name, draw, reportArea, reportPerimeter } from './modules/circle.js';
import { name, draw, reportArea, reportPerimeter } from './modules/triangle.js';</pre>
<p>то браузер выдал бы ошибку — "SyntaxError: redeclaration of import name" (Firefox).</p>
<p>Вместо этого нам нужно переименовать импорт, чтобы он был уникальным:</p>
<pre class="brush: js; notranslate">import { name as squareName,
draw as drawSquare,
reportArea as reportSquareArea,
reportPerimeter as reportSquarePerimeter } from './modules/square.js';
import { name as circleName,
draw as drawCircle,
reportArea as reportCircleArea,
reportPerimeter as reportCirclePerimeter } from './modules/circle.js';
import { name as triangleName,
draw as drawTriangle,
reportArea as reportTriangleArea,
reportPerimeter as reportTrianglePerimeter } from './modules/triangle.js';</pre>
<p>Обратите внимание, что вместо этого вы можете решить проблему в файлах модуля, например.</p>
<pre class="brush: js; notranslate">// внутри square.js
export { name as squareName,
draw as drawSquare,
reportArea as reportSquareArea,
reportPerimeter as reportSquarePerimeter };</pre>
<pre class="brush: js; notranslate">// внутри main.js
import { squareName, drawSquare, reportSquareArea, reportSquarePerimeter } from './modules/square.js';</pre>
<p>И это сработало бы точно так же.
Какой способ вы будете использовать, зависит от вас, однако, возможно, имеет смысл оставить код модуля в покое и внести изменения в импорт.
Это особенно важно, когда вы импортируете из сторонних модулей, над которыми у вас нет никакого контроля.</p>
<h2 id="Создание_объекта_модуля">Создание объекта модуля</h2>
<p>Вышеупомянутый способ работает нормально, но он немного запутан и многословен.
Существует решение получше — импортировать функции каждого модуля внутри объекта модуля.
Для этого используется следующая синтаксическая форма:</p>
<pre class="brush: js; notranslate">import * as Module from './modules/module.js';</pre>
<p>Эта конструкция берёт все экспорты, доступные внутри <code>module.js</code> и делает их доступными в качестве свойств объекта <code>Module</code>, фактически давая ему собственное пространство имен. Так например:</p>
<pre class="brush: js; notranslate">Module.function1()
Module.function2()
</pre>
и т.д.
<p>Опять же, давайте посмотрим на реальный пример. Если вы посмотрите на нашу директорию <a href="https://github.com/mdn/js-examples/tree/master/modules/module-objects">module-objects</a>,
вы снова увидите тот же самый пример, но переписанный с учётом преимуществ этого нового синтаксиса.
В модулях все экспорты представлены в следующей простой форме:</p>
<pre class="brush: js; notranslate">export { name, draw, reportArea, reportPerimeter };</pre>
<p>С другой стороны, импорт выглядит так:</p>
<pre class="brush: js; notranslate">import * as Canvas from './modules/canvas.js';
import * as Square from './modules/square.js';
import * as Circle from './modules/circle.js';
import * as Triangle from './modules/triangle.js';</pre>
<p>В каждом случае теперь вы можете получить доступ к импорту модуля под указанным свойством объекта, например:</p>
<pre class="brush: js; notranslate">let square1 = Square.draw(myCanvas.ctx, 50, 50, 100, 'blue');
Square.reportArea(square1.length, reportList);
Square.reportPerimeter(square1.length, reportList);</pre>
<p>Таким образом, теперь вы можете написать код точно так же, как и раньше (при условии, что вы включаете имена объектов там, где это необходимо), и импорт будет намного более аккуратным.</p>
<h2 id="Модули_и_классы">Модули и классы</h2>
<p>Как мы намекали ранее, вы также можете экспортировать и импортировать классы — это ещё один способ избежать конфликтов в вашем коде, и он особенно полезен, если у вас уже есть код модуля, написанный в объектно-ориентированном стиле.</p>
<p>Вы можете увидеть пример нашего модуля для рисования фигур, переписанного с помощью классов ES в нашей <a href="https://github.com/mdn/js-examples/tree/master/modules/classes">classes</a> директории.
В качестве примера, файд <code><a href="https://github.com/mdn/js-examples/blob/master/modules/classes/modules/square.js">square.js</a></code> теперь содержит всю свою функциональность в одном классе:</p>
<pre class="brush: js; notranslate">class Square {
constructor(ctx, listId, length, x, y, color) {
...
}
draw() {
...
}
...
}</pre>
<p>который мы затем экспортируем:</p>
<pre class="brush: js; notranslate">export { Square };</pre>
<p>Далее в <code><a href="https://github.com/mdn/js-examples/blob/master/modules/classes/main.js">main.js</a></code>, мы импортируем его так:</p>
<pre class="brush: js; notranslate">import { Square } from './modules/square.js';</pre>
<p>А затем используем импортированный класс, чтобы нарисовать наш квадрат:</p>
<pre class="brush: js; notranslate">let square1 = new Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');
square1.draw();
square1.reportArea();
square1.reportPerimeter();</pre>
<h2 id="Агрегирующие_модули">Агрегирующие модули</h2>
<p>Возможны случаи, когда вы захотите объединить модули вместе.
У вас может быть несколько уровней зависимостей, где вы хотите упростить вещи, объединив несколько подмодулей в один родительский модуль.
Это возможно с использованием следующего синтаксиса экспорта в родительском модуле:</p>
<pre class="brush: js; notranslate">export * from 'x.js'
export { name } from 'x.js'</pre>
<p>Для примера посмотрите на нашу директорию <a href="https://github.com/mdn/js-examples/tree/master/modules/module-aggregation">module-aggregation</a>.
В этом примере (на основе нашего предыдущего примера с классами) у нас есть дополнительный модуль с именем <code>shapes.js</code>,
который собирает функциональность <code>circle.js</code>, <code>square.js</code> и <code>triangle.js</code> вместе.
Мы также переместили наши подмодули в дочернюю директорию внутри директории <code>modules</code> под названием <code>shape</code>.
Итак, структура модуля в этом примере:</p>
<pre class="notranslate">modules/
canvas.js
shapes.js
shapes/
circle.js
square.js
triangle.js</pre>
<p>В каждом из подмодулей экспорт имеет одинаковую форму, например:</p>
<pre class="brush: js; notranslate">export { Square };</pre>
<p>Далее идет агрегирование.
Внутри <code><a href="https://github.com/mdn/js-examples/blob/master/modules/module-aggregation/modules/shapes.js">shapes.js</a></code>, мы добавляем следующие строки:</p>
<pre class="brush: js; notranslate">export { Square } from './shapes/square.js';
export { Triangle } from './shapes/triangle.js';
export { Circle } from './shapes/circle.js';</pre>
<p>Они берут экспорт из отдельных подмодулей и фактически делают их доступными из модуля <code>shape.js</code>.</p>
<div class="blockIndicator note">
<p><strong>Примечание</strong>: Экспорты, указанные в <code>shape.js</code>, по сути перенаправляются через файл и на самом деле там не существуют, поэтому вы не сможете написать какой-либо полезный связанный код внутри того же файла.</p>
</div>
<p>Итак, теперь в файле <code>main.js</code> мы можем получить доступ ко всем трём классам модулей, заменив:</p>
<pre class="brush: js; notranslate">import { Square } from './modules/square.js';
import { Circle } from './modules/circle.js';
import { Triangle } from './modules/triangle.js';</pre>
<p>на единственную строку кода:</p>
<pre class="brush: js; notranslate">import { Square, Circle, Triangle } from './modules/shapes.js';</pre>
<h2 id="Динамическая_загрузка_модулей">Динамическая загрузка модулей</h2>
<p>Самая свежая возмжность JavaScript-модулей доступная в браузерах,— это динамическая загрузка модулей.
Это позволяет вам динамически загружать модули только тогда, когда они необходимы, вместо того, чтобы загружать всё заранее.
Это даёт очевидные преимущества в производительности — давайте продолжим читать и посмотрим, как это работает.</p>
<p>Поддержка динамической загрузки модулей позволяет вызывать {{JSxRef("Statements/import", "import()", "#Dynamic_Imports")}} в качестве функции, передав ей аргументом путь к модулю.
Данный вызов возвращает {{JSxRef("Promise")}}, который резолвится объектом модуля (см. <a href="#Создание_объекта_модуля">Создание объекта модуля</a>), предоставляя вам доступ к экспорту указанного модуля, например:</p>
<pre class="brush: js; notranslate">import('./modules/myModule.js')
.then((module) => {
// делаем что-то с функционалом из импортированного модуля
});</pre>
<p>Давайте посмотрим на пример.
В директории <a href="https://github.com/mdn/js-examples/tree/master/modules/dynamic-module-imports">dynamic-module-imports</a> у нас есть ещё один пример, основанный на примере наших классов.
Однако на этот раз мы ничего не рисуем на холсте при загрузке страницы.
Вместо этого мы добавляем на страницу три кнопки — «Circle», «Square» и «Triangle», которые при нажатии динамически загружают требуемый модуль, а затем используют его для рисования указанной фигуры.</p>
<p>В этом примере мы внесли изменения только в наши <code><a href="https://github.com/mdn/js-examples/blob/master/modules/dynamic-module-imports/index.html">index.html</a></code> и <code><a href="https://github.com/mdn/js-examples/blob/master/modules/dynamic-module-imports/main.mjs">main.js</a></code> — экспорт модуля остается таким же, как и раньше.</p>
<p>Далее в <code>main.js</code> мы взяли ссылку на каждую кнопку, используя вызов <a href="/en-US/docs/Web/API/Document/querySelector"><code>document.querySelector()</code></a>:</p>
<pre class="brush: js; notranslate">let squareBtn = document.querySelector('.square');</pre>
<p>Затем мы добавляем обработчик событий на каждую кнопку, чтобы при нажатии соответствующий модуль динамически загружался и использовался для рисования фигуры:</p>
<pre class="brush: js; notranslate">squareBtn.addEventListener('click', () => {
import('./modules/square.js').then((Module) => {
let square1 = new Module.Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');
square1.draw();
square1.reportArea();
square1.reportPerimeter();
})
});</pre>
<p>Обратите внимание: поскольку выполнение Promise возвращает объект модуля, класс затем становится подкомпонентом объекта, поэтому теперь для получения доступа к конструктору нам нужно добавить к нему <code>Module.</code>, например <code>Module.Square(...)</code>.</p>
<h2 id="Устранение_проблем">Устранение проблем</h2>
<p>Вот несколько советов, которые могут помочь вам, если вам не удаётся заставить ваши модули работать.
Не стесняйтесь дополнять список, если найдете что-то ещё!</p>
<ul>
<li>Мы упоминали об этом раньше, но повторяем:<code>.js</code>-файлы должны быть загружены с MIME-type равным <code>text/javascript</code>
(или любым другим JavaScript-совместимым MIME-type, но <code>text/javascript</code> является рекомендованным),
в противном случае вы получите ошибку проверки MIME-type, например — "The server responded with a non-JavaScript MIME type".</li>
<li>Если вы попытаетесь загрузить HTML-файл локально (то есть по ссылке <code>file://</code>), вы столкнетесь с ошибками CORS из-за требований безопасности JavaScript-модулей.
Вам нужно проводить тестирование через сервер.
GitHub pages идеально для этого подходят, так как отдают <code>.js</code>-файлы с нужным MIME-type.</li>
<li>Поскольку <code>.mjs</code> — нестандартное расширение файла, некоторые операционные системы могут его не распознать или попытаться заменить на что-то другое.
Например, мы обнаружили, что macOS незаметно добавляла <code>.js</code> в конец файлов <code>.mjs</code>, а затем автоматически скрывала расширение файла.
Таким образом, все наши файлы на самом деле имели название типа <code>x.mjs.js</code>.
Когда мы отключили автоматическое скрытие расширений файлов и научили macOS принимать <code>.mjs</code>, всё стало в порядке.</li>
</ul>
<h2 id="Смотрите_также">Смотрите также</h2>
<ul>
<li><a href="https://developers.google.com/web/fundamentals/primers/modules#mjs">Ипользование JavaScript-модулей в вебе</a>, от Эдди Османи и Матиаса Байненса (англ.)</li>
<li><a href="https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/">Глубокое погружение в ES-модули в картинках</a>, от Лина Кларка на Hacks blog (англ.)</li>
<li><a href="https://medium.com/web-standards/es-modules-cartoon-dive-71f42c1e851a">Глубокое погружение в ES-модули в картинках</a>, перевод на русский язык от «Веб-стандартов»</li>
<li><a href="https://hacks.mozilla.org/2015/08/es6-in-depth-modules/">ES6 в деталях: Модули</a>, статья от Джейсона Орендорфа на Hacks blog (англ.)</li>
<li><a href="http://exploringjs.com/es6/ch_modules.html">Изучаем JS: Модули (англ)</a>, книга Акселя Раушмайера (англ.)</li>
</ul>
<p>{{Previous("Web/JavaScript/Guide/Meta_programming")}}</p>
|