Introducció

Els mòduls ECMAScript ( o ESM) són el format estàndard oficial per empaquetar codi JavaScript per a la seva reutilització.

Els mòduls es defineixen mitjançant les instruccions import i export.

Un mòdul et permet aïllar un conjunt de funcions de tal manera que només aquelles funcions que tu declaris expresament es podran utilitzar des de altres funcions.

Això és molt important per evitar la col.lisió de noms i evitar dependències no volgudes entre mòduls (desacoplament modular)

Mòdul

Crea un projecte school:

$ mkdir school && cd school
$ npm init -y

Modifica el fitxer package.json i afegeix el camp "type perquè tots els fitxers *.js es carreguin com a mòduls ES:

{
  "name": "school",
  "version": "1.0.0",
  "description": "",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Crea un fitxer data.js que exporti la funció enroll:

export const enroll = function (course, student) {
	console.log(`${student} enrolled in '${course}' course.`)
}

El que fem és exportar una const amb el nom enroll que té com a valor una funció anònima.

Fixa't que la sintaxis és una mica diferent respecte la declaració normal d'una funció:

function enroll(course, student) {
  console.log(`${student} enrolled in '${course}' course.`)
}

Enroll

Crea un fitxer index.js que importi la funció enroll:

import { enroll } from "./data.js"

enroll('Database', 'David de Mingo')

Executa el fixer index.js:

$ node index.js 
David de Mingo enrolled in 'Database' course.

Molt fàcil? Si, però per apendre l'has de pifiar un mica, i millor que sigui expressament 🧐.

Puc utilitzar una funció d'un altre fitxer sense importar-la?

Modifico el fitxer index.js:

enroll('Database', 'David de Mingo')

I executo:

$ node index.js 
file:///home/david/kekule/index.js:1
enroll('Database', 'David de Mingo')
^

ReferenceError: enroll is not defined
    at file:///home/david/school/index.js:1:1
    ...

Node.js v20.12.2

Doncs no. No sap on està la funció enroll.

Ja ser que tu ho saps, però node no ho sap i no la pensa buscar 🤨.

Crea el fitxer admin.js.

Puc importar la funció enroll del fitxer admin.js?

Segur que saps la resposta i dirás que és una ximpleria i no penses provar-ho.

Però si no ho fas mai serás un bon programador perquè quan estás treballant amb un codi amb milers de linies tens que tenir les coses molt, molt clares.

I molts alumnes amb 100 línies de codi ja us ofegueu 😮‍💨.

Modifico el fitxer index.js:

import { enroll } from "./admin.js"

enroll('Database', 'David de Mingo')

I executo:

$ node index.js 
file:///home/david/school/index.js:1
import { enroll } from "./admin.js"
         ^^^^^^
SyntaxError: The requested module './admin.js' does not provide an export named 'enroll'
    ...

node ho diu ben clar: The requested module './admin.js' does not provide an export named 'enroll'

El que passa és que ho diu amb anglés i és llenguatge técnic 😭.

En informàtica no es tracta de cantar cançons i veure sèries en anglés, sinó d'apendre ràpid l'anglés técnic i saber del que t'estan parlant.

I ara una de molt bona. ¿Que passa si importo la funció enroll d'un fitxer que no existeix?

Modifico el fitxer index.js:

import { enroll } from "./none.js"

enroll('Database', 'David de Mingo')

I executo:

$ node index.js 
node:internal/modules/esm/resolve:265
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/david/school/none.js' imported from /home/david/school/index.js
    ...
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///home/david/school/none.js'
}

node ho diu ben clar: ERR_MODULE_NOT_FOUND !!!

Pots pensar que és un error una mica tonto, però acostuma a passar amb molta freqüènica per mil raons encara que siguis un programador expert.

I que passa si data.js no vol exportar la funció enroll i index.js la vol importar?

És una pregunta que no l'has de respondre pensant, perquè cada llenguatge decideix a la seva manera al respecte.

L'únic que hem de fer és averiguar que ha decidit la gent de Javascript al respecte, i no es pregunta a ChatGPT 🤨.

Modifico el fitxer index.js:

import { enroll } from "./data.js"

enroll('Database', 'David de Mingo')

Provo que tot funciona:

$ node index.js 
David de Mingo enrolled in 'Database' course.

Per cert, et pot semblar un pas innecessari, però sempre has d'estar segur de que tot està funcionat com penses que està funcionant.

Modifico el fitxer data.js:

function enroll(course, student) {
  console.log(`${student} enrolled in '${course}' course.`);
}

I executo:

$ node index.js 
file:///home/david/school/index.js:1
import { enroll } from "./data.js"
         ^^^^^^
SyntaxError: The requested module './data.js' does not provide an export named 'enroll'
    ...

La funció enroll està definida en el fitxer data.js, però node diu que no, que no l'executa.

Però fixa't en el detall, no diu que no existeix, sinò que no hi ha cap funció que s'exporti amb el nom enroll!

Register

Ja saps per experiènicia que primer t'has d'inscriure (register), i si la teva sol.licitut és acceptada, llavors et pots matricular.

Afegim la funció register a data.js:

export const enroll = function (course, student) {
	console.log(`${student} enrolled in '${course}' course.`)
}

function register(student) {
  console.log(`${student} registered`)
}

I ara primer registrem i després matriculem.

Ja se que no funcionarà, però recorda que estás aprenent!

$ node index.js 
file:///home/david/school/index.js:3
register('David de Mingo')
^

ReferenceError: register is not defined
    at file:///home/david/school/index.js:3:1
    ...

Has d'exportar la funció register tal com ja sabia i confirma l'error 🙂!:

export const enroll = function (course, student) {
	console.log(`${student} enrolled in '${course}' course.`)
}

export const register = function (student) {
  console.log(`${student} registered`)
}

Tampoc funciona tal com estava previst 😀!

$ node index.js 
file:///home/david/school/index.js:3
register('David de Mingo')
^

ReferenceError: register is not defined
    at file:///home/david/school/index.js:3:1

Ja saps que data.js pot exportar totes les funcions que vulgui, però si index.js no importa les funcions no hi ha res a fer.

No és un problema exclusiu de la informàtica, sinó que a la teva vida és el mateix: relacions d'amistat, de parella, feina, comerç, etc.

Per sort en la infromàtica té una solució molt senzilla:

import { enroll } from "./data.js"
import { register } from "./data.js"


register('David de Mingo')
enroll('Database', 'David de Mingo')

Solucionat:

$ node index.js 
David de Mingo registered
David de Mingo enrolled in 'Database' course.

Import

El codi torna ha funcionat, però es pot solucionar sense tenir que escriute tant?

import { enroll, register } from "./data.js"

register('David de Mingo')
enroll('Database', 'David de Mingo')

També puc importar el mòdul en conjunt enlloc d'anar seleccionat quines funcions vull importar:

import * as data from "./data.js"

data.register('David de Mingo')
data.enroll('Database', 'David de Mingo')

A més d'aquesta manera quedar clar que les funcions són del mòdul data, encara que això és una questió d'estil i de com s'entén millor el codi.

Per exemple si nomé hi ha un Joan a classe no hi ha problema, però si n'hi ha tres hem de ser més explicits.

Normalment importem el mòdul amb el nom del fitxer perqué sigui més fàcil de saber d'on ve la funció, però si vull li puc canviar el nom al mòdul:

import * as db from "./data.js"

db.register('David de Mingo')
db.enroll('Database', 'David de Mingo')

Com també li puc canviar el nom a una funció concreta:

import {enroll, register as reg} from "./data.js"

reg('David de Mingo')
enroll('Database', 'David de Mingo')

TODO

Now, what if there was only one thing exported? Using export default, we can remove the brackets.

// script.js
import defaultExample from "./library.js";
defaultExample();

// library.js
export default function () {
	console.log("hello world");
}

Another thing you might want to do is dynamically import something. Dynamic imports allow you to import with a function rather than a statement that must be at the top of the file, which means you can import something conditionally or even import a module with a dynamic URL. In other words, dynamic imports are like asynchronous require() calls:

import("./library.js").then((library) => {
	// handle the promise here
	library.default();
});

Web

Native ESM

La manera més senzilla d'utilitzar ESM al web és utilitzant el suport natiu dels navegadors.

Gairebé tots el navegadors són compatibles amb ESM tal com pots veure en aquest enllaç: Can I use es6-module

Només el IE i l'Opera Mini no poden.

Per carregar un script amb ESM, has d'afegir type="module" a l'etiqueta de l'script:

<script type="module" src="index.js"></script>

A més, tots els mòduls que importi aquest script també es carregaran com a mòduls ES.

Crea una pàgina index.html:

<!doctype html>
<html>

<head>
  <title>Scholl</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href=" https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css">
  <script type="module" src="index.js"></script>
</head>

<body>
  <h1>School</h1>
  <div id="enroll"/>

</body>

</html>

A continuació executem un servidor amb http-server:

$ npm install http-server --save-dev

$ ./node_modules/http-server/bin/http-server -c1

Starting up http-server, serving ./
...
Available on:
  http://127.0.0.1:8080
  http://172.17.185.222:8080
Hit CTRL-C to stop the server

Obre el navegador a l'adreça http://172.17.185.222:8080.

Modifica index.js:

import * as data from "./data.js"

const student = 'David de Mingo'
const course = "Database"

data.register(student)
data.enroll(course, student)

document.getElementById('enroll').appendChild(
  document.createElement('p').appendChild(  
    document.createTextNode(`${student} has been registered and enrolled in ${course}`)
  )
)

Fes un "refresh" del navegador i mira com s'ha executa l'script:

Webpack

A continuació executem un servidor amb Webpack.

TODO