feat: make less, sass, and stylus optional - #8
Merged
Conversation
The preprocessor wrappers already lived in separate modules so they would only
load when used, but that never reduced what consumers installed: JSR resolves a
package's dependencies as one flat set across every export. A project importing
only `postCSSPlugin` still resolved less, sass, and stylus — 53 of the 129 npm
packages in its graph, for features it never touched.
Splitting entry points further cannot fix that, and neither can a dynamic
import, since JSR analyses the whole module graph. The package has to stop
referencing the preprocessors, so the caller now passes the module in:
import * as sass from "sass";
sassPreprocessor(sass)
Each wrapper declares the small structural interface it actually calls, which
the real module satisfies. Annotate the options generic with the preprocessor's
own option type to keep full checking:
`sassPreprocessor<sass.Options<"async">>(sass, { ... })`.
This is a breaking change for anyone using a preprocessor. Consumers add the
preprocessor to their own dependencies — which is the point: they now pick the
version, and projects that build plain CSS carry none of it.
Also drops the brace-expansion advisory (CVE-2026-14257, reachable only through
stylus -> glob@10 -> minimatch@9) from every consumer that doesn't use Stylus.
Anyone who does use it inherits that choice explicitly, which is the right place
for it. Closes #7.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces the publish workflow, which ran `npx jsr publish` on every push to main and so shipped whatever version happened to be in deno.json, with the semantic-release setup juniper uses: the version and changelog are derived from Conventional Commit messages, and the release only runs after CI passes. - `feat` cuts a minor, `fix`/`perf`/`revert` and `chore(deps)`/`build(deps)` a patch, and a breaking change a major. - semantic-release runs on Node rather than Deno — semantic-release-jsr shell-spawns its own `deno publish`, whose args Deno's child_process compat layer mangles. Its npx packages are pinned exactly, since npx has no lockfile and the job holds `id-token: write`. - Adds the PR-title check so a squash-merged PR title is a valid commit message, which is what the version is computed from. No new repository secrets. Publishing is OIDC (`id-token: write` with the package linked to this repo), and @semantic-release/git pushes the release commit with the checkout-persisted GITHUB_TOKEN under `contents: write`. Unlike juniper, this repo's `main` is unprotected, so no SSH deploy key is needed — that only becomes necessary if `main` is protected later, since GITHUB_TOKEN cannot push past branch protection. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
KyleJune
force-pushed
the
feat/optional-preprocessors
branch
from
July 25, 2026 19:34
adf096c to
a2549b9
Compare
|
🎉 This PR is included in version 0.4.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes
less,sass, andstylusoptional — what the split entry points were always meant to achieve — and switches releases to semantic-release.Closes #7.
Why the entry-point split wasn't enough
postcss.tsimports no preprocessor and each wrapper imports exactly one, so the modules were already correct. But JSR resolves a package's dependencies as one flat set across every export, so that split never reduced what anyone installed. Two confirmations:@udibo/esbuild-plugin-postcssstill resolved all three.Measured cost: a consumer importing only
postCSSPluginresolves 129 npm packages, 53 of them the less/sass/stylus subtree — 41% of the graph for features it never touches.Splitting further can't fix it, and neither can a dynamic
import()(JSR analyses the whole graph). The package has to stop referencing the preprocessors.The change
The caller passes the module in:
Each wrapper declares the small structural interface it actually calls, which the real module satisfies. Options stay fully typed via the generic:
Breaking for anyone using a preprocessor — they add it to their own dependencies. That's the point: they pick the version, and plain-CSS projects carry none of it.
deno infonow reports zero preprocessor deps in the graph of all four exports. The import map keeps them for the tests, which is safe: JSR derives deps from the published module graph, not the import map — visible today in that@std/assert,autoprefixer, andtailwindcssare all in the import map but absent from JSR's dep list.Release automation
publish.ymlrannpx jsr publishon every push to main, shipping whatever version was indeno.json. Replaced with juniper's setup: version and changelog derived from Conventional Commits, release gated behind CI.feat→ minor,fix/perf/chore(deps)→ patch, breaking → major. Adds the PR-title check, since a squash-merged title becomes the commit the version is computed from.Versioning note
The
feat:commit cuts 0.4.0, not 1.0.0 — I deliberately did not mark itfeat!, since on 0.x semantic-release maps a breaking change to 1.0.0 and I didn't want to declare 1.0 on your behalf. The break is documented in the commit body, the changelog entry, and the README. Say the word if you'd rather this be 1.0.0 and I'll amend tofeat!.Testing
deno lint,deno fmt --check, anddeno checkclean. Tests: 5 passed, same as before the change.One pre-existing failure, unchanged by this PR:
tailwindcss 4 … should add tailwindcss to the cssfails locally with an esbuildObject.prototype.__proto__error. It fails identically on a pristine checkout and CI is green on main, so it's a newer-local-Deno issue rather than a regression — worth a separate look.Before merging
The release job needs a
DEPLOY_KEYrepository secret so semantic-release can push the release commit back to main.🤖 Generated with Claude Code