diff --git a/docs/guides/javascript/react/importmap.md b/docs/guides/javascript/react/importmap.md new file mode 100644 index 0000000000..1aef0ee4cb --- /dev/null +++ b/docs/guides/javascript/react/importmap.md @@ -0,0 +1,174 @@ +--- +title: Aliasing and Import Maps +tags: + - Javascript + - ESM + - React + - Import Maps +description: How Moodle uses native browser import maps to resolve bare module specifiers to real URLs at runtime, including built-in specifiers, custom entries, and the ESM serving endpoint. +--- + +Moodle uses native browser import maps as the mechanism for resolving bare module specifiers (for example `react` or `@moodle/lms/`) to real URLs at runtime. This replaces the need for bundler-specific alias configuration and allows Moodle components to write standard ESM `import` statements that work directly in the browser. + +## What is an Import Map? + +An import map is a JSON object, embedded in the page as a ` +``` + +With this map in place, any ES module on the page can write: + +```js +import React from 'react'; +import { someUtil } from '@moodle/lms/core/utils'; +``` + +…and the browser resolves the specifier to the correct URL without any bundler step at runtime. + +## How Moodle generates the Import Map + +The import map is built and injected into the page automatically by `page_requirements_manager::get_import_map()`, which is called during the page `
` render phase. + +### The `import_map` class + +**`core\output\requirements\import_map`** is the central registry that holds all specifier-to-URL mappings. It implements `JsonSerializable` so it can be written directly into the page as JSON. + +Key responsibilities: + +- Holds a list of specifier → loader entries. +- Accepts a **default loader URL** (the ESM serving endpoint) which is used to derive concrete URLs for all entries whose path is relative. +- Provides `add_import()` to register additional specifiers, or to override the built-in ones, from a `pre_render` hook. + +#### Built-in specifiers + +The following specifiers are registered by default in `add_standard_imports()`: + +| Specifier | Resolves to | +|---|---| +| `@moodle/lms/` | ESM endpoint root (Moodle component modules) | +| `react` | `external/react` | +| `react-dom` | `external/react-dom` | +| `react/jsx-runtime` | `external/react/jsx-runtime` | +| `react/jsx-dev-runtime` | `external/react/jsx-dev-runtime` | +| `@moodlehq/design-system` | `external/design-system` | + +#### Adding a custom specifier + +You can extend the import map from a `pre_render` hook before the page is rendered: + +```php +use core\output\requirements\import_map; + +// Fetch the shared singleton from the DI container. +$importmap = \core\di::get(import_map::class); + +// Map 'my-lib' to an absolute URL. +$importmap->add_import('my-lib', loader: new \core\url('https://cdn.example.com/my-lib.js')); + +// Map '@myplugin/' using a path relative to the default ESM loader root. +$importmap->add_import('@myplugin/', path: 'local_myplugin/'); +``` + +The `add_import()` signature is: + +```php +public function add_import( + string $specifier, + ?\core\url $loader = null, + ?string $path = null, +): void +``` + +- **`$specifier`** — The bare specifier string used in `import` statements (e.g. `react`, `@moodle/lms/`). +- **`$loader`** — An absolute `\core\url`. When provided, used directly. +- **`$path`** — A path suffix appended to the default loader base URL. When both `$loader` and `$path` are `null`, `$specifier` itself is appended to the base URL. + +## The ESM serving endpoint + +All ESM files are served by `core\route\shim\esm_controller::serve`, registered under the route: + +``` +/{revision:[0-9-]+}/{scriptpath:.*} +``` + +The controller dispatches on the value of `scriptpath`: + +### External (vendored) bundles — `external/` + +Paths that begin with `external/` are resolved against a fixed map of vendored files stored under `$CFG->root/lib/platform_bundles/`: + +| Specifier (after `external/`) | File | +|---|---| +| `react` | `lib/platform_bundles/react/