元素转换+代码生成
元素转换补充内容
// 创建节点的标识
const CREATE_VNODE = Symbol('CREATE_VNODE')
// 创建一个虚拟节点给codegenNode用
function createVnodeCall(
ctx,
vnodeTag,
vnodeProps,
vnodeChildren,
vnodePatchFlg,
){
// 标识一下创建vnode的节点
ctx.setHelp(CREATE_VNODE)
return {
type: NodeTypes.VNODE_CALL, // 节点本身的类型
tag: vnodeTag,
props: vnodeProps,
children: vnodeChildren,
patchFlg: vnodePatchFlg,
}
}
function transformElement(node, ctx){
// 转换器1 处理元素的
// 需要筛选一下这个是处理元素
// 想在整颗树处理完之后再去处理这个。
if(node.type != NodeTypes.ELEMENT){
// 代表元素节点 此节点是元素 1
// 如果不是1就不处理了
return
}
// coding
return () => { // 这种函数叫做退出函数,洋葱模型 最里面的限制性 逻辑是 parent 返回fn child 返回fn sunzi 返回fn 执行的时候从内向外。执行 【element】【test】 执行 【test】【element】
console.log('处理元素的')
// createVnode('h1', {}, 'hello')
// tag 标签名字
// children 孩子
const {tag, children} = node
const vnodeTag = `'${tag}'`
const vnodeProps = undefined
let vnodeChildren = children
let patchFlg = 0
let vnodePatchFlg = 0
// 儿子的处理
if(vnodeChildren.length > 0) {
if(vnodeChildren.length === 1){
const child = vnodeChildren[0]
// 判断是不是动态节点如果是动态节点需要给当前的父标签加一个动态类型。
const type = child.type
const hasDynNode = false
if(type == NodeTypes.COMPOUND_EXPERSION || type == NodeTypes.INTERPOLATION) {
// 含有动态节点。
hasDynNode = true
}
if(hasDynNode) {
patchFlg |= PatchFlgs.TEXT
}
vnodeChildren = child // 就一个孩子直接赋值过来行了
} else{
// 多个孩子的情况
vnodeChildren = children
}
}
if(patchFlg !== 0) {
// 说明有动态节点。
vnodePatchFlg = `'${patchFlg}'`
}
// 给当前节点加一个codegen 如何动态生成代码。
node.codegenNode = createVnodeCall(
ctx,
vnodeTag,
vnodeProps,
vnodeChildren,
vnodePatchFlg,
)
}
}
// 修改 traversNode 方法的switch
function traversNode(node, ctx){
// 遍历 node 这个玩意需要调用 ctx这里面的CL进行转换
const {CL} = ctx
// 定位一下当前node
ctx.currentNode = node
// 把所有的nodes里面的节点都传入到CL这个集合的方法中
const existsAy = []
CL.forEach(cb => { // CL 的顺序一定要放对了影响回调顺序
// 洋葱模型这里可以拿到返回的函数。收集起来从后向前执行
const onExit = cb(node, ctx)
onExit && existsAy.push(onExit)
})
const TO_DISPLAY_STRING = symbol("TO_DISPLAY_STRING")
// 如果有儿子就遍历起来
switch(node.type) {
case NodeTypes.ROOT:
case NodeTypes.ELEMENT:
// 跟节点和元素节点可能有儿子需要拿出来遍历儿子在调用这个方法
traversChildren(node, ctx)
case NodeTypes.INTERPOLATION:
ctx.setHelp(TO_DISPLAY_STRING) // 加了一种处理标识
}
// 执行回调从内到外依此执行。
ctx.currentNode = node
for(let i = existsAy.length-1
existsAy[i]()
// 注意currentNode的指向问题
// 一层层的进来出来的时候current指向的是最层那个内容。
// 为了保证退出的方法对应的current是正确的
}
}
const OPEN_BLOCK = symbol('OPEN_BLOCK')
const FRAGMENT = symbol('FRAGMENT')
const CREATE_BLOCK = symbol('CREATE_BLOCK')
// 转换的时候需要根元素处理下
function createRootCodeGen(ast, ctx){
const {setHelp} = ctx
const children = ast.children
setHelp(OPEN_BLOCK)
setHelp(CREATE_BLOCK)
if(children.length == 1) {
const child = chiildren[0]
// 用child作为根
const codegen = child.codegenNode
codegen.isBlock = true
ast.codegenNode = codegen
} else if(children.length > 1) {
// 生成一个fragment来包裹。
ast.codegenNode = createVnodeCall(
ctx,
setHelp(FRAGMENT),
undefined,
children,
PatchFLgs.STABLE_FRAGMENT
)
ast.codegenNode.isBlock = true
}
}
export function transform(ast = {}, {CL}) { // -1
// 创建AST的时候有个解析上下文 createParserContext
// 转换的时候可以创建一个转换上下文
const ctx = createTransformContext(ast, CL)
// 有了转换上下文开始遍历。
// 遍历根据是否有孩子有就继续递归
// 遍历节点
// 没有上下文一堆参数需要在这里传递。
traversNode(ast, ctx)
// 根节点的处理在最外边包裹一层。
// 如果有多个跟节点需要用Fragment套一层
// <div></div>
// <div></div>
createRootCodeGen(ast, ctx)
ast.helpers = [...ctx.helpers] // 把ctx的属性挂到 节点上否则generate拿不到。
}
开始生成代码
const helperNameMap = {
[FRAGMENT]: 'Fragment' // FRAGMENT 对应的symbol值拿的symbol做的key NodeTypes的那一批
.....
}
function createGenerateCtx(ast){
const newLine = (n) => {
ctx.push('\r\n' + ' '.repeat(n));
}
const ctx = {
code: ``, // 拼的结果。
indentLevel: 0, // 代码的缩紧要不太丑了。 0就是没有锁进
setindentLevel(){
newLine(++ctx.indentLevel)
},
helper(key){
return `${helperNameMap[key]}`;
},
push(c){
ctx.code += c
},
deindent(){
newLine(--ctx.indentLevel)
},
newline(){
newLine(ctx.indentLevel);
}
};
return ctx;
}
function genVNodeCall(codegen, ctx){
const {push, newline,setindentLevel,deindent,helper} = ctx;
const {tag,children,props,patchFlgs,isBlock} = codegen;
if(isBlock) {
push(`(${helper(OPEN_BLOCK)}(),`)
}
}
function genNode(codegen, ctx){
switch(node.type){
case NodeTypes.ELEMENT:;break;
case NodeTypes.TEXT:;break;
case NodeTypes.INTERPOLATION:;break;
case NodeTypes.SIMPLE_EXPERSION:;break;
case NodeTypes.COMPOUND_EXPERSION:;break;
case NodeTypes.TEXT_CALL:;break;
case NodeTypes.VNODE_CALL:
genVNodeCall(codegen, ctx);
;break;
case NodeTypes.JS_CALL_EXPERSION:;break;
}
}
function generate(ast){
const ctx = createGenerateCtx(ast);
const {push, newline,setindentLevel,deindent} = ctx;
push(`const _Vue = vue`);
newline();
push(`return function render(_ctx){`);
setindentLevel();
push(`with(_ctx){`)
setindentLevel();
push(
`const {${ast.helpers.map(s=> `${helperNameMap[s]}`).join(',')}} = _Vue`
)
newline();
push(`稍后继续`);
genNode(ast.codegenNode,ctx);
deindent();
push(`}`)
deindent();
push(`}`)
return ctx.code;
}
export function buildCompiler(template) {
const ast = baseParse(template);
const CL = getBaseTransformPreset();
transform(ast, {CL});
return generate(ast)
}
最终对代码new Function 实现。