feat(css): export PostCSS config type for type-safe configs#22792
feat(css): export PostCSS config type for type-safe configs#22792linyiru wants to merge 4 commits into
Conversation
f3fc791 to
ec6bbc4
Compare
ec6bbc4 to
28d864f
Compare
|
Can we replace |
| * ``` | ||
| * | ||
| * `postcss-load-config` is bundled into Vite and its types are declared with | ||
| * CommonJS `export =` syntax, so they cannot be re-exported through the bundled | ||
| * `.d.ts`. This mirrors its `Config` type instead, and a type-level test in | ||
| * `__tests_dts__/postcss.ts` asserts the two stay structurally identical. | ||
| */ |
There was a problem hiding this comment.
I don't think this part of the comment is suitable for the public API, so we should remove it
| * ``` | |
| * | |
| * `postcss-load-config` is bundled into Vite and its types are declared with | |
| * CommonJS `export =` syntax, so they cannot be re-exported through the bundled | |
| * `.d.ts`. This mirrors its `Config` type instead, and a type-level test in | |
| * `__tests_dts__/postcss.ts` asserts the two stay structurally identical. | |
| */ | |
| * ``` | |
| */ |
Alternatively, can be moved as a normal comment if you prefer.
There was a problem hiding this comment.
Addressed in 1e8c087. The mirrored type is gone in favor of the literal re-export, and that paragraph went with it. The JSDoc now only has the description and the usage example; the bundling details moved to the plugin comment in rolldown.dts.config.ts.
28d864f to
febafaf
Compare
|
Done, switched to your suggested approach in the latest commit. Two things turned out to be needed in the plugin besides the
With the module rewritten to plain ESM, The plugin verifies the dts shape it rewrites and fails the build with a clear message if a future postcss-load-config update changes it. The inlined postcss import collides with Vite's own Resulting public d.ts: type ConfigPlugin = Transformer | PostCSS.Plugin | Processor;
interface Config {
parser?: string | ProcessOptions['parser'] | false;
stringifier?: string | ProcessOptions['stringifier'] | false;
syntax?: string | ProcessOptions['syntax'] | false;
map?: string | false;
from?: string;
to?: string;
plugins?: Array<ConfigPlugin | false> | Record<string, object | false>;
}Verified locally: |
Re-exports the PostCSS user config shape as `PostcssUserConfig` from
`vite`, so PostCSS configs can be typed against the same definition Vite
loads them with:
```ts
import type { PostcssUserConfig } from 'vite'
const config: PostcssUserConfig = { plugins: [] }
export default config
```
The type mirrors `postcss-load-config`'s `Config`. `postcss-load-config`
is bundled into Vite and its declarations use CommonJS `export =` syntax,
which `rolldown-plugin-dts` cannot re-export through the bundled `.d.ts`,
so the shape is mirrored from `postcss` types instead. A type-level test
asserts the mirror stays structurally identical to the upstream `Config`,
so it cannot silently drift.
Closes vitejs#19109
Replaces the mirrored PostcssUserConfig type with a literal re-export of postcss-load-config's Config, per review feedback. A new plugin in rolldown.dts.config.ts rewrites postcss-load-config's dts to plain ESM: - redirects the deep `postcss/lib/processor` import to the `postcss` package entry (external), whose `export =` declarations otherwise fail to bundle - unwraps the `declare namespace postcssrc` block into top-level named exports so tree shaking keeps only `Config` and `ConfigPlugin` - replaces `export = postcssrc` with `export default postcssrc` The plugin verifies the dts shape it rewrites and fails the build if a postcss-load-config update changes it. The inlined postcss import collides with Vite's own `Plugin` type, handled by an `identifierReplacements` entry (`Plugin$2` -> `PostCSS.Plugin`).
1e8c087 to
3615f3c
Compare
Description
Re-exports the PostCSS user config shape as
PostcssUserConfigfromvite, so PostCSS configs can be typed against the same definition Vite loads them with:Closes #19109 (approved by @bluwy in the issue). This continues the work of #22525, which was closed by its author; the only outstanding review feedback there (from @sapphi-red) was "We should use the types from
postcss-load-config" instead of hand-writing the shape — this PR addresses exactly that.Why it mirrors the type instead of re-exporting it
A literal re-export turned out not to be possible with the current type-bundling setup, for two compounding reasons:
postcss-load-configis a bundleddevDependency, not a runtime dependency — so it can't be referenced as an externalimport('postcss-load-config')in the published.d.ts(consumers don't have it installed).export =syntax, whichrolldown-plugin-dtscannot re-export through the bundled.d.ts. A named re-export fails codegen (Export 'PostcssUserConfig' is not defined), and resolving the type inline pulls in non-externalizedpostcss/lib/*subpaths and fails on theirexport =declarations.So
PostcssUserConfigis defined by mirroringpostcss-load-config'sConfigusing the already-publicpostcsstypes. To make sure the mirror can never silently drift, a type-level test asserts it stays structurally identical to the upstreamConfig:This also caught a subtle bug in the previous attempt: it used
PostCSS.TransformCallbackwhereConfigactually usesTransformer(which additionally requirespostcssPlugin/postcssVersion).Tests
packages/vite/src/node/__tests_dts__/postcss.ts— a type-only test that (1) locksPostcssUserConfigtopostcss-load-config'sConfigviaEqual, and (2) checks both the array and object plugin formats are accepted and an invalid value is rejected. It runs as part ofpnpm typecheck(tsc -p src/node/__tests_dts__).pnpm typecheck,pnpm build-types(roll + check),eslint, andoxfmt --checkall pass.Notes for reviewers
Equallock is the approach I landed on given theexport =/ bundled-dep constraints above. If you'd prefer a literal reference (e.g. by also externalizingpostcsssubpaths inrolldown.dts.config.ts, which leaksimport ... from "postcss/lib/processor"into the public.d.ts), happy to switch — let me know.