From f3f908c2fe3acfede99ca1473efbc8df843e8c0e Mon Sep 17 00:00:00 2001 From: meirzamoodle Date: Thu, 12 Mar 2026 23:13:50 +0700 Subject: [PATCH] MDL-87987 [docs] add React Profiler guide for dev-mode mounting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new guide at docs/guides/javascript/react/profiler.md that explains Moodle’s React profiling workflow in developer mode. The document covers: - How jsrev = -1 enables React development/profiling behavior in both PHP and browser layers - Programmatic mounting and unmounting via core/mount - Required module/export structure for new React components - Use of withProfiler for components mounted outside mountReactApp - Expected console output and render-time warnings in dev mode Also update project-words.txt to include unminified for spell-check consistency. --- docs/guides/javascript/react/profiler.md | 83 ++++++++++++++++++++++++ project-words.txt | 1 + 2 files changed, 84 insertions(+) create mode 100644 docs/guides/javascript/react/profiler.md diff --git a/docs/guides/javascript/react/profiler.md b/docs/guides/javascript/react/profiler.md new file mode 100644 index 000000000..e9a86b324 --- /dev/null +++ b/docs/guides/javascript/react/profiler.md @@ -0,0 +1,83 @@ +--- +title: React Profiler +tags: + - react + - javascript + - moodle +description: How to use Moodle's React profiling and dev-mode bundle infrastructure, including programmatic mounting and the Profiler HOC. +--- + +## How it works + +Developer mode is signalled by `jsrev = -1`, which is set when **Site administration → Development → Debugging → Cache JavaScript** is disabled. This is the same convention used by Moodle's existing JS and CSS loaders. + +When `jsrev === -1`: + +- **PHP** — `import_map::resolve_react_dev_path()` substitutes `client.development.js` (the unminified React DOM profiling build) for `client.js` at file-serve time, giving cleaner stack traces and full React warnings. +- **Browser** — `isProfilerEnabled()` returns `true`. `mountReactApp()` automatically wraps every component tree in a React [``](https://react.dev/reference/react/Profiler) and logs render timings to the console. + +--- + +## Mounting programmatically + +Use `core/mount` when mounting from TypeScript/JavaScript directly. + +```ts +import { mountReactApp, unmountReactApp } from "@moodle/lms/core/mount"; + +const unmount = mountReactApp(container, MyComponent, props, { id: "my-app" }); + +// Unmount when done: +unmount(); +// or, if you only have the container element: +unmountReactApp(container); +``` + +`mountReactApp` wraps the component in `` automatically when dev mode is active — no extra code needed. + +--- + +## Writing a new React component + +Place source files under `/js/esm/src/` and build output to `/js/esm/build/`. The import map resolves `@moodle/lms//` to `/js/esm/build/.js`. + +The module must have a **default-exported** React function component: + +```ts +// mod_book/js/esm/src/viewer.ts +export default function Viewer({ title }: { title: string }) { + return

{title}

; +} +``` + +--- + +## Profiler HOC + +For components mounted outside of `mountReactApp` (e.g. inside another React tree), wrap them with the `withProfiler` HOC: + +```ts +import { withProfiler } from "@moodle/lms/core/profiler"; + +function MyComponent(props) { /* ... */ } + +export default withProfiler(MyComponent, "MyComponent"); +``` + +In production (`jsrev !== -1`) `withProfiler` returns the component unchanged — zero runtime cost. + +--- + +## Console output in dev mode + +When profiling is active the browser console shows: + +| Output | Meaning | +|--------|---------| +| `[mount] MyComponent - 2.34ms` | Collapsed group for each render | +| `console.warn` Slow render | Actual render time exceeded 16 ms (60 fps budget) | +| `console.error` Very slow render | Actual render time exceeded 50 ms | +| `[react_autoinit] Initializing...` | Auto-init started | +| `[react_autoinit] Found N component(s) to mount` | Components detected in DOM | +| `[react_autoinit] Mounted via default: ` | Successful mount | +| `[react_autoinit] Unmounted: ` | Clean unmount after DOM removal | diff --git a/project-words.txt b/project-words.txt index ca0a0486d..b65677cbe 100644 --- a/project-words.txt +++ b/project-words.txt @@ -361,3 +361,4 @@ savechanges hideif formslib autoinit +unminified