Skip to content

Add SPA build caching with folder hash detection#72

Merged
PieterjanDeClippel merged 7 commits into
masterfrom
feature/spa-build-caching
Feb 1, 2026
Merged

Add SPA build caching with folder hash detection#72
PieterjanDeClippel merged 7 commits into
masterfrom
feature/spa-build-caching

Conversation

@PieterjanDeClippel

@PieterjanDeClippel PieterjanDeClippel commented Feb 1, 2026

Copy link
Copy Markdown
Member

Summary

  • Implements intelligent build caching for Angular/React SPA projects using MintPlayer.FolderHasher.Targets
  • Computes SHA-256 hash of SPA folder contents before running npm build commands
  • Skips unnecessary npm run build / npm run build:ssr when hash unchanged
  • Significantly reduces build times (90%+) when SPA source hasn't changed

Features

  • Hash-based caching: Compares current folder hash with stored hash to determine if rebuild is needed
  • .hasherignore support: Exclude files/folders from hash calculation (similar to .gitignore syntax)
  • Auto-created defaults: Creates .hasherignore with sensible defaults if missing
  • Configurable: New MSBuild properties EnableSpaBuildCaching, SpaHashFilePath, ForceSpaBuild, CreateDefaultHasherIgnore
  • Transitive support: Works for all projects referencing the package directly or indirectly
  • Fail-safe: Missing or corrupted hash files trigger a full rebuild

New MSBuild Properties

Property Default Description
EnableSpaBuildCaching true Enable/disable the caching feature
SpaHashFilePath $(IntermediateOutputPath)spa-folder.hash Location to store the hash file
ForceSpaBuild false Force rebuild even if hash unchanged
CreateDefaultHasherIgnore true Auto-create .hasherignore with default patterns if missing

Build Command Selection

BuildServerSideRenderer Build Command
true (default) npm run build:ssr:production
false npm run build -- --configuration production

Default Ignore Patterns

The auto-created .hasherignore excludes:

  • Dependencies: node_modules/
  • Build outputs: dist/, dist-server/, build/, out/
  • Framework caches: .angular/, .cache/, .npm/
  • Test outputs: coverage/, test-results/, .nyc_output/
  • IDE files: .idea/, .vscode/
  • Editor temp files: *.swp, *.swo, *~
  • OS files: .DS_Store, Thumbs.db

Testing Instructions

1. First publish (should run full build and create hash file):

cd Demo/Prerendering/Demo.Web
dotnet publish -c Release

Look for messages like:

  • First SPA build detected (hash: xxx). Will build.
  • npm install and npm run build:ssr:production should execute

2. Second publish (should skip build):

dotnet publish -c Release

Look for:

  • SPA source unchanged (hash: xxx). Skipping npm build.
  • Using cached SPA build output from ClientApp\dist

3. Modify a source file and publish (should rebuild):

# Make a small change to any .ts file in ClientApp/src
dotnet publish -c Release

Look for:

  • SPA source changed. Will rebuild. (Previous: xxx, Current: yyy)

4. Force rebuild:

dotnet publish -c Release -p:ForceSpaBuild=true

5. Check hash file location:

cat obj/Release/net10.0/spa-folder.hash

Test plan

  • First publish creates hash file and runs npm build
  • Second publish (no changes) skips npm build and uses cached output
  • Changing a .ts file triggers rebuild
  • ForceSpaBuild=true forces rebuild even when hash unchanged
  • Deleting dist/ folder triggers rebuild
  • Works with transitive package references
  • .hasherignore is auto-created with default patterns

🤖 Generated with Claude Code

PieterjanDeClippel and others added 7 commits February 1, 2026 20:12
Implements intelligent build caching for Angular/React SPA projects to skip
unnecessary npm builds when the SPA folder contents haven't changed.

Features:
- Computes SHA-256 hash of SPA folder using MintPlayer.FolderHasher.Targets
- Stores hash in obj/spa-folder.hash for comparison
- Skips npm build when hash unchanged and dist/ exists
- Supports .hasherignore files for excluding files from hash calculation
- New MSBuild properties: EnableSpaBuildCaching, SpaHashFilePath, ForceSpaBuild
- Works transitively for all projects referencing the package

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move the npm build conditional logic inside PublishRunWebpack target
instead of splitting into two separate targets. This ensures the
ResolvedFileToPublish block always runs when EnableSpaBuilder is true.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The double-dash (--) characters inside XML comments confuse parsers
since -- is part of the comment delimiter syntax.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Simplify _RunNpmBuild logic: default to true (fail-safe), only set
  false when explicitly skipping
- Add warning when SpaRoot folder doesn't exist
- Prevent npm commands from running if SpaRoot doesn't exist
- Don't write empty hash to file if hash computation failed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Auto-create .hasherignore file with sensible defaults if missing
- Default patterns exclude: node_modules/, .angular/, dist/, dist-server/,
  .cache/, coverage/
- Add CreateDefaultHasherIgnore property to control auto-creation
- Add SpaHashDefaultIgnorePattern item group for customizing defaults
- Add example .hasherignore file in Demo project
- Update README documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add additional patterns to SpaHashDefaultIgnorePattern:
- Build outputs: build/, out/
- Framework caches: .npm/
- Test outputs: test-results/, .nyc_output/
- IDE files: .idea/, .vscode/
- Editor temp files: *.swp, *.swo, *~
- OS files: .DS_Store, Thumbs.db

Update Demo project .hasherignore and README documentation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Empty Include="" is not allowed in MSBuild ItemGroup elements.
Removed the blank line item from _HasherIgnoreLines.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@PieterjanDeClippel PieterjanDeClippel merged commit 7e84d90 into master Feb 1, 2026
3 of 4 checks passed
@PieterjanDeClippel PieterjanDeClippel deleted the feature/spa-build-caching branch February 1, 2026 20:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant