@babel/core 源码分析(4)

144 阅读2分钟

@babel/core 源码分析(3) 我们可以看到loadFullConfig函数的作用。那么现在我们先回到最初的起点。

transform-file.ts

const transformFileRunner = gensync(function* (
  filename: string,
  opts?: InputOptions,
): Handler<FileResult | null> {
  const options = { ...opts, filename };

  const config: ResolvedConfig | null = yield* loadConfig(options); // 加载配置
  if (config === null) return null;

  const code = yield* fs.readFile(filename, "utf8");
  return yield* run(config, code);
});

可以看出,当我们获取了完整的配置之后,下一步就是读取文件内容fs.readFile
最后把配置和文件内容传进 run 函数中,接下来我们就看看 run函数做了什么。

transformation/index.ts

run函数


export function* run(
  config: ResolvedConfig,
  code: string,
  ast?: t.File | t.Program | null,
): Handler<FileResult> {
  const file = yield* normalizeFile(
    config.passes,
    normalizeOptions(config),
    code,
    ast,
  );

  const opts = file.opts;
  try {
    yield* transformFile(file, config.passes);
  } catch (e) {
    e.message = `${opts.filename ?? "unknown file"}: ${e.message}`;
    if (!e.code) {
      e.code = "BABEL_TRANSFORM_ERROR";
    }
    throw e;
  }

  let outputCode, outputMap;
  try {
    if (opts.code !== false) {
      ({ outputCode, outputMap } = generateCode(config.passes, file));
    }
  } catch (e) {
    e.message = `${opts.filename ?? "unknown file"}: ${e.message}`;
    if (!e.code) {
      e.code = "BABEL_GENERATE_ERROR";
    }
    throw e;
  }

run函数接受了三个参数,一个是configcode和一个非必要的ast。 那么接下来我们一个一个函数来进行分析。

normalizeFile

 const file = yield* normalizeFile(
    config.passes,
    normalizeOptions(config),
    code,
    ast,
  );

normalizeFile接收了四个参数。先看看normalizeFile函数做了什么。

transformation/normalize-file.ts

export default function* normalizeFile(
  pluginPasses: PluginPasses,
  options: { [key: string]: any },
  code: string,
  ast?: t.File | t.Program | null,
): Handler<File> {
// 把code确定好是个字符串
  code = `${code || ""}`;
    
 // 判断是否有ast   
  if (ast) {
    if (ast.type === "Program") {
      ast = file(ast, [], []);
    } else if (ast.type !== "File") {
      throw new Error("AST root must be a Program or File node");
    }

    if (options.cloneInputAst) {
      ast = cloneDeep(ast);
    }
  } else {
  // 本例子没有ast,我们直接看这里
  // 运行了parse函数,导入了 @babel/parser 的方法
  // 把字符串解析成AST
    // @ts-expect-error todo: use babel-types ast typings in Babel parser
    ast = yield* parser(pluginPasses, options, code);
  }

  let inputMap = null;
  // 这里判断配置中是否有 inputSourceMap,他的默认值是TRUE
  if (options.inputSourceMap !== false) {
    // If an explicit object is passed in, it overrides the processing of
    // source maps that may be in the file itself.
    // 如果inputSourceMap是个对象,他就会被视为源映射本身
    // 这个的意思就是,如果没有配置inputSourceMap,那么就是以自身的文件内容生成sourceMap
    // 但是如果配置了这个inputSourceMap的话,这个sourceMap就是以这个inputSourceMap来生成
    if (typeof options.inputSourceMap === "object") {
      inputMap = convertSourceMap.fromObject(options.inputSourceMap);
    }

// 这里下面就是从各个渠道去获取inputMap
    if (!inputMap) {
      const lastComment = extractComments(INLINE_SOURCEMAP_REGEX, ast);
      if (lastComment) {
        try {
          inputMap = convertSourceMap.fromComment(lastComment);
        } catch (err) {
          debug("discarding unknown inline input sourcemap", err);
        }
      }
    }

    if (!inputMap) {
      const lastComment = extractComments(EXTERNAL_SOURCEMAP_REGEX, ast);
      if (typeof options.filename === "string" && lastComment) {
        try {
          // when `lastComment` is non-null, EXTERNAL_SOURCEMAP_REGEX must have matches
          const match: [string, string] = EXTERNAL_SOURCEMAP_REGEX.exec(
            lastComment,
          ) as any;
          const inputMapContent = fs.readFileSync(
            path.resolve(path.dirname(options.filename), match[1]),
            "utf8",
          );
          inputMap = convertSourceMap.fromJSON(inputMapContent);
        } catch (err) {
          debug("discarding unknown file input sourcemap", err);
        }
      } else if (lastComment) {
        debug("discarding un-loadable file input sourcemap");
      }
    }
  }
// 最后返回File 对象
  return new File(options, {
    code,
    ast: ast as t.File,
    inputMap,
  });
}

总结:normalizeFile就是接收源文件的内容和相关配置,然后经过parser等处理对内容进行处理,把JS转换成AST,最后返回File对象。里面重点就是parse函数。

parse

import type { Handler } from "gensync";
import { parse } from "@babel/parser";
import { codeFrameColumns } from "@babel/code-frame";
import generateMissingPluginMessage from "./util/missing-plugin-helper";
import type { PluginPasses } from "../config";

// ReturnType获取函数返回的类型
// 所以这里ParseResult是parse函数返回的值的类型
export type ParseResult = ReturnType<typeof parse>;

export default function* parser(
  pluginPasses: PluginPasses,
  { parserOpts, highlightCode = true, filename = "unknown" }: any,
  code: string,
): Handler<ParseResult> {
  try {
    
    const results = [];
    // to do: figure out what id pluginPasses
    // 正常来说,@babel/parse已经足够AST转换的,
    // 但还是存在要自定义的时候,这时候就可以自己写一个parse,通过插件的形式,
    // 指定这个parser为babel的编译器(override 覆盖)
    for (const plugins of pluginPasses) {
      for (const plugin of plugins) {
        const { parserOverride } = plugin;
        if (parserOverride) {
          const ast = parserOverride(code, parserOpts, parse);

          if (ast !== undefined) results.push(ast);
        }
      }
    }

    if (results.length === 0) {
      return parse(code, parserOpts);
    } else if (results.length === 1) {
      // @ts-expect-error - If we want to allow async parsers
      yield* [];
      if (typeof results[0].then === "function") {
        throw new Error(
          `You appear to be using an async parser plugin, ` +
            `which your current version of Babel does not support. ` +
            `If you're using a published plugin, you may need to upgrade ` +
            `your @babel/core version.`,
        );
      }
      return results[0];
    }
    // TODO: Add an error code
    // 只能有一个插件编译器,不然多了话,babel不知道要执行哪一个
    throw new Error("More than one plugin attempted to override parsing.");
  } catch (err) {
    if (err.code === "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED") {
      err.message +=
        "\nConsider renaming the file to '.mjs', or setting sourceType:module " +
        "or sourceType:unambiguous in your Babel config for this file.";
      // err.code will be changed to BABEL_PARSE_ERROR later.
    }

    const { loc, missingPlugin } = err;
    if (loc) {
      ...
    }
    throw err;
  }
}

transformFile

  const opts = file.opts;
  try {
    yield* transformFile(file, config.passes);
  } catch (e) {
    e.message = `${opts.filename ?? "unknown file"}: ${e.message}`;
    if (!e.code) {
      e.code = "BABEL_TRANSFORM_ERROR";
    }
    throw e;
  }

接下来分析transformFile函数

function* transformFile(file: File, pluginPasses: PluginPasses): Handler<void> {
  for (const pluginPairs of pluginPasses) {
    const passPairs: [Plugin, PluginPass][] = [];
    const passes = [];
    const visitors = [];

    // loadBlockHoistPlugin 用于排序
    for (const plugin of pluginPairs.concat([loadBlockHoistPlugin()])) {
      const pass = new PluginPass(file, plugin.key, plugin.options);

      passPairs.push([plugin, pass]);
      passes.push(pass);
      visitors.push(plugin.visitor);
    }

    // 执行所有插件的pre方法
    for (const [plugin, pass] of passPairs) {
      const fn = plugin.pre;
      if (fn) {
        // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression
        const result = fn.call(pass, file);

        // @ts-expect-error - If we want to support async .pre
        yield* [];

        if (isThenable(result)) {
          throw new Error(
            `You appear to be using an plugin with an async .pre, ` +
              `which your current version of Babel does not support. ` +
              `If you're using a published plugin, you may need to upgrade ` +
              `your @babel/core version.`,
          );
        }
      }
    }

    // merge all plugin visitors into a single visitor
    // 调用了@@babel/traverse,把所有插件的visitors合并成一个visitor。
    // 然后使用traverse遍历所有AST,在遍历过程中执行插件。
    const visitor = traverse.visitors.merge(
      visitors,
      passes,
      file.opts.wrapPluginVisitorMethod,
    );
    traverse(file.ast, visitor, file.scope);

    // 最后执行所有插件的post方法
    for (const [plugin, pass] of passPairs) {
      const fn = plugin.post;
      if (fn) {
        // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression
        const result = fn.call(pass, file);

        // @ts-expect-error - If we want to support async .post
        yield* [];

        if (isThenable(result)) {
          throw new Error(
            `You appear to be using an plugin with an async .post, ` +
              `which your current version of Babel does not support. ` +
              `If you're using a published plugin, you may need to upgrade ` +
              `your @babel/core version.`,
          );
        }
      }
    }
  }
}

总结:transformFile中最重要的就是插件。这一步就是根据插件修改AST的内容。插件使用visitor模式来决定特定节点该如何操作。babel将这个对AST树的遍历和节点的增删放在了 @babel/traverse。
然后我们继续往下看。

generateCode

  let outputCode, outputMap;
  try {
    if (opts.code !== false) {
      ({ outputCode, outputMap } = generateCode(config.passes, file));
    }
  } catch (e) {
    e.message = `${opts.filename ?? "unknown file"}: ${e.message}`;
    if (!e.code) {
      e.code = "BABEL_GENERATE_ERROR";
    }
    throw e;
  }

调用了generateCode方法,往下看看此方法具体内容。

generate.ts

import convertSourceMap from "convert-source-map";
type SourceMap = any;
import generate from "@babel/generator";

import type File from "./file";
import mergeSourceMap from "./merge-map";

export default function generateCode(
  pluginPasses: PluginPasses,
  file: File,
): {
  outputCode: string;
  outputMap: SourceMap | null;
} {
  const { opts, ast, code, inputMap } = file;
  const { generatorOpts } = opts;

  generatorOpts.inputSourceMap = inputMap?.toObject();

  // 遍历插件的generatorOverride方法,但是这个方法只能是一个,因为最终的转换结果只能有一个。
  // 如果没有generatorOverride方法,
  // 就使用 @babel/generator 提供的generate方法
  const results = [];
  for (const plugins of pluginPasses) {
    for (const plugin of plugins) {
      const { generatorOverride } = plugin;
      if (generatorOverride) {
        const result = generatorOverride(ast, generatorOpts, code, generate);

        if (result !== undefined) results.push(result);
      }
    }
  }

  let result;
  if (results.length === 0) {
    result = generate(ast, generatorOpts, code);
  } else if (results.length === 1) {
    result = results[0];

    if (typeof result.then === "function") {
      throw new Error(
        `You appear to be using an async codegen plugin, ` +
          `which your current version of Babel does not support. ` +
          `If you're using a published plugin, ` +
          `you may need to upgrade your @babel/core version.`,
      );
    }
  } else {
    throw new Error("More than one plugin attempted to override codegen.");
  }

  // Decoded maps are faster to merge, so we attempt to get use the decodedMap
  // first. But to preserve backwards compat with older Generator, we'll fall
  // back to the encoded map.
  let { code: outputCode, decodedMap: outputMap = result.map } = result;

  // For backwards compat.
  if (result.__mergedMap) {
    /**
     * @see mergeSourceMap
     */
    outputMap = { ...result.map };
  } else {
    if (outputMap) {
      if (inputMap) {
        // mergeSourceMap returns an encoded map
        outputMap = mergeSourceMap(
          inputMap.toObject(),
          outputMap,
          generatorOpts.sourceFileName,
        );
      } else {
        // We cannot output a decoded map, so retrieve the encoded form. Because
        // the decoded form is free, it's fine to prioritize decoded first.
        outputMap = result.map;
      }
    }
  }

  // 把最后生成的sourceMap 根据配置写进相关的位置
  if (opts.sourceMaps === "inline" || opts.sourceMaps === "both") {
    outputCode += "\n" + convertSourceMap.fromObject(outputMap).toComment();
  }

  if (opts.sourceMaps === "inline") {
    outputMap = null;
  }

  return { outputCode, outputMap };
}

总结: 最后这一步,就是把AST重新生成code。他会去遍历插件是否有generate方法,如果有,就用插件的。如果没有,就使用@babel/generator提供的。同时还会生成sourceMap相关代码。
到这里,其实@babel/core就分析的差不多了。