À proposExpertiseRéalisationsR&DBlogOutilsCommencerContact

Développer pour SharePoint Server Subscription Edition : les pratiques qui tiennent

Subscription Edition est pris en charge jusqu'en 2035 au moins et son plafond SharePoint Framework n'a pas bougé depuis 2023. Cette combinaison, c'est tout le travail : une longue piste sur une base stable. Comment bien construire dessus, avec du code qui tourne sur la version que votre ferme accepte réellement.

pH7x Systems® · · 24 min de lecture

Subscription Edition est pris en charge jusqu'au 31 décembre 2035 au moins, sous la Modern Lifecycle Policy, sans fin de support prévue. C'est une piste plus longue que celle de la plupart des logiciels que vous écrirez cette année.

Son plafond SharePoint Framework, lui, n'a pas bougé depuis 2023.

Ces deux faits réunis constituent tout le travail. Vous construisez quelque chose qui doit continuer à fonctionner pendant une décennie, sur une version de framework qui ne changera probablement pas sous vos pieds. C'est une position peu commune, et elle récompense un soin très particulier.

Connaître son plafond avant d'écrire quoi que ce soit

SharePoint on-premises n'exécute que les versions de SPFx correspondant à ses dépendances côté serveur. Le tableau de compatibilité donne à Subscription Edition v1.0 à v1.5, et la mise à jour de fonctionnalités 23H1 a ajouté la prise en charge de SPFx 1.5.1, décrite à l'époque comme "une étape de notre parcours à long terme visant à améliorer et à étendre les capacités de SharePoint Framework dans SharePoint Server Subscription Edition".

La mise à jour suivante, la 23H2, est allée un cran plus loin et a ajouté la prise en charge de React 16 et d'Office UI Fabric React 7 dans les solutions SPFx. Après cela, plus rien : en lisant de la 24H1 à la 26H1, aucune ne mentionne même SPFx. La 26H1 apporte Modern People Card, Document Intelligence et une option de configuration du People Picker. Rien sur le framework.

Vous trouverez aussi des articles de la communauté revendiquant un plafond nettement plus haut. Nous n'avons pu en vérifier aucun auprès d'une source de première main, donc nous n'allons pas répéter un numéro de version que nous ne pouvons pas défendre.

Il en découle une instruction pratique plutôt qu'un numéro : vérifiez sur la ferme, pas dans l'article. Déployez un paquet trivial, construit avec la version que vous comptez utiliser, dans votre propre catalogue d'applications, et regardez s'il se charge. Quinze minutes de cela valent mieux que n'importe quel tableau, y compris celui ci-dessus, parce que le plafond est une propriété de votre build.

Quel que soit le résultat, comptez qu'il ne bougera pas. Concevez comme si la version que vous avez était la version qui reste.

L'environnement qui construit vraiment

Le premier piège, c'est la chaîne d'outils, et il attrape presque tout le monde une fois.

Le générateur Yeoman actuel ne vous aidera pas. À partir de la version 1.13.0, il ne produit que des projets pour SharePoint Online. Installez le plus récent et la cible on-premises n'est tout simplement pas proposée. Il vous faut un générateur assez ancien pour poser encore la question :

bash
npm install gulp-cli@2.3.0 --global
npm install yo@2.0.6 --global
npm install @microsoft/generator-sharepoint@1.10.0 --global

Ensuite, choisissez la cible on-premises dans le flux du générateur.

Node est ancien. SPFx 1.4.1 et 1.5.1 visent par défaut Node 6 et 8. La documentation note qu'on peut les faire fonctionner sur Node v12.18.1, v14.17.1 et v16.15.0 avec deux changements : passer graceful-fs en v4 ou ultérieure, et remplacer node-sass par sass. C'est la différence entre une machine de build que quelqu'un peut encore monter en 2026 et une qu'il ne peut pas.

Consignez tout cela dans le dépôt. La version du générateur, celle de Node, celle de gulp-cli. Dans trois ans, la personne qui reprendra ceci ne devinera pas, et il n'y aura aucun article pour le lui dire. Un .nvmrc et une courte section CONTRIBUTING ne coûtent rien maintenant et font gagner une semaine plus tard.

Prévoyez le cas déconnecté. La génération du projet a besoin d'un accès à npm. Si vos machines de développement n'atteignent pas le registre, il vous en faut un local, et la recommandation de Microsoft elle-même est franche sur le coût : plus de logiciels et une quantité de travail de maintenance significative. Budgétez-le honnêtement au lieu de le découvrir en troisième semaine.

Les commandes de build sont celles de gulp, seul endroit où les articles plus anciens ont encore raison :

bash
gulp serve
gulp bundle --ship
gulp package-solution --ship

Parler à SharePoint

SPHttpClient est la voie. Il gère le request digest et l'authentification, et il se comporte de la même façon sur toutes les versions de SPFx, ce qui en fait la chose la plus durable de la boîte.

Mettez-le dans un service. Pas dans un composant.

typescript
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';

export interface IDocument {
  Id: number;
  Title: string;
  Modified: string;
}

// Built apart from the call that uses it, so it can be exercised without a
// client, a farm or a browser. It is also the line most likely to be wrong.
export function itemsUrl(webUrl: string, listTitle: string, top: number): string {
  // A list called "HR & Payroll" or "Q1 'draft' items" breaks this. The test
  // list is always called Documents, which is why it reaches production.
  var list = encodeURIComponent(listTitle.replace(/'/g, "''"));

  return webUrl + "/_api/web/lists/getbytitle('" + list + "')/items" +
    '?$select=Id,Title,Modified' +
    '&$orderby=Modified desc' +
    '&$top=' + top;
}

// One place decides what an HTTP status means. Components never see numbers.
// Not called describe, because mocha puts a global function of that name in
// every test file, and the collision confuses long before it fails.
export function describeStatus(status: number): string {
  if (status === 401) { return 'notAuthenticated'; }
  if (status === 403) { return 'accessDenied'; }
  if (status === 404) { return 'notFound'; }
  if (status === 429 || status === 503) { return 'throttled'; }
  return 'unknown';
}

export class DocumentService {
  constructor(
    private readonly client: SPHttpClient,
    private readonly webUrl: string
  ) {}

  public getRecent(listTitle: string, top: number): Promise<IDocument[]> {
    return this.client
      .get(itemsUrl(this.webUrl, listTitle, top), SPHttpClient.configurations.v1)
      .then(function (response: SPHttpClientResponse): Promise<any> {
        if (!response.ok) {
          return Promise.reject(describeStatus(response.status));
        }
        return response.json();
      })
      .then(function (json: { value: IDocument[] }): IDocument[] {
        return json.value;
      });
  }
}

Quatre détails là-dedans comptent plus qu'il n'y paraît.

L'URL est construite par une fonction à elle. Ce n'est pas du rangement : c'est la seule partie du service qu'on peut tester sans ferme, et la section suivante s'appuie dessus.

L'apostrophe est doublée avant que le titre soit encodé, et l'ordre compte. La syntaxe REST de SharePoint place le titre de la liste entre apostrophes, donc une liste nommée Q1 'draft' items ferme la chaîne trop tôt ; OData échappe une apostrophe en la doublant. encodeURIComponent laisse volontairement l'apostrophe tranquille, donc doubler après serait trop tard.

$select est explicite. Demander tout et utiliser trois champs, c'est de la bande passante payée à chaque rendu, et sur une ferme qui sert quelques milliers de personnes, ce n'est pas gratuit.

La chaîne de promesses n'est pas de la nostalgie. TypeScript 2.4 compilant vers ES5 ne vous donnera ni async ni await sans un runtime de générateurs. La chaîne est la version qui compile toujours.

Si vous voulez une API plus aimable, PnPjs fonctionne, mais vous devez épingler une version qui prend en charge votre SPFx et traiter cette épingle comme définitive. Un npm update qui vous pousse en avant en silence produit une solution qui compile sur votre machine et échoue sur le serveur, ce qui est la pire catégorie de défaut à diagnostiquer.

Les composants, et quel React vous obtenez vraiment

La documentation se contredit ici, et il vaut mieux trancher que contourner.

Trois sources de première main vont dans le même sens. Le tableau de compatibilité des plateformes SPFx donne à Subscription Edition v1.0 à v1.5. La note de version 23H1 ajoute la prise en charge de SPFx 1.5.1. La note de version 23H2 ajoute la prise en charge de React 16 et d'Office UI Fabric React 7, "permettant aux développeurs d'utiliser ces versions de composants plus récentes dans leurs solutions SharePoint Framework".

Une page va dans l'autre sens. L'article de développement SPFx pour l'on-premises affirme toujours que Subscription Edition "a exactement les mêmes dépendances et exigences pour SharePoint Framework que SharePoint Server 2019", et vous renvoie vers la v1.4.1. Cette page n'a pas suivi : elle contredit le tableau de compatibilité publié sur le même site, et elle décrit le produit tel qu'il était avant 23H1. Deux sources à jour contre une périmée, ce n'est pas une égalité : ce sont les notes de version qui font foi.

Il en découle quelque chose de concret. Les hooks sont arrivés dans React 16.8. Sur une ferme en 23H2 ou ultérieure, React 16 est pris en charge, donc les hooks vous sont accessibles dès lors que vous installez React 16.8 ou supérieur et que vous l'épinglez. La note nomme la version majeure et non la mineure, ce qui est précisément la raison de l'épingler et de consigner cette épingle.

En dessous de 23H2, et sur SharePoint 2019 et 2016, vous êtes en React 15 et tous les composants sont des classes.

Le composant de classe vaut la peine dans tous les cas, parce que c'est celui qui tourne partout : en 1.4.1 comme en 1.5.1, au-dessus comme en dessous de 23H2. Si vous écrivez quelque chose qui doit survivre à un calendrier de mises à jour que vous ne maîtrisez pas, c'est le plancher, et il coûte trois choses à faire à la main.

typescript
import * as React from 'react';
import { DocumentService, IDocument } from '../services/DocumentService';

export interface IRecentDocumentsProps {
  service: DocumentService;
  listTitle: string;
  top: number;
}

export interface IRecentDocumentsState {
  status: string;
  items: IDocument[];
}

export default class RecentDocuments extends React.Component<
  IRecentDocumentsProps,
  IRecentDocumentsState
> {
  private _mounted: boolean = false;
  private _request: number = 0;

  constructor(props: IRecentDocumentsProps) {
    super(props);
    this.state = { status: 'loading', items: [] };
  }

  public componentDidMount(): void {
    this._mounted = true;
    this._load();
  }

  public componentWillUnmount(): void {
    // There is no cleanup function to return here. Without this flag, a slow
    // list on a busy farm updates a component that is already gone every time
    // somebody navigates away mid-load.
    this._mounted = false;
  }

  public componentDidUpdate(previous: IRecentDocumentsProps): void {
    // Compare first. Reloading unconditionally is an infinite loop: load, set
    // state, re-render, load again. It is silent until somebody opens the
    // network tab.
    if (previous.listTitle !== this.props.listTitle || previous.top !== this.props.top) {
      this._load();
    }
  }

  private _load(): void {
    var self = this;
    var request = ++this._request;

    this.setState({ status: 'loading', items: [] });

    this.props.service
      .getRecent(this.props.listTitle, this.props.top)
      .then(function (items: IDocument[]): void {
        // A slower answer to an older request must never overwrite a newer one.
        if (!self._mounted || request !== self._request) { return; }
        self.setState({ status: items.length ? 'ready' : 'empty', items: items });
      })
      .catch(function (failure: string): void {
        if (!self._mounted || request !== self._request) { return; }
        self.setState({
          status: failure === 'accessDenied' ? 'accessDenied' : 'error',
          items: []
        });
      });
  }

  public render(): React.ReactElement<IRecentDocumentsProps> {
    if (this.state.status === 'loading') {
      return <p role="status">Loading recent documents.</p>;
    }
    if (this.state.status === 'accessDenied') {
      return (
        <p role="alert">
          You do not have access to this library. Ask the site owner for read access.
        </p>
      );
    }
    if (this.state.status === 'error') {
      return <p role="alert">The library could not be reached. Try again in a moment.</p>;
    }
    if (this.state.status === 'empty') {
      return <p>No documents have been added yet.</p>;
    }

    return (
      <ul>
        {this.state.items.map(function (item: IDocument): JSX.Element {
          return <li key={item.Id}>{item.Title}</li>;
        })}
      </ul>
    );
  }
}

Le drapeau _mounted remplace la fonction de nettoyage que renverrait un hook moderne.

Le compteur _request remplace tout ce vers quoi on se tournerait autrement pour gérer la concurrence. Deux changements rapprochés dans le volet de propriétés lancent deux requêtes, et elles reviennent dans l'ordre que la ferme décide. Sans le compteur, la réponse la plus ancienne peut arriver en dernier et l'emporter.

La comparaison dans componentDidUpdate est l'erreur classique des composants de classe. Sans elle, vous avez construit une boucle infinie contre votre propre ferme.

Thèmes

Il n'y a pas de service ThemeProvider dans cette version, ni de Fluent UI v9. Ce que vous avez, ce sont des jetons de thème en SCSS, et cela suffit :

scss
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';

.recentDocuments {
  color: "[theme: bodyText, default: #333333]";
  background-color: "[theme: white, default: #ffffff]";
  padding: 12px;
}

.recentDocuments a {
  color: "[theme: themePrimary, default: #0078d4]";
}

.recentDocuments a:hover {
  color: "[theme: themeDarkAlt, default: #106ebe]";
}

.recentDocuments .meta {
  color: "[theme: neutralSecondary, default: #666666]";
}

La règle est celle qui s'applique sur n'importe quelle plateforme et n'importe quelle version : la solution prend ses couleurs sur le site. Écrivez une valeur hexadécimale en dur et vous avez construit ce qui casse le jour où quelqu'un applique un autre thème, et sur une ferme qui sert plusieurs services avec plusieurs thèmes, ce jour est proche.

La valeur default: est ce qui s'affiche là où aucun thème n'est appliqué. Choisissez-la pour que la web part reste lisible plutôt qu'invisible.

Les permissions font partie de l'expérience

Un échec n'est pas une seule chose, et en on-premises cela pèse plus qu'on ne l'imagine, parce que les permissions dans une ferme avec des structures héritées et du ciblage d'audience sont réellement compliquées.

Cinq situations, et la personne peut agir sur quatre d'entre elles :

  • Session non ouverte. Rafraîchir et se reconnecter.
  • Accès refusé. Demander au propriétaire du site. Ce n'est pas une erreur que le lecteur a causée, et cela mérite une instruction plutôt qu'une excuse.
  • Introuvable. La liste a été renommée ou supprimée. Dites laquelle vous cherchiez.
  • Limitation ou indisponibilité. La ferme est chargée. Proposez un moyen de réessayer.
  • Il n'y a rien. La requête a fonctionné et la réponse est vide. Ce n'est pas un échec du tout.

Faites la correspondance une fois, dans le service, comme le fait le code ci-dessus. Puis concevez chacune. Une web part qui dit "Une erreur est survenue" pour les cinq est une web part qui génère un ticket de support pour chacune d'elles.

Et Microsoft Graph

Cette question revient toujours, et la réponse habituelle, que vos données sont sur vos serveurs et que Graph est donc hors sujet, est trop facile. Beaucoup de fermes cohabitent avec un tenant Microsoft 365, et vouloir en tirer des données de profil est parfaitement raisonnable.

La réponse précise sert davantage. MSGraphClient et AadHttpClient ont tous deux été introduits dans SPFx 1.4.1, donc les classes existent dans votre version. Deux choses font obstacle.

Les notes de version 1.4.1 les décrivent comme developer preview, "disponibles en préversion dans SharePoint Online", et explicitement "pas destinés à un usage en production pour l'instant".

Plus décisif encore, le modèle de permissions dont ils dépendent est de l'infrastructure SharePoint Online de bout en bout. On déclare webApiPermissionRequests dans package-solution.json ; le déploiement dans le catalogue d'applications crée des demandes de permission ; un administrateur les approuve sur la page d'accès aux API du centre d'administration SharePoint ; l'octroi est stocké sur l'application SharePoint Online Client Extensibility que Microsoft provisionne dans chaque Entra ID. Une ferme à vous n'a rien de tout cela, donc il n'y a personne à qui demander ni personne à qui accorder.

Le contournement évident est fermé lui aussi : la documentation indique que l'usage direct de la Microsoft Authentication Library avec SPFx n'est pas pris en charge à partir de la v1.4.1.

Pour être précis sur ce qui est établi et ce qui ne l'est pas : il est documenté que les clients étaient en préversion et circonscrits à SharePoint Online, que leur mécanisme de permissions est un service SharePoint Online, et que MSAL n'est pas prise en charge. Ce qui n'est documenté nulle part où nous ayons cherché, c'est une voie on-premises prise en charge. L'absence de documentation ne prouve pas que rien ne fonctionne, mais une solution en production sur une voie non documentée, dans une ferme qu'il faudra corriger pendant neuf ans, est une décision à prendre les yeux ouverts.

Le motif qui tient, c'est un service à vous. Une API déployée là où vous la maîtrisez, sécurisée comme vous l'entendez, qui parle à Graph côté serveur et que votre web part appelle avec SPHttpClient ou un simple HttpClient. La gestion des jetons cesse d'être un problème du navigateur, et la solution reste dans ce qui est pris en charge.

Tests : le runner est déjà dans la boîte

On suppose d'ordinaire qu'il n'y a pas de runner de tests ici. L'article CI/CD de Microsoft lui-même est à l'origine de cette idée, et il vaut la peine d'en lire la phrase attentivement : "SharePoint Framework ne fournit pas de framework de tests par défaut (depuis la 1.8.0)".

Depuis la 1.8.0. En dessous, il en fournit un, et la version qu'accepte Subscription Edition est en dessous.

En 1.5.1, le paquet de build @microsoft/sp-build-web dépend de @microsoft/gulp-core-build-karma, qui apporte Karma, Mocha, Chai, Sinon, sinon-chai, karma-coverage, l'instrumenteur Istanbul et PhantomJS. Le générateur place @types/chai et @types/mocha dans le projet qu'il écrit pour vous. Une tâche gulp test attend, documentée comme "exécute les tests unitaires, s'il y en a".

Ce qui manque, ce n'est pas la machinerie. C'est un fichier de configuration, et le moindre test. Personne ne vous le dit, donc gulp test a l'air cassé et tout le monde conclut qu'il n'y a rien.

Trois choses que rien ne signale

La tâche ne fait rien tant qu'un fichier de configuration n'existe pas. Elle cherche ./karma.config.js à la racine du projet. Si le fichier est absent, elle écrit un avertissement et rend la main, et le build reste vert. Écrivez-le une fois :

bash
gulp test --initkarma

Cela copie la configuration par défaut dans votre projet, et à partir de là gulp test s'exécute vraiment.

Le runner lit le compilé, pas vos sources. Le motif par défaut est /.+\.test\.js?$/ appliqué au dossier lib. La tâche génère temp/tests.js contenant un require.context webpack sur lib, et Karma charge ce fichier unique. Ainsi un test écrit dans src/webparts/recentDocuments/services/DocumentService.test.ts est trouvé sous lib/webparts/recentDocuments/services/DocumentService.test.js une fois TypeScript passé. Deux conséquences : nommez le fichier .test.ts et rien d'autre (un .spec.ts est ignoré en silence, sans erreur ni avertissement), et souvenez-vous qu'un dossier lib périmé exécute des tests périmés. gulp clean dès qu'un résultat vous surprend.

Des tests en échec ne font pas échouer le build. failBuildOnErrors vaut false par défaut, donc un test rouge écrit un avertissement et le build continue. Le mode production fait exception : avec --ship, l'échec l'arrête. Cette valeur par défaut mérite d'être changée dès le premier jour, parce qu'un avertissement au milieu d'un mur de sortie de build, c'est une suite de tests que personne ne lit. Dans gulpfile.js, avant build.initialize(gulp) :

javascript
build.karma.setConfig({ failBuildOnErrors: true });

Le test

Karma charge les frameworks mocha et sinon-chai, donc describe et it sont globaux et Chai est là pour être importé. Rien n'a besoin d'un moteur de rendu, parce que rien de ce qui mérite d'être testé ici ne se rend.

typescript
// src/webparts/recentDocuments/services/DocumentService.test.ts
import { expect } from 'chai';
import { itemsUrl, describeStatus } from './DocumentService';

describe('itemsUrl', () => {
  // Everybody develops against a list called Documents. Production has
  // "HR & Payroll" and "Q1 'draft' items", and that is where this breaks.
  it('doubles a single quote, which OData would read as the end of the title', () => {
    expect(itemsUrl('https://sp/sites/hr', "Q1 'draft' items", 5))
      .to.contain("getbytitle('Q1%20''draft''%20items')");
  });

  it('encodes an ampersand, which would otherwise start a new parameter', () => {
    expect(itemsUrl('https://sp/sites/hr', 'HR & Payroll', 5))
      .to.contain("getbytitle('HR%20%26%20Payroll')");
  });

  it('asks only for the three fields it renders', () => {
    expect(itemsUrl('https://sp', 'Documents', 5))
      .to.contain('$select=Id,Title,Modified');
  });
});

describe('describeStatus', () => {
  it('separates the failures a person can act on', () => {
    expect(describeStatus(401)).to.equal('notAuthenticated');
    expect(describeStatus(403)).to.equal('accessDenied');
    expect(describeStatus(404)).to.equal('notFound');
  });

  it('reads a busy farm as throttled rather than broken', () => {
    expect(describeStatus(429)).to.equal('throttled');
    expect(describeStatus(503)).to.equal('throttled');
  });

  it('does not pretend to recognise a status it has never seen', () => {
    expect(describeStatus(500)).to.equal('unknown');
  });
});
bash
gulp test                     # once, with coverage
gulp test --match itemsUrl    # passed through to mocha as a grep
gulp test --debug             # leaves the browser open, skips instrumentation

PhantomJS, la partie qui va vraiment vous arrêter

La configuration par défaut ne liste qu'un seul navigateur, et c'est PhantomJS, dont le développement a été suspendu en 2018.

phantomjs-prebuilt télécharge un binaire à l'installation, depuis une table de quatre entrées : linux x64, linux ia32, darwin et win32. Tout le reste reçoit "aucun binaire disponible pour votre plateforme ou architecture" et l'installation échoue. Notez ce que darwin signifie là, parce que c'est ce qui a l'air de marcher sans marcher : il n'y a qu'une seule build macOS et elle est x86_64, donc sur Apple Silicon le téléchargement réussit et vous obtenez un binaire Intel qui a besoin de Rosetta. Sur Linux arm64, il n'y a aucune entrée.

L'hôte de téléchargement est une release GitHub, et il est remplaçable :

bash
PHANTOMJS_CDNURL=https://your-mirror/phantomjs npm install

C'est le même problème de machines déconnectées que plus haut dans cet article, et il a la même forme : une chose de plus à répliquer, budgétée honnêtement plutôt que découverte le matin du build.

Si PhantomJS n'est pas viable, et après 2018 il ne l'est généralement pas, changez le navigateur, pas le framework. Mocha, Chai et tous les tests ci-dessus restent exactement tels quels ; seul le launcher dans karma.config.js change, pour karma-chrome-launcher avec ChromeHeadless. Sachez que Karma est ici en 0.13, donc quelle version de launcher coopère se découvre en essayant, pas en lisant. C'est un après-midi circonscrit et cela ne touche pas une ligne de votre code de test.

L'autre voie, si vous préférez laisser le navigateur entièrement de côté, c'est Jest, vers lequel l'article CI/CD de Microsoft pointe directement et vers lequel SPFx est allé de toute façon à partir de la 1.8.0. Pour un projet en React 15, cette documentation nomme le preset : @voitanos/jest-preset-spfx-react15. Il tourne dans Node, donc PhantomJS cesse d'être votre problème, au prix d'une seconde chaîne d'outils à côté de gulp test.

Quoi tester, et quoi non

Mérite sa place :

  • La construction de l'URL. Une apostrophe, une esperluette, un espace, un accent. Le test de plus grande valeur de toute la solution, et ce sont les trois cas ci-dessus.
  • La correspondance des états. Un 403 donne un état différent d'un 500, et un 404 différent des deux.
  • La lecture de la réponse. Un champ absent, une date qui ne se convertit pas, une liste vide.

Ne mérite pas sa place : affirmer que React a rendu un <ul>.

Et une discipline qui compte plus que tout le reste : cassez chaque garde exprès et vérifiez qu'un test passe au rouge. Supprimez le drapeau _mounted, ou la comparaison de componentDidUpdate, et lancez la suite. Si tout passe encore, vous avez appris quelque chose de réel : soit cette garde n'est pas testée, soit le scénario ne l'exerce pas, soit le test mesure la mauvaise propriété. Une suite verte qui reste verte après qu'on a saboté le code ne dit pas que le code est juste. Elle ne dit rien du tout.

Accessibilité

React 15 rend le même HTML que React 19. Rien de tout cela ne dépend de votre version.

  • Une liste de résultats est un <ul> de <li>, pas une pile de <div>.
  • Le lien porte le titre, pour que celui qui navigue de lien en lien entende des noms de documents plutôt que "lire la suite" répété.
  • Les changements d'état vont dans une live region, ce que font role="status" et role="alert" dans le composant ci-dessus.
  • Toutes les commandes sont atteignables au clavier et le focus est visible.
  • Rien n'est porté par la couleur seule. Un état a besoin d'un mot.

Vérifiez-le dans l'arbre d'accessibilité du navigateur, et pas à l'œil. Ce que lit un lecteur d'écran n'est pas toujours ce que le balisage suggère.

Moins de dépendances, et pourquoi cela pèse davantage ici

Chaque bibliothèque ajoutée est figée sur un vieux TypeScript et un vieux React, que cela vous plaise ou non, et elle y reste figée. Sur un tenant, on met à jour discrètement ; sur une ferme, une fenêtre de mise à jour est une demande de changement avec un week-end de maintenance attaché.

Avant d'ajouter quoi que ce soit, demandez-vous si SharePoint le fait déjà. Un graphique vaut souvent mieux en nombre accompagné d'une barre proportionnelle. Un PDF vaut généralement mieux en impression du navigateur avec une feuille de style d'impression, ce qui donne en plus du texte sélectionnable.

La mesure qui tranche, c'est la taille du paquet sur un build propre, avant et après.

Gardez le volet de propriétés ennuyeux

Deux questions décident si un réglage a sa place là.

Celui qui ajoute la web part le règle-t-il une fois, ou le lecteur le change-t-il en cours d'usage ? Si c'est le second cas, sa place est dans l'interface.

Change-t-il le comportement ? Si une propriété est acceptée puis ignorée, ce n'est pas une place réservée pour une version future. C'est un défaut que quelqu'un signalera, après lui avoir fait confiance.

Quatre propriétés qui font toutes quelque chose valent mieux que douze dont presque aucune n'agit, sur n'importe quelle version de n'importe quoi.

Déploiement

Le catalogue d'applications de votre propre ferme, et le workbench sur /_layouts/workbench.aspx d'un site que vous contrôlez. Ce workbench n'est pas celui hébergé qui est retiré côté SharePoint Online ; le vôtre vit aussi longtemps que la ferme.

Deux choses à installer dans la routine dès le départ. Versionnez le .sppkg délibérément dans package-solution.json au lieu de le laisser en 1.0.0.0 pour toujours, parce qu'un catalogue d'applications qui affiche la même version pour trois builds différents est un problème de support en attente. Et tenez un registre de quelle build est déployée où, puisqu'aucun service ne vous le dira.

Le résumé

Une longue piste sur une base immobile n'est pas un mauvais endroit où se trouver. Cela récompense les décisions bon marché maintenant et coûteuses plus tard : mettre les appels dans un service, faire correspondre les erreurs une fois, prendre les couleurs sur le site, protéger les mises à jour d'état, tester les parties pures, ajouter le moins possible.

Une web part écrite ainsi en 1.5.1 fonctionnera encore quand la fenêtre de support de la plateforme se fermera, en 2035. Une écrite autrement ne survivra pas à son premier changement de thème, et c'est vous qui la maintiendrez dans les deux cas.

Avant de commencer

  • Confirmez le niveau de mise à jour de fonctionnalités de la ferme et vérifiez le plafond SPFx en déployant un paquet trivial, plutôt qu'en vous fiant à un tableau
  • Vérifiez si la ferme est en 23H2 ou ultérieure, car c'est ce qui décide de la disponibilité de React 16 et des hooks, et épinglez la version de React dans tous les cas
  • Épinglez et consignez dans le dépôt les versions du générateur, de Node, de gulp-cli et de Yeoman
  • Réglez la question de l'accès à npm avant le premier jour, si les machines sont déconnectées
  • Mettez tous les appels dans un service, et faites correspondre les statuts HTTP aux états en un seul endroit
  • Échappez les apostrophes et encodez les titres de liste dans toutes les URL REST
  • Protégez les mises à jour d'état par un drapeau de montage et un compteur de requêtes
  • Comparez les props avant de recharger dans componentDidUpdate
  • Utilisez des jetons de thème en SCSS, jamais une valeur hexadécimale
  • Concevez un état pour chaque échec, y compris celui de la réponse vide
  • Lancez gulp test --initkarma une fois, nommez les fichiers de test .test.ts, et mettez failBuildOnErrors: true
  • Réglez la question PhantomJS avant d'écrire des tests, en changeant de launcher ou en passant à Jest
  • Testez la construction de l'URL, la correspondance des états et la lecture de la réponse, et cassez chaque garde pour prouver que le test passe au rouge
  • Épinglez PnPjs sur une version qui prend en charge votre SPFx, et traitez cette épingle comme définitive
  • Versionnez le paquet délibérément et consignez ce qui est déployé où

Poursuivre la lecture

Migrations

SharePoint 2016, 2019, Subscription Edition et Online : ce qui change vraiment

2016 et 2019 s'arrêtent tous deux le 14 juillet 2026. Subscription Edition n'a pas de date de fin et ne s'achète pas une fois pour toutes. Online a des fonctionnalités que les autres n'auront jamais, et Subscription Edition en a deux qui sont habituellement listées comme impossibles. Une comparaison pratique, avec la licence qui tranche.

·22 min de lecture
IA et agents

Un manifeste d'agent Copilot est une frontière de sécurité. Voici le vérificateur.

Toutes les propriétés de portée d'un manifeste d'agent Microsoft 365 Copilot sont facultatives et, dans six cas, en omettre une donne la portée la plus large au lieu de la plus étroite. Un validateur JSON Schema approuve ces manifestes, parce qu'aucun n'a quoi que ce soit d'invalide : il manque quelque chose de facultatif. Nous avons donc écrit la vérification qui les attrape. Elle trouve six problèmes dans un manifeste qui passe tous les autres tests.

·7 min de lecture
Développement et automatisation

SPFx avant la version 1.0 : trois de nos web parts dans les samples officiels de Microsoft

La developer preview du SharePoint Framework est sortie en août 2016. La version 1.0 est arrivée en février 2017. Notre première contribution au dépôt officiel des samples Microsoft 365 date d'octobre 2016, cinq mois avant qu'il existe une 1.0 sur laquelle construire. Trois de nos web parts s'y trouvent aujourd'hui, et voici à quoi sert chacune.

·5 min de lecture