Your First Web Part: Real Code, Dissected
Lesson 4: Your First Web Part โ Real Code, Dissected
Scaffolding gives you a working web part in seconds; understanding it takes one careful read. We'll walk the two files that matter most โ the manifest (how SharePoint knows your component) and the web part class (what it renders) โ from a no-framework ("vanilla" TypeScript) scaffold.
The manifest: your component's ID card
Every SPFx component has a .manifest.json. The GUID in it is sacred: it's how the app catalog, the site, and the runtime all refer to this specific web part. Change it and you've created a different component. The scaffolded manifest looks like this:
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "a12b3456-78c9-4def-a012-3456789abcde",
"alias": "AnnouncementsWebPart",
"componentType": "WebPart",
"version": "*",
"manifestVersion": 2,
"supportsThemeVariants": true,
"supportedHosts": ["SharePointWebPart", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"],
"preconfiguredEntries": [{
"groupId": "5c31a052-22c4-4f9a-b1d1-1c3f2c4b8e11",
"group": { "default": "Other" },
"title": { "default": "Announcements" },
"description": { "default": "Shows recent announcements from a list" },
"officeFabricIconFontName": "Megaphone",
"properties": { "title": "Announcements" }
}]
}
idโ unique component identity (generate once, never reuse across components).supportedHostsโ where authors can add it (SharePoint pages, Teams, full-page).preconfiguredEntriesโ the picker entry: toolbox group, title, icon, and the default properties a new instance starts with.
The web part class
A no-framework web part is one TypeScript class extending BaseClientSideWebPart, typed with your properties interface. The scaffolded version (trimmed to the essentials):
import { Version } from '@microsoft/sp-core-library';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { escape } from '@microsoft/sp-lodash-subset';
export interface IAnnouncementsWebPartProps {
title: string; // configurable via the property pane
}
export default class AnnouncementsWebPart
extends BaseClientSideWebPart<IAnnouncementsWebPartProps> {
public render(): void {
// this.domElement is the empty div SharePoint gave this web part
this.domElement.innerHTML = `
<div class="awp">
<h2>${escape(this.properties.title)}</h2>
<p>Hello from my first web part!</p>
</div>`;
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [{
header: { description: 'Announcements settings' },
groups: [{
groupName: 'Display',
groupFields: [
PropertyPaneTextField('title', { label: 'Heading' })
]
}]
}]
};
}
}
Reading the important lines
render()is the contract: whenever the web part must (re)draw, SPFx calls it โ on load, after a property-pane change, on canvas resize. It's your job to write the DOM (or hand off to a framework like React).this.domElementโ the container the page gave you. ReassigninginnerHTMLon each render is fine at this scale; more complex web parts build a tree and diff it.escape()โ always escape user-supplied strings before injecting HTML. Property values are content, not code โ treat them as hostile.- Property pane โ
getPropertyPaneConfiguration()describes the edit panel on the right.PropertyPaneTextField('title', ...)binds the Title field to thetitleproperty; type a new value andrender()runs again automatically.
components/ folder. The "no framework" option keeps you close to the metal while you learn; nothing stops you adding React to the same project later.
render() re-fires. If you attach event listeners or start background work in render() unconditionally, you'll stack duplicates on every property change. Guard with a flag, or better, use lifecycle hooks like onInit() for one-time setup.
๐ง Knowledge Check
1. What happens if you change the id GUID in a web part's manifest after publishing?
2. When does SharePoint call your web part's render() method?
3. Why should property values be passed through escape() (or rendered as text) before going into innerHTML?