vite-plugin-taro 0.6.3 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +3 -3
- package/README.md +3 -3
- package/dist/node/plugins/wx/dev/dev-host.d.ts +5 -4
- package/dist/node/plugins/wx/dev/dev-host.js +19 -43
- package/dist/node/plugins/wx/dev/plugins.d.ts +4 -3
- package/dist/node/plugins/wx/dev/plugins.js +4 -4
- package/dist/node/plugins/wx/dev/wx-dev-options.d.ts +2 -3
- package/dist/node/plugins/wx/dev/wx-dev-options.js +4 -4
- package/dist/node/plugins/wx/plugins.js +5 -11
- package/dist/node/plugins/wx/styles/plugins.d.ts +141 -3
- package/dist/node/plugins/wx/styles/plugins.js +401 -89
- package/dist/node/utils/vite.d.ts +4 -14
- package/dist/node/utils/vite.js +7 -40
- package/package.json +4 -3
- package/src/node/plugins/wx/dev/dev-host.ts +22 -44
- package/src/node/plugins/wx/dev/plugins.ts +5 -4
- package/src/node/plugins/wx/dev/wx-dev-options.ts +5 -7
- package/src/node/plugins/wx/plugins.ts +5 -11
- package/src/node/plugins/wx/styles/plugins.ts +518 -93
- package/src/node/utils/vite.ts +13 -58
- package/dist/node/plugins/wx/dev/create-style-capture.d.ts +0 -54
- package/dist/node/plugins/wx/dev/create-style-capture.js +0 -173
- package/dist/node/plugins/wx/styles/transform-wx-style.d.ts +0 -8
- package/dist/node/plugins/wx/styles/transform-wx-style.js +0 -9
- package/dist/node/plugins/wx/styles/utils.d.ts +0 -39
- package/dist/node/plugins/wx/styles/utils.js +0 -95
- package/src/node/plugins/wx/dev/create-style-capture.ts +0 -248
- package/src/node/plugins/wx/styles/transform-wx-style.ts +0 -11
- package/src/node/plugins/wx/styles/utils.ts +0 -119
|
@@ -1,111 +1,423 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { Scanner } from '@tailwindcss/oxide';
|
|
3
|
+
import { isCSSRequest } from 'vite';
|
|
4
|
+
import { createContext } from 'weapp-tailwindcss/core';
|
|
5
|
+
import { createWeappTailwindcssGenerator, resolveTailwindV4Source } from 'weapp-tailwindcss/generator';
|
|
6
|
+
import { normalizeModuleId } from '../../../utils/modules.js';
|
|
7
|
+
import { wrapPluginTransform } from '../../../utils/vite.js';
|
|
3
8
|
import { tailwindcssBasedir } from '../../tailwind/tailwind-css.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
import { globalWxssFileName } from '../dev/hmr-files.js';
|
|
10
|
+
/** Vite CSS request modes that do not represent graph-owned application stylesheets. */
|
|
11
|
+
const ignoredStyleQueries = ['direct', 'inline', 'inline-css', 'raw', 'style-attr', 'transform-only', 'url'];
|
|
12
|
+
/** Whole-file conversion policy applied equally to complete builds and development updates. */
|
|
13
|
+
const wxStyleOptions = {
|
|
14
|
+
cssCalc: false,
|
|
15
|
+
autoprefixer: false,
|
|
16
|
+
rem2rpx: true,
|
|
17
|
+
px2rpx: true
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Creates the single owner of global WX style compilation, graph projection, JavaScript class rewriting, and publication.
|
|
7
21
|
*
|
|
8
|
-
*
|
|
9
|
-
* → vpt:wx-style-finalizer
|
|
10
|
-
* → vpt:wx native companion emission
|
|
22
|
+
* ## Architectural invariant
|
|
11
23
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
*
|
|
24
|
+
* A WX transaction must expose JavaScript and WXSS produced from one class-identity snapshot. Tailwind utility names can be
|
|
25
|
+
* rewritten for WeChat—for example, `py-5.5` becomes `py-5_d5`—so publishing either side independently can leave running code
|
|
26
|
+
* referring to selectors that do not yet exist. This plugin therefore treats reachable CSS, Tailwind candidates, converted
|
|
27
|
+
* WXSS, and converted JavaScript as one output. Complete builds and HMR updates both call `finalizeOutput()`; they differ only
|
|
28
|
+
* in how the returned bytes are materialized.
|
|
29
|
+
*
|
|
30
|
+
* ## Ownership boundaries
|
|
31
|
+
*
|
|
32
|
+
* The pipeline deliberately gives each subsystem one responsibility:
|
|
33
|
+
*
|
|
34
|
+
* 1. Rolldown owns module reachability and invalidation. VPT reads `getModuleInfo()` and registers watch files, but does not
|
|
35
|
+
* maintain a second import graph or decide independently which root should rerun.
|
|
36
|
+
* 2. The persistent Tailwind generator owns candidate discovery and incremental candidate removal. VPT invokes it only from
|
|
37
|
+
* the owning CSS root's Rolldown transform and never rescans the project during output publication.
|
|
38
|
+
* 3. Vite owns preprocessors, PostCSS, CSS Modules, and final module CSS semantics. VPT observes the input to the resolved
|
|
39
|
+
* `vite:css-post` hook only after the original hook succeeds; it never rereads source files or repeats CSS preprocessing.
|
|
40
|
+
* 4. The Weapp transformation context owns WX selector conversion and JavaScript class-string conversion. One retained context
|
|
41
|
+
* and one projected candidate set drive both operations.
|
|
42
|
+
* 5. VPT owns physical global WXSS and patch publication. Vite's browser CSS asset is only an intermediate carrier and is
|
|
43
|
+
* removed before VPT emits `assets/global.wxss`.
|
|
44
|
+
*
|
|
45
|
+
* Native Page and component WXSS are outside this global pipeline. The WX output plugin is registered after this style plugin
|
|
46
|
+
* and emits those opaque companions later. The WX configuration also enforces `cssCodeSplit: false`, so Vite contributes at
|
|
47
|
+
* most one browser compiler stylesheet for this plugin to replace.
|
|
48
|
+
*
|
|
49
|
+
* ## Compilation phases
|
|
50
|
+
*
|
|
51
|
+
* ### 1. Tailwind pre-transform
|
|
52
|
+
*
|
|
53
|
+
* The pre-transform checks physical application CSS for Tailwind imports or directives. Ordinary styles pass through. A
|
|
54
|
+
* Tailwind root compiles to browser CSS before Vite's normal CSS pipeline runs. Successful generation records the generator,
|
|
55
|
+
* scanner, current class set, compiler dependencies, and exact root source under the normalized physical module ID.
|
|
56
|
+
*
|
|
57
|
+
* Candidate files and compiler dependencies intentionally have different invalidation behavior:
|
|
58
|
+
*
|
|
59
|
+
* - Candidate-file changes rerun the root with the existing generator and scanner. `incrementalCache: true` updates additions
|
|
60
|
+
* and removals without discarding the generator's authoritative cache.
|
|
61
|
+
* - Compiler-dependency changes mark the root invalid. Its next Rolldown transform resolves a new Tailwind source and creates a
|
|
62
|
+
* new generator and scanner. Replacement is delayed until that transform has current source and plugin context.
|
|
63
|
+
* - If a stylesheet stops being a Tailwind root, its generator is disposed and its Tailwind state is removed. The later Vite
|
|
64
|
+
* CSS hook replaces the retained CSS after normal processing succeeds.
|
|
65
|
+
*
|
|
66
|
+
* ### 2. Vite-final CSS capture
|
|
67
|
+
*
|
|
68
|
+
* `configResolved` wraps the concrete `vite:css-post` transform while preserving its hook metadata, filter, ordering, and
|
|
69
|
+
* plugin context. The original Vite hook executes first, which preserves CSS Module exports and Vite's internal extraction
|
|
70
|
+
* state. Only a successful transform updates `styleByModuleId`; syntax errors therefore leave the last successful CSS available
|
|
71
|
+
* to the currently running application. Query modes such as `?raw`, `?url`, and `?inline` are excluded because they represent
|
|
72
|
+
* values rather than graph-owned stylesheets.
|
|
73
|
+
*
|
|
74
|
+
* ### 3. Live-graph projection
|
|
37
75
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
76
|
+
* Output finalization starts from resolved App/Page entry IDs and traverses Rolldown's current static and dynamic import edges
|
|
77
|
+
* in dependency-first post-order. Transaction-local visited sets terminate cycles and deduplicate shared modules and physical
|
|
78
|
+
* stylesheets. A retained stylesheet contributes only when its module is still reachable, so removing an import prunes its CSS
|
|
79
|
+
* and Tailwind candidates without a separate prune protocol or persistent topology cache. Candidate sets are unioned only from
|
|
80
|
+
* the Tailwind roots whose captured CSS survives that exact traversal, preserving the CSS/class identity invariant.
|
|
81
|
+
*
|
|
82
|
+
* ### 4. Shared WX finalization
|
|
83
|
+
*
|
|
84
|
+
* `finalizeOutput()` first converts the concatenated reachable CSS to WXSS, then transforms every supplied JavaScript artifact
|
|
85
|
+
* with the same projected class set. It returns data and performs no bundle mutation or filesystem publication. If either
|
|
86
|
+
* transformation fails, the promise rejects before callers expose partial output. JavaScript conversion is skipped when the
|
|
87
|
+
* projection contains no Tailwind candidates, preserving ordinary bundle bytes.
|
|
88
|
+
*
|
|
89
|
+
* ### 5a. Complete-build commit
|
|
90
|
+
*
|
|
91
|
+
* The post-order `generateBundle` hook gathers all JavaScript chunks, finalizes them as one operation, and only then mutates the
|
|
92
|
+
* bundle. It assigns converted code, clears invalid source maps, removes Vite's intermediate browser stylesheet, and always
|
|
93
|
+
* emits `assets/global.wxss`. Emitting an empty global file is required because `app.wxss` imports it even when the application
|
|
94
|
+
* currently has no styles. Native output hooks run afterward and emit Page/component companion files independently.
|
|
95
|
+
*
|
|
96
|
+
* ### 5b. Development commit
|
|
97
|
+
*
|
|
98
|
+
* The development host calls `finalizeUpdate()` after Rolldown produces patch factories or a complete-output notification.
|
|
99
|
+
* Finalization uses the `PluginContext` captured by `buildStart`, so it observes the same current graph as the compiler. After
|
|
100
|
+
* all conversion succeeds, the host's atomic writer publishes changed WXSS before `finalizeUpdate()` returns converted patch
|
|
101
|
+
* factories. The patch publisher therefore cannot expose newer JavaScript class identities before matching selectors exist.
|
|
102
|
+
* `publishedWxss` advances only after a successful write and suppresses byte-identical writes that would otherwise trigger
|
|
103
|
+
* unnecessary WeChat DevTools reload events.
|
|
104
|
+
*
|
|
105
|
+
* ## Retained state and lifecycle
|
|
106
|
+
*
|
|
107
|
+
* The factory retains four explicit mutable state owners plus one library-owned transformation context:
|
|
108
|
+
*
|
|
109
|
+
* - `entryIds`: graph-exact App/Page entry identities resolved at the start of each build;
|
|
110
|
+
* - `graphContext`: the active Rolldown graph reader needed by host calls made outside plugin hooks;
|
|
111
|
+
* - `styleByModuleId`: the latest successful Vite CSS plus optional Tailwind state at one normalized module identity;
|
|
112
|
+
* - `publishedWxss`: the last durably published development stylesheet used for unchanged-write suppression;
|
|
113
|
+
* - `weappContext`: Weapp's internal conversion state, retained so selector and JavaScript rewriting share one context.
|
|
114
|
+
*
|
|
115
|
+
* The state owners remain scoped to one plugin instance; `entryIds` is atomically replaced after each complete resolution.
|
|
116
|
+
* Build-command bundles dispose Tailwind generators after bundle generation. A development watcher otherwise keeps them alive
|
|
117
|
+
* across updates and disposes them when it closes. Captured CSS survives compiler cleanup because output notifications can
|
|
118
|
+
* arrive after that cleanup, and is cleared only when the watcher terminates.
|
|
119
|
+
*
|
|
120
|
+
* ## Cost model
|
|
121
|
+
*
|
|
122
|
+
* Projection is `O(V + E + B + C)` for reachable modules, import edges, concatenated CSS bytes, and candidate insertions.
|
|
123
|
+
* JavaScript conversion is linear in the total supplied chunk or patch-factory bytes, subject to the Weapp parser's own cost.
|
|
124
|
+
* Retained memory is `O(B + C + D + F)` for latest CSS, candidate sets, compiler dependencies, and scanner file identities; no
|
|
125
|
+
* second application graph is retained. Tailwind's generator and Oxide scanner caches are intentionally persistent because
|
|
126
|
+
* recreating them on every candidate edit would repeat source normalization and scanning work.
|
|
43
127
|
*/
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
128
|
+
export function createWxStylePlugin(applicationEntryIds) {
|
|
129
|
+
// This mutable root list is replaced in buildStart with Vite/Rolldown's exact cross-platform graph identities.
|
|
130
|
+
let entryIds = applicationEntryIds;
|
|
131
|
+
// One retained Weapp context guarantees that CSS selectors and JavaScript class strings use the same conversion rules.
|
|
132
|
+
const weappContext = createContext({ appType: 'weapp-vite', logLevel: 'silent' });
|
|
133
|
+
// buildStart installs this mutable context because the development host finalizes output outside a Rolldown plugin hook.
|
|
134
|
+
let graphContext;
|
|
135
|
+
// This mutable map is the only retained style store: Vite and Tailwind update separate fields at one module identity.
|
|
136
|
+
const styleByModuleId = new Map();
|
|
137
|
+
// This mutable frontier advances only after the host durably writes WXSS, suppressing byte-identical filesystem events.
|
|
138
|
+
let publishedWxss;
|
|
139
|
+
/** Binds retained plugin state to the context of the complete build or development transaction being finalized. */
|
|
140
|
+
const finalizeCurrentOutput = (context, javaScript) => {
|
|
141
|
+
return finalizeOutput(entryIds, styleByModuleId, context.getModuleInfo.bind(context), weappContext, javaScript);
|
|
142
|
+
};
|
|
143
|
+
/** Releases native compiler resources while retaining captured CSS needed by subsequent output callbacks. */
|
|
144
|
+
const disposeTailwindRoots = () => {
|
|
145
|
+
styleByModuleId.forEach((style, styleId) => {
|
|
146
|
+
if (style.tailwind) {
|
|
147
|
+
// Dispose each generator exactly once, then remove the root reference while preserving Vite-final CSS.
|
|
148
|
+
style.tailwind.generator.dispose?.();
|
|
149
|
+
styleByModuleId.set(styleId, { css: style.css, tailwind: undefined });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
};
|
|
53
153
|
return {
|
|
54
|
-
name: 'vpt:wx-
|
|
154
|
+
name: 'vpt:wx-styles',
|
|
155
|
+
/** Installs the single private Vite integration used to observe fully processed module CSS. */
|
|
156
|
+
configResolved(config) {
|
|
157
|
+
// `vite:css-post` is the boundary after all public CSS processing and before browser-module serialization.
|
|
158
|
+
const cssPostPlugin = config.plugins.find((plugin) => plugin.name === 'vite:css-post');
|
|
159
|
+
wrapPluginTransform(cssPostPlugin, (transform) => {
|
|
160
|
+
return async function (css, id, options) {
|
|
161
|
+
// Run Vite first so a failed CSS transform never replaces the last successful retained artifact.
|
|
162
|
+
const result = await transform.call(this, css, id, options);
|
|
163
|
+
// Only physical application styles participate in WX graph projection; virtual request modes keep Vite semantics.
|
|
164
|
+
if (isApplicationStyle(id)) {
|
|
165
|
+
const styleId = normalizeModuleId(id);
|
|
166
|
+
styleByModuleId.set(styleId, {
|
|
167
|
+
css: css,
|
|
168
|
+
// Tailwind compilation runs earlier, so CSS capture must preserve the root state at this identity.
|
|
169
|
+
tailwind: styleByModuleId.get(styleId)?.tailwind
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
/** Resolves exact graph roots and captures the graph reader used by host calls outside plugin hooks. */
|
|
177
|
+
async buildStart() {
|
|
178
|
+
// Resolve through Rolldown instead of reconstructing real paths, whose drive casing and separators vary on Windows.
|
|
179
|
+
const resolvedEntryIds = await Promise.all(applicationEntryIds.map(async (entryId) => (await this.resolve(entryId)).id));
|
|
180
|
+
// Commit the complete root set together so finalization never observes a partially resolved application graph.
|
|
181
|
+
entryIds = resolvedEntryIds;
|
|
182
|
+
graphContext = this;
|
|
183
|
+
},
|
|
184
|
+
transform: {
|
|
185
|
+
// Tailwind must expand before Vite's normal CSS pipeline produces the final module CSS captured above.
|
|
186
|
+
order: 'pre',
|
|
187
|
+
/** Compiles only Tailwind roots and registers every input needed for Rolldown-driven invalidation. */
|
|
188
|
+
async handler(code, id) {
|
|
189
|
+
// Query variants such as `?raw` are values, not application stylesheets, and must remain untouched.
|
|
190
|
+
if (!isApplicationStyle(id)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
// Join this early Tailwind phase to the later Vite CSS capture through one normalized module identity.
|
|
194
|
+
const rootId = normalizeModuleId(id);
|
|
195
|
+
const style = styleByModuleId.get(rootId);
|
|
196
|
+
const previous = style?.tailwind;
|
|
197
|
+
// A file can stop being a Tailwind root during HMR; dispose its compiler without discarding last-good Vite CSS.
|
|
198
|
+
if (!isTailwindRoot(code)) {
|
|
199
|
+
previous?.generator.dispose?.();
|
|
200
|
+
styleByModuleId.set(rootId, { css: style?.css, tailwind: undefined });
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// Candidate-only updates reuse incremental caches; compiler-input updates replace the entire generator.
|
|
204
|
+
const reusable = previous?.invalidated ? undefined : previous;
|
|
205
|
+
const compiled = await compileTailwindRoot(this.environment.config.root, rootId, code, reusable);
|
|
206
|
+
if (compiled.root.generator !== previous?.generator) {
|
|
207
|
+
previous?.generator.dispose?.();
|
|
208
|
+
}
|
|
209
|
+
// Replace the retained root record only after generation has produced a complete result.
|
|
210
|
+
styleByModuleId.set(rootId, { css: style?.css, tailwind: compiled.root });
|
|
211
|
+
// Compiler dependencies trigger generator replacement, while candidate files trigger incremental regeneration.
|
|
212
|
+
compiled.root.dependencies.forEach((file) => {
|
|
213
|
+
this.addWatchFile(file);
|
|
214
|
+
});
|
|
215
|
+
compiled.root.scanner.files.forEach((file) => {
|
|
216
|
+
this.addWatchFile(file);
|
|
217
|
+
});
|
|
218
|
+
// Vite receives browser CSS and remains the sole owner of PostCSS, preprocessors, and CSS Modules.
|
|
219
|
+
return { code: compiled.css, map: null };
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
/** Marks roots whose compiler inputs changed; Rolldown still decides when those roots are transformed. */
|
|
223
|
+
watchChange(id) {
|
|
224
|
+
const dependencyId = normalizeModuleId(id);
|
|
225
|
+
// A dependency may feed multiple roots, so every retained root must be checked before the next transform wave.
|
|
226
|
+
styleByModuleId.forEach((style, styleId) => {
|
|
227
|
+
if (style.tailwind?.dependencies.has(dependencyId)) {
|
|
228
|
+
styleByModuleId.set(styleId, {
|
|
229
|
+
css: style.css,
|
|
230
|
+
// Delay generator replacement until the owning root transform has current root source and graph context.
|
|
231
|
+
tailwind: { ...style.tailwind, invalidated: true }
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
},
|
|
55
236
|
generateBundle: {
|
|
237
|
+
// Vite must finish chunking and CSS extraction before VPT can finalize the complete WX output transaction.
|
|
56
238
|
order: 'post',
|
|
239
|
+
/** Converts every JavaScript chunk and the reachable CSS projection with one authoritative class set. */
|
|
57
240
|
async handler(_, bundle) {
|
|
58
|
-
const
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.emitFile({ type: 'asset', fileName: 'assets/global.wxss', source: '' });
|
|
241
|
+
const outputs = Object.values(bundle);
|
|
242
|
+
// Step 1: preserve bundle order so finalized code can be assigned back by index without a second lookup map.
|
|
243
|
+
const chunks = outputs.filter((output) => output.type === 'chunk');
|
|
244
|
+
// Step 2: finish all fallible CSS and JavaScript conversion before mutating any bundle output.
|
|
245
|
+
const finalized = await finalizeCurrentOutput(this, chunks.map((chunk) => ({ code: chunk.code, filename: chunk.fileName })));
|
|
246
|
+
// Step 3: commit the converted JavaScript as one completed result and discard now-invalid source maps.
|
|
247
|
+
chunks.forEach((chunk, index) => {
|
|
248
|
+
chunk.code = finalized.javaScript[index];
|
|
249
|
+
chunk.map = null;
|
|
250
|
+
});
|
|
251
|
+
// Step 4: remove Vite's browser CSS carrier; VPT owns the sole physical global WX stylesheet.
|
|
252
|
+
Object.entries(bundle).forEach(([fileName, output]) => {
|
|
253
|
+
if (isStyleAsset(output)) {
|
|
254
|
+
delete bundle[fileName];
|
|
73
255
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
256
|
+
});
|
|
257
|
+
// Step 5: always emit the imported global file, including an empty file for applications without styles.
|
|
258
|
+
this.emitFile({ type: 'asset', fileName: globalWxssFileName, source: finalized.wxss });
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
/** Releases build-only compiler resources after the final bundle has consumed their candidate sets. */
|
|
262
|
+
closeBundle() {
|
|
263
|
+
if (this.environment.config.command === 'build') {
|
|
264
|
+
disposeTailwindRoots();
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
/** Releases long-lived development resources and clears captured CSS when the owning watcher terminates. */
|
|
268
|
+
closeWatcher() {
|
|
269
|
+
disposeTailwindRoots();
|
|
270
|
+
styleByModuleId.clear();
|
|
271
|
+
},
|
|
272
|
+
/** Finalizes one development result and publishes matching WXSS before exposing converted patch factories. */
|
|
273
|
+
finalizeUpdate: async (artifacts, writeWxss) => {
|
|
274
|
+
// Step 1: complete every fallible conversion against one snapshot of the current module graph.
|
|
275
|
+
const output = await finalizeCurrentOutput(graphContext, artifacts);
|
|
276
|
+
// Step 2: publish changed WXSS first so DevTools cannot observe JavaScript containing newer class identities.
|
|
277
|
+
if (output.wxss !== publishedWxss) {
|
|
278
|
+
await writeWxss(output.wxss);
|
|
279
|
+
// Advance the frontier only after the atomic writer succeeds; failed writes remain retryable.
|
|
280
|
+
publishedWxss = output.wxss;
|
|
85
281
|
}
|
|
282
|
+
// Step 3: preserve patch metadata and replace only code after the matching stylesheet is durable.
|
|
283
|
+
return artifacts.map((artifact, index) => ({ ...artifact, code: output.javaScript[index] }));
|
|
86
284
|
}
|
|
87
285
|
};
|
|
88
286
|
}
|
|
89
287
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* Vite first groups whole plugins by `enforce`, then orders individual hooks. Upstream's output plugins specify both
|
|
93
|
-
* `enforce: 'post'` and `generateBundle.order: 'post'`. The plugin-level phase overrides their earlier registration and
|
|
94
|
-
* places them after VPT's normal plugins, so VPT observes incomplete CSS. Making all of VPT post-enforced would fix that
|
|
95
|
-
* one hook while unnecessarily reordering resolution and transforms.
|
|
288
|
+
* Produces WXSS and JavaScript from one live-graph projection.
|
|
96
289
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* tie-breaker between upstream generation, VPT finalization and native output.
|
|
290
|
+
* The function receives every stateful dependency explicitly so tests and both output modes execute the same algorithm. It
|
|
291
|
+
* completes WXSS conversion before JavaScript conversion and returns bytes without publishing or mutating caller artifacts.
|
|
100
292
|
*/
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
293
|
+
export async function finalizeOutput(entryIds, styleByModuleId, getModuleInfo, weappContext, javaScript) {
|
|
294
|
+
// Step 1: derive cascade order, reachable CSS, and raw Tailwind candidates from the same current graph snapshot.
|
|
295
|
+
const projection = projectStyles(entryIds, styleByModuleId, getModuleInfo);
|
|
296
|
+
// Step 2: convert the complete stylesheet once; this fixes the selector identities JavaScript must subsequently use.
|
|
297
|
+
const wxss = (await weappContext.transformWxss(projection.css, wxStyleOptions)).css;
|
|
298
|
+
// Step 3: transform artifacts independently but with the exact candidate set used by the stylesheet conversion.
|
|
299
|
+
const transformedJavaScript = await Promise.all(javaScript.map(async (artifact) => {
|
|
300
|
+
// Ordinary CSS needs no class-string rewrite, so preserve JavaScript bytes when Tailwind contributed no candidates.
|
|
301
|
+
if (projection.classSet.size === 0) {
|
|
302
|
+
return artifact.code;
|
|
303
|
+
}
|
|
304
|
+
const result = await weappContext.transformJs(artifact.code, {
|
|
305
|
+
filename: artifact.filename,
|
|
306
|
+
generateMap: false,
|
|
307
|
+
runtimeSet: projection.classSet
|
|
308
|
+
});
|
|
309
|
+
if (result.error) {
|
|
310
|
+
// Reject the whole transaction; callers have not mutated chunks or published WXSS at this point.
|
|
311
|
+
throw result.error;
|
|
312
|
+
}
|
|
313
|
+
return result.code;
|
|
314
|
+
}));
|
|
315
|
+
// Returning data keeps physical bundle mutation and development filesystem publication at their respective owners.
|
|
316
|
+
return { javaScript: transformedJavaScript, wxss: wxss };
|
|
317
|
+
}
|
|
318
|
+
/** Selects styles reachable from the configured entries in deterministic dependency-first cascade order. */
|
|
319
|
+
function projectStyles(entryIds, styleByModuleId, getModuleInfo) {
|
|
320
|
+
// This mutable transaction-local set terminates cycles and prevents repeated traversal through shared JavaScript modules.
|
|
321
|
+
const visitedModuleIds = new Set();
|
|
322
|
+
// This mutable transaction-local set emits a physical stylesheet once even when multiple graph paths import it.
|
|
323
|
+
const visitedStyleIds = new Set();
|
|
324
|
+
// This mutable transaction-local list records dependency-first CSS order for the final concatenated stylesheet.
|
|
325
|
+
const css = [];
|
|
326
|
+
// This mutable transaction-local set unions candidates from exactly the Tailwind roots contributing reachable CSS.
|
|
327
|
+
const classSet = new Set();
|
|
328
|
+
/** Performs a post-order graph visit so dependencies precede the modules that import them in the CSS cascade. */
|
|
329
|
+
const visit = (moduleId) => {
|
|
330
|
+
// Step 1: claim the module before recursion to terminate cycles and shared dependency paths.
|
|
331
|
+
if (visitedModuleIds.has(moduleId)) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
visitedModuleIds.add(moduleId);
|
|
335
|
+
// Step 2: ignore IDs absent from the current graph; retained CSS alone never makes a removed module reachable.
|
|
336
|
+
const moduleInfo = getModuleInfo(moduleId);
|
|
337
|
+
if (!moduleInfo) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
// Step 3: visit static and dynamic dependencies before considering this module's own stylesheet contribution.
|
|
341
|
+
moduleInfo.importedIds.forEach(visit);
|
|
342
|
+
moduleInfo.dynamicallyImportedIds.forEach(visit);
|
|
343
|
+
// Step 4: join graph identity to captured style identity and append each reachable physical stylesheet once.
|
|
344
|
+
const styleId = normalizeModuleId(moduleId);
|
|
345
|
+
const style = styleByModuleId.get(styleId);
|
|
346
|
+
if (style?.css === undefined || visitedStyleIds.has(styleId)) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
visitedStyleIds.add(styleId);
|
|
350
|
+
css.push(style.css);
|
|
351
|
+
// Step 5: union candidates only from roots whose CSS survived this same reachability projection.
|
|
352
|
+
style.tailwind?.classSet.forEach((className) => {
|
|
353
|
+
classSet.add(className);
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
// Each App/Page entry is a root; shared visited sets deduplicate styles across the complete application projection.
|
|
357
|
+
entryIds.forEach(visit);
|
|
358
|
+
return { classSet: classSet, css: css.join('\n') };
|
|
359
|
+
}
|
|
360
|
+
/** Compiles one Tailwind root and returns replacement state without mutating the retained module store. */
|
|
361
|
+
async function compileTailwindRoot(projectRoot, rootId, css, previous) {
|
|
362
|
+
// Step 1: exact root-source equality proves that candidate changes can reuse the existing compiler and scanner caches.
|
|
363
|
+
const reuse = previous?.source === css;
|
|
364
|
+
// Step 2: compiler-input changes resolve a fresh Tailwind source and create a new generator before retained state changes.
|
|
365
|
+
const generator = reuse
|
|
366
|
+
? previous.generator
|
|
367
|
+
: createWeappTailwindcssGenerator(await resolveTailwindV4Source({
|
|
368
|
+
projectRoot: projectRoot,
|
|
369
|
+
cwd: tailwindcssBasedir,
|
|
370
|
+
cssSources: [{ css: css, base: path.dirname(rootId), file: rootId }]
|
|
371
|
+
}));
|
|
372
|
+
try {
|
|
373
|
+
// Step 3: authoritative source scanning updates additions and removals in the persistent incremental candidate cache.
|
|
374
|
+
const generated = await generator.generate({ target: 'web', scanSources: true, incrementalCache: true });
|
|
375
|
+
// Step 4: return a complete immutable replacement record; the caller commits it only after this function succeeds.
|
|
376
|
+
return {
|
|
377
|
+
css: generated.css,
|
|
378
|
+
root: {
|
|
379
|
+
source: css,
|
|
380
|
+
classSet: generated.classSet,
|
|
381
|
+
dependencies: new Set(generated.dependencies.map(normalizeModuleId)),
|
|
382
|
+
generator: generator,
|
|
383
|
+
// Source patterns change with compiler inputs, while candidate-only updates can reuse their native matcher.
|
|
384
|
+
scanner: reuse ? previous.scanner : new Scanner({ sources: generated.sources }),
|
|
385
|
+
invalidated: false
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
// A new generator has no retained owner on failure; reused generators remain owned by the previous root record.
|
|
391
|
+
if (!reuse) {
|
|
392
|
+
generator.dispose?.();
|
|
393
|
+
}
|
|
394
|
+
throw error;
|
|
104
395
|
}
|
|
105
|
-
// Clone rather than mutate: upstream may retain or reuse the descriptor returned by its factory.
|
|
106
|
-
return { ...plugin, enforce: undefined };
|
|
107
396
|
}
|
|
108
|
-
/**
|
|
397
|
+
/** Returns whether a Vite request represents a physical CSS module that contributes to application WXSS. */
|
|
398
|
+
function isApplicationStyle(id) {
|
|
399
|
+
// Step 1: use Vite's predicate so every supported preprocessor extension follows the same path.
|
|
400
|
+
if (!isCSSRequest(id)) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
// Step 2: a query-free CSS request is always a physical application stylesheet.
|
|
404
|
+
const queryStart = id.indexOf('?');
|
|
405
|
+
if (queryStart < 0) {
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
// Step 3: reject Vite request modes whose values must not enter the global CSS projection.
|
|
409
|
+
const fragmentStart = id.indexOf('#', queryStart);
|
|
410
|
+
const query = id.slice(queryStart + 1, fragmentStart < 0 ? undefined : fragmentStart);
|
|
411
|
+
const parameters = new URLSearchParams(query);
|
|
412
|
+
return ignoredStyleQueries.every((parameter) => !parameters.has(parameter));
|
|
413
|
+
}
|
|
414
|
+
/** Detects source forms that require Tailwind compilation before Vite processes the resulting CSS. */
|
|
415
|
+
function isTailwindRoot(code) {
|
|
416
|
+
// Tailwind v4 uses package imports; legacy directives remain accepted because the generator supports both forms.
|
|
417
|
+
return (/@import\s+(?:url\(\s*)?['"]tailwindcss(?:\/[^'"]*)?['"]/.test(code) ||
|
|
418
|
+
/@tailwind\s+(?:base|components|utilities)\b/.test(code));
|
|
419
|
+
}
|
|
420
|
+
/** Identifies Vite's browser stylesheet carrier, which VPT replaces after all final CSS has been captured. */
|
|
109
421
|
function isStyleAsset(output) {
|
|
110
422
|
return output.type === 'asset' && /\.(?:css|wxss)$/.test(output.fileName);
|
|
111
423
|
}
|
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
import type { HookHandler, Plugin
|
|
1
|
+
import type { HookHandler, Plugin } from 'vite';
|
|
2
2
|
type TransformHook = HookHandler<NonNullable<Plugin['transform']>>;
|
|
3
|
-
|
|
4
|
-
export type AsyncTransformHook = (this: ThisParameterType<TransformHook>, ...args: Parameters<TransformHook>) => Promise<TransformHookResult>;
|
|
3
|
+
type AsyncTransformHook = (this: ThisParameterType<TransformHook>, ...args: Parameters<TransformHook>) => Promise<Awaited<ReturnType<TransformHook>>>;
|
|
5
4
|
export type TransformHookWrapper = (transform: AsyncTransformHook) => AsyncTransformHook;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export declare function transformVitePlugin(pluginOptions: PluginOption[], mapPlugin: PluginMapper): PluginOption[];
|
|
9
|
-
/**
|
|
10
|
-
* Clones a Vite transform hook with middleware that controls execution of the original handler.
|
|
11
|
-
*
|
|
12
|
-
* Function and object hook forms retain their original plugin context. Object metadata such as `order` and `filter` is copied
|
|
13
|
-
* unchanged, and the input descriptor is never mutated. The wrapper receives the normalized asynchronous transform with its
|
|
14
|
-
* complete plugin context, code, ID, and metadata signature, and returns the handler that continues Vite's plugin pipeline.
|
|
15
|
-
*/
|
|
16
|
-
export declare function wrapPluginTransform(plugin: Plugin, wrapper: TransformHookWrapper): Plugin;
|
|
5
|
+
/** Mutates one concrete plugin to interpose on its transform while preserving hook metadata and plugin context. */
|
|
6
|
+
export declare function wrapPluginTransform(plugin: Plugin, wrapper: TransformHookWrapper): void;
|
|
17
7
|
export {};
|
package/dist/node/utils/vite.js
CHANGED
|
@@ -1,43 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
export function transformVitePlugin(pluginOptions, mapPlugin) {
|
|
3
|
-
return pluginOptions.map((option) => transformPluginOption(option, mapPlugin));
|
|
4
|
-
}
|
|
5
|
-
function transformPluginOption(option, mapPlugin) {
|
|
6
|
-
if (option instanceof Promise) {
|
|
7
|
-
return option.then((resolvedOption) => transformPluginOption(resolvedOption, mapPlugin));
|
|
8
|
-
}
|
|
9
|
-
if (Array.isArray(option)) {
|
|
10
|
-
return transformVitePlugin(option, mapPlugin);
|
|
11
|
-
}
|
|
12
|
-
return isPlugin(option) ? mapPlugin(option) : option;
|
|
13
|
-
}
|
|
14
|
-
function isPlugin(option) {
|
|
15
|
-
return (option !== null &&
|
|
16
|
-
option !== false &&
|
|
17
|
-
option !== undefined &&
|
|
18
|
-
typeof option === 'object' &&
|
|
19
|
-
!Array.isArray(option) &&
|
|
20
|
-
'name' in option);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Clones a Vite transform hook with middleware that controls execution of the original handler.
|
|
24
|
-
*
|
|
25
|
-
* Function and object hook forms retain their original plugin context. Object metadata such as `order` and `filter` is copied
|
|
26
|
-
* unchanged, and the input descriptor is never mutated. The wrapper receives the normalized asynchronous transform with its
|
|
27
|
-
* complete plugin context, code, ID, and metadata signature, and returns the handler that continues Vite's plugin pipeline.
|
|
28
|
-
*/
|
|
1
|
+
/** Mutates one concrete plugin to interpose on its transform while preserving hook metadata and plugin context. */
|
|
29
2
|
export function wrapPluginTransform(plugin, wrapper) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const isTransformFunction = typeof transform === 'function';
|
|
35
|
-
const handler = isTransformFunction ? transform : transform.handler;
|
|
36
|
-
const wrappedHandler = wrapper(async function (code, id, meta) {
|
|
37
|
-
return handler.call(this, code, id, meta);
|
|
3
|
+
const transform = plugin.transform;
|
|
4
|
+
const handler = typeof transform === 'function' ? transform : transform.handler;
|
|
5
|
+
const wrapped = wrapper(async function (code, id, options) {
|
|
6
|
+
return handler.call(this, code, id, options);
|
|
38
7
|
});
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
transform: isTransformFunction ? wrappedHandler : { ...transform, handler: wrappedHandler }
|
|
42
|
-
};
|
|
8
|
+
// Installation-time mutation keeps Vite's existing plugin identity and resolved hook registration.
|
|
9
|
+
plugin.transform = typeof transform === 'function' ? wrapped : { ...transform, handler: wrapped };
|
|
43
10
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-taro",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"author": "sep2",
|
|
5
5
|
"description": "Vite 8 plugin for building one React/Taro codebase for WeChat Mini Program and H5 targets.",
|
|
6
6
|
"type": "module",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"@oxc-project/types": "0.143.0",
|
|
58
58
|
"@rolldown/plugin-babel": "^0.2.3",
|
|
59
59
|
"@tailwindcss-mangle/engine": "0.2.0",
|
|
60
|
+
"@tailwindcss/oxide": "4.3.3",
|
|
60
61
|
"@tarojs/components": "4.2.0",
|
|
61
62
|
"@tarojs/helper": "4.2.0",
|
|
62
63
|
"@tarojs/plugin-platform-h5": "4.2.0",
|
|
@@ -75,8 +76,8 @@
|
|
|
75
76
|
"rxjs": "^7.8.2",
|
|
76
77
|
"tailwindcss": "^4.3.3",
|
|
77
78
|
"weapp-tailwindcss": "^5.2.11",
|
|
78
|
-
"@tarojs/plugin-framework-react": "npm:vite-plugin-taro-plugin-framework-react@0.6.
|
|
79
|
-
"@tarojs/react": "npm:vite-plugin-taro-react@0.6.
|
|
79
|
+
"@tarojs/plugin-framework-react": "npm:vite-plugin-taro-plugin-framework-react@0.6.6",
|
|
80
|
+
"@tarojs/react": "npm:vite-plugin-taro-react@0.6.6"
|
|
80
81
|
},
|
|
81
82
|
"peerDependencies": {
|
|
82
83
|
"react": "^19.0.0",
|