Skip to content

Upgrade the misc toolchain: Style Dictionary 5, cssnano 8, sass, js-yaml 5, npm-run-all2 9 - #2422

Merged
spaceninja merged 8 commits into
mainfrom
upgrade-misc-toolchain
Aug 21, 2026
Merged

Upgrade the misc toolchain: Style Dictionary 5, cssnano 8, sass, js-yaml 5, npm-run-all2 9#2422
spaceninja merged 8 commits into
mainfrom
upgrade-misc-toolchain

Conversation

@spaceninja

Copy link
Copy Markdown
Member

Overview

This is item 5 of #2391 — the last group of ordinary dependency upgrades before removing Gulp. Five packages move: js-yaml 4→5, npm-run-all2 8→9, sass 1.102→1.103, cssnano 5→8, and style-dictionary 3→5. Only the last two are more than a version bump, and both change files we publish, so each commit records what moved in the output and why. They are one commit each so the two interesting ones can be read on their own.

Style Dictionary 4 was a rewrite, so that one is a migration: the package is ESM-only, the build hooks were all renamed, and reference syntax dropped its .value suffix. Two of its changes were silent rather than loud, which is the part worth a reviewer's attention. Broken references are now an error instead of a warning, which is what surfaced 25 stale references that had been resolving by luck. More subtly, the built-in color/css transform stopped matching our tokens at all — its filter used to key off the CTI attributes.category and now requires an explicit type: 'color' that our tokens don't declare — so the build stayed green while colours quietly passed through unnormalised. It is re-registered with the old filter, which is why the generated Sass is byte-identical.

cssnano 8 is the only change to published output. It grows dist/standalone.min.css by 3.2kB raw, 227 bytes gzipped, almost entirely because the newer SVGO keeps the viewBox on inlined SVGs where the old one stripped it. That is a fidelity fix rather than a visible one — rendered and compared pixel for pixel, the old and new bundles differ only by antialiasing — but it means the minified bundle is once again a faithful minification of the unminified one. The remaining differences are all in the same direction: columns and currentColor now survive as authored instead of being rewritten.

Screenshots

Testing

Most of this is checkable on the deploy preview, but one part is not: Storybook compiles Sass through Vite and never loads dist/standalone.min.css, which is the only file cssnano changed. The last section covers that separately.

Design tokens, on the deploy preview — these exercise the Style Dictionary migration end to end, since the pages read the generated token files:

  • Open Design → Colors. Every swatch should show a colour with a hex or rgba(…) value beside it — no blanks, no undefined, and nothing rendering as black or transparent that shouldn't.
  • Open Design → Tokens → Size. The table should list every size token with a name, a value, and comments where they exist.
  • Open Design → Tokens → Modular Scale and Design → Tokens → Z-index. Both should show full tables rather than empty ones.
  • Open Design → Tokens → Aspect Ratio and note the ratios listed, then open Objects → Embed. Its aspect-ratio dropdown should offer exactly those ratios and nothing else.

Components that use an inlined SVG background:

  • Open Components → Card → Cloudy. The cloud illustration should sit flush in the bottom-right corner, fully drawn — not cut off, squashed, or stretched.
  • Open Components → Cloud Cover. The clouds should sit along the bottom edge at their normal size.
  • Open Components → Checkbox and tick a box. The check mark should be centred inside the box, not clipped at an edge.
  • Open Components → Pagination. The previous and next arrows should both render at full size.

The published minified stylesheet, locally — this is the part the preview can't show:

  • Run npm run build, then save the snippet below as check.html in the project root and open it in a browser. The blue and white clouds should be fully drawn and anchored to the bottom-right of the bordered box.
<linkrel="stylesheet" href="dist/standalone.min.css" /><divclass="c-card c-card--cloudy"
style="position: relative; inline-size: 420px; block-size: 200px; border: 1px solid #ccc"
></div>
  • Change the stylesheet in that file to dist/standalone.css and reload. It should look the same. The two bundles agreeing is the point of the cssnano change; they should no longer drift apart.
  • Delete check.html when you're done — it's a scratch file, not something to commit.

The only call site is `yaml.load` in the svgToTwig gulp task, reading
`.svgo.yml`. All 72 generated `.svg.twig` files are byte-identical.
Requires Node ^22.22.2 || ^24.15.0 || >=26, satisfied by the 24.19.0 in
`.nvmrc` and CI. All `run-s`/`run-p` globs still expand, including the
nested `lint:*:check` pattern.
`dist/standalone.css` and `standalone.min.css` are byte-identical.
Changes `dist/standalone.min.css`. Every difference was accounted for
against the unminified stylesheet, which is byte-identical:
- Inline SVG data URIs are percent-encoded more thoroughly (spaces, quotes,
`=`, `:`, `/`). They decode identically.
- 23 of 25 inline SVGs regain the `viewBox` the source SVGs declare. The
older bundled SVGO stripped it via `removeViewBox`, which is why that
plugin left `preset-default` upstream.
- A trailing `Z` is dropped from 9 paths, all fill-only, where it is a no-op.
- Path coordinates round slightly: 0 structural changes, largest delta 0.05
user units.
- `columns: N` and `border: … solid currentColor` now survive as authored;
cssnano 5 rewrote them to `column-count` and dropped `currentColor`.
- Custom property values are no longer minified, which is the safer reading
of an arbitrary token stream.
Rendered old and new in Chromium: 11 of 250,000 pixels differ, max channel
delta 6/255, and the new output is closer to the unminified reference than
the old one was. Raw size +3.2kB, but only +227 bytes gzipped.
Style Dictionary 4 was a rewrite, so this is a migration rather than a bump.
The package is ESM-only, hence the rename to `.mjs`, matching
`eslint.config.mjs` and `.storybook/main.mjs`.
API moves: `.extend(config)` becomes `new StyleDictionary(config)` with an
awaited `buildAllPlatforms()`; `transformer`/`formatter` become
`transform`/`format`; formats receive `{ dictionary }` and read `allTokens`
and `tokens` rather than `allProperties` and `properties`; and the built-in
`name/cti/kebab` is now `name/kebab`.
Two changes were needed beyond renaming:
- References drop the `.value` suffix, so all 25 of them across seven token
files become e.g. `{color.base.blue}`. Broken references are also an error
rather than a warning now, which is what surfaced these — and is why the
config no longer needs `log: 'warn'`.
- `color/css` no longer applies. Its filter used to match our CTI
`attributes.category`, but now requires an explicit `type: 'color'` that
our tokens do not declare, so colors would have passed through exactly as
authored (`#3d84F5`, `#000`, `rgba(0, 0, 0, 0.60)`). Re-registered as
`custom/value/color/css` with the category filter.
`name/kebab` also switched from lodash to change-case, which does not split
letter/digit boundaries. That is a no-op here: it emits `size-n1` directly
instead of the `size-n-1` that `custom/name/i/kebab-rejoin-n` existed to
repair, and the regex simply finds nothing.
Output: all 15 SCSS files and `tokens.json` are byte-identical apart from the
header, which loses its build timestamp and so is now deterministic. `dist`
is unchanged in full. The two JS token files gain a `key` field on each leaf
token — group nodes are unaffected, so the `Object.keys` in embed's story
still yields only the aspect ratios — and 40 `original.value` entries record
the new reference syntax. No resolved value changed anywhere.
Matches the other 63 devDependencies; `npm install` had added carets.
The viewBox restoration is a fidelity fix, not a visible one: measured
against the previous bundle it is an antialiasing-level difference.
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0205aa6

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

This PR includes changesets to release 1 package
NameType
@cloudfour/patternsPatch

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

@netlify

netlifyBot commented Aug 21, 2026

Copy link
Copy Markdown

Deploy Preview for cloudfour-patterns ready!

NameLink
🔨 Latest commit0205aa6
🔍 Latest deploy loghttps://app.netlify.com/projects/cloudfour-patterns/deploys/6a88da1433352e00088d9d48
😎 Deploy Previewhttps://deploy-preview-2422--cloudfour-patterns.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@spaceninja
spaceninja merged commit 3492198 into mainAug 21, 2026
8 checks passed
@spaceninja
spaceninja deleted the upgrade-misc-toolchain branch August 21, 2026 23:17
@github-actionsgithub-actionsBot mentioned this pull request Aug 21, 2026
spaceninja added a commit that referenced this pull request Aug 22, 2026
* Replace gulp with plain Node build scripts
Removes the last of the gulp toolchain: 280 lines across 5 tasks, and 8
packages (gulp, gulp-postcss, gulp-rename, gulp-run, gulp-sass, gulp-svgmin,
through2, tiny-glob), which takes 199 packages out of the tree.
Each task moves to a script in `scripts/`, matching the `.mjs` convention
`.style-dictionary/build.mjs` already set:
- `buildJS` and `buildTypes` were already plain Rollup API, so they only
needed converting to ESM. They share the virtual root entry via
`rollup-root-entry.mjs`.
- `buildSass` calls sass and postcss directly, matching what gulp-sass and
gulp-postcss were doing: compile, run `postcss.config.js`, write
`standalone.css`, then minify that with cssnano.
- `buildTokens` was a 5-line wrapper shelling out to `preprocess:tokens`, so
it is simply gone; the watcher runs the token build directly.
- `watchPreprocess` uses `node:fs.watch` with `recursive: true` rather than
taking on a watcher dependency. It was the only thing in the build needing
one.
`tiny-glob` is replaced by `node:fs.glob`, which finds the same 63 files. The
results are now sorted explicitly: the entry file is built by joining those
paths, so the published bundle and type declarations were quietly depending
on one glob library's traversal order. Sorting reproduces the previous order
exactly.
All of `dist` is byte-identical, as are the generated token files.
The SVG task is the exception. `gulp-svgmin` pins SVGO 2, so taking svgo as a
direct dependency means SVGO 4 -- and SVGO 4 rejects the `.svgo.yml` plugin
format outright rather than warning, so the config fix and the upgrade cannot
be separated. `.svgo.yml` now uses the current format, which has three
consequences:
- `removeXMLNS` finally applies. It has been silently dropped for years,
because it is not a `preset-default` plugin and the old format could only
express preset overrides. These partials are only `{% include %}`d into
HTML, where the namespace is implied.
- `removeViewBox: false` is no longer needed. SVGO 4 dropped that plugin from
`preset-default`, so viewBox is preserved by default -- which is what this
config already asked for, and what #2422 restored in the minified CSS.
- Coordinates round slightly differently. Rendered and compared pixel for
pixel, 55 of the 72 partials are unchanged, and the largest difference in
the rest is 0.53% of pixels at the edges of curves.
Verified with a headless sweep: all 337 entries (249 stories, 82 docs) render
with content and zero page errors.
* Update the project structure in CONTRIBUTING
Beyond replacing gulpfile.js with scripts/, the tree had drifted: Style
Dictionary's files became .mjs in #2422, .storybook/main became .mjs in
#2405, and stories stopped being .stories.mdx in the same PR.
* Add a changeset for the SVG partial changes
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@spaceninja