vite-plugin-theme-css 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/README.md +202 -0
- package/dist/defineTheme.d.ts +3 -0
- package/dist/generate/css/index.d.ts +5 -0
- package/dist/generate/js/index.d.ts +8 -0
- package/dist/generate/scss/index.d.ts +2 -0
- package/dist/index.cjs +306 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.mjs +268 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/types/index.d.ts +9 -0
- package/dist/runtime/virtual-entry.d.ts +6 -0
- package/dist/runtime-entry.cjs +76 -0
- package/dist/runtime-entry.mjs +46 -0
- package/dist/runtime.cjs +64 -0
- package/dist/runtime.mjs +39 -0
- package/dist/types.d.ts +15 -0
- package/dist/utils/flatten.d.ts +6 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# vite-plugin-theme-css
|
|
2
|
+
|
|
3
|
+
Vite 插件:用一份主题配置生成 CSS 变量、SCSS helper 与 JS 运行时,支持多主题切换与热更新。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add vite-plugin-theme-css
|
|
9
|
+
# 或
|
|
10
|
+
npm i vite-plugin-theme-css
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
需配合 Vite `>=5`,若使用 SCSS helper,请自行安装 `sass`。
|
|
14
|
+
|
|
15
|
+
## 快速开始
|
|
16
|
+
|
|
17
|
+
### 1. 创建主题配置
|
|
18
|
+
|
|
19
|
+
默认读取项目根目录的 `theme.config.ts`:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { defineTheme } from 'vite-plugin-theme-css'
|
|
23
|
+
|
|
24
|
+
export default defineTheme({
|
|
25
|
+
themes: {
|
|
26
|
+
light: {
|
|
27
|
+
text: {
|
|
28
|
+
primary: '#111',
|
|
29
|
+
secondary: '#666'
|
|
30
|
+
},
|
|
31
|
+
bg: {
|
|
32
|
+
page: '#fff'
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
dark: {
|
|
36
|
+
text: {
|
|
37
|
+
primary: '#f5f5f5',
|
|
38
|
+
secondary: '#aaa'
|
|
39
|
+
},
|
|
40
|
+
bg: {
|
|
41
|
+
page: '#111'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
common: {
|
|
46
|
+
font: {
|
|
47
|
+
size: {
|
|
48
|
+
h1: '32px',
|
|
49
|
+
body: '14px'
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
嵌套对象会扁平化为 CSS 变量:
|
|
57
|
+
|
|
58
|
+
| 配置路径 | CSS 变量 |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `themes.light.text.primary` | `--text-primary` |
|
|
61
|
+
| `common.font.size.h1` | `--common-font-size-h1` |
|
|
62
|
+
|
|
63
|
+
主题变量挂在 `:root[data-theme="..."]`,公共变量挂在 `:root`。
|
|
64
|
+
|
|
65
|
+
> `defineTheme` 为类型辅助(恒等函数)。配置里调用了 `defineTheme` 但未 import 时,插件会自动注入,类似 Vite 的 `defineConfig`。
|
|
66
|
+
|
|
67
|
+
### 2. 注册插件
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// vite.config.ts
|
|
71
|
+
import { defineConfig } from 'vite'
|
|
72
|
+
import { themeCss } from 'vite-plugin-theme-css'
|
|
73
|
+
|
|
74
|
+
export default defineConfig({
|
|
75
|
+
plugins: [themeCss()]
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 3. 引入 CSS 变量
|
|
80
|
+
|
|
81
|
+
在入口文件中导入虚拟 CSS:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import 'virtual:theme-css/css'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
生成示例:
|
|
88
|
+
|
|
89
|
+
```css
|
|
90
|
+
:root {
|
|
91
|
+
--common-font-size-h1: 32px;
|
|
92
|
+
--common-font-size-body: 14px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
:root[data-theme="light"] {
|
|
96
|
+
--text-primary: #111;
|
|
97
|
+
--text-secondary: #666;
|
|
98
|
+
--bg-page: #fff;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
:root[data-theme="dark"] {
|
|
102
|
+
--text-primary: #f5f5f5;
|
|
103
|
+
--text-secondary: #aaa;
|
|
104
|
+
--bg-page: #111;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 用法
|
|
109
|
+
|
|
110
|
+
### CSS / 内联样式
|
|
111
|
+
|
|
112
|
+
```css
|
|
113
|
+
.card {
|
|
114
|
+
color: var(--text-primary);
|
|
115
|
+
background: var(--bg-page);
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### SCSS
|
|
120
|
+
|
|
121
|
+
插件会通过 `css.preprocessorOptions.scss.additionalData` 自动注入 helper(命名空间默认 `token`),可直接使用:
|
|
122
|
+
|
|
123
|
+
```scss
|
|
124
|
+
.title {
|
|
125
|
+
color: token.theme(text, primary); // var(--text-primary)
|
|
126
|
+
font-size: token.common(font, size, h1); // var(--common-font-size-h1)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.banner {
|
|
130
|
+
@include token.on-theme(dark) {
|
|
131
|
+
opacity: 0.9;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
| API | 说明 |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| `token.theme($keys...)` | 主题变量 → `var(--a-b-c)` |
|
|
139
|
+
| `token.common($keys...)` | 公共变量 → `var(--common-a-b-c)` |
|
|
140
|
+
| `@include token.on-theme($name)` | 在指定主题下生效 |
|
|
141
|
+
|
|
142
|
+
也可手动导入:
|
|
143
|
+
|
|
144
|
+
```scss
|
|
145
|
+
@use 'virtual:theme-css/scss' as token;
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### JS 运行时
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { theme, common, setTheme, getTheme } from '@scope/theme-css'
|
|
152
|
+
|
|
153
|
+
theme('text', 'primary') // 当前主题下的原始值,如 '#111'
|
|
154
|
+
common('font', 'size', 'h1') // '32px'
|
|
155
|
+
|
|
156
|
+
setTheme('dark') // 设置 html[data-theme="dark"]
|
|
157
|
+
getTheme() // 'dark'
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`setTheme` 默认在主题名为 `dark` 时同步给 `<html>` 加上 `dark` class(可用 `syncHtmlDark: false` 关闭)。
|
|
161
|
+
|
|
162
|
+
## 插件选项
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
themeCss({
|
|
166
|
+
/** 配置文件路径,默认 './theme.config.ts' */
|
|
167
|
+
config: './theme.config.ts',
|
|
168
|
+
|
|
169
|
+
/** SCSS 命名空间,默认 'token' */
|
|
170
|
+
scssNamespace: 'token',
|
|
171
|
+
|
|
172
|
+
/** 是否同步 html.dark class,默认 true */
|
|
173
|
+
syncHtmlDark: true,
|
|
174
|
+
|
|
175
|
+
/** 排除自动注入 SCSS helper 的文件 */
|
|
176
|
+
exclude: [/node_modules/]
|
|
177
|
+
// 或: (filename) => filename.includes('legacy')
|
|
178
|
+
})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## 虚拟模块
|
|
182
|
+
|
|
183
|
+
| 模块 ID | 内容 |
|
|
184
|
+
| --- | --- |
|
|
185
|
+
| `virtual:theme-css/css` | 生成的 CSS 变量 |
|
|
186
|
+
| `virtual:theme-css/scss` | SCSS helper(`theme` / `common` / `on-theme`) |
|
|
187
|
+
| `@scope/theme-css` | 运行时 API(`theme` / `common` / `setTheme` / `getTheme`) |
|
|
188
|
+
|
|
189
|
+
## 热更新
|
|
190
|
+
|
|
191
|
+
修改 `theme.config.ts` 后会重新生成上述模块并触发页面 full-reload。
|
|
192
|
+
|
|
193
|
+
## 开发
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
pnpm test # 单元测试
|
|
197
|
+
pnpm build # 构建 dist
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
createThemeRuntime: () => createThemeRuntime,
|
|
34
|
+
defineTheme: () => defineTheme,
|
|
35
|
+
themeCss: () => themeCss
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(src_exports);
|
|
38
|
+
var import_node_fs = require("fs");
|
|
39
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
40
|
+
var import_node_url = require("url");
|
|
41
|
+
var import_node_module = require("module");
|
|
42
|
+
var import_jiti = require("jiti");
|
|
43
|
+
|
|
44
|
+
// src/types.ts
|
|
45
|
+
var defaultOptions = {
|
|
46
|
+
config: "./theme.config.ts",
|
|
47
|
+
scssNamespace: "token",
|
|
48
|
+
syncHtmlDark: true,
|
|
49
|
+
exclude: []
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// src/utils/flatten.ts
|
|
53
|
+
var flattenTokens = (obj, prefix = "") => {
|
|
54
|
+
const result = {};
|
|
55
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
56
|
+
const newKey = prefix ? `${prefix}-${key}` : key;
|
|
57
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
58
|
+
Object.assign(result, flattenTokens(value, newKey));
|
|
59
|
+
} else {
|
|
60
|
+
result[newKey] = String(value);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/generate/css/index.ts
|
|
67
|
+
var generateThemeCss = (config) => {
|
|
68
|
+
const { themes, common } = config;
|
|
69
|
+
const lines = [];
|
|
70
|
+
if (common) {
|
|
71
|
+
const flatCommon = flattenTokens(common, "common");
|
|
72
|
+
lines.push(":root {");
|
|
73
|
+
for (const [key, value] of Object.entries(flatCommon)) {
|
|
74
|
+
lines.push(` --${key}: ${value};`);
|
|
75
|
+
}
|
|
76
|
+
lines.push("}");
|
|
77
|
+
}
|
|
78
|
+
for (const [themeName, tokens] of Object.entries(themes)) {
|
|
79
|
+
const flatTokens = flattenTokens(tokens);
|
|
80
|
+
lines.push(`:root[data-theme="${themeName}"] {`);
|
|
81
|
+
for (const [key, value] of Object.entries(flatTokens)) {
|
|
82
|
+
lines.push(` --${key}: ${value};`);
|
|
83
|
+
}
|
|
84
|
+
lines.push("}");
|
|
85
|
+
}
|
|
86
|
+
return lines.join("\n");
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// src/generate/scss/helper.scss
|
|
90
|
+
var helper_default = `// \u5185\u90E8\u5DE5\u5177\uFF1A\u62FC\u63A5\u591A\u4E2A key\r
|
|
91
|
+
@function _join-keys($keys...) {\r
|
|
92
|
+
$result: '';\r
|
|
93
|
+
@each $key in $keys {\r
|
|
94
|
+
@if $result == '' {\r
|
|
95
|
+
$result: $key;\r
|
|
96
|
+
} @else {\r
|
|
97
|
+
$result: #{$result}-#{$key};\r
|
|
98
|
+
}\r
|
|
99
|
+
}\r
|
|
100
|
+
@return $result;\r
|
|
101
|
+
}\r
|
|
102
|
+
\r
|
|
103
|
+
// \u83B7\u53D6\u4E3B\u9898\u53D8\u91CF\uFF1Atoken.theme(text, primary) -> var(--text-primary)\r
|
|
104
|
+
@function theme($keys...) {\r
|
|
105
|
+
@return var(--#{_join-keys($keys...)});\r
|
|
106
|
+
}\r
|
|
107
|
+
\r
|
|
108
|
+
// \u83B7\u53D6\u516C\u5171\u53D8\u91CF\uFF1Atoken.common(font, size, h1) -> var(--common-font-size-h1)\r
|
|
109
|
+
@function common($keys...) {\r
|
|
110
|
+
@return var(--common-#{_join-keys($keys...)});\r
|
|
111
|
+
}\r
|
|
112
|
+
\r
|
|
113
|
+
// \u7279\u5B9A\u4E3B\u9898\u9009\u62E9\u5668\uFF1A@include token.on-theme(dark) { ... }\r
|
|
114
|
+
@mixin on-theme($theme) {\r
|
|
115
|
+
:root[data-theme="#{$theme}"] & {\r
|
|
116
|
+
@content;\r
|
|
117
|
+
}\r
|
|
118
|
+
}\r
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
// src/generate/scss/index.ts
|
|
122
|
+
var generateScssHelper = () => helper_default;
|
|
123
|
+
|
|
124
|
+
// src/generate/js/index.ts
|
|
125
|
+
function generateJs(config, options) {
|
|
126
|
+
return `export const config = ${JSON.stringify(config, null, 2)}
|
|
127
|
+
export const options = ${JSON.stringify(options)}
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/defineTheme.ts
|
|
132
|
+
function defineTheme(config) {
|
|
133
|
+
return config;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/runtime/index.ts
|
|
137
|
+
function getByPath(obj, keys) {
|
|
138
|
+
return keys.reduce((acc, key) => {
|
|
139
|
+
if (acc && typeof acc === "object" && key in acc) {
|
|
140
|
+
return acc[key];
|
|
141
|
+
}
|
|
142
|
+
return void 0;
|
|
143
|
+
}, obj);
|
|
144
|
+
}
|
|
145
|
+
function createThemeRuntime(config, options = {}) {
|
|
146
|
+
const syncHtmlDark = options.syncHtmlDark ?? true;
|
|
147
|
+
let currentTheme = typeof document !== "undefined" && document.documentElement.dataset.theme || "light";
|
|
148
|
+
return {
|
|
149
|
+
theme(...keys) {
|
|
150
|
+
const themeTokens = config.themes?.[currentTheme] ?? {};
|
|
151
|
+
const value = getByPath(themeTokens, keys);
|
|
152
|
+
return value !== void 0 ? String(value) : "";
|
|
153
|
+
},
|
|
154
|
+
common(...keys) {
|
|
155
|
+
const value = getByPath(config.common ?? {}, keys);
|
|
156
|
+
return value !== void 0 ? String(value) : "";
|
|
157
|
+
},
|
|
158
|
+
setTheme(name) {
|
|
159
|
+
currentTheme = name;
|
|
160
|
+
if (typeof document !== "undefined") {
|
|
161
|
+
document.documentElement.dataset.theme = name;
|
|
162
|
+
if (syncHtmlDark) {
|
|
163
|
+
document.documentElement.classList.toggle("dark", name === "dark");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
getTheme() {
|
|
168
|
+
return currentTheme;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/index.ts
|
|
174
|
+
var import_meta = {};
|
|
175
|
+
var require2 = (0, import_node_module.createRequire)(import_meta.url);
|
|
176
|
+
var babelTransform = (0, import_node_module.createRequire)(require2.resolve("jiti/package.json"))("./dist/babel.cjs");
|
|
177
|
+
var VIRTUAL_CSS_ID = "virtual:theme-css/css";
|
|
178
|
+
var VIRTUAL_CSS_RESOLVED = "\0virtual:theme-css.css";
|
|
179
|
+
var VIRTUAL_SCSS_ID = "virtual:theme-css/scss";
|
|
180
|
+
var VIRTUAL_SCSS_RESOLVED = "\0virtual:theme-css.scss";
|
|
181
|
+
var VIRTUAL_RUNTIME_ID = "@scope/theme-css";
|
|
182
|
+
var VIRTUAL_RUNTIME_DATA_ID = "virtual:theme-css/runtime-data";
|
|
183
|
+
var VIRTUAL_RUNTIME_DATA_RESOLVED = "\0virtual:theme-css/runtime-data";
|
|
184
|
+
function resolveRuntimeEntryPath() {
|
|
185
|
+
const here = import_node_path.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
186
|
+
const distEntry = import_node_path.default.join(here, "runtime-entry.mjs");
|
|
187
|
+
if ((0, import_node_fs.existsSync)(distEntry)) return distEntry;
|
|
188
|
+
return import_node_path.default.join(here, "runtime", "virtual-entry.ts");
|
|
189
|
+
}
|
|
190
|
+
function themeCss(options = {}) {
|
|
191
|
+
const resolvedOptions = { ...defaultOptions, ...options };
|
|
192
|
+
const { config: configPath, scssNamespace, syncHtmlDark, exclude } = resolvedOptions;
|
|
193
|
+
let viteConfig;
|
|
194
|
+
let themeConfig;
|
|
195
|
+
let cssContent = "";
|
|
196
|
+
let scssHelperContent = "";
|
|
197
|
+
let runtimeDataContent = "";
|
|
198
|
+
const runtimeEntryPath = resolveRuntimeEntryPath();
|
|
199
|
+
async function loadConfigAndGenerate() {
|
|
200
|
+
const jiti = (0, import_jiti.createJiti)(import_meta.url, {
|
|
201
|
+
interopDefault: true,
|
|
202
|
+
transform: (opts) => {
|
|
203
|
+
let { source } = opts;
|
|
204
|
+
const usesDefineTheme = /\bdefineTheme\s*\(/.test(source);
|
|
205
|
+
const hasBinding = /\bimport\s[\s\S]*?\bdefineTheme\b/.test(source) || /\b(?:const|let|var|function)\s+defineTheme\b/.test(source);
|
|
206
|
+
if (usesDefineTheme && !hasBinding) {
|
|
207
|
+
source = `const defineTheme = (c) => c;
|
|
208
|
+
${source}`;
|
|
209
|
+
}
|
|
210
|
+
return babelTransform({ ...opts, source });
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
const mod = await jiti.import(import_node_path.default.resolve(configPath));
|
|
214
|
+
themeConfig = mod.default;
|
|
215
|
+
cssContent = generateThemeCss(themeConfig);
|
|
216
|
+
scssHelperContent = generateScssHelper();
|
|
217
|
+
runtimeDataContent = generateJs(themeConfig, { syncHtmlDark });
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
name: "vite-plugin-theme-css",
|
|
221
|
+
async configResolved(config) {
|
|
222
|
+
viteConfig = config;
|
|
223
|
+
await loadConfigAndGenerate();
|
|
224
|
+
},
|
|
225
|
+
resolveId(id) {
|
|
226
|
+
switch (id) {
|
|
227
|
+
case VIRTUAL_CSS_ID:
|
|
228
|
+
return VIRTUAL_CSS_RESOLVED;
|
|
229
|
+
case VIRTUAL_SCSS_ID:
|
|
230
|
+
return VIRTUAL_SCSS_RESOLVED;
|
|
231
|
+
case VIRTUAL_RUNTIME_ID:
|
|
232
|
+
return runtimeEntryPath;
|
|
233
|
+
case VIRTUAL_RUNTIME_DATA_ID:
|
|
234
|
+
return VIRTUAL_RUNTIME_DATA_RESOLVED;
|
|
235
|
+
default:
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
load(id) {
|
|
240
|
+
switch (id) {
|
|
241
|
+
case VIRTUAL_CSS_RESOLVED:
|
|
242
|
+
return cssContent;
|
|
243
|
+
case VIRTUAL_SCSS_RESOLVED:
|
|
244
|
+
return scssHelperContent;
|
|
245
|
+
case VIRTUAL_RUNTIME_DATA_RESOLVED:
|
|
246
|
+
return runtimeDataContent;
|
|
247
|
+
default:
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
config(userConfig) {
|
|
252
|
+
const scssOptions = userConfig.css?.preprocessorOptions?.scss ?? {};
|
|
253
|
+
const userAdditionalData = scssOptions.additionalData;
|
|
254
|
+
const ourPrefix = `@use "${VIRTUAL_SCSS_ID}" as ${scssNamespace};
|
|
255
|
+
`;
|
|
256
|
+
const wrappedAdditionalData = async (source, filename) => {
|
|
257
|
+
if (filename.includes("virtual:theme-css")) {
|
|
258
|
+
return typeof userAdditionalData === "function" ? await userAdditionalData(source, filename) : userAdditionalData ?? "";
|
|
259
|
+
}
|
|
260
|
+
const shouldExclude = Array.isArray(exclude) ? exclude.some((rule) => {
|
|
261
|
+
if (typeof rule === "string") return filename.includes(rule);
|
|
262
|
+
if (rule instanceof RegExp) return rule.test(filename);
|
|
263
|
+
return false;
|
|
264
|
+
}) : typeof exclude === "function" ? exclude(filename) : false;
|
|
265
|
+
if (shouldExclude) {
|
|
266
|
+
return typeof userAdditionalData === "function" ? await userAdditionalData(source, filename) : userAdditionalData ?? "";
|
|
267
|
+
}
|
|
268
|
+
const userContent = typeof userAdditionalData === "function" ? await userAdditionalData(source, filename) : userAdditionalData ?? "";
|
|
269
|
+
return ourPrefix + userContent;
|
|
270
|
+
};
|
|
271
|
+
return {
|
|
272
|
+
css: {
|
|
273
|
+
preprocessorOptions: {
|
|
274
|
+
scss: {
|
|
275
|
+
...scssOptions,
|
|
276
|
+
additionalData: wrappedAdditionalData
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
},
|
|
282
|
+
async handleHotUpdate(ctx) {
|
|
283
|
+
const configAbsPath = import_node_path.default.resolve(configPath);
|
|
284
|
+
if (ctx.file === configAbsPath) {
|
|
285
|
+
await loadConfigAndGenerate();
|
|
286
|
+
const moduleGraph = ctx.server.moduleGraph;
|
|
287
|
+
for (const id of [
|
|
288
|
+
VIRTUAL_CSS_RESOLVED,
|
|
289
|
+
VIRTUAL_SCSS_RESOLVED,
|
|
290
|
+
VIRTUAL_RUNTIME_DATA_RESOLVED,
|
|
291
|
+
runtimeEntryPath
|
|
292
|
+
]) {
|
|
293
|
+
const mod = moduleGraph.getModuleById(id);
|
|
294
|
+
if (mod) moduleGraph.invalidateModule(mod);
|
|
295
|
+
}
|
|
296
|
+
ctx.server.ws.send({ type: "full-reload" });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
302
|
+
0 && (module.exports = {
|
|
303
|
+
createThemeRuntime,
|
|
304
|
+
defineTheme,
|
|
305
|
+
themeCss
|
|
306
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import type { ThemeCssOptions } from './types';
|
|
3
|
+
export declare function themeCss(options?: ThemeCssOptions): Plugin;
|
|
4
|
+
export type { ThemeCssOptions, ThemeConfig } from './types';
|
|
5
|
+
export type { ThemeRuntime, ThemeRuntimeOptions } from './runtime';
|
|
6
|
+
export { defineTheme } from './defineTheme';
|
|
7
|
+
export { createThemeRuntime } from './runtime';
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
import { createJiti } from "jiti";
|
|
7
|
+
|
|
8
|
+
// src/types.ts
|
|
9
|
+
var defaultOptions = {
|
|
10
|
+
config: "./theme.config.ts",
|
|
11
|
+
scssNamespace: "token",
|
|
12
|
+
syncHtmlDark: true,
|
|
13
|
+
exclude: []
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/utils/flatten.ts
|
|
17
|
+
var flattenTokens = (obj, prefix = "") => {
|
|
18
|
+
const result = {};
|
|
19
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
20
|
+
const newKey = prefix ? `${prefix}-${key}` : key;
|
|
21
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
22
|
+
Object.assign(result, flattenTokens(value, newKey));
|
|
23
|
+
} else {
|
|
24
|
+
result[newKey] = String(value);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// src/generate/css/index.ts
|
|
31
|
+
var generateThemeCss = (config) => {
|
|
32
|
+
const { themes, common } = config;
|
|
33
|
+
const lines = [];
|
|
34
|
+
if (common) {
|
|
35
|
+
const flatCommon = flattenTokens(common, "common");
|
|
36
|
+
lines.push(":root {");
|
|
37
|
+
for (const [key, value] of Object.entries(flatCommon)) {
|
|
38
|
+
lines.push(` --${key}: ${value};`);
|
|
39
|
+
}
|
|
40
|
+
lines.push("}");
|
|
41
|
+
}
|
|
42
|
+
for (const [themeName, tokens] of Object.entries(themes)) {
|
|
43
|
+
const flatTokens = flattenTokens(tokens);
|
|
44
|
+
lines.push(`:root[data-theme="${themeName}"] {`);
|
|
45
|
+
for (const [key, value] of Object.entries(flatTokens)) {
|
|
46
|
+
lines.push(` --${key}: ${value};`);
|
|
47
|
+
}
|
|
48
|
+
lines.push("}");
|
|
49
|
+
}
|
|
50
|
+
return lines.join("\n");
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/generate/scss/helper.scss
|
|
54
|
+
var helper_default = `// \u5185\u90E8\u5DE5\u5177\uFF1A\u62FC\u63A5\u591A\u4E2A key\r
|
|
55
|
+
@function _join-keys($keys...) {\r
|
|
56
|
+
$result: '';\r
|
|
57
|
+
@each $key in $keys {\r
|
|
58
|
+
@if $result == '' {\r
|
|
59
|
+
$result: $key;\r
|
|
60
|
+
} @else {\r
|
|
61
|
+
$result: #{$result}-#{$key};\r
|
|
62
|
+
}\r
|
|
63
|
+
}\r
|
|
64
|
+
@return $result;\r
|
|
65
|
+
}\r
|
|
66
|
+
\r
|
|
67
|
+
// \u83B7\u53D6\u4E3B\u9898\u53D8\u91CF\uFF1Atoken.theme(text, primary) -> var(--text-primary)\r
|
|
68
|
+
@function theme($keys...) {\r
|
|
69
|
+
@return var(--#{_join-keys($keys...)});\r
|
|
70
|
+
}\r
|
|
71
|
+
\r
|
|
72
|
+
// \u83B7\u53D6\u516C\u5171\u53D8\u91CF\uFF1Atoken.common(font, size, h1) -> var(--common-font-size-h1)\r
|
|
73
|
+
@function common($keys...) {\r
|
|
74
|
+
@return var(--common-#{_join-keys($keys...)});\r
|
|
75
|
+
}\r
|
|
76
|
+
\r
|
|
77
|
+
// \u7279\u5B9A\u4E3B\u9898\u9009\u62E9\u5668\uFF1A@include token.on-theme(dark) { ... }\r
|
|
78
|
+
@mixin on-theme($theme) {\r
|
|
79
|
+
:root[data-theme="#{$theme}"] & {\r
|
|
80
|
+
@content;\r
|
|
81
|
+
}\r
|
|
82
|
+
}\r
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
// src/generate/scss/index.ts
|
|
86
|
+
var generateScssHelper = () => helper_default;
|
|
87
|
+
|
|
88
|
+
// src/generate/js/index.ts
|
|
89
|
+
function generateJs(config, options) {
|
|
90
|
+
return `export const config = ${JSON.stringify(config, null, 2)}
|
|
91
|
+
export const options = ${JSON.stringify(options)}
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/defineTheme.ts
|
|
96
|
+
function defineTheme(config) {
|
|
97
|
+
return config;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/runtime/index.ts
|
|
101
|
+
function getByPath(obj, keys) {
|
|
102
|
+
return keys.reduce((acc, key) => {
|
|
103
|
+
if (acc && typeof acc === "object" && key in acc) {
|
|
104
|
+
return acc[key];
|
|
105
|
+
}
|
|
106
|
+
return void 0;
|
|
107
|
+
}, obj);
|
|
108
|
+
}
|
|
109
|
+
function createThemeRuntime(config, options = {}) {
|
|
110
|
+
const syncHtmlDark = options.syncHtmlDark ?? true;
|
|
111
|
+
let currentTheme = typeof document !== "undefined" && document.documentElement.dataset.theme || "light";
|
|
112
|
+
return {
|
|
113
|
+
theme(...keys) {
|
|
114
|
+
const themeTokens = config.themes?.[currentTheme] ?? {};
|
|
115
|
+
const value = getByPath(themeTokens, keys);
|
|
116
|
+
return value !== void 0 ? String(value) : "";
|
|
117
|
+
},
|
|
118
|
+
common(...keys) {
|
|
119
|
+
const value = getByPath(config.common ?? {}, keys);
|
|
120
|
+
return value !== void 0 ? String(value) : "";
|
|
121
|
+
},
|
|
122
|
+
setTheme(name) {
|
|
123
|
+
currentTheme = name;
|
|
124
|
+
if (typeof document !== "undefined") {
|
|
125
|
+
document.documentElement.dataset.theme = name;
|
|
126
|
+
if (syncHtmlDark) {
|
|
127
|
+
document.documentElement.classList.toggle("dark", name === "dark");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
getTheme() {
|
|
132
|
+
return currentTheme;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/index.ts
|
|
138
|
+
var require2 = createRequire(import.meta.url);
|
|
139
|
+
var babelTransform = createRequire(require2.resolve("jiti/package.json"))("./dist/babel.cjs");
|
|
140
|
+
var VIRTUAL_CSS_ID = "virtual:theme-css/css";
|
|
141
|
+
var VIRTUAL_CSS_RESOLVED = "\0virtual:theme-css.css";
|
|
142
|
+
var VIRTUAL_SCSS_ID = "virtual:theme-css/scss";
|
|
143
|
+
var VIRTUAL_SCSS_RESOLVED = "\0virtual:theme-css.scss";
|
|
144
|
+
var VIRTUAL_RUNTIME_ID = "@scope/theme-css";
|
|
145
|
+
var VIRTUAL_RUNTIME_DATA_ID = "virtual:theme-css/runtime-data";
|
|
146
|
+
var VIRTUAL_RUNTIME_DATA_RESOLVED = "\0virtual:theme-css/runtime-data";
|
|
147
|
+
function resolveRuntimeEntryPath() {
|
|
148
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
149
|
+
const distEntry = path.join(here, "runtime-entry.mjs");
|
|
150
|
+
if (existsSync(distEntry)) return distEntry;
|
|
151
|
+
return path.join(here, "runtime", "virtual-entry.ts");
|
|
152
|
+
}
|
|
153
|
+
function themeCss(options = {}) {
|
|
154
|
+
const resolvedOptions = { ...defaultOptions, ...options };
|
|
155
|
+
const { config: configPath, scssNamespace, syncHtmlDark, exclude } = resolvedOptions;
|
|
156
|
+
let viteConfig;
|
|
157
|
+
let themeConfig;
|
|
158
|
+
let cssContent = "";
|
|
159
|
+
let scssHelperContent = "";
|
|
160
|
+
let runtimeDataContent = "";
|
|
161
|
+
const runtimeEntryPath = resolveRuntimeEntryPath();
|
|
162
|
+
async function loadConfigAndGenerate() {
|
|
163
|
+
const jiti = createJiti(import.meta.url, {
|
|
164
|
+
interopDefault: true,
|
|
165
|
+
transform: (opts) => {
|
|
166
|
+
let { source } = opts;
|
|
167
|
+
const usesDefineTheme = /\bdefineTheme\s*\(/.test(source);
|
|
168
|
+
const hasBinding = /\bimport\s[\s\S]*?\bdefineTheme\b/.test(source) || /\b(?:const|let|var|function)\s+defineTheme\b/.test(source);
|
|
169
|
+
if (usesDefineTheme && !hasBinding) {
|
|
170
|
+
source = `const defineTheme = (c) => c;
|
|
171
|
+
${source}`;
|
|
172
|
+
}
|
|
173
|
+
return babelTransform({ ...opts, source });
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
const mod = await jiti.import(path.resolve(configPath));
|
|
177
|
+
themeConfig = mod.default;
|
|
178
|
+
cssContent = generateThemeCss(themeConfig);
|
|
179
|
+
scssHelperContent = generateScssHelper();
|
|
180
|
+
runtimeDataContent = generateJs(themeConfig, { syncHtmlDark });
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
name: "vite-plugin-theme-css",
|
|
184
|
+
async configResolved(config) {
|
|
185
|
+
viteConfig = config;
|
|
186
|
+
await loadConfigAndGenerate();
|
|
187
|
+
},
|
|
188
|
+
resolveId(id) {
|
|
189
|
+
switch (id) {
|
|
190
|
+
case VIRTUAL_CSS_ID:
|
|
191
|
+
return VIRTUAL_CSS_RESOLVED;
|
|
192
|
+
case VIRTUAL_SCSS_ID:
|
|
193
|
+
return VIRTUAL_SCSS_RESOLVED;
|
|
194
|
+
case VIRTUAL_RUNTIME_ID:
|
|
195
|
+
return runtimeEntryPath;
|
|
196
|
+
case VIRTUAL_RUNTIME_DATA_ID:
|
|
197
|
+
return VIRTUAL_RUNTIME_DATA_RESOLVED;
|
|
198
|
+
default:
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
load(id) {
|
|
203
|
+
switch (id) {
|
|
204
|
+
case VIRTUAL_CSS_RESOLVED:
|
|
205
|
+
return cssContent;
|
|
206
|
+
case VIRTUAL_SCSS_RESOLVED:
|
|
207
|
+
return scssHelperContent;
|
|
208
|
+
case VIRTUAL_RUNTIME_DATA_RESOLVED:
|
|
209
|
+
return runtimeDataContent;
|
|
210
|
+
default:
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
config(userConfig) {
|
|
215
|
+
const scssOptions = userConfig.css?.preprocessorOptions?.scss ?? {};
|
|
216
|
+
const userAdditionalData = scssOptions.additionalData;
|
|
217
|
+
const ourPrefix = `@use "${VIRTUAL_SCSS_ID}" as ${scssNamespace};
|
|
218
|
+
`;
|
|
219
|
+
const wrappedAdditionalData = async (source, filename) => {
|
|
220
|
+
if (filename.includes("virtual:theme-css")) {
|
|
221
|
+
return typeof userAdditionalData === "function" ? await userAdditionalData(source, filename) : userAdditionalData ?? "";
|
|
222
|
+
}
|
|
223
|
+
const shouldExclude = Array.isArray(exclude) ? exclude.some((rule) => {
|
|
224
|
+
if (typeof rule === "string") return filename.includes(rule);
|
|
225
|
+
if (rule instanceof RegExp) return rule.test(filename);
|
|
226
|
+
return false;
|
|
227
|
+
}) : typeof exclude === "function" ? exclude(filename) : false;
|
|
228
|
+
if (shouldExclude) {
|
|
229
|
+
return typeof userAdditionalData === "function" ? await userAdditionalData(source, filename) : userAdditionalData ?? "";
|
|
230
|
+
}
|
|
231
|
+
const userContent = typeof userAdditionalData === "function" ? await userAdditionalData(source, filename) : userAdditionalData ?? "";
|
|
232
|
+
return ourPrefix + userContent;
|
|
233
|
+
};
|
|
234
|
+
return {
|
|
235
|
+
css: {
|
|
236
|
+
preprocessorOptions: {
|
|
237
|
+
scss: {
|
|
238
|
+
...scssOptions,
|
|
239
|
+
additionalData: wrappedAdditionalData
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
},
|
|
245
|
+
async handleHotUpdate(ctx) {
|
|
246
|
+
const configAbsPath = path.resolve(configPath);
|
|
247
|
+
if (ctx.file === configAbsPath) {
|
|
248
|
+
await loadConfigAndGenerate();
|
|
249
|
+
const moduleGraph = ctx.server.moduleGraph;
|
|
250
|
+
for (const id of [
|
|
251
|
+
VIRTUAL_CSS_RESOLVED,
|
|
252
|
+
VIRTUAL_SCSS_RESOLVED,
|
|
253
|
+
VIRTUAL_RUNTIME_DATA_RESOLVED,
|
|
254
|
+
runtimeEntryPath
|
|
255
|
+
]) {
|
|
256
|
+
const mod = moduleGraph.getModuleById(id);
|
|
257
|
+
if (mod) moduleGraph.invalidateModule(mod);
|
|
258
|
+
}
|
|
259
|
+
ctx.server.ws.send({ type: "full-reload" });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
export {
|
|
265
|
+
createThemeRuntime,
|
|
266
|
+
defineTheme,
|
|
267
|
+
themeCss
|
|
268
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ThemeConfig } from '../types';
|
|
2
|
+
import type { ThemeRuntime, ThemeRuntimeOptions } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* 创建主题运行时 API。虚拟模块只负责注入 config,逻辑写在这里。
|
|
5
|
+
*/
|
|
6
|
+
export declare function createThemeRuntime(config: ThemeConfig, options?: ThemeRuntimeOptions): ThemeRuntime;
|
|
7
|
+
export type { ThemeRuntime, ThemeRuntimeOptions };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 用户侧 `@scope/theme-css` 的真实入口。
|
|
3
|
+
* 配置由 virtual:theme-css/runtime-data 注入,此处是普通 TS。
|
|
4
|
+
*/
|
|
5
|
+
declare const theme: (...keys: string[]) => string, common: (...keys: string[]) => string, setTheme: (name: string) => void, getTheme: () => string;
|
|
6
|
+
export { theme, common, setTheme, getTheme };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/runtime/virtual-entry.ts
|
|
21
|
+
var virtual_entry_exports = {};
|
|
22
|
+
__export(virtual_entry_exports, {
|
|
23
|
+
common: () => common,
|
|
24
|
+
getTheme: () => getTheme,
|
|
25
|
+
setTheme: () => setTheme,
|
|
26
|
+
theme: () => theme
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(virtual_entry_exports);
|
|
29
|
+
|
|
30
|
+
// src/runtime/index.ts
|
|
31
|
+
function getByPath(obj, keys) {
|
|
32
|
+
return keys.reduce((acc, key) => {
|
|
33
|
+
if (acc && typeof acc === "object" && key in acc) {
|
|
34
|
+
return acc[key];
|
|
35
|
+
}
|
|
36
|
+
return void 0;
|
|
37
|
+
}, obj);
|
|
38
|
+
}
|
|
39
|
+
function createThemeRuntime(config2, options2 = {}) {
|
|
40
|
+
const syncHtmlDark = options2.syncHtmlDark ?? true;
|
|
41
|
+
let currentTheme = typeof document !== "undefined" && document.documentElement.dataset.theme || "light";
|
|
42
|
+
return {
|
|
43
|
+
theme(...keys) {
|
|
44
|
+
const themeTokens = config2.themes?.[currentTheme] ?? {};
|
|
45
|
+
const value = getByPath(themeTokens, keys);
|
|
46
|
+
return value !== void 0 ? String(value) : "";
|
|
47
|
+
},
|
|
48
|
+
common(...keys) {
|
|
49
|
+
const value = getByPath(config2.common ?? {}, keys);
|
|
50
|
+
return value !== void 0 ? String(value) : "";
|
|
51
|
+
},
|
|
52
|
+
setTheme(name) {
|
|
53
|
+
currentTheme = name;
|
|
54
|
+
if (typeof document !== "undefined") {
|
|
55
|
+
document.documentElement.dataset.theme = name;
|
|
56
|
+
if (syncHtmlDark) {
|
|
57
|
+
document.documentElement.classList.toggle("dark", name === "dark");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
getTheme() {
|
|
62
|
+
return currentTheme;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/runtime/virtual-entry.ts
|
|
68
|
+
var import_runtime_data = require("virtual:theme-css/runtime-data");
|
|
69
|
+
var { theme, common, setTheme, getTheme } = createThemeRuntime(import_runtime_data.config, import_runtime_data.options);
|
|
70
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
71
|
+
0 && (module.exports = {
|
|
72
|
+
common,
|
|
73
|
+
getTheme,
|
|
74
|
+
setTheme,
|
|
75
|
+
theme
|
|
76
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/runtime/index.ts
|
|
2
|
+
function getByPath(obj, keys) {
|
|
3
|
+
return keys.reduce((acc, key) => {
|
|
4
|
+
if (acc && typeof acc === "object" && key in acc) {
|
|
5
|
+
return acc[key];
|
|
6
|
+
}
|
|
7
|
+
return void 0;
|
|
8
|
+
}, obj);
|
|
9
|
+
}
|
|
10
|
+
function createThemeRuntime(config2, options2 = {}) {
|
|
11
|
+
const syncHtmlDark = options2.syncHtmlDark ?? true;
|
|
12
|
+
let currentTheme = typeof document !== "undefined" && document.documentElement.dataset.theme || "light";
|
|
13
|
+
return {
|
|
14
|
+
theme(...keys) {
|
|
15
|
+
const themeTokens = config2.themes?.[currentTheme] ?? {};
|
|
16
|
+
const value = getByPath(themeTokens, keys);
|
|
17
|
+
return value !== void 0 ? String(value) : "";
|
|
18
|
+
},
|
|
19
|
+
common(...keys) {
|
|
20
|
+
const value = getByPath(config2.common ?? {}, keys);
|
|
21
|
+
return value !== void 0 ? String(value) : "";
|
|
22
|
+
},
|
|
23
|
+
setTheme(name) {
|
|
24
|
+
currentTheme = name;
|
|
25
|
+
if (typeof document !== "undefined") {
|
|
26
|
+
document.documentElement.dataset.theme = name;
|
|
27
|
+
if (syncHtmlDark) {
|
|
28
|
+
document.documentElement.classList.toggle("dark", name === "dark");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
getTheme() {
|
|
33
|
+
return currentTheme;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/runtime/virtual-entry.ts
|
|
39
|
+
import { config, options } from "virtual:theme-css/runtime-data";
|
|
40
|
+
var { theme, common, setTheme, getTheme } = createThemeRuntime(config, options);
|
|
41
|
+
export {
|
|
42
|
+
common,
|
|
43
|
+
getTheme,
|
|
44
|
+
setTheme,
|
|
45
|
+
theme
|
|
46
|
+
};
|
package/dist/runtime.cjs
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/runtime/index.ts
|
|
21
|
+
var runtime_exports = {};
|
|
22
|
+
__export(runtime_exports, {
|
|
23
|
+
createThemeRuntime: () => createThemeRuntime
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(runtime_exports);
|
|
26
|
+
function getByPath(obj, keys) {
|
|
27
|
+
return keys.reduce((acc, key) => {
|
|
28
|
+
if (acc && typeof acc === "object" && key in acc) {
|
|
29
|
+
return acc[key];
|
|
30
|
+
}
|
|
31
|
+
return void 0;
|
|
32
|
+
}, obj);
|
|
33
|
+
}
|
|
34
|
+
function createThemeRuntime(config, options = {}) {
|
|
35
|
+
const syncHtmlDark = options.syncHtmlDark ?? true;
|
|
36
|
+
let currentTheme = typeof document !== "undefined" && document.documentElement.dataset.theme || "light";
|
|
37
|
+
return {
|
|
38
|
+
theme(...keys) {
|
|
39
|
+
const themeTokens = config.themes?.[currentTheme] ?? {};
|
|
40
|
+
const value = getByPath(themeTokens, keys);
|
|
41
|
+
return value !== void 0 ? String(value) : "";
|
|
42
|
+
},
|
|
43
|
+
common(...keys) {
|
|
44
|
+
const value = getByPath(config.common ?? {}, keys);
|
|
45
|
+
return value !== void 0 ? String(value) : "";
|
|
46
|
+
},
|
|
47
|
+
setTheme(name) {
|
|
48
|
+
currentTheme = name;
|
|
49
|
+
if (typeof document !== "undefined") {
|
|
50
|
+
document.documentElement.dataset.theme = name;
|
|
51
|
+
if (syncHtmlDark) {
|
|
52
|
+
document.documentElement.classList.toggle("dark", name === "dark");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
getTheme() {
|
|
57
|
+
return currentTheme;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
62
|
+
0 && (module.exports = {
|
|
63
|
+
createThemeRuntime
|
|
64
|
+
});
|
package/dist/runtime.mjs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/runtime/index.ts
|
|
2
|
+
function getByPath(obj, keys) {
|
|
3
|
+
return keys.reduce((acc, key) => {
|
|
4
|
+
if (acc && typeof acc === "object" && key in acc) {
|
|
5
|
+
return acc[key];
|
|
6
|
+
}
|
|
7
|
+
return void 0;
|
|
8
|
+
}, obj);
|
|
9
|
+
}
|
|
10
|
+
function createThemeRuntime(config, options = {}) {
|
|
11
|
+
const syncHtmlDark = options.syncHtmlDark ?? true;
|
|
12
|
+
let currentTheme = typeof document !== "undefined" && document.documentElement.dataset.theme || "light";
|
|
13
|
+
return {
|
|
14
|
+
theme(...keys) {
|
|
15
|
+
const themeTokens = config.themes?.[currentTheme] ?? {};
|
|
16
|
+
const value = getByPath(themeTokens, keys);
|
|
17
|
+
return value !== void 0 ? String(value) : "";
|
|
18
|
+
},
|
|
19
|
+
common(...keys) {
|
|
20
|
+
const value = getByPath(config.common ?? {}, keys);
|
|
21
|
+
return value !== void 0 ? String(value) : "";
|
|
22
|
+
},
|
|
23
|
+
setTheme(name) {
|
|
24
|
+
currentTheme = name;
|
|
25
|
+
if (typeof document !== "undefined") {
|
|
26
|
+
document.documentElement.dataset.theme = name;
|
|
27
|
+
if (syncHtmlDark) {
|
|
28
|
+
document.documentElement.classList.toggle("dark", name === "dark");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
getTheme() {
|
|
33
|
+
return currentTheme;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
createThemeRuntime
|
|
39
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ThemeConfig {
|
|
2
|
+
themes: Record<string, Record<string, any>>;
|
|
3
|
+
common?: Record<string, any>;
|
|
4
|
+
}
|
|
5
|
+
export interface ThemeCssOptions {
|
|
6
|
+
/** 配置文件路径,默认 './theme.config.ts' */
|
|
7
|
+
config?: string;
|
|
8
|
+
/** SCSS 命名空间,默认 'token' */
|
|
9
|
+
scssNamespace?: string;
|
|
10
|
+
/** 是否同步 html.dark,默认 true */
|
|
11
|
+
syncHtmlDark?: boolean;
|
|
12
|
+
/** 排除注入 SCSS helper 的文件规则 */
|
|
13
|
+
exclude?: (string | RegExp)[] | ((filename: string) => boolean);
|
|
14
|
+
}
|
|
15
|
+
export declare const defaultOptions: Required<ThemeCssOptions>;
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-theme-css",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vite 插件:用一份主题配置生成 CSS 变量、SCSS helper 与 JS 运行时,支持多主题切换与热更新",
|
|
5
|
+
"author": "yuzinan <1589937631@qq.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/yvygyyth/theme-css.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/yvygyyth/theme-css#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/yvygyyth/theme-css/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"vite",
|
|
17
|
+
"vite-plugin",
|
|
18
|
+
"theme",
|
|
19
|
+
"css",
|
|
20
|
+
"css-variables",
|
|
21
|
+
"scss",
|
|
22
|
+
"dark-mode",
|
|
23
|
+
"theme-switcher"
|
|
24
|
+
],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public",
|
|
27
|
+
"registry": "https://registry.npmjs.org/"
|
|
28
|
+
},
|
|
29
|
+
"type": "module",
|
|
30
|
+
"main": "./dist/index.cjs",
|
|
31
|
+
"module": "./dist/index.mjs",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.mjs",
|
|
37
|
+
"require": "./dist/index.cjs"
|
|
38
|
+
},
|
|
39
|
+
"./runtime": {
|
|
40
|
+
"types": "./dist/runtime.d.ts",
|
|
41
|
+
"import": "./dist/runtime.mjs",
|
|
42
|
+
"require": "./dist/runtime.cjs"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist"
|
|
47
|
+
],
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"jiti": "^2.7.0"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"vite": ">=5.0.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^26.2.0",
|
|
56
|
+
"prettier": "3.9.6",
|
|
57
|
+
"sass": "^1.103.0",
|
|
58
|
+
"tsup": "^8.5.1",
|
|
59
|
+
"typescript": "^7.0.2",
|
|
60
|
+
"vite": "^8.2.2",
|
|
61
|
+
"vitest": "^4.1.11"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "tsup && tsc -p tsconfig.build.json",
|
|
65
|
+
"dev": "tsup --watch",
|
|
66
|
+
"test": "vitest",
|
|
67
|
+
"format": "prettier --write .",
|
|
68
|
+
"format:check": "prettier --check .",
|
|
69
|
+
"release": "pnpm publish --access public"
|
|
70
|
+
}
|
|
71
|
+
}
|