Repository navigation
PoC: Move CSS linting from server to client-side - #367
bartveneman wants to merge 5 commits into
Conversation
Drives stylelint's linting core directly (lintPostcssResult, normalizeAllRuleSettings, createPartialStylelintResult, prepareReturnValue) instead of routing through standalone()/lint(), which statically pulls in globby, cosmiconfig and other Node-only deps. The four internals aren't part of stylelint's public exports map, so they're aliased to the real files on disk in vite.config.js and pinned to stylelint ~17.13.0 via stylelint-internals.d.ts. Replaces the /api/lint-css POST round-trip: the Linter component now fetches CSS (for URL input) via the existing client-side get_css() helper and lints it in-browser with src/lib/stylelint-browser/lint.ts, cutting out a full server round-trip per lint. CSS-scraping stays server-side in /api/get-css (Node-only deps, CORS). Includes browser-safe shims for node:os, node:process and stylelint's own bare-specifier entry point (needed because @projectwallace/stylelint-plugin's rule files import `stylelint` just to reach createPlugin/utils), plus a stub for stylelint's CLI timing/table output which isn't reachable from the browser and doesn't bundle cleanly.
✅ Deploy Preview for projectwallace ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
The Audit check is failing on the pre-existing high-severity Generated by Claude Code |
…able Added a dedicated pinned-transitive-deps.ts, imported for its side effect from lint.ts, that imports css-tree, @csstools/css-syntax-patches-for-csstree, @csstools/css-tokenizer and fastest-levenshtein - packages stylelint's own internals (getLexer.mjs, assignDisabledRanges.mjs, reportUnknownRuleNames.mjs) import by name but our own code never did directly. Without a real usage site, dependency-usage tooling has no way to tell they're needed rather than stray entries, and pnpm's strict node_modules requires them declared in our own package.json regardless (they're normally hoisted transitively, but only for packages we actually import from). Also drops `table` from dependencies: it was added while investigating why stylelint/lib/timing.mjs doesn't bundle for the browser, but the fix ended up aliasing that whole relative import away (see vite.config.js), so the real `table` package was never reachable to begin with.
stylelint published an actual 18.0.0-next.0 prerelease (under the `next` dist-tag) with the same Node-dependency cleanup we verified on the v18 branch. Bumping to the real published version lets two of our browser workarounds go: - `timing.mjs` no longer pulls in `table` (→ supports-hyperlinks → supports-color, which never bundled for the browser) - it's gone from stylelint's dependencies entirely, and the import became a runtime-gated dynamic import() besides. Dropped our alias and shims/stylelint-timing.ts; confirmed zero trace of it in the built bundle. - `reportUnknownRuleNames.mjs` now uses an in-house levenshteinDistance.mjs instead of the `fastest-levenshtein` package - dropped that pin too. Everything else from the original browser driver is still required as-is: stylelint's `exports` map, the node:os/node:process imports in the core lint path, and createPlugin's dependence on the bare `stylelint` import are all unchanged in this prerelease. Needed a temporary minimumReleaseAgeExclude entry in pnpm-workspace.yaml - the supply-chain policy blocks packages published less than 7 days ago, and this prerelease is 2 days old. Worth revisiting once 18.0.0 is tagged stable or this entry has aged out on its own. Verified: full check/lint/format/vitest/build pass, and a real-browser smoke test (raw CSS, clean CSS, parse errors, URL mode) against the actual published package shows identical behavior and zero console errors.
… browser driver main drifted significantly since this PR opened: $lib/$components were replaced by Node `imports`-map subpaths (#lib/*, with explicit file extensions), pnpm-workspace.yaml adopted a `catalog:` section, svelte.config.js was folded into vite.config.js, and SvelteKit/adapter-netlify moved to "next" prereleases ($app/environment became $app/env along the way). Resolved conflicts: - package.json/pnpm-workspace.yaml: kept main's structure, layered our additions on top (stylelint in dependencies at 18.0.0-next.0 instead of main's independent 17.15.0 devDependency bump; postcss/css-tree/@csstools pins; the minimumReleaseAgeExclude entry for the prerelease) - vite.config.js: kept main's adapter/csp/preprocess config as-is (including the edge:false/split:true stylelint-specific comments - not touched, worth revisiting separately now that linting is client-side) and added our resolve.alias block alongside it - src/routes/api/lint-css/+server.ts: kept deleted (main only modernized its imports/response helper, no logic change - moot once the file is gone) - src/lib/components/Linter.svelte: auto-merged cleanly; migrated our newly added import lines (get_css, lint, PRESET_MAP) to #lib/*.ts, which main couldn't do since those lines didn't exist on main - src/lib/stylelint-browser/presets.ts: same #lib/*.ts migration - pnpm-lock.yaml: regenerated via `pnpm install` on top of main's lockfile Also fixed two `curly` lint violations in our own files that main's oxlint config now catches (lint.ts, Linter.svelte) - everything else flagged by lint:ts/format:check is pre-existing on main in files we never touched. Verified: pnpm check / lint:ts (our files) / format:check (our files) / test:unit (117 tests) / build all pass. KNOWN ISSUE: `vite dev` is currently broken for /lint-css specifically - see follow-up notes, not yet pushed.
|
Audit is failing on advisories unrelated to this PR's changes (sharp/librsvg, devalue, vite/launch-editor) — confirmed by installing Generated by Claude Code |
|
Lint JS (which also runs stylelint against every Root cause: This is a real blocker, not a flake, and not something a local fix in this PR can paper over without either patching Generated by Claude Code |
stylelint-config-standard@40.0.0 (peerDependencies: ^17.0.0) references
declaration-block-single-line-max-declarations, which 18.0.0-next.0 removed
outright - breaking the repo's own Lint CSS/JS CI check on ~130 call sites,
unrelated to anything this PR's own code touches. Reverting to the last
stable 17.16.0 un-breaks that (re-added fastest-levenshtein and
shims/stylelint-timing.ts, both dropped when bumping to 18 since that
prerelease's own internals briefly didn't need them).
Separately, and more seriously: production builds of /lint-css 404 on
every lint that hits a not-yet-loaded built-in rule (confirmed via a real
build+preview+browser run, not just `vite dev`). Root cause has nothing to
do with the stylelint version - it reproduces identically on 17.13.0,
17.16.0 and 18.0.0-next.0 alike. stylelint resolves built-in rules lazily
via `import(\`./${ruleName}/index.mjs\`)`, a dynamic import whose path is
only known at runtime; Rollup can't statically enumerate that, and because
the real file lives inside node_modules, Vite's dynamic-import-vars
handling skips it rather than glob-expanding it. So no per-rule chunk ever
gets emitted, and the browser 404s fetching one.
Fixed by aliasing stylelint's lib/rules/index.mjs to a new
shims/stylelint-rules.ts that uses `import.meta.glob` over the real rule
files instead - a literal pattern Vite/Rollup *can* statically expand into
one real, analyzable dynamic import per rule, restoring correct lazy
per-rule chunking (confirmed: real per-rule chunks now show up in the
build, and a real-browser run against vite preview resolves warnings,
clean-CSS, and parse-error cases with zero console/network errors).
Verified: full check/lint:ts/format:check/stylelint/vitest/build pass, and
a direct chromium run against the production preview build confirms all
three Linter.svelte code paths work with no console or network errors.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YSKEQXzpmfmag1HcDNUaQ
|
Pushed a fix for both outstanding blockers. Lint JS/CSS (declaration-block-single-line-max-declarations unknown-rule failures): reverted Playwright e2e failures (all 4 failures were in this PR's own Fixed by aliasing stylelint's Verified locally: full Generated by Claude Code |
Summary
This PR moves stylelint-based CSS linting from a server-side API endpoint to client-side execution in the browser, eliminating the need for a backend linting service while reducing bundle dependencies.
Key Changes
New browser-based linting module (
src/lib/stylelint-browser/lint.ts): Implements a direct stylelint core runner that bypasses thestandalone()entry point and its file-resolution machinery (globby, cosmiconfig, write-file-atomic). This deep-imports stylelint internals (lintPostcssResult,normalizeAllRuleSettings,createPartialStylelintResult,prepareReturnValue) and pins their shapes via TypeScript declarations.Removed server API endpoint (
src/routes/api/lint-css/+server.ts): The POST endpoint that previously handled linting requests is no longer needed.Updated Linter component (
src/lib/components/Linter.svelte): Refactored to call the new client-sidelint()function directly instead of making HTTP requests. CSS fetching (viaget_css) remains client-side, and formatting is applied before linting.Vite configuration updates (
vite.config.js): Added resolve aliases to:node:os,node:process) and stylelint's timing modulestylelintspecifier to a shim that exposes only the public plugin APIBrowser shims (new files in
src/lib/stylelint-browser/shims/):node-os.ts: MinimalEOLexportnode-process.ts: Stubs forenv,noDeprecation,emitWarning,on,hrtimestylelint-timing.ts: No-op timing module to avoid pulling in thetablepackagestylelint-shim.ts: ExposescreatePluginand public utils without loading Node-only dependenciesPreset configuration (
src/lib/stylelint-browser/presets.ts): Extracted preset-to-rules mapping into a shared module used by both the component and tests.Test updates (
src/routes/(public)/lint-css/spec.ts): Updated to mock the CSS-fetching API instead of the linting API, and adjusted assertions to match real stylelint output.Unit tests (
src/lib/stylelint-browser/lint.test.ts): Added smoke tests verifying the deep-imported stylelint internals work correctly with the plugin.Notable Implementation Details
stylelint-internals.d.ts) manually pin the shapes of unpublished stylelint internals to stylelint ~17.13.0, with instructions to re-verify on version bumps.resolve.aliasis used (not a custom plugin resolver) because the dependency optimizer pre-bundles packages before the plugin pipeline runs.extendsresolution, custom syntaxes, caching, ignore files, or autofix support—the implementation focuses on the core linting use case.https://claude.ai/code/session_015YSKEQXzpmfmag1HcDNUaQ