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.
- package/dist/node/build-context.d.ts +1 -1
- package/dist/node/build-context.js +1 -2
- package/dist/node/css/css-pipeline.d.ts +26 -12
- package/dist/node/css/css-pipeline.js +126 -101
- package/dist/node/targets/wx/companion-assets.d.ts +2 -1
- package/dist/node/targets/wx/companion-assets.js +13 -5
- package/dist/node/targets/wx/dev-server/development-session.d.ts +1 -0
- package/dist/node/targets/wx/dev-server/development-session.js +20 -7
- package/dist/node/targets/wx/plugin.js +3 -2
- package/dist/node/vite-plugin.js +3 -3
- package/dist/options.d.ts +1 -1
- package/dist/runtime/wx/update-client.js +2 -1
- package/package.json +5 -4
- package/src/node/build-context.ts +2 -3
- package/src/node/css/css-pipeline.ts +135 -111
- package/src/node/targets/wx/companion-assets.ts +20 -6
- package/src/node/targets/wx/dev-server/development-session.ts +24 -6
- package/src/node/targets/wx/plugin.ts +3 -2
- package/src/node/vite-plugin.ts +3 -4
- package/src/options.ts +1 -1
- package/src/runtime/wx/update-client.ts +2 -1
|
@@ -7,7 +7,7 @@ type ProjectContext = Readonly<{
|
|
|
7
7
|
pages: readonly VitePluginTaroPageOption[];
|
|
8
8
|
appConfig: JsonObject;
|
|
9
9
|
projectConfigJson: JsonObject;
|
|
10
|
-
projectPrivateConfigJson
|
|
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 {
|
|
1
|
+
import type { PluginOption } from 'vite';
|
|
2
2
|
import type { VitePluginTaroTarget } from '../../options.ts';
|
|
3
|
-
type
|
|
3
|
+
type PatchResult = {
|
|
4
4
|
code: string;
|
|
5
|
-
|
|
5
|
+
} | {
|
|
6
|
+
requiresFullBuild: true;
|
|
6
7
|
};
|
|
7
|
-
/** Owns
|
|
8
|
+
/** Owns Tailwind's Vite integration and native WX patch synchronization. */
|
|
8
9
|
export declare class CssPipeline {
|
|
9
|
-
readonly
|
|
10
|
-
private readonly
|
|
11
|
-
private readonly
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
15
|
+
/** Owns Tailwind's Vite integration and native WX patch synchronization. */
|
|
12
16
|
export class CssPipeline {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
80
|
+
return { code: result.code };
|
|
37
81
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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('
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
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
|
-
|
|
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
|
{
|
|
@@ -98,13 +98,10 @@ export class WxDevelopmentSession {
|
|
|
98
98
|
};
|
|
99
99
|
handleBundleOutput(output) {
|
|
100
100
|
const appStyles = normalizeWxBundleStyles(output);
|
|
101
|
-
|
|
102
|
-
|
|
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.
|
|
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: {
|
package/dist/node/vite-plugin.js
CHANGED
|
@@ -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.
|
|
14
|
+
...context.css.plugins,
|
|
16
15
|
...react(),
|
|
17
|
-
...
|
|
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
|
@@ -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.
|
|
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": "
|
|
69
|
-
"@tarojs/plugin-framework-react": "npm:vite-plugin-taro-plugin-framework-react@0.3.
|
|
70
|
-
"@tarojs/react": "npm:vite-plugin-taro-react@0.3.
|
|
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
|
|
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
|
|
3
|
+
import { extractSourceCandidates } from '@tailwindcss-mangle/engine'
|
|
4
|
+
import type { PluginOption, ResolvedConfig } from 'vite'
|
|
3
5
|
import { createContext } from 'weapp-tailwindcss/core'
|
|
4
|
-
import {
|
|
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
|
|
16
|
-
type
|
|
23
|
+
type WxContext = ReturnType<typeof createContext>
|
|
24
|
+
type CssEntry = { source: string; generator: WeappTailwindcssGenerator }
|
|
25
|
+
type PatchResult = { code: string } | { requiresFullBuild: true }
|
|
17
26
|
|
|
18
|
-
/** Owns
|
|
27
|
+
/** Owns Tailwind's Vite integration and native WX patch synchronization. */
|
|
19
28
|
export class CssPipeline {
|
|
20
|
-
readonly
|
|
21
|
-
|
|
22
|
-
private readonly
|
|
23
|
-
|
|
24
|
-
private
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
this.
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
95
|
+
return { code: result.code }
|
|
45
96
|
}
|
|
46
97
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
135
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
return
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
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(
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
135
|
-
if (isWxFullBuildOutput(output) && this.snapshot.latestAppStyles) {
|
|
136
|
-
setWxAppStyles(output, this.snapshot.latestAppStyles)
|
|
137
|
-
}
|
|
134
|
+
const fullBuild = isWxFullBuildOutput(output)
|
|
138
135
|
|
|
139
|
-
if (!
|
|
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.
|
|
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: {
|
package/src/node/vite-plugin.ts
CHANGED
|
@@ -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.
|
|
18
|
+
...context.css.plugins,
|
|
21
19
|
...react(),
|
|
22
|
-
...
|
|
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
|
@@ -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?.()
|