UNPKG

@mpxjs/webpack-plugin

Version:

mpx compile core

128 lines (121 loc) 3.77 kB
class Node { constructor (type, condition = null) { this.type = type // 'If', 'ElseIf', 'Else' 或 'Text' this.condition = condition // If 或 Elif 的条件 this.children = [] this.value = '' } } // 提取 css string 为 token function tokenize (cssString) { const regex = /\/\*\s*@mpx-(if|elif|else|endif)(?:\s*\((.*?)\))?\s*\*\//g const tokens = [] let lastIndex = 0 let match while ((match = regex.exec(cssString)) !== null) { // 如果 token 前有普通文本,生成文本 token if (match.index > lastIndex) { const text = cssString.substring(lastIndex, match.index) tokens.push({ type: 'text', content: text }) } // match[1] 为关键字:if, elif, else, endif // match[2] 为条件(如果存在) tokens.push({ type: match[1], // 'if'、'elif'、'else' 或 'endif' condition: match[2] ? match[2].trim() : null }) lastIndex = regex.lastIndex } // 处理结尾剩余的文本 if (lastIndex < cssString.length) { const text = cssString.substring(lastIndex) tokens.push({ type: 'text', content: text }) } return tokens } // parse:将生成的 token 数组构造成嵌套的 AST function parse (cssString) { const tokens = tokenize(cssString) const ast = [] const nodeStack = [] let currentChildren = ast tokens.forEach(token => { if (token.type === 'text') { const node = new Node('Text') node.value = token.content currentChildren.push(node) } else if (token.type === 'if') { const node = new Node('If', token.condition) currentChildren.push(node) nodeStack.push(currentChildren) currentChildren = node.children } else if (token.type === 'elif') { if (nodeStack.length === 0) { throw new Error('elif without a preceding if') } currentChildren = nodeStack[nodeStack.length - 1] const node = new Node('ElseIf', token.condition) currentChildren.push(node) currentChildren = node.children } else if (token.type === 'else') { if (nodeStack.length === 0) { throw new Error('else without a preceding if') } currentChildren = nodeStack[nodeStack.length - 1] const node = new Node('Else') currentChildren.push(node) currentChildren = node.children } else if (token.type === 'endif') { if (nodeStack.length > 0) { currentChildren = nodeStack.pop() } } }) return ast } function evaluateCondition (condition, defs) { try { const keys = Object.keys(defs) const values = keys.map(key => defs[key]) /* eslint-disable no-new-func */ const func = new Function(...keys, `return (${condition});`) return func(...values) } catch (e) { console.error(`Error evaluating condition: ${condition}`, e) return false } } function traverseAndEvaluate (ast, defs) { let output = '' let batchedIf = false function traverse (nodes) { for (const node of nodes) { if (node.type === 'Text') { output += node.value } else if (node.type === 'If') { // 直接判断 If 节点 batchedIf = false if (evaluateCondition(node.condition, defs)) { traverse(node.children) batchedIf = true } } else if (node.type === 'ElseIf' && !batchedIf) { if (evaluateCondition(node.condition, defs)) { traverse(node.children) batchedIf = true } } else if (node.type === 'Else' && !batchedIf) { traverse(node.children) } } } traverse(ast) return output } module.exports = function (css) { this.cacheable() const mpx = this.getMpx() const defs = mpx.defs const ast = parse(css) return traverseAndEvaluate(ast, defs) }