vite-plugin-taro 0.5.12 → 0.5.13

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.
Files changed (49) hide show
  1. package/dist/node/plugins/h5/plugins.js +22 -1
  2. package/dist/node/plugins/tailwind/tailwind-css.d.ts +1 -0
  3. package/dist/node/plugins/tailwind/tailwind-css.js +3 -0
  4. package/dist/node/plugins/wx/dev/dev-host.d.ts +6 -2
  5. package/dist/node/plugins/wx/dev/dev-host.js +70 -5
  6. package/dist/node/plugins/wx/dev/hmr-files.d.ts +9 -0
  7. package/dist/node/plugins/wx/dev/hmr-files.js +11 -0
  8. package/dist/node/plugins/wx/dev/plugins.d.ts +21 -2
  9. package/dist/node/plugins/wx/dev/plugins.js +38 -2
  10. package/dist/node/plugins/wx/dev/styles/create-style-capture-plugin.d.ts +12 -0
  11. package/dist/node/plugins/wx/dev/styles/create-style-capture-plugin.js +21 -0
  12. package/dist/node/plugins/wx/dev/styles/publish-style-hmr.d.ts +27 -0
  13. package/dist/node/plugins/wx/dev/styles/publish-style-hmr.js +48 -0
  14. package/dist/node/plugins/wx/dev/wx-dev-options.d.ts +3 -2
  15. package/dist/node/plugins/wx/dev/wx-dev-options.js +7 -2
  16. package/dist/node/plugins/wx/output/files.d.ts +1 -1
  17. package/dist/node/plugins/wx/output/files.js +6 -1
  18. package/dist/node/plugins/wx/plugins.js +13 -6
  19. package/dist/node/plugins/wx/resolve/resolver.d.ts +2 -1
  20. package/dist/node/plugins/wx/resolve/resolver.js +31 -17
  21. package/dist/node/plugins/wx/styles/plugins.d.ts +3 -0
  22. package/dist/node/plugins/wx/styles/plugins.js +92 -0
  23. package/dist/node/plugins/wx/styles/transform-wx-style.d.ts +8 -0
  24. package/dist/node/plugins/wx/styles/transform-wx-style.js +9 -0
  25. package/dist/node/plugins/wx/styles/utils.d.ts +39 -0
  26. package/dist/node/plugins/wx/styles/utils.js +95 -0
  27. package/dist/node/utils/vite.d.ts +17 -0
  28. package/dist/node/utils/vite.js +43 -0
  29. package/dist/node/vite-plugin.js +0 -2
  30. package/package.json +3 -3
  31. package/src/node/plugins/h5/plugins.ts +22 -1
  32. package/src/node/plugins/tailwind/tailwind-css.ts +4 -0
  33. package/src/node/plugins/wx/dev/dev-host.ts +87 -5
  34. package/src/node/plugins/wx/dev/hmr-files.ts +12 -0
  35. package/src/node/plugins/wx/dev/plugins.ts +44 -4
  36. package/src/node/plugins/wx/dev/styles/create-style-capture-plugin.ts +34 -0
  37. package/src/node/plugins/wx/dev/styles/publish-style-hmr.ts +71 -0
  38. package/src/node/plugins/wx/dev/wx-dev-options.ts +9 -2
  39. package/src/node/plugins/wx/output/files.ts +6 -1
  40. package/src/node/plugins/wx/plugins.ts +16 -6
  41. package/src/node/plugins/wx/resolve/resolver.ts +33 -17
  42. package/src/node/plugins/wx/styles/plugins.ts +105 -0
  43. package/src/node/plugins/wx/styles/transform-wx-style.ts +11 -0
  44. package/src/node/plugins/wx/styles/utils.ts +119 -0
  45. package/src/node/utils/vite.ts +67 -0
  46. package/src/node/vite-plugin.ts +0 -2
  47. package/dist/node/plugins/css/plugins.d.ts +0 -4
  48. package/dist/node/plugins/css/plugins.js +0 -119
  49. package/src/node/plugins/css/plugins.ts +0 -135
@@ -8,6 +8,8 @@ import { specializePageCapsule } from './specialize-page-capsule.js';
8
8
  export function createResolver(options) {
9
9
  const normalizedBootstrapPath = normalizeModuleId(bootstrapPath);
10
10
  const normalizedPageCapsulePath = normalizeModuleId(pageCapsulePath);
11
+ // Construct output input and application traversal roots together once so style order cannot drift from route order.
12
+ const entryGraph = createEntryGraph(options.pages);
11
13
  // Provide constant-time route validation and access to each configured Page JSON object.
12
14
  const pageByPath = new Map(options.pages.map((page) => [page.path, page]));
13
15
  const privateIdResolvers = new Map([
@@ -32,7 +34,7 @@ export function createResolver(options) {
32
34
  ]
33
35
  ]);
34
36
  return {
35
- input: createInput(options.pages),
37
+ ...entryGraph,
36
38
  resolveId(id, importer, projectRoot) {
37
39
  // Unknown IDs fall through so Vite and other plugins retain normal resolution.
38
40
  return privateIdResolvers.get(id)?.(importer, projectRoot);
@@ -53,22 +55,34 @@ export function createResolver(options) {
53
55
  }
54
56
  };
55
57
  }
56
- /** Declares native shells, infrastructure, and capsules as independent output entries. */
57
- function createInput(pages) {
58
- return Object.fromEntries([
59
- ['bootstrap', bootstrapPath],
60
- ['transport', transportPath],
61
- [appShellFileName, appShellPath],
62
- ['app-capsule', appCapsulePath],
63
- [componentShellFileName, componentShellPath],
64
- ['component-capsule', componentCapsulePath],
65
- ...pages.flatMap((page) => {
66
- return [
67
- [`${page.path}.js`, createRouteModuleId({ moduleId: pageShellPath, pagePath: page.path })],
68
- [`${page.path}-capsule`, createRouteModuleId({ moduleId: pageCapsulePath, pagePath: page.path })]
69
- ];
70
- })
71
- ]);
58
+ /** Declares output entries and the ordered application subset that can own user styles. */
59
+ function createEntryGraph(pages) {
60
+ const pageEntries = pages.map((page) => {
61
+ return {
62
+ capsuleId: createRouteModuleId({ moduleId: pageCapsulePath, pagePath: page.path }),
63
+ capsuleName: `${page.path}-capsule`,
64
+ shellId: createRouteModuleId({ moduleId: pageShellPath, pagePath: page.path }),
65
+ shellName: `${page.path}.js`
66
+ };
67
+ });
68
+ return {
69
+ // The App owns the first global cascade layer; configured Pages follow in their declared route order.
70
+ applicationEntryIds: [appCapsulePath, ...pageEntries.map((entry) => entry.capsuleId)],
71
+ input: Object.fromEntries([
72
+ ['bootstrap', bootstrapPath],
73
+ ['transport', transportPath],
74
+ [appShellFileName, appShellPath],
75
+ ['app-capsule', appCapsulePath],
76
+ [componentShellFileName, componentShellPath],
77
+ ['component-capsule', componentCapsulePath],
78
+ ...pageEntries.flatMap((entry) => {
79
+ return [
80
+ [entry.shellName, entry.shellId],
81
+ [entry.capsuleName, entry.capsuleId]
82
+ ];
83
+ })
84
+ ])
85
+ };
72
86
  }
73
87
  /** Creates one route-qualified module ID. */
74
88
  function createRouteModuleId({ moduleId, pagePath }) {
@@ -0,0 +1,3 @@
1
+ import type { PluginOption } from 'vite';
2
+ /** Creates the complete WX Tailwind and global-style pipeline. */
3
+ export declare function createWxStylePlugins(): PluginOption[];
@@ -0,0 +1,92 @@
1
+ import { WeappTailwindcss } from 'weapp-tailwindcss/vite';
2
+ import { transformVitePlugin } from '../../../utils/vite.js';
3
+ import { tailwindcssBasedir } from '../../tailwind/tailwind-css.js';
4
+ import { transformWxStyle, wxStyleOptions } from './transform-wx-style.js';
5
+ /*
6
+ * WX style output order:
7
+ *
8
+ * weapp-tailwindcss output hooks
9
+ * → vpt:wx-style-finalizer
10
+ * → vpt:wx native companion emission
11
+ *
12
+ * All three generateBundle hooks retain hook-level `order: 'post'` and therefore execute in registration order. The
13
+ * upstream plugin normally also uses plugin-level `enforce: 'post'`, which would move it behind both VPT plugins and
14
+ * break this sequence. `alignGenerateBundleOrder` removes only that broader phase from upstream output hooks.
15
+ */
16
+ /** Creates the complete WX Tailwind and global-style pipeline. */
17
+ export function createWxStylePlugins() {
18
+ const tailwindPlugins = WeappTailwindcss({
19
+ // VPT is a custom Vite compiler.
20
+ // Using Taro's adapter would import Taro-specific CSS ownership rules which we don't need.
21
+ appType: 'weapp-vite',
22
+ // WX generation rewrites Tailwind's split package imports before Vite tries to resolve them in the app.
23
+ // Without this, strict workspaces fail on imports such as `tailwindcss/theme.css`.
24
+ rewriteCssImports: true,
25
+ platform: 'weapp',
26
+ tailwindcssBasedir,
27
+ generator: {
28
+ target: 'weapp'
29
+ },
30
+ cssOptions: wxStyleOptions,
31
+ logLevel: 'warn'
32
+ }) ?? [];
33
+ return [transformVitePlugin(tailwindPlugins, alignGenerateBundleOrder), createWxStyleFinalizer(transformWxStyle)];
34
+ }
35
+ /**
36
+ * Finalizes the one global stylesheet after upstream Tailwind generation.
37
+ *
38
+ * `cssCodeSplit: false` makes the compiler style global, but upstream can name it `.css` or `.wxss` depending on build
39
+ * mode. This hook converts its complete final contents once and renames that compiler asset to `assets/global.wxss`. An
40
+ * application without styles receives an empty global asset at the same stable path. Running earlier loses CSS from
41
+ * dynamic chunks; running after native companion emission would also see Page and native-component WXSS files that must
42
+ * remain opaque.
43
+ */
44
+ function createWxStyleFinalizer(transformStyle) {
45
+ return {
46
+ name: 'vpt:wx-style-finalizer',
47
+ generateBundle: {
48
+ order: 'post',
49
+ async handler(_, bundle) {
50
+ const styles = Object.values(bundle).filter(isStyleAsset);
51
+ // Multiple compiler styles mean cssCodeSplit was re-enabled. Choosing one would silently lose CSS.
52
+ if (styles.length > 1) {
53
+ throw new Error('WX builds support at most one compiler-emitted stylesheet');
54
+ }
55
+ if (styles.length === 0) {
56
+ this.emitFile({ type: 'asset', fileName: 'assets/global.wxss', source: '' });
57
+ return;
58
+ }
59
+ const [style] = styles;
60
+ const source = typeof style.source === 'string' ? style.source : new TextDecoder().decode(style.source);
61
+ const transformedResult = await transformStyle(source);
62
+ // Preserve the compiler stylesheet as the real global asset so its bundle metadata and ownership remain
63
+ // intact. Only its finalized contents and stable WXSS identity change.
64
+ style.source = transformedResult.css;
65
+ style.fileName = 'assets/global.wxss';
66
+ }
67
+ }
68
+ };
69
+ }
70
+ /**
71
+ * Adapts upstream plugin descriptors without mutating `weapp-tailwindcss` or patching node_modules.
72
+ *
73
+ * Vite first groups whole plugins by `enforce`, then orders individual hooks. Upstream's output plugins specify both
74
+ * `enforce: 'post'` and `generateBundle.order: 'post'`. The plugin-level phase overrides their earlier registration and
75
+ * places them after VPT's normal plugins, so VPT observes incomplete CSS. Making all of VPT post-enforced would fix that
76
+ * one hook while unnecessarily reordering resolution and transforms.
77
+ *
78
+ * For upstream plugins that actually own generateBundle, clone the descriptor without plugin-level enforcement. Keep
79
+ * hook-level `order: 'post'`: it still waits for ordinary bundle generation, while registration order becomes the sole
80
+ * tie-breaker between upstream generation, VPT finalization and native output.
81
+ */
82
+ function alignGenerateBundleOrder(plugin) {
83
+ if (plugin.enforce !== 'post' || plugin.generateBundle === undefined) {
84
+ return plugin;
85
+ }
86
+ // Clone rather than mutate: upstream may retain or reuse the descriptor returned by its factory.
87
+ return { ...plugin, enforce: undefined };
88
+ }
89
+ /** Selects only the compiler stylesheet; native WXSS assets are emitted by the later WX hook. */
90
+ function isStyleAsset(output) {
91
+ return output.type === 'asset' && /\.(?:css|wxss)$/.test(output.fileName);
92
+ }
@@ -0,0 +1,8 @@
1
+ export declare const wxStyleOptions: {
2
+ readonly cssCalc: false;
3
+ readonly autoprefixer: false;
4
+ readonly rem2rpx: true;
5
+ readonly px2rpx: true;
6
+ };
7
+ /** Shared finalization policy for complete builds and host-owned style HMR. */
8
+ export declare const transformWxStyle: import("@weapp-tailwindcss/postcss").StyleHandler;
@@ -0,0 +1,9 @@
1
+ import { createStyleHandler } from '@weapp-tailwindcss/postcss';
2
+ export const wxStyleOptions = {
3
+ cssCalc: false,
4
+ autoprefixer: false,
5
+ rem2rpx: true,
6
+ px2rpx: true
7
+ };
8
+ /** Shared finalization policy for complete builds and host-owned style HMR. */
9
+ export const transformWxStyle = createStyleHandler(wxStyleOptions);
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Creates an auxiliary style request without replacing the physical Rolldown graph module.
3
+ *
4
+ * `weapp-vite-sidecar` is an upstream protocol marker, not a cache-busting nonce. `weapp-tailwindcss` detects the query
5
+ * key, strips the complete query when resolving the physical CSS pipeline file, and excludes this synthetic request from
6
+ * transformed-source candidate collection. That lets the request use the latest candidate state without feeding generated
7
+ * CSS back into Tailwind's source memory. The descriptive `style` value is stable; upstream treats the presence of the key
8
+ * as the protocol contract.
9
+ */
10
+ export declare function createTailwindSidecarId(rootId: string): string;
11
+ /** Selects imported style modules whose Vite development output owns a runtime CSS payload. */
12
+ export declare function isGlobalStyleRequest(id: string): boolean;
13
+ /**
14
+ * Creates the ordered style plan for one completed HMR transaction.
15
+ *
16
+ * `entryIds` carries semantic cascade ownership from the WX resolver: the App capsule first, followed by Page capsules in
17
+ * configured route order. Each entry is traversed depth-first. Static imports retain source order and precede dynamic imports;
18
+ * dynamic branches are included because WX cannot inject lazy CSS at browser runtime. A style follows its dependencies,
19
+ * matching evaluation order.
20
+ *
21
+ * Graph IDs may contain queries while `hasStyle` reads physical IDs. Normalization lets aliases share one captured style.
22
+ * JavaScript and non-runtime CSS requests remain traversal nodes but do not enter the plan. All mutable traversal state is
23
+ * transaction-local, so import additions and removals need no invalidation bookkeeping. Complexity is O(modules + edges),
24
+ * with O(modules + styles) temporary memory.
25
+ */
26
+ export declare function createGraphStylePlan(entryIds: readonly string[], getModuleInfo: (moduleId: string) => Readonly<{
27
+ importedIds: readonly string[];
28
+ dynamicallyImportedIds: readonly string[];
29
+ }> | null, hasStyle: (styleId: string) => boolean): readonly string[];
30
+ /** Renders one immutable graph plan after any Tailwind roots in that same plan have been refreshed. */
31
+ export declare function composeGraphStyleCss(styleIds: readonly string[], getStyleCss: (styleId: string) => string): string;
32
+ /**
33
+ * Extracts Vite's final CSS payload from the development module without evaluating its browser HMR code.
34
+ *
35
+ * Remove this parser when either Vite exposes a supported plugin-container API that returns final CSS directly, or
36
+ * `weapp-tailwindcss` exposes generated root CSS after candidate updates. Until then the sidecar receives Vite's JavaScript
37
+ * style module, so this function isolates the version-specific `__vite__css` serialization contract.
38
+ */
39
+ export declare function extractViteCss(moduleCode: string, rootId: string): string;
@@ -0,0 +1,95 @@
1
+ import { isCSSRequest } from 'vite';
2
+ import { normalizeModuleId } from '../../../utils/modules.js';
3
+ const nonRuntimeStyleQueries = ['direct', 'inline', 'inline-css', 'raw', 'style-attr', 'transform-only', 'url'];
4
+ /**
5
+ * Creates an auxiliary style request without replacing the physical Rolldown graph module.
6
+ *
7
+ * `weapp-vite-sidecar` is an upstream protocol marker, not a cache-busting nonce. `weapp-tailwindcss` detects the query
8
+ * key, strips the complete query when resolving the physical CSS pipeline file, and excludes this synthetic request from
9
+ * transformed-source candidate collection. That lets the request use the latest candidate state without feeding generated
10
+ * CSS back into Tailwind's source memory. The descriptive `style` value is stable; upstream treats the presence of the key
11
+ * as the protocol contract.
12
+ */
13
+ export function createTailwindSidecarId(rootId) {
14
+ return `${rootId}?weapp-vite-sidecar=style`;
15
+ }
16
+ /** Selects imported style modules whose Vite development output owns a runtime CSS payload. */
17
+ export function isGlobalStyleRequest(id) {
18
+ if (!isCSSRequest(id)) {
19
+ return false;
20
+ }
21
+ const queryStart = id.indexOf('?');
22
+ if (queryStart < 0) {
23
+ return true;
24
+ }
25
+ const fragmentStart = id.indexOf('#', queryStart);
26
+ const query = id.slice(queryStart + 1, fragmentStart < 0 ? undefined : fragmentStart);
27
+ const parameters = new URLSearchParams(query);
28
+ return (!parameters.has('weapp-vite-sidecar') && nonRuntimeStyleQueries.every((parameter) => !parameters.has(parameter)));
29
+ }
30
+ /**
31
+ * Creates the ordered style plan for one completed HMR transaction.
32
+ *
33
+ * `entryIds` carries semantic cascade ownership from the WX resolver: the App capsule first, followed by Page capsules in
34
+ * configured route order. Each entry is traversed depth-first. Static imports retain source order and precede dynamic imports;
35
+ * dynamic branches are included because WX cannot inject lazy CSS at browser runtime. A style follows its dependencies,
36
+ * matching evaluation order.
37
+ *
38
+ * Graph IDs may contain queries while `hasStyle` reads physical IDs. Normalization lets aliases share one captured style.
39
+ * JavaScript and non-runtime CSS requests remain traversal nodes but do not enter the plan. All mutable traversal state is
40
+ * transaction-local, so import additions and removals need no invalidation bookkeeping. Complexity is O(modules + edges),
41
+ * with O(modules + styles) temporary memory.
42
+ */
43
+ export function createGraphStylePlan(entryIds, getModuleInfo, hasStyle) {
44
+ // These local collections collapse cycles, shared modules, and aliases without retaining derived topology between batches.
45
+ const visitedModuleIds = new Set();
46
+ const visitedStyleIds = new Set();
47
+ const styleIds = [];
48
+ const visit = (moduleId) => {
49
+ if (visitedModuleIds.has(moduleId)) {
50
+ return;
51
+ }
52
+ visitedModuleIds.add(moduleId);
53
+ const moduleInfo = getModuleInfo(moduleId);
54
+ if (!moduleInfo) {
55
+ return;
56
+ }
57
+ moduleInfo.importedIds.forEach(visit);
58
+ moduleInfo.dynamicallyImportedIds.forEach(visit);
59
+ const styleId = normalizeModuleId(moduleId);
60
+ if (hasStyle(styleId) && !visitedStyleIds.has(styleId)) {
61
+ visitedStyleIds.add(styleId);
62
+ styleIds.push(styleId);
63
+ }
64
+ };
65
+ entryIds.forEach(visit);
66
+ return styleIds;
67
+ }
68
+ /** Renders one immutable graph plan after any Tailwind roots in that same plan have been refreshed. */
69
+ export function composeGraphStyleCss(styleIds, getStyleCss) {
70
+ return styleIds.map(getStyleCss).join('\n');
71
+ }
72
+ /**
73
+ * Extracts Vite's final CSS payload from the development module without evaluating its browser HMR code.
74
+ *
75
+ * Remove this parser when either Vite exposes a supported plugin-container API that returns final CSS directly, or
76
+ * `weapp-tailwindcss` exposes generated root CSS after candidate updates. Until then the sidecar receives Vite's JavaScript
77
+ * style module, so this function isolates the version-specific `__vite__css` serialization contract.
78
+ */
79
+ export function extractViteCss(moduleCode, rootId) {
80
+ const assignmentPrefix = 'const __vite__css = ';
81
+ const assignmentStart = moduleCode.indexOf(assignmentPrefix);
82
+ if (assignmentStart < 0) {
83
+ throw new Error(`Vite CSS transform for ${rootId} did not expose __vite__css`);
84
+ }
85
+ // Vite serializes the payload with JSON.stringify on one assignment line. Parse that literal so quotes, escapes, and
86
+ // embedded CSS newlines are decoded by JSON rather than by a second, subtly different unescaping implementation.
87
+ const valueStart = assignmentStart + assignmentPrefix.length;
88
+ const lineEnd = moduleCode.indexOf('\n', valueStart);
89
+ const serializedCss = moduleCode.slice(valueStart, lineEnd < 0 ? moduleCode.length : lineEnd);
90
+ const css = JSON.parse(serializedCss);
91
+ if (typeof css !== 'string') {
92
+ throw new Error(`Vite CSS transform for ${rootId} exposed a non-string __vite__css value`);
93
+ }
94
+ return css;
95
+ }
@@ -0,0 +1,17 @@
1
+ import type { HookHandler, Plugin, PluginOption } from 'vite';
2
+ type TransformHook = HookHandler<NonNullable<Plugin['transform']>>;
3
+ export type TransformHookResult = Awaited<ReturnType<TransformHook>>;
4
+ export type AsyncTransformHook = (this: ThisParameterType<TransformHook>, ...args: Parameters<TransformHook>) => Promise<TransformHookResult>;
5
+ export type TransformHookWrapper = (transform: AsyncTransformHook) => AsyncTransformHook;
6
+ export type PluginMapper = (plugin: Plugin) => Plugin;
7
+ /** Transforms every concrete plugin while preserving nested arrays, falsy options, and promised options. */
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;
17
+ export {};
@@ -0,0 +1,43 @@
1
+ /** Transforms every concrete plugin while preserving nested arrays, falsy options, and promised options. */
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
+ */
29
+ export function wrapPluginTransform(plugin, wrapper) {
30
+ const { transform } = plugin;
31
+ if (!transform) {
32
+ throw new Error(`${plugin.name} must expose a transform hook`);
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);
38
+ });
39
+ return {
40
+ ...plugin,
41
+ transform: isTransformFunction ? wrappedHandler : { ...transform, handler: wrappedHandler }
42
+ };
43
+ }
@@ -1,7 +1,6 @@
1
1
  import react from '@vitejs/plugin-react';
2
2
  import { createClientTaroPlugin } from './plugins/client/client-taro.js';
3
3
  import { createConditionalDirectivePlugin } from './plugins/conditional/conditional-directives.js';
4
- import { createCssPlugins } from './plugins/css/plugins.js';
5
4
  import { createH5TargetPlugins } from './plugins/h5/plugins.js';
6
5
  import { createWxTargetPlugins } from './plugins/wx/plugins.js';
7
6
  /** Creates the Vite plugins for one Taro target. */
@@ -9,7 +8,6 @@ export default function vitePluginTaro(options) {
9
8
  return [
10
9
  createConditionalDirectivePlugin(options.target),
11
10
  createClientTaroPlugin(options.target),
12
- ...createCssPlugins(options.target),
13
11
  ...react(),
14
12
  ...(options.target === 'wx' ? createWxTargetPlugins(options) : []),
15
13
  ...(options.target === 'h5' ? createH5TargetPlugins(options) : [])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-taro",
3
- "version": "0.5.12",
3
+ "version": "0.5.13",
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",
@@ -75,8 +75,8 @@
75
75
  "rolldown": "1.2.3",
76
76
  "tailwindcss": "^4.3.3",
77
77
  "weapp-tailwindcss": "^5.2.11",
78
- "@tarojs/plugin-framework-react": "npm:vite-plugin-taro-plugin-framework-react@0.5.12",
79
- "@tarojs/react": "npm:vite-plugin-taro-react@0.5.12"
78
+ "@tarojs/plugin-framework-react": "npm:vite-plugin-taro-plugin-framework-react@0.5.13",
79
+ "@tarojs/react": "npm:vite-plugin-taro-react@0.5.13"
80
80
  },
81
81
  "peerDependencies": {
82
82
  "react": "^19.0.0",
@@ -1,17 +1,38 @@
1
1
  import babel, { defineRolldownBabelPreset } from '@rolldown/plugin-babel'
2
2
  import type { HtmlTagDescriptor, Plugin, PluginOption } from 'vite'
3
+ import { WeappTailwindcss } from 'weapp-tailwindcss/vite'
3
4
  import type { VitePluginTaroOptions } from '../../../options.ts'
4
5
  import { esTarget } from '../../utils/constant.ts'
5
6
  import { toViteFileImportPath } from '../../utils/modules.ts'
6
7
  import { packageRequire } from '../../utils/packages.ts'
7
8
  import { clientTaroApiId } from '../client/client-taro.ts'
9
+ import { tailwindcssBasedir } from '../tailwind/tailwind-css.ts'
8
10
  import { h5AppPath } from './constant.ts'
9
11
  import { createStencilClientAdapter } from './create-stencil-client-adapter.ts'
10
12
  import { createModuleResolver } from './resolver/module-resolver.ts'
11
13
 
12
14
  /** Creates the plugins that own the H5 target. */
13
15
  export function createH5TargetPlugins(options: VitePluginTaroOptions): PluginOption[] {
14
- return [...createH5SupportPlugins(), createH5TargetPlugin(options)]
16
+ return [
17
+ WeappTailwindcss({
18
+ appType: 'weapp-vite',
19
+ rewriteCssImports: false,
20
+ platform: 'web',
21
+ tailwindcssBasedir,
22
+ generator: {
23
+ target: 'web'
24
+ },
25
+ cssOptions: {
26
+ cssCalc: false,
27
+ autoprefixer: true,
28
+ rem2rpx: true,
29
+ px2rpx: true
30
+ },
31
+ logLevel: 'warn'
32
+ }),
33
+ ...createH5SupportPlugins(),
34
+ createH5TargetPlugin(options)
35
+ ]
15
36
  }
16
37
 
17
38
  /** Configures H5 resolution and supplies the specialized physical application entry. */
@@ -0,0 +1,4 @@
1
+ import path from 'node:path'
2
+ import { packageRequire } from '../../utils/packages.ts'
3
+
4
+ export const tailwindcssBasedir = path.dirname(packageRequire.resolve('tailwindcss/package.json'))
@@ -1,21 +1,28 @@
1
+ import { readFile } from 'node:fs/promises'
1
2
  import type { ServerResponse } from 'node:http'
2
3
  import path from 'node:path'
3
4
  import colors from 'picocolors'
5
+ import type { GetModuleInfo } from 'rolldown'
4
6
  import { type DevEngine, type DevOptions, dev } from 'rolldown/experimental'
5
- import type { Connect, ViteDevServer } from 'vite'
7
+ import { type Connect, isCSSRequest, type ViteDevServer } from 'vite'
6
8
  import type { VitePluginTaroOptions } from '../../../../options.ts'
7
9
  import { SerializedTaskQueue } from '../../../utils/serialized-task-queue.ts'
10
+ import { createGraphStylePlan, isGlobalStyleRequest } from '../styles/utils.ts'
8
11
  import {
12
+ developmentAppWxssFileName,
9
13
  type HmrInfo,
10
14
  hmrControlPath,
11
15
  hmrInfoFileName,
12
16
  hmrPatchesFileName,
13
17
  type PatchUpdate,
18
+ renderDevelopmentAppWxss,
14
19
  renderHmrInfo,
15
20
  renderInitialHmrPatches,
16
21
  writeHmrFile
17
22
  } from './hmr-files.ts'
18
23
  import { PatchPublisher } from './patch-publisher.ts'
24
+ import { createStyleCapturePlugin, type ProcessedStyle } from './styles/create-style-capture-plugin.ts'
25
+ import { globalWxssFileName, publishStyleHmr, refreshTailwindStyles } from './styles/publish-style-hmr.ts'
19
26
  import { type BundledDev, installWxDevOptions, requireSingleOutput } from './wx-dev-options.ts'
20
27
 
21
28
  export type WxDevHost = Readonly<{
@@ -45,15 +52,41 @@ type DevOutputResult = Parameters<NonNullable<DevOptions['onOutput']>>[0]
45
52
  * with dev(...)) and the patch publisher, and replaces Vite's bundledDev.listen so the
46
53
  * engine writes directly to the Mini Program output directory instead of serving browser
47
54
  * HMR over HTTP.
55
+ *
56
+ * `applicationEntryIds` is the resolver's immutable cascade policy, not a second graph: it selects the App capsule followed
57
+ * by configured Page capsules from Rolldown's larger entry set. Rolldown remains the authority for every live import edge.
48
58
  */
49
59
  export async function createWxDevHost({
50
60
  server,
51
- options
61
+ options,
62
+ applicationEntryIds
52
63
  }: {
53
64
  server: ViteDevServer
54
65
  options: VitePluginTaroOptions
66
+ applicationEntryIds: readonly string[]
55
67
  }): Promise<WxDevHost> {
56
68
  const bundledDev = getBundledDev(server)
69
+ // This is the host's mutable style projection: CSS absent from Rolldown, the live graph capability rebound by each
70
+ // complete build, and the last durable WXSS bytes used only to suppress identical filesystem publications. Style capture
71
+ // remains O(1); derived order and reachability stay local to each HMR transaction.
72
+ const styleState: {
73
+ getModuleInfo: GetModuleInfo | undefined
74
+ processedStyles: Map<string, ProcessedStyle>
75
+ publishedWxss: string | undefined
76
+ } = {
77
+ getModuleInfo: undefined,
78
+ processedStyles: new Map(),
79
+ publishedWxss: undefined
80
+ }
81
+ const styleCapturePlugin = createStyleCapturePlugin({
82
+ captureGraph(reader) {
83
+ styleState.getModuleInfo = reader
84
+ },
85
+ captureStyle(id, style) {
86
+ styleState.processedStyles.set(id, style)
87
+ }
88
+ })
89
+
57
90
  // Rolldown invokes output callbacks without awaiting their promises. This queue is the single owner of mutable HMR host
58
91
  // state and physical metadata writes, preventing a later patch or build identity from being overwritten by older work.
59
92
  const hostTasks = new SerializedTaskQueue((operation, error) => logWxError(server.config.logger, operation, error))
@@ -64,7 +97,7 @@ export async function createWxDevHost({
64
97
 
65
98
  // DevEngine does not reject run() after an initial plugin failure. The options layer owns a first-build buildEnd barrier
66
99
  // and exposes only its result; later build errors continue independently through onOutput and onHmrUpdates.
67
- const initialBuild = installWxDevOptions({ bundledDev, server, options })
100
+ const initialBuild = installWxDevOptions({ bundledDev, server, options, hostPlugins: [styleCapturePlugin] })
68
101
  const engine: DevEngine = await createEngine()
69
102
 
70
103
  // The wx dev host owns the only DevEngine. Vite's default listen() would create a second
@@ -198,6 +231,12 @@ export async function createWxDevHost({
198
231
  return
199
232
  }
200
233
 
234
+ // `onHmrUpdates` is the transaction boundary after every affected transform has updated graph and candidate state.
235
+ // Every non-CSS edit may alter imports or Tailwind classes; rendering broadly and comparing finalized bytes avoids
236
+ // source scanning while preventing unrelated JavaScript edits from notifying DevTools through an identical rename.
237
+ await publishChangedStyles(batch)
238
+
239
+ // Publish global.wxss before the matching JavaScript patch so DevTools observes a coherent HMR transaction.
201
240
  // The physical file must exist before Rolldown advances: once committed, later patches may be generated relative to
202
241
  // this batch even if DevTools has not observed its file event yet. PatchPublisher keeps the unapplied range cumulative,
203
242
  // so any later file generation still carries every factory needed to bridge the runtime's older application frontier.
@@ -206,6 +245,37 @@ export async function createWxDevHost({
206
245
  await commitPublishedBatch(batch)
207
246
  }
208
247
 
248
+ /** Publishes the style projection for one completed patch transaction when its source or topology may have changed. */
249
+ async function publishChangedStyles(batch: readonly PatchUpdate[]): Promise<void> {
250
+ const changedIds = batch.flatMap((patch) => patch.changedIds)
251
+ const styleChanged = changedIds.some(isGlobalStyleRequest)
252
+ const candidatesChanged = changedIds.some((id) => !isCSSRequest(id))
253
+ if (!styleChanged && !candidatesChanged) {
254
+ return
255
+ }
256
+
257
+ // buildStart installs this reader before Rolldown can produce either a complete output or an incremental batch.
258
+ const getModuleInfo = styleState.getModuleInfo
259
+ if (!getModuleInfo) {
260
+ throw new Error('WX style graph is unavailable before HMR publication')
261
+ }
262
+ // Traverse topology exactly once; root refresh and final rendering consume this immutable transaction plan.
263
+ const styleIds = createGraphStylePlan(applicationEntryIds, getModuleInfo, (styleId) =>
264
+ styleState.processedStyles.has(styleId)
265
+ )
266
+ if (candidatesChanged) {
267
+ await refreshTailwindStyles(styleIds, styleState.processedStyles, async (rootId, requestId) =>
268
+ server.environments.client.pluginContainer.transform(await readFile(rootId, 'utf8'), requestId)
269
+ )
270
+ }
271
+ styleState.publishedWxss = await publishStyleHmr({
272
+ styleIds: styleIds,
273
+ outDir: server.config.build.outDir,
274
+ processedStyles: styleState.processedStyles,
275
+ publishedWxss: styleState.publishedWxss
276
+ })
277
+ }
278
+
209
279
  /**
210
280
  * Advances Rolldown's published frontier in the same sequence order materialized in the cumulative physical file.
211
281
  *
@@ -228,7 +298,13 @@ export async function createWxDevHost({
228
298
  logWxError(server.config.logger, 'wx dev build failed', result)
229
299
  return
230
300
  }
231
- hostTasks.enqueue('wx dev build finalization failed', rotateBuildSession)
301
+ hostTasks.enqueue('wx dev build finalization failed', finalizeDevOutput)
302
+ }
303
+
304
+ /** Rebinds the byte frontier after a complete build replaces the physical stylesheet outside HMR publication. */
305
+ async function finalizeDevOutput(): Promise<void> {
306
+ styleState.publishedWxss = await readFile(path.join(server.config.build.outDir, globalWxssFileName), 'utf8')
307
+ await rotateBuildSession()
232
308
  }
233
309
  }
234
310
 
@@ -245,7 +321,7 @@ function boundPort(server: ViteDevServer): number | undefined {
245
321
  return address.port
246
322
  }
247
323
 
248
- /** Resets physical patches before exposing the matching immutable build identity to a new App heap. */
324
+ /** Resets physical patches and then exposes one coherent build identity to a freshly compiled App heap. */
249
325
  async function publishBuildMetadata(server: ViteDevServer, buildId: string, port: number): Promise<void> {
250
326
  const info: HmrInfo = {
251
327
  buildId,
@@ -254,6 +330,12 @@ async function publishBuildMetadata(server: ViteDevServer, buildId: string, port
254
330
 
255
331
  await writeHmrFile(server.config.build.outDir, hmrPatchesFileName, renderInitialHmrPatches())
256
332
  await writeHmrFile(server.config.build.outDir, hmrInfoFileName, renderHmrInfo(info))
333
+ // `removeDevelopmentAppWxss` kept the previous physical wrapper in place while the complete output was written. Replace
334
+ // it only now, after the empty patch frontier and matching identity are durable. DevTools treats `app.wxss` as an App root,
335
+ // so this write intentionally causes the one full refresh allowed at a complete-build boundary; the refreshed App reads
336
+ // the new info above. Incremental updates must never write this file because an App refresh could destroy the heap while
337
+ // its JavaScript patch is being acknowledged. They publish only the imported `assets/global.wxss` stylesheet instead.
338
+ await writeHmrFile(server.config.build.outDir, developmentAppWxssFileName, renderDevelopmentAppWxss(buildId))
257
339
  }
258
340
 
259
341
  /** Replaces browser server URLs with the physical project directory consumed by WeChat DevTools. */