diff --git a/README.md b/README.md
index 5804c63..581e9bb 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ but not important for functionality, change them to suit your own needs):
 }
 ```
 
-Now we can npm install the rest:
+Now we can `npm install` (or `yarn add`) the rest:
 
 ```
 $ cd </path/to/your/lektor/project>/webpack
diff --git a/lektor_webpack_support.py b/lektor_webpack_support.py
index 11e35f8..cc05e09 100644
--- a/lektor_webpack_support.py
+++ b/lektor_webpack_support.py
@@ -2,7 +2,7 @@ import os
 
 from lektor.pluginsystem import Plugin
 from lektor.reporter import reporter
-from lektor.utils import portable_popen
+from lektor.utils import locate_executable, portable_popen
 
 
 class WebpackSupportPlugin(Plugin):
@@ -23,16 +23,24 @@ class WebpackSupportPlugin(Plugin):
             args.append('--watch')
         return portable_popen(args, cwd=webpack_root)
 
-    def npm_install(self):
-        reporter.report_generic('Running npm install')
+    def install_node_dependencies(self):
         webpack_root = os.path.join(self.env.root_path, 'webpack')
-        portable_popen(['npm', 'install'], cwd=webpack_root).wait()
+
+        # Use yarn over npm if it's availabe and there is a yarn lockfile
+        has_yarn_lockfile = os.path.exists(os.path.join(
+            webpack_root, 'yarn.lock'))
+        pkg_manager = 'npm'
+        if locate_executable('yarn') is not None and has_yarn_lockfile:
+            pkg_manager = 'yarn'
+
+        reporter.report_generic('Running {} install'.format(pkg_manager))
+        portable_popen([pkg_manager, 'install'], cwd=webpack_root).wait()
 
     def on_server_spawn(self, **extra):
         extra_flags = extra.get("extra_flags") or extra.get("build_flags") or {}
         if not self.is_enabled(extra_flags):
             return
-        self.npm_install()
+        self.install_node_dependencies()
         reporter.report_generic('Spawning webpack watcher')
         self.webpack_process = self.run_webpack(watch=True)
 
@@ -48,7 +56,7 @@ class WebpackSupportPlugin(Plugin):
         if not self.is_enabled(extra_flags) \
            or self.webpack_process is not None:
             return
-        self.npm_install()
+        self.install_node_dependencies()
         reporter.report_generic('Starting webpack build')
         self.run_webpack().wait()
         reporter.report_generic('Webpack build finished')

Yarn support
Would you be open to a PR adding support for using [`yarn`](https://yarnpkg.com/) in place of `npm install`? It both runs faster and uses a lockfile (for package version pinning). The implementation I have in mind would [rename and] update `npm_install()` to check for a `yarn` executable and a `.../webpack/yarn.lock` file, using `yarn` in that case or defaulting to current behavior.
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index 96cecd3..764fa0a 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -66,3 +66,25 @@ def test_server_stop(plugin, mocker):
     plugin.webpack_process = mocker.Mock()
     plugin.on_server_stop()
     plugin.webpack_process.kill.assert_called_with()
+
+
+def test_yarn_support(plugin, env, mocker):
+    mock_popen = mocker.patch('lektor_webpack_support.portable_popen')
+
+    mock_path_exists = mocker.patch('os.path.exists')
+    mock_path_exists.return_value = True
+
+    mock_locate_executable = mocker.patch('lektor_webpack_support.locate_executable')
+    mock_locate_executable.return_value = '/usr/local/bin/yarn'
+
+    env_path = py.path.local(env.root_path)
+    plugin.install_node_dependencies()
+
+    assert mock_path_exists.called
+    assert mock_path_exists.call_args[0][0].endswith('yarn.lock')
+    assert mock_locate_executable.called
+    mock_locate_executable.assert_any_call('yarn')
+    mock_popen.assert_any_call(
+        ['yarn', 'install'],
+        cwd=env_path / 'webpack'
+    )

pip install -e .[dev]
parse_log_pytest
pytest
lektor pytest pytest-cov pytest-mock
apt-get update apt-get install -y gcc
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
babel==2.17.0
blinker==1.9.0
certifi==2025.1.31
charset-normalizer==3.4.1
click==8.1.8
coverage==7.8.0
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
ExifRead==3.0.0
filetype==1.2.0
Flask==2.3.3
idna==3.10
importlib_metadata==8.6.1
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
inifile==0.4.1
itsdangerous==2.2.0
Jinja2==3.1.6
Lektor==3.3.12
-e git+https://github.com/lektor/lektor-webpack-support.git@ab6134744dec30980dc41e1cd0f7736759cc26a0#egg=lektor_webpack_support
MarkupSafe==3.0.2
mistune==0.8.4
packaging @ file:///croot/packaging_1734472117206/work
pluggy @ file:///croot/pluggy_1733169602837/work
pytest @ file:///croot/pytest_1738938843180/work
pytest-cov==6.0.0
pytest-mock==3.14.0
python-slugify==8.0.4
pytz==2025.2
requests==2.32.3
text-unidecode==1.3
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
urllib3==2.3.0
watchdog==6.0.0
Werkzeug==2.3.8
zipp==3.21.0

name: lektor-webpack-support
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py39h06a4308_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - babel==2.17.0
      - blinker==1.9.0
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - click==8.1.8
      - coverage==7.8.0
      - exifread==3.0.0
      - filetype==1.2.0
      - flask==2.3.3
      - idna==3.10
      - importlib-metadata==8.6.1
      - inifile==0.4.1
      - itsdangerous==2.2.0
      - jinja2==3.1.6
      - lektor==3.3.12
      - markupsafe==3.0.2
      - mistune==0.8.4
      - pytest-cov==6.0.0
      - pytest-mock==3.14.0
      - python-slugify==8.0.4
      - pytz==2025.2
      - requests==2.32.3
      - text-unidecode==1.3
      - urllib3==2.3.0
      - watchdog==6.0.0
      - werkzeug==2.3.8
      - zipp==3.21.0
prefix: /opt/conda/envs/lektor-webpack-support

tests/test_plugin.py::test_yarn_support
tests/test_plugin.py::test_disabled_by_default tests/test_plugin.py::test_enabled_with_webpack_flag tests/test_plugin.py::test_basic tests/test_plugin.py::test_watcher tests/test_plugin.py::test_watcher_build_flags tests/test_plugin.py::test_watcher_plugin_disabled tests/test_plugin.py::test_server_stop
lektor_webpack_support.py README.md
lektor_webpack_support.py README.md