@griffel/transform 1.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/CHANGELOG.md +14 -0
- package/README.md +78 -0
- package/evaluation/astEvaluator.d.mts +12 -0
- package/evaluation/batchEvaluator.d.mts +11 -0
- package/evaluation/types.d.mts +5 -0
- package/evaluation/vmEvaluator.d.mts +3 -0
- package/index.d.mts +4 -0
- package/package.json +26 -0
- package/transform.js +428 -0
- package/transformSync.d.mts +30 -0
- package/types.d.mts +12 -0
- package/utils/dedupeCSSRules.d.mts +6 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Change Log - @griffel/transform
|
|
2
|
+
|
|
3
|
+
This log was last generated on Mon, 03 Nov 2025 15:42:47 GMT and should not be manually modified.
|
|
4
|
+
|
|
5
|
+
<!-- Start content -->
|
|
6
|
+
|
|
7
|
+
## 1.1.0
|
|
8
|
+
|
|
9
|
+
Mon, 03 Nov 2025 15:42:47 GMT
|
|
10
|
+
|
|
11
|
+
### Minor changes
|
|
12
|
+
|
|
13
|
+
- Add initial @griffel/transform package boilerplate (copilot@github.com)
|
|
14
|
+
- feat: add functionality (copilot@github.com)
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# @griffel/transform
|
|
2
|
+
|
|
3
|
+
A high-performance transformation package for Griffel that unifies CSS-in-JS transformation and extraction functionality.
|
|
4
|
+
|
|
5
|
+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
|
6
|
+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
7
|
+
|
|
8
|
+
- [Overview](#overview)
|
|
9
|
+
- [Features](#features)
|
|
10
|
+
- [Install](#install)
|
|
11
|
+
- [Usage](#usage)
|
|
12
|
+
- [Basic Transformation](#basic-transformation)
|
|
13
|
+
- [API Reference](#api-reference)
|
|
14
|
+
- [transformSync(sourceCode, options)](#transformsyncsourcecode-options)
|
|
15
|
+
|
|
16
|
+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
`@griffel/transform` provides a unified approach to CSS-in-JS transformation and extraction for Griffel. It replaces Babel-based processing with modern OXC-based parsing for improved performance while maintaining full compatibility with existing Griffel APIs.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
yarn add --dev @griffel/transform
|
|
26
|
+
# or
|
|
27
|
+
npm install --save-dev @griffel/transform
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### Basic Transformation
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { transformSync } from '@griffel/transform';
|
|
36
|
+
|
|
37
|
+
const sourceCode = `
|
|
38
|
+
import { makeStyles } from '@griffel/react';
|
|
39
|
+
|
|
40
|
+
const useStyles = makeStyles({
|
|
41
|
+
root: { color: 'red' }
|
|
42
|
+
});
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const result = transformSync(sourceCode, {
|
|
46
|
+
filename: 'styles.ts',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
console.log(result.code); // Transformed code with __css calls
|
|
50
|
+
console.log(result.cssRulesByBucket); // Extracted CSS rules
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## API Reference
|
|
54
|
+
|
|
55
|
+
### transformSync(sourceCode, options)
|
|
56
|
+
|
|
57
|
+
Transforms source code containing `makeStyles` or `makeResetStyles` calls.
|
|
58
|
+
|
|
59
|
+
**Parameters:**
|
|
60
|
+
|
|
61
|
+
- `sourceCode` (string): Source code to transform
|
|
62
|
+
- `options` (TransformOptions): Transformation options
|
|
63
|
+
|
|
64
|
+
**Returns:** `TransformResult` object containing:
|
|
65
|
+
|
|
66
|
+
- `code`: Transformed source code
|
|
67
|
+
- `cssRulesByBucket`: Extracted CSS rules organized by bucket type
|
|
68
|
+
- `usedProcessing`: Whether any transformations were applied
|
|
69
|
+
- `usedVMForEvaluation`: Whether VM evaluation was used
|
|
70
|
+
|
|
71
|
+
**TransformOptions:**
|
|
72
|
+
|
|
73
|
+
- `filename` (string): File path for error reporting and source maps
|
|
74
|
+
- `classNameHashSalt?` (string): Salt for CSS class name generation
|
|
75
|
+
- `generateMetadata?` (boolean): Include metadata in CSS rules
|
|
76
|
+
- `modules?` (string[]): Module sources to process
|
|
77
|
+
- `babelOptions?` (object): Babel configuration for complex evaluations
|
|
78
|
+
- `evaluationRules?` (array): Rules for determining evaluation strategy
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Node } from 'oxc-parser';
|
|
2
|
+
import { EvaluationResult } from './types.mjs';
|
|
3
|
+
/**
|
|
4
|
+
* Simple static evaluator for object expressions with nested objects.
|
|
5
|
+
* Based on Babel's evaluation approach but simplified for our specific use case.
|
|
6
|
+
*
|
|
7
|
+
* Handles:
|
|
8
|
+
* - Objects with nested objects: { root: { color: 'red', padding: 0 } }
|
|
9
|
+
* - String literals, numeric literals, boolean literals, null
|
|
10
|
+
* - Simple property access
|
|
11
|
+
*/
|
|
12
|
+
export declare function astEvaluator(node: Node): EvaluationResult;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StrictOptions } from '@linaria/babel-preset';
|
|
2
|
+
import { StyleCall } from '../types.mjs';
|
|
3
|
+
/**
|
|
4
|
+
* Batch evaluates all style calls in a file for better performance.
|
|
5
|
+
* Uses static evaluation first, then falls back to VM evaluation for complex expressions.
|
|
6
|
+
* Optimizes VM evaluation by sharing module loading and parsing overhead.
|
|
7
|
+
*/
|
|
8
|
+
export declare function batchEvaluator(sourceCode: string, filename: string, styleCalls: StyleCall[], babelOptions: NonNullable<StrictOptions['babelOptions']>, evaluationRules: NonNullable<StrictOptions['rules']>): {
|
|
9
|
+
usedVMForEvaluation: boolean;
|
|
10
|
+
evaluationResults: unknown[];
|
|
11
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { StrictOptions } from '@linaria/babel-preset';
|
|
2
|
+
import { EvaluationResult } from './types.mjs';
|
|
3
|
+
export declare function vmEvaluator(sourceCode: string, filename: string, expressionCode: string, babelOptions: NonNullable<StrictOptions['babelOptions']>, evaluationRules: NonNullable<StrictOptions['rules']>): EvaluationResult;
|
package/index.d.mts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { default as shakerEvaluator } from '@linaria/shaker';
|
|
2
|
+
export { EvalCache, Module } from '@linaria/babel-preset';
|
|
3
|
+
export type { Evaluator, EvalRule } from '@linaria/babel-preset';
|
|
4
|
+
export { transformSync, type TransformOptions, type TransformResult } from './transformSync.mjs';
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@griffel/transform",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "A package that performs build time transforms for Griffel",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/microsoft/griffel"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"node": "./transform.js",
|
|
14
|
+
"types": "./index.d.mts"
|
|
15
|
+
},
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@griffel/core": "^1.19.2",
|
|
20
|
+
"@linaria/babel-preset": "^3.0.0-beta.24",
|
|
21
|
+
"@linaria/shaker": "^3.0.0-beta.22",
|
|
22
|
+
"magic-string": "^0.30.19",
|
|
23
|
+
"oxc-parser": "^0.90.0",
|
|
24
|
+
"oxc-walker": "^0.5.2"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/transform.js
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import _shaker from "@linaria/shaker";
|
|
2
|
+
import { default as default2 } from "@linaria/shaker";
|
|
3
|
+
import { Module } from "@linaria/babel-preset";
|
|
4
|
+
import { EvalCache, Module as Module2 } from "@linaria/babel-preset";
|
|
5
|
+
import { parseSync } from "oxc-parser";
|
|
6
|
+
import { walk } from "oxc-walker";
|
|
7
|
+
import MagicString from "magic-string";
|
|
8
|
+
import { resolveResetStyleRules, resolveStyleRulesForSlots, normalizeCSSBucketEntry } from "@griffel/core";
|
|
9
|
+
class DeoptError extends Error {
|
|
10
|
+
}
|
|
11
|
+
function evaluateNode(node) {
|
|
12
|
+
switch (node.type) {
|
|
13
|
+
case "Literal":
|
|
14
|
+
return node.value;
|
|
15
|
+
case "ObjectExpression":
|
|
16
|
+
return evaluateObjectExpression(node);
|
|
17
|
+
case "TemplateLiteral":
|
|
18
|
+
return evaluateTemplateLiteral(node);
|
|
19
|
+
case "MemberExpression":
|
|
20
|
+
return evaluateMemberExpression(node);
|
|
21
|
+
default:
|
|
22
|
+
throw new DeoptError(`Unsupported node type: ${node.type}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function evaluateObjectExpression(node) {
|
|
26
|
+
const obj = {};
|
|
27
|
+
for (const prop of node.properties) {
|
|
28
|
+
if (prop.type !== "Property" || prop.kind !== "init") {
|
|
29
|
+
throw new DeoptError("Only standard properties are supported");
|
|
30
|
+
}
|
|
31
|
+
let key;
|
|
32
|
+
if (prop.computed) {
|
|
33
|
+
throw new DeoptError("Computed properties are not supported");
|
|
34
|
+
} else if (prop.key.type === "Identifier") {
|
|
35
|
+
key = prop.key.name;
|
|
36
|
+
} else if (prop.key.type === "Literal") {
|
|
37
|
+
const keyLiteral = prop.key;
|
|
38
|
+
if (typeof keyLiteral.value === "string" || typeof keyLiteral.value === "number") {
|
|
39
|
+
key = String(keyLiteral.value);
|
|
40
|
+
} else {
|
|
41
|
+
throw new DeoptError("Unsupported literal key type");
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
throw new DeoptError("Unsupported key type");
|
|
45
|
+
}
|
|
46
|
+
obj[key] = evaluateNode(prop.value);
|
|
47
|
+
}
|
|
48
|
+
return obj;
|
|
49
|
+
}
|
|
50
|
+
function evaluateTemplateLiteral(node) {
|
|
51
|
+
let result = "";
|
|
52
|
+
for (let i = 0; i < node.quasis.length; i++) {
|
|
53
|
+
result += node.quasis[i].value.cooked;
|
|
54
|
+
if (i < node.expressions.length) {
|
|
55
|
+
const expression = node.expressions[i];
|
|
56
|
+
if (expression.type === "MemberExpression" && expression.object.type === "Identifier" && expression.object.name === "tokens" && expression.property.type === "Identifier" && !expression.computed) {
|
|
57
|
+
result += `var(--${expression.property.name})`;
|
|
58
|
+
} else {
|
|
59
|
+
throw new DeoptError("Only tokens.propertyName expressions are supported in template literals");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
function evaluateMemberExpression(node) {
|
|
66
|
+
if (node.object.type === "Identifier" && node.object.name === "tokens" && node.property.type === "Identifier" && !node.computed) {
|
|
67
|
+
return `var(--${node.property.name})`;
|
|
68
|
+
} else {
|
|
69
|
+
throw new DeoptError("Only tokens.propertyName member expressions are supported");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function astEvaluator(node) {
|
|
73
|
+
try {
|
|
74
|
+
return {
|
|
75
|
+
confident: true,
|
|
76
|
+
value: evaluateNode(node)
|
|
77
|
+
};
|
|
78
|
+
} catch {
|
|
79
|
+
return {
|
|
80
|
+
confident: false,
|
|
81
|
+
value: void 0
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function isError(e) {
|
|
86
|
+
return Object.prototype.toString.call(e) === "[object Error]";
|
|
87
|
+
}
|
|
88
|
+
function vmEvaluator(sourceCode, filename, expressionCode, babelOptions, evaluationRules) {
|
|
89
|
+
const codeForEvaluation = `
|
|
90
|
+
${sourceCode}
|
|
91
|
+
|
|
92
|
+
const __mkPreval = (() => {
|
|
93
|
+
try {
|
|
94
|
+
return ([${expressionCode}]);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return e;
|
|
97
|
+
}
|
|
98
|
+
})();
|
|
99
|
+
|
|
100
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
101
|
+
module.exports = { __mkPreval };
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
try {
|
|
105
|
+
const options = {
|
|
106
|
+
displayName: false,
|
|
107
|
+
evaluate: true,
|
|
108
|
+
rules: evaluationRules,
|
|
109
|
+
babelOptions: {
|
|
110
|
+
...babelOptions,
|
|
111
|
+
configFile: false,
|
|
112
|
+
babelrc: false
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const mod = new Module(filename, options);
|
|
116
|
+
mod.evaluate(codeForEvaluation, ["__mkPreval"]);
|
|
117
|
+
const result = mod.exports.__mkPreval;
|
|
118
|
+
if (isError(result)) {
|
|
119
|
+
return { confident: false, error: result };
|
|
120
|
+
}
|
|
121
|
+
return { confident: true, value: result };
|
|
122
|
+
} catch (err) {
|
|
123
|
+
return { confident: false, error: err };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function batchEvaluator(sourceCode, filename, styleCalls, babelOptions, evaluationRules) {
|
|
127
|
+
const evaluationResults = new Array(styleCalls.length);
|
|
128
|
+
const argumentsCode = new Array(styleCalls.length).fill(null);
|
|
129
|
+
let vmEvaluationNeeded = false;
|
|
130
|
+
for (let i = 0; i < styleCalls.length; i++) {
|
|
131
|
+
const styleCall = styleCalls[i];
|
|
132
|
+
const staticResult = astEvaluator(styleCall.argumentNode);
|
|
133
|
+
if (staticResult.confident) {
|
|
134
|
+
evaluationResults[i] = staticResult.value;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
vmEvaluationNeeded = true;
|
|
138
|
+
argumentsCode[i] = styleCall.argumentCode;
|
|
139
|
+
}
|
|
140
|
+
if (!vmEvaluationNeeded) {
|
|
141
|
+
return {
|
|
142
|
+
usedVMForEvaluation: false,
|
|
143
|
+
evaluationResults
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const vmResult = vmEvaluator(sourceCode, filename, argumentsCode.join(","), babelOptions, evaluationRules);
|
|
147
|
+
if (!vmResult.confident) {
|
|
148
|
+
if (vmResult.error) {
|
|
149
|
+
throw vmResult.error;
|
|
150
|
+
} else {
|
|
151
|
+
throw new Error("Evaluation of a code fragment failed, this is a bug, please report it");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const vmValues = vmResult.value;
|
|
155
|
+
for (let i = 0; i < vmValues.length; i++) {
|
|
156
|
+
if (vmValues[i] === null) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
evaluationResults[i] = vmValues[i];
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
usedVMForEvaluation: true,
|
|
163
|
+
evaluationResults
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function dedupeCSSRules(cssRulesByBucket) {
|
|
167
|
+
return Object.fromEntries(
|
|
168
|
+
Object.entries(cssRulesByBucket).map(([styleBucketName, cssBucketEntries]) => {
|
|
169
|
+
if (styleBucketName === "m") {
|
|
170
|
+
return [
|
|
171
|
+
styleBucketName,
|
|
172
|
+
cssBucketEntries.filter(
|
|
173
|
+
(entryA, index, entries) => entries.findIndex((entryB) => entryA[0] === entryB[0]) === index
|
|
174
|
+
)
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
return [styleBucketName, [...new Set(cssBucketEntries)]];
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
const shakerEvaluator = _shaker.default || _shaker;
|
|
182
|
+
function buildCSSResetEntriesMetadata(cssResetEntries, cssRules, declaratorId) {
|
|
183
|
+
const cssRulesByBucket = Array.isArray(cssRules) ? { d: cssRules } : cssRules;
|
|
184
|
+
cssResetEntries[declaratorId] ??= [];
|
|
185
|
+
cssResetEntries[declaratorId] = Object.values(cssRulesByBucket).flatMap((bucketEntries) => {
|
|
186
|
+
return bucketEntries.map((bucketEntry) => {
|
|
187
|
+
if (Array.isArray(bucketEntry)) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`CSS rules in buckets for "makeResetStyles()" should not contain arrays, got: ${JSON.stringify(
|
|
190
|
+
bucketEntry
|
|
191
|
+
)})}`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return bucketEntry;
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function buildCSSEntriesMetadata(cssEntries, classnamesMapping, cssRulesByBucket, declaratorId) {
|
|
199
|
+
const classesBySlot = Object.fromEntries(
|
|
200
|
+
Object.entries(classnamesMapping).map(([slot, cssClassesMap]) => {
|
|
201
|
+
const uniqueClasses = /* @__PURE__ */ new Set();
|
|
202
|
+
Object.values(cssClassesMap).forEach((cssClasses) => {
|
|
203
|
+
if (typeof cssClasses === "string") {
|
|
204
|
+
uniqueClasses.add(cssClasses);
|
|
205
|
+
} else if (Array.isArray(cssClasses)) {
|
|
206
|
+
cssClasses.forEach((cssClass) => uniqueClasses.add(cssClass));
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
return [slot, Array.from(uniqueClasses)];
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
const cssRules = Object.values(cssRulesByBucket).flatMap((cssRulesByBucket2) => {
|
|
213
|
+
return cssRulesByBucket2.map((bucketEntry) => {
|
|
214
|
+
const [cssRule] = normalizeCSSBucketEntry(bucketEntry);
|
|
215
|
+
return cssRule;
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
cssEntries[declaratorId] = Object.fromEntries(
|
|
219
|
+
Object.entries(classesBySlot).map(([slot, cssClasses]) => {
|
|
220
|
+
return [
|
|
221
|
+
slot,
|
|
222
|
+
cssClasses.map((cssClass) => {
|
|
223
|
+
return cssRules.find((rule) => rule.includes(cssClass));
|
|
224
|
+
})
|
|
225
|
+
];
|
|
226
|
+
})
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
function concatCSSRulesByBucket(bucketA = {}, bucketB) {
|
|
230
|
+
Object.entries(bucketB).forEach(([cssBucketName, cssBucketEntries]) => {
|
|
231
|
+
bucketA[cssBucketName] = cssBucketEntries.concat(
|
|
232
|
+
bucketA[cssBucketName] || []
|
|
233
|
+
);
|
|
234
|
+
});
|
|
235
|
+
return bucketA;
|
|
236
|
+
}
|
|
237
|
+
function transformSync(sourceCode, options) {
|
|
238
|
+
const {
|
|
239
|
+
babelOptions = {},
|
|
240
|
+
filename,
|
|
241
|
+
classNameHashSalt = "",
|
|
242
|
+
generateMetadata = false,
|
|
243
|
+
modules = ["@griffel/core", "@griffel/react", "@fluentui/react-components"],
|
|
244
|
+
evaluationRules = [
|
|
245
|
+
{ action: shakerEvaluator },
|
|
246
|
+
{
|
|
247
|
+
test: /[/\\]node_modules[/\\]/,
|
|
248
|
+
action: "ignore"
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
} = options;
|
|
252
|
+
if (!filename) {
|
|
253
|
+
throw new Error('Transform error: "filename" option is required');
|
|
254
|
+
}
|
|
255
|
+
const parseResult = parseSync(filename, sourceCode);
|
|
256
|
+
parseResult.module.staticImports;
|
|
257
|
+
if (parseResult.errors.length > 0) {
|
|
258
|
+
throw new Error(`Failed to parse "${filename}": ${parseResult.errors.map((e) => e.message).join(", ")}`);
|
|
259
|
+
}
|
|
260
|
+
const magicString = new MagicString(sourceCode);
|
|
261
|
+
const programAst = parseResult.program;
|
|
262
|
+
const imports = [];
|
|
263
|
+
const styleCalls = [];
|
|
264
|
+
const cssEntries = {};
|
|
265
|
+
const cssResetEntries = {};
|
|
266
|
+
let cssRulesByBucket = {};
|
|
267
|
+
walk(programAst, {
|
|
268
|
+
enter(node, parent) {
|
|
269
|
+
if (node.type === "ImportDeclaration") {
|
|
270
|
+
const moduleSource = node.source.value;
|
|
271
|
+
if (modules.includes(moduleSource)) {
|
|
272
|
+
const specifiers = node.specifiers.reduce((acc, spec) => {
|
|
273
|
+
if (spec.type === "ImportSpecifier") {
|
|
274
|
+
const importedName = spec.imported;
|
|
275
|
+
if (importedName.type === "Identifier" && (importedName.name === "makeStyles" || importedName.name === "makeResetStyles")) {
|
|
276
|
+
acc.push({
|
|
277
|
+
imported: importedName.name,
|
|
278
|
+
local: spec.local.name,
|
|
279
|
+
start: spec.start,
|
|
280
|
+
end: spec.end
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return acc;
|
|
285
|
+
}, []);
|
|
286
|
+
if (specifiers.length > 0) {
|
|
287
|
+
imports.push({
|
|
288
|
+
source: moduleSource,
|
|
289
|
+
specifiers,
|
|
290
|
+
start: node.start,
|
|
291
|
+
end: node.end
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (node.type === "CallExpression") {
|
|
297
|
+
let functionKind = null;
|
|
298
|
+
let importId;
|
|
299
|
+
if (node.callee.type === "Identifier") {
|
|
300
|
+
const calleeName = node.callee.name;
|
|
301
|
+
for (const importInfo of imports) {
|
|
302
|
+
const specifier = importInfo.specifiers.find((s) => s.local === calleeName);
|
|
303
|
+
if (specifier) {
|
|
304
|
+
if (node.arguments.length !== 1) {
|
|
305
|
+
throw new Error(`${functionKind}() function accepts only a single param`);
|
|
306
|
+
}
|
|
307
|
+
functionKind = specifier.imported;
|
|
308
|
+
importId = specifier.local;
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (functionKind && importId) {
|
|
314
|
+
let declaratorId = "unknownHook";
|
|
315
|
+
let current = parent;
|
|
316
|
+
while (current) {
|
|
317
|
+
if (!current) {
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
if (current.type === "VariableDeclarator" && current.id.type === "Identifier") {
|
|
321
|
+
declaratorId = current.id.name;
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
if ("parent" in current) {
|
|
325
|
+
current = current.parent;
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
const argument = node.arguments[0];
|
|
331
|
+
styleCalls.push({
|
|
332
|
+
declaratorId,
|
|
333
|
+
functionKind,
|
|
334
|
+
argumentStart: argument.start,
|
|
335
|
+
argumentEnd: argument.end,
|
|
336
|
+
argumentCode: sourceCode.slice(argument.start, argument.end),
|
|
337
|
+
argumentNode: argument,
|
|
338
|
+
callStart: node.start,
|
|
339
|
+
callEnd: node.end,
|
|
340
|
+
importId
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
if (imports.length === 0 || styleCalls.length === 0) {
|
|
347
|
+
return {
|
|
348
|
+
code: sourceCode,
|
|
349
|
+
usedProcessing: false,
|
|
350
|
+
usedVMForEvaluation: false
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const { evaluationResults, usedVMForEvaluation } = batchEvaluator(
|
|
354
|
+
sourceCode,
|
|
355
|
+
filename,
|
|
356
|
+
styleCalls,
|
|
357
|
+
babelOptions,
|
|
358
|
+
evaluationRules
|
|
359
|
+
);
|
|
360
|
+
for (let i = 0; i < styleCalls.length; i++) {
|
|
361
|
+
const styleCall = styleCalls[i];
|
|
362
|
+
const evaluationResult = evaluationResults[i];
|
|
363
|
+
switch (styleCall.functionKind) {
|
|
364
|
+
case "makeStyles":
|
|
365
|
+
{
|
|
366
|
+
const stylesBySlots = evaluationResult;
|
|
367
|
+
const [classnamesMapping, cssRulesByBucketA] = resolveStyleRulesForSlots(stylesBySlots, classNameHashSalt);
|
|
368
|
+
if (generateMetadata) {
|
|
369
|
+
buildCSSEntriesMetadata(
|
|
370
|
+
cssEntries,
|
|
371
|
+
classnamesMapping,
|
|
372
|
+
dedupeCSSRules(cssRulesByBucket),
|
|
373
|
+
styleCall.declaratorId
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
magicString.overwrite(styleCall.argumentStart, styleCall.argumentEnd, `${JSON.stringify(classnamesMapping)}`);
|
|
377
|
+
cssRulesByBucket = concatCSSRulesByBucket(cssRulesByBucket, cssRulesByBucketA);
|
|
378
|
+
}
|
|
379
|
+
break;
|
|
380
|
+
case "makeResetStyles":
|
|
381
|
+
{
|
|
382
|
+
const styles = evaluationResult;
|
|
383
|
+
const [ltrClassName, rtlClassName, cssRules] = resolveResetStyleRules(styles, classNameHashSalt);
|
|
384
|
+
if (generateMetadata) {
|
|
385
|
+
buildCSSResetEntriesMetadata(cssResetEntries, cssRules, styleCall.declaratorId);
|
|
386
|
+
}
|
|
387
|
+
magicString.overwrite(
|
|
388
|
+
styleCall.argumentStart,
|
|
389
|
+
styleCall.argumentEnd,
|
|
390
|
+
`${JSON.stringify(ltrClassName)}, ${JSON.stringify(rtlClassName)}`
|
|
391
|
+
);
|
|
392
|
+
cssRulesByBucket = concatCSSRulesByBucket(
|
|
393
|
+
cssRulesByBucket,
|
|
394
|
+
Array.isArray(cssRules) ? { r: cssRules } : cssRules
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
for (const importInfo of imports) {
|
|
401
|
+
for (const specifier of importInfo.specifiers) {
|
|
402
|
+
if (specifier.imported === "makeStyles") {
|
|
403
|
+
magicString.overwrite(specifier.start, specifier.end, "__css");
|
|
404
|
+
} else if (specifier.imported === "makeResetStyles") {
|
|
405
|
+
magicString.overwrite(specifier.start, specifier.end, "__resetCSS");
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
for (const styleCall of styleCalls) {
|
|
410
|
+
magicString.overwrite(
|
|
411
|
+
styleCall.callStart,
|
|
412
|
+
styleCall.callStart + styleCall.importId.length,
|
|
413
|
+
"__" + (styleCall.functionKind === "makeStyles" ? "css" : "resetCSS")
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
code: magicString.toString(),
|
|
418
|
+
cssRulesByBucket,
|
|
419
|
+
usedProcessing: true,
|
|
420
|
+
usedVMForEvaluation
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
export {
|
|
424
|
+
EvalCache,
|
|
425
|
+
Module2 as Module,
|
|
426
|
+
default2 as shakerEvaluator,
|
|
427
|
+
transformSync
|
|
428
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { StrictOptions } from '@linaria/babel-preset';
|
|
2
|
+
import { CSSRulesByBucket } from '@griffel/core';
|
|
3
|
+
export type TransformOptions = {
|
|
4
|
+
filename: string;
|
|
5
|
+
classNameHashSalt?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Returns the evaluated CSS rules in the file result metadata
|
|
8
|
+
* @default false
|
|
9
|
+
*/
|
|
10
|
+
generateMetadata?: boolean;
|
|
11
|
+
/** Defines set of modules and imports handled by a transformPlugin. */
|
|
12
|
+
modules?: string[];
|
|
13
|
+
/**
|
|
14
|
+
* If you need to specify custom Babel configuration, you can pass them here. These options will be used by the
|
|
15
|
+
* transformPlugin when parsing and evaluating modules.
|
|
16
|
+
*/
|
|
17
|
+
babelOptions?: Pick<StrictOptions['babelOptions'], 'plugins' | 'presets'>;
|
|
18
|
+
/** The set of rules that defines how the matched files will be transformed during the evaluation. */
|
|
19
|
+
evaluationRules?: StrictOptions['rules'];
|
|
20
|
+
};
|
|
21
|
+
export type TransformResult = {
|
|
22
|
+
code: string;
|
|
23
|
+
cssRulesByBucket?: CSSRulesByBucket;
|
|
24
|
+
usedProcessing: boolean;
|
|
25
|
+
usedVMForEvaluation: boolean;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Transforms passed source code with oxc-parser and oxc-walker instead of Babel.
|
|
29
|
+
*/
|
|
30
|
+
export declare function transformSync(sourceCode: string, options: TransformOptions): TransformResult;
|
package/types.d.mts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Node } from 'oxc-parser';
|
|
2
|
+
export interface StyleCall {
|
|
3
|
+
declaratorId: string;
|
|
4
|
+
functionKind: 'makeStyles' | 'makeResetStyles';
|
|
5
|
+
argumentStart: number;
|
|
6
|
+
argumentEnd: number;
|
|
7
|
+
argumentCode: string;
|
|
8
|
+
argumentNode: Node;
|
|
9
|
+
callStart: number;
|
|
10
|
+
callEnd: number;
|
|
11
|
+
importId: string;
|
|
12
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { CSSRulesByBucket } from '@griffel/core';
|
|
2
|
+
/**
|
|
3
|
+
* Rules that are returned by `resolveStyles()` are not deduplicated.
|
|
4
|
+
* It's critical to filter out duplicates for build-time transform to avoid duplicated rules in a bundle.
|
|
5
|
+
*/
|
|
6
|
+
export declare function dedupeCSSRules(cssRulesByBucket: CSSRulesByBucket): CSSRulesByBucket;
|