@weapp-vite/tailwindcss 0.2.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,4 +10,4 @@ Tailwind 扫描、候选删除、CSS 生成与类名转换仍由上游 core 实
10
10
 
11
11
  首期以微信双宿主运行时为验收目标;支付宝与抖音提供编译/适配契约验证,不宣称其运行时 HMR 已完成验收。
12
12
 
13
- Node.js 版本要求随编译内核:`^22.18.0 || >=24.11.0`。
13
+ Node.js 版本要求随编译内核:`^22.18.0 || ^24.11.0 || >=26.0.0`,不支持 Node 20、23、25 或低于最低补丁版本的环境。
package/dist/index.d.mts CHANGED
@@ -22,6 +22,10 @@ export declare function createTailwindController(options?: TailwindControllerOpt
22
22
  /** 同一份 core 快照用于所有转换;宿主提供最终资产,公共层不推断框架输出归属。 */
23
23
  export declare function createTailwindPreparation(compiler: Compiler$1, snapshot: CompilerSnapshot$1, assets?: NonNullable<HmrCompilerPreparation['assets']>): HmrCompilerPreparation;
24
24
  //#endregion
25
+ //#region src/generationValue.d.ts
26
+ /** 只复制可比较的生成配置;保留函数身份、属性描述与原型,拒绝不可安全封存的输入。 */
27
+ export declare function copyTailwindGenerationValue<T>(value: T, parents?: Set<object>): T;
28
+ //#endregion
25
29
  //#region src/output.d.ts
26
30
  export interface TailwindOutputInput {
27
31
  id: string;
@@ -45,14 +49,16 @@ export interface TailwindRootState {
45
49
  result: CompilerGenerateResult$1;
46
50
  rawSources: ReadonlyMap<string, string | null | undefined>;
47
51
  }
48
- export interface TailwindRootHost {
52
+ export interface TailwindRootHost<K = unknown> {
49
53
  cwd: string;
50
54
  sourceId: (file: string) => string;
51
55
  isOutput?: (file: string) => boolean;
52
56
  reusePreprocessedCss?: (file: string, previous: string | null | undefined, current: string | null | undefined) => boolean;
57
+ /** 捕获实际内存请求的独立只读副本;生成和缓存命中均调用,失败保留原始错误。 */
58
+ captureRequest?: (key: K, request: CompilerGenerateRequest$1) => void | Promise<void>;
53
59
  }
54
60
  /** 使用封存源码驱动上游完整候选生成;框架预处理与输出排除由宿主提供。 */
55
- export declare function prepareTailwindRoots<K>(compiler: Compiler$1, request: HmrCompilerRequest, roots: ReadonlyMap<K, TailwindRootState>, options: TailwindRootHost): Promise<Map<K, CompilerGenerateResult$1>>;
61
+ export declare function prepareTailwindRoots<K>(compiler: Compiler$1, request: HmrCompilerRequest, roots: ReadonlyMap<K, TailwindRootState>, options: TailwindRootHost<K>): Promise<Map<K, CompilerGenerateResult$1>>;
56
62
  //#endregion
57
63
  //#region src/sources.d.ts
58
64
  /** 返回上游实际扫描边界中的文件,宿主将它们登记到现有 watcher。 */
package/dist/index.mjs CHANGED
@@ -1,8 +1,11 @@
1
1
  import { CompilerHmrResyncError, normalizeHmrSourceMap } from "@weapp-vite/hmr";
2
+ import { types } from "node:util";
2
3
  import { createHash } from "node:crypto";
3
4
  import { readFileSync, statSync } from "node:fs";
4
- import { createTailwindV4CompiledSourceEntries, createTailwindV4SourceEntryMatcher, resolveProjectSourceFiles } from "@weapp-tailwindcss/engine";
5
5
  import path from "pathe";
6
+ import path$1 from "node:path";
7
+ import { resolveSourceScanPathWithApi, sourcePathApi, toPosixPath } from "@weapp-tailwindcss/source-scan";
8
+ import micromatch from "micromatch";
6
9
  //#region src/controller.ts
7
10
  /** 编译器按宿主生命周期惰性创建;不创建 Vite、DevEngine、watcher 或输出写入者。 */
8
11
  var TailwindController = class {
@@ -63,6 +66,34 @@ function createTailwindPreparation(compiler, snapshot, assets = []) {
63
66
  };
64
67
  }
65
68
  //#endregion
69
+ //#region src/generationValue.ts
70
+ /** 只复制可比较的生成配置;保留函数身份、属性描述与原型,拒绝不可安全封存的输入。 */
71
+ function copyTailwindGenerationValue(value, parents = /* @__PURE__ */ new Set()) {
72
+ if (value === null || typeof value !== "object") return value;
73
+ if (types.isProxy(value)) throw new TypeError("Tailwind generation configuration must not contain proxies");
74
+ const array = Array.isArray(value);
75
+ if (parents.has(value) || (array ? Object.getPrototypeOf(value) !== Array.prototype : Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)) throw new TypeError("Unsupported Tailwind generation configuration");
76
+ parents.add(value);
77
+ try {
78
+ const keys = Reflect.ownKeys(value);
79
+ if (array && keys.some((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9]\d*)$/.test(key) || Number(key) >= value.length))) throw new TypeError("Tailwind generation configuration must not contain custom array properties");
80
+ const entries = keys.filter((key) => !array || key !== "length").map((key) => {
81
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
82
+ if (!("value" in descriptor)) throw new TypeError("Tailwind generation configuration must not contain accessors");
83
+ return [key, copyTailwindGenerationValue(descriptor.value, parents)];
84
+ });
85
+ const copy = array ? [] : Object.create(Object.getPrototypeOf(value));
86
+ if (array) copy.length = value.length;
87
+ for (const [key, item] of entries) Object.defineProperty(copy, key, {
88
+ value: item,
89
+ enumerable: Object.getOwnPropertyDescriptor(value, key).enumerable
90
+ });
91
+ return Object.freeze(copy);
92
+ } finally {
93
+ parents.delete(value);
94
+ }
95
+ }
96
+ //#endregion
66
97
  //#region src/output.ts
67
98
  /** 在宿主回调内封存投影,再异步转换;Vite 最终 CSS 和所有补丁共用同一身份集合。 */
68
99
  function prepareTailwindOutput(compiler, input) {
@@ -84,6 +115,127 @@ function prepareTailwindOutput(compiler, input) {
84
115
  });
85
116
  }
86
117
  //#endregion
118
+ //#region src/sources/compact.ts
119
+ const MAX_COMPACTED_PATTERN_LENGTH = 8192;
120
+ function isLiteral(value) {
121
+ return /^[\w.-]+$/.test(value) && value !== "." && !value.includes("..");
122
+ }
123
+ function classify(entry) {
124
+ if (entry.negated !== true || typeof entry.base !== "string" || typeof entry.pattern !== "string" || !entry.pattern.startsWith("**/")) return;
125
+ const rest = entry.pattern.slice(3);
126
+ if (rest.endsWith("/**")) {
127
+ const token = rest.slice(0, -3);
128
+ return isLiteral(token) ? {
129
+ shape: "directory",
130
+ token
131
+ } : void 0;
132
+ }
133
+ const extension = /^\*\.([\w-]+)$/.exec(rest)?.[1];
134
+ if (extension) return {
135
+ shape: "extension",
136
+ token: extension
137
+ };
138
+ return isLiteral(rest) ? {
139
+ shape: "file",
140
+ token: rest
141
+ } : void 0;
142
+ }
143
+ function combinedPattern(shape, tokens) {
144
+ const prefix = shape === "extension" ? "**/*." : "**/";
145
+ const suffix = shape === "directory" ? "/**" : "";
146
+ return `${prefix}{${[...tokens].join(",")}}${suffix}`;
147
+ }
148
+ /** 仅合并连续的固定否定形态;保留上游 globstar、隐藏文件与异常求值顺序。 */
149
+ function compactTailwindSourceEntries(entries) {
150
+ const result = [];
151
+ for (let index = 0; index < entries.length;) {
152
+ const first = entries[index];
153
+ const shape = classify(first);
154
+ if (!shape) {
155
+ result.push(first);
156
+ index += 1;
157
+ continue;
158
+ }
159
+ const run = [first];
160
+ const tokens = /* @__PURE__ */ new Set([shape.token]);
161
+ let next = index + 1;
162
+ while (next < entries.length) {
163
+ const current = entries[next];
164
+ const member = classify(current);
165
+ if (current.base !== first.base || member?.shape !== shape.shape) break;
166
+ run.push(current);
167
+ tokens.add(member.token);
168
+ next += 1;
169
+ }
170
+ const pattern = combinedPattern(shape.shape, tokens);
171
+ result.push(...tokens.size > 1 && pattern.length <= MAX_COMPACTED_PATTERN_LENGTH ? [{
172
+ ...first,
173
+ pattern
174
+ }] : run);
175
+ index = next;
176
+ }
177
+ return result;
178
+ }
179
+ //#endregion
180
+ //#region src/sources/matcher.ts
181
+ /** 检查不执行访问器;不明对象仍由上游读取并保持原异常顺序。 */
182
+ function hasPlainEntries(entries) {
183
+ if (types.isProxy(entries) || !Array.isArray(entries) || Object.getPrototypeOf(entries) !== Array.prototype || Reflect.ownKeys(entries).some((key) => typeof key !== "string" || key !== "length" && !/^(?:0|[1-9]\d*)$/.test(key))) return false;
184
+ for (let index = 0; index < entries.length; index++) {
185
+ const item = Object.getOwnPropertyDescriptor(entries, String(index));
186
+ if (!item || !("value" in item)) return false;
187
+ const entry = item.value;
188
+ if (!entry || typeof entry !== "object" || types.isProxy(entry)) return false;
189
+ const prototype = Object.getPrototypeOf(entry);
190
+ if (prototype !== null && prototype !== Object.prototype) return false;
191
+ const descriptors = Object.getOwnPropertyDescriptors(entry);
192
+ if (Reflect.ownKeys(entry).some((key) => !("value" in Object.getOwnPropertyDescriptor(entry, key)))) return false;
193
+ if (typeof descriptors.base?.value !== "string" || typeof descriptors.pattern?.value !== "string" || typeof descriptors.negated?.value !== "boolean") return false;
194
+ }
195
+ return true;
196
+ }
197
+ /** 绝对模式沿上游词法 base 归一化;不能替换成真实路径 base。 */
198
+ function normalizePattern(entry) {
199
+ const windows = /^[a-z]:[\\/]/i.test(entry.base) || entry.base.includes("\\") || /^[a-z]:[\\/]/i.test(entry.pattern);
200
+ const pathApi = windows ? path$1.win32 : sourcePathApi(entry.base);
201
+ return pathApi.isAbsolute(entry.pattern) ? toPosixPath(pathApi.relative(pathApi.resolve(entry.base), entry.pattern)) : windows ? toPosixPath(entry.pattern) : entry.pattern;
202
+ }
203
+ /** 每文件刷新真实路径,仅复用本批次与文件系统无关的延迟 glob 编译。 */
204
+ function createTailwindSourceMatcher(original) {
205
+ const patterns = /* @__PURE__ */ new Map();
206
+ return (file, entries) => {
207
+ if (typeof file !== "string" || !hasPlainEntries(entries) || !entries.length) return original(file, entries);
208
+ const sources = compactTailwindSourceEntries(entries);
209
+ const resolvedFile = resolveSourceScanPathWithApi(file, sourcePathApi(file, ...sources.map((entry) => entry.base)));
210
+ const bases = /* @__PURE__ */ new Map();
211
+ const matches = (entry) => {
212
+ const pathApi = sourcePathApi(entry.base, resolvedFile);
213
+ let paths = bases.get(pathApi);
214
+ if (!paths) {
215
+ paths = /* @__PURE__ */ new Map();
216
+ bases.set(pathApi, paths);
217
+ }
218
+ let base = paths.get(entry.base);
219
+ if (base === void 0) {
220
+ base = resolveSourceScanPathWithApi(entry.base, pathApi);
221
+ paths.set(entry.base, base);
222
+ }
223
+ const relative = toPosixPath(pathApi.relative(base, resolvedFile));
224
+ if (!relative || relative.startsWith("../") || pathApi.isAbsolute(relative)) return false;
225
+ const pattern = normalizePattern(entry);
226
+ let match = patterns.get(pattern);
227
+ if (!match) {
228
+ match = micromatch.matcher(pattern);
229
+ patterns.set(pattern, match);
230
+ }
231
+ return match(relative);
232
+ };
233
+ const positive = sources.filter((entry) => !entry.negated);
234
+ const negative = sources.filter((entry) => entry.negated);
235
+ return positive.some(matches) && !negative.some(matches);
236
+ };
237
+ }
238
+ //#endregion
87
239
  //#region src/roots.ts
88
240
  const caches = /* @__PURE__ */ new WeakMap();
89
241
  /** 使用封存源码驱动上游完整候选生成;框架预处理与输出排除由宿主提供。 */
@@ -97,14 +249,16 @@ async function prepareTailwindRoots(compiler, request, roots, options) {
97
249
  sources: new Map(request.sources)
98
250
  };
99
251
  const entries = /* @__PURE__ */ new Map();
252
+ if (!capturedRoots.size) return entries;
253
+ const { isFileMatchedByTailwindV4SourceEntries } = await import("@weapp-tailwindcss/engine");
254
+ const matches = createTailwindSourceMatcher(isFileMatchedByTailwindV4SourceEntries);
100
255
  for (const [index, root] of capturedRoots) {
101
- const base = root.request.sourceOptions?.projectRoot ?? root.request.source?.base ?? options.cwd;
102
- const matches = createTailwindV4SourceEntryMatcher(createTailwindV4CompiledSourceEntries(root.result.root, [...root.result.sources], base));
103
- const sources = [...request.sources].flatMap(([file, content]) => content !== null && matches?.(file) && !options.isOutput?.(file) ? [{
256
+ const sourceOptions = root.request.sourceOptions;
257
+ const sourceEntries = [...root.result.sources];
258
+ const sources = [...request.sources].flatMap(([file, content]) => content !== null && sourceEntries.length > 0 && matches(file, sourceEntries) && !options.isOutput?.(file) ? [{
104
259
  content,
105
260
  extension: path.extname(file).slice(1)
106
261
  }] : []);
107
- const sourceOptions = root.request.sourceOptions;
108
262
  if (!sourceOptions) throw new Error("Tailwind HMR requires a captured source entry");
109
263
  const cssSources = sourceOptions.cssSources?.map((source) => {
110
264
  if (!source.file) return source;
@@ -149,7 +303,7 @@ async function prepareTailwindRoots(compiler, request, roots, options) {
149
303
  [...dependencyVersions]
150
304
  ])).digest("hex");
151
305
  const cached = cache.get(root);
152
- const generated = cached?.signature === signature ? cached.result : await compiler.generate({
306
+ let effectiveRequest = {
153
307
  ...root.request,
154
308
  sourceOptions: {
155
309
  ...sourceOptions,
@@ -157,8 +311,14 @@ async function prepareTailwindRoots(compiler, request, roots, options) {
157
311
  cssSources: [...cssSources ?? [], ...entrySources]
158
312
  },
159
313
  sources,
314
+ scanMode: "compiled",
160
315
  scanSources: false
161
- });
316
+ };
317
+ if (options.captureRequest) {
318
+ effectiveRequest = copyTailwindGenerationValue(effectiveRequest);
319
+ await options.captureRequest(index, effectiveRequest);
320
+ }
321
+ const generated = cached?.signature === signature ? cached.result : await compiler.generate(effectiveRequest);
162
322
  entries.set(index, generated);
163
323
  for (const file of generated.dependencies) checkDependency(file);
164
324
  cache.set(root, {
@@ -171,8 +331,11 @@ async function prepareTailwindRoots(compiler, request, roots, options) {
171
331
  //#endregion
172
332
  //#region src/sources.ts
173
333
  /** 返回上游实际扫描边界中的文件,宿主将它们登记到现有 watcher。 */
174
- function listTailwindSourceFiles(result, cwd) {
175
- const sources = createTailwindV4CompiledSourceEntries(result.root, [...result.sources], cwd);
334
+ async function listTailwindSourceFiles(result, cwd) {
335
+ const root = result.root;
336
+ const scannedSources = [...result.sources];
337
+ const { createTailwindV4CompiledSourceEntries, createTailwindV4SourceEntryMatcher, resolveProjectSourceFiles } = await import("@weapp-tailwindcss/engine");
338
+ const sources = createTailwindV4CompiledSourceEntries(root, scannedSources, cwd);
176
339
  return resolveProjectSourceFiles({
177
340
  cwd,
178
341
  sources,
@@ -180,4 +343,4 @@ function listTailwindSourceFiles(result, cwd) {
180
343
  });
181
344
  }
182
345
  //#endregion
183
- export { TailwindController, createTailwindController, createTailwindPreparation, listTailwindSourceFiles, prepareTailwindOutput, prepareTailwindRoots };
346
+ export { TailwindController, copyTailwindGenerationValue, createTailwindController, createTailwindPreparation, listTailwindSourceFiles, prepareTailwindOutput, prepareTailwindRoots };
package/package.json CHANGED
@@ -1,14 +1,24 @@
1
1
  {
2
2
  "name": "@weapp-vite/tailwindcss",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "1.0.1",
5
5
  "description": "基于 weapp-tailwindcss/core 的宿主无关编译控制器",
6
6
  "license": "MIT",
7
+ "homepage": "https://github.com/weapp-vite/weapp-vite#readme",
7
8
  "repository": {
8
9
  "type": "git",
9
10
  "url": "git+https://github.com/weapp-vite/weapp-vite.git",
10
11
  "directory": "packages/tailwindcss"
11
12
  },
13
+ "bugs": {
14
+ "url": "https://github.com/weapp-vite/weapp-vite/issues"
15
+ },
16
+ "keywords": [
17
+ "weapp-vite",
18
+ "weapp",
19
+ "mini-program",
20
+ "tailwindcss"
21
+ ],
12
22
  "sideEffects": false,
13
23
  "exports": {
14
24
  ".": {
@@ -24,13 +34,18 @@
24
34
  "dist"
25
35
  ],
26
36
  "engines": {
27
- "node": "^22.18.0 || >=24.11.0"
37
+ "node": "^22.18.0 || ^24.11.0 || >=26.0.0"
28
38
  },
29
39
  "dependencies": {
30
- "@weapp-tailwindcss/engine": "0.1.3",
31
- "@weapp-vite/hmr": "0.2.0",
40
+ "@weapp-tailwindcss/engine": "0.1.6",
41
+ "@weapp-tailwindcss/source-scan": "0.1.2",
42
+ "@weapp-vite/hmr": "0.2.2",
43
+ "micromatch": "4.0.8",
32
44
  "pathe": "^2.0.3",
33
- "weapp-tailwindcss": "^5.5.11"
45
+ "weapp-tailwindcss": "^5.6.3"
46
+ },
47
+ "devDependencies": {
48
+ "@types/micromatch": "4.0.10"
34
49
  },
35
50
  "publishConfig": {
36
51
  "access": "public",
@@ -40,7 +55,8 @@
40
55
  "build": "tsdown",
41
56
  "typecheck": "tsc --noEmit -p tsconfig.json",
42
57
  "test": "vitest run -c vitest.config.ts",
43
- "test:types": "tsd --files \"test-d/**/*.test-d.ts\"",
58
+ "test:types": "pnpm run test:types:check",
59
+ "test:types:check": "tsd --files \"test-d/**/*.test-d.ts\"",
44
60
  "lint": "eslint ."
45
61
  }
46
62
  }