Afin de développer cette application les pré-requis suivants ont été installés sur WSL : Node v22, Gulp, Yeoman et, bien sûr, Yeoman SharePoint generator.
Pourquoi utiliser WSL ? Car c’est bien plus rapide !
Une fois le projet initié via la commande yo @microsoft/sharepoint
il est temps d’organiser le projet !
Voici l’organisation retenue :
celine@HP-Liline:/var/www/html/SPFX_ClubLecture/src$ tree
.
├── index.ts
└── webparts
└── clubLecture
├── ClubLectureWebPart.manifest.json
├── ClubLectureWebPart.ts # Classe principale du web part SPFx (extends BaseClientSideWebPart)
├── components
│ └── icons
│ └── FluentIcons.tsx # Bibliothèque d'icônes personnalisées
├── features # Dossier regroupant les vues fonctionnelles
│ ├── CalendrierSessions
│ │ └── CalendrierSessions.tsx
│ ├── ClubLecture # Feature principale : ClubLecture
│ │ ├── ClubLecture.module.scss # Styles locaux au composant ClubLecture (scopés via CSS Modules)
│ │ ├── ClubLecture.module.scss.ts # Fichier TypeScript généré automatiquement pour l'import des classes CSS
│ │ ├── ClubLecture.tsx # Composant React de ClubLecture
│ │ ├── IImageModern.ts # Interface TypeScript pour les données d'image (colonne moderne)
│ │ └── ILivre.ts # Interface TypeScript pour un livre (modèle de données)
│ ├── InscriptionLecture
│ │ └── InscriptionLecture.tsx
│ ├── LesLivres
│ │ ├── LesLivres.module.scss
│ │ ├── LesLivres.module.scss.ts
│ │ └── LesLivres.tsx
│ └── SondageLivre
│ └── SondageLivre.tsx
├── services # Dossier pour logique d'accès aux données
│ └── livreService.ts # Service pour interagir avec la liste SharePoint
└── styles
├── global.scss # Fichier SCSS global
└── global.scss.ts
Le fichier ClubLectureWebPart.ts exporte ClubLectureWebPart qui est la classe principale du web part SPFx.
Il configure le contexte SharePoint, instancie le composant React (ClubLecture.tsx) et contrôle son rendu dans la page.
C’est le point de liaison entre l’environnement SharePoint et ton interface React.
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import { SPFx, spfi } from "@pnp/sp";
/*
SPFx fournit la classe de base BaseClientSideWebPart à étendre
Fournit le cadre d'exécution de la WebPart
*/
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import ClubLecture from './features/ClubLecture/ClubLecture';
export let sp: any;
/*
Classe principale nommée ClubLectureWebPart qui étend BaseClientSideWebPart
<{}> signifie qu'elle n'attend pas de propriétés configurales via l'interface SharePoint
*/
export default class ClubLectureWebPart extends BaseClientSideWebPart<{}> {
// Méthode appelée automatiquement au démarrage de la WebPart
public onInit(): Promise<void> {
/*
Appel de l'initialisation de base SPFx et configuration de PnPJS
Création d'une instance PnPJS avec spfi() et injection du contexte SPFx
PnP pourra utiliser les permissions, l'URL du site, etc.
*/
return super.onInit().then(() => {
sp = spfi().using(SPFx(this.context));
});
}
/*
Méthode render()
Création du composant React avec React.createElement()
Rendu dans la page SharePoint via this.domElement avec ReactDom.render()
*/
public render(): void {
const element: React.ReactElement = React.createElement(ClubLecture);
ReactDom.render(element, this.domElement);
}
/*
Méthode onDispose()
Appelée quand la WebPart est supprimée de la page. Retire proprement le composant React pour éviter les fuites de mémoire
*/
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
/*
Méthode get dataVersion()
Indique la version de données de la WebPart, utile pour la gestion de version des propriétés
*/
protected get dataVersion(): Version {
return Version.parse('1.0');
}
}
Via le service livreService.ts les éléments de la liste SharePoint Lectures sont récupérés.
import { sp } from '../ClubLectureWebPart'
import { ILivre } from '../features/ClubLecture/ILivre';
// Fonction asynchrone qui va retourner une promesse d'un tableau de livres
export const loadLectures = async (): Promise<ILivre[]> => {
let result: ILivre[] = [];
try {
const items = await sp.web.lists
.getByTitle("Lectures")
.items
.select("Id", "Title", "Auteur", "Description", "DateDebut", "Image")
.top(10)();
const webUrl = "/sites/Developpement/Lists/Lectures";
// Parcours de chaque élément retourné par SharePoint
result = items.map((item: { Image: string; Id: any; Couverture: any; }) => {
let imageModern = null;
if (item.Image) {
try {
// Parse le champ Image (colonne moderne) qui est une chaîne JSON
const parsed = JSON.parse(item.Image);
if (parsed?.fileName) {
const fullUrl = `${webUrl}/Attachments/${item.Id}/${parsed.fileName}`;
imageModern = {
...parsed,
// Ajout de l’URL calculée aux métadonnées de l’image
fullUrl
};
}
} catch (err) {
console.warn("Erreur de parsing colonne Image :", err);
}
}
// Retourne l’élément avec un objet Image complet
return {
...item,
Image: imageModern
};
});
} catch (err) {
console.error("Erreur lors du chargement des livres :", err);
}
// Retourne le tableau final des livres (avec image formatée si présente)
return result;
};
Ces éléments seront ensuite utilisés dans LesLivres.tsx :
import * as React from 'react';
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
import { BookIcon } from '../../components/icons/FluentIcons';
import '../../styles/global.scss'
import styles from './ClubLecture.module.scss';
import { ILivre } from './ILivre';
import { loadLectures } from '../../services/livreService';
const LesLivres: React.FC = () => {
const [livres, setLivres] = React.useState<ILivre[]>([]);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState<string | null>(null);
React.useEffect(() => {
const load = async () => {
try {
// Appelle de la fonction qui récupère les livres (lectures) depuis SharePoint
const lectures = await loadLectures();
setLivres(lectures);
} catch (error) {
setError("Impossible de charger les livres.");
}
setLoading(false);
};
load();
}, []);
// Rendu JSX
return (
<section>
<div className={styles.sectionTitle}>
<BookIcon className="icon iconLarge"></BookIcon>
<h2>Les livres</h2>
</div>
{loading && <p>Chargement en cours...</p>}
{error && <p style={{ color: 'red' }}>{error}</p>}
{!loading && !error && (
<div>
{livres.map((livre) => (
<div key={livre.Id} className={styles.card}>
<div>
<strong>{livre.Title}</strong><br />
{livre.Auteur && <em>{livre.Auteur}</em>}<br />
</div>
<div className={styles.bookInfos}>
{livre.Description && <p>{livre.Description}</p>}
{livre.Image?.fullUrl && (
<img className="thumbnailBook"
src={livre.Image.fullUrl}
alt={livre.Image.originalImageName || "Image"}
/>
)}
</div>
</div>
))}
</div>
)}
</section>
);
};
export default ClubLecture;
Voici l’affichage des éléments via le composant SPFX en comparaison avec l’affichage natif.


Consultante spécialisée dans les technologies Microsoft, je justifie de plus de 14 ans d’expérience dans le secteur IT.
Mon parcours professionnel, riche et varié, m’a permis de développer de solides compétences, tant techniques qu’humaines. Je suis reconnue pour ma capacité à analyser rapidement les enjeux techniques et à concevoir des solutions pertinentes, y compris sur des problématiques complexes.
Mon approche proactive, alliée à mes compétences en développement, me permet de proposer des évolutions efficaces, toujours en cohérence avec les objectifs métiers.
En parallèle de mon activité professionnelle, je prends plaisir à approfondir mes compétences techniques en réalisant des projets personnels. Je développe notamment des jeux web basés sur React et PHP, ainsi que des applications orientées métier avec SPFx.