@pre.style/core 3.0.0-alpha.0 → 3.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +332 -76
- package/dist/compiler/block.d.ts +30 -0
- package/dist/compiler/block.js +86 -26
- package/dist/compiler/compiler.d.ts +16 -0
- package/dist/compiler/compiler.js +294 -25
- package/dist/compiler/config.d.ts +16 -0
- package/dist/compiler/config.js +61 -0
- package/dist/compiler/index.d.ts +6 -1
- package/dist/compiler/index.js +3 -0
- package/dist/compiler/keyframes.d.ts +6 -0
- package/dist/compiler/keyframes.js +120 -0
- package/dist/compiler/libraries.d.ts +16 -0
- package/dist/compiler/libraries.js +76 -0
- package/dist/compiler/mapper.d.ts +1 -0
- package/dist/compiler/mapper.js +165 -0
- package/dist/compiler/stylesheet.d.ts +15 -0
- package/dist/compiler/stylesheet.js +244 -0
- package/package.json +15 -3
- package/package.schema.json +117 -0
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# pre.style
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package
|
|
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
|
|
|
7
7
|
```js
|
|
8
|
-
import { css } from
|
|
8
|
+
import { css } from "@pre.style/core";
|
|
9
9
|
|
|
10
10
|
const button = css`
|
|
11
11
|
background-color: #0071ba;
|
|
@@ -25,14 +25,28 @@ const button = css`
|
|
|
25
25
|
After the build, `button` is a plain string, `"B C E D A G F"`. The block is replaced, and these classes are added to a single CSS file:
|
|
26
26
|
|
|
27
27
|
```css
|
|
28
|
-
.B{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.
|
|
32
|
-
|
|
28
|
+
.B {
|
|
29
|
+
background-color: #0071ba;
|
|
30
|
+
}
|
|
31
|
+
.C {
|
|
32
|
+
color: #fff;
|
|
33
|
+
}
|
|
34
|
+
.E {
|
|
35
|
+
padding-block-start: 0.5em;
|
|
36
|
+
}
|
|
37
|
+
.D {
|
|
38
|
+
padding-block-end: 0.5em;
|
|
39
|
+
}
|
|
40
|
+
.A:hover {
|
|
41
|
+
background-color: #005a94;
|
|
42
|
+
}
|
|
33
43
|
@media (width>=768px) {
|
|
34
|
-
.G{
|
|
35
|
-
|
|
44
|
+
.G {
|
|
45
|
+
padding-block-start: 1em;
|
|
46
|
+
}
|
|
47
|
+
.F {
|
|
48
|
+
padding-block-end: 1em;
|
|
49
|
+
}
|
|
36
50
|
}
|
|
37
51
|
```
|
|
38
52
|
|
|
@@ -59,8 +73,8 @@ npm install --save-dev sass
|
|
|
59
73
|
Add the plugin to `vite.config.ts`:
|
|
60
74
|
|
|
61
75
|
```ts
|
|
62
|
-
import { defineConfig } from
|
|
63
|
-
import preStyle from
|
|
76
|
+
import { defineConfig } from "vite";
|
|
77
|
+
import preStyle from "@pre.style/vite";
|
|
64
78
|
|
|
65
79
|
export default defineConfig({
|
|
66
80
|
plugins: [preStyle()],
|
|
@@ -70,7 +84,7 @@ export default defineConfig({
|
|
|
70
84
|
Then import `css` (or `scss`) from `@pre.style/core` and use the class strings:
|
|
71
85
|
|
|
72
86
|
```jsx
|
|
73
|
-
import { css } from
|
|
87
|
+
import { css } from "@pre.style/core";
|
|
74
88
|
|
|
75
89
|
export function Card({ children }) {
|
|
76
90
|
return (
|
|
@@ -88,12 +102,14 @@ export function Card({ children }) {
|
|
|
88
102
|
|
|
89
103
|
In a build, the plugin writes `pre.style.css` to your output directory and links it from `index.html`. In dev, the CSS is served by Vite and updates as you edit.
|
|
90
104
|
|
|
105
|
+
You can also write styles in `.css` and `.scss` files and import their classes, like CSS Modules. See [Stylesheet files](#stylesheet-files).
|
|
106
|
+
|
|
91
107
|
### Frameworks without `index.html`
|
|
92
108
|
|
|
93
109
|
Frameworks that render on the server, such as React Router's framework mode, don't use `index.html`. Import pre.style's CSS in your root module instead:
|
|
94
110
|
|
|
95
111
|
```ts
|
|
96
|
-
import
|
|
112
|
+
import "virtual:pre.style.css";
|
|
97
113
|
```
|
|
98
114
|
|
|
99
115
|
Vite then bundles the CSS with your app's other CSS, and the framework links it when rendering on the server, both in builds and in dev. The plugin doesn't write `output.file` when the CSS is imported this way.
|
|
@@ -104,8 +120,8 @@ Add the plugin to Astro's Vite config, and import the CSS in your layout's front
|
|
|
104
120
|
|
|
105
121
|
```js
|
|
106
122
|
// astro.config.mjs
|
|
107
|
-
import { defineConfig } from
|
|
108
|
-
import preStyle from
|
|
123
|
+
import { defineConfig } from "astro/config";
|
|
124
|
+
import preStyle from "@pre.style/vite";
|
|
109
125
|
|
|
110
126
|
export default defineConfig({
|
|
111
127
|
vite: { plugins: [preStyle()] },
|
|
@@ -125,7 +141,7 @@ const title = css`
|
|
|
125
141
|
<h1 class={title}>Hello</h1>
|
|
126
142
|
```
|
|
127
143
|
|
|
128
|
-
Blocks work in frontmatter, in template expressions such as ``
|
|
144
|
+
Blocks work in frontmatter, in template expressions such as ``class={css`…`}``, in `<script>` tags, and in the islands of any framework. Astro links the CSS on every page that uses the layout, prerendered or rendered on demand. Astro compiles components before the plugin sees them, but errors and source maps still point to the component.
|
|
129
145
|
|
|
130
146
|
## Writing blocks
|
|
131
147
|
|
|
@@ -136,7 +152,7 @@ A block styles one element. Each block is treated as its own small stylesheet, w
|
|
|
136
152
|
`css` blocks are plain CSS. `scss` blocks are compiled with Sass first, so you can use variables, mixins, functions, and everything else Sass supports:
|
|
137
153
|
|
|
138
154
|
```js
|
|
139
|
-
import { scss } from
|
|
155
|
+
import { scss } from "@pre.style/core";
|
|
140
156
|
|
|
141
157
|
const title = scss`
|
|
142
158
|
$size: 2rem;
|
|
@@ -159,7 +175,8 @@ css`
|
|
|
159
175
|
&::before { content: '→'; }
|
|
160
176
|
& > * + * { margin-top: 1rem; }
|
|
161
177
|
.dark & { color: white; }
|
|
162
|
-
a { text-decoration: underline; } /* the same as
|
|
178
|
+
a { text-decoration: underline; } /* the same as ` &
|
|
179
|
+
a` */
|
|
163
180
|
`;
|
|
164
181
|
```
|
|
165
182
|
|
|
@@ -172,13 +189,39 @@ Other at-rules, such as `@keyframes`, `@font-face`, and `@property`, are copied
|
|
|
172
189
|
```js
|
|
173
190
|
css`
|
|
174
191
|
@keyframes fade-in {
|
|
175
|
-
from {
|
|
192
|
+
from {
|
|
193
|
+
opacity: 0;
|
|
194
|
+
}
|
|
176
195
|
}
|
|
177
196
|
|
|
178
197
|
animation: fade-in 200ms ease-out;
|
|
179
198
|
`;
|
|
180
199
|
```
|
|
181
200
|
|
|
201
|
+
#### Local keyframes
|
|
202
|
+
|
|
203
|
+
By default, keyframes names are global, so two blocks that each define a different `@keyframes spin` collide, and the last one wins everywhere. Set `"keyframeLocalization": true` in your `package.json`'s `pre.style` field to give each `@keyframes` a generated name, as styled-components' `keyframes` helper does:
|
|
204
|
+
|
|
205
|
+
- A block's or stylesheet file's `@keyframes` get a short generated name, and its `animation` and `animation-name` declarations use it. In a stylesheet file, every class in the file shares the file's keyframes, as in CSS Modules.
|
|
206
|
+
- Keyframes with the same contents get the same name wherever they're defined, so they're written once.
|
|
207
|
+
- Keyframes from [prepended files](#sharing-values-with-prepended-files) are localized too. Every block gets the same copy, so prepended files are the way to share keyframes between blocks.
|
|
208
|
+
- 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
|
+
- Names in custom properties (`--anim: spin`) aren't renamed, because pre.style can't tell they name keyframes.
|
|
210
|
+
|
|
211
|
+
### `composes` in blocks
|
|
212
|
+
|
|
213
|
+
A block can build on classes from a [stylesheet file](#stylesheet-files), or on global class names, with `composes`, as in CSS Modules:
|
|
214
|
+
|
|
215
|
+
```js
|
|
216
|
+
css`
|
|
217
|
+
composes: button from "./button.css";
|
|
218
|
+
composes: legacy-btn from global;
|
|
219
|
+
color: white;
|
|
220
|
+
`;
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The block's class string then includes the composed classes, and its own declarations replace the composed ones they conflict with, as `cn` would. `composes` must come before the block's other declarations, and can't be used inside nested selectors or at-rules.
|
|
224
|
+
|
|
182
225
|
### In HTML
|
|
183
226
|
|
|
184
227
|
HTML can't import anything, so blocks in HTML files that Vite processes, such as `index.html`, are found by matching `` css` `` and `` scss` ``:
|
|
@@ -192,7 +235,7 @@ HTML can't import anything, so blocks in HTML files that Vite processes, such as
|
|
|
192
235
|
Atomic classes all have the same specificity, so when two classes on one element set the same property, the order of the CSS file decides which wins, not the order in your `class` attribute. Use `cn` to combine class strings: when two classes conflict, it keeps the later one.
|
|
193
236
|
|
|
194
237
|
```js
|
|
195
|
-
import { css, cn } from
|
|
238
|
+
import { css, cn } from "@pre.style/core";
|
|
196
239
|
|
|
197
240
|
const button = css`
|
|
198
241
|
background-color: #0071ba;
|
|
@@ -213,20 +256,167 @@ When every argument is a block or a string literal, the plugin resolves the call
|
|
|
213
256
|
|
|
214
257
|
> **Note:** `cn` removes `!important` declarations too. A later class that sets the same property wins, even when the earlier one is `!important`.
|
|
215
258
|
|
|
216
|
-
##
|
|
259
|
+
## Stylesheet files
|
|
260
|
+
|
|
261
|
+
Styles can also live in `.css` and `.scss` files, imported with the `pre.style` import attribute. Each class in the file is exported with its class string, like [CSS Modules](https://github.com/css-modules/css-modules):
|
|
262
|
+
|
|
263
|
+
```css
|
|
264
|
+
/* button.css */
|
|
265
|
+
.button {
|
|
266
|
+
background-color: #0071ba;
|
|
267
|
+
color: white;
|
|
268
|
+
|
|
269
|
+
&:hover {
|
|
270
|
+
background-color: #005a94;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.primary-button {
|
|
275
|
+
composes: button;
|
|
276
|
+
font-weight: 600;
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```jsx
|
|
281
|
+
import {
|
|
282
|
+
button,
|
|
283
|
+
"primary-button" as primaryButton,
|
|
284
|
+
} from "./button.css" with { type: "pre.style" };
|
|
285
|
+
|
|
286
|
+
<button className={primaryButton}>Save</button>;
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
`.css` files are compiled like `css` blocks, and `.scss` files like `scss` blocks, into the same atomic classes and the same CSS file. Each export is a plain string constant, so bundlers drop the ones you don't use. A class that isn't a valid identifier (`primary-button`) is exported by its string name, so import it with `as`. Only imports with `with { type: 'pre.style' }` are compiled this way; plain `import './global.css'` and `.module.css` imports work as usual. Your `tsconfig.json` needs `"module"` set to `"esnext"`, `"preserve"`, or a `"node*"` setting for TypeScript to allow the attribute.
|
|
290
|
+
|
|
291
|
+
How a file becomes classes:
|
|
292
|
+
|
|
293
|
+
- **Each class is a block.** A class's rules, wherever they are in the file, are compiled as one block with the class replaced by `&`. `.button:hover` is `&:hover`, `@media (…) { .button { … } }` is an at-rule class of `button`, and a later rule for the class overrides an earlier one, as in plain CSS.
|
|
294
|
+
- **A selector belongs to the class it styles:** the first class in the rightmost part of the selector that has one. `.card .title` belongs to `title`, and `.list > li` to `list` (as `& > li`).
|
|
295
|
+
- **Selector lists** (`.a, .b { … }`) apply the declarations to each class.
|
|
296
|
+
- **Rules without a class,** such as `:root`, `body`, `@keyframes`, and `@font-face`, are written to the CSS file as they are. So are `@import` rules, as in blocks.
|
|
297
|
+
- **Empty classes** (`.empty {}`) are exported as `''`.
|
|
298
|
+
- **Everything is global.** There's no `:global()` or `:local()`. Classes that aren't exported from the file, such as `.dark` in `.dark .button`, are written as they are, so you add them to elements yourself.
|
|
299
|
+
- **Classes from prepended files aren't exported.** Their rules are written as they are.
|
|
300
|
+
- **Every class in an imported file is written to the CSS file,** even the ones you don't use, as with CSS Modules.
|
|
301
|
+
|
|
302
|
+
In `.scss` files, relative `@use`, `@forward`, and `@import` resolve from the file, and [prepended files](#sharing-values-with-prepended-files) work as they do in blocks. Classes a prepended mixin writes into the file are the file's own.
|
|
303
|
+
|
|
304
|
+
### Classes used as context
|
|
305
|
+
|
|
306
|
+
Atomic classes are shared, so no single class in a class string identifies one export. When an exported class is used as context for another, as `card` is in `.card:hover .title`, pre.style gives it a **marker**: one extra class, added to `card`'s class string, that the selector uses.
|
|
307
|
+
|
|
308
|
+
```css
|
|
309
|
+
.card {
|
|
310
|
+
padding-top: 1rem;
|
|
311
|
+
}
|
|
312
|
+
.title {
|
|
313
|
+
color: black;
|
|
314
|
+
}
|
|
315
|
+
.card:hover .title {
|
|
316
|
+
color: red;
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
```css
|
|
321
|
+
.D {
|
|
322
|
+
padding-top: 1rem;
|
|
323
|
+
}
|
|
324
|
+
.C {
|
|
325
|
+
color: #000;
|
|
326
|
+
}
|
|
327
|
+
.A:hover .B {
|
|
328
|
+
color: red;
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
`card` is `"D A"`, where `A` is its marker, and `title` is `"C B"`.
|
|
333
|
+
|
|
334
|
+
> [!IMPORTANT]
|
|
335
|
+
> **Watch out for compound selectors.** In `.a.b { … }`, the first class, `a`, gets the declarations, and `b` becomes a marker. `b`'s export is only its marker, so `b` on its own applies none of those styles: they apply to elements with both `a` and `b`. Prefer separate classes, or a selector variant on one class (`.a { &.is-open { … } }`), when you can.
|
|
336
|
+
|
|
337
|
+
### `composes` in stylesheet files
|
|
338
|
+
|
|
339
|
+
`composes` works as in CSS Modules:
|
|
340
|
+
|
|
341
|
+
```css
|
|
342
|
+
.base {
|
|
343
|
+
color: gray;
|
|
344
|
+
padding: 1rem;
|
|
345
|
+
}
|
|
346
|
+
.primary {
|
|
347
|
+
composes: base;
|
|
348
|
+
color: blue;
|
|
349
|
+
}
|
|
350
|
+
.wide {
|
|
351
|
+
composes: card from "./card.css";
|
|
352
|
+
}
|
|
353
|
+
.legacy {
|
|
354
|
+
composes: old-btn from global;
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
> [!IMPORTANT]
|
|
359
|
+
> **`composes` resolves conflicts, unlike CSS Modules.** CSS Modules only joins class names. pre.style resolves `composes` like `cn(composed, own)`: the class's own declarations replace the composed ones they conflict with, and those are dropped from the class string. Above, `primary` has `color: blue` and not `color: gray`. As in CSS Modules, `composes` must come before the rule's other declarations, can't be used in nested selectors or at-rules, and fails the build if classes compose each other in a cycle.
|
|
217
360
|
|
|
218
|
-
|
|
361
|
+
Classes composed `from global` are added to the class string as they are. Composed classes keep their markers.
|
|
362
|
+
|
|
363
|
+
### Using exports with `cn`
|
|
364
|
+
|
|
365
|
+
Stylesheet exports are plain class strings, so they work with `cn`. Calls whose arguments are all blocks, string literals, or stylesheet exports (named imports, or members of a namespace import such as `styles.button`) are resolved during the build.
|
|
366
|
+
|
|
367
|
+
### Types
|
|
368
|
+
|
|
369
|
+
TypeScript 7.1 adds [content mappers](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/), which `@pre.style/core` provides, so named imports are typed with each file's exact classes. Register it in `tsconfig.json`:
|
|
370
|
+
|
|
371
|
+
```json
|
|
372
|
+
{
|
|
373
|
+
"contentMappers": [
|
|
374
|
+
{ "package": "@pre.style/core", "extensions": [".css", ".scss"] }
|
|
375
|
+
]
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Content mappers run code from a package, so `tsc` needs the `--runExternalCode` flag, and editors need the workspace to be trusted. Mappers are chosen by file extension, so every `.css` and `.scss` file in the program gets pre.style's types, including ones imported without the attribute.
|
|
380
|
+
|
|
381
|
+
Before TypeScript 7.1, add this declaration to your project instead (for example as `src/stylesheets.d.ts`):
|
|
219
382
|
|
|
220
383
|
```ts
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
384
|
+
declare module "*.css" {
|
|
385
|
+
const classes: { readonly [name: string]: string };
|
|
386
|
+
export = classes;
|
|
387
|
+
}
|
|
388
|
+
declare module "*.scss" {
|
|
389
|
+
const classes: { readonly [name: string]: string };
|
|
390
|
+
export = classes;
|
|
391
|
+
}
|
|
227
392
|
```
|
|
228
393
|
|
|
229
|
-
|
|
394
|
+
TypeScript can't type named imports from it, so use a namespace import:
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
import * as styles from "./button.css" with { type: "pre.style" };
|
|
398
|
+
|
|
399
|
+
styles.button;
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Namespace imports still let bundlers drop the classes you don't use. **Remove the declaration when you register the content mapper:** TypeScript prefers it over the mapper.
|
|
403
|
+
|
|
404
|
+
## Sharing values with prepended files
|
|
405
|
+
|
|
406
|
+
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.
|
|
407
|
+
|
|
408
|
+
```json
|
|
409
|
+
{
|
|
410
|
+
"pre.style": {
|
|
411
|
+
"prependFiles": {
|
|
412
|
+
"css": ["src/styles/tokens.css"],
|
|
413
|
+
"scss": ["src/styles/_variables.scss", "src/styles/_mixins.scss"]
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
An array applies the files to every block. An object applies `css` files only to `css` blocks and `.css` files, and `scss` files only to `scss` blocks and `.scss` files. Paths are relative to the `package.json`.
|
|
230
420
|
|
|
231
421
|
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.
|
|
232
422
|
|
|
@@ -238,15 +428,17 @@ A component library styled with pre.style ships its blocks uncompiled, and each
|
|
|
238
428
|
|
|
239
429
|
Install it. The plugin finds dependencies with a `pre.style` field in their `package.json` (and the libraries those depend on) and compiles them. There's nothing to configure.
|
|
240
430
|
|
|
241
|
-
To compile a package that doesn't have the field, or to change how one is compiled, use the `libraries`
|
|
431
|
+
To compile a package that doesn't have the field, or to change how one is compiled, use the `libraries` setting in your `package.json`'s `pre.style` field. `true` uses the defaults, and `false` leaves a package out.
|
|
242
432
|
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
433
|
+
```json
|
|
434
|
+
{
|
|
435
|
+
"pre.style": {
|
|
436
|
+
"libraries": {
|
|
437
|
+
"legacy-ui": { "include": "dist/**/*.js" },
|
|
438
|
+
"some-ui": false
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
250
442
|
```
|
|
251
443
|
|
|
252
444
|
A library's class names depend on the whole app, so they differ between apps. Don't depend on them.
|
|
@@ -273,12 +465,14 @@ A library's class names depend on the whole app, so they differ between apps. Do
|
|
|
273
465
|
}
|
|
274
466
|
```
|
|
275
467
|
|
|
276
|
-
| Field
|
|
277
|
-
|
|
|
278
|
-
| `include`
|
|
279
|
-
| `
|
|
468
|
+
| Field | Default | Description |
|
|
469
|
+
| ---------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
470
|
+
| `include` | Every JS, TS, and Astro file | Globs of the files with blocks, relative to the package. A glob or an array of them. Stylesheet files the library imports are found from its imports. |
|
|
471
|
+
| `exclude` | | Globs removed from `include`. |
|
|
472
|
+
| `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
|
+
| `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. |
|
|
280
474
|
|
|
281
|
-
`"pre.style": true` uses the defaults.
|
|
475
|
+
`"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.
|
|
282
476
|
|
|
283
477
|
To let apps use the library's tokens in their own blocks, export the token files, and apps can add them to their own `prependFiles`.
|
|
284
478
|
|
|
@@ -299,20 +493,33 @@ Production builds scan your files first, so names are stable between builds. In
|
|
|
299
493
|
Shorthand properties are expanded into their longhands, so `cn` can resolve conflicts between them:
|
|
300
494
|
|
|
301
495
|
```js
|
|
302
|
-
css`
|
|
496
|
+
css`
|
|
497
|
+
margin: auto;
|
|
498
|
+
`;
|
|
303
499
|
```
|
|
304
500
|
|
|
305
501
|
```css
|
|
306
|
-
.D{
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
.
|
|
502
|
+
.D {
|
|
503
|
+
margin-top: auto;
|
|
504
|
+
}
|
|
505
|
+
.C {
|
|
506
|
+
margin-right: auto;
|
|
507
|
+
}
|
|
508
|
+
.A {
|
|
509
|
+
margin-bottom: auto;
|
|
510
|
+
}
|
|
511
|
+
.B {
|
|
512
|
+
margin-left: auto;
|
|
513
|
+
}
|
|
310
514
|
```
|
|
311
515
|
|
|
312
516
|
`border` (which sets 12 longhands), shorthands whose values aren't known until runtime (values with `var()`, `env()`, or `attr()`), and shorthands set to a CSS-wide keyword (`font: inherit`, `margin: initial`) are kept as one class. They're written before longhands, so a longhand still wins:
|
|
313
517
|
|
|
314
518
|
```js
|
|
315
|
-
css`
|
|
519
|
+
css`
|
|
520
|
+
padding: var(--space);
|
|
521
|
+
padding-top: 0;
|
|
522
|
+
`;
|
|
316
523
|
```
|
|
317
524
|
|
|
318
525
|
Expanding means some shorthands produce many classes. For example, `font` also resets the `font-variant-*` properties, among others, so it becomes about 20 classes. The CSS file barely grows, because the classes are shared across your project, but class strings get longer.
|
|
@@ -321,7 +528,7 @@ Expanding means some shorthands produce many classes. For example, `font` also r
|
|
|
321
528
|
|
|
322
529
|
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:
|
|
323
530
|
|
|
324
|
-
1. Hoisted at-rules (`@layer` statements, `@import`, `@namespace`), then other copied at-rules.
|
|
531
|
+
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.
|
|
325
532
|
2. Classes on the element.
|
|
326
533
|
3. Selector variants, with pseudo-classes in LVHFA order (`:link`, `:visited`, `:hover`, `:focus`, `:active`).
|
|
327
534
|
4. At-rule groups. `@media` queries with only a `min-width` come last, smallest first (mobile-first).
|
|
@@ -338,38 +545,80 @@ Utility-first frameworks such as [Tailwind CSS](https://tailwindcss.com/) also p
|
|
|
338
545
|
|
|
339
546
|
## Configuration
|
|
340
547
|
|
|
341
|
-
|
|
548
|
+
Options live in one of two places. Each option is set in exactly one of them.
|
|
549
|
+
|
|
550
|
+
### In `package.json`
|
|
551
|
+
|
|
552
|
+
Options every tool that reads your styles has to agree on (the bundler plugin, and TypeScript's [content mapper](#types)) go in a `pre.style` field in your app's `package.json`. The plugin reads the `package.json` nearest Vite's root, and paths in it are relative to that file. All options are optional.
|
|
553
|
+
|
|
554
|
+
```json
|
|
555
|
+
{
|
|
556
|
+
"pre.style": {
|
|
557
|
+
"include": ["src/**/*.{ts,tsx}", "index.html"],
|
|
558
|
+
"prependFiles": ["src/styles/tokens.css"]
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
| Option | Default | Description |
|
|
564
|
+
| ---------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
565
|
+
| `include` | `**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts,html,astro}` | Globs of the files with blocks, relative to the `package.json`. A glob or an array of them. Scanned before a build to assign names; stylesheet files are found from the scanned files' imports. `node_modules` and the output directory are always skipped. |
|
|
566
|
+
| `exclude` | | Globs removed from `include`. |
|
|
567
|
+
| `prependFiles` | | Files added to the start of the app's blocks and stylesheet files. See [Sharing values with prepended files](#sharing-values-with-prepended-files). |
|
|
568
|
+
| `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
|
+
| `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
|
+
| `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. |
|
|
571
|
+
|
|
572
|
+
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
|
+
|
|
574
|
+
```json
|
|
575
|
+
{
|
|
576
|
+
"json.schemas": [
|
|
577
|
+
{
|
|
578
|
+
"fileMatch": ["package.json"],
|
|
579
|
+
"schema": {
|
|
580
|
+
"properties": {
|
|
581
|
+
"pre.style": {
|
|
582
|
+
"$ref": "./node_modules/@pre.style/core/package.schema.json"
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
]
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Plugin options
|
|
592
|
+
|
|
593
|
+
Options about how a build writes its output go to the plugin. All options are optional.
|
|
342
594
|
|
|
343
595
|
```ts
|
|
344
596
|
preStyle({
|
|
345
|
-
include: ['src/**/*.{ts,tsx}', 'index.html'],
|
|
346
|
-
prependFiles: ['src/styles/tokens.css'],
|
|
347
597
|
lightningcss: { minify: true, targets: { safari: 16 << 16 } },
|
|
348
598
|
sourceMap: true,
|
|
349
|
-
|
|
599
|
+
layer: "pre.style",
|
|
600
|
+
quotes: "single-quote",
|
|
350
601
|
output: {
|
|
351
|
-
file:
|
|
352
|
-
banner:
|
|
602
|
+
file: "css/pre.style.css",
|
|
603
|
+
banner: "/* Generated by pre.style */",
|
|
353
604
|
},
|
|
354
605
|
});
|
|
355
606
|
```
|
|
356
607
|
|
|
357
|
-
| Option
|
|
358
|
-
|
|
|
359
|
-
| `
|
|
360
|
-
| `
|
|
361
|
-
| `
|
|
362
|
-
| `
|
|
363
|
-
| `
|
|
364
|
-
| `
|
|
365
|
-
| `
|
|
366
|
-
| `quotes` | `'double-quote'` | Quotes around the class string that replaces each block in JS: `'single-quote'`, `'double-quote'`, or `'backtick'`. |
|
|
367
|
-
| `output.file` | `'pre.style.css'` | Path and name of the CSS file, relative to Vite's `build.outDir`. |
|
|
368
|
-
| `output.banner`| | Text added to the start of the CSS file. |
|
|
369
|
-
| `output.footer`| | Text added to the end of the CSS file. |
|
|
608
|
+
| Option | Default | Description |
|
|
609
|
+
| --------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
610
|
+
| `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. |
|
|
612
|
+
| `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
|
+
| `quotes` | `'double-quote'` | Quotes around the class string that replaces each block in JS: `'single-quote'`, `'double-quote'`, or `'backtick'`. |
|
|
614
|
+
| `output.file` | `'pre.style.css'` | Path and name of the CSS file, relative to Vite's `build.outDir`. |
|
|
615
|
+
| `output.banner` | | Text added to the start of the CSS file. |
|
|
616
|
+
| `output.footer` | | Text added to the end of the CSS file. |
|
|
370
617
|
|
|
371
618
|
The CSS isn't minified unless you set `lightningcss: { minify: true }`.
|
|
372
619
|
|
|
620
|
+
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
|
+
|
|
373
622
|
### Quirks mode
|
|
374
623
|
|
|
375
624
|
> [!WARNING]
|
|
@@ -377,7 +626,7 @@ The CSS isn't minified unless you set `lightningcss: { minify: true }`.
|
|
|
377
626
|
|
|
378
627
|
Pages without a doctype render in quirks mode, where class selectors ignore case. pre.style uses both uppercase and lowercase letters in class names, so `.A` and `.a` would apply to each other's elements. Add `<!doctype html>` to fix this. The plugin warns during the build when an HTML file it processes has no doctype.
|
|
379
628
|
|
|
380
|
-
If you can't add one, for example because your CSS is loaded into a page you don't control, set `quirksMode: true
|
|
629
|
+
If you can't add one, for example because your CSS is loaded into a page you don't control, set `"quirksMode": true` in your `package.json`'s `pre.style` field. Class names then use only lowercase letters, digits, `-`, and `_`. That leaves 27 one-character names instead of 53, and about 1,050 two-character names instead of 3,445, so names get longer once a project has more than about 27 classes.
|
|
381
630
|
|
|
382
631
|
## TypeScript
|
|
383
632
|
|
|
@@ -385,6 +634,8 @@ If you can't add one, for example because your CSS is loaded into a page you don
|
|
|
385
634
|
|
|
386
635
|
`css` and `scss` must be used as tagged templates (`` css`…` ``), and every block must be compiled by the plugin. If code that uses them runs without the plugin, for example in tests run directly in Node, they throw an error saying the transform wasn't applied, instead of silently rendering unstyled.
|
|
387
636
|
|
|
637
|
+
For stylesheet files, see [Types](#types).
|
|
638
|
+
|
|
388
639
|
## Errors
|
|
389
640
|
|
|
390
641
|
The build fails, with the file, line, and column, when:
|
|
@@ -393,7 +644,9 @@ The build fails, with the file, line, and column, when:
|
|
|
393
644
|
- Sass fails to compile an `scss` block;
|
|
394
645
|
- a block contains `${…}` interpolation;
|
|
395
646
|
- `css` or `scss` is used other than as a tagged template;
|
|
396
|
-
- a project has an `scss` block but Sass isn't installed
|
|
647
|
+
- a project has an `scss` block or file but Sass isn't installed;
|
|
648
|
+
- a stylesheet file has invalid CSS, or fails to compile with Sass;
|
|
649
|
+
- `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.
|
|
397
650
|
|
|
398
651
|
## Known limitations
|
|
399
652
|
|
|
@@ -403,15 +656,18 @@ The build fails, with the file, line, and column, when:
|
|
|
403
656
|
- Blocks in PHP and other templates aren't supported, because Vite doesn't output them.
|
|
404
657
|
- Component libraries are found among the app's dependencies and the dependencies of other libraries. A library used only through a package that isn't one (a wrapper without a `pre.style` field) isn't found automatically; add it with the `libraries` option.
|
|
405
658
|
- Before a build, Astro components are scanned like HTML, by matching `` css` `` and `` scss` ``. In a component that imports from `@pre.style/core`, text that only looks like a block, such as a code sample, is compiled too, and fails the build if it isn't valid CSS.
|
|
406
|
-
- Positions in `scss` blocks point to the start of the block, since Sass's output doesn't match the original lines.
|
|
659
|
+
- Positions in `scss` blocks point to the start of the block, since Sass's output doesn't match the original lines. In `.scss` files, Sass errors have positions, but source maps don't.
|
|
660
|
+
- In `.scss` files, rules from modules loaded with `@use` are written as they are, like prepended files' rules, and their classes aren't exported: Sass writes them before the file's own rules.
|
|
661
|
+
- Stylesheet imports must be static strings (`import`, `export … from`, and `import('./file.css', { with: { type: 'pre.style' } })`), and can't have a query.
|
|
662
|
+
- TypeScript's content mapper handles every `.css` and `.scss` file in the program, since mappers are chosen by extension. A project that also imports Vite's `.module.css` files without the attribute gets pre.style's types for them.
|
|
407
663
|
- Pages need a doctype (`<!doctype html>`). Without one, browsers use quirks mode, where class selectors ignore case, so classes like `.A` and `.a` would apply to each other's elements. See [Quirks mode](#quirks-mode).
|
|
408
664
|
|
|
409
665
|
## Packages
|
|
410
666
|
|
|
411
|
-
| Package
|
|
412
|
-
|
|
|
413
|
-
| `@pre.style/core`
|
|
414
|
-
| `@pre.style/vite` | The Vite plugin.
|
|
667
|
+
| Package | Description |
|
|
668
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
669
|
+
| `@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
|
+
| `@pre.style/vite` | The Vite plugin. |
|
|
415
671
|
|
|
416
672
|
## Development
|
|
417
673
|
|
|
@@ -435,7 +691,7 @@ npm run format # Oxfmt
|
|
|
435
691
|
npm run build
|
|
436
692
|
```
|
|
437
693
|
|
|
438
|
-
`demo/` has three apps for trying pre.style on the client and server: a React Router app in framework mode (`react/react-router`), a TanStack Start app (`react/tanstack-start`), and an Astro app with React islands (`astro`). They have the same pages, and share a small React component library (`react/ui`), which ships its source with the blocks still in it and its design tokens, for each app's pre.style plugin to compile. The demos use the packages' TypeScript source, so changes to them show up without a build.
|
|
694
|
+
`demo/` has three apps for trying pre.style on the client and server: a React Router app in framework mode (`react/react-router`), a TanStack Start app (`react/tanstack-start`), and an Astro app with React islands (`astro`). They have the same pages, and share a small React component library (`react/ui`), which ships its source with the blocks still in it (and a component styled from a `.scss` stylesheet file) and its design tokens, for each app's pre.style plugin to compile. The demos use the packages' TypeScript source, so changes to them show up without a build.
|
|
439
695
|
|
|
440
696
|
```sh
|
|
441
697
|
npm run demo:react-router # dev server
|