Releases: Automattic/juice
Releases · Automattic/juice
v12.1.1
v12.1.0
v12.0.0
Juice 12
Modernizes the entire stack: ESM-only, PostCSS-based parsers to support CSS Nesting + spec-correct selector specificity, Node 22.12+ floor, Vitest 4.
Breaking changes
- Juice is now ESM-only. CJS consumers on Node ≥ 22.12 keep working transparently via Node's built-in
require(esm)interop. Older Node versions need to upgrade. - Node ≥ 22.12.0 required. Drops support for Node 18 and Node 20. CI matrix is now Node 22, 24, 26.
- Browser:
client.jsis ESM. Modern bundlers (Vite, webpack 5, esbuild, Rollup, Parcel 2+) handle it via the"browser"condition in theexportsmap. Browserify is no longer supported, it cannot parse ESM. README updated to point at modern bundlers. - CSS parser swapped:
mensch→postcss+postcss-safe-parser. Selector parser swapped:slick→postcss-selector-parser. Both old libs were unmaintained since 2022. Inlining semantics unchanged; preserved-CSS output inside<style>blocks is now canonically formatted (mensch had quirks like;}squashed onto one line). 4 fixture.outfiles refreshed to match. - Spec-correct selector specificity for
:is(),:where(),:has(),:not()per CSS Selectors Level 4::where(...)contributes 0, the others contributemax(spec(args)). Previously juice treated all four as a generic:pseudoand:notused "first arg only" (a slick legacy). Any cascade resolution that depended on the old quirks will produce different inlined styles. commanderupgraded to v14,entitiesupgraded to v8 (ESM-only),cheeriopinned to1.2.0.- TypeScript declarations renamed
juice.d.ts → index.d.tsand restructured for ESM default-export resolution. Import asimport juice from 'juice'.
New features
- CSS Nesting (Level 1) is supported. Nested rules (
.card { &:hover { ... } }), nested at-rules (.card { @media (...) { ... } }), the&parent selector, and bare nested selectors (per the 2023 CSSWG resolution) are flattened viapostcss-nestingbefore inlining. Previously these were silently dropped. No behavior change for already-flat CSS. @containerand@layerat-rules are preserved through inlining (mirrors how@media/@font-face/@keyframesalready worked). New optionspreserveContainerQueriesandpreserveLayersdefault totrue.- Long-standing crashes/hangs in
:not(a, b, …)are gone as a side effect of the parser swap — the underlying causes of #390, #471, and #398 were quirks in mensch/slick that don't exist in postcss. - TypeScript types ship via the
typescondition inexportsand resolve correctly undernodenextmodule resolution.
Tooling
- Test runner: Mocha → Vitest 4 with
@vitest/coverage-v8. New scripts:npm test,npm run test:watch,npm run coverage. The previously brokentestcoverscript is gone. - CLI extraction:
bin/juice's logic moved intolib/cli.jsas a testablecli.run(argv, deps)with dependency injection. The bin itself is now a 3-line ESM shim. - Test files renamed:
cli.js → cli.test.js,test.js → integration.test.js,run.js → cases.test.js. TypeScript test now uses a dedicatedtest/typescript/tsconfig.jsonwithnodenextresolution. - Test assertions migrated from Node's
assertto Vitest'sexpect: better failure diffs, more matchers, idiomatic. - Removed devDependencies:
mocha,should,batch,browserify. Added:vitest,@vitest/coverage-v8,postcss,postcss-safe-parser,postcss-selector-parser,postcss-nesting.
Migration notes
| Scenario | Action needed |
|---|---|
| Node 18 or 20 user | Upgrade to Node 22.12+ |
const juice = require('juice') on Node ≥22.12 |
None, require(esm) handles it |
const juice = require('juice') on older Node |
Upgrade Node, or switch to import juice from 'juice' |
TypeScript import juice = require('juice') |
Switch to import juice from 'juice' |
juice/client via Browserify |
Switch bundler (Vite, webpack 5, esbuild, Rollup, Parcel 2+) |
Using :is/:where/:has in email CSS |
Specificity is now per-spec; cascade may resolve differently |
Using @container or @layer |
They now pass through inlining instead of being silently dropped |
| Using CSS Nesting | Was silently dropped — now flattened and inlined correctly |
Fixes #390, fixes #392, fixes #398, fixes #403, fixes #471, fixes #557, fixes #587, fixes #593
v12.0.0-beta.2
v12.0.0-beta.1
- fix: apply default options consistently across all client functions 04ebded
v12.0.0-beta.0
This is the first beta for Juice 12.
For details on this release, see the PR:
- feat: support CSS nesting via postcss-nesting 7ebefd5
- test: migrate assertions from node assert to vitest expect c25f011
- chore: drop redundant 'use strict' directives (ESM is strict by default) 879ff57
- test(numbers): cover romanize NaN early-return d923614
- test(selector): close coverage gaps in lib/selector.js 8932d29
- test(utils): close coverage gaps in lib/utils.js 1f6ca6d
- test(inline): close coverage gaps in lib/inline.js e8c5846
- test(index): cover error paths and codeBlocks merge c3ff857
- chore: update contributors list 7e49a76
- feat!: modern CSS specificity + at-rule preservation d306f78
- refactor!: replace mensch+slick with postcss 1926c82
- refactor!: convert to ESM-only ffe9d67
- build(deps): bump cheerio to 1.2.0 f2f16e6
- chore: require node >=22.12.0 34ab2b2
- build(deps): upgrade commander to ^14 6894ca9
- refactor(cli): extract bin into testable cli.run() for coverage e030df3
- test: migrate to vitest 4 with v8 coverage b8eefd1
- build(deps-dev): bump @types/node from 25.3.3 to 25.3.5 de6d8d7
- build(deps-dev): bump @types/node from 25.3.0 to 25.3.3 e42ecf6
- build(deps-dev): bump @types/node from 25.2.3 to 25.3.0 18a1c32
- build(deps-dev): bump @types/node from 25.2.0 to 25.2.3 bd54313
- chore: update changelog.md f093f01
v11.1.1
v11.1.0
v11.1.0-2
v11.1.0-1
This pre-release adds the preservedSelectors option, which can be used to preserve CSS rules in <style> tags when removeStyleTags or removeInlinedSelectors are true.
For example, this:
juice(`
<style>
div { color: red; }
.preserve-me { background: blue; }
</style>
<div class="preserve-me">Test</div>
`,
{
removeStyleTags: true,
preservedSelectors: ['.preserve-me']
}
)... would return this HTML:
<style>
.preserve-me { background: blue; }
</style>
<div class="preserve-me" style="color: red; background: blue">Test</div>