UNPKG

weapp-tailwindcss

Version:

把 tailwindcss 原子化样式思想,带给小程序开发者们! bring tailwindcss to miniprogram developers!

1,431 lines 78.7 kB
import { createRequire } from "node:module"; import process from "node:process"; import semver from "semver"; import path from "node:path"; import { access, mkdir, writeFile } from "node:fs/promises"; import { logger } from "@weapp-tailwindcss/logger"; import { LRUCache } from "lru-cache"; import { md5 as md5Hash } from "@weapp-tailwindcss/shared/node"; import { MappingChars2String, escape } from "@weapp-core/escape"; import "@weapp-tailwindcss/shared"; import { constants, existsSync, readFileSync } from "node:fs"; import _createDebug from "debug"; import { splitCandidateTokens } from "@tailwindcss-mangle/engine"; import { createStyleHandler } from "@weapp-tailwindcss/postcss"; import "micromatch"; import "fast-glob"; import "tailwindcss-config"; import _babelTraverse from "@babel/traverse"; import { parse } from "@babel/parser"; import MagicString from "magic-string"; import "@babel/types"; import "htmlparser2"; //#region src/js/babel/cache-options.ts const DEFAULT_PARSE_CACHE_MAX_SOURCE_LENGTH = 5e4; const HARD_PARSE_CACHE_MAX_ENTRIES = 1024; //#endregion //#region src/debug/index.ts const _debug = _createDebug("weapp-tw"); function createDebug(prefix) { const debug = ((formatter, ...args) => { return _debug((prefix ?? "") + formatter, ...args); }); Object.defineProperty(debug, "enabled", { enumerable: false, configurable: false, get() { return _debug.enabled; } }); return debug; } //#endregion //#region src/tailwindcss/v4-engine/generator/rpx-candidates.ts const RPX_LENGTH_UTILITY_PATTERN = String.raw`text|border(?:-[trblxyse])?|bg|outline|ring`; new RegExp(String.raw`(^|:)(!?)(${RPX_LENGTH_UTILITY_PATTERN})-\[([-+]?(?:\d+|\d*\.\d+)rpx)\](.*)$`, "u"); new RegExp(String.raw`(?:^|:)!?(${RPX_LENGTH_UTILITY_PATTERN})-\[length:([-+]?(?:\d+|\d*\.\d+)rpx)\].*$`, "u"); new RegExp(String.raw`(${RPX_LENGTH_UTILITY_PATTERN})-\\\[length\\:((?:\\[.+-]|[+\-.\d])+rpx)\\\]`, "g"); new LRUCache({ max: 8 }); new LRUCache({ max: 32 }); createStyleHandler({ cssChildCombinatorReplaceValue: ["view", "text"], cssRemoveHoverPseudoClass: true, isMainChunk: true, majorVersion: 4 }); createRequire(import.meta.url); //#endregion //#region \0@oxc-project+runtime@0.140.0/helpers/esm/typeof.js function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) { return typeof o; } : function(o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } //#endregion //#region \0@oxc-project+runtime@0.140.0/helpers/esm/toPrimitive.js function toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } //#endregion //#region \0@oxc-project+runtime@0.140.0/helpers/esm/toPropertyKey.js function toPropertyKey(t) { var i = toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } //#endregion //#region \0@oxc-project+runtime@0.140.0/helpers/esm/defineProperty.js function _defineProperty(e, r, t) { return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } //#endregion //#region src/tailwindcss/source-scan.ts const DEFAULT_SOURCE_SCAN_EXTENSIONS = [ "html", "wxml", "axml", "jxml", "ksml", "ttml", "qml", "tyml", "xhsml", "swan", "vue", "mpx", "js", "jsx", "ts", "tsx" ]; const FULL_SOURCE_SCAN_EXTENSIONS = [ "js", "jsx", "mjs", "cjs", "ts", "tsx", "mts", "cts", "vue", "uvue", "nvue", "svelte", "mpx", "html", "wxml", "axml", "jxml", "ksml", "ttml", "qml", "tyml", "xhsml", "swan", "css", "wxss", "acss", "jxss", "ttss", "qss", "tyss", "scss", "sass", "less", "styl", "stylus" ]; function createSourceScanPattern(extensions = DEFAULT_SOURCE_SCAN_EXTENSIONS) { return `**/*.{${extensions.join(",")}}`; } createSourceScanPattern(FULL_SOURCE_SCAN_EXTENSIONS); new RegExp(`\\.(?:${FULL_SOURCE_SCAN_EXTENSIONS.map((extension) => extension.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})$`); //#endregion //#region src/utils/object.ts function definedEntries(value) { return Object.entries(value).filter(([, item]) => item !== void 0); } function omitUndefined(value) { return Object.fromEntries(definedEntries(value)); } createRequire(import.meta.url); createDebug("[tailwindcss:runtime] "); //#endregion //#region src/tailwindcss/runtime-logs.ts const runtimeLogDedupeHolder = globalThis; runtimeLogDedupeHolder.__WEAPP_TW_RUNTIME_LOG_DEDUPE__ ?? (runtimeLogDedupeHolder.__WEAPP_TW_RUNTIME_LOG_DEDUPE__ = /* @__PURE__ */ new Set()); createRequire(import.meta.url); const globalCacheHolder = globalThis; globalCacheHolder.__WEAPP_TW_COMPILER_CONTEXT_CACHE__ ?? (globalCacheHolder.__WEAPP_TW_COMPILER_CONTEXT_CACHE__ = /* @__PURE__ */ new Map()); //#endregion //#region src/babel/index.ts function _interopDefaultCompat(e) { return e && typeof e === "object" && "default" in e ? e.default : e; } const traverse = _interopDefaultCompat(_babelTraverse); //#endregion //#region src/utils/regex.ts function escapeStringRegexp(str) { if (typeof str !== "string") throw new TypeError("Expected a string"); return str.replaceAll(/[$()*+.?[\\\]^{|}]/g, "\\$&").replaceAll("-", "\\x2d"); } //#endregion //#region src/utils/nameMatcher.ts const NEVER_MATCH_NAME$1 = () => false; const GLOBAL_FLAG_REGEXP = /g/g; function buildFuzzyMatcher(fuzzyStrings) { if (fuzzyStrings.length === 0) return; if (fuzzyStrings.length === 1) { const [needle] = fuzzyStrings; if (needle === void 0) return; return (value) => value.includes(needle); } const unique = [...new Set(fuzzyStrings)]; const pattern = new RegExp(unique.map(escapeStringRegexp).join("|")); return (value) => pattern.test(value); } function normaliseRegex(regex) { const { source, flags } = regex; if (!flags.includes("g")) return regex; return new RegExp(source, flags.replace(GLOBAL_FLAG_REGEXP, "")); } function createNameMatcher(list, { exact = false } = {}) { if (!list || list.length === 0) return NEVER_MATCH_NAME$1; const exactStrings = exact ? /* @__PURE__ */ new Set() : void 0; const fuzzyStrings = []; const regexList = []; for (const item of list) if (typeof item === "string") if (exact) exactStrings.add(item); else fuzzyStrings.push(item); else regexList.push(normaliseRegex(item)); if (exact) { const exactStringCount = exactStrings?.size ?? 0; if (exactStringCount === 1 && regexList.length === 0) { const [needle] = exactStrings; if (needle === void 0) return NEVER_MATCH_NAME$1; return (value) => value === needle; } if (regexList.length === 0) return (value) => exactStrings.has(value); if (exactStringCount === 0 && regexList.length === 1) { const [regex] = regexList; if (!regex) return NEVER_MATCH_NAME$1; return (value) => regex.test(value); } return (value) => { if (exactStrings?.has(value)) return true; return regexList.some((regex) => regex.test(value)); }; } const fuzzyMatcher = exact ? void 0 : buildFuzzyMatcher(fuzzyStrings); const hasRegex = regexList.length > 0; if (fuzzyMatcher && !hasRegex) return fuzzyMatcher; if (!fuzzyMatcher && regexList.length === 1) { const [regex] = regexList; if (!regex) return NEVER_MATCH_NAME$1; return (value) => regex.test(value); } return (value) => { if (fuzzyMatcher?.(value)) return true; if (!hasRegex) return false; return regexList.some((regex) => regex.test(value)); }; } //#endregion //#region src/js/babel/parse.ts const parseCache = new LRUCache({ max: HARD_PARSE_CACHE_MAX_ENTRIES }); function genCacheKey(source, options) { if (typeof options === "string") return `${md5Hash(source)}:${options}`; return `${md5Hash(source)}:${JSON.stringify(options, (_, val) => typeof val === "function" ? val.toString() : val)}`; } function normalizeCacheMaxEntries(value) { if (typeof value !== "number" || !Number.isFinite(value)) return 128; return Math.min(Math.max(Math.floor(value), 0), HARD_PARSE_CACHE_MAX_ENTRIES); } function normalizeCacheMaxSourceLength(value) { if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_PARSE_CACHE_MAX_SOURCE_LENGTH; return Math.max(Math.floor(value), 0); } function trimParseCache(maxEntries) { while (parseCache.size > maxEntries) parseCache.pop(); } function babelParse(code, opts = {}) { const { cache, cacheKey, cacheMaxEntries, cacheMaxSourceLength, ...rest } = opts; const maxEntries = normalizeCacheMaxEntries(cacheMaxEntries); const maxSourceLength = normalizeCacheMaxSourceLength(cacheMaxSourceLength); const shouldCache = cache === true && maxEntries > 0 && code.length <= maxSourceLength; const cacheKeyString = shouldCache ? genCacheKey(code, cacheKey ?? rest) : void 0; let result; if (shouldCache) { trimParseCache(maxEntries); result = parseCache.get(cacheKeyString); } if (!result) { const { cache: _cache, cacheKey: _cacheKey, cacheMaxEntries: _cacheMaxEntries, cacheMaxSourceLength: _cacheMaxSourceLength, ...parseOptions } = opts; result = parse(code, parseOptions); if (shouldCache) { parseCache.set(cacheKeyString, result); trimParseCache(maxEntries); } } return result; } //#endregion //#region src/wxml/shared.ts const NEWLINE_RE = /[\n\r]+/g; function replaceWxml(original, options = { keepEOL: false, escapeMap: MappingChars2String }) { const { keepEOL, escapeMap, ignoreHead } = options; let res = original; if (!keepEOL) res = res.replaceAll(NEWLINE_RE, ""); res = escape(res, omitUndefined({ map: escapeMap, ignoreHead })); return res; } //#endregion //#region src/shared/classname-transform.ts const escapedCandidateCacheByEscapeMap = /* @__PURE__ */ new WeakMap(); const defaultEscapedCandidateCache = /* @__PURE__ */ new Map(); let lastEscapedCandidateEscapeMap; let lastEscapedCandidateCacheStore; function isUrlLikeCandidate(candidate) { return candidate.startsWith("//") || candidate.startsWith("http://") || candidate.startsWith("https://"); } function isPlainSlashPathCandidate(candidate) { const slashIndex = candidate.indexOf("/"); if (slashIndex <= 0) return false; if (isUrlLikeCandidate(candidate)) return true; if (candidate.includes("[") || candidate.includes("]") || candidate.includes(":")) return false; return !candidate.slice(0, slashIndex).includes("-"); } function isArbitraryValueCandidate(candidate) { let hasOpenBracket = false; let hasCloseBracket = false; for (let i = 0; i < candidate.length; i++) { const char = candidate[i]; if (char === "[") hasOpenBracket = true; else if (char === "]") hasCloseBracket = true; if (hasOpenBracket && hasCloseBracket) break; } if (!hasOpenBracket || !hasCloseBracket) return false; if (isUrlLikeCandidate(candidate.trim())) return false; return true; } function shouldEnableArbitraryValueFallbackByInputs(classNameSet, jsArbitraryValueFallback, tailwindcssMajorVersion) { if (jsArbitraryValueFallback === true) return true; if (jsArbitraryValueFallback === false) return false; return tailwindcssMajorVersion === 4 && (!classNameSet || classNameSet.size === 0); } function shouldEnableArbitraryValueFallback({ classNameSet, jsArbitraryValueFallback, tailwindcssMajorVersion }) { return shouldEnableArbitraryValueFallbackByInputs(classNameSet, jsArbitraryValueFallback, tailwindcssMajorVersion); } const SKIP_RESULT = { decision: "skip" }; const DIRECT_RESULT = { decision: "direct" }; const FALLBACK_RESULT = { decision: "fallback" }; function getEscapedCandidateCacheStore(escapeMap) { if (!escapeMap) return defaultEscapedCandidateCache; if (escapeMap === lastEscapedCandidateEscapeMap && lastEscapedCandidateCacheStore) return lastEscapedCandidateCacheStore; let store = escapedCandidateCacheByEscapeMap.get(escapeMap); if (!store) { store = /* @__PURE__ */ new Map(); escapedCandidateCacheByEscapeMap.set(escapeMap, store); } lastEscapedCandidateEscapeMap = escapeMap; lastEscapedCandidateCacheStore = store; return store; } function getEscapedCandidate(candidate, escapeMap, store = getEscapedCandidateCacheStore(escapeMap)) { let cached = store.get(candidate); if (cached === void 0) { cached = replaceWxml(candidate, { escapeMap }); store.set(candidate, cached); } return cached; } /** * JS 转译严格遵循 runtime class set: * 1. 直接命中 classNameSet 原始值; * 2. 兼容命中 classNameSet 中已转义值; * 3. 仅在受控条件下允许 class 语义兜底。 * * 返回结构化结果,附带已计算的 escapedValue 以避免下游重复 escape。 */ function resolveClassNameTransformWithResult(candidate, { alwaysEscape, classNameSet, escapeMap, jsArbitraryValueFallback, jsPreserveClass, tailwindcssMajorVersion, classContext }) { if (jsPreserveClass?.(candidate)) return SKIP_RESULT; if (alwaysEscape) return DIRECT_RESULT; if (!classContext && isPlainSlashPathCandidate(candidate)) return SKIP_RESULT; if (classNameSet?.has(candidate)) return DIRECT_RESULT; if (classNameSet && classNameSet.size > 0) { const escapedCandidate = getEscapedCandidate(candidate, escapeMap); if (escapedCandidate !== candidate && classNameSet.has(escapedCandidate)) return { decision: "escaped", escapedValue: escapedCandidate }; } if (classContext && shouldEnableArbitraryValueFallbackByInputs(classNameSet, jsArbitraryValueFallback, tailwindcssMajorVersion) && isArbitraryValueCandidate(candidate)) return FALLBACK_RESULT; return SKIP_RESULT; } //#endregion //#region src/utils/decode.ts const unicodeEscapeRE = /\\u([\dA-Fa-f]{4})/g; const unicodeEscapeTestRE = /\\u[\dA-Fa-f]{4}/; function decodeUnicode(value) { if (!unicodeEscapeTestRE.test(value)) return value; return value.replace(unicodeEscapeRE, (_match, hex) => { const codePoint = Number.parseInt(hex, 16); return Number.isNaN(codePoint) ? _match : String.fromCharCode(codePoint); }); } function decodeUnicode2(input) { if (!unicodeEscapeTestRE.test(input)) return input; try { return JSON.parse(`"${input}"`); } catch (_error) { return decodeUnicode(input); } } //#endregion //#region src/js/class-context.ts const CLASS_LIKE_KEYWORDS = /* @__PURE__ */ new Set([ "class", "classname", "hoverclass", "virtualhostclass", "rootclass" ]); const CLASS_HELPER_IDENTIFIERS = /* @__PURE__ */ new Set([ "cn", "clsx", "classnames", "twmerge", "cva", "tv", "cx", "r" ]); const DASH_CODE = 45; const COLON_CODE = 58; const UPPERCASE_A_CODE = 65; const UPPERCASE_Z_CODE = 90; const UNDERSCORE_CODE = 95; const ASCII_MAX_CODE = 127; const NORMALIZE_KEYWORD_REGEXP = /[-_:]/g; function normalizeKeyword(name) { const length = name.length; let firstNormalizedIndex = -1; for (let i = 0; i < length; i++) { const code = name.charCodeAt(i); if (code === DASH_CODE || code === UNDERSCORE_CODE || code === COLON_CODE || code >= UPPERCASE_A_CODE && code <= UPPERCASE_Z_CODE) { firstNormalizedIndex = i; break; } if (code > ASCII_MAX_CODE) return name.replace(NORMALIZE_KEYWORD_REGEXP, "").toLowerCase(); } if (firstNormalizedIndex === -1) return name; let normalized = name.slice(0, firstNormalizedIndex); for (let i = firstNormalizedIndex; i < length; i++) { const code = name.charCodeAt(i); if (code === DASH_CODE || code === UNDERSCORE_CODE || code === COLON_CODE) continue; if (code >= UPPERCASE_A_CODE && code <= UPPERCASE_Z_CODE) { normalized += String.fromCharCode(code + 32); continue; } if (code > ASCII_MAX_CODE) return name.replace(NORMALIZE_KEYWORD_REGEXP, "").toLowerCase(); normalized += name[i]; } return normalized; } function readObjectKeyName(path) { if (path.isIdentifier()) return path.node.name; if (path.isStringLiteral()) return path.node.value; if (path.isTemplateLiteral() && path.node.expressions.length === 0) return path.node.quasis[0]?.value.cooked ?? path.node.quasis[0]?.value.raw; } function isClassLikeObjectProperty(path, valuePath) { if (!path.isObjectProperty()) return false; if (path.get("value") !== valuePath) return false; const keyName = readObjectKeyName(path.get("key")); if (!keyName) return false; return CLASS_LIKE_KEYWORDS.has(normalizeKeyword(keyName)); } function isClassLikeJsxAttribute(path) { if (!path.isJSXAttribute()) return false; const namePath = path.get("name"); if (!namePath.isJSXIdentifier()) return false; return CLASS_LIKE_KEYWORDS.has(normalizeKeyword(namePath.node.name)); } function readCallHelperName(calleePath) { if (calleePath.isIdentifier()) return calleePath.node.name; if (calleePath.isMemberExpression()) { const propertyPath = calleePath.get("property"); if (propertyPath.isIdentifier()) return propertyPath.node.name; if (propertyPath.isStringLiteral()) return propertyPath.node.value; } } function isClassLikeCallExpression(path, valuePath) { if (!path.isCallExpression()) return false; const helperName = readCallHelperName(path.get("callee")); if (!helperName || !CLASS_HELPER_IDENTIFIERS.has(normalizeKeyword(helperName))) return false; return path.get("arguments").some((argumentPath) => argumentPath.node === valuePath.node); } /** * 判断字符串字面量是否处于 class 语义上下文。 * 仅用于受控兜底场景,避免将普通业务文本误判为 class。 */ function isClassContextLiteralPath(path) { let current = path; while (current.parentPath) { const parent = current.parentPath; if (isClassLikeObjectProperty(parent, current)) return true; if (isClassLikeJsxAttribute(parent)) return true; if (isClassLikeCallExpression(parent, current)) return true; current = parent; } return false; } //#endregion //#region src/js/js-string-escape.ts function jsStringEscape(value) { return String(value).replaceAll(/[\n\r"'\\\u2028\u2029]/g, (character) => { switch (character) { case "\"": case "'": case "\\": return `\\${character}`; case "\n": return "\\n"; case "\r": return "\\r"; case "\u2028": return "\\u2028"; case "\u2029": return "\\u2029"; default: return character; } }); } //#endregion //#region src/js/replacement-cache.ts const replacementCacheByEscapeMap = /* @__PURE__ */ new WeakMap(); const defaultReplacementCache = /* @__PURE__ */ new Map(); function getReplacementCacheStore(escapeMap) { if (!escapeMap) return defaultReplacementCache; let store = replacementCacheByEscapeMap.get(escapeMap); if (!store) { store = /* @__PURE__ */ new Map(); replacementCacheByEscapeMap.set(escapeMap, store); } return store; } function getReplacement(candidate, escapeMap, store = getReplacementCacheStore(escapeMap)) { let cached = store.get(candidate); if (cached === void 0) { cached = replaceWxml(candidate, { escapeMap }); store.set(candidate, cached); } return cached; } //#endregion //#region src/js/handlers.ts const debug = createDebug("[js:handlers] "); const WEAPP_TW_IGNORE_MARKER = "weapp-tw"; const IGNORE_MARKER = "ignore"; function hasIgnoreComment(node) { const { leadingComments } = node; if (!Array.isArray(leadingComments) || leadingComments.length === 0) return false; for (const comment of leadingComments) { const { value } = comment; if (value.includes(WEAPP_TW_IGNORE_MARKER) && value.includes(IGNORE_MARKER)) return true; } return false; } function isConditionTestLiteral(path) { let current = path; while (current?.parentPath) { const parent = current.parentPath; if (parent.isConditionalExpression()) return parent.node.test === current.node; if (parent.isBinaryExpression() || parent.isCallExpression() || parent.isLogicalExpression() || parent.isMemberExpression() || parent.isUnaryExpression()) { current = parent; continue; } return false; } return false; } function extractLiteralValue(path, { unescapeUnicode }) { const { node } = path; let offset = 0; let original; if (node.type === "StringLiteral") { offset = 1; original = node.value; } else if (node.type === "TemplateElement") original = node.value.raw; else original = ""; let literal = original; if (unescapeUnicode && original.includes("\\u")) literal = decodeUnicode2(original); return { literal, offset, original }; } function createCandidatePlanResolver(options, classContext) { const { escapeMap } = options; const replacementCache = getReplacementCacheStore(escapeMap); const transformOptions = classContext ? { ...options, classContext } : options; let firstCandidate = ""; let firstPlan; let cache; const buildCandidatePlan = (candidate) => { const result = resolveClassNameTransformWithResult(candidate, transformOptions); if (result.decision === "skip") return { result }; let replacement; if (result.decision === "escaped" && result.escapedValue) { replacement = result.escapedValue; replacementCache.set(candidate, replacement); } else replacement = getReplacement(candidate, escapeMap, replacementCache); return { result, replacement }; }; return (candidate) => { if (cache) { const cached = cache.get(candidate); if (cached) return cached; } else if (firstPlan && candidate === firstCandidate) return firstPlan; const plan = buildCandidatePlan(candidate); if (!firstPlan) { firstCandidate = candidate; firstPlan = plan; return plan; } if (!cache) { cache = /* @__PURE__ */ new Map(); cache.set(firstCandidate, firstPlan); } cache.set(candidate, plan); return plan; }; } function replaceHandleValue(path, options) { const { needEscaped = false } = options; const { classNameSet, alwaysEscape } = options; const fallbackEnabled = shouldEnableArbitraryValueFallback(options); if (!alwaysEscape && !fallbackEnabled && (!classNameSet || classNameSet.size === 0)) return; if (hasIgnoreComment(path.node)) return; if (isConditionTestLiteral(path)) return; const { literal, original, offset } = extractLiteralValue(path, options); const candidates = splitCandidateTokens(literal); if (candidates.length === 0) return; const debugEnabled = debug.enabled; const classContext = options.wrapExpression || isClassContextLiteralPath(path); let transformed = literal; let mutated = false; let matchedCandidateCount = 0; let escapedDecisionCount = 0; let fallbackDecisionCount = 0; let escapedSamples; let skippedSamples; const resolveCandidatePlan = createCandidatePlanResolver(options, classContext); for (const candidate of candidates) { const plan = resolveCandidatePlan(candidate); if (plan.result.decision === "skip") { if (debugEnabled) { if (!skippedSamples) skippedSamples = []; if (skippedSamples.length < 6) skippedSamples.push(candidate); } continue; } if (debugEnabled) { matchedCandidateCount += 1; if (plan.result.decision === "escaped") { escapedDecisionCount += 1; if (!escapedSamples) escapedSamples = []; if (escapedSamples.length < 6) escapedSamples.push(candidate); } if (plan.result.decision === "fallback") fallbackDecisionCount += 1; } const replaced = transformed.replace(candidate, plan.replacement); if (replaced !== transformed) { transformed = replaced; mutated = true; } } const node = path.node; if (!mutated || typeof node.start !== "number" || typeof node.end !== "number") return; if (debugEnabled) debug("runtimeSet size=%d fallbackTriggered=%s candidates=%d matched=%d escapedHits=%d skipped=%d file=%s escapedSamples=%s skippedSamples=%s", classNameSet?.size ?? 0, fallbackDecisionCount > 0, candidates.length, matchedCandidateCount, escapedDecisionCount, skippedSamples?.length ?? 0, options.filename ?? "unknown", escapedSamples?.join(",") || "-", skippedSamples?.join(",") || "-"); const start = node.start + offset; const end = node.end - offset; if (start >= end || transformed === original) return; return { start, end, value: needEscaped ? jsStringEscape(transformed) : transformed, path }; } //#endregion //#region src/js/sourceAnalysis.ts function hasReplacementEntries(replacements) { for (const key in replacements) if (Object.hasOwn(replacements, key)) return true; return false; } function createModuleSpecifierReplacementToken(path, replacement) { const node = path.node; if (node.value === replacement) return; if (typeof node.start !== "number" || typeof node.end !== "number") return; const start = node.start + 1; const end = node.end - 1; if (start >= end) return; return { start, end, value: replacement, path }; } function collectModuleSpecifierReplacementTokens(analysis, replacements) { if (!hasReplacementEntries(replacements)) return []; if (analysis.importDeclarations.size === 0 && analysis.exportDeclarations.size === 0 && analysis.requireCallPaths.length === 0 && analysis.walker.imports.size === 0) return []; const tokens = []; const applyReplacement = (path) => { const replacement = replacements[path.node.value]; if (!replacement) return; const token = createModuleSpecifierReplacementToken(path, replacement); if (token) tokens.push(token); }; for (const importPath of analysis.importDeclarations) { const source = importPath.get("source"); if (source.isStringLiteral()) applyReplacement(source); } for (const exportPath of analysis.exportDeclarations) if (exportPath.isExportNamedDeclaration() || exportPath.isExportAllDeclaration()) { const source = exportPath.get("source"); if (source && !Array.isArray(source) && source.isStringLiteral()) applyReplacement(source); } for (const literalPath of analysis.requireCallPaths) applyReplacement(literalPath); for (const token of analysis.walker.imports) { const replacement = replacements[token.source]; if (replacement) token.source = replacement; } return tokens; } //#endregion //#region src/js/babel/process.ts const optionVariantsCache = /* @__PURE__ */ new WeakMap(); function getNeedEscapedOptions(options, needEscaped) { if (options.needEscaped === needEscaped) return options; let cached = optionVariantsCache.get(options); if (!cached) { cached = {}; optionVariantsCache.set(options, cached); } if (needEscaped) { if (!cached.stringLiteralOptions) cached.stringLiteralOptions = { ...options, needEscaped: true }; return cached.stringLiteralOptions; } if (!cached.templateLiteralOptions) cached.templateLiteralOptions = { ...options, needEscaped: false }; return cached.templateLiteralOptions; } function processUpdatedSource(rawSource, options, analysis) { const { targetPaths, jsTokenUpdater, ignoredPaths } = analysis; if (targetPaths.length === 0 && !options.moduleSpecifierReplacements && jsTokenUpdater.length === 0) return new MagicString(rawSource); const replacementTokens = []; for (const path of targetPaths) { if (ignoredPaths.has(path)) continue; const token = replaceHandleValue(path, path.isStringLiteral() ? getNeedEscapedOptions(options, true) : getNeedEscapedOptions(options, false)); if (token) replacementTokens.push(token); } if (options.moduleSpecifierReplacements) replacementTokens.push(...collectModuleSpecifierReplacementTokens(analysis, options.moduleSpecifierReplacements)); if (jsTokenUpdater.length + replacementTokens.length === 0) return new MagicString(rawSource); const ms = new MagicString(rawSource); jsTokenUpdater.push(...replacementTokens).filter((token) => !ignoredPaths.has(token.path)).updateMagicString(ms); return ms; } //#endregion //#region src/js/evalTransforms.ts const evalHandlerOptionsCache = /* @__PURE__ */ new WeakMap(); const EVAL_SCOPE_ERROR_REGEXP = /pass a scope and parentPath|traversing a Program\/File/i; function isEvalPath(path) { if (path.isCallExpression()) return path.get("callee").isIdentifier({ name: "eval" }); return false; } function createEvalReplacementToken(path, updated) { const node = path.node; let offset = 0; let original; if (path.isStringLiteral()) { offset = 1; original = path.node.value; } else if (path.isTemplateElement()) original = path.node.value.raw; else original = ""; if (typeof node.start !== "number" || typeof node.end !== "number") return; const start = node.start + offset; const end = node.end - offset; if (start >= end) return; if (original === updated) return; return { start, end, value: path.isStringLiteral() ? jsStringEscape(updated) : updated, path }; } function handleEvalStringLiteral(path, handlerOptions, updater, handler) { const { code } = handler(path.node.value, handlerOptions); if (!code) return; const token = createEvalReplacementToken(path, code); if (token) updater.addToken(token); } function handleEvalTemplateElement(path, handlerOptions, updater, handler) { const { code } = handler(path.node.value.raw, handlerOptions); if (!code) return; const token = createEvalReplacementToken(path, code); if (token) updater.addToken(token); } function getEvalStringHandlerOptions(options) { if (options.needEscaped === false && options.generateMap === false) return options; let cached = evalHandlerOptionsCache.get(options); if (!cached) { cached = {}; evalHandlerOptionsCache.set(options, cached); } if (!cached.stringLiteralOptions) cached.stringLiteralOptions = { ...options, needEscaped: false, generateMap: false }; return cached.stringLiteralOptions; } function getEvalTemplateHandlerOptions(options) { if (options.generateMap === false) return options; let cached = evalHandlerOptionsCache.get(options); if (!cached) { cached = {}; evalHandlerOptionsCache.set(options, cached); } if (!cached.templateLiteralOptions) cached.templateLiteralOptions = { ...options, generateMap: false }; return cached.templateLiteralOptions; } function walkEvalExpression(path, options, updater, handler) { const stringHandlerOptions = getEvalStringHandlerOptions(options); const templateHandlerOptions = getEvalTemplateHandlerOptions(options); const maybeTraverse = path?.traverse; if (typeof maybeTraverse === "function") try { return maybeTraverse.call(path, { StringLiteral(innerPath) { handleEvalStringLiteral(innerPath, stringHandlerOptions, updater, handler); }, TemplateElement(innerPath) { handleEvalTemplateElement(innerPath, templateHandlerOptions, updater, handler); } }); } catch (error) { const msg = error?.message ?? ""; if (!EVAL_SCOPE_ERROR_REGEXP.test(msg)) throw error; } const getArgs = path?.get?.("arguments"); if (Array.isArray(getArgs)) { for (const arg of getArgs) { if (arg?.isStringLiteral?.()) { handleEvalStringLiteral(arg, stringHandlerOptions, updater, handler); continue; } if (arg?.isTemplateLiteral?.()) for (const quasi of arg.get("quasis")) handleEvalTemplateElement(quasi, templateHandlerOptions, updater, handler); } return; } const nodeArgs = path?.node?.arguments; if (Array.isArray(nodeArgs)) { for (const n of nodeArgs) if (n?.type === "StringLiteral") handleEvalStringLiteral({ node: n, isStringLiteral: () => true }, stringHandlerOptions, updater, handler); else if (n?.type === "TemplateLiteral" && Array.isArray(n.quasis)) for (const q of n.quasis) handleEvalTemplateElement({ node: q, isStringLiteral: () => false, isTemplateElement: () => true }, templateHandlerOptions, updater, handler); } } //#endregion //#region src/js/JsTokenUpdater.ts /** * Lightweight helper that batches updates to {@link MagicString}. * It keeps the transformation logic out of the traversal code and makes * it easier to reason about the order in which tokens are written back. */ var JsTokenUpdater = class { constructor({ value } = {}) { _defineProperty(this, "tokens", void 0); this.tokens = value ? [...value] : []; } addToken(token) { if (token) this.tokens.push(token); } push(...args) { this.tokens.push(...args); return this; } /** * 待写入的 token 数量。 */ get length() { return this.tokens.length; } map(callbackfn) { this.tokens = this.tokens.map(callbackfn); return this; } filter(callbackfn) { this.tokens = this.tokens.filter(callbackfn); return this; } updateMagicString(ms) { for (const { start, end, value } of this.tokens) ms.update(start, end, value); return ms; } }; //#endregion //#region src/js/module-graph/ignored-exports.ts var IgnoredExportsTracker = class { constructor(options) { this.options = options; _defineProperty(this, "ignoredExportNames", /* @__PURE__ */ new Map()); } addIgnoredExport(filename, exportName) { if (!exportName) return; let pending = this.ignoredExportNames.get(filename); if (!pending) { pending = /* @__PURE__ */ new Set(); this.ignoredExportNames.set(filename, pending); } if (pending.has(exportName)) return; pending.add(exportName); const existing = this.options.modules.get(filename); if (existing) this.applyIgnoredExportsToAnalysis(filename, existing.analysis); } registerIgnoredExportsFromTokens(resolved, tokens) { for (const token of tokens) if (token.type === "ImportSpecifier") this.addIgnoredExport(resolved, token.imported); else if (token.type === "ImportDefaultSpecifier") this.addIgnoredExport(resolved, "default"); } applyIgnoredExportsToAnalysis(filename, analysis) { const pending = this.ignoredExportNames.get(filename); if (!pending || pending.size === 0) return; const names = new Set(pending); pending.clear(); const propagate = []; for (const exportPath of analysis.exportDeclarations) { if (names.size === 0) break; if (exportPath.isExportDefaultDeclaration()) { if (names.has("default")) { analysis.walker.walkExportDefaultDeclaration(exportPath); names.delete("default"); } continue; } if (exportPath.isExportNamedDeclaration()) { const source = exportPath.node.source?.value; if (typeof source === "string") { for (const spec of exportPath.get("specifiers")) { if (!spec.isExportSpecifier()) continue; const exported = spec.get("exported"); let exportedName; if (exported.isIdentifier()) exportedName = exported.node.name; else if (exported.isStringLiteral()) exportedName = exported.node.value; if (!exportedName || !names.has(exportedName)) continue; const local = spec.get("local"); if (local.isIdentifier()) { propagate.push({ specifier: source, exportName: local.node.name }); names.delete(exportedName); } else if (local.isStringLiteral()) { propagate.push({ specifier: source, exportName: local.node.value }); names.delete(exportedName); } } continue; } const declaration = exportPath.get("declaration"); if (declaration.isVariableDeclaration()) for (const decl of declaration.get("declarations")) { const id = decl.get("id"); if (id.isIdentifier()) { const exportName = id.node.name; if (names.has(exportName)) { analysis.walker.walkVariableDeclarator(decl); names.delete(exportName); } } } for (const spec of exportPath.get("specifiers")) { if (!spec.isExportSpecifier()) continue; const exported = spec.get("exported"); let exportedName; if (exported.isIdentifier()) exportedName = exported.node.name; else if (exported.isStringLiteral()) exportedName = exported.node.value; if (!exportedName || !names.has(exportedName)) continue; const local = spec.get("local"); analysis.walker.walkNode(local); names.delete(exportedName); } continue; } if (exportPath.isExportAllDeclaration()) { const source = exportPath.node.source?.value; if (typeof source === "string") { for (const exportName of names) propagate.push({ specifier: source, exportName }); names.clear(); } } } for (const { specifier, exportName } of propagate) { let resolved; try { resolved = this.options.resolve(specifier, filename); } catch { resolved = void 0; } if (!resolved) { pending.add(exportName); continue; } if (this.options.filter && !this.options.filter(resolved, specifier, filename)) { pending.add(exportName); continue; } this.addIgnoredExport(resolved, exportName); } for (const name of names) pending.add(name); } }; //#endregion //#region src/js/ModuleGraph.ts var JsModuleGraph = class { constructor(entry, graphOptions) { _defineProperty(this, "modules", /* @__PURE__ */ new Map()); _defineProperty(this, "queue", []); _defineProperty(this, "resolve", void 0); _defineProperty(this, "load", void 0); _defineProperty(this, "filter", void 0); _defineProperty(this, "maxDepth", void 0); _defineProperty(this, "baseOptions", void 0); _defineProperty(this, "parserOptions", void 0); _defineProperty(this, "rootFilename", void 0); _defineProperty(this, "ignoredExports", void 0); this.resolve = graphOptions.resolve; this.load = graphOptions.load; this.filter = graphOptions.filter; this.maxDepth = graphOptions.maxDepth ?? Number.POSITIVE_INFINITY; const { moduleGraph: _moduleGraph, filename: _ignoredFilename, ...rest } = entry.handlerOptions; this.baseOptions = { ...rest, filename: entry.filename }; this.parserOptions = entry.handlerOptions.babelParserOptions; this.rootFilename = entry.filename; this.ignoredExports = new IgnoredExportsTracker({ resolve: this.resolve, filter: this.filter, modules: this.modules }); this.modules.set(entry.filename, { filename: entry.filename, source: entry.source, analysis: entry.analysis }); this.queue.push({ filename: entry.filename, depth: 0 }); } build() { this.collectDependencies(); let linked; for (const [filename, state] of this.modules) { if (filename === this.rootFilename) continue; const childOptions = { ...this.baseOptions, filename }; const code = processUpdatedSource(state.source, childOptions, state.analysis).toString(); if (code !== state.source) { if (!linked) linked = {}; linked[filename] = { code }; } } return linked; } collectDependencies() { while (this.queue.length > 0) { const { filename, depth } = this.queue.shift(); if (depth >= this.maxDepth) continue; const state = this.modules.get(filename); if (!state) continue; const dependencySpecifiers = /* @__PURE__ */ new Map(); for (const token of state.analysis.walker.imports) { if (!dependencySpecifiers.has(token.source)) dependencySpecifiers.set(token.source, []); dependencySpecifiers.get(token.source).push(token); } for (const exportPath of state.analysis.exportDeclarations) if (exportPath.isExportAllDeclaration() || exportPath.isExportNamedDeclaration()) { const source = exportPath.node.source?.value; if (typeof source === "string" && !dependencySpecifiers.has(source)) dependencySpecifiers.set(source, []); } for (const [specifier, tokens] of dependencySpecifiers) { let resolved; try { resolved = this.resolve(specifier, filename); } catch { continue; } if (!resolved) continue; if (this.filter && !this.filter(resolved, specifier, filename)) continue; if (tokens.length > 0) this.ignoredExports.registerIgnoredExportsFromTokens(resolved, tokens); if (this.modules.has(resolved)) continue; let source; try { source = this.load(resolved); } catch { continue; } if (typeof source !== "string") continue; let analysis; try { analysis = analyzeSource(babelParse(source, { ...this.parserOptions, sourceFilename: resolved }), { ...this.baseOptions, filename: resolved }); this.ignoredExports.applyIgnoredExportsToAnalysis(resolved, analysis); } catch { continue; } this.modules.set(resolved, { filename: resolved, source, analysis }); this.queue.push({ filename: resolved, depth: depth + 1 }); } } } }; //#endregion //#region src/js/node-path-walker/export-handlers.ts function walkExportDeclaration(ctx, path) { if (path.isExportDeclaration()) { if (path.isExportNamedDeclaration()) walkExportNamedDeclaration(ctx, path); else if (path.isExportDefaultDeclaration()) walkExportDefaultDeclaration(ctx, path); else if (path.isExportAllDeclaration()) walkExportAllDeclaration(ctx, path); } } function walkExportNamedDeclaration(ctx, path) { const declaration = path.get("declaration"); if (declaration.isVariableDeclaration()) for (const decl of declaration.get("declarations")) ctx.walkNode(decl); const specifiers = path.get("specifiers"); for (const spec of specifiers) if (spec.isExportSpecifier()) { const local = spec.get("local"); if (local.isIdentifier()) ctx.walkNode(local); } } function walkExportDefaultDeclaration(ctx, path) { const decl = path.get("declaration"); if (decl.isIdentifier()) ctx.walkNode(decl); else ctx.walkNode(decl); } function walkExportAllDeclaration(ctx, path) { const source = path.get("source"); if (source.isStringLiteral()) ctx.addImportToken({ declaration: path, source: source.node.value, type: "ExportAllDeclaration" }); } //#endregion //#region src/js/node-path-walker/import-tokens.ts function maybeAddImportToken(imports, arg) { if (!(arg.isImportSpecifier() && arg.node.importKind !== "type" || arg.isImportDefaultSpecifier())) return false; const importDeclaration = arg.parentPath; if (!importDeclaration.isImportDeclaration() || importDeclaration.node.importKind === "type") return false; if (arg.isImportSpecifier()) { const imported = arg.get("imported"); if (imported.isIdentifier()) imports.add({ declaration: importDeclaration, specifier: arg, imported: imported.node.name, local: arg.node.local.name, source: importDeclaration.node.source.value, type: "ImportSpecifier" }); return true; } imports.add({ declaration: importDeclaration, specifier: arg, local: arg.node.local.name, source: importDeclaration.node.source.value, type: "ImportDefaultSpecifier" }); return true; } //#endregion //#region src/js/NodePathWalker.ts const EMPTY_IGNORE_CALL_EXPRESSION_IDENTIFIERS = []; const EMPTY_IMPORT_TOKENS = /* @__PURE__ */ new Set(); function NOOP_STRING_PATH_CALLBACK() {} const NEVER_MATCH_NAME = () => false; /** * 遍历我们关注的调用表达式所关联的绑定,收集后续需要转换的字符串节点。 */ var NodePathWalker = class { constructor({ ignoreCallExpressionIdentifiers, callback } = {}) { _defineProperty(this, "ignoreCallExpressionIdentifiers", void 0); _defineProperty(this, "callback", void 0); _defineProperty(this, "isIgnoredCallIdentifier", void 0); _defineProperty(this, "hasIgnoredCallIdentifiers", void 0); _defineProperty(this, "importsStore", void 0); _defineProperty(this, "visitedStore", void 0); this.hasIgnoredCallIdentifiers = Boolean(ignoreCallExpressionIdentifiers && ignoreCallExpressionIdentifiers.length > 0); this.ignoreCallExpressionIdentifiers = ignoreCallExpressionIdentifiers ?? EMPTY_IGNORE_CALL_EXPRESSION_IDENTIFIERS; this.callback = callback ?? NOOP_STRING_PATH_CALLBACK; this.isIgnoredCallIdentifier = this.hasIgnoredCallIdentifiers ? createNameMatcher(this.ignoreCallExpressionIdentifiers, { exact: true }) : NEVER_MATCH_NAME; } get imports() { return this.importsStore ?? EMPTY_IMPORT_TOKENS; } getWritableImports() { if (!this.importsStore) this.importsStore = /* @__PURE__ */ new Set(); return this.importsStore; } addImportToken(token) { this.getWritableImports().add(token); } getVisited() { if (!this.visitedStore) this.visitedStore = /* @__PURE__ */ new WeakSet(); return this.visitedStore; } walkVariableDeclarator(path) { const init = path.get("init"); this.walkNode(init); } walkTemplateLiteral(path) { for (const exp of path.get("expressions")) this.walkNode(exp); for (const quasis of path.get("quasis")) this.callback(quasis); } walkStringLiteral(path) { this.callback(path); } walkBinaryExpression(path) { const left = path.get("left"); this.walkNode(left); const right = path.get("right"); this.walkNode(right); } walkLogicalExpression(path) { const left = path.get("left"); this.walkNode(left); const right = path.get("right"); this.walkNode(right); } walkObjectExpression(path) { const props = path.get("properties"); for (const prop of props) if (prop.isObjectProperty()) { const key = prop.get("key"); this.walkNode(key); const value = prop.get("value"); this.walkNode(value); } } walkArrayExpression(path) { const elements = path.get("elements"); for (const element of elements) this.walkNode(element); } walkNode(arg) { const visited = this.getVisited(); if (visited.has(arg)) return; visited.add(arg); if (arg.isIdentifier()) { const binding = arg?.scope?.getBinding?.(arg.node.name); if (binding) this.walkNode(binding.path); } else if (arg.isMemberExpression()) { const objectPath = arg.get("object"); if (objectPath.isIdentifier()) { const binding = arg?.scope?.getBinding?.(objectPath.node.name); if (binding) { if (binding.path.isVariableDeclarator()) this.walkVariableDeclarator(binding.path); } } } else if (arg.isTemplateLiteral()) this.walkTemplateLiteral(arg); else if (arg.isStringLiteral()) this.walkStringLiteral(arg); else if (arg.isBinaryExpression()) this.walkBinaryExpression(arg); else if (arg.isLogicalExpression()) this.walkLogicalExpression(arg); else if (arg.isObjectExpression()) this.walkObjectExpression(arg); else if (arg.isArrayExpression()) this.walkArrayExpression(arg); else if (arg.isVariableDeclarator()) this.walkVariableDeclarator(arg); else if (maybeAddImportToken(this.getWritableImports(), arg)) {} } /** * Walk the arguments of a desired call expression so their bindings can be analysed. */ walkCallExpression(path) { if (!this.hasIgnoredCallIdentifiers) return; const calleePath = path.get("callee"); if (calleePath.isIdentifier() && this.isIgnoredCallIdentifier(calleePath.node.name)) for (const arg of path.get("arguments")) this.walkNode(arg); } walkExportDeclaration(path) { walkExportDeclaration(this, path); } walkExportNamedDeclaration(path) { walkExportNamedDeclaration(this, path); } walkExportDefaultDeclaration(path) { walkExportDefaultDeclaration(this, path); } walkExportAllDeclaration(path) { walkExportAllDeclaration(this, path); } }; //#endregion //#region src/js/taggedTemplateIgnore.ts function createTaggedTemplateIgnore({ matcher, names }) { const bindingIgnoreCache = /* @__PURE__ */ new Map(); const taggedTemplateIgnoreCache = /* @__PURE__ */ new WeakMap(); const seenBindings = /* @__PURE__ */ new Set(); let singleCanonicalIgnoreName; let canonicalIgnoreNames; for (const item of names ?? []) { if (typeof item !== "string") continue; if (singleCanonicalIgnoreName === void 0) { singleCanonicalIgnoreName = item; continue; } if (item === singleCanonicalIgnoreName) continue; if (!canonicalIgnoreNames) { canonicalIgnoreNames = /* @__PURE__ */ new Set([singleCanonicalIgnoreName, item]); continue; } canonicalIgnoreNames.add(item); } const hasCanonicalIgnoreNames = singleCanonicalIgnoreName !== void 0; const matchesIgnoreName = (value) => { if (hasCanonicalIgnoreNames) { if (canonicalIgnoreNames) { if (canonicalIgnoreNames.has(value)) return true; } else if (value === singleCanonicalIgnoreName) return true; } return matcher(value); }; const propertyMatches = (propertyPath) => { if (!propertyPath) return false; if (propertyPath.isIdentifier()) return matchesIgnoreName(propertyPath.node.name); if (propertyPath.isStringLiteral()) return matchesIgnoreName(propertyPath.node.value); return false; }; const resolvesMemberExpressionToIgnore = (path, seen) => { const propertyPath = path.get("property"); if (propertyMatches(propertyPath)) return true; const objectPath = path.get("object"); if (objectPath.isIdentifier()) { const binding = (objectPath?.scope)?.getBinding?.(objectPath.node.name); if (binding) return resolvesToWeappTwIgnore(binding, seen); } return false; }; const resolvesToWeappTwIgnore = (binding, seen) => { const cached = bindingIgnoreCache.get(binding); if (cached !== void 0) return cached; if (seen.has(binding)) return false; seen.add(binding); let result = false; const bindingPath = binding.path; if (bindingPath.isImportSpecifier()) { const imported = bindingPath.node.imported; if (imported.type === "Identifier" && matchesIgnoreName(imported.name)) result = true; else if (imported.type === "StringLiteral" && matchesIgnoreName(imported.value)) result = true; } else if (bindingPath.isVariableDeclarator()) { const init = bindingPath.get("init"); if (init && init.node) { if (init.isIdentifier()) { const target = binding?.scope?.getBinding?.(init.node.name); if (target) result = resolvesToWeappTwIgnore(target, seen); } else if (init.isMemberExpression()) result = resolvesMemberExpressionToIgnore(init, seen); } } bindingIgnoreCache.set(binding, result); seen.delete(binding); return result; }; const getEffectiveTagPath = (tagPath) => { let current = tagPath; while (true) { if (current.isParenthesizedExpression?.() || current.node.type === "ParenthesizedExpression") { current = current.get("expression"); continue; } if (current.isTSAsExpression() || current.isTSTypeAssertion()) { current = current.get("expression"); continue; } if (current.isTSNonNullExpression()) { current = current.get("expression"); continue; } if (current.isTypeCastExpression?.()) { current = current.get("expression"); continue; } if (current.isSequenceExpression()) { const last = current.get("expressions").at(-1); if (last) { current = last; continue; } } if (current.isCallExpression?.() || current.node.type === "CallExpression") { current = current.get("callee"); continue; } break; } return current; }; const evaluateTagPath = (tagPath, seen) => { if (tagPath.isCallExpression?.() || tagPath.node.type === "CallExpression") { const calleePath = tagPath.get("callee"); return evaluateTagPath(calleePath, seen); } if (tagPa