aboutsummaryrefslogtreecommitdiff
path: root/files/es/web/javascript/guide/módulos/index.html
blob: eacc6835f6356aac7aa9d8d2caa25443c7550aa8 (plain)
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
---
title: Módulos JavaScript
slug: Web/JavaScript/Guide/Módulos
tags:
  - Guía
  - JavaScript
  - Modules
  - Módulos
  - export
  - import
translation_of: Web/JavaScript/Guide/Modules
---
<div>{{JSSidebar("Guía de JavaScript")}}{{Previous("Web/JavaScript/Guide/Meta_programming")}}</div>

<p>Esta guía te brinda todo lo que necesitas para comenzar con la sintaxis de los módulos JavaScript.</p>

<h2 id="Un_antecedente_sobre_módulos">Un antecedente sobre módulos</h2>

<p>Los programas JavaScript comenzaron siendo bastante pequeños — la mayor parte de su uso en los primeros días era para realizar tareas de scripting aisladas, proporcionando un poco de interactividad a tus páginas web donde fuera necesario, por lo que generalmente no se necesitaban grandes scripts. Avancemos unos años y ahora tenemos aplicaciones completas que se ejecutan en navegadores con mucho JavaScript, JavaScript ahora se usa en otros contextos (<a href="/es/docs/Glossary/Node.js">Node.js</a>, por ejemplo).</p>

<p>Por lo tanto, en los últimos años se ha comenzado a pensar en proporcionar mecanismos para dividir programas JavaScript en módulos separados que se puedan importar cuando sea necesario. Node.js ha tenido esta capacidad durante mucho tiempo, y hay una serie de bibliotecas y marcos de JavaScript que permiten el uso de módulos (por ejemplo, <a href="https://en.wikipedia.org/wiki/CommonJS">CommonJS</a> y <a href="https://github.com/amdjs/amdjs-api/blob/master/AMD.md">AMD</a> otros basados en sistemas de módulos como <a href="https://requirejs.org/">RequireJS</a>, y recientemente <a href="https://webpack.github.io/">Webpack</a> y <a href="https://babeljs.io/">Babel</a>).</p>

<p>La buena noticia es que los navegadores modernos han comenzado a admitir la funcionalidad de los módulos de forma nativa, y de esto se trata este artículo. Esto solo puede ser algo bueno — los navegadores pueden optimizar la carga de módulos, haciéndolo más eficiente que tener que usar una biblioteca y hacer todo ese procesamiento adicional de lado del cliente, ahorrando viajes de ida y vuelta adicionales.</p>

<h2 id="Soporte_del_navegador">Soporte del navegador</h2>

<p>El uso de módulos JavaScript nativos depende de las declaraciones {{jsxref("Statements/import", "import")}} y {{jsxref("Statements/export", "export")}}; estas son compatibles con los navegadores de la siguiente manera:</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="Introducción_—_un_ejemplo">Introducción — un ejemplo</h2>

<p>Para demostrar el uso de módulos, hemos creado un <a href="https://github.com/mdn/js-examples/tree/master/modules">sencillo conjunto de ejemplos</a> que puedes encontrar en GitHub. Estos ejemplos demuestran un sencillo conjunto de módulos que crean un elemento <a href="/es/docs/Web/HTML/Element/canvas" title="Usa el elemento ↑&lt;canvas>↓ de HTML con el scripting de la API de canvas o la API WebGL para dibujar gráficos y animaciones."><code>&lt;canvas&gt;</code></a> en una página web, y luego dibujan (y reportan información sobre) diferentes formas en el lienzo.</p>

<p>Estos son bastante triviales, pero se han mantenido deliberadamente simples para demostrar los módulos con claridad.</p>

<div class="blockIndicator note">
<p><strong>Nota</strong>: Si deseas descargar los ejemplos y ejecutarlos localmente, deberás ejecutarlos a través de un servidor web local.</p>
</div>

<h2 id="Estructura_básica_de_los_ejemplos">Estructura básica de los ejemplos</h2>

<p>En nuestro primer ejemplo (ve <a href="https://github.com/mdn/js-examples/tree/master/modules/basic-modules">basic-modules</a>) tenemos la siguiente estructura de archivos:</p>

<pre class="notranslate">index.html
main.js
modules/
    canvas.js
    square.js</pre>

<div class="blockIndicator note">
<p><strong>Nota</strong>: Todos los ejemplos de esta guía básicamente tienen la misma estructura; lo anterior debería empezar a resultarte bastante familiar.</p>
</div>

<p>Los dos módulos del directorio <code>modules</code> se describen a continuación:</p>

<ul>
 <li><code>canvas.js</code> — contiene funciones relacionadas con la configuración del lienzo (<code>canvas</code>):

  <ul>
   <li><code>create()</code> — crea un lienzo (<code>canvas</code>) con un <code>width</code> y <code>height</code> especificados dentro de un contenedor <a href="/es/docs/Web/HTML/Element/div" title='El elemento ↑div↑ de HTML con ↑id="content"↓ es el contenedor genérico para el flujo de contenido. No tiene ningún efecto en el contenido o el diseño hasta que se le aplica estilo usando CSS.'><code>&lt;div&gt;</code></a> con un ID especificado, que a su vez se añade dentro de un elemento padre especificado. Devuelve un objeto que contiene el contexto 2D del lienzo y el ID del contenedor.</li>
   <li><code>createReportList()</code> — crea una lista desordenada adjunta dentro de un elemento contenedor específico, que se puede usar para generar datos de informes. Devuelve el ID de la lista.</li>
  </ul>
 </li>
 <li><code>square.js</code> — contiene:
  <ul>
   <li><code>name</code> — una constante que contiene la cadena 'square'.</li>
   <li><code>draw()</code> — dibuja una figura cuadrada en un lienzo específico, con un tamaño, posición y color específicos. Devuelve un objeto que contiene el tamaño, la posición y el color del cuadrado.</li>
   <li><code>reportArea()</code> — escribe el área de un cuadrado en una lista de informes específica, dada su longitud.</li>
   <li><code>reportPerimeter()</code> — escribe el perímetro de un cuadrado en una lista de informes específica, dada su longitud.</li>
  </ul>
 </li>
</ul>

<h2 id="Reflexión_—_.mjs_versus_.js">Reflexión — <code>.mjs</code> versus <code>.js</code></h2>

<p>A través de este artículo, usaremos extensiones <code>.js</code> para nuestros archivos de módulo, pero en otros recursos, puedes ver que en su lugar se usa la extensión <code>.mjs</code>. <a href="https://v8.dev/features/modules#mjs">La documentación de V8 recomienda esto</a>, por ejemplo. Las razones dadas son:</p>

<ul>
 <li>Es bueno por claridad, es decir, deja claro qué archivos son módulos y cuáles JavaScript.</li>
 <li>Asegura que tus archivos de módulo sean analizados como un módulo por los entornos de ejecución como <a href="https://nodejs.org/api/esm.html#esm_enabling">Node.js</a> y herramientas de compilación como <a href="https://babeljs.io/docs/en/options#sourcetype">Babel</a>.</li>
</ul>

<p>Sin embargo, decidimos seguir usando <code>.js</code>, al menos por el momento. Para que los módulos funcionen correctamente en un navegador, debes asegurarte de que tu servidor los esté sirviendo con un encabezado <code>Content-Type</code> que contenga un tipo MIME de JavaScript como <code>text/javascript</code>. Si no lo haces, obtendrás un estricto error de verificación de tipo MIME como "El servidor respondió con un tipo MIME que no es JavaScript" y el navegador no ejecutará tu JavaScript. La mayoría de los servidores ya configuran el tipo correcto para archivos <code>.js</code>, pero todavía no para archivos <code>.mjs</code>. Los servidores que ya sirven archivos <code>.mjs</code> incluyen <a href="https://pages.github.com/">GitHub Pages</a> y <code><a href="https://github.com/http-party/http-server#readme">http-server</a></code> para Node.js.</p>

<p>Esto está bien si ya estás utilizando un entorno de este tipo, o si no, pero sabes lo que estás haciendo y tiene acceso (es decir, puedes configurar tu servidor para establecer el <code><a href="/es/docs/Web/HTTP/Headers/Content-Type">Content-Type</a></code> para archivos <code>.mjs</code>). Sin embargo, podría causar confusión si no controlas el servidor desde el que estás sirviendo archivos, o si estás publicando archivos para uso público, como lo hacemos aquí.</p>

<p>Por motivos de aprendizaje y portabilidad, decidimos mantenernos en <code>.js</code>.</p>

<p>Si realmente valoras la claridad de usar <code>.mjs</code> para módulos en lugar de usar <code>.js</code> para archivos JavaScript "normales", pero no quieres encontrarte con el problema descrito anteriormente, siempre puedes usar <code>.mjs</code> durante el desarrollo y convertirlos a <code>.js</code> durante tu paso de compilación.</p>

<p>También vale la pena señalar que:</p>

<ul>
 <li>Es posible que algunas herramientas nunca admitan <code>.mjs</code>, tal como <a href="https://www.typescriptlang.org/">TypeScript</a>.</li>
 <li>El atributo <code>&lt;script type="module"&gt;</code> se usa para indicar cuándo se está apuntando a un módulo, como verás a continuación.</li>
</ul>

<h2 id="Exportar_características_del_módulo">Exportar características del módulo</h2>

<p>Lo primero que debes hacer para acceder a las funciones del módulo es exportarlas. Esto se hace usando la declaración {{jsxref("Statements/export", "export")}}.</p>

<p>La forma más sencilla de utilizarla es colocarla delante de cualquier elemento que desees exportar fuera del módulo, por ejemplo:</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>Puedes exportar funciones, <code>var</code>, <code>let</code>, <code>const</code> y, como veremos más adelante — clases. Deben ser elementos de nivel superior; no puedes usar <code>export</code> dentro de una función, por ejemplo.</p>

<p>Una forma más conveniente de exportar todos los elementos que deseas exportar es usar una sola declaración de exportación al final de tu archivo de módulo, seguida de una lista separada por comas de las características que deseas exportar entre llaves. Por ejemplo:</p>

<pre class="brush: js; notranslate">export { name, draw, reportArea, reportPerimeter };</pre>

<h2 id="Importación_de_características_en_tu_script">Importación de características en tu script</h2>

<p>Una vez que hayas declarado las funciones y características que deseas exportar de tu módulo, debes importarlas en tu script para poder usarlas. La forma más sencilla de hacerlo es la siguiente:</p>

<pre class="brush: js; notranslate">import { name, draw, reportArea, reportPerimeter } from './modules/square.js';</pre>

<p>Utiliza la declaración {{jsxref("Statements/import", "import")}}, seguida de una lista separada por comas de las características que deseas importar entre llaves, seguida de la palabra clave <code>from</code>, seguida de la ruta al archivo del módulo — una ruta relativa a la raíz del sitio, que para nuestro ejemplo de <code>basic-modules</code> sería <code>/js-examples/modules/basic-modules</code>.</p>

<p>Sin embargo, hemos escrito la ruta de manera un poco diferente — estamos usando la sintaxis de punto (<code>.</code>) para significar "la ubicación actual", seguida de la ruta más allá del archivo que estamos tratando de encontrar. Esto es mucho mejor que escribir la ruta relativa completa cada vez, ya que es más corta y hace que la URL sea portátil — el ejemplo seguirá funcionando si lo mueve a una ubicación diferente en la jerarquía del sitio.</p>

<p>Así por ejemplo:</p>

<pre class="notranslate">/js-examples/modules/basic-modules/modules/square.js</pre>

<p>se convierte en</p>

<pre class="notranslate">./modules/square.js</pre>

<p>Puedes ver estas líneas en acción en <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>Nota</strong>: En algunos sistemas de módulos, puedes omitir la extensión del archivo y el punto (por ejemplo, <code>'/modules/square'</code>). Esto no funciona en módulos de JavaScript nativos.</p>
</div>

<p>Una vez que hayas importado las funciones a tu script, las puedes usar tal como se definieron dentro del mismo archivo. Lo siguiente se encuentra en <code>main.js</code>, debajo de las líneas <code>import</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>Nota</strong>: Aunque las funciones importadas están disponibles en el archivo, son vistas de solo lectura de la función que se exportó. No puedes cambiar la variable que se importó, pero aún puedes modificar propiedades similares a <code>const</code>. Además, estas características se importan como enlaces activos, lo cual significa que pueden cambiar de valor incluso si no puedes modificar el enlace a diferencia de <code>const</code>.</p>
</div>

<h2 id="Aplicar_el_módulo_a_tu_HTML">Aplicar el módulo a tu HTML</h2>

<p>Ahora solo necesitamos aplicar el módulo <code>main.js</code> a nuestra página HTML. Esto es muy similar a cómo aplicamos un script normal a una página, con algunas diferencias notables.</p>

<p>En primer lugar, debes incluir <code>type="module"</code> en el elemento <a href="/es/docs/Web/HTML/Element/script" title="El elemento ↑&lt;script>↓ de HTML se utiliza para incrustar o hacer referencia al código ejecutable; esto se usa normalmente para incrustar o hacer referencia a código JavaScript."><code>&lt;script&gt;</code></a>, para declarar este script como un módulo. Para importar el script <code>main.js</code>, usamos esto:</p>

<pre class="brush: html; no-line-numbers notranslate">&lt;script type="module" src="main.js"&gt;&lt;/script&gt;</pre>

<p>También puedes incrustar el script del módulo directamente en el archivo HTML colocando el código JavaScript dentro del cuerpo del elemento <code>&lt;script&gt;</code>:</p>

<pre class="brush: js notranslate">&lt;script type="module"&gt;
  /* El código del módulo JavaScript va aquí */
&lt;/script&gt;</pre>

<p>El script en el que importas las características del módulo básicamente actúa como el módulo de nivel superior. Si lo omite, Firefox, por ejemplo, te da un error de "SyntaxError: Las declaraciones import solo pueden aparecer en el nivel superior de un módulo".</p>

<p>Solo puede usar instrucciones <code>import</code> y <code>export</code> dentro de los módulos, no en scripts normales.</p>

<h2 id="Otras_diferencias_entre_módulos_y_scripts_estándar">Otras diferencias entre módulos y scripts estándar</h2>

<ul>
 <li>Debes prestar atención a las pruebas locales — si intentas cargar el archivo HTML localmente (es decir, con una URL <code>file:///</code>), te encontrarás con errores de CORS debido a los requisitos de seguridad del módulo JavaScript. Necesitas hacer tus pruebas a través de un servidor.</li>
 <li>Además, ten en cuenta que puedes obtener un comportamiento diferente de las secciones del script definidas dentro de los módulos en comparación con los scripts estándar. Esto se debe a que los módulos automáticamente usan {{jsxref("Strict_mode", "strict mode", "", 1)}}.</li>
 <li>No es necesario utilizar el atributo <code>defer</code> (ve <a href="/es/docs/Web/HTML/Element/script#Attributes" title="El elemento ↑&lt;script>↓ de HTML se utiliza para incrustar o hacer referencia al código ejecutable; esto se usa normalmente para incrustar o hacer referencia a código JavaScript."> atributos de <code>&lt;script&gt;</code></a>) al cargar un script de módulo; los módulos se difieren automáticamente.</li>
 <li>Los módulos solo se ejecutan una vez, incluso si se les ha hecho referencia en varias etiquetas <code>&lt;script&gt;</code>.</li>
 <li>Por último, pero no menos importante, dejemos esto en claro — las características del módulo se importan al alcance de un solo script — no están disponibles en el alcance global. Por lo tanto, solo podrás acceder a las funciones importadas en el script en el que se importan y no podrás acceder a ellas desde la consola de JavaScript, por ejemplo. Seguirás recibiendo errores de sintaxis en DevTools, pero no podrás utilizar algunas de las técnicas de depuración que esperabas utilizar.</li>
</ul>

<h2 id="Exportaciones_predeterminadas_vs._exportaciones_con_nombre">Exportaciones predeterminadas vs. exportaciones con nombre</h2>

<p>La funcionalidad que hemos exportado hasta ahora se compone de <strong>exportaciones con nombre</strong> — cada elemento (ya sea una función, <code>const</code>, etc.) se ha denominado por su nombre en <code>export</code>, y ese nombre también se ha utilizado para referirse a él en <code>import</code>.</p>

<p>También hay un tipo de exportación llamado <strong>exportación predeterminada</strong> — está diseñado para facilitar que un módulo proporcione una función predeterminada, y también ayuda a los módulos JavaScript a interoperar con los sistemas de módulos CommonJS y AMD existentes (como se explica muy bien en <a href="https://hacks.mozilla.org/2015/08/es6-in-depth-modules/">ES6 en profundidad: módulos</a> de Jason Orendorff; busca "Exportaciones predeterminadas").</p>

<p>Veamos un ejemplo mientras explicamos cómo funciona. En nuestros ↑basic-modules↓ <code>square.js</code> puedes encontrar una función llamada <code>randomSquare()</code> que crea un cuadrado con un color, tamaño y posición aleatorios. Lo queremos exportar como nuestro predeterminado, por lo que en la parte inferior del archivo escribimos esto:</p>

<pre class="brush: js; notranslate">export default randomSquare;</pre>

<p>Ten en cuenta la falta de llaves.</p>

<p>En su lugar, podríamos anteponer <code>export default</code> a la función y definirla como una función anónima, así:</p>

<pre class="brush: js; notranslate">export default function(ctx) {
  ...
}</pre>

<p>En nuestro archivo <code>main.js</code>, importamos la función predeterminada usando esta línea:</p>

<pre class="brush: js; notranslate">import randomSquare from './modules/square.js';</pre>

<p>Una vez más, ten en cuenta la falta de llaves. Esto se debe a que solo se permite una exportación predeterminada por módulo, y sabemos que <code>randomSquare</code> lo es. La línea anterior es básicamente una abreviatura de:</p>

<pre class="brush: js; notranslate">import {default as randomSquare} from './modules/square.js';</pre>

<div class="blockIndicator note">
<p><strong>Nota</strong>: La sintaxis as para cambiar el nombre de los elementos exportados se explica a continuación en la sección <a href="#Renombrar_impotaciones_y_exportaciones">Renombrar importaciones y exportaciones</a>.</p>
</div>

<h2 id="Evitar_conflictos_de_nombres">Evitar conflictos de nombres</h2>

<p>Hasta ahora, nuestros módulos de dibujo de formas en el lienzo parecen estar funcionando bien. Pero, ¿qué pasa si intentamos agregar un módulo que se ocupa de dibujar otra forma, como un círculo o un triángulo? Estas formas probablemente también tendrían funciones asociadas como <code>draw()</code>, <code>reportArea()</code>, etc.; si intentáramos importar diferentes funciones del mismo nombre en el mismo archivo de módulo de nivel superior, terminaríamos con conflictos y errores.</p>

<p>Afortunadamente, hay varias formas de evitar esto. Los veremos en las siguientes secciones.</p>

<h2 id="Renombrar_importaciones_y_exportaciones">Renombrar importaciones y exportaciones</h2>

<p>Dentro de las llaves de tu instrucciones <code>import</code> y <code>export</code>, puedes usar la palabra clave <code>as</code> junto con un nuevo nombre de función, para cambiar el nombre de identificación que utilizará una función dentro del módulo de nivel superior.</p>

<p>Entonces, por ejemplo, ambos de los siguientes harían el mismo trabajo, aunque de una manera ligeramente diferente:</p>

<pre class="brush: js; notranslate">// dentro de module.js
export {
  function1 as newFunctionName,
  function2 as anotherNewFunctionName
};

// dentro de main.js
import {newFunctionName, anotherNewFunctionName} from './modules/module.js';</pre>

<pre class="brush: js; notranslate">// dentro de module.js
export {function1, function2};

// dentro de main.js
import {function1 as newFunctionName,
         function2 as anotherNewFunctionName } from './modules/module.js';</pre>

<p>Veamos un ejemplo real. En nuestro directorio <a href="https://github.com/mdn/js-examples/tree/master/modules/renaming">renaming</a>, verás el mismo sistema de módulos que en el ejemplo anterior, excepto que hemos agregado los módulos <code>circle.js</code> y <code>triangle.js</code> para dibujar e informar sobre círculos y triángulos.</p>

<p>Dentro de cada uno de estos módulos, tenemos características con los mismos nombres que se exportan y, por lo tanto, cada una tiene la misma instrucción <code>export</code> en la parte inferior:</p>

<pre class="brush: js; notranslate">export { name, draw, reportArea, reportPerimeter };</pre>

<p>Al importarlos a <code>main.js</code>, si intentamos usar esto:</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>El navegador arrojará un error como "SyntaxError: redeclaración de nombre import" (Firefox).</p>

<p>En su lugar, necesitamos cambiar el nombre de las importaciones para que sean únicas:</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>Ten en cuenta que podrías resolver el problema en los archivos del módulo, p. ej.</p>

<pre class="brush: js; notranslate">// en square.js
export {name as squareName,
         draw as drawSquare,
         reportArea as reportSquareArea,
         reportPerimeter as reportSquarePerimeter };</pre>

<pre class="brush: js; notranslate">// en main.js
import {squareName, drawSquare, reportSquareArea, reportSquarePerimeter} from './modules/square.js';</pre>

<p>Y funcionaría igual. El estilo que uses depende de ti, sin embargo, podría decirse que tiene más sentido dejar el código de tu módulo tal cual y realizar los cambios en las importaciones. Esto tiene sentido especialmente cuando estás importando desde módulos de terceros sobre los que no tienes ningún control.</p>

<h2 id="Crear_un_objeto_module">Crear un objeto <code>module</code></h2>

<p>El método anterior funciona bien, pero es un poco complicado y largo. Una solución aún mejor es importar las características de cada módulo dentro de un objeto <code>module</code>. La siguiente forma de sintaxis hace eso:</p>

<pre class="brush: js; notranslate">import * as Module from './modules/module.js';</pre>

<p>Esto toma todas las exportaciones disponibles dentro de <code>module.js</code> y las hace disponibles como miembros de un objeto <code>Module</code>, dándole efectivamente su propio espacio de nombres. Así por ejemplo:</p>

<pre class="brush: js; notranslate">Module.function1()
Module.function2()
etc.</pre>

<p>De nuevo, veamos un ejemplo real. Si vas a nuestro directorio <a href="https://github.com/mdn/js-examples/tree/master/modules/module-objects">module-objects</a>, verás el mismo ejemplo nuevamente, pero reescrito para aprovechar esta nueva sintaxis. En los módulos, las exportaciones están todas en la siguiente forma simple:</p>

<pre class="brush: js; notranslate">export { name, draw, reportArea, reportPerimeter };</pre>

<p>Las importaciones, por otro lado, se ven así:</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>En cada caso, ahora puedes acceder a las importaciones del módulo debajo del nombre del objeto especificado, por ejemplo:</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>Por lo tanto, ahora puedes escribir el código de la misma manera que antes (siempre que incluyas los nombres de los objetos donde sea necesario), y las importaciones son mucho más ordenadas.</p>

<h2 id="Módulos_y_clases">Módulos y clases</h2>

<p>Como dijimos antes, también puedes exportar e importar clases; esta es otra opción para evitar conflictos en tu código, y especialmente es útil si ya tienes el código de tu módulo escrito en un estilo orientado a objetos.</p>

<p>Puedes ver un ejemplo de nuestro módulo de dibujo de formas reescrito con clases ES en nuestro directorio <a href="https://github.com/mdn/js-examples/tree/master/modules/classes">classes</a>. Como ejemplo, el archivo <code><a href="https://github.com/mdn/js-examples/blob/master/modules/classes/modules/square.js">square.js</a></code> ahora contiene toda su funcionalidad en una sola clase:</p>

<pre class="brush: js; notranslate">class Square {
  constructor(ctx, listId, length, x, y, color) {
    ...
  }

  draw() {
    ...
  }

  ...
}</pre>

<p>que luego exportamos:</p>

<pre class="brush: js; notranslate">export { Square };</pre>

<p>En <code><a href="https://github.com/mdn/js-examples/blob/master/modules/classes/main.js">main.js</a></code>, lo importamos así:</p>

<pre class="brush: js; notranslate">import { Square } from './modules/square.js';</pre>

<p>Y luego usas la clase para dibujar nuestro cuadrado:</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="Carga_estática_de_módulos">Carga estática de módulos</h2>

<p>Habrá ocasiones en las que querrás agregar módulos juntos. Es posible que tengas varios niveles de dependencias, donde desees simplificar las cosas, combinando varios submódulos en un módulo principal. Esto es posible utilizando la sintaxis de exportación de los siguientes formas en el módulo principal:</p>

<pre class="brush: js; notranslate">export * from 'x.js'
export { name } from 'x.js'</pre>

<p>Para ver un ejemplo, ve nuestro directorio <a href="https://github.com/mdn/js-examples/tree/master/modules/module-aggregation">module-aggregation</a>. En este ejemplo (basado en nuestro ejemplo de clases anterior) tenemos un módulo adicional llamado <code>shapes.js</code>, que reúne toda la funcionalidad de <code>circle.js</code>, <code>square.js</code> y <code>triangle.js</code>. También hemos movido nuestros submódulos dentro de un subdirectorio dentro del directorio <code>modules</code> llamado <code>shapes</code>. Entonces, la estructura del módulo en este ejemplo es:</p>

<pre class="notranslate">modules/
  canvas.js
  shapes.js
  shapes/
    circle.js
    square.js
    triangle.js</pre>

<p>En cada uno de los submódulos, la exportación es de la misma forma, p. ej.</p>

<pre class="brush: js; notranslate">export { Square };</pre>

<p>Luego viene la parte de agregación. Dentro de <code><a href="https://github.com/mdn/js-examples/blob/master/modules/module-aggregation/modules/shapes.js">shapes.js</a></code>, incluimos las siguientes líneas:</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>Estas toman las exportaciones de los submódulos individuales y las ponen a disposición de manera efectiva desde el módulo <code>shapes.js</code>.</p>

<div class="blockIndicator note">
<p><strong>Nota</strong>: Las exportaciones a las que se hace referencia en <code>shapes.js</code> básicamente se redirigen a través del archivo y realmente no existen allí, por lo que no podrás escribir ningún código relacionado útil dentro del mismo archivo.</p>
</div>

<p>Entonces, ahora en el archivo <code>main.js</code>, podemos obtener acceso a las tres clases de módulos reemplazando</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>con la siguiente única línea:</p>

<pre class="brush: js; notranslate">import { Square, Circle, Triangle } from './modules/shapes.js';</pre>

<h2 id="Carga_dinámica_de_módulos">Carga dinámica de módulos</h2>

<p>La parte más nueva de la funcionalidad de los módulos de JavaScript que estará disponible en los navegadores es la carga dinámica de módulos. Esto te permite cargar módulos dinámicamente solo cuando son necesarios, en lugar de tener que cargar todo por adelantado. Esto tiene algunas obvias ventajas de rendimiento; sigue leyendo y veamos cómo funciona.</p>

<p>Esta nueva funcionalidad te permite llamar a {{jsxref("Statements/import", "import()", "#Importaciones_Dinámicas")}} como una función, pasándole la ruta al módulo como parámetro. Devuelve una {{jsxref("Promise")}}, que se cumple con un objeto <code>module</code> (consulta <a href="#Crear_un_objeto_module">Crear un objeto <code>module</code></a>) que te da acceso a las exportaciones de ese objeto, p. ej.</p>

<pre class="brush: js; notranslate">import('./modules/myModule.js')
  .then((module) =&gt; {
    // Haz algo con el módulo.
  });</pre>

<p>Veamos un ejemplo. En el directorio <a href="https://github.com/mdn/js-examples/tree/master/modules/dynamic-module-imports">dynamic-module-import</a> tenemos otro ejemplo basado en nuestro ejemplo de clases. Esta vez, sin embargo, no dibujamos nada en el lienzo cuando se carga el ejemplo. En su lugar, incluimos tres botones — "Círculo", "Cuadrado" y "Triángulo" — que, cuando se presionan, cargan dinámicamente el módulo requerido y luego lo usan para dibujar la forma asociada.</p>

<p>En este ejemplo, solo hemos realizado cambios en nuestros archivos <code><a href="https://github.com/mdn/js-examples/blob/master/modules/dynamic-module-imports/index.html">index.html</a></code> y <code><a href="https://github.com/mdn/js-examples/blob/master/modules/dynamic-module-imports/main.mjs">main.js</a></code> — el módulo <code>exports</code> sigue siendo el mismo que antes.</p>

<p>En <code>main.js</code> hemos tomado una referencia a cada botón usando una llamada a <a href="/es/docs/Web/API/Document/querySelector"><code>Document.querySelector()</code></a>, por ejemplo:</p>

<pre class="brush: js; notranslate">let squareBtn = document.querySelector('.square');</pre>

<p>Luego adjuntamos un escucha de eventos a cada botón para que cuando se presione, el módulo relevante se cargue dinámicamente y se use para dibujar la forma:</p>

<pre class="brush: js; notranslate">squareBtn.addEventListener('click', () =&gt; {
  import('./modules/square.js').then((Module) =&gt; {
    let square1 = new Module.Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');
    square1.draw();
    square1.reportArea();
    square1.reportPerimeter();
  })
});</pre>

<p>Ten en cuenta que, debido a que el cumplimiento de la promesa devuelve un objeto <code>module</code>, la clase se convierte en una subfunción del objeto, por lo que ahora necesitamos acceder al constructor prefijado con <code>Module.</code>, p. ej. <code>Module.Square(...)</code>.</p>

<h2 id="Solución_de_problemas">Solución de problemas</h2>

<p>Aquí hay algunos consejos que te pueden ayudar si tienes problemas para hacer que tus módulos funcionen. ¡No dude en agregarlos a la lista si descubres más!</p>

<ul>
 <li>Mencionamos esto antes, pero para reiterar: los archivos <code>.js</code> se deben cargar con un tipo MIME de <code>text/javascript</code> (u otro tipo MIME compatible con JavaScript, pero se recomienda <code>text/javascript</code>), de lo contrario, obtendrás un error de comprobación de tipo MIME estricto como "El servidor respondió con un tipo MIME no JavaScript".</li>
 <li>Si intentas cargar el archivo HTML localmente (es decir, con una URL <code>file:///</code>), encontrarás errores CORS debido a los requisitos de seguridad del módulo JavaScript. Necesitas hacer tus pruebas a través de un servidor. Las páginas de GitHub son ideales ya que también sirven archivos <code>.js</code> con el tipo MIME correcto.</li>
 <li>Debido a que <code>.mjs</code> es una extensión de archivo no estándar, es posible que algunos sistemas operativos no la reconozcan o intenten reemplazarla por otra. Por ejemplo, descubrimos que macOS silenciosamente agregaba <code>.js</code> al final de los archivos <code>.mjs</code> y luego, automáticamente ocultaba la extensión del archivo. Entonces, todos nuestros archivos salían realmente como <code>x.mjs.js</code>. Una vez que desactivamos la acción de ocultar automáticamente las extensiones de archivo y lo entrenamos para aceptar <code>.mjs</code>, todo estuvo bien.</li>
</ul>

<h2 id="Ve_también">Ve también</h2>

<ul>
 <li><a href="https://developers.google.com/web/fundamentals/primers/modules#mjs">Uso de módulos JavaScript en la web</a>, por Addy Osmani y Mathias Bynens</li>
 <li><a href="https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/">Módulos ES: un análisis profundo de dibujos animados</a>, publicación en el blog Hacks de Lin Clark</li>
 <li><a href="https://hacks.mozilla.org/2015/08/es6-in-depth-modules/">ES6 en profundidad: Módulos</a>, publicación en el blog Hacks de Jason Orendorff</li>
 <li>Libro de Axel Rauschmayer <a href="http://exploringjs.com/es6/ch_modules.html">Explorando JS: Módulos</a></li>
</ul>

<p>{{Previous("Web/JavaScript/Guide/Meta_programming")}}</p>