diff --git a/pdm_build.py b/pdm_build.py
index 4e0410cc53b07aacf6a321698b41fbcf12d31678..a9a1eb3dc6c526966621cdfbf367f5f0f2c9ef7f 100644
--- a/pdm_build.py
+++ b/pdm_build.py
@@ -1,20 +1,35 @@
 from __future__ import annotations
 
 import json
-from pathlib import Path
 from typing import TYPE_CHECKING
 
+from pynpm import NPMPackage
+
 if TYPE_CHECKING:
     from pdm.backend.hooks.base import Context
 
 
-def pdm_build_update_files(context: Context, files: dict[str, Path]) -> None:
-    pkg_root = Path("schilder2000")
-    manifest_path = pkg_root / "static" / "manifest.json"
-    with (context.root / manifest_path).open() as fp:
+def pdm_build_initialize(context: Context) -> None:
+    if not context.target == "sdist":
+        return
+    context.config.build_config.includes.append("schilder2000/static/manifest.json")
+    if "without-npm" not in context.config_settings:
+        # Assume built webpack is already present otherwise
+        context.ensure_build_dir()
+        pkg = NPMPackage(context.root, commands=["clean-install", "run-script"])
+        pkg.clean_install()
+        pkg.run_script(
+            "build",
+            "--",
+            "--output-path",
+            context.build_dir / "schilder2000" / "static",
+        )
+        manifest_path = context.build_dir / "schilder2000" / "static" / "manifest.json"
+    else:
+        manifest_path = context.root / "schilder2000" / "static" / "manifest.json"
+    with manifest_path.open() as fp:
         manifest: dict[str, str | dict] = json.load(fp)
     for k, v in manifest.items():
         if k == "entrypoints" or not isinstance(v, str):
             continue
-        v = v.removeprefix("/")
-        files[str(pkg_root / v)] = context.root / pkg_root / v
+        context.config.build_config.includes.append("schilder2000" + v)
diff --git a/pyproject.toml b/pyproject.toml
index 904eae0021085da9c024fb21f00e0c9c78e5e00e..bb66b11dbe904565874dbea099a9067ce3d312ce 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -50,10 +50,9 @@ serve = "flask -A schilder2000 run --debug"
 includes = [
     "schilder2000/**/*.py",
     "schilder2000/templates",
-    "schilder2000/static/manifest.json",
     # Referenced files in manifest.json included via pdm_build.py
 ]
 
 [build-system]
-requires = ["pdm-backend"]
+requires = ["pdm-backend", "pynpm"]
 build-backend = "pdm.backend"