unplugin-vue-cssvars
Version:
🌀 A vue plugin that allows you to use vue's CSSVars feature in css files
1,521 lines (1,490 loc) • 48.2 kB
JavaScript
// ../packages/core/index.ts
import { createUnplugin } from "unplugin";
import { NAME as NAME2 } from "../utils/index.js";
import { log as log2, normalizePath as normalizePath5, setGlobalPrefix } from "baiwusanyu-utils";
import { createFilter } from "@rollup/pluginutils";
import MagicString3 from "magic-string";
// ../packages/core/runtime/pre-process-css.ts
import { parse as parse2, resolve as resolve2 } from "path";
import fg from "fast-glob";
import fs from "fs-extra";
import {
FG_IGNORE_LIST,
SUPPORT_FILE as SUPPORT_FILE2,
completeSuffix as completeSuffix2
} from "../utils/index.js";
import { normalizePath as normalizePath2 } from "baiwusanyu-utils";
// ../packages/core/parser/parser-variable.ts
import { parse as babelParse } from "@babel/parser";
// ../node_modules/.pnpm/estree-walker-ts@1.0.1/node_modules/estree-walker-ts/index.js
var u = class {
should_skip;
should_remove;
replacement;
context;
constructor() {
this.should_skip = false, this.should_remove = false, this.replacement = null, this.context = { skip: () => this.should_skip = true, remove: () => this.should_remove = true, replace: (e) => this.replacement = e };
}
replace(e, t, s, i) {
e && t && (s != null ? e[t][s] = i : e[t] = i);
}
remove(e, t, s) {
e && t && (s != null ? e[t].splice(s, 1) : delete e[t]);
}
};
function c(r) {
return r !== null && typeof r == "object" && "type" in r && typeof r.type == "string";
}
var d = class extends u {
enter;
leave;
constructor(e, t) {
super(), this.should_skip = false, this.should_remove = false, this.replacement = null, this.context = { skip: () => this.should_skip = true, remove: () => this.should_remove = true, replace: (s) => this.replacement = s }, this.enter = e, this.leave = t;
}
visit(e, t, s, i) {
if (e) {
if (this.enter) {
let o = this.should_skip, l = this.should_remove, n = this.replacement;
this.should_skip = false, this.should_remove = false, this.replacement = null, this.enter.call(this.context, e, t, s, i), this.replacement && (e = this.replacement, this.replace(t, s, i, e)), this.should_remove && this.remove(t, s, i);
let h = this.should_skip, f = this.should_remove;
if (this.should_skip = o, this.should_remove = l, this.replacement = n, h)
return e;
if (f)
return null;
}
let a;
for (a in e) {
let o = e[a];
if (o && typeof o == "object")
if (Array.isArray(o)) {
let l = o;
for (let n = 0; n < l.length; n += 1) {
let h = l[n];
c(h) && (this.visit(h, e, a, n) || n--);
}
} else
c(o) && this.visit(o, e, a, null);
}
if (this.leave) {
let o = this.replacement, l = this.should_remove;
this.replacement = null, this.should_remove = false, this.leave.call(this.context, e, t, s, i), this.replacement && (e = this.replacement, this.replace(t, s, i, e)), this.should_remove && this.remove(t, s, i);
let n = this.should_remove;
if (this.replacement = o, this.should_remove = l, n)
return null;
}
}
return e;
}
};
function A(r, { enter: e, leave: t }) {
return new d(e, t).visit(r, null);
}
// ../packages/core/parser/parser-variable.ts
import { extend, isEmptyObj } from "baiwusanyu-utils";
var getVariable = (descriptor) => {
let variableName = {};
variableName = getVariableNameBySetup(setScriptContent(descriptor, "setup"));
variableName = getVariableNameByScript(setScriptContent(descriptor, "script"), variableName);
return variableName;
};
function setScriptContent(descriptor, type) {
let content = "";
if (descriptor.scriptSetup && type === "setup")
content = descriptor.scriptSetup.content;
if (descriptor.script && type === "script")
content = descriptor.script.content;
return content;
}
function getVariableNameBySetup(content, contextAst) {
const variableNameBySetup = {};
if (!content && !contextAst)
return variableNameBySetup;
const ast = contextAst || babelParse(content, {
sourceType: "module",
plugins: ["typescript", "jsx"]
});
A(ast, {
enter(node, parent) {
if (parent && parent.type === "Program" && node.type === "VariableDeclaration") {
const declarations = node.declarations;
declarations.forEach((declare) => {
const identifier = declare.id;
variableNameBySetup[identifier.name] = declare.init;
});
}
}
});
return variableNameBySetup;
}
function getVariableNameByScript(content, variableName) {
if (!content)
return variableName;
let variableNameInScript = {};
let isEmptyVariableNames = false;
let setupIndex = 0;
let dataIndex = 0;
let hasSetup = false;
let hasData = false;
let setupReturnNode = {};
let dataReturnNode = {};
let index = 2;
if (isEmptyObj(variableName))
isEmptyVariableNames = true;
const ast = babelParse(content, {
sourceType: "module",
plugins: ["typescript", "jsx"]
});
A(ast, {
enter(node, parent) {
if (parent && parent.type === "ObjectMethod" && node.type === "Identifier" && node.name === "setup") {
hasSetup = true;
setupIndex = index;
index--;
}
if (parent && parent.type === "ObjectMethod" && node.type === "Identifier" && node.name === "data") {
hasData = true;
dataIndex = index;
index--;
}
if (parent && parent.type === "ReturnStatement" && node.type === "ObjectExpression" && hasSetup) {
hasSetup = false;
setupReturnNode = node;
}
if (parent && parent.type === "ReturnStatement" && node.type === "ObjectExpression" && hasData) {
hasData = false;
dataReturnNode = node;
}
}
});
if (setupIndex === 0 && dataIndex === 0) {
if (isEmptyVariableNames) {
return variableName;
} else {
variableNameInScript = getVariableNameBySetup("", ast);
}
variableNameInScript = extend(variableNameInScript, variableName);
setupReturnNode = {};
dataReturnNode = {};
return variableNameInScript;
}
const setupReturnRes = getObjectExpressionReturnNode(setupReturnNode);
const dataReturnRes = getObjectExpressionReturnNode(dataReturnNode);
variableNameInScript = setupIndex > dataIndex ? extend(setupReturnRes, dataReturnRes) : extend(dataReturnRes, setupReturnRes);
if (!isEmptyVariableNames)
variableNameInScript = extend(variableNameInScript, variableName);
return variableNameInScript;
}
function getObjectExpressionReturnNode(node) {
const res = {};
if (!node.properties)
return res;
node.properties.forEach((value) => {
const key = value.key;
res[key.name] = value.value;
});
return res;
}
function matchVariable(importCSSModule, variableName) {
const res = [];
importCSSModule.forEach((val) => {
const varNode = variableName[val];
const resObj = {
value: val,
has: false,
isRef: false
};
if (varNode) {
resObj.has = true;
if (varNode.type === "CallExpression" && varNode.callee.type === "Identifier" && varNode.callee.name === "ref")
resObj.isRef = true;
}
res.push(resObj);
});
return res;
}
// ../packages/core/parser/parser-import.ts
var innerAtRule = "media,extend,at-root,debug,warn,forward,mixin,include,function,error,keyframes,font-face,page,supports,namespace,return,if,else,for,while,each,content,tailwind,apply,layer,screen,responsive,variants,config,applyRule";
function parseImports(content, helper) {
const imports = [];
let currentImport;
let state = 0 /* Initial */;
let i = 0;
let AtPath = "";
const source = content;
while (i < source.length) {
const char = source[i];
if (/[\r\t\f\v\\]/g.test(char)) {
i++;
continue;
}
switch (state) {
case 0 /* Initial */:
if (char === "@")
state = 3 /* AtStart */;
if (char === "/" && source[i + 1] === "/")
state = 1 /* InlineComment */;
if (char === "/" && source[i + 1] === "*")
state = 2 /* Comment */;
break;
case 1 /* InlineComment */:
if (char === "\n")
state = 0 /* Initial */;
break;
case 2 /* Comment */:
if (char === "*" && source[i + 1] === "/")
state = 0 /* Initial */;
break;
case 3 /* AtStart */:
if (/[A-Za-z]$/.test(char))
AtPath = AtPath + char;
else
state = 4 /* AtEnd */;
if (i === source.length - 1) {
if (char === '"' || char === "'")
throw new Error("syntax error: unmatched quotes");
else
walkContentEnd(i);
}
if (!/[A-Za-z]$/.test(char) && char !== "\n" && char !== " " && char !== "-")
throw new Error("syntax error");
break;
case 4 /* AtEnd */:
if (char !== "\n" && char !== " " && char !== "-") {
if (char === "/")
throw new Error("syntax error");
if (AtPath === "import") {
AtPath = "";
state = 5 /* AtImport */;
currentImport = { type: "import", path: "" };
i--;
} else if (AtPath === "use") {
AtPath = "";
state = 6 /* AtUse */;
currentImport = { type: "use", path: "" };
i--;
} else if (AtPath === "require") {
AtPath = "";
state = 7 /* AtRequire */;
currentImport = { type: "require", path: "" };
i--;
} else {
if (!innerAtRule.includes(AtPath))
throw new Error("syntax error: unknown At Rule");
AtPath = "";
state = 0 /* Initial */;
}
}
break;
case 5 /* AtImport */:
case 6 /* AtUse */:
case 7 /* AtRequire */:
if (char === "@" && !/[A-Za-z]$/.test(source[i - 1])) {
i--;
state = 0 /* Initial */;
break;
}
if (char === "/" && (source[i + 1] === "/" || source[i + 1] === "*")) {
i--;
state = 0 /* Initial */;
break;
}
if (char === "'" || char === '"') {
currentImport.start = i;
state = 8 /* QuotesStart */;
break;
}
if (char !== "\n" && char !== " ") {
currentImport.start = i;
currentImport.path += char;
state = 10 /* StringLiteral */;
break;
}
break;
case 8 /* QuotesStart */:
if (char === "'" || char === '"') {
currentImport.end = i;
state = 9 /* QuotesEnd */;
if (i === source.length - 1)
walkContentEnd(i);
break;
}
if (i === source.length - 1)
throw new Error("syntax error: unmatched quotes");
currentImport.path += char;
break;
case 9 /* QuotesEnd */:
if (i === source.length - 1 || char === ";" || char === "\n") {
walkContentEnd(i);
} else {
i--;
state = 10 /* StringLiteral */;
}
break;
case 10 /* StringLiteral */:
if (char === ";" || char === "\n") {
walkContentEnd(i);
break;
}
if (char !== "@" && (char === " " || char === "," || char === '"' || char === "'")) {
const curType = currentImport?.type;
walkContentEnd(i);
if (curType === "import") {
state = 5 /* AtImport */;
currentImport = { type: "import", path: "" };
} else if (curType === "use") {
state = 6 /* AtUse */;
currentImport = { type: "use", path: "" };
} else if (curType === "require") {
state = 7 /* AtRequire */;
currentImport = { type: "require", path: "" };
}
if (char === "'" || char === '"') {
currentImport.start = i;
state = 8 /* QuotesStart */;
if (i === source.length - 1 && (char === '"' || char === "'"))
throw new Error("syntax error: unmatched quotes");
break;
}
break;
}
currentImport.path += char;
if (i === source.length - 1)
walkContentEnd(i);
break;
}
i++;
}
function walkContentEnd(index) {
pushCurrentImport(index);
state = 0 /* Initial */;
}
function pushCurrentImport(index) {
if (currentImport && currentImport.start !== void 0) {
currentImport.end = index;
if (helper) {
helper.forEach((fn) => {
currentImport = fn(currentImport);
});
}
imports.push(currentImport);
currentImport = void 0;
}
}
function getCurState() {
return state;
}
function getCurImport() {
return currentImport;
}
return {
imports,
getCurState,
getCurImport
};
}
// ../packages/core/parser/parser-vbind-m.ts
function lexBinding(content, start) {
let state = 0 /* inParens */;
let parenDepth = 0;
for (let i = start; i < content.length; i++) {
const char = content.charAt(i);
switch (state) {
case 0 /* inParens */:
if (char === "'") {
state = 1 /* inSingleQuoteString */;
} else if (char === '"') {
state = 2 /* inDoubleQuoteString */;
} else if (char === "(") {
parenDepth++;
} else if (char === ")") {
if (parenDepth > 0)
parenDepth--;
else
return i;
}
break;
case 1 /* inSingleQuoteString */:
if (char === "'")
state = 0 /* inParens */;
break;
case 2 /* inDoubleQuoteString */:
if (char === '"')
state = 0 /* inParens */;
break;
}
}
return null;
}
function normalizeExpression(exp) {
exp = exp.trim();
if (exp[0] === "'" && exp[exp.length - 1] === "'" || exp[0] === '"' && exp[exp.length - 1] === '"')
return exp.slice(1, -1);
return exp;
}
var vBindRE = /v-bind-m\s*\(/g;
function parseCssVars(styles, hook) {
const vars = [];
styles.forEach((style) => {
let match = null;
const content = style.replace(/\/\*([\s\S]*?)\*\//g, "");
const offset = style.length - content.length;
while (match = vBindRE.exec(content)) {
const start = match.index + match[0].length;
const end = lexBinding(content, start);
if (end !== null) {
const variable = normalizeExpression(content.slice(start, end));
hook && hook.getIndex(start, end, offset, variable);
if (!vars.includes(variable))
vars.push(variable);
}
}
});
return vars;
}
// ../packages/core/parser/parser-compiled-sfc.ts
import { parse as babelParse2 } from "@babel/parser";
var isSetupEnter = false;
var setupBodyNode = {};
function parseSetupBody(node) {
if (node.type === "Identifier" && node.name === "setup") {
isSetupEnter = true;
return;
}
if (isSetupEnter && node.type === "BlockStatement") {
isSetupEnter = false;
setupBodyNode = node;
}
}
var hasCSSVars = false;
function parseHasCSSVars(node, parent) {
if (node.type === "Identifier" && node.name === "useCssVars" && parent && parent.type === "ImportSpecifier")
hasCSSVars = true;
}
var useCSSVarsNode = {};
var isUseCSSVarsEnter = false;
function parseUseCSSVars(node, parent) {
if (node.type === "Identifier" && node.name === "_useCssVars" && parent && parent.type === "CallExpression")
isUseCSSVarsEnter = true;
if (isUseCSSVarsEnter && node.type === "ObjectExpression") {
isUseCSSVarsEnter = false;
useCSSVarsNode = node;
}
}
function parserCompiledSfc(code) {
resetVar();
const ast = babelParse2(code, {
sourceType: "module",
plugins: ["typescript", "jsx"]
});
A(ast, {
enter(node, parent) {
parseSetupBody(node);
parseHasCSSVars(node, parent);
parseUseCSSVars(node, parent);
}
});
return {
setupBodyNode,
hasCSSVars,
useCSSVarsNode
};
}
function resetVar() {
isSetupEnter = false;
setupBodyNode = {};
isUseCSSVarsEnter = false;
useCSSVarsNode = {};
hasCSSVars = false;
}
// ../packages/core/parser/parser-script-bindings.ts
import { ts } from "@ast-grep/napi";
// ../packages/core/parser/utils.ts
var CSSVarsBindingTypes = {
/**
* returned from data()
*/
DATA: "data",
/**
* declared as a prop ✅
*/
PROPS: "props",
/**
* a local alias of a `<script setup>` destructured prop.
* the original is stored in __propsAliases of the bindingMetadata object.
*/
PROPS_ALIASED: "props-aliased",
/**
* a let binding (may or may not be a ref) ✅
*/
SETUP_LET: "setup-let",
/**
* a const binding that can never be a ref.
* these bindings don't need `unref()` calls when processed in inlined
* template expressions. ✅
*/
SETUP_CONST: "setup-const",
/**
* a const binding that does not need `unref()`, but may be mutated. ✅
*/
SETUP_REACTIVE_CONST: "setup-reactive-const",
/**
* a const binding that may be a ref. ✅
*/
SETUP_MAYBE_REF: "setup-maybe-ref",
/**
* bindings that are guaranteed to be refs ✅
*/
SETUP_REF: "setup-ref",
/**
* declared by other options, e.g. computed, inject
*/
OPTIONS: "options",
/**
* a literal constant, e.g. 'foo', 1, true ✅
*/
LITERAL_CONST: "literal-const"
};
// ../packages/core/parser/ast-grep-rules.ts
var astGrepRules = {
IDENT_NAME: {
has: {
kind: "identifier",
field: "name"
}
},
FN_CALL: {
has: {
kind: "variable_declarator",
has: {
kind: "call_expression"
}
}
},
ARROW_FN: {
has: {
kind: "variable_declarator",
has: {
kind: "arrow_function"
}
}
},
NOR_FN_VAR: {
has: {
kind: "variable_declarator",
has: {
kind: "function"
}
}
},
OBJ_VAR: {
has: {
kind: "variable_declarator",
has: {
kind: "object",
field: "value"
}
}
},
ARR_VAR: {
has: {
kind: "variable_declarator",
has: {
kind: "array",
field: "value"
}
}
},
NOR_FN: {
has: {
kind: "function_declaration",
has: {
kind: "identifier",
field: "name"
}
}
},
PROPS_DEFAULT_CALL: {
has: {
kind: "call_expression",
has: {
kind: "identifier",
field: "function"
}
}
},
PROPS_DEFAULT_ARG: {
kind: "property_identifier",
inside: {
kind: "pair",
inside: {
kind: "object",
inside: {
kind: "arguments"
}
}
}
},
PROPS_DEFAULT_VAL: {
kind: "object",
inside: {
kind: "arguments"
}
},
CONST_VAR: {
any: [
{
pattern: "const $VAR"
}
]
},
LET_VAR: {
any: [
{
pattern: "let $VAR"
}
]
},
CONST_REF_VAR: {
any: [
{
pattern: "const $VAR = ref($VAL)"
}
]
},
CONST_REACTIVE_VAR: {
any: [
{
pattern: "const $VAR = reactive($VAL)"
}
]
},
CONST_PROPS_VAR: {
any: [
{
pattern: "const $VAR = defineProps($VAL)"
}
]
},
CONST_NOR_FN: {
has: {
kind: "function_declaration"
}
},
OBJ_PATTERN: {
has: {
kind: "object_pattern"
}
},
NEW_EXP: {
has: {
kind: "new_expression",
field: "value"
}
},
OBJ_PATTERN_VAL: {
any: [
{
kind: "identifier"
},
{
kind: "shorthand_property_identifier_pattern"
}
]
}
};
var getRules = (name) => {
return {
rule: {
matches: name
},
utils: astGrepRules
};
};
// ../packages/core/parser/parser-script-bindings.ts
function analyzeScriptBindings(descriptor) {
const scriptSetupContent = descriptor.scriptSetup?.content || "";
const scriptContent = descriptor.script?.content || "";
if (!scriptSetupContent && !scriptContent)
return {};
const bindings = {};
const sgNodeScriptSetup = ts.parse(scriptSetupContent).root();
walkSgNodeToGetBindings(sgNodeScriptSetup, bindings);
const sgNodeScript = ts.parse(scriptContent).root();
walkSgNodeToGetBindings(sgNodeScript, bindings);
return bindings;
}
function walkSgNodeToGetBindings(node, bindings) {
node.findAll(getRules("LET_VAR")).forEach((n) => {
const key = n.getMatch("VAR")?.text() || "";
bindings[key] = CSSVarsBindingTypes.SETUP_LET;
});
let constVARs = node.findAll(getRules("CONST_VAR"));
constVARs = constVARs.concat(node.findAll(getRules("CONST_NOR_FN")));
constVARs.forEach((n) => {
const key = n.getMatch("VAR")?.text() || n.find(getRules("IDENT_NAME"))?.text() || "";
if (!key)
return;
bindings[key] = CSSVarsBindingTypes.LITERAL_CONST;
if (n.find(getRules("CONST_REF_VAR"))) {
bindings[key] = CSSVarsBindingTypes.SETUP_REF;
} else if (n.find(getRules("CONST_REACTIVE_VAR"))) {
bindings[key] = CSSVarsBindingTypes.SETUP_REACTIVE_CONST;
} else if (n.find(getRules("FN_CALL")) || n.find(getRules("NEW_EXP")) || n.find(getRules("OBJ_PATTERN"))?.text().startsWith("{") && n.find(getRules("OBJ_PATTERN"))?.text().endsWith("}")) {
bindings[key] = CSSVarsBindingTypes.SETUP_MAYBE_REF;
const deconstructVal = n.find(getRules("OBJ_PATTERN"));
const deconstructKeyNode = deconstructVal?.findAll(getRules("OBJ_PATTERN_VAL"));
deconstructKeyNode && deconstructKeyNode.forEach((nI) => {
Reflect.deleteProperty(bindings, key);
bindings[nI.text()] = CSSVarsBindingTypes.SETUP_MAYBE_REF;
});
} else if (n.find(getRules("ARROW_FN")) || n.find(getRules("NOR_FN")) || n.find(getRules("OBJ_VAR")) || n.find(getRules("ARR_VAR")) || n.kind() === "function_declaration" || n.find(getRules("NOR_FN_VAR"))) {
if (n.kind() === "function_declaration")
bindings[n.find(getRules("IDENT_NAME"))?.text() || "unk"] = CSSVarsBindingTypes.SETUP_CONST;
else
bindings[key] = CSSVarsBindingTypes.SETUP_CONST;
}
if (n.find(getRules("PROPS_DEFAULT_CALL")) && n.find(getRules("PROPS_DEFAULT_CALL"))?.text().includes("withDefaults")) {
const argObjNode = n.findAll(getRules("PROPS_DEFAULT_ARG"));
argObjNode.forEach((nI) => {
!bindings[nI.text()] && (bindings[nI.text()] = CSSVarsBindingTypes.PROPS);
});
bindings[key] = CSSVarsBindingTypes.SETUP_CONST;
}
if (n.find(getRules("CONST_PROPS_VAR"))) {
const argObjNode = n.findAll(getRules("PROPS_DEFAULT_ARG"));
argObjNode.forEach((nI) => {
!bindings[nI.text()] && (bindings[nI.text()] = CSSVarsBindingTypes.PROPS);
});
bindings[key] = CSSVarsBindingTypes.SETUP_REACTIVE_CONST;
}
});
}
// ../packages/core/transform/transform-quotes.ts
function transformQuotes(importer2) {
if (!importer2.path.startsWith('"') && !importer2.path.endsWith('"')) {
if (importer2.path.startsWith("'") && importer2.path.endsWith("'"))
importer2.path = `"${importer2.path.slice(1, -1)}"`;
else
importer2.path = `"${importer2.path}"`;
}
return importer2;
}
// ../packages/core/runtime/process-css.ts
import { parse, resolve } from "path";
import { SUPPORT_FILE, completeSuffix, setTArray } from "../utils/index.js";
import { normalizePath } from "baiwusanyu-utils";
var getCSSFileRecursion = (lang = SUPPORT_FILE.CSS, key, cssFiles, cb, sfcPath, matchedMark = /* @__PURE__ */ new Set()) => {
key = completeSuffix(key, lang);
if (!cssFiles.get(key))
key = completeSuffix(key, SUPPORT_FILE.CSS, true);
if (matchedMark.has(key))
return;
const cssFile = cssFiles.get(key);
if (cssFile) {
if (!cssFile.sfcPath)
cssFile.sfcPath = /* @__PURE__ */ new Set();
cssFile.sfcPath?.add(sfcPath || "");
matchedMark.add(key);
cb(cssFile);
if (cssFile.importer.size > 0) {
cssFile.importer.forEach((value) => {
getCSSFileRecursion(lang, value, cssFiles, cb, sfcPath, matchedMark);
});
}
} else {
throw new Error("path");
}
};
var getVBindVariableListByPath = (descriptor, id, cssFiles, server, alias) => {
const vbindVariable = /* @__PURE__ */ new Set();
const injectCSSContent = /* @__PURE__ */ new Set();
for (let i = 0; i < descriptor.styles.length; i++) {
const content = descriptor.styles[i].content;
const lang = descriptor.styles[i].lang === SUPPORT_FILE.STYLUS ? SUPPORT_FILE.STYL : descriptor.styles[i].lang;
const idDirParse = parse(id);
const parseImporterRes = parseImports(content);
parseImporterRes.imports.forEach((res) => {
const importerPath = handleAlias(res.path, alias, idDirParse.dir);
try {
getCSSFileRecursion(lang, importerPath, cssFiles, (res2) => {
if (res2.vBindCode) {
!server && injectCSSContent.add({ content: res2.content, lang: res2.lang, styleTagIndex: i });
res2.vBindCode.forEach((vb) => {
vbindVariable.add(vb);
});
}
}, id);
} catch (e) {
if (e.message === "path") {
const doc = "https://github.com/baiwusanyu-c/unplugin-vue-cssvars/pull/29";
throw new Error(`Unable to resolve file under path '${res.path}', see: ${doc}`);
} else {
throw new Error(e.message);
}
}
});
}
return {
vbindVariableListByPath: setTArray(vbindVariable),
injectCSSContent
};
};
function handleAlias(path, alias, idDirPath) {
let importerPath = "";
if (!alias && !idDirPath)
return path;
if (alias) {
for (const aliasKey in alias) {
if (alias[aliasKey] && path.startsWith(aliasKey)) {
importerPath = path.replace(aliasKey, alias[aliasKey]);
break;
}
}
if (importerPath)
return normalizePath(importerPath);
importerPath = idDirPath ? resolve(idDirPath, path) : path;
} else {
idDirPath && (importerPath = resolve(idDirPath, path));
}
return normalizePath(importerPath);
}
// ../packages/core/runtime/pre-process-css.ts
function preProcessCSS(options, alias, filesPath) {
const { rootDir, includeCompile } = options;
const files = filesPath || getAllCSSFilePath(includeCompile, rootDir);
return createCSSFileModuleMap(files, rootDir, alias);
}
function getAllCSSFilePath(includeCompile, rootDir) {
return fg.sync(includeCompile, {
ignore: FG_IGNORE_LIST,
cwd: rootDir
});
}
function createCSSFileModuleMap(files, rootDir, alias) {
const cssFiles = /* @__PURE__ */ new Map();
for (const file of files) {
let absoluteFilePath = resolve2(parse2(file).dir, parse2(file).base);
absoluteFilePath = normalizePath2(absoluteFilePath);
if (!cssFiles.get(absoluteFilePath)) {
cssFiles.set(absoluteFilePath, {
importer: /* @__PURE__ */ new Set(),
vBindCode: [],
content: "",
lang: "css"
});
}
}
for (const file of files) {
const fileDirParse = parse2(file);
const fileSuffix = fileDirParse.ext;
const code = fs.readFileSync(normalizePath2(resolve2(rootDir, file)), { encoding: "utf-8" });
const { imports } = parseImports(code, [transformQuotes]);
const absoluteFilePath = normalizePath2(resolve2(fileDirParse.dir, fileDirParse.base));
const cssF = cssFiles.get(absoluteFilePath);
imports.forEach((value) => {
const importerPath = handleAlias(value.path.replace(/^"|"$/g, ""), alias, fileDirParse.dir);
let importerVal = completeSuffix2(importerPath, SUPPORT_FILE2.CSS);
if (fileSuffix !== `.${SUPPORT_FILE2.CSS}`) {
const importerValBySuffix = completeSuffix2(
importerPath,
fileSuffix.split(".")[1],
true
);
if (cssFiles.get(importerValBySuffix))
importerVal = importerValBySuffix;
}
cssF.importer.add(importerVal);
});
cssF.vBindCode = parseCssVars([code]);
cssF.content = code;
cssF.lang = fileSuffix.replaceAll(".", "");
cssFiles.set(absoluteFilePath, cssF);
}
return cssFiles;
}
// ../packages/core/option/index.ts
import { resolve as resolve3 } from "path";
import {
DEFAULT_EXCLUDE_REG,
DEFAULT_INCLUDE_REG,
SUPPORT_FILE_LIST
} from "../utils/index.js";
import { extend as extend2 } from "baiwusanyu-utils";
var defaultOption = {
rootDir: resolve3(),
include: DEFAULT_INCLUDE_REG,
exclude: DEFAULT_EXCLUDE_REG,
includeCompile: SUPPORT_FILE_LIST
};
function initOption(option) {
option = extend2(defaultOption, option);
return option;
}
// ../packages/core/runtime/vite.ts
import { SUPPORT_FILE_REG } from "../utils/index.js";
import { normalizePath as normalizePath3 } from "baiwusanyu-utils";
// ../packages/core/inject/inject-css.ts
import hash from "hash-sum";
// ../packages/core/transform/transform-inject-css.ts
import MagicString from "magic-string";
function transformInjectCSS(code, importer2) {
const mgc = new MagicString(code);
importer2.forEach((imp) => {
mgc.overwrite(imp.start, imp.end, "");
});
return mgc.toString().replaceAll("@import ;", "").replaceAll("v-bind-m", "v-bind");
}
// ../packages/core/inject/inject-css.ts
function injectCSSOnServer(mgcStr, vbindVariableList, isHMR) {
const pCssVarsArr = [];
parseCssVars([mgcStr.toString()], {
getIndex(start, end, offset, variable) {
pCssVarsArr.push({ start, end, offset, variable });
}
});
pCssVarsArr.forEach((pca) => {
if (vbindVariableList) {
for (let i = 0; i < vbindVariableList.length; i++) {
const vbVar = vbindVariableList[i];
if (!vbVar.hash && isHMR)
vbVar.hash = hash(vbVar.value + vbVar.has);
if (vbVar.value === pca.variable) {
const offset = pca.offset;
const start = pca.start + offset;
const end = pca.end + offset;
vbVar.hash && (mgcStr = mgcStr.overwrite(start, end, `--${vbVar.hash}`));
break;
}
}
}
});
mgcStr = mgcStr.replace(/v-bind-m\s*\(/g, "var(");
return mgcStr;
}
function injectCssOnBuild(mgcStr, injectCSSContent, descriptor) {
if (!injectCSSContent && !descriptor)
return mgcStr;
const cssContent = [...injectCSSContent];
let resCode = "";
descriptor.styles && descriptor.styles.forEach((value, index) => {
let injectCssCode = "";
cssContent.forEach((value2) => {
if (value2.styleTagIndex === index)
injectCssCode = `${injectCssCode}
${transformInjectCSS(value2.content, parseImports(value2.content).imports)}`;
});
const lang = value.lang || "css";
const scoped = value.scoped ? "scoped" : "";
resCode = `${resCode}
<style lang="${lang}" ${scoped}> ${injectCssCode}
${transformInjectCSS(value.content, parseImports(value.content).imports)} </style>`;
});
resCode && (mgcStr = removeStyleTagsAndContent(mgcStr));
return mgcStr.prependRight(mgcStr.length(), resCode);
}
function removeStyleTagsAndContent(mgcStr) {
return mgcStr.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<style\b[^>]*>/gi, "").replace(/<\/style>/gi, "");
}
// ../packages/core/inject/inject-cssvars.ts
import hash2 from "hash-sum";
import { ts as ts2 } from "@ast-grep/napi";
import MagicString2 from "magic-string";
var importer = 'import { useCssVars as _useCssVars } from "vue"\n';
var importerUnref = 'import { unref as _unref } from "vue"\n';
function findIdentifierFromExp(cssContent) {
return ts2.parse(cssContent).root().findAll({
rule: {
matches: "cssComplexExpIdentifier"
},
utils: {
cssComplexExpIdentifier: {
any: [
{
kind: "identifier"
}
]
}
}
});
}
var injectCSSVars = (vbindVariableList, isScriptSetup, parserRes, mgcStr, bindings) => {
if (!vbindVariableList || vbindVariableList.length === 0)
return { vbindVariableList, mgcStr };
return injectCSSVarsOnServer(vbindVariableList, isScriptSetup, parserRes, mgcStr, bindings);
};
function injectCSSVarsOnServer(vbindVariableList, isScriptSetup, parserRes, mgcStr, bindings) {
let resMgcStr = mgcStr;
const hasUseCssVars = parserRes.hasCSSVars;
const cssvarsObjectCode = createCSSVarsObjCode(vbindVariableList, isScriptSetup, resMgcStr, bindings);
if (isScriptSetup) {
if (hasUseCssVars) {
resMgcStr = injectUseCssVarsSetup(resMgcStr, cssvarsObjectCode, true, parserRes);
} else {
const useCssVars = createUseCssVarsCode(cssvarsObjectCode, true);
resMgcStr = injectUseCssVarsSetup(resMgcStr, useCssVars, false, parserRes);
}
} else {
if (hasUseCssVars) {
resMgcStr = injectUseCssVarsOption(resMgcStr, cssvarsObjectCode, true, parserRes);
} else {
const useCssVars = createUseCssVarsCode(cssvarsObjectCode, false);
resMgcStr = injectUseCssVarsOption(resMgcStr, useCssVars, false, parserRes);
}
}
return { vbindVariableList, mgcStr: resMgcStr };
}
function injectUseCssVarsSetup(mgcStr, useCssVars, hasUseCssVars, parserRes) {
let resMgcStr = mgcStr;
if (!resMgcStr.toString().includes("_unref"))
resMgcStr = resMgcStr.prependLeft(0, importerUnref);
if (parserRes) {
if (!hasUseCssVars && parserRes.setupBodyNode && parserRes.setupBodyNode.start) {
const start = parserRes.setupBodyNode.start + 1;
resMgcStr = resMgcStr.prependLeft(start, useCssVars);
resMgcStr = resMgcStr.prependLeft(0, importer);
} else if (hasUseCssVars && parserRes.useCSSVarsNode && parserRes.useCSSVarsNode.start) {
const start = parserRes.useCSSVarsNode.start + 1;
resMgcStr = resMgcStr.prependLeft(start, useCssVars);
}
}
return resMgcStr;
}
function injectUseCssVarsOption(mgcStr, useCssVars, hasUseCssVars, parserRes) {
let resMgcStr = mgcStr;
if (!resMgcStr.toString().includes("_unref"))
resMgcStr = resMgcStr.prependLeft(0, importerUnref);
if (!hasUseCssVars) {
if (resMgcStr.toString().includes("const _sfc_main"))
resMgcStr = resMgcStr.replaceAll("const _sfc_main", "const __default__");
else if (resMgcStr.toString().includes("import _sfc_main"))
resMgcStr = resMgcStr.replaceAll("import _sfc_main", "import __default__");
else
resMgcStr = resMgcStr.replaceAll("export default {", "const __default__ = {");
if (resMgcStr.toString().includes("function _sfc_render")) {
resMgcStr = resMgcStr.replaceAll(
"function _sfc_render",
`${useCssVars}
const __setup__ = __default__.setup
__default__.setup = __setup__
? (props, ctx) => { __injectCSSVars__(); return __setup__(props, ctx) }
: __injectCSSVars__
const _sfc_main = __default__
function _sfc_render`
);
} else if (resMgcStr.toString().includes("const __default__")) {
resMgcStr = resMgcStr.prependRight(
resMgcStr.length(),
`${useCssVars}
const __setup__ = __default__.setup
__default__.setup = __setup__
? (props, ctx) => { __injectCSSVars__(); return __setup__(props, ctx) }
: __injectCSSVars__
const _sfc_main = __default__
export default _sfc_main`
);
}
resMgcStr = resMgcStr.prependLeft(0, importer);
} else if (hasUseCssVars && parserRes.useCSSVarsNode && parserRes.useCSSVarsNode.start) {
const start = parserRes.useCSSVarsNode.start + 1;
resMgcStr = resMgcStr.prependLeft(start, useCssVars);
}
return resMgcStr;
}
function createCSSVarsObjCode(vbindVariableList, isScriptSetup, mgcStr, bindings) {
let resCode = "";
vbindVariableList.forEach((vbVar) => {
const hashVal = vbVar.hash || hash2(vbVar.value + vbVar.has);
vbVar.hash = hashVal;
let varStr = "";
const ms = new MagicString2(vbVar.value);
const cssBindKeySgNodes = findIdentifierFromExp(vbVar.value);
cssBindKeySgNodes.forEach((node) => {
const range = node.range();
ms.overwrite(
range.start.index,
range.end.index,
// non-inline composition api 和 option api 一直帶 _ctx
!isScriptSetup ? `(_ctx.${node.text()})` : genCSSVarsValue(node, bindings)
);
});
varStr = ms.toString();
resCode = `
"${hashVal}": ${varStr},${resCode}`;
});
if (mgcStr && mgcStr.toString().includes(resCode))
return "";
return resCode;
}
function createUCVCSetupUnHas(cssvarsObjectCode) {
return `
_useCssVars((_ctx) => ({
${cssvarsObjectCode}
}));`;
}
function createUCVCOptionUnHas(resCode) {
return `
const __injectCSSVars__ = () => {
_useCssVars((_ctx) => ({
${resCode}
}))
};`;
}
function createUseCssVarsCode(cssvarsObjectCode, isScriptSetup) {
let resCode = "";
if (isScriptSetup) {
resCode = createUCVCSetupUnHas(cssvarsObjectCode);
} else {
resCode = createUCVCOptionUnHas(cssvarsObjectCode);
}
return resCode;
}
function genCSSVarsValue(node, bindings) {
let res = `_ctx.${node.text()}`;
if (bindings) {
const binding = bindings[node.text()];
switch (binding) {
case CSSVarsBindingTypes.PROPS:
case CSSVarsBindingTypes.SETUP_CONST:
case CSSVarsBindingTypes.SETUP_REACTIVE_CONST:
case CSSVarsBindingTypes.LITERAL_CONST:
res = node.text();
break;
case CSSVarsBindingTypes.SETUP_MAYBE_REF:
case CSSVarsBindingTypes.SETUP_LET:
res = `_unref(${node.text()})`;
break;
case CSSVarsBindingTypes.SETUP_REF:
res = `${node.text()}.value`;
break;
default:
res = `_ctx.${node.text()}`;
}
}
return res;
}
// ../packages/core/hmr/hmr.ts
import { setTArray as setTArray2 } from "../utils/index.js";
function viteHMR(CSSFileModuleMap, userOptions, file, server) {
const sfcModulesPathList = CSSFileModuleMap.get(file);
if (!(sfcModulesPathList && sfcModulesPathList.sfcPath))
return;
updatedCSSModules(CSSFileModuleMap, userOptions, file);
reloadSFCModules(CSSFileModuleMap, userOptions, sfcModulesPathList, file, server);
}
function webpackHMR(CSSFileModuleMap, userOptions, file) {
updatedCSSModules(CSSFileModuleMap, userOptions, file);
}
function updatedCSSModules(CSSFileModuleMap, userOptions, file) {
const updatedCSSMS = preProcessCSS(userOptions, userOptions.alias, [file]).get(file);
const sfcPath = CSSFileModuleMap.get(file).sfcPath || /* @__PURE__ */ new Set();
const res = {
...updatedCSSMS,
sfcPath
};
CSSFileModuleMap.set(file, res);
}
function reloadSFCModules(CSSFileModuleMap, userOptions, sfcModulesPathList, file, server) {
if (sfcModulesPathList && sfcModulesPathList.sfcPath) {
const ls = setTArray2(sfcModulesPathList.sfcPath);
ls.forEach((sfcp) => {
const modules = server.moduleGraph.fileToModulesMap.get(sfcp) || /* @__PURE__ */ new Set();
const modulesList = setTArray2(modules);
for (let i = 0; i < modulesList.length; i++) {
if (modulesList[i].id && modulesList[i].id.endsWith(".vue"))
server.reloadModule(modulesList[i]);
}
});
}
}
// ../packages/core/runtime/handle-variable.ts
import { parse as parse3 } from "@vue/compiler-sfc";
function handleVBindVariable(code, id, ctx) {
const { descriptor } = parse3(code);
ctx.isScriptSetup = !!descriptor.scriptSetup;
const {
vbindVariableListByPath,
injectCSSContent
} = getVBindVariableListByPath(
descriptor,
id,
ctx.CSSFileModuleMap,
ctx.isServer,
ctx.userOptions.alias
);
const variableName = getVariable(descriptor);
ctx.vbindVariableList.set(id, matchVariable(vbindVariableListByPath, variableName));
ctx.isScriptSetup && (ctx.bindingsTypeMap[id] = analyzeScriptBindings(descriptor));
return {
descriptor,
injectCSSContent
};
}
// ../packages/core/runtime/handle-inject-css.ts
function handleInjectCss(id, code, mgcStr, ctx) {
const parseRes = parserCompiledSfc(code);
const injectRes = injectCSSVars(
ctx.vbindVariableList.get(id),
ctx.isScriptSetup,
parseRes,
mgcStr,
ctx.bindingsTypeMap[id]
);
mgcStr = injectRes.mgcStr;
injectRes.vbindVariableList && ctx.vbindVariableList.set(id, injectRes.vbindVariableList);
return mgcStr;
}
// ../packages/core/runtime/vite.ts
function transformPreVite(id, code, mgcStr, ctx) {
let injectCSSContent = null;
let descriptor = null;
if (id.endsWith(".vue")) {
const res = handleVBindVariable(code, id, ctx);
if (res) {
descriptor = res.descriptor;
injectCSSContent = res.injectCSSContent;
}
}
if (mgcStr && !ctx.isServer && ctx.framework !== "webpack" && ctx.framework !== "rspack")
mgcStr = injectCssOnBuild(mgcStr, injectCSSContent, descriptor);
return mgcStr;
}
function transformPostViteDev(id, code, mgcStr, ctx) {
if (id.endsWith(".vue") || id.includes("&lang.tsx") || id.includes("&lang.jsx"))
mgcStr = handleInjectCss(id.split("?vue")[0], code, mgcStr, ctx);
if (id.includes("?vue&type=style")) {
mgcStr = injectCSSOnServer(
mgcStr,
ctx.vbindVariableList.get(id.split("?vue")[0]),
ctx.isHMR
);
}
return mgcStr;
}
var vitePlugin = (ctx) => {
return {
// Vite plugin
configResolved(config) {
if (ctx.userOptions.server !== void 0)
ctx.isServer = ctx.userOptions.server;
else
ctx.isServer = config.command === "serve";
},
handleHotUpdate(hmr) {
if (SUPPORT_FILE_REG.test(hmr.file)) {
ctx.isHMR = true;
viteHMR(
ctx.CSSFileModuleMap,
ctx.userOptions,
normalizePath3(hmr.file),
hmr.server
);
}
}
};
};
// ../packages/core/runtime/webpack.ts
import { NAME, SUPPORT_FILE_REG as SUPPORT_FILE_REG2, setTArray as setTArray3 } from "../utils/index.js";
import { log, normalizePath as normalizePath4 } from "baiwusanyu-utils";
function transformPreWebpack(id, code, ctx) {
if (id.endsWith(".vue"))
handleVBindVariable(code, id, ctx);
if ((id.includes("?vue&type=style") || id.includes("?vue&type=script")) && ctx.isHMR && ctx.framework === "webpack") {
id = id.split("?vue")[0];
handleVBindVariable(code, id, ctx);
}
}
function transformPostWebpack(id, code, mgcStr, ctx) {
if (id.includes("?vue&type=script")) {
id = id.split("?vue")[0];
handleInjectCss(id, code, mgcStr, ctx);
}
const cssFMM = ctx.CSSFileModuleMap.get(id);
if (cssFMM && cssFMM.sfcPath && cssFMM.sfcPath.size > 0) {
const sfcPathIdList = setTArray3(cssFMM.sfcPath);
sfcPathIdList.forEach((v) => {
mgcStr = injectCSSOnServer(
mgcStr,
ctx.vbindVariableList.get(v),
ctx.isHMR
);
});
}
return mgcStr;
}
var webpackPlugin = (ctx, compiler) => {
let modifiedFile = "";
compiler.hooks.watchRun.tapAsync(NAME, (compilation1, watchRunCallBack) => {
if (compilation1.modifiedFiles) {
modifiedFile = normalizePath4(setTArray3(compilation1.modifiedFiles)[0]);
if (SUPPORT_FILE_REG2.test(modifiedFile)) {
ctx.isHMR = true;
webpackHMR(
ctx.CSSFileModuleMap,
ctx.userOptions,
modifiedFile
);
}
}
watchRunCallBack();
});
compiler.hooks.compilation.tap(NAME, (compilation) => {
compilation.hooks.finishModules.tapAsync(NAME, async (modules, callback) => {
if (ctx.isHMR) {
const needRebuildModules = /* @__PURE__ */ new Map();
for (const value of modules) {
const resource = normalizePath4(value.resource);
if (resource.includes("?vue&type=script")) {
const sfcPathKey = resource.split("?vue")[0];
const cm = ctx.CSSFileModuleMap.get(modifiedFile);
if (cm && cm.sfcPath && cm.sfcPath.has(sfcPathKey))
needRebuildModules.set(sfcPathKey, value);
}
}
if (needRebuildModules.size > 0) {
const promises = [];
for (const [key] of needRebuildModules) {
const promise = new Promise((resolve4, reject) => {
compilation.rebuildModule(needRebuildModules.get(key), (e) => {
if (e)
reject(e);
else
resolve4(true);
});
});
promises.push(promise);
}
Promise.all(promises).then(() => {
callback();
ctx.isHMR = false;
}).catch((e) => {
log("error", e);
});
} else {
callback();
}
} else {
callback();
}
});
});
};
// ../packages/core/index.ts
var unplugin = createUnplugin(
(options = {}, meta) => {
setGlobalPrefix(`[${NAME2}]:`);
const userOptions = initOption(options);
const filter = createFilter(
userOptions.include,
userOptions.exclude
);
if (userOptions.server === void 0) {
log2("warning", "The server of option is not set, you need to specify whether you are using the development server or building the project");
log2("warning", "See: https://github.com/baiwusanyu-c/unplugin-vue-cssvars/blob/master/README.md#option");
}
const context = {
CSSFileModuleMap: preProcessCSS(userOptions, userOptions.alias),
vbindVariableList: /* @__PURE__ */ new Map(),
isServer: !!userOptions.server,
isHMR: false,
userOptions,
framework: meta.framework,
isScriptSetup: false,
bindingsTypeMap: {}
};
return [
{
name: NAME2,
enforce: "pre",
transformInclude(id) {
return filter(id);
},
async transform(code, id) {
const transId = normalizePath5(id);
let mgcStr = new MagicString3(code);
try {
if (context.framework === "vite" || context.framework === "rollup" || context.framework === "esbuild") {
mgcStr = transformPreVite(
transId,
code,
mgcStr,
context
);
}
if (context.framework === "webpack") {
transformPreWebpack(
transId,
code,
context
);
}
return {
code: mgcStr.toString(),
get map() {
return mgcStr.generateMap({
source: id,
includeContent: true,
hires: true
});
}
};
} catch (err) {
this.error(`[${NAME2}] ${err}`);
}
},
// handle hmr with vite and command
vite: vitePlugin(context),
// handle hmr with webpack
webpack(compiler) {
webpackPlugin(context, compiler);
}
},
{
name: `${NAME2}:inject`,
enforce: "post",
transformInclude(id) {
return filter(id);
},
async transform(code, id) {
const transId = normalizePath5(id);
let mgcStr = new MagicString3(code);
try {
if (context.isServer) {
if (context.framework === "vite" || context.framework === "rollup" || context.framework === "esbuild")
mgcStr = transformPostViteDev(transId, code, mgcStr, context);
}
if (context.framework === "webpack")
mgcStr = transformPostWebpack(transId, code, mgcStr, context);
return {
code: mgcStr.toString(),
get map() {
return mgcStr.generateMap({
source: id,
includeContent: true,
hires: true
});
}
};
} catch (err) {
this.error(`[${NAME2}] ${err}`);
}
},
buildEnd() {
if (context.isServer) {
if (context.framework === "vite" || context.framework === "rollup" || context.framework === "esbuild")
context.isHMR = false;
}
}
}
];
}
);
var viteVueCSSVars = unplugin.vite;
var rollupVueCSSVars = unplugin.rollup;
var webpackVueCSSVars = unplugin.webpack;
var esbuildVueCSSVars = unplugin.esbuild;
export {
esbuildVueCSSVars,
rollupVueCSSVars,
viteVueCSSVars,
webpackVueCSSVars
};