lightningcss-plugin-css-variables 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ocavue
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # lightningcss-plugin-css-variables
2
+
3
+ [![NPM version](https://img.shields.io/npm/v/lightningcss-plugin-css-variables?color=a1b858&label=)](https://www.npmjs.com/package/lightningcss-plugin-css-variables)
4
+
5
+ A [LightningCSS](https://lightningcss.dev/) plugin that inlines CSS custom properties at build time. Matching variable declarations are removed from the output and all `var()` references are replaced with their values.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install lightningcss-plugin-css-variables lightningcss
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```js
16
+ import { transform } from 'lightningcss'
17
+ import cssVariables from 'lightningcss-plugin-css-variables'
18
+
19
+ const { code } = transform({
20
+ filename: 'style.css',
21
+ code: Buffer.from(`
22
+ :root {
23
+ --_color: red;
24
+ --_spacing: 4px;
25
+ }
26
+
27
+ .button {
28
+ color: var(--_color);
29
+ padding: calc(var(--_spacing) * 2);
30
+ }
31
+ `),
32
+ visitor: cssVariables(),
33
+ })
34
+
35
+ console.log(code.toString())
36
+ // .button {
37
+ // color: red;
38
+ // padding: 8px;
39
+ // }
40
+ ```
41
+
42
+ By default, all custom properties whose name starts with `--_` are inlined. Other variables are left untouched.
43
+
44
+ ## Options
45
+
46
+ ### `include`
47
+
48
+ A `RegExp` or an array of `RegExp` patterns to match the custom property names that should be inlined.
49
+
50
+ ```js
51
+ cssVariables({
52
+ // Inline variables starting with --my-prefix-
53
+ include: /^--my-prefix-/,
54
+ })
55
+
56
+ cssVariables({
57
+ // Inline variables matching any of the patterns
58
+ include: [/^--_/, /^--internal-/],
59
+ })
60
+ ```
61
+
62
+ ## License
63
+
64
+ MIT
@@ -0,0 +1,14 @@
1
+ import { Visitor } from "lightningcss";
2
+
3
+ //#region src/index.d.ts
4
+ interface Options {
5
+ /**
6
+ * A regular expression or an array of regular expressions to match the custom variable names that should
7
+ * be inlined. By default, all custom variables that start with `--_` will be inlined.
8
+ */
9
+ include?: RegExp | RegExp[];
10
+ }
11
+ declare function cssVariables(options?: Options): Visitor<never>;
12
+ //#endregion
13
+ export { Options, cssVariables, cssVariables as default };
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;UAIiB,OAAA;;AAAjB;;;EAKE,OAAA,GAAU,MAAA,GAAS,MAAA;AAAA;AAAA,iBAaL,YAAA,CAAa,OAAA,GAAU,OAAA,GAAU,OAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,261 @@
1
+ //#region src/calc.ts
2
+ function isWhiteSpaceToken(token) {
3
+ return token.type === "token" && token.value.type === "white-space";
4
+ }
5
+ function isDelimToken(token) {
6
+ return token.type === "token" && token.value.type === "delim" && (token.value.value === "+" || token.value.value === "-" || token.value.value === "*" || token.value.value === "/");
7
+ }
8
+ function isLengthToken(token) {
9
+ return token.type === "length";
10
+ }
11
+ function isTimeToken(token) {
12
+ return token.type === "time";
13
+ }
14
+ function isAngleToken(token) {
15
+ return token.type === "angle";
16
+ }
17
+ function isNumberToken(token) {
18
+ return token.type === "token" && token.value.type === "number";
19
+ }
20
+ function isPercentageToken(token) {
21
+ return token.type === "token" && token.value.type === "percentage";
22
+ }
23
+ function isParenOpenToken(token) {
24
+ return token.type === "token" && token.value.type === "parenthesis-block";
25
+ }
26
+ function isParenCloseToken(token) {
27
+ return token.type === "token" && token.value.type === "close-parenthesis";
28
+ }
29
+ function parseCalcTokens(input) {
30
+ const output = [];
31
+ for (const token of input) {
32
+ if (isWhiteSpaceToken(token)) continue;
33
+ if (isDelimToken(token)) {
34
+ output.push(token);
35
+ continue;
36
+ }
37
+ if (isLengthToken(token)) {
38
+ output.push(token);
39
+ continue;
40
+ }
41
+ if (isTimeToken(token)) {
42
+ output.push(token);
43
+ continue;
44
+ }
45
+ if (isAngleToken(token)) {
46
+ output.push(token);
47
+ continue;
48
+ }
49
+ if (isNumberToken(token)) {
50
+ output.push(token);
51
+ continue;
52
+ }
53
+ if (isPercentageToken(token)) {
54
+ output.push(token);
55
+ continue;
56
+ }
57
+ if (isParenOpenToken(token)) {
58
+ output.push(token);
59
+ continue;
60
+ }
61
+ if (isParenCloseToken(token)) {
62
+ output.push(token);
63
+ continue;
64
+ }
65
+ return;
66
+ }
67
+ return output;
68
+ }
69
+ function toEvalResult(token) {
70
+ if (isLengthToken(token)) return {
71
+ value: token.value.value,
72
+ unit: token.value.unit
73
+ };
74
+ if (isTimeToken(token)) return {
75
+ value: token.value.value,
76
+ unit: token.value.type === "milliseconds" ? "ms" : "s"
77
+ };
78
+ if (isAngleToken(token)) return {
79
+ value: token.value.value,
80
+ unit: token.value.type
81
+ };
82
+ if (isNumberToken(token)) return {
83
+ value: token.value.value,
84
+ unit: null
85
+ };
86
+ if (isPercentageToken(token)) return {
87
+ value: token.value.value,
88
+ unit: "%"
89
+ };
90
+ }
91
+ const ANGLE_UNITS = new Set([
92
+ "deg",
93
+ "rad",
94
+ "grad",
95
+ "turn"
96
+ ]);
97
+ function calcValueToToken(v) {
98
+ if (v.unit === null) return {
99
+ type: "token",
100
+ value: {
101
+ type: "number",
102
+ value: v.value
103
+ }
104
+ };
105
+ if (v.unit === "%") return {
106
+ type: "token",
107
+ value: {
108
+ type: "percentage",
109
+ value: v.value
110
+ }
111
+ };
112
+ if (ANGLE_UNITS.has(v.unit)) return {
113
+ type: "angle",
114
+ value: {
115
+ type: v.unit,
116
+ value: v.value
117
+ }
118
+ };
119
+ if (v.unit === "ms") return {
120
+ type: "time",
121
+ value: {
122
+ type: "milliseconds",
123
+ value: v.value
124
+ }
125
+ };
126
+ if (v.unit === "s") return {
127
+ type: "time",
128
+ value: {
129
+ type: "seconds",
130
+ value: v.value
131
+ }
132
+ };
133
+ return {
134
+ type: "length",
135
+ value: {
136
+ unit: v.unit,
137
+ value: v.value
138
+ }
139
+ };
140
+ }
141
+ function evaluateCalc(tokens) {
142
+ const calcTokens = parseCalcTokens(tokens);
143
+ if (calcTokens == null) return;
144
+ let pos = 0;
145
+ const peek = () => calcTokens[pos];
146
+ const next = () => calcTokens[pos++];
147
+ function expr() {
148
+ let left = term();
149
+ if (!left) return void 0;
150
+ let t = peek();
151
+ while (t && isDelimToken(t) && (t.value.value === "+" || t.value.value === "-")) {
152
+ const op = t.value.value;
153
+ next();
154
+ const right = term();
155
+ if (!right || left.unit !== right.unit) return void 0;
156
+ left = {
157
+ value: op === "+" ? left.value + right.value : left.value - right.value,
158
+ unit: left.unit
159
+ };
160
+ t = peek();
161
+ }
162
+ return left;
163
+ }
164
+ function term() {
165
+ let left = factor();
166
+ if (!left) return void 0;
167
+ let t = peek();
168
+ while (t && isDelimToken(t) && (t.value.value === "*" || t.value.value === "/")) {
169
+ const op = t.value.value;
170
+ next();
171
+ const right = factor();
172
+ if (!right) return void 0;
173
+ if (op === "*") if (left.unit === null) left = {
174
+ value: left.value * right.value,
175
+ unit: right.unit
176
+ };
177
+ else if (right.unit === null) left = {
178
+ value: left.value * right.value,
179
+ unit: left.unit
180
+ };
181
+ else return void 0;
182
+ else {
183
+ if (right.unit !== null || right.value === 0) return void 0;
184
+ left = {
185
+ value: left.value / right.value,
186
+ unit: left.unit
187
+ };
188
+ }
189
+ t = peek();
190
+ }
191
+ return left;
192
+ }
193
+ function factor() {
194
+ const t = peek();
195
+ if (!t) return void 0;
196
+ if (isParenOpenToken(t)) {
197
+ next();
198
+ const result = expr();
199
+ if (!result) return void 0;
200
+ const closing = peek();
201
+ if (!closing || !isParenCloseToken(closing)) return void 0;
202
+ next();
203
+ return result;
204
+ }
205
+ const val = toEvalResult(t);
206
+ if (val) {
207
+ next();
208
+ return val;
209
+ }
210
+ }
211
+ const result = expr();
212
+ if (!result || pos !== calcTokens.length) return void 0;
213
+ return calcValueToToken(result);
214
+ }
215
+
216
+ //#endregion
217
+ //#region src/index.ts
218
+ const defaultInclude = /^--_/;
219
+ function ensureArray(value) {
220
+ return Array.isArray(value) ? value : [value];
221
+ }
222
+ function warn(message) {
223
+ console.warn(`[lightningcss-plugin-css-variables] ${message}`);
224
+ }
225
+ function cssVariables(options) {
226
+ const includePatterns = ensureArray(options?.include || defaultInclude);
227
+ const check = (name) => {
228
+ return includePatterns.some((pattern) => pattern.test(name));
229
+ };
230
+ const variables = /* @__PURE__ */ new Map();
231
+ return {
232
+ Declaration: { custom(property) {
233
+ const name = property.name;
234
+ const value = property.value;
235
+ if (!check(name)) return;
236
+ if (variables.has(name)) {
237
+ warn(`Variable "${name}" is already defined. Ignoring duplicate declaration.`);
238
+ return;
239
+ }
240
+ variables.set(name, value);
241
+ return [];
242
+ } },
243
+ Variable(variable) {
244
+ const name = variable.name.ident;
245
+ if (!check(name)) return;
246
+ const value = variables.get(name);
247
+ if (value == null) {
248
+ warn(`Variable "${name}" is not defined. Cannot inline value.`);
249
+ return;
250
+ }
251
+ return value;
252
+ },
253
+ FunctionExit: { calc(fn) {
254
+ return evaluateCalc(fn.arguments);
255
+ } }
256
+ };
257
+ }
258
+
259
+ //#endregion
260
+ export { cssVariables, cssVariables as default };
261
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/calc.ts","../src/index.ts"],"sourcesContent":["import type { Angle, LengthValue, Time, TokenOrValue } from 'lightningcss'\n\n// --- Token types and guards ---\n\ntype WhiteSpaceToken = {\n type: 'token'\n value: {\n type: 'white-space'\n value: string\n }\n}\n\nfunction isWhiteSpaceToken(token: TokenOrValue): token is WhiteSpaceToken {\n return token.type === 'token' && token.value.type === 'white-space'\n}\n\ntype DelimToken = {\n type: 'token'\n value: {\n type: 'delim'\n value: '+' | '-' | '*' | '/'\n }\n}\n\nfunction isDelimToken(token: TokenOrValue): token is DelimToken {\n return (\n token.type === 'token' &&\n token.value.type === 'delim' &&\n (token.value.value === '+' ||\n token.value.value === '-' ||\n token.value.value === '*' ||\n token.value.value === '/')\n )\n}\n\ntype LengthToken = {\n type: 'length'\n value: LengthValue\n}\n\nfunction isLengthToken(token: TokenOrValue): token is LengthToken {\n return token.type === 'length'\n}\n\ninterface TimeToken {\n type: 'time'\n value: Time\n}\n\nfunction isTimeToken(token: TokenOrValue): token is TimeToken {\n return token.type === 'time'\n}\n\ninterface AngleToken {\n type: 'angle'\n value: Angle\n}\n\nfunction isAngleToken(token: TokenOrValue): token is AngleToken {\n return token.type === 'angle'\n}\n\ntype NumberToken = {\n type: 'token'\n value: {\n type: 'number'\n value: number\n }\n}\n\nfunction isNumberToken(token: TokenOrValue): token is NumberToken {\n return token.type === 'token' && token.value.type === 'number'\n}\n\ntype PercentageToken = {\n type: 'token'\n value: {\n type: 'percentage'\n value: number\n }\n}\n\nfunction isPercentageToken(token: TokenOrValue): token is PercentageToken {\n return token.type === 'token' && token.value.type === 'percentage'\n}\n\ntype ParenOpenToken = {\n type: 'token'\n value: {\n type: 'parenthesis-block'\n }\n}\n\nfunction isParenOpenToken(token: TokenOrValue): token is ParenOpenToken {\n return token.type === 'token' && token.value.type === 'parenthesis-block'\n}\n\ntype ParenCloseToken = {\n type: 'token'\n value: {\n type: 'close-parenthesis'\n }\n}\n\nfunction isParenCloseToken(token: TokenOrValue): token is ParenCloseToken {\n return token.type === 'token' && token.value.type === 'close-parenthesis'\n}\n\n// --- CalcToken union and parsing ---\n\ntype CalcToken =\n | DelimToken\n | LengthToken\n | TimeToken\n | AngleToken\n | NumberToken\n | PercentageToken\n | ParenOpenToken\n | ParenCloseToken\n\nfunction parseCalcTokens(input: TokenOrValue[]): CalcToken[] | undefined {\n const output: CalcToken[] = []\n\n for (const token of input) {\n if (isWhiteSpaceToken(token)) {\n continue\n }\n if (isDelimToken(token)) {\n output.push(token)\n continue\n }\n if (isLengthToken(token)) {\n output.push(token)\n continue\n }\n if (isTimeToken(token)) {\n output.push(token)\n continue\n }\n if (isAngleToken(token)) {\n output.push(token)\n continue\n }\n if (isNumberToken(token)) {\n output.push(token)\n continue\n }\n if (isPercentageToken(token)) {\n output.push(token)\n continue\n }\n if (isParenOpenToken(token)) {\n output.push(token)\n continue\n }\n if (isParenCloseToken(token)) {\n output.push(token)\n continue\n }\n // unknown token — bail\n return\n }\n return output\n}\n\n// --- Evaluation ---\n\ninterface EvalResult {\n value: number\n unit: string | null\n}\n\nfunction toEvalResult(token: CalcToken): EvalResult | undefined {\n if (isLengthToken(token)) {\n return { value: token.value.value, unit: token.value.unit }\n }\n if (isTimeToken(token)) {\n return {\n value: token.value.value,\n unit: token.value.type === 'milliseconds' ? 'ms' : 's',\n }\n }\n if (isAngleToken(token)) {\n return { value: token.value.value, unit: token.value.type }\n }\n if (isNumberToken(token)) {\n return { value: token.value.value, unit: null }\n }\n if (isPercentageToken(token)) {\n return { value: token.value.value, unit: '%' }\n }\n return undefined\n}\n\nconst ANGLE_UNITS = new Set(['deg', 'rad', 'grad', 'turn'])\n\nfunction calcValueToToken(v: EvalResult): TokenOrValue {\n if (v.unit === null)\n return { type: 'token', value: { type: 'number', value: v.value } }\n if (v.unit === '%')\n return { type: 'token', value: { type: 'percentage', value: v.value } }\n if (ANGLE_UNITS.has(v.unit))\n return {\n type: 'angle',\n value: { type: v.unit as 'deg', value: v.value },\n }\n if (v.unit === 'ms')\n return { type: 'time', value: { type: 'milliseconds', value: v.value } }\n if (v.unit === 's')\n return { type: 'time', value: { type: 'seconds', value: v.value } }\n return {\n type: 'length',\n value: { unit: v.unit as 'px', value: v.value },\n }\n}\n\nexport function evaluateCalc(tokens: TokenOrValue[]): TokenOrValue | undefined {\n const calcTokens = parseCalcTokens(tokens)\n if (calcTokens == null) {\n return\n }\n\n let pos = 0\n const peek = () => calcTokens[pos] as CalcToken | undefined\n const next = () => calcTokens[pos++]\n\n // expr = term (('+' | '-') term)*\n function expr(): EvalResult | undefined {\n let left = term()\n if (!left) return undefined\n let t = peek()\n while (\n t &&\n isDelimToken(t) &&\n (t.value.value === '+' || t.value.value === '-')\n ) {\n const op = t.value.value\n next()\n const right = term()\n if (!right || left.unit !== right.unit) return undefined\n left = {\n value: op === '+' ? left.value + right.value : left.value - right.value,\n unit: left.unit,\n }\n t = peek()\n }\n return left\n }\n\n // term = factor (('*' | '/') factor)*\n function term(): EvalResult | undefined {\n let left = factor()\n if (!left) return undefined\n let t = peek()\n while (\n t &&\n isDelimToken(t) &&\n (t.value.value === '*' || t.value.value === '/')\n ) {\n const op = t.value.value\n next()\n const right = factor()\n if (!right) return undefined\n if (op === '*') {\n if (left.unit === null)\n left = { value: left.value * right.value, unit: right.unit }\n else if (right.unit === null)\n left = { value: left.value * right.value, unit: left.unit }\n else return undefined\n } else {\n if (right.unit !== null || right.value === 0) return undefined\n left = { value: left.value / right.value, unit: left.unit }\n }\n t = peek()\n }\n return left\n }\n\n // factor = '(' expr ')' | value\n function factor(): EvalResult | undefined {\n const t = peek()\n if (!t) return undefined\n if (isParenOpenToken(t)) {\n next()\n const result = expr()\n if (!result) return undefined\n const closing = peek()\n if (!closing || !isParenCloseToken(closing)) return undefined\n next()\n return result\n }\n const val = toEvalResult(t)\n if (val) {\n next()\n return val\n }\n return undefined\n }\n\n const result = expr()\n if (!result || pos !== calcTokens.length) return undefined\n return calcValueToToken(result)\n}\n","import type { TokenOrValue, Visitor } from 'lightningcss'\n\nimport { evaluateCalc } from './calc.ts'\n\nexport interface Options {\n /**\n * A regular expression or an array of regular expressions to match the custom variable names that should\n * be inlined. By default, all custom variables that start with `--_` will be inlined.\n */\n include?: RegExp | RegExp[]\n}\n\nconst defaultInclude = /^--_/\n\nfunction ensureArray<T>(value: T | T[]): T[] {\n return Array.isArray(value) ? value : [value]\n}\n\nfunction warn(message: string) {\n console.warn(`[lightningcss-plugin-css-variables] ${message}`)\n}\n\nexport function cssVariables(options?: Options): Visitor<never> {\n const includePatterns: RegExp[] = ensureArray(\n options?.include || defaultInclude,\n )\n\n const check = (name: string): boolean => {\n return includePatterns.some((pattern) => pattern.test(name))\n }\n\n const variables = new Map<string, TokenOrValue[]>()\n\n return {\n Declaration: {\n custom(property) {\n const name: string = property.name\n const value: TokenOrValue[] = property.value\n\n if (!check(name)) {\n return\n }\n\n if (variables.has(name)) {\n warn(\n `Variable \"${name}\" is already defined. Ignoring duplicate declaration.`,\n )\n return\n }\n\n variables.set(name, value)\n return []\n },\n },\n Variable(variable) {\n const name: string = variable.name.ident\n if (!check(name)) {\n return\n }\n\n const value = variables.get(name)\n if (value == null) {\n warn(`Variable \"${name}\" is not defined. Cannot inline value.`)\n return\n }\n\n return value\n },\n FunctionExit: {\n calc(fn: { name: string; arguments: TokenOrValue[] }) {\n return evaluateCalc(fn.arguments)\n },\n },\n }\n}\n\nexport default cssVariables\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "lightningcss-plugin-css-variables",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "description": "A plugin for LightningCSS to inline CSS variables.",
6
+ "author": "ocavue <ocavue@gmail.com>",
7
+ "license": "MIT",
8
+ "funding": "https://github.com/sponsors/ocavue",
9
+ "homepage": "https://github.com/ocavue/lightningcss-plugin-css-variables#readme",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/ocavue/lightningcss-plugin-css-variables.git"
13
+ },
14
+ "bugs": "https://github.com/ocavue/lightningcss-plugin-css-variables/issues",
15
+ "keywords": [
16
+ "lightningcss-plugin"
17
+ ],
18
+ "sideEffects": false,
19
+ "main": "./dist/index.js",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js"
26
+ }
27
+ },
28
+ "typesVersions": {
29
+ "*": {
30
+ "*": [
31
+ "./dist/*",
32
+ "./dist/index.d.ts"
33
+ ]
34
+ }
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "src"
39
+ ],
40
+ "peerDependencies": {
41
+ "lightningcss": "^1.31.1"
42
+ },
43
+ "peerDependenciesMeta": {
44
+ "lightningcss": {
45
+ "optional": true
46
+ }
47
+ },
48
+ "devDependencies": {
49
+ "@ocavue/eslint-config": "^4.2.0",
50
+ "@ocavue/tsconfig": "^0.6.3",
51
+ "@types/node": "^22.19.11",
52
+ "eslint": "^10.0.2",
53
+ "lightningcss": "^1.31.1",
54
+ "prettier": "^3.8.1",
55
+ "tsdown": "^0.20.3",
56
+ "typescript": "^5.9.3",
57
+ "vite": "^7.3.1",
58
+ "vitest": "^4.0.18"
59
+ },
60
+ "scripts": {
61
+ "build": "tsdown",
62
+ "dev": "tsdown --watch",
63
+ "lint": "eslint .",
64
+ "fix": "eslint --fix . && prettier --write .",
65
+ "test": "vitest",
66
+ "typecheck": "tsc -b"
67
+ }
68
+ }
package/src/calc.ts ADDED
@@ -0,0 +1,303 @@
1
+ import type { Angle, LengthValue, Time, TokenOrValue } from 'lightningcss'
2
+
3
+ // --- Token types and guards ---
4
+
5
+ type WhiteSpaceToken = {
6
+ type: 'token'
7
+ value: {
8
+ type: 'white-space'
9
+ value: string
10
+ }
11
+ }
12
+
13
+ function isWhiteSpaceToken(token: TokenOrValue): token is WhiteSpaceToken {
14
+ return token.type === 'token' && token.value.type === 'white-space'
15
+ }
16
+
17
+ type DelimToken = {
18
+ type: 'token'
19
+ value: {
20
+ type: 'delim'
21
+ value: '+' | '-' | '*' | '/'
22
+ }
23
+ }
24
+
25
+ function isDelimToken(token: TokenOrValue): token is DelimToken {
26
+ return (
27
+ token.type === 'token' &&
28
+ token.value.type === 'delim' &&
29
+ (token.value.value === '+' ||
30
+ token.value.value === '-' ||
31
+ token.value.value === '*' ||
32
+ token.value.value === '/')
33
+ )
34
+ }
35
+
36
+ type LengthToken = {
37
+ type: 'length'
38
+ value: LengthValue
39
+ }
40
+
41
+ function isLengthToken(token: TokenOrValue): token is LengthToken {
42
+ return token.type === 'length'
43
+ }
44
+
45
+ interface TimeToken {
46
+ type: 'time'
47
+ value: Time
48
+ }
49
+
50
+ function isTimeToken(token: TokenOrValue): token is TimeToken {
51
+ return token.type === 'time'
52
+ }
53
+
54
+ interface AngleToken {
55
+ type: 'angle'
56
+ value: Angle
57
+ }
58
+
59
+ function isAngleToken(token: TokenOrValue): token is AngleToken {
60
+ return token.type === 'angle'
61
+ }
62
+
63
+ type NumberToken = {
64
+ type: 'token'
65
+ value: {
66
+ type: 'number'
67
+ value: number
68
+ }
69
+ }
70
+
71
+ function isNumberToken(token: TokenOrValue): token is NumberToken {
72
+ return token.type === 'token' && token.value.type === 'number'
73
+ }
74
+
75
+ type PercentageToken = {
76
+ type: 'token'
77
+ value: {
78
+ type: 'percentage'
79
+ value: number
80
+ }
81
+ }
82
+
83
+ function isPercentageToken(token: TokenOrValue): token is PercentageToken {
84
+ return token.type === 'token' && token.value.type === 'percentage'
85
+ }
86
+
87
+ type ParenOpenToken = {
88
+ type: 'token'
89
+ value: {
90
+ type: 'parenthesis-block'
91
+ }
92
+ }
93
+
94
+ function isParenOpenToken(token: TokenOrValue): token is ParenOpenToken {
95
+ return token.type === 'token' && token.value.type === 'parenthesis-block'
96
+ }
97
+
98
+ type ParenCloseToken = {
99
+ type: 'token'
100
+ value: {
101
+ type: 'close-parenthesis'
102
+ }
103
+ }
104
+
105
+ function isParenCloseToken(token: TokenOrValue): token is ParenCloseToken {
106
+ return token.type === 'token' && token.value.type === 'close-parenthesis'
107
+ }
108
+
109
+ // --- CalcToken union and parsing ---
110
+
111
+ type CalcToken =
112
+ | DelimToken
113
+ | LengthToken
114
+ | TimeToken
115
+ | AngleToken
116
+ | NumberToken
117
+ | PercentageToken
118
+ | ParenOpenToken
119
+ | ParenCloseToken
120
+
121
+ function parseCalcTokens(input: TokenOrValue[]): CalcToken[] | undefined {
122
+ const output: CalcToken[] = []
123
+
124
+ for (const token of input) {
125
+ if (isWhiteSpaceToken(token)) {
126
+ continue
127
+ }
128
+ if (isDelimToken(token)) {
129
+ output.push(token)
130
+ continue
131
+ }
132
+ if (isLengthToken(token)) {
133
+ output.push(token)
134
+ continue
135
+ }
136
+ if (isTimeToken(token)) {
137
+ output.push(token)
138
+ continue
139
+ }
140
+ if (isAngleToken(token)) {
141
+ output.push(token)
142
+ continue
143
+ }
144
+ if (isNumberToken(token)) {
145
+ output.push(token)
146
+ continue
147
+ }
148
+ if (isPercentageToken(token)) {
149
+ output.push(token)
150
+ continue
151
+ }
152
+ if (isParenOpenToken(token)) {
153
+ output.push(token)
154
+ continue
155
+ }
156
+ if (isParenCloseToken(token)) {
157
+ output.push(token)
158
+ continue
159
+ }
160
+ // unknown token — bail
161
+ return
162
+ }
163
+ return output
164
+ }
165
+
166
+ // --- Evaluation ---
167
+
168
+ interface EvalResult {
169
+ value: number
170
+ unit: string | null
171
+ }
172
+
173
+ function toEvalResult(token: CalcToken): EvalResult | undefined {
174
+ if (isLengthToken(token)) {
175
+ return { value: token.value.value, unit: token.value.unit }
176
+ }
177
+ if (isTimeToken(token)) {
178
+ return {
179
+ value: token.value.value,
180
+ unit: token.value.type === 'milliseconds' ? 'ms' : 's',
181
+ }
182
+ }
183
+ if (isAngleToken(token)) {
184
+ return { value: token.value.value, unit: token.value.type }
185
+ }
186
+ if (isNumberToken(token)) {
187
+ return { value: token.value.value, unit: null }
188
+ }
189
+ if (isPercentageToken(token)) {
190
+ return { value: token.value.value, unit: '%' }
191
+ }
192
+ return undefined
193
+ }
194
+
195
+ const ANGLE_UNITS = new Set(['deg', 'rad', 'grad', 'turn'])
196
+
197
+ function calcValueToToken(v: EvalResult): TokenOrValue {
198
+ if (v.unit === null)
199
+ return { type: 'token', value: { type: 'number', value: v.value } }
200
+ if (v.unit === '%')
201
+ return { type: 'token', value: { type: 'percentage', value: v.value } }
202
+ if (ANGLE_UNITS.has(v.unit))
203
+ return {
204
+ type: 'angle',
205
+ value: { type: v.unit as 'deg', value: v.value },
206
+ }
207
+ if (v.unit === 'ms')
208
+ return { type: 'time', value: { type: 'milliseconds', value: v.value } }
209
+ if (v.unit === 's')
210
+ return { type: 'time', value: { type: 'seconds', value: v.value } }
211
+ return {
212
+ type: 'length',
213
+ value: { unit: v.unit as 'px', value: v.value },
214
+ }
215
+ }
216
+
217
+ export function evaluateCalc(tokens: TokenOrValue[]): TokenOrValue | undefined {
218
+ const calcTokens = parseCalcTokens(tokens)
219
+ if (calcTokens == null) {
220
+ return
221
+ }
222
+
223
+ let pos = 0
224
+ const peek = () => calcTokens[pos] as CalcToken | undefined
225
+ const next = () => calcTokens[pos++]
226
+
227
+ // expr = term (('+' | '-') term)*
228
+ function expr(): EvalResult | undefined {
229
+ let left = term()
230
+ if (!left) return undefined
231
+ let t = peek()
232
+ while (
233
+ t &&
234
+ isDelimToken(t) &&
235
+ (t.value.value === '+' || t.value.value === '-')
236
+ ) {
237
+ const op = t.value.value
238
+ next()
239
+ const right = term()
240
+ if (!right || left.unit !== right.unit) return undefined
241
+ left = {
242
+ value: op === '+' ? left.value + right.value : left.value - right.value,
243
+ unit: left.unit,
244
+ }
245
+ t = peek()
246
+ }
247
+ return left
248
+ }
249
+
250
+ // term = factor (('*' | '/') factor)*
251
+ function term(): EvalResult | undefined {
252
+ let left = factor()
253
+ if (!left) return undefined
254
+ let t = peek()
255
+ while (
256
+ t &&
257
+ isDelimToken(t) &&
258
+ (t.value.value === '*' || t.value.value === '/')
259
+ ) {
260
+ const op = t.value.value
261
+ next()
262
+ const right = factor()
263
+ if (!right) return undefined
264
+ if (op === '*') {
265
+ if (left.unit === null)
266
+ left = { value: left.value * right.value, unit: right.unit }
267
+ else if (right.unit === null)
268
+ left = { value: left.value * right.value, unit: left.unit }
269
+ else return undefined
270
+ } else {
271
+ if (right.unit !== null || right.value === 0) return undefined
272
+ left = { value: left.value / right.value, unit: left.unit }
273
+ }
274
+ t = peek()
275
+ }
276
+ return left
277
+ }
278
+
279
+ // factor = '(' expr ')' | value
280
+ function factor(): EvalResult | undefined {
281
+ const t = peek()
282
+ if (!t) return undefined
283
+ if (isParenOpenToken(t)) {
284
+ next()
285
+ const result = expr()
286
+ if (!result) return undefined
287
+ const closing = peek()
288
+ if (!closing || !isParenCloseToken(closing)) return undefined
289
+ next()
290
+ return result
291
+ }
292
+ const val = toEvalResult(t)
293
+ if (val) {
294
+ next()
295
+ return val
296
+ }
297
+ return undefined
298
+ }
299
+
300
+ const result = expr()
301
+ if (!result || pos !== calcTokens.length) return undefined
302
+ return calcValueToToken(result)
303
+ }
@@ -0,0 +1,139 @@
1
+ import { transform } from 'lightningcss'
2
+ import { expect, it } from 'vitest'
3
+
4
+ import cssVariables from './index.ts'
5
+
6
+ const css = String.raw
7
+
8
+ it('should inline CSS variables start with --_', () => {
9
+ const input = css`
10
+ :root {
11
+ --_color: red;
12
+ }
13
+
14
+ body {
15
+ color: var(--_color);
16
+ }
17
+ `
18
+
19
+ const output = transform({
20
+ filename: 'test.css',
21
+ code: Buffer.from(input),
22
+ visitor: cssVariables(),
23
+ }).code.toString()
24
+
25
+ expect(output).toMatchInlineSnapshot(`
26
+ "body {
27
+ color: red;
28
+ }
29
+ "
30
+ `)
31
+ })
32
+
33
+ it('should not inline normal CSS variables', () => {
34
+ const input = css`
35
+ :root {
36
+ --color: red;
37
+ }
38
+
39
+ body {
40
+ color: var(--color);
41
+ }
42
+ `
43
+
44
+ const output = transform({
45
+ filename: 'test.css',
46
+ code: Buffer.from(input),
47
+ visitor: cssVariables(),
48
+ }).code.toString()
49
+
50
+ expect(output).toMatchInlineSnapshot(`
51
+ ":root {
52
+ --color: red;
53
+ }
54
+
55
+ body {
56
+ color: var(--color);
57
+ }
58
+ "
59
+ `)
60
+ })
61
+
62
+ it('can calculate CSS variables', () => {
63
+ const input = css`
64
+ :root {
65
+ --__padding-1: 1px;
66
+ --__padding-2: 2px;
67
+ }
68
+
69
+ .box1 {
70
+ padding: calc(var(--__padding-1) + var(--__padding-2));
71
+ }
72
+ .box2 {
73
+ padding: calc(var(--__padding-1) * 2 + var(--__padding-2) + 10px);
74
+ }
75
+ .box3 {
76
+ padding: calc(10px + 20px);
77
+ }
78
+ .box4 {
79
+ padding: calc((var(--__padding-1) + var(--__padding-2)) * 3);
80
+ }
81
+ .box5 {
82
+ padding: calc(var(--__padding-1) / 2);
83
+ }
84
+ `
85
+
86
+ const output = transform({
87
+ filename: 'test.css',
88
+ code: Buffer.from(input),
89
+ visitor: cssVariables(),
90
+ }).code.toString()
91
+
92
+ expect(output).toMatchInlineSnapshot(`
93
+ ".box1 {
94
+ padding: 3px;
95
+ }
96
+
97
+ .box2 {
98
+ padding: 14px;
99
+ }
100
+
101
+ .box3 {
102
+ padding: 30px;
103
+ }
104
+
105
+ .box4 {
106
+ padding: 9px;
107
+ }
108
+
109
+ .box5 {
110
+ padding: .5px;
111
+ }
112
+ "
113
+ `)
114
+ })
115
+
116
+ it('keeps calc with mixed units as-is', () => {
117
+ const input = css`
118
+ :root {
119
+ --_size: 20px;
120
+ }
121
+
122
+ .box {
123
+ width: calc(100% - var(--_size));
124
+ }
125
+ `
126
+
127
+ const output = transform({
128
+ filename: 'test.css',
129
+ code: Buffer.from(input),
130
+ visitor: cssVariables(),
131
+ }).code.toString()
132
+
133
+ expect(output).toMatchInlineSnapshot(`
134
+ ".box {
135
+ width: calc(100% - 20px);
136
+ }
137
+ "
138
+ `)
139
+ })
package/src/index.ts ADDED
@@ -0,0 +1,77 @@
1
+ import type { TokenOrValue, Visitor } from 'lightningcss'
2
+
3
+ import { evaluateCalc } from './calc.ts'
4
+
5
+ export interface Options {
6
+ /**
7
+ * A regular expression or an array of regular expressions to match the custom variable names that should
8
+ * be inlined. By default, all custom variables that start with `--_` will be inlined.
9
+ */
10
+ include?: RegExp | RegExp[]
11
+ }
12
+
13
+ const defaultInclude = /^--_/
14
+
15
+ function ensureArray<T>(value: T | T[]): T[] {
16
+ return Array.isArray(value) ? value : [value]
17
+ }
18
+
19
+ function warn(message: string) {
20
+ console.warn(`[lightningcss-plugin-css-variables] ${message}`)
21
+ }
22
+
23
+ export function cssVariables(options?: Options): Visitor<never> {
24
+ const includePatterns: RegExp[] = ensureArray(
25
+ options?.include || defaultInclude,
26
+ )
27
+
28
+ const check = (name: string): boolean => {
29
+ return includePatterns.some((pattern) => pattern.test(name))
30
+ }
31
+
32
+ const variables = new Map<string, TokenOrValue[]>()
33
+
34
+ return {
35
+ Declaration: {
36
+ custom(property) {
37
+ const name: string = property.name
38
+ const value: TokenOrValue[] = property.value
39
+
40
+ if (!check(name)) {
41
+ return
42
+ }
43
+
44
+ if (variables.has(name)) {
45
+ warn(
46
+ `Variable "${name}" is already defined. Ignoring duplicate declaration.`,
47
+ )
48
+ return
49
+ }
50
+
51
+ variables.set(name, value)
52
+ return []
53
+ },
54
+ },
55
+ Variable(variable) {
56
+ const name: string = variable.name.ident
57
+ if (!check(name)) {
58
+ return
59
+ }
60
+
61
+ const value = variables.get(name)
62
+ if (value == null) {
63
+ warn(`Variable "${name}" is not defined. Cannot inline value.`)
64
+ return
65
+ }
66
+
67
+ return value
68
+ },
69
+ FunctionExit: {
70
+ calc(fn: { name: string; arguments: TokenOrValue[] }) {
71
+ return evaluateCalc(fn.arguments)
72
+ },
73
+ },
74
+ }
75
+ }
76
+
77
+ export default cssVariables