@pre.style/core 3.1.0-alpha.4 → 3.1.0-alpha.6
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/README.md +39 -3
- package/dist/compiler/block.d.ts +8 -5
- package/dist/compiler/block.js +89 -9
- package/dist/compiler/compiler.d.ts +2 -0
- package/dist/compiler/compiler.js +80 -17
- package/dist/compiler/custom-properties.js +5 -9
- package/dist/compiler/index.d.ts +1 -1
- package/dist/compiler/index.js +1 -1
- package/dist/compiler/keyframes.js +4 -11
- package/dist/compiler/libraries.d.ts +3 -0
- package/dist/compiler/libraries.js +6 -0
- package/dist/compiler/mapper.js +4 -10
- package/dist/compiler/stylesheet.js +2 -2
- package/dist/compiler/variables.js +9 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -759,7 +759,7 @@ Namespace imports still let bundlers drop the classes you don't use. **Remove th
|
|
|
759
759
|
|
|
760
760
|
## Sharing values with prepended files
|
|
761
761
|
|
|
762
|
-
Use `prependFiles`, in the `pre.style` field of your `package.json`, to add files to the start of every block and stylesheet file, for custom properties, SCSS variables, mixins, and so on. Each file is read once per build.
|
|
762
|
+
Use `prependFiles`, in the `pre.style` field of your `package.json`, to add files to the start of every block and stylesheet file, for custom properties, SCSS variables, mixins, and so on. Each file is read once per build. In dev, saving one updates every block and stylesheet file it's prepended to. Changes to the `pre.style` field itself need a restart of the dev server, which the plugin warns about.
|
|
763
763
|
|
|
764
764
|
```json
|
|
765
765
|
{
|
|
@@ -922,13 +922,40 @@ Expanding means some shorthands produce many classes. For example, `font` also r
|
|
|
922
922
|
|
|
923
923
|
### Order
|
|
924
924
|
|
|
925
|
-
Within one block, a later declaration overrides an earlier one, as in plain CSS. Across blocks, the CSS file is ordered so the usual rules hold:
|
|
925
|
+
Within one block, a later declaration overrides an earlier one, as in plain CSS (see [Fallbacks](#fallbacks) for the same property twice in a row). Across blocks, the CSS file is ordered so the usual rules hold:
|
|
926
926
|
|
|
927
927
|
1. Hoisted at-rules (`@layer` statements, `@import`, `@namespace`), then other copied at-rules. With the `layer` option, everything but `@import` and `@namespace` is inside the layer.
|
|
928
928
|
2. Classes on the element.
|
|
929
929
|
3. Selector variants, with pseudo-classes in LVHFA order (`:link`, `:visited`, `:hover`, `:focus`, `:active`).
|
|
930
930
|
4. At-rule groups. `@media` queries with only a `min-width` come last, smallest first (mobile-first).
|
|
931
931
|
|
|
932
|
+
### Fallbacks
|
|
933
|
+
|
|
934
|
+
Declaring the same property more than once in a rule keeps the earlier values as fallbacks, for browsers that can't parse the later ones. They stay together in one class:
|
|
935
|
+
|
|
936
|
+
```ts
|
|
937
|
+
css`
|
|
938
|
+
height: 100%;
|
|
939
|
+
height: 100svh;
|
|
940
|
+
`;
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
```css
|
|
944
|
+
.A {
|
|
945
|
+
height: 100%;
|
|
946
|
+
height: 100svh;
|
|
947
|
+
}
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
Lightning CSS drops the fallbacks your [`targets`](#configuration) don't need, so set `targets` to keep them. Without `targets`, it assumes current browsers and writes only `height: 100svh` (which then shares a class with any other `height: 100svh`). Values it can't parse, such as `calc-size()`, always keep their fallbacks.
|
|
951
|
+
|
|
952
|
+
A few things aren't fallbacks:
|
|
953
|
+
|
|
954
|
+
- **A declaration in another rule**, such as `height: 100%; & { height: 100svh; }`, overrides the earlier one, since separate rules become separate classes.
|
|
955
|
+
- **A value with `var()`, `env()`, or `${…}`** replaces the declarations before it. Browsers only check these values when they're used, so they never fall back: an invalid value, or a `${…}` that is `null`, makes the property `unset`. To fall back from `${…}`, use a default (`height: var(${h}, 100%)`). A `${…}` followed by a static value is a real fallback (`height: ${h}; height: 100svh`).
|
|
956
|
+
- **Custom properties**, which accept any value.
|
|
957
|
+
- **Shorthands** are expanded into longhands, so `background: red; background: linear-gradient(…)` loses the `red`: the gradient's `background-color` (transparent) is a valid value in every browser.
|
|
958
|
+
|
|
932
959
|
## Compared to utility-first frameworks
|
|
933
960
|
|
|
934
961
|
Utility-first frameworks such as [Tailwind CSS](https://tailwindcss.com/) also produce atomic CSS, so the same styles cost about the same in either. The differences are elsewhere:
|
|
@@ -1006,6 +1033,7 @@ preStyle({
|
|
|
1006
1033
|
| --------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1007
1034
|
| `lightningcss` | | [Lightning CSS options](https://lightningcss.dev/transforms.html), such as `minify`, `targets`, `drafts`, and `visitor`. A `visitor` runs before pre.style processes each block. `filename`, `code`, `sourceMap`, `projectRoot`, `inputSourceMap`, and `cssModules` are ignored. |
|
|
1008
1035
|
| `sourceMap` | `true` in dev, `false` in builds | Generate a source map for the CSS, mapping each class to the first block it appears in (a source map can only point to one place; the [DevTools extension](#tracing-classes-with-the-devtools-extension) lists them all). In dev, it's shown in the browser (the plugin turns on Vite's `css.devSourcemap`, unless you set it). In builds, it's written next to `output.file`. When you import `virtual:pre.style.css`, builds have no CSS source map, because Vite doesn't write them for bundled CSS. |
|
|
1036
|
+
| `sourceAttributes` | `true` in dev, except in test runs | In dev, mark each `className`/`class` attribute in JSX and Astro components with where it's written (as `data-pre-style-at` on elements and `data-pre-style-from` on components), so the [DevTools extension](#tracing-classes-with-the-devtools-extension) can link an element to the line that set its classes. Needs `sourceMap`. Off by default in Vite's `test` mode (as in Vitest), so rendered markup and snapshots don't change; set it to `true` to have them there too. Builds never have the attributes. |
|
|
1009
1037
|
| `layer` | `false` | Wrap the CSS in a [cascade layer](https://developer.mozilla.org/docs/Web/CSS/@layer): `false` for none, `""` for an unnamed layer, or the layer's name, such as `"pre.style"` or `"vendor.pre-style"`. `@import` and `@namespace` rules stay before the layer, since they can't be inside one. `@layer` rules from blocks become layers nested in it. Browsers without cascade layers (Chrome before 99, Safari before 15.4, Firefox before 97) ignore everything inside it. |
|
|
1010
1038
|
| `quotes` | `'double-quote'` | Quotes around the class string that replaces each block in JS: `'single-quote'`, `'double-quote'`, or `'backtick'`. |
|
|
1011
1039
|
| `output.file` | `'pre.style.css'` | Path and name of the CSS file, relative to Vite's `build.outDir`. |
|
|
@@ -1020,7 +1048,15 @@ Before 3.1, `include`, `exclude`, `prependFiles`, `libraries`, and `quirksMode`
|
|
|
1020
1048
|
|
|
1021
1049
|
Classes are shared, so one class can come from many blocks, and a source map can only point to one of them. The [pre.style DevTools extension](packages/devtools) adds a **pre.style** tab to the Elements panel in Chrome and Firefox. For the selected element, it lists each pre.style rule like the Styles pane does, with a link to every block that uses it.
|
|
1022
1050
|
|
|
1023
|
-
It works with the Vite dev server while `sourceMap` is on, which is the default in dev. Setting `sourceMap: false` turns it off.
|
|
1051
|
+
It works with the Vite dev server while `sourceMap` is on, which is the default in dev. Setting `sourceMap: false` turns it off.
|
|
1052
|
+
|
|
1053
|
+
It also links to where the element got its classes: the `className` or `class` attribute on the element, and the one on the component it was passed to, when the component passes its props on. For this, the plugin adds `data-pre-style-at` and `data-pre-style-from` attributes to your JSX and Astro components in dev (not in builds or test runs), which you'll see in the Elements panel. If you'd rather not see them, turn them off with the `sourceAttributes` option.
|
|
1054
|
+
|
|
1055
|
+
```ts
|
|
1056
|
+
preStyle({ sourceAttributes: false });
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
It only runs on `localhost`, `*.localhost`, loopback addresses, and hostnames you add in its settings, and sends other sites no requests.
|
|
1024
1060
|
|
|
1025
1061
|
Its links open your editor through Vite, which guesses which one you use. When the dev server runs in VS Code's terminal, the plugin sets the `LAUNCH_EDITOR` environment variable to `code` if it isn't set, which also applies to Vite's error overlay. To choose an editor yourself, set `LAUNCH_EDITOR`.
|
|
1026
1062
|
|
package/dist/compiler/block.d.ts
CHANGED
|
@@ -16,15 +16,17 @@ export interface Atom {
|
|
|
16
16
|
key: string;
|
|
17
17
|
selectors: SelectorList;
|
|
18
18
|
} | null;
|
|
19
|
-
declaration:
|
|
20
|
-
key: string;
|
|
21
|
-
value: Declaration;
|
|
22
|
-
important: boolean;
|
|
23
|
-
};
|
|
19
|
+
declaration: AtomDeclaration;
|
|
24
20
|
property: string;
|
|
25
21
|
shorthand: boolean;
|
|
26
22
|
position: Position | null;
|
|
27
23
|
}
|
|
24
|
+
export interface AtomDeclaration {
|
|
25
|
+
key: string;
|
|
26
|
+
values: Declaration[];
|
|
27
|
+
important: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare const atomDeclaration: (values: Declaration[], important: boolean) => AtomDeclaration;
|
|
28
30
|
export interface GlobalRule {
|
|
29
31
|
key: string;
|
|
30
32
|
rule: Rule;
|
|
@@ -77,6 +79,7 @@ export interface ParseOptions {
|
|
|
77
79
|
export declare const key: (value: unknown) => string;
|
|
78
80
|
export declare function parseBlock(code: string, lang: Lang, options: ParseOptions): ParsedBlock;
|
|
79
81
|
export declare function parseRules(css: string, lightningcss: LightningOptions, position: ToPosition, collect: (rules: Rule[]) => void): void;
|
|
82
|
+
export declare function pruneFallbacks(atoms: Atom[], lightningcss: LightningOptions): Atom[];
|
|
80
83
|
export declare function dropOverridden(atoms: Atom[]): Atom[];
|
|
81
84
|
export type ToPosition = (loc: {
|
|
82
85
|
line: number;
|
package/dist/compiler/block.js
CHANGED
|
@@ -5,9 +5,16 @@ import { localizeCustomProperties } from './custom-properties.js';
|
|
|
5
5
|
import { SLOT_PREFIX } from './interpolate.js';
|
|
6
6
|
import { localizeKeyframes } from './keyframes.js';
|
|
7
7
|
import { compose, fromPlaceholder, isElement, PLACEHOLDER, repair } from './selectors.js';
|
|
8
|
+
import { print } from './print.js';
|
|
8
9
|
import { splitBlock } from './split.js';
|
|
9
10
|
import { extractVariables } from './variables.js';
|
|
10
11
|
import { SHORTHANDS } from '../shorthands.js';
|
|
12
|
+
// An atom's declaration. A single declaration's key is its own key.
|
|
13
|
+
export const atomDeclaration = (values, important) => ({
|
|
14
|
+
key: key(values.length === 1 ? values[0] : values) + (important ? '!' : ''),
|
|
15
|
+
values,
|
|
16
|
+
important,
|
|
17
|
+
});
|
|
11
18
|
export class PreStyleError extends Error {
|
|
12
19
|
position;
|
|
13
20
|
constructor(message, position, options) {
|
|
@@ -86,7 +93,7 @@ export function parseBlock(code, lang, options) {
|
|
|
86
93
|
// Sass output positions don't match the block, so scss atoms have none.
|
|
87
94
|
const position = (loc) => lang === 'css' && loc ? toBlock(loc.line, loc.column) : null;
|
|
88
95
|
parseRules(css, options.lightningcss, position, (rules) => walk(rules, [], null, parsed, position));
|
|
89
|
-
let atoms = dropOverridden(parsed.atoms);
|
|
96
|
+
let atoms = dropOverridden(pruneFallbacks(parsed.atoms, options.lightningcss));
|
|
90
97
|
let { globals } = parsed;
|
|
91
98
|
if (options.customPropertyLocalization) {
|
|
92
99
|
const local = localizeCustomProperties(atoms, globals, `block\n${code}`, lang, options);
|
|
@@ -127,6 +134,60 @@ export function parseRules(css, lightningcss, position, collect) {
|
|
|
127
134
|
throw new PreStyleError(`Invalid CSS: ${error.message}`, loc ? position({ line: loc.line - 1, column: loc.column }) : null, { cause: error });
|
|
128
135
|
}
|
|
129
136
|
}
|
|
137
|
+
// Drops the fallbacks in each stack that Lightning CSS wouldn't print for the
|
|
138
|
+
// user's targets, so a stack that collapses to one declaration shares its
|
|
139
|
+
// class. Values Lightning CSS can't parse (such as `calc-size()`) are kept.
|
|
140
|
+
// Printing and parsing back changes some values' trees, so the round trip
|
|
141
|
+
// only decides which of the original declarations are kept.
|
|
142
|
+
export function pruneFallbacks(atoms, lightningcss) {
|
|
143
|
+
if (atoms.every((atom) => atom.declaration.values.length === 1))
|
|
144
|
+
return atoms;
|
|
145
|
+
const { visitor: _visitor, minify: _minify, ...options } = lightningcss;
|
|
146
|
+
// The stack as Lightning CSS prints it, parsed back, as keys. Declarations
|
|
147
|
+
// it adds for other properties (vendor prefixes) are left out.
|
|
148
|
+
const printed = (stack, important, property) => {
|
|
149
|
+
const block = important ? { importantDeclarations: stack } : { declarations: stack };
|
|
150
|
+
const rule = {
|
|
151
|
+
type: 'style',
|
|
152
|
+
value: {
|
|
153
|
+
selectors: [[{ type: 'class', name: 'x' }]],
|
|
154
|
+
declarations: block,
|
|
155
|
+
rules: [],
|
|
156
|
+
loc: { source_index: 0, line: 0, column: 1 },
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
let values = [];
|
|
160
|
+
parseRules(print([rule], [], options, false).code, options, () => null, (rules) => {
|
|
161
|
+
const parsed = valueOf(rules[0]).declarations ?? {};
|
|
162
|
+
values = (important ? parsed.importantDeclarations : parsed.declarations) ?? [];
|
|
163
|
+
});
|
|
164
|
+
return values.filter((value) => propertyOf(value).property === property).map(key);
|
|
165
|
+
};
|
|
166
|
+
return atoms.map((atom) => {
|
|
167
|
+
const { values: stack, important } = atom.declaration;
|
|
168
|
+
if (stack.length === 1)
|
|
169
|
+
return atom;
|
|
170
|
+
let kept;
|
|
171
|
+
try {
|
|
172
|
+
const remaining = printed(stack, important, atom.property);
|
|
173
|
+
if (remaining.length === stack.length)
|
|
174
|
+
return atom;
|
|
175
|
+
kept = stack.filter((value) => {
|
|
176
|
+
const own = printed([value], important, atom.property).at(-1);
|
|
177
|
+
if (own !== remaining[0])
|
|
178
|
+
return false;
|
|
179
|
+
remaining.shift();
|
|
180
|
+
return true;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return atom;
|
|
185
|
+
}
|
|
186
|
+
if (kept.length === 0)
|
|
187
|
+
return atom;
|
|
188
|
+
return { ...atom, declaration: atomDeclaration(kept, important) };
|
|
189
|
+
});
|
|
190
|
+
}
|
|
130
191
|
// Within a block, a later declaration overrides an earlier one that sets the
|
|
131
192
|
// same property under the same at-rules and selector variant, as in CSS.
|
|
132
193
|
// `!important` declarations are collected after normal ones in each rule,
|
|
@@ -225,24 +286,43 @@ export function addDeclarations(block, atRules, selectors, atoms, composes, posi
|
|
|
225
286
|
composes.push(parseComposes(d, position));
|
|
226
287
|
normal = normal.slice(own);
|
|
227
288
|
}
|
|
289
|
+
// Declarations of the same property in this list are stacked, in order, as
|
|
290
|
+
// fallbacks, at the place of the last one. A declaration that always parses
|
|
291
|
+
// replaces the stack, since nothing before it can ever apply.
|
|
228
292
|
const add = (declarations = [], important) => {
|
|
293
|
+
const stacks = new Map();
|
|
294
|
+
const list = [];
|
|
229
295
|
for (const declaration of declarations) {
|
|
230
296
|
for (const value of expand(declaration)) {
|
|
231
297
|
const { property, shorthand } = propertyOf(value);
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
position,
|
|
239
|
-
});
|
|
298
|
+
const stack = stacks.get(property);
|
|
299
|
+
if (stack)
|
|
300
|
+
list.splice(list.findIndex((item) => item.values === stack), 1);
|
|
301
|
+
const values = stack && !alwaysParses(value, property) ? [...stack, value] : [value];
|
|
302
|
+
stacks.set(property, values);
|
|
303
|
+
list.push({ property, shorthand, values });
|
|
240
304
|
}
|
|
241
305
|
}
|
|
306
|
+
for (const { property, shorthand, values } of list)
|
|
307
|
+
atoms.push({
|
|
308
|
+
atRules,
|
|
309
|
+
variant,
|
|
310
|
+
declaration: atomDeclaration(values, important),
|
|
311
|
+
property,
|
|
312
|
+
shorthand,
|
|
313
|
+
position,
|
|
314
|
+
});
|
|
242
315
|
};
|
|
243
316
|
add(normal, false);
|
|
244
317
|
add(block?.importantDeclarations, true);
|
|
245
318
|
}
|
|
319
|
+
// Whether a browser always parses a declaration, so it can't fall back to an
|
|
320
|
+
// earlier one: custom properties take any value, and `var()` and `env()`
|
|
321
|
+
// (including `${…}`, which becomes a `var()`) are only checked once they're
|
|
322
|
+
// substituted, when an invalid value makes the property `unset`.
|
|
323
|
+
function alwaysParses(value, property) {
|
|
324
|
+
return property.startsWith('--') || /"type":"(?:var|env)"/.test(key(value));
|
|
325
|
+
}
|
|
246
326
|
const ident = (t) => t?.type === 'token' && t.value.type === 'ident' ? t.value.value : null;
|
|
247
327
|
// Reads `composes: a b`, `composes: a from './file.css'`, or
|
|
248
328
|
// `composes: a from global`.
|
|
@@ -83,6 +83,8 @@ export declare class Compiler {
|
|
|
83
83
|
classNames(sheet: Stylesheet): string[];
|
|
84
84
|
usages(classNames: Iterable<string>): ClassUsage[];
|
|
85
85
|
forgetSources(file: string): void;
|
|
86
|
+
prependFiles(): string[];
|
|
87
|
+
fileChanged(file: string): boolean;
|
|
86
88
|
stylesheet(sheet: Stylesheet): CompiledStylesheet;
|
|
87
89
|
css(): Printed;
|
|
88
90
|
cnTable(): CnTable;
|
|
@@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import { dirname, relative, resolve as resolvePath, sep } from 'node:path';
|
|
3
3
|
import { transform } from 'lightningcss';
|
|
4
4
|
import SweatMap from 'sweatmap';
|
|
5
|
-
import { dropOverridden, parseBlock, PreStyleError, valueOf } from './block.js';
|
|
5
|
+
import { atomDeclaration, dropOverridden, parseBlock, PreStyleError, valueOf } from './block.js';
|
|
6
6
|
import { interpolate } from './interpolate.js';
|
|
7
7
|
import { print } from './print.js';
|
|
8
8
|
import { lvhfaRank, specificity, withClass } from './selectors.js';
|
|
@@ -171,9 +171,10 @@ export class Compiler {
|
|
|
171
171
|
};
|
|
172
172
|
});
|
|
173
173
|
}
|
|
174
|
-
// The files a block's class string depends on
|
|
174
|
+
// The files a block's class string depends on: its prepended files, and
|
|
175
|
+
// files it composes from.
|
|
175
176
|
dependencies(block) {
|
|
176
|
-
const dependencies = [];
|
|
177
|
+
const dependencies = [...this.#library(block.file).prependFiles[block.lang]];
|
|
177
178
|
this.#resolveBlock(block, this.#parse(block), dependencies);
|
|
178
179
|
return [...new Set(dependencies)];
|
|
179
180
|
}
|
|
@@ -218,10 +219,45 @@ export class Compiler {
|
|
|
218
219
|
if (source.file === file)
|
|
219
220
|
record.sources.delete(key);
|
|
220
221
|
}
|
|
222
|
+
// Every prepended file, the app's and each library's, as absolute paths.
|
|
223
|
+
prependFiles() {
|
|
224
|
+
return [...new Set(this.#prepends().flatMap(({ files: { css, scss } }) => [...css, ...scss]))];
|
|
225
|
+
}
|
|
226
|
+
// Call when a file changes. Drops the places it uses atoms (see
|
|
227
|
+
// `forgetSources`) and, if it's a prepended file, reads it again, so blocks
|
|
228
|
+
// and stylesheet files compiled after this use its new code. Returns whether
|
|
229
|
+
// it was a prepended file.
|
|
230
|
+
fileChanged(file) {
|
|
231
|
+
this.forgetSources(file);
|
|
232
|
+
let changed = false;
|
|
233
|
+
for (const prepended of this.#prepends()) {
|
|
234
|
+
if (![...prepended.files.css, ...prepended.files.scss].includes(file))
|
|
235
|
+
continue;
|
|
236
|
+
try {
|
|
237
|
+
prepended.code = readPrependCode(prepended.files);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
// Kept as it was, until the file can be read again.
|
|
241
|
+
this.#parseOptions.warn(`Can't read ${file}, a prepended file, so blocks use its last contents: ${error.message}`);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
changed = true;
|
|
245
|
+
}
|
|
246
|
+
if (changed) {
|
|
247
|
+
// Parsed with the old code. Cache keys hold the code they parsed, but
|
|
248
|
+
// not the code prepended to it.
|
|
249
|
+
this.#blocks.clear();
|
|
250
|
+
this.#stylesheets.clear();
|
|
251
|
+
}
|
|
252
|
+
return changed;
|
|
253
|
+
}
|
|
221
254
|
// Each class of a stylesheet file, with its class string.
|
|
222
255
|
stylesheet(sheet) {
|
|
223
256
|
const parsed = this.#parseSheet(sheet);
|
|
224
|
-
const dependencies = [
|
|
257
|
+
const dependencies = [
|
|
258
|
+
...this.#library(sheet.file).prependFiles[sheet.lang],
|
|
259
|
+
...parsed.dependencies,
|
|
260
|
+
];
|
|
225
261
|
const exports = [];
|
|
226
262
|
for (const name of parsed.classes.keys())
|
|
227
263
|
exports.push([
|
|
@@ -287,6 +323,10 @@ export class Compiler {
|
|
|
287
323
|
variables[this.#names.variables.set(key)] = property ? this.#propertyName(property) : '';
|
|
288
324
|
return variables;
|
|
289
325
|
}
|
|
326
|
+
// The app's prepended files, then each library's.
|
|
327
|
+
#prepends() {
|
|
328
|
+
return [this.#prepend, ...this.#libraries.map((library) => library.prepend)];
|
|
329
|
+
}
|
|
290
330
|
// A library's blocks and files get its prepended files and keyframes
|
|
291
331
|
// setting instead of the app's.
|
|
292
332
|
#library(file) {
|
|
@@ -300,9 +340,10 @@ export class Compiler {
|
|
|
300
340
|
: this.#libraries[library];
|
|
301
341
|
return {
|
|
302
342
|
library,
|
|
343
|
+
prependFiles: prepend.files,
|
|
303
344
|
options: {
|
|
304
345
|
...this.#parseOptions,
|
|
305
|
-
prepend,
|
|
346
|
+
prepend: prepend.code,
|
|
306
347
|
keyframeLocalization: keyframeLocalization ?? false,
|
|
307
348
|
customPropertyLocalization: customPropertyLocalization ?? false,
|
|
308
349
|
},
|
|
@@ -544,12 +585,12 @@ export class Compiler {
|
|
|
544
585
|
}
|
|
545
586
|
#atomRule(record, loc) {
|
|
546
587
|
const { atom } = record;
|
|
547
|
-
const
|
|
548
|
-
? this.#named(atom.declaration.
|
|
549
|
-
: atom.declaration.
|
|
588
|
+
const values = hasGenerated(atom.declaration.key)
|
|
589
|
+
? this.#named(atom.declaration.values)
|
|
590
|
+
: atom.declaration.values;
|
|
550
591
|
const declarations = atom.declaration.important
|
|
551
|
-
? { importantDeclarations:
|
|
552
|
-
: { declarations:
|
|
592
|
+
? { importantDeclarations: values }
|
|
593
|
+
: { declarations: values };
|
|
553
594
|
return {
|
|
554
595
|
type: 'style',
|
|
555
596
|
value: { selectors: this.#atomSelectors(record), declarations, rules: [], loc },
|
|
@@ -574,6 +615,20 @@ export class Compiler {
|
|
|
574
615
|
};
|
|
575
616
|
return emit(this.#atomGroups());
|
|
576
617
|
}
|
|
618
|
+
// An atom's rule as text. Without the user's targets, Lightning CSS would
|
|
619
|
+
// drop a stack's fallbacks, so each declaration is printed on its own.
|
|
620
|
+
#printedStack(record, rule, text) {
|
|
621
|
+
const code = text(rule);
|
|
622
|
+
const { values, important } = record.atom.declaration;
|
|
623
|
+
if (values.length === 1)
|
|
624
|
+
return code;
|
|
625
|
+
const lines = values.map((value) => {
|
|
626
|
+
const atom = { ...record.atom, declaration: atomDeclaration([value], important) };
|
|
627
|
+
const own = text(this.#atomRule({ ...record, atom }, valueOf(rule).loc));
|
|
628
|
+
return own.slice(own.indexOf(' {\n') + 3, own.lastIndexOf('\n}'));
|
|
629
|
+
});
|
|
630
|
+
return `${code.slice(0, code.indexOf(' {\n'))} {\n${lines.join('\n')}\n}`;
|
|
631
|
+
}
|
|
577
632
|
#usage(record, order) {
|
|
578
633
|
const { atom } = record;
|
|
579
634
|
const loc = { source_index: 0, line: 0, column: 1 };
|
|
@@ -581,7 +636,7 @@ export class Compiler {
|
|
|
581
636
|
// Printed for reading, so without the user's `minify` and `targets`.
|
|
582
637
|
const { drafts, nonStandard, errorRecovery } = this.#parseOptions.lightningcss;
|
|
583
638
|
const text = (rules) => print([rules], [], { drafts, nonStandard, errorRecovery }, false).code;
|
|
584
|
-
const code = (record.printed ??=
|
|
639
|
+
const code = (record.printed ??= this.#printedStack(record, rule, text));
|
|
585
640
|
const open = code.indexOf(' {\n');
|
|
586
641
|
return {
|
|
587
642
|
className: record.className,
|
|
@@ -595,7 +650,9 @@ export class Compiler {
|
|
|
595
650
|
declaration: code
|
|
596
651
|
.slice(open + 3, code.lastIndexOf('\n}'))
|
|
597
652
|
.trim()
|
|
598
|
-
.replace(/;$/, '')
|
|
653
|
+
.replace(/;$/, '')
|
|
654
|
+
// Fallbacks, on one line.
|
|
655
|
+
.replaceAll(/;\n\s*/g, '; '),
|
|
599
656
|
property: this.#propertyName(atom.property),
|
|
600
657
|
covers: Object.hasOwn(SHORTHANDS, atom.property) ? SHORTHANDS[atom.property] : [],
|
|
601
658
|
important: atom.declaration.important,
|
|
@@ -645,13 +702,19 @@ function layerRule(layer) {
|
|
|
645
702
|
}
|
|
646
703
|
// A stylesheet file, for positions and source maps.
|
|
647
704
|
const fileBlock = (file) => ({ code: '', lang: 'css', file, line: 1, column: 1 });
|
|
648
|
-
// Reads prepended files, relative to `dir
|
|
705
|
+
// Reads prepended files, relative to `dir`.
|
|
649
706
|
function readPrepend(dir, files) {
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
? { css:
|
|
653
|
-
: { css:
|
|
707
|
+
const resolve = (list = []) => list.map((file) => resolvePath(dir, file));
|
|
708
|
+
const paths = Array.isArray(files)
|
|
709
|
+
? { css: resolve(files), scss: resolve(files) }
|
|
710
|
+
: { css: resolve(files?.css), scss: resolve(files?.scss) };
|
|
711
|
+
return { files: paths, code: readPrependCode(paths) };
|
|
654
712
|
}
|
|
713
|
+
const readFiles = (files) => files.map((file) => readFileSync(file, 'utf8')).join('\n');
|
|
714
|
+
const readPrependCode = (files) => ({
|
|
715
|
+
css: readFiles(files.css),
|
|
716
|
+
scss: readFiles(files.scss),
|
|
717
|
+
});
|
|
655
718
|
const count = (map, key) => map.set(key, (map.get(key) ?? 0) + 1);
|
|
656
719
|
// The at-rules and selector variant an atom is under. Empty for atoms on the
|
|
657
720
|
// element.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { key, overrideKey, parseRules, valueOf, walk } from './block.js';
|
|
2
|
+
import { atomDeclaration, key, overrideKey, parseRules, valueOf, walk } from './block.js';
|
|
3
3
|
import { VARIABLE_PREFIX } from './variables.js';
|
|
4
4
|
// With `customPropertyLocalization`, a custom property that a block or
|
|
5
5
|
// stylesheet file declares gets a generated name, used only in that block or
|
|
@@ -103,7 +103,7 @@ export function localizeCustomProperties(atoms, globals, scope, lang, options) {
|
|
|
103
103
|
local.set(name, VARIABLE_PREFIX + hash);
|
|
104
104
|
};
|
|
105
105
|
for (const atom of atoms)
|
|
106
|
-
eachDeclared(atom.declaration.
|
|
106
|
+
eachDeclared(atom.declaration.values, declared);
|
|
107
107
|
const own = (global) => !shared.keys.has(global.key);
|
|
108
108
|
for (const global of globals)
|
|
109
109
|
if (own(global))
|
|
@@ -125,23 +125,19 @@ export function localizeCustomProperties(atoms, globals, scope, lang, options) {
|
|
|
125
125
|
};
|
|
126
126
|
}),
|
|
127
127
|
atoms: (list) => list.map((atom) => {
|
|
128
|
-
const
|
|
128
|
+
const values = rename(atom.declaration.values, local);
|
|
129
129
|
const atRules = atom.atRules.map((segment) => {
|
|
130
130
|
const rule = rename(segment.rule, local);
|
|
131
131
|
return rule === segment.rule ? segment : { key: key(valueKey(rule)), rule };
|
|
132
132
|
});
|
|
133
|
-
const unchanged =
|
|
133
|
+
const unchanged = values === atom.declaration.values && atRules.every((s, i) => s === atom.atRules[i]);
|
|
134
134
|
if (unchanged)
|
|
135
135
|
return atom;
|
|
136
136
|
return {
|
|
137
137
|
...atom,
|
|
138
138
|
atRules,
|
|
139
139
|
property: local.get(atom.property) ?? atom.property,
|
|
140
|
-
declaration:
|
|
141
|
-
...atom.declaration,
|
|
142
|
-
key: key(value) + (atom.declaration.important ? '!' : ''),
|
|
143
|
-
value,
|
|
144
|
-
},
|
|
140
|
+
declaration: atomDeclaration(values, atom.declaration.important),
|
|
145
141
|
};
|
|
146
142
|
}),
|
|
147
143
|
};
|
package/dist/compiler/index.d.ts
CHANGED
|
@@ -6,6 +6,6 @@ export type { Slot } from './interpolate.ts';
|
|
|
6
6
|
export type { Printed } from './print.ts';
|
|
7
7
|
export { checkField, readConfig } from './config.ts';
|
|
8
8
|
export type { PackageConfig } from './config.ts';
|
|
9
|
-
export { findLibraries } from './libraries.ts';
|
|
9
|
+
export { configFiles, findLibraries } from './libraries.ts';
|
|
10
10
|
export type { LibraryConfig, ResolvedLibrary } from './libraries.ts';
|
|
11
11
|
export { isIdentifier } from './stylesheet.ts';
|
package/dist/compiler/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Compiler, createCompiler, interpolatedBlock } from './compiler.js';
|
|
2
2
|
export { PreStyleError } from './block.js';
|
|
3
3
|
export { checkField, readConfig } from './config.js';
|
|
4
|
-
export { findLibraries } from './libraries.js';
|
|
4
|
+
export { configFiles, findLibraries } from './libraries.js';
|
|
5
5
|
export { isIdentifier } from './stylesheet.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { key, overrideKey, valueOf } from './block.js';
|
|
2
|
+
import { atomDeclaration, key, overrideKey, valueOf } from './block.js';
|
|
3
3
|
// With `keyframeLocalization`, each `@keyframes` name is replaced with a
|
|
4
4
|
// generated one, like styled-components' `keyframes`. In parsed rules, a
|
|
5
5
|
// local name is an ident with this prefix and a hash of every definition of
|
|
@@ -55,17 +55,10 @@ export function localizeKeyframes(globals) {
|
|
|
55
55
|
atoms: (atoms) => atoms.map((atom) => {
|
|
56
56
|
if (!ANIMATION_PROPERTIES.has(atom.property.replace(/^-\w+-/, '')))
|
|
57
57
|
return atom;
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
58
|
+
const values = renameIdents(atom.declaration.values, local);
|
|
59
|
+
if (values === atom.declaration.values)
|
|
60
60
|
return atom;
|
|
61
|
-
return {
|
|
62
|
-
...atom,
|
|
63
|
-
declaration: {
|
|
64
|
-
...atom.declaration,
|
|
65
|
-
key: key(value) + (atom.declaration.important ? '!' : ''),
|
|
66
|
-
value,
|
|
67
|
-
},
|
|
68
|
-
};
|
|
61
|
+
return { ...atom, declaration: atomDeclaration(values, atom.declaration.important) };
|
|
69
62
|
}),
|
|
70
63
|
};
|
|
71
64
|
}
|
|
@@ -16,3 +16,6 @@ export interface ResolvedLibrary {
|
|
|
16
16
|
customPropertyLocalization?: boolean;
|
|
17
17
|
}
|
|
18
18
|
export declare function findLibraries(root: string, overrides?: Record<string, LibraryConfig | boolean>, warn?: (message: string) => void): ResolvedLibrary[];
|
|
19
|
+
export declare const configFiles: (appDir: string, libraries: {
|
|
20
|
+
dir: string;
|
|
21
|
+
}[]) => string[];
|
|
@@ -81,3 +81,9 @@ export function findLibraries(root, overrides = {}, warn = () => { }) {
|
|
|
81
81
|
visit(name, root);
|
|
82
82
|
return libraries;
|
|
83
83
|
}
|
|
84
|
+
// The package.json files whose `pre.style` fields configure the compiler: the
|
|
85
|
+
// app's and each library's.
|
|
86
|
+
export const configFiles = (appDir, libraries) => [
|
|
87
|
+
join(appDir, 'package.json'),
|
|
88
|
+
...libraries.map(({ dir }) => join(dir, 'package.json')),
|
|
89
|
+
];
|
package/dist/compiler/mapper.js
CHANGED
|
@@ -10,17 +10,16 @@
|
|
|
10
10
|
// "contentMappers": [{ "package": "@pre.style/core", "extensions": [".css", ".scss"] }]
|
|
11
11
|
import { createHash } from 'node:crypto';
|
|
12
12
|
import { existsSync, readFileSync } from 'node:fs';
|
|
13
|
-
import { dirname
|
|
13
|
+
import { dirname } from 'node:path';
|
|
14
14
|
import { PreStyleError } from './block.js';
|
|
15
15
|
import { createCompiler } from './compiler.js';
|
|
16
16
|
import { readConfig } from './config.js';
|
|
17
|
-
import { findLibraries } from './libraries.js';
|
|
17
|
+
import { configFiles, findLibraries } from './libraries.js';
|
|
18
18
|
import { isIdentifier } from './stylesheet.js';
|
|
19
19
|
// Each open project's compiler, by its handle.
|
|
20
20
|
const projects = new Map();
|
|
21
21
|
// Diagnostic codes, for errors in a stylesheet file.
|
|
22
22
|
const INVALID_STYLESHEET = 9001;
|
|
23
|
-
const prependPaths = (dir, files) => (Array.isArray(files) ? files : [...(files?.css ?? []), ...(files?.scss ?? [])]).map((file) => resolve(dir, file));
|
|
24
23
|
async function openProject(params) {
|
|
25
24
|
// The project's package.json, nearest its tsconfig.json.
|
|
26
25
|
const root = params.configFileName ? dirname(params.configFileName) : process.cwd();
|
|
@@ -41,15 +40,10 @@ async function openProject(params) {
|
|
|
41
40
|
warn: () => { },
|
|
42
41
|
});
|
|
43
42
|
projects.set(params.projectHandle, compiler);
|
|
44
|
-
await compiler;
|
|
45
43
|
// Changes to these files change which classes files export.
|
|
46
44
|
const watchedFiles = [
|
|
47
|
-
|
|
48
|
-
...
|
|
49
|
-
...libraries.flatMap(({ dir, prependFiles }) => [
|
|
50
|
-
join(dir, 'package.json'),
|
|
51
|
-
...prependPaths(dir, prependFiles),
|
|
52
|
-
]),
|
|
45
|
+
...configFiles(app.dir, libraries),
|
|
46
|
+
...(await compiler).prependFiles(),
|
|
53
47
|
].filter((file) => existsSync(file));
|
|
54
48
|
const hash = createHash('sha256');
|
|
55
49
|
for (const file of watchedFiles)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
2
2
|
import { atRuleInfo } from './at-rules.js';
|
|
3
|
-
import { addDeclarations, addGlobal, dropOverridden, key, parseRules, PreStyleError, valueOf, walk, } from './block.js';
|
|
3
|
+
import { addDeclarations, addGlobal, dropOverridden, pruneFallbacks, key, parseRules, PreStyleError, valueOf, walk, } from './block.js';
|
|
4
4
|
import { localizeCustomProperties } from './custom-properties.js';
|
|
5
5
|
import { localizeKeyframes } from './keyframes.js';
|
|
6
6
|
import { compose, isElement } from './selectors.js';
|
|
@@ -145,7 +145,7 @@ export function parseStylesheet(code, lang, file, id, options) {
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
for (const target of classes.values())
|
|
148
|
-
target.atoms = dropOverridden(target.atoms);
|
|
148
|
+
target.atoms = dropOverridden(pruneFallbacks(target.atoms, options.lightningcss));
|
|
149
149
|
// Custom properties are local to the whole file, like keyframes.
|
|
150
150
|
if (options.customPropertyLocalization) {
|
|
151
151
|
const all = [...classes.values()].flatMap((target) => target.atoms);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { transform } from 'lightningcss';
|
|
3
|
-
import { key, overrideKey, PreStyleError, valueOf } from './block.js';
|
|
3
|
+
import { atomDeclaration, key, overrideKey, PreStyleError, valueOf } from './block.js';
|
|
4
4
|
import { atRuleInfo } from './at-rules.js';
|
|
5
5
|
import { SLOT_PREFIX } from './interpolate.js';
|
|
6
6
|
import { print } from './print.js';
|
|
@@ -43,7 +43,9 @@ const IGNORED = (token) => token.type === 'token' && token.value?.type === 'whit
|
|
|
43
43
|
function directSlot(atom) {
|
|
44
44
|
if (atom.atRules.length > 0 || atom.variant !== null || atom.declaration.important)
|
|
45
45
|
return null;
|
|
46
|
-
|
|
46
|
+
if (atom.declaration.values.length !== 1)
|
|
47
|
+
return null;
|
|
48
|
+
const declaration = atom.declaration.values[0];
|
|
47
49
|
// Lightning CSS also uses `custom` for properties it doesn't parse, such as
|
|
48
50
|
// `content` with a `var()`.
|
|
49
51
|
if (declaration.property !== 'custom' || !atom.property.startsWith('--'))
|
|
@@ -251,22 +253,17 @@ export function extractVariables(atoms, globals) {
|
|
|
251
253
|
}
|
|
252
254
|
continue;
|
|
253
255
|
}
|
|
254
|
-
const
|
|
256
|
+
const values = structuredClone(atom.declaration.values);
|
|
255
257
|
const selector = selectorOf(atom);
|
|
256
258
|
let index = 0;
|
|
257
|
-
|
|
259
|
+
// Counted across the whole stack, so `height: ${a}; height: ${b}` is two
|
|
260
|
+
// variables.
|
|
261
|
+
eachSlot(values, (node) => {
|
|
258
262
|
const { slot, syntax } = parseSlot(node);
|
|
259
263
|
const identity = atomIdentity(atom, index++);
|
|
260
264
|
useVariable(node, identity, slot, syntax, variables, added, atom.position, selector);
|
|
261
265
|
});
|
|
262
|
-
keptAtoms.push({
|
|
263
|
-
...atom,
|
|
264
|
-
declaration: {
|
|
265
|
-
...atom.declaration,
|
|
266
|
-
key: key(value) + (atom.declaration.important ? '!' : ''),
|
|
267
|
-
value,
|
|
268
|
-
},
|
|
269
|
-
});
|
|
266
|
+
keptAtoms.push({ ...atom, declaration: atomDeclaration(values, atom.declaration.important) });
|
|
270
267
|
}
|
|
271
268
|
return { atoms: keptAtoms, globals: [...kept, ...added.values()], variables };
|
|
272
269
|
}
|