|
| 1 | +--- |
| 2 | +title: React Profiler |
| 3 | +tags: |
| 4 | + - react |
| 5 | + - javascript |
| 6 | + - moodle |
| 7 | +description: How to use Moodle's React profiling and dev-mode bundle infrastructure, including programmatic mounting and the Profiler HOC. |
| 8 | +--- |
| 9 | + |
| 10 | +## How it works |
| 11 | + |
| 12 | +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. |
| 13 | + |
| 14 | +When `jsrev === -1`: |
| 15 | + |
| 16 | +- **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. |
| 17 | +- **Browser** — `isProfilerEnabled()` returns `true`. `mountReactApp()` automatically wraps every component tree in a React [`<Profiler>`](https://react.dev/reference/react/Profiler) and logs render timings to the console. |
| 18 | + |
| 19 | +## Writing a new React component |
| 20 | + |
| 21 | +Place source files under `<component>/js/esm/src/` and build output to `<component>/js/esm/build/`. The import map resolves `@moodle/lms/<component>/<module>` to `<componentdir>/js/esm/build/<module>.js`. |
| 22 | + |
| 23 | +:::caution |
| 24 | + |
| 25 | +The module must have a **default-exported** React function component: |
| 26 | + |
| 27 | +::: |
| 28 | + |
| 29 | +```ts |
| 30 | +// mod_book/js/esm/src/viewer.ts |
| 31 | +export default function Viewer({ title }: { title: string }) { |
| 32 | + return <h1>{title}</h1>; |
| 33 | +} |
| 34 | +``` |
| 35 | + |
| 36 | +## Mounting programmatically |
| 37 | + |
| 38 | +Most components should be mounted via the Mustache helper, which handles auto-initialisation automatically — see [Mustache helper](./reactautoinit.md#mustache-helper-react). Use `core/mount` directly when you need to mount from TypeScript/JavaScript — for example, when the mount point is created dynamically, when you need the unmount handle, or when mounting inside an existing JS module lifecycle. |
| 39 | + |
| 40 | +```ts |
| 41 | +import { mountReactApp, unmountReactApp } from "@moodle/lms/core/mount"; |
| 42 | + |
| 43 | +const unmount = mountReactApp(container, MyComponent, props, { id: "my-app" }); |
| 44 | + |
| 45 | +// Unmount when done: |
| 46 | +unmount(); |
| 47 | +// or, if you only have the container element: |
| 48 | +unmountReactApp(container); |
| 49 | +``` |
| 50 | + |
| 51 | +`mountReactApp` wraps the component in `<Profiler>` automatically when dev mode is active — no extra code needed. |
| 52 | + |
| 53 | +## Console output in dev mode |
| 54 | + |
| 55 | +When profiling is active the browser console shows: |
| 56 | + |
| 57 | +| Output | Meaning | |
| 58 | +|--------|---------| |
| 59 | +| `[mount] MyComponent - 2.34ms` | Collapsed group for each render | |
| 60 | +| `console.warn` Slow render | Actual render time exceeded 16 ms (60 fps budget) | |
| 61 | +| `console.error` Very slow render | Actual render time exceeded 50 ms | |
| 62 | +| `[react_autoinit] Initializing...` | Auto-init started | |
| 63 | +| `[react_autoinit] Found N component(s) to mount` | Components detected in DOM | |
| 64 | +| `[react_autoinit] Mounted via default: <specifier>` | Successful mount | |
| 65 | +| `[react_autoinit] Unmounted: <specifier>` | Clean unmount after DOM removal | |
0 commit comments