Skip to content

Building and packaging

What each command produces — build outputs (dist/, release/) on this page, catalog upload and CDN on deployment.md. See Microsoft docs: Package and deploy SPFx solutions and Host SPFx from Office 365 CDN.

For daily flow see getting-started.md; for ZIP layout see reference/FORMATS.md.

Commands

CommandOutputNotes
rspfx builddist/ + release/manifests/*.manifest.json + release/assets/*--no-minify, --sourcemap; bundler config optional
rspfx packagesharepoint/solution/<name>.sppkgRuns build first; --no-build to skip
rspfx analyzeConsole table + .rspfx/analyze.htmlModule counts via bundler or .rspfx/stats.json
rspfx cleanRemoves dist/, release/, .rspfx, etc.
rspfx doctorChecks env, config, ports, certsExit 1 on fail — use in CI

All commands read config/* and src/*/*.manifest.json.

With a bundler config (vite.config.ts, rsbuild.config.ts, or rspack.config.ts) it is loaded via jiti; without it the CLI synthesizes config from manifests and runs Vite, Rsbuild, or Rspack directly.

See migrating-from-gulp-heft.md#same-manifest-for-heftgulp-and-rspfx.

Tip: No manual @microsoft/sp-* install for most web parts — externalized, SharePoint resolves built-in copies.

Tip: bun run build (or pnpm / npm / yarn run build) and rspfx build are the same codepath — use either.

Project layout

Default is the official SPFx layout.

Customize via paths in plugin options:

OptionDefaultControls
paths.webpartsDirsrc/webpartsWeb part discovery
paths.configDirconfigconfig.json + serve.json
paths.srcDirsrclocalizedResources rewriting

Bundle name = web part folder name by default — that name becomes loaderConfig.entryModuleId.

No bundler config needed — zero-config synthesis handles standard layouts.

What rspfx build does

  1. Reads project — bundles, externals, localizedResources from config.json; scans src/webparts/* when bundles is absent.

  2. Loads framework preset — JSX/TS transform, resolve, plugins.

  3. Compiles (Vite by default, Rsbuild or Rspack if configured) — one AMD bundle per web part (define('<id>_<version>', …)), chunks, SCSS, assets; @microsoft/sp-* never bundled; CSS inlined, no .css in .sppkg — see styling.md.

  4. Writes manifests to release/manifests/version from package.json, entryModuleId = bundle name, scriptResources per external, internalModuleBaseUrls from cdnBasePath.

  5. Copies assets to release/assets/.

rspfx build alone does not create a .sppkg — that is rspfx package.

Tip: rspfx build --no-minify --sourcemap for debuggable staging builds; CI should use default minified output.

What rspfx package does

Creates a DEFLATE zip at config/package-solution.json paths.zippedPackage (default sharepoint/solution/<name>.sppkg).

Content is driven by that file:

  • includeClientSideAssets: true — bundles embedded under ClientSideAssets/, manifests rewritten to HTTPS://SPCLIENTSIDEASSETLIBRARY/ — see Host SPFx from Office 365 CDN.
  • includeClientSideAssets: false or cdnBasePath in write-manifests.json — external CDN URL in manifests; upload release/assets/* there.
  • webApiPermissionRequestsRequestedWebApiPermission in AppManifest.xml.
  • Extensions → Extension_<id>.xml with Location; libraries → Library_<id>.xml.
  • Auto-detects teams/ (icons → ClientSideAssets/teams/) and sharepoint/Resources*.resx.

Full ZIP entry list and ordering: reference/FORMATS.md.

Deployment steps (catalog, CDN, permissions, Teams): deployment.md.

rspfx deploy automates upload with a token; without it prints manual steps.

Comparison vs official

AreaOfficialRSPFx
Build outputdist/ + temp/ (Heft)dist/ + release/manifests/ + release/assets/
Bundle formatAMD define('<id>_<version>', …)Same — byte-compatible (see compatibility.md)
CSSExtracted or inlined per rigInlined — no .css files in .sppkg
Manifestsrelease/ via write-manifestsSame semantics, cdnBasePathSPCLIENTSIDEASSETLIBRARY
Packagegulp package-solutionrspfx package — same ZIP layout

CI

npm ci
yaml
- run: rspfx doctor
- run: rspfx package
- upload: sharepoint/solution/*.sppkg

Size and speed

Defaults: minify: true, splitChunks: false, sourcemap: false.

splitChunks: false is correct for SPFx — one AMD bundle per web part.

true saves duplicate code via chunk.*.js but adds a request.

Example: PnP Modern Search (4 web parts, 178 files, Fluent UI) → ~2 s on a laptop.

Troubleshooting

SymptomFix
UNRESOLVED_EXTERNALRemove that externals key or add the package
sp-* code in bundleRemove resolve alias that collides with sp-*
Can't resolve 'XxxWebPartStrings'localizedResources missing or not lib/.../{locale}.js shaped
@import 'pkg:...' failsRewritten by rspfx migrate for sass-loader <16.5
.html import failsRebuild CLI — asset/source handles it
Bundle 404 in workbench (https://localhost:4321/dist/...)Bundle name must match entryModuleId — default: folder name — see project-structure.md
Package goes to solution/paths.zippedPackage is authoritative

Released under the MIT License.