babel-plugin-import 按需加载原理

假如需要转化的源码是

import { Button } from "antd";

console.log(Button);

配置是

// babel.config.js
const plugins = [
    [
        "babel-plugin-import", 
        {
            "style": true,
            "libraryName": "antd",
            "libraryDirectory": "lib",
        }
    ]
]

在执行的时候首先根据配置生成一个 Plugin 实例,源码,初始化的时候会将配置信息挂在到 this 上,源码。

const Program = {
  enter(path, { opts = {} }) {
    if (!plugins) {
      plugins = [
        new Plugin(
          opts.libraryName,
          opts.libraryDirectory,
          opts.style,
          types,
        ),
      ];
    }
    applyInstance('ProgramEnter', arguments, this);
  },
  exit() {
    applyInstance('ProgramExit', arguments, this);
  },
};

之后过滤所有 import xx from “antd” 的 ImportDeclaration,并将组件名添加到自定义的属性 specified 上,源码

ImportDeclaration(path, state) {
  const { node } = path;
  if (!node) return;
  const { value } = node.source;
  const { libraryName } = this;
  const { types } = this;
  const pluginState = this.getPluginState(state);
  // libraryName 就是配置中的 antd
  if (value === libraryName) {
    node.specifiers.forEach(spec => {
      if (types.isImportSpecifier(spec)) {
        // local.name 是导入进来的别名,比如 import { Button as MyButton } from 'antd' 的 MyButton
        pluginState.specified[spec.local.name] = spec.imported.name;
      } else { 
        pluginState.libraryObjs[spec.local.name] = true;
      }
    });
    pluginState.pathsToRemove.push(path);
  }
}

在 CallExpression 中将过滤到的组件名,也就是 Button 和其他一些参数传入到 importMethod 进行执行,源码

CallExpression(path, state) {
  const { node } = path;
  const file = (path && path.hub && path.hub.file) || (state && state.file);
  const { name } = node.callee;
  const { types } = this;
  const pluginState = this.getPluginState(state);

  if (types.isIdentifier(node.callee)) {
    if (pluginState.specified[name]) {
      node.callee = this.importMethod(pluginState.specified[name], file, pluginState);
    }
  }

  node.arguments = node.arguments.map(arg => {
    const { name: argName } = arg;
    if (
      pluginState.specified[argName] &&
      path.scope.hasBinding(argName) &&
      path.scope.getBinding(argName).path.type === 'ImportSpecifier'
    ) {
      // 找到 specifier,调用 importMethod 方法
      return this.importMethod(pluginState.specified[argName], file, pluginState);
    }
    return arg;
  });
}

在 importMethod 中修改组件的引入路径和样式的引入路径为按需引入,源码

importMethod(methodName, file, pluginState) {
  if (!pluginState.selectedMethods[methodName]) {
    const { style, libraryDirectory } = this;
    const transformedMethodName = transCamel(methodName, '');
    // 兼容 windows 路径 path$1 == 'antd/lib/button'
    const path$1 = winPath(path.join(this.libraryName, libraryDirectory, transformedMethodName));
    // 生成 import 语句 import Button from 'antd/lib/button'
    pluginState.selectedMethods[methodName] = helperModuleImports.addDefault(file.path, path$1, { nameHint: methodName });
    if (style) {
      // 生成样式 import 语句 import 'antd/lib/button/style'
      helperModuleImports.addSideEffect(file.path, `${path$1}/style`);
    }
  }
  return { ...pluginState.selectedMethods[methodName] };
}

此时如果在编辑器中断点可以发现 AST 中新增两个节点,value 就是我们处理好的路径

执行后生成代码如下

"use strict";

require("antd/lib/button/style");

var _button = _interopRequireDefault(require("antd/lib/button"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

console.log(_button.default);

以上就是 babel-plugin-import 实现按需加载的原理。

你可能感兴趣的:(babel,babel)