vite-plugin-taro 0.3.0 → 0.3.2

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.
@@ -7,7 +7,7 @@ type ProjectContext = Readonly<{
7
7
  pages: readonly VitePluginTaroPageOption[];
8
8
  appConfig: JsonObject;
9
9
  projectConfigJson: JsonObject;
10
- projectPrivateConfigJson: JsonObject;
10
+ projectPrivateConfigJson?: JsonObject;
11
11
  sitemapJson: JsonObject;
12
12
  }>;
13
13
  /** Owns the shared project, Vite lifecycle state, and cross-target services for one build. */
@@ -7,6 +7,7 @@ export class BuildContext {
7
7
  developmentMode;
8
8
  resolvedViteConfig;
9
9
  constructor(options) {
10
+ this.css = new CssPipeline(options.target);
10
11
  this.project = {
11
12
  target: options.target,
12
13
  appComponentFile: path.resolve(options.app),
@@ -19,7 +20,6 @@ export class BuildContext {
19
20
  projectPrivateConfigJson: options.projectPrivateConfigJson,
20
21
  sitemapJson: options.sitemapJson
21
22
  };
22
- this.css = new CssPipeline(options.target);
23
23
  }
24
24
  configure(environment) {
25
25
  if (this.developmentMode !== undefined)
@@ -33,7 +33,6 @@ export class BuildContext {
33
33
  if (this.resolvedViteConfig)
34
34
  throw new Error('vite-plugin-taro build context was already resolved.');
35
35
  this.resolvedViteConfig = config;
36
- this.css.resolve(config.root);
37
36
  }
38
37
  get development() {
39
38
  if (this.developmentMode === undefined)
@@ -1,21 +1,35 @@
1
- import type { Plugin } from 'vite';
1
+ import type { PluginOption } from 'vite';
2
2
  import type { VitePluginTaroTarget } from '../../options.ts';
3
- type CssTransformResult = {
3
+ type PatchResult = {
4
4
  code: string;
5
- map: null;
5
+ } | {
6
+ requiresFullBuild: true;
6
7
  };
7
- /** Owns CSS generation and the class-name state shared by normal chunks and literal WX patches. */
8
+ /** Owns Tailwind's Vite integration and native WX patch synchronization. */
8
9
  export declare class CssPipeline {
9
- readonly plugin: Plugin;
10
- private readonly target;
11
- private readonly runtimeClassSet;
12
- private projectRoot;
10
+ readonly plugins: PluginOption[];
11
+ private readonly entries;
12
+ private readonly sourceCache;
13
13
  private wxContext;
14
+ private root;
15
+ private builtClassSet;
16
+ /** Composes the upstream web/WX plugins and adds candidate tracking only for native WX patches. */
14
17
  constructor(target: VitePluginTaroTarget);
15
- resolve(projectRoot: string): void;
16
- transformWxClassNames(code: string, filename: string): Promise<CssTransformResult>;
17
- private createPlugin;
18
- private getProjectRoot;
18
+ /** Records every utility guaranteed to exist after a successful full WX build. */
19
+ captureFullBuild(): Promise<void>;
20
+ /** Applies upstream mini-program compatibility transforms to Taro's fully materialized WXSS. */
21
+ transformWxss(code: string): Promise<string>;
22
+ /** Escapes a JavaScript-only WX patch, or rejects it when its utilities require new WXSS. */
23
+ transformNativePatch(code: string, filename: string, files: string[]): Promise<PatchResult>;
24
+ /** Retains Vite's root for resolving later CSS transforms and changed-file notifications. */
25
+ private resolveWx;
26
+ /** Creates one validator per Tailwind entry and refreshes the shared upstream WX transformer. */
27
+ private registerCssEntry;
28
+ /** Incrementally checks changed source files for utilities absent from the completed WX build. */
29
+ private hasAddedCandidates;
30
+ /** Returns the initialized WX transformer and detects invalid lifecycle ordering. */
19
31
  private getWxContext;
32
+ /** Returns the resolved project root and detects use before Vite configuration. */
33
+ private getRoot;
20
34
  }
21
35
  export {};
@@ -1,129 +1,154 @@
1
+ import fs from 'node:fs/promises';
1
2
  import path from 'node:path';
3
+ import { extractSourceCandidates } from '@tailwindcss-mangle/engine';
2
4
  import { createContext } from 'weapp-tailwindcss/core';
3
5
  import { createWeappTailwindcssGenerator, resolveTailwindV4Source } from 'weapp-tailwindcss/generator';
6
+ import { WeappTailwindcss } from 'weapp-tailwindcss/vite';
4
7
  import { normalizeModuleId } from '../utils/modules.js';
8
+ import { resolvePackageFile } from '../utils/packages.js';
5
9
  const wxStyleOptions = {
6
10
  cssCalc: false,
7
11
  autoprefixer: false,
8
12
  rem2rpx: true,
9
13
  px2rpx: true
10
14
  };
11
- /** Owns CSS generation and the class-name state shared by normal chunks and literal WX patches. */
15
+ /** Owns Tailwind's Vite integration and native WX patch synchronization. */
12
16
  export class CssPipeline {
13
- plugin;
14
- target;
15
- runtimeClassSet = new Set();
16
- projectRoot;
17
+ plugins;
18
+ // Each Tailwind CSS entry can define a different design system, so additions must pass every relevant validator.
19
+ entries = new Map();
20
+ // Vite can report the same source more than once; avoid repeating extraction and validation.
21
+ sourceCache = new Map();
22
+ // Upstream owns WX JavaScript escaping; this context is deliberately reused between patches.
17
23
  wxContext;
24
+ // CSS transforms arrive after configResolved, so the Vite root is retained for source and module resolution.
25
+ root;
26
+ // Only classes represented by a completed WX build are safe to publish in a JavaScript-only patch.
27
+ builtClassSet = new Set();
28
+ /** Composes the upstream web/WX plugins and adds candidate tracking only for native WX patches. */
18
29
  constructor(target) {
19
- this.target = target;
20
- this.plugin = this.createPlugin();
30
+ const pipeline = this;
31
+ const wx = target === 'wx';
32
+ this.plugins = [
33
+ wx
34
+ ? {
35
+ name: 'vite-plugin-taro:wx-tailwind-pipeline',
36
+ enforce: 'pre',
37
+ configResolved(config) {
38
+ pipeline.resolveWx(config);
39
+ },
40
+ async transform(code, id) {
41
+ if (!isTailwindCssEntry(code, id))
42
+ return;
43
+ await pipeline.registerCssEntry(id, code);
44
+ }
45
+ }
46
+ : undefined,
47
+ ...(WeappTailwindcss({
48
+ appType: 'taro',
49
+ // Route split Tailwind imports around Vite's unresolved production CSS package imports.
50
+ rewriteCssImports: true,
51
+ generator: { target: wx ? 'weapp' : 'web' },
52
+ ...(wx ? wxStyleOptions : {}),
53
+ logLevel: 'silent'
54
+ }) ?? [])
55
+ ];
21
56
  }
22
- resolve(projectRoot) {
23
- if (this.projectRoot)
24
- throw new Error('vite-plugin-taro CSS pipeline was already resolved.');
25
- this.projectRoot = projectRoot;
26
- this.wxContext = this.target === 'wx' ? createWxCssContext(projectRoot) : undefined;
57
+ /** Records every utility guaranteed to exist after a successful full WX build. */
58
+ async captureFullBuild() {
59
+ this.sourceCache.clear();
60
+ // Preserve removed development CSS, matching upstream's append-only HMR default.
61
+ for (const { generator } of this.entries.values()) {
62
+ const result = await generator.generate({ scanSources: true });
63
+ for (const candidate of result.classSet)
64
+ this.builtClassSet.add(candidate);
65
+ }
27
66
  }
28
- async transformWxClassNames(code, filename) {
29
- if (!this.wxContext || this.runtimeClassSet.size === 0)
30
- return { code, map: null };
31
- const result = await this.wxContext.transformJs(code, {
32
- runtimeSet: this.runtimeClassSet,
67
+ /** Applies upstream mini-program compatibility transforms to Taro's fully materialized WXSS. */
68
+ async transformWxss(code) {
69
+ return (await this.getWxContext().transformWxss(code)).css;
70
+ }
71
+ /** Escapes a JavaScript-only WX patch, or rejects it when its utilities require new WXSS. */
72
+ async transformNativePatch(code, filename, files) {
73
+ if (await this.hasAddedCandidates(files))
74
+ return { requiresFullBuild: true };
75
+ const result = await this.getWxContext().transformJs(code, {
76
+ runtimeSet: this.builtClassSet,
33
77
  filename,
34
78
  generateMap: false
35
79
  });
36
- return { code: result.code, map: null };
80
+ return { code: result.code };
37
81
  }
38
- createPlugin() {
39
- const pipeline = this;
40
- return {
41
- name: 'vite-plugin-taro:css',
42
- enforce: 'pre',
43
- buildStart() {
44
- pipeline.runtimeClassSet.clear();
45
- },
46
- async transform(code, id) {
47
- if (!isCssModuleId(id) || !shouldGenerateTailwindCss(code))
48
- return;
49
- const projectRoot = pipeline.getProjectRoot();
50
- const cssFile = resolveCssFile(id, projectRoot);
51
- const cssBase = path.dirname(cssFile);
52
- const source = await resolveTailwindV4Source({
53
- projectRoot,
54
- cwd: projectRoot,
55
- base: cssBase,
56
- css: code,
57
- cssSources: [{ file: cssFile, base: cssBase, css: code, dependencies: [cssFile] }]
58
- });
59
- const generator = createWeappTailwindcssGenerator(source);
60
- const generated = await generator.generate({
61
- target: pipeline.target === 'wx' ? 'weapp' : 'web',
62
- scanSources: true,
63
- candidates: [],
64
- styleOptions: pipeline.target === 'wx' ? wxStyleOptions : undefined
65
- });
66
- for (const className of generated.classSet)
67
- pipeline.runtimeClassSet.add(className);
68
- for (const dependency of generated.dependencies)
69
- this.addWatchFile(dependency);
70
- return generated.css;
71
- },
72
- async renderChunk(code, chunk) {
73
- if (pipeline.target !== 'wx')
74
- return;
75
- return await pipeline.transformWxClassNames(code, chunk.fileName);
76
- },
77
- async generateBundle(_, bundle) {
78
- if (pipeline.target !== 'wx')
79
- return;
80
- const core = pipeline.getWxContext();
81
- await Promise.all(Object.entries(bundle).map(async ([fileName, item]) => {
82
- if (item.type === 'asset' && fileName.endsWith('.css')) {
83
- await transformWxssAsset(core, item);
84
- }
85
- }));
86
- }
87
- };
82
+ /** Retains Vite's root for resolving later CSS transforms and changed-file notifications. */
83
+ resolveWx(config) {
84
+ this.root = config.root;
88
85
  }
89
- getProjectRoot() {
90
- if (!this.projectRoot)
91
- throw new Error('vite-plugin-taro CSS pipeline was used before configuration resolved.');
92
- return this.projectRoot;
86
+ /** Creates one validator per Tailwind entry and refreshes the shared upstream WX transformer. */
87
+ async registerCssEntry(id, source) {
88
+ const file = resolveFile(id, this.getRoot());
89
+ if (this.entries.get(file)?.source === source)
90
+ return;
91
+ const base = path.dirname(file);
92
+ const resolved = await resolveTailwindV4Source({
93
+ projectRoot: this.getRoot(),
94
+ cwd: this.getRoot(),
95
+ base,
96
+ baseFallbacks: [resolvePackageFile()],
97
+ css: source,
98
+ cssSources: [{ file, base, css: source, dependencies: [file] }]
99
+ });
100
+ this.entries.set(file, { source, generator: createWeappTailwindcssGenerator(resolved) });
101
+ this.wxContext = createContext({
102
+ appType: 'taro',
103
+ cssEntries: [...this.entries.keys()],
104
+ tailwindcssBasedir: this.getRoot(),
105
+ generator: { target: 'weapp' },
106
+ ...wxStyleOptions,
107
+ logLevel: 'silent'
108
+ });
93
109
  }
110
+ /** Incrementally checks changed source files for utilities absent from the completed WX build. */
111
+ async hasAddedCandidates(files) {
112
+ for (const input of files) {
113
+ const file = resolveFile(input, this.getRoot());
114
+ if (!/\.[cm]?[jt]sx?$/.test(file))
115
+ continue;
116
+ const source = await fs.readFile(file, 'utf8');
117
+ if (this.sourceCache.get(file) === source)
118
+ continue;
119
+ this.sourceCache.set(file, source);
120
+ const extracted = await extractSourceCandidates(source, path.extname(file).slice(1));
121
+ for (const { generator } of this.entries.values()) {
122
+ const candidates = await generator.validateCandidates(extracted);
123
+ // Native WX patches bypass Vite; a new utility therefore requires synchronized WXSS first.
124
+ for (const candidate of candidates)
125
+ if (!this.builtClassSet.has(candidate))
126
+ return true;
127
+ }
128
+ }
129
+ return false;
130
+ }
131
+ /** Returns the initialized WX transformer and detects invalid lifecycle ordering. */
94
132
  getWxContext() {
95
133
  if (!this.wxContext)
96
- throw new Error('vite-plugin-taro expected a resolved WeChat CSS pipeline.');
134
+ throw new Error('WX CSS pipeline was used before Vite resolved.');
97
135
  return this.wxContext;
98
136
  }
137
+ /** Returns the resolved project root and detects use before Vite configuration. */
138
+ getRoot() {
139
+ if (!this.root)
140
+ throw new Error('WX CSS pipeline was used before Vite resolved.');
141
+ return this.root;
142
+ }
99
143
  }
100
- function createWxCssContext(projectRoot) {
101
- return createContext({
102
- appType: 'taro',
103
- tailwindcssBasedir: projectRoot,
104
- generator: { target: 'weapp' },
105
- ...wxStyleOptions,
106
- logLevel: 'silent'
107
- });
108
- }
109
- async function transformWxssAsset(core, item) {
110
- const result = await core.transformWxss(getAssetSource(item), { isMainChunk: true });
111
- item.source = result.css;
112
- }
113
- function shouldGenerateTailwindCss(code) {
114
- const tailwindEntryImportPattern = /@(import|reference)\s+(?:url\(\s*)?(?:["'])tailwindcss(?:\/(?:theme|preflight|utilities)(?:\.css)?)?(?:["'])/;
115
- return code.includes('tailwindcss') && tailwindEntryImportPattern.test(code);
116
- }
117
- function isCssModuleId(id) {
118
- return /\.(?:css|scss|sass|less|styl|stylus)(?:$|[?#])/.test(id);
119
- }
120
- function getAssetSource(item) {
121
- if (typeof item.source === 'string')
122
- return item.source;
123
- return item.source ? new TextDecoder().decode(item.source) : '';
144
+ /** Identifies style modules that establish a Tailwind design system. */
145
+ function isTailwindCssEntry(code, id) {
146
+ return (/\.(?:css|scss|sass|less|styl|stylus)(?:$|[?#])/.test(id) &&
147
+ /@(?:import|reference)\s+(?:url\(\s*)?["']tailwindcss(?:\/[^"']*)?["']/.test(code));
124
148
  }
125
- function resolveCssFile(id, root) {
126
- const normalizedId = normalizeModuleId(id);
127
- const cleanId = normalizedId.startsWith('/@fs/') ? normalizedId.slice('/@fs'.length) : normalizedId;
128
- return path.isAbsolute(cleanId) ? cleanId : path.resolve(root, cleanId);
149
+ /** Converts Vite IDs, including /@fs/ IDs and query strings, into normalized absolute paths. */
150
+ function resolveFile(id, root) {
151
+ const normalized = normalizeModuleId(id).replace(/[?#].*$/, '');
152
+ const file = normalized.startsWith('/@fs/') ? normalized.slice('/@fs'.length) : normalized;
153
+ return normalizeModuleId(path.resolve(root, file));
129
154
  }
@@ -13,6 +13,7 @@ type WxAssetEmitter = {
13
13
  fileName: string;
14
14
  source: WxAssetSource;
15
15
  }): string;
16
+ warn(message: string): void;
16
17
  };
17
- export declare function emitWxCompanionAssets(emitter: WxAssetEmitter, bundle: WxBundle, context: BuildContext): void;
18
+ export declare function emitWxCompanionAssets(emitter: WxAssetEmitter, bundle: WxBundle, context: BuildContext): Promise<void>;
18
19
  export {};
@@ -5,16 +5,22 @@ import { normalizeModuleId } from '../../utils/modules.js';
5
5
  import { packageRequire } from '../../utils/packages.js';
6
6
  const taroWxComponentsPath = packageRequire.resolve('@tarojs/plugin-platform-weapp/dist/components-react');
7
7
  const templateBuilder = createWxTemplateBuilder();
8
- export function emitWxCompanionAssets(emitter, bundle, context) {
9
- for (const asset of createWxCompanionAssets(bundle, context)) {
8
+ export async function emitWxCompanionAssets(emitter, bundle, context) {
9
+ for (const asset of await createWxCompanionAssets(bundle, context)) {
10
+ if (!asset)
11
+ continue;
12
+ if (asset.source === undefined) {
13
+ emitter.warn(`[vite-plugin-taro] WX companion asset "${asset.fileName}" is missing its source and was not emitted.`);
14
+ continue;
15
+ }
10
16
  emitter.emitFile({ type: 'asset', fileName: asset.fileName, source: asset.source });
11
17
  }
12
18
  }
13
- function createWxCompanionAssets(bundle, context) {
19
+ async function createWxCompanionAssets(bundle, context) {
14
20
  const json = (value) => (context.development ? JSON.stringify(value, null, 2) : JSON.stringify(value));
15
21
  return [
16
22
  { fileName: 'app.json', source: json(context.project.appConfig) },
17
- { fileName: 'app.wxss', source: collectWxBundleStyles(bundle) },
23
+ { fileName: 'app.wxss', source: await context.css.transformWxss(collectWxBundleStyles(bundle)) },
18
24
  {
19
25
  fileName: 'base.wxml',
20
26
  source: templateBuilder.buildTemplate(collectWxTemplateComponentConfig(bundle))
@@ -23,7 +29,9 @@ function createWxCompanionAssets(bundle, context) {
23
29
  { fileName: 'comp.wxml', source: templateBuilder.buildBaseComponentTemplate('.wxml') },
24
30
  { fileName: 'comp.json', source: json(createWxComponentConfig()) },
25
31
  { fileName: 'project.config.json', source: json(createWxProjectConfig(context)) },
26
- { fileName: 'project.private.config.json', source: json(context.project.projectPrivateConfigJson) },
32
+ context.project.projectPrivateConfigJson
33
+ ? { fileName: 'project.private.config.json', source: json(context.project.projectPrivateConfigJson) }
34
+ : undefined,
27
35
  { fileName: 'sitemap.json', source: json(context.project.sitemapJson) },
28
36
  ...context.project.pages.flatMap((page) => [
29
37
  {
@@ -21,6 +21,7 @@ export declare class WxDevelopmentSession {
21
21
  private readonly handleHttpListening;
22
22
  private readonly handleWatchedFile;
23
23
  private handleBundleOutput;
24
+ private finalizeAppStyles;
24
25
  private handlePatch;
25
26
  private handleError;
26
27
  private requestFullBuild;
@@ -98,13 +98,10 @@ export class WxDevelopmentSession {
98
98
  };
99
99
  handleBundleOutput(output) {
100
100
  const appStyles = normalizeWxBundleStyles(output);
101
- if (appStyles !== undefined)
102
- this.snapshot = { ...this.snapshot, latestAppStyles: appStyles };
103
- if (isWxFullBuildOutput(output) && this.snapshot.latestAppStyles) {
104
- setWxAppStyles(output, this.snapshot.latestAppStyles);
105
- }
106
- if (!isWxFullBuildOutput(output)) {
101
+ const fullBuild = isWxFullBuildOutput(output);
102
+ if (!fullBuild) {
107
103
  this.outputQueue.enqueue(async () => {
104
+ await this.finalizeAppStyles(output, appStyles, false);
108
105
  await transformWxOutputChunks(output);
109
106
  await writeDevelopmentOutput(this.outDir, output);
110
107
  });
@@ -114,6 +111,7 @@ export class WxDevelopmentSession {
114
111
  setDevelopmentAsset(output, wxUpdateControlFile, this.updateTransport.createControlSource(buildId));
115
112
  setDevelopmentAsset(output, wxUpdateFile, 'void 0;\n');
116
113
  this.outputQueue.enqueue(async () => {
114
+ await this.finalizeAppStyles(output, appStyles, true);
117
115
  await transformWxOutputChunks(output);
118
116
  if (this.snapshot.outputPhase === 'starting') {
119
117
  // The directory is plugin-owned; clearing it once removes stale files from previous protocol designs.
@@ -123,12 +121,23 @@ export class WxDevelopmentSession {
123
121
  this.updateTransport.commitFullBuild(buildId);
124
122
  await writeDevelopmentOutput(this.outDir, output);
125
123
  await copyDirectoryIfExists(this.config.publicDir, this.outDir);
124
+ await this.context.css.captureFullBuild();
126
125
  const moduleCount = this.adapter.registerBundleModules(output);
127
126
  this.snapshot = { ...this.snapshot, outputPhase: 'ready' };
128
127
  this.initialBundle.resolve();
129
128
  this.server.config.logger.info(`[vite-plugin-taro] WX bundle ready (${moduleCount} modules, ${output.length} files)`);
130
129
  });
131
130
  }
131
+ async finalizeAppStyles(output, source, fullBuild) {
132
+ if (source !== undefined) {
133
+ const transformed = await this.context.css.transformWxss(source);
134
+ this.snapshot = { ...this.snapshot, latestAppStyles: transformed };
135
+ setWxAppStyles(output, transformed);
136
+ }
137
+ else if (fullBuild && this.snapshot.latestAppStyles) {
138
+ setWxAppStyles(output, this.snapshot.latestAppStyles);
139
+ }
140
+ }
132
141
  handlePatch(files, output) {
133
142
  if (!isSafeJavaScriptPatch(files, output)) {
134
143
  this.requestFullBuild();
@@ -136,7 +145,11 @@ export class WxDevelopmentSession {
136
145
  }
137
146
  this.adapter.registerPatchModules(output.code);
138
147
  this.outputQueue.enqueue(async () => {
139
- const transformed = await this.context.css.transformWxClassNames(output.code, output.filename);
148
+ const transformed = await this.context.css.transformNativePatch(output.code, output.filename, files);
149
+ if ('requiresFullBuild' in transformed) {
150
+ this.requestFullBuild();
151
+ return;
152
+ }
140
153
  const compatibleCode = await transformWxCompatibleJavaScript(transformed.code, output.filename);
141
154
  if (this.updateTransport.retainedDeltaCount >= maxRetainedDeltaCount ||
142
155
  this.updateTransport.retainedDeltaBytes + Buffer.byteLength(compatibleCode) >= maxRetainedDeltaBytes) {
@@ -41,8 +41,8 @@ function createWxTargetPlugin(context) {
41
41
  },
42
42
  generateBundle: {
43
43
  order: 'post',
44
- handler(_, bundle) {
45
- emitWxCompanionAssets(this, bundle, context);
44
+ async handler(_, bundle) {
45
+ await emitWxCompanionAssets(this, bundle, context);
46
46
  }
47
47
  },
48
48
  configureServer: {
@@ -90,6 +90,7 @@ export function createWxViteConfig(context) {
90
90
  target: 'es2018',
91
91
  assetsInlineLimit: 1024,
92
92
  cssCodeSplit: false,
93
+ // weapp-tailwindcss has no minifier setting; retain Vite's production CSS minification.
93
94
  cssMinify: context.development ? false : 'lightningcss',
94
95
  minify: !context.development,
95
96
  rolldownOptions: {
@@ -7,14 +7,14 @@ import { createWxTargetPlugins, createWxViteConfig } from './targets/wx/plugin.j
7
7
  /** Creates the Vite plugins for the selected Taro target. */
8
8
  export default function vitePluginTaro(options) {
9
9
  const context = new BuildContext(options);
10
- const targetPlugins = context.project.target === 'wx' ? createWxTargetPlugins(context) : createH5TargetPlugins(context);
11
10
  return [
12
11
  createBuildCoordinator(context),
13
12
  createConditionalDirectivePlugin(context),
14
13
  createTaroRuntimePlugin(),
15
- context.css.plugin,
14
+ ...context.css.plugins,
16
15
  ...react(),
17
- ...targetPlugins
16
+ ...(context.project.target === 'wx' ? createWxTargetPlugins(context) : []),
17
+ ...(context.project.target === 'h5' ? createH5TargetPlugins(context) : [])
18
18
  ];
19
19
  }
20
20
  /** Establishes build mode and resolved Vite state before target hooks consume the context. */
package/dist/options.d.ts CHANGED
@@ -16,6 +16,6 @@ export interface VitePluginTaroOptions {
16
16
  pages: VitePluginTaroPageOption[];
17
17
  appJson: JsonObject;
18
18
  projectConfigJson: JsonObject;
19
- projectPrivateConfigJson: JsonObject;
19
+ projectPrivateConfigJson?: JsonObject;
20
20
  sitemapJson: JsonObject;
21
21
  }
@@ -116,7 +116,8 @@ export function startWxUpdateClient() {
116
116
  batchApply();
117
117
  dispatch({ type: 'batch-executed', targetVersion: command.targetVersion });
118
118
  }
119
- catch {
119
+ catch (error) {
120
+ console.error('[vite-plugin-taro] WX update execution failed', error);
120
121
  dispatch({ type: 'batch-failed' });
121
122
  }
122
123
  finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-taro",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
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",
@@ -53,6 +53,7 @@
53
53
  "dependencies": {
54
54
  "@babel/core": "^7.29.7",
55
55
  "@rolldown/plugin-babel": "^0.2.3",
56
+ "@tailwindcss-mangle/engine": "0.1.3",
56
57
  "@tarojs/components": "^4.2.0",
57
58
  "@tarojs/helper": "^4.2.0",
58
59
  "@tarojs/plugin-platform-h5": "^4.2.0",
@@ -65,9 +66,9 @@
65
66
  "picocolors": "^1.1.1",
66
67
  "react-refresh": "^0.18.0",
67
68
  "tailwindcss": "^4.3.2",
68
- "weapp-tailwindcss": "^5.1.14",
69
- "@tarojs/plugin-framework-react": "npm:vite-plugin-taro-plugin-framework-react@0.3.0",
70
- "@tarojs/react": "npm:vite-plugin-taro-react@0.3.0"
69
+ "weapp-tailwindcss": "5.1.14",
70
+ "@tarojs/plugin-framework-react": "npm:vite-plugin-taro-plugin-framework-react@0.3.2",
71
+ "@tarojs/react": "npm:vite-plugin-taro-react@0.3.2"
71
72
  },
72
73
  "peerDependencies": {
73
74
  "react": "^19.0.0",
@@ -9,7 +9,7 @@ type ProjectContext = Readonly<{
9
9
  pages: readonly VitePluginTaroPageOption[]
10
10
  appConfig: JsonObject
11
11
  projectConfigJson: JsonObject
12
- projectPrivateConfigJson: JsonObject
12
+ projectPrivateConfigJson?: JsonObject
13
13
  sitemapJson: JsonObject
14
14
  }>
15
15
 
@@ -21,6 +21,7 @@ export class BuildContext {
21
21
  private resolvedViteConfig: ResolvedConfig | undefined
22
22
 
23
23
  constructor(options: VitePluginTaroOptions) {
24
+ this.css = new CssPipeline(options.target)
24
25
  this.project = {
25
26
  target: options.target,
26
27
  appComponentFile: path.resolve(options.app),
@@ -33,7 +34,6 @@ export class BuildContext {
33
34
  projectPrivateConfigJson: options.projectPrivateConfigJson,
34
35
  sitemapJson: options.sitemapJson
35
36
  }
36
- this.css = new CssPipeline(options.target)
37
37
  }
38
38
 
39
39
  configure(environment: ConfigEnv): void {
@@ -48,7 +48,6 @@ export class BuildContext {
48
48
  }
49
49
  if (this.resolvedViteConfig) throw new Error('vite-plugin-taro build context was already resolved.')
50
50
  this.resolvedViteConfig = config
51
- this.css.resolve(config.root)
52
51
  }
53
52
 
54
53
  get development(): boolean {
@@ -1,9 +1,17 @@
1
+ import fs from 'node:fs/promises'
1
2
  import path from 'node:path'
2
- import type { Plugin } from 'vite'
3
+ import { extractSourceCandidates } from '@tailwindcss-mangle/engine'
4
+ import type { PluginOption, ResolvedConfig } from 'vite'
3
5
  import { createContext } from 'weapp-tailwindcss/core'
4
- import { createWeappTailwindcssGenerator, resolveTailwindV4Source } from 'weapp-tailwindcss/generator'
6
+ import {
7
+ createWeappTailwindcssGenerator,
8
+ resolveTailwindV4Source,
9
+ type WeappTailwindcssGenerator
10
+ } from 'weapp-tailwindcss/generator'
11
+ import { WeappTailwindcss } from 'weapp-tailwindcss/vite'
5
12
  import type { VitePluginTaroTarget } from '../../options.ts'
6
13
  import { normalizeModuleId } from '../utils/modules.ts'
14
+ import { resolvePackageFile } from '../utils/packages.ts'
7
15
 
8
16
  const wxStyleOptions = {
9
17
  cssCalc: false,
@@ -12,136 +20,152 @@ const wxStyleOptions = {
12
20
  px2rpx: true
13
21
  } as const
14
22
 
15
- type WxCssContext = ReturnType<typeof createContext>
16
- type CssTransformResult = { code: string; map: null }
23
+ type WxContext = ReturnType<typeof createContext>
24
+ type CssEntry = { source: string; generator: WeappTailwindcssGenerator }
25
+ type PatchResult = { code: string } | { requiresFullBuild: true }
17
26
 
18
- /** Owns CSS generation and the class-name state shared by normal chunks and literal WX patches. */
27
+ /** Owns Tailwind's Vite integration and native WX patch synchronization. */
19
28
  export class CssPipeline {
20
- readonly plugin: Plugin
21
- private readonly target: VitePluginTaroTarget
22
- private readonly runtimeClassSet = new Set<string>()
23
- private projectRoot: string | undefined
24
- private wxContext: WxCssContext | undefined
25
-
29
+ readonly plugins: PluginOption[]
30
+ // Each Tailwind CSS entry can define a different design system, so additions must pass every relevant validator.
31
+ private readonly entries = new Map<string, CssEntry>()
32
+ // Vite can report the same source more than once; avoid repeating extraction and validation.
33
+ private readonly sourceCache = new Map<string, string>()
34
+ // Upstream owns WX JavaScript escaping; this context is deliberately reused between patches.
35
+ private wxContext: WxContext | undefined
36
+ // CSS transforms arrive after configResolved, so the Vite root is retained for source and module resolution.
37
+ private root: string | undefined
38
+ // Only classes represented by a completed WX build are safe to publish in a JavaScript-only patch.
39
+ private builtClassSet = new Set<string>()
40
+
41
+ /** Composes the upstream web/WX plugins and adds candidate tracking only for native WX patches. */
26
42
  constructor(target: VitePluginTaroTarget) {
27
- this.target = target
28
- this.plugin = this.createPlugin()
43
+ const pipeline = this
44
+
45
+ const wx = target === 'wx'
46
+
47
+ this.plugins = [
48
+ wx
49
+ ? {
50
+ name: 'vite-plugin-taro:wx-tailwind-pipeline',
51
+ enforce: 'pre',
52
+ configResolved(config) {
53
+ pipeline.resolveWx(config)
54
+ },
55
+ async transform(code, id) {
56
+ if (!isTailwindCssEntry(code, id)) return
57
+ await pipeline.registerCssEntry(id, code)
58
+ }
59
+ }
60
+ : undefined,
61
+ ...(WeappTailwindcss({
62
+ appType: 'taro',
63
+ // Route split Tailwind imports around Vite's unresolved production CSS package imports.
64
+ rewriteCssImports: true,
65
+ generator: { target: wx ? 'weapp' : 'web' },
66
+ ...(wx ? wxStyleOptions : {}),
67
+ logLevel: 'silent'
68
+ }) ?? [])
69
+ ]
70
+ }
71
+
72
+ /** Records every utility guaranteed to exist after a successful full WX build. */
73
+ async captureFullBuild(): Promise<void> {
74
+ this.sourceCache.clear()
75
+ // Preserve removed development CSS, matching upstream's append-only HMR default.
76
+ for (const { generator } of this.entries.values()) {
77
+ const result = await generator.generate({ scanSources: true })
78
+ for (const candidate of result.classSet) this.builtClassSet.add(candidate)
79
+ }
29
80
  }
30
81
 
31
- resolve(projectRoot: string): void {
32
- if (this.projectRoot) throw new Error('vite-plugin-taro CSS pipeline was already resolved.')
33
- this.projectRoot = projectRoot
34
- this.wxContext = this.target === 'wx' ? createWxCssContext(projectRoot) : undefined
82
+ /** Applies upstream mini-program compatibility transforms to Taro's fully materialized WXSS. */
83
+ async transformWxss(code: string): Promise<string> {
84
+ return (await this.getWxContext().transformWxss(code)).css
35
85
  }
36
86
 
37
- async transformWxClassNames(code: string, filename: string): Promise<CssTransformResult> {
38
- if (!this.wxContext || this.runtimeClassSet.size === 0) return { code, map: null }
39
- const result = await this.wxContext.transformJs(code, {
40
- runtimeSet: this.runtimeClassSet,
87
+ /** Escapes a JavaScript-only WX patch, or rejects it when its utilities require new WXSS. */
88
+ async transformNativePatch(code: string, filename: string, files: string[]): Promise<PatchResult> {
89
+ if (await this.hasAddedCandidates(files)) return { requiresFullBuild: true }
90
+ const result = await this.getWxContext().transformJs(code, {
91
+ runtimeSet: this.builtClassSet,
41
92
  filename,
42
93
  generateMap: false
43
94
  })
44
- return { code: result.code, map: null }
95
+ return { code: result.code }
45
96
  }
46
97
 
47
- private createPlugin(): Plugin {
48
- const pipeline = this
49
- return {
50
- name: 'vite-plugin-taro:css',
51
- enforce: 'pre',
52
-
53
- buildStart() {
54
- pipeline.runtimeClassSet.clear()
55
- },
56
-
57
- async transform(code, id) {
58
- if (!isCssModuleId(id) || !shouldGenerateTailwindCss(code)) return
59
-
60
- const projectRoot = pipeline.getProjectRoot()
61
- const cssFile = resolveCssFile(id, projectRoot)
62
- const cssBase = path.dirname(cssFile)
63
- const source = await resolveTailwindV4Source({
64
- projectRoot,
65
- cwd: projectRoot,
66
- base: cssBase,
67
- css: code,
68
- cssSources: [{ file: cssFile, base: cssBase, css: code, dependencies: [cssFile] }]
69
- })
70
- const generator = createWeappTailwindcssGenerator(source)
71
- const generated = await generator.generate({
72
- target: pipeline.target === 'wx' ? 'weapp' : 'web',
73
- scanSources: true,
74
- candidates: [],
75
- styleOptions: pipeline.target === 'wx' ? wxStyleOptions : undefined
76
- })
77
-
78
- for (const className of generated.classSet) pipeline.runtimeClassSet.add(className)
79
- for (const dependency of generated.dependencies) this.addWatchFile(dependency)
80
- return generated.css
81
- },
82
-
83
- async renderChunk(code, chunk) {
84
- if (pipeline.target !== 'wx') return
85
- return await pipeline.transformWxClassNames(code, chunk.fileName)
86
- },
87
-
88
- async generateBundle(_, bundle) {
89
- if (pipeline.target !== 'wx') return
90
- const core = pipeline.getWxContext()
91
- await Promise.all(
92
- Object.entries(bundle).map(async ([fileName, item]) => {
93
- if (item.type === 'asset' && fileName.endsWith('.css')) {
94
- await transformWxssAsset(core, item)
95
- }
96
- })
97
- )
98
- }
99
- }
98
+ /** Retains Vite's root for resolving later CSS transforms and changed-file notifications. */
99
+ private resolveWx(config: ResolvedConfig): void {
100
+ this.root = config.root
100
101
  }
101
102
 
102
- private getProjectRoot(): string {
103
- if (!this.projectRoot) throw new Error('vite-plugin-taro CSS pipeline was used before configuration resolved.')
104
- return this.projectRoot
103
+ /** Creates one validator per Tailwind entry and refreshes the shared upstream WX transformer. */
104
+ private async registerCssEntry(id: string, source: string): Promise<void> {
105
+ const file = resolveFile(id, this.getRoot())
106
+ if (this.entries.get(file)?.source === source) return
107
+ const base = path.dirname(file)
108
+ const resolved = await resolveTailwindV4Source({
109
+ projectRoot: this.getRoot(),
110
+ cwd: this.getRoot(),
111
+ base,
112
+ baseFallbacks: [resolvePackageFile()],
113
+ css: source,
114
+ cssSources: [{ file, base, css: source, dependencies: [file] }]
115
+ })
116
+ this.entries.set(file, { source, generator: createWeappTailwindcssGenerator(resolved) })
117
+ this.wxContext = createContext({
118
+ appType: 'taro',
119
+ cssEntries: [...this.entries.keys()],
120
+ tailwindcssBasedir: this.getRoot(),
121
+ generator: { target: 'weapp' },
122
+ ...wxStyleOptions,
123
+ logLevel: 'silent'
124
+ })
105
125
  }
106
126
 
107
- private getWxContext(): WxCssContext {
108
- if (!this.wxContext) throw new Error('vite-plugin-taro expected a resolved WeChat CSS pipeline.')
109
- return this.wxContext
127
+ /** Incrementally checks changed source files for utilities absent from the completed WX build. */
128
+ private async hasAddedCandidates(files: string[]): Promise<boolean> {
129
+ for (const input of files) {
130
+ const file = resolveFile(input, this.getRoot())
131
+ if (!/\.[cm]?[jt]sx?$/.test(file)) continue
132
+ const source = await fs.readFile(file, 'utf8')
133
+ if (this.sourceCache.get(file) === source) continue
134
+ this.sourceCache.set(file, source)
135
+ const extracted = await extractSourceCandidates(source, path.extname(file).slice(1))
136
+ for (const { generator } of this.entries.values()) {
137
+ const candidates = await generator.validateCandidates(extracted)
138
+ // Native WX patches bypass Vite; a new utility therefore requires synchronized WXSS first.
139
+ for (const candidate of candidates) if (!this.builtClassSet.has(candidate)) return true
140
+ }
141
+ }
142
+ return false
110
143
  }
111
- }
112
144
 
113
- function createWxCssContext(projectRoot: string): WxCssContext {
114
- return createContext({
115
- appType: 'taro',
116
- tailwindcssBasedir: projectRoot,
117
- generator: { target: 'weapp' },
118
- ...wxStyleOptions,
119
- logLevel: 'silent'
120
- })
121
- }
122
-
123
- async function transformWxssAsset(core: WxCssContext, item: { source?: string | Uint8Array }): Promise<void> {
124
- const result = await core.transformWxss(getAssetSource(item), { isMainChunk: true })
125
- item.source = result.css
126
- }
127
-
128
- function shouldGenerateTailwindCss(code: string): boolean {
129
- const tailwindEntryImportPattern =
130
- /@(import|reference)\s+(?:url\(\s*)?(?:["'])tailwindcss(?:\/(?:theme|preflight|utilities)(?:\.css)?)?(?:["'])/
131
- return code.includes('tailwindcss') && tailwindEntryImportPattern.test(code)
132
- }
145
+ /** Returns the initialized WX transformer and detects invalid lifecycle ordering. */
146
+ private getWxContext(): WxContext {
147
+ if (!this.wxContext) throw new Error('WX CSS pipeline was used before Vite resolved.')
148
+ return this.wxContext
149
+ }
133
150
 
134
- function isCssModuleId(id: string): boolean {
135
- return /\.(?:css|scss|sass|less|styl|stylus)(?:$|[?#])/.test(id)
151
+ /** Returns the resolved project root and detects use before Vite configuration. */
152
+ private getRoot(): string {
153
+ if (!this.root) throw new Error('WX CSS pipeline was used before Vite resolved.')
154
+ return this.root
155
+ }
136
156
  }
137
157
 
138
- function getAssetSource(item: { source?: string | Uint8Array }): string {
139
- if (typeof item.source === 'string') return item.source
140
- return item.source ? new TextDecoder().decode(item.source) : ''
158
+ /** Identifies style modules that establish a Tailwind design system. */
159
+ function isTailwindCssEntry(code: string, id: string): boolean {
160
+ return (
161
+ /\.(?:css|scss|sass|less|styl|stylus)(?:$|[?#])/.test(id) &&
162
+ /@(?:import|reference)\s+(?:url\(\s*)?["']tailwindcss(?:\/[^"']*)?["']/.test(code)
163
+ )
141
164
  }
142
165
 
143
- function resolveCssFile(id: string, root: string): string {
144
- const normalizedId = normalizeModuleId(id)
145
- const cleanId = normalizedId.startsWith('/@fs/') ? normalizedId.slice('/@fs'.length) : normalizedId
146
- return path.isAbsolute(cleanId) ? cleanId : path.resolve(root, cleanId)
166
+ /** Converts Vite IDs, including /@fs/ IDs and query strings, into normalized absolute paths. */
167
+ function resolveFile(id: string, root: string): string {
168
+ const normalized = normalizeModuleId(id).replace(/[?#].*$/, '')
169
+ const file = normalized.startsWith('/@fs/') ? normalized.slice('/@fs'.length) : normalized
170
+ return normalizeModuleId(path.resolve(root, file))
147
171
  }
@@ -19,6 +19,7 @@ export type WxBundle = Record<
19
19
 
20
20
  type WxAssetEmitter = {
21
21
  emitFile(asset: { type: 'asset'; fileName: string; source: WxAssetSource }): string
22
+ warn(message: string): void
22
23
  }
23
24
 
24
25
  type WxTemplateComponentConfig = {
@@ -31,21 +32,32 @@ type WxTemplateComponentConfig = {
31
32
  const taroWxComponentsPath = packageRequire.resolve('@tarojs/plugin-platform-weapp/dist/components-react')
32
33
  const templateBuilder = createWxTemplateBuilder()
33
34
 
34
- export function emitWxCompanionAssets(emitter: WxAssetEmitter, bundle: WxBundle, context: BuildContext): void {
35
- for (const asset of createWxCompanionAssets(bundle, context)) {
35
+ export async function emitWxCompanionAssets(
36
+ emitter: WxAssetEmitter,
37
+ bundle: WxBundle,
38
+ context: BuildContext
39
+ ): Promise<void> {
40
+ for (const asset of await createWxCompanionAssets(bundle, context)) {
41
+ if (!asset) continue
42
+ if (asset.source === undefined) {
43
+ emitter.warn(
44
+ `[vite-plugin-taro] WX companion asset "${asset.fileName}" is missing its source and was not emitted.`
45
+ )
46
+ continue
47
+ }
36
48
  emitter.emitFile({ type: 'asset', fileName: asset.fileName, source: asset.source })
37
49
  }
38
50
  }
39
51
 
40
- function createWxCompanionAssets(
52
+ async function createWxCompanionAssets(
41
53
  bundle: WxBundle,
42
54
  context: BuildContext
43
- ): { fileName: string; source: WxAssetSource }[] {
55
+ ): Promise<({ fileName: string; source: WxAssetSource } | undefined)[]> {
44
56
  const json = (value: JsonObject) => (context.development ? JSON.stringify(value, null, 2) : JSON.stringify(value))
45
57
 
46
58
  return [
47
59
  { fileName: 'app.json', source: json(context.project.appConfig) },
48
- { fileName: 'app.wxss', source: collectWxBundleStyles(bundle) },
60
+ { fileName: 'app.wxss', source: await context.css.transformWxss(collectWxBundleStyles(bundle)) },
49
61
  {
50
62
  fileName: 'base.wxml',
51
63
  source: templateBuilder.buildTemplate(collectWxTemplateComponentConfig(bundle))
@@ -54,7 +66,9 @@ function createWxCompanionAssets(
54
66
  { fileName: 'comp.wxml', source: templateBuilder.buildBaseComponentTemplate('.wxml') },
55
67
  { fileName: 'comp.json', source: json(createWxComponentConfig()) },
56
68
  { fileName: 'project.config.json', source: json(createWxProjectConfig(context)) },
57
- { fileName: 'project.private.config.json', source: json(context.project.projectPrivateConfigJson) },
69
+ context.project.projectPrivateConfigJson
70
+ ? { fileName: 'project.private.config.json', source: json(context.project.projectPrivateConfigJson) }
71
+ : undefined,
58
72
  { fileName: 'sitemap.json', source: json(context.project.sitemapJson) },
59
73
  ...context.project.pages.flatMap((page) => [
60
74
  {
@@ -131,13 +131,11 @@ export class WxDevelopmentSession {
131
131
 
132
132
  private handleBundleOutput(output: WxOutputFile[]): void {
133
133
  const appStyles = normalizeWxBundleStyles(output)
134
- if (appStyles !== undefined) this.snapshot = { ...this.snapshot, latestAppStyles: appStyles }
135
- if (isWxFullBuildOutput(output) && this.snapshot.latestAppStyles) {
136
- setWxAppStyles(output, this.snapshot.latestAppStyles)
137
- }
134
+ const fullBuild = isWxFullBuildOutput(output)
138
135
 
139
- if (!isWxFullBuildOutput(output)) {
136
+ if (!fullBuild) {
140
137
  this.outputQueue.enqueue(async () => {
138
+ await this.finalizeAppStyles(output, appStyles, false)
141
139
  await transformWxOutputChunks(output)
142
140
  await writeDevelopmentOutput(this.outDir, output)
143
141
  })
@@ -148,6 +146,7 @@ export class WxDevelopmentSession {
148
146
  setDevelopmentAsset(output, wxUpdateControlFile, this.updateTransport.createControlSource(buildId))
149
147
  setDevelopmentAsset(output, wxUpdateFile, 'void 0;\n')
150
148
  this.outputQueue.enqueue(async () => {
149
+ await this.finalizeAppStyles(output, appStyles, true)
151
150
  await transformWxOutputChunks(output)
152
151
  if (this.snapshot.outputPhase === 'starting') {
153
152
  // The directory is plugin-owned; clearing it once removes stale files from previous protocol designs.
@@ -157,6 +156,7 @@ export class WxDevelopmentSession {
157
156
  this.updateTransport.commitFullBuild(buildId)
158
157
  await writeDevelopmentOutput(this.outDir, output)
159
158
  await copyDirectoryIfExists(this.config.publicDir, this.outDir)
159
+ await this.context.css.captureFullBuild()
160
160
  const moduleCount = this.adapter.registerBundleModules(output)
161
161
  this.snapshot = { ...this.snapshot, outputPhase: 'ready' }
162
162
  this.initialBundle.resolve()
@@ -166,6 +166,20 @@ export class WxDevelopmentSession {
166
166
  })
167
167
  }
168
168
 
169
+ private async finalizeAppStyles(
170
+ output: WxOutputFile[],
171
+ source: string | undefined,
172
+ fullBuild: boolean
173
+ ): Promise<void> {
174
+ if (source !== undefined) {
175
+ const transformed = await this.context.css.transformWxss(source)
176
+ this.snapshot = { ...this.snapshot, latestAppStyles: transformed }
177
+ setWxAppStyles(output, transformed)
178
+ } else if (fullBuild && this.snapshot.latestAppStyles) {
179
+ setWxAppStyles(output, this.snapshot.latestAppStyles)
180
+ }
181
+ }
182
+
169
183
  private handlePatch(files: string[], output: WxDevEngineUpdate): boolean {
170
184
  if (!isSafeJavaScriptPatch(files, output)) {
171
185
  this.requestFullBuild()
@@ -174,7 +188,11 @@ export class WxDevelopmentSession {
174
188
 
175
189
  this.adapter.registerPatchModules(output.code)
176
190
  this.outputQueue.enqueue(async () => {
177
- const transformed = await this.context.css.transformWxClassNames(output.code, output.filename)
191
+ const transformed = await this.context.css.transformNativePatch(output.code, output.filename, files)
192
+ if ('requiresFullBuild' in transformed) {
193
+ this.requestFullBuild()
194
+ return
195
+ }
178
196
  const compatibleCode = await transformWxCompatibleJavaScript(transformed.code, output.filename)
179
197
  if (
180
198
  this.updateTransport.retainedDeltaCount >= maxRetainedDeltaCount ||
@@ -53,8 +53,8 @@ function createWxTargetPlugin(context: BuildContext): Plugin {
53
53
 
54
54
  generateBundle: {
55
55
  order: 'post',
56
- handler(_, bundle) {
57
- emitWxCompanionAssets(this, bundle as WxBundle, context)
56
+ async handler(_, bundle) {
57
+ await emitWxCompanionAssets(this, bundle as WxBundle, context)
58
58
  }
59
59
  },
60
60
 
@@ -107,6 +107,7 @@ export function createWxViteConfig(context: BuildContext): UserConfig {
107
107
  target: 'es2018',
108
108
  assetsInlineLimit: 1024,
109
109
  cssCodeSplit: false,
110
+ // weapp-tailwindcss has no minifier setting; retain Vite's production CSS minification.
110
111
  cssMinify: context.development ? false : 'lightningcss',
111
112
  minify: !context.development,
112
113
  rolldownOptions: {
@@ -10,16 +10,15 @@ import { createWxTargetPlugins, createWxViteConfig } from './targets/wx/plugin.t
10
10
  /** Creates the Vite plugins for the selected Taro target. */
11
11
  export default function vitePluginTaro(options: VitePluginTaroOptions): PluginOption[] {
12
12
  const context = new BuildContext(options)
13
- const targetPlugins =
14
- context.project.target === 'wx' ? createWxTargetPlugins(context) : createH5TargetPlugins(context)
15
13
 
16
14
  return [
17
15
  createBuildCoordinator(context),
18
16
  createConditionalDirectivePlugin(context),
19
17
  createTaroRuntimePlugin(),
20
- context.css.plugin,
18
+ ...context.css.plugins,
21
19
  ...react(),
22
- ...targetPlugins
20
+ ...(context.project.target === 'wx' ? createWxTargetPlugins(context) : []),
21
+ ...(context.project.target === 'h5' ? createH5TargetPlugins(context) : [])
23
22
  ]
24
23
  }
25
24
 
package/src/options.ts CHANGED
@@ -20,6 +20,6 @@ export interface VitePluginTaroOptions {
20
20
  pages: VitePluginTaroPageOption[]
21
21
  appJson: JsonObject
22
22
  projectConfigJson: JsonObject
23
- projectPrivateConfigJson: JsonObject
23
+ projectPrivateConfigJson?: JsonObject
24
24
  sitemapJson: JsonObject
25
25
  }
@@ -168,7 +168,8 @@ export function startWxUpdateClient(): void {
168
168
  try {
169
169
  batchApply()
170
170
  dispatch({ type: 'batch-executed', targetVersion: command.targetVersion })
171
- } catch {
171
+ } catch (error) {
172
+ console.error('[vite-plugin-taro] WX update execution failed', error)
172
173
  dispatch({ type: 'batch-failed' })
173
174
  } finally {
174
175
  bridge.endUpdate?.()