unocss-preset-overwrite 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/index.d.mts +30 -0
- package/dist/index.mjs +100 -0
- package/package.json +80 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 zyyv <https://github.com/zyyv>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# unocss-preset-overwrite [](https://npmjs.com/package/unocss-preset-overwrite)
|
|
2
|
+
|
|
3
|
+
Rebuild UnoCSS output with a new theme while keeping the same tokens.
|
|
4
|
+
|
|
5
|
+
This preset parses selectors from a previously generated CSS string and feeds them back into UnoCSS through `safelist`.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
When you only have compiled CSS (instead of source templates), switching theme values usually requires regenerating the same utility tokens.
|
|
10
|
+
|
|
11
|
+
`unocss-preset-overwrite` helps by:
|
|
12
|
+
|
|
13
|
+
- extracting class tokens (e.g. `.text-red-500`, `.md\:grid-cols-2`)
|
|
14
|
+
- extracting attributify tokens (e.g. `[bg~="red-500"]`, `[flex=""]`)
|
|
15
|
+
- safelisting those tokens for the next UnoCSS run
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm add -D unocss unocss-preset-overwrite
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// uno.config.ts
|
|
27
|
+
import { readFileSync } from 'node:fs'
|
|
28
|
+
import { fileURLToPath } from 'node:url'
|
|
29
|
+
import { defineConfig } from 'unocss'
|
|
30
|
+
import { presetOverwrite } from 'unocss-preset-overwrite'
|
|
31
|
+
import presetWind4 from 'unocss/preset-wind4'
|
|
32
|
+
|
|
33
|
+
const css = readFileSync(
|
|
34
|
+
fileURLToPath(import.meta.resolve('./style.css')),
|
|
35
|
+
'utf-8',
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
export default defineConfig({
|
|
39
|
+
presets: [
|
|
40
|
+
presetWind4(),
|
|
41
|
+
presetOverwrite({
|
|
42
|
+
css,
|
|
43
|
+
}),
|
|
44
|
+
],
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### API
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
interface PresetOverwriteOptions {
|
|
52
|
+
css?: string | (() => string)
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- `css` can be:
|
|
57
|
+
- a plain CSS string
|
|
58
|
+
- a callback returning a CSS string (useful when the value is lazily loaded)
|
|
59
|
+
|
|
60
|
+
### Attributify support
|
|
61
|
+
|
|
62
|
+
Use your regular attributify preset together with this preset:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import presetAttributify from '@unocss/preset-attributify'
|
|
66
|
+
import { defineConfig } from 'unocss'
|
|
67
|
+
import { presetOverwrite } from 'unocss-preset-overwrite'
|
|
68
|
+
import presetWind4 from 'unocss/preset-wind4'
|
|
69
|
+
|
|
70
|
+
export default defineConfig({
|
|
71
|
+
presets: [
|
|
72
|
+
presetWind4(),
|
|
73
|
+
presetAttributify(),
|
|
74
|
+
presetOverwrite({ css: previousCompiledCss }),
|
|
75
|
+
],
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Utility function
|
|
80
|
+
|
|
81
|
+
You can also use the extractor directly:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { extractUnoClassTokensFromCss } from 'unocss-preset-overwrite'
|
|
85
|
+
|
|
86
|
+
const tokens = extractUnoClassTokensFromCss(previousCompiledCss)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Notes
|
|
90
|
+
|
|
91
|
+
- The extractor skips UnoCSS `base`, `theme`, and `properties` layers when layer markers are present, to avoid false positives from preflight selectors.
|
|
92
|
+
- Output quality depends on how complete your previous compiled CSS is.
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
[MIT](./LICENSE) License © 2026 [chizuki](https://github.com/chizukicn)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as _$_unocss_core0 from "@unocss/core";
|
|
2
|
+
|
|
3
|
+
//#region src/extract.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Parse Uno-compatible tokens from a CSS string:
|
|
6
|
+
*
|
|
7
|
+
* - **class**: names from `.utility`, including escapes as in Uno output (e.g. `hover:bg-red-500`).
|
|
8
|
+
* - **Attributify** (`@unocss/preset-attributify`): selectors like `[bg~="red-500"]`, `[flex=""]`,
|
|
9
|
+
* `[un-bg~="red-500"]` are kept verbatim when they match the same attributify pattern as @unocss/core.
|
|
10
|
+
*
|
|
11
|
+
* If the file contains Uno layer markers (block comments whose text starts with `layer:`), only
|
|
12
|
+
* rules in layers other than `base`, `theme`, and `properties` are scanned—so preflight selectors
|
|
13
|
+
* like `[type="button"]` are not picked up as utilities.
|
|
14
|
+
*
|
|
15
|
+
* Callers pass the full CSS string; no filesystem I/O. Compound class selectors may yield extra
|
|
16
|
+
* tokens; filter upstream if needed.
|
|
17
|
+
*/
|
|
18
|
+
declare function extractUnoClassTokensFromCss(css: string): string[];
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/index.d.ts
|
|
21
|
+
interface PresetOverwriteOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Full compiled CSS string. PostCSS parses selectors; classes and attributify selectors become
|
|
24
|
+
* Uno utility tokens and are added via safelist.
|
|
25
|
+
*/
|
|
26
|
+
css?: (() => string) | string;
|
|
27
|
+
}
|
|
28
|
+
declare const presetOverwrite: _$_unocss_core0.PresetFactory<object, PresetOverwriteOptions>;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { type PresetOverwriteOptions, extractUnoClassTokensFromCss, presetOverwrite };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { definePreset } from "@unocss/core";
|
|
2
|
+
import postcss from "postcss";
|
|
3
|
+
import selectorParser from "postcss-selector-parser";
|
|
4
|
+
//#region src/extract.ts
|
|
5
|
+
/** Same regex shape as `isAttributifySelector` in @unocss/core (capturing groups required). */
|
|
6
|
+
const ATTRIBUTIFY_SELECTOR_RE = /^\[.+?~?=".*"\]$/;
|
|
7
|
+
function isAttributifyToken(selector) {
|
|
8
|
+
return ATTRIBUTIFY_SELECTOR_RE.test(selector);
|
|
9
|
+
}
|
|
10
|
+
/** Layers to skip: preflight/base CSS should not contribute utility tokens (avoids false attributify matches). */
|
|
11
|
+
const SKIP_LAYERS = new Set([
|
|
12
|
+
"base",
|
|
13
|
+
"theme",
|
|
14
|
+
"properties"
|
|
15
|
+
]);
|
|
16
|
+
const LAYER_LINE_RE = /^layer:\s*(\S+)/;
|
|
17
|
+
const ROOT_LAYER_MARKER_RE = /^\s*layer:\s*\S+/;
|
|
18
|
+
function attributifyTokenFromAttribute(attr) {
|
|
19
|
+
const op = attr.operator;
|
|
20
|
+
if (op !== "~=" && op !== "=") return null;
|
|
21
|
+
const name = attr.attribute;
|
|
22
|
+
const val = attr.value ?? "";
|
|
23
|
+
if (op === "~=") return `[${name}~="${val}"]`;
|
|
24
|
+
return `[${name}="${val}"]`;
|
|
25
|
+
}
|
|
26
|
+
function extractFromSelector(sel, tokens) {
|
|
27
|
+
if (!sel) return;
|
|
28
|
+
try {
|
|
29
|
+
selectorParser((selectors) => {
|
|
30
|
+
selectors.walkClasses((classNode) => {
|
|
31
|
+
tokens.add(classNode.value);
|
|
32
|
+
});
|
|
33
|
+
selectors.walkAttributes((attr) => {
|
|
34
|
+
const raw = attributifyTokenFromAttribute(attr);
|
|
35
|
+
if (raw && isAttributifyToken(raw)) tokens.add(raw);
|
|
36
|
+
});
|
|
37
|
+
}).processSync(sel);
|
|
38
|
+
} catch {}
|
|
39
|
+
}
|
|
40
|
+
function walkForTokens(container, layer, tokens) {
|
|
41
|
+
let currentLayer = layer;
|
|
42
|
+
const nodes = container.nodes ?? [];
|
|
43
|
+
for (const node of nodes) if (node.type === "comment") {
|
|
44
|
+
const m = node.text.trim().match(LAYER_LINE_RE);
|
|
45
|
+
if (m) currentLayer = m[1];
|
|
46
|
+
} else if (node.type === "rule") {
|
|
47
|
+
if (currentLayer && !SKIP_LAYERS.has(currentLayer)) extractFromSelector(node.selector, tokens);
|
|
48
|
+
} else if (node.type === "atrule") walkForTokens(node, currentLayer, tokens);
|
|
49
|
+
}
|
|
50
|
+
function rootDeclaresLayers(root) {
|
|
51
|
+
let found = false;
|
|
52
|
+
root.walkComments((c) => {
|
|
53
|
+
if (ROOT_LAYER_MARKER_RE.test(c.text.trim())) found = true;
|
|
54
|
+
});
|
|
55
|
+
return found;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Parse Uno-compatible tokens from a CSS string:
|
|
59
|
+
*
|
|
60
|
+
* - **class**: names from `.utility`, including escapes as in Uno output (e.g. `hover:bg-red-500`).
|
|
61
|
+
* - **Attributify** (`@unocss/preset-attributify`): selectors like `[bg~="red-500"]`, `[flex=""]`,
|
|
62
|
+
* `[un-bg~="red-500"]` are kept verbatim when they match the same attributify pattern as @unocss/core.
|
|
63
|
+
*
|
|
64
|
+
* If the file contains Uno layer markers (block comments whose text starts with `layer:`), only
|
|
65
|
+
* rules in layers other than `base`, `theme`, and `properties` are scanned—so preflight selectors
|
|
66
|
+
* like `[type="button"]` are not picked up as utilities.
|
|
67
|
+
*
|
|
68
|
+
* Callers pass the full CSS string; no filesystem I/O. Compound class selectors may yield extra
|
|
69
|
+
* tokens; filter upstream if needed.
|
|
70
|
+
*/
|
|
71
|
+
function extractUnoClassTokensFromCss(css) {
|
|
72
|
+
const trimmed = css.trim();
|
|
73
|
+
if (!trimmed) return [];
|
|
74
|
+
let root;
|
|
75
|
+
try {
|
|
76
|
+
root = postcss.parse(trimmed, { from: void 0 });
|
|
77
|
+
} catch {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
81
|
+
if (rootDeclaresLayers(root)) walkForTokens(root, null, tokens);
|
|
82
|
+
else root.walkRules((rule) => extractFromSelector(rule.selector, tokens));
|
|
83
|
+
return [...tokens];
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/index.ts
|
|
87
|
+
function toValue(value) {
|
|
88
|
+
if (typeof value === "function") return value();
|
|
89
|
+
return value ?? "";
|
|
90
|
+
}
|
|
91
|
+
const presetOverwrite = definePreset((options = {}) => {
|
|
92
|
+
return {
|
|
93
|
+
name: "unocss-preset-overwrite",
|
|
94
|
+
safelist: [() => {
|
|
95
|
+
return extractUnoClassTokensFromCss(toValue(options.css));
|
|
96
|
+
}]
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
//#endregion
|
|
100
|
+
export { extractUnoClassTokensFromCss, presetOverwrite };
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "unocss-preset-overwrite",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Rebuild UnoCSS output with a new theme while keeping the same tokens.",
|
|
6
|
+
"author": "chizukicn <https://github.com/chizukicn>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/unocss-community/unocss-preset-overwrite#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/unocss-community/unocss-preset-overwrite.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/unocss-community/unocss-preset-overwrite/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"unocss",
|
|
18
|
+
"unocss-preset",
|
|
19
|
+
"unocss-community",
|
|
20
|
+
"unocss-preset-overwrite"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"exports": {
|
|
24
|
+
".": "./dist/index.mjs",
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"typesVersions": {
|
|
29
|
+
"*": {
|
|
30
|
+
"*": [
|
|
31
|
+
"./dist/*",
|
|
32
|
+
"./dist/index.d.ts"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@unocss/core": "^66.6.8",
|
|
41
|
+
"maybe-types": "^0.2.0",
|
|
42
|
+
"postcss": "^8.5.9",
|
|
43
|
+
"postcss-selector-parser": "^7.1.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@antfu/eslint-config": "^8.1.1",
|
|
47
|
+
"@babel/types": "^7.29.0",
|
|
48
|
+
"@types/node": "^24.12.2",
|
|
49
|
+
"@unocss/eslint-plugin": "^66.6.8",
|
|
50
|
+
"@unocss/preset-attributify": "^66.6.8",
|
|
51
|
+
"bumpp": "^11.0.1",
|
|
52
|
+
"eslint": "^10.2.0",
|
|
53
|
+
"lint-staged": "^16.4.0",
|
|
54
|
+
"simple-git-hooks": "^2.13.1",
|
|
55
|
+
"taze": "^19.11.0",
|
|
56
|
+
"tsdown": "^0.21.7",
|
|
57
|
+
"typescript": "^6.0.2",
|
|
58
|
+
"unocss": "^66.6.8",
|
|
59
|
+
"vitest": "^4.1.3",
|
|
60
|
+
"vue": "^3.5.32"
|
|
61
|
+
},
|
|
62
|
+
"simple-git-hooks": {
|
|
63
|
+
"pre-commit": "pnpm lint-staged"
|
|
64
|
+
},
|
|
65
|
+
"lint-staged": {
|
|
66
|
+
"*.{js,ts}": [
|
|
67
|
+
"eslint --fix"
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"scripts": {
|
|
71
|
+
"build": "tsdown",
|
|
72
|
+
"dev": "tsdown --watch",
|
|
73
|
+
"release": "bumpp --commit --push --tag && pnpm publish",
|
|
74
|
+
"lint": "eslint .",
|
|
75
|
+
"lint:fix": "eslint . --fix",
|
|
76
|
+
"test": "vitest",
|
|
77
|
+
"test:update": "vitest --update",
|
|
78
|
+
"up:deps": "taze"
|
|
79
|
+
}
|
|
80
|
+
}
|