From 6ee48436f0968dec4f8f764f4a995bb4405f6a2e Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Fri, 15 Nov 2024 23:06:28 +0100 Subject: [PATCH] Content about nodejs loader (#349) * first draft + support mjs * Fix typo Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> * fix typo Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> * use findPackageJSON * use register * feedback * Update how-to-use-nodejs-loader.en.mdx * Update how-to-use-nodejs-loader.en.mdx * fix code style * Update content/blog/how-to-use-nodejs-loader.en.mdx Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> * Update content/blog/how-to-use-nodejs-loader.en.mdx Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> * fr --------- Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> --- biome.json | 17 +- components/Common/Codebox/index.tsx | 4 +- content/blog/how-to-use-nodejs-loader.en.mdx | 295 +++++++++++++++++++ content/blog/how-to-use-nodejs-loader.fr.mdx | 295 +++++++++++++++++++ 4 files changed, 606 insertions(+), 5 deletions(-) create mode 100644 content/blog/how-to-use-nodejs-loader.en.mdx create mode 100644 content/blog/how-to-use-nodejs-loader.fr.mdx diff --git a/biome.json b/biome.json index f60a130..ea46499 100644 --- a/biome.json +++ b/biome.json @@ -1,7 +1,7 @@ { "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", "files": { - "include": ["**/*.{ts,tsx,js,jsx,mts,cts,css}"], + "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.css", "**/*.json"], "ignore": [ "./node_modules", "./.pnp", @@ -41,8 +41,7 @@ "useSortedClasses": "error", "noCommonJs": "error", "noEnum": "error", - "noUnknownTypeSelector": "error", - "noDescendingSpecificity": "error" + "noUnknownTypeSelector": "error" }, "recommended": false, "complexity": { @@ -69,7 +68,8 @@ "noUnsafeDeclarationMerging": "error", "useNamespaceKeyword": "error", "noEmptyBlock": "error", - "noDuplicateAtImportRules": "error" + "noDuplicateAtImportRules": "error", + "noDuplicateObjectKeys": "error" } } }, @@ -139,5 +139,14 @@ "parser": { "cssModules": true } + }, + "json": { + "formatter": { + "enabled": true, + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 80, + "indentStyle": "space" + } } } diff --git a/components/Common/Codebox/index.tsx b/components/Common/Codebox/index.tsx index 80f9173..0ea1d85 100644 --- a/components/Common/Codebox/index.tsx +++ b/components/Common/Codebox/index.tsx @@ -15,7 +15,9 @@ const Codebox: FC = async props => { if (!isValidElement(props.children)) return null; const code = props.children.props.children.trim(); - const lang = props.children.props.className.replace('language-', ''); + const lang = props.children.props.className + .replace('language-', '') + .replace('mjs', 'js'); const html = await codeToHtml(code, { theme: 'vitesse-light', diff --git a/content/blog/how-to-use-nodejs-loader.en.mdx b/content/blog/how-to-use-nodejs-loader.en.mdx new file mode 100644 index 0000000..d94be5f --- /dev/null +++ b/content/blog/how-to-use-nodejs-loader.en.mdx @@ -0,0 +1,295 @@ +--- +title: How to use Node.js ESM loader +description: With this article, you will understand Node.js loaders and how to use them. +date: 2024-11-08 +authors: AugustinMauroy +--- + +### What is a loader? + +A loader in the context of Node.js is a mechanism that allows developers to customize the behavior of module resolution and loading. It provides hooks that can be used to intercept and modify how Node.js handles `import` statements and `require` calls. This customization can be particularly useful for tasks such as: + +- Loading non-JavaScript files (e.g., TypeScript, CoffeeScript, CSS, yaml) +- Transpiling code on-the-fly +- Implementing custom module formats +- Overriding or extending the default module resolution logic + +By using loaders, developers can tailor the module system to better suit their specific needs, making it easier to work with different file types and module formats. This can be especially beneficial during development and testing phases, where flexibility and experimentation are key. + +### What a loader contains? + +A loader in Node.js is essentially a set of custom functions that allow you to control how modules are resolved and loaded. These functions, known as hooks, provide a way to intercept and modify the default behavior of Node.js when it processes `import` statements and `require` calls. The two main hooks that a loader contains are the `resolve` hook and the `load` hook. + +#### The `resolve` Hook + +The `resolve` hook is responsible for determining the location of a module, given a specifier. A specifier is the string or URL that you pass to `import` or `require`. For example, when you write `import 'some-module'`, the `resolve` hook helps Node.js figure out where `some-module` is located. + +Here's a breakdown of what the `resolve` hook does: + +- **Function Signature**: `async function resolve(specifier, context, nextResolve)` +- **Parameters**: + - `specifier`: The string or URL passed to `import` or `require`. + - `context`: An object containing information about the context in which the module is being resolved, such as the parent URL and export conditions. + - `nextResolve`: A function that calls the next `resolve` hook in the chain, or the default Node.js resolver if this is the last hook. +- **Returns**: An object containing the resolved URL and optionally the format of the module (e.g., `'module'`, `'commonjs'`). + +The `resolve` hook allows you to customize how Node.js finds modules. For example, you could use it to map certain specifiers to different file paths or even to URLs on the web. + +#### The `load` Hook + +The `load` hook is responsible for loading the content of a module given its resolved URL. Once the `resolve` hook has determined where a module is located, the `load` hook takes over to actually load the module's code. + +Here's a breakdown of what the `load` hook does: + +- **Function Signature**: `async function load(url, context, nextLoad)` +- **Parameters**: + - `url`: The URL returned by the `resolve` hook. + - `context`: An object containing information about the context in which the module is being loaded, such as the format and import attributes. + - `nextLoad`: A function that calls the next `load` hook in the chain, or the default Node.js loader if this is the last hook. +- **Returns**: An object containing the format of the module and the source code to be evaluated. + +The `load` hook allows you to customize how Node.js loads modules. For example, you could use it to transpile code from one language to another (e.g., CoffeeScript to JavaScript) or to load modules from non-standard sources. + +### Example of a Loader + +Let's look at a simple example of a loader that handles `.coffee` files by transpiling them to JavaScript: + +```mjs fileName="coffee-loader.mjs" +import { readFile } from 'node:fs/promises'; +import { dirname, extname, resolve as resolvePath } from 'node:path'; +import { cwd } from 'node:process'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import coffeescript from 'coffeescript'; +import { findPackageJSON } from 'node:module'; + +const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/; + +export async function load(url, context, nextLoad) { + if (extensionsRegex.test(url)) { + const format = await getPackageType(url); + const { source: rawSource } = await nextLoad(url, { ...context, format }); + const transformedSource = coffeescript.compile(rawSource.toString(), url); + + return { + format, + shortCircuit: true, + source: transformedSource, + }; + } + + return nextLoad(url); +} + +async function getPackageType(url) { + const packagePath = findPackageJSON('..', import.meta.url); + if (packagePath) { + const type = await readFile(packagePath, { encoding: 'utf8' }) + .then((filestring) => JSON.parse(filestring).type) + .catch((err) => { + if (err?.code !== 'ENOENT') console.error(err); + }); + return type; + } + return 'module'; // Default to 'module' if no package.json is found +} +``` + +In this example, the `load` hook checks if the file has a `.coffee` extension and, if so, transpiles the CoffeeScript code to JavaScript before returning it. The `resolve` hook is not shown here, but it would be responsible for resolving the specifier to the correct file URL. + +By defining these hooks, the loader can customize how Node.js handles different types of modules, making it a powerful tool for developers. This customization can be particularly useful for tasks such as: + +- Loading non-JavaScript files (e.g., TypeScript, CoffeeScript) +- Transpiling code on-the-fly +- Implementing custom module formats +- Overriding or extending the default module resolution logic + +### How to use Node.js ESM loader + +Using the Node.js ESM loader involves creating custom hooks that intercept and modify the default behavior of module resolution and loading. These hooks allow you to handle non-JavaScript files, transpile code on-the-fly, and implement custom module formats. Here's a step-by-step guide on how to use the Node.js ESM loader: + +### Step 1: Create the Loader Module + +First, create the loader that handles `.coffee` files by transpiling them to JavaScript: + +```mjs fileName="coffee-loader.mjs" +import { readFile } from 'node:fs/promises'; +import { dirname, extname, resolve as resolvePath } from 'node:path'; +import { cwd } from 'node:process'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import coffeescript from 'coffeescript'; +import { findPackageJSON } from 'node:module'; + +const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/; + +export async function resolve(specifier, context, nextResolve) { + const resolvedUrl = new URL(specifier, context.parentURL).href; + if (! extensionsRegex.test(resolvedUrl)) return nextResolve(specifier, context); + + const format = await getPackageType(resolvedUrl); + + return { + url: resolvedUrl, + format, + shortCircuit: true, + }; +} + +export async function load(url, context, nextLoad) { + if (extensionsRegex.test(url)) { + const { source: rawSource } = await nextLoad(url, context); + const transformedSource = coffeescript.compile(rawSource.toString(), url); + + return { + format: context.format, + shortCircuit: true, + source: transformedSource, + }; + } + + return nextLoad(url); +} + +async function getPackageType(url) { + const packagePath = findPackageJSON('..', import.meta.url); + if (packagePath) { + const type = await readFile(packagePath, { encoding: 'utf8' }) + .then((filestring) => JSON.parse(filestring).type) + .catch((err) => { + if (err?.code !== 'ENOENT') console.error(err); + }); + return type; + } + return 'module'; // Default to 'module' if no package.json is found +} +``` + +### Step 2: Create the Registration Module + +Next, create a separate registration module that registers the loader: + +```mjs fileName="register-coffee-loader.mjs" +import { register } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +register(pathToFileURL(new URL('./coffee-loader.mjs', import.meta.url))); +``` + +### Step 3: Use the Registration Module with `--import` + +Now, you can use the `--import` flag to import the registration module when running your Node.js application: + +```bash +node --import ./register-coffee-loader.mjs ./main.coffee +``` + +### Step 4: Create the Main Application File + +Create the main application file that imports `.coffee` files: + +```coffee fileName="main.coffee" +import { scream } from './scream.coffee' +console.log scream 'hello, world' + +import { version } from 'node:process' +console.log "Brought to you by Node.js version #{version}" +``` + +```coffee fileName="scream.coffee" +export scream = (str) -> str.toUpperCase() +``` + +### Step 5: Run the Application + +Finally, run the application with the registered loader: + +```bash +node --import ./register-coffee-loader.mjs ./main.coffee +``` + +### Conclusion + +Using the Node.js ESM loader involves creating custom hooks that intercept and modify the default behavior of module resolution and loading. By registering these hooks, you can handle non-JavaScript files, transpile code on-the-fly, and implement custom module formats. This customization can be particularly useful for tasks such as loading TypeScript or CoffeeScript files, testing React components, and more. + +By following these steps, you can leverage the power of the Node.js ESM loader to tailor the module system to better suit your specific needs, making it easier to work with different file types and module formats. + +### What can a loader do? + +A loader in Node.js can significantly enhance the capabilities of the module system by allowing developers to customize how modules are resolved and loaded. This customization can be particularly useful for a variety of tasks, including: + +#### 1. Loading Non-JavaScript Files + +One of the most powerful features of a loader is the ability to handle non-JavaScript files. For example, you can create a loader that transpiles TypeScript, CoffeeScript, or other languages to JavaScript on-the-fly. This means you can write your code in a language that is more convenient or expressive for you, and the loader will take care of converting it to JavaScript before it is executed by Node.js. + +#### 2. Transpiling Code on-the-fly + +Loaders can be used to transpile code from one format to another. This is particularly useful during development and testing phases. For instance, you can use a loader to transpile modern JavaScript (ES6+) code to a version that is compatible with older environments. This allows you to use the latest features of the language without worrying about compatibility issues. + +#### 3. Implementing Custom Module Formats + +Loaders can also be used to implement custom module formats. For example, you might want to create a module format that supports additional metadata or a different syntax. By defining custom hooks, you can control how these modules are resolved and loaded, allowing you to extend the capabilities of the Node.js module system. + +#### 4. Overriding or Extending Default Module Resolution Logic + +Sometimes, the default module resolution logic provided by Node.js may not be sufficient for your needs. Loaders allow you to override or extend this logic to better suit your specific requirements. For example, you can create a loader that resolves modules from a remote server or a custom directory structure. + +#### 5. Handling Import Maps + +Loaders can be used to implement import maps, which allow you to define custom mappings for module specifiers. This can be useful for overriding the default behavior of `import` statements and `require` calls, making it easier to manage dependencies and module versions. + +#### 6. Enabling Hot Module Replacement (HMR) + +In development environments, loaders can be used to enable hot module replacement (HMR). This allows you to make changes to your code and see the results immediately without having to restart the application. This can significantly speed up the development process and improve productivity. + +#### 7. Integrating with Build Tools + +Loaders can be integrated with build tools like Webpack, Rollup, or Parcel. This allows you to use the same custom module resolution and loading logic in both your development environment and your production build. This can help ensure consistency and reduce the risk of errors. + +### Example: Transpiling CoffeeScript + +Here's an example of a loader that transpiles CoffeeScript files to JavaScript: + +```mjs fileName="coffee-loader.mjs" +import { readFile } from 'node:fs/promises'; +import { dirname, extname, resolve as resolvePath } from 'node:path'; +import { cwd } from 'node:process'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import coffeescript from 'coffeescript'; +import { findPackageJSON } from 'node:module'; + +const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/; + +export async function load(url, context, nextLoad) { + if (extensionsRegex.test(url)) { + const format = await getPackageType(url); + const { source: rawSource } = await nextLoad(url, { ...context, format }); + const transformedSource = coffeescript.compile(rawSource.toString(), url); + + return { + format, + shortCircuit: true, + source: transformedSource, + }; + } + + return nextLoad(url); +} + +async function getPackageType(url) { + const packagePath = findPackageJSON('..', import.meta.url); + if (packagePath) { + const type = await readFile(packagePath, { encoding: 'utf8' }) + .then((filestring) => JSON.parse(filestring).type) + .catch((err) => { + if (err?.code !== 'ENOENT') console.error(err); + }); + return type; + } + return 'module'; // Default to 'module' if no package.json is found +} +``` + +In this example, the `load` hook checks if the file has a `.coffee` extension and, if so, transpiles the CoffeeScript code to JavaScript before returning it. This allows you to write your code in CoffeeScript and have it automatically converted to JavaScript when it is loaded by Node.js. + +### Conclusion + +A loader in Node.js can do a lot to enhance the capabilities of the module system. By defining custom hooks, you can handle non-JavaScript files, transpile code on-the-fly, implement custom module formats, override or extend the default module resolution logic, handle import maps, enable hot module replacement, and integrate with build tools. This customization can be particularly useful for tasks such as loading TypeScript or CoffeeScript files, testing React components, and more. By leveraging the power of loaders, you can tailor the module system to better suit your specific needs, making it easier to work with different file types and module formats. diff --git a/content/blog/how-to-use-nodejs-loader.fr.mdx b/content/blog/how-to-use-nodejs-loader.fr.mdx new file mode 100644 index 0000000..0d95963 --- /dev/null +++ b/content/blog/how-to-use-nodejs-loader.fr.mdx @@ -0,0 +1,295 @@ +--- +title: Comment utiliser le chargeur ESM de Node.js +description: Avec cet article, vous comprendrez les chargeurs de Node.js et comment les utiliser. +date: 2024-11-08 +authors: AugustinMauroy +--- + +### Qu'est-ce qu'un chargeur (loader) ? + +Un chargeur dans le contexte de Node.js est un mécanisme qui permet aux développeurs de personnaliser le comportement de la résolution et du chargement des modules. Il fournit des hooks qui peuvent être utilisés pour intercepter et modifier la manière dont Node.js gère les instructions `import` et les appels `require`. Cette personnalisation peut être particulièrement utile pour des tâches telles que : + +- Charger des fichiers non-JavaScript (par exemple, TypeScript, CoffeeScript, CSS, yaml) +- Transpiler du code à la volée +- Implémenter des formats de modules personnalisés +- Remplacer ou étendre la logique de résolution de modules par défaut + +En utilisant des chargeurs, les développeurs peuvent adapter le système de modules pour mieux répondre à leurs besoins spécifiques, facilitant ainsi le travail avec différents types de fichiers et formats de modules. Cela peut être particulièrement bénéfique pendant les phases de développement et de test, où la flexibilité et l'expérimentation sont essentielles. + +### Que contient un chargeur ? + +Un chargeur dans Node.js est essentiellement un ensemble de fonctions personnalisées qui permettent de contrôler la manière dont les modules sont résolus et chargés. Ces fonctions, appelées hooks, fournissent un moyen d'intercepter et de modifier le comportement par défaut de Node.js lorsqu'il traite les instructions `import` et les appels `require`. Les deux principaux hooks qu'un chargeur contient sont le hook `resolve` et le hook `load`. + +#### Le Hook `resolve` + +Le hook `resolve` est responsable de la détermination de l'emplacement d'un module, étant donné un spécificateur. Un spécificateur est la chaîne ou l'URL que vous passez à `import` ou `require`. Par exemple, lorsque vous écrivez `import 'some-module'`, le hook `resolve` aide Node.js à déterminer où se trouve `some-module`. + +Voici un aperçu de ce que fait le hook `resolve` : + +- **Signature de la fonction** : `async function resolve(specifier, context, nextResolve)` +- **Paramètres** : + - `specifier` : La chaîne ou l'URL passée à `import` ou `require`. + - `context` : Un objet contenant des informations sur le contexte dans lequel le module est résolu, comme l'URL parente et les conditions d'exportation. + - `nextResolve` : Une fonction qui appelle le hook `resolve` suivant dans la chaîne, ou le résolveur par défaut de Node.js si c'est le dernier hook. +- **Retourne** : Un objet contenant l'URL résolue et éventuellement le format du module (par exemple, `'module'`, `'commonjs'`). + +Le hook `resolve` permet de personnaliser la manière dont Node.js trouve les modules. Par exemple, vous pourriez l'utiliser pour mapper certains spécificateurs à différents chemins de fichiers ou même à des URL sur le web. + +#### Le Hook `load` + +Le hook `load` est responsable du chargement du contenu d'un module étant donné son URL résolue. Une fois que le hook `resolve` a déterminé où se trouve un module, le hook `load` prend le relais pour charger effectivement le code du module. + +Voici un aperçu de ce que fait le hook `load` : + +- **Signature de la fonction** : `async function load(url, context, nextLoad)` +- **Paramètres** : + - `url` : L'URL retournée par le hook `resolve`. + - `context` : Un objet contenant des informations sur le contexte dans lequel le module est chargé, comme le format et les attributs d'importation. + - `nextLoad` : Une fonction qui appelle le hook `load` suivant dans la chaîne, ou le chargeur par défaut de Node.js si c'est le dernier hook. +- **Retourne** : Un objet contenant le format du module et le code source à évaluer. + +Le hook `load` permet de personnaliser la manière dont Node.js charge les modules. Par exemple, vous pourriez l'utiliser pour transpiler du code d'un langage à un autre (par exemple, CoffeeScript en JavaScript) ou pour charger des modules à partir de sources non standard. + +### Exemple d'un Chargeur + +Regardons un exemple simple d'un chargeur qui gère les fichiers `.coffee` en les transpilant en JavaScript : + +```mjs fileName="coffee-loader.mjs" +import { readFile } from 'node:fs/promises'; +import { dirname, extname, resolve as resolvePath } from 'node:path'; +import { cwd } from 'node:process'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import coffeescript from 'coffeescript'; +import { findPackageJSON } from 'node:module'; + +const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/; + +export async function load(url, context, nextLoad) { + if (extensionsRegex.test(url)) { + const format = await getPackageType(url); + const { source: rawSource } = await nextLoad(url, { ...context, format }); + const transformedSource = coffeescript.compile(rawSource.toString(), url); + + return { + format, + shortCircuit: true, + source: transformedSource, + }; + } + + return nextLoad(url); +} + +async function getPackageType(url) { + const packagePath = findPackageJSON('..', import.meta.url); + if (packagePath) { + const type = await readFile(packagePath, { encoding: 'utf8' }) + .then((filestring) => JSON.parse(filestring).type) + .catch((err) => { + if (err?.code !== 'ENOENT') console.error(err); + }); + return type; + } + return 'module'; // Par défaut à 'module' si aucun package.json n'est trouvé +} +``` + +Dans cet exemple, le hook `load` vérifie si le fichier a une extension `.coffee` et, si c'est le cas, transpile le code CoffeeScript en JavaScript avant de le retourner. Le hook `resolve` n'est pas montré ici, mais il serait responsable de la résolution du spécificateur en URL de fichier correcte. + +En définissant ces hooks, le chargeur peut personnaliser la manière dont Node.js gère différents types de modules, en faisant de lui un outil puissant pour les développeurs. Cette personnalisation peut être particulièrement utile pour des tâches telles que : + +- Charger des fichiers non-JavaScript (par exemple, TypeScript, CoffeeScript) +- Transpiler du code à la volée +- Implémenter des formats de modules personnalisés +- Remplacer ou étendre la logique de résolution de modules par défaut + +### Comment utiliser le chargeur ESM de Node.js + +L'utilisation du chargeur ESM de Node.js implique la création de hooks personnalisés qui interceptent et modifient le comportement par défaut de la résolution et du chargement des modules. Ces hooks permettent de gérer des fichiers non-JavaScript, de transpiler du code à la volée et d'implémenter des formats de modules personnalisés. Voici un guide étape par étape sur la manière d'utiliser le chargeur ESM de Node.js : + +### Étape 1 : Créer le Module de Chargeur + +Tout d'abord, créez le chargeur qui gère les fichiers `.coffee` en les transpilant en JavaScript : + +```mjs fileName="coffee-loader.mjs" +import { readFile } from 'node:fs/promises'; +import { dirname, extname, resolve as resolvePath } from 'node:path'; +import { cwd } from 'node:process'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import coffeescript from 'coffeescript'; +import { findPackageJSON } from 'node:module'; + +const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/; + +export async function resolve(specifier, context, nextResolve) { + const resolvedUrl = new URL(specifier, context.parentURL).href; + if (! extensionsRegex.test(resolvedUrl)) return nextResolve(specifier, context); + + const format = await getPackageType(resolvedUrl); + + return { + url: resolvedUrl, + format, + shortCircuit: true, + }; +} + +export async function load(url, context, nextLoad) { + if (extensionsRegex.test(url)) { + const { source: rawSource } = await nextLoad(url, context); + const transformedSource = coffeescript.compile(rawSource.toString(), url); + + return { + format: context.format, + shortCircuit: true, + source: transformedSource, + }; + } + + return nextLoad(url); +} + +async function getPackageType(url) { + const packagePath = findPackageJSON('..', import.meta.url); + if (packagePath) { + const type = await readFile(packagePath, { encoding: 'utf8' }) + .then((filestring) => JSON.parse(filestring).type) + .catch((err) => { + if (err?.code !== 'ENOENT') console.error(err); + }); + return type; + } + return 'module'; // Par défaut à 'module' si aucun package.json n'est trouvé +} +``` + +### Étape 2 : Créer le Module d'Enregistrement (Register) + +Ensuite, créez un module d'enregistrement séparé qui enregistre le chargeur : + +```mjs fileName="register-coffee-loader.mjs" +import { register } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +register(pathToFileURL(new URL('./coffee-loader.mjs', import.meta.url))); +``` + +### Étape 3 : Utiliser le Module d'Enregistrement avec `--import` + +Maintenant, vous pouvez utiliser le drapeau `--import` pour importer le module d'enregistrement lors de l'exécution de votre application Node.js : + +```bash +node --import ./register-coffee-loader.mjs ./main.coffee +``` + +### Étape 4 : Créer le Fichier Principal de l'Application + +Créez le fichier principal de l'application qui importe des fichiers `.coffee` : + +```coffee fileName="main.coffee" +import { scream } from './scream.coffee' +console.log scream 'hello, world' + +import { version } from 'node:process' +console.log "Brought to you by Node.js version #{version}" +``` + +```coffee fileName="scream.coffee" +export scream = (str) -> str.toUpperCase() +``` + +### Étape 5 : Exécuter l'Application + +Enfin, exécutez l'application avec le chargeur enregistré : + +```bash +node --import ./register-coffee-loader.mjs ./main.coffee +``` + +### Conclusion + +L'utilisation du chargeur ESM de Node.js implique la création de hooks personnalisés qui interceptent et modifient le comportement par défaut de la résolution et du chargement des modules. En enregistrant ces hooks, vous pouvez gérer des fichiers non-JavaScript, transpiler du code à la volée et implémenter des formats de modules personnalisés. Cette personnalisation peut être particulièrement utile pour des tâches telles que le chargement de fichiers TypeScript ou CoffeeScript, le test de composants React, et plus encore. + +En suivant ces étapes, vous pouvez tirer parti de la puissance du chargeur ESM de Node.js pour adapter le système de modules afin de mieux répondre à vos besoins spécifiques, facilitant ainsi le travail avec différents types de fichiers et formats de modules. + +### Que peut faire un chargeur ? + +Un chargeur dans Node.js peut considérablement améliorer les capacités du système de modules en permettant aux développeurs de personnaliser la manière dont les modules sont résolus et chargés. Cette personnalisation peut être particulièrement utile pour une variété de tâches, notamment : + +#### 1. Charger des Fichiers Non-JavaScript + +L'une des fonctionnalités les plus puissantes d'un chargeur est la capacité à gérer des fichiers non-JavaScript. Par exemple, vous pouvez créer un chargeur qui transpile TypeScript, CoffeeScript ou d'autres langages en JavaScript à la volée. Cela signifie que vous pouvez écrire votre code dans un langage qui est plus pratique ou expressif pour vous, et le chargeur s'occupera de le convertir en JavaScript avant qu'il ne soit exécuté par Node.js. + +#### 2. Transpiler du Code à la Volée + +Les chargeurs peuvent être utilisés pour transpiler du code d'un format à un autre. Cela est particulièrement utile pendant les phases de développement et de test. Par exemple, vous pouvez utiliser un chargeur pour transpiler du JavaScript moderne (ES6+) en une version compatible avec des environnements plus anciens. Cela vous permet d'utiliser les dernières fonctionnalités du langage sans vous soucier des problèmes de compatibilité. + +#### 3. Implémenter des Formats de Modules Personnalisés + +Les chargeurs peuvent également être utilisés pour implémenter des formats de modules personnalisés. Par exemple, vous pourriez vouloir créer un format de module qui prend en charge des métadonnées supplémentaires ou une syntaxe différente. En définissant des hooks personnalisés, vous pouvez contrôler la manière dont ces modules sont résolus et chargés, vous permettant d'étendre les capacités du système de modules de Node.js. + +#### 4. Remplacer ou Étendre la Logique de Résolution de Modules par Défaut + +Parfois, la logique de résolution de modules par défaut fournie par Node.js peut ne pas être suffisante pour vos besoins. Les chargeurs permettent de remplacer ou d'étendre cette logique pour mieux répondre à vos exigences spécifiques. Par exemple, vous pouvez créer un chargeur qui résout les modules à partir d'un serveur distant ou d'une structure de répertoires personnalisée. + +#### 5. Gérer les Import Maps + +Les chargeurs peuvent être utilisés pour implémenter des import maps, qui permettent de définir des mappages personnalisés pour les spécificateurs de modules. Cela peut être utile pour remplacer le comportement par défaut des instructions `import` et des appels `require`, facilitant ainsi la gestion des dépendances et des versions de modules. + +#### 6. Activer le Remplacement à Chaud de Modules (HMR) + +Dans les environnements de développement, les chargeurs peuvent être utilisés pour activer le remplacement à chaud de modules (HMR). Cela vous permet de faire des modifications à votre code et de voir les résultats immédiatement sans avoir à redémarrer l'application. Cela peut considérablement accélérer le processus de développement et améliorer la productivité. + +#### 7. Intégrer avec des Outils de Build + +Les chargeurs peuvent être intégrés avec des outils de build comme Webpack, Rollup ou Parcel. Cela vous permet d'utiliser la même logique de résolution et de chargement de modules personnalisée à la fois dans votre environnement de développement et dans votre build de production. Cela peut aider à garantir la cohérence et à réduire le risque d'erreurs. + +### Exemple : Transpiler CoffeeScript + +Voici un exemple de chargeur qui transpile les fichiers CoffeeScript en JavaScript : + +```mjs fileName="coffee-loader.mjs" +import { readFile } from 'node:fs/promises'; +import { dirname, extname, resolve as resolvePath } from 'node:path'; +import { cwd } from 'node:process'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import coffeescript from 'coffeescript'; +import { findPackageJSON } from 'node:module'; + +const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/; + +export async function load(url, context, nextLoad) { + if (extensionsRegex.test(url)) { + const format = await getPackageType(url); + const { source: rawSource } = await nextLoad(url, { ...context, format }); + const transformedSource = coffeescript.compile(rawSource.toString(), url); + + return { + format, + shortCircuit: true, + source: transformedSource, + }; + } + + return nextLoad(url); +} + +async function getPackageType(url) { + const packagePath = findPackageJSON('..', import.meta.url); + if (packagePath) { + const type = await readFile(packagePath, { encoding: 'utf8' }) + .then((filestring) => JSON.parse(filestring).type) + .catch((err) => { + if (err?.code !== 'ENOENT') console.error(err); + }); + return type; + } + return 'module'; // Par défaut à 'module' si aucun package.json n'est trouvé +} +``` + +Dans cet exemple, le hook `load` vérifie si le fichier a une extension `.coffee` et, si c'est le cas, transpile le code CoffeeScript en JavaScript avant de le retourner. Cela vous permet d'écrire votre code en CoffeeScript et de le voir automatiquement converti en JavaScript lorsqu'il est chargé par Node.js. + +### Conclusion + +Un chargeur dans Node.js peut faire beaucoup pour améliorer les capacités du système de modules. En définissant des hooks personnalisés, vous pouvez gérer des fichiers non-JavaScript, transpiler du code à la volée, implémenter des formats de modules personnalisés, remplacer ou étendre la logique de résolution de modules par défaut, gérer les import maps, activer le remplacement à chaud de modules, et intégrer avec des outils de build. Cette personnalisation peut être particulièrement utile pour des tâches telles que le chargement de fichiers TypeScript ou CoffeeScript, le test de composants React, et plus encore. En tirant parti de la puissance des chargeurs, vous pouvez adapter le système de modules pour mieux répondre à vos besoins spécifiques, facilitant ainsi le travail avec différents types de fichiers et formats de modules.