Skip to content

创建生成上下文

生成代码时我们需要将生成的代码拼接成字符串,同时添加换行缩进等。

function createCodegeContext() {
  const context = {
    code: ``,
    indentLevel: 0,
    helper(key) {
      return `_${helperNameMap[key]}`;
    },
    push(code) {
      context.code += code;
    },
    indent() {
      // 前进
      newline(++context.indentLevel);
    },
    deindent(withoutnewline = false) {
      // 缩进
      if (withoutnewline) {
        --context.indentLevel;
      } else {
        newline(--context.indentLevel);
      }
    },
    newline() {
      newline(context.indentLevel);
    }, // 换行
  };
  function newline(n) {
    context.push("\n" + `  `.repeat(n));
  }
  return context;
}
function generate(ast) {
  const context = createCodegeContext();
}
export function compile(template) {
  // 1.将模板转化成ast语法树
  const ast = baseParse(template);
  // 2.对ast语法树进行转化
  transform(ast);
  // // 3.生成代码
  return generate(ast);
}

生成文本代码

let r = compile("hello, jw");

如果仅仅是文本直接返回即可~

function genText(node, context) {
  context.push(JSON.stringify(node.content)); // 添加文本代码
}
function genFunctionPreamble(ast, context) {
  // 生成函数
  const { push, newline } = context;
  if (ast.helpers.length > 0) {
    // 生成导入语句
    push(
      `const {${ast.helpers
        .map((s) => `${helperNameMap[s]}:_${helperNameMap[s]}`)
        .join(", ")}} = Vue`
    );
  }
  newline();
  push(`return `);
}
function genNode(node, context) {
  switch (node.type) {
    case NodeTypes.TEXT:
      genText(node, context);
      break;
  }
}
function generate(ast) {
  const context = createCodegeContext();
  const { push, indent } = context;
  genFunctionPreamble(ast, context);
  const functionName = "render";
  const args = ["_ctx", "$props"];
  push(`function ${functionName}(${args.join(", ")}){`);
  indent();
  push(`return `);
  if (ast.codegenNode) {
    genNode(ast.codegenNode, context);
  } else {
    push(`null`);
  }
  deindent();
  push(`}`);
  return context.code;
}

生成表达式代码

let r = compile("{{age}}");
function genExpression(node, context) {
  const { content } = node;
  context.push(content);
}
function genInterpolation(node, context) {
  const { push, helper } = context;
  push(`${helper(TO_DISPLAY_STRING)}(`);
  genNode(node.content, context);
  push(`)`);
}
function genNode(node, context) {
  switch (node.type) {
    case NodeTypes.TEXT:
      genText(node, context);
      break;
    case NodeTypes.INTERPOLATION: // 生成表达式
      genInterpolation(node, context);
      break;
    case NodeTypes.SIMPLE_EXPRESSION: // 简单表达式的处理
      genExpression(node, context);
      break;
  }
}

生成元素表达式

let r = compile(`<div a='1' b='2'>123</div>`);
function genNodeList(nodes, context) {
  // 生成节点列表,用","分割
  const { push } = context;
  for (let i = 0; i < nodes.length; i++) {
    const node = nodes[i];
    if (isString(node)) {
      push(`${node}`); // 如果是字符串直接放入
    } else if (Array.isArray(node)) {
      genNodeList(node, context);
    } else {
      genNode(node, context);
    }
    if (i < nodes.length - 1) {
      push(", ");
    }
  }
}
function genVNodeCall(node, context) {
  const { push, helper } = context;
  const { tag, props, children, isBlock } = node;

  if (isBlock) {
    push(`(${helper(OPEN_BLOCK)}(),`);
  }
  // 生成createElementBlock或者createElementVnode
  const callHelper = isBlock ? CREATE_ELEMENT_BLOCK : CREATE_ELEMENT_VNODE;
  push(helper(callHelper));
  push("(");
  genNodeList(
    [tag, props, children].map((item) => item || "null"),
    context
  );
  push(`)`);
  if (isBlock) {
    push(`)`);
  }
}
function genNode(node, context) {
  switch (node.type) {
    case NodeTypes.VNODE_CALL: // 元素调用
      genVNodeCall(node, context);
      break;
  }
}

生成元素属性

function genObjectExpression(node, context) {
  const { push, newline } = context;
  const { properties } = node;
  if (!properties.length) {
    push(`{}`);
    return;
  }
  push("{");
  for (let i = 0; i < properties.length; i++) {
    const { key, value } = properties[i];
    // key
    push(key);
    push(`: `);
    push(JSON.stringify(value));
    // value
    if (i < properties.length - 1) {
      push(`,`);
    }
  }
  push("}");
}
function genNode(node, context) {
  switch (node.type) {
    case NodeTypes.JS_OBJECT_EXPRESSION: // 元素属性
      genObjectExpression(node, context);
      break;
  }
}

处理 Fragment 情况

如果是 Fragment,则将字符串和 symbol 直接放入代码中,对元素再次进行递归处理

let r = compile(`<div>123</div><div>123</div>`);
function genNode(node, context) {
  if (isString(node)) {
    context.push(node);
    return;
  }
  if (isSymbol(node)) {
    context.push(context.helper(node));
    return;
  }
  switch (node.type) {
    // ...
    case NodeTypes.ELEMENT:
      genNode(node.codegenNode, context);
  }
}

处理复合表达式

let r = compile(`<div>123 {{abc}}</div>`);
function genCompoundExpression(node,context) {
    for (let i = 0; i < node.children!.length; i++) {
        const child = node.children![i]
        if (isString(child)) {
            context.push(child)
        } else {
            genNode(child, context)
        }
    }
}
function genNode(node,context){
    switch(node.type){
        // ...
        case NodeTypes.COMPOUND_EXPRESSION:
            genCompoundExpression(node, context)
            break
    }
}

复合表达式+元素处理

let r = compile(`<div>123 {{name}} <span>{{name}}</span></div>`);
function genCallExpression(node, context) {
  const { push, helper } = context;
  const callee = helper(node.callee);

  push(callee + `(`, node);
  genNodeList(node.arguments, context);
  push(`)`);
}
function genNode(node, context) {
  switch (node.type) {
    // ...
    case NodeTypes.TEXT_CALL: // 对文本处理
      genNode(node.codegenNode, context);
      break;
    case NodeTypes.JS_CALL_EXPRESSION: // 表达式处理
      genCallExpression(node, context);
      break;
  }
}

Released under the MIT License.