@plumeria/vite-plugin 0.15.7 → 0.16.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/dist/create.d.ts +5 -0
- package/dist/create.js +99 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +646 -2
- package/dist/types.d.ts +14 -0
- package/dist/types.js +1 -0
- package/package.json +12 -3
- package/zero-virtual.css +1 -0
package/dist/create.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CSSProperties, CreateStyleType, CreateTheme, CreateValues } from 'zss-engine';
|
|
2
|
+
declare function createCSS<T extends Record<string, CSSProperties>>(object: CreateStyleType<T>): string;
|
|
3
|
+
declare const createVars: <const T extends CreateValues>(object: T) => Record<string, CreateValues>;
|
|
4
|
+
declare const createTheme: <const T extends CreateTheme>(object: T) => Record<string, Record<string, string | number | object>>;
|
|
5
|
+
export { createCSS, createVars, createTheme };
|
package/dist/create.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { splitAtomicAndNested, processAtomicProps, genBase36Hash, transpile, } from 'zss-engine';
|
|
2
|
+
function compileToSingleCSS(object) {
|
|
3
|
+
const baseSheets = [];
|
|
4
|
+
const querySheets = [];
|
|
5
|
+
const processedHashes = new Set();
|
|
6
|
+
Object.entries(object).forEach(([key, styleObj]) => {
|
|
7
|
+
const flat = {};
|
|
8
|
+
const nonFlat = {};
|
|
9
|
+
splitAtomicAndNested(styleObj, flat, nonFlat);
|
|
10
|
+
const records = [];
|
|
11
|
+
Object.entries(flat).forEach(([prop, value]) => {
|
|
12
|
+
const hashes = new Set();
|
|
13
|
+
const sheets = new Set();
|
|
14
|
+
processAtomicProps({ [prop]: value }, hashes, sheets);
|
|
15
|
+
const propBaseSheets = [];
|
|
16
|
+
const propQuerySheets = [];
|
|
17
|
+
for (const sheet of sheets) {
|
|
18
|
+
if (sheet.includes('@media') || sheet.includes('@container')) {
|
|
19
|
+
propQuerySheets.push(sheet);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
propBaseSheets.push(sheet);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const hash = [...hashes].join(' ');
|
|
26
|
+
const sheet = [...propBaseSheets, ...propQuerySheets].join('');
|
|
27
|
+
records.push({
|
|
28
|
+
key: prop,
|
|
29
|
+
hash,
|
|
30
|
+
sheet,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
if (Object.keys(nonFlat).length > 0) {
|
|
34
|
+
const nonFlatObj = { [key]: nonFlat };
|
|
35
|
+
const nonFlatHash = genBase36Hash(nonFlatObj, 1, 7);
|
|
36
|
+
const { styleSheet } = transpile(nonFlatObj, nonFlatHash);
|
|
37
|
+
Object.entries(nonFlat).forEach(([atRule, nestedObj]) => {
|
|
38
|
+
Object.entries(nestedObj).forEach(([prop]) => {
|
|
39
|
+
records.push({
|
|
40
|
+
key: atRule + prop,
|
|
41
|
+
hash: nonFlatHash,
|
|
42
|
+
sheet: styleSheet,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
records.forEach(({ hash, sheet }) => {
|
|
48
|
+
if (!processedHashes.has(hash)) {
|
|
49
|
+
processedHashes.add(hash);
|
|
50
|
+
if (sheet.includes('@media') || sheet.includes('@container')) {
|
|
51
|
+
querySheets.push(sheet);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
baseSheets.push(sheet);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
return [...baseSheets, ...querySheets].join('\n');
|
|
60
|
+
}
|
|
61
|
+
function createCSS(object) {
|
|
62
|
+
const result = {};
|
|
63
|
+
Object.entries(object).forEach(([key, styleObj]) => {
|
|
64
|
+
Object.defineProperty(result, key, {
|
|
65
|
+
get: () => Object.freeze(styleObj),
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
const compiledCSS = compileToSingleCSS(object);
|
|
69
|
+
return compiledCSS;
|
|
70
|
+
}
|
|
71
|
+
const createVars = (object) => {
|
|
72
|
+
const styles = {
|
|
73
|
+
':root': {},
|
|
74
|
+
};
|
|
75
|
+
Object.entries(object).forEach(([key, value]) => {
|
|
76
|
+
styles[':root'][`--${key}`] = value;
|
|
77
|
+
});
|
|
78
|
+
styles;
|
|
79
|
+
return styles;
|
|
80
|
+
};
|
|
81
|
+
const createTheme = (object) => {
|
|
82
|
+
const styles = {};
|
|
83
|
+
Object.entries(object).forEach(([key, value]) => {
|
|
84
|
+
Object.entries(value).forEach(([subKey, subValue]) => {
|
|
85
|
+
if (subKey.startsWith('@media')) {
|
|
86
|
+
styles[':root'] ||= {};
|
|
87
|
+
styles[':root'][subKey] ||= {};
|
|
88
|
+
styles[':root'][subKey][`--${key}`] = subValue;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const themeSelector = subKey === 'default' ? ':root' : `:root[data-theme="${subKey}"]`;
|
|
92
|
+
styles[themeSelector] ||= {};
|
|
93
|
+
styles[themeSelector][`--${key}`] = subValue;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
return styles;
|
|
98
|
+
};
|
|
99
|
+
export { createCSS, createVars, createTheme };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
|
-
export
|
|
2
|
+
export declare function plumeria(): Plugin;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,474 @@
|
|
|
1
|
-
|
|
1
|
+
import { parseSync, traverse, transformFromAstSync, } from '@babel/core';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { createCSS, createTheme, createVars } from './create';
|
|
6
|
+
import { genBase36Hash, transpile, camelToKebabCase } from 'zss-engine';
|
|
7
|
+
import { globSync } from '@rust-gear/glob';
|
|
8
|
+
const PROJECT_ROOT = process.cwd().split('node_modules')[0];
|
|
9
|
+
const PATTERN_PATH = path.join(PROJECT_ROOT, '**/*.{js,jsx,ts,tsx}');
|
|
10
|
+
const GLOB_OPTIONS = {
|
|
11
|
+
exclude: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**'],
|
|
12
|
+
cwd: PROJECT_ROOT,
|
|
13
|
+
};
|
|
14
|
+
let constTable = {};
|
|
15
|
+
let variableTable = {};
|
|
16
|
+
let themeTable = {};
|
|
17
|
+
let keyframesHashTable = {};
|
|
18
|
+
let keyframesObjectTable = {};
|
|
19
|
+
let defineVarsObjectTable = {};
|
|
20
|
+
let defineThemeObjectTable = {};
|
|
21
|
+
function objectExpressionToObject(node, constTable, keyframesHashTable, variableTable, themeTable) {
|
|
22
|
+
const obj = {};
|
|
23
|
+
node.properties.forEach((prop) => {
|
|
24
|
+
if (!t.isObjectProperty(prop))
|
|
25
|
+
return;
|
|
26
|
+
const key = getPropertyKey(prop.key, constTable, variableTable);
|
|
27
|
+
if (!key)
|
|
28
|
+
return;
|
|
29
|
+
const val = prop.value;
|
|
30
|
+
if (t.isIdentifier(val) || t.isMemberExpression(val)) {
|
|
31
|
+
const resolvedKeyframe = resolveKeyframesTableMemberExpression(val, keyframesHashTable);
|
|
32
|
+
if (resolvedKeyframe !== undefined) {
|
|
33
|
+
obj[key] = resolvedKeyframe;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const resolvedVariable = resolveVariableTableMemberExpressionByNode(val, variableTable);
|
|
37
|
+
if (resolvedVariable !== undefined) {
|
|
38
|
+
obj[key] = resolvedVariable;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const resolvedTheme = resolveThemeTableMemberExpressionByNode(val, themeTable);
|
|
42
|
+
if (resolvedTheme !== undefined) {
|
|
43
|
+
obj[key] = resolvedTheme;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (t.isStringLiteral(val) ||
|
|
48
|
+
t.isNumericLiteral(val) ||
|
|
49
|
+
t.isBooleanLiteral(val)) {
|
|
50
|
+
obj[key] = val.value;
|
|
51
|
+
}
|
|
52
|
+
else if (t.isUnaryExpression(val)) {
|
|
53
|
+
obj[key] = evaluateUnaryExpression(val);
|
|
54
|
+
}
|
|
55
|
+
else if (t.isObjectExpression(val)) {
|
|
56
|
+
obj[key] = objectExpressionToObject(val, constTable, keyframesHashTable, variableTable, themeTable);
|
|
57
|
+
}
|
|
58
|
+
else if (t.isMemberExpression(val)) {
|
|
59
|
+
const resolved = resolveConstTableMemberExpression(val, constTable);
|
|
60
|
+
obj[key] = resolved !== undefined ? resolved : '[unresolved]';
|
|
61
|
+
}
|
|
62
|
+
else if (t.isIdentifier(val)) {
|
|
63
|
+
if (constTable[val.name] !== undefined) {
|
|
64
|
+
obj[key] = constTable[val.name];
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
obj[key] = '[unresolved identifier]';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
obj[key] = '[unsupported value type]';
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return obj;
|
|
75
|
+
}
|
|
76
|
+
function getPropertyKey(node, constTable, variableTable) {
|
|
77
|
+
if (t.isIdentifier(node)) {
|
|
78
|
+
if (constTable[node.name] && typeof constTable[node.name] === 'string') {
|
|
79
|
+
return constTable[node.name];
|
|
80
|
+
}
|
|
81
|
+
return node.name;
|
|
82
|
+
}
|
|
83
|
+
if (t.isStringLiteral(node))
|
|
84
|
+
return node.value;
|
|
85
|
+
if (t.isMemberExpression(node)) {
|
|
86
|
+
const result = resolveConstTableMemberExpression(node, constTable);
|
|
87
|
+
if (typeof result === 'string')
|
|
88
|
+
return result;
|
|
89
|
+
throw new Error(`Resolved key is not a string: ${JSON.stringify(result)}`);
|
|
90
|
+
}
|
|
91
|
+
if (t.isTemplateLiteral(node)) {
|
|
92
|
+
return evaluateTemplateLiteral(node, constTable, variableTable);
|
|
93
|
+
}
|
|
94
|
+
if (t.isBinaryExpression(node)) {
|
|
95
|
+
return evaluateBinaryExpression(node, constTable, variableTable);
|
|
96
|
+
}
|
|
97
|
+
throw new Error(`Unsupported property key type: ${node.type}`);
|
|
98
|
+
}
|
|
99
|
+
function evaluateTemplateLiteral(node, constTable, variableTable) {
|
|
100
|
+
let result = '';
|
|
101
|
+
for (let i = 0; i < node.quasis.length; i++) {
|
|
102
|
+
result += node.quasis[i].value.cooked || node.quasis[i].value.raw;
|
|
103
|
+
if (i < node.expressions.length) {
|
|
104
|
+
const expr = node.expressions[i];
|
|
105
|
+
const evaluatedExpr = evaluateExpression(expr, constTable, variableTable);
|
|
106
|
+
result += String(evaluatedExpr);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
function evaluateBinaryExpression(node, constTable, variableTable) {
|
|
112
|
+
const left = evaluateExpression(node.left, constTable, variableTable);
|
|
113
|
+
const right = evaluateExpression(node.right, constTable, variableTable);
|
|
114
|
+
if (node.operator === '+') {
|
|
115
|
+
return String(left) + String(right);
|
|
116
|
+
}
|
|
117
|
+
throw new Error(`Unsupported binary operator: ${node.operator}`);
|
|
118
|
+
}
|
|
119
|
+
function evaluateExpression(node, constTable, variableTable) {
|
|
120
|
+
if (t.isStringLiteral(node))
|
|
121
|
+
return node.value;
|
|
122
|
+
if (t.isNumericLiteral(node))
|
|
123
|
+
return node.value;
|
|
124
|
+
if (t.isBooleanLiteral(node))
|
|
125
|
+
return node.value;
|
|
126
|
+
if (t.isNullLiteral(node))
|
|
127
|
+
return null;
|
|
128
|
+
if (t.isIdentifier(node)) {
|
|
129
|
+
if (constTable[node.name] !== undefined)
|
|
130
|
+
return constTable[node.name];
|
|
131
|
+
if (keyframesHashTable[node.name] !== undefined)
|
|
132
|
+
return keyframesHashTable[node.name];
|
|
133
|
+
return `[unresolved: ${node.name}]`;
|
|
134
|
+
}
|
|
135
|
+
if (t.isMemberExpression(node)) {
|
|
136
|
+
const resolved = resolveConstTableMemberExpression(node, constTable);
|
|
137
|
+
if (resolved !== undefined)
|
|
138
|
+
return resolved;
|
|
139
|
+
const resolvedVar = resolveVariableTableMemberExpressionByNode(node, variableTable);
|
|
140
|
+
if (resolvedVar !== undefined)
|
|
141
|
+
return resolvedVar;
|
|
142
|
+
const resolvedTheme = resolveThemeTableMemberExpressionByNode(node, themeTable);
|
|
143
|
+
if (resolvedTheme !== undefined)
|
|
144
|
+
return resolvedTheme;
|
|
145
|
+
return `[unresolved member expression]`;
|
|
146
|
+
}
|
|
147
|
+
if (t.isBinaryExpression(node))
|
|
148
|
+
return evaluateBinaryExpression(node, constTable, variableTable);
|
|
149
|
+
if (t.isTemplateLiteral(node))
|
|
150
|
+
return evaluateTemplateLiteral(node, constTable, variableTable);
|
|
151
|
+
if (t.isUnaryExpression(node))
|
|
152
|
+
return evaluateUnaryExpression(node);
|
|
153
|
+
throw new Error(`Unsupported expression type: ${node.type}`);
|
|
154
|
+
}
|
|
155
|
+
function evaluateUnaryExpression(node) {
|
|
156
|
+
const arg = node.argument;
|
|
157
|
+
switch (node.operator) {
|
|
158
|
+
case '-':
|
|
159
|
+
if (t.isNumericLiteral(arg))
|
|
160
|
+
return -arg.value;
|
|
161
|
+
break;
|
|
162
|
+
case '+':
|
|
163
|
+
if (t.isNumericLiteral(arg))
|
|
164
|
+
return +arg.value;
|
|
165
|
+
break;
|
|
166
|
+
default:
|
|
167
|
+
throw new Error(`Unsupported unary operator: ${node.operator}`);
|
|
168
|
+
}
|
|
169
|
+
throw new Error(`Unsupported UnaryExpression argument type: ${arg.type}`);
|
|
170
|
+
}
|
|
171
|
+
function resolveKeyframesTableMemberExpression(node, keyframesHashTable) {
|
|
172
|
+
if (t.isIdentifier(node))
|
|
173
|
+
return keyframesHashTable[node.name];
|
|
174
|
+
if (t.isMemberExpression(node)) {
|
|
175
|
+
if (t.isIdentifier(node.object))
|
|
176
|
+
return keyframesHashTable[node.object.name];
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
function resolveConstTableMemberExpression(node, constTable) {
|
|
181
|
+
if (t.isIdentifier(node.object) && t.isIdentifier(node.property)) {
|
|
182
|
+
const varName = node.object.name;
|
|
183
|
+
const key = node.property.name;
|
|
184
|
+
const tableValue = constTable[varName];
|
|
185
|
+
if (typeof tableValue === 'string') {
|
|
186
|
+
return tableValue;
|
|
187
|
+
}
|
|
188
|
+
if (tableValue && typeof tableValue === 'object' && key in tableValue) {
|
|
189
|
+
return tableValue[key];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
function resolveVariableTableMemberExpressionByNode(node, variableTable, asObject = false) {
|
|
195
|
+
if (t.isIdentifier(node)) {
|
|
196
|
+
if (asObject && typeof variableTable[node.name] === 'object') {
|
|
197
|
+
return { ...variableTable[node.name] };
|
|
198
|
+
}
|
|
199
|
+
const cssVarName = camelToKebabCase(node.name);
|
|
200
|
+
return `var(--${cssVarName})`;
|
|
201
|
+
}
|
|
202
|
+
if (t.isMemberExpression(node) && t.isIdentifier(node.object)) {
|
|
203
|
+
const varName = node.object.name;
|
|
204
|
+
let key;
|
|
205
|
+
if (t.isIdentifier(node.property)) {
|
|
206
|
+
key = node.property.name;
|
|
207
|
+
}
|
|
208
|
+
else if (t.isStringLiteral(node.property)) {
|
|
209
|
+
key = node.property.value;
|
|
210
|
+
}
|
|
211
|
+
if (key &&
|
|
212
|
+
variableTable[varName] &&
|
|
213
|
+
variableTable[varName][key] !== undefined) {
|
|
214
|
+
if (asObject) {
|
|
215
|
+
return { [key]: variableTable[varName][key] };
|
|
216
|
+
}
|
|
217
|
+
const cssVarName = camelToKebabCase(key);
|
|
218
|
+
return `var(--${cssVarName})`;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
function resolveThemeTableMemberExpressionByNode(node, themeTable, asObject = false) {
|
|
224
|
+
if (t.isIdentifier(node)) {
|
|
225
|
+
if (asObject && typeof themeTable[node.name] === 'object') {
|
|
226
|
+
return { ...themeTable[node.name] };
|
|
227
|
+
}
|
|
228
|
+
const cssVarName = camelToKebabCase(node.name);
|
|
229
|
+
return `var(--${cssVarName})`;
|
|
230
|
+
}
|
|
231
|
+
if (t.isMemberExpression(node) && t.isIdentifier(node.object)) {
|
|
232
|
+
const varName = node.object.name;
|
|
233
|
+
let key;
|
|
234
|
+
if (t.isIdentifier(node.property)) {
|
|
235
|
+
key = node.property.name;
|
|
236
|
+
}
|
|
237
|
+
else if (t.isStringLiteral(node.property)) {
|
|
238
|
+
key = node.property.value;
|
|
239
|
+
}
|
|
240
|
+
if (key && themeTable[varName] && themeTable[varName][key] !== undefined) {
|
|
241
|
+
if (asObject) {
|
|
242
|
+
return { [key]: themeTable[varName][key] };
|
|
243
|
+
}
|
|
244
|
+
const cssVarName = camelToKebabCase(key);
|
|
245
|
+
return `var(--${cssVarName})`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
function collectLocalConsts(ast) {
|
|
251
|
+
const localConsts = {};
|
|
252
|
+
traverse(ast, {
|
|
253
|
+
VariableDeclarator(path) {
|
|
254
|
+
const { node } = path;
|
|
255
|
+
if (t.isIdentifier(node.id) &&
|
|
256
|
+
node.init &&
|
|
257
|
+
t.isStringLiteral(node.init)) {
|
|
258
|
+
localConsts[node.id.name] = node.init.value;
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
return localConsts;
|
|
263
|
+
}
|
|
264
|
+
function isCSSDefineFile(filePath, target) {
|
|
265
|
+
if (fs.statSync(filePath).isDirectory())
|
|
266
|
+
return false;
|
|
267
|
+
const code = fs.readFileSync(filePath, 'utf8');
|
|
268
|
+
let ast;
|
|
269
|
+
try {
|
|
270
|
+
ast = parseSync(code, {
|
|
271
|
+
sourceType: 'module',
|
|
272
|
+
presets: [
|
|
273
|
+
['@babel/preset-typescript', { isTSX: true, allExtensions: true }],
|
|
274
|
+
],
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
console.error(err);
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
if (!ast)
|
|
282
|
+
return false;
|
|
283
|
+
let found = false;
|
|
284
|
+
traverse(ast, {
|
|
285
|
+
CallExpression(path) {
|
|
286
|
+
const callee = path.node.callee;
|
|
287
|
+
if (callee.type === 'MemberExpression' &&
|
|
288
|
+
callee.object.type === 'Identifier' &&
|
|
289
|
+
callee.object.name === 'css' &&
|
|
290
|
+
callee.property.type === 'Identifier' &&
|
|
291
|
+
callee.property.name === target) {
|
|
292
|
+
found = true;
|
|
293
|
+
path.stop();
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
return found;
|
|
298
|
+
}
|
|
299
|
+
function scanForDefineConsts() {
|
|
300
|
+
const constTableLocal = {};
|
|
301
|
+
const files = globSync(PATTERN_PATH, GLOB_OPTIONS);
|
|
302
|
+
for (const filePath of files) {
|
|
303
|
+
if (!isCSSDefineFile(filePath, 'defineConsts'))
|
|
304
|
+
continue;
|
|
305
|
+
const source = fs.readFileSync(filePath, 'utf8');
|
|
306
|
+
const ast = parseSync(source, {
|
|
307
|
+
sourceType: 'module',
|
|
308
|
+
presets: [
|
|
309
|
+
['@babel/preset-typescript', { isTSX: true, allExtensions: true }],
|
|
310
|
+
],
|
|
311
|
+
});
|
|
312
|
+
if (!ast)
|
|
313
|
+
continue;
|
|
314
|
+
for (const node of ast.program.body) {
|
|
315
|
+
const declarations = t.isVariableDeclaration(node)
|
|
316
|
+
? node.declarations
|
|
317
|
+
: t.isExportNamedDeclaration(node) &&
|
|
318
|
+
node.declaration &&
|
|
319
|
+
t.isVariableDeclaration(node.declaration)
|
|
320
|
+
? node.declaration.declarations
|
|
321
|
+
: [];
|
|
322
|
+
for (const decl of declarations) {
|
|
323
|
+
if (t.isVariableDeclarator(decl) &&
|
|
324
|
+
t.isIdentifier(decl.id) &&
|
|
325
|
+
t.isCallExpression(decl.init) &&
|
|
326
|
+
t.isMemberExpression(decl.init.callee) &&
|
|
327
|
+
t.isIdentifier(decl.init.callee.object, { name: 'css' }) &&
|
|
328
|
+
t.isIdentifier(decl.init.callee.property, { name: 'defineConsts' }) &&
|
|
329
|
+
t.isObjectExpression(decl.init.arguments[0])) {
|
|
330
|
+
const varName = decl.id.name;
|
|
331
|
+
const obj = objectExpressionToObject(decl.init.arguments[0], constTableLocal, keyframesHashTable, variableTable, themeTable);
|
|
332
|
+
constTableLocal[varName] = obj;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return constTableLocal;
|
|
338
|
+
}
|
|
339
|
+
function scanForKeyframes() {
|
|
340
|
+
const keyframesHashTableLocal = {};
|
|
341
|
+
const keyframesObjectTableLocal = {};
|
|
342
|
+
const files = globSync(PATTERN_PATH, GLOB_OPTIONS);
|
|
343
|
+
for (const filePath of files) {
|
|
344
|
+
if (!isCSSDefineFile(filePath, 'keyframes'))
|
|
345
|
+
continue;
|
|
346
|
+
const source = fs.readFileSync(filePath, 'utf8');
|
|
347
|
+
const ast = parseSync(source, {
|
|
348
|
+
sourceType: 'module',
|
|
349
|
+
presets: [
|
|
350
|
+
['@babel/preset-typescript', { isTSX: true, allExtensions: true }],
|
|
351
|
+
],
|
|
352
|
+
});
|
|
353
|
+
if (!ast)
|
|
354
|
+
continue;
|
|
355
|
+
for (const node of ast.program.body) {
|
|
356
|
+
const declarations = t.isVariableDeclaration(node)
|
|
357
|
+
? node.declarations
|
|
358
|
+
: t.isExportNamedDeclaration(node) &&
|
|
359
|
+
node.declaration &&
|
|
360
|
+
t.isVariableDeclaration(node.declaration)
|
|
361
|
+
? node.declaration.declarations
|
|
362
|
+
: [];
|
|
363
|
+
for (const decl of declarations) {
|
|
364
|
+
if (t.isVariableDeclarator(decl) &&
|
|
365
|
+
t.isIdentifier(decl.id) &&
|
|
366
|
+
t.isCallExpression(decl.init) &&
|
|
367
|
+
t.isMemberExpression(decl.init.callee) &&
|
|
368
|
+
t.isIdentifier(decl.init.callee.object, { name: 'css' }) &&
|
|
369
|
+
t.isIdentifier(decl.init.callee.property, { name: 'keyframes' }) &&
|
|
370
|
+
t.isObjectExpression(decl.init.arguments[0])) {
|
|
371
|
+
const varName = decl.id.name;
|
|
372
|
+
const obj = objectExpressionToObject(decl.init.arguments[0], constTable, keyframesHashTableLocal, variableTable, themeTable);
|
|
373
|
+
const hash = genBase36Hash(obj, 1, 8);
|
|
374
|
+
keyframesHashTableLocal[varName] = hash;
|
|
375
|
+
keyframesObjectTableLocal[hash] = obj;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return { keyframesHashTableLocal, keyframesObjectTableLocal };
|
|
381
|
+
}
|
|
382
|
+
function scanForDefineVars() {
|
|
383
|
+
const variableTableLocal = {};
|
|
384
|
+
const defineVarsObjectTableLocal = {};
|
|
385
|
+
const files = globSync(PATTERN_PATH, GLOB_OPTIONS);
|
|
386
|
+
for (const filePath of files) {
|
|
387
|
+
if (!isCSSDefineFile(filePath, 'defineVars'))
|
|
388
|
+
continue;
|
|
389
|
+
const source = fs.readFileSync(filePath, 'utf8');
|
|
390
|
+
const ast = parseSync(source, {
|
|
391
|
+
sourceType: 'module',
|
|
392
|
+
presets: [
|
|
393
|
+
['@babel/preset-typescript', { isTSX: true, allExtensions: true }],
|
|
394
|
+
],
|
|
395
|
+
});
|
|
396
|
+
if (!ast)
|
|
397
|
+
continue;
|
|
398
|
+
for (const node of ast.program.body) {
|
|
399
|
+
const declarations = t.isVariableDeclaration(node)
|
|
400
|
+
? node.declarations
|
|
401
|
+
: t.isExportNamedDeclaration(node) &&
|
|
402
|
+
node.declaration &&
|
|
403
|
+
t.isVariableDeclaration(node.declaration)
|
|
404
|
+
? node.declaration.declarations
|
|
405
|
+
: [];
|
|
406
|
+
for (const decl of declarations) {
|
|
407
|
+
if (t.isVariableDeclarator(decl) &&
|
|
408
|
+
t.isIdentifier(decl.id) &&
|
|
409
|
+
t.isCallExpression(decl.init) &&
|
|
410
|
+
t.isMemberExpression(decl.init.callee) &&
|
|
411
|
+
t.isIdentifier(decl.init.callee.object, { name: 'css' }) &&
|
|
412
|
+
t.isIdentifier(decl.init.callee.property, { name: 'defineVars' }) &&
|
|
413
|
+
t.isObjectExpression(decl.init.arguments[0])) {
|
|
414
|
+
const varName = decl.id.name;
|
|
415
|
+
const obj = objectExpressionToObject(decl.init.arguments[0], constTable, keyframesHashTable, variableTableLocal, themeTable);
|
|
416
|
+
variableTableLocal[varName] = obj;
|
|
417
|
+
defineVarsObjectTableLocal[varName] = obj;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return { variableTableLocal, defineVarsObjectTableLocal };
|
|
423
|
+
}
|
|
424
|
+
function scanForDefineTheme() {
|
|
425
|
+
const themeTableLocal = {};
|
|
426
|
+
const defineThemeObjectTableLocal = {};
|
|
427
|
+
const files = globSync(PATTERN_PATH, GLOB_OPTIONS);
|
|
428
|
+
for (const filePath of files) {
|
|
429
|
+
if (!isCSSDefineFile(filePath, 'defineTheme'))
|
|
430
|
+
continue;
|
|
431
|
+
const source = fs.readFileSync(filePath, 'utf8');
|
|
432
|
+
const ast = parseSync(source, {
|
|
433
|
+
sourceType: 'module',
|
|
434
|
+
presets: [
|
|
435
|
+
['@babel/preset-typescript', { isTSX: true, allExtensions: true }],
|
|
436
|
+
],
|
|
437
|
+
});
|
|
438
|
+
if (!ast)
|
|
439
|
+
continue;
|
|
440
|
+
for (const node of ast.program.body) {
|
|
441
|
+
const declarations = t.isVariableDeclaration(node)
|
|
442
|
+
? node.declarations
|
|
443
|
+
: t.isExportNamedDeclaration(node) &&
|
|
444
|
+
node.declaration &&
|
|
445
|
+
t.isVariableDeclaration(node.declaration)
|
|
446
|
+
? node.declaration.declarations
|
|
447
|
+
: [];
|
|
448
|
+
for (const decl of declarations) {
|
|
449
|
+
if (t.isVariableDeclarator(decl) &&
|
|
450
|
+
t.isIdentifier(decl.id) &&
|
|
451
|
+
t.isCallExpression(decl.init) &&
|
|
452
|
+
t.isMemberExpression(decl.init.callee) &&
|
|
453
|
+
t.isIdentifier(decl.init.callee.object, { name: 'css' }) &&
|
|
454
|
+
t.isIdentifier(decl.init.callee.property, { name: 'defineTheme' }) &&
|
|
455
|
+
t.isObjectExpression(decl.init.arguments[0])) {
|
|
456
|
+
const varName = decl.id.name;
|
|
457
|
+
const obj = objectExpressionToObject(decl.init.arguments[0], constTable, keyframesHashTable, variableTable, themeTableLocal);
|
|
458
|
+
themeTableLocal[varName] = obj;
|
|
459
|
+
defineThemeObjectTableLocal[varName] = obj;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return { themeTableLocal, defineThemeObjectTableLocal };
|
|
465
|
+
}
|
|
466
|
+
export function plumeria() {
|
|
467
|
+
const stylesByFile = new Map();
|
|
468
|
+
let hasScanned = false;
|
|
469
|
+
const outputPath = path.join(import.meta.dirname, '..', 'zero-virtual.css');
|
|
2
470
|
return {
|
|
3
|
-
name: '
|
|
471
|
+
name: 'plumeria-vite-plugin',
|
|
4
472
|
config: ({ build = {} }) => ({
|
|
5
473
|
build: {
|
|
6
474
|
...build,
|
|
@@ -13,5 +481,181 @@ export default function plumeria() {
|
|
|
13
481
|
},
|
|
14
482
|
},
|
|
15
483
|
}),
|
|
484
|
+
buildStart() {
|
|
485
|
+
if (hasScanned)
|
|
486
|
+
return;
|
|
487
|
+
constTable = {};
|
|
488
|
+
variableTable = {};
|
|
489
|
+
themeTable = {};
|
|
490
|
+
keyframesHashTable = {};
|
|
491
|
+
keyframesObjectTable = {};
|
|
492
|
+
defineVarsObjectTable = {};
|
|
493
|
+
defineThemeObjectTable = {};
|
|
494
|
+
constTable = scanForDefineConsts();
|
|
495
|
+
const keyframesResult = scanForKeyframes();
|
|
496
|
+
keyframesHashTable = keyframesResult.keyframesHashTableLocal;
|
|
497
|
+
keyframesObjectTable = keyframesResult.keyframesObjectTableLocal;
|
|
498
|
+
const varsResult = scanForDefineVars();
|
|
499
|
+
variableTable = varsResult.variableTableLocal;
|
|
500
|
+
defineVarsObjectTable = varsResult.defineVarsObjectTableLocal;
|
|
501
|
+
const themeResult = scanForDefineTheme();
|
|
502
|
+
themeTable = themeResult.themeTableLocal;
|
|
503
|
+
defineThemeObjectTable = themeResult.defineThemeObjectTableLocal;
|
|
504
|
+
hasScanned = true;
|
|
505
|
+
},
|
|
506
|
+
async transform(source, id) {
|
|
507
|
+
if (id.includes('node_modules') ||
|
|
508
|
+
!/\.(js|jsx|ts|tsx|vue|svelte|astro)$/.test(id)) {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
try {
|
|
512
|
+
let ast = null;
|
|
513
|
+
try {
|
|
514
|
+
ast = parseSync(source, {
|
|
515
|
+
sourceType: 'module',
|
|
516
|
+
presets: [
|
|
517
|
+
[
|
|
518
|
+
'@babel/preset-typescript',
|
|
519
|
+
{ isTSX: true, allExtensions: true },
|
|
520
|
+
],
|
|
521
|
+
],
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
catch (err) {
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
if (!ast)
|
|
528
|
+
return null;
|
|
529
|
+
const localConsts = collectLocalConsts(ast);
|
|
530
|
+
Object.assign(constTable, localConsts);
|
|
531
|
+
let extractedObject = null;
|
|
532
|
+
const extractedGlobalObjects = [];
|
|
533
|
+
const pluginAst = {
|
|
534
|
+
visitor: {
|
|
535
|
+
CallExpression(path) {
|
|
536
|
+
const callee = path.node.callee;
|
|
537
|
+
if (t.isMemberExpression(callee) &&
|
|
538
|
+
t.isIdentifier(callee.object, { name: 'css' }) &&
|
|
539
|
+
t.isIdentifier(callee.property)) {
|
|
540
|
+
const args = path.node.arguments;
|
|
541
|
+
if (callee.property.name === 'create' &&
|
|
542
|
+
args.length === 1 &&
|
|
543
|
+
t.isObjectExpression(args[0])) {
|
|
544
|
+
extractedObject = objectExpressionToObject(args[0], constTable, keyframesHashTable, variableTable, themeTable);
|
|
545
|
+
}
|
|
546
|
+
if (callee.property.name === 'global' &&
|
|
547
|
+
args.length === 1 &&
|
|
548
|
+
t.isObjectExpression(args[0])) {
|
|
549
|
+
extractedGlobalObjects.push(objectExpressionToObject(args[0], constTable, keyframesHashTable, variableTable, themeTable));
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
};
|
|
555
|
+
transformFromAstSync(ast, source, {
|
|
556
|
+
code: false,
|
|
557
|
+
plugins: [pluginAst],
|
|
558
|
+
configFile: false,
|
|
559
|
+
});
|
|
560
|
+
const fileStyles = {};
|
|
561
|
+
let hasCSSDefinitions = false;
|
|
562
|
+
if (extractedObject) {
|
|
563
|
+
const base = createCSS(extractedObject);
|
|
564
|
+
if (base) {
|
|
565
|
+
fileStyles.baseStyles = base;
|
|
566
|
+
hasCSSDefinitions = true;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (extractedGlobalObjects.length > 0) {
|
|
570
|
+
fileStyles.globalStyles = extractedGlobalObjects
|
|
571
|
+
.map((obj) => transpile(obj, undefined, '--global').styleSheet)
|
|
572
|
+
.join('\n');
|
|
573
|
+
hasCSSDefinitions = true;
|
|
574
|
+
}
|
|
575
|
+
if (Object.keys(keyframesObjectTable).length > 0) {
|
|
576
|
+
fileStyles.keyframeStyles = Object.entries(keyframesObjectTable)
|
|
577
|
+
.map(([hash, obj]) => transpile({ [`@keyframes ${hash}`]: obj }, undefined, '--global').styleSheet)
|
|
578
|
+
.join('\n');
|
|
579
|
+
hasCSSDefinitions = true;
|
|
580
|
+
}
|
|
581
|
+
if (Object.keys(defineVarsObjectTable).length > 0) {
|
|
582
|
+
fileStyles.varStyles = Object.values(defineVarsObjectTable)
|
|
583
|
+
.map((obj) => transpile(createVars(obj), undefined, '--global')
|
|
584
|
+
.styleSheet)
|
|
585
|
+
.join('\n');
|
|
586
|
+
hasCSSDefinitions = true;
|
|
587
|
+
}
|
|
588
|
+
if (Object.keys(defineThemeObjectTable).length > 0) {
|
|
589
|
+
fileStyles.themeStyles = Object.values(defineThemeObjectTable)
|
|
590
|
+
.map((obj) => transpile(createTheme(obj), undefined, '--global')
|
|
591
|
+
.styleSheet)
|
|
592
|
+
.join('\n');
|
|
593
|
+
hasCSSDefinitions = true;
|
|
594
|
+
}
|
|
595
|
+
if (Object.keys(fileStyles).length > 0) {
|
|
596
|
+
stylesByFile.set(id, {
|
|
597
|
+
...fileStyles,
|
|
598
|
+
lastUpdated: Date.now(),
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
if (hasCSSDefinitions) {
|
|
602
|
+
const allStyles = Array.from(stylesByFile.values());
|
|
603
|
+
const keyframeStylesSet = new Set();
|
|
604
|
+
const varStylesSet = new Set();
|
|
605
|
+
const themeStylesSet = new Set();
|
|
606
|
+
const globalStylesSet = new Set();
|
|
607
|
+
const baseStylesSet = new Set();
|
|
608
|
+
for (const style of allStyles) {
|
|
609
|
+
if (style.keyframeStyles)
|
|
610
|
+
keyframeStylesSet.add(style.keyframeStyles);
|
|
611
|
+
if (style.varStyles)
|
|
612
|
+
varStylesSet.add(style.varStyles);
|
|
613
|
+
if (style.themeStyles)
|
|
614
|
+
themeStylesSet.add(style.themeStyles);
|
|
615
|
+
if (style.globalStyles)
|
|
616
|
+
globalStylesSet.add(style.globalStyles);
|
|
617
|
+
if (style.baseStyles)
|
|
618
|
+
baseStylesSet.add(style.baseStyles);
|
|
619
|
+
}
|
|
620
|
+
const css = [
|
|
621
|
+
...Array.from(keyframeStylesSet),
|
|
622
|
+
...Array.from(varStylesSet),
|
|
623
|
+
...Array.from(themeStylesSet),
|
|
624
|
+
...Array.from(globalStylesSet),
|
|
625
|
+
...Array.from(baseStylesSet),
|
|
626
|
+
]
|
|
627
|
+
.filter(Boolean)
|
|
628
|
+
.join('\n');
|
|
629
|
+
fs.writeFileSync(outputPath, css);
|
|
630
|
+
return {
|
|
631
|
+
code: `${source}\nimport '${path.relative(path.dirname(id), outputPath)}';`,
|
|
632
|
+
map: null,
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
catch (error) {
|
|
637
|
+
console.error(`Error processing ${id}:`, error);
|
|
638
|
+
}
|
|
639
|
+
return null;
|
|
640
|
+
},
|
|
641
|
+
configureServer(server) {
|
|
642
|
+
server.watcher.on('change', (filePath) => {
|
|
643
|
+
if (filePath.endsWith('.ts') ||
|
|
644
|
+
filePath.endsWith('.tsx') ||
|
|
645
|
+
filePath.endsWith('.js') ||
|
|
646
|
+
filePath.endsWith('.jsx')) {
|
|
647
|
+
if (isCSSDefineFile(filePath, 'defineConsts') ||
|
|
648
|
+
isCSSDefineFile(filePath, 'keyframes') ||
|
|
649
|
+
isCSSDefineFile(filePath, 'defineVars') ||
|
|
650
|
+
isCSSDefineFile(filePath, 'defineTheme')) {
|
|
651
|
+
hasScanned = false;
|
|
652
|
+
console.log('🔄 CSS definition changed, rescanning...');
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
server.watcher.on('unlink', (filePath) => {
|
|
657
|
+
stylesByFile.delete(filePath);
|
|
658
|
+
});
|
|
659
|
+
},
|
|
16
660
|
};
|
|
17
661
|
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type CSSPrimitive = string | number | boolean | null;
|
|
2
|
+
type ParseErrorString = `[unresolved]` | `[unresolved identifier]` | `[unsupported value type]` | `[unresolved: ${string}]` | `[unresolved member expression]`;
|
|
3
|
+
export type CSSValue = CSSPrimitive | CSSObject | ParseErrorString;
|
|
4
|
+
export type CSSObject = {
|
|
5
|
+
[key: string]: CSSValue;
|
|
6
|
+
};
|
|
7
|
+
export type ConstTable = Record<string, CSSObject | string>;
|
|
8
|
+
export type VariableTable = Record<string, CSSObject>;
|
|
9
|
+
export type ThemeTable = Record<string, CSSObject>;
|
|
10
|
+
export type KeyframesHashTable = Record<string, string>;
|
|
11
|
+
export type KeyframesObjectTable = Record<string, CSSObject>;
|
|
12
|
+
export type DefineVarsObjectTable = Record<string, CSSObject>;
|
|
13
|
+
export type DefineThemeObjectTable = Record<string, CSSObject>;
|
|
14
|
+
export {};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumeria/vite-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Plumeria Vite plugin",
|
|
6
6
|
"repository": {
|
|
@@ -12,10 +12,19 @@
|
|
|
12
12
|
"types": "dist/index.d.ts",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"files": [
|
|
15
|
-
"dist/"
|
|
15
|
+
"dist/",
|
|
16
|
+
"zero-virtual.css"
|
|
16
17
|
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@babel/core": "^7.28.0",
|
|
20
|
+
"@babel/preset-typescript": "^7.27.1",
|
|
21
|
+
"@babel/types": "^7.28.2"
|
|
22
|
+
},
|
|
17
23
|
"devDependencies": {
|
|
18
|
-
"vite": "7.1.1"
|
|
24
|
+
"vite": "7.1.1",
|
|
25
|
+
"@rust-gear/glob": "^0.2.2",
|
|
26
|
+
"@types/babel__core": "^7.20.5",
|
|
27
|
+
"zss-engine": "^0.2.80"
|
|
19
28
|
},
|
|
20
29
|
"publishConfig": {
|
|
21
30
|
"access": "public"
|
package/zero-virtual.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/** Placeholder file */
|