@mpxjs/webpack-plugin
Version:
mpx compile core
128 lines (121 loc) • 3.77 kB
JavaScript
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)
}