@weapp-vite/tailwindcss 0.1.0
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/LICENSE +21 -0
- package/README.md +13 -0
- package/dist/index.d.mts +61 -0
- package/dist/index.mjs +183 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 ice breaker
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @weapp-vite/tailwindcss
|
|
2
|
+
|
|
3
|
+
基于 `weapp-tailwindcss/core` 的实验性、宿主无关 Tailwind 控制器。
|
|
4
|
+
|
|
5
|
+
`createTailwindController` 惰性创建上游编译器并管理失效与释放。`prepareTailwindRoots` 用封存的输入生成本批编译结果,预处理判定和输出排除由宿主提供。`prepareTailwindOutput` 封存宿主的最终 CSS 投影和候选集合,返回原生 CSS 与使用同一份 `CompilerSnapshot` 的 JS/模板转换。
|
|
6
|
+
|
|
7
|
+
宿主负责 Vite 预处理、CSS Modules、活跃模块图、文件监听、原生样式策略及 emit/write。控制器不创建 watcher,不启动 DevEngine,不写构建输出,也不调用 `notifyPayloadDelivered`。
|
|
8
|
+
|
|
9
|
+
Tailwind 扫描、候选删除、CSS 生成与类名转换仍由上游 core 实现。框架特定的 HTML defaults、Vue SFC、Page/layout 和传输协议不在本包中。
|
|
10
|
+
|
|
11
|
+
首期以微信双宿主运行时为验收目标;支付宝与抖音提供编译/适配契约验证,不宣称其运行时 HMR 已完成验收。
|
|
12
|
+
|
|
13
|
+
Node.js 版本要求随编译内核:`^22.18.0 || >=24.11.0`。
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { HmrCompilerPreparation, HmrCompilerRequest } from "@weapp-vite/hmr";
|
|
2
|
+
import { Compiler, Compiler as Compiler$1, CompilerCssTransformOptions, CompilerGenerateRequest, CompilerGenerateRequest as CompilerGenerateRequest$1, CompilerGenerateResult, CompilerGenerateResult as CompilerGenerateResult$1, CompilerSnapshot, CompilerSnapshot as CompilerSnapshot$1, CreateCompilerOptions, CreateCompilerOptions as CreateCompilerOptions$1 } from "weapp-tailwindcss/core";
|
|
3
|
+
//#region src/controller.d.ts
|
|
4
|
+
export interface TailwindControllerOptions {
|
|
5
|
+
compiler?: CreateCompilerOptions$1;
|
|
6
|
+
loadCore?: () => Promise<Pick<typeof import('weapp-tailwindcss/core'), 'createCompiler'>>;
|
|
7
|
+
}
|
|
8
|
+
/** 编译器按宿主生命周期惰性创建;不创建 Vite、DevEngine、watcher 或输出写入者。 */
|
|
9
|
+
export declare class TailwindController {
|
|
10
|
+
private readonly options;
|
|
11
|
+
private instance?;
|
|
12
|
+
private closed;
|
|
13
|
+
private disposal?;
|
|
14
|
+
constructor(options?: TailwindControllerOptions);
|
|
15
|
+
getCompiler(): Promise<Compiler$1>;
|
|
16
|
+
/** 空控制器的失效不启动 Tailwind;实际编译根由 core 统一失效。 */
|
|
17
|
+
invalidate(files: Iterable<string>): Promise<readonly string[]>;
|
|
18
|
+
remove(id: string): Promise<void>;
|
|
19
|
+
dispose(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export declare function createTailwindController(options?: TailwindControllerOptions): TailwindController;
|
|
22
|
+
/** 同一份 core 快照用于所有转换;宿主提供最终资产,公共层不推断框架输出归属。 */
|
|
23
|
+
export declare function createTailwindPreparation(compiler: Compiler$1, snapshot: CompilerSnapshot$1, assets?: NonNullable<HmrCompilerPreparation['assets']>): HmrCompilerPreparation;
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/output.d.ts
|
|
26
|
+
export interface TailwindOutputInput {
|
|
27
|
+
id: string;
|
|
28
|
+
target?: CompilerSnapshot$1['target'];
|
|
29
|
+
css: string;
|
|
30
|
+
classSet: Iterable<string>;
|
|
31
|
+
dependencies?: Iterable<string>;
|
|
32
|
+
styleOptions?: CompilerCssTransformOptions;
|
|
33
|
+
}
|
|
34
|
+
export interface TailwindOutput {
|
|
35
|
+
css: string;
|
|
36
|
+
snapshot: CompilerSnapshot$1;
|
|
37
|
+
preparation: HmrCompilerPreparation;
|
|
38
|
+
}
|
|
39
|
+
/** 在宿主回调内封存投影,再异步转换;Vite 最终 CSS 和所有补丁共用同一身份集合。 */
|
|
40
|
+
export declare function prepareTailwindOutput(compiler: Compiler$1 | Promise<Compiler$1>, input: TailwindOutputInput): Promise<TailwindOutput>;
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/roots.d.ts
|
|
43
|
+
export interface TailwindRootState {
|
|
44
|
+
request: CompilerGenerateRequest$1;
|
|
45
|
+
result: CompilerGenerateResult$1;
|
|
46
|
+
rawSources: ReadonlyMap<string, string | null | undefined>;
|
|
47
|
+
}
|
|
48
|
+
export interface TailwindRootHost {
|
|
49
|
+
cwd: string;
|
|
50
|
+
sourceId: (file: string) => string;
|
|
51
|
+
isOutput?: (file: string) => boolean;
|
|
52
|
+
reusePreprocessedCss?: (file: string, previous: string | null | undefined, current: string | null | undefined) => boolean;
|
|
53
|
+
}
|
|
54
|
+
/** 使用封存源码驱动上游完整候选生成;框架预处理与输出排除由宿主提供。 */
|
|
55
|
+
export declare function prepareTailwindRoots<K>(compiler: Compiler$1, request: HmrCompilerRequest, roots: ReadonlyMap<K, TailwindRootState>, options: TailwindRootHost): Promise<Map<K, CompilerGenerateResult$1>>;
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/sources.d.ts
|
|
58
|
+
/** 返回上游实际扫描边界中的文件,宿主将它们登记到现有 watcher。 */
|
|
59
|
+
export declare function listTailwindSourceFiles(result: CompilerGenerateResult$1, cwd: string): Promise<string[]>;
|
|
60
|
+
//#endregion
|
|
61
|
+
export type { Compiler, CompilerGenerateRequest, CompilerGenerateResult, CompilerSnapshot, CreateCompilerOptions };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { CompilerHmrResyncError, normalizeHmrSourceMap } from "@weapp-vite/hmr";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { readFileSync, statSync } from "node:fs";
|
|
4
|
+
import { createTailwindV4CompiledSourceEntries, createTailwindV4SourceEntryMatcher, resolveProjectSourceFiles } from "@weapp-tailwindcss/engine";
|
|
5
|
+
import path from "pathe";
|
|
6
|
+
//#region src/controller.ts
|
|
7
|
+
/** 编译器按宿主生命周期惰性创建;不创建 Vite、DevEngine、watcher 或输出写入者。 */
|
|
8
|
+
var TailwindController = class {
|
|
9
|
+
options;
|
|
10
|
+
instance;
|
|
11
|
+
closed = false;
|
|
12
|
+
disposal;
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
getCompiler() {
|
|
17
|
+
if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("Tailwind controller is disposed"));
|
|
18
|
+
this.instance ??= (this.options.loadCore?.() ?? import("weapp-tailwindcss/core")).then((core) => core.createCompiler(this.options.compiler));
|
|
19
|
+
return this.instance;
|
|
20
|
+
}
|
|
21
|
+
/** 空控制器的失效不启动 Tailwind;实际编译根由 core 统一失效。 */
|
|
22
|
+
async invalidate(files) {
|
|
23
|
+
return this.instance ? (await this.instance).invalidate(files) : [];
|
|
24
|
+
}
|
|
25
|
+
async remove(id) {
|
|
26
|
+
if (this.instance) await (await this.instance).remove(id);
|
|
27
|
+
}
|
|
28
|
+
dispose() {
|
|
29
|
+
this.closed = true;
|
|
30
|
+
this.disposal ??= this.instance?.then((compiler) => compiler.dispose()) ?? Promise.resolve();
|
|
31
|
+
return this.disposal;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
function createTailwindController(options = {}) {
|
|
35
|
+
return new TailwindController(options);
|
|
36
|
+
}
|
|
37
|
+
function labelMap(value, fileName, code) {
|
|
38
|
+
const map = normalizeHmrSourceMap(value);
|
|
39
|
+
if (!map?.sources.includes("")) return map;
|
|
40
|
+
return {
|
|
41
|
+
...map,
|
|
42
|
+
sources: map.sources.map((source) => source || fileName),
|
|
43
|
+
sourcesContent: map.sources.map((source, index) => map.sourcesContent?.[index] ?? (source === "" ? code : null))
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** 同一份 core 快照用于所有转换;宿主提供最终资产,公共层不推断框架输出归属。 */
|
|
47
|
+
function createTailwindPreparation(compiler, snapshot, assets = []) {
|
|
48
|
+
return {
|
|
49
|
+
assets,
|
|
50
|
+
dependencies: snapshot.dependencies,
|
|
51
|
+
transformTemplate: async ({ code, fileName }) => ({ code: await compiler.transformTemplate(code, snapshot, { filename: fileName }) }),
|
|
52
|
+
transformJavaScript: async ({ code, fileName, sourcemap }) => {
|
|
53
|
+
const result = await compiler.transformJavaScript(code, snapshot, {
|
|
54
|
+
filename: fileName,
|
|
55
|
+
generateMap: sourcemap !== false
|
|
56
|
+
});
|
|
57
|
+
if (result.error) throw result.error;
|
|
58
|
+
return {
|
|
59
|
+
code: result.code,
|
|
60
|
+
map: labelMap(result.map, fileName, code)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/output.ts
|
|
67
|
+
/** 在宿主回调内封存投影,再异步转换;Vite 最终 CSS 和所有补丁共用同一身份集合。 */
|
|
68
|
+
function prepareTailwindOutput(compiler, input) {
|
|
69
|
+
const css = input.css;
|
|
70
|
+
const snapshotInput = {
|
|
71
|
+
id: input.id,
|
|
72
|
+
classSet: new Set(input.classSet),
|
|
73
|
+
dependencies: input.dependencies ? [...input.dependencies] : [],
|
|
74
|
+
target: input.target ?? "weapp"
|
|
75
|
+
};
|
|
76
|
+
const styleOptions = input.styleOptions ? { ...input.styleOptions } : void 0;
|
|
77
|
+
return Promise.resolve(compiler).then(async (instance) => {
|
|
78
|
+
const snapshot = instance.createSnapshot(snapshotInput);
|
|
79
|
+
return {
|
|
80
|
+
css: (await instance.transformCss(css, snapshot, styleOptions)).css,
|
|
81
|
+
snapshot,
|
|
82
|
+
preparation: createTailwindPreparation(instance, snapshot)
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/roots.ts
|
|
88
|
+
const caches = /* @__PURE__ */ new WeakMap();
|
|
89
|
+
/** 使用封存源码驱动上游完整候选生成;框架预处理与输出排除由宿主提供。 */
|
|
90
|
+
async function prepareTailwindRoots(compiler, request, roots, options) {
|
|
91
|
+
const cache = caches.get(compiler) ?? /* @__PURE__ */ new WeakMap();
|
|
92
|
+
caches.set(compiler, cache);
|
|
93
|
+
const capturedRoots = new Map(roots);
|
|
94
|
+
request = {
|
|
95
|
+
...request,
|
|
96
|
+
changedFiles: [...request.changedFiles],
|
|
97
|
+
sources: new Map(request.sources)
|
|
98
|
+
};
|
|
99
|
+
const entries = /* @__PURE__ */ new Map();
|
|
100
|
+
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) ? [{
|
|
104
|
+
content,
|
|
105
|
+
extension: path.extname(file).slice(1)
|
|
106
|
+
}] : []);
|
|
107
|
+
const sourceOptions = root.request.sourceOptions;
|
|
108
|
+
if (!sourceOptions) throw new Error("Tailwind HMR requires a captured source entry");
|
|
109
|
+
const cssSources = sourceOptions.cssSources?.map((source) => {
|
|
110
|
+
if (!source.file) return source;
|
|
111
|
+
const id = options.sourceId(source.file ?? "");
|
|
112
|
+
const raw = request.sources.get(id);
|
|
113
|
+
const previous = root.rawSources.get(id);
|
|
114
|
+
if (raw === previous || raw === void 0) return source;
|
|
115
|
+
if (options.reusePreprocessedCss?.(id, previous, raw)) return source;
|
|
116
|
+
if (typeof raw !== "string" || source.css !== previous) throw new CompilerHmrResyncError(request.changedFiles, "Compiler root requires a new preprocessed input");
|
|
117
|
+
return {
|
|
118
|
+
...source,
|
|
119
|
+
css: raw
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
const entrySources = (sourceOptions.cssEntries ?? []).map((file) => {
|
|
123
|
+
const css = request.sources.get(options.sourceId(file));
|
|
124
|
+
if (typeof css !== "string") throw new TypeError(`Missing captured Tailwind CSS input: ${file}`);
|
|
125
|
+
return {
|
|
126
|
+
file,
|
|
127
|
+
css,
|
|
128
|
+
base: path.dirname(file),
|
|
129
|
+
dependencies: [file]
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
const memoryRoots = new Set([...cssSources ?? [], ...entrySources].flatMap((source) => source.file ? [options.sourceId(source.file)] : []));
|
|
133
|
+
const dependencyVersions = /* @__PURE__ */ new Map();
|
|
134
|
+
const checkDependency = (file) => {
|
|
135
|
+
const id = options.sourceId(file);
|
|
136
|
+
if (memoryRoots.has(id)) return;
|
|
137
|
+
const expected = request.sources.get(id);
|
|
138
|
+
const source = readFileSync(file, "utf8");
|
|
139
|
+
const stat = statSync(file, { bigint: true });
|
|
140
|
+
const version = `${stat.mtimeNs}:${stat.ctimeNs}:${stat.ino}:${stat.size}`;
|
|
141
|
+
if (expected !== source || dependencyVersions.has(id) && dependencyVersions.get(id) !== version) throw new CompilerHmrResyncError(request.changedFiles, "Compiler dependency changed outside the captured input view");
|
|
142
|
+
dependencyVersions.set(id, version);
|
|
143
|
+
};
|
|
144
|
+
for (const file of root.result.dependencies) checkDependency(file);
|
|
145
|
+
const signature = createHash("sha256").update(JSON.stringify([
|
|
146
|
+
cssSources,
|
|
147
|
+
entrySources,
|
|
148
|
+
sources,
|
|
149
|
+
[...dependencyVersions]
|
|
150
|
+
])).digest("hex");
|
|
151
|
+
const cached = cache.get(root);
|
|
152
|
+
const generated = cached?.signature === signature ? cached.result : await compiler.generate({
|
|
153
|
+
...root.request,
|
|
154
|
+
sourceOptions: {
|
|
155
|
+
...sourceOptions,
|
|
156
|
+
cssEntries: [],
|
|
157
|
+
cssSources: [...cssSources ?? [], ...entrySources]
|
|
158
|
+
},
|
|
159
|
+
sources,
|
|
160
|
+
scanSources: false
|
|
161
|
+
});
|
|
162
|
+
entries.set(index, generated);
|
|
163
|
+
for (const file of generated.dependencies) checkDependency(file);
|
|
164
|
+
cache.set(root, {
|
|
165
|
+
signature,
|
|
166
|
+
result: generated
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return entries;
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/sources.ts
|
|
173
|
+
/** 返回上游实际扫描边界中的文件,宿主将它们登记到现有 watcher。 */
|
|
174
|
+
function listTailwindSourceFiles(result, cwd) {
|
|
175
|
+
const sources = createTailwindV4CompiledSourceEntries(result.root, [...result.sources], cwd);
|
|
176
|
+
return resolveProjectSourceFiles({
|
|
177
|
+
cwd,
|
|
178
|
+
sources,
|
|
179
|
+
filter: createTailwindV4SourceEntryMatcher(sources)
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
export { TailwindController, createTailwindController, createTailwindPreparation, listTailwindSourceFiles, prepareTailwindOutput, prepareTailwindRoots };
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@weapp-vite/tailwindcss",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "基于 weapp-tailwindcss/core 的宿主无关编译控制器",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/weapp-vite/weapp-vite.git",
|
|
10
|
+
"directory": "packages/tailwindcss"
|
|
11
|
+
},
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.mts",
|
|
16
|
+
"import": "./dist/index.mjs"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/index.mjs",
|
|
21
|
+
"types": "./dist/index.d.mts",
|
|
22
|
+
"files": [
|
|
23
|
+
"README.md",
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": "^22.18.0 || >=24.11.0"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@weapp-tailwindcss/engine": "0.1.1",
|
|
31
|
+
"@weapp-vite/hmr": "0.1.0",
|
|
32
|
+
"pathe": "^2.0.3",
|
|
33
|
+
"weapp-tailwindcss": "^5.5.8"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public",
|
|
37
|
+
"registry": "https://registry.npmjs.org"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsdown",
|
|
41
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
42
|
+
"test": "vitest run -c vitest.config.ts",
|
|
43
|
+
"test:types": "tsd --files \"test-d/**/*.test-d.ts\"",
|
|
44
|
+
"lint": "eslint ."
|
|
45
|
+
}
|
|
46
|
+
}
|