├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── babel.config.js ├── color-test.js ├── commitlint.config.js ├── husky.config.js ├── images ├── example.gif ├── includeStyles_p.png └── includeStyles_r.png ├── lint-staged.config.js ├── package.json ├── src ├── arbitraryMode │ ├── browser.js │ ├── index.js │ └── utils.js ├── bin │ └── index.js ├── getLess.js ├── getSass.js ├── index.js ├── packPath.js ├── postcss-addScopeName.js └── utils.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /dist 3 | /node_modules 4 | /test/fixtures -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["@webpack-contrib/eslint-config-webpack", "prettier"], 4 | }; 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | bin/* eol=lf 3 | yarn.lock -diff 4 | package-lock.json -diff 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | pids 10 | logs 11 | results 12 | npm-debug.log 13 | /node_modules 14 | /coverage 15 | .idea 16 | .nyc_output 17 | npm-debug.log* 18 | yarn-debug.log* 19 | .eslintcache 20 | /coverage 21 | /dist 22 | /local 23 | /reports 24 | /test/fixtures/css 25 | /test/fixtures/generated-1.less 26 | /test/fixtures/generated-2.less 27 | /test/fixtures/generated-3.less 28 | /test/output 29 | .DS_Store 30 | Thumbs.db 31 | .vscode 32 | *.sublime-project 33 | *.sublime-workspace 34 | *.iml 35 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /dist 3 | /node_modules 4 | /test/fixtures 5 | CHANGELOG.md -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "semi": true, 5 | "singleQuote": true, 6 | "end-of-line": "lf" 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | 'Software'), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @zougt/some-loader-utils 2 | 3 | 提供了 4 | 5 | - [getLess](#getLess),本质上不是针对 less-loader 的扩展,而是[less 包](https://github.com/less/less.js)的扩展 6 | - [getSass](#getSass),本质上不是针对 sass-loader 的扩展,而是[sass 包](https://github.com/sass/dart-sass)的扩展 7 | 8 | 让你轻松实现基于`less`、`sass`的 web 应用在线动态主题切换。 9 | 10 | 有[动态主题模式](#动态主题模式)和[预设主题模式](#预设主题模式) 11 | 12 | 特点: 13 | 14 | - 使用成本很低 15 | - 不限 ui 框架,Element-ui、iview、Ant-design 等等等(只要基于 less/sass) 16 | - 不依赖 css3 vars 17 | - 浏览器兼容性良好(IE9+ ?) 18 | 19 | [demo repositories](https://github.com/GitOfZGT/dynamic-theme-demos) 20 | 21 | # 动态主题模式 22 | 23 | > v1.4.0+支持 24 | 25 | 可用颜色板选择任意颜色切换相关的梯度颜色,这里以 scss 为例 26 | 27 | [one inline demo](https://gitofzgt.github.io/dynamic-theme-demos/webpack-vuecli4-elementui-dynamic-theme/) 28 | 29 | [one demo repository](https://github.com/GitOfZGT/dynamic-theme-demos/tree/master/projects/webpack-vuecli4-elementui-dynamic-theme/) 30 | 31 | ![效果图](https://img-blog.csdnimg.cn/56589b227df9470c83389b4fc0adbaaf.gif#pic_center) 32 | 33 | ## 在 webpack 中使用 34 | 35 | ```bash 36 | # use npm or pnpm 37 | npm install color@3.2.1 @zougt/some-loader-utils @zougt/theme-css-extract-webpack-plugin -D 38 | # use yarn 39 | yarn add color@3.2.1 @zougt/some-loader-utils @zougt/theme-css-extract-webpack-plugin -D 40 | ``` 41 | 42 | **webpack.config.js** 43 | 44 | ```js 45 | const path = require('path'); 46 | 47 | const { getSass } = require('@zougt/some-loader-utils'); 48 | 49 | const ThemeCssExtractWebpackPlugin = require('@zougt/theme-css-extract-webpack-plugin'); 50 | 51 | const multipleScopeVars = [ 52 | { 53 | // 必需,任意名称 54 | scopeName: 'theme-vars', 55 | // path和varsContent选一个 56 | path: path.resolve('src/theme/theme-vars.scss'), 57 | // varsContent:`@--color-primary:#9c26b;` 58 | }, 59 | ]; 60 | 61 | module.exports = { 62 | module: { 63 | rules: [ 64 | { 65 | // 添加 setCustomTheme 的热更新loader 66 | test: /setCustomTheme\.js$/, 67 | enforce: 'pre', 68 | loader: require.resolve( 69 | '@zougt/theme-css-extract-webpack-plugin/dist/hot-loader/index.js' 70 | ), 71 | }, 72 | { 73 | test: /\.(scss|sass)$/i, 74 | // 请确保支持 implementation 属性的 sass-loader版本,webpack4 => sass-loader v10.x,webpack5 => sass-loader v12.x,请安装sass, 非 node-sass 75 | loader: 'sass-loader', 76 | options: { 77 | implementation: getSass({ 78 | // getMultipleScopeVars优先于 sassOptions.multipleScopeVars 79 | getMultipleScopeVars: (lessOptions) => 80 | multipleScopeVars, 81 | // 可选项 82 | // implementation:sass 83 | }), 84 | }, 85 | }, 86 | ], 87 | }, 88 | plugins: [ 89 | new ThemeCssExtractWebpackPlugin({ 90 | // 启用动态主题模式 91 | arbitraryMode: true, 92 | // 默认主题色,与"src/theme/theme-vars.scss"的@--color-primary主题色相同 93 | defaultPrimaryColor: '#512da7', 94 | multipleScopeVars, 95 | // 【注意】includeStyleWithColors作用: css中不是由主题色变量生成的颜色,也让它抽取到主题css内,可以提高权重 96 | includeStyleWithColors: [ 97 | { 98 | // color也可以是array,如 ["#ffffff","#000"] 99 | color: '#ffffff', 100 | // 排除属性,如 不提取背景色的#ffffff 101 | // excludeCssProps:["background","background-color"] 102 | // 排除选择器,如 不提取以下选择器的 #ffffff 103 | // excludeSelectors: [ 104 | // ".ant-btn-link:hover, .ant-btn-link:focus, .ant-btn-link:active", 105 | // ], 106 | }, 107 | ], 108 | // 是否在html默认添加主题的style标签 109 | InjectDefaultStyleTagToHtml: true, 110 | // setCustomTheme.js的一个依赖的生成路径,默认是 @zougt/theme-css-extract-webpack-plugin/dist/hot-loader/setCustomThemeContent.js 111 | customThemeOutputPath: '', 112 | // 调整色相值偏差,某些颜色值是由主题色通过mix等函数转化后,两者色相值不相等,无法确认是梯度颜色,可以调整low和high,允许偏差范围, 例如 hueDiffControls:{low: 2,high:2} 113 | // hueDiffControls: { 114 | // low: 0, 115 | // high: 0, 116 | // }, 117 | }), 118 | ], 119 | }; 120 | ``` 121 | 122 | **src/theme/theme-vars.scss** 123 | 124 | ```scss 125 | /*说明:此文件不应该被其他@import,此文件的变量并不是来设置项目的主题(当然,你可以作为加载时的默认主题),主要作用是,这里的变量值只要与项目的原变量值有差别,编译后就会抽取跟随主题色梯度变化的css*/ 126 | 127 | /*注意(重要):此文件的内容一旦固定下来就不需要改,在线动态切换主题,调用setCustomTheme方法即可*/ 128 | 129 | /*注意(强调):变量值改动会影响 gradientReplacer 和 targetValueReplacer 的可用属性的变化,所以内容一旦固定下来就不需要改(强调)*/ 130 | 131 | /*主题色,通常与 插件的 defaultPrimaryColor 相同, 使用setCustomTheme({primaryColor})切换*/ 132 | 133 | $--color-primary: #512da7; 134 | 135 | /*与此颜色对应的样式,默认情况也会跟主色变化的,要切换它对应的梯度颜色,使用setCustomTheme({gradientReplacer:{"#F7D06B":"#F7D06B"}})切换 */ 136 | $--color-success: #f7d06b; 137 | 138 | // /*圆角值,尽量与原值差别大一点,方便分析 targetValueReplacer 的可用属性,非颜色值的切换,可以使用 setCustomTheme({targetValueReplacer:{"6px"}}) 精准替换*/ 139 | // @border-radius-base:6px; 140 | ``` 141 | 142 | **在线切换主题** 143 | 144 | 动态主题切换必须使用的 "setCustomTheme" 模块,会自动处理项目中包括组件库涉及的梯度颜色替换 145 | 146 | ```js 147 | // color@4 使用了Numeric separators,如需良好兼容性应该安装 color@3 148 | import Color from 'color'; 149 | // setCustomTheme的参数必须提供Color模块,至于为什么不把 Color 直接依赖进去是有原因的 150 | import setCustomTheme from '@zougt/theme-css-extract-webpack-plugin/dist/setCustomTheme'; 151 | // 设置任意主题色既可 152 | setCustomTheme({ 153 | Color, 154 | primaryColor: '#FF005A', 155 | //gradientReplacer:{}, 156 | //targetValueReplacer:{} 157 | }); 158 | ``` 159 | 160 | `setCustomTheme` 的可选参数 gradientReplacer 与 targetValueReplacer 的可用属性会跟随 .scss 内容变化的,所以整个项目动态主题的模型应该最开始固化下来 161 | 162 | ```shell 163 | # npm run dev 之后 164 | # 可以在终端使用 z-theme 命令查看 gradientReplacer 与 targetValueReplacer 的可用属性 165 | npx z-theme inspect 166 | ``` 167 | 168 | ## 预设主题模式 169 | 170 | 只预设多种可选主题,这里以less为例 171 | 172 | [one inline demo](https://gitofzgt.github.io/dynamic-theme-demos/webpack-vuecli4-antdvue-preset-theme/) 173 | 174 | [one demo repository](https://github.com/GitOfZGT/dynamic-theme-demos/tree/master/projects/webpack-vuecli4-antdvue-preset-theme/) 175 | 176 | ![效果图](https://img-blog.csdnimg.cn/c11382d232a84aebab80b9f87eb66cc5.gif#pic_center) 177 | 178 | ## 在 webpack 中使用 179 | 180 | ```bash 181 | # use npm or pnpm 182 | npm install @zougt/some-loader-utils @zougt/theme-css-extract-webpack-plugin -D 183 | # use yarn 184 | yarn add @zougt/some-loader-utils @zougt/theme-css-extract-webpack-plugin -D 185 | ``` 186 | 187 | **webpack.config.js** 188 | 189 | ```js 190 | const path = require('path'); 191 | const webpack = require('webpack'); 192 | 193 | const { getLess } = require('@zougt/some-loader-utils'); 194 | 195 | const ThemeCssExtractWebpackPlugin = require('@zougt/theme-css-extract-webpack-plugin'); 196 | 197 | const multipleScopeVars = [ 198 | { 199 | // 必需 200 | scopeName: 'theme-default', 201 | // path 和 varsContent 必选一个 202 | path: path.resolve('src/theme/theme-default.less'), 203 | // varsContent参数等效于 path文件的内容 204 | // varsContent:`@primary-color:${defaultPrimaryColor};` 205 | }, 206 | 207 | { 208 | scopeName: 'theme-red', 209 | path: path.resolve('src/theme/theme-red.less'), 210 | }, 211 | ]; 212 | const extract = process.env.NODE_ENV === 'production'; 213 | const publicPath = '/'; 214 | const assetsDir = 'assets'; 215 | const extractCssOutputDir = `${assetsDir}/css`; 216 | 217 | module.exports = { 218 | output: { 219 | publicPath, 220 | }, 221 | module: { 222 | rules: [ 223 | { 224 | test: /\.less$/i, 225 | // webpack4 => less-loader v7.x , webpack5 => less-loader v10.x 226 | loader: 'less-loader', 227 | options: { 228 | lessOptions: { 229 | javascriptEnabled: true, 230 | }, 231 | implementation: getLess({ 232 | // getMultipleScopeVars优先于 lessOptions.multipleScopeVars 233 | getMultipleScopeVars: (lessOptions) => 234 | multipleScopeVars, 235 | // 可选项 236 | // implementation:less 237 | }), 238 | }, 239 | }, 240 | ], 241 | }, 242 | plugins: [ 243 | // 添加参数到浏览器端 244 | new webpack.DefinePlugin({ 245 | 'env.themeConfig': { 246 | multipleScopeVars: JSON.stringify(multipleScopeVars), 247 | extract: JSON.stringify(extract), 248 | publicPath: JSON.stringify(publicPath), 249 | extractCssOutputDir: JSON.stringify(extractCssOutputDir), 250 | }, 251 | }), 252 | 253 | new ThemeCssExtractWebpackPlugin({ 254 | multipleScopeVars, 255 | // 【注意】includeStyleWithColors作用: css中不是由主题色变量生成的颜色,也让它抽取到主题css内,可以提高权重 256 | includeStyleWithColors: [ 257 | { 258 | // color也可以是array,如 ["#ffffff","#000"] 259 | color: '#ffffff', 260 | // 排除属性,如 不提取背景色的#ffffff 261 | // excludeCssProps:["background","background-color"] 262 | // 排除选择器,如 不提取以下选择器的 #ffffff 263 | // excludeSelectors: [ 264 | // ".ant-btn-link:hover, .ant-btn-link:focus, .ant-btn-link:active", 265 | // ], 266 | }, 267 | { 268 | color: ['transparent', 'none'], 269 | }, 270 | ], 271 | // 默认使用哪份主题,默认取 multipleScopeVars[0].scopeName 272 | defaultScopeName: '', 273 | // 在生产模式是否抽取独立的主题css文件,extract为true以下属性有效 274 | extract, 275 | // 独立主题css文件的输出路径 276 | outputDir: extractCssOutputDir, 277 | // 会选取defaultScopeName对应的主题css文件在html添加link 278 | themeLinkTagId: 'theme-link-tag', 279 | // 是否对抽取的css文件内对应scopeName的权重类名移除 280 | removeCssScopeName: false, 281 | }), 282 | ], 283 | }; 284 | ``` 285 | 286 | **在线切换主题** 287 | 288 | 预设主题切换,需要做的事情 289 | 290 | 1、开发时只需,html 标签的 calss 添加对应的 scopeName,移除上个 scopeName 291 | 2、打包后,如果开启 extract: true,需要切换对应的 link 标签的 href 292 | 293 | 可以选择使用如下封装好的方法 294 | 295 | ```js 296 | import { toggleTheme } from '@zougt/theme-css-extract-webpack-plugin/dist/toggleTheme'; 297 | // env.themeConfig 来源 (webpack.DefinePlugin) 298 | const themeConfig = env.themeConfig; 299 | toggleTheme({ 300 | scopeName, 301 | multipleScopeVars: themeConfig.multipleScopeVars, 302 | extract: themeConfig.extract, 303 | publicPath: themeConfig.publicPath, 304 | outputDir: themeConfig.extractCssOutputDir, 305 | // customLinkHref: (href) => href, 306 | // themeLinkTagId: "theme-link-tag", 307 | // removeCssScopeName: false, 308 | // loading: { 309 | // show: () => {}, 310 | // hide: () => {}, 311 | // }, 312 | }); 313 | ``` 314 | 315 | **预设多主题编译原理示例(以 sass 为例)** 316 | 317 | **主题包含的可能不只是颜色部分** 318 | 319 | ```scss 320 | //src/theme/default-vars.scss 321 | /** 322 | *此scss变量文件作为multipleScopeVars去编译时,会自动移除!default以达到变量提升 323 | *同时此scss变量文件作为默认主题变量文件,被其他.scss通过 @import 时,必需 !default 324 | */ 325 | $primary-color: #0081ff !default; 326 | $--border-radius-base: 4px !default; 327 | ``` 328 | 329 | ```scss 330 | //src/theme/mauve-vars.scss 331 | $primary-color: #9c26b0 !default; 332 | $--border-radius-base: 8px !default; 333 | ``` 334 | 335 | ```scss 336 | //src/components/Button/style.scss 337 | @import '../../theme/default-vars'; 338 | .un-btn { 339 | position: relative; 340 | display: inline-block; 341 | font-weight: 400; 342 | white-space: nowrap; 343 | text-align: center; 344 | border: 1px solid transparent; 345 | background-color: $primary-color; 346 | border-radius: $--border-radius-base; 347 | .anticon { 348 | line-height: 1; 349 | } 350 | } 351 | ``` 352 | 353 | 编译之后 354 | 355 | src/components/Button/style.css 356 | 357 | ```css 358 | .un-btn { 359 | position: relative; 360 | display: inline-block; 361 | font-weight: 400; 362 | white-space: nowrap; 363 | text-align: center; 364 | border: 1px solid transparent; 365 | } 366 | .theme-default .un-btn { 367 | background-color: #0081ff; 368 | border-radius: 4px; 369 | } 370 | .theme-mauve .un-btn { 371 | background-color: #9c26b0; 372 | border-radius: 8px; 373 | } 374 | .un-btn .anticon { 375 | line-height: 1; 376 | } 377 | ``` 378 | 379 | 在`html`中改变 classname 切换主题,只作用于 html 标签 : 380 | 381 | ```html 382 | 383 | 384 | 385 | 386 | title 387 | 388 | 389 |
390 | 391 | 392 | 393 | ``` 394 | 395 | ```js 396 | document.documentElement.className = 'theme-mauve'; 397 | ``` 398 | 399 | ### 使用 Css Modules 400 | 401 | 如果是模块化的 scss,得到的 css 类似: 402 | 403 | ```css 404 | .src-components-Button-style_theme-default-3CPvz 405 | .src-components-Button-style_un-btn-1n85E { 406 | background-color: #0081ff; 407 | } 408 | .src-components-Button-style_theme-mauve-3yajX 409 | .src-components-Button-style_un-btn-1n85E { 410 | background-color: #9c26b0; 411 | } 412 | ``` 413 | 414 | 实际需要的结果应该是这样: 415 | 416 | ```css 417 | .theme-default .src-components-Button-style_un-btn-1n85E { 418 | background-color: #0081ff; 419 | } 420 | .theme-mauve .src-components-Button-style_un-btn-1n85E { 421 | background-color: #9c26b0; 422 | } 423 | ``` 424 | 425 | 在 webpack.config.js 需要对`css-loader` (v4.0+) 的 modules 属性添加 getLocalIdent: 426 | 427 | ```js 428 | const path = require('path'); 429 | // const sass = require("sass"); 430 | const { getSass } = require('@zougt/some-loader-utils'); 431 | const { interpolateName } = require('loader-utils'); 432 | function normalizePath(file) { 433 | return path.sep === '\\' ? file.replace(/\\/g, '/') : file; 434 | } 435 | const multipleScopeVars = [ 436 | { 437 | scopeName: 'theme-default', 438 | path: path.resolve('src/theme/default-vars.scss'), 439 | }, 440 | { 441 | scopeName: 'theme-mauve', 442 | path: path.resolve('src/theme/mauve-vars.scss'), 443 | }, 444 | ]; 445 | module.exports = { 446 | module: { 447 | rules: [ 448 | { 449 | test: /\.module.scss$/i, 450 | use: [ 451 | { 452 | loader: 'css-loader', 453 | options: { 454 | importLoaders: 1, 455 | modules: { 456 | localIdentName: 457 | process.env.NODE_ENV === 'production' 458 | ? '[hash:base64:5]' 459 | : '[path][name]_[local]-[hash:base64:5]', 460 | //使用 getLocalIdent 自定义模块化名称 , css-loader v4.0+ 461 | getLocalIdent: ( 462 | loaderContext, 463 | localIdentName, 464 | localName, 465 | options 466 | ) => { 467 | if ( 468 | multipleScopeVars.some( 469 | (item) => 470 | item.scopeName === localName 471 | ) 472 | ) { 473 | //localName 属于 multipleScopeVars 的不用模块化 474 | return localName; 475 | } 476 | const { context, hashPrefix } = options; 477 | const { resourcePath } = loaderContext; 478 | const request = normalizePath( 479 | path.relative(context, resourcePath) 480 | ); 481 | // eslint-disable-next-line no-param-reassign 482 | options.content = `${ 483 | hashPrefix + request 484 | }\x00${localName}`; 485 | const inname = interpolateName( 486 | loaderContext, 487 | localIdentName, 488 | options 489 | ); 490 | 491 | return inname.replace( 492 | /\\?\[local\\?]/gi, 493 | localName 494 | ); 495 | }, 496 | }, 497 | }, 498 | }, 499 | { 500 | loader: 'sass-loader', 501 | options: { 502 | implementation: getSass({ 503 | // getMultipleScopeVars优先于 sassOptions.multipleScopeVars 504 | getMultipleScopeVars: (sassOptions) => 505 | multipleScopeVars, 506 | // 可选项 507 | // implementation:sass 508 | }), 509 | }, 510 | }, 511 | ], 512 | }, 513 | ], 514 | }, 515 | }; 516 | ``` 517 | 518 | > 以上是基于 webpack 的多主题的编译方案实现,如需 vite 版本的请看 vite 插件[@zougt/vite-plugin-theme-preprocessor](https://github.com/GitOfZGT/vite-plugin-theme-preprocessor) 519 | 520 | ### multipleScopeVars 521 | 522 | 必需的 523 | 524 | > 当 multipleScopeVars 只有一项时, scopeName 就没有意义,但是 path 可以起到 变量提升的作用 525 | 526 | Type `object[]` 527 | 528 | #### multipleScopeVars[].scopeName 529 | 530 | Type `string` 531 | 532 | #### multipleScopeVars[].path 533 | 534 | 必需的,变量文件的绝对路径 535 | 536 | Type `string || string[]` 537 | 538 | ```js 539 | const multipleScopeVars = [ 540 | { 541 | scopeName: 'theme-default', 542 | path: path.resolve('src/theme/default-vars.less'), 543 | }, 544 | { 545 | scopeName: 'theme-mauve', 546 | path: path.resolve('src/theme/mauve-vars.less'), 547 | }, 548 | ]; 549 | ``` 550 | 551 | ### multipleScopeVars[].includeStyles 552 | 553 | > v1.3.0 支持 includeStyles,只在预设主题模式有效 554 | 555 | Type: `Object` 556 | 557 | 当存在以下情况时,可以用这个属性处理 558 | 559 | ```css 560 | .theme-blue .el-button:focus, 561 | .theme-blue .el-button:hover { 562 | /*这里的color值由 $primary-color 编译得来的,所以选择器前面加了 .theme-blue 提高了权重*/ 563 | color: #0281ff; 564 | border-color: #b3d9ff; 565 | background-color: #e6f2ff; 566 | } 567 | .el-button--primary:focus, 568 | .el-button--primary:hover { 569 | /*这里的color值不是由 变量 编译得来的,这时就会被上面那个 color 覆盖了, 实际上这里的color才是需要的效果*/ 570 | color: #fff; 571 | } 572 | ``` 573 | 574 | ```js 575 | const includeStyles = { 576 | '.el-button--primary:hover, .el-button--primary:focus': { 577 | color: '#FFFFFF', 578 | }, 579 | }; 580 | const multipleScopeVars = [ 581 | { 582 | scopeName: 'theme-default', 583 | path: path.resolve('src/theme/default-vars.less'), 584 | includeStyles, 585 | }, 586 | { 587 | scopeName: 'theme-mauve', 588 | path: path.resolve('src/theme/mauve-vars.less'), 589 | includeStyles, 590 | }, 591 | ]; 592 | ``` 593 | 594 | 得到 595 | 596 | ```css 597 | .theme-blue .el-button:focus, 598 | .theme-blue .el-button:hover { 599 | /*这里的color值由 $primary-color 编译得来的,所以选择器前面加了 .theme-blue 提高了权重*/ 600 | color: #0281ff; 601 | border-color: #b3d9ff; 602 | background-color: #e6f2ff; 603 | } 604 | .theme-blue .el-button--primary:focus, 605 | .theme-blue .el-button--primary:hover { 606 | /*这里的color值不是由 变量 编译得来的,通过includeStyles也提高了权重得到实际的效果*/ 607 | color: #ffffff; 608 | } 609 | ``` 610 | 611 | 出现权重问题效果图 612 | 613 | ![includeStyles](https://user-images.githubusercontent.com/21262000/133917696-804f8a75-2540-48e4-8b46-84ddc0b3fef1.png) 614 | 615 | 使用了 includeStyles 的效果图 616 | 617 | ![includeStyles](https://user-images.githubusercontent.com/21262000/133917724-4d64f4e5-af9b-4dd6-8481-b10b20f3204f.png) 618 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const MIN_BABEL_VERSION = 7; 2 | 3 | module.exports = (api) => { 4 | api.assertVersion(MIN_BABEL_VERSION); 5 | api.cache(true); 6 | 7 | return { 8 | presets: [ 9 | [ 10 | "@babel/preset-env", 11 | { 12 | targets: { 13 | node: "10.13.0", 14 | }, 15 | }, 16 | ], 17 | ], 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /color-test.js: -------------------------------------------------------------------------------- 1 | const Color = require('color'); 2 | // const differenceAggMap1 = require('./differenceAggMap2') 3 | // // const differenceAggMap2 = require('./differenceAggMap2') 4 | 5 | // const agg= new Set(); 6 | // for (const key in differenceAggMap1) { 7 | // if (Object.hasOwnProperty.call(differenceAggMap1, key)) { 8 | // const element = differenceAggMap1[key]; 9 | // element.forEach(val=>{ 10 | // agg.add(val) 11 | // }) 12 | // } 13 | // } 14 | // console.log(Array.from(agg)) 15 | // console.log(Color('#09A500').hsv().array()) 16 | // console.log(Color('#CADCC9').hsv().array()) 17 | // console.log(Color('hsla(120,0%,75%,0.3)').hsv()) 18 | 19 | // 298 84 64 20 | // 298 2 86 21 | // 0 82 -12 22 | 23 | // const values = [ 24 | // '#F4791E', 25 | // '#fef2e9', 26 | // '#fde4d2', 27 | // '#f4791e', 28 | // '#f6944b', 29 | // '#fbc9a5', 30 | // '#f8af78', 31 | // '#fcd7bc', 32 | // '#dc6d1b', 33 | // '#fabc8f', 34 | // '#fef4ed', 35 | // '#fffcfb', 36 | // '#fef8f4', 37 | // '#f6964f', 38 | // ]; 39 | // const values =[ 40 | // '#9D26B0', '#F4791E', '#f5e9f7', 41 | // '#ebd4ef', '#9d26b0', '#b151c0', 42 | // '#d8a8df', '#fef2e9', '#fde4d2', 43 | // '#f4791e', '#f6944b', '#fbc9a5', 44 | // '#c47dd0', '#f8af78', '#e2bee7', 45 | // '#8d229e', '#ce93d8', '#fcd7bc', 46 | // '#dc6d1b', '#fabc8f', '#f7eef9', 47 | // '#fef4ed', '#fdfbfd', '#fffcfb', 48 | // '#faf4fb', '#fef8f4', '#be36d3', 49 | // '#f6964f' 50 | // ] 51 | // const values = [ 52 | // '#9D26B0', 53 | // '-1px 0 0 0 #c47dd0', 54 | // '5px 12px', 55 | // '#f5e9f7', 56 | // '#ebd4ef', 57 | // '#9d26b0', 58 | // '#b151c0', 59 | // '#d8a8df', 60 | // '#faf4fb', 61 | // '2px dashed #9D26B0', 62 | // '0 0 2px 2px #9D26B0', 63 | // '10px', 64 | // '#be36d3', 65 | // '10px 10px 0 20px', 66 | // '10px 10px 10px 20px', 67 | // '10px 20px', 68 | // '10px 20px 20px 20px', 69 | // '#e2bee7', 70 | // '#8d229e', 71 | // '#ce93d8', 72 | // '#c47dd0', 73 | // '5px', 74 | // '#fdfbfd', 75 | // '2px solid #9D26B0', 76 | // '0 0 2px 2px #9D26B0 inset', 77 | // '#f7eef9', 78 | // ].reduce((tol, curr) => { 79 | // const n = curr.split(/\s+/); 80 | // return [...tol, ...n.filter((c) => c.includes('#'))]; 81 | // }, []); 82 | // const primaryArr = Color('#9D26B0').hsv().array(); 83 | // values.forEach((val) => { 84 | // const carr = Color(val).hsv().array(); 85 | 86 | // console.log(carr.map((v, i) => primaryArr[i] - v)); 87 | // }); 88 | // const jiezhi = [ 89 | // [0, 0, 0], 90 | // [0.41382922996353955, 38.50524475524475, -12.549019607843135], 91 | // [0.3105590062112924, 72.74107471475892, -27.84313725490196], 92 | // [0.6280193236716514, 67.1120197793838, -24.705882352941174], 93 | // [0, 0, 0], 94 | // [-0.15276145710913624, 20.596590909090907, -6.274509803921575], 95 | // [-0.6245059288537504, 53.74541377904606, -18.431372549019613], 96 | // [0.3105590062114061, 75.62024628757698, -29.411764705882348], 97 | // [0, 0, 0], 98 | // [0, 0, 0], 99 | // [-0.2353918582108463, 4.001507970702292, -13.725490196078425], 100 | // [-0.9437963944856165, 60.66017316017316, -21.5686274509804], 101 | // [-0.03506311360433756, -0.07192174913694771, 7.058823529411761], 102 | // [0.43478260869574115, 46.464646464646464, -15.686274509803923], 103 | // [0.41382922996353955, 38.50524475524475, -12.549019607843135], 104 | // [-8.260869565217263, 77.6185770750988, -30.196078431372555], 105 | // [0, 0, 0], 106 | // [0, 0, 0], 107 | // [2.648221343873729, 73.99142022635999, -28.627450980392155], 108 | // ]; 109 | // function outAllHex(color) { 110 | // const primaryArr = Color(color).hsv().array(); 111 | // const outhex = jiezhi.map((item) => 112 | // Color.hsv( 113 | // item.map((v, i) => { 114 | // const newv = primaryArr[i] - v; 115 | // const max = i === 0 ? 360 : 100; 116 | // return newv < 0 ? 0 : newv > max ? max : newv; 117 | // }) 118 | // ) 119 | // .hex() 120 | // .toString() 121 | // ); 122 | // console.log(outhex); 123 | // } 124 | // outAllHex('#F4791E'); 125 | 126 | function getHsvPercentGias(primaryColor, resultColor) { 127 | const primaryHsvArr = primaryColor.hsv().array(); 128 | const resultHsvArr = resultColor.hsv().array(); 129 | const getGiasPercent = (index) => 130 | primaryHsvArr[index] !== 0 131 | ? (primaryHsvArr[index] - resultHsvArr[index]) / 132 | primaryHsvArr[index] 133 | : primaryHsvArr[index]; 134 | return [getGiasPercent(0), getGiasPercent(1), getGiasPercent(2)]; 135 | } 136 | 137 | function getTargetColor(newPrimaryColor, resultColor, percentGias) { 138 | const primaryHsvArr = newPrimaryColor.hsv().array(); 139 | const getTargetVal = (index) => 140 | primaryHsvArr[index] - percentGias[index] * primaryHsvArr[index]; 141 | const targetColor = Color.hsv([ 142 | getTargetVal(0), 143 | getTargetVal(1), 144 | getTargetVal(2), 145 | ]); 146 | targetColor.valpha = resultColor.valpha; 147 | return targetColor; 148 | } 149 | 150 | // console.log(Color('#CA359D').hsv().rgb()); 151 | 152 | // const percentGias = getHsvPercentGias(Color('#CA359D'), Color('#B6E8E6')); 153 | // console.log(getTargetColor(Color('#296CDC'),Color('#B6E8E6'),percentGias).hex()); 154 | 155 | function mix(color1, color2, weight) { 156 | const rgb1 = color1.rgb(); 157 | const rgb2 = color2.rgb(); 158 | weight = weight ? parseInt(weight, 10) : 50; 159 | const p = weight / 100.0; 160 | const w = p * 2 - 1; 161 | const a = rgb1.valpha - rgb2.valpha; 162 | const w1 = ((w * a === -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0; 163 | const alpha = rgb1.valpha * p + rgb2.valpha * (1 - p); 164 | const getRgbVal = (index) => 165 | Math.round(rgb1.color[index] * w1 + rgb2.color[index] * (1 - w1)); 166 | // const rgb = [getRgbVal(0), getRgbVal(1), getRgbVal(2)]; 167 | return Color( 168 | `rgba(${getRgbVal(0)}, ${getRgbVal(1)}, ${getRgbVal(2)}, ${alpha})` 169 | ); 170 | } 171 | 172 | function reverseMix(defaultPrimary, resultColor, weight) { 173 | const pRgb = defaultPrimary.rgb(); 174 | const rRgb = resultColor.rgb(); 175 | weight = weight ? parseInt(weight, 10) : 50; 176 | const p = weight / 100.0; 177 | const w = p * 2 - 1; 178 | const xalpha = (resultColor.valpha - pRgb.valpha * (1 - p)) / p; 179 | const a = xalpha - pRgb.valpha; 180 | const w1 = ((w * a === -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0; 181 | const getRgbVal = (index) => 182 | Math.round((rRgb.color[index] - pRgb.color[index] * (1 - w1)) / w1); 183 | // const rgb = [getRgbVal(0), getRgbVal(1), getRgbVal(2)]; 184 | return Color( 185 | `rgba(${getRgbVal(0)}, ${getRgbVal(1)}, ${getRgbVal(2)}, ${xalpha})` 186 | ); 187 | } 188 | // const mixWeights = { 189 | // '#ea9fa3': '30%', 190 | // }; 191 | // const resultColorString = '#ea9fa3'; 192 | // const defaultPrimary = Color('#F6CD9F'); 193 | // const resultColor = Color(resultColorString); 194 | // const xColor = reverseMix( 195 | // defaultPrimary, 196 | // resultColor, 197 | // mixWeights[resultColorString] 198 | // ); 199 | // const primaryColor = Color('#34DC70'); 200 | // const targetColor = mix(xColor, primaryColor, mixWeights[resultColorString]); 201 | // console.log(Color('blanchedalmond')); 202 | 203 | const values = [ 204 | '#9D26B0', 205 | '-1px 0 0 0 #c47dd0', 206 | '5px 12px', 207 | '#f5e9f7', 208 | '#ebd4ef', 209 | '#9d26b0', 210 | '#b151c0', 211 | '#d8a8df', 212 | '#faf4fb', 213 | '2px dashed #9D26B0', 214 | '0 0 2px 2px #9D26B0', 215 | '10px', 216 | '#be36d3', 217 | '10px 10px 0 20px', 218 | '10px 10px 10px 20px', 219 | '10px 20px', 220 | '10px 20px 20px 20px', 221 | '#e2bee7', 222 | '#8d229e', 223 | '#ce93d8', 224 | '#c47dd0', 225 | '5px', 226 | '#fdfbfd', 227 | '2px solid #9D26B0', 228 | '0 0 2px 2px #9D26B0 inset', 229 | '#f7eef9', 230 | ]; 231 | 232 | const colorValueReg = { 233 | hex: /#([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3})/gi, 234 | rgb: /rgb\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*\)/gi, 235 | rgba: /rgba\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*(0|1|1.0|0?.[0-9])\s*\)/gi, 236 | hsl: /hsl\(\s*\d{1,3}\s*,\s*(0|\d{1,3}%)\s*,\s*(0|\d{1,3}%)\s*\)/gi, 237 | hsla: /hsla\(\s*\d{1,3}\s*,\s*(0|\d{1,3}%)\s*,\s*(0|\d{1,3}%)\s*,\s*(0|1|1.0|0?.[0-9])\s*\)/gi, 238 | }; 239 | 240 | function separatValue(cssValues) { 241 | const cssColors = []; 242 | const hybridValueMap = {}; 243 | const otherValues = []; 244 | cssValues.forEach((val) => { 245 | const hasSpace = /\s+/.test(val); 246 | const hex = val.match(colorValueReg.hex); 247 | const rgb = val.match(colorValueReg.rgb); 248 | const rgba = val.match(colorValueReg.rgba); 249 | const hsl = val.match(colorValueReg.hsl); 250 | const hsla = val.match(colorValueReg.hsla); 251 | if (!hex && !rgb && !rgba && !hsl && !hsla) { 252 | otherValues.push(val); 253 | } else { 254 | let residueVal = val; 255 | const getResidueVal = (colorArr, cssColors, residueVal) => { 256 | let newVal = residueVal; 257 | if (colorArr) { 258 | colorArr.forEach((cval) => { 259 | cssColors.push(cval); 260 | newVal = residueVal.replace( 261 | cval, 262 | hasSpace ? '#{color}' : '' 263 | ); 264 | }); 265 | } 266 | return newVal; 267 | }; 268 | residueVal = getResidueVal(hex, cssColors, residueVal); 269 | residueVal = getResidueVal(rgb, cssColors, residueVal); 270 | residueVal = getResidueVal(rgba, cssColors, residueVal); 271 | residueVal = getResidueVal(hsl, cssColors, residueVal); 272 | residueVal = getResidueVal(hsla, cssColors, residueVal); 273 | if (residueVal) { 274 | hybridValueMap[residueVal] = val; 275 | } 276 | } 277 | }); 278 | return { 279 | cssColors: Array.from(new Set(cssColors)), 280 | hybridValueMap, 281 | otherValues, 282 | }; 283 | } 284 | const { cssColors, hybridValueMap, otherValues } = separatValue(values); 285 | const varsColorValues = ['#9D26B0']; 286 | const defaultPrimaryColor = '#9D26B0'; 287 | function getResultColorReplaceMap({ varsColorValues, defaultPrimaryColor }) { 288 | const sourceColorMap = {}; 289 | // const mixWeightsMap = {}; 290 | cssColors.forEach((colorString) => { 291 | const resultColor = Color(colorString).hsv(); 292 | let finded = false; 293 | for (let index = 0; index < varsColorValues.length; index++) { 294 | const varStr = varsColorValues[index]; 295 | const varColor = Color(varStr).hsv(); 296 | if (varColor.color[0] === resultColor.color[0]) { 297 | sourceColorMap[colorString] = { 298 | percentGias: getHsvPercentGias(varColor, resultColor), 299 | varColorString: varStr, 300 | }; 301 | finded = true; 302 | break; 303 | } 304 | } 305 | if (!finded) { 306 | const varColor = Color(defaultPrimaryColor).hsv(); 307 | sourceColorMap[colorString] = { 308 | percentGias: getHsvPercentGias(varColor, resultColor), 309 | varColorString: defaultPrimaryColor, 310 | }; 311 | // if (varColor.color[0] > resultColor.color[0]) { 312 | // sourceColorMap[colorString].percentGias = getHsvPercentGias( 313 | // varColor, 314 | // resultColor 315 | // ); 316 | // } else { 317 | // mixWeightsMap[colorString] = '50%'; 318 | // } 319 | } 320 | }); 321 | return sourceColorMap; 322 | } 323 | const sourceColorMap = getResultColorReplaceMap({ 324 | varsColorValues, 325 | defaultPrimaryColor, 326 | }); 327 | // console.log(mixWeightsMap); 328 | 329 | function getReplaceStyleValues({ 330 | primaryColor = '', 331 | targetValueReplacer = {}, 332 | sourceColorMap = {}, 333 | hybridValueMap = {}, 334 | otherValues = [], 335 | }) { 336 | const replaceColorMap = {}; 337 | for (const key in sourceColorMap) { 338 | if (Object.hasOwnProperty.call(sourceColorMap, key)) { 339 | const item = sourceColorMap[key]; 340 | replaceColorMap[key] = getTargetColor( 341 | Color(primaryColor), 342 | Color(key), 343 | item.percentGias 344 | ) 345 | .hex() 346 | .toString(); 347 | } 348 | } 349 | const replaceHybridValueMap = {}; 350 | for (const sourceValue in hybridValueMap) { 351 | if (Object.hasOwnProperty.call(hybridValueMap, sourceValue)) { 352 | const temp = hybridValueMap[sourceValue]; 353 | const sourceColors = Object.keys(sourceColorMap); 354 | const findColor = sourceColors.find((colorStr) => 355 | sourceValue.includes(colorStr) 356 | ); 357 | if (findColor) { 358 | replaceHybridValueMap[sourceValue] = ( 359 | targetValueReplacer[temp] || temp 360 | ).replace('#{color}', replaceColorMap[findColor]); 361 | } 362 | } 363 | } 364 | const replaceOtherValueMap = {}; 365 | otherValues.forEach((val) => { 366 | if (targetValueReplacer[val]) { 367 | replaceOtherValueMap[val] = targetValueReplacer[val]; 368 | } 369 | }); 370 | return { replaceColorMap, replaceHybridValueMap, replaceOtherValueMap }; 371 | } 372 | 373 | function setNewThemeStyle({ 374 | styleTagId, 375 | primaryColor, 376 | targetValueReplacer, 377 | sourceThemeStyle, 378 | sourceColorMap, 379 | hybridValueMap, 380 | otherValues, 381 | }) { 382 | const { replaceColorMap, replaceHybridValueMap, replaceOtherValueMap } = 383 | getReplaceStyleValues({ 384 | primaryColor, 385 | targetValueReplacer, 386 | sourceColorMap, 387 | hybridValueMap, 388 | otherValues, 389 | }); 390 | 391 | console.log(replaceColorMap, replaceHybridValueMap, replaceOtherValueMap); 392 | let newStyleContent = sourceThemeStyle; 393 | Object.keys(replaceOtherValueMap).forEach((sourceValue) => { 394 | newStyleContent = newStyleContent.replace( 395 | new RegExp(sourceValue, 'gi'), 396 | replaceOtherValueMap[sourceValue] 397 | ); 398 | }); 399 | Object.keys(replaceHybridValueMap).forEach((sourceValue) => { 400 | newStyleContent = newStyleContent.replace( 401 | new RegExp(sourceValue, 'gi'), 402 | replaceHybridValueMap[sourceValue] 403 | ); 404 | }); 405 | Object.keys(replaceColorMap).forEach((sourceValue) => { 406 | newStyleContent = newStyleContent.replace( 407 | new RegExp(sourceValue, 'gi'), 408 | replaceColorMap[sourceValue] 409 | ); 410 | }); 411 | console.log(newStyleContent) 412 | // const styleTag = document.getElementById(styleTagId); 413 | // if (styleTag) { 414 | // styleTag.content = newStyleContent; 415 | // } 416 | } 417 | 418 | const sourceThemeStyle = `.el-checkbox.is-bordered.is-checked { border-color: #9D26B0;}.el-checkbox__input.is-checked .el-checkbox__inner { background-color: #9D26B0; border-color: #9D26B0;}.el-checkbox__input.is-checked + .el-checkbox__label { color: #9D26B0;}.el-checkbox__input.is-focus .el-checkbox__inner { border-color: #9D26B0;}.el-checkbox__input.is-indeterminate .el-checkbox__inner { background-color: #9D26B0; border-color: #9D26B0;}.el-checkbox__inner:hover { border-color: #9D26B0;}.el-checkbox-button__inner:hover { color: #9D26B0;}.el-checkbox-button.is-checked .el-checkbox-button__inner { background-color: #9D26B0; border-color: #9D26B0; box-shadow: -1px 0 0 0 #c47dd0;}.el-checkbox-button.is-checked:first-child .el-checkbox-button__inner { border-left-color: #9D26B0;}.el-checkbox-button.is-focus .el-checkbox-button__inner { border-color: #9D26B0;}.el-checkbox-button--mini .el-checkbox-button__inner { padding: 5px 12px;}.el-checkbox-button--mini .el-checkbox-button__inner.is-round { padding: 5px 12px;}.el-tag { background-color: #f5e9f7; border-color: #ebd4ef; color: #9D26B0;}.el-tag.is-hit { border-color: #9D26B0;}.el-tag .el-tag__close { color: #9d26b0;}.el-tag .el-tag__close:hover { background-color: #9d26b0;}.el-tag--dark { background-color: #9d26b0; border-color: #9d26b0;}.el-tag--dark.is-hit { border-color: #9D26B0;}.el-tag--dark .el-tag__close:hover { background-color: #b151c0;}.el-tag--plain { border-color: #d8a8df; color: #9d26b0;}.el-tag--plain.is-hit { border-color: #9D26B0;}.el-tag--plain .el-tag__close { color: #9d26b0;}.el-tag--plain .el-tag__close:hover { background-color: #9d26b0;}.el-table-filter__list-item:hover { background-color: #f5e9f7; color: #b151c0;}.el-table-filter__list-item.is-active { background-color: #9D26B0;}.el-table-filter__bottom button:hover { color: #9D26B0;}.yb-main-aside .el-menu:not([class~=el-menu--collapse]) .el-menu-item.is-active::after { background-color: #f5e9f7;}.yb-main-aside-footer:hover { background-color: #faf4fb;}.yb-main-body-tab-center .router-tab__item.is-active,.yb-main-body-tab-center .router-tab__item:hover { color: #9D26B0;}.el-textarea__inner:focus { border-color: #9D26B0;}.el-input__inner:focus { border-color: #9D26B0;}.el-input.is-active .el-input__inner { border-color: #9D26B0;}.el-input-number__increase:hover, .el-input-number__decrease:hover { color: #9D26B0;}.el-input-number__increase:hover:not(.is-disabled) ~ .el-input .el-input__inner:not(.is-disabled), .el-input-number__decrease:hover:not(.is-disabled) ~ .el-input .el-input__inner:not(.is-disabled) { border-color: #9D26B0;}.el-tag { background-color: #f5e9f7; border-color: #ebd4ef; color: #9D26B0;}.el-tag.is-hit { border-color: #9D26B0;}.el-tag .el-tag__close { color: #9d26b0;}.el-tag .el-tag__close:hover { background-color: #9d26b0;}.el-tag--dark { background-color: #9d26b0; border-color: #9d26b0;}.el-tag--dark.is-hit { border-color: #9D26B0;}.el-tag--dark .el-tag__close:hover { background-color: #b151c0;}.el-tag--plain { border-color: #d8a8df; color: #9d26b0;}.el-tag--plain.is-hit { border-color: #9D26B0;}.el-tag--plain .el-tag__close { color: #9d26b0;}.el-tag--plain .el-tag__close:hover { background-color: #9d26b0;}.el-progress-bar__inner { background-color: #9D26B0;}.el-upload--picture-card:hover { border-color: #9D26B0; color: #9D26B0;}.el-upload:focus { border-color: #9D26B0; color: #9D26B0;}.el-upload:focus .el-upload-dragger { border-color: #9D26B0;}.el-upload-dragger .el-upload__text em { color: #9D26B0;}.el-upload-dragger:hover { border-color: #9D26B0;}.el-upload-dragger.is-dragover { border: 2px dashed #9D26B0;}.el-upload-list__item .el-icon-close-tip { color: #9D26B0;}.el-upload-list__item.is-success .el-upload-list__item-name:hover, .el-upload-list__item.is-success .el-upload-list__item-name:focus { color: #9D26B0;}.el-upload-list__item-delete:hover { color: #9D26B0;}.yb-page-404-content span { color: #9D26B0;}.el-textarea__inner:focus { border-color: #9D26B0;}.el-input__inner:focus { border-color: #9D26B0;}.el-input.is-active .el-input__inner { border-color: #9D26B0;}.el-tag { background-color: #f5e9f7; border-color: #ebd4ef; color: #9D26B0;}.el-tag.is-hit { border-color: #9D26B0;}.el-tag .el-tag__close { color: #9d26b0;}.el-tag .el-tag__close:hover { background-color: #9d26b0;}.el-tag--dark { background-color: #9d26b0; border-color: #9d26b0;}.el-tag--dark.is-hit { border-color: #9D26B0;}.el-tag--dark .el-tag__close:hover { background-color: #b151c0;}.el-tag--plain { border-color: #d8a8df; color: #9d26b0;}.el-tag--plain.is-hit { border-color: #9D26B0;}.el-tag--plain .el-tag__close { color: #9d26b0;}.el-tag--plain .el-tag__close:hover { background-color: #9d26b0;}.el-checkbox.is-bordered.is-checked { border-color: #9D26B0;}.el-checkbox__input.is-checked .el-checkbox__inner { background-color: #9D26B0; border-color: #9D26B0;}.el-checkbox__input.is-checked + .el-checkbox__label { color: #9D26B0;}.el-checkbox__input.is-focus .el-checkbox__inner { border-color: #9D26B0;}.el-checkbox__input.is-indeterminate .el-checkbox__inner { background-color: #9D26B0; border-color: #9D26B0;}.el-checkbox__inner:hover { border-color: #9D26B0;}.el-checkbox-button__inner:hover { color: #9D26B0;}.el-checkbox-button.is-checked .el-checkbox-button__inner { background-color: #9D26B0; border-color: #9D26B0; box-shadow: -1px 0 0 0 #c47dd0;}.el-checkbox-button.is-checked:first-child .el-checkbox-button__inner { border-left-color: #9D26B0;}.el-checkbox-button.is-focus .el-checkbox-button__inner { border-color: #9D26B0;}.el-checkbox-button--mini .el-checkbox-button__inner { padding: 5px 12px;}.el-checkbox-button--mini .el-checkbox-button__inner.is-round { padding: 5px 12px;}.el-radio.is-bordered.is-checked { border-color: #9D26B0;}.el-radio__input.is-checked .el-radio__inner { border-color: #9D26B0; background: #9D26B0;}.el-radio__input.is-checked + .el-radio__label { color: #9D26B0;}.el-radio__input.is-focus .el-radio__inner { border-color: #9D26B0;}.el-radio__inner:hover { border-color: #9D26B0;}.el-radio:focus:not(.is-focus):not(:active):not(.is-disabled) .el-radio__inner { box-shadow: 0 0 2px 2px #9D26B0;}.el-cascader-node.in-active-path, .el-cascader-node.is-selectable.in-checked-path, .el-cascader-node.is-active { color: #9D26B0;}.el-cascader .el-input .el-input__inner:focus { border-color: #9D26B0;}.el-cascader .el-input.is-focus .el-input__inner { border-color: #9D26B0;}.el-cascader__suggestion-item.is-checked { color: #9D26B0;}.el-drawer__header { padding: 10px;}.yb-ball-loading:not(:required) { background: #9D26B0;}.yb-ball-loading:not(:required)::after { background: #be36d3;}.yb-table-control-icon:hover { color: #9D26B0;}.type-radio::v-deep .el-radio.is-checked .el-radio__inner { color: #9D26B0;}.el-drawer__header { margin-bottom: 10px; padding: 10px 10px 0 20px;}.el-dialog__header { padding: 10px 10px 10px 20px;}.el-dialog__body { padding: 10px 20px;}.el-dialog__footer { padding: 10px 20px 20px 20px;}.el-loading-spinner .el-loading-text { color: #9D26B0;}.el-loading-spinner .path { stroke: #9D26B0;}.el-loading-spinner i { color: #9D26B0;}.el-button:hover, .el-button:focus { color: #9D26B0; border-color: #e2bee7; background-color: #f5e9f7;}.el-button:active { color: #8d229e; border-color: #8d229e;}.el-button.is-plain:hover, .el-button.is-plain:focus { border-color: #9D26B0; color: #9D26B0;}.el-button.is-plain:active { border-color: #8d229e; color: #8d229e;}.el-button.is-active { color: #8d229e; border-color: #8d229e;}.el-button--primary { background-color: #9D26B0; border-color: #9D26B0;}.el-button--primary:hover, .el-button--primary:focus { background: #b151c0; border-color: #b151c0;}.el-button--primary:active { background: #8d229e; border-color: #8d229e;}.el-button--primary.is-active { background: #8d229e; border-color: #8d229e;}.el-button--primary.is-disabled, .el-button--primary.is-disabled:hover, .el-button--primary.is-disabled:focus, .el-button--primary.is-disabled:active { background-color: #ce93d8; border-color: #ce93d8;}.el-button--primary.is-plain { color: #9D26B0; background: #f5e9f7; border-color: #d8a8df;}.el-button--primary.is-plain:hover, .el-button--primary.is-plain:focus { background: #9D26B0; border-color: #9D26B0;}.el-button--primary.is-plain:active { background: #8d229e; border-color: #8d229e;}.el-button--primary.is-plain.is-disabled, .el-button--primary.is-plain.is-disabled:hover, .el-button--primary.is-plain.is-disabled:focus, .el-button--primary.is-plain.is-disabled:active { color: #c47dd0; background-color: #f5e9f7; border-color: #ebd4ef;}.el-button--mini { padding: 5px 12px;}.el-button--mini.is-round { padding: 5px 12px;}.el-button--mini.is-circle { padding: 5px;}.el-button--text { color: #9D26B0;}.el-button--text:hover, .el-button--text:focus { color: #b151c0;}.el-button--text:active { color: #8d229e;}.el-progress-bar__inner { background-color: #9D26B0;}.el-drawer__header { margin-bottom: 10px; padding: 10px 10px 0 20px;}.el-dialog__header { padding: 10px 10px 10px 20px;}.el-dialog__body { padding: 10px 20px;}.el-dialog__footer { padding: 10px 20px 20px 20px;}.el-date-table td.today span { color: #9D26B0;}.el-date-table td.available:hover { color: #9D26B0;}.el-date-table td.current:not(.disabled) span { background-color: #9D26B0;}.el-date-table td.start-date span, .el-date-table td.end-date span { background-color: #9D26B0;}.el-date-table td.selected span { background-color: #9D26B0;}.el-month-table td.today .cell { color: #9D26B0;}.el-month-table td .cell:hover { color: #9D26B0;}.el-month-table td.start-date .cell, .el-month-table td.end-date .cell { background-color: #9D26B0;}.el-month-table td.current:not(.disabled) .cell { color: #9D26B0;}.el-year-table td.today .cell { color: #9D26B0;}.el-year-table td .cell:hover { color: #9D26B0;}.el-year-table td.current:not(.disabled) .cell { color: #9D26B0;}.el-time-spinner__arrow:hover { color: #9D26B0;}.el-range-editor.is-active { border-color: #9D26B0;}.el-range-editor.is-active:hover { border-color: #9D26B0;}.el-picker-panel__shortcut:hover { color: #9D26B0;}.el-picker-panel__shortcut.active { color: #9D26B0;}.el-picker-panel__icon-btn:hover { color: #9D26B0;}.el-date-picker__header-label:hover { color: #9D26B0;}.el-date-picker__header-label.active { color: #9D26B0;}.el-time-panel__btn.confirm { color: #9D26B0;}.el-textarea__inner:focus { border-color: #9D26B0;}.el-input__inner:focus { border-color: #9D26B0;}.el-input.is-active .el-input__inner { border-color: #9D26B0;}.el-radio.is-bordered.is-checked { border-color: #9D26B0;}.el-radio__input.is-checked .el-radio__inner { border-color: #9D26B0; background: #9D26B0;}.el-radio__input.is-checked + .el-radio__label { color: #9D26B0;}.el-radio__input.is-focus .el-radio__inner { border-color: #9D26B0;}.el-radio__inner:hover { border-color: #9D26B0;}.el-radio:focus:not(.is-focus):not(:active):not(.is-disabled) .el-radio__inner { box-shadow: 0 0 2px 2px #9D26B0;}.el-checkbox.is-bordered.is-checked { border-color: #9D26B0;}.el-checkbox__input.is-checked .el-checkbox__inner { background-color: #9D26B0; border-color: #9D26B0;}.el-checkbox__input.is-checked + .el-checkbox__label { color: #9D26B0;}.el-checkbox__input.is-focus .el-checkbox__inner { border-color: #9D26B0;}.el-checkbox__input.is-indeterminate .el-checkbox__inner { background-color: #9D26B0; border-color: #9D26B0;}.el-checkbox__inner:hover { border-color: #9D26B0;}.el-checkbox-button__inner:hover { color: #9D26B0;}.el-checkbox-button.is-checked .el-checkbox-button__inner { background-color: #9D26B0; border-color: #9D26B0; box-shadow: -1px 0 0 0 #c47dd0;}.el-checkbox-button.is-checked:first-child .el-checkbox-button__inner { border-left-color: #9D26B0;}.el-checkbox-button.is-focus .el-checkbox-button__inner { border-color: #9D26B0;}.el-checkbox-button--mini .el-checkbox-button__inner { padding: 5px 12px;}.el-checkbox-button--mini .el-checkbox-button__inner.is-round { padding: 5px 12px;}.el-tag { background-color: #f5e9f7; border-color: #ebd4ef; color: #9D26B0;}.el-tag.is-hit { border-color: #9D26B0;}.el-tag .el-tag__close { color: #9d26b0;}.el-tag .el-tag__close:hover { background-color: #9d26b0;}.el-tag--dark { background-color: #9d26b0; border-color: #9d26b0;}.el-tag--dark.is-hit { border-color: #9D26B0;}.el-tag--dark .el-tag__close:hover { background-color: #b151c0;}.el-tag--plain { border-color: #d8a8df; color: #9d26b0;}.el-tag--plain.is-hit { border-color: #9D26B0;}.el-tag--plain .el-tag__close { color: #9d26b0;}.el-tag--plain .el-tag__close:hover { background-color: #9d26b0;}.el-table th.el-table__cell > .cell.highlight { color: #9D26B0;}.el-table .ascending .sort-caret.ascending { border-bottom-color: #9D26B0;}.el-table .descending .sort-caret.descending { border-top-color: #9D26B0;}.el-table--striped .el-table__body tr.el-table__row--striped.current-row td.el-table__cell { background-color: #f5e9f7;}.el-table__body tr.current-row > td.el-table__cell { background-color: #f5e9f7;}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected { color: #9D26B0;}.el-textarea__inner:focus { border-color: #9D26B0;}.el-input__inner:focus { border-color: #9D26B0;}.el-input.is-active .el-input__inner { border-color: #9D26B0;}.el-tag { background-color: #f5e9f7; border-color: #ebd4ef; color: #9D26B0;}.el-tag.is-hit { border-color: #9D26B0;}.el-tag .el-tag__close { color: #9d26b0;}.el-tag .el-tag__close:hover { background-color: #9d26b0;}.el-tag--dark { background-color: #9d26b0; border-color: #9d26b0;}.el-tag--dark.is-hit { border-color: #9D26B0;}.el-tag--dark .el-tag__close:hover { background-color: #b151c0;}.el-tag--plain { border-color: #d8a8df; color: #9d26b0;}.el-tag--plain.is-hit { border-color: #9D26B0;}.el-tag--plain .el-tag__close { color: #9d26b0;}.el-tag--plain .el-tag__close:hover { background-color: #9d26b0;}.el-select-dropdown__item.selected { color: #9D26B0;}.el-select .el-input__inner:focus { border-color: #9D26B0;}.el-select .el-input.is-focus .el-input__inner { border-color: #9D26B0;}.el-pagination button:hover { color: #9D26B0;}.el-pagination__sizes .el-input .el-input__inner:hover { border-color: #9D26B0;}.el-pagination.is-background .btn-prev,.el-pagination.is-background .btn-next,.el-pagination.is-background .el-pager li { background-color: #fdfbfd;}.el-pagination.is-background .el-pager li:not(.disabled):hover { color: #9D26B0;}.el-pagination.is-background .el-pager li:not(.disabled).active { background-color: #9D26B0;}.el-pager li:hover { color: #9D26B0;}.el-pager li.active { color: #9D26B0;}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected { color: #9D26B0;}.el-textarea__inner:focus { border-color: #9D26B0;}.el-input__inner:focus { border-color: #9D26B0;}.el-input.is-active .el-input__inner { border-color: #9D26B0;}.el-tag { background-color: #f5e9f7; border-color: #ebd4ef; color: #9D26B0;}.el-tag.is-hit { border-color: #9D26B0;}.el-tag .el-tag__close { color: #9d26b0;}.el-tag .el-tag__close:hover { background-color: #9d26b0;}.el-tag--dark { background-color: #9d26b0; border-color: #9d26b0;}.el-tag--dark.is-hit { border-color: #9D26B0;}.el-tag--dark .el-tag__close:hover { background-color: #b151c0;}.el-tag--plain { border-color: #d8a8df; color: #9d26b0;}.el-tag--plain.is-hit { border-color: #9D26B0;}.el-tag--plain .el-tag__close { color: #9d26b0;}.el-tag--plain .el-tag__close:hover { background-color: #9d26b0;}.el-select-dropdown__item.selected { color: #9D26B0;}.el-select .el-input__inner:focus { border-color: #9D26B0;}.el-select .el-input.is-focus .el-input__inner { border-color: #9D26B0;}.el-textarea__inner:focus { border-color: #9D26B0;}.el-input__inner:focus { border-color: #9D26B0;}.el-input.is-active .el-input__inner { border-color: #9D26B0;}.el-menu--horizontal > .el-submenu.is-active .el-submenu__title { border-bottom: 2px solid #9D26B0;}.el-menu--horizontal > .el-menu-item.is-active { border-bottom: 2px solid #9D26B0;}.el-menu-item:hover, .el-menu-item:focus { background-color: #f5e9f7;}.el-menu-item.is-active { color: #9D26B0;}.el-submenu__title:hover, .el-submenu__title:focus { background-color: #f5e9f7;}.el-submenu__title:hover { background-color: #f5e9f7;}.el-submenu.is-active .el-submenu__title { border-bottom-color: #9D26B0;}.el-button:hover, .el-button:focus { color: #9D26B0; border-color: #e2bee7; background-color: #f5e9f7;}.el-button:active { color: #8d229e; border-color: #8d229e;}.el-button.is-plain:hover, .el-button.is-plain:focus { border-color: #9D26B0; color: #9D26B0;}.el-button.is-plain:active { border-color: #8d229e; color: #8d229e;}.el-button.is-active { color: #8d229e; border-color: #8d229e;}.el-button--primary { background-color: #9D26B0; border-color: #9D26B0;}.el-button--primary:hover, .el-button--primary:focus { background: #b151c0; border-color: #b151c0;}.el-button--primary:active { background: #8d229e; border-color: #8d229e;}.el-button--primary.is-active { background: #8d229e; border-color: #8d229e;}.el-button--primary.is-disabled, .el-button--primary.is-disabled:hover, .el-button--primary.is-disabled:focus, .el-button--primary.is-disabled:active { background-color: #ce93d8; border-color: #ce93d8;}.el-button--primary.is-plain { color: #9D26B0; background: #f5e9f7; border-color: #d8a8df;}.el-button--primary.is-plain:hover, .el-button--primary.is-plain:focus { background: #9D26B0; border-color: #9D26B0;}.el-button--primary.is-plain:active { background: #8d229e; border-color: #8d229e;}.el-button--primary.is-plain.is-disabled, .el-button--primary.is-plain.is-disabled:hover, .el-button--primary.is-plain.is-disabled:focus, .el-button--primary.is-plain.is-disabled:active { color: #c47dd0; background-color: #f5e9f7; border-color: #ebd4ef;}.el-button--mini { padding: 5px 12px;}.el-button--mini.is-round { padding: 5px 12px;}.el-button--mini.is-circle { padding: 5px;}.el-button--text { color: #9D26B0;}.el-button--text:hover, .el-button--text:focus { color: #b151c0;}.el-button--text:active { color: #8d229e;}.el-dropdown-menu__item:not(.is-disabled):hover, .el-dropdown-menu__item:focus { background-color: #f5e9f7; color: #b151c0;}.el-tabs__active-bar { background-color: #9D26B0;}.el-tabs__new-tab:hover { color: #9D26B0;}.el-tabs__item:focus.is-active.is-focus:not(:active) { box-shadow: 0 0 2px 2px #9D26B0 inset;}.el-tabs__item.is-active { color: #9D26B0;}.el-tabs__item:hover { color: #9D26B0;}.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active { color: #9D26B0;}.el-tabs--border-card > .el-tabs__header .el-tabs__item:not(.is-disabled):hover { color: #9D26B0;}.el-checkbox.is-bordered.is-checked { border-color: #9D26B0;}.el-checkbox__input.is-checked .el-checkbox__inner { background-color: #9D26B0; border-color: #9D26B0;}.el-checkbox__input.is-checked + .el-checkbox__label { color: #9D26B0;}.el-checkbox__input.is-focus .el-checkbox__inner { border-color: #9D26B0;}.el-checkbox__input.is-indeterminate .el-checkbox__inner { background-color: #9D26B0; border-color: #9D26B0;}.el-checkbox__inner:hover { border-color: #9D26B0;}.el-checkbox-button__inner:hover { color: #9D26B0;}.el-checkbox-button.is-checked .el-checkbox-button__inner { background-color: #9D26B0; border-color: #9D26B0; box-shadow: -1px 0 0 0 #c47dd0;}.el-checkbox-button.is-checked:first-child .el-checkbox-button__inner { border-left-color: #9D26B0;}.el-checkbox-button.is-focus .el-checkbox-button__inner { border-color: #9D26B0;}.el-checkbox-button--mini .el-checkbox-button__inner { padding: 5px 12px;}.el-checkbox-button--mini .el-checkbox-button__inner.is-round { padding: 5px 12px;}.el-radio.is-bordered.is-checked { border-color: #9D26B0;}.el-radio__input.is-checked .el-radio__inner { border-color: #9D26B0; background: #9D26B0;}.el-radio__input.is-checked + .el-radio__label { color: #9D26B0;}.el-radio__input.is-focus .el-radio__inner { border-color: #9D26B0;}.el-radio__inner:hover { border-color: #9D26B0;}.el-radio:focus:not(.is-focus):not(:active):not(.is-disabled) .el-radio__inner { box-shadow: 0 0 2px 2px #9D26B0;}.el-cascader-node.in-active-path, .el-cascader-node.is-selectable.in-checked-path, .el-cascader-node.is-active { color: #9D26B0;}.yb-main-header { background-color: #9D26B0;}.el-checkbox.is-bordered.is-checked { border-color: #9D26B0;}.el-checkbox__input.is-checked .el-checkbox__inner { background-color: #9D26B0; border-color: #9D26B0;}.el-checkbox__input.is-checked + .el-checkbox__label { color: #9D26B0;}.el-checkbox__input.is-focus .el-checkbox__inner { border-color: #9D26B0;}.el-checkbox__input.is-indeterminate .el-checkbox__inner { background-color: #9D26B0; border-color: #9D26B0;}.el-checkbox__inner:hover { border-color: #9D26B0;}.el-checkbox-button__inner:hover { color: #9D26B0;}.el-checkbox-button.is-checked .el-checkbox-button__inner { background-color: #9D26B0; border-color: #9D26B0; box-shadow: -1px 0 0 0 #c47dd0;}.el-checkbox-button.is-checked:first-child .el-checkbox-button__inner { border-left-color: #9D26B0;}.el-checkbox-button.is-focus .el-checkbox-button__inner { border-color: #9D26B0;}.el-checkbox-button--mini .el-checkbox-button__inner { padding: 5px 12px;}.el-checkbox-button--mini .el-checkbox-button__inner.is-round { padding: 5px 12px;}.el-button:hover, .el-button:focus { color: #9D26B0; border-color: #e2bee7; background-color: #f5e9f7;}.el-button:active { color: #8d229e; border-color: #8d229e;}.el-button.is-plain:hover, .el-button.is-plain:focus { border-color: #9D26B0; color: #9D26B0;}.el-button.is-plain:active { border-color: #8d229e; color: #8d229e;}.el-button.is-active { color: #8d229e; border-color: #8d229e;}.el-button--primary { background-color: #9D26B0; border-color: #9D26B0;}.el-button--primary:hover, .el-button--primary:focus { background: #b151c0; border-color: #b151c0;}.el-button--primary:active { background: #8d229e; border-color: #8d229e;}.el-button--primary.is-active { background: #8d229e; border-color: #8d229e;}.el-button--primary.is-disabled, .el-button--primary.is-disabled:hover, .el-button--primary.is-disabled:focus, .el-button--primary.is-disabled:active { background-color: #ce93d8; border-color: #ce93d8;}.el-button--primary.is-plain { color: #9D26B0; background: #f5e9f7; border-color: #d8a8df;}.el-button--primary.is-plain:hover, .el-button--primary.is-plain:focus { background: #9D26B0; border-color: #9D26B0;}.el-button--primary.is-plain:active { background: #8d229e; border-color: #8d229e;}.el-button--primary.is-plain.is-disabled, .el-button--primary.is-plain.is-disabled:hover, .el-button--primary.is-plain.is-disabled:focus, .el-button--primary.is-plain.is-disabled:active { color: #c47dd0; background-color: #f5e9f7; border-color: #ebd4ef;}.el-button--mini { padding: 5px 12px;}.el-button--mini.is-round { padding: 5px 12px;}.el-button--mini.is-circle { padding: 5px;}.el-button--text { color: #9D26B0;}.el-button--text:hover, .el-button--text:focus { color: #b151c0;}.el-button--text:active { color: #8d229e;}.el-textarea__inner:focus { border-color: #9D26B0;}.el-input__inner:focus { border-color: #9D26B0;}.el-input.is-active .el-input__inner { border-color: #9D26B0;}.el-message-box__headerbtn:focus .el-message-box__close, .el-message-box__headerbtn:hover .el-message-box__close { color: #9D26B0;}.el-checkbox.is-bordered.is-checked { border-color: #9D26B0;}.el-checkbox__input.is-checked .el-checkbox__inner { background-color: #9D26B0; border-color: #9D26B0;}.el-checkbox__input.is-checked + .el-checkbox__label { color: #9D26B0;}.el-checkbox__input.is-focus .el-checkbox__inner { border-color: #9D26B0;}.el-checkbox__input.is-indeterminate .el-checkbox__inner { background-color: #9D26B0; border-color: #9D26B0;}.el-checkbox__inner:hover { border-color: #9D26B0;}.el-checkbox-button__inner:hover { color: #9D26B0;}.el-checkbox-button.is-checked .el-checkbox-button__inner { background-color: #9D26B0; border-color: #9D26B0; box-shadow: -1px 0 0 0 #c47dd0;}.el-checkbox-button.is-checked:first-child .el-checkbox-button__inner { border-left-color: #9D26B0;}.el-checkbox-button.is-focus .el-checkbox-button__inner { border-color: #9D26B0;}.el-checkbox-button--mini .el-checkbox-button__inner { padding: 5px 12px;}.el-checkbox-button--mini .el-checkbox-button__inner.is-round { padding: 5px 12px;}.el-tree__drop-indicator { background-color: #9D26B0;}.el-tree-node.is-drop-inner > .el-tree-node__content .el-tree-node__label { background-color: #9D26B0;}.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content { background-color: #f7eef9;}.el-select-dropdown__item.selected { color: #9D26B0;}.el-table__body tr.current-row::after { background-color: #9D26B0;}`; 419 | /** 420 | * 421 | * @param {string} options.primaryColor 新的主题色 422 | * @param {object} options.targetValueReplacer 可用于非颜色值的替换,如"padding:10px;" 中的 "10px" 423 | */ 424 | function setCustomTheme({ primaryColor, targetValueReplacer }) { 425 | setNewThemeStyle({ 426 | primaryColor, 427 | targetValueReplacer, 428 | styleTagId: 'coustom-theme-tagid', 429 | sourceThemeStyle, 430 | sourceColorMap, 431 | hybridValueMap, 432 | otherValues, 433 | }); 434 | } 435 | 436 | setCustomTheme({ primaryColor: '#F4791E' ,targetValueReplacer:{"5px 12px":"6px 18px"}}); 437 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | }; 4 | -------------------------------------------------------------------------------- /husky.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | hooks: { 3 | "pre-commit": "lint-staged", 4 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /images/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitOfZGT/some-loader-utils/659526602fffa75ef0c74bcd210840ab3bc9b829/images/example.gif -------------------------------------------------------------------------------- /images/includeStyles_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitOfZGT/some-loader-utils/659526602fffa75ef0c74bcd210840ab3bc9b829/images/includeStyles_p.png -------------------------------------------------------------------------------- /images/includeStyles_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GitOfZGT/some-loader-utils/659526602fffa75ef0c74bcd210840ab3bc9b829/images/includeStyles_r.png -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "*.js": ["eslint --fix", "prettier --write"], 3 | "*.{json,md,yml,css,ts}": ["prettier --write"], 4 | }; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "bin": { 3 | "z-theme": "dist/bin/index.js" 4 | }, 5 | "name": "@zougt/some-loader-utils", 6 | "version": "1.4.3", 7 | "description": "implementation for less-loader or sass-loader. Compiles Less or sass to CSS.", 8 | "license": "MIT", 9 | "repository": "GitOfZGT/some-loader-utils", 10 | "author": "zougt ", 11 | "homepage": "https://github.com/GitOfZGT/some-loader-utils", 12 | "bugs": "https://github.com/GitOfZGT/some-loader-utils/issues", 13 | "main": "dist/index.js", 14 | "engines": { 15 | "node": ">= 10.13.0" 16 | }, 17 | "scripts": { 18 | "start": "npm run build -- -w", 19 | "clean": "del-cli dist", 20 | "prebuild": "npm run clean", 21 | "build": "cross-env NODE_ENV=production babel src -d dist --ignore 'src/arbitraryMode/browser.js' --copy-files", 22 | "commitlint": "commitlint --from=master", 23 | "security": "npm audit", 24 | "lint:prettier": "prettier --list-different .", 25 | "lint:js": "eslint --cache .", 26 | "lint": "npm-run-all -l -p \"lint:**\"", 27 | "test:only": "cross-env NODE_ENV=test jest", 28 | "test:watch": "npm run test:only -- --watch", 29 | "test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage", 30 | "pretest": "npm run lint", 31 | "test": "npm run test:coverage", 32 | "prepare": "npm run build", 33 | "release": "standard-version", 34 | "defaults": "webpack-defaults" 35 | }, 36 | "files": [ 37 | "dist" 38 | ], 39 | "dependencies": { 40 | "cac": "^6.7.12", 41 | "color": "^4.0.1", 42 | "cssnano": "^5.0.11", 43 | "cssnano-preset-lite": "^2.0.1", 44 | "fs-extra": "^10.0.0", 45 | "postcss": "^8.2.9", 46 | "prettier": "^2.5.0", 47 | "uuid": "^8.3.2" 48 | }, 49 | "devDependencies": { 50 | "@babel/cli": "^7.12.10", 51 | "@babel/core": "^7.12.10", 52 | "@babel/preset-env": "^7.12.11", 53 | "@commitlint/cli": "^11.0.0", 54 | "@commitlint/config-conventional": "^11.0.0", 55 | "@webpack-contrib/defaults": "^6.3.0", 56 | "@webpack-contrib/eslint-config-webpack": "^3.0.0", 57 | "babel-jest": "^26.6.3", 58 | "color": "^4.0.1", 59 | "cross-env": "^7.0.3", 60 | "del": "^6.0.0", 61 | "del-cli": "^3.0.1", 62 | "eslint": "^7.18.0", 63 | "eslint-config-prettier": "^7.2.0", 64 | "eslint-plugin-import": "^2.22.1", 65 | "eslint-plugin-prettier": "^3.3.0", 66 | "husky": "^4.3.8", 67 | "jest": "^26.6.3", 68 | "less": "^4.1.0", 69 | "lint-staged": "^10.5.3", 70 | "memfs": "^3.2.0", 71 | "npm-run-all": "^4.1.5", 72 | "standard-version": "^9.1.0", 73 | "strip-ansi": "^6.0.0", 74 | "webpack": "^5.16.0" 75 | }, 76 | "keywords": [ 77 | "loader", 78 | "less", 79 | "sass", 80 | "theme", 81 | "css", 82 | "preprocessor" 83 | ], 84 | "publishConfig": { 85 | "registry": "https://registry.npmjs.org" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/arbitraryMode/browser.js: -------------------------------------------------------------------------------- 1 | /** 此文件内容,可能会不使用babel等工具编译,保证最好兼容性 */ 2 | /* eslint-disable prefer-destructuring */ 3 | /* eslint-disable prefer-arrow-callback */ 4 | /* eslint-disable func-names */ 5 | /* eslint-disable vars-on-top */ 6 | /* eslint-disable object-shorthand */ 7 | /* eslint-disable no-var */ 8 | /* eslint-disable import/prefer-default-export */ 9 | /* eslint-disable no-unused-vars */ 10 | /** 11 | * @param {object} options 12 | * @param {function} options.Color see https://github.com/Qix-/color 13 | * */ 14 | function getSetNewThemeMethod(options) { 15 | var Color = options.Color; 16 | function mix(color1, color2, weight) { 17 | var p = weight / 100.0; 18 | var w = p * 2 - 1; 19 | var a = color1.hsl().valpha - color2.hsl().valpha; 20 | 21 | var w1 = ((w * a === -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0; 22 | var w2 = 1 - w1; 23 | var alpha = color1.hsl().valpha * p + color2.hsl().valpha * (1 - p); 24 | var arr1 = color1.rgb().array(); 25 | var arr2 = color2.rgb().array(); 26 | var rgb = [ 27 | arr1[0] * w1 + arr2[0] * w2, 28 | arr1[1] * w1 + arr2[1] * w2, 29 | arr1[2] * w1 + arr2[2] * w2, 30 | alpha, 31 | ]; 32 | 33 | return Color.rgb(rgb); 34 | } 35 | function getTargetColor(newPrimaryColor, percentGias) { 36 | var primaryHsvArr = newPrimaryColor.hsv().array(); 37 | var getTargetVal = function (index) { 38 | return ( 39 | primaryHsvArr[index] - percentGias[index] * primaryHsvArr[index] 40 | ); 41 | }; 42 | var targetColor = Color.hsv([ 43 | getTargetVal(0), 44 | getTargetVal(1), 45 | getTargetVal(2), 46 | newPrimaryColor.valpha - percentGias[3] * newPrimaryColor.valpha, 47 | ]); 48 | return targetColor; 49 | } 50 | /** 51 | * 52 | * @param {object} opt 53 | * @param {string} opt.primaryColor 切换主题色 54 | * @param {string} opt.defaultPrimaryColor 默认主题色 55 | * @param {string} opt.sourceThemeStyle 源主题样式 56 | * @param {object} opt.sourceColorMap 梯度映射 57 | * @param {object} opt.hybridValueMap 混合值的映射 58 | * @param {array} opt.otherValues 非颜色的值 59 | * @param {object} [opt.gradientReplacer] 存在多个梯度主色,可对应替换梯度主色 60 | * @param {object} [opt.targetValueReplacer] 可用于非颜色值的替换,如"padding:10px;" 中的 "10px",(如果是颜色值,则是精确替换颜色) 61 | */ 62 | function getReplaceStyleValues(opt) { 63 | var primaryColor = opt.primaryColor || ''; 64 | var gradientReplacer = opt.gradientReplacer || {}; 65 | var targetValueReplacer = opt.targetValueReplacer || {}; 66 | var sourceColorMap = opt.sourceColorMap || {}; 67 | var hybridValueMap = opt.hybridValueMap || {}; 68 | var otherValues = opt.otherValues || []; 69 | 70 | var replaceColorMap = {}; 71 | for (var key in sourceColorMap) { 72 | if (Object.hasOwnProperty.call(sourceColorMap, key)) { 73 | if (targetValueReplacer[key]) { 74 | replaceColorMap[key] = targetValueReplacer[key]; 75 | } else { 76 | var item = sourceColorMap[key]; 77 | 78 | var replacer = item.sourceVarColorString 79 | ? gradientReplacer[item.sourceVarColorString] 80 | : ''; 81 | var newColor = ''; 82 | if (item.mixColorString) { 83 | newColor = mix( 84 | Color(item.mixColorString), 85 | Color(replacer || primaryColor), 86 | item.weight 87 | ); 88 | } else { 89 | newColor = getTargetColor( 90 | Color(replacer || primaryColor), 91 | replacer ? item.sourcePercentGias : item.percentGias 92 | ); 93 | } 94 | replaceColorMap[key] = 95 | newColor.valpha < 1 96 | ? newColor.rgb().toString() 97 | : newColor.hex().toString(); 98 | } 99 | } 100 | } 101 | var replaceHybridValueMap = {}; 102 | Object.keys(hybridValueMap).forEach(function (temp) { 103 | var sourceValue = hybridValueMap[temp]; 104 | var sourceColors = Object.keys(sourceColorMap); 105 | var findColor = null; 106 | for (var i = 0; i < sourceColors.length; i++) { 107 | if (sourceValue.indexOf(sourceColors[i]) > -1) { 108 | findColor = sourceColors[i]; 109 | break; 110 | } 111 | } 112 | if (findColor) { 113 | replaceHybridValueMap[sourceValue] = ( 114 | targetValueReplacer[temp] || temp 115 | ).replace('#{color}', replaceColorMap[findColor]); 116 | } 117 | }); 118 | var replaceOtherValueMap = {}; 119 | otherValues.forEach(function (val) { 120 | if (targetValueReplacer[val]) { 121 | replaceOtherValueMap[val] = targetValueReplacer[val]; 122 | } 123 | }); 124 | return { 125 | replaceColorMap: replaceColorMap, 126 | replaceHybridValueMap: replaceHybridValueMap, 127 | replaceOtherValueMap: replaceOtherValueMap, 128 | }; 129 | } 130 | /** 131 | * 132 | * @param {object} opt 133 | * @param {string} opt.primaryColor 主题色 134 | * @param {string} opt.styleTagId html中主题样式的style tag id 135 | * @param {string} opt.sourceThemeStyle 源主题样式 136 | * @param {object} opt.sourceColorMap 梯度映射 137 | * @param {object} opt.hybridValueMap 混合值的映射 138 | * @param {array} opt.otherValues 非颜色的值 139 | * @param {object} [opt.gradientReplacer] 存在多个梯度主色,可对应替换梯度主色 140 | * @param {object} [opt.targetValueReplacer] 可用于非颜色值的替换,如"padding:10px;" 中的 "10px",(如果是颜色值,则是精确替换颜色) 141 | */ 142 | function setNewThemeStyle(opt) { 143 | var styleTagId = opt.styleTagId || ''; 144 | var sourceThemeStyle = opt.sourceThemeStyle || ''; 145 | var resultParam = getReplaceStyleValues(opt); 146 | var replaceColorMap = resultParam.replaceColorMap; 147 | var replaceHybridValueMap = resultParam.replaceHybridValueMap; 148 | var replaceOtherValueMap = resultParam.replaceOtherValueMap; 149 | 150 | var newStyleContent = sourceThemeStyle; 151 | Object.keys(replaceOtherValueMap).forEach(function (sourceValue) { 152 | newStyleContent = newStyleContent.replace( 153 | new RegExp(sourceValue.replace(/(\(|\)|\.)/g, '\\$1'), 'gi'), 154 | replaceOtherValueMap[sourceValue] 155 | ); 156 | }); 157 | Object.keys(replaceHybridValueMap).forEach(function (sourceValue) { 158 | newStyleContent = newStyleContent.replace( 159 | new RegExp(sourceValue.replace(/(\(|\)|\.)/g, '\\$1'), 'gi'), 160 | replaceHybridValueMap[sourceValue] 161 | ); 162 | }); 163 | Object.keys(replaceColorMap).forEach(function (sourceValue) { 164 | newStyleContent = newStyleContent.replace( 165 | new RegExp(sourceValue.replace(/(\(|\)|\.)/g, '\\$1'), 'gi'), 166 | replaceColorMap[sourceValue] 167 | ); 168 | }); 169 | // console.log(newStyleContent); 170 | // eslint-disable-next-line no-undef 171 | var styleTag = document.getElementById(styleTagId); 172 | if (styleTag) { 173 | styleTag.innerHTML = newStyleContent; 174 | } else { 175 | // eslint-disable-next-line no-undef 176 | styleTag = document.createElement('style'); 177 | styleTag.id = styleTagId; 178 | styleTag.type = 'text/css'; 179 | styleTag.innerHTML = newStyleContent; 180 | // eslint-disable-next-line no-undef 181 | document.body.appendChild(styleTag); 182 | } 183 | } 184 | return { setNewThemeStyle: setNewThemeStyle }; 185 | } 186 | 187 | // 此行代码会在某些情况,会在不使用babel等编译时被移除掉 188 | export { getSetNewThemeMethod }; 189 | -------------------------------------------------------------------------------- /src/arbitraryMode/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import Color from 'color'; 4 | 5 | import fs from 'fs-extra'; 6 | 7 | import prettier from 'prettier'; 8 | 9 | import { extractThemeCss } from '../utils'; 10 | 11 | import { getCurrentPackRequirePath } from '../packPath'; 12 | 13 | import { colorValueReg, isSameColor } from './utils'; 14 | 15 | function mix(color1, color2, weight) { 16 | const p = weight / 100.0; 17 | const w = p * 2 - 1; 18 | const a = color1.hsl().valpha - color2.hsl().valpha; 19 | 20 | const w1 = ((w * a === -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0; 21 | const w2 = 1 - w1; 22 | const alpha = color1.hsl().valpha * p + color2.hsl().valpha * (1 - p); 23 | const arr1 = color1.rgb().array(); 24 | const arr2 = color2.rgb().array(); 25 | const rgb = [ 26 | arr1[0] * w1 + arr2[0] * w2, 27 | arr1[1] * w1 + arr2[1] * w2, 28 | arr1[2] * w1 + arr2[2] * w2, 29 | alpha, 30 | ]; 31 | 32 | return Color.rgb(rgb); 33 | } 34 | function getMixColorAndWeight({ primaryColor, targetColor, otherMixColors }) { 35 | const arr = ['#ffffff', '#000000'] 36 | .concat(otherMixColors || []) 37 | .map((cstr) => Color(cstr)); 38 | const arr1 = targetColor.rgb().array(); 39 | let weight = 0; 40 | let mixColor = null; 41 | for (let i = 0; i < arr.length; i++) { 42 | const color1 = arr[i]; 43 | for (let j = 1; j <= 100; j++) { 44 | const sourceColor = mix(color1, primaryColor, j); 45 | const arr2 = sourceColor.rgb().array(); 46 | if ( 47 | Math.round(arr1[0]) === Math.round(arr2[0]) && 48 | Math.round(arr1[1]) === Math.round(arr2[1]) && 49 | Math.round(arr1[2]) === Math.round(arr2[2]) 50 | ) { 51 | weight = j; 52 | mixColor = color1; 53 | break; 54 | } 55 | } 56 | if (mixColor) { 57 | break; 58 | } 59 | } 60 | return { mixColor, weight, valpha: targetColor.valpha }; 61 | } 62 | 63 | function getHsvPercentGias(primaryColor, resultColor) { 64 | const primaryHsvArr = primaryColor.hsv().array(); 65 | const resultHsvArr = resultColor.hsv().array(); 66 | const getGiasPercent = (index) => 67 | primaryHsvArr[index] !== 0 68 | ? (primaryHsvArr[index] - resultHsvArr[index]) / 69 | primaryHsvArr[index] 70 | : primaryHsvArr[index]; 71 | return [ 72 | getGiasPercent(0), 73 | getGiasPercent(1), 74 | getGiasPercent(2), 75 | primaryColor.valpha !== 0 76 | ? (primaryColor.valpha - resultColor.valpha) / primaryColor.valpha 77 | : primaryColor.valpha, 78 | ]; 79 | } 80 | 81 | function separatValue(cssValues) { 82 | const cssColors = []; 83 | const hybridValueMap = {}; 84 | const otherValues = []; 85 | cssValues.forEach((val) => { 86 | const hasSpace = /\s+/.test(val); 87 | const hex = val.match(colorValueReg.hex); 88 | const rgb = val.match(colorValueReg.rgb); 89 | const rgba = val.match(colorValueReg.rgba); 90 | const hsl = val.match(colorValueReg.hsl); 91 | const hsla = val.match(colorValueReg.hsla); 92 | if (!hex && !rgb && !rgba && !hsl && !hsla) { 93 | otherValues.push(val); 94 | } else { 95 | let residueVal = val; 96 | const getResidueVal = (colorArr, cssColors, residueVal) => { 97 | let newVal = residueVal; 98 | if (colorArr) { 99 | colorArr.forEach((cval) => { 100 | cssColors.push(cval); 101 | newVal = residueVal.replace( 102 | cval, 103 | hasSpace ? '#{color}' : '' 104 | ); 105 | }); 106 | } 107 | return newVal; 108 | }; 109 | residueVal = getResidueVal(hex, cssColors, residueVal); 110 | residueVal = getResidueVal(rgb, cssColors, residueVal); 111 | residueVal = getResidueVal(rgba, cssColors, residueVal); 112 | residueVal = getResidueVal(hsl, cssColors, residueVal); 113 | residueVal = getResidueVal(hsla, cssColors, residueVal); 114 | if (residueVal) { 115 | hybridValueMap[residueVal] = val; 116 | } 117 | } 118 | }); 119 | return { 120 | cssColors: Array.from(new Set(cssColors)), 121 | hybridValueMap, 122 | otherValues, 123 | }; 124 | } 125 | function getResultColorReplaceMap({ 126 | cssColors, 127 | varsColorValues, 128 | otherMixColors, 129 | defaultPrimaryColor, 130 | includeStyleWithColors, 131 | hueDiffControls, 132 | }) { 133 | // if (defaultPrimaryColor) { 134 | 135 | // const defaultColor = Color(defaultPrimaryColor).hsv(); 136 | // if ( 137 | // !varsColorValues.some((varStr) => { 138 | // const varColor = Color(varStr).hsv(); 139 | // return ( 140 | // defaultColor.color[0] === varColor.color[0] && 141 | // defaultColor.color[1] === varColor.color[1] && 142 | // defaultColor.color[2] === varColor.color[2] && 143 | // defaultColor.valpha === varColor.valpha 144 | // ); 145 | // }) 146 | // ) { 147 | // console.warn( 148 | // `warning: defaultPrimaryColor:${defaultPrimaryColor} can not found in multipleScopeVars[].path` 149 | // ); 150 | // } 151 | // } 152 | let primaryVarColor = null; 153 | try { 154 | primaryVarColor = Color(defaultPrimaryColor).hsv(); 155 | } catch (e) { 156 | throw Error( 157 | `error:defaultPrimaryColor: ${defaultPrimaryColor} not a color value` 158 | ); 159 | } 160 | const hueDiffControler = { low: 0, high: 0, ...(hueDiffControls || {}) }; 161 | const sourceColorMap = {}; 162 | 163 | cssColors.forEach((colorString) => { 164 | // 在 includeStyleWithColors 存在的颜色值就不会根据主色转换,除非启用了includeStyleWithColors[].inGradient:true 165 | if ( 166 | includeStyleWithColors 167 | .filter((item) => !item.inGradient) 168 | .some((item) => { 169 | if (Array.isArray(item.color)) { 170 | return item.color.some((co) => 171 | isSameColor(co, colorString) 172 | ); 173 | } 174 | return isSameColor(item.color, colorString); 175 | }) 176 | ) { 177 | return; 178 | } 179 | const resultColor = Color(colorString).hsv(); 180 | let finded = false; 181 | for (let index = 0; index < varsColorValues.length; index++) { 182 | const varStr = varsColorValues[index]; 183 | const varColor = Color(varStr).hsv(); 184 | const varHue = Math.floor(varColor.color[0]); 185 | const hues = [varHue]; 186 | for (let i = 0; i < hueDiffControler.low; i++) { 187 | const h = varHue - (i + 1); 188 | hues.push(h < 0 ? 0 : h); 189 | } 190 | for (let i = 0; i < hueDiffControler.high; i++) { 191 | const h = varHue + (i + 1); 192 | hues.push(h > 360 ? 360 : h); 193 | } 194 | const reHue = Math.floor(resultColor.color[0]); 195 | if (hues.some((h) => h === reHue)) { 196 | const { mixColor, weight, valpha } = getMixColorAndWeight({ 197 | primaryColor: varColor, 198 | targetColor: resultColor, 199 | otherMixColors, 200 | }); 201 | if (mixColor) { 202 | sourceColorMap[colorString] = { 203 | mixColorString: mixColor.hex(), 204 | weight, 205 | // varColorString: defaultPrimaryColor, 206 | sourceVarColorString: varStr, 207 | valpha, 208 | }; 209 | } else { 210 | sourceColorMap[colorString] = { 211 | percentGias: getHsvPercentGias( 212 | primaryVarColor, 213 | resultColor 214 | ), 215 | // varColorString: defaultPrimaryColor, 216 | sourcePercentGias: getHsvPercentGias( 217 | varColor, 218 | resultColor 219 | ), 220 | sourceVarColorString: varStr, 221 | }; 222 | } 223 | finded = true; 224 | break; 225 | } 226 | } 227 | if (!finded && defaultPrimaryColor) { 228 | const { mixColor, weight } = getMixColorAndWeight({ 229 | primaryColor: primaryVarColor, 230 | targetColor: resultColor, 231 | otherMixColors, 232 | }); 233 | if (mixColor) { 234 | sourceColorMap[colorString] = { 235 | mixColorString: mixColor.hex(), 236 | weight, 237 | // varColorString: defaultPrimaryColor, 238 | }; 239 | } else { 240 | sourceColorMap[colorString] = { 241 | percentGias: getHsvPercentGias( 242 | primaryVarColor, 243 | resultColor 244 | ), 245 | // varColorString: defaultPrimaryColor, 246 | }; 247 | } 248 | } 249 | }); 250 | return sourceColorMap; 251 | } 252 | function getThemeStyleContent(scopeName, removeCssScopeName) { 253 | return extractThemeCss({ scopeName, removeCssScopeName }).then( 254 | ({ themeCss, themeRuleValues }) => { 255 | let styleContent = ''; 256 | Object.values(themeCss).forEach((css) => { 257 | styleContent = `${styleContent}${css}`; 258 | }); 259 | return { styleContent, themeRuleValues }; 260 | } 261 | ); 262 | } 263 | 264 | function createSetCustomThemeFile({ 265 | defaultPrimaryColor, 266 | styleTagId = 'coustom-theme-tagid', 267 | includeStyleWithColors, 268 | styleContent, 269 | themeRuleValues, 270 | customThemeOutputPath, 271 | appendedContent, 272 | preAppendedContent, 273 | importUtils, 274 | hueDiffControls, 275 | otherMixColors, 276 | }) { 277 | if (typeof defaultPrimaryColor !== 'string' || !defaultPrimaryColor) { 278 | throw Error('defaultPrimaryColor can not found.'); 279 | } 280 | const targetRsoleved = getCurrentPackRequirePath(); 281 | let baseVarColorsJson = {}; 282 | if (fs.existsSync(`${targetRsoleved}/baseVarColors.json`)) { 283 | baseVarColorsJson = JSON.parse( 284 | fs.readFileSync(`${targetRsoleved}/baseVarColors.json`).toString() 285 | ); 286 | } 287 | 288 | const { cssColors, hybridValueMap, otherValues } = 289 | separatValue(themeRuleValues); 290 | const sourceColorMap = getResultColorReplaceMap({ 291 | cssColors, 292 | varsColorValues: baseVarColorsJson.baseVarColors || [], 293 | defaultPrimaryColor, 294 | includeStyleWithColors, 295 | hueDiffControls, 296 | otherMixColors, 297 | }); 298 | 299 | const targetValueReplacer = Object.keys(sourceColorMap) 300 | .concat(Object.keys(hybridValueMap)) 301 | .concat(otherValues) 302 | .reduce((tol, curr) => { 303 | return { ...tol, [curr]: curr }; 304 | }, {}); 305 | const gradientReplacer = Object.keys(sourceColorMap) 306 | .map((key) => sourceColorMap[key].sourceVarColorString) 307 | .reduce((tol, curr) => { 308 | return { ...tol, [curr]: curr }; 309 | }, {}); 310 | const browerCodes = fs 311 | .readFileSync(path.join(__dirname, './browser.js')) 312 | .toString(); 313 | 314 | fs.outputFile( 315 | `${targetRsoleved}/customThemeOptions.json`, 316 | 317 | JSON.stringify({ 318 | Color: 'see https://github.com/Qix-/color', 319 | primaryColor: defaultPrimaryColor, 320 | gradientReplacer, 321 | targetValueReplacer, 322 | }), 323 | () => {} 324 | ); 325 | 326 | const fileContent = ` 327 | ${ 328 | importUtils 329 | ? 'import { getSetNewThemeMethod } from "@zougt/some-loader-utils/dist/arbitraryMode/browser";' 330 | : '' 331 | } 332 | ${ 333 | !importUtils 334 | ? browerCodes 335 | .replace( 336 | /import\s+([^\s]+|\{[^{}]+\})\s+from ['"][^'"]+['"];?/g, 337 | '' 338 | ) 339 | .replace(/export\s+\{[^{}]+\};?/g, '') 340 | : '' 341 | }\n 342 | var saveThemeOptions = { 343 | defaultPrimaryColor:${JSON.stringify(defaultPrimaryColor)}, 344 | primaryColor:${JSON.stringify(defaultPrimaryColor)}, 345 | sourceThemeStyle:${JSON.stringify(styleContent)}, 346 | hybridValueMap:${JSON.stringify(hybridValueMap)}, 347 | otherValues:${JSON.stringify(otherValues)}, 348 | sourceColorMap:${JSON.stringify(sourceColorMap)}, 349 | styleTagId: "${styleTagId}" 350 | }; 351 | function setSaveThemeOptions(opt){saveThemeOptions=Object.assign(saveThemeOptions,opt||{});}; 352 | /** 353 | * 通常只需 primaryColor , gradientReplacer 和 targetValueReplacer 作为辅助使用 354 | * 355 | * @param {object} options 356 | * @param {function} options.Color required , 传入即可, 来源: https://github.com/Qix-/color 357 | * @param {string} options.primaryColor 新的主题色,替换所有颜色 358 | * @param {object} [options.gradientReplacer=${JSON.stringify( 359 | gradientReplacer 360 | )}] 存在多个梯度主色,可对应替换梯度主色 361 | * @param {object} [options.targetValueReplacer=${JSON.stringify( 362 | targetValueReplacer 363 | )}] 可用于非颜色值的替换,如"padding:10px;" 中的 "10px",(如果是颜色值,则是精确替换颜色) 364 | */ 365 | function setCustomTheme(options) { 366 | setSaveThemeOptions(options); 367 | getSetNewThemeMethod({Color:options.Color}).setNewThemeStyle(saveThemeOptions); 368 | }`; 369 | return prettier.resolveConfig(process.cwd()).then((options) => { 370 | const setCustomThemeConent = prettier.format( 371 | `${preAppendedContent || ''}${fileContent}${appendedContent || ''}`, 372 | { ...(options || {}), parser: 'babel' } 373 | ); 374 | if (customThemeOutputPath) { 375 | fs.outputFileSync(customThemeOutputPath, setCustomThemeConent); 376 | } 377 | return { 378 | styleContent, 379 | setCustomThemeConent, 380 | hybridValueMap, 381 | otherValues, 382 | sourceColorMap, 383 | }; 384 | }); 385 | } 386 | 387 | // eslint-disable-next-line import/prefer-default-export 388 | export { createSetCustomThemeFile, getThemeStyleContent }; 389 | -------------------------------------------------------------------------------- /src/arbitraryMode/utils.js: -------------------------------------------------------------------------------- 1 | import Color from 'color'; 2 | 3 | export const colorValueReg = { 4 | hex: /#([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3})/gi, 5 | rgb: /rgb\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*\)/gi, 6 | rgba: /rgba\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*(0|1|1.0|0?.[0-9]+)\s*\)/gi, 7 | hsl: /hsl\(\s*\d{1,3}\s*,\s*(0|\d{1,3}%)\s*,\s*(0|\d{1,3}%)\s*\)/gi, 8 | hsla: /hsla\(\s*\d{1,3}\s*,\s*(0|\d{1,3}%)\s*,\s*(0|\d{1,3}%)\s*,\s*(0|1|1.0|0?.[0-9]+)\s*\)/gi, 9 | }; 10 | 11 | export function isSameColor(str1, str2) { 12 | if (str1 === str2) { 13 | return true; 14 | } 15 | let same = false; 16 | try { 17 | const color1 = Color(str1).rgb().array(); 18 | const color2 = Color(str2).rgb().array(); 19 | same = 20 | color1[0] === color2[0] && 21 | color1[1] === color2[1] && 22 | color1[2] === color2[2] && 23 | color1[3] === color2[3]; 24 | } catch (e) { 25 | same = false; 26 | } 27 | return same; 28 | } 29 | 30 | export function removeSpaceInColor(val = '') { 31 | let newVal = val; 32 | const rgb = val.match(colorValueReg.rgb) || []; 33 | const rgba = val.match(colorValueReg.rgba) || []; 34 | const hsl = val.match(colorValueReg.hsl) || []; 35 | const hsla = val.match(colorValueReg.hsla) || []; 36 | const removeSpace = (colors, value) => { 37 | let replacer = value; 38 | colors.forEach((c) => { 39 | replacer = replacer.replace(c, c.replace(/\s+/g, '')); 40 | }); 41 | return replacer; 42 | }; 43 | newVal = removeSpace(rgb, newVal); 44 | newVal = removeSpace(rgba, newVal); 45 | newVal = removeSpace(hsl, newVal); 46 | newVal = removeSpace(hsla, newVal); 47 | return newVal; 48 | } 49 | 50 | export default {}; 51 | -------------------------------------------------------------------------------- /src/bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import cac from 'cac'; 3 | 4 | import fs from 'fs-extra'; 5 | 6 | import pack from '../../package.json'; 7 | 8 | import { getCurrentPackRequirePath } from '../packPath'; 9 | 10 | const cli = cac(); 11 | const init = (optionName) => { 12 | const targetRsoleved = getCurrentPackRequirePath(); 13 | if (fs.existsSync(`${targetRsoleved}/customThemeOptions.json`)) { 14 | try { 15 | const opts = JSON.parse( 16 | fs 17 | .readFileSync(`${targetRsoleved}/customThemeOptions.json`) 18 | .toString() 19 | ); 20 | console.log( 21 | !optionName ? opts : { [optionName]: opts[optionName] } 22 | ); 23 | // eslint-disable-next-line no-empty 24 | } catch (e) {} 25 | } 26 | }; 27 | cli.command('inspect [optionName]', 'inspect setCustomTheme options').action( 28 | init 29 | ); 30 | cli.command('ins [optionName]', 'inspect setCustomTheme options').action(init); 31 | cli.help(); 32 | cli.version(pack.version); 33 | cli.parse(); 34 | -------------------------------------------------------------------------------- /src/getLess.js: -------------------------------------------------------------------------------- 1 | import { 2 | getScopeProcessResult, 3 | getAllStyleVarFiles, 4 | getVarsContent, 5 | createArbitraryModeVarColors, 6 | getPluginParams, 7 | } from './utils'; 8 | /** 9 | * 10 | * @param {Object} opt 11 | * @param {Object} opt.implementation 12 | * @param {Function} opt.getMultipleScopeVars 13 | * @returns less 14 | */ 15 | export function getLess(opt = {}) { 16 | const packname = 'less'; 17 | let less = opt.implementation; 18 | if (!less) { 19 | try { 20 | less = require(packname); 21 | } catch (e) { 22 | throw new Error( 23 | `Dependency "${packname}" not found. Did you install it?` 24 | ); 25 | } 26 | } 27 | const { render } = less; 28 | // eslint-disable-next-line func-names 29 | less.render = function (input, options = {}, callback) { 30 | const renderOptions = { ...options }; 31 | const defaultPluginOpt = getPluginParams(opt); 32 | const multipleScopeVars = 33 | typeof opt.getMultipleScopeVars === 'function' 34 | ? opt.getMultipleScopeVars(renderOptions) 35 | : renderOptions.multipleScopeVars; 36 | 37 | delete renderOptions.multipleScopeVars; 38 | 39 | const allStyleVarFiles = getAllStyleVarFiles( 40 | { 41 | emitError: (msg) => { 42 | throw new Error(msg); 43 | }, 44 | }, 45 | { multipleScopeVars, arbitraryMode: defaultPluginOpt.arbitraryMode } 46 | ); 47 | const preProcessor = (code) => render.call(less, code, renderOptions); 48 | // 按allStyleVarFiles的个数对当前文件编译多次得到多个结果 49 | const rePromise = Promise.all( 50 | allStyleVarFiles.map((file) => { 51 | const varscontent = getVarsContent(file.path, packname); 52 | if (defaultPluginOpt.arbitraryMode) { 53 | createArbitraryModeVarColors(varscontent); 54 | } 55 | 56 | return preProcessor( 57 | `${input}\n${varscontent}\n${file.varsContent || ''}` 58 | ); 59 | }) 60 | ) 61 | .then((prs) => 62 | getScopeProcessResult( 63 | prs.map((item) => { 64 | return { ...item, code: item.css, deps: item.imports }; 65 | }), 66 | allStyleVarFiles, 67 | renderOptions.filename, 68 | defaultPluginOpt.includeStyleWithColors, 69 | defaultPluginOpt.arbitraryMode, 70 | defaultPluginOpt.extract 71 | ) 72 | ) 73 | .then((result) => { 74 | const cssResult = { 75 | css: result.code, 76 | imports: result.deps, 77 | map: result.map, 78 | }; 79 | if (callback) { 80 | callback(null, cssResult); 81 | return null; 82 | } 83 | return cssResult; 84 | }); 85 | if (callback) { 86 | rePromise.catch(callback); 87 | } 88 | return rePromise; 89 | }; 90 | return less; 91 | } 92 | 93 | export default getLess; 94 | -------------------------------------------------------------------------------- /src/getSass.js: -------------------------------------------------------------------------------- 1 | import { v5 as uuidv5 } from 'uuid'; 2 | 3 | import { 4 | getScopeProcessResult, 5 | getAllStyleVarFiles, 6 | getVarsContent, 7 | createArbitraryModeVarColors, 8 | getPluginParams, 9 | // eslint-disable-next-line import/no-extraneous-dependencies 10 | } from './utils'; 11 | 12 | /** 13 | * 14 | * @param {Object} opt 15 | * @param {Object} opt.implementation 16 | * @param {Function} opt.getMultipleScopeVars 17 | * @returns sass 18 | */ 19 | export function getSass(opt = {}) { 20 | const packname = 'sass'; 21 | 22 | let sass = opt.implementation; 23 | if (!sass) { 24 | try { 25 | sass = require(packname); 26 | } catch (e) { 27 | throw new Error( 28 | `Dependency "${packname}" not found. Did you install it?` 29 | ); 30 | } 31 | } 32 | 33 | const { render } = sass; 34 | 35 | // eslint-disable-next-line func-names 36 | sass.render = function (options = {}, callback) { 37 | const renderOptions = { ...options }; 38 | const defaultPluginOpt = getPluginParams(opt); 39 | const multipleScopeVars = 40 | typeof opt.getMultipleScopeVars === 'function' 41 | ? opt.getMultipleScopeVars(renderOptions) 42 | : renderOptions.multipleScopeVars; 43 | 44 | delete renderOptions.multipleScopeVars; 45 | 46 | const allStyleVarFiles = getAllStyleVarFiles( 47 | { 48 | emitError: (msg) => { 49 | throw new Error(msg); 50 | }, 51 | }, 52 | { multipleScopeVars, arbitraryMode: defaultPluginOpt.arbitraryMode } 53 | ); 54 | const preProcessor = (code) => 55 | new Promise((resolve, reject) => { 56 | render.call( 57 | sass, 58 | { ...renderOptions, data: code }, 59 | (err, res) => { 60 | if (err) { 61 | reject(err); 62 | } else { 63 | resolve(res); 64 | } 65 | } 66 | ); 67 | }); 68 | let cssResult = {}; 69 | // 按allStyleVarFiles的个数对当前文件编译多次得到多个结果 70 | const rePromise = Promise.all( 71 | allStyleVarFiles.map((file) => { 72 | const varscontent = getVarsContent(file.path, packname); 73 | if (defaultPluginOpt.arbitraryMode) { 74 | createArbitraryModeVarColors(varscontent); 75 | } 76 | return preProcessor( 77 | `${file.varsContent || ''}${varscontent}\n${ 78 | renderOptions.data 79 | }` 80 | ); 81 | }) 82 | ) 83 | .then((prs) => { 84 | // eslint-disable-next-line prefer-destructuring 85 | cssResult = prs[0]; 86 | 87 | return getScopeProcessResult( 88 | prs.map((item) => { 89 | return { 90 | ...item, 91 | code: item.css.toString(), 92 | deps: item.stats.includedFiles, 93 | }; 94 | }), 95 | allStyleVarFiles, 96 | // sass-loader v8.x 没有传入renderOptions.file, 用uuid生成一个,防止报错 97 | renderOptions.file || 98 | (renderOptions.data && 99 | uuidv5( 100 | renderOptions.data, 101 | '4725327a-3250-4226-86cf-ae5ce775795b' 102 | )), 103 | defaultPluginOpt.includeStyleWithColors, 104 | defaultPluginOpt.arbitraryMode, 105 | defaultPluginOpt.extract 106 | ); 107 | }) 108 | 109 | .then((result) => { 110 | cssResult.css = result.code; 111 | cssResult.stats.includedFiles = result.deps; 112 | 113 | if (callback) { 114 | callback(null, cssResult); 115 | return null; 116 | } 117 | 118 | return cssResult; 119 | }); 120 | if (callback) { 121 | rePromise.catch(callback); 122 | } 123 | return rePromise; 124 | }; 125 | return sass; 126 | } 127 | 128 | export default getSass; 129 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | export * from './getLess'; 3 | export * from './getSass'; 4 | export * from './arbitraryMode'; 5 | -------------------------------------------------------------------------------- /src/packPath.js: -------------------------------------------------------------------------------- 1 | import pack from '../package.json'; 2 | 3 | export function getCurrentPackRequirePath() { 4 | const targetRsoleved = require 5 | .resolve(pack.name) 6 | .replace(/[\\/]dist[\\/]index\.js$/, '') 7 | .replace(/\\/g, '/'); 8 | return targetRsoleved; 9 | } 10 | 11 | export default {}; 12 | -------------------------------------------------------------------------------- /src/postcss-addScopeName.js: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss'; 2 | 3 | import { isSameColor, removeSpaceInColor } from './arbitraryMode/utils'; 4 | 5 | export default ( 6 | opts = { 7 | allStyleVarFiles: [], 8 | allCssCodes: [], 9 | startIndex: 0, 10 | arbitraryMode: false, 11 | includeStyleWithColors: [], 12 | extract: false, 13 | }, 14 | themeRuleValues = [], 15 | themeRuleMap = {} 16 | ) => { 17 | const { allStyleVarFiles, arbitraryMode, extract } = opts; 18 | function addScopeName(selector, scopeName) { 19 | if (/^(\.[^:]+)?:root/i.test(selector)) { 20 | return `.${scopeName}${selector}`; 21 | } 22 | if (/^html/i.test(selector)) { 23 | return selector.replace( 24 | /^html[^\s]*(?=\s*)/gi, 25 | (word) => `${word}.${scopeName}` 26 | ); 27 | } 28 | return `.${scopeName}\x20${selector}`; 29 | } 30 | // allStyleVarFiles的个数与allCssCodes对应的 31 | // 除去startIndex的,其他的css转成ast 32 | const restCssAsts = opts.allCssCodes 33 | .filter((item, i) => i !== opts.startIndex) 34 | .map((code) => postcss.parse(code)); 35 | return { 36 | postcssPlugin: 'postcss-addScopeName', 37 | Rule(rule, { Rule, Declaration }) { 38 | let blendRule = null; 39 | // 与当前样式规则不相同的其他主题样式规则 40 | const themeRules = []; 41 | // 当前规则中与其他主题规则中不相同的属性 42 | const currentThemeProps = {}; 43 | // 当前规则中所有属性,按顺序去重 44 | const currentRuleNodeMap = {}; 45 | rule.nodes.forEach((node) => { 46 | if (node.type === 'decl') { 47 | currentRuleNodeMap[node.prop] = node; 48 | } 49 | }); 50 | const currentThemeKeyframes = []; 51 | 52 | const extractThemeKeyframesMap = {}; 53 | const getThemeRules = (themeRule, varFile) => { 54 | const childNodes = []; 55 | // 先将主题规则属性按顺序去重 56 | const themeRuleNodeMap = {}; 57 | themeRule.nodes.forEach((node) => { 58 | if (node.type === 'decl') { 59 | themeRuleNodeMap[node.prop] = node; 60 | } 61 | }); 62 | Object.keys(currentRuleNodeMap).forEach((prop) => { 63 | if (!themeRuleNodeMap[prop]) { 64 | return; 65 | } 66 | // 比对属性值不相等的,或者存在 includeStyleWithColors 中对应的颜值,就进行分离 67 | if ( 68 | currentRuleNodeMap[prop].value !== 69 | themeRuleNodeMap[prop].value || 70 | (!varFile.arbitraryMode && 71 | (opts.includeStyleWithColors || []).some((item) => { 72 | if ( 73 | Array.isArray(item.excludeSelectors) && 74 | themeRuleNodeMap[prop].parent && 75 | themeRuleNodeMap[prop].parent.type === 76 | 'rule' 77 | ) { 78 | const { selectors } = 79 | themeRuleNodeMap[prop].parent; 80 | if ( 81 | item.excludeSelectors.some( 82 | (selectorStr) => 83 | selectorStr.replace( 84 | /,\s+/g, 85 | ',' 86 | ) === selectors.join(',') 87 | ) 88 | ) { 89 | return false; 90 | } 91 | } 92 | const isExcludeProperty = 93 | Array.isArray(item.excludeCssProps) && 94 | item.excludeCssProps.includes(prop); 95 | if (isExcludeProperty) { 96 | return false; 97 | } 98 | if (Array.isArray(item.color)) { 99 | return item.color.some((co) => 100 | isSameColor( 101 | co, 102 | themeRuleNodeMap[prop].value 103 | ) 104 | ); 105 | } 106 | return isSameColor( 107 | item.color, 108 | themeRuleNodeMap[prop].value 109 | ); 110 | })) 111 | ) { 112 | themeRuleValues.add( 113 | removeSpaceInColor(themeRuleNodeMap[prop].value) 114 | ); 115 | 116 | childNodes.push(themeRuleNodeMap[prop]); 117 | currentThemeProps[prop] = currentRuleNodeMap[prop]; 118 | } 119 | delete themeRuleNodeMap[prop]; 120 | }); 121 | // 假如比对后还有剩余,也纳入主题属性 122 | Object.keys(themeRuleNodeMap).forEach((prop) => { 123 | childNodes.push(themeRuleNodeMap[prop]); 124 | }); 125 | themeRule.nodes = childNodes; 126 | if (!arbitraryMode && varFile.arbitraryMode) { 127 | blendRule = themeRule.clone(); 128 | } else { 129 | themeRules.push(themeRule.clone()); 130 | } 131 | themeRule.remove(); 132 | }; 133 | const restVarFiles = allStyleVarFiles.slice(0); 134 | restVarFiles.splice(opts.startIndex, 1); 135 | restCssAsts.forEach((root, i) => { 136 | for ( 137 | let index = 0; 138 | index < (root.nodes || []).length; 139 | index++ 140 | ) { 141 | const themeRule = root.nodes[index]; 142 | 143 | /* ast第一层节点属于选择器类型的 144 | * 找到与rule选择器匹配的 145 | */ 146 | const isSameRule = 147 | themeRule.type === 'rule' && 148 | themeRule.selector === rule.selector; 149 | if (isSameRule) { 150 | getThemeRules(themeRule, restVarFiles[i]); 151 | break; 152 | } 153 | // 当这条规则在@media内 154 | const isInMedia = 155 | rule.parent.type === 'atrule' && 156 | rule.parent.name === 'media'; 157 | const isSameInMedia = 158 | themeRule.type === 'atrule' && 159 | themeRule.name === 'media' && 160 | themeRule.params === rule.parent.params; 161 | if (isInMedia && isSameInMedia) { 162 | // 刚好是同一个@media时,就往里面找到匹配的规则处理 163 | const atruleChild = (themeRule.nodes || []).find( 164 | (item) => 165 | item.type === 'rule' && 166 | item.selector === rule.selector 167 | ); 168 | if (atruleChild) { 169 | getThemeRules(atruleChild, restVarFiles[i]); 170 | } 171 | 172 | break; 173 | } 174 | // 当这条规则在@keyframes内 175 | const isInKeyframes = 176 | rule.parent.type === 'atrule' && 177 | rule.parent.name === 'keyframes'; 178 | if (isInKeyframes) { 179 | // 又当@keyframes在@media内部时 180 | const isKeyframeInMedia = 181 | rule.parent.parent && 182 | rule.parent.parent.type === 'atrule' && 183 | rule.parent.parent.name === 'media'; 184 | const isSameKeyframeInMedia = 185 | themeRule.type === 'atrule' && 186 | themeRule.name === 'media' && 187 | rule.parent.parent && 188 | rule.parent.parent.params === themeRule.params; 189 | if (isKeyframeInMedia && isSameKeyframeInMedia) { 190 | // 当刚好是同一个@media,往里面找到匹配的@keyframes 191 | const atruleChild = (themeRule.nodes || []).find( 192 | (item) => 193 | item.type === 'atrule' && 194 | item.name === 'keyframes' && 195 | item.params === rule.parent.params 196 | ); 197 | 198 | if (atruleChild) { 199 | const childRules = atruleChild.nodes || []; 200 | const existsDiffValue = childRules.some( 201 | (item) => { 202 | const isExst = 203 | item.type === 'rule' && 204 | item.selector === rule.selector && 205 | item.nodes.some((node) => { 206 | const isDiffValue = 207 | node.type === 'decl' && 208 | currentRuleNodeMap[ 209 | node.prop 210 | ].value !== node.value; 211 | if (isDiffValue) { 212 | themeRuleValues.add( 213 | removeSpaceInColor( 214 | node.value 215 | ) 216 | ); 217 | } 218 | return isDiffValue; 219 | }); 220 | return isExst; 221 | } 222 | ); 223 | if (existsDiffValue) { 224 | if ( 225 | !arbitraryMode && 226 | !restVarFiles[i].arbitraryMode 227 | ) { 228 | currentThemeKeyframes.push( 229 | atruleChild.clone() 230 | ); 231 | const currentMedia = 232 | rule.parent.parent.clone(); 233 | currentMedia.removeAll(); 234 | currentMedia.append( 235 | rule.parent.clone() 236 | ); 237 | extractThemeKeyframesMap[ 238 | allStyleVarFiles[ 239 | opts.startIndex 240 | ].scopeName 241 | ] = currentMedia; 242 | rule.parent.remove(); 243 | } 244 | const media = themeRule.clone(); 245 | media.removeAll(); 246 | media.append(atruleChild.clone()); 247 | extractThemeKeyframesMap[ 248 | restVarFiles[i].scopeName 249 | ] = media; 250 | atruleChild.remove(); 251 | } 252 | } 253 | 254 | break; 255 | } 256 | const isSameKeyFrame = 257 | themeRule.type === 'atrule' && 258 | themeRule.name === 'keyframes' && 259 | themeRule.params === rule.parent.params; 260 | if (isSameKeyFrame) { 261 | const childRules = themeRule.nodes || []; 262 | const existsDiffValue = childRules.some((item) => { 263 | const isExst = 264 | item.type === 'rule' && 265 | item.selector === rule.selector && 266 | item.nodes.some((node) => { 267 | const isDiffValue = 268 | node.type === 'decl' && 269 | currentRuleNodeMap[node.prop] 270 | .value !== node.value; 271 | 272 | if (isDiffValue) { 273 | themeRuleValues.add( 274 | removeSpaceInColor(node.value) 275 | ); 276 | } 277 | return isDiffValue; 278 | }); 279 | return isExst; 280 | }); 281 | if (existsDiffValue) { 282 | if ( 283 | !arbitraryMode && 284 | !restVarFiles[i].arbitraryMode 285 | ) { 286 | currentThemeKeyframes.push( 287 | themeRule.clone() 288 | ); 289 | extractThemeKeyframesMap[ 290 | allStyleVarFiles[ 291 | opts.startIndex 292 | ].scopeName 293 | ] = rule.parent.clone(); 294 | rule.parent.remove(); 295 | } 296 | extractThemeKeyframesMap[ 297 | restVarFiles[i].scopeName 298 | ] = themeRule.clone(); 299 | themeRule.remove(); 300 | break; 301 | } 302 | } 303 | } 304 | } 305 | }); 306 | const root = rule.parent; 307 | rule.nodes.forEach((node) => { 308 | if ( 309 | node.type === 'decl' && 310 | currentThemeProps[node.prop] === node 311 | ) { 312 | node.remove(); 313 | } 314 | }); 315 | 316 | if (themeRules.length) { 317 | const firstThemeRule = rule.clone(); 318 | if (!arbitraryMode) { 319 | firstThemeRule.removeAll(); 320 | } 321 | Object.keys(currentThemeProps).forEach((key) => { 322 | for (let index = 0; index < themeRules.length; index++) { 323 | const tRule = themeRules[index]; 324 | // 如果当前规则的样式属性在其他主题规则中不存在,说明该样式属性是所有主题样式属性,但是在上面比对中发现值一致未被添加,这里要添加回去 325 | if ( 326 | !tRule.nodes.some( 327 | (node) => 328 | node.type === 'decl' && node.prop === key 329 | ) 330 | ) { 331 | tRule.append(currentThemeProps[key].clone()); 332 | } 333 | } 334 | if (!arbitraryMode) { 335 | firstThemeRule.append(currentThemeProps[key].clone()); 336 | } 337 | }); 338 | if (!arbitraryMode) { 339 | // 保持themeRules的顺序对应 opts.allStyleVarFiles的顺序,然后添加scopeName 340 | themeRules.splice(opts.startIndex, 0, firstThemeRule); 341 | } 342 | let arbitraryModeScopeItem = null; 343 | const scopeItems = ( 344 | !arbitraryMode ? allStyleVarFiles : restVarFiles 345 | ).filter((item) => { 346 | if (item.arbitraryMode) { 347 | arbitraryModeScopeItem = item; 348 | } 349 | return !item.arbitraryMode; 350 | }); 351 | 352 | themeRules.forEach((item, i) => { 353 | if (item && item.nodes.length) { 354 | const removeNodes = []; 355 | if (blendRule && blendRule.nodes.length) { 356 | item.nodes.forEach((node) => { 357 | if ( 358 | node.type === 'decl' && 359 | blendRule.nodes.some( 360 | (b) => 361 | b.type === 'decl' && 362 | b.prop === node.prop && 363 | b.value === node.prop 364 | ) 365 | ) { 366 | removeNodes.push(node); 367 | } 368 | }); 369 | } 370 | removeNodes.forEach((node) => { 371 | item.removeChild(node); 372 | }); 373 | if (!arbitraryMode && item.nodes.length) { 374 | // eslint-disable-next-line no-param-reassign 375 | item.selectors = item.selectors.map((selector) => 376 | addScopeName(selector, scopeItems[i].scopeName) 377 | ); 378 | if (!extract) { 379 | root.insertBefore(rule, item); 380 | } 381 | } 382 | let themeCssItem = item; 383 | if ( 384 | themeCssItem.nodes.length && 385 | rule.parent.type === 'atrule' && 386 | rule.parent.name === 'media' 387 | ) { 388 | const pclone = rule.parent.clone(); 389 | pclone.removeAll(); 390 | pclone.append(item.clone()); 391 | themeCssItem = pclone; 392 | } 393 | if (themeCssItem.nodes.length) { 394 | const scopeSet = 395 | themeRuleMap[scopeItems[i].scopeName] || 396 | new Set(); 397 | scopeSet.add(themeCssItem.toString()); 398 | themeRuleMap[scopeItems[i].scopeName] = scopeSet; 399 | } 400 | } 401 | }); 402 | if (!arbitraryMode && blendRule && arbitraryModeScopeItem) { 403 | let themeCssItem = blendRule; 404 | if (themeCssItem.nodes.length) { 405 | // eslint-disable-next-line no-param-reassign 406 | themeCssItem.selectors = themeCssItem.selectors.map( 407 | (selector) => 408 | addScopeName( 409 | selector, 410 | arbitraryModeScopeItem.scopeName 411 | ) 412 | ); 413 | } 414 | 415 | if ( 416 | themeCssItem.nodes.length && 417 | rule.parent.type === 'atrule' && 418 | rule.parent.name === 'media' 419 | ) { 420 | const pclone = rule.parent.clone(); 421 | pclone.removeAll(); 422 | pclone.append(themeCssItem.clone()); 423 | themeCssItem = pclone; 424 | } 425 | if (themeCssItem.nodes.length) { 426 | const scopeSet = 427 | themeRuleMap[arbitraryModeScopeItem.scopeName] || 428 | new Set(); 429 | scopeSet.add(themeCssItem.toString()); 430 | themeRuleMap[arbitraryModeScopeItem.scopeName] = 431 | scopeSet; 432 | } 433 | } 434 | if (!arbitraryMode) { 435 | const selectorMap = rule.selectors.reduce((tol, key) => { 436 | return { ...tol, [key]: true }; 437 | }, {}); 438 | 439 | scopeItems.forEach((item) => { 440 | if ( 441 | item.includeStyles && 442 | typeof item.includeStyles === 'object' && 443 | !Array.isArray(item.includeStyles) 444 | ) { 445 | const includeKey = Object.keys( 446 | item.includeStyles 447 | ).find((key) => { 448 | const selectors = key 449 | .replace(/,\s+/g, ',') 450 | .split(','); 451 | return selectors.every((s) => selectorMap[s]); 452 | }); 453 | if (includeKey) { 454 | const newRule = new Rule({ 455 | selector: rule.selector, 456 | }); 457 | newRule.selectors = newRule.selectors.map( 458 | (selector) => 459 | addScopeName(selector, item.scopeName) 460 | ); 461 | Object.keys( 462 | item.includeStyles[includeKey] 463 | ).forEach((prop) => { 464 | const decl = new Declaration({ 465 | prop, 466 | value: item.includeStyles[includeKey][ 467 | prop 468 | ], 469 | }); 470 | newRule.append(decl); 471 | const currDecl = rule.nodes.find((node) => { 472 | if (node.prop === decl.prop) { 473 | return ( 474 | node.value === decl.value || 475 | isSameColor( 476 | node.value, 477 | decl.value 478 | ) 479 | ); 480 | } 481 | return false; 482 | }); 483 | 484 | if (currDecl) { 485 | rule.removeChild(currDecl); 486 | } 487 | }); 488 | const scopeSet = 489 | themeRuleMap[item.scopeName] || new Set(); 490 | scopeSet.add(newRule.toString()); 491 | themeRuleMap[item.scopeName] = scopeSet; 492 | if (!extract) { 493 | root.insertBefore(rule, newRule); 494 | } 495 | } 496 | } 497 | }); 498 | } 499 | if (!rule.nodes.length) { 500 | rule.remove(); 501 | } 502 | } 503 | if (!arbitraryMode && !extract) { 504 | currentThemeKeyframes.forEach((item) => { 505 | if (rule.parent && rule.parent.parent) { 506 | rule.parent.parent.insertBefore(rule.parent, item); 507 | } 508 | }); 509 | } 510 | 511 | for (const key in extractThemeKeyframesMap) { 512 | if (Object.hasOwnProperty.call(extractThemeKeyframesMap, key)) { 513 | const keyframesRule = extractThemeKeyframesMap[key]; 514 | const scopeSet = themeRuleMap[key] || new Set(); 515 | scopeSet.add(keyframesRule.toString()); 516 | themeRuleMap[key] = scopeSet; 517 | } 518 | } 519 | }, 520 | }; 521 | }; 522 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require */ 2 | /* eslint-disable import/extensions */ 3 | /* eslint-disable import/no-unresolved */ 4 | 5 | import fs from 'fs-extra'; 6 | 7 | import { v5 as uuidv5 } from 'uuid'; 8 | 9 | import postcss from 'postcss'; 10 | 11 | import cssnano from 'cssnano'; 12 | 13 | import lite from 'cssnano-preset-lite'; 14 | 15 | import postcssAddScopeName from './postcss-addScopeName'; 16 | 17 | import { colorValueReg } from './arbitraryMode/utils'; 18 | 19 | // import browerColorMap from './arbitraryMode/colors'; 20 | 21 | import { getCurrentPackRequirePath } from './packPath'; 22 | 23 | function getBlendVarFiles(allStyleVarFiles) { 24 | const allStyleVarFilesCopy = allStyleVarFiles.slice(0); 25 | let blendVarItem = null; 26 | for (let index = 0; index < allStyleVarFiles.length; index++) { 27 | const item = allStyleVarFiles[index]; 28 | if (item.arbitraryMode) { 29 | blendVarItem = item; 30 | allStyleVarFilesCopy.splice(index, 1); 31 | break; 32 | } 33 | } 34 | allStyleVarFilesCopy.forEach((item) => { 35 | item.arbitraryMode = false; 36 | }); 37 | if (blendVarItem) { 38 | allStyleVarFilesCopy.push(blendVarItem); 39 | } 40 | return { 41 | allStyleVarFiles: allStyleVarFilesCopy, 42 | blendVarFile: blendVarItem, 43 | }; 44 | } 45 | const getAllStyleVarFiles = (loaderContext, options) => { 46 | const varsSet = {}; 47 | (options.multipleScopeVars || []).forEach((item) => { 48 | varsSet[item.scopeName] = item; 49 | }); 50 | const styleVarFiles = Object.keys(varsSet).reduce( 51 | (tol, key) => [...tol, varsSet[key]], 52 | [] 53 | ); 54 | let allStyleVarFiles = [{ scopeName: '', path: '' }]; 55 | if (Array.isArray(styleVarFiles) && styleVarFiles.length) { 56 | if (styleVarFiles.length === 1) { 57 | allStyleVarFiles = styleVarFiles.map((item) => { 58 | if (Array.isArray(item.path)) { 59 | const exist = item.path.every((pathstr) => { 60 | const exists = pathstr && fs.existsSync(pathstr); 61 | if (!exists) { 62 | loaderContext.emitError( 63 | new Error( 64 | `Not found path: ${pathstr} in multipleScopeVars` 65 | ) 66 | ); 67 | } 68 | return exists; 69 | }); 70 | if (!exist) { 71 | return { scopeName: '', path: '' }; 72 | } 73 | } else if ( 74 | (!item.path || 75 | typeof item.path !== 'string' || 76 | !fs.existsSync(item.path)) && 77 | typeof item.varsContent !== 'string' 78 | ) { 79 | loaderContext.emitError( 80 | new Error( 81 | `Not found path or varsContent: ${item.path} in multipleScopeVars` 82 | ) 83 | ); 84 | return { scopeName: '', path: '' }; 85 | } 86 | if (options.arbitraryMode) { 87 | if (!item.scopeName) { 88 | return { ...item, scopeName: 'theme-default' }; 89 | } 90 | return item; 91 | } 92 | return { ...item, scopeName: '' }; 93 | }); 94 | return ( 95 | options.arbitraryMode ? [{ scopeName: '', path: '' }] : [] 96 | ).concat( 97 | allStyleVarFiles.filter( 98 | (item) => 99 | !!item.path || typeof item.varsContent === 'string' 100 | ) 101 | ); 102 | } 103 | allStyleVarFiles = styleVarFiles.filter((item) => { 104 | if (!item.scopeName) { 105 | loaderContext.emitError( 106 | new Error('Not found scopeName in multipleScopeVars') 107 | ); 108 | return false; 109 | } 110 | if (Array.isArray(item.path)) { 111 | return ( 112 | item.path.every((pathstr) => { 113 | const exists = pathstr && fs.existsSync(pathstr); 114 | if (!exists) { 115 | loaderContext.emitError( 116 | new Error( 117 | `Not found path : ${pathstr} in multipleScopeVars` 118 | ) 119 | ); 120 | } 121 | return exists; 122 | }) || typeof item.varsContent === 'string' 123 | ); 124 | } 125 | if ( 126 | (!item.path || 127 | typeof item.path !== 'string' || 128 | !fs.existsSync(item.path)) && 129 | typeof item.varsContent !== 'string' 130 | ) { 131 | loaderContext.emitError( 132 | new Error( 133 | `Not found path or varsContent: ${item.path} in multipleScopeVars` 134 | ) 135 | ); 136 | return false; 137 | } 138 | return true; 139 | }); 140 | } 141 | return options.arbitraryMode 142 | ? allStyleVarFiles.slice(0, 2) 143 | : getBlendVarFiles(allStyleVarFiles).allStyleVarFiles; 144 | }; 145 | 146 | // const cssFragReg = /[^{}/\\]+{[^{}]*?}/g; 147 | // const classNameFragReg = /[^{}/\\]+(?={)/; 148 | 149 | /** 150 | * 把多个 css 内容按 multipleScopeVars 对应顺序合并,并去重 151 | * @param {Array} cssResults [ 152 | { 153 | map: sourceMap || null, 154 | code: ` 155 | .un-btn { 156 | position: relative; 157 | background-color: #0081ff; 158 | } 159 | .un-btn .anticon { 160 | line-height: 1; 161 | }`, 162 | deps: ["E:\\sub\\panel1.less", "E:\\sub\\panel2.less"], 163 | }, 164 | { 165 | map: sourceMap || null, 166 | code: ` 167 | .un-btn { 168 | position: relative; 169 | background-color: #9c26b0; 170 | } 171 | .un-btn .anticon { 172 | line-height: 1; 173 | }`, 174 | deps: ["E:\\sub\\panel1.less", "E:\\sub\\panel2.less"], 175 | }, 176 | ] 177 | * @param {Array} allStyleVarFiles 178 | [ 179 | { scopeName: "theme-default", path: "E:\\sub\\default-vars.less" }, 180 | { scopeName: "theme-mauve", path: "E:\\sub\\mauve-vars.less" }, 181 | ] 182 | * @param {String} resourcePath "E:\\sub\\style.less" 183 | * @returns 184 | */ 185 | const getScopeProcessResult = ( 186 | cssResults = [], 187 | allStyleVarFiles = [], 188 | resourcePath, 189 | includeStyleWithColors, 190 | arbitraryMode, 191 | extract 192 | ) => { 193 | const preprocessResult = { deps: [], code: '', errors: [] }; 194 | if (cssResults.length === 1) { 195 | preprocessResult.code = cssResults[0].code; 196 | preprocessResult.deps = cssResults[0].deps; 197 | return Promise.resolve(preprocessResult); 198 | } 199 | cssResults.forEach((item, i) => { 200 | preprocessResult.errors = [ 201 | ...(preprocessResult.errors || []), 202 | ...(item.errors || []), 203 | ]; 204 | const deps = Array.isArray(allStyleVarFiles[i].path) 205 | ? allStyleVarFiles[i].path 206 | : [allStyleVarFiles[i].path]; 207 | deps.forEach((str) => { 208 | if (str) { 209 | preprocessResult.deps.push(str); 210 | } 211 | }); 212 | }); 213 | preprocessResult.deps = [ 214 | ...preprocessResult.deps, 215 | ...(cssResults[0].deps || []), 216 | ]; 217 | /** 218 | * 用cssResults的第一个css内容进入postcss 219 | */ 220 | const startIndex = 0; 221 | const themeRuleValues = new Set(); 222 | const themeRuleMap = {}; 223 | return postcss([ 224 | postcssAddScopeName( 225 | { 226 | allStyleVarFiles, 227 | allCssCodes: cssResults.map((r) => r.code), 228 | // 除去allCssCodes中的第几个 229 | startIndex, 230 | arbitraryMode, 231 | includeStyleWithColors, 232 | extract, 233 | }, 234 | themeRuleValues, 235 | themeRuleMap 236 | ), 237 | ]) 238 | .process(cssResults[startIndex].code, { 239 | from: resourcePath, 240 | to: resourcePath, 241 | }) 242 | .then((postResult) => { 243 | const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341'; 244 | const filename = uuidv5(resourcePath, MY_NAMESPACE); 245 | const dirName = 'extractTheme'; 246 | const targetRsoleved = getCurrentPackRequirePath(); 247 | if (!fs.existsSync(`${targetRsoleved}/${dirName}`)) { 248 | fs.mkdirSync(`${targetRsoleved}/${dirName}`); 249 | } 250 | const cssRules = {}; 251 | for (const key in themeRuleMap) { 252 | if (Object.hasOwnProperty.call(themeRuleMap, key)) { 253 | const ruleSet = themeRuleMap[key]; 254 | const cssArr = Array.from(ruleSet); 255 | if (cssArr.length) { 256 | cssRules[key] = cssArr; 257 | } 258 | } 259 | } 260 | const themeRuleValuesArr = Array.from(themeRuleValues); 261 | 262 | const filecontent = { 263 | cssRules, 264 | ruleValues: themeRuleValuesArr, 265 | resourcePath, 266 | }; 267 | fs.writeFileSync( 268 | `${targetRsoleved}/${dirName}/${filename}.json`, 269 | JSON.stringify(filecontent, null, 4) 270 | ); 271 | 272 | preprocessResult.code = postResult.css; 273 | return preprocessResult; 274 | }); 275 | }; 276 | 277 | /** 278 | * getScropProcessResult 修正命名 getScopeProcessResult后的兼容 279 | */ 280 | const getScropProcessResult = getScopeProcessResult; 281 | /** 282 | * 283 | * @param {String} url 284 | * @param {String} type "less" | "sass" 285 | * @returns code 286 | */ 287 | const replaceFormSass = (url, type) => { 288 | let code = url ? fs.readFileSync(url).toString() : ''; 289 | if (type === 'sass') { 290 | if (/\.less$/i.test(url)) { 291 | code = code.replace(/@/g, '$'); 292 | } 293 | return code.replace(/!default/g, ''); 294 | } 295 | if (/\.(scss|sass)$/i.test(url)) { 296 | code = code.replace(/\$/g, '@').replace(/!default/g, ''); 297 | } 298 | return code; 299 | }; 300 | /** 301 | * 302 | * @param {String} url 303 | * @param {String} type "less" | "sass" 304 | * @returns code 305 | */ 306 | const getVarsContent = (url, type) => { 307 | let content = ''; 308 | if (Array.isArray(url)) { 309 | url.forEach((p) => { 310 | content += replaceFormSass(p, type); 311 | }); 312 | } else { 313 | content = replaceFormSass(url, type); 314 | } 315 | return content; 316 | }; 317 | function removeThemeFiles() { 318 | const dirName = 'extractTheme'; 319 | const targetRsoleved = getCurrentPackRequirePath(); 320 | if (fs.existsSync(`${targetRsoleved}/${dirName}`)) { 321 | fs.removeSync(`${targetRsoleved}/${dirName}`); 322 | } 323 | } 324 | function getExtractThemeCode(opt) { 325 | const { scopeName = '', excludeScopeNames = [] } = opt || {}; 326 | const targetRsoleved = getCurrentPackRequirePath(); 327 | const dirName = 'extractTheme'; 328 | if (fs.existsSync(`${targetRsoleved}/${dirName}`)) { 329 | const files = fs.readdirSync(`${targetRsoleved}/${dirName}`) || []; 330 | const themeRuleCodes = {}; 331 | let themeRuleValues = []; 332 | files.forEach((file) => { 333 | const { cssRules, ruleValues } = JSON.parse( 334 | fs 335 | .readFileSync(`${targetRsoleved}/${dirName}/${file}`) 336 | .toString() 337 | ); 338 | if (scopeName) { 339 | if (cssRules[scopeName]) { 340 | let scopeCssArr = themeRuleCodes[scopeName] || []; 341 | scopeCssArr = scopeCssArr.concat(cssRules[scopeName]); 342 | themeRuleCodes[scopeName] = scopeCssArr; 343 | } 344 | } else { 345 | Object.keys(cssRules) 346 | .filter((key) => !(excludeScopeNames || []).includes(key)) 347 | .forEach((key) => { 348 | let scopeCssArr = themeRuleCodes[key] || []; 349 | scopeCssArr = scopeCssArr.concat(cssRules[key]); 350 | themeRuleCodes[key] = scopeCssArr; 351 | }); 352 | } 353 | themeRuleValues = themeRuleValues.concat(ruleValues); 354 | }); 355 | return { 356 | themeRuleCodes, 357 | themeRuleValues: Array.from(new Set(themeRuleValues)), 358 | }; 359 | } 360 | return { themeRules: {}, themeRuleValues: [] }; 361 | } 362 | 363 | /** 364 | * 365 | * @param {Object} options 366 | * @param {Boolean} options.removeCssScopeName 抽取的css是否移除scopeName 367 | * @returns { css: String, themeCss: Object , themeCommonCss: String } 368 | */ 369 | const extractThemeCss = ({ 370 | removeCssScopeName, 371 | scopeName, 372 | excludeScopeNames, 373 | }) => { 374 | const { themeRuleCodes, themeRuleValues } = getExtractThemeCode({ 375 | scopeName, 376 | excludeScopeNames, 377 | }); 378 | 379 | const allPro = Object.keys(themeRuleCodes || {}).map((key) => { 380 | const codes = ( 381 | removeCssScopeName 382 | ? themeRuleCodes[key].map((frag) => 383 | frag.replace(new RegExp(`\\.${key}`, 'g'), '') 384 | ) 385 | : themeRuleCodes[key] 386 | ).join(''); 387 | return postcss([ 388 | cssnano({ 389 | preset: lite({}), 390 | }), 391 | ]) 392 | .process(codes) 393 | .then((postResult) => { 394 | return { key, css: postResult.css }; 395 | }); 396 | }); 397 | return Promise.all(allPro).then((res) => { 398 | const themeCss = {}; 399 | res.forEach((item) => { 400 | themeCss[item.key] = item.css; 401 | }); 402 | return { themeCss, themeRuleValues }; 403 | }); 404 | }; 405 | 406 | const addScopnameToHtmlClassname = (html, defaultScopeName) => { 407 | let newHtml = html; 408 | const htmlTagAttrStrings = html.match(/<\s*html[^<>]*>/gi) || []; 409 | 410 | htmlTagAttrStrings.forEach((attrstr) => { 411 | const classnameStrings = attrstr.match(/class\s*=['"].+['"]/g); 412 | if (classnameStrings) { 413 | classnameStrings.forEach((classstr) => { 414 | const classnamestr = classstr.replace( 415 | /(^class\s*=['"]|['"]$)/g, 416 | '' 417 | ); 418 | const classnames = classnamestr.split(' '); 419 | if (!classnames.includes(defaultScopeName)) { 420 | classnames.push(defaultScopeName); 421 | newHtml = newHtml.replace( 422 | attrstr, 423 | attrstr.replace( 424 | classstr, 425 | classstr.replace(classnamestr, classnames.join(' ')) 426 | ) 427 | ); 428 | } 429 | }); 430 | } else { 431 | newHtml = newHtml.replace( 432 | attrstr, 433 | `${attrstr.replace(/>$/, '')} class="${defaultScopeName}">` 434 | ); 435 | } 436 | }); 437 | return newHtml; 438 | }; 439 | 440 | function createArbitraryModeVarColors(filecontent) { 441 | if (filecontent) { 442 | const hex = (filecontent.match(colorValueReg.hex) || []).map((color) => 443 | color.replace(/\s+/g, '') 444 | ); 445 | const rgb = (filecontent.match(colorValueReg.rgb) || []).map((color) => 446 | color.replace(/\s+/g, '') 447 | ); 448 | const rgba = (filecontent.match(colorValueReg.rgba) || []).map( 449 | (color) => color.replace(/\s+/g, '') 450 | ); 451 | const hsl = (filecontent.match(colorValueReg.hsl) || []).map((color) => 452 | color.replace(/\s+/g, '').replace(/,0(?=,|\))/g, ',0%') 453 | ); 454 | const hsla = (filecontent.match(colorValueReg.hsla) || []).map( 455 | (color) => color.replace(/\s+/g, '').replace(/,0(?=,)/g, ',0%') 456 | ); 457 | // const browerColorReg = new RegExp( 458 | // `(?<=:\\s*)(${Object.keys(browerColorMap).join('|')})(?=\\s*)`, 459 | // 'ig' 460 | // ); 461 | // const browerColors = filecontent.match(browerColorReg) || []; 462 | const colors = Array.from( 463 | new Set( 464 | [].concat(hex).concat(rgb).concat(rgba).concat(hsl).concat(hsla) 465 | ) 466 | ); 467 | const targetRsoleved = getCurrentPackRequirePath(); 468 | fs.writeFileSync( 469 | `${targetRsoleved}/baseVarColors.json`, 470 | JSON.stringify({ baseVarColors: colors }) 471 | ); 472 | } 473 | } 474 | 475 | function createPulignParamsFile(options = {}) { 476 | const targetRsoleved = getCurrentPackRequirePath(); 477 | fs.writeFileSync( 478 | `${targetRsoleved}/pulignParams.js`, 479 | `module.exports = ${JSON.stringify(options)};` 480 | ); 481 | } 482 | 483 | function getPluginParams(opt) { 484 | const targetRsoleved = getCurrentPackRequirePath(); 485 | // webpack等插件将与getSass相同的参数,打入到pulignParams.js,这里就获取到作为默认参数 486 | let defaultPluginOpt = {}; 487 | const parmasPath = `${targetRsoleved}/pulignParams.js`; 488 | if (fs.existsSync(parmasPath)) { 489 | defaultPluginOpt = require(parmasPath); 490 | } 491 | defaultPluginOpt = { ...defaultPluginOpt, ...opt }; 492 | return defaultPluginOpt; 493 | } 494 | 495 | export { 496 | getAllStyleVarFiles, 497 | getScopeProcessResult, 498 | getScropProcessResult, 499 | getVarsContent, 500 | extractThemeCss, 501 | addScopnameToHtmlClassname, 502 | removeThemeFiles, 503 | createArbitraryModeVarColors, 504 | getCurrentPackRequirePath, 505 | createPulignParamsFile, 506 | getPluginParams, 507 | getBlendVarFiles, 508 | }; 509 | --------------------------------------------------------------------------------