This commit is contained in:
xuziqiang
2024-05-15 17:29:42 +08:00
commit d0155dbe3c
7296 changed files with 1832517 additions and 0 deletions

View File

@@ -0,0 +1,224 @@
import type { Plugin, ResolvedConfig } from 'vite';
import path from 'path';
import fs from 'fs-extra';
import less from 'less';
import { createFileHash, minifyCSS, extractVariable } from './utils';
import chalk from 'chalk';
import { colorRE, linkID } from './constants';
import { injectClientPlugin } from './injectClientPlugin';
import { lessPlugin } from './preprocessor/less';
export interface AntdDarkThemeOption {
darkModifyVars?: any;
fileName?: string;
verbose?: boolean;
selector?: string;
filter?: (id: string) => boolean;
extractCss?: boolean;
preloadFiles?: string[];
loadMethod?: 'link' | 'ajax';
}
export function antdDarkThemePlugin(options: AntdDarkThemeOption): Plugin[] {
const {
darkModifyVars,
verbose = true,
fileName = 'app-antd-dark-theme-style',
selector,
filter,
extractCss = true,
preloadFiles = [],
loadMethod = 'link',
} = options;
let isServer = false;
let needSourcemap = false;
let config: ResolvedConfig;
let extCssString = '';
const styleMap = new Map<string, string>();
const codeCache = new Map<string, { code: string; css: string }>();
const cssOutputName = `${fileName}.${createFileHash()}.css`;
const hrefProtocals = [ 'http://' ];
const getCss = (css: string) => {
return `[${selector || 'data-theme="dark"'}] {${css}}`;
};
async function preloadLess() {
if (!preloadFiles || !preloadFiles.length) {
return;
}
for (const id of preloadFiles) {
const code = fs.readFileSync(id, 'utf-8');
less
.render(code, {
javascriptEnabled: true,
modifyVars: darkModifyVars,
filename: path.resolve(id),
plugins: [lessPlugin(id, config)],
})
.then(({ css }) => {
const colors = css.match(colorRE);
if (colors) {
css = extractVariable(css, colors.concat(['transparent']));
codeCache.set(id, { code, css });
}
});
}
}
function getProtocal(path): string | undefined {
let protocal:string | undefined;
hrefProtocals.forEach(hrefProtocal => {
if(path.startsWith(hrefProtocal)){
protocal = hrefProtocal;
}
})
return protocal;
}
return [
injectClientPlugin('antdDarkPlugin', {
antdDarkCssOutputName: cssOutputName,
antdDarkExtractCss: extractCss,
antdDarkLoadLink: loadMethod === 'link',
}),
{
name: 'vite:antd-dark-theme',
enforce: 'pre',
configResolved(resolvedConfig) {
config = resolvedConfig;
isServer = resolvedConfig.command === 'serve';
needSourcemap = !!resolvedConfig.build.sourcemap;
isServer && preloadLess();
},
transformIndexHtml(html) {
let href;
const protocal = getProtocal(config.base);
if (isServer || loadMethod !== 'link') {
return html;
}
if(protocal) {
href = protocal + path.posix.join(config.base.slice(protocal.length), config.build.assetsDir, cssOutputName);
}
else {
href = path.posix.join(config.base, config.build.assetsDir, cssOutputName)
}
return {
html,
tags: [
{
tag: 'link',
attrs: {
disabled: true,
id: linkID,
rel: 'alternate stylesheet',
href: href,
},
injectTo: 'head',
},
],
};
},
async transform(code, id) {
if (!id.endsWith('.less') || !code.includes('@')) {
return null;
}
if (typeof filter === 'function' && !filter(id)) {
return null;
}
const getResult = (content: string) => {
return {
map: needSourcemap ? this.getCombinedSourcemap() : null,
code: content,
};
};
let processCss = '';
const cache = codeCache.get(id);
const isUpdate = !cache || cache.code !== code;
if (isUpdate) {
const { css } = await less.render(code, {
javascriptEnabled: true,
modifyVars: darkModifyVars,
filename: path.resolve(id),
plugins: [lessPlugin(id, config)],
});
const colors = css.match(colorRE);
if (colors) {
// The theme only extracts css related to color
// Can effectively reduce the size
processCss = extractVariable(css, colors.concat(['transparent']));
}
} else {
processCss = cache!.css;
}
if (isServer || !extractCss) {
isUpdate && codeCache.set(id, { code, css: processCss });
return getResult(`${getCss(processCss)}\n` + code);
} else {
if (!styleMap.has(id)) {
const { css } = await less.render(getCss(processCss), {
filename: path.resolve(id),
plugins: [lessPlugin(id, config)],
});
extCssString += `${css}\n`;
}
styleMap.set(id, processCss);
}
return null;
},
async writeBundle() {
if (!extractCss) {
return;
}
const {
root,
build: { outDir, assetsDir, minify },
} = config;
if (minify) {
extCssString = await minifyCSS(extCssString, config);
}
const cssOutputPath = path.resolve(root, outDir, assetsDir, cssOutputName);
fs.writeFileSync(cssOutputPath, extCssString);
},
closeBundle() {
if (verbose && !isServer && extractCss) {
const {
build: { outDir, assetsDir },
} = config;
console.log(
chalk.cyan('\n✨ [vite-plugin-theme:antd-dark]') +
` - extract antd dark css code file is successfully:`
);
try {
const { size } = fs.statSync(path.join(outDir, assetsDir, cssOutputName));
console.log(
chalk.dim(outDir + '/') +
chalk.magentaBright(`${assetsDir}/${cssOutputName}`) +
`\t\t${chalk.dim((size / 1024).toFixed(2) + 'kb')}` +
'\n'
);
} catch (error) {}
}
},
},
];
}

View File

@@ -0,0 +1,46 @@
import path, { resolve } from 'path';
import { normalizePath } from 'vite';
import { existsSync } from 'node:fs';
export const VITE_CLIENT_ENTRY = '/@vite/client';
let clientPath = process.cwd();
if (
!existsSync(
normalizePath(path.resolve(clientPath, 'node_modules/vite-plugin-theme/es//client.js')),
)
) {
clientPath = resolve(process.cwd(), '../');
console.log('子目录运行');
}
export const VITE_PLUGIN_THEME_CLIENT_ENTRY = normalizePath(
path.resolve(clientPath, 'node_modules/vite-plugin-theme/es/'),
);
export const CLIENT_PUBLIC_ABSOLUTE_PATH = normalizePath(
VITE_PLUGIN_THEME_CLIENT_ENTRY + '/client.js',
);
export const CLIENT_PUBLIC_PATH = `/${VITE_PLUGIN_THEME_CLIENT_ENTRY}/client.js`;
export const commentRE = /\\\\?n|\n|\\\\?r|\/\*[\s\S]+?\*\//g;
const cssLangs = `\\.(css|less|sass|scss|styl|stylus|postcss)($|\\?)`;
export const colorRE =
/#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})|rgba?\((.*),\s*(.*),\s*(.*)(?:,\s*(.*(?:.*)?))?\)/gi;
export const cssVariableString = `const css = "`;
export const cssBlockRE = /[^}]*\{[^{]*\}/g;
export const cssLangRE = new RegExp(cssLangs);
export const ruleRE = /(\w+-)*\w+:/;
export const cssValueRE = /(\s?[a-z0-9]+\s)*/;
export const safeEmptyRE = /\s?/;
export const importSafeRE = /(\s*!important)?/;
export const linkID = '__VITE_PLUGIN_THEME-ANTD_DARK_THEME_LINK__';

View File

@@ -0,0 +1,39 @@
import path from 'path';
import { Plugin } from 'esbuild';
import less from 'less';
/** Less-loader for esbuild */
export const lessLoader = (content, options: Less.Options = {}): Plugin => {
return {
name: 'less-loader',
setup: (build) => {
build.onResolve({ filter: /\.less$/, namespace: 'file' }, (args) => {
const filePath = path.resolve(
process.cwd(),
path.relative(process.cwd(), args.resolveDir),
args.path
);
return {
path: filePath,
};
});
// Build .less files
build.onLoad({ filter: /\.less$/, namespace: 'file' }, async (args) => {
const dir = path.dirname(args.path);
try {
const result = await less.render(content, {
...options,
paths: [...(options.paths || []), dir],
});
return {
contents: result.css,
loader: 'css',
resolveDir: dir,
};
} catch (e) {}
});
},
};
};

199
build/plugin/theme/index.ts Normal file
View File

@@ -0,0 +1,199 @@
import { Plugin, ResolvedConfig } from 'vite';
import path from 'path';
import fs from 'fs-extra';
import { debug as Debug } from 'debug';
import { extractVariable, minifyCSS } from './utils';
// export * from '../client/colorUtils';
export { antdDarkThemePlugin } from './antdDarkThemePlugin';
import { VITE_CLIENT_ENTRY, cssLangRE, cssVariableString, CLIENT_PUBLIC_PATH } from './constants';
export type ResolveSelector = (selector: string) => string;
export type InjectTo = 'head' | 'body' | 'body-prepend';
export interface ViteThemeOptions {
colorVariables: string[];
wrapperCssSelector?: string;
resolveSelector?: ResolveSelector;
customerExtractVariable?: (code: string) => string;
fileName?: string;
injectTo?: InjectTo;
verbose?: boolean;
isProd: boolean; // 必须传递环境标识
}
import { createFileHash, formatCss } from './utils';
import chalk from 'chalk';
import { injectClientPlugin } from './injectClientPlugin';
const debug = Debug('vite-plugin-theme');
export function viteThemePlugin(opt: ViteThemeOptions): Plugin[] {
let isServer = false;
let config: ResolvedConfig;
let clientPath = '';
const styleMap = new Map<string, string>();
let extCssSet = new Set<string>();
const emptyPlugin: Plugin = {
name: 'vite:theme',
};
const options: ViteThemeOptions = Object.assign(
{
colorVariables: [],
wrapperCssSelector: '',
fileName: 'app-theme-style',
injectTo: 'body',
verbose: true,
isProd: true, // 默认为 true切换主题只在生产环境生效。
},
opt,
);
debug('plugin options:', options);
const {
colorVariables,
wrapperCssSelector,
resolveSelector,
customerExtractVariable,
fileName,
verbose,
} = options;
if (!colorVariables || colorVariables.length === 0) {
console.error('colorVariables is not empty!');
return [emptyPlugin];
}
const resolveSelectorFn = resolveSelector || ((s: string) => `${wrapperCssSelector} ${s}`);
const cssOutputName = `${fileName}.${createFileHash()}.css`;
let needSourcemap = false;
console.log('options.isProd', options.isProd);
return [
injectClientPlugin('colorPlugin', {
colorPluginCssOutputName: cssOutputName,
colorPluginOptions: options,
}),
{
...emptyPlugin,
enforce: options.isProd ? undefined : 'post', // 生产环境不设置 enforce开发环境设置为 post切换主题才会都生效。
configResolved(resolvedConfig) {
config = resolvedConfig;
isServer = resolvedConfig.command === 'serve';
clientPath = JSON.stringify(path.posix.join(config.base, CLIENT_PUBLIC_PATH));
needSourcemap = !!resolvedConfig.build.sourcemap;
debug('plugin config:', resolvedConfig);
},
async transform(code, id) {
if (!cssLangRE.test(id)) {
return null;
}
const getResult = (content: string) => {
return {
map: needSourcemap ? this.getCombinedSourcemap() : null,
code: content,
};
};
const clientCode = isServer
? await getClientStyleString(code)
: code.replace('export default', '').replace('"', '');
// Used to extract the relevant color configuration in css, you can pass in the function to override
const extractCssCodeTemplate =
typeof customerExtractVariable === 'function'
? customerExtractVariable(clientCode)
: extractVariable(clientCode, colorVariables, resolveSelectorFn);
debug('extractCssCodeTemplate:', id, extractCssCodeTemplate);
if (!extractCssCodeTemplate) {
return null;
}
// dev-server
if (isServer) {
const retCode = [
`import { addCssToQueue } from ${clientPath}`,
`const themeCssId = ${JSON.stringify(id)}`,
`const themeCssStr = ${JSON.stringify(formatCss(extractCssCodeTemplate))}`,
`addCssToQueue(themeCssId, themeCssStr)`,
code,
];
return getResult(retCode.join('\n'));
} else {
if (!styleMap.has(id)) {
extCssSet.add(extractCssCodeTemplate);
}
styleMap.set(id, extractCssCodeTemplate);
}
return null;
},
async writeBundle() {
const {
root,
build: { outDir, assetsDir, minify },
} = config;
let extCssString = '';
for (const css of extCssSet) {
extCssString += css;
}
if (minify) {
extCssString = await minifyCSS(extCssString, config);
}
const cssOutputPath = path.resolve(root, outDir, assetsDir, cssOutputName);
fs.writeFileSync(cssOutputPath, extCssString);
},
closeBundle() {
if (verbose && !isServer) {
const {
build: { outDir, assetsDir },
} = config;
console.log(
chalk.cyan('\n✨ [vite-plugin-theme]') + ` - extract css code file is successfully:`,
);
try {
const { size } = fs.statSync(path.join(outDir, assetsDir, cssOutputName));
console.log(
chalk.dim(outDir + '/') +
chalk.magentaBright(`${assetsDir}/${cssOutputName}`) +
`\t\t${chalk.dim((size / 1024).toFixed(2) + 'kb')}` +
'\n',
);
} catch (error) {}
}
},
},
];
}
// Intercept the css code embedded in js
async function getClientStyleString(code: string) {
if (!code.includes(VITE_CLIENT_ENTRY)) {
return code;
}
code = code.replace(/\\n/g, '');
const cssPrefix = cssVariableString;
const cssPrefixLen = cssPrefix.length;
const cssPrefixIndex = code.indexOf(cssPrefix);
const len = cssPrefixIndex + cssPrefixLen;
const cssLastIndex = code.indexOf('\n', len + 1);
if (cssPrefixIndex !== -1) {
code = code.slice(len, cssLastIndex);
}
return code;
}

View File

@@ -0,0 +1,113 @@
import path from 'path';
import { ResolvedConfig, normalizePath, Plugin } from 'vite';
import { ViteThemeOptions } from '.';
import { CLIENT_PUBLIC_PATH, CLIENT_PUBLIC_ABSOLUTE_PATH } from './constants';
import { debug as Debug } from 'debug';
const debug = Debug('vite:inject-vite-plugin-theme-client');
type PluginType = 'colorPlugin' | 'antdDarkPlugin';
export function injectClientPlugin(
type: PluginType,
{
colorPluginOptions,
colorPluginCssOutputName,
antdDarkCssOutputName,
antdDarkExtractCss,
antdDarkLoadLink,
}: {
colorPluginOptions?: ViteThemeOptions;
antdDarkCssOutputName?: string;
colorPluginCssOutputName?: string;
antdDarkExtractCss?: boolean;
antdDarkLoadLink?: boolean;
}
): Plugin {
let config: ResolvedConfig;
let isServer: boolean;
let needSourcemap = false;
return {
name: 'vite:inject-vite-plugin-theme-client',
enforce: 'pre',
configResolved(resolvedConfig) {
config = resolvedConfig;
isServer = resolvedConfig.command === 'serve';
needSourcemap = !!resolvedConfig.build.sourcemap;
},
transformIndexHtml: {
enforce: 'pre',
async transform(html) {
if (html.includes(CLIENT_PUBLIC_PATH)) {
return html;
}
return {
html,
tags: [
{
tag: 'script',
attrs: {
type: 'module',
src: path.posix.join(CLIENT_PUBLIC_PATH),
},
injectTo: 'head-prepend',
},
],
};
},
},
async transform(code, id) {
const nid = normalizePath(id);
const path = normalizePath('vite-plugin-theme/es/client.js');
const getMap = () => (needSourcemap ? this.getCombinedSourcemap() : null);
if (
nid === CLIENT_PUBLIC_ABSOLUTE_PATH ||
nid.includes(path) ||
// support .vite cache
nid.includes(path.replace(/\//gi, '_'))
) {
debug('transform client file:', id, code);
const {
build: { assetsDir },
} = config;
const getOutputFile = (name?: string) => {
return JSON.stringify(`${config.base}${assetsDir}/${name}`);
};
if (type === 'colorPlugin') {
code = code
.replace('__COLOR_PLUGIN_OUTPUT_FILE_NAME__', getOutputFile(colorPluginCssOutputName))
.replace('__COLOR_PLUGIN_OPTIONS__', JSON.stringify(colorPluginOptions));
}
if (type === 'antdDarkPlugin') {
code = code.replace(
'__ANTD_DARK_PLUGIN_OUTPUT_FILE_NAME__',
getOutputFile(antdDarkCssOutputName)
);
if (typeof antdDarkExtractCss === 'boolean') {
code = code.replace(
'__ANTD_DARK_PLUGIN_EXTRACT_CSS__',
JSON.stringify(antdDarkExtractCss)
);
}
if (typeof antdDarkLoadLink === 'boolean') {
code = code.replace(
'__ANTD_DARK_PLUGIN_LOAD_LINK__',
JSON.stringify(antdDarkExtractCss)
);
}
}
return {
code: code.replace('__PROD__', JSON.stringify(!isServer)),
map: getMap(),
};
}
},
};
}

View File

@@ -0,0 +1,174 @@
import path from 'path';
import fs from 'fs';
import { Alias, normalizePath, ResolvedConfig } from 'vite';
import less from 'less';
export type ResolveFn = (
id: string,
importer?: string,
aliasOnly?: boolean
) => Promise<string | undefined>;
type CssUrlReplacer = (url: string, importer?: string) => string | Promise<string>;
export const externalRE = /^(https?:)?\/\//;
export const isExternalUrl = (url: string) => externalRE.test(url);
export const dataUrlRE = /^\s*data:/i;
export const isDataUrl = (url: string) => dataUrlRE.test(url);
const cssUrlRE = /url\(\s*('[^']+'|"[^"]+"|[^'")]+)\s*\)/;
let ViteLessManager: any;
function createViteLessPlugin(
rootFile: string,
alias: Alias[],
resolvers: { less: ResolveFn }
): Less.Plugin {
if (!ViteLessManager) {
ViteLessManager = class ViteManager extends less.FileManager {
resolvers;
rootFile;
alias;
constructor(rootFile: string, resolvers: ResolveFn, alias: Alias[]) {
super();
this.rootFile = rootFile;
this.resolvers = resolvers;
this.alias = alias;
}
supports() {
return true;
}
supportsSync() {
return false;
}
async loadFile(
filename: string,
dir: string,
opts: any,
env: any
): Promise<Less.FileLoadResult> {
const resolved = await this.resolvers.less(filename, path.join(dir, '*'));
if (resolved) {
const result = await rebaseUrls(resolved, this.rootFile, this.alias);
let contents;
if (result && 'contents' in result) {
contents = result.contents;
} else {
contents = fs.readFileSync(resolved, 'utf-8');
}
return {
filename: path.resolve(resolved),
contents,
};
} else {
return super.loadFile(filename, dir, opts, env);
}
}
};
}
return {
install(_, pluginManager) {
pluginManager.addFileManager(new ViteLessManager(rootFile, resolvers, alias));
},
minVersion: [3, 0, 0],
};
}
export function lessPlugin(id, config: ResolvedConfig) {
const resolvers = createCSSResolvers(config);
return createViteLessPlugin(id, config.resolve.alias, resolvers);
}
function createCSSResolvers(config: ResolvedConfig): { less: ResolveFn } {
let lessResolve: ResolveFn | undefined;
return {
get less() {
return (
lessResolve ||
(lessResolve = config.createResolver({
extensions: ['.less', '.css'],
mainFields: ['less', 'style'],
tryIndex: false,
preferRelative: true,
}))
);
},
};
}
/**
* relative url() inside \@imported sass and less files must be rebased to use
* root file as base.
*/
async function rebaseUrls(file: string, rootFile: string, alias: Alias[]): Promise<any> {
file = path.resolve(file); // ensure os-specific flashes
// in the same dir, no need to rebase
const fileDir = path.dirname(file);
const rootDir = path.dirname(rootFile);
if (fileDir === rootDir) {
return { file };
}
// no url()
const content = fs.readFileSync(file, 'utf-8');
if (!cssUrlRE.test(content)) {
return { file };
}
const rebased = await rewriteCssUrls(content, (url) => {
if (url.startsWith('/')) return url;
// match alias, no need to rewrite
for (const { find } of alias) {
const matches = typeof find === 'string' ? url.startsWith(find) : find.test(url);
if (matches) {
return url;
}
}
const absolute = path.resolve(fileDir, url);
const relative = path.relative(rootDir, absolute);
return normalizePath(relative);
});
return {
file,
contents: rebased,
};
}
function rewriteCssUrls(css: string, replacer: CssUrlReplacer): Promise<string> {
return asyncReplace(css, cssUrlRE, async (match) => {
const [matched, rawUrl] = match;
return await doUrlReplace(rawUrl, matched, replacer);
});
}
export async function asyncReplace(
input: string,
re: RegExp,
replacer: (match: RegExpExecArray) => string | Promise<string>
) {
let match: RegExpExecArray | null;
let remaining = input;
let rewritten = '';
while ((match = re.exec(remaining))) {
rewritten += remaining.slice(0, match.index);
rewritten += await replacer(match);
remaining = remaining.slice(match.index + match[0].length);
}
rewritten += remaining;
return rewritten;
}
async function doUrlReplace(rawUrl: string, matched: string, replacer: CssUrlReplacer) {
let wrap = '';
const first = rawUrl[0];
if (first === `"` || first === `'`) {
wrap = first;
rawUrl = rawUrl.slice(1, -1);
}
if (isExternalUrl(rawUrl) || isDataUrl(rawUrl) || rawUrl.startsWith('#')) {
return matched;
}
return `url(${wrap}${await replacer(rawUrl)}${wrap})`;
}

128
build/plugin/theme/utils.ts Normal file
View File

@@ -0,0 +1,128 @@
import { ResolvedConfig } from 'vite';
import { createHash } from 'crypto';
import { ResolveSelector } from '.';
import { commentRE, cssBlockRE, ruleRE, cssValueRE, safeEmptyRE, importSafeRE } from './constants';
import CleanCSS from 'clean-css';
export function getVariablesReg(colors: string[]) {
return new RegExp(
colors
.map(
(i) =>
`(${i
.replace(/\s/g, ' ?')
.replace(/\(/g, `\\(`)
.replace(/\)/g, `\\)`)
.replace(/0?\./g, `0?\\.`)})`
)
.join('|')
);
}
export function combineRegs(decorator = '', joinString = '', ...args: any[]) {
const regString = args
.map((item) => {
const str = item.toString();
return `(${str.slice(1, str.length - 1)})`;
})
.join(joinString);
return new RegExp(regString, decorator);
}
export function formatCss(s: string) {
s = s.replace(/\s*([{}:;,])\s*/g, '$1');
s = s.replace(/;\s*;/g, ';');
s = s.replace(/,[\s.#\d]*{/g, '{');
s = s.replace(/([^\s])\{([^\s])/g, '$1 {\n\t$2');
s = s.replace(/([^\s])\}([^\n]*)/g, '$1\n}\n$2');
s = s.replace(/([^\s]);([^\s}])/g, '$1;\n\t$2');
return s;
}
export function createFileHash() {
return createHash('sha256').digest('hex').substr(0, 8);
}
/**
* Compress the generated code
*/
export async function minifyCSS(css: string, config: ResolvedConfig) {
const res = new CleanCSS({
rebase: false,
...config.build.cleanCssOptions,
}).minify(css);
if (res.errors && res.errors.length) {
console.error(`error when minifying css:\n${res.errors}`);
throw res.errors[0];
}
if (res.warnings && res.warnings.length) {
config.logger.warn(`warnings when minifying css:\n${res.warnings}`);
}
return res.styles;
}
// Used to extract relevant color configuration in css
export function extractVariable(
code: string,
colorVariables: string[],
resolveSelector?: ResolveSelector,
colorRE?: RegExp
) {
colorVariables = Array.from(new Set(colorVariables));
code = code.replace(commentRE, '');
const cssBlocks = code.match(cssBlockRE);
if (!cssBlocks || cssBlocks.length === 0) {
return '';
}
let allExtractedVariable = '';
const variableReg = getVariablesReg(colorVariables);
for (let index = 0; index < cssBlocks.length; index++) {
const cssBlock = cssBlocks[index];
if (!variableReg.test(cssBlock) || !cssBlock) {
continue;
}
const cssSelector = cssBlock.match(/[^{]*/)?.[0] ?? '';
if (!cssSelector) {
continue;
}
if (/^@.*keyframes/.test(cssSelector)) {
allExtractedVariable += `${cssSelector}{${extractVariable(
cssBlock.replace(/[^{]*\{/, '').replace(/}$/, ''),
colorVariables,
resolveSelector,
colorRE
)}}`;
continue;
}
const colorReg = combineRegs(
'g',
'',
ruleRE,
cssValueRE,
safeEmptyRE,
variableReg,
importSafeRE
);
const colorReplaceTemplates = cssBlock.match(colorRE || colorReg);
if (!colorReplaceTemplates) {
continue;
}
allExtractedVariable += `${
resolveSelector ? resolveSelector(cssSelector) : cssSelector
} {${colorReplaceTemplates.join(';')}}`;
}
return allExtractedVariable;
}