Babel插件之组件按需引用

组件按需引入

安装插件babel-plugin-component,引入需要的组件,以达到降低项目体积。

目的

一般的组件引入的方式:import Button from “../UI/packages/button”

想要达成的引入方式:import {Button} from “../UI”

措施

前置知识babel-plugin-component插件

babel-plugin-component通过修改引入代码对应的AST树来达到极简的引入组件的目的

import { Alert } from 'xui'对应的ASTimage-20211202150933670.png

1.首先在babel.config.js同级新增一个babel-plugin-component.js文件,作为我们插件文件,然后修改一下babel.config.js文件:

module.exports =
 {  *// ...*  
 plugins: ['./babel-plugin-component.js'] }
复制代码

2.我们就是要修改babel.config.js使得import { Alert } from 'xui'对应的AST树转化成import {Alert from 'xui/packages/alert'对应的AST树。

通过观察import { Alert } from 'xui'代码对应的AST树,我们发现整体是一个importDeclaration,通过source.value判断导入的来源,specifiers数组可以找到导入的变量,每个变量是一个ImportSpecifier。根据这些信息,我们只需要修改source.value导入的来源,并修改specifiers.imported.name导入的组件名,就可以实现极简地引入组件。

3.目标明确后,得到代码

// babel-plugin-component.js
module.exports = ({
                      types
                  }) => {
    return {
        //Babel采用递归的方式访问所有的AST结点
        visitor: {
            //每个AST结点都有对应的节点类型,如Identifier(标识符),FunctionDeclaration(函数声明)
            //一旦访问到相同类型的结点触发事件。通过path可以代表该结点的属性与该结点的相邻结点
            //state代表插件的状态
            ImportDeclaration(path) {
                const {
                    node
                } = path
                // path是类型为ImportDeclaration的AST结点
                const {
                    value
                } = node.source  // node.source是导入的来源
​
                if (value === '../UI') {
                    // 找出引入的组件名称列表
                    let specifiersList = []
                    // specifiers数组里可以找到导入的变量,每个变量是一个ImportSpecifier
                    node.specifiers.forEach(spec => {
                        if (types.isImportSpecifier(spec)) {
                            if(spec.imported.name.toLowerCase()==='ui'){
                                return types.importDeclaration([
                                    types.importDefaultSpecifier(types.identifier(spec.imported.name))
                                ], types.stringLiteral('../UI'))    //stringLiteral字符串字面量,@babel/types负责babel转换的步骤,对结点进行增删改
                            }
                            specifiersList.push(spec.imported.name)
                            //spec.imported.name是组件导入的名称,spec.local.name作为组件的别名
                        }
                    })
                    // 给每个组件创建一条导入语句
                    const importDeclarationList = specifiersList.map((name) => {
                        // 文件夹的名称首字母为小写
                        let lowerCaseName = name.toLowerCase();
                        // 构造importDeclaration节点
                        return types.importDeclaration([
                            types.importDefaultSpecifier(types.identifier(name))
                        ], types.stringLiteral('../UI/packages/' + lowerCaseName))
                    })
                    // 用多节点替换单节点
                    path.replaceWithMultiple(importDeclarationList)
                }
            }
        },
    }
}
​
复制代码

在自己的项目中,需要改动if (value === 'xui')和types.stringLiteral('xui/packages/' + lowerCaseName))两段代码。改成自己项目的组件路径即可。

babel的处理步骤

主要有三个阶段:解析(parse), 转换 (transform),生成(generate)。

  • parse

将源码转成 AST,用到@babel/parser模块。

  • transform

对AST 进行遍历,在此过程中对节点进行添加、更新及移除等操作。因此这是bebel处理代码的核心步骤,是我们的讨论重点,主要使用@babel/traverse和@babel/types模块。

  • generate

打印 AST 成目标代码并生成 sourcemap,用到@babel/generate模块。

浅谈前端AST的概念与实际应用 – 掘金 (juejin.cn)

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享