Skip to content

Commit db8aff1

Browse files
wobbaVesaJuvonen
authored andcommitted
Added guidance on dynamic loading of components in SPFx (SharePoint#2425)
* Added guidance on bundle sizes in SPFx * Added TOC entry * Fixed image reference * Fixed path slashes * Remove toc entry * Add toc entry * Minor edits * Toc update to reflect new title
1 parent b730dd6 commit db8aff1

File tree

3 files changed

+188
-0
lines changed

3 files changed

+188
-0
lines changed

docs/images/dynamic-bundling.png

97.1 KB
Loading

docs/spfx/bundle-size-guidance.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
---
2+
title: SharePoint Framework guidance on dynamic loading of packages
3+
description: Techniques to optimize the run-time size and execution speed of your SharePoint Framework component.
4+
ms.date: 08/27/2018
5+
ms.prod: sharepoint
6+
---
7+
8+
# SharePoint Framework guidance on dynamic loading of packages
9+
10+
When building SharePoint Framework components it is common to reference third party libraries such as [Office UI Fabric React](https://www.npmjs.com/package/office-ui-fabric-react) for layout controls or [moment](https://www.npmjs.com/package/moment) for time handling. Each of these libraries will add to the byte size of the bundled JavaScript file for the component. As an example, moment adds ~250KB, a considerable amount.
11+
12+
*Note: A _bundle_ is one or more JavaScript fragments and style sheets, put into the same `.js` file. When you package an SPFx solution, all your code is usually bundled into one `.js` file. Splitting a bundle, is the operation of generating multiple `.js` files instead of one, so that they can be loaded individually.*
13+
14+
Depending on the component, these third party libraries may or may not be used in all parts of the components lifecycle. Maybe they are only used in edit mode or in the property pane, or maybe they are needed once a user clicks a link or button in the component.
15+
16+
If the SPFx component is more like a single page application (SPA), optimizing the size might not be important, but if it's a web part added to a page or even used multiple times on a page, like a search web part, then reducing the amount of script loaded and the amount of script being executed on a page matters for load time of the page. Especially for mobile devices.
17+
18+
To improve on the speed a SPFx component takes to load on a page it is possible to take advantage of splitting the bundle into multiple JavaScript parts, and dynamically load these individually bundled packages as needed.
19+
20+
## Splitting up multiple web parts to be loaded individually
21+
22+
In an SPFx project with multiple web parts or extensions a typical structure in the `config/config.json` file might look like this.
23+
24+
```json
25+
{
26+
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
27+
"version": "2.0",
28+
"bundles": {
29+
"my-spfx": {
30+
"components": [
31+
{
32+
"entrypoint": "./lib/webparts/part1/part1.js",
33+
"manifest": "./src/webparts/part1/part1.manifest.json"
34+
},
35+
{
36+
"entrypoint": "./lib/webparts/part2/part2.js",
37+
"manifest": "./src/webparts/part2/part2.manifest.json"
38+
}
39+
]
40+
}
41+
},
42+
"externals": {},
43+
"localizedResources": {}
44+
}
45+
```
46+
47+
In the above configuration both web parts are included in the same JavaScript file or bundle. This means that if a user adds one of the web parts to a page, both are actually loaded. For scenarios where the web parts almost always coincide on the same page, this is fine, as it reduced load time. If however the web parts are used separately, then it is better to have them split into two files to reduce what is loaded on the page.
48+
49+
This is easily achieved by changing `config/config.json` so that each web part is bundled as a separate JavaScript file.
50+
51+
```json
52+
{
53+
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
54+
"version": "2.0",
55+
"bundles": {
56+
"my-spfx-1": {
57+
"components": [
58+
{
59+
"entrypoint": "./lib/webparts/part1/part1.js",
60+
"manifest": "./src/webparts/part1/part1.manifest.json"
61+
}
62+
]
63+
},
64+
"my-spfx-2": {
65+
"components": [
66+
{
67+
"entrypoint": "./lib/webparts/part2/part2.js",
68+
"manifest": "./src/webparts/part2/part2.manifest.json"
69+
}
70+
]
71+
}
72+
},
73+
"externals": {},
74+
"localizedResources": {}
75+
}
76+
```
77+
78+
## Analyzing your bundle
79+
80+
In order to get an overview of where to start optimize, see the article [Optimize SharePoint Framework builds for production](https://github.com/SharePoint/sp-dev-docs/blob/master/docs/spfx/toolchain/optimize-builds-for-production.md) which illustrates how you can get a visual map of which part of your code or third party library takes up space in your bundle.
81+
82+
## Loading third party components dynamically
83+
84+
When a SharePoint Framework project is packaged up it, it is using webpack behind the scenes to create the bundle(s). One of the features of webpack is the ability to dynamically import parts of an application. Taking advantage of this in SharePoint Framework requires only a small refactoring.
85+
86+
**Normal import**
87+
88+
In the code below the moment library will be included into the packages JavaScript file, always being loaded even though the `GetTime` function is never used.
89+
90+
```typescript
91+
// Import moment as a dependency
92+
import * as moment from moment
93+
.
94+
.
95+
export default class MyClass {
96+
public GetTime(dateString:string){
97+
return moment(dateString).format("LL");
98+
}
99+
}
100+
```
101+
102+
**Dynamic import**
103+
104+
However, in the code below the moment library is loaded asynchronous when the `GetTime` function is called, thus reducing the initial load and execution time.
105+
106+
```typescript
107+
export default class MyClass {
108+
public GetTime(dateString:string){
109+
const moment = await import(
110+
/* webpackChunkName: 'my-moment' */
111+
'moment'
112+
);
113+
return moment(dateString).format("LL");
114+
}
115+
}
116+
```
117+
118+
*Note: If your `config/tsconfig.json` file has `"module": "commonjs"` and not `"module": "esnext"` you need the following work-around in order for the dynamic library to be split out of the bundle.*
119+
120+
```typescript
121+
declare var System: any;
122+
123+
export default class MyClass {
124+
public GetTime(dateString:string){
125+
const moment = await System.import(
126+
/* webpackChunkName: 'my-moment' */
127+
'moment'
128+
);
129+
return moment(dateString).format("LL");
130+
}
131+
}
132+
```
133+
134+
### Verify code splitting and dynamic import
135+
136+
To verify that dynamic import is happening, open up the `dist` folder after a build and look for the dynamically loaded part as a separate JavaScript file. In the below image the moment library is split out. Also look at the webpack analyzer output referenced above, which will show that your code is split into multiple bundles/chunks.
137+
138+
![Multiple bundles](../images/dynamic-bundling.png)
139+
140+
Not every single file or library has to be dynamically imported. Instead choose an abstraction where everything needed for a certain scenario or capability is bundled together.
141+
142+
For example create a file `MyStuff.ts` which references a number of libraries, and then dynamically load `MyStuff`. In this case, `third-party` and `left-party` will be bundled into `my-stuff.js`.
143+
144+
```typescript
145+
// MyStuff.ts
146+
147+
import { Something } from 'third-party;'
148+
import * as Foo from 'left-party';
149+
150+
// Other file
151+
await import(
152+
/* webpackChunkName: 'my-stuff' */
153+
'./MyStuff'
154+
);
155+
```
156+
157+
## Special property pane dynamic loading
158+
159+
In your main web part file create a function named `loadPropertyPaneResources.` This function will execute before the property pane of a web part is shown. This allows dynamic loading of resources needed for the property pane only.
160+
161+
* Create a new file (for example, HelloWorldWebPartPropertyPaneStuff.ts)
162+
* Move all property pane related code to that file
163+
* Create the following method in the main web part class
164+
165+
```typescript
166+
protected loadPropertyPaneResources(): Promise<void> {
167+
return import(
168+
/* webpackChunkName: 'HelloWorldWebPartPropertyPaneStuff' */
169+
'./HelloWorldWebPartPropertyPaneStuff'
170+
).then(component => {
171+
this._propertyPaneHelper = new component.HelloWorldWebPartPropertyPaneStuff(this);
172+
});
173+
}
174+
```
175+
176+
- Rebuild
177+
178+
## Summary
179+
180+
When building SPFx solutions consisting of several web parts or extensions, or if building an SPFx web part with a lot of code and which uses third party libraries, consider analyzing the resulting bundle size, and use the strategies defined above to split the code into multiple smaller bundles, where each one is loaded only when needed - thus reducing the time it takes for an end-user to load and execute a modern page.
181+
182+
## See also
183+
184+
- [An even better bundle optimization method for SPFx using webpack dynamic imports (techmikael.com)](https://www.techmikael.com/2018/08/an-even-better-bundle-optimization.html)
185+
- [Sample code from Modern Script Editor web part](https://github.com/SharePoint/sp-dev-fx-webparts/blob/dev/samples/react-script-editor/src/webparts/scriptEditor/ScriptEditorWebPart.ts#L39)
186+
- [Access to script root of a web part and dynamic loading of resources (github)](https://github.com/SharePoint/sp-dev-docs/issues/2388)

docs/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@
113113
href: spfx/team-based-development-on-sharepoint-framework.md
114114
- name: Governance considerations
115115
href: spfx/web-parts/guidance/governance-considerations.md
116+
- name: Guidance on dynamic loading of packages
117+
href: spfx/bundle-size-guidance.md
116118
- name: Connecting components
117119
href: spfx/dynamic-data.md
118120
- name: External libraries

0 commit comments

Comments
 (0)