@teacss/postcss 0.1.0-alpha.1 → 0.3.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 +4 -4
- package/README.md +57 -1
- package/dist/esm.d.mts +21 -0
- package/dist/esm.mjs +440 -0
- package/dist/index.d.ts +27 -11
- package/dist/index.js +1 -32743
- package/package.json +29 -33
- package/dist/index.cjs +0 -1
- package/dist/index.d.mts +0 -11
- package/dist/index.mjs +0 -1
package/LICENSE
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
MIT License
|
|
1
|
+
# MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
4
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2021-PRESENT Anthony Fu <https://github.com/antfu>
|
|
4
|
+
Copyright (c) 2026-PRESENT Billgo <hi@billgo.me>
|
|
5
5
|
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -19,4 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
19
19
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
20
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
21
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
SOFTWARE.
|
|
22
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1 +1,57 @@
|
|
|
1
|
-
# @teacss/postcss
|
|
1
|
+
# @teacss/postcss
|
|
2
|
+
|
|
3
|
+
**PostCSS integration for TeaCSS.**
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
The plugin runs TeaCSS during the PostCSS pipeline, transforms TeaCSS CSS
|
|
8
|
+
directives, and injects generated utilities into the current stylesheet.
|
|
9
|
+
|
|
10
|
+
Use this package when PostCSS is the application's CSS build boundary. It owns
|
|
11
|
+
TeaCSS's PostCSS plugin behavior and reports file and directory dependencies to
|
|
12
|
+
the surrounding runner; the runner remains responsible for file watching and
|
|
13
|
+
rebuilds. Projects whose Vite, Astro, Rsbuild, or Bun integration already owns
|
|
14
|
+
TeaCSS generation do not need to add this plugin as a second pipeline.
|
|
15
|
+
|
|
16
|
+
PostCSS is a host peer rather than a bundled runtime. Install one compatible
|
|
17
|
+
PostCSS 8 copy in the application:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
bun add teacss
|
|
21
|
+
bun add -d @teacss/postcss postcss
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
import { pluginTeacss } from "teacss/postcss";
|
|
28
|
+
|
|
29
|
+
export default {
|
|
30
|
+
plugins: [pluginTeacss()],
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```css
|
|
35
|
+
@preset "standard";
|
|
36
|
+
@source "./src/**/*.{ts,tsx,html}";
|
|
37
|
+
@custom { --color-brand-500: #0f766e; }
|
|
38
|
+
@teacss;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Use `@teacss;` to choose where generated utilities are injected.
|
|
42
|
+
When a stylesheet has multiple `@teacss` insertion points, each generated layer
|
|
43
|
+
is emitted at most once across bare, `all`, explicit, and negated layer forms.
|
|
44
|
+
Use `@custom {}` to define theme values in the CSS entry; PostCSS resolves
|
|
45
|
+
`theme("...")` in declaration values but does not provide an `@theme` at-rule.
|
|
46
|
+
The plugin reports both file dependencies and stable directory dependencies for
|
|
47
|
+
positive `@source` globs, so PostCSS runners can rebuild when a new matching file
|
|
48
|
+
is created. If a glob's static root does not exist yet, the directory dependency
|
|
49
|
+
falls back to its nearest existing parent. Relative `@source` globs resolve from
|
|
50
|
+
the CSS entry directory, including when the discovered entry is nested at
|
|
51
|
+
`src/index.css`.
|
|
52
|
+
|
|
53
|
+
## Status
|
|
54
|
+
|
|
55
|
+
Pre-1.0. Install `@teacss/postcss` beside `teacss`, then use the stable
|
|
56
|
+
`teacss/postcss` facade subpath in applications. Integration authors may import
|
|
57
|
+
`@teacss/postcss` directly when they intentionally do not use the facade.
|
package/dist/esm.d.mts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { UserConfig } from "@teacss/core";
|
|
2
|
+
import { Result, Root } from "postcss";
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
interface TeacssPostcssPluginOptions {
|
|
5
|
+
content?: (string | {
|
|
6
|
+
raw: string;
|
|
7
|
+
extension: string;
|
|
8
|
+
})[];
|
|
9
|
+
directiveMap?: {
|
|
10
|
+
apply?: string;
|
|
11
|
+
screen?: string;
|
|
12
|
+
teacss?: string;
|
|
13
|
+
};
|
|
14
|
+
cwd?: string;
|
|
15
|
+
configOrPath?: string | UserConfig;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/esm.d.ts
|
|
19
|
+
declare function createPlugin(options: TeacssPostcssPluginOptions): (root: Root, result: Result) => Promise<void>;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { createPlugin };
|
package/dist/esm.mjs
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { dirname, extname, join, normalize, relative, resolve } from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { createRecoveryConfigLoader, entryDiscoveryFiles, getConfigSourcesFromError } from "@teacss/config";
|
|
5
|
+
import { createGenerator, expandVariantGroup, splitClassTokens } from "@teacss/core";
|
|
6
|
+
import { hasIconFn, hasThemeFn } from "@teacss/rule-helpers";
|
|
7
|
+
import { defaultFilesystemGlobs } from "@teacss/runtime";
|
|
8
|
+
import postcss from "postcss";
|
|
9
|
+
import { glob, isDynamicPattern } from "tinyglobby";
|
|
10
|
+
import { formatMissingApplyTokens, missingApplyTokens, prepareApplyUtilities, renderStandaloneApplyUtility, resolveApplyTokens, resolveCssFunction, resolveScreenQuery } from "@teacss/directives/core";
|
|
11
|
+
import { generate, parse, walk } from "css-tree";
|
|
12
|
+
//#region src/apply.ts
|
|
13
|
+
async function parseApply(root, generator, directiveName) {
|
|
14
|
+
const rules = [];
|
|
15
|
+
const lastParentAfterNode = /* @__PURE__ */ new Map();
|
|
16
|
+
const applyHosts = /* @__PURE__ */ new Map();
|
|
17
|
+
root.walkAtRules(directiveName, (rule) => {
|
|
18
|
+
rules.push(rule);
|
|
19
|
+
});
|
|
20
|
+
const resolvedRules = await Promise.all(rules.map(async (rule) => {
|
|
21
|
+
const parsed = await resolveApplyTokens(splitClassTokens(expandVariantGroup(rule.params)), generator);
|
|
22
|
+
const missing = missingApplyTokens(parsed);
|
|
23
|
+
if (missing.length) throw rule.error(formatMissingApplyTokens(missing));
|
|
24
|
+
return {
|
|
25
|
+
parsed,
|
|
26
|
+
rule
|
|
27
|
+
};
|
|
28
|
+
}));
|
|
29
|
+
for (const { parsed, rule } of resolvedRules) {
|
|
30
|
+
const hostRule = rule.parent;
|
|
31
|
+
if (!hostRule) continue;
|
|
32
|
+
const source = rule.source;
|
|
33
|
+
const utils = prepareApplyUtilities(parsed, generator.parentOrders);
|
|
34
|
+
if (!utils.length) {
|
|
35
|
+
rule.remove();
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const parentAfterNodes = [];
|
|
39
|
+
let applyHost;
|
|
40
|
+
for (const utility of utils) if (utility.kind === "standalone") {
|
|
41
|
+
if (!applyHost) {
|
|
42
|
+
applyHost = applyHosts.get(hostRule);
|
|
43
|
+
if (!applyHost) {
|
|
44
|
+
let prelude;
|
|
45
|
+
if (hostRule.type === "rule") prelude = parse(hostRule.selector, { context: "selectorList" });
|
|
46
|
+
else prelude = parse(hostRule.toString(), { context: "rule" }).prelude;
|
|
47
|
+
const selector = generate(prelude);
|
|
48
|
+
applyHost = {
|
|
49
|
+
prelude,
|
|
50
|
+
selector,
|
|
51
|
+
replacementSelector: selector
|
|
52
|
+
};
|
|
53
|
+
applyHosts.set(hostRule, applyHost);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const css = renderStandaloneApplyUtility(applyHost, utility);
|
|
57
|
+
const parsedCss = postcss.parse(css);
|
|
58
|
+
parsedCss.walkDecls((declaration) => {
|
|
59
|
+
declaration.source = source;
|
|
60
|
+
});
|
|
61
|
+
parentAfterNodes.push(...parsedCss.nodes);
|
|
62
|
+
} else {
|
|
63
|
+
const css = postcss.parse(utility.body);
|
|
64
|
+
css.walkDecls((declaration) => {
|
|
65
|
+
declaration.source = source;
|
|
66
|
+
});
|
|
67
|
+
hostRule.insertAfter(rule, css);
|
|
68
|
+
}
|
|
69
|
+
if (parentAfterNodes.length) {
|
|
70
|
+
const anchor = lastParentAfterNode.get(hostRule);
|
|
71
|
+
if (anchor) anchor.after(parentAfterNodes);
|
|
72
|
+
else hostRule.after(parentAfterNodes);
|
|
73
|
+
lastParentAfterNode.set(hostRule, parentAfterNodes.at(-1));
|
|
74
|
+
}
|
|
75
|
+
rule.remove();
|
|
76
|
+
if (hostRule.type === "rule" && !hostRule.nodes?.length) hostRule.remove();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/screen.ts
|
|
81
|
+
async function parseScreen(root, generator, directiveName) {
|
|
82
|
+
root.walkAtRules(directiveName, (rule) => {
|
|
83
|
+
const query = resolveScreenQuery(rule.params, generator.config.theme);
|
|
84
|
+
if (!query) return;
|
|
85
|
+
rule.name = "media";
|
|
86
|
+
rule.params = query;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/theme.ts
|
|
91
|
+
async function parseTheme(root, generator) {
|
|
92
|
+
const tasks = [];
|
|
93
|
+
root.walkDecls((decl) => {
|
|
94
|
+
if (!hasThemeFn(decl.value) && !hasIconFn(decl.value)) return;
|
|
95
|
+
tasks.push(transformDeclarationValue(decl.value, generator).then((value) => {
|
|
96
|
+
decl.value = value;
|
|
97
|
+
}));
|
|
98
|
+
});
|
|
99
|
+
await Promise.all(tasks);
|
|
100
|
+
}
|
|
101
|
+
async function transformDeclarationValue(value, generator) {
|
|
102
|
+
const replacements = [];
|
|
103
|
+
const tasks = [];
|
|
104
|
+
walk(parse(value, {
|
|
105
|
+
context: "value",
|
|
106
|
+
parseCustomProperty: true,
|
|
107
|
+
positions: true
|
|
108
|
+
}), function(node) {
|
|
109
|
+
if (node.type !== "Function" || node.name !== "theme" && node.name !== "icon" || !node.loc) return;
|
|
110
|
+
tasks.push(resolveCssFunction(generator, node, (start, end) => value.slice(start, end)).then((replacement) => {
|
|
111
|
+
if (replacement === void 0) return;
|
|
112
|
+
replacements.push({
|
|
113
|
+
start: node.loc.start.offset,
|
|
114
|
+
end: node.loc.end.offset,
|
|
115
|
+
value: replacement
|
|
116
|
+
});
|
|
117
|
+
}));
|
|
118
|
+
return this.skip;
|
|
119
|
+
});
|
|
120
|
+
await Promise.all(tasks);
|
|
121
|
+
return applyReplacements(value, replacements);
|
|
122
|
+
}
|
|
123
|
+
function applyReplacements(value, replacements) {
|
|
124
|
+
let output = value;
|
|
125
|
+
for (const replacement of replacements.sort((a, b) => b.start - a.start)) output = `${output.slice(0, replacement.start)}${replacement.value}${output.slice(replacement.end)}`;
|
|
126
|
+
return output;
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/esm.ts
|
|
130
|
+
const DEFAULT_GENERATED_IGNORE = [
|
|
131
|
+
"**/dist/**",
|
|
132
|
+
"**/build/**",
|
|
133
|
+
"**/storybook-static/**"
|
|
134
|
+
];
|
|
135
|
+
const MAX_CONFIG_STABILITY_ATTEMPTS = 5;
|
|
136
|
+
function stripQueryAndHash(id) {
|
|
137
|
+
return id.replace(/[?#].*$/, "");
|
|
138
|
+
}
|
|
139
|
+
async function existingDirectory(target) {
|
|
140
|
+
let current = target;
|
|
141
|
+
while (true) {
|
|
142
|
+
try {
|
|
143
|
+
if ((await stat(current)).isDirectory()) return current;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
const code = error.code;
|
|
146
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
|
|
147
|
+
}
|
|
148
|
+
const parent = dirname(current);
|
|
149
|
+
if (parent === current) return current;
|
|
150
|
+
current = parent;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function resolveDirectoryDependency(cwd, pattern, literal = false) {
|
|
154
|
+
if (pattern.startsWith("!")) return;
|
|
155
|
+
const absolute = resolve(cwd, pattern);
|
|
156
|
+
let staticPattern = pattern;
|
|
157
|
+
if (!literal) while (isDynamicPattern(staticPattern)) staticPattern = dirname(staticPattern);
|
|
158
|
+
let dir = resolve(cwd, staticPattern);
|
|
159
|
+
if (literal || staticPattern === pattern) dir = dirname(dir);
|
|
160
|
+
dir = await existingDirectory(dir);
|
|
161
|
+
return {
|
|
162
|
+
dir,
|
|
163
|
+
glob: relative(dir, absolute).replaceAll("\\", "/")
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function fileSignature({ mtimeMs, ctimeMs, size, ino }) {
|
|
167
|
+
return `${mtimeMs}:${ctimeMs}:${size}:${ino}`;
|
|
168
|
+
}
|
|
169
|
+
async function readChangedFile(file, previousSignature) {
|
|
170
|
+
try {
|
|
171
|
+
const signature = fileSignature(await stat(file));
|
|
172
|
+
if (signature === previousSignature) return null;
|
|
173
|
+
return {
|
|
174
|
+
content: await readFile(file, "utf8"),
|
|
175
|
+
signature
|
|
176
|
+
};
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if (error.code === "ENOENT") return;
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function replaceRuleWithCss(rule, generatedCss) {
|
|
183
|
+
const css = postcss.parse(generatedCss);
|
|
184
|
+
css.walkDecls((declaration) => {
|
|
185
|
+
declaration.source = rule.source;
|
|
186
|
+
});
|
|
187
|
+
rule.replaceWith(css);
|
|
188
|
+
}
|
|
189
|
+
function replaceTeacssAtRules(root, generation, directiveName) {
|
|
190
|
+
const emittedLayers = /* @__PURE__ */ new Set();
|
|
191
|
+
root.walkAtRules(directiveName, (rule) => {
|
|
192
|
+
if (!rule.params) {
|
|
193
|
+
const layers = generation.layers.filter((layer) => !emittedLayers.has(layer));
|
|
194
|
+
replaceRuleWithCss(rule, generation.getLayers(layers) || "");
|
|
195
|
+
for (const layer of layers) emittedLayers.add(layer);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const excludeLayers = [];
|
|
199
|
+
const includeLayers = [];
|
|
200
|
+
for (const layer of rule.params.split(",")) {
|
|
201
|
+
const name = layer.trim();
|
|
202
|
+
if (!name) continue;
|
|
203
|
+
if (name.startsWith("!")) {
|
|
204
|
+
if (name.slice(1)) excludeLayers.push(name.slice(1));
|
|
205
|
+
} else includeLayers.push(name);
|
|
206
|
+
}
|
|
207
|
+
if (excludeLayers.length > 0 && includeLayers.length > 0) console.warn(`Warning: Mixing normal and negated layer names in "@${directiveName} ${rule.params}" is not recommended.`);
|
|
208
|
+
let layers = [];
|
|
209
|
+
if (includeLayers.length > 0) {
|
|
210
|
+
const included = includeLayers.includes("all") ? generation.layers : includeLayers;
|
|
211
|
+
layers = included.filter((layer, index) => generation.layers.includes(layer) && included.indexOf(layer) === index && !emittedLayers.has(layer));
|
|
212
|
+
} else if (excludeLayers.length > 0) layers = generation.layers.filter((layer) => !excludeLayers.includes(layer) && !emittedLayers.has(layer));
|
|
213
|
+
replaceRuleWithCss(rule, generation.getLayers(layers) || "");
|
|
214
|
+
for (const layer of layers) emittedLayers.add(layer);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
function createPlugin(options) {
|
|
218
|
+
const { cwd = process.cwd(), configOrPath } = options;
|
|
219
|
+
const configTarget = typeof configOrPath === "string" ? resolve(cwd, configOrPath) : cwd;
|
|
220
|
+
const hasInlineConfig = Boolean(configOrPath && typeof configOrPath === "object");
|
|
221
|
+
const hasExplicitEntry = typeof configOrPath === "string" && extname(configTarget).toLowerCase() === ".css";
|
|
222
|
+
const inlineOptionContent = (options.content ?? []).map((content, idx) => {
|
|
223
|
+
if (typeof content === "string") return content;
|
|
224
|
+
const extension = content.extension.replace(/^\./, "") || "html";
|
|
225
|
+
return {
|
|
226
|
+
code: content.raw,
|
|
227
|
+
id: `__postcss_content_${idx}.${extension}`
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
const directiveMap = {
|
|
231
|
+
apply: "apply",
|
|
232
|
+
screen: "screen",
|
|
233
|
+
teacss: "teacss",
|
|
234
|
+
...options.directiveMap
|
|
235
|
+
};
|
|
236
|
+
const loadConfig = createRecoveryConfigLoader();
|
|
237
|
+
let config;
|
|
238
|
+
const getConfig = () => {
|
|
239
|
+
config ??= loadConfig(cwd, configOrPath).catch((error) => {
|
|
240
|
+
config = void 0;
|
|
241
|
+
throw error;
|
|
242
|
+
});
|
|
243
|
+
return config;
|
|
244
|
+
};
|
|
245
|
+
const getConfigSourceFiles = (activeSources) => {
|
|
246
|
+
const files = new Set(activeSources);
|
|
247
|
+
if (hasInlineConfig) return files;
|
|
248
|
+
if (hasExplicitEntry) files.add(configTarget);
|
|
249
|
+
else for (const file of entryDiscoveryFiles) files.add(join(configTarget, file));
|
|
250
|
+
return files;
|
|
251
|
+
};
|
|
252
|
+
const getConfigSignature = async (activeSources) => {
|
|
253
|
+
if (hasInlineConfig) return "";
|
|
254
|
+
return (await Promise.all(Array.from(getConfigSourceFiles(activeSources)).sort().map(async (file) => {
|
|
255
|
+
try {
|
|
256
|
+
return `${file}:${fileSignature(await stat(file))}`;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
if (error.code === "ENOENT") return `${file}:missing`;
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
}))).join("|");
|
|
262
|
+
};
|
|
263
|
+
let generatorState;
|
|
264
|
+
let configRequestVersion = 0;
|
|
265
|
+
let pendingConfig;
|
|
266
|
+
async function getGeneratorState() {
|
|
267
|
+
const signature = await getConfigSignature(generatorState?.sources ?? []);
|
|
268
|
+
if (generatorState?.signature === signature) return generatorState;
|
|
269
|
+
if (pendingConfig?.signature === signature) return pendingConfig.promise;
|
|
270
|
+
const version = ++configRequestVersion;
|
|
271
|
+
const forceReload = Boolean(generatorState || pendingConfig || config);
|
|
272
|
+
let task;
|
|
273
|
+
task = (async () => {
|
|
274
|
+
let expectedSignature = signature;
|
|
275
|
+
let mustReload = forceReload;
|
|
276
|
+
let stableResolved;
|
|
277
|
+
let next;
|
|
278
|
+
for (let attempt = 0; attempt < MAX_CONFIG_STABILITY_ATTEMPTS; attempt++) {
|
|
279
|
+
const resolved = mustReload ? await loadConfig(cwd, configOrPath) : await getConfig();
|
|
280
|
+
const resolvedSignature = await getConfigSignature(resolved.sources);
|
|
281
|
+
if (resolvedSignature === expectedSignature) {
|
|
282
|
+
stableResolved = resolved;
|
|
283
|
+
next = {
|
|
284
|
+
generator: await createGenerator(resolved.config),
|
|
285
|
+
signature: resolvedSignature,
|
|
286
|
+
sources: resolved.sources,
|
|
287
|
+
entryDir: resolved.sources[0] ? dirname(resolved.sources[0]) : cwd,
|
|
288
|
+
fileMap: /* @__PURE__ */ new Map(),
|
|
289
|
+
fileClassMap: /* @__PURE__ */ new Map(),
|
|
290
|
+
scanRequestVersion: 0
|
|
291
|
+
};
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
expectedSignature = resolvedSignature;
|
|
295
|
+
mustReload = true;
|
|
296
|
+
}
|
|
297
|
+
if (!stableResolved || !next) throw new Error(`TeaCSS config did not stabilize after ${MAX_CONFIG_STABILITY_ATTEMPTS} load attempts`);
|
|
298
|
+
if (configRequestVersion === version) {
|
|
299
|
+
config = Promise.resolve(stableResolved);
|
|
300
|
+
generatorState = next;
|
|
301
|
+
return next;
|
|
302
|
+
}
|
|
303
|
+
const latest = pendingConfig?.promise;
|
|
304
|
+
if (latest && latest !== task) return await latest;
|
|
305
|
+
return generatorState ?? next;
|
|
306
|
+
})();
|
|
307
|
+
pendingConfig = {
|
|
308
|
+
signature,
|
|
309
|
+
promise: task
|
|
310
|
+
};
|
|
311
|
+
try {
|
|
312
|
+
return await task;
|
|
313
|
+
} finally {
|
|
314
|
+
if (pendingConfig?.promise === task) pendingConfig = void 0;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const directiveNames = new Set(Object.values(directiveMap));
|
|
318
|
+
return async function plugin(root, result) {
|
|
319
|
+
const from = stripQueryAndHash(result.opts.from ?? "") || join(cwd, "__teacss_postcss_input.css");
|
|
320
|
+
let isTarget = false;
|
|
321
|
+
let isScanTarget = false;
|
|
322
|
+
root.walk((node) => {
|
|
323
|
+
if (node.type === "atrule") {
|
|
324
|
+
if (node.name === directiveMap.teacss) {
|
|
325
|
+
isTarget = true;
|
|
326
|
+
isScanTarget = true;
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
if (directiveNames.has(node.name)) isTarget = true;
|
|
330
|
+
} else if (!isTarget && node.type === "decl" && (hasThemeFn(node.value) || hasIconFn(node.value))) isTarget = true;
|
|
331
|
+
});
|
|
332
|
+
if (!isTarget) return;
|
|
333
|
+
const dependencyFiles = /* @__PURE__ */ new Set();
|
|
334
|
+
const dependencyDirectories = /* @__PURE__ */ new Set();
|
|
335
|
+
const addDependency = (file) => {
|
|
336
|
+
const normalized = normalize(file);
|
|
337
|
+
if (dependencyFiles.has(normalized)) return;
|
|
338
|
+
dependencyFiles.add(normalized);
|
|
339
|
+
result.messages.push({
|
|
340
|
+
type: "dependency",
|
|
341
|
+
plugin: directiveMap.teacss,
|
|
342
|
+
file: normalized,
|
|
343
|
+
parent: from
|
|
344
|
+
});
|
|
345
|
+
};
|
|
346
|
+
const addDirectoryDependency = async (base, pattern, literal = false) => {
|
|
347
|
+
const dependency = await resolveDirectoryDependency(base, pattern, literal);
|
|
348
|
+
if (!dependency) return;
|
|
349
|
+
const key = `${dependency.dir}\0${dependency.glob}`;
|
|
350
|
+
if (dependencyDirectories.has(key)) return;
|
|
351
|
+
dependencyDirectories.add(key);
|
|
352
|
+
result.messages.push({
|
|
353
|
+
type: "dir-dependency",
|
|
354
|
+
plugin: directiveMap.teacss,
|
|
355
|
+
...dependency,
|
|
356
|
+
parent: from
|
|
357
|
+
});
|
|
358
|
+
};
|
|
359
|
+
let state;
|
|
360
|
+
try {
|
|
361
|
+
state = await getGeneratorState();
|
|
362
|
+
} catch (error) {
|
|
363
|
+
for (const file of getConfigSourcesFromError(error)) {
|
|
364
|
+
addDependency(file);
|
|
365
|
+
await addDirectoryDependency(cwd, file, true);
|
|
366
|
+
}
|
|
367
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
368
|
+
throw new Error(`TeaCSS config not found: ${message}`);
|
|
369
|
+
}
|
|
370
|
+
const { generator } = state;
|
|
371
|
+
for (const file of getConfigSourceFiles(state.sources)) addDependency(file);
|
|
372
|
+
await parseApply(root, generator, directiveMap.apply);
|
|
373
|
+
await parseTheme(root, generator);
|
|
374
|
+
await parseScreen(root, generator, directiveMap.screen);
|
|
375
|
+
if (!isScanTarget) {
|
|
376
|
+
if (result.opts.from) addDependency(from);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const scanRequestVersion = ++state.scanRequestVersion;
|
|
380
|
+
const fileMap = new Map(state.fileMap);
|
|
381
|
+
const fileClassMap = new Map(state.fileClassMap);
|
|
382
|
+
const configuredGlobs = generator.config.content?.filesystem;
|
|
383
|
+
const globs = configuredGlobs ?? defaultFilesystemGlobs;
|
|
384
|
+
const scanCwd = configuredGlobs === void 0 ? cwd : state.entryDir;
|
|
385
|
+
for (const pattern of globs) await addDirectoryDependency(scanCwd, pattern);
|
|
386
|
+
const ignore = [...globs.some((i) => i.includes("node_modules")) ? [] : ["**/node_modules/**"], ...configuredGlobs === void 0 ? DEFAULT_GENERATED_IGNORE : []];
|
|
387
|
+
const plainContent = [...inlineOptionContent, ...generator.config.content?.inline ?? []];
|
|
388
|
+
const entries = await glob(globs, {
|
|
389
|
+
cwd: scanCwd,
|
|
390
|
+
absolute: true,
|
|
391
|
+
ignore,
|
|
392
|
+
expandDirectories: false
|
|
393
|
+
});
|
|
394
|
+
const activeFiles = new Set(entries);
|
|
395
|
+
for (const file of fileMap.keys()) if (!activeFiles.has(file)) fileMap.delete(file);
|
|
396
|
+
for (const file of fileClassMap.keys()) if (!activeFiles.has(file)) fileClassMap.delete(file);
|
|
397
|
+
const classes = /* @__PURE__ */ new Set();
|
|
398
|
+
const inlineMatches = await Promise.all(plainContent.map(async (c, idx) => {
|
|
399
|
+
if (typeof c === "function") c = await c();
|
|
400
|
+
if (typeof c === "string") c = { code: c };
|
|
401
|
+
return (await generator.generate(c.code, {
|
|
402
|
+
id: c.id ?? `__plain_content_${idx}__`,
|
|
403
|
+
preflights: false,
|
|
404
|
+
safelist: false
|
|
405
|
+
})).matched;
|
|
406
|
+
}));
|
|
407
|
+
for (const matched of inlineMatches) for (const candidate of matched) classes.add(candidate);
|
|
408
|
+
const BATCH_SIZE = 500;
|
|
409
|
+
const scanFile = async (file) => {
|
|
410
|
+
addDependency(file);
|
|
411
|
+
const source = await readChangedFile(file, fileMap.get(file));
|
|
412
|
+
if (source === void 0) {
|
|
413
|
+
fileMap.delete(file);
|
|
414
|
+
fileClassMap.delete(file);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (source === null) return;
|
|
418
|
+
const { matched } = await generator.generate(source.content, {
|
|
419
|
+
id: file,
|
|
420
|
+
preflights: false,
|
|
421
|
+
safelist: false
|
|
422
|
+
});
|
|
423
|
+
fileClassMap.set(file, matched);
|
|
424
|
+
fileMap.set(file, source.signature);
|
|
425
|
+
};
|
|
426
|
+
for (let i = 0; i < entries.length; i += BATCH_SIZE) await Promise.all(entries.slice(i, i + BATCH_SIZE).map(scanFile));
|
|
427
|
+
for (const file of [...fileClassMap.keys()].sort()) {
|
|
428
|
+
const set = fileClassMap.get(file);
|
|
429
|
+
for (const candidate of set) classes.add(candidate);
|
|
430
|
+
}
|
|
431
|
+
const generation = await generator.generate(classes);
|
|
432
|
+
if (state.scanRequestVersion === scanRequestVersion) {
|
|
433
|
+
state.fileMap = fileMap;
|
|
434
|
+
state.fileClassMap = fileClassMap;
|
|
435
|
+
}
|
|
436
|
+
replaceTeacssAtRules(root, generation, directiveMap.teacss);
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
//#endregion
|
|
440
|
+
export { createPlugin };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { Result, Root } from "postcss";
|
|
2
|
+
import { UserConfig } from "@teacss/core";
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
interface TeacssPostcssPluginOptions {
|
|
5
|
+
content?: (string | {
|
|
6
|
+
raw: string;
|
|
7
|
+
extension: string;
|
|
8
|
+
})[];
|
|
9
|
+
directiveMap?: {
|
|
10
|
+
apply?: string;
|
|
11
|
+
screen?: string;
|
|
12
|
+
teacss?: string;
|
|
13
|
+
};
|
|
14
|
+
cwd?: string;
|
|
15
|
+
configOrPath?: string | UserConfig;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/index.d.ts
|
|
19
|
+
declare function pluginTeacss(options?: TeacssPostcssPluginOptions): {
|
|
20
|
+
postcssPlugin: string;
|
|
21
|
+
plugins: ((root: Root, result: Result) => Promise<void>)[];
|
|
22
|
+
};
|
|
23
|
+
declare namespace pluginTeacss {
|
|
24
|
+
var postcss: boolean;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { TeacssPostcssPluginOptions, pluginTeacss };
|