diff --git a/.gitignore b/.gitignore
index 53b18701b93a3cc68fbab2d05c32ffbde6f8bdda..3cefb865aa6751e04aa3550e84cfe158cd8b1acd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,6 @@ djblets/htdocs/*
 Djblets.egg-info
 docs/djblets/coderef/python
 node_modules
-package.json
 
 .coverage
 .noseids
diff --git a/contrib/internal/build-npm-deps.py b/contrib/internal/build-npm-deps.py
new file mode 100755
index 0000000000000000000000000000000000000000..ed2d8e855f4cfe45ebda25bfdcaadc71ea921ab0
--- /dev/null
+++ b/contrib/internal/build-npm-deps.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+"""Write node.js dependencies to djblets/dependencies.py.
+
+Version Added:
+    4.0
+"""
+
+import json
+import os
+from typing import Dict, TextIO
+
+
+MARKER_START = '# Auto-generated Node.js dependencies {\n'
+MARKER_END = '# } Auto-generated Node.js dependencies\n'
+
+
+LINT_DEP_NAMES = {
+    'eslint',
+    '@beanbag/eslint-plugin',
+}
+
+
+def _write_deps(
+    *,
+    fp: TextIO,
+    doc: str,
+    name: str,
+    deps: Dict[str, str],
+) -> None:
+    """Write dependencies to the file.
+
+    This will write the Python code to list each dependency and the
+    matching version.
+
+    Args:
+        fp (io.TextIO):
+            The file to write to.
+
+        doc (str):
+            The doc comment contents.
+
+        name (str):
+            The name of the variable to write to.
+
+        deps (dict):
+            The dependencies to write.
+    """
+    fp.write(
+        '#: %(doc)s\n'
+        '%(name)s: Dict[str, str] = {\n'
+        '%(deps)s\n'
+        '}\n'
+        '\n'
+        % {
+            'doc': doc,
+            'name': name,
+            'deps': '\n'.join(
+                f"    '{dep_name}': '{dep_ver}',"
+                for dep_name, dep_ver in deps.items()
+            ),
+        })
+
+
+def main() -> None:
+    """Embed package.json into djblets/dependencies.py."""
+    scripts_dir = os.path.abspath(os.path.dirname(__file__))
+    top_dir = os.path.abspath(os.path.join(scripts_dir, '..', '..'))
+    djblets_dir = os.path.join(top_dir, 'djblets')
+    package_json_path = os.path.join(djblets_dir, 'package.json')
+    deps_py_path = os.path.join(djblets_dir, 'dependencies.py')
+
+    # Load the dependencies and organize them.
+    with open(package_json_path, 'r') as fp:
+        package_json = json.load(fp)
+
+    frontend_deps: Dict[str, str] = {}
+    lint_deps: Dict[str, str] = {}
+
+    for dep_name, dep_ver in sorted(package_json['dependencies'].items()):
+        if dep_name in LINT_DEP_NAMES:
+            lint_deps[dep_name] = dep_ver
+        else:
+            frontend_deps[dep_name] = dep_ver
+
+    # Parse out the existing dependencies.py and grab everything outside the
+    # markers.
+    new_lines_pre: str = ''
+    new_lines_post: str = ''
+
+    with open(deps_py_path, 'r') as fp:
+        data = fp.read()
+
+        i = data.find(MARKER_START)
+        assert i != -1
+
+        j = data.find(MARKER_END, i)
+        assert j != -1
+
+        new_lines_pre = data[:i]
+        new_lines_post = data[j + len(MARKER_END) + 1:]
+
+    # Write out the new dependencies.py.
+    with open(deps_py_path, 'w') as fp:
+        fp.write(new_lines_pre)
+        fp.write(f'{MARKER_START}\n\n')
+
+        _write_deps(
+            fp=fp,
+            doc='Dependencies required for static media building.',
+            name='frontend_buildkit_npm_dependencies',
+            deps=frontend_deps)
+
+        _write_deps(
+            fp=fp,
+            doc='Dependencies required for static media linting.',
+            name='lint_npm_dependencies',
+            deps=lint_deps)
+
+        fp.write(f'\n{MARKER_END}\n')
+        fp.write(new_lines_post)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/djblets/dependencies.py b/djblets/dependencies.py
index 8612c91983136f9d90dc985ffa607d49d16e2f36..a283addfb8af496ad23425ba4e94351d1499c87c 100644
--- a/djblets/dependencies.py
+++ b/djblets/dependencies.py
@@ -12,6 +12,7 @@ you're going to make use of data from this file, code defensively.
 #       installed.
 
 import os
+from typing import Dict
 
 
 ###########################################################################
@@ -58,32 +59,37 @@ package_dependencies = {
 
 ###########################################################################
 # JavaScript dependencies
+#
+# These are auto-generated when running `npm install --save ...` (if the
+# package is not already in node_modules).
+#
+# To re-generate manually, run: `./contrib/internal/build-npm-deps.py`.
 ###########################################################################
 
+# Auto-generated Node.js dependencies {
+
+
 #: Dependencies required for static media building.
-frontend_buildkit_npm_dependencies = {
-    # Customizable Beanbag-built dependencies.
-    '@beanbag/frontend-buildkit': (
-        os.environ.get('BEANBAG_FRONTEND_BUILDKIT_PATH') or
-        '^1.1.0'),
-
-    # Consumers are required to provide their own Spina file on any pages
-    # using it, but this is still needed for types.
-    '@beanbag/spina': (
-        os.environ.get('BEANBAG_SPINA_PATH') or
-        '^1.0.1'),
+frontend_buildkit_npm_dependencies: Dict[str, str] = {
+    '@beanbag/frontend-buildkit': '^1.1.0',
+    '@beanbag/spina': '^1.0.1',
+    '@types/jquery': '^3.5.16',
+    '@types/underscore': '^1.11.4',
+    'backbone': '^1.4.1',
+    'jquery': '^3.6.3',
+    'jquery-ui': '^1.13.2',
 }
 
 #: Dependencies required for static media linting.
-lint_npm_dependencies = {
+lint_npm_dependencies: Dict[str, str] = {
+    '@beanbag/eslint-plugin': '^1.0.0',
     'eslint': '^8.29.0',
-
-    # Customizable Beanbag-built dependencies.
-    '@beanbag/eslint-plugin': (
-        os.environ.get('BEANBAG_ESLINT_PLUGIN_PATH') or
-        '^1.0.0'),
 }
 
+
+# } Auto-generated Node.js dependencies
+
+
 #: Node dependencies required to package/develop/test Djblets.
 npm_dependencies = {}
 npm_dependencies.update(frontend_buildkit_npm_dependencies)
diff --git a/djblets/package.json b/djblets/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..2978488144ac91c8398536198139a2179957b243
--- /dev/null
+++ b/djblets/package.json
@@ -0,0 +1,19 @@
+{
+  "name": "@beanbag/djblets",
+  "private": "true",
+  "files": [],
+  "scripts": {
+    "dependencies": "./contrib/internal/build-npm-deps.py"
+  },
+  "dependencies": {
+    "@beanbag/eslint-plugin": "^1.0.0",
+    "@beanbag/frontend-buildkit": "^1.1.0",
+    "@beanbag/spina": "^1.0.1",
+    "@types/jquery": "^3.5.16",
+    "@types/underscore": "^1.11.4",
+    "backbone": "^1.4.1",
+    "eslint": "^8.29.0",
+    "jquery": "^3.6.3",
+    "jquery-ui": "^1.13.2"
+  }
+}
diff --git a/package-lock.json b/package-lock.json
index 6f3dc4f0e27985340490373a921d615c0b51434d..fcc83171a6c254d508e0470479ffe3f5ad344d27 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,9 +9,13 @@
         "@beanbag/eslint-plugin": "^1.0.0",
         "@beanbag/frontend-buildkit": "^1.1.0",
         "@beanbag/spina": "^1.0.1",
-        "eslint": "^8.29.0"
-      },
-      "devDependencies": {}
+        "@types/jquery": "^3.5.16",
+        "@types/underscore": "^1.11.4",
+        "backbone": "^1.4.1",
+        "eslint": "^8.29.0",
+        "jquery": "^3.6.3",
+        "jquery-ui": "^1.13.2"
+      }
     },
     "node_modules/@ampproject/remapping": {
       "version": "2.2.0",
@@ -3245,6 +3249,19 @@
       "version": "2.0.0",
       "license": "ISC"
     },
+    "node_modules/jquery": {
+      "version": "3.6.3",
+      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz",
+      "integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg=="
+    },
+    "node_modules/jquery-ui": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.13.2.tgz",
+      "integrity": "sha512-wBZPnqWs5GaYJmo1Jj0k/mrSkzdQzKDwhXNtHKcBdAcKVxMM3KNYFq+iJ2i1rwiG53Z8M4mTn3Qxrm17uH1D4Q==",
+      "dependencies": {
+        "jquery": ">=1.8.0 <4.0.0"
+      }
+    },
     "node_modules/js-sdsl": {
       "version": "4.2.0",
       "license": "MIT",
@@ -3286,8 +3303,9 @@
       "license": "MIT"
     },
     "node_modules/json5": {
-      "version": "2.2.1",
-      "license": "MIT",
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
       "bin": {
         "json5": "lib/cli.js"
       },
diff --git a/package.json b/package.json
new file mode 120000
index 0000000000000000000000000000000000000000..4abe941a93459a153849dadee5afc8f8576a303b
--- /dev/null
+++ b/package.json
@@ -0,0 +1 @@
+djblets/package.json
\ No newline at end of file
diff --git a/setup.py b/setup.py
index e42f16ede5732d5adb6d14d4a261048b91bcc0f4..3a6c3ed63b64301cfb42ec324b8339f83cc175da 100755
--- a/setup.py
+++ b/setup.py
@@ -66,50 +66,6 @@ else:
     check_run = subprocess.check_call
 
 
-class AuditNodeDependenciesCommand(Command):
-    """Audit all node.js dependencies, checking for security issues.
-
-    This will scan the Node build-time dependencies, looking for any
-    security issues. It does this through the :command:`npm audit` command.
-    """
-
-    description = 'Audit the node build-time packages for security issues.'
-    user_options = []
-
-    def initialize_options(self):
-        """Initialize options for the command.
-
-        This is required, but does not actually do anything.
-        """
-        pass
-
-    def finalize_options(self):
-        """Finalize options for the command.
-
-        This is required, but does not actually do anything.
-        """
-        pass
-
-    def run(self):
-        """Run the commands to audit node packages.
-
-        Raises:
-            RuntimeError:
-                There was an error finding or invoking the package manager.
-        """
-        try:
-            check_run(['npm', '--version'])
-        except (subprocess.CalledProcessError, OSError):
-            raise RuntimeError(
-                'Unable to locate npm in the path, which is needed to '
-                'audit dependencies required to build this package.')
-
-        self.run_command('list_node_deps')
-
-        print('Auditing node.js modules...')
-        os.system('npm audit')
-
-
 class BuildEggInfoCommand(egg_info):
     """Build the egg information for the package.
 
@@ -354,59 +310,6 @@ class FetchPublicSuffixListCommand(Command):
         print('Public suffix list stored at %s' % filename)
 
 
-class ListNodeDependenciesCommand(Command):
-    """"Write all node.js dependencies to standard output."""
-
-    description = 'Generate a package.json that lists node.js dependencies'
-
-    user_options = [
-        (str('to-stdout'), None,
-         'Write to standard output instead of a package.json file.')
-    ]
-
-    boolean_options = [str('to-stdout')]
-
-    def initialize_options(self):
-        """Set the command's option defaults."""
-        self.to_stdout = False
-
-    def finalize_options(self):
-        """Post-process command options.
-
-        This method intentionally left blank.
-        """
-        pass
-
-    def run(self):
-        """Run the command."""
-        if self.to_stdout:
-            self._write_deps(sys.stdout)
-        else:
-            with open('package.json', 'w') as f:
-                self._write_deps(f)
-
-    def _write_deps(self, f):
-        """Write the packaage.json to the given file handle.
-
-        Args:
-            f (file):
-                The file handle to write to.
-        """
-        f.write(json.dumps(
-            {
-                '__note__': (
-                    'DO NOT EDIT OR COMMIT THIS FILE! All dependencies must '
-                    'be recorded in djblets/dependencies.py instead.'
-                ),
-                'name': 'djblets',
-                'private': 'true',
-                'devDependencies': {},
-                'dependencies': npm_dependencies,
-            },
-            indent=2))
-        f.write('\n')
-
-
 class InstallNodeDependenciesCommand(Command):
     """Installs all node.js dependencies from npm.
 
@@ -455,8 +358,6 @@ class InstallNodeDependenciesCommand(Command):
                 'install dependencies required to build this package.'
                 % npm_command)
 
-        self.run_command('list_node_deps')
-
         print('Installing node.js modules...')
         result = os.system('%s install' % npm_command)
 
@@ -496,14 +397,12 @@ setup(
     zip_safe=False,
     test_suite='dummy',
     cmdclass={
-        'audit_node_deps': AuditNodeDependenciesCommand,
         'build_media': BuildMediaCommand,
         'build_i18n': BuildI18nCommand,
         'develop': DevelopCommand,
         'egg_info': BuildEggInfoCommand,
         'fetch_public_suffix_list': FetchPublicSuffixListCommand,
         'install_node_deps': InstallNodeDependenciesCommand,
-        'list_node_deps': ListNodeDependenciesCommand,
     },
     classifiers=[
         'Development Status :: 5 - Production/Stable',
