Utilisation de la bibliothèque @pnp/sp (PnPJS) avec les composants WebPart SharePoint Framework
Vous pouvez choisir d’utiliser la bibliothèque @pnp/sp lors de la création de vos composants WebPart SharePoint Framework (SPFx). Cette bibliothèque fournit une API Fluent pour rendre la création de vos requêtes REST intuitive et prend en charge le traitement par lots et la mise en cache. Pour plus d’informations, consultez la page d’accueil du projet, qui contient des liens vers la documentation, des exemples et d’autres ressources pour vous aider à commencer.
Notes
PnPJS est une solution open source pour laquelle le support est assuré par la communauté active. Il n’existe pas de contrat SLA Microsoft pour le support technique relatif à cet outil open source.
Vous pouvez télécharger la source complète pour cet article à partir du site d’exemples.
Notes
Avant de suivre les étapes décrites dans cet article, n’oubliez pas de configurer votre environnement de développement de composant WebPart côté client SharePoint.
Création d’un projet
Créez un dossier pour le projet à l’aide de la console de votre choix :
md spfx-pnp-js-exampleSaisissez le dossier suivant :
cd spfx-pnp-js-exampleExécutez le générateur Yeoman pour SPFx :
yo @microsoft/sharepointEntrez les valeurs suivantes lorsque vous y êtes invité pendant la configuration du nouveau projet :
- spfx-sp-pnp-js-example comme nom de solution (conservez la valeur par défaut)
- SharePoint Online uniquement (dernière version) comme version de packages de référence
- Current Folder comme emplacement de la solution
- Y pour autoriser l’administrateur client à déployer la solution sur tous les sites
- WebPart pour le composant à créer
- SPPnPJSExample comme nom de composant WebPart
- Exemple d’utilisation sp-pnp-js within SPFx comme description
- Knockout comme infrastructure

Une fois la génération de modèles automatique terminée, verrouillez la version de dépendances du projet en exécutant la commande suivante :
npm shrinkwrapOuvrez le projet dans l’éditeur de code de votre choix. Les captures d’écran ci-dessous illustrent Visual Studio Code. Pour ouvrir le répertoire dans Visual Studio Code, entrez les informations suivantes dans la console :
code .
Installation et configuration d’@pnp/sp
Une fois votre projet créé, installez et configurez la bibliothèque @pnp/sp, en commençant par l’installation du package. Ces étapes sont communes à tout type de projet (React, etc.).
npm install @pnp/logging @pnp/common @pnp/odata @pnp/sp --save
Étant donné que la bibliothèque @pnp/sp crée des demandes REST, elle doit connaître l’URL à laquelle envoyer les demandes. Quand elle est utilisée dans des sites et des pages classiques, vous pouvez utiliser la variable générale_spPageContextInfo. Dans SPFx, soit l’URL n’est pas disponible, soit elle risque d’être incorrecte. C’est pourquoi nous vous recommandons d’utiliser l’objet context fourni par l’infrastructure.
Il existe deux manières de vous assurer que vous avez correctement configuré vos demandes. Nous employons la méthode onInit dans cet exemple.
Mise à jour de la méthode onInit dans SpPnPjsExampleWebPart.ts
Ouvrez le fichier src\webparts\spPnPjsExample\SpPnPjsExampleWebPart.ts et ajoutez l’instruction import de l’objet racine pnp :
import { sp } from "@pnp/sp";Dans la méthode
onInit(), mettez à jour le code pour qu’il apparaisse de la manière suivante. Ajoutez le bloc après l’appelsuper.onInit(). Cette étape doit être réalisée après l’appelsuper.onInit()pour permettre à l’infrastructure d’initialiser tous les éléments requis et pour procéder à la configuration de la bibliothèque après la réalisation de ces étapes./** * Initialize the web part. */ protected onInit(): Promise<void> { this._id = _instance++; const tagName: string = `ComponentElement-${this._id}`; this._componentElement = this._createComponentElement(tagName); this._registerComponent(tagName); // When web part description is changed, notify view model to update. this._koDescription.subscribe((newValue: string) => { this._shouter.notifySubscribers(newValue, 'description'); }); const bindings: IPnPjsExampleBindingContext = { description: this.properties.description, shouter: this._shouter }; ko.applyBindings(bindings, this._componentElement); sp.setup({ spfxContext: this.context }); // optional, we are setting up the @pnp/logging for debugging Logger.activeLogLevel = LogLevel.Info; Logger.subscribe(new ConsoleListener()); return super.onInit(); }
Mise à jour de ViewModel
Remplacez ensuite les contenus du fichier PnPjsExampleViewModel.ts par le code suivant. Nous avons ajouté une instruction d’importation pour les éléments pnp, une interface pour définir les champs d’élément de notre liste, des éléments visibles pour suivre à la fois notre liste d’éléments et le nouveau formulaire d’éléments, et des méthodes pour prendre en charge les éléments de recherche, d’ajout et de suppression.
Nous avons également ajouté une méthodeensureList() qui utilise la méthode lists.ensure()@pnp/sp pour vérifier que nous possédons la liste (et pour la créer, le cas échéant). Il existe plusieurs façons de configurer les ressources, mais cette méthode permet d’illustrer la création d’une liste, d’un champ et d’éléments en utilisant le traitement par lot dans une seule méthode.
En utilisant @pnp/sp, le résultat est que nous écrivons beaucoup moins de code pour gérer les demandes et que nous pouvons nous concentrer sur notre logique métier.
import * as ko from 'knockout';
import styles from './PnPjsExample.module.scss';
import { IPnPjsExampleWebPartProps } from './PnPjsExampleWebPart';
require("tslib");
require("@pnp/logging");
require("@pnp/common");
require("@pnp/odata");
import { sp, List, ListEnsureResult, ItemAddResult, FieldAddResult } from "@pnp/sp";
export interface IPnPjsExampleBindingContext extends IPnPjsExampleWebPartProps {
shouter: KnockoutSubscribable<{}>;
}
/**
* Interface which defines the fields in our list items
*/
export interface OrderListItem {
Id: number;
Title: string;
OrderNumber: string;
}
export default class PnPjsExampleViewModel {
public description: KnockoutObservable<string> = ko.observable('');
public newItemTitle: KnockoutObservable<string> = ko.observable('');
public newItemNumber: KnockoutObservable<string> = ko.observable('');
public items: KnockoutObservableArray<OrderListItem> = ko.observableArray([]);
public labelClass: string = styles.label;
public helloWorldClass: string = styles.pnPjsExample;
public containerClass: string = styles.container;
public rowClass: string = `ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}`;
public buttonClass: string = `ms-Button ${styles.button}`;
constructor(bindings: IPnPjsExampleBindingContext) {
this.description(bindings.description);
// When web part description is updated, change this view model's description.
bindings.shouter.subscribe((value: string) => {
this.description(value);
}, this, 'description');
// call the load the items
this.getItems().then(items => {
this.items(items);
});
}
/**
* Gets the items from the list
*/
private getItems(): Promise<OrderListItem[]> {
return this.ensureList().then(list => {
// here we are using the getAs operator so that our returned value will be typed
return list.items.select("Id", "Title", "OrderNumber").get<OrderListItem[]>();
});
}
/**
* Adds an item to the list
*/
public addItem(): void {
if (this.newItemTitle() !== "" && this.newItemNumber() !== "") {
this.ensureList().then(list => {
// add the new item to the SharePoint list
list.items.add({
Title: this.newItemTitle(),
OrderNumber: this.newItemNumber(),
}).then((iar: ItemAddResult) => {
// add the new item to the display
this.items.push({
Id: iar.data.Id,
OrderNumber: iar.data.OrderNumber,
Title: iar.data.Title,
});
// clear the form
this.newItemTitle("");
this.newItemNumber("");
});
});
}
}
/**
* Deletes an item from the list
*/
public deleteItem(data): void {
if (confirm("Are you sure you want to delete this item?")) {
this.ensureList().then(list => {
list.items.getById(data.Id).delete().then(_ => {
this.items.remove(data);
});
}).catch((e: Error) => {
alert(`There was an error deleting the item ${e.message}`);
});
}
}
/**
* Ensures the list exists and if it creates it adds some default example data
*/
private ensureList(): Promise<List> {
return new Promise<List>((resolve, reject) => {
// use lists.ensure to always have the list available
sp.web.lists.ensure("SPPnPJSExampleList").then((ler: ListEnsureResult) => {
if (ler.created) {
// we created the list on this call so let's add a column
ler.list.fields.addText("OrderNumber").then(_ => {
// and we will also add a few items so we can see some example data
// here we use batching
// create a batch
let batch = sp.web.createBatch();
ler.list.getListItemEntityTypeFullName().then(typeName => {
ler.list.items.inBatch(batch).add({
Title: "Title 1",
OrderNumber: "4826492"
}, typeName);
ler.list.items.inBatch(batch).add({
Title: "Title 2",
OrderNumber: "828475"
}, typeName);
ler.list.items.inBatch(batch).add({
Title: "Title 3",
OrderNumber: "75638923"
}, typeName);
// excute the batched operations
batch.execute().then(_ => {
// all of the items have been added within the batch
resolve(ler.list);
}).catch(e => reject(e));
}).catch(e => reject(e));
}).catch(e => reject(e));
} else {
resolve(ler.list);
}
}).catch(e => reject(e));
});
}
}
Mise à jour du modèle
Enfin, nous devons mettre à jour le modèle pour qu’il corresponde aux fonctionnalités que nous avons ajoutées au ViewModel. Copiez le code suivant dans le fichier PnPjsExample.template.html. Nous avons ajouté une ligne de titre, un répéteur foreach pour la collection d’éléments et un formulaire vous permettant d’ajouter de nouveaux éléments à la liste.
<div data-bind="attr: {class:spPnPjsExampleClass}">
<div data-bind="attr: {class:containerClass}">
<div data-bind="attr: {class:rowClass}">
<div class="ms-Grid-col ms-u-sm12">
<span class="ms-font-xl ms-fontColor-white ms-fontWeight-semibold" data-bind="text: description"></span>
</div>
</div>
<div data-bind="attr: {class:rowClass}">
<div class="ms-Grid-col ms-u-sm6">
<span class="ms-font-l ms-fontColor-white ms-fontWeight-semibold">Title</span>
</div>
<div class="ms-Grid-col ms-u-sm6">
<span class="ms-font-l ms-fontColor-white ms-fontWeight-semibold">Order Number</span>
</div>
</div>
<!-- ko foreach: items -->
<div data-bind="attr: {class:$parent.rowClass}">
<div class="ms-Grid-col ms-u-sm6">
<span class="ms-font-l ms-fontColor-white" data-bind="text: Title"></span>
</div>
<div class="ms-Grid-col ms-u-sm5">
<span class="ms-font-l ms-fontColor-white" data-bind="text: OrderNumber"></span>
</div>
<div class="ms-Grid-col ms-u-sm1">
<i class="ms-Icon ms-Icon--Delete" aria-hidden="true" data-bind="click: $parent.deleteItem.bind($parent, $data)"></i>
</div>
</div>
<!-- /ko -->
<div data-bind="attr: {class:rowClass}">
<div class="ms-Grid-col ms-u-sm12">
<span class="ms-font-l ms-fontColor-white ms-fontWeight-semibold">Add New</span>
</div>
</div>
<div data-bind="attr: {class:rowClass}">
<form data-bind="submit: addItem">
<div class="ms-Grid-col ms-u-sm5">
<input class="ms-TextField-field" placeholder="Title" data-bind='value: newItemTitle, valueUpdate: "afterkeydown"' />
</div>
<div class="ms-Grid-col ms-u-sm5">
<input class="ms-TextField-field" placeholder="Order Number" data-bind='value: newItemNumber, valueUpdate: "afterkeydown"'
/>
</div>
<div class="ms-Grid-col ms-u-sm2">
<button class="ms-Button--default ms-Button" type="submit" data-bind="enable: newItemTitle().length > 0 && newItemNumber().length > 0"><span class="ms-Button-label">Add</span></button>
</div>
</form>
</div>
</div>
</div>
Exécution de l’exemple
Lancez l’exemple et ajoutez le composant WebPart à votre Workbench hébergé par SharePoint (/_layouts/workbench.aspx) pour le voir en action.
gulp serve --nobrowser
Vous pouvez supprimer les éléments existants en sélectionnant l’icône de la Corbeille, ou ajouter de nouveaux éléments en indiquant des valeurs dans les deux champs et en sélectionnant Ajouter.

Étapes suivantes
La bibliothèque @pnp/sp contient une large gamme de fonctionnalités et d’extensibilité. Pour obtenir des exemples, des instructions et des conseils sur l’utilisation et la configuration de la bibliothèque, consultez le Guide du Développeur.
Déploiement de production
Lorsque vous êtes prêt à déployer votre solution et que vous souhaitez créer à l’aide de l’indicateur --ship, vous devez marquer @pnp/sp comme bibliothèque externe dans la configuration. Pour ce faire, vous devez mettre à jour le fichier SPFx config/config.js pour inclure ces lignes dans la section externe :
"externals": {
"tslib": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/tslib/1.9.3/tslib.min.js",
"globalName": "tslib"
},
"@pnp/common": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/pnp-common/1.2.5/common.es5.umd.bundle.min.js",
"globalName": "pnp.common"
},
"@pnp/logging": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/pnp-logging/1.2.5/logging.es5.umd.min.js",
"globalName": "pnp.logging",
"globalDependencies": [
"tslib"
]
},
"@pnp/odata": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/pnp-odata/1.2.5/odata.es5.umd.min.js",
"globalName": "pnp.odata",
"globalDependencies": [
"@pnp/common",
"@pnp/logging",
"tslib"
]
},
"@pnp/sp": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/pnp-sp/1.2.5/sp.es5.umd.min.js",
"globalName": "pnp.sp",
"globalDependencies": [
"@pnp/logging",
"@pnp/common",
"@pnp/odata",
"tslib"
]
}
},
Dans cette configuration, nous utilisons le CDN public, mais l’URL peut être un chemin d’accès interne ou tout autre emplacement que vous souhaiteriez utiliser. Veillez toutefois à mettre à jour le numéro de version dans l’URL pour qu’il corresponde à la version que vous ciblez.
Amélioration de l’exemple de données fictives
Dans l’idéal, l’exemple devrait fonctionner à la fois dans le Workbench local et dans le Workbench hébergé par SharePoint. Pour ce faire, nous devons simuler notre ViewModel et effectuer une mise à jour du code du composant WebPart, comme indiqué dans les sections suivantes.
Ajouter un fichier ViewModel fictif
Ajoutez un nouveau fichier nommé MockSpPnPjsExampleViewModel.ts avec les autres fichiers de composants WebPart. Mettez à jour le contenu de ce fichier à l’aide du code suivant. Il fournit le même ensemble de fonctionnalités et fonctionne dans l’environnement local, mais il ne dépend pas de la disponibilité de SharePoint.
import * as ko from 'knockout';
import styles from './PnPjsExample.module.scss';
import { IPnPjsExampleBindingContext, OrderListItem } from './PnPjsExampleViewModel';
export default class MockPnPjsExampleViewModel {
public description: KnockoutObservable<string> = ko.observable('');
public newItemTitle: KnockoutObservable<string> = ko.observable('');
public newItemNumber: KnockoutObservable<string> = ko.observable('');
public items: KnockoutObservableArray<OrderListItem> = ko.observableArray([]);
public labelClass: string = styles.label;
public helloWorldClass: string = styles.pnPjsExample;
public containerClass: string = styles.container;
public rowClass: string = `ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}`;
public buttonClass: string = `ms-Button ${styles.button}`;
constructor(bindings: IPnPjsExampleBindingContext) {
this.description(bindings.description);
// When web part description is updated, change this view model's description.
bindings.shouter.subscribe((value: string) => {
this.description(value);
}, this, 'description');
// call the load the items
this.getItems().then(items => {
this.items(items);
});
}
/**
* Gets the items from the list
*/
private getItems(): Promise<OrderListItem[]> {
return Promise.resolve([{
Id: 1,
Title: "Mock Item 1",
OrderNumber: "12345"
},
{
Id: 2,
Title: "Mock Item 2",
OrderNumber: "12345"
},
{
Id: 3,
Title: "Mock Item 3",
OrderNumber: "12345"
}]);
}
/**
* Adds an item to the list
*/
public addItem(): void {
if (this.newItemTitle() !== "" && this.newItemNumber() !== "") {
// add the new item to the display
this.items.push({
Id: this.items.length,
OrderNumber: this.newItemNumber(),
Title: this.newItemTitle(),
});
// clear the form
this.newItemTitle("");
this.newItemNumber("");
}
}
/**
* Deletes an item from the list
*/
public deleteItem(data): void {
if (confirm("Are you sure you want to delete this item?")) {
this.items.remove(data);
}
}
}
Mettre à jour le composant WebPart
Enfin, mettez à jour le composant WebPart pour utiliser les données fictives, le cas échéant.
Ouvrez le fichier PnPjsExampleWebPart.ts, puis importez le fichier web ViewModel fictif que vous venez de créer :
import MockSpPnPjsExampleViewModel from './MockPnPjsExampleViewModel';Importez les types
EnvironmentetEnvironmentTypeà utiliser pour détecter le type d’environnement dans lequel est exécuté le composant WebPart :import { Version, Environment, EnvironmentType } from '@microsoft/sp-core-library';Recherchez la méthode
_registerComponentet mettez-la à jour comme indiqué ci-dessous :private _registerComponent(tagName: string): void { if (Environment.type === EnvironmentType.Local) { console.log("here I am.") ko.components.register( tagName, { viewModel: MockSpPnPjsExampleViewModel, template: require('./PnPjsExample.template.html'), synchronous: false } ); } else { ko.components.register( tagName, { viewModel: PnPjsExampleViewModel, template: require('./PnPjsExample.template.html'), synchronous: false } ); } }Tapez gulp serve dans la console pour afficher le Workbench local, qui fonctionne désormais avec les données fictives. (Si le serveur est déjà en cours d’exécution, arrêtez-le en sélectionnant Ctrl+C, puis redémarrez-le) :
gulp serve