Skip to content

Commit 98ee5ff

Browse files
committed
AE-87 [docs] Frontend development with ESM
1 parent 11cedf3 commit 98ee5ff

1 file changed

Lines changed: 387 additions & 0 deletions

File tree

docs/guides/frontend/index.md

Lines changed: 387 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
1+
---
2+
title: Frontend Development
3+
tags:
4+
- react
5+
- javascript
6+
- moodle
7+
---
8+
## Overview
9+
10+
Moodle now supports modern frontend development using **ECMAScript modules (ESM)**, **React**, and **TypeScript**.
11+
12+
These technologies enable component-based UI development while remaining compatible with Moodle's existing frontend systems.
13+
14+
This document describes the recommended approach for implementing frontend functionality in Moodle, ensuring consistency, maintainability, and compatibility with the theming system.
15+
16+
## Mechanics: Building Frontend Features
17+
18+
### Frontend Source Structure
19+
20+
Frontend source code should be located within:
21+
22+
```console
23+
├── component
24+
│ └── js
25+
│ └── esm
26+
│ └── src
27+
```
28+
29+
- Source code is written in **TypeScript**
30+
- Code is compiled into browser-ready JavaScript
31+
- Compiled files should not be edited directly
32+
33+
### Rendering React Components from Templates
34+
35+
React components are rendered using the **React template helper**. See the [Mustache Helper docs](./javascript/react/reactautoinit) for more details.
36+
37+
```mustache
38+
{{#react}}
39+
{
40+
"component": "@moodle/lms/mod_book/viewer",
41+
"props": {
42+
"title": "{{title}}",
43+
"chapter": "{{chapter}}"
44+
},
45+
"id": "book-viewer",
46+
"class": "book-viewer-wrapper"
47+
}
48+
<p>Loading…</p>
49+
{{/react}}
50+
```
51+
52+
This:
53+
54+
- inserts a container element
55+
- registers the component for automatic initialisation
56+
57+
Templates **determine where the component appears**, while React defines the UI.
58+
59+
### Auto initialisation
60+
61+
When the page loads:
62+
63+
1. Moodle finds components registered by the React helper
64+
2. The corresponding ESM module is loaded
65+
3. The module's **default export** is treated as a React component. Moodle automatically renders this component into the container created by the template helper. The component receives the props defined in the template.
66+
67+
The default function mounts the React component. This is covered in more detail on the [Mustache helper and Autoinit](./javascript/react/reactautoinit) page.
68+
69+
### Component contract
70+
71+
React modules should export a **default React component**.
72+
73+
In practice, this means exporting a function that returns JSX:
74+
75+
```ts
76+
type Props = {
77+
title: string;
78+
};
79+
80+
export default function Viewer({title}: Props) {
81+
return <h1>{title}</h1>;
82+
}
83+
```
84+
85+
Core components follow this pattern consistently, and developers are strongly encouraged to do the same.
86+
87+
While other patterns may work, using a React component ensures consistency, maintainability, and compatibility with Moodle's frontend architecture.
88+
89+
### Passing Props
90+
91+
Templates should pass only the minimal data required to initialise the component.
92+
93+
In most cases, this means passing identifiers (such as IDs) or simple configuration values, rather than full data objects.
94+
95+
<ValidExample>
96+
97+
```json
98+
{
99+
"courseid": 42
100+
}
101+
```
102+
103+
```ts
104+
type Props = {
105+
courseid: number;
106+
};
107+
```
108+
109+
Component:
110+
111+
```ts
112+
useEffect(() => {
113+
fetchCourse(courseid).then(setCourse);
114+
}, [courseid]);
115+
```
116+
117+
</ValidExample>
118+
119+
<InvalidExample>
120+
121+
```json
122+
{
123+
"course": {
124+
"id": 42,
125+
"fullname": "Physics 101",
126+
"teachers": [],
127+
"activities": []
128+
}
129+
}
130+
```
131+
132+
</InvalidExample>
133+
134+
Why this is bad
135+
136+
- Duplicates backend logic in PHP
137+
- Couples template structure to component internals
138+
- Bloats page payload
139+
- Makes reuse harder
140+
141+
:::info
142+
143+
If the data can be fetched by the component, it should not be passed via props.
144+
145+
:::
146+
147+
:::warning
148+
149+
In some cases, small amounts of preloaded data may be passed to avoid unnecessary requests. This should be limited and carefully considered.
150+
151+
:::
152+
153+
### Using Moodle APIs
154+
155+
Moodle is in the process of improving support for using existing JavaScript APIs from ESM based code.
156+
157+
Commonly used APIs such as string handling and web service helpers are being progressively rolled out as ESM compatible modules.
158+
159+
Where ESM component wrappers are available, developers should prefer them.
160+
161+
Where they are not available, AMD files may be accessed using compatibility helpers.
162+
163+
Developers should:
164+
165+
- prefer ESM native APIs where available
166+
- use compatibility helpers for AMD files when required
167+
- avoid introducing new AMD based patterns
168+
169+
Support for ESM compatible APIs will continue to improve over time.
170+
171+
### Recommended pattern for Data Fetching
172+
173+
React components should avoid directly embedding data fetching logic where possible. Instead, data should be handled through service modules.
174+
175+
This helps keep components simple, improves reuse, and avoids duplication of API logic
176+
177+
#### Service Modules
178+
179+
Data fetching should be implemented in dedicated service files.
180+
181+
Example structure:
182+
183+
```
184+
services/
185+
└── courses.ts
186+
187+
```
188+
189+
Example:
190+
191+
```ts
192+
// services/courses.ts
193+
export const fetchCourse = (courseid: number) => {
194+
const request = {
195+
methodname: 'core_course_get_courses',
196+
args: {ids: [courseid]}
197+
};
198+
199+
return Ajax.call([request]).then(([result]) => result);
200+
}
201+
```
202+
203+
Service modules should:
204+
205+
- encapsulate API calls
206+
- handle request / response transformations
207+
- remain independent of the UI
208+
209+
#### Using Services in Components
210+
211+
Components should call service functions and manage the resulting state.
212+
213+
Example:
214+
215+
```ts
216+
const [course, setCourse] = useState(null);
217+
const [loading, setLoading] = useState(false);
218+
219+
useEffect(() => {
220+
setLoading(true)
221+
fetchCourse(courseid)
222+
.then(setCourse)
223+
.finally(() => setLoading(false));
224+
}, [courseid]);
225+
```
226+
227+
Components are responsible for:
228+
229+
- triggering data loading
230+
- managing loading and error states
231+
- rendering UI based on state
232+
233+
This pattern makes it easier to migrate to ESM implementations as they become available.
234+
235+
This approach is already used in parts of Moodle, where data fetching is separated into dedicated modules.
236+
The exact structure may differ, but the key principle is to keep API interactions separate.
237+
238+
<InvalidExample>
239+
240+
```ts
241+
useEffect(() => {
242+
Ajax.call([{
243+
methodname: 'core_course_get_courses',
244+
args: {ids: [courseid]}
245+
}])[0]
246+
.then(setCourse)
247+
.catch(setError)
248+
.finally(() => setLoading(false));
249+
}, [courseid]);
250+
251+
```
252+
253+
</InvalidExample>
254+
255+
While this example works, it tightly couples the component to the API and makes the code harder to reuse and maintain.
256+
257+
### Styling and Theming
258+
259+
Components must remain compatible with Moodle's theming system.
260+
261+
Although React components control their own markup, themes must still be able to customise the appearance of those components
262+
263+
#### Use Design System and Tokens
264+
265+
Where available, components should use the Moodle design system and design tokens rather than defining custom styles.
266+
267+
This helps ensure visual consistency across the platform and reduces duplication.
268+
269+
Avoid:
270+
271+
- hard coded colours
272+
- hard coded spacing
273+
- custom styles that duplicate the design system components
274+
275+
#### Avoid Inline Styles
276+
277+
Inline styles should be avoided unless absolutely necessary.
278+
279+
Inline styles:
280+
281+
- cannot be overridden by themes
282+
- make styling harder to maintain
283+
284+
Instead prefer class based styling
285+
286+
Where appropriate, components should support passing additional class names via props.
287+
288+
#### Do Not Assume Fixed Styling
289+
290+
Components should not assume a specific visual appearance.
291+
292+
Avoid:
293+
294+
- relying on specific colours or spacing
295+
- tightly coupling layouts to styling assumptions
296+
Themes may significantly alter the look and feel of components.
297+
298+
### Initialising Frontend Behaviour
299+
300+
Historically, templates used the `{{#js}}` helper:
301+
302+
```mustache
303+
{{#js}}
304+
require(['core/module'], function(module) {
305+
module.init();
306+
});
307+
{{/js}}
308+
```
309+
310+
This pattern remains supported but is **discouraged for new React-based components**.
311+
312+
Developers should prefer the **React template helper** for new UI.
313+
314+
The `{{#js}}` helper may still be used for:
315+
316+
- enhancing existing Mustache-rendered markup
317+
- working with legacy components
318+
319+
## Design Philosophy
320+
321+
### Templates Provide Placement, Not Structure
322+
323+
Historically:
324+
325+
`PHP → Template → UI`
326+
327+
Now:
328+
329+
`Template → React component → UI`
330+
331+
Templates define **where a component appears**.
332+
333+
React components define **how the UI is structured**.
334+
335+
### Minimal Server Context
336+
337+
Previously, PHP assembled large template contexts.
338+
339+
Now, the server should provide only **minimal props**.
340+
341+
```
342+
Server → minimal props
343+
344+
React initialises
345+
346+
Component fetches data
347+
```
348+
349+
Components retrieve additional data asynchronously.
350+
351+
### Components are Self Contained
352+
353+
React components should encapsulate:
354+
355+
- UI structure
356+
- state
357+
- user interaction
358+
- data loading state and calls to service files
359+
360+
This improves maintainability and reuse.
361+
362+
Components should orchestrate when and how data is loaded, while delegating actual API calls to service functions (in separate files).
363+
364+
### Maintainability and Consistency
365+
366+
Frontend code should prioritise:
367+
368+
- small, composable components
369+
- reuse of existing APIs and design system elements
370+
- predictable markup for theming
371+
- separation of concerns between server and client
372+
373+
### Transition from Legacy Patterns
374+
375+
| Historical approach | Modern approach |
376+
|----------------------------------|------------------------------------|
377+
| PHP builds full template context | PHP provides minimal props |
378+
| Mustache renders UI | React renders UI |
379+
| JavaScript enhances templates | Components manage UI and behaviour |
380+
381+
### Relationship to Reactive UI System
382+
383+
Moodle previously introduced a custom [reactive UI system](./javascript/reactive/) to support dynamic interfaces.
384+
385+
With the adoption of React, new reactive UI development should use **React-based components** instead.
386+
387+
The reactive system remains supported for existing code but should not be used for new features.

0 commit comments

Comments
 (0)