The druxt.js Nuxt/Vue monorepo, the fully decoupled Drupal frontend
framework. Druxt = DRUpal + nUXT. Repository:
github.com/druxt/druxt.js.
- NEVER push, comment, open/merge PRs, or otherwise write to
github.com/druxt*without explicit per-action permission, regardless of whatGH_TOKENaccess technically allows. Surface diffs locally for review. - NEVER commit, push, or create branches/tags without explicit permission. Only the project owner commits.
- Use Conventional Commits; scope is the
package name (e.g.
fix(router): …).
Node and Yarn are pinned in .mise.toml and package.json engines. From a
fresh clone:
mise install # activates Node 16.20.1 from .mise.toml
corepack enable # enables the corepack shim (reads packageManager field)
yarn install # uses Yarn 3.6.1 via corepack
yarn build # = yarn clean && siroc build → produces packages/*/distOr simply make setup && make build.
yarn build is the regression gate. Every config/tooling change must keep it
green. All 11 packages (druxt, blocks, breadcrumb, entity, menu,
router, schema, site, views, docgen, test-utils) must produce their
dist/*.ssr.js + dist/*.esm.js (docgen outputs bin/druxt-docgen.js).
Nuxt 2's esm config loader patches the module system: a nuxt.config.js
build hook that requires a modern ESM-leaning package can die silently
(no stack, exit 1 in CI). Spawn a clean child process for such work.
The build stack (Node 16, Yarn 3, jest 29, eslint 7, Vue 2.7, Nuxt 2, siroc) is
intentionally pinned. A future major upgrade (Node 18+, Vue 3, Nuxt 3/4) is a
separate, deliberate effort, not something to drift into via routine dependency
bumps. renovate.json freezes these packages from automated updates.
The full local verification gate, in order:
yarn lint && yarn build && yarn test:unityarn build: siroc build of all packagesyarn test:unit: jest (NODE_OPTIONS=--unhandled-rejections=warn)yarn lint: eslint (eslint:recommended+plugin:nuxt/recommended+plugin:vue/recommended, matching every sibling package) acrosspackages/*/srcyarn lint:md/yarn lint:cspell/yarn lint:format: markdownlint / cspell / prettieryarn lint:renovate: validaterenovate.jsonyarn lint:audit:yarn npm audit, production dependencies only, fails on high/critical (the CI gate).yarn lint:audit:fullincludes devDependencies and is reporting-only. See the note below.yarn lint:knip: knip, scoped todependencies,unlisted(unused and undeclared-but-imported packages). Blocking in CI.--no-config-hints: this knip version's "unused item in ignoreDependencies" check is flaky (observed contradictory results across successive runs with no code changes between them), so don't trust it to decide whether an ignore entry is still needed; verify withgrepinstead, the way every entry inknip.jsoncalready is. Seeknip.jsoncfor confirmed false positives (Vue SFC parsing isn't supported at this Node-16-forced version, Nuxt module-string registration, JSON-config-file references. None of these are things knip's static analysis can trace).yarn bundlewatch: bundle size guard (packages/**/dist/*.js≤ 50kb)yarn perf:audit/yarn perf:audit:test: the example performance audit and itsnode:testunits. Both run under Node 22, not the repository's Node 16 (mise exec node@22 -- yarn perf:audit). Not part of the gate above. The audit needs the examples served first. Seescripts/perf-audit/README.md.
yarn lint:audit (production-only) is the blocking gate and is currently
clean - keep it that way. yarn lint:audit:full also covers
devDependencies. As of this writing it reports ~50 advisories, almost all
inherited transitively through renovate (used only for yarn lint:renovate) and other build/lint/test tooling. This isn't neglect: the
patched versions of renovate, jest, eslint, etc. all require Node 18+,
which conflicts with the Node 16 toolchain freeze above - renovate itself is
effectively frozen for the same reason vue/nuxt/jest are, even though
it's not in renovate.json's explicit freeze list. Don't chase these
piecemeal. They resolve together whenever the Node 16 → 18+ upgrade happens.
Every JS/Vue source file's JSDoc is scraped by packages/docgen into
Markdown that the druxtjs.org API reference renders
(the site itself now lives in druxt/druxtjs.org;
this repo provides the generator). The JSDoc you
write is the public documentation, verbatim - there's no separate editing
pass, so a sloppy @param renders as a sloppy docs page.
The rule, and the reason it exists: every @param line must have both a
{type} and a - description, with no exceptions (a {typedef} reference
like @param {addCollectionPayload} payload - The mutation payload. counts -
you don't have to re-enumerate a typedef's own properties inline). This is
enforced by ESLint: jsdoc/require-param-type, jsdoc/require-param-description,
jsdoc/require-param, jsdoc/check-param-names and jsdoc/valid-types are
all error in .eslintrc.js - yarn lint fails on a bare
@param context.name with no type/description, and on any undocumented or
misnamed param.
This rule exists because of a real regression: an earlier pass added bare
@param context.name / @param context.theme stub lines across ~10 packages
(no type, no description) to silence the separate jsdoc/require-param
warning ("this destructured property isn't mentioned at all"). The intent
was reasonable, but the execution left the properties mentioned with
nothing to say about them, which renders as empty Type/Description table
cells - worse than not mentioning them at all. The stub lines were reverted, and
every destructured property has since been documented for real, so the
param-coverage rules now sit at error alongside the type/description pair.
The pre-commit hook applies eslint's suggestion-type fixes, so a new
undocumented param gets its @param line scaffolded automatically - and the
type/description errors then block the commit until the line says something.
A @param that exists but says nothing is strictly worse than a missing
one - it looks intentional and finished when it isn't.
If a param is legitimately hard to give a real one-line description, prefer a
named @typedef (see addCollectionPayload and siblings in
packages/druxt/src/stores/druxt.js, or PropsData/ComponentOptions in
packages/blocks/src/components/DruxtBlockRegion.vue) over a half-documented
inline breakdown. Consistency matters here more than most repos: the docs
site is the entire public-facing reference for the framework.
| Path | npm name | Role |
|---|---|---|
packages/druxt |
druxt |
Core module, Nuxt plugin, DruxtModule base |
packages/blocks |
druxt-blocks |
Block region components |
packages/breadcrumb |
druxt-breadcrumb |
Breadcrumb components |
packages/entity |
druxt-entity |
Entity/field components |
packages/menu |
druxt-menu |
Menu components |
packages/router |
druxt-router |
Routing, path translation |
packages/schema |
druxt-schema |
Schema generation |
packages/site |
druxt-site |
Site integration (tome/preview) |
packages/views |
druxt-views |
Views components |
packages/docgen |
druxt-docgen |
Private CLI (bin/druxt-docgen.js) |
packages/test-utils |
druxt-test-utils |
Shared test helpers (private) |
Drupal-side counterparts (druxt, decoupled_router, jsonapi_menu_items,
jsonapi_views, …) are separate drupal.org projects, not part of this repo.
This repo uses GitFlow:
developis the integration branch. Feature branches and dependency PRs start here and merge back here.mainreceives release merges only (release/*→main, then merge-back todevelop).- Renovate (
baseBranches: ["develop"]) and changesets (baseBranch: develop) targetdevelop. CodeQL scansdevelop.
When starting work, branch from develop:
git checkout develop && git pull && git checkout -b feature/<short-desc>Branch prefix is feature/, not feat/. (The docs site's Lagoon project
keyed its branch previews on that prefix; the site now deploys from
druxt/druxtjs.org, and the prefix
stays as this repo's convention.) This is unrelated to commit-message
feat: types (Conventional Commits), which stay as-is.
- GitHub Actions (
.github/workflows/ci.yml): canonical CI, on Node 16.20.1. Jobs:build,lint,test-unit(coverage uploaded to Codecov),test-e2e(Drupal backend + Cypress). Runs on push/PR todevelop/main. Replaces CircleCI, which is no longer used. - GitLab CI (
.gitlab-ci.yml): additive pipeline (lint + test +secret-detection+previewstages). - Dependency/security auditing:
yarn npm audit(native Yarn Berry, not a third-party action) andknip, run in both CI systems. Production-only audit and knip block. The full audit is reporting-only. See "Dependency audit: production vs. full" above. - CodeQL (
.github/workflows/codeql-analysis.yml): scansdevelopweekly. - Performance audit: advisory on both hosts, compared against
perf/baseline.<environment>.json. On GitHub theperf-auditlabel on a pull request starts it. A push todevelopalso runs it with the baseline refresh switched on, and opens a pull/merge request when the numbers moved, so a later pull request's audit is never diffed against a fix that already merged. Counts (backend requests, API calls after load, discarded nodes, payload) compare across machines. Lighthouse scores do not. Seescripts/perf-audit/README.md.
examples/drupal is the minimal Umami dev backend (D11, demo_umami, the
Druxt stack, /en+/es, the examples' OAuth consumer). Its local/CI
workflow is Docker-free: PHP's built-in server plus a throwaway SQLite
database (examples/drupal/.devtools/, make build); a
.ddev/config.yaml provides the DDEV alternative locally. test-e2e
uses the Docker-free path, pinned to PHP 8.3. demo_umami provisions
its demo content in English and Spanish, and multilingual.cy.js runs
against this backend in CI. Provision checks that the translations landed
(a note locally, fatal under CI, REQUIRE_TRANSLATIONS overrides either);
the provisioned database is per-checkout, override with DB_FILE. See examples/drupal/.devtools/README.md for how the SQLite
path works. The full druxtjs.org backend (Tome, curated translations)
lives in druxt/druxtjs.org;
DDEV-based full-site setups live in the quickstart repo, not here.
- druxtjs.org: docs site