Skip to content

Commit cc7d855

Browse files
authored
Merge branch 'next/fluentui' into merge/from-main
2 parents b7c610c + 6317ccf commit cc7d855

File tree

18 files changed

+517
-68
lines changed

18 files changed

+517
-68
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
"source.fixAll.tslint": true
66
},
77
"lit-html.tags": ["mgtHtml"],
8-
"typescript.tsdk": "node_modules/typescript/lib"
8+
"typescript.tsdk": "node_modules/typescript/lib",
9+
"cSpell.words": ["Msal", "spfx"]
910
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@
139139
"tslint": "^6.1.3",
140140
"tslint-config-prettier": "^1.18.0",
141141
"tslint-microsoft-contrib": "^6.2.0",
142-
"typescript": "^4.8.2",
142+
"typescript": "^4.9.4",
143143
"web-component-analyzer": "^1.1.6",
144144
"whatwg-fetch": "^3.6.2"
145145
},
@@ -156,6 +156,7 @@
156156
"embeddedLanguageFormatting": "off"
157157
},
158158
"resolutions": {
159+
"@microsoft/microsoft-graph-client": "3.0.2",
159160
"react": "16.13.1",
160161
"react-dom": "16.13.1",
161162
"responselike": "2.0.0"

packages/mgt-components/README.md

Lines changed: 179 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ The components can be used on their own, but they are at their best when they ar
4141
```html
4242
<script type="module">
4343
import {Providers} from '@microsoft/mgt-element';
44-
import {MsalProvider} from '@microsoft/mgt-msal-provider';
44+
import {Msal2Provider} from '@microsoft/mgt-msal2-provider';
4545
4646
// import the components
4747
import '@microsoft/mgt-components';
@@ -55,6 +55,184 @@ The components can be used on their own, but they are at their best when they ar
5555
<mgt-agenda group-by-day></mgt-agenda>
5656
```
5757

58+
59+
## <a id="disambiguation">Disambiguation</a>
60+
61+
MGT is built using [web components](https://developer.mozilla.org/en-US/docs/Web/Web_Components). Web components use their tag name as a unique key when registering within a browser. Any attempt to register a component using a previously registered tag name results in an error being thrown when calling `CustomElementRegistry.define()`. In scenarios where multiple custom applications can be loaded into a single page this created issues for MGT, most notably in developing solutions using SharePoint Framework.
62+
63+
To mitigate this challenge we built the [`mgt-spfx`](https://github.com/microsoftgraph/microsoft-graph-toolkit/tree/main/packages/mgt-spfx) package. Using `mgt-spfx` developers can centralize the registration of MGT web components across all SPFx solutions deployed on the tenant. By reusing MGT components from a central location web parts from different solutions can be loaded into a single page without throwing errors. When using `mgt-spfx` all MGT based web parts in a SharePoint tenant use the same version of MGT.
64+
65+
To allow developers to build web parts using the latest version of MGT and load them on pages along with web parts that use v2.x of MGT, we've added a new disambiguation feature to MGT. Using this feature developers can specify a unique string to add to the tag name of all MGT web components in their application.
66+
67+
### Usage in standard HTML and JavaScript
68+
69+
The earlier example can be updated to use the disambiguation feature as follows:
70+
71+
```html
72+
<script type="module">
73+
import { Providers, customElementHelper } from '@microsoft/mgt-element';
74+
import { Msal2Provider } from '@microsoft/mgt-msal2-provider';
75+
// configure disambiguation
76+
customElementHelper.withDisambiguation('contoso');
77+
78+
// initialize the auth provider globally
79+
Providers.globalProvider = new Msal2Provider({clientId: 'clientId'});
80+
81+
// import the components using dynamic import to avoid hoisting
82+
import('@microsoft/mgt-components');
83+
</script>
84+
85+
<mgt-contoso-login></mgt-contoso-login>
86+
<mgt-contoso-person person-query="Bill Gates" person-card="hover"></mgt-contoso-person>
87+
<mgt-contoso-agenda group-by-day></mgt-contoso-agenda>
88+
```
89+
90+
> Note: the `import` of `mgt-components` must use a dynamic import to ensure that the disambiguation is applied before the components are imported.
91+
92+
To simplify this pattern when developing SharePoint Framework web parts we have provided helper utilities in the [`mgt-spfx-utils`](https://github.com/microsoftgraph/microsoft-graph-toolkit/tree/main/packages/mgt-spfx-utils) package. Example usages are provided below.
93+
94+
### Usage in a SharePoint web part with no framework
95+
96+
The `importMgtComponentsLibrary` helper function wraps a dynamic import of the `@microsoft/mgt-components` library.
97+
98+
A more complete example is available in the [No Framework Web Part Sample](https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/main/samples/sp-mgt/src/webparts/helloWorld/HelloWorldWebPart.ts).
99+
100+
```ts
101+
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
102+
import { Providers } from '@microsoft/mgt-element';
103+
import { SharePointProvider } from '@microsoft/mgt-sharepoint-provider';
104+
import { customElementHelper } from '@microsoft/mgt-element/dist/es6/components/customElementHelper';
105+
import { importMgtComponentsLibrary } from '@microsoft/mgt-spfx-utils';
106+
107+
export default class MgtWebPart extends BaseClientSideWebPart<Record<string, unknown>> {
108+
private _hasImportedMgtScripts = false;
109+
private _errorMessage = '';
110+
111+
protected onInit(): Promise<void> {
112+
if (!Providers.globalProvider) {
113+
Providers.globalProvider = new SharePointProvider(this.context);
114+
}
115+
customElementHelper.withDisambiguation('foo');
116+
return super.onInit();
117+
}
118+
119+
private onScriptsLoadedSuccessfully() {
120+
this.render();
121+
}
122+
123+
public render(): void {
124+
importMgtComponentsLibrary(this._hasImportedMgtScripts, this.onScriptsLoadedSuccessfully, this.setErrorMessage);
125+
126+
this.domElement.innerHTML = `
127+
<section class="${styles.helloWorld} ${this.context.sdks.microsoftTeams ? styles.teams : ''}">
128+
${this._renderMgtComponents()}
129+
${this._renderErrorMessage()}
130+
</section>`;
131+
}
132+
133+
private _renderMgtComponents(): string {
134+
return this._hasImportedMgtScripts
135+
? '<mgt-foo-login></mgt-foo-login>'
136+
: '';
137+
}
138+
139+
private setErrorMessage(e?: Error): void {
140+
if (e) this.renderError(e);
141+
142+
this._errorMessage = 'An error ocurred loading MGT scripts';
143+
this.render();
144+
}
145+
146+
private _renderErrorMessage(): string {
147+
return this._errorMessage
148+
? `<span>${this._errorMessage}</span>`
149+
: '';
150+
}
151+
}
152+
```
153+
154+
### Usage in a SharePoint web part using React
155+
156+
The `lazyLoadComponent` helper function leverages `React.lazy` and `React.Suspense` to asynchronously load the components which have a direct dependency on `@microsoft/mgt-react` from the top level web part component.
157+
158+
A complete example is available in the [React SharePoint Web Part Sample](https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/main/samples/sp-webpart/src/webparts/mgtDemo/MgtDemoWebPart.ts).
159+
160+
```ts
161+
// [...] trimmed for brevity
162+
import { Providers } from '@microsoft/mgt-element/dist/es6/providers/Providers';
163+
import { customElementHelper } from '@microsoft/mgt-element/dist/es6/components/customElementHelper';
164+
import { SharePointProvider } from '@microsoft/mgt-sharepoint-provider/dist/es6/SharePointProvider';
165+
import { lazyLoadComponent } from '@microsoft/mgt-spfx-utils';
166+
167+
// Async import of a component that uses the @microsoft/mgt-react Components
168+
const MgtDemo = React.lazy(() => import('./components/MgtDemo'));
169+
170+
export interface IMgtDemoWebPartProps {
171+
description: string;
172+
}
173+
// set the disambiguation before initializing any webpart
174+
customElementHelper.withDisambiguation('bar');
175+
176+
export default class MgtDemoWebPart extends BaseClientSideWebPart<IMgtDemoWebPartProps> {
177+
// set the global provider
178+
protected async onInit() {
179+
if (!Providers.globalProvider) {
180+
Providers.globalProvider = new SharePointProvider(this.context);
181+
}
182+
}
183+
184+
public render(): void {
185+
const element = lazyLoadComponent(MgtDemo, { description: this.properties.description });
186+
187+
ReactDom.render(element, this.domElement);
188+
}
189+
190+
// [...] trimmed for brevity
191+
}
192+
```
193+
194+
### Dynamic imports aka Lazy Loading
195+
196+
Using dynamic imports you can load dependencies asynchronously. This pattern allows you to load dependencies only when needed. For example, you may want to load a component only when a user clicks a button. This is a great way to reduce the initial load time of your application. In the context of disambiguation, you need to use this technique because components register themselves in the browser when they are imported.
197+
198+
**Important:** If you import the components before you have applied the disambiguation, the disambiguation will not be applied and using the disambiguated tag name will not work.
199+
200+
When using an `import` statement the import statement is hoisted and executed before any other code in the code block. To use dynamic imports you must use the `import()` function. The `import()` function returns a promise that resolves to the module. You can also use the `then` method to execute code after the module is loaded and the `catch` method to handle any errors if necessary.
201+
202+
#### Example using dynamic imports
203+
204+
```typescript
205+
// static import via a statement
206+
import { Providers, customElementHelper } from '@microsoft/mgt-element';
207+
import { Msal2Provider } from '@microsoft/mgt-msal2-provider';
208+
209+
customElementHelper.withDisambiguation('contoso');
210+
Providers.globalProvider = new Msal2Provider({clientId: 'clientId'});
211+
212+
// dynamic import via a function
213+
import('@microsoft/mgt-components').then(() => {
214+
// code to execute after the module is loaded
215+
document.body.innerHTML = '<mgt-contoso-login></mgt-contoso-login>';
216+
}).catch((e) => {
217+
// handle any errors
218+
});
219+
```
220+
221+
#### Example using static imports
222+
223+
```typescript
224+
// static import via a statement
225+
import { Provider } from '@microsoft/mgt-element';
226+
import { Msal2Provider } from '@microsoft/mgt-msal2-provider';
227+
import '@microsoft/mgt-components';
228+
229+
Providers.globalProvider = new Msal2Provider({clientId: 'clientId'});
230+
231+
document.body.innerHTML = '<mgt-login></mgt-login>';
232+
```
233+
234+
> Note: it is not possible to use disambiguation with static imports.
235+
58236
## Sea also
59237
* [Microsoft Graph Toolkit docs](https://aka.ms/mgt-docs)
60238
* [Microsoft Graph Toolkit repository](https://aka.ms/mgt)

packages/mgt-element/src/components/customElementHelper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class CustomElementHelper {
1717
* @memberof CustomElementHelper
1818
*/
1919
public withDisambiguation(disambiguation: string) {
20-
this._disambiguation = disambiguation;
20+
if (disambiguation && !this._disambiguation) this._disambiguation = disambiguation;
2121
return this;
2222
}
2323

packages/mgt-spfx-utils/README.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Utilities for SharePoint Framework Web Parts using Microsoft Graph Toolkit
2+
3+
[![npm](https://img.shields.io/npm/v/@microsoft/mgt-spfx-utils?style=for-the-badge)](https://www.npmjs.com/package/@microsoft/mgt-spfx-utils)
4+
5+
![SPFx 1.16.1](https://img.shields.io/badge/SPFx-1.16.1-green.svg?style=for-the-badge)
6+
7+
Helper functions to simplify lazy loading of Microsoft Graph Toolkit components when using disambiguated web components in SharePoint Framework web parts. For more information on disambiguation in MGT see https://github.com/microsoftgraph/microsoft-graph-toolkit/tree/main/packages/mgt-components#disambiguation
8+
9+
## Installation
10+
11+
To lazy load Microsoft Graph Toolkit components from the library, add the `@microsoft/mgt-spfx-utils` package as a dependency to your SharePoint Framework project. If you use React, also add the `@microsoft/mgt-react` package:
12+
13+
```bash
14+
npm install @microsoft/mgt-spfx-utils
15+
```
16+
17+
or
18+
19+
```bash
20+
yarn add @microsoft/mgt-spfx-utils
21+
```
22+
23+
or when using React:
24+
25+
```bash
26+
npm install @microsoft/mgt-spfx-utils @microsoft/mgt-react
27+
```
28+
29+
or
30+
31+
```bash
32+
yarn add @microsoft/mgt-spfx-utils @microsoft/mgt-react
33+
```
34+
35+
## Usage
36+
37+
Disambiguation is intended to provide developers with a mechanism to use a specific version of MGT in their solution without encountering collisions with other solutions that may be using MGT. `mgt-spfx` will allow all SPFx solutions in a tenant to use a single shared version, either v2.x or v3.x, of MGT. Currently multiple versions of `mgt-spfx` cannot be used in the same tenant. This is a limitation of the SharePoint Framework.
38+
39+
By disambiguating tag names of Microsoft Graph Toolkit components, you can use your own version of MGT rather than using the centrally deployed `@microsoft/mgt-spfx` package. This allows you to avoid colliding with SharePoint Framework components built by other developers. When disambiguating tag names, MGT is included in the generated SPFx bundle, increasing its size. It is strongly recommended that you use a disambiguation value unique to your organization and solution to avoid collisions with other solutions, e.g. `contoso-hr-extensions`.
40+
41+
> **Important:** Since a given web component tag can only be registered once these approaches **must** be used along with the `customElementHelper.withDisambiguation('foo')` approach as this allows developers to create disambiguated tag names.
42+
43+
### When using no framework web parts
44+
45+
When building SharePoint Framework web parts without a JavaScript framework the `@microsoft/mgt-components` library must be asynchronously loaded after configuring the disambiguation setting. The `importMgtComponentsLibrary` helper function wraps this functionality. Once the `@microsoft/mgt-components` library is loaded you can load components directly in your web part.
46+
47+
Below is a minimal example web part that demonstrates how to use MGT with disambiguation in SharePoint Framework Web parts. A more complete example is available in the [No Framework Web Part Sample](https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/main/samples/sp-mgt/src/webparts/helloWorld/HelloWorldWebPart.ts).
48+
49+
```ts
50+
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
51+
import { Providers } from '@microsoft/mgt-element';
52+
import { SharePointProvider } from '@microsoft/mgt-sharepoint-provider';
53+
import { customElementHelper } from '@microsoft/mgt-element/dist/es6/components/customElementHelper';
54+
import { importMgtComponentsLibrary } from '@microsoft/mgt-spfx-utils';
55+
56+
export default class MgtWebPart extends BaseClientSideWebPart<Record<string, unknown>> {
57+
private _hasImportedMgtScripts = false;
58+
private _errorMessage = '';
59+
60+
protected onInit(): Promise<void> {
61+
if (!Providers.globalProvider) {
62+
Providers.globalProvider = new SharePointProvider(this.context);
63+
}
64+
// Use the solution name to ensure unique tag names
65+
customElementHelper.withDisambiguation('spfx-solution-name');
66+
return super.onInit();
67+
}
68+
69+
private onScriptsLoadedSuccessfully() {
70+
this.render();
71+
}
72+
73+
public render(): void {
74+
importMgtComponentsLibrary(this._hasImportedMgtScripts, this.onScriptsLoadedSuccessfully, this.setErrorMessage);
75+
76+
this.domElement.innerHTML = `
77+
<section class="${styles.helloWorld} ${this.context.sdks.microsoftTeams ? styles.teams : ''}">
78+
${this._renderMgtComponents()}
79+
${this._renderErrorMessage()}
80+
</section>`;
81+
}
82+
83+
private _renderMgtComponents(): string {
84+
return this._hasImportedMgtScripts
85+
? '<mgt-foo-login></mgt-foo-login>'
86+
: '';
87+
}
88+
89+
private setErrorMessage(e?: Error): void {
90+
if (e) this.renderError(e);
91+
92+
this._errorMessage = 'An error ocurred loading MGT scripts';
93+
this.render();
94+
}
95+
96+
private _renderErrorMessage(): string {
97+
return this._errorMessage
98+
? `<span>${this._errorMessage}</span>`
99+
: '';
100+
}
101+
}
102+
```
103+
104+
### When using React to build web parts
105+
106+
When building SharePoint Framework web parts using React any component that imports from the `@microsoft/mgt-react` library must be asynchronously loaded after configuring the disambiguation setting. The `lazyLoadComponent` helper function exists to facilitate using `React.lazy` and `React.Suspense` to lazy load these components from the top level web part.
107+
108+
Below is a minimal example web part that demonstrates how to use MGT with disambiguation in React based SharePoint Framework Web parts. A complete example is available in the [React SharePoint Web Part Sample](https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/main/samples/sp-webpart/src/webparts/mgtDemo/MgtDemoWebPart.ts).
109+
110+
```ts
111+
// [...] trimmed for brevity
112+
import { Providers } from '@microsoft/mgt-element/dist/es6/providers/Providers';
113+
import { customElementHelper } from '@microsoft/mgt-element/dist/es6/components/customElementHelper';
114+
import { SharePointProvider } from '@microsoft/mgt-sharepoint-provider/dist/es6/SharePointProvider';
115+
import { lazyLoadComponent } from '@microsoft/mgt-spfx-utils';
116+
117+
// Async import of component that imports the React Components
118+
const MgtDemo = React.lazy(() => import('./components/MgtDemo'));
119+
120+
export interface IMgtDemoWebPartProps {
121+
description: string;
122+
}
123+
// set the disambiguation before initializing any webpart
124+
// Use the solution name to ensure unique tag names
125+
customElementHelper.withDisambiguation('spfx-solution-name');
126+
127+
export default class MgtDemoWebPart extends BaseClientSideWebPart<IMgtDemoWebPartProps> {
128+
// set the global provider
129+
protected async onInit() {
130+
if (!Providers.globalProvider) {
131+
Providers.globalProvider = new SharePointProvider(this.context);
132+
}
133+
}
134+
135+
public render(): void {
136+
const element = lazyLoadComponent(MgtDemo, { description: this.properties.description });
137+
138+
ReactDom.render(element, this.domElement);
139+
}
140+
141+
// [...] trimmed for brevity
142+
}
143+
```
144+
145+
The underlying components can then use MGT components from the `@microsoft/mgt-react` package as usual. Because of the earlier setup steps the the MGT React components will render html using the disambiguated tag names:
146+
147+
```tsx
148+
import { Person } from '@microsoft/mgt-react';
149+
150+
// [...] trimmed for brevity
151+
152+
export default class MgtReact extends React.Component<IMgtReactProps, {}> {
153+
public render(): React.ReactElement<IMgtReactProps> {
154+
return (
155+
<div className={ styles.mgtReact }>
156+
<Person personQuery="me" />
157+
</div>
158+
);
159+
}
160+
}
161+
```

0 commit comments

Comments
 (0)