@pre.style/core 3.1.0-alpha.1 → 3.1.0-alpha.3
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 +395 -6
- package/dist/cn.d.ts +2 -1
- package/dist/cn.js +4 -2
- package/dist/compiler/block.d.ts +3 -0
- package/dist/compiler/block.js +17 -4
- package/dist/compiler/compiler.d.ts +36 -0
- package/dist/compiler/compiler.js +187 -44
- package/dist/compiler/config.d.ts +1 -0
- package/dist/compiler/config.js +4 -0
- package/dist/compiler/custom-properties.d.ts +5 -0
- package/dist/compiler/custom-properties.js +150 -0
- package/dist/compiler/index.d.ts +3 -2
- package/dist/compiler/index.js +1 -1
- package/dist/compiler/interpolate.d.ts +11 -0
- package/dist/compiler/interpolate.js +282 -0
- package/dist/compiler/libraries.d.ts +2 -0
- package/dist/compiler/libraries.js +9 -2
- package/dist/compiler/mapper.js +3 -1
- package/dist/compiler/selectors.d.ts +3 -0
- package/dist/compiler/selectors.js +38 -0
- package/dist/compiler/stylesheet.js +9 -0
- package/dist/compiler/variables.d.ts +13 -0
- package/dist/compiler/variables.js +272 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -2
- package/dist/runtime-dev.d.ts +2 -0
- package/dist/runtime-dev.js +9 -0
- package/dist/runtime.d.ts +16 -0
- package/dist/runtime.js +99 -0
- package/package.json +11 -1
- package/package.schema.json +8 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pre.style
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@pre.style/core)
|
|
4
4
|
|
|
5
5
|
Zero-runtime CSS-in-JS. Write plain CSS or SCSS next to your markup, and pre.style compiles it at build time into atomic CSS classes: one small class per declaration, shared across your whole project.
|
|
6
6
|
|
|
@@ -50,7 +50,7 @@ After the build, `button` is a plain string, `"B C E D A G F"`. The block is rep
|
|
|
50
50
|
}
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
- **No runtime.** Blocks are replaced with class strings during the build
|
|
53
|
+
- **No runtime by default.** Blocks are replaced with class strings during the build, and nothing is shipped to parse or inject CSS. The exception is [runtime values](#runtime-values), which you opt in to by writing `${…}`.
|
|
54
54
|
- **Small CSS.** Each declaration is written once, however many components use it, so the CSS file grows slowly as your project grows.
|
|
55
55
|
- **Just CSS.** Blocks are standard CSS (with nesting) or SCSS. There's no new syntax to learn.
|
|
56
56
|
- **Fast.** Built on [Lightning CSS](https://lightningcss.dev/). Every block is parsed once, in one process.
|
|
@@ -161,7 +161,302 @@ const title = scss`
|
|
|
161
161
|
`;
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
-
|
|
164
|
+
### Runtime values
|
|
165
|
+
|
|
166
|
+
Use `${…}` for values that are only known while your app runs, such as props:
|
|
167
|
+
|
|
168
|
+
```jsx
|
|
169
|
+
function Bar({ width }) {
|
|
170
|
+
const bar = css`
|
|
171
|
+
width: ${width}px;
|
|
172
|
+
`;
|
|
173
|
+
return <div className={bar} />;
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Each `${…}` becomes a custom property, and the block's CSS is still written to the CSS file during the build:
|
|
178
|
+
|
|
179
|
+
```css
|
|
180
|
+
@property --A {
|
|
181
|
+
syntax: "*";
|
|
182
|
+
inherits: false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.A {
|
|
186
|
+
width: var(--A);
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
In the browser, a small runtime generates one class for each distinct value and inserts its rule once. `bar` is still a plain string, `"A _A3fK9"`, so it works anywhere a class string does, including `cn`.
|
|
191
|
+
|
|
192
|
+
```css
|
|
193
|
+
._A3fK9 {
|
|
194
|
+
--A: 240px;
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
> [!IMPORTANT]
|
|
199
|
+
> **`${…}` adds JavaScript to your bundle.** It's the one part of pre.style with a runtime, and only apps that use `${…}` load it. Everything else stays static, so use `${…}` only for values that change at runtime. For constants, such as design tokens, use [Sass or prepended files](#sharing-values-with-prepended-files), which cost nothing in the browser. `${tokens.red}` uses the runtime, even though its value never changes.
|
|
200
|
+
|
|
201
|
+
The generated `@property` keeps the variable from inheriting. Variables are shared by every block with the same declaration, so without it, an element with no value of its own would take its parent's.
|
|
202
|
+
|
|
203
|
+
#### Default values
|
|
204
|
+
|
|
205
|
+
Write `var()` around a value to give it a default:
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
css`
|
|
209
|
+
width: var(${width}px, 120px);
|
|
210
|
+
color: var(${color}, currentColor);
|
|
211
|
+
background-color: var(${color}, var(--surface));
|
|
212
|
+
`;
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
```css
|
|
216
|
+
.A {
|
|
217
|
+
width: var(--A, 120px);
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The default can be any CSS. It's used when the value is `null` or `undefined`, in which case no class is generated, and until the runtime sets the value in the browser (see [Server rendering](#server-rendering)). A value of the wrong type doesn't use it: `width: var(${"red"}, 120px)` resets `width`. To catch those, use [`type()`](#typed-values-with-type).
|
|
222
|
+
|
|
223
|
+
#### Several values
|
|
224
|
+
|
|
225
|
+
Each `${…}` and the text touching it is one value, with its own variable:
|
|
226
|
+
|
|
227
|
+
```js
|
|
228
|
+
css`
|
|
229
|
+
color: #${hex};
|
|
230
|
+
margin-top: calc(${gap}px * 2);
|
|
231
|
+
background-image: linear-gradient(${from}, ${to});
|
|
232
|
+
`;
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Values inside one `var()` share a variable, and so share a default:
|
|
236
|
+
|
|
237
|
+
```js
|
|
238
|
+
css`
|
|
239
|
+
margin: var(${y}px ${x}px, auto);
|
|
240
|
+
`;
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
```css
|
|
244
|
+
.A {
|
|
245
|
+
margin: var(--A, auto);
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Values work in nested selectors too, including pseudo-elements and other elements. The generated class's rule uses the same selector, so the value reaches them. `content` needs a string, so pass one:
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
css`
|
|
253
|
+
&::before {
|
|
254
|
+
content: var(${JSON.stringify(String(count))}, "0");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
& > * {
|
|
258
|
+
margin-top: ${gap}px;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
& + li {
|
|
262
|
+
color: ${color};
|
|
263
|
+
}
|
|
264
|
+
`;
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
```css
|
|
268
|
+
/* Inserted by the runtime */
|
|
269
|
+
._A3fK9::before {
|
|
270
|
+
--A: "5";
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
#### Custom properties
|
|
275
|
+
|
|
276
|
+
Name a custom property to share a value: with other elements, which inherit it, with something else that reads that name, or across several declarations in a block. For a value that's only used on one element, write `${…}` directly instead. A name is global, so two blocks using the same one can affect each other. To avoid that, use distinctive names, or [`customPropertyLocalization`](#local-custom-properties).
|
|
277
|
+
|
|
278
|
+
A custom property set directly on the element is set by name. It needs no generated variable or `@property`, only the generated class:
|
|
279
|
+
|
|
280
|
+
```js
|
|
281
|
+
const badge = css`
|
|
282
|
+
--brand: ${color};
|
|
283
|
+
color: var(--brand);
|
|
284
|
+
`;
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
```css
|
|
288
|
+
.A {
|
|
289
|
+
color: var(--brand);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* Inserted by the runtime */
|
|
293
|
+
._B7xQ2 {
|
|
294
|
+
--brand: #00f;
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
To give it a default, write `var()` around the value, or register it with `@property`, whose `initial-value` is the default. Write the rule yourself for full control, such as `inherits: true`:
|
|
299
|
+
|
|
300
|
+
```js
|
|
301
|
+
css`
|
|
302
|
+
@property --brand {
|
|
303
|
+
syntax: "<color>";
|
|
304
|
+
inherits: false;
|
|
305
|
+
initial-value: #f00;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
--brand: ${color};
|
|
309
|
+
color: var(--brand);
|
|
310
|
+
`;
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
With `inherits: true`, an element with no value of its own takes its parent's rather than the `initial-value`.
|
|
314
|
+
|
|
315
|
+
#### Typed values with `type()`
|
|
316
|
+
|
|
317
|
+
Add `type()` to say what kind of value a variable holds, the same way as in [`attr()`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/attr). It takes any [`@property` syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@property/syntax):
|
|
318
|
+
|
|
319
|
+
```js
|
|
320
|
+
css`
|
|
321
|
+
width: var(${width}px type(<length>), 120px);
|
|
322
|
+
height: var(${height} type(<length> | auto), auto);
|
|
323
|
+
border-color: var(${color} type(<color>), red);
|
|
324
|
+
translate: var(${x}px ${y}px type(<length>+), 0px);
|
|
325
|
+
`;
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
The default moves to the variable's `initial-value`, and the variable gets the type:
|
|
329
|
+
|
|
330
|
+
```css
|
|
331
|
+
@property --A {
|
|
332
|
+
syntax: "<length>";
|
|
333
|
+
inherits: false;
|
|
334
|
+
initial-value: 120px;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.A {
|
|
338
|
+
width: var(--A);
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
On a custom property set directly on the element, it registers that property, so you don't have to write the `@property` yourself:
|
|
343
|
+
|
|
344
|
+
```js
|
|
345
|
+
css`
|
|
346
|
+
--brand: var(${color} type(<color>), #f00);
|
|
347
|
+
color: var(--brand);
|
|
348
|
+
transition: --brand 200ms;
|
|
349
|
+
`;
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
```css
|
|
353
|
+
@property --brand {
|
|
354
|
+
syntax: "<color>";
|
|
355
|
+
inherits: false;
|
|
356
|
+
initial-value: red;
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Typed values are larger, and each type and default gets its own variable, so they're opt-in. In return:
|
|
361
|
+
|
|
362
|
+
- A value of the wrong type uses the default, instead of resetting the property.
|
|
363
|
+
- The variable can be transitioned and animated, as in the example above.
|
|
364
|
+
- The default is checked against the type during the build.
|
|
365
|
+
|
|
366
|
+
A typed value must have a default, which can't use `${…}`, `var()`, or anything that depends on where it's used, such as `em`, `rem`, container units, `env()`, or `attr()`: browsers ignore the whole `@property` rule otherwise, so the build fails. Browsers without `@property` (before July 2024) have no default for a typed value until the runtime sets it.
|
|
367
|
+
|
|
368
|
+
#### Animations
|
|
369
|
+
|
|
370
|
+
`@keyframes`, `@position-try`, and `@function` read variables from the element using them, so they can hold values too:
|
|
371
|
+
|
|
372
|
+
```js
|
|
373
|
+
const bar = css`
|
|
374
|
+
@keyframes grow {
|
|
375
|
+
to {
|
|
376
|
+
width: var(${width}px, 200px);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
animation: grow 1s forwards;
|
|
381
|
+
`;
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
The generated class has to be on the element that runs the animation, since variables don't inherit.
|
|
385
|
+
|
|
386
|
+
#### Using `cn`
|
|
387
|
+
|
|
388
|
+
Blocks with values work with `cn`, and the later value wins. A `cn` call with one runs in the browser, so it includes the lookup table:
|
|
389
|
+
|
|
390
|
+
```js
|
|
391
|
+
cn(
|
|
392
|
+
css`width: ${small}px;`,
|
|
393
|
+
css`width: ${large}px;`,
|
|
394
|
+
);
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
A block whose value is `null` or `undefined` adds no class, so it doesn't clear an earlier block's value of the same variable.
|
|
398
|
+
|
|
399
|
+
Use `cn` to combine blocks that set the same declaration, rather than joining their class strings yourself. Blocks with the same declaration share a variable, so with `` `${a} ${b}` `` the value inserted last wins, whichever order the classes are in.
|
|
400
|
+
|
|
401
|
+
#### Where values can go
|
|
402
|
+
|
|
403
|
+
`${…}` becomes a `var()`, so it can only go where `var()` works: in a declaration's value. These fail the build:
|
|
404
|
+
|
|
405
|
+
```js
|
|
406
|
+
css`
|
|
407
|
+
.${name} { color: red; } /* selectors */
|
|
408
|
+
margin-${side}: 0; /* property names */
|
|
409
|
+
@media (min-width: ${bp}px) { top: 0; } /* at-rule conditions, see Sharing media queries */
|
|
410
|
+
@font-face { font-weight: ${weight}; } /* @font-face, @property, @page, and similar */
|
|
411
|
+
content: "${label}"; /* quoted strings */
|
|
412
|
+
background-image: url(${path}); /* url() */
|
|
413
|
+
`;
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
For strings and URLs, pass the whole value:
|
|
417
|
+
|
|
418
|
+
```js
|
|
419
|
+
css`
|
|
420
|
+
content: ${JSON.stringify(label)};
|
|
421
|
+
--image: ${`url("${path}")`};
|
|
422
|
+
background-image: var(--image);
|
|
423
|
+
`;
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Values must be strings or numbers, or `null` or `undefined` to use the default. Anything else, including `false`, `NaN`, and objects, [throws an error](#runtime-errors) when the block runs, so write `${open ? width : null}` rather than `${open && width}`. To combine blocks, use `cn` rather than interpolating one into another.
|
|
427
|
+
|
|
428
|
+
#### Server rendering
|
|
429
|
+
|
|
430
|
+
The server renders the same class strings as the browser, so hydration matches, but it doesn't write the generated rules. The page uses the defaults until the runtime sets the values in the browser, so pick a default close to the usual value to avoid a visible jump. Without a default, the declaration has no effect until then.
|
|
431
|
+
|
|
432
|
+
Values need code that runs in the browser. `${…}` fails the build in HTML, and in the frontmatter and templates of Astro components, where it could never be set (Astro client scripts are fine). React Server Components have the same problem, but the build can't detect it there: use client components for blocks with values.
|
|
433
|
+
|
|
434
|
+
#### Browser support
|
|
435
|
+
|
|
436
|
+
Runtime values work in every [Baseline](https://developer.mozilla.org/en-US/docs/Glossary/Baseline/Compatibility) browser. They need [constructable stylesheets](https://caniuse.com/mdn-api_document_adoptedstylesheets) and [`@property`](https://caniuse.com/mdn-css_at-rules_property), so they fully work from:
|
|
437
|
+
|
|
438
|
+
| Chrome and Edge | Firefox | Safari and iOS Safari | Samsung Internet |
|
|
439
|
+
| --- | --- | --- | --- |
|
|
440
|
+
| 99 | 128 | 16.4 | 18 |
|
|
441
|
+
|
|
442
|
+
In older browsers:
|
|
443
|
+
|
|
444
|
+
- **Without `@property`** (Firefox 101 to 127), values work, but an element with no value of its own takes its parent's instead of its default, and a typed value has no default.
|
|
445
|
+
- **Without constructable stylesheets, or where they can't be added to** (Chrome before 99, Firefox before 101, Safari before 16.4), values aren't set, so the page keeps its defaults.
|
|
446
|
+
|
|
447
|
+
`@keyframes` works wherever these do. `@position-try` is newer: it needs Chrome 125, Firefox 147, or Safari 26. `@function` isn't Baseline: it needs Chrome 139, and isn't yet in Firefox or Safari, so a block using it there keeps the rest of its styles but not the function's.
|
|
448
|
+
|
|
449
|
+
#### When to use `style` instead
|
|
450
|
+
|
|
451
|
+
Every new value adds a rule that's never removed. That suits values with a limited set of results, such as a theme color or a chosen size. For values that change constantly, such as a drag position or anything set per frame, use the `style` attribute and read it from the block:
|
|
452
|
+
|
|
453
|
+
```jsx
|
|
454
|
+
const box = css`
|
|
455
|
+
transform: translateX(var(--x));
|
|
456
|
+
`;
|
|
457
|
+
|
|
458
|
+
<div className={box} style={{ "--x": `${x}px` }} />;
|
|
459
|
+
```
|
|
165
460
|
|
|
166
461
|
### Selectors
|
|
167
462
|
|
|
@@ -208,6 +503,41 @@ By default, keyframes names are global, so two blocks that each define a differe
|
|
|
208
503
|
- Names a block or file doesn't define are left as they are, so `animation-name: spin` still refers to a global `@keyframes spin`, for example one in a CSS file that pre.style doesn't process. That also means a block can't use keyframes another block defines.
|
|
209
504
|
- Names in custom properties (`--anim: spin`) aren't renamed, because pre.style can't tell they name keyframes.
|
|
210
505
|
|
|
506
|
+
#### Local custom properties
|
|
507
|
+
|
|
508
|
+
By default, custom properties are global, so two blocks that each set `--gap` on nested elements affect each other. Set `"customPropertyLocalization": true` in your `package.json`'s `pre.style` field to give each custom property a generated name:
|
|
509
|
+
|
|
510
|
+
```js
|
|
511
|
+
const card = css`
|
|
512
|
+
--gap: 8px;
|
|
513
|
+
padding: var(--gap);
|
|
514
|
+
`;
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
```css
|
|
518
|
+
.A {
|
|
519
|
+
--B: 8px;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.C {
|
|
523
|
+
padding: var(--B);
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
- A name a block or stylesheet file declares (with `--x: …` or `@property --x`) gets a generated name there, including in `var()`, `transition-property`, `will-change`, `@keyframes`, and `@container style()`. In a stylesheet file, every class in the file shares it.
|
|
528
|
+
- Names it only reads are left as they are, so `color: var(--token)` still refers to a global `--token`.
|
|
529
|
+
- Names from [prepended files](#sharing-values-with-prepended-files) are left as they are, even when a block sets them, so they're the way to share custom properties.
|
|
530
|
+
- Other names that start with `--`, such as `anchor-name` and `@custom-media`, aren't custom properties, and aren't renamed.
|
|
531
|
+
- [Runtime values](#custom-properties) follow: `--brand: ${color}` sets the generated name.
|
|
532
|
+
|
|
533
|
+
> [!WARNING]
|
|
534
|
+
> This changes what a block's CSS means, and failures are silent:
|
|
535
|
+
>
|
|
536
|
+
> - `style={{ "--gap": "16px" }}` no longer overrides a block's `--gap`, since the block's name has changed. Neither does `getPropertyValue("--gap")` find it.
|
|
537
|
+
> - A child block can't read a `--gap` its parent block sets. Put a value that's shared between blocks in a prepended file.
|
|
538
|
+
|
|
539
|
+
**It's for avoiding collisions, not for smaller output.** Blocks stop sharing classes for the same declaration, and in our tests that usually made the CSS larger: 50 blocks setting `--gap: 8px` grew by about 170%, gzipped. Long names used once are the exception: they shrink, by about 10% gzipped. Most projects don't need this.
|
|
540
|
+
|
|
211
541
|
### `composes` in blocks
|
|
212
542
|
|
|
213
543
|
A block can build on classes from a [stylesheet file](#stylesheet-files), or on global class names, with `composes`, as in CSS Modules:
|
|
@@ -252,7 +582,7 @@ const className = cn(button, isDanger && danger);
|
|
|
252
582
|
|
|
253
583
|
Two classes conflict when they set the same property under the same at-rules and selector. A later shorthand also removes earlier longhands it covers.
|
|
254
584
|
|
|
255
|
-
When every argument is a block or a string literal, the plugin resolves the call during the build, so it costs nothing at runtime. Other calls use a small lookup table the plugin generates for your project.
|
|
585
|
+
When every argument is a block or a string literal, the plugin resolves the call during the build, so it costs nothing at runtime. Arguments can also name a top-level `const` holding one of these, or holding another call that's resolved this way (`const base = css`…`;` then `cn(base, …)`). Other calls use a small lookup table the plugin generates for your project.
|
|
256
586
|
|
|
257
587
|
> **Note:** `cn` removes `!important` declarations too. A later class that sets the same property wins, even when the earlier one is `!important`.
|
|
258
588
|
|
|
@@ -420,6 +750,45 @@ An array applies the files to every block. An object applies `css` files only to
|
|
|
420
750
|
|
|
421
751
|
Rules the files output, such as `:root { --space: 4px }`, appear in the CSS file once. Keep prepended files to things that don't output CSS, or output it once (variables, mixins, custom properties). Large libraries slow the build down.
|
|
422
752
|
|
|
753
|
+
### Sharing media queries
|
|
754
|
+
|
|
755
|
+
Use Sass variables to share media queries. They need `scss` blocks, and they compile to ordinary `@media` rules:
|
|
756
|
+
|
|
757
|
+
```scss
|
|
758
|
+
// src/styles/_variables.scss, in prependFiles.scss
|
|
759
|
+
$md: 768px;
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
```js
|
|
763
|
+
scss`
|
|
764
|
+
flex-direction: column;
|
|
765
|
+
|
|
766
|
+
@media (min-width: $md) {
|
|
767
|
+
flex-direction: row;
|
|
768
|
+
}
|
|
769
|
+
`;
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
`@custom-media` isn't resolved by pre.style. It's copied to the output as written, and no browser supports it by default yet, so `@media (--md)` rules don't apply. To use it anyway, pass the output CSS through another tool that resolves it, such as another Lightning CSS run:
|
|
773
|
+
|
|
774
|
+
```js
|
|
775
|
+
import { Features, transform } from 'lightningcss';
|
|
776
|
+
|
|
777
|
+
const { code } = transform({
|
|
778
|
+
filename: 'pre.style.css',
|
|
779
|
+
code: Buffer.from(css),
|
|
780
|
+
drafts: { customMedia: true },
|
|
781
|
+
include: Features.CustomMediaQueries,
|
|
782
|
+
});
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
or PostCSS with [`postcss-custom-media`](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-media).
|
|
786
|
+
|
|
787
|
+
Either way, pre.style has already written the file by then, and it treats an alias as its own query:
|
|
788
|
+
|
|
789
|
+
- Aliases aren't sorted with `min-width` queries, so an alias for a wider breakpoint can come before a narrower query, which then wins. Check the order of the output.
|
|
790
|
+
- `cn` doesn't treat `(--md)` and the query it stands for as conflicting, so it keeps classes from both.
|
|
791
|
+
|
|
423
792
|
## Component libraries
|
|
424
793
|
|
|
425
794
|
A component library styled with pre.style ships its blocks uncompiled, and each app's pre.style plugin compiles them along with the app's own. The library's classes share names and the CSS file with the app's, so a `color: red` in the library and one in the app are the same class, and `cn` resolves conflicts between them.
|
|
@@ -471,6 +840,7 @@ A library's class names depend on the whole app, so they differ between apps. Do
|
|
|
471
840
|
| `exclude` | | Globs removed from `include`. |
|
|
472
841
|
| `prependFiles` | | Files added to the start of the library's blocks and stylesheet files, relative to the package. They apply only to the library's, not the app's, so the library's tokens and mixins are always available to it and never leak into the app. |
|
|
473
842
|
| `keyframeLocalization` | `false` | Give each `@keyframes` in the library's blocks and stylesheet files a generated name. See [Local keyframes](#local-keyframes). The library's setting applies to its blocks, whatever the app sets, since the library's styles are written for it. |
|
|
843
|
+
| `customPropertyLocalization` | `false` | Give each custom property declared in the library's blocks and stylesheet files a generated name, used only there. See [Local custom properties](#local-custom-properties). |
|
|
474
844
|
|
|
475
845
|
`"pre.style": true` uses the defaults. A library's field uses the same name and shape as an app's (see [Configuration](#configuration)). `libraries` and `quirksMode` only apply to the app, so in a library's field they're ignored, with a warning. A package that is both, such as a library with its own demo app, is read as the app when it's the Vite root, and as a library when it's a dependency.
|
|
476
846
|
|
|
@@ -568,6 +938,7 @@ Options every tool that reads your styles has to agree on (the bundler plugin, a
|
|
|
568
938
|
| `libraries` | | Component libraries to compile, by package name, in addition to dependencies with a `pre.style` field. A config replaces the package's field, `true` uses the defaults, and `false` leaves the package out. See [Component libraries](#component-libraries). |
|
|
569
939
|
| `quirksMode` | `false` | **You probably don't want this.** Use lowercase class names only, for pages in quirks mode. Names get longer. See [Quirks mode](#quirks-mode). |
|
|
570
940
|
| `keyframeLocalization` | `false` | Give each `@keyframes` in the app's blocks and stylesheet files a generated name, used only in the block or file that defines it. See [Local keyframes](#local-keyframes). Libraries use their own setting. |
|
|
941
|
+
| `customPropertyLocalization` | `false` | Give each custom property declared in the app's blocks and stylesheet files a generated name, used only there. See [Local custom properties](#local-custom-properties). |
|
|
571
942
|
|
|
572
943
|
The plugin warns about keys it doesn't know, and fails the build on values of the wrong type. For completion and validation in your editor, `@pre.style/core` includes a JSON Schema for the field, at `node_modules/@pre.style/core/package.schema.json`. In VS Code, add it to your settings:
|
|
573
944
|
|
|
@@ -608,7 +979,7 @@ preStyle({
|
|
|
608
979
|
| Option | Default | Description |
|
|
609
980
|
| --------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
610
981
|
| `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. |
|
|
611
|
-
| `sourceMap` | `true` in dev, `false` in builds | Generate a source map for the CSS, mapping each class to the first block it appears in. 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. |
|
|
982
|
+
| `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. |
|
|
612
983
|
| `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. |
|
|
613
984
|
| `quotes` | `'double-quote'` | Quotes around the class string that replaces each block in JS: `'single-quote'`, `'double-quote'`, or `'backtick'`. |
|
|
614
985
|
| `output.file` | `'pre.style.css'` | Path and name of the CSS file, relative to Vite's `build.outDir`. |
|
|
@@ -619,6 +990,14 @@ The CSS isn't minified unless you set `lightningcss: { minify: true }`.
|
|
|
619
990
|
|
|
620
991
|
Before 3.1, `include`, `exclude`, `prependFiles`, `libraries`, and `quirksMode` were plugin options. Passing them to the plugin now fails with a message pointing to `package.json`.
|
|
621
992
|
|
|
993
|
+
### Tracing classes with the DevTools extension
|
|
994
|
+
|
|
995
|
+
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.
|
|
996
|
+
|
|
997
|
+
It works with the Vite dev server while `sourceMap` is on, which is the default in dev. Setting `sourceMap: false` turns it off. It only runs on `localhost`, `*.localhost`, loopback addresses, and hostnames you add in its settings, and sends other sites no requests.
|
|
998
|
+
|
|
999
|
+
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`.
|
|
1000
|
+
|
|
622
1001
|
### Quirks mode
|
|
623
1002
|
|
|
624
1003
|
> [!WARNING]
|
|
@@ -642,12 +1021,21 @@ The build fails, with the file, line, and column, when:
|
|
|
642
1021
|
|
|
643
1022
|
- a block has invalid CSS syntax;
|
|
644
1023
|
- Sass fails to compile an `scss` block;
|
|
645
|
-
- a block
|
|
1024
|
+
- a block has `${…}` anywhere but a declaration's value, or in HTML or an Astro component's frontmatter or template (see [Where values can go](#where-values-can-go) and [Server rendering](#server-rendering));
|
|
1025
|
+
- a `type()` has no default, or its default is invalid for the type or depends on where it's used;
|
|
646
1026
|
- `css` or `scss` is used other than as a tagged template;
|
|
647
1027
|
- a project has an `scss` block or file but Sass isn't installed;
|
|
648
1028
|
- a stylesheet file has invalid CSS, or fails to compile with Sass;
|
|
649
1029
|
- `composes` comes after other declarations, is used in a nested selector or at-rule, names a class or file that doesn't exist, or forms a cycle.
|
|
650
1030
|
|
|
1031
|
+
### Runtime errors
|
|
1032
|
+
|
|
1033
|
+
[Runtime values](#runtime-values) are checked in the browser and on the server. Outside production builds, errors have full messages. To keep the runtime small, production builds only have a number, the value's type, and a link to [a page that explains it](https://pre.style/errors):
|
|
1034
|
+
|
|
1035
|
+
| Error | Meaning |
|
|
1036
|
+
| --- | --- |
|
|
1037
|
+
| [`pre.style error 1 (<type>)`](https://pre.style/errors/1) | A `${…}` value wasn't a string or a finite number, or `null` or `undefined` to use the default. `number` means it was `NaN` or `Infinity`. For a condition, write `${open ? width : null}` rather than `${open && width}`. |
|
|
1038
|
+
|
|
651
1039
|
## Known limitations
|
|
652
1040
|
|
|
653
1041
|
- `@charset` is dropped. Lightning CSS always writes UTF-8.
|
|
@@ -668,6 +1056,7 @@ The build fails, with the file, line, and column, when:
|
|
|
668
1056
|
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
669
1057
|
| `@pre.style/core` | `css`, `scss`, and `cn`, the compiler at `@pre.style/core/compiler` for bundler plugins, TypeScript's content mapper for stylesheet files, and the JSON Schema for the `package.json` field. |
|
|
670
1058
|
| `@pre.style/vite` | The Vite plugin. |
|
|
1059
|
+
| [pre.style DevTools](packages/devtools) | A side pane for the Elements panel in Chrome and Firefox that lists every block using each class. Not published to the extension stores yet. |
|
|
671
1060
|
|
|
672
1061
|
## Development
|
|
673
1062
|
|
package/dist/cn.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type CnTable = Record<string, Record<string, string>>;
|
|
2
|
+
export type CnLookup = Map<string, readonly [prefix: string, property: string]>;
|
|
2
3
|
export type ClassValue = string | number | bigint | boolean | null | undefined | ClassDictionary | ClassValue[];
|
|
3
4
|
export type ClassDictionary = Record<string, unknown>;
|
|
4
|
-
export declare function createCn(table: CnTable): (...inputs: ClassValue[]) => string;
|
|
5
|
+
export declare function createCn(table: CnTable, lookup?: CnLookup): (...inputs: ClassValue[]) => string;
|
package/dist/cn.js
CHANGED
|
@@ -45,8 +45,10 @@ function resolve(classes, lookup) {
|
|
|
45
45
|
}
|
|
46
46
|
return kept.toReversed();
|
|
47
47
|
}
|
|
48
|
-
export function createCn(table
|
|
49
|
-
|
|
48
|
+
export function createCn(table,
|
|
49
|
+
// Filled from the table. `generatedClasses` passes one that also knows
|
|
50
|
+
// classes generated at runtime.
|
|
51
|
+
lookup = new Map()) {
|
|
50
52
|
for (const [prefix, properties] of Object.entries(table))
|
|
51
53
|
for (const [property, classes] of Object.entries(properties))
|
|
52
54
|
for (const c of classes.split(' '))
|
package/dist/compiler/block.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Declaration, Rule, SelectorList, TransformOptions, CustomAtRules } from 'lightningcss';
|
|
2
|
+
import type { Variable } from './variables.ts';
|
|
2
3
|
export type Lang = 'css' | 'scss';
|
|
3
4
|
export type LightningOptions = Omit<TransformOptions<CustomAtRules>, 'filename' | 'code' | 'sourceMap' | 'inputSourceMap' | 'projectRoot' | 'cssModules'>;
|
|
4
5
|
export interface Position {
|
|
@@ -47,6 +48,7 @@ export interface ParsedBlock {
|
|
|
47
48
|
atoms: Atom[];
|
|
48
49
|
globals: GlobalRule[];
|
|
49
50
|
composes: Compose[];
|
|
51
|
+
variables?: Variable[];
|
|
50
52
|
}
|
|
51
53
|
export declare class PreStyleError extends Error {
|
|
52
54
|
readonly position: Position | null;
|
|
@@ -70,6 +72,7 @@ export interface ParseOptions {
|
|
|
70
72
|
sass: SassCompiler | null;
|
|
71
73
|
warn: (message: string) => void;
|
|
72
74
|
keyframeLocalization: boolean;
|
|
75
|
+
customPropertyLocalization: boolean;
|
|
73
76
|
}
|
|
74
77
|
export declare const key: (value: unknown) => string;
|
|
75
78
|
export declare function parseBlock(code: string, lang: Lang, options: ParseOptions): ParsedBlock;
|
package/dist/compiler/block.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { composeVisitors, transform } from 'lightningcss';
|
|
2
2
|
import { atRuleInfo } from './at-rules.js';
|
|
3
3
|
import { expand } from './expand.js';
|
|
4
|
+
import { localizeCustomProperties } from './custom-properties.js';
|
|
5
|
+
import { SLOT_PREFIX } from './interpolate.js';
|
|
4
6
|
import { localizeKeyframes } from './keyframes.js';
|
|
5
7
|
import { compose, fromPlaceholder, isElement, PLACEHOLDER, repair } from './selectors.js';
|
|
6
8
|
import { splitBlock } from './split.js';
|
|
9
|
+
import { extractVariables } from './variables.js';
|
|
7
10
|
import { SHORTHANDS } from '../shorthands.js';
|
|
8
11
|
export class PreStyleError extends Error {
|
|
9
12
|
position;
|
|
@@ -83,11 +86,21 @@ export function parseBlock(code, lang, options) {
|
|
|
83
86
|
// Sass output positions don't match the block, so scss atoms have none.
|
|
84
87
|
const position = (loc) => lang === 'css' && loc ? toBlock(loc.line, loc.column) : null;
|
|
85
88
|
parseRules(css, options.lightningcss, position, (rules) => walk(rules, [], null, parsed, position));
|
|
86
|
-
|
|
89
|
+
let atoms = dropOverridden(parsed.atoms);
|
|
90
|
+
let { globals } = parsed;
|
|
91
|
+
if (options.customPropertyLocalization) {
|
|
92
|
+
const local = localizeCustomProperties(atoms, globals, `block\n${code}`, lang, options);
|
|
93
|
+
atoms = local.atoms(atoms);
|
|
94
|
+
({ globals } = local);
|
|
95
|
+
}
|
|
96
|
+
let variables;
|
|
97
|
+
if (code.includes(SLOT_PREFIX))
|
|
98
|
+
({ atoms, globals, variables } = extractVariables(atoms, globals));
|
|
99
|
+
const composes = parsed.composes;
|
|
87
100
|
if (!options.keyframeLocalization)
|
|
88
|
-
return { atoms, globals
|
|
89
|
-
const local = localizeKeyframes(
|
|
90
|
-
return { atoms: local.atoms(atoms), globals: local.globals, composes
|
|
101
|
+
return { atoms, globals, composes, variables };
|
|
102
|
+
const local = localizeKeyframes(globals);
|
|
103
|
+
return { atoms: local.atoms(atoms), globals: local.globals, composes, variables };
|
|
91
104
|
}
|
|
92
105
|
// Parses CSS with Lightning CSS, and passes its rules to `collect` once the
|
|
93
106
|
// user's visitor has changed them. `position` converts error locations.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Lang, LightningOptions, SassCompiler } from './block.ts';
|
|
2
|
+
import type { Slot } from './interpolate.ts';
|
|
2
3
|
import type { Printed } from './print.ts';
|
|
3
4
|
import type { CnTable } from '../cn.ts';
|
|
5
|
+
import type { CnVariables } from '../runtime.ts';
|
|
4
6
|
export type PrependFiles = string[] | {
|
|
5
7
|
css?: string[];
|
|
6
8
|
scss?: string[];
|
|
@@ -9,6 +11,7 @@ export interface Library {
|
|
|
9
11
|
dir: string;
|
|
10
12
|
prependFiles?: PrependFiles;
|
|
11
13
|
keyframeLocalization?: boolean;
|
|
14
|
+
customPropertyLocalization?: boolean;
|
|
12
15
|
}
|
|
13
16
|
export interface CompilerOptions {
|
|
14
17
|
prependFiles?: PrependFiles;
|
|
@@ -19,6 +22,7 @@ export interface CompilerOptions {
|
|
|
19
22
|
warn?: (message: string) => void;
|
|
20
23
|
quirksMode?: boolean;
|
|
21
24
|
keyframeLocalization?: boolean;
|
|
25
|
+
customPropertyLocalization?: boolean;
|
|
22
26
|
layer?: false | string;
|
|
23
27
|
}
|
|
24
28
|
export interface Block {
|
|
@@ -27,6 +31,13 @@ export interface Block {
|
|
|
27
31
|
file?: string;
|
|
28
32
|
line?: number;
|
|
29
33
|
column?: number;
|
|
34
|
+
slots?: Slot[];
|
|
35
|
+
}
|
|
36
|
+
export interface BlockVariable {
|
|
37
|
+
name: string;
|
|
38
|
+
property: string;
|
|
39
|
+
slot: Slot;
|
|
40
|
+
selector: string[] | null;
|
|
30
41
|
}
|
|
31
42
|
export interface Stylesheet {
|
|
32
43
|
code: string;
|
|
@@ -37,6 +48,27 @@ export interface CompiledStylesheet {
|
|
|
37
48
|
exports: [name: string, classString: string][];
|
|
38
49
|
dependencies: string[];
|
|
39
50
|
}
|
|
51
|
+
export interface Source {
|
|
52
|
+
file: string;
|
|
53
|
+
line: number;
|
|
54
|
+
column: number;
|
|
55
|
+
}
|
|
56
|
+
export interface ClassUsage {
|
|
57
|
+
className: string;
|
|
58
|
+
selector: string;
|
|
59
|
+
atRules: string[];
|
|
60
|
+
declaration: string;
|
|
61
|
+
property: string;
|
|
62
|
+
covers: string[];
|
|
63
|
+
important: boolean;
|
|
64
|
+
specificity: [ids: number, classes: number, types: number];
|
|
65
|
+
order: number;
|
|
66
|
+
sources: Source[];
|
|
67
|
+
}
|
|
68
|
+
export declare function interpolatedBlock(block: Omit<Block, 'code' | 'slots'> & {
|
|
69
|
+
strings: string[];
|
|
70
|
+
expressions: string[];
|
|
71
|
+
}): Block;
|
|
40
72
|
export declare function createCompiler(options?: CompilerOptions): Promise<Compiler>;
|
|
41
73
|
export declare class Compiler {
|
|
42
74
|
#private;
|
|
@@ -46,9 +78,13 @@ export declare class Compiler {
|
|
|
46
78
|
scanStylesheet(sheet: Stylesheet): void;
|
|
47
79
|
assignNames(): void;
|
|
48
80
|
classString(block: Block): string;
|
|
81
|
+
variables(block: Block): BlockVariable[];
|
|
49
82
|
dependencies(block: Block): string[];
|
|
50
83
|
classNames(sheet: Stylesheet): string[];
|
|
84
|
+
usages(classNames: Iterable<string>): ClassUsage[];
|
|
85
|
+
forgetSources(file: string): void;
|
|
51
86
|
stylesheet(sheet: Stylesheet): CompiledStylesheet;
|
|
52
87
|
css(): Printed;
|
|
53
88
|
cnTable(): CnTable;
|
|
89
|
+
cnVariables(): CnVariables;
|
|
54
90
|
}
|