likes
comments
collection
share

解析vue-simple-compiler

作者站长头像
站长
· 阅读数 30
import fs from 'fs';
import { compileTemplate } from 'vue/compiler-sfc'
import { compile } from 'vue-simple-compiler'

fs.readFile('index.vue', (err, data) => {
    const {js, css} = compile(data.toString(), {
        filename: 'foo.vue',
        autoImportCss: true,
        autoResolveImports: true,
        isProd: true
      })
    fs.writeFile('index2.js', js.code, () => {})
})

开始打断点,分析compile方法做了什么。

进入到compile方法,首先出现的是一个这个东西。

var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));

这就是一个给__createBinding赋值的操作,现在断点,走到这来了,应该是调用了__createBinding这个函数。我们现在看一下,__createBinding是在哪,被谁调用的。

我看了一下,下面讲解的,是如何使用模块化的东西,不是今天的重点,我就不再探究了。

接下来,直接上正式代码。

const compile = (source, options) => {
    const context = (0, context_1.createContext)(source, options);
    // get the code structure
    const { descriptor, errors: mainCompilerErrors } = (0, compiler_sfc_1.parse)(source, {
        filename: context.filename,
    });
    if (mainCompilerErrors.length) {
        return getErrorResult(mainCompilerErrors, context.destFilename);
    }
    // get the features
    (0, context_1.resolveFeatures)(descriptor, context);
    const { result: scriptResult, errors: scriptErrors } = (0, script_1.resolveScript)(descriptor, context);
    const { result: templateResult, errors: templateErrors } = (0, template_1.resolveTemplate)(descriptor, context);
    const { files: cssFiles, importList: cssImportList, errors: styleErrors, } = (0, style_1.resolveStyles)(descriptor, context);
    const errors = [
        ...mainCompilerErrors,
        ...(scriptErrors ?? []),
        ...(templateErrors ?? []),
        ...(styleErrors ?? []),
    ];
    if (errors.length ||
        !scriptResult ||
        !templateResult ||
        !cssFiles ||
        !cssImportList) {
        return getErrorResult(errors, context.destFilename);
    }
    // No source map update technically.
    const jsCode = context.options?.autoResolveImports
        ? (0, transform_1.resolveImports)(scriptResult.code, context.options)
        : scriptResult.code;
    const initialSourceMap = {
        version: '3',
        file: context.destFilename,
        sources: [context.filename],
        names: [],
        mappings: '',
        sourcesContent: [source],
    };
    // assemble the final code
    const finalTransformedResult = (0, map_1.bundleSourceMap)([
        { code: cssImportList.join('\n'), sourceMap: initialSourceMap },
        { code: jsCode, sourceMap: scriptResult.sourceMap },
        { code: templateResult.code, sourceMap: templateResult.sourceMap },
        { code: context.addedCodeList.join('\n') },
        {
            code: context.addedProps
                .map(([key, value]) => `${constants_1.COMP_ID}.${key} = ${value}`)
                .join('\n'),
        },
        { code: `export default ${constants_1.COMP_ID}` },
    ]);
    return {
        js: {
            filename: context.destFilename,
            code: finalTransformedResult.code,
            sourceMap: finalTransformedResult.sourceMap,
        },
        css: cssFiles,
        externalJs: context.externalJsList,
        externalCss: context.externalCssList,
        errors: [],
    };
};

这段代码代码有点长,我们慢慢看。但是可以明确的就是,我们在代码里面调用的compile方法,就是这个地方定义的。

const context = (0, context_1.createContext)(source, options);

第一行,调用了一个函数,叫做createContext,看名字,有点像使用状态机的方式去处理我给到库的代码的。很明显,这个函数会创建一个context,具体这个context长啥样,我们来看看。

{
    isProd: true,
    hmr: false,
    root: "@anonymous",
    filename: "foo.vue",
    fullpath: "@anonymous\\foo.vue",
    id: "3f328616",
    destFilename: "foo.vue.js",
    options: {
      filename: "foo.vue",
      autoImportCss: true,
      autoResolveImports: true,
      isProd: true,
    },
    features: {
    },
    addedProps: [
    ],
    addedCodeList: [
    ],
    externalJsList: [
    ],
    externalCssList: [
    ],
    bindingMetadata: undefined,
  }

看到这,从属性看不出来,他是否使用了状态机,但是我猜,大概率是没有使用状态机的,但是至于他为什么要定义一个context,这个地方我很疑惑,对于业务来说,这个地方并不重要。但是,从算法角度,这个地方究竟有什么用,我很好奇。

那就带着你们研究一下吧。

我看了一下,compile函数中,出现了17次context,要么创建时被赋值,要么是作为参数传给一个函数,要么作为结果的一部分返回回来。所以我猜想,他的作用应该是存储编译的参数和编译的部分结果。

那么,我要验证一下,所以,我会在每次context作为函数入参时,在函数调用后,看看context参数有什么变化从而了解这个context里面发生了哪些变化,从而了解他的作用。

我一路跟了下来,context没有任何变化,还是那些值。所以,他应该就是一个编译时,提供参数的作用。

OK,这一行就先到这里了。

下面看,

    const { descriptor, errors: mainCompilerErrors } = (0, compiler_sfc_1.parse)(source, {
        filename: context.filename,
    });

这一行代码,作者写了注释,就是// get the code structure。获取代码结构的。所谓代码结构,是什么东西?这个需要具体看一下parse函数的作用。

function parse$2(source, options = {}) {
  const sourceKey = genCacheKey(source, options);
  const cache = parseCache$1.get(sourceKey);
  if (cache) {
    return cache;
  }
  const {
    sourceMap = true,
    filename = DEFAULT_FILENAME,
    sourceRoot = "",
    pad = false,
    ignoreEmpty = true,
    compiler = CompilerDOM__namespace,
    templateParseOptions = {},
    parseExpressions = true
  } = options;
  const descriptor = {
    filename,
    source,
    template: null,
    script: null,
    scriptSetup: null,
    styles: [],
    customBlocks: [],
    cssVars: [],
    slotted: false,
    shouldForceReload: (prevImports) => hmrShouldReload(prevImports, descriptor)
  };
  const errors = [];
  const ast = compiler.parse(source, {
    parseMode: "sfc",
    prefixIdentifiers: parseExpressions,
    ...templateParseOptions,
    onError: (e) => {
      errors.push(e);
    }
  });
  ast.children.forEach((node) => {
    if (node.type !== 1) {
      return;
    }
    if (ignoreEmpty && node.tag !== "template" && isEmpty(node) && !hasSrc(node)) {
      return;
    }
    switch (node.tag) {
      case "template":
        if (!descriptor.template) {
          const templateBlock = descriptor.template = createBlock(
            node,
            source,
            false
          );
          if (!templateBlock.attrs.src) {
            templateBlock.ast = compilerCore.createRoot(node.children, source);
          }
          if (templateBlock.attrs.functional) {
            const err = new SyntaxError(
              `<template functional> is no longer supported in Vue 3, since functional components no longer have significant performance difference from stateful ones. Just use a normal <template> instead.`
            );
            err.loc = node.props.find(
              (p) => p.type === 6 && p.name === "functional"
            ).loc;
            errors.push(err);
          }
        } else {
          errors.push(createDuplicateBlockError(node));
        }
        break;
      case "script":
        const scriptBlock = createBlock(node, source, pad);
        const isSetup = !!scriptBlock.attrs.setup;
        if (isSetup && !descriptor.scriptSetup) {
          descriptor.scriptSetup = scriptBlock;
          break;
        }
        if (!isSetup && !descriptor.script) {
          descriptor.script = scriptBlock;
          break;
        }
        errors.push(createDuplicateBlockError(node, isSetup));
        break;
      case "style":
        const styleBlock = createBlock(node, source, pad);
        if (styleBlock.attrs.vars) {
          errors.push(
            new SyntaxError(
              `<style vars> has been replaced by a new proposal: https://github.com/vuejs/rfcs/pull/231`
            )
          );
        }
        descriptor.styles.push(styleBlock);
        break;
      default:
        descriptor.customBlocks.push(createBlock(node, source, pad));
        break;
    }
  });
  if (!descriptor.template && !descriptor.script && !descriptor.scriptSetup) {
    errors.push(
      new SyntaxError(
        `At least one <template> or <script> is required in a single file component.`
      )
    );
  }
  if (descriptor.scriptSetup) {
    if (descriptor.scriptSetup.src) {
      errors.push(
        new SyntaxError(
          `<script setup> cannot use the "src" attribute because its syntax will be ambiguous outside of the component.`
        )
      );
      descriptor.scriptSetup = null;
    }
    if (descriptor.script && descriptor.script.src) {
      errors.push(
        new SyntaxError(
          `<script> cannot use the "src" attribute when <script setup> is also present because they must be processed together.`
        )
      );
      descriptor.script = null;
    }
  }
  let templateColumnOffset = 0;
  if (descriptor.template && (descriptor.template.lang === "pug" || descriptor.template.lang === "jade")) {
    [descriptor.template.content, templateColumnOffset] = dedent(
      descriptor.template.content
    );
  }
  if (sourceMap) {
    const genMap = (block, columnOffset = 0) => {
      if (block && !block.src) {
        block.map = generateSourceMap(
          filename,
          source,
          block.content,
          sourceRoot,
          !pad || block.type === "template" ? block.loc.start.line - 1 : 0,
          columnOffset
        );
      }
    };
    genMap(descriptor.template, templateColumnOffset);
    genMap(descriptor.script);
    descriptor.styles.forEach((s) => genMap(s));
    descriptor.customBlocks.forEach((s) => genMap(s));
  }
  descriptor.cssVars = parseCssVars(descriptor);
  const slottedRE = /(?:::v-|:)slotted\(/;
  descriptor.slotted = descriptor.styles.some(
    (s) => s.scoped && slottedRE.test(s.content)
  );
  const result = {
    descriptor,
    errors
  };
  parseCache$1.set(sourceKey, result);
  return result;
}

我还以为作者是多么的高大上,github的follower有七千多,竟然没有自己原生实现,反而用了vue的compile-sfc包,让我太失望了。

不过,只要他的compile函数的结果正确,那么我还是看一下呗。毕竟文章已经写这么长了。

parse函数首先就把我给吓着了。竟然调用了一个函数叫做,genCacheKey

const sourceKey = genCacheKey(source, options);

他为什么要先生成一个缓存键呢?第一个问题是,什么是缓存键,第二个问题,缓存键有什么用?

待我休息一会,继续再战。

继续战斗。

看了一下,这个缓存键,好像没什么用,不对,我得再看看,看他在什么其他地方有什么用处。

    const sourceKey = genCacheKey(source, options);
    const cache = parseCache$1.get(sourceKey);
    if (cache) {
      return cache;
    }

看来我一开始搞错了,这三行要一起看,就是用来缓存parse的结果的。

去买了一个西瓜,剪了一个头发,继续开始。

  const ast = compiler.parse(source, {
    parseMode: "sfc",
    prefixIdentifiers: parseExpressions,
    ...templateParseOptions,
    onError: (e) => {
      errors.push(e);
    }
  });

接下来,就是重头戏了。它调用parse方法,把source字符串转换成为ast。下面,我们看看这个parse方法如何实现的。

function parse(template, options = {}) {
  return compilerCore.baseParse(template, shared.extend({}, parserOptions, options));
}

parse方法直接是这样的一个形式,那么我们应该去看看baseParse函数里面做了什么。

function baseParse(input, options) {
  reset();
  currentInput = input;
  currentOptions = shared.extend({}, defaultParserOptions);
  if (options) {
    let key;
    for (key in options) {
      if (options[key] != null) {
        currentOptions[key] = options[key];
      }
    }
  }
  {
    if (currentOptions.decodeEntities) {
      console.warn(
        `[@vue/compiler-core] decodeEntities option is passed but will be ignored in non-browser builds.`
      );
    }
  }
  tokenizer.mode = currentOptions.parseMode === "html" ? 1 : currentOptions.parseMode === "sfc" ? 2 : 0;
  tokenizer.inXML = currentOptions.ns === 1 || currentOptions.ns === 2;
  const delimiters = options && options.delimiters;
  if (delimiters) {
    tokenizer.delimiterOpen = toCharCodes(delimiters[0]);
    tokenizer.delimiterClose = toCharCodes(delimiters[1]);
  }
  const root = currentRoot = createRoot([], input);
  tokenizer.parse(currentInput);
  root.loc = getLoc(0, input.length);
  root.children = condenseWhitespace(root.children);
  currentRoot = null;
  return root;
}

现在我们来看看,baseParse函数做了什么?所以,这个地方就是这样的,调用parse函数,parse实际上里面调用了baseParse函数,现在baseParse函数才是重点。

他在parse之前,首先要reset一下,为什么要reset一下,我们看看reset里面做了什么。

function reset() {
  tokenizer.reset();
  currentOpenTag = null;
  currentProp = null;
  currentAttrValue = "";
  currentAttrStartIndex = -1;
  currentAttrEndIndex = -1;
  stack.length = 0;
}

这就是reset函数,

看了一下,tokenizer.reset(),可以猜到,baseParse使用了状态机,对字符串进行解析,生成ast。状态机这比较复杂,里面会定义各种状态参数,然后在解析的时候,会根据遇到字符串的种类,去改变状态参数,从而完成对字符串中解析到的特定种类的字符串的解析。

那么回到baseParse函数,

比较重要的是这三行代码,

const root = currentRoot = createRoot([], input);
tokenizer.parse(currentInput);
return root;

三行代码一起看,就可以知道,他先创建了一个ast的根,然后解析这个模板,最后返回这个根。

如何解析的,我们先不管,因为打算,后面讲算法的时候,再讲算法。

这样,

const { descriptor, errors: mainCompilerErrors } = (0, compiler_sfc_1.parse)(source, { filename: context.filename, });

这段代码就讲完了,最后总结一下,就是他最后,生成了一个ast。

但是,我之所写这篇文章,主要不是为了关注template到ast的生成,我主要想关注,ast到代码的生成。因为这会涉及到后面,我去看vue-demi,包括tinyvue是如何磨平框架之间的差异性,调用同一套api的。

(0, context_1.resolveFeatures)(descriptor, context);

这个地方,注释写着// get the features,到底,他要获取什么特征呢?

走进代码,看看去。

看了一下,没有找到,跟template相关的东西,那么,就往下走把。

const { result: templateResult, errors: templateErrors } = (0, template_1.resolveTemplate)(descriptor, context);

这一行代码,他在解析模板,所以,我先看看他解析出了什么内容。

看了一下,确实是我想要的东西,

import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
function render(_ctx, _cache) {  
    return (_openBlock(), _createElementBlock("h1", null, "hello world"))
}
__sfc__.render = render

那么也就是说,resolveTemplate就是我想找的函数,因此,直接看这个函数。

const resolveTemplate = (descriptor, context) => {
    if (descriptor.template && !descriptor.scriptSetup) {
        if (descriptor.template.lang && descriptor.template.lang !== 'html') {
            return {
                errors: [
                    new Error(`Unsupported template lang: ${descriptor.template.lang}`),
                ],
            };
        }
        if (descriptor.template.src) {
            return {
                errors: [
                    new Error(`Unsupported external template: ${descriptor.template.src}.`),
                ],
            };
        }
        const templateResult = (0, compiler_sfc_1.compileTemplate)({
            ...context.options.sfcTemplateCompilerOptions,
            id: `data-v-${context.id}`,
            filename: context.filename,
            source: descriptor.template.content,
            scoped: context.features.hasScoped,
            compilerOptions: {
                bindingMetadata: context.bindingMetadata,
                ...context.options.sfcTemplateCompilerOptions?.compilerOptions,
            },
            inMap: descriptor.template.map,
            isProd: context.isProd,
        });
        if (templateResult.errors.length) {
            // eslint-disable-next-line @typescript-eslint/no-throw-literal
            throw templateResult.errors;
        }
        // No source map update technically.
        const templateCode = `${templateResult.code.replace(/\nexport (function|const) (render|ssrRender)/, `\n$1 render`)}\n${constants_1.COMP_ID}.render = render`;
        return {
            result: {
                code: templateCode,
                sourceMap: templateResult.map,
            },
        };
    }
    return { result: { code: '' } };
};

这个函数,不长,只有四十几行。我们一行行看。

核心代码就在这,

        const templateResult = (0, compiler_sfc_1.compileTemplate)({
            ...context.options.sfcTemplateCompilerOptions,
            id: `data-v-${context.id}`,
            filename: context.filename,
            source: descriptor.template.content,
            scoped: context.features.hasScoped,
            compilerOptions: {
                bindingMetadata: context.bindingMetadata,
                ...context.options.sfcTemplateCompilerOptions?.compilerOptions,
            },
            inMap: descriptor.template.map,
            isProd: context.isProd,
        });

我不得不吐槽一下,一个七千多follower的作者,却只是包装了一下别人的vue/compiler-sfc,实在令人汗颜。

我们回到正题,compileTemplate函数就长这个样子。

function compileTemplate(options) {
  const { preprocessLang, preprocessCustomRequire } = options;
  const preprocessor = preprocessLang ? preprocessCustomRequire ? preprocessCustomRequire(preprocessLang) : consolidate$1[preprocessLang] : false;
  if (preprocessor) {
    try {
      return doCompileTemplate({
        ...options,
        source: preprocess$1(options, preprocessor),
        ast: void 0
        // invalidate AST if template goes through preprocessor
      });
    } catch (e) {
      return {
        code: `export default function render() {}`,
        source: options.source,
        tips: [],
        errors: [e]
      };
    }
  } else if (preprocessLang) {
    return {
      code: `export default function render() {}`,
      source: options.source,
      tips: [
        `Component ${options.filename} uses lang ${preprocessLang} for template. Please install the language preprocessor.`
      ],
      errors: [
        `Component ${options.filename} uses lang ${preprocessLang} for template, however it is not installed.`
      ]
    };
  } else {
    return doCompileTemplate(options);
  }
}

实际上,他执行的是这个函数,doCompileTemplate(options)。我们看一下,这个函数。

function doCompileTemplate({
  filename,
  id,
  scoped,
  slotted,
  inMap,
  source,
  ast: inAST,
  ssr = false,
  ssrCssVars,
  isProd = false,
  compiler,
  compilerOptions = {},
  transformAssetUrls
}) {
  const errors = [];
  const warnings = [];
  let nodeTransforms = [];
  if (shared.isObject(transformAssetUrls)) {
    const assetOptions = normalizeOptions(transformAssetUrls);
    nodeTransforms = [
      createAssetUrlTransformWithOptions(assetOptions),
      createSrcsetTransformWithOptions(assetOptions)
    ];
  } else if (transformAssetUrls !== false) {
    nodeTransforms = [transformAssetUrl, transformSrcset];
  }
  if (ssr && !ssrCssVars) {
    warnOnce(
      `compileTemplate is called with \`ssr: true\` but no corresponding \`cssVars\` option.`
    );
  }
  if (!id) {
    warnOnce(`compileTemplate now requires the \`id\` option.`);
    id = "";
  }
  const shortId = id.replace(/^data-v-/, "");
  const longId = `data-v-${shortId}`;
  const defaultCompiler = ssr ? CompilerSSR__namespace : CompilerDOM__namespace;
  compiler = compiler || defaultCompiler;
  if (compiler !== defaultCompiler) {
    inAST = void 0;
  }
  if (inAST == null ? void 0 : inAST.transformed) {
    const newAST = (ssr ? CompilerDOM__namespace : compiler).parse(inAST.source, {
      prefixIdentifiers: true,
      ...compilerOptions,
      parseMode: "sfc",
      onError: (e) => errors.push(e)
    });
    const template = newAST.children.find(
      (node) => node.type === 1 && node.tag === "template"
    );
    inAST = compilerCore.createRoot(template.children, inAST.source);
  }
  let { code, ast, preamble, map } = compiler.compile(inAST || source, {
    mode: "module",
    prefixIdentifiers: true,
    hoistStatic: true,
    cacheHandlers: true,
    ssrCssVars: ssr && ssrCssVars && ssrCssVars.length ? genCssVarsFromList(ssrCssVars, shortId, isProd, true) : "",
    scopeId: scoped ? longId : void 0,
    slotted,
    sourceMap: true,
    ...compilerOptions,
    hmr: !isProd,
    nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
    filename,
    onError: (e) => errors.push(e),
    onWarn: (w) => warnings.push(w)
  });
  if (inMap && !inAST) {
    if (map) {
      map = mapLines(inMap, map);
    }
    if (errors.length) {
      patchErrors(errors, source, inMap);
    }
  }
  const tips = warnings.map((w) => {
    let msg = w.message;
    if (w.loc) {
      msg += `
${shared.generateCodeFrame(
        (inAST == null ? void 0 : inAST.source) || source,
        w.loc.start.offset,
        w.loc.end.offset
      )}`;
    }
    return msg;
  });
  return { code, ast, preamble, source, errors, tips, map };
}

然后,这里面,最终走了这个函数。

let { code, ast, preamble, map } = compiler.compile(inAST || source, {
    mode: "module",
    prefixIdentifiers: true,
    hoistStatic: true,
    cacheHandlers: true,
    ssrCssVars: ssr && ssrCssVars && ssrCssVars.length ? genCssVarsFromList(ssrCssVars, shortId, isProd, true) : "",
    scopeId: scoped ? longId : void 0,
    slotted,
    sourceMap: true,
    ...compilerOptions,
    hmr: !isProd,
    nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
    filename,
    onError: (e) => errors.push(e),
    onWarn: (w) => warnings.push(w)
  });

实际又走了这个函数。

function compile(src, options = {}) {
  return compilerCore.baseCompile(
    src,
    shared.extend({}, parserOptions, options, {
      nodeTransforms: [
        // ignore <script> and <tag>
        // this is not put inside DOMNodeTransforms because that list is used
        // by compiler-ssr to generate vnode fallback branches
        ignoreSideEffectTags,
        ...DOMNodeTransforms,
        ...options.nodeTransforms || []
      ],
      directiveTransforms: shared.extend(
        {},
        DOMDirectiveTransforms,
        options.directiveTransforms || {}
      ),
      transformHoist: stringifyStatic
    })
  );
}

我们看看,baseCompile

function baseCompile(source, options = {}) {
  const onError = options.onError || defaultOnError;
  const isModuleMode = options.mode === "module";
  const prefixIdentifiers = options.prefixIdentifiers === true || isModuleMode;
  if (!prefixIdentifiers && options.cacheHandlers) {
    onError(createCompilerError(49));
  }
  if (options.scopeId && !isModuleMode) {
    onError(createCompilerError(50));
  }
  const resolvedOptions = shared.extend({}, options, {
    prefixIdentifiers
  });
  const ast = shared.isString(source) ? baseParse(source, resolvedOptions) : source;
  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(prefixIdentifiers);
  if (options.isTS) {
    const { expressionPlugins } = options;
    if (!expressionPlugins || !expressionPlugins.includes("typescript")) {
      options.expressionPlugins = [...expressionPlugins || [], "typescript"];
    }
  }
  transform(
    ast,
    shared.extend({}, resolvedOptions, {
      nodeTransforms: [
        ...nodeTransforms,
        ...options.nodeTransforms || []
        // user transforms
      ],
      directiveTransforms: shared.extend(
        {},
        directiveTransforms,
        options.directiveTransforms || {}
        // user transforms
      )
    })
  );
  return generate(ast, resolvedOptions);
}

baseCompile里面,最终又调了那个函数,还是直接就执行了。

发现他对ast做了一次转换

transform(
    ast,
    shared.extend({}, resolvedOptions, {
      nodeTransforms: [
        ...nodeTransforms,
        ...options.nodeTransforms || []
        // user transforms
      ],
      directiveTransforms: shared.extend(
        {},
        directiveTransforms,
        options.directiveTransforms || {}
        // user transforms
      )
    })
  );
return generate(ast, resolvedOptions);

最后就生成了结果。我们一个个来看,先看转换过程。

转换过程,没有什么重点,就忽略了。

接下来,看看generate

function generate(ast, options = {}) {
  const context = createCodegenContext(ast, options);
  if (options.onContextCreated) options.onContextCreated(context);
  const {
    mode,
    push,
    prefixIdentifiers,
    indent,
    deindent,
    newline,
    scopeId,
    ssr
  } = context;
  const helpers = Array.from(ast.helpers);
  const hasHelpers = helpers.length > 0;
  const useWithBlock = !prefixIdentifiers && mode !== "module";
  const genScopeId = scopeId != null && mode === "module";
  const isSetupInlined = !!options.inline;
  const preambleContext = isSetupInlined ? createCodegenContext(ast, options) : context;
  if (mode === "module") {
    genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined);
  } else {
    genFunctionPreamble(ast, preambleContext);
  }
  const functionName = ssr ? `ssrRender` : `render`;
  const args = ssr ? ["_ctx", "_push", "_parent", "_attrs"] : ["_ctx", "_cache"];
  if (options.bindingMetadata && !options.inline) {
    args.push("$props", "$setup", "$data", "$options");
  }
  const signature = options.isTS ? args.map((arg) => `${arg}: any`).join(",") : args.join(", ");
  if (isSetupInlined) {
    push(`(${signature}) => {`);
  } else {
    push(`function ${functionName}(${signature}) {`);
  }
  indent();
  if (useWithBlock) {
    push(`with (_ctx) {`);
    indent();
    if (hasHelpers) {
      push(
        `const { ${helpers.map(aliasHelper).join(", ")} } = _Vue
`,
        -1 /* End */
      );
      newline();
    }
  }
  if (ast.components.length) {
    genAssets(ast.components, "component", context);
    if (ast.directives.length || ast.temps > 0) {
      newline();
    }
  }
  if (ast.directives.length) {
    genAssets(ast.directives, "directive", context);
    if (ast.temps > 0) {
      newline();
    }
  }
  if (ast.filters && ast.filters.length) {
    newline();
    genAssets(ast.filters, "filter", context);
    newline();
  }
  if (ast.temps > 0) {
    push(`let `);
    for (let i = 0; i < ast.temps; i++) {
      push(`${i > 0 ? `, ` : ``}_temp${i}`);
    }
  }
  if (ast.components.length || ast.directives.length || ast.temps) {
    push(`
`, 0 /* Start */);
    newline();
  }
  if (!ssr) {
    push(`return `);
  }
  if (ast.codegenNode) {
    genNode(ast.codegenNode, context);
  } else {
    push(`null`);
  }
  if (useWithBlock) {
    deindent();
    push(`}`);
  }
  deindent();
  push(`}`);
  return {
    ast,
    code: context.code,
    preamble: isSetupInlined ? preambleContext.code : ``,
    map: context.map ? context.map.toJSON() : void 0
  };
}

我看了一下,要会写这个东西,必须对于vue的编译后的使用,十分清楚,我目前还没这个功力。其实从generate函数可以看到,操作就好似一个模板里面填内容。很多东西都是固定好的,只需要稍微转换一下,往里面填内容就可以了。

至此,整个解析过程就完成了。

可以看到,他其实并没有那么神秘。

现在解答,我一开始疑问的问题。就是他是怎么把ast转换成一个js文件的。

他通过一些固定格式,把格式设置成一个模板,然后,对ast中,template的内容该转换成什么,放在什么地方,做了一个转换。

转载自:https://juejin.cn/post/7393481473083457577
评论
请登录