当在 SharePoint 框架上生成客户端 Web 部件时,可以从使用现有的 JavaScript 库生成功能强大的解决方案中受益。 但是,有一些应该加以考虑的注意事项,需要注意以确保 Web 部件不会对正在使用它们的 SharePoint 页面的性能造成负面影响。
以打包方式引用现有库
引用 SharePoint Framework 客户端 Web 部件中的现有 JavaScript 库最常见的方法是将其作为项目中的包进行安装。
以 Angular 为例,为了在客户端 Web 部件中使用 Angular,将首先使用 npm 进行安装:
npm install angular --save
为了将 Angular 与 TypeScript 一起使用,将使用 npm 安装类型定义:
npm install @types/angular --save-dev
在 Web 部件中通过使用
import
语句引用 Angular:import { Version } from '@microsoft/sp-core-library'; import { BaseClientSideWebPart, IPropertyPaneConfiguration, PropertyPaneTextField } from '@microsoft/sp-webpart-base'; import { escape } from '@microsoft/sp-lodash-subset'; import styles from './HelloWorld.module.scss'; import * as strings from 'helloWorldStrings'; import { IHelloWorldWebPartProps } from './IHelloWorldWebPartProps'; import * as angular from 'angular'; export default class HelloWorldWebPart extends BaseClientSideWebPart<IHelloWorldWebPartProps> { public render(): void { this.domElement.innerHTML = ` <div class="${styles.helloWorld}"> <!-- omitted for brevity --> </div>`; angular.module('helloworld', []); angular.bootstrap(this.domElement, ['helloworld']); } // omitted for brevity }
捆绑 Web 部件资源
SharePoint 框架使用基于开源工具(例如 gulp 和 Webpack)的生成工具链。 当生成 SharePoint 框架项目时,这些生成工具在被称为捆绑的过程中将所有引用资源自动组合成单个 JavaScript 文件。
捆绑可提供很多优势。 首先,Web 部件所需的全部资源都在单个 JavaScript 文件中可用。 这简化了部署,因为 Web 部件由单个文件组成,并且在部署过程中不可能会漏掉依赖项。
由于 Web 部件使用不同的资源,必须按正确顺序进行加载。 Webpack 在生成过程中生成的 Web 部件包为你管理不同资源的加载,包括解决这些资源之间的任何依赖项。
捆绑 Web 部件还对最终用户有好处:通常情况下与大量的小文件相比,下载单个较大文件的速度会更快。 通过将大量较小文件组合成一个较大的捆绑,Web 部件在页面上的加载速度会更快。
但是,将现有 JavaScript 库与 SharePoint 框架客户端 Web 部件捆绑并非没有缺点。
当捆绑 SharePoint 框架中的现有 JavaScript 框架时,所有引用脚本都包括在已生成的捆绑文件中。 按照 Angular 示例,包括 Angular 在内的已优化 Web 部件捆绑超过 170 KB。
如果你将另一个 Web 部件添加到同样使用 Angular 的项目中,并且你构建了该项目,则会得到两个捆绑文件,每个 Web 部件一个捆绑文件,每个文件大于 170 KB。
如果在页面添加这些 Web 部件,每个用户将会多次下载 Angular,一次与页面上的每个 Web 部件一起下载。 此方法效率低下,并会延长页面加载时间。
引用现有库作为外部资源
针对 SharePoint 框架客户端 Web 部件中的现有库,更好的利用方法是将其作为外部资源进行引用。 这样一来,有关 Web 部件中包含的特定脚本的唯一信息是该脚本的 URL。 添加到页面时,Web 部件自动尝试从指定的 URL 加载所需的全部资源。
引用 SharePoint Framework 中的现有 JavaScript 库非常简单,不需要对代码进行任何特定的更改。 因为此库在运行时已从指定的 URL 上加载,不需要再作为项目中的包进行安装。
以 Angular 为例,为了在客户端 Web 部件中将其作为外部资源进行引用,首先使用 npm 安装 TypeScript 类型定义:
npm install @types/angular --save-dev
在 config/config.json 文件中,向 externals
属性添加以下项:
"angular": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js",
"globalName": "angular"
}
完整的 config/config.json 文件看起来类似于:
{
"entries": [
{
"entry": "./lib/webparts/helloWorld/HelloWorldWebPart.js",
"manifest": "./src/webparts/helloWorld/HelloWorldWebPart.manifest.json",
"outputPath": "./dist/hello-world.bundle.js"
}
],
"externals": {
"angular": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js",
"globalName": "angular"
}
},
"localizedResources": {
"helloWorldStrings": "webparts/helloWorld/loc/{locale}.js"
}
}
在 Web 部件中引用 Angular:
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { escape } from '@microsoft/sp-lodash-subset';
import styles from './HelloWorld.module.scss';
import * as strings from 'helloWorldStrings';
import { IHelloWorldWebPartProps } from './IHelloWorldWebPartProps';
import * as angular from 'angular';
export default class HelloWorldWebPart extends BaseClientSideWebPart<IHelloWorldWebPartProps> {
public render(): void {
this.domElement.innerHTML = `
<div class="${styles.helloWorld}">
<!-- omitted for brevity -->
</div>`;
angular.module('helloworld', []);
angular.bootstrap(this.domElement, ['helloworld']);
}
// omitted for brevity
}
如果现在生成项目并看一眼生成的捆绑文件的大小,你会注意到它只有 6 KB。
如果将另一个 Web 部件添加到也使用 Angular 的项目,并重新生成该项目,这两个捆绑文件将均为 6 KB。
不能假定刚刚保存超过 300 KB。 这两个 Web 部件仍然需要 Angular,并在用户第一次访问放置 Web 部件之一的页面时对其进行加载。
即使将两个 Angular Web 部件添加到页面上,SharePoint Framework 仍会只下载 Angular 一次。
引用现有 JavaScript 库作为外部资源的真正好处的情况是,组织有一个集中的位置存储所有常用的脚本,或者你使用 CDN。 在这种情况下,特定的 JavaScript 库很有可能已经存在于用户的浏览器缓存中。 因此,仅需加载 Web 部件捆绑,该捆绑会使页面加载速度显著加快。
上一示例虽然显示了如何从 CDN 加载 Angular,但未要求使用公用 CDN。 可以在配置中指向任何位置:从公用 CDN、私人托管存储库到 SharePoint 文档库。 只要使用 Web 部件的用户能访问指定的 URL,Web 部件就会按预期工作。
CDN 在全球范围内优化以用于资源快速交付。 从公用 CDN 引用脚本的另一个优点是,很有可能相同的脚本已应用到用户过去访问的一些其他网站上。 由于该脚本已存在于本地浏览器的缓存中,不需要再特地为 Web 部件进行下载,该脚本将使具有 Web 部件的页面以更快的速度加载。
某些组织不允许从公司网络访问公用 CDN。 在这种情况下,使用常用 JavaScript 框架的私人托管存储位置是很好的备用选择。 因为组织托管库,它还能控制缓存标头,该标头可以帮助进一步优化资源性能。
JavaScript 库格式
不同的 JavaScript 库以不同的方式生成和打包。 一些库作为模块打包,其他库是在全局范围内运行的普通脚本(这些脚本通常作为非模块脚本引用)。 从 URL 加载 JavaScript 库时,在 SharePoint 框架项目中注册外部脚本的方式取决于该脚本的格式。 存在例如 AMD、UMD 或 CommonJS 等多个模块格式,但你只需了解某个特殊脚本是否是一个模块。
将打包脚本注册为模块时,唯一必须指定的是下载该特殊脚本的 URL。 对其他脚本的依赖项在脚本模块构造内处理。
非模块脚本至少需要下载该脚本的 URL 和在全局范围中注册该脚本所用的变量名称。 如果非模块脚本依赖于其他脚本,它们可以被列为依赖项。 为说明这一点,让我们看看几个示例。
Angular v1.x 是一个非模块脚本。 在 SharePoint Framework 项目中通过指定 URL 和全局变量名称将其作为外部资源注册,在注册时应包含以下内容:
"angular": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js",
"globalName": "angular"
}
属性中指定的 globalName
名称与脚本使用的名称相对应,这一点很重要。 这样一来可以将自身正确地展示给可能依赖它的其他脚本。
ngOfficeUIFabric 是 Office UI 结构的 Angular 指令,它是依赖于 Angular 的 UMD 模块。 Angular 上的依赖项已在模块中处理,因此为进行注册你只需指定其 URL:
"ng-office-ui-fabric": "https://cdnjs.cloudflare.com/ajax/libs/ngOfficeUiFabric/0.12.3/ngOfficeUiFabric.js"
jQuery 是一个 AMD 脚本。 若要注册,可以仅使用:
"jquery": "https://code.jquery.com/jquery-2.2.4.js"
现在假设需要使用具有 jQuery 插件的 jQuery,该插件本身作为非模块脚本分发。
如果使用以下代码注册了两个脚本,则加载 Web 部件很可能会导致错误。 这两个脚本可能并行加载,并且该插件可能不能利用 jQuery 注册自身。
"jquery": "https://code.jquery.com/jquery-2.2.4.js",
"simpleWeather": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/jquery.simpleWeather/3.1.0/jquery.simpleWeather.min.js",
"globalName": "jQuery"
}
如前文所述,SharePoint 框架允许指定非模块插件的依赖项。这些依赖项使用 globalDependencies
属性进行指定:
"jquery": "https://code.jquery.com/jquery-2.2.4.js",
"simpleWeather": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/jquery.simpleWeather/3.1.0/jquery.simpleWeather.min.js",
"globalName": "jQuery",
"globalDependencies": [ "jquery" ]
}
globalDependencies
属性指定的每个依赖项都必须指向 config/config.json 文件的 externals
部分中的另一个依赖项。
如果尝试立即生成项目,则会出现另一个错误,这次错误指出不能指定非模块脚本的依赖项。
若要解决此问题,需要注册 jQuery 作为非模块脚本:
"jquery": {
"path": "https://code.jquery.com/jquery-2.1.1.min.js",
"globalName": "jQuery"
},
"simpleWeather": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/jquery.simpleWeather/3.1.0/jquery.simpleWeather.min.js",
"globalName": "jQuery",
"globalDependencies": [ "jquery" ]
}
这样一来,可以指定 simpleWeather 脚本应在 jQuery 后进行加载,并且该 jQuery 应该在全局可用变量 jQuery
下可用,这也是 simpleWeather jQuery 插件注册自身时所需要的。
注意
请注意用于注册 jQuery 的条目如何使用 jquery 作为外部资源名称,但 jQuery 作为全局变量名称。 外部资源名称是在代码的 import
语句中使用的名称。 这也是必须与 TypeScript 类型声明匹配的名称。 使用 globalName
属性指定的全局变量名称是其他脚本(如基于库构建的插件)已知的名称。 虽然对于某些库,这些名称可能相同,但这不是必需的,并且应仔细检查是否使用了正确的名称,以避免出现任何问题。
很难手动确定要加载的脚本是模块脚本还是非模块脚本。 如果你尝试加载的是缩小的脚本,则情况尤其如此。 如果你的脚本托管于可公开访问的 URL 中,则可以使用免费的 Rencore SharePoint 框架脚本检查工具为你确定脚本的类型。 另外,此工具可以让你知道你从中加载脚本的托管位置是否正确配置。
非模块脚本注意事项
过去开发的许多 JavaScript 库和脚本作为非模块脚本分发。 虽然 SharePoint Framework 支持加载非模块脚本,但也应在任何可能的情况下尽量避免使用它们。
非模块脚本在页面的全局范围内注册:一个 Web 部件加载的脚本可用于页面上的所有其他 Web 部件。 如果拥有两个使用 jQuery 不同版本的 Web 部件同时作为非模块脚本加载,最后加载的 Web 部件会覆盖以前注册的所有 jQuery 版本。
可以想象,这可能会导致不可预知的结果并很难调试仅在某些情况下才会发生的问题,例如通过在页面上使用不同 版本的 jQuery 仅使用其他 Web 部件时和当它们仅以特定顺序进行加载时。 此模块体系结构通过隔离脚本并防止它们相互影响解决了此问题。
应考虑绑定的时机
将现有 JavaScript 库捆绑到 Web 部件中可能会导致大型 Web 部件文件,并可能导致使用该 Web 部件的页面性能不佳。 虽然使用 Web 部件通常应避免绑定 JavaScript 库,但有些应用场景中绑定大有益处。
如果你正在构建一个适用于每个 Intranet 的标准解决方案,那么将所有资源与 Web 部件捆绑在一起可以帮助你确保 Web 部件按预期工作。 因为不会提前知道解决方案的安装位置,包括在 Web 部件捆绑文件中的所有依赖项将允许其正常工作,即使组织不允许从 CDN 或其他外部位置下载资源。
如果解决方案包含许多相互共享某些功能的 Web 部件,将共享功能构建为一个独立库并在所有 Web 部件中将其作为外部资源引用会更好。 这样一来,用户只需要下载公用库一次并在所有 Web 部件对其重用。
摘要
当在 SharePoint 框架上生成客户端 Web 部件时,可以从使用现有的 JavaScript 库生成功能强大的解决方案中受益。 SharePoint 框架允许将这些库和 Web 部件捆绑在一起,或者作为外部资源进行加载。 虽然从 URL 加载现有的库通常是推荐的方法,但有些情况下捆绑可能是有益的,并且你必须仔细评估你的需求以选择最能满足你需求的方法。