@styleframe/plugin 2.4.1 → 3.0.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/CHANGELOG.md +13 -0
- package/dist/astro.d.ts +1 -1
- package/dist/astro.d.ts.map +1 -1
- package/dist/astro.js +2 -3
- package/dist/astro.js.map +1 -1
- package/dist/esbuild.d.ts +3 -3
- package/dist/esbuild.js +1 -2
- package/dist/esbuild.js.map +1 -1
- package/dist/farm.d.ts +1 -1
- package/dist/farm.js +1 -2
- package/dist/farm.js.map +1 -1
- package/dist/index-CXVebONK.d.ts +36 -0
- package/dist/index-CXVebONK.d.ts.map +1 -0
- package/dist/index.d.ts +2 -10
- package/dist/index.js +2 -3
- package/dist/nuxt.d.ts +3 -2
- package/dist/nuxt.d.ts.map +1 -1
- package/dist/nuxt.js +3 -4
- package/dist/nuxt.js.map +1 -1
- package/dist/plugin-WmJKFcn_.js +595 -0
- package/dist/plugin-WmJKFcn_.js.map +1 -0
- package/dist/rollup.d.ts +1 -1
- package/dist/rollup.js +1 -2
- package/dist/rollup.js.map +1 -1
- package/dist/rspack.d.ts +1 -1
- package/dist/rspack.js +1 -2
- package/dist/rspack.js.map +1 -1
- package/dist/{vite-BY9qXnXW.js → vite-zWsCwvYT.js} +2 -2
- package/dist/vite-zWsCwvYT.js.map +1 -0
- package/dist/vite.d.ts +3 -3
- package/dist/vite.js +2 -3
- package/dist/{webpack-DpR7fAjO.js → webpack-De3KlGzm.js} +2 -2
- package/dist/webpack-De3KlGzm.js.map +1 -0
- package/dist/webpack.d.ts +3 -3
- package/dist/webpack.js +2 -3
- package/package.json +8 -5
- package/dist/constants-CnbAL4bY.js +0 -17
- package/dist/constants-CnbAL4bY.js.map +0 -1
- package/dist/constants.d.ts +0 -17
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/src-4YtKkWMH.js +0 -162
- package/dist/src-4YtKkWMH.js.map +0 -1
- package/dist/types-Clt7t35T.d.ts +0 -12
- package/dist/types-Clt7t35T.d.ts.map +0 -1
- package/dist/types.d.ts +0 -2
- package/dist/types.js +0 -1
- package/dist/vite-BY9qXnXW.js.map +0 -1
- package/dist/webpack-DpR7fAjO.js.map +0 -1
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { consola } from "consola";
|
|
3
|
+
import { transform } from "esbuild";
|
|
4
|
+
import { createUnplugin } from "unplugin";
|
|
5
|
+
import { glob } from "tinyglobby";
|
|
6
|
+
import { loadExtensionModule, loadModule } from "@styleframe/loader";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import { transpile } from "@styleframe/transpiler";
|
|
10
|
+
import { getLicenseKeyFromEnv, validateInstanceLicense } from "@styleframe/license";
|
|
11
|
+
import fs$1 from "node:fs/promises";
|
|
12
|
+
import { createScanner } from "@styleframe/scanner";
|
|
13
|
+
|
|
14
|
+
//#region src/plugin/constants.ts
|
|
15
|
+
const DEFAULT_ENTRY = "./styleframe.config.ts";
|
|
16
|
+
const DEFAULT_OPTIONS = {
|
|
17
|
+
entry: DEFAULT_ENTRY,
|
|
18
|
+
silent: false
|
|
19
|
+
};
|
|
20
|
+
const PLUGIN_NAME = "styleframe";
|
|
21
|
+
const IMPORT_V_PREFIX = "virtual:";
|
|
22
|
+
const ROLLUP_V_PREFIX = "\0";
|
|
23
|
+
const VIRTUAL_CSS_MODULE_ID = `${IMPORT_V_PREFIX}${PLUGIN_NAME}.css`;
|
|
24
|
+
const RESOLVED_VIRTUAL_CSS_MODULE_ID = `${ROLLUP_V_PREFIX}${VIRTUAL_CSS_MODULE_ID}`;
|
|
25
|
+
const VIRTUAL_TS_MODULE_ID = `${IMPORT_V_PREFIX}${PLUGIN_NAME}`;
|
|
26
|
+
const RESOLVED_VIRTUAL_TS_MODULE_ID = `${ROLLUP_V_PREFIX}${VIRTUAL_TS_MODULE_ID}`;
|
|
27
|
+
const RESOLVED_VIRTUAL_EXTENSION_ID = `${ROLLUP_V_PREFIX}${IMPORT_V_PREFIX}${PLUGIN_NAME}:extension`;
|
|
28
|
+
const RESOLVED_VIRTUAL_CONSUMER_ID = `${ROLLUP_V_PREFIX}${IMPORT_V_PREFIX}${PLUGIN_NAME}:consumer`;
|
|
29
|
+
const DEFAULT_IGNORE_PATTERNS = [
|
|
30
|
+
"**/node_modules/**",
|
|
31
|
+
"**/.git/**",
|
|
32
|
+
"**/dist/**",
|
|
33
|
+
"**/build/**",
|
|
34
|
+
"**/.next/**",
|
|
35
|
+
"**/.nuxt/**",
|
|
36
|
+
"**/coverage/**"
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/plugin/state.ts
|
|
41
|
+
/**
|
|
42
|
+
* Create a new plugin state instance
|
|
43
|
+
*/
|
|
44
|
+
function createPluginState(configPath) {
|
|
45
|
+
return {
|
|
46
|
+
globalInstance: null,
|
|
47
|
+
configFile: {
|
|
48
|
+
path: configPath,
|
|
49
|
+
loadOrder: -1,
|
|
50
|
+
exports: /* @__PURE__ */ new Map(),
|
|
51
|
+
lastModified: 0
|
|
52
|
+
},
|
|
53
|
+
files: /* @__PURE__ */ new Map(),
|
|
54
|
+
loadingFiles: /* @__PURE__ */ new Set(),
|
|
55
|
+
initialized: false
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Find if an export name is already used by another file.
|
|
60
|
+
* Returns the source file path if collision found, null otherwise.
|
|
61
|
+
*/
|
|
62
|
+
function findExportCollision(state, exportName, excludeFile) {
|
|
63
|
+
if (state.configFile && state.configFile.path !== excludeFile && state.configFile.exports.has(exportName)) return state.configFile.path;
|
|
64
|
+
for (const [filePath, fileInfo] of state.files) {
|
|
65
|
+
if (filePath === excludeFile) continue;
|
|
66
|
+
if (fileInfo.exports.has(exportName)) return filePath;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Reset all state (used when config changes)
|
|
72
|
+
*/
|
|
73
|
+
function resetState(state) {
|
|
74
|
+
state.globalInstance = null;
|
|
75
|
+
if (state.configFile) {
|
|
76
|
+
state.configFile.exports.clear();
|
|
77
|
+
state.configFile.lastModified = 0;
|
|
78
|
+
}
|
|
79
|
+
state.files.clear();
|
|
80
|
+
state.loadingFiles.clear();
|
|
81
|
+
state.initialized = false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/plugin/discovery.ts
|
|
86
|
+
/**
|
|
87
|
+
* Discover all *.styleframe.ts files in the project
|
|
88
|
+
*/
|
|
89
|
+
async function discoverStyleframeFiles(options) {
|
|
90
|
+
return await glob(options.include.length > 0 ? options.include : ["**/*.styleframe.ts"], {
|
|
91
|
+
cwd: options.cwd,
|
|
92
|
+
absolute: true,
|
|
93
|
+
ignore: [...DEFAULT_IGNORE_PATTERNS, ...options.exclude]
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Sort files by the specified load order strategy
|
|
98
|
+
*/
|
|
99
|
+
function sortByLoadOrder(files, strategy) {
|
|
100
|
+
const sorted = [...files];
|
|
101
|
+
switch (strategy) {
|
|
102
|
+
case "alphabetical": return sorted.sort((a, b) => a.localeCompare(b));
|
|
103
|
+
case "depth-first": return sorted.sort((a, b) => {
|
|
104
|
+
const depthA = a.split(path.sep).length;
|
|
105
|
+
const depthB = b.split(path.sep).length;
|
|
106
|
+
if (depthA !== depthB) return depthA - depthB;
|
|
107
|
+
return a.localeCompare(b);
|
|
108
|
+
});
|
|
109
|
+
default: {
|
|
110
|
+
const _exhaustive = strategy;
|
|
111
|
+
throw new Error(`Unknown load order strategy: ${_exhaustive}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/plugin/errors.ts
|
|
118
|
+
/**
|
|
119
|
+
* Base error class for Styleframe plugin errors
|
|
120
|
+
*/
|
|
121
|
+
var StyleframePluginError = class extends Error {
|
|
122
|
+
constructor(message) {
|
|
123
|
+
super(`[styleframe] ${message}`);
|
|
124
|
+
this.name = "StyleframePluginError";
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Thrown when two different files export the same name
|
|
129
|
+
*/
|
|
130
|
+
var ExportCollisionError = class extends StyleframePluginError {
|
|
131
|
+
constructor(exportName, source1, source2) {
|
|
132
|
+
super(`Export collision: "${exportName}" is exported from both:\n - ${source1}\n - ${source2}\n\nRename one of the exports to resolve this collision.`);
|
|
133
|
+
this.name = "ExportCollisionError";
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Thrown when trying to use the global instance before it's initialized
|
|
138
|
+
*/
|
|
139
|
+
var GlobalInstanceNotInitializedError = class extends StyleframePluginError {
|
|
140
|
+
constructor() {
|
|
141
|
+
super("Global instance not initialized. Make sure styleframe.config.ts is loaded before *.styleframe.ts files.");
|
|
142
|
+
this.name = "GlobalInstanceNotInitializedError";
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Thrown when a circular dependency is detected
|
|
147
|
+
*/
|
|
148
|
+
var CircularDependencyError = class extends StyleframePluginError {
|
|
149
|
+
constructor(filePath, chain) {
|
|
150
|
+
super(`Circular dependency detected:\n ${chain.join(" -> ")} -> ${filePath}`);
|
|
151
|
+
this.name = "CircularDependencyError";
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/plugin/global-loader.ts
|
|
157
|
+
const GLOBAL_INSTANCE_KEY = "__STYLEFRAME_GLOBAL_INSTANCE__";
|
|
158
|
+
let extensionShimPath = null;
|
|
159
|
+
/**
|
|
160
|
+
* Get or create the extension shim file.
|
|
161
|
+
* The shim reads the global instance from globalThis and exports it.
|
|
162
|
+
*/
|
|
163
|
+
function getExtensionShimPath() {
|
|
164
|
+
if (extensionShimPath && fs.existsSync(extensionShimPath)) return extensionShimPath;
|
|
165
|
+
const shimContent = `
|
|
166
|
+
export function styleframe() {
|
|
167
|
+
const instance = globalThis["${GLOBAL_INSTANCE_KEY}"];
|
|
168
|
+
if (!instance) {
|
|
169
|
+
throw new Error('[styleframe] Global instance not available during loading');
|
|
170
|
+
}
|
|
171
|
+
return instance;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export default { styleframe };
|
|
175
|
+
`;
|
|
176
|
+
const tempDir = os.tmpdir();
|
|
177
|
+
extensionShimPath = path.join(tempDir, `styleframe-shim-${process.pid}.mjs`);
|
|
178
|
+
fs.writeFileSync(extensionShimPath, shimContent);
|
|
179
|
+
return extensionShimPath;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Check exports for collisions and throw if found.
|
|
183
|
+
*/
|
|
184
|
+
function checkExportCollisions(state, exports, filePath) {
|
|
185
|
+
for (const [name] of exports) {
|
|
186
|
+
const collision = findExportCollision(state, name, filePath);
|
|
187
|
+
if (collision) throw new ExportCollisionError(name, collision, filePath);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Load the config file and initialize the global instance
|
|
192
|
+
*/
|
|
193
|
+
async function loadConfigFile(state) {
|
|
194
|
+
if (!state.configFile) throw new Error("[styleframe] Config file not set");
|
|
195
|
+
const configPath = state.configFile.path;
|
|
196
|
+
const { instance, exports } = await loadModule(configPath);
|
|
197
|
+
checkExportCollisions(state, exports, configPath);
|
|
198
|
+
state.globalInstance = instance;
|
|
199
|
+
state.configFile.exports = exports;
|
|
200
|
+
state.configFile.lastModified = Date.now();
|
|
201
|
+
return instance;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Load a single *.styleframe.ts file using the global instance
|
|
205
|
+
*/
|
|
206
|
+
async function loadStyleframeFile(state, filePath, loadOrder) {
|
|
207
|
+
if (!state.globalInstance) throw new GlobalInstanceNotInitializedError();
|
|
208
|
+
if (state.loadingFiles.has(filePath)) throw new CircularDependencyError(filePath, [...state.loadingFiles]);
|
|
209
|
+
state.loadingFiles.add(filePath);
|
|
210
|
+
try {
|
|
211
|
+
globalThis[GLOBAL_INSTANCE_KEY] = state.globalInstance;
|
|
212
|
+
const { exports } = await loadExtensionModule(filePath, { alias: { "virtual:styleframe": getExtensionShimPath() } });
|
|
213
|
+
checkExportCollisions(state, exports, filePath);
|
|
214
|
+
state.files.set(filePath, {
|
|
215
|
+
path: filePath,
|
|
216
|
+
loadOrder,
|
|
217
|
+
exports,
|
|
218
|
+
lastModified: Date.now()
|
|
219
|
+
});
|
|
220
|
+
} finally {
|
|
221
|
+
state.loadingFiles.delete(filePath);
|
|
222
|
+
delete globalThis[GLOBAL_INSTANCE_KEY];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Load all styleframe files in order
|
|
227
|
+
*/
|
|
228
|
+
async function loadAllStyleframeFiles(state, files) {
|
|
229
|
+
for (let i = 0; i < files.length; i++) {
|
|
230
|
+
const file = files[i];
|
|
231
|
+
if (file) await loadStyleframeFile(state, file, i);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Reload the config and all styleframe files
|
|
236
|
+
*/
|
|
237
|
+
async function reloadAll(state, files) {
|
|
238
|
+
resetState(state);
|
|
239
|
+
await loadConfigFile(state);
|
|
240
|
+
await loadAllStyleframeFiles(state, files);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/plugin/generate/extension-module.ts
|
|
245
|
+
/**
|
|
246
|
+
* Generate the extension module content.
|
|
247
|
+
* This is what *.styleframe.ts files receive when they import from 'virtual:styleframe'.
|
|
248
|
+
* It imports the config directly and returns the same instance.
|
|
249
|
+
*/
|
|
250
|
+
function generateExtensionModule(configPath) {
|
|
251
|
+
return `
|
|
252
|
+
import config from '${configPath.replace(/\\/g, "/")}';
|
|
253
|
+
|
|
254
|
+
export function styleframe() {
|
|
255
|
+
return config;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export default config;
|
|
259
|
+
`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/plugin/generate/consumer-module.ts
|
|
264
|
+
/**
|
|
265
|
+
* Generate the consumer module content using the transpiler.
|
|
266
|
+
* Uses the TypeScript transpiler to generate proper recipe exports with serialized runtime data.
|
|
267
|
+
*/
|
|
268
|
+
async function generateConsumerModule(state) {
|
|
269
|
+
if (!state.globalInstance) return `// Styleframe not initialized`;
|
|
270
|
+
return (await transpile(state.globalInstance, { type: "ts" })).files.find((f) => f.name === "index.ts")?.content ?? "";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/plugin/generate/global-css.ts
|
|
275
|
+
/**
|
|
276
|
+
* Generate global CSS from the global instance.
|
|
277
|
+
*/
|
|
278
|
+
async function generateGlobalCSS(state, isBuild, options) {
|
|
279
|
+
if (!state.globalInstance) return { code: "/* Styleframe not initialized */" };
|
|
280
|
+
await validateInstanceLicense(state.globalInstance, {
|
|
281
|
+
licenseKey: getLicenseKeyFromEnv() || "",
|
|
282
|
+
environment: process.env.NODE_ENV || "development",
|
|
283
|
+
isBuild
|
|
284
|
+
});
|
|
285
|
+
const css = (await transpile(state.globalInstance, { type: "css" })).files.map((f) => f.content).join("\n");
|
|
286
|
+
if (!options.silent) consola.success(`[styleframe] Built global CSS successfully.`);
|
|
287
|
+
return { code: css };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region src/plugin/generate/dts.ts
|
|
292
|
+
const DEFAULT_OUT_DIR = ".styleframe";
|
|
293
|
+
/**
|
|
294
|
+
* Generates type declarations for virtual:styleframe module.
|
|
295
|
+
* Uses the transpiler's dts mode to generate the types.
|
|
296
|
+
*/
|
|
297
|
+
async function generateTypeDeclarations(state, cwd, options = {}, silent = false) {
|
|
298
|
+
if (!state.globalInstance) return;
|
|
299
|
+
const outDir = path.resolve(cwd, options.outDir ?? DEFAULT_OUT_DIR);
|
|
300
|
+
await fs$1.mkdir(outDir, { recursive: true });
|
|
301
|
+
const content = (await transpile(state.globalInstance, { type: "dts" })).files.find((f) => f.name === "index.d.ts")?.content ?? "";
|
|
302
|
+
const outputPath = path.join(outDir, "styleframe.d.ts");
|
|
303
|
+
await fs$1.writeFile(outputPath, content);
|
|
304
|
+
if (!silent) consola.success(`[styleframe] Generated type declarations.`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
//#endregion
|
|
308
|
+
//#region src/plugin/scanner.ts
|
|
309
|
+
/**
|
|
310
|
+
* Create a plugin scanner instance.
|
|
311
|
+
*
|
|
312
|
+
* @param contentPatterns Glob patterns for content files to scan
|
|
313
|
+
* @param cwd Working directory for glob resolution
|
|
314
|
+
* @returns Plugin scanner state
|
|
315
|
+
*/
|
|
316
|
+
function createPluginScanner(contentPatterns, cwd) {
|
|
317
|
+
return {
|
|
318
|
+
scanner: createScanner({
|
|
319
|
+
content: contentPatterns,
|
|
320
|
+
cwd
|
|
321
|
+
}),
|
|
322
|
+
scannedFiles: /* @__PURE__ */ new Set()
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Full scan: scan all content files and register detected utilities.
|
|
327
|
+
*
|
|
328
|
+
* Used at build start and after reloadAll.
|
|
329
|
+
*
|
|
330
|
+
* @param pluginState The plugin global state
|
|
331
|
+
* @param scannerState The scanner state
|
|
332
|
+
* @param options Options for logging
|
|
333
|
+
* @returns Number of newly registered utility values
|
|
334
|
+
*/
|
|
335
|
+
async function scanAndRegister(pluginState, scannerState, options) {
|
|
336
|
+
if (!pluginState.globalInstance) return 0;
|
|
337
|
+
const result = await scannerState.scanner.scan();
|
|
338
|
+
scannerState.scannedFiles.clear();
|
|
339
|
+
for (const filePath of result.files.keys()) scannerState.scannedFiles.add(filePath);
|
|
340
|
+
const matches = scannerState.scanner.match(result.allParsed, pluginState.globalInstance.root);
|
|
341
|
+
const registered = registerMatchedUtilities(pluginState, matches);
|
|
342
|
+
if (!options?.silent) {
|
|
343
|
+
const unmatched = matches.filter((m) => m.factory === null);
|
|
344
|
+
if (unmatched.length > 0) {
|
|
345
|
+
const names = [...new Set(unmatched.map((m) => m.parsed.raw))];
|
|
346
|
+
consola.warn(`[styleframe] Scanner found ${unmatched.length} utility class(es) without a matching factory: ${names.join(", ")}`);
|
|
347
|
+
}
|
|
348
|
+
if (registered > 0) consola.info(`[styleframe] Scanner registered ${registered} utility value(s) from ${result.files.size} content file(s).`);
|
|
349
|
+
}
|
|
350
|
+
return registered;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Incremental scan: scan a single file and register any new utilities.
|
|
354
|
+
*
|
|
355
|
+
* Used for HMR when a content file changes.
|
|
356
|
+
*
|
|
357
|
+
* @param pluginState The plugin global state
|
|
358
|
+
* @param scannerState The scanner state
|
|
359
|
+
* @param filePath The changed file path
|
|
360
|
+
* @returns true if new values were registered (CSS needs invalidation)
|
|
361
|
+
*/
|
|
362
|
+
async function scanFileAndRegister(pluginState, scannerState, filePath) {
|
|
363
|
+
if (!pluginState.globalInstance) return false;
|
|
364
|
+
scannerState.scanner.invalidate(filePath);
|
|
365
|
+
const fileResult = await scannerState.scanner.scanFile(filePath);
|
|
366
|
+
return registerMatchedUtilities(pluginState, scannerState.scanner.match(fileResult.parsed, pluginState.globalInstance.root)) > 0;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Check if a file is a tracked content file.
|
|
370
|
+
*/
|
|
371
|
+
function isContentFile(scannerState, filePath) {
|
|
372
|
+
return scannerState?.scannedFiles.has(filePath) ?? false;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Register utility values detected by the scanner that don't yet exist on the root.
|
|
376
|
+
*
|
|
377
|
+
* For non-arbitrary values, uses the factory's autogenerate function (treating the
|
|
378
|
+
* value key as a token reference). For arbitrary values, registers with the literal
|
|
379
|
+
* CSS value directly.
|
|
380
|
+
*
|
|
381
|
+
* @param pluginState The plugin global state
|
|
382
|
+
* @param matches Scanner match results
|
|
383
|
+
* @returns Number of newly registered values
|
|
384
|
+
*/
|
|
385
|
+
function registerMatchedUtilities(pluginState, matches) {
|
|
386
|
+
const unregistered = matches.filter((m) => m.factory !== null && !m.exists);
|
|
387
|
+
if (unregistered.length === 0) return 0;
|
|
388
|
+
const groups = /* @__PURE__ */ new Map();
|
|
389
|
+
for (const match of unregistered) {
|
|
390
|
+
const factory = match.factory;
|
|
391
|
+
const key = `${factory.name}:${match.parsed.value}`;
|
|
392
|
+
const existing = groups.get(key);
|
|
393
|
+
if (!existing) groups.set(key, {
|
|
394
|
+
factory,
|
|
395
|
+
parsed: match.parsed,
|
|
396
|
+
modifiers: [...match.modifierFactories]
|
|
397
|
+
});
|
|
398
|
+
else for (const mod of match.modifierFactories) {
|
|
399
|
+
const modKey = mod.key.join(",");
|
|
400
|
+
if (!existing.modifiers.some((m) => m.key.join(",") === modKey)) existing.modifiers.push(mod);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
let count = 0;
|
|
404
|
+
for (const { factory, parsed, modifiers } of groups.values()) {
|
|
405
|
+
const modifierArgs = modifiers.length > 0 ? modifiers : void 0;
|
|
406
|
+
if (parsed.isArbitrary && parsed.arbitraryValue !== void 0) factory.create({ [parsed.value]: parsed.arbitraryValue }, modifierArgs);
|
|
407
|
+
else {
|
|
408
|
+
const autoGenerated = factory.autogenerate(`@${parsed.value}`);
|
|
409
|
+
factory.create(autoGenerated, modifierArgs);
|
|
410
|
+
}
|
|
411
|
+
count++;
|
|
412
|
+
}
|
|
413
|
+
return count;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region src/plugin/index.ts
|
|
418
|
+
const STYLEFRAME_SOURCE_REGEX = /\.styleframe\.ts$/;
|
|
419
|
+
function isStyleframeSourceFile(id) {
|
|
420
|
+
return STYLEFRAME_SOURCE_REGEX.test(id);
|
|
421
|
+
}
|
|
422
|
+
const unpluginFactory = (options = DEFAULT_OPTIONS) => {
|
|
423
|
+
const cwd = process.cwd();
|
|
424
|
+
const entry = options.entry ?? DEFAULT_ENTRY;
|
|
425
|
+
const configPath = path.isAbsolute(entry) ? entry : path.resolve(cwd, entry);
|
|
426
|
+
const state = createPluginState(configPath);
|
|
427
|
+
let isBuildCommand = false;
|
|
428
|
+
let scannerState = null;
|
|
429
|
+
let typeGenTimeout = null;
|
|
430
|
+
const scheduleTypeGeneration = () => {
|
|
431
|
+
if (options.dts?.enabled === false) return;
|
|
432
|
+
if (typeGenTimeout) clearTimeout(typeGenTimeout);
|
|
433
|
+
typeGenTimeout = setTimeout(async () => {
|
|
434
|
+
try {
|
|
435
|
+
await generateTypeDeclarations(state, cwd, options.dts, options.silent);
|
|
436
|
+
} catch (error) {
|
|
437
|
+
consola.error(`[styleframe] Type generation failed:`, error);
|
|
438
|
+
}
|
|
439
|
+
}, 100);
|
|
440
|
+
};
|
|
441
|
+
return {
|
|
442
|
+
name: PLUGIN_NAME,
|
|
443
|
+
enforce: "pre",
|
|
444
|
+
async buildStart() {
|
|
445
|
+
isBuildCommand = process.argv.includes("build");
|
|
446
|
+
if (!options.silent) {
|
|
447
|
+
console.log("");
|
|
448
|
+
consola.info(`[styleframe] Initializing...`);
|
|
449
|
+
}
|
|
450
|
+
try {
|
|
451
|
+
await loadConfigFile(state);
|
|
452
|
+
const sortedFiles = sortByLoadOrder((await discoverStyleframeFiles({
|
|
453
|
+
cwd,
|
|
454
|
+
include: options.include ?? [],
|
|
455
|
+
exclude: options.exclude ?? []
|
|
456
|
+
})).filter((f) => f !== configPath), options.loadOrder ?? "alphabetical");
|
|
457
|
+
await loadAllStyleframeFiles(state, sortedFiles);
|
|
458
|
+
if (options.content?.length) {
|
|
459
|
+
scannerState = createPluginScanner(options.content, cwd);
|
|
460
|
+
await scanAndRegister(state, scannerState, { silent: options.silent });
|
|
461
|
+
}
|
|
462
|
+
this.addWatchFile(configPath);
|
|
463
|
+
for (const file of sortedFiles) this.addWatchFile(file);
|
|
464
|
+
if (scannerState) for (const file of scannerState.scannedFiles) this.addWatchFile(file);
|
|
465
|
+
state.initialized = true;
|
|
466
|
+
if (options.dts?.enabled !== false) await generateTypeDeclarations(state, cwd, options.dts, options.silent);
|
|
467
|
+
if (!options.silent) {
|
|
468
|
+
consola.success(`[styleframe] Initialized with ${sortedFiles.length} styleframe file(s).`);
|
|
469
|
+
console.log("");
|
|
470
|
+
}
|
|
471
|
+
} catch (error) {
|
|
472
|
+
consola.error(`[styleframe] Initialization failed:`, error);
|
|
473
|
+
throw error;
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
resolveId(id, importer) {
|
|
477
|
+
if (id === VIRTUAL_TS_MODULE_ID) {
|
|
478
|
+
if (importer && isStyleframeSourceFile(importer)) return RESOLVED_VIRTUAL_EXTENSION_ID;
|
|
479
|
+
return RESOLVED_VIRTUAL_CONSUMER_ID;
|
|
480
|
+
}
|
|
481
|
+
if (id === VIRTUAL_CSS_MODULE_ID) return RESOLVED_VIRTUAL_CSS_MODULE_ID;
|
|
482
|
+
return null;
|
|
483
|
+
},
|
|
484
|
+
async load(id) {
|
|
485
|
+
if (id === RESOLVED_VIRTUAL_EXTENSION_ID) return { code: generateExtensionModule(configPath) };
|
|
486
|
+
if (id === RESOLVED_VIRTUAL_CONSUMER_ID) return { code: await generateConsumerModule(state) };
|
|
487
|
+
if (id === RESOLVED_VIRTUAL_CSS_MODULE_ID) return generateGlobalCSS(state, isBuildCommand, options);
|
|
488
|
+
return null;
|
|
489
|
+
},
|
|
490
|
+
vite: {
|
|
491
|
+
async transform(code, id) {
|
|
492
|
+
if (id === RESOLVED_VIRTUAL_TS_MODULE_ID || id === RESOLVED_VIRTUAL_EXTENSION_ID || id === RESOLVED_VIRTUAL_CONSUMER_ID) {
|
|
493
|
+
const result = await transform(code, {
|
|
494
|
+
loader: "ts",
|
|
495
|
+
format: "esm",
|
|
496
|
+
target: "esnext"
|
|
497
|
+
});
|
|
498
|
+
return {
|
|
499
|
+
code: result.code,
|
|
500
|
+
map: result.map || null
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
return null;
|
|
504
|
+
},
|
|
505
|
+
configureServer(server) {
|
|
506
|
+
server.watcher.on("add", async (file) => {
|
|
507
|
+
if (isStyleframeSourceFile(file) && file !== configPath && !state.files.has(file)) try {
|
|
508
|
+
const loadOrder = state.files.size;
|
|
509
|
+
await loadStyleframeFile(state, file, loadOrder);
|
|
510
|
+
if (!options.silent) consola.info(`[styleframe] Discovered new file: ${path.relative(cwd, file)}`);
|
|
511
|
+
if (scannerState) await scanAndRegister(state, scannerState, { silent: options.silent });
|
|
512
|
+
const consumerMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_CONSUMER_ID);
|
|
513
|
+
const cssMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_CSS_MODULE_ID);
|
|
514
|
+
if (consumerMod) server.moduleGraph.invalidateModule(consumerMod);
|
|
515
|
+
if (cssMod) server.moduleGraph.invalidateModule(cssMod);
|
|
516
|
+
scheduleTypeGeneration();
|
|
517
|
+
server.ws.send({ type: "full-reload" });
|
|
518
|
+
} catch (error) {
|
|
519
|
+
consola.error(`[styleframe] Failed to load new file: ${file}`, error);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
server.watcher.on("unlink", async (file) => {
|
|
523
|
+
if (state.files.has(file)) {
|
|
524
|
+
if (!options.silent) consola.info(`[styleframe] File removed: ${path.relative(cwd, file)}`);
|
|
525
|
+
try {
|
|
526
|
+
await reloadAll(state, [...state.files.entries()].filter(([filePath]) => filePath !== file).sort(([, a], [, b]) => a.loadOrder - b.loadOrder).map(([filePath]) => filePath));
|
|
527
|
+
if (scannerState) await scanAndRegister(state, scannerState, { silent: options.silent });
|
|
528
|
+
const consumerMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_CONSUMER_ID);
|
|
529
|
+
const cssMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_CSS_MODULE_ID);
|
|
530
|
+
if (consumerMod) server.moduleGraph.invalidateModule(consumerMod);
|
|
531
|
+
if (cssMod) server.moduleGraph.invalidateModule(cssMod);
|
|
532
|
+
scheduleTypeGeneration();
|
|
533
|
+
server.ws.send({ type: "full-reload" });
|
|
534
|
+
} catch (error) {
|
|
535
|
+
consola.error(`[styleframe] Failed to reload after file removal: ${file}`, error);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
},
|
|
540
|
+
async handleHotUpdate(ctx) {
|
|
541
|
+
const getModuleById = async (id) => {
|
|
542
|
+
return ctx.server?.moduleGraph.getModuleById(id) ?? await ctx.server?.moduleGraph.getModuleByUrl(id);
|
|
543
|
+
};
|
|
544
|
+
if (ctx.file === configPath) {
|
|
545
|
+
if (!options.silent) consola.info(`[styleframe] Config changed, reloading...`);
|
|
546
|
+
try {
|
|
547
|
+
await reloadAll(state, [...state.files.keys()]);
|
|
548
|
+
if (scannerState) await scanAndRegister(state, scannerState, { silent: options.silent });
|
|
549
|
+
const modulesToInvalidate = [
|
|
550
|
+
await getModuleById(RESOLVED_VIRTUAL_CSS_MODULE_ID),
|
|
551
|
+
await getModuleById(RESOLVED_VIRTUAL_EXTENSION_ID),
|
|
552
|
+
await getModuleById(RESOLVED_VIRTUAL_CONSUMER_ID)
|
|
553
|
+
].filter(Boolean);
|
|
554
|
+
scheduleTypeGeneration();
|
|
555
|
+
if (modulesToInvalidate.length > 0) return modulesToInvalidate;
|
|
556
|
+
} catch (error) {
|
|
557
|
+
consola.error(`[styleframe] Reload failed:`, error);
|
|
558
|
+
}
|
|
559
|
+
return ctx.modules;
|
|
560
|
+
}
|
|
561
|
+
if (state.files.has(ctx.file)) {
|
|
562
|
+
if (!options.silent) consola.info(`[styleframe] File changed: ${path.relative(cwd, ctx.file)}`);
|
|
563
|
+
try {
|
|
564
|
+
await reloadAll(state, [...state.files.keys()]);
|
|
565
|
+
if (scannerState) await scanAndRegister(state, scannerState, { silent: options.silent });
|
|
566
|
+
const modulesToInvalidate = [await getModuleById(RESOLVED_VIRTUAL_CSS_MODULE_ID), await getModuleById(RESOLVED_VIRTUAL_CONSUMER_ID)].filter(Boolean);
|
|
567
|
+
scheduleTypeGeneration();
|
|
568
|
+
if (modulesToInvalidate.length > 0) return modulesToInvalidate;
|
|
569
|
+
} catch (error) {
|
|
570
|
+
consola.error(`[styleframe] Failed to reload: ${ctx.file}`, error);
|
|
571
|
+
}
|
|
572
|
+
return ctx.modules;
|
|
573
|
+
}
|
|
574
|
+
if (isContentFile(scannerState, ctx.file)) {
|
|
575
|
+
try {
|
|
576
|
+
if (await scanFileAndRegister(state, scannerState, ctx.file)) {
|
|
577
|
+
const cssModule = await getModuleById(RESOLVED_VIRTUAL_CSS_MODULE_ID);
|
|
578
|
+
if (cssModule) return [cssModule, ...ctx.modules];
|
|
579
|
+
}
|
|
580
|
+
} catch (error) {
|
|
581
|
+
consola.error(`[styleframe] Failed to scan content file: ${ctx.file}`, error);
|
|
582
|
+
}
|
|
583
|
+
return ctx.modules;
|
|
584
|
+
}
|
|
585
|
+
return ctx.modules;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
};
|
|
590
|
+
const unplugin = /* @__PURE__ */ createUnplugin(unpluginFactory);
|
|
591
|
+
var plugin_default = unplugin;
|
|
592
|
+
|
|
593
|
+
//#endregion
|
|
594
|
+
export { plugin_default, unplugin, unpluginFactory };
|
|
595
|
+
//# sourceMappingURL=plugin-WmJKFcn_.js.map
|