From c0bb0ccb44663052ff554b2b96765c92f4d04cf0 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Fri, 8 Nov 2024 14:27:54 +0000 Subject: [PATCH 01/12] first draft + support mjs --- components/Common/Codebox/index.tsx | 2 +- content/blog/how-to-use-nodejs-loader.en.mdx | 289 +++++++++++++++++++ content/blog/how-to-use-nodejs-loader.fr.mdx | 6 + 3 files changed, 296 insertions(+), 1 deletion(-) 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/components/Common/Codebox/index.tsx b/components/Common/Codebox/index.tsx index 80f9173..7db22b7 100644 --- a/components/Common/Codebox/index.tsx +++ b/components/Common/Codebox/index.tsx @@ -15,7 +15,7 @@ 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..f5d99f7 --- /dev/null +++ b/content/blog/how-to-use-nodejs-loader.en.mdx @@ -0,0 +1,289 @@ +--- +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) +- 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 +// 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'; + +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 isFilePath = !!extname(url); + const dir = isFilePath ? dirname(fileURLToPath(url)) : url; + const packagePath = resolvePath(dir, 'package.json'); + const type = await readFile(packagePath, { encoding: 'utf8' }) + .then((filestring) => JSON.parse(filestring).type) + .catch((err) => { + if (err?.code !== 'ENOENT') console.error(err); + }); + + return dir.length > 1 && getPackageType(resolvePath(dir, '..')); +} +``` + +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 a Loader Module + +First, you need to create a module that exports the custom hooks. This module will contain the `resolve` and `load` hooks that define how modules are resolved and loaded. + +Here's an example of a loader module that handles `.coffee` files by transpiling them to JavaScript: + +```mjs +// 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'; + +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 isFilePath = !!extname(url); + const dir = isFilePath ? dirname(fileURLToPath(url)) : url; + const packagePath = resolvePath(dir, 'package.json'); + const type = await readFile(packagePath, { encoding: 'utf8' }) + .then((filestring) => JSON.parse(filestring).type) + .catch((err) => { + if (err?.code !== 'ENOENT') console.error(err); + }); + + return dir.length > 1 && getPackageType(resolvePath(dir, '..')); +} +``` + +#### Step 2: Register the Loader + +Next, you need to register the loader module using the `register` method from `node:module`. This can be done using the `--import` flag when running your Node.js application. + +Here's an example of how to register the loader module: + +```bash +node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffee-loader.mjs"));' ./main.coffee +``` + +In this example, the `register` method is called with the path to the `coffee-loader.mjs` module. This ensures that the custom hooks are registered before any application files are imported. + +#### Step 3: Use the Loader in Your Application + +Once the loader is registered, you can use it in your application to handle non-JavaScript files. For example, you can import `.coffee` files directly in your JavaScript code: + +```coffee +# 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 +# scream.coffee +export scream = (str) -> str.toUpperCase() +``` + +When you run the application with the registered loader, the `.coffee` files will be transpiled to JavaScript on-the-fly, and the transpiled code will be executed by Node.js. + +#### Step 4: Chain Multiple Loaders (Optional) + +You can also chain multiple loaders to create more complex module resolution and loading behaviors. To do this, simply call the `register` method multiple times with different loader modules: + +```mjs +// entrypoint.mjs +import { register } from 'node:module'; + +register('./foo-loader.mjs', import.meta.url); +register('./bar-loader.mjs', import.meta.url); +await import('./my-app.mjs'); +``` + +In this example, the `foo-loader.mjs` and `bar-loader.mjs` modules are registered in sequence. The registered hooks will form chains that run last-in, first-out (LIFO). This means that the hooks in `bar-loader.mjs` will be called first, followed by the hooks in `foo-loader.mjs`, and finally the default Node.js hooks. + +### 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 a loader can 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 +// 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'; + +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 isFilePath = !!extname(url); + const dir = isFilePath ? dirname(fileURLToPath(url)) : url; + const packagePath = resolvePath(dir, 'package.json'); + const type = await readFile(packagePath, { encoding: 'utf8' }) + .then((filestring) => JSON.parse(filestring).type) + .catch((err) => { + if (err?.code !== 'ENOENT') console.error(err); + }); + + return dir.length > 1 && getPackageType(resolvePath(dir, '..')); +} +``` + +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..6c03bc4 --- /dev/null +++ b/content/blog/how-to-use-nodejs-loader.fr.mdx @@ -0,0 +1,6 @@ +--- +title: Comment utiliser le chargeur ESM de Node.js +description: Avec cet article, vous comprendrez les chargeurs Node.js et comment les utiliser. +date: 2024-11-08 +authors: AugustinMauroy +--- From 50650b5da6fd0e7cf545f407e3c9a6f605f6905c Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Thu, 14 Nov 2024 21:44:53 +0100 Subject: [PATCH 02/12] Fix typo Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> --- content/blog/how-to-use-nodejs-loader.en.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/how-to-use-nodejs-loader.en.mdx b/content/blog/how-to-use-nodejs-loader.en.mdx index f5d99f7..7351c5a 100644 --- a/content/blog/how-to-use-nodejs-loader.en.mdx +++ b/content/blog/how-to-use-nodejs-loader.en.mdx @@ -206,7 +206,7 @@ Using the Node.js ESM loader involves creating custom hooks that intercept and m 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 a loader can do? +### 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: From 9adf29d2b7749141857089c4e4e5315e9e068765 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Fri, 15 Nov 2024 10:46:29 +0100 Subject: [PATCH 03/12] fix typo Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> --- content/blog/how-to-use-nodejs-loader.en.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/how-to-use-nodejs-loader.en.mdx b/content/blog/how-to-use-nodejs-loader.en.mdx index 7351c5a..52e1ad8 100644 --- a/content/blog/how-to-use-nodejs-loader.en.mdx +++ b/content/blog/how-to-use-nodejs-loader.en.mdx @@ -22,7 +22,7 @@ A loader in Node.js is essentially a set of custom functions that allow you to c #### 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. +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: From 82e8d7d182f9dbe82b67deb7a0258624fc966871 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:17:51 +0100 Subject: [PATCH 04/12] use findPackageJSON --- content/blog/how-to-use-nodejs-loader.en.mdx | 63 ++++++++++---------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/content/blog/how-to-use-nodejs-loader.en.mdx b/content/blog/how-to-use-nodejs-loader.en.mdx index 52e1ad8..7368e7f 100644 --- a/content/blog/how-to-use-nodejs-loader.en.mdx +++ b/content/blog/how-to-use-nodejs-loader.en.mdx @@ -61,6 +61,7 @@ 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)$/; @@ -81,16 +82,16 @@ export async function load(url, context, nextLoad) { } async function getPackageType(url) { - const isFilePath = !!extname(url); - const dir = isFilePath ? dirname(fileURLToPath(url)) : url; - const packagePath = resolvePath(dir, 'package.json'); - const type = await readFile(packagePath, { encoding: 'utf8' }) - .then((filestring) => JSON.parse(filestring).type) - .catch((err) => { - if (err?.code !== 'ENOENT') console.error(err); - }); - - return dir.length > 1 && getPackageType(resolvePath(dir, '..')); + 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 } ``` @@ -120,6 +121,7 @@ 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)$/; @@ -140,16 +142,16 @@ export async function load(url, context, nextLoad) { } async function getPackageType(url) { - const isFilePath = !!extname(url); - const dir = isFilePath ? dirname(fileURLToPath(url)) : url; - const packagePath = resolvePath(dir, 'package.json'); - const type = await readFile(packagePath, { encoding: 'utf8' }) - .then((filestring) => JSON.parse(filestring).type) - .catch((err) => { - if (err?.code !== 'ENOENT') console.error(err); - }); - - return dir.length > 1 && getPackageType(resolvePath(dir, '..')); + 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 } ``` @@ -249,6 +251,7 @@ 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)$/; @@ -269,16 +272,16 @@ export async function load(url, context, nextLoad) { } async function getPackageType(url) { - const isFilePath = !!extname(url); - const dir = isFilePath ? dirname(fileURLToPath(url)) : url; - const packagePath = resolvePath(dir, 'package.json'); - const type = await readFile(packagePath, { encoding: 'utf8' }) - .then((filestring) => JSON.parse(filestring).type) - .catch((err) => { - if (err?.code !== 'ENOENT') console.error(err); - }); - - return dir.length > 1 && getPackageType(resolvePath(dir, '..')); + 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 } ``` From 196329f1353aff2c998c1d2aa8deb7f552f9f301 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:22:00 +0100 Subject: [PATCH 05/12] use register --- content/blog/how-to-use-nodejs-loader.en.mdx | 47 +++++++++----------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/content/blog/how-to-use-nodejs-loader.en.mdx b/content/blog/how-to-use-nodejs-loader.en.mdx index 7368e7f..af9614f 100644 --- a/content/blog/how-to-use-nodejs-loader.en.mdx +++ b/content/blog/how-to-use-nodejs-loader.en.mdx @@ -108,11 +108,9 @@ By defining these hooks, the loader can customize how Node.js handles different 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 a Loader Module +### Step 1: Create the Loader Module -First, you need to create a module that exports the custom hooks. This module will contain the `resolve` and `load` hooks that define how modules are resolved and loaded. - -Here's an example of a loader module that handles `.coffee` files by transpiling them to JavaScript: +First, create the loader module that handles `.coffee` files by tranÒspiling them to JavaScript: ```mjs // coffee-loader.mjs @@ -155,21 +153,29 @@ async function getPackageType(url) { } ``` -#### Step 2: Register the Loader +### Step 2: Create the Registration Module -Next, you need to register the loader module using the `register` method from `node:module`. This can be done using the `--import` flag when running your Node.js application. +Next, create a separate registration module that registers the loader: -Here's an example of how to register the loader module: +```mjs +// register-coffee-loader.mjs +import { register } from 'node:module'; +import { pathToFileURL } from 'node:url'; -```bash -node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffee-loader.mjs"));' ./main.coffee +register(pathToFileURL(new URL('./coffee-loader.mjs', import.meta.url))); ``` -In this example, the `register` method is called with the path to the `coffee-loader.mjs` module. This ensures that the custom hooks are registered before any application files are imported. +### 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 3: Use the Loader in Your Application +### Step 4: Create the Main Application File -Once the loader is registered, you can use it in your application to handle non-JavaScript files. For example, you can import `.coffee` files directly in your JavaScript code: +Create the main application file that imports `.coffee` files: ```coffee # main.coffee @@ -185,23 +191,14 @@ console.log "Brought to you by Node.js version #{version}" export scream = (str) -> str.toUpperCase() ``` -When you run the application with the registered loader, the `.coffee` files will be transpiled to JavaScript on-the-fly, and the transpiled code will be executed by Node.js. - -#### Step 4: Chain Multiple Loaders (Optional) +### Step 5: Run the Application -You can also chain multiple loaders to create more complex module resolution and loading behaviors. To do this, simply call the `register` method multiple times with different loader modules: +Finally, run the application with the registered loader: -```mjs -// entrypoint.mjs -import { register } from 'node:module'; - -register('./foo-loader.mjs', import.meta.url); -register('./bar-loader.mjs', import.meta.url); -await import('./my-app.mjs'); +```bash +node --import ./register-coffee-loader.mjs ./main.coffee ``` -In this example, the `foo-loader.mjs` and `bar-loader.mjs` modules are registered in sequence. The registered hooks will form chains that run last-in, first-out (LIFO). This means that the hooks in `bar-loader.mjs` will be called first, followed by the hooks in `foo-loader.mjs`, and finally the default Node.js hooks. - ### 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. From b9e9325926836e81fdb6c91e4ffff8b71878f188 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:25:52 +0100 Subject: [PATCH 06/12] feedback --- content/blog/how-to-use-nodejs-loader.en.mdx | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/content/blog/how-to-use-nodejs-loader.en.mdx b/content/blog/how-to-use-nodejs-loader.en.mdx index af9614f..9fdfa1d 100644 --- a/content/blog/how-to-use-nodejs-loader.en.mdx +++ b/content/blog/how-to-use-nodejs-loader.en.mdx @@ -110,7 +110,7 @@ Using the Node.js ESM loader involves creating custom hooks that intercept and m ### Step 1: Create the Loader Module -First, create the loader module that handles `.coffee` files by tranÒspiling them to JavaScript: +First, create the loader module that handles `.coffee` files by transpiling them to JavaScript: ```mjs // coffee-loader.mjs @@ -123,14 +123,26 @@ 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)) { + const format = await getPackageType(resolvedUrl); + return { + url: resolvedUrl, + format, + shortCircuit: true, + }; + } + return nextResolve(specifier, context); +} + 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 { source: rawSource } = await nextLoad(url, context); const transformedSource = coffeescript.compile(rawSource.toString(), url); return { - format, + format: context.format, shortCircuit: true, source: transformedSource, }; From 074af43dd840e985165ad1eda4d3c8acb95c0734 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Fri, 15 Nov 2024 12:02:53 +0100 Subject: [PATCH 07/12] Update how-to-use-nodejs-loader.en.mdx --- content/blog/how-to-use-nodejs-loader.en.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/how-to-use-nodejs-loader.en.mdx b/content/blog/how-to-use-nodejs-loader.en.mdx index 9fdfa1d..43bc5f7 100644 --- a/content/blog/how-to-use-nodejs-loader.en.mdx +++ b/content/blog/how-to-use-nodejs-loader.en.mdx @@ -9,7 +9,7 @@ authors: AugustinMauroy 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) +- 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 From 33802a87b46ab42f1d1f8f9e6d998130425eb59d Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Fri, 15 Nov 2024 12:06:49 +0100 Subject: [PATCH 08/12] Update how-to-use-nodejs-loader.en.mdx --- content/blog/how-to-use-nodejs-loader.en.mdx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/content/blog/how-to-use-nodejs-loader.en.mdx b/content/blog/how-to-use-nodejs-loader.en.mdx index 43bc5f7..acb8159 100644 --- a/content/blog/how-to-use-nodejs-loader.en.mdx +++ b/content/blog/how-to-use-nodejs-loader.en.mdx @@ -54,8 +54,7 @@ The `load` hook allows you to customize how Node.js loads modules. For example, Let's look at a simple example of a loader that handles `.coffee` files by transpiling them to JavaScript: -```mjs -// coffee-loader.mjs +```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'; @@ -112,8 +111,7 @@ Using the Node.js ESM loader involves creating custom hooks that intercept and m First, create the loader module that handles `.coffee` files by transpiling them to JavaScript: -```mjs -// coffee-loader.mjs +```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'; @@ -169,8 +167,7 @@ async function getPackageType(url) { Next, create a separate registration module that registers the loader: -```mjs -// register-coffee-loader.mjs +```mjs fileName="register-coffee-loader.mjs" import { register } from 'node:module'; import { pathToFileURL } from 'node:url'; @@ -189,8 +186,7 @@ node --import ./register-coffee-loader.mjs ./main.coffee Create the main application file that imports `.coffee` files: -```coffee -# main.coffee +```coffee fileName="main.coffee" import { scream } from './scream.coffee' console.log scream 'hello, world' @@ -198,8 +194,7 @@ import { version } from 'node:process' console.log "Brought to you by Node.js version #{version}" ``` -```coffee -# scream.coffee +```coffee fileName="scream.coffee" export scream = (str) -> str.toUpperCase() ``` @@ -253,8 +248,7 @@ Loaders can be integrated with build tools like Webpack, Rollup, or Parcel. This Here's an example of a loader that transpiles CoffeeScript files to JavaScript: -```mjs -// coffee-loader.mjs +```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'; From 11ec4cc2c3bc0ff48bfd4b2948684386b949e222 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Fri, 15 Nov 2024 12:23:05 +0100 Subject: [PATCH 09/12] fix code style --- biome.json | 17 +++++++++++++---- components/Common/Codebox/index.tsx | 4 +++- 2 files changed, 16 insertions(+), 5 deletions(-) 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 7db22b7..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-', '').replace('mjs', 'js'); + const lang = props.children.props.className + .replace('language-', '') + .replace('mjs', 'js'); const html = await codeToHtml(code, { theme: 'vitesse-light', From f114770aa3a0172aeb80e8c829dcad103f0e45dc Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Fri, 15 Nov 2024 22:44:28 +0100 Subject: [PATCH 10/12] Update content/blog/how-to-use-nodejs-loader.en.mdx Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> --- content/blog/how-to-use-nodejs-loader.en.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/how-to-use-nodejs-loader.en.mdx b/content/blog/how-to-use-nodejs-loader.en.mdx index acb8159..3cdc283 100644 --- a/content/blog/how-to-use-nodejs-loader.en.mdx +++ b/content/blog/how-to-use-nodejs-loader.en.mdx @@ -109,7 +109,7 @@ Using the Node.js ESM loader involves creating custom hooks that intercept and m ### Step 1: Create the Loader Module -First, create the loader module that handles `.coffee` files by transpiling them to JavaScript: +First, create the loader that handles `.coffee` files by transpiling them to JavaScript: ```mjs fileName="coffee-loader.mjs" import { readFile } from 'node:fs/promises'; From cdb7065e2f952a4f33826a8b37ca3b8998417c69 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Fri, 15 Nov 2024 22:44:51 +0100 Subject: [PATCH 11/12] Update content/blog/how-to-use-nodejs-loader.en.mdx Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> --- content/blog/how-to-use-nodejs-loader.en.mdx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/content/blog/how-to-use-nodejs-loader.en.mdx b/content/blog/how-to-use-nodejs-loader.en.mdx index 3cdc283..d94be5f 100644 --- a/content/blog/how-to-use-nodejs-loader.en.mdx +++ b/content/blog/how-to-use-nodejs-loader.en.mdx @@ -123,15 +123,15 @@ 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)) { - const format = await getPackageType(resolvedUrl); - return { - url: resolvedUrl, - format, - shortCircuit: true, - }; - } - return nextResolve(specifier, context); + 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) { From ab65da06b4e1bc948a48e534c64293f7917f18fc Mon Sep 17 00:00:00 2001 From: Augustin Mauroy Date: Fri, 15 Nov 2024 23:05:55 +0100 Subject: [PATCH 12/12] fr --- content/blog/how-to-use-nodejs-loader.fr.mdx | 291 ++++++++++++++++++- 1 file changed, 290 insertions(+), 1 deletion(-) diff --git a/content/blog/how-to-use-nodejs-loader.fr.mdx b/content/blog/how-to-use-nodejs-loader.fr.mdx index 6c03bc4..0d95963 100644 --- a/content/blog/how-to-use-nodejs-loader.fr.mdx +++ b/content/blog/how-to-use-nodejs-loader.fr.mdx @@ -1,6 +1,295 @@ --- title: Comment utiliser le chargeur ESM de Node.js -description: Avec cet article, vous comprendrez les chargeurs Node.js et comment les utiliser. +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.