From f02ad5841273123563271befa3f01563b3330b0e Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Thu, 27 Feb 2025 09:20:03 -0500 Subject: [PATCH 1/4] docs: add ADR about using caret ranges for npm deps --- .../oep-0067-bp-tools-and-technology.rst | 15 +++++ .../0011-caret-ranges-dependencies.rst | 55 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst diff --git a/oeps/best-practices/oep-0067-bp-tools-and-technology.rst b/oeps/best-practices/oep-0067-bp-tools-and-technology.rst index 7cad486db..72304ea18 100644 --- a/oeps/best-practices/oep-0067-bp-tools-and-technology.rst +++ b/oeps/best-practices/oep-0067-bp-tools-and-technology.rst @@ -181,6 +181,21 @@ Frontend Technology Selection Documentation on how to configure Renovate automation on a repository is available in the `Upgrade Automation How-to`_. +#. **JavaScript projects should use caret ranges for npm dependencies** + + **Rationale**: Using caret ranges (e.g., ``"^1.2.3"``) in our package.json more clearly + communicates that our projects are designed to accept semver-compatible (minor and patch) + updates. This intent is important because merely pinning top-level dependencies does not + extend to their transitive dependencies—potentially leading to duplicative or outdated + packages in our bundles. Moreover, specifying dependencies with caret ranges streamlines + our Renovate workflow. With caret ranges, non-breaking updates are typically resolved + automatically during installation, so Renovate will primarily flag major version bumps + that require manual intervention. In contrast, pinned dependencies can trigger frequent + Renovate updates for every minor or patch change, increasing maintenance overhead. + + **Decision Record**: For details, see + :doc:`OEP-0067-0011 Caret Ranges for npm Dependencies `. + #. **JavaScript should be bundled using Webpack** **Rationale**: `Webpack`_ is the tool of choice for code optimization and diff --git a/oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst b/oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst new file mode 100644 index 000000000..a98f377b1 --- /dev/null +++ b/oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst @@ -0,0 +1,55 @@ +Use Caret Ranges for npm Dependency Versions +############################################ + +Status +****** + +Proposed (February 2025) + +Context +******* + +Managing dependencies in JavaScript projects requires balancing stability, maintainability, and performance. Historically, most projects +have opted to pin exact dependency versions to ensure consistency. However, this approach has notable downsides, particularly when +considering transitive dependencies, package duplication, and update workflows. + +Decision +******** + +JavaScript projects in the Open edX community should define dependencies using caret ranges (e.g., ``^1.2.3``) instead of pinning exact +versions (e.g., ``1.2.3``). + +Why? + +#. **Pinned Dependencies Do Not Fully Lock Transitive Dependencies**: Even if a top-level dependency is pinned, its transitive dependencies + are not necessarily locked. This means updates can still introduce changes, making pinning less effective than intended. + +#. **Reduces Webpack Bundle Size and Dependency Fragmentation**: When different packages pin slightly different versions of the same dependency, + multiple versions of that dependency may be included in the final bundle. Using caret ranges allows different packages to share a single version, + reducing duplication and improving performance. + +#. **More Clearly Communicates Engineering Intent**: Specifying caret ranges explicitly indicates that a project intends to accept non-breaking + updates in accordance with semantic versioning. This improves clarity for maintainers and contributors. + +#. **Improves Renovate Workflow and Reduces Maintenance Overhead**: With caret ranges, Renovate still detects and opens PRs for minor and patch + updates, but these updates typically require only a package-lock.json change rather than modifying package.json. This allows Renovate to group + updates more efficiently and reduces unnecessary PR churn. In contrast, pinned dependencies require Renovate to open a PR for every minor or patch + update to explicitly modify package.json, increasing maintenance overhead. + +Consequence +*********** + +#. Projects will automatically receive non-breaking dependency updates within the specified range, reducing the need for manual intervention. +#. Webpack bundle sizes may decrease due to fewer redundant versions of dependencies. +#. Renovate will generate fewer update PRs, focusing primarily on major version changes that require attention. +#. Developers should continue using lockfiles (``package-lock.json``) to ensure consistent installations across environments. + +Rejected Alternatives +********************* + +#. **Continuing to pin dependencies in JavaScript projects** + + * Pinning dependencies does not effectively prevent updates to transitive dependencies. + * Leads to unnecessary duplication of packages in Webpack bundles, increasing size and complexity. + * Results in excessive Renovate PRs for minor and patch updates, increasing maintenance burden. + From 9e20ecb802e26e29f06b7539f55e114112de30cd Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Tue, 11 Mar 2025 17:55:29 -0400 Subject: [PATCH 2/4] chore: apply suggestions from code review Co-authored-by: Brian Smith <112954497+brian-smith-tcril@users.noreply.github.com> Co-authored-by: Sarina Canelake --- oeps/best-practices/oep-0067-bp-tools-and-technology.rst | 6 +++--- .../decisions/frontend/0011-caret-ranges-dependencies.rst | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/oeps/best-practices/oep-0067-bp-tools-and-technology.rst b/oeps/best-practices/oep-0067-bp-tools-and-technology.rst index 72304ea18..1c1b9ddbc 100644 --- a/oeps/best-practices/oep-0067-bp-tools-and-technology.rst +++ b/oeps/best-practices/oep-0067-bp-tools-and-technology.rst @@ -183,18 +183,18 @@ Frontend Technology Selection #. **JavaScript projects should use caret ranges for npm dependencies** - **Rationale**: Using caret ranges (e.g., ``"^1.2.3"``) in our package.json more clearly + **Rationale**: Using caret ranges (e.g., ``"^1.2.3"``) in our ``package.json`` more clearly communicates that our projects are designed to accept semver-compatible (minor and patch) updates. This intent is important because merely pinning top-level dependencies does not extend to their transitive dependencies—potentially leading to duplicative or outdated packages in our bundles. Moreover, specifying dependencies with caret ranges streamlines - our Renovate workflow. With caret ranges, non-breaking updates are typically resolved + our `Renovate`_ workflow. With caret ranges, non-breaking updates are typically resolved automatically during installation, so Renovate will primarily flag major version bumps that require manual intervention. In contrast, pinned dependencies can trigger frequent Renovate updates for every minor or patch change, increasing maintenance overhead. **Decision Record**: For details, see - :doc:`OEP-0067-0011 Caret Ranges for npm Dependencies `. + :ref:`Use Caret Ranges for npm Dependency Versions`. #. **JavaScript should be bundled using Webpack** diff --git a/oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst b/oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst index a98f377b1..7ba0d0013 100644 --- a/oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst +++ b/oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst @@ -1,3 +1,5 @@ +.. _Use Caret Ranges for npm Dependency Versions: + Use Caret Ranges for npm Dependency Versions ############################################ From ce9341684244977e28efe9970696a384bc75e59c Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Tue, 11 Mar 2025 18:05:13 -0400 Subject: [PATCH 3/4] chore: add link to Renovate and brief blurb --- .../frontend/0011-caret-ranges-dependencies.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst b/oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst index 7ba0d0013..f327b96e2 100644 --- a/oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst +++ b/oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst @@ -33,10 +33,10 @@ Why? #. **More Clearly Communicates Engineering Intent**: Specifying caret ranges explicitly indicates that a project intends to accept non-breaking updates in accordance with semantic versioning. This improves clarity for maintainers and contributors. -#. **Improves Renovate Workflow and Reduces Maintenance Overhead**: With caret ranges, Renovate still detects and opens PRs for minor and patch - updates, but these updates typically require only a package-lock.json change rather than modifying package.json. This allows Renovate to group - updates more efficiently and reduces unnecessary PR churn. In contrast, pinned dependencies require Renovate to open a PR for every minor or patch - update to explicitly modify package.json, increasing maintenance overhead. +#. **Improves Renovate Workflow and Reduces Maintenance Overhead**: The Open edX platform uses `Renovate`_ to automate and manage dependency updates. With + caret ranges, Renovate still detects and opens PRs for minor and patch updates, but these updates typically require only a ``package-lock.json`` change + rather than modifying ``package.json``. This allows Renovate to group updates more efficiently and reduces unnecessary PR churn. In contrast, pinned + dependencies require Renovate to open a PR for every minor or patch update to explicitly modify ``package.json``, increasing maintenance overhead. Consequence *********** @@ -55,3 +55,5 @@ Rejected Alternatives * Leads to unnecessary duplication of packages in Webpack bundles, increasing size and complexity. * Results in excessive Renovate PRs for minor and patch updates, increasing maintenance burden. +.. Cross-references +.. _Renovate: https://renovatebot.com/ From 441a2b8a75997fc6f7e1ffa49e136539bbef33e6 Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Tue, 11 Mar 2025 18:14:49 -0400 Subject: [PATCH 4/4] chore: update the renovate link, with a ref to the adr --- .../oep-0067/decisions/frontend/0009-renovate.rst | 2 ++ .../frontend/0011-caret-ranges-dependencies.rst | 12 +++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/oeps/best-practices/oep-0067/decisions/frontend/0009-renovate.rst b/oeps/best-practices/oep-0067/decisions/frontend/0009-renovate.rst index eef283829..1b4ca3235 100644 --- a/oeps/best-practices/oep-0067/decisions/frontend/0009-renovate.rst +++ b/oeps/best-practices/oep-0067/decisions/frontend/0009-renovate.rst @@ -1,3 +1,5 @@ +.. _Use Renovate to update dependencies: + Use Renovate to update dependencies ################################### diff --git a/oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst b/oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst index f327b96e2..52154b940 100644 --- a/oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst +++ b/oeps/best-practices/oep-0067/decisions/frontend/0011-caret-ranges-dependencies.rst @@ -33,10 +33,11 @@ Why? #. **More Clearly Communicates Engineering Intent**: Specifying caret ranges explicitly indicates that a project intends to accept non-breaking updates in accordance with semantic versioning. This improves clarity for maintainers and contributors. -#. **Improves Renovate Workflow and Reduces Maintenance Overhead**: The Open edX platform uses `Renovate`_ to automate and manage dependency updates. With - caret ranges, Renovate still detects and opens PRs for minor and patch updates, but these updates typically require only a ``package-lock.json`` change - rather than modifying ``package.json``. This allows Renovate to group updates more efficiently and reduces unnecessary PR churn. In contrast, pinned - dependencies require Renovate to open a PR for every minor or patch update to explicitly modify ``package.json``, increasing maintenance overhead. +#. **Improves Renovate Workflow and Reduces Maintenance Overhead**: The Open edX platform to automate and manage dependency updates (see + :ref:`Use Renovate to update dependencies` for more details). With caret ranges, Renovate still detects and opens PRs for minor and patch updates, + but these updates typically require only a ``package-lock.json`` change rather than modifying ``package.json``. This allows Renovate to group updates + more efficiently and reduces unnecessary PR churn. In contrast, pinned dependencies require Renovate to open a PR for every minor or patch update to + explicitly modify ``package.json``, increasing maintenance overhead. Consequence *********** @@ -54,6 +55,3 @@ Rejected Alternatives * Pinning dependencies does not effectively prevent updates to transitive dependencies. * Leads to unnecessary duplication of packages in Webpack bundles, increasing size and complexity. * Results in excessive Renovate PRs for minor and patch updates, increasing maintenance burden. - -.. Cross-references -.. _Renovate: https://renovatebot.com/