babel 插件为react元素自动添加属性

原文链接: babel 插件为react元素自动添加属性

上一篇: clip-path 绘制css常见图形 制作有趣的动画

下一篇: js 生成器 协程

参考

https://www.imliyan.com/blogs/article/%E5%A6%82%E4%BD%95%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA%20babel%20%E6%8F%92%E4%BB%B6/

安装

yarn add babel

yarn add @babel/plugin-transform-react-jsx

webpack 处理 React 文件(js/jsx)使用 babel-loader,babel 就是我们的 JavaScript 编译器,它接收我们的源代码作为输入,产出编译后的可运行于浏览器的目标代码作为输出。babel 支持插件(plugin),可以视作编译器前端与后端之间的中间件:前端根据源代码生成抽象语法树(AST)等,后端根据抽象语法树生成目标代码,而插件作为中间件则是在生成目标代码之前对抽象语法树做相应的修改。

转换简单的js

const parser  = require('@babel/parser')
let code = 'let result = x + y'
let ast = parser.parse(code)
console.log(ast)
console.log(ast.program.body)

Node {
  type: 'File',
  start: 0,
  end: 18,
  loc: SourceLocation {
    start: Position { line: 1, column: 0 },
    end: Position { line: 1, column: 18 }
  },
  errors: [],
  program: Node {
    type: 'Program',
    start: 0,
    end: 18,
    loc: SourceLocation { start: [Position], end: [Position] },
    sourceType: 'script',
    interpreter: null,
    body: [ [Node] ],
    directives: []
  },
  comments: []
}
[
  Node {
    type: 'VariableDeclaration',
    start: 0,
    end: 18,
    loc: SourceLocation { start: [Position], end: [Position] },
    declarations: [ [Node] ],
    kind: 'let'
  }
]

Process finished with exit code 0

可以看到抽象语法树是由一个个 Node 组成的,每个 Node 有很多属性,而 Node 还会有一些 body、left 之类的属性,这些属性的值的类型也可能是 Node。对抽象语法树的修改就是修改这些 Node 的值或者属性。

babel会调用插件提供的函数, 并将ast作为参数传给插件处理

访问者模式

访问者模式是一种将算法与对象结构分离的软件设计模式。

这个模式的基本想法如下:首先我们拥有一个由许多对象构成的对象结构,这些对象的类都拥有一个accept方法用来接受访问者对象;访问者是一个接口,它拥有一个visit方法,这个方法对访问到的对象结构中不同类型的元素作出不同的反应;在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施accept方法,在每一个元素的accept方法中回调访问者的visit方法,从而使访问者得以处理对象结构的每一个元素。我们可以针对对象结构设计不同的实在的访问者类来完成不同的操作。

———— 维基百科

具体来说,我们的 AST 的每一个 Node 有一个 accept 方法,当我们用一个 visitor 来遍历我们的 AST 时,每遍历到一个 Node 就会调用这个 Node 的 accept 方法来 接待 这个 visitor,而在 accept 方法内,我们会回调 visitor 的 visit 方法。我们来用访问者模式来实现一个 旅行者访问城市景点 的逻辑。

* 实际上 Node 是有两个方法,enter 和 exit,指遍历进入和离开 Node 的时候。通常访问者的 visit 方法会在 enter 内被调用。

// 旅游景点
class ScenicPoint {
  constructor(name) {
    this.name = name
  }

  // 景点的 accept 方法接收 visitor,函数内调用 visitor 的 visit 方法来 visit 景点的实例
  accept(visitor) {
    visitor.visit(this)
  }
}

class Park extends ScenicPoint {
}

class Museum extends ScenicPoint {
}

// 我们的城市
class City {
  constructor(name, scenicPointList) {
    this.name = name
    this.scenicPointList = scenicPointList
  }

  accept(visitor) {
    for (let scenicPoint of this.scenicPointList) {
      scenicPoint.accept(visitor)
    }
  }
}

// visitors: Alice 与 Bob
let Alice = {
  name: 'Alice',
  visit(scenicPoint) {
    if (scenicPoint instanceof Park) {
      console.log(`${scenicPoint.name} is a wonderful park~`)
    } else {
      console.log(`${this.name} visiting ${scenicPoint.name}`)
    }
  }
}
let Bob = {
  name: 'Bob',
  visit(scenicPoint) {
    if (!(scenicPoint instanceof Museum)) {
      console.log(`I want to go to some Museum.`)
    } else {
      console.log(`${scenicPoint.name} is a wonderful Museum~`)
    }
  }
}

let BeiJing = new City('BeiJing', [
  new ScenicPoint('八达岭长城'),
  new Park('玉渊潭公园'),
  new Museum('国家博物馆'),
])



BeiJing.accept(Alice)
// Alice visiting 八达岭长城
// 玉渊潭公园 is a wonderful park~
// Alice visiting 国家博物馆

BeiJing.accept(Bob)
// I want to go to some Museum.
// I want to go to some Museum.
// 国家博物馆 is a wonderful Museum~

传入我们的代码, 并提供需要添加的属性配置

const input = `let b1 = <Button color="red"  />;
let b2 = <Button size="big"  />;
let d1 = <div size="big"  />;
`


  plugins: [
    plugin,
    [
      ButtonParser, {
      'Button': {
        size: 'small',
        className: "common-btn"
      },
      "div": {
        className: "common-div"
      }
    }]],

可以看到最后已经成功为各个元素添加了属性值, 不过对于同名属性, 还是有优化空间的, 至于如何去除, 以后再说吧....

up-8eca62f8c61240add03ca6307db5f4394cf.png

const plugin = require('@babel/plugin-transform-react-jsx')
const {transformAsync} = require('@babel/core');
const input = `let b1 = <Button color="red"  />;
let b2 = <Button size="big"  />;
let d1 = <div size="big"  />;
`

const defaults = {
  plugins: [
    plugin,
    [
      ButtonParser, {
      'Button': {
        size: 'small',
        className: "common-btn"
      },
      "div": {
        className: "common-div"
      }
    }]],
  sourceType: 'module',
};

async function main() {
  const {code} = await transformAsync(input, {
    ...defaults,
    sourceType: 'module',
  });
  console.log('code:\n', code)
}

main()

const types = require('@babel/types')
const parser = require('@babel/parser')

function ButtonParser() {
  return {
    // 我们的 visitor
    visitor: {
      // 针对函数调用的单独逻辑处理
      CallExpression(path, state) {
        // 我们只处理 React.createElement 函数调用
        let {callee} = path.node
        if (
          !(
            types.isMemberExpression(callee) &&
            types.isIdentifier(callee.object) &&
            callee.object.name === 'React' &&
            types.isIdentifier(callee.property) &&
            callee.property.name === 'createElement'
          )
        ) {
          return
        }

        // 从第一个参数获取组件名称(Button)
        // 从第二个参数获取组件属性
        let [element, propsExpression] = path.node.arguments
        let elementType
        if (types.isStringLiteral(element)) {
          elementType = element.value
        } else if (types.isIdentifier(element)) {
          elementType = element.name
        }

        // 我们的插件支持自定义选项,针对不同的组件类型传入不同的额外自定义属性
        const options = state.opts
        console.log('plugin-args', options)
        let extraProps = options[elementType]

        // 如果没有针对次组件类型的额外参数,我们的插件什么都不做
        if (!extraProps) {
          return
        }

        // 否则,我们利用 parser.parseExpression 方法以及我们的自定义属性生成一个 ObjectExpression
        let stringLiteral = JSON.stringify(extraProps)
        console.log('stringLiteral', stringLiteral, extraProps)
        let extraPropsExpression = parser.parseExpression(stringLiteral)
        console.log('extraPropsExpression', extraPropsExpression)
        // 如果组件原本 props 为空,我们直接将我们的自定义属性作为属性参数
        if (types.isNullLiteral(propsExpression)) {
          path.node.arguments[1] = extraPropsExpression
        } else if (types.isObjectExpression(propsExpression)) {
          // 否则,我们将我们的自定义属性与原属性进行合并(只处理对象类型的 props)
          path.node.arguments[1] = types.objectExpression(
            propsExpression.properties.concat(
              extraPropsExpression.properties,
            ),
          )
        }
      },
    },
  }
}


/*
// babel.config.js
const plugins = [
    [
        'babel-plugin-react-auto-props',
        {
            'Button': {
                size: 'small',
            },
        },
    ],
]

module.exports = { presets, plugins }
 */

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Babel 插件通常是使用 `babel.transformFromAst` 方法将 AST 转换为代码。该方法接收两个参数:AST 和选项对象。 AST(抽象语法树)是由 Babel 解析 JavaScript 代码时生成的一个对象,它表示代码的结构。 选项对象是可选的,用于指定转换过程的一些选项,比如源代码的文件路径和目标语言的版本。 使用方法如下: ``` const { transformFromAst } = require('babel'); const code = `const x = 1;`; const ast = parse(code); // parse 函数用于将代码解析为 AST const options = {}; const { code: transformedCode } = transformFromAst(ast, options); ``` 在这个例子中,变量 `transformedCode` 将包含转换后的代码字符串。 ### 回答2: Babel插件可以通过对AST进行遍历和修改来生成将加工完成的AST翻译成代码。 首先,Babel解析器负责将代码文件转换成AST(Abstract Syntax Tree)。AST是代码的抽象表示形式,它以树状结构表示代码的逻辑结构和语法信息。 接下来,Babel插件通过对AST进行遍历来访问和分析节点。遍历可以使用Babel提供的访问者模式来实现。在遍历过程中,插件可以选择性地对AST节点进行修改,例如添加、删除或替换节点。 插件在遍历AST期间可以使用Babel提供的访问者方法来操作节点。例如,可以使用`enter`方法在进入某个节点时触发相应的回调函数,或者使用`exit`方法在离开节点时触发回调函数。这样,插件就能够在特定节点上执行自定义的操作。 最后,当所有的AST节点遍历和修改都完成后,Babel插件可以使用Babel提供的代码生成器将最终的AST转换回代码。代码生成器会根据AST的结构和规则生成相应的代码字符串。 总结而言,Babel插件通过遍历AST并对节点进行修改,最终将加工完成的AST转换回代码。这样可以实现对源代码的转译、转换或优化等操作,提供了一种灵活且可扩展的方式来处理JavaScript代码。 ### 回答3: Babel 是一个非常强大的 JavaScript 编译器工具,它可以将最新版本的 JavaScript 代码转换为向后兼容的代码,使得我们可以在老旧的浏览器或环境中运行新的 JavaScript 语法或特性。 Babel 的核心功能是生成抽象语法树 (AST),将源代码解析成一个包含了语法结构的对象。然后,它针对这个 AST 进行扩展、修改和转换工作,最后将经过修改后的 AST 生成新的代码。 插件Babel 中实现这些转换工作的关键。当 Babel 解析源代码后,它会顺序地应用一系列插件来修改 AST。每个插件都对应一个或一组转换规则,这些规则告诉 Babel 如何修改和遍历 AST。 当 Babel 遍历 AST 时,它会从顶层节点开始,递归地遍历所有节点。每当遇到一个节点时,Babel 会调用相应插件中的对应转换规则来对节点进行转换。这些规则可以通过修改当前节点,或者增加、删除、移动其他节点来实现。 在所有插件的转换规则应用完毕后,Babel 会将最终的 AST 重新生成为代码。这个过程称为代码生成。Babel 会从 AST 的根节点开始,生成与源代码对应的逐行代码。在此过程中,Babel 还会根据需要添加适当的缩进和换行符,以保持代码的可读性。 通过这种方式,Babel 插件可以将加工完成的 AST 翻译成代码。我们可以通过编写自定义的插件,来扩展 Babel 的功能,实现更多自定义的转换规则,从而将源代码转换成我们期望的形式。插件的生成代码过程是 Babel 实现 JavaScript 代码转换的核心机制之一。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值