Skip to content

Fix: HMR for Non-Module Systems#483

Open
rleeson wants to merge 8 commits intodevelopfrom
feature/hmr-wds-v5
Open

Fix: HMR for Non-Module Systems#483
rleeson wants to merge 8 commits intodevelopfrom
feature/hmr-wds-v5

Conversation

@rleeson
Copy link

@rleeson rleeson commented Feb 25, 2026

Related Issue/RFC: #428

Important Note: This does not solve the issue of HMR (--hot) not running with script modules. It does resolve failures for --hot to work on projects which use HMR, have upgraded WDS to v5, and have useScriptModule and useBlockAssets disabled. It also establishes concrete steps to properly upgrade peer dependencies and warn users when manual intervention is required.

Description of the Change

This PR addresses webpack-dev-server (WDS) compatibility and upgrade behavior for projects using the toolkit’s dev server and hot reload:

  1. webpack-dev-server as peer dependency
    webpack-dev-server is moved from dependencies to peerDependencies (^5.2.2) so the consuming project’s dependency tree owns the version. Upgrade the 10up-toolkit package and run npm install (npm 7+) to resolve the matching WDS version more predictably, which helps avoids keeping an outdated, transitive version in the project lockfile. NPM 7+ will manage peer dependency installation by default.

  2. Correct WDS v5 proxy configuration
    Hot reload proxy config is updated for webpack-dev-server v5: the proxy is now passed as an array of context configs (e.g. [{ context: '/dist', pathRewrite: { '^/dist': '' } }]) instead of the v4-style object form. A useLegacyProxy option is supported so legacy WDS versions can still use the object format.

  3. Version check and warning in config assembly
    A minimum WDS version check and user-facing warning are added to config/webpack.config.js. The config layer reads the resolved WDS version, computes useLegacyProxy = !isMinimumPackageVersion(version, '5.2.2'), and passes useLegacyProxy into the devServer helper. The warning is only shown when useLegacyProxy is true and the process is not running in Jest (JEST_WORKER_ID unset), so test output stays clean.

  4. Shared version helper
    isMinimumPackageVersion(actual, min) is added in utils/package.js (and re-exported from utils) for semver-style version comparison and reused where the minimum WDS version is enforced.

  5. Documentation
    README is updated with: an “Upgrading the toolkit” section (lockfile behavior, npm update webpack-dev-server, clean reinstall); note that webpack-dev-server is a peer dependency; and scenarios where an older version can still be used (e.g. --legacy-peer-deps, pinned or transitive old version), with a note that the toolkit will warn at runtime. The npm < 7 manual install list now includes webpack-dev-server.

  6. Tests
    In config/webpack/__tests__/devServer.js, new tests validate devServer proxy configuration: undefined when neither devServer nor hot is enabled; array (v5) proxy when useLegacyProxy is false or omitted; object (legacy) proxy when useLegacyProxy is true.

Files changed:

  • packages/toolkit/package.json
  • packages/toolkit/README.md
  • packages/toolkit/config/webpack.config.js
  • packages/toolkit/config/webpack/devServer.js
  • packages/toolkit/config/webpack/__tests__/devServer.js
  • packages/toolkit/utils/package.js
  • packages/toolkit/utils/index.js.

Alternate Designs

  • Always warn when useLegacyProxy is true: We skip the warning when JEST_WORKER_ID is set so Jest runs don’t print the warning; the version check and useLegacyProxy still run in tests. Alternatively we could mock webpack-dev-server in tests to return a 5.x version and keep the warning unconditional in production code.

Possible Drawbacks

  • Breaking for npm < 7: Projects on npm < 7 must install webpack-dev-server manually (documented in the peer dependency warning and README).
  • Legacy peer / lockfile edge cases: Using --legacy-peer-deps or having an older pinned or transitive webpack-dev-server can still result in an old version; we document this and warn at runtime when the resolved version is below the minimum.

Verification Process

  • Ran the toolkit test suite (including config/__tests__ and config/webpack/__tests__/devServer.js) and confirmed all tests pass and no console.warn appears from the version check in tests.
  • Confirmed devServer tests cover: undefined when neither devServer nor hot; array proxy when useLegacyProxy is false or omitted; object proxy when useLegacyProxy is true.
  • Manually verified that with a project depending on the toolkit, upgrading the toolkit and running npm install (npm 7+) installs a compatible webpack-dev-server via the peer dependency.
  • Checked that README upgrade instructions and peer dependency / legacy-peer-deps notes are accurate and complete.

Checklist:

  • I have read the CONTRIBUTING document.
  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my change.
  • All new and existing tests passed.
  • I have added a changeset to my PR. See CONTRIBUTING document for instructions

@changeset-bot
Copy link

changeset-bot bot commented Feb 25, 2026

🦋 Changeset detected

Latest commit: f7ca85d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
10up-toolkit Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves 10up-toolkit hot reload/dev-server compatibility for non–script-module setups after upgrading to webpack-dev-server (WDS) v5, and adds upgrade guidance + safeguards around WDS version resolution.

Changes:

  • Move webpack-dev-server from dependencies to peerDependencies and document upgrade/lockfile considerations.
  • Update devServer proxy config to support WDS v5’s array-based proxy format, with a legacy fallback via useLegacyProxy.
  • Add a shared version helper plus a runtime warning/min-version check, and extend Jest coverage for devServer proxy behavior.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/toolkit/utils/package.js Adds isMinimumPackageVersion helper for version comparisons.
packages/toolkit/utils/index.js Re-exports the new version helper from the utils entrypoint.
packages/toolkit/package.json Moves webpack-dev-server to peerDependencies at ^5.2.2.
packages/toolkit/config/webpack/devServer.js Switches proxy config to WDS v5 array style with a legacy toggle.
packages/toolkit/config/webpack/__tests__/devServer.js Adds tests validating proxy output for legacy vs v5 mode and disabled scenarios.
packages/toolkit/config/webpack.config.js Resolves WDS version to decide legacy proxy usage and emits a runtime warning when below min.
packages/toolkit/README.md Documents peer dependency behavior and upgrade steps/workarounds.
.changeset/config.json Changes Changesets baseBranch from trunk to develop.
.changeset/angry-rockets-smash.md Adds a changeset describing the HMR/peer-dep changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

"@linaria/webpack-loader": ">=4.1.11",
"typescript": ">=5.0.0"
"typescript": ">=5.0.0",
"webpack-dev-server": "^5.2.2"
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

webpack-dev-server@^5.2.2 requires Node >=18.12.0 (per its published engines), which can conflict with consumers running Node 16 (and with this package’s own engines.node if it’s still set lower). Consider either documenting/enforcing the Node >=18 requirement more explicitly here (e.g. update engines) or widening the peer range to allow a Node-16-compatible WDS major (and rely on useLegacyProxy for v4).

Suggested change
"webpack-dev-server": "^5.2.2"
"webpack-dev-server": "^4.0.0 || ^5.2.2"

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can update the engines declaration for this package, perhaps that update was missed when the overall .nvmrc was updated to use v18?

"linked": [],
"access": "restricted",
"baseBranch": "trunk",
"baseBranch": "develop",
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing Changesets baseBranch from trunk to develop is likely to break the existing release automation that runs on both develop and trunk (see .github/workflows/release.yml and CONTRIBUTING, which describe stable releases from trunk). If the intent is to target develop only for @next, consider handling that in the workflow/action inputs instead of globally changing baseBranch, or confirm Changesets can still open the correct PRs/publish when running on trunk.

Suggested change
"baseBranch": "develop",
"baseBranch": "trunk",

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsure on this one, I have not used changeset that much before. It did fail when I tried to run it with trunk set as the baseBranch.

Comment on lines +58 to +81
/**
* Compares two semver-like version strings (e.g. "5.2.2", "1.0.0-beta.1").
* Only the numeric segments are compared; non-numeric segments are treated as 0.
*
* @param {string} actual - The resolved version (e.g. from a package).
* @param {string} min - The minimum required version.
* @returns {boolean} True if actual >= min, false otherwise.
*/
const isMinimumPackageVersion = (actual, min) => {
const actualVersions = actual.split('.').map(Number);
const minimumVersions = min.split('.').map(Number);

for (let i = 0; i < Math.max(actualVersions.length, minimumVersions.length); i++) {
const actualVersionLevel = actualVersions[i] || 0;
const minimumVersionLevel = minimumVersions[i] || 0;
if (actualVersionLevel > minimumVersionLevel) {
return true;
}
if (actualVersionLevel < minimumVersionLevel) {
return false;
}
}

return true;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isMinimumPackageVersion doesn’t perform real semver comparison despite the JSDoc examples (e.g. 1.0.0-beta.1 will be treated as >= 1.0.0 because the trailing .1 is compared as an extra numeric segment). Either switch to a proper semver comparator (preferred, since this is a shared util) or tighten the docstring/implementation to clearly define the supported version formats (e.g. x.y.z only).

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can switch to use node-semver or something similar if wanted. This was a simple comparator, I don't think it warrants a package for this single use case of testing major versions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants