Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Части этой статьи, относящиеся к надстройкам SharePoint, являются устаревшими рекомендациями. Приведенное ниже прекращение поддержки не означает прекращение поддержки других API или платформ, обсуждаемых здесь.
Важно!
Поддержка надстроек SharePoint в SharePoint Online завершилась 2 апреля 2026 г. Надстройки, размещенные в SharePoint и у поставщика, больше не работают в SharePoint Online, включая облака для государственных организаций. Руководство по разработке, развертыванию и использованию надстроек сохранено для справки за прошлые годы и для применимых сценариев SharePoint Server, а не для новых решений SharePoint Online.
Надстройки SharePoint Server, развернутые через локальный каталог приложений, не удаляются этим изменением. Приобретение надстроек SharePoint из общедоступного магазина для SharePoint Server завершилось 2 апреля 2026 г.
Это снятие с учета не прекращает поддержку REST API SHAREPOINT, CSOM, JSOM или SharePoint Framework (SPFx). Для SharePoint Online используйте SPFx для расширений пользовательского интерфейса и Microsoft Entra ID для проверки подлинности во внешних приложениях. См. объявление о прекращении поддержки и руководство по переносу надстроек.
Много лет назад ASP.NET предоставили нам рендеринг управления пользовательским интерфейсом на стороне сервера, и это было хорошо. Однако для такой визуализации на стороне сервера требуется код с полным доверием. Теперь, когда мы перешли на SharePoint и Office 365, код полного доверия больше не поддерживается. Это означает, что рендеринг элементов управления пользовательского интерфейса на стороне сервера больше не будет работать.
Тем не менее, компаниям по-прежнему нужны настраиваемые функции пользовательского интерфейса для их веб-сайтов и приложений. Это означает, что пользовательские функции пользовательского интерфейса должны быть перенесены со стороны сервера на клиентскую.
JavaScript на стороне клиента — это лучший вариант для рендеринга элементов управления пользовательским интерфейсом.
Шаблоны JavaScript
Поскольку клиентский JavaScript — это путь, каковы лучшие способы реализации клиентского JavaScript? С чего начать?
Есть несколько вариантов:
| Вариант | Описание |
|---|---|
| Внедрение JavaScript | Site.UserCustomActions или Web.UserCustomActions позволяют включать скрипт прямо в разметку страницы. Используется в шаблоне загрузчика, который обсуждается ниже |
| Дисплейные шаблоны | Применяется к представлениям и поиску. Вам не нужно развертывать какой-либо код приложения или поставщика. Это просто файл JavaScript, который можно отправить (например) в библиотеку стилей для настройки представлений. С помощью JavaScript можно создать любое представление, необходимое |
| Размещенные Add-Ins SharePoint | Использует JSOM для обратной связи с хост-сайтом или веб-сайтом надстроек. Он предоставляет доступ к веб-прокси для междоменных звонков |
| Поставщик размещенных Add-Ins | Позволяет создавать сложные приложения на основе различных стеков технологий, сохраняя при этом безопасную интеграцию с SharePoint |
| JSLink | Позволяет загружать один или несколько файлов JavaScript во многие веб-части и представления OOTB |
| Веб-часть редактора скриптов | Добавляйте сценарии напрямую или загружайте их через теги сценариев с разметкой для создания сложных одностраничных приложений, полностью размещенных на сайте SharePoint |
Не думайте, что вы заперты в этом выборе, если вы чувствуете, что другой вариант был бы лучше для вашей ситуации.
Производительность JavaScript
На каждом этапе процесса разработки важно помнить о производительности. Вот несколько вещей, которые имеют большое значение для производительности JavaScript:
| Вариант | Описание |
|---|---|
| Уменьшите количество запросов | Меньше запросов означает меньше круговых путей к серверу, что снижает задержку. |
| Извлекайте только нужные данные | Сократите объем данных, передаваемых по проводу. Также снижает нагрузку на сервер. |
| Обеспечение хорошей загрузки страницы | Поддерживайте отзывчивость пользовательского интерфейса для пользователя. Например, обновите меню на странице перед тем, как начать загрузку 100+ записей. |
| По возможности используйте асинхронные вызовы и шаблоны | Опрос является более тяжелой нагрузкой на производительность, чем использование асинхронного вызова или обратного вызова. |
| Кэширование — ключ к успеху | Кэширование еще больше снижает нагрузку на сервер, обеспечивая немедленное повышение производительности. |
| Приготовьтесь к большему количеству просмотров страниц, чем вы могли себе представить | Лендинг с большим количеством данных — это нормально, если у вас всего несколько обращений. Но если вы получаете тысячи ударов, это действительно может повлиять на производительность. |
Что делает мой код
Для производительности важно знать, что ваш код делает в любой момент. Это позволяет определить способы повышения эффективности. Ниже приведены несколько хороших способов сделать это.
Уменьшите количество запросов
Всегда отправляйте как можно меньше наименьших запросов. Каждый удаленный запрос снижает нагрузку на производительность сервера и клиента. А выполнение более мелких запросов еще больше снижает нагрузку на производительность.
Есть несколько способов уменьшить количество запросов и уменьшить их размер.
- Ограничение количества файлов JavaScript в рабочей среде. Разделение файлов JavaScript хорошо подходит для разработки, но не очень хорошо для рабочей среды. Объедините файлы JavaScript в один файл JavaScript или как можно меньше файлов JavaScript.
- Уменьшайте размеры файлов. Сверните рабочие файлы JavaScript, удалив разрывы строк, пробелы и комментарии. Существует несколько программ и веб-сайтов для минификации JavaScript, которые вы можете использовать, чтобы значительно уменьшить размер файлов JavaScript.
- Используйте кэширование файлов браузера, чтобы уменьшить количество запросов. Обновленный шаблон загрузчика ниже - хороший способ развить эту идею.
Извлекайте только нужные данные
При запросе данных не забывайте фокусировать запросы на том, что вам действительно нужно. Например, скачивание всей статьи для получения заголовка немного снизит производительность.
- Используйте фильтрацию, выбор и ограничения сервера, чтобы свести к минимуму трафик по проводной сети.
- Не запрашивайте все статьи, если вам нужны только первые пять, как другой пример.
- Не просите всю сумку недвижимости, если вам нужна только одна недвижимость. В одном примере сценарию требовалось только одно свойство, но запрашивался весь контейнер свойств, который оказался 800 КБ. Также помните, что размер объекта может меняться со временем, поэтому то, что сейчас составляет всего несколько килобайт, может стать мегабайтом на более поздних этапах жизненного цикла продукта.
Не запрашивайте данные, которые будут удалены без использования
Если вы получаете больше данных, чем фактически используете, рассматривайте это как возможность лучше отфильтровать исходный запрос.
- Запросите только нужные поля, такие как имя и адрес, а не всю запись.
- Делайте конкретные, преднамеренные запросы на фильтрацию. Например, если вы хотите перечислить доступные статьи, получите Title, PublishingDate и Author. Остальные поля не следует указывать в запросе.
Хорошее взаимодействие с пользователем
Прерывистые, несогласованные пользовательские интерфейсы влияют не только на производительность, но и на восприятие производительности. Пишите код таким образом, чтобы обеспечить удобное взаимодействие.
- Используйте счетчик, чтобы показать, что что-то загружается или требует времени.
- Изучите порядок выполнения кода и сформируйте его для наилучшего взаимодействия с пользователем. Например, если вы собираетесь получать много данных с сервера и изменить пользовательский интерфейс, скрывая меню, сначала скройте меню. Это предотвратит ступенчатое взаимодействие с пользовательским интерфейсом.
Все асинхронно
Каждое действие с кодом в браузере следует считать асинхронным. Файлы загружаются в определенном порядке, нужно дождаться загрузки DOM, а запросы в SharePoint будут выполняться с разной скоростью.
- Узнайте о том, как ваш код работает вовремя.
- Использование событий и обратных вызовов вместо опросов.
- Используйте отложенные фрагменты. В jQuery они называются отложенными объектами. Похожие концепции есть в Q, WinJS и ES6.
- Используйте асинхронный вызов вместо неасинхронного.
- Используйте asynchronous в любое время, когда может возникнуть задержка:
- Во время AJAX-запроса.
- Во время любых значимых манипуляций с DOM.
Асинхронные шаблоны улучшают производительность и скорость реагирования, а также позволяют эффективно связывать зависимые действия в цепочки.
Кэширование на стороне клиента
Кэширование на стороне клиента — одно из наиболее часто упускаемых улучшений производительности, которые можно добавить в код.
Существует три различных места кэширования данных:
| Вариант | Описание |
|---|---|
| Хранилище сеанса | Сохраняет данные в клиенте в виде пары "ключ-значение". Это хранилище для каждого сеанса, которое всегда хранится в виде строк. JSON.stringify() преобразует ваши объекты JavaScript в строки, что помогает хранить объекты. |
| Локальное хранилище | Сохраняет данные в клиенте в виде пары "ключ-значение". Этот параметр сохраняется в течение нескольких сеансов и всегда сохраняется в виде строк. JSON.stringify() преобразует ваши объекты JavaScript в строки, что помогает хранить объекты. |
| Локальная база данных | Хранит реляционные данные в клиенте. Часто использует SQL-Lite в качестве ядра СУБД. Хранилище локальной базы данных не всегда доступно во всех браузерах — проверьте поддержку целевого браузера |
При кэшировании учитывайте доступные вам ограничения хранилища и актуальность данных.
- Если хранилище подходит к концу, имеет смысл удалить более старые или менее важные кэшированные данные.
- Разные типы данных могут устаревать быстрее, чем другие. Список новостных статей может устареть за пять-десять минут, но имя профиля пользователя часто можно безопасно кэшировать на 24 часа и более.
Локальное и сеансовое хранилище не имеет встроенного срока действия, но файлы cookie имеют. Сохраненные данные можно привязать к файлу cookie, чтобы увеличить срок действия локального хранилища и хранилища сеанса. Вы также можете создать оболочку хранилища, которая включает дату окончания срока действия и ее проверку в коде.
Цена популярности
Как часто просматривается ваша страница? В классическом сценарии корпоративная домашняя страница устанавливается в качестве страницы запуска для всех браузеров в организации. Затем вы внезапно получаете гораздо больше трафика, чем вы когда-либо могли себе представить. Каждый байт контента внезапно увеличивается в производительности сервера и пропускной способности, потребляемой вашей домашней страницей.
Решение: переходите на домашнюю страницу налегке и ссылайтесь на другие материалы.
Панели мониторинга с большим объемом данных также являются кандидатом на роль приложения, размещенного у поставщика, которое может масштабироваться независимо.
Шаблон загрузчика
Целью шаблона загрузчика является предоставление способа встраивания неизвестного количества удаленных сценариев в сайт без необходимости обновления сайта. Обновления можно выполнять в удаленной сети CDN, и они обновят все сайты.
Шаблон загрузчика создает URL-адрес с меткой даты и времени в конце, чтобы файл не кэшировался. Он устанавливает jQuery как зависимость от файла загрузчика, а затем выполняет функцию в загрузчике. Это гарантирует, что ваш пользовательский JavaScript будет загружен после завершения загрузки jQuery.
PnP-dev\Samples\Core.JavaScript\Core.JavaScript.Embedder\Program.cs:
static void Main(string[] args)
{
ContextManager.WithContext((context) =>
// this is the script block that will be embedded into the page
// in practice this can be done during provisioning of the site/web
// make sure to include ';' at end to play nice with page embedding
// using the script on demand feature built into SharePoint we load jQuery, then our remote loader(pnp-loader.js or pnp-loader-cached.js) file using a dependency
var script = @"(function (loaderFile, nocache) {
var url = loaderFile + ((nocache) ? '?' + encodeURIComponent((new Date()).getTime()) : '');
SP.SOD.registerSod('pnp-jquery.js', 'https://localhost:44324/js/jquery.js');
SP.SOD.registerSod('pnp-loader.js', url);
SP.SOD.registerSodDep('pnp-loader.js', 'pnp-jquery.js');
SP.SOD.executeFunc('pnp-loader.js', null, function() {});
})('https://localhost:44324/pnp-loader.js', true);";
// this version of the script along with pnp-loaderMDS.js (or pnp-loaderMDS-cached.js) handles pages where the minimum download strategy is active
var script2 = @"ExecuteOrDelayUntilBodyLoaded(function () {
var url = 'https://localhost:44324/js/pnp-loaderMDS.js?' + encodeURIComponent((new Date()).getTime());
SP.SOD.registerSod('pnp-jquery.js', 'https://localhost:44324/js/jquery.js');
SP.SOD.registerSod('pnp-loader.js', url);
SP.SOD.registerSodDep('pnp-loader.js', 'pnp-jquery.js');
SP.SOD.executeFunc('pnp-loader.js', null, function () {
if (typeof pnpLoadFiles === 'undefined') {
RegisterModuleInit('https://localhost:44324/js/pnp-loaderMDS.js', pnpLoadFiles);
} else {
pnpLoadFiles();
}
});
});";
// load the collection of existing links
var links = context.Site.RootWeb.UserCustomActions;
context.Load(links, ls => ls.Include(l => l.Title));
context.ExecuteQueryRetry();
// this block handles deleting previous test custom actions
var doDelete = false;
foreach (var link in links.ToArray().Where(l => l.Title.Equals("MyTestCustomAction", StringComparison.OrdinalIgnoreCase)))
{
link.DeleteObject();
doDelete = true;
}
if (doDelete)
{
context.ExecuteQueryRetry();
}
// now we embed our script into the user custom action
var newLink = context.Site.RootWeb.UserCustomActions.Add();
newLink.Title = "MyTestCustomAction";
newLink.Description = "Doing some testing.";
newLink.ScriptBlock = script2;
newLink.Location = "ScriptLink";
newLink.Update();
context.ExecuteQueryRetry();
});
}
Это SP.SOD.registerSodDep('pnp-loader.js', 'pnp-jquery.js'); настраивает зависимость и SP.SOD.executeFunc('pnp-loader.js', null, function() {}); заставляет jQuery полностью загружаться перед загрузкой пользовательского JavaScript.
И newLink.ScriptBlock = script2; являются ключевыми частями newLink.Location = "ScriptLink"; добавления этого в действие пользователя для клиента.
Затем из файла pnp-loader.js загружается список файлов JavaScript с обещанием, которое можно выполнить при загрузке каждого файла.
PnP-dev\Samples\Core.JavaScript\Core.JavaScript.CDN\js\pnp-loader.js:
(function () {
var urlbase = 'https://localhost:44324';
var files = [
'/js/pnp-settings.js',
'/js/pnp-core.js',
'/js/pnp-clientcache.js',
'/js/pnp-config.js',
'/js/pnp-logging.js',
'/js/pnp-devdashboard.js',
'/js/pnp-uimods.js'
];
// create a promise
var promise = $.Deferred();
// this function will be used to recursively load all the files
var engine = function () {
// maintain context
var self = this;
// get the next file to load
var file = self.files.shift();
var fullPath = urlbase + file;
// load the remote script file
$.getScript(fullPath).done(function () {
if (self.files.length > 0) {
engine.call(self);
}
else {
self.promise.resolve();
}
}).fail(self.promise.reject);
};
// create our "this" we will apply to the engine function
var ctx = {
files: files,
promise: promise
};
// call the engine with our context
engine.call(ctx);
// give back the promise
return promise.promise();
})().done(function () {
/* all scripts are loaded and I could take actions here */
}).fail(function () {
/* something failed, take some action here if needed */
});
Файл pnp-loader.js не кэшируется, что хорошо подходит для среды разработки. Файл pnp-loader-cached.js заменяет $.getScript функцию $.ajax функцией, которая позволяет кэшировать файлы в браузере и лучше подходит для производства.
Из PnP-dev\Samples\Core.JavaScript\Core.JavaScript.CDN\js\pnp-loader.js
// load the remote script file
$.ajax({
type: 'GET',
url: fullPath,
cache: true,
dataType: 'script'
}).done(function () {
if (self.files.length > 0) {
engine.call(self);
}
else {
self.promise.resolve();
}
}).fail(self.promise.reject);
Этот шаблон упрощает развертывание и обновление сайтов. Это особенно полезно при развертывании или обновлении в тысячах семейств веб-сайтов.
Кэширование текущего пользователя
Если сведения о пользователе уже кэшированы, функция получает данные из кэша сеанса. Если сведения о пользователе не хранятся в кэше, мы получаем нужные нам сведения о пользователе и сохраняем их в кэше.
При этом также используется Deferred (версия обещания в jQuery). Если мы получаем данные из кэша или с сервера, Deferred разрешается. В случае ошибки отложенное отклонение отклоняется.
Из PnP-dev\Samples\Core.JavaScript\Core.JavaScript.CDN\js\pnp-core.js:
getCurrentUserInfo: function (ctx) {
var self = this;
if (self._currentUserInfoPromise == null) {
self._currentUserInfoPromise = $.Deferred(function (def) {
var cachingTest = $pnp.session !== 'undefined' && $pnp.session.enabled;
// if we have the caching module loaded
if (cachingTest) {
var userInfo = $pnp.session.get(self._currentUserInfoCacheKey);
if (userInfo !== null) {
self._currentUserInfo = userInfo;
def.resolveWith(ctx || self._currentUserInfo, [self._currentUserInfo]);
return;
}
}
// send the request and allow caching
$.ajax({
method: 'GET',
url: '/_api/SP.UserProfiles.PeopleManager/GetMyProperties?$select=AccountName,DisplayName,Title',
headers: { "Accept": "application/json; odata=verbose" },
cache: true
}).done(function (response) {
// we also parse and add some custom properties as an example
self._currentUserInfo = $.extend(response.d,
{
ParsedLoginName: $pnp.core.getUserIdFromLogin(response.d.AccountName)
});
if (cachingTest) {
$pnp.session.add(self._currentUserInfoCacheKey, self._currentUserInfo);
}
def.resolveWith(ctx || self._currentUserInfo, [self._currentUserInfo]);
}).fail(function (jqXHR, textStatus, errorThrown) {
console.error('[PNP]=>[Fatal Error] Could not load current user data data from /_api/SP.UserProfiles.PeopleManager/GetMyProperties. status: ' + textStatus + ', error: ' + errorThrown);
def.rejectWith(ctx || null);
});
});
}
return this._currentUserInfoPromise.promise();
}
}
Шаблон кэширования с использованием асинхронного и отложенного
Другой шаблон кэширования можно найти в pnp-clientcache.js storageTest, который взят из modernizer storageTest. Он содержит функции add, get, remove и getOrAdd, которые будут возвращать кэшированные данные, если они есть в кэше, или извлекать данные с сервера и добавлять их в кэш, если их нет в кэше, что избавляет от написания повторяющегося кода в вызывающей функции. get использует JSON.parse для проверки срока действия, так как срок действия не является функцией в локальном хранилище. _createPersistable сохраняет объект JavaScript в локальном кэше хранилища.
Из PnP-dev\Samples\Core.JavaScript\Core.JavaScript.CDN\js\pnp-clientcache.js:
// adds the client cache capability
caching: {
// determine if we have local storage once
enabled: storageTest(),
add: function (/*string*/ key, /*object*/ value, /*datetime*/ expiration) {
if (this.enabled) {
localStorage.setItem(key, this._createPersistable(value, expiration));
}
},
// gets an item from the cache, checking the expiration and removing the object if it is expired
get: function (/*string*/ key) {
if (!this.enabled) {
return null;
}
var o = localStorage.getItem(key);
if (o == null) {
return o;
}
var persistable = JSON.parse(o);
if (new Date(persistable.expiration) <= new Date()) {
this.remove(key);
o = null;
} else {
o = persistable.value;
}
return o;
},
// removes an item from local storage by key
remove: function (/*string*/ key) {
if (this.enabled) {
localStorage.removeItem(key);
}
},
// gets an item from the cache or adds it using the supplied getter function
getOrAdd: function (/*string*/ key, /*function*/ getter) {
if (!this.enabled) {
return getter();
}
if (!$.isFunction(getter)) {
throw 'Function expected for parameter "getter".';
}
var o = this.get(key);
if (o == null) {
o = getter();
this.add(key, o);
}
return o;
},
// creates the persisted object wrapper using the value and the expiration, setting the default expiration if none is applied
_createPersistable: function (/*object*/ o, /*datetime*/ expiration) {
if (typeof expiration === 'undefined') {
expiration = $pnp.core.dateAdd(new Date(), 'minute', $pnp.settings.localStorageDefaultTimeoutMinutes);
}
return JSON.stringify({
value: o,
expiration: expiration
});
}
},
Для более сложного использования асинхронных и отложенных инструментов вы можете обратиться к панели мониторинга разработчика в pnp-clientcache.js
Ресурсы
См. также
- Рекомендации по производительности в модели надстроек SharePoint
- Complete basic operations using JavaScript library code in SharePoint 2013
- Примеры кода для рендеринга на стороне клиента (ссылка JS)
- Внедрение JavaScript (настройка пользовательского интерфейса сайта SharePoint с помощью JavaScript)
- Настройки поиска для SharePoint
- Рецепт надстройки SharePoint — настраиваемый тип поля (с помощью отрисовки на стороне клиента)