疑问

书摘&心得

  • Traverse的实现是vistor访问者模式的体现

    • 被操作者稳定,操作方法经常变化时采用。
    • 这里被操作者是AST,操作方法是visitor

      1、path的属性和方法

  • 这个东西没有官方文档的么?先记录一下

    1. path {
    2. // 属性:
    3. node 当前 AST 节点
    4. parent AST 节点
    5. parentPath AST 节点的 path
    6. scope 作用域
    7. hub path.hub.file 拿到最外层 File 对象;
    8. path.hub.getScope 拿到最外层作用域;
    9. path.hub.getCode 拿到源码字符串
    10. container (不常用)当前 AST 节点所在的父节点属性的属性值
    11. key (不常用)当前 AST 节点所在父节点属性的属性名或所在数组的下标
    12. listKey (不常用)当前 AST 节点所在父节点属性的属性值为数组时 listkey 为该属性名,
    13. 否则为 undefined
    14. // 方法
    15. get(key) 获取某个属性的 path
    16. set(key, node) 设置某个属性的值
    17. inList() 判断节点是否在数组中,有 listkey 的时候,返回 true
    18. getSibling(key) 获取某个下标的兄弟节点
    19. getNextSibling() 获取下一个兄弟节点
    20. getPrevSibling() 获取上一个兄弟节点
    21. getAllPrevSiblings() 获取之前的所有兄弟节点
    22. getAllNextSiblings() 获取之后的所有兄弟节点
    23. isXxx(opts) 判断当前节点是否是某个类型,可以传入属性和属性值进一步判断,
    24. 比如path.isIdentifier({name: 'a'})
    25. assertXxx(opts) isXxx,但是不返回布尔值,而是抛出异常
    26. find(callback) 从当前节点到根节点来查找节点(包括当前节点),
    27. 调用 callback(传入 path)来决定是否终止查找
    28. findParent(callback) 从当前节点到根节点来查找节点(不包括当前节点),
    29. 调用 callback(传入 path)来决定是否终止查找
    30. insertBefore(nodes) 在之前插入节点,可以是单个节点或者节点数组
    31. insertAfter(nodes) 在之后插入节点,可以是单个节点或者节点数组
    32. replaceWith(replacement) 用某个节点替换当前节点
    33. replaceWithMultiple(nodes) 用多个节点替换当前节点
    34. replaceWithSourceString(replacement) 解析源码成 AST,然后替换当前节点
    35. remove() 删除当前节点
    36. traverse(visitor, state) 遍历当前节点的子节点
    37. skip() 跳过当前节点的子节点的遍历
    38. stop() 结束所有遍历
    39. }

    作用域path.scope

    这里的作用域就是指js中的作用域

    1. path.scope {
    2. bindings 当前作用域内声明的所有变量
    3. block 生成作用域的 block,详见下文
    4. parent
    5. parentBlock
    6. path 生成作用域的节点对应的 path
    7. references 所有 binding 的引用对应的 path,详见下文
    8. dump() 打印作用域链的所有 binding 到控制台
    9. parentBlock() 父级作用域的 block
    10. getAllBindings() 从当前作用域到根作用域的所有 binding 的合并
    11. getBinding(name) 查找某个 binding,从当前作用域一直查找到根作用域
    12. hasBinding(namenoGlobals) 从当前作用域查找 binding
    13. getOwnBinding(name) 从当前作用域查找 binding
    14. parentHasBinding(name) 查找某个 binding,从父作用域查到根作用域,不包括当前作用域。
    15. 可以通过 noGlobals 参数指定是否算上全局变量,默认是 false
    16. removeBinding(name) 删除某个 binding
    17. moveBindingTo(name, scope) 把当前作用域中的某个 binding 移动到其他作用域
    18. generateUid(name) 生成作用域内唯一的名字,根据 name 添加下划线
    19. }

    block

    不是所有节点都可以生成scope,只有部分可以,按常识就是各种会生成作用域的语句,这些节点又称block节点

    binding

    在当前 scope 中声明了 a 这个变量,所以 bindings 中有 a 的 binding

    1. bindings: {
    2. a: {
    3. constant: true, 变量是否被修改过
    4. constantViolations: [], 所有修改的语句的 path
    5. identifier: {type: 'Identifier', ...} 标识符的 AST
    6. kind:'const', 绑定的类型
    7. path: {node,...} 整个声明语句的 AST
    8. referenced: false 声明的变量是否被引用
    9. referencePaths: [], 所有引用的语句的 path
    10. references: 0,
    11. scope: ...
    12. }
    13. }

    2、state

    原生state

  • opts,也就是插件的配置项

  • file 对象,该对象也可以通过path.hub.file获取

主要用途:

  • 除了原生state以外,可以挂载一些自己定义的state来在节点间传递,babel-plugin-import中大量使用了这种数据传递。
  • 可以在遍历的过程中在 state 中存一些状态信息,用于后续的 AST 处理。
  • 不是遍历过程中的数据,可以通过 file.set、file.get 来保存。

    3、AST的别名

  • 所谓别名,更像是指是要处理一个更广泛的节点还是更具体的节点。

    • 比如:更广泛的节点是Declarations.,而更具体的节点是ClassDeclaration
  • 可以在文档中查到某个 AST 类型的别名是啥
  • 某个别名都包含哪些 AST 类型可以在babel-types的类型定义处查。

    4、节点创建

  • 节点创建需要借助@babel/types

  • 每一个 AST 节点怎么创建、怎么校验、怎么遍历,其实都与 AST 的结构有关系,这些都在 babel-types 里面定义,具体使用具体查看即可
  • 个人觉得@babel/templete可能更容易上手