require() of ES Module string-width: fix yarn.lock in Docker
Why yarn install in a node:18 container dies in node-gyp with ERR_REQUIRE_ESM from wide-align, how to spot the broken yarn.lock entry, and two fixes reproduced in Docker.
TL;DR#
The require() that fails is not in your code. wide-align (pulled in by gauge, npmlog and a project-local node-gyp) does require('string-width') and accepts versions 1 to 4. string-width 5 is an ES module. The broken install puts version 5 at the top of node_modules, with no copy of version 4 for wide-align to find, The trigger documented in yarnpkg/yarn#8994 is a Yarn 1 yarn.lock in which the alias string-width-cjs@npm:string-width@^4.2.0 shares one entry with the ordinary string-width 4 ranges. Installing from a lockfile hand-edited into that shape reproduced the error here: Yarn skips unpacking string-width 4 and hoists 5. In the test, Yarn never wrote that merged entry by itself. Regenerate yarn.lock and commit it, or add "resolutions": { "string-width": "4.2.3" } to package.json. The asker tried other Node versions and base images without success.
The error#
The Stack Overflow question builds an Angular project with a three-line Dockerfile, FROM node:18.18, COPY ./ ./, then RUN yarn install, and the install stops while @parcel/watcher runs its install script:
error /node_modules/@parcel/watcher: Command failed.
Exit code: 1
Command: node-gyp-build
Arguments:
Directory: /node_modules/@parcel/watcher
Output:
/node_modules/wide-align/align.js:2
var stringWidth = require('string-width')
^
Error [ERR_REQUIRE_ESM]: require() of ES Module /node_modules/string-width/index.js from /node_modules/wide-align/align.js not supported.
Instead change the require of index.js in /node_modules/wide-align/align.js to a dynamic import() which is available in all CommonJS modules.
at Object.<anonymous> (/node_modules/wide-align/align.js:2:19)
at Object.<anonymous> (/node_modules/gauge/lib/render-template.js:2:13)
at Object.<anonymous> (/node_modules/gauge/lib/plumbing.js:3:22)
at Object.<anonymous> (/node_modules/gauge/lib/index.js:2:16)
at Object.<anonymous> (/node_modules/npmlog/lib/log.js:3:13)
at Object.<anonymous> (/node_modules/node-gyp/lib/node-gyp.js:5:13)
at Object.<anonymous> (/node_modules/node-gyp/bin/node-gyp.js:8:13) {
code: 'ERR_REQUIRE_ESM'
}Read the stack from the bottom up: node-gyp loads npmlog, which loads gauge, which loads wide-align, which calls require('string-width'). The message tells you to switch that call to import(), but the file is inside node_modules and belongs to a package you do not control, so that advice does not apply. The asker also tried other Node versions and the bitnami/node image, and the error stayed.
Why it happens#
Three facts combine.
wide-align is CommonJS and wants string-width 4 or older. Its source starts with var stringWidth = require('string-width'), and its latest release on the npm registry, 1.1.5, declares the range ^1.0.2 || 2 || 3 || 4.
string-width 5 is an ES module. Its package.json at 5.1.2 sets "type": "module", and its entry point uses export default. On Node 18, require() refuses such a file and throws ERR_REQUIRE_ESM.
Some packages want both majors. @isaacs/cliui 8.0.2 depends on string-width ^5.1.2 and also on string-width-cjs, an npm alias for string-width@^4.2.0. When the tree also contains wide-align, Yarn has to place version 4 next to wide-align and version 5 where @isaacs/cliui can reach it.
The open issue yarnpkg/yarn#8994 shows the lockfile shape that breaks this: one entry starting "string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", ... that serves the alias and the plain ranges together. Fed that lockfile, Yarn 1 decides the plain string-width patterns would unpack into the same cache folder as the alias, skips them, and leaves version 5 as the only string-width it hoists. The same failure is reported against wide-align in iarna/wide-align#63, which is where the accepted answer points. Both issues are still open.
Why node-gyp runs at all: @parcel/watcher has node-gyp-build as its install script. The current node-gyp-build source tests for a usable prebuilt binary first. If there is none, it falls back to node-gyp rebuild, and it prefers a node-gyp it can resolve from the project. That project-local node-gyp is what loads npmlog, gauge and wide-align in the trace.
Reproduce it in a container#
This was reproduced on 21 October 2026 in the official node:18-slim image, which ships Node 18.20.8 and Yarn 1.22.22 (the asker used node:18.18). The project had two dependencies, @isaacs/cliui 8.0.2 and wide-align 1.1.5. A fresh install, and a yarn add on top of an existing lockfile, both wrote the alias as a separate entry and worked. The reproduction never saw Yarn write the merged entry by itself, and the issue does not say how it gets there. So the lockfile was edited by hand into the shape quoted in #8994:
71,80c71
< "string-width-cjs@npm:string-width@^4.2.0":
< version "4.2.3"
< resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
< integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
< dependencies:
< emoji-regex "^8.0.0"
< is-fullwidth-code-point "^3.0.0"
< strip-ansi "^6.0.1"
<
< "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0:
---
> "string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0:yarn install against that lockfile succeeds, but prints two warnings:
warning Pattern ["string-width@^4.1.0"] is trying to unpack in the same destination "/usr/local/share/.cache/yarn/v6/npm-string-width-cjs-4.2.3-269c7117d27b05ad2e536830a8ec895ef9c6d010-integrity/node_modules/string-width-cjs" as pattern ["string-width-cjs@npm:string-width@^4.2.0"]. This could result in non-deterministic behavior, skipping.
warning Pattern ["string-width@^1.0.2 || 2 || 3 || 4"] is trying to unpack in the same destination "/usr/local/share/.cache/yarn/v6/npm-string-width-cjs-4.2.3-269c7117d27b05ad2e536830a8ec895ef9c6d010-integrity/node_modules/string-width-cjs" as pattern ["string-width-cjs@npm:string-width@^4.2.0"]. This could result in non-deterministic behavior, skipping.Loading wide-align afterwards gives the asker's error, with /app as the project root:
/app/node_modules/wide-align/align.js:2
var stringWidth = require('string-width')
^
Error [ERR_REQUIRE_ESM]: require() of ES Module /app/node_modules/string-width/index.js from /app/node_modules/wide-align/align.js not supported.node_modules/wide-align has no nested node_modules folder, so the only string-width it can reach is the hoisted version 5.
Confirm your lockfile has the merged entry#
Two commands tell you whether this is your case. First, look at the alias line in yarn.lock. In the broken file it shares a line with the plain ranges:
grep -n 'string-width-cjs@npm' yarn.lock71:"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0:In a healthy lockfile the same command prints the alias alone:
71:"string-width-cjs@npm:string-width@^4.2.0":Second, ask Yarn which string-width it installed. In the broken install it finds only version 5, and nothing mentions wide-align:
yarn why string-widthinfo => Found "string-width@5.1.2"
info Reasons this module exists
- "@isaacs#cliui" depends on it
- Hoisted from "@isaacs#cliui#string-width"
- Hoisted from "@isaacs#cliui#wrap-ansi#string-width"Check the version with grep '"version"' node_modules/string-width/package.json, not with node -p "require('string-width/package.json').version". Version 5 has an exports map that does not list ./package.json, so that one-liner fails with a second error, ERR_PACKAGE_PATH_NOT_EXPORTED. If you hit it, the ERR_PACKAGE_PATH_NOT_EXPORTED page explains the exports map.
Fix#
1. Regenerate yarn.lock and commit it#
The accepted answer's workaround is to remove yarn.lock. The durable version is to regenerate it once and commit the result, not to keep the file out of the image. Run this on your machine or in a throwaway container with the same Yarn 1:
rm yarn.lock
rm -rf node_modules
yarn install
grep -n 'string-width-cjs@npm' yarn.lockFrom the broken lockfile above, the regenerated file wrote the alias as its own entry again, string-width 4.2.3 came back to the top of node_modules, and node -e "require('wide-align')" loaded. The cost is that every transitive dependency moves to the newest version its range allows, so run your lint and tests before you commit. In yarnpkg/yarn#8994, one user reports that deleting the lockfile helped but the error came back later, so keep the grep check above in mind after future upgrades.
2. Pin string-width with resolutions#
If you cannot regenerate the whole lockfile now, force a single version, as the accepted answer suggests. Yarn 1 reads selective version resolutions from package.json:
{
"resolutions": {
"string-width": "4.2.3"
}
}Merge that key into your existing package.json, then run yarn install. From the same broken lockfile it printed:
warning Resolution field "string-width@4.2.3" is incompatible with requested version "string-width@^5.1.2"
warning Resolution field "string-width@4.2.3" is incompatible with requested version "string-width@^5.0.1"
success Saved lockfile.Those warnings are expected. The pin applies to every string-width in the tree, including the ones that ask for 5, and the Yarn docs say a resolution that is not compatible with the original range produces a warning. In the reproduction @isaacs/cliui still rendered text with 4.2.3, but that is one small check, not a guarantee for every package in your tree. Treat the pin as temporary and remove it once the lockfile is clean.
The other answers on the question are untested here. One moves the project to Yarn 3.6.4. Another pins jackspeak to 2.1.1. On the npm registry, jackspeak 2.1.1 depends on cliui, while the 2.2.0 release and later depend on @isaacs/cliui, the package that brings the alias. The accepted answer gives "overrides" as the npm equivalent of resolutions.
Verify the fix#
Inside the image, check that wide-align can load and which string-width sits at the top:
grep '"version"' node_modules/string-width/package.json
node -e "require('wide-align'); console.log('wide-align loaded')" "version": "4.2.3",
wide-align loadedThen rebuild the image without cache so the install step actually runs again:
docker build --no-cache -t angular-lint .For comparison, npm does not trip over this project. With no lockfile, npm install hoisted string-width 5.1.2 to the top and nested 4.2.3 under wide-align:
node_modules/string-width/package.json: "version": "5.1.2",
node_modules/wide-align/node_modules/string-width/package.json: "version": "4.2.3",That is the layout Yarn should have produced. It confirms the fault is in the lockfile, not in wide-align or Node 18.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
On Node 22 the error changes to a TypeError#
Upgrading the base image looks like an escape route, because the Node versions released after the question was asked can require() an ES module. The Node.js modules documentation says this no longer needs a flag from v22.12.0, and adds: "The result returned by require() is the module namespace object, which places the default export in the .default property". string-width 5 exposes its function only as the default export, so wide-align gets an object where it expects a function.
With the same broken lockfile on node:22-slim (Node 22.23.3, Yarn 1.22.22), require('wide-align') loads without complaint, and the first call fails:
/app/node_modules/wide-align/align.js:56
var strWidth = stringWidth(trimmed)
^
TypeError: stringWidth is not a function
at Object.alignCenter [as center] (/app/node_modules/wide-align/align.js:56:18)The failure moves from the require() to the first call. Whether the node-gyp install path ever reaches that call was not tested, so a newer image may or may not get the install through, and it does not repair the tree. If a Yarn 1 project on Node 22 or later reports stringWidth is not a function from wide-align, it is likely the same hoisting problem: check with the grep above, then apply the same two fixes.
Check your own module error#
If your ERR_REQUIRE_ESM comes from your own code rather than from node_modules, put the full message and its first at lines into the explainer below to see which file Node loaded as CommonJS and which as an ES module. It runs in your browser, and nothing is sent anywhere.
Runs entirely in your browser — nothing you paste is sent anywhere. Tokens and passwords that appear in paths or URLs are masked in the output.
Optional: package.json, file extension, Node.js version (sharper advice)
For an ESM-only package that your own require() loads, the gulp ERR_REQUIRE_ESM page walks through the pin-or-migrate choice with gulp-imagemin. If the error is the reverse, ES module code that meets a CommonJS global, see exports is not defined in ES module scope. For other installs that work on the host and fail in a container, the esbuild-linux-64 page covers a host node_modules leaking into a Linux image through a bind mount, and Next.js builds locally, fails in Docker covers next build crashing in the container on a statically imported image until sharp is added.
The #8994 thread is also the best evidence on the "works locally, fails in the container" part of the question: one user reports that yarn upgrade fixed the problem locally while their CI machine kept failing, and another fixed a NestJS Docker build by not copying yarn.lock into the image. Neither the thread nor the question shows exactly why the host install survived. What the reproduction does show is the end state to aim for: once yarn.lock has separate entries for string-width-cjs and string-width, a clean yarn install puts string-width 4.2.3 where wide-align can load it. The reproduction project had no @parcel/watcher, no node-gyp build and no lint step, so whether your full docker build then gets through yarn lint is not tested here.
Further reading: Fix print "%s.%s.%s" invalid syntax in yarn install. Also see Fix RangeError: Invalid time zone specified in Node.js.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix ERR_REQUIRE_ESM in gulp: ESM-only plugins, old gulp-cli
Why renaming to gulpfile.mjs still fails on Node 24 with gulp 4, what changes on newer Node, and three fixes reproduced in Docker.
Fix RangeError: Invalid time zone specified in Node.js
Find which of the three causes threw the error, from the value printed after the colon, and make toLocaleString and Intl.DateTimeFormat accept the zone again.
Fix Docker npm EBADENGINE: Unsupported engine
Read the Required and Actual lines, distinguish npm self-upgrades from application installs, and stop a floating npm release breaking an unchanged Dockerfile.


