oklchtohex 0.1.0 → 0.3.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/README.md CHANGED
@@ -20,60 +20,80 @@ A build-time package is the most practical fix:
20
20
  npm i -D oklchtohex
21
21
  ```
22
22
 
23
- ## CLI usage
23
+ ## JS API
24
24
 
25
- Convert one value:
25
+ ```js
26
+ import { oklchToHex, replaceOklchInText, convertTailwindCssToHex } from "oklchtohex";
26
27
 
27
- ```bash
28
- npx oklchtohex --value "oklch(70.4% 0.191 22.216)"
28
+ const hex = oklchToHex("oklch(70.4% 0.191 22.216)");
29
+ const css = replaceOklchInText("color: oklch(70.4% 0.191 22.216);");
30
+ const convertedTailwindCss = convertTailwindCssToHex(css);
29
31
  ```
30
32
 
31
- Convert a CSS file and write to a new file:
33
+ ### Conversion behavior
32
34
 
33
- ```bash
34
- npx oklchtohex ./dist/app.css -o ./dist/app.hex.css
35
- ```
35
+ - Known Tailwind default variables like `--color-red-500` are replaced with exact Tailwind HEX palette values.
36
+ - Unknown variables and raw `oklch(...)` values use functional conversion.
37
+ - `gamut` option supports `clip` (default) and `fit`.
36
38
 
37
- Print transformed CSS to stdout:
39
+ ## Vite plugin (auto on dev + build)
38
40
 
39
- ```bash
40
- npx oklchtohex ./dist/app.css --stdout
41
+ Use the built-in plugin to auto-convert CSS during `vite dev` and `vite build`.
42
+
43
+ ```js
44
+ import { defineConfig } from "vite";
45
+ import react from "@vitejs/plugin-react";
46
+ import { oklchToHexVitePlugin } from "oklchtohex/vite";
47
+
48
+ export default defineConfig({
49
+ plugins: [
50
+ react(),
51
+ oklchToHexVitePlugin({
52
+ gamut: "clip",
53
+ convertDev: true,
54
+ convertBuild: true
55
+ })
56
+ ]
57
+ });
41
58
  ```
42
59
 
43
- ### Options
60
+ ### Plugin options
61
+
62
+ - `gamut`: `"clip"` (default) or `"fit"`
63
+ - `includeAlpha`: `"auto"` (default), `"always"`, `"never"`
64
+ - `uppercase`: `false` (default)
65
+ - `onError`: `"preserve"` (default) or `"throw"`
66
+ - `include`: `RegExp | (id) => boolean` (default matches CSS-like files)
67
+ - `exclude`: `RegExp | (id) => boolean`
68
+ - `convertDev`: `true` by default
69
+ - `convertBuild`: `true` by default
70
+
71
+ ## Tailwind v4 build integration (package-only)
72
+
73
+ Create a small Node script in your app, for example `scripts/convert-tailwind-to-hex.mjs`:
44
74
 
45
- - `--gamut fit|clip`
46
- `clip` (default) hard-clamps channels; `fit` reduces chroma to stay in sRGB.
47
- - `--alpha auto|always|never`
48
- defaults to `auto`.
49
- - `--upper`
50
- outputs uppercase HEX.
75
+ ```js
76
+ import fs from "node:fs/promises";
77
+ import { convertTailwindCssToHex } from "oklchtohex";
78
+
79
+ const inputPath = "./dist/tailwind.css";
80
+ const outputPath = "./dist/tailwind.css"; // in-place rewrite
51
81
 
52
- When converting Tailwind CSS, known default variables like `--color-red-500` are replaced with the exact Tailwind HEX palette value first. Unknown variables and raw `oklch(...)` values use functional conversion.
82
+ const css = await fs.readFile(inputPath, "utf8");
83
+ const converted = convertTailwindCssToHex(css, { gamut: "clip" });
84
+ await fs.writeFile(outputPath, converted, "utf8");
85
+ ```
53
86
 
54
- ## Tailwind v4 integration (Inspect Element workflow)
87
+ Then call it in your app `package.json`:
55
88
 
56
89
  ```json
57
90
  {
58
91
  "scripts": {
59
92
  "build:css": "tailwindcss -i ./src/input.css -o ./dist/tailwind.css",
60
- "build:css:hex": "oklchtohex ./dist/tailwind.css -o ./dist/tailwind.css",
93
+ "build:css:hex": "node ./scripts/convert-tailwind-to-hex.mjs",
61
94
  "build": "npm run build:css && npm run build:css:hex"
62
95
  }
63
96
  }
64
97
  ```
65
98
 
66
- This rewrites Tailwind output in-place, so your existing HTML `<link>` remains unchanged and DevTools "Styles" pane shows HEX declarations.
67
-
68
- If you want two files instead, output to `./dist/tailwind.hex.css` and link that file.
69
-
70
- Note: some browsers still show `rgb(...)` in the "Computed" pane. The "Styles" pane reflects your source declaration format.
71
-
72
- ## JS API
73
-
74
- ```js
75
- import { oklchToHex, replaceOklchInText } from "oklchtohex";
76
-
77
- const hex = oklchToHex("oklch(70.4% 0.191 22.216)");
78
- const css = replaceOklchInText("color: oklch(70.4% 0.191 22.216);");
79
- ```
99
+ This keeps the package library-only while still giving build-time conversion.
package/package.json CHANGED
@@ -1,27 +1,25 @@
1
1
  {
2
2
  "name": "oklchtohex",
3
- "version": "0.1.0",
4
- "description": "Convert OKLCH colors to HEX and rewrite CSS at build time.",
3
+ "version": "0.3.0",
4
+ "description": "Convert OKLCH colors to HEX as a JavaScript package.",
5
5
  "type": "module",
6
- "main": "./src/converter.js",
6
+ "main": "./src/index.js",
7
7
  "exports": {
8
- ".": "./src/converter.js"
9
- },
10
- "bin": {
11
- "oklchtohex": "./src/cli.js"
8
+ ".": "./src/index.js",
9
+ "./vite": "./src/vite-plugin.js",
10
+ "./converter": "./src/converter.js"
12
11
  },
13
12
  "files": [
14
13
  "src",
15
14
  "README.md"
16
15
  ],
17
- "scripts": {
18
- "check": "node ./src/cli.js --help"
19
- },
20
16
  "keywords": [
21
17
  "oklch",
22
18
  "hex",
23
19
  "tailwind",
24
20
  "tailwindcss",
21
+ "vite",
22
+ "vite-plugin",
25
23
  "color",
26
24
  "converter"
27
25
  ],
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./converter.js";
2
+ export { oklchToHexVitePlugin } from "./vite-plugin.js";
3
+
@@ -0,0 +1,105 @@
1
+ import { replaceOklchInText } from "./converter.js";
2
+
3
+ const DEFAULT_CSS_FILE_REGEX =
4
+ /\.(css|pcss|postcss|scss|sass|less|styl|stylus)(?:$|\?)/i;
5
+
6
+ function isRegexMatch(regex, value) {
7
+ if (!(regex instanceof RegExp)) {
8
+ return false;
9
+ }
10
+ regex.lastIndex = 0;
11
+ return regex.test(value);
12
+ }
13
+
14
+ function matches(matcher, value) {
15
+ if (!matcher) {
16
+ return true;
17
+ }
18
+ if (matcher instanceof RegExp) {
19
+ return isRegexMatch(matcher, value);
20
+ }
21
+ if (typeof matcher === "function") {
22
+ return Boolean(matcher(value));
23
+ }
24
+ return false;
25
+ }
26
+
27
+ function shouldProcessId(id, include, exclude) {
28
+ if (!matches(include, id)) {
29
+ return false;
30
+ }
31
+ if (exclude && matches(exclude, id)) {
32
+ return false;
33
+ }
34
+ return true;
35
+ }
36
+
37
+ function maybeConvertCss(code, convertOptions) {
38
+ if (typeof code !== "string") {
39
+ return null;
40
+ }
41
+ if (!code.toLowerCase().includes("oklch(")) {
42
+ return null;
43
+ }
44
+ const converted = replaceOklchInText(code, convertOptions);
45
+ return converted === code ? null : converted;
46
+ }
47
+
48
+ export function oklchToHexVitePlugin(options = {}) {
49
+ const {
50
+ include = DEFAULT_CSS_FILE_REGEX,
51
+ exclude,
52
+ convertDev = true,
53
+ convertBuild = true,
54
+ ...convertOptions
55
+ } = options;
56
+
57
+ let command = "build";
58
+
59
+ return {
60
+ name: "oklchtohex-vite-plugin",
61
+ enforce: "post",
62
+ configResolved(config) {
63
+ command = config.command;
64
+ },
65
+ transform(code, id) {
66
+ if (command === "serve" && !convertDev) {
67
+ return null;
68
+ }
69
+ if (command === "build" && !convertBuild) {
70
+ return null;
71
+ }
72
+ if (!shouldProcessId(id, include, exclude)) {
73
+ return null;
74
+ }
75
+
76
+ const converted = maybeConvertCss(code, convertOptions);
77
+ if (!converted) {
78
+ return null;
79
+ }
80
+ return { code: converted, map: null };
81
+ },
82
+ generateBundle(_, bundle) {
83
+ if (!convertBuild) {
84
+ return;
85
+ }
86
+ for (const [fileName, asset] of Object.entries(bundle)) {
87
+ if (asset.type !== "asset") {
88
+ continue;
89
+ }
90
+ if (!shouldProcessId(fileName, include, exclude)) {
91
+ continue;
92
+ }
93
+ if (typeof asset.source !== "string") {
94
+ continue;
95
+ }
96
+
97
+ const converted = maybeConvertCss(asset.source, convertOptions);
98
+ if (converted) {
99
+ asset.source = converted;
100
+ }
101
+ }
102
+ }
103
+ };
104
+ }
105
+
package/src/cli.js DELETED
@@ -1,152 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from "node:fs/promises";
4
- import path from "node:path";
5
- import process from "node:process";
6
- import { oklchToHex, replaceOklchInText } from "./converter.js";
7
-
8
- function printHelp() {
9
- const help = `
10
- oklchtohex
11
-
12
- Convert OKLCH values to HEX, or rewrite an entire CSS file.
13
-
14
- Usage:
15
- oklchtohex --value "oklch(70.4% 0.191 22.216)"
16
- oklchtohex ./dist/app.css -o ./dist/app.hex.css
17
- oklchtohex ./dist/app.css --stdout
18
-
19
- Options:
20
- --value <oklch> Convert a single OKLCH value to HEX
21
- -o, --output <file> Output file path for transformed CSS
22
- --stdout Print transformed CSS to stdout
23
- --gamut <fit|clip> Gamut strategy (default: clip)
24
- --alpha <auto|always|never>
25
- Include alpha in HEX (default: auto)
26
- --upper Uppercase HEX output
27
- -h, --help Show this help text
28
- `;
29
- process.stdout.write(help);
30
- }
31
-
32
- function parseArgs(argv) {
33
- const parsed = {
34
- input: undefined,
35
- output: undefined,
36
- value: undefined,
37
- stdout: false,
38
- gamut: "clip",
39
- includeAlpha: "auto",
40
- uppercase: false,
41
- help: false
42
- };
43
-
44
- for (let i = 0; i < argv.length; i += 1) {
45
- const arg = argv[i];
46
-
47
- if (arg === "-h" || arg === "--help") {
48
- parsed.help = true;
49
- continue;
50
- }
51
- if (arg === "--value") {
52
- parsed.value = argv[i + 1];
53
- i += 1;
54
- continue;
55
- }
56
- if (arg === "-o" || arg === "--output") {
57
- parsed.output = argv[i + 1];
58
- i += 1;
59
- continue;
60
- }
61
- if (arg === "--stdout") {
62
- parsed.stdout = true;
63
- continue;
64
- }
65
- if (arg === "--gamut") {
66
- parsed.gamut = argv[i + 1];
67
- i += 1;
68
- continue;
69
- }
70
- if (arg === "--alpha") {
71
- parsed.includeAlpha = argv[i + 1];
72
- i += 1;
73
- continue;
74
- }
75
- if (arg === "--upper") {
76
- parsed.uppercase = true;
77
- continue;
78
- }
79
- if (!arg.startsWith("-") && !parsed.input) {
80
- parsed.input = arg;
81
- continue;
82
- }
83
-
84
- throw new Error(`Unknown argument: ${arg}`);
85
- }
86
-
87
- return parsed;
88
- }
89
-
90
- function normalizeIncludeAlpha(value) {
91
- if (!value || value === "auto") {
92
- return "auto";
93
- }
94
- if (value === "always") {
95
- return true;
96
- }
97
- if (value === "never") {
98
- return false;
99
- }
100
- throw new Error(`Invalid --alpha value: ${value}`);
101
- }
102
-
103
- function normalizeGamut(value) {
104
- if (value === "fit" || value === "clip") {
105
- return value;
106
- }
107
- throw new Error(`Invalid --gamut value: ${value}`);
108
- }
109
-
110
- async function run() {
111
- const args = parseArgs(process.argv.slice(2));
112
- if (args.help) {
113
- printHelp();
114
- return;
115
- }
116
-
117
- const options = {
118
- gamut: normalizeGamut(args.gamut),
119
- includeAlpha: normalizeIncludeAlpha(args.includeAlpha),
120
- uppercase: args.uppercase
121
- };
122
-
123
- if (args.value) {
124
- const hex = oklchToHex(args.value, options);
125
- process.stdout.write(`${hex}\n`);
126
- return;
127
- }
128
-
129
- if (!args.input) {
130
- throw new Error("Missing input. Provide --value or a CSS file path.");
131
- }
132
-
133
- const inputPath = path.resolve(process.cwd(), args.input);
134
- const css = await fs.readFile(inputPath, "utf8");
135
- const transformed = replaceOklchInText(css, options);
136
-
137
- if (args.stdout || !args.output) {
138
- process.stdout.write(transformed);
139
- if (!transformed.endsWith("\n")) {
140
- process.stdout.write("\n");
141
- }
142
- return;
143
- }
144
-
145
- const outputPath = path.resolve(process.cwd(), args.output);
146
- await fs.writeFile(outputPath, transformed, "utf8");
147
- }
148
-
149
- run().catch((error) => {
150
- process.stderr.write(`${error.message}\n`);
151
- process.exit(1);
152
- });