import { type Plugin } from 'vite'; import type { MiniContract } from '../mini-contract.ts'; /** JavaScript code plus the physical filename required by the Mini Program class-name transformer. */ type JavaScriptArtifact = Readonly<{ code: string; filename: string; }>; /** Vite plugin with the host operation that finalizes one coherent native-style/JavaScript transaction. */ export type MiniStylePlugin = Plugin & Readonly<{ /** Neutralizes browser CSS payloads and atomically publishes the matching global native stylesheet. */ finalizeUpdate: (artifacts: readonly Artifact[], writeStylesheet: (stylesheet: string) => Promise) => Promise; }>; /** * Creates the single owner of global native-style compilation, graph projection, class rewriting, and publication. * * ## Architectural invariant * * One Mini Program transaction must expose JavaScript and native CSS produced from one class-identity snapshot. Tailwind utility * names can be escaped—for example, `py-5.5` becomes `py-5_d5`—so publishing either side independently can leave running code * referring to selectors that do not yet exist. This plugin therefore treats reachable CSS, Tailwind candidates, converted * native CSS, and converted JavaScript as one output. Complete builds and HMR updates both call `finalizeOutput()`; they differ only * in how the returned bytes are materialized. * * ## Ownership boundaries * * The pipeline deliberately gives each subsystem one responsibility: * * 1. Rolldown owns module reachability and invalidation. VPT reads `getModuleInfo()` and registers watch files, but does not * maintain a second import graph or decide independently which root should rerun. * 2. The persistent Tailwind generator owns candidate discovery and incremental candidate removal. VPT invokes it only from * the owning CSS root's Rolldown transform and never rescans the project during output publication. * 3. Vite owns preprocessors, PostCSS, CSS Modules, and final module CSS semantics. VPT observes the input to the resolved * `vite:css-post` hook only after the original hook succeeds; it never rereads source files or repeats CSS preprocessing. * 4. The fixed Mini transformer owns selector conversion and Oxc-based JavaScript class-string conversion. One retained * transformer and one projected candidate set drive both operations without loading a framework project context. * 5. VPT owns physical global native CSS and patch publication. Vite's browser CSS asset is only an intermediate carrier and is * removed before VPT emits the contract-selected global stylesheet. * * Native Page and component styles are outside this global pipeline. The target skeleton is generated after this style plugin * and emits those opaque companions later. Mini output also enforces `cssCodeSplit: false`, so Vite contributes at * most one browser compiler stylesheet for this plugin to replace. * * ## Compilation phases * * ### 1. Tailwind pre-transform * * The pre-transform checks physical application CSS for Tailwind imports. Ordinary styles pass through. A * Tailwind root compiles to browser CSS before Vite's normal CSS pipeline runs. Successful generation records the generator, * watched source files, current class set, compiler dependencies, and exact root source under the normalized module ID. * * Candidate files and compiler dependencies intentionally have different invalidation behavior: * * - Candidate-file changes rerun the root with the existing Tailwind engine. The engine rescans the authoritative * source set and returns one complete stylesheet containing both additions and removals. * - Compiler-dependency changes mark the root invalid. Its next Rolldown transform resolves a new Tailwind source, generator, * and watched file set. Replacement is delayed until that transform has current source and plugin context. * - If a stylesheet stops being a Tailwind root, its Tailwind state is removed. The later Vite CSS hook replaces the retained * CSS after normal processing succeeds. * * ### 2. Vite-final CSS capture * * `configResolved` wraps the concrete `vite:css-post` transform while preserving its hook metadata, filter, ordering, and * plugin context. The original Vite hook executes first, which preserves CSS Module exports and Vite's internal extraction * state. Only a successful transform updates `styleByModuleId`; syntax errors therefore leave the last successful CSS available * to the currently running application. Query modes such as `?raw`, `?url`, and `?inline` are excluded because they represent * values rather than graph-owned stylesheets. * * ### 3. Live-graph projection * * Output finalization starts from resolved App/Page entry IDs and traverses Rolldown's current static and dynamic import edges * in dependency-first post-order. Transaction-local visited sets terminate cycles and deduplicate shared modules and physical * stylesheets. A retained stylesheet contributes only when its module is still reachable, so removing an import prunes its CSS * and Tailwind candidates without a separate prune protocol or persistent topology cache. Candidate sets are unioned only from * the Tailwind roots whose captured CSS survives that exact traversal, preserving the CSS/class identity invariant. * * ### 4. Shared native-style finalization * * `finalizeOutput()` converts the concatenated reachable CSS to native CSS, optionally minifies that complete global file * (including HTML defaults), then transforms each JavaScript artifact with the same projected class set. Both builds and HMR * follow `build.cssMinify`, defaulting to `build.minify`, using Lightning CSS. Vite's intermediate CSS minification * remains disabled so only final native bytes are optimized. The function returns data without bundle mutation or filesystem * publication. Any conversion or minification failure rejects before callers expose partial output. JavaScript conversion is * skipped when the projection contains no Tailwind candidates, preserving ordinary bundle bytes. * * `createFinalizeOutput()` captures the resolved output policy once and retains only the latest successful CSS conversion and * candidate identity. Every transaction supplies a fresh live-graph projection, but byte-identical CSS skips both PostCSS and * Lightning CSS. Equal candidate contents reuse the same set and replacement table even when the projection allocated a new * set. Changed candidates never reuse stale replacements; conversion failures do not advance the snapshot. * * ### 5a. Complete-build commit * * The post-order `generateBundle` hook gathers all JavaScript chunks, finalizes them as one operation, and only then mutates the * bundle. It assigns converted code, clears invalid source maps, removes Vite's intermediate browser stylesheet, and always * emits the contract-selected global file, including the HTML compatibility base even when the application has no styles. * Native output hooks run afterward and emit Page/component companion files independently. * * ### 5b. Development commit * * The development host calls `finalizeUpdate()` after Rolldown produces patch factories or a complete-output notification. * Finalization uses the `PluginContext` captured by `buildStart`, so it observes the same current graph as the compiler. After * all conversion succeeds, the host's atomic writer publishes changed native CSS before `finalizeUpdate()` returns converted * patch factories. Their captured Vite CSS literals are emptied first; factories, exports, changed IDs, and sequences remain * intact. The patch publisher therefore cannot expose newer JavaScript class identities before matching selectors exist. * `publishedStylesheet` advances only after a successful write and suppresses byte-identical native-tool reload events. * * ## Retained state and lifecycle * * Each plugin instance owns the following bounded state and transformation services: * * - `cssMinify`: requested global-style minification captured before disabling Vite's intermediate pass, then resolved once; * - `entryIds`: graph-exact App/Page entry identities resolved at the start of each build; * - `graphContext`: the active Rolldown graph reader needed by host calls made outside plugin hooks; * - `styleByModuleId`: the latest successful Vite CSS plus optional Tailwind state at one normalized module identity; * - `publishedStylesheet`: the last durably published development stylesheet used for unchanged-write suppression; * - `finalizeOutput`: resolved output options, the fixed Mini transformer, and the latest successful CSS/candidate snapshot. * * The state owners remain scoped to one plugin instance; `entryIds` is atomically replaced after each complete resolution. * A development watcher retains them across updates; build and watcher shutdown clear the complete style store. * * ## Cost model * * Projection is `O(V + E + B + C)` for reachable modules, import edges, concatenated CSS bytes, and candidate insertions. * Building one exact candidate precheck costs `O(C)` candidate bytes and testing a chunk costs `O(J)` source bytes. Matching * chunks then parse and walk in `O(J)`; replacing `Kᵢ` candidate tokens in literal `i` costs `O(LᵢKᵢ)` while preserving * untouched bytes through Rolldown's native editor. Comparing candidate sets costs `O(C)` without sorting. Retained memory * is `O(B + C + D + F)` for latest CSS, candidate sets, compiler dependencies, and watched file identities; no second application * graph is retained. The Tailwind generator stays alive across candidate edits to avoid repeating source normalization and * compiler initialization. Native CSS conversion/minification runs only when projected CSS changes; * no extra source reads or graph traversals are needed. */ export declare function createMiniStylePlugin(contract: Pick, applicationEntryIds: readonly string[]): MiniStylePlugin; export {};