@mpxjs/core
Version:
mpx runtime core
290 lines (256 loc) • 8.53 kB
JavaScript
import cssSelect from './css-select'
// todo: stringify wxs 模块只能放到逻辑层执行,主要还是因为生成 vdom tree 需要根据 class 去做匹配,需要看下这个代码从哪引入
import stringify from '@mpxjs/webpack-plugin/lib/runtime/stringify.wxs'
import Interpreter from './interpreter'
import { dash2hump, isString, error } from '@mpxjs/utils'
const deepCloneNode = function (val) {
return JSON.parse(JSON.stringify(val))
}
function simpleNormalizeChildren (children) {
for (let i = 0; i < children.length; i++) {
if (Array.isArray(children[i])) {
return Array.prototype.concat.apply([], children)
}
}
return children
}
export default function _genVnodeTree (astData, contextScope, options) {
const { template = {}, styles = [] } = astData || {}
const { moduleId, location } = options || {}
// 解除引用
const templateAst = deepCloneNode(template)
// 获取实例 uid
const uid = contextScope[0]?.__mpxProxy?.uid || contextScope[0]?.uid
// 动态化组件 slots 通过上下文传递,相当于 props
const slots = contextScope[0]?.slots || {}
function createEmptyNode () {
return createNode('block')
}
function genVnodeTree (node) {
if (node.type === 1) {
// wxs 模块不需要动态渲染
if (node.tag === 'wxs') {
return createEmptyNode()
} else if (node.for && !node.forProcessed) {
return genFor(node)
} else if (node.if && !node.ifProcessed) {
return genIf(node)
} else if (node.tag === 'slot') {
return genSlot(node)
} else {
const data = genData(node)
let children = genChildren(node)
// 运行时组件的子组件都通过 slots 属性传递,样式规则在当前组件内匹配后挂载
if (node.dynamic) {
data.slots = resolveSlot(children.map(item => genVnodeWithStaticCss(deepCloneNode(item))))
children = []
}
return createNode(node.tag, data, children)
}
} else if (node.type === 3) {
return genText(node)
}
}
function evalExps (exps) {
const interpreter = new Interpreter(contextScope)
// 消除引用关系
let value
try {
value = interpreter.eval(JSON.parse(JSON.stringify(exps)))
} catch (e) {
const errmsg = e.message
console.warn(errmsg)
error('interprete the expression wrong: ', location, {
errType: 'mpx-dynamic-interprete',
errmsg
})
}
return value
}
function createNode (tag, data = {}, children = []) {
if (Array.isArray(data)) {
children = data
data = {}
}
if (typeof tag === 'object') {
return tag
}
// 处理 for 循环产生的数组,同时清除空节点
children = simpleNormalizeChildren(children).filter(node => !!node?.nt)
return {
nt: tag,
d: data,
c: children
}
}
/**
*
* 样式隔离的匹配策略优化:
*
* 条件1: 子组件不能影响到父组件的样式
* 条件2: slot 的内容必须在父组件的上下文当中完成样式匹配
* 条件3: 匹配过程只能进行一次
*
* 方案一:根据 moduleId 即作用域来进行匹配
* 方案二:根据虚拟树来进行匹配
*/
// function createDynamicNode (moduleId, data = {}, children = []) {
// const { template = {}, styles = [] } = staticMap[moduleId]
// data.$slots = resolveSlot(children) // 将 slot 通过上下文传递到子组件的渲染流程中
// const vnodeTree = _genVnodeTree(template, [data], styles, moduleId)
// return vnodeTree
// }
function resolveSlot (children) {
const slots = {}
if (children.length) {
for (let i = 0; i < children.length; i++) {
const child = children[i]
const name = child.d?.slot
if (name) {
const slot = (slots[name] || (slots[name] = []))
if (child.tag === 'template') {
slot.push.apply(slot, child.children || [])
} else {
slot.push(child)
}
} else {
(slots.default || (slots.default = [])).push(child)
}
}
}
return slots
}
function genData (node) {
const res = {
uid,
moduleId
}
if (!node.attrsList) {
return res
}
node.attrsList.forEach((attr) => {
if (attr.name === 'class' || attr.name === 'style') {
// class/style 的表达式为数组形式,class/style的计算过程需要放到逻辑层,主要是因为有逻辑匹配的过程去生成 vnodeTree
const helper = attr.name === 'class' ? stringify.c : stringify.s
let value = ''
if (attr.__exp) {
let valueArr = evalExps(attr.__exp)
valueArr = Array.isArray(valueArr) ? valueArr : [valueArr]
value = helper(...valueArr)
// dynamic style + wx:show
const showStyle = valueArr[2]
if (showStyle) {
value = value + ';' + showStyle
}
} else {
value = attr.value
}
res[attr.name] = value
} else {
res[dash2hump(attr.name)] = attr.__exp
? evalExps(attr.__exp)
: attr.value
}
})
return res
}
function genChildren (node) {
const res = []
const children = node.children || []
if (children.length) {
children.forEach((item) => {
res.push(genNode(item))
})
}
return res
}
function genNode (node) {
if (node.type === 1) {
return genVnodeTree(node)
} else if (node.type === 3 && node.isComment) {
return ''
// TODO: 注释暂不处理
// return _genComment(node)
} else {
return genText(node) // 文本节点统一通过 _genText 来生成,type = 2(带有表达式的文本,在 mpx 统一处理为了3) || type = 3(纯文本,非注释)
}
}
function genText (node) {
return {
nt: '#text',
ct: node.__exp ? evalExps(node.__exp) : node.text
}
}
function genFor (node) {
node.forProcessed = true
const itemKey = node.for.item || 'item'
const indexKey = node.for.index || 'index'
const scope = {
[itemKey]: null,
[indexKey]: null
}
const forExp = node.for
const res = []
let forValue = evalExps(forExp.__exp)
// 和微信的模版渲染策略保持一致:当 wx:for 的值为字符串时,会将字符串解析成字符串数组
if (isString(forValue)) {
forValue = forValue.split('')
}
if (Array.isArray(forValue)) {
forValue.forEach((item, index) => {
// item、index 模板当中如果没申明,需要给到默认值
scope[itemKey] = item
scope[indexKey] = index
contextScope.push(scope)
// 针对 for 循环避免每次都操作的同一个 node 导致数据的污染的问题
res.push(deepCloneNode(genVnodeTree(deepCloneNode(node))))
contextScope.pop()
})
}
return res
}
// 对于 if 而言最终生成 <= 1 节点
function genIf (node) {
if (!node.ifConditions) {
node.ifConditions = []
return {} // 一个空节点
}
node.ifProcessed = true
const ifConditions = node.ifConditions.slice()
let res = {} // 空节点
for (let i = 0; i < ifConditions.length; i++) {
const condition = ifConditions[i]
// 非 else 节点
if (condition.ifExp) {
const identifierValue = evalExps(condition.__exp)
if (identifierValue) {
res = genVnodeTree(condition.block === 'self' ? node : condition.block)
break
}
} else { // else 节点
res = genVnodeTree(condition.block)
break
}
}
return res
}
// 暂时不支持作用域插槽
function genSlot (node) {
const data = genData(node) // 计算属性值
const slotName = data.name || 'default'
return slots[slotName] || null
}
function genVnodeWithStaticCss (vnodeTree) {
styles.forEach((item) => {
const [selector, style] = item
const nodes = cssSelect(selector, { moduleId })(vnodeTree)
nodes?.forEach((node) => {
// todo style 合并策略问题:合并过程中缺少了权重关系 style, class 的判断,需要优化
node.d.style = node.d.style ? style + node.d.style : style
})
})
return vnodeTree
}
const interpreteredVnodeTree = genVnodeTree(templateAst)
return genVnodeWithStaticCss(interpreteredVnodeTree)
}