美文网首页
tn babel实战篇

tn babel实战篇

作者: skogt | 来源:发表于2024-01-15 16:21 被阅读0次

    前景回顾

    客户端视角下的babel(理论篇)介绍了AST(抽象语法树)的概念,以及Babel作为JavaScript编译器的运作原理。我们深入讲解了Babel的各个阶段,详细介绍了它们所具备的基础概念及相应的功能。最后,我们阐述了Babel插件如何对AST节点进行操作和转换的。与上篇理论相比,本文将围绕TN项目中的一些具体案例,重点介绍Babel在实践应用中的功能和用途。

    总览

    在正式讲述具体项目前,我们还是按照惯例先提几个问题(带着问题感触更深:)

    1. TN从Source Code到AST经历了哪些阶段?

    2. 这些阶段TN处理了哪些事情,遇到了哪些难题,并如何解决的?

    问题1:

    image.png

    TN会在编译期间通过Babel将Source Code转成AST。其中核心的两个阶段:parse、transform

    • parse:对Source Code进行词法分析语法分析生成AST

    • transform:对parse阶段生成的AST进行遍历。在此过程中对节点进行添加、更新及移除等操作

    细节的内容这边就不一一展开了,客户端视角下的babel(理论篇)讲解的非常详细了

    问题2:

    TN任务的处理主要在transform阶段。在该阶段我们具体处理了哪些事项呢?

    1. 支持高级语法:比如Enum(JavaScript中是没有枚举类型的)

    2. 支持语法降级:比如foreach降级到for

    3. 移除换行符多出来的JSXText节点

    4. 多文件依赖导入

    5. 变量名冲突

    6. 函数声明提升到作用域的顶部

    7. ...

    image.png

    针对不同种类的事项,我们可以创建不同的业务插件,来对节点进行添加、更新、移除等操作,从而达到我们的诉求。

    当然,期间也遇到一些难点:比如多文件依赖如何有序导入、变量名冲突如何解决;对于换行符多出来的JSXText节点,我们如何处理,并保证处理的结果和SolidJS是一致的等等。

    下面我们将讲述具体的几个事项,来帮助我们更好的了解TN的处理机制,以及难点问题的解决方式。

    实践

    前景声明

    实践项目中会以SolidJS的表现作为依据去处理节点。为何是SolidJS而非React等其他前端框架呢?归根结底取决于TN的愿景:极致的渲染速度+开发体验效率。

    下面会简单说明为何使用SolidJS作为首选的前端开发框架,更加详细的内容可参考

    https://graffersid.com/solidjs-vs-react-which-is-better/

    开发体验和效率

    逻辑表达式 VS React

    语法与React相似,实现与Composition API相似

    useState -> createSignal
    useMemo -> createMemo
    useEffect -> createEffect
    useLayoutEffect -> createRenderEffect
    

    PS:Composition API 是 Vue 3 新增的 API,旨在解决 Vue 2 中大型组件的可读性、代码重用和类型安全等问题。Composition API 的核心思想是将组件的逻辑按照功能划分为不同的逻辑部分(composition),然后通过组合这些逻辑部分来创建复杂的组件逻辑。

    依赖追踪 VS Vue3

    Solid和Vue3相似,定义响应式数据,添加副作用函数,只不过Solid是读写分离的。

    Solid 是一款基于 JavaScript 的 UI 库,它的读写分离是指对 UI 组件状态进行分离,将其分为可读状态和可写状态。具体来说,Solid 将各个 UI 组件的状态分解为两部分:

    • 可读状态:指 UI 组件的非响应式数据和方法,即只能被访问而不能进行修改的状态。这些状态的访问不会导致 UI 重新渲染。Solid 通过将这些状态管理在普通 JavaScript 对象中,来实现它们的可访问性。
    • 可写状态:指 UI 组件的响应式数据和方法,即在修改状态时会导致 UI 重新渲染的状态。Solid 通过将这些状态管理在响应式数据流中,来实现它们的响应式。

    基于编译的运行时优化

    在React与Vue中存在一层「虚拟DOM」(React中叫Fiber树)。

    每当发生更新,「虚拟DOM」会进行比较(Diff算法),比较的结果会执行不同的DOM操作(增、删、改)。

    而SolidJS在发生更新时,可以直接调用编译好的DOM操作方法,省去了「虚拟DOM比较」这一步所消耗的时间。

    enum转换

    enum 是 TypeScript 中的一个特性,它是一种枚举类型,用于定义具有固定值的常量集合。而在 JavaScript 中,目前并没有原生的 enum 类型。

    当你定义了一个枚举

    enum Fruit {
        Apple = 0,
        Banner = 1
    }
    

    其被编译成 JavaScript 后如下所示:

    var Fruit = /*#__PURE__*/function (Fruit) {
      Fruit[Fruit["Apple"] = 0] = "Apple";
      Fruit[Fruit["Banner"] = 1] = "Banner";
      return Fruit;
    }(Fruit || {});
    

    对应的AST如下:

    {
        "type":"VariableDeclaration",
        "kind":"var",
        "declarations":[
            {
                "type":"VariableDeclarator",
                "id":{
                    ...
                    "name":"Fruit"
                },
                "init":{
                    "type":"CallExpression",
                    "callee":{
                        "type":"FunctionExpression",
                        ...
                        "body":{
                            "type":"BlockStatement",
                            "body":[
                                {
                                    "type":"ExpressionStatement",
                                    "expression":{
                                        "type":"AssignmentExpression",
                                        "operator":"=",
                                        "left":{
                                            "type":"MemberExpression",
                                            ...
                                            "property":{
                                                "type":"AssignmentExpression",
                                                "operator":"=",
                                                "left":{
                                                    "type":"MemberExpression",
                                                    ...
                                                    "property":{
                                                        "type":"StringLiteral",
                                                        "value":"Apple"
                                                    },
                                                },
                                                "right":{
                                                    "type":"NumericLiteral",
                                                    "value":0
                                                }
                                            },
                                        }
                                    }
                                },
                                {
                                    "type":"ExpressionStatement",
                                    "expression":{
                                        "type":"AssignmentExpression",
                                        "operator":"=",
                                        "left":{
                                            "type":"MemberExpression",
                                            ...
                                            },
                                            "property":{
                                                "type":"AssignmentExpression",
                                                "operator":"=",
                                                "left":{
                                                    "type":"MemberExpression",
                                                    ...
                                                    "property":{
                                                        "type":"StringLiteral",
                                                        "value":"Banner"
                                                    },
                                                },
                                                "right":{
                                                    "type":"NumericLiteral",
                                                    "value":1
                                                }
                                            },
                                        }
                                    }
                                },
                                ...
                            ]
                        },
                        ...
                    },
                   ...
                }
            }
        ],
        ...
    }
    

    拿到上述AST后,我们发现一个简单的枚举被paser后变得极其复杂,而且可读性极差。那么是否有办法可以将其处理的精简并且可读化呢?答案是肯定的:通过构建TSEnumDeclaration的转换插件就可以完成目标

    visitor: {
          VariableDeclaration(path) {
            if (path.node.type === 'VariableDeclaration' && (path.node.kind === 'var' || path.node.kind === 'let') && path.node.declarations.length === 1) {
              const declarationNode = path.node.declarations[0];
              if (declarationNode.type === 'VariableDeclarator' && declarationNode.init?.type === 'CallExpression' && declarationNode.init?.callee?.type === 'FunctionExpression') {
                const enumName = declarationNode.id.name;
                const entries = [];
                declarationNode.init.callee?.body?.body.filter(function(propertyNode) {
                  return propertyNode.type === 'ExpressionStatement';
                }).forEach(propertyNode => {
                  // enum Fruit {
                  //   Apple = "apple",
                  //   Banana = "banana",
                  //   Orange = "orange"
                  // }
    
                  // enum Fruit {
                  //   Apple,
                  //   Banana,
                  //   Orange
                  // }
    
                  // enum构建
                  const memberName = propertyNode.expression.left.property.left?.property.value || propertyNode.expression.left.property.value;
                  // initial value 处理
                  const rightValue = propertyNode.expression.right.value;
                  const leftValue = propertyNode.expression.left.property.right?.value;
                  const memberValue = typeof leftValue === 'undefined' || leftValue === null || leftValue === '' ? rightValue : leftValue;
                  entries.push(
                    tsEnumMember(identifier(memberName), typeof memberValue === 'string' ? stringLiteral(memberValue) : numericLiteral(memberValue))
                  );
                });
    
                // TSEnumDeclaration
                const tsEnumDeclWithComment = tsEnumDeclaration(identifier(enumName), entries);
                ...
              }
            }
          }
        }
    

    PS: 节点类型的判断可以根据type值判断,也可以根据@babel/types定义的isX进行判断,两者等价

    上述AST节点经转换后,我们就得到了预期的结果:

    {
      type: 'TSEnumDeclaration',
      id: { type: 'Identifier', name: 'Fruit' },
      members: [
        { type: 'TSEnumMember', id: [Object], initializer: [Object] },
        { type: 'TSEnumMember', id: [Object], initializer: [Object] }
      ]
    }
    
    {"type":"TSEnumDeclaration","id":{"type":"Identifier","name":"Fruit"},"members":[{"type":"TSEnumMember","id":{"type":"Identifier","name":"Apple"},"initializer":{"type":"NumericLiteral","value":0}},{"type":"TSEnumMember","id":{"type":"Identifier","name":"Banner"},"initializer":{"type":"NumericLiteral","value":1}}]}
    

    换行符处理

    我们通过几个例子看下Babel对于换行符是如何处理的(不添加插件默认输出的结果)

    • Case1:文本标签换行,换行符会包含在文本内容中(eg:文本数据前后包含上下的换行符及前置的空格符)
    <text>
      文本数据
    </text>
    
    "children": [
      {
        "type": "JSXText",
        "value": "\n  文本数据\n",
        "raw": "\n  文本数据\n"
      }
    ]
    
    • Case2:标签换行会生产多余的JSXText节点(eg:view标签换行)
    <view>
        <text>
        文本数据1
        </text>
    </view>
    
    "children": [
      {
        "type": "JSXText",
        "value": "\n\t",
        "raw": "\n\t"
      },
      {
        ...
        "children": [
          {
            "type": "JSXText",
            "value": "\n  \t\t文本数据1\n\t",
            "raw": "\n  \t\t文本数据1\n\t"
          }
        ]
      },
      {
        "type": "JSXText",
        "value": "\n",
        "raw": "\n"
      }
    ],
    

    通过上述case,我们大概发现了一个结论:如果在原始的JSX代码中有多余的换行符,则Babel将这些换行符解释为一个JSXText元素,这样可能会导致最终渲染出来的DOM树中多出一些空行节点。

    那我们看下React是如何处理空行符的?

    image.png

    通过上述demo我们非常清晰看到,首先标签换行并不会产生多余的换行符;文本内容如果需要换行则需要{'\n'}表示,否则换行效果只是多一个空格;同一行文本内部有多个空格,则显示多个空格,同一行文本的首尾空格不会显示。

    看完React,我们了解下SolidJS是怎么样的一个现象

    image.png

    SolidJS和React处理类似,区别点在于:两个文本间多个空格符会合并成单个;换行符会转换成单空格。

    基于SolidJS对空行符的处理机制,处理JSX空行符的业务插件具体实现就很明确了

    visitor: {
      JSXText(path) {
        // 获取当前节点在父节点中的前一个相邻节点
        const prevSibling = path.getSibling(path.key - 1)
        // 获取当前节点在父节点中的下一个相邻节点
        const nextSibling = path.getSibling(path.key + 1)
    
        // 判断该节点是否只包含空白字符或换行符
        if (path.node.value.trim() === '') {
          if (!prevSibling.node) {
            path.remove()
          } else if (prevSibling.node.type === "JSXElement") {
            path.remove()
          } else if (!nextSibling.node) { // 末节点
            path.remove()
          } else if (prevSibling.node.type === 'JSXExpressionContainer') { // {count()}
            const text = path.node.value.replace(/\s+/g, " ") // 前置后置中间空格>=1替换为一个空格
            path.node.value = text
          } 
        } else { // 文本中存在多个空格、换行,变成单个空格
          var text = path.node.value
          if (!prevSibling.node && !nextSibling.node) { // 单一节点
            text = text.trim() // 去除首尾空格
            text = text.replace(/\s+/g, " ") // 前置后置中间空格>=1替换为一个空格
          } else if (!prevSibling.node) { // 首节点
            text = text.trimStart() // 去除前置空格
            text = text.replace(/\s+/g, " ") // 前置后置中间空格>=1替换为一个空格
          } else if (!nextSibling.node) { // 末节点
            text = text.replace(/\s+/g, " ") // 前置后置中间空格>=1替换为一个空格
            text = text.trimEnd() // 去除后置空格
          } else {
            text = text.replace(/\s+/g, " ") // 前置后置中间空格>=1替换为一个空格
          }
          path.node.value = text
        }
      }
    }
    

    基于上述插件,Case1的输出结果:

    {
      "type":"JSXElement",
      ...
      "openingElement":Object{...},
      "closingElement":Object{...},
        "children":[
        {
          "type":"JSXText",
          ...
          "value":"文本数据"
        }]
    }
    

    Case2的输出结果:

    {
      "type":"JSXElement",
      ...
      "openingElement":Object{...},
      "closingElement":Object{...},
      "children":[
        {
          "type":"JSXElement",
          ...
          "openingElement":Object{...},
            "closingElement":Object{...},
          "children":[
            {
              "type":"JSXText",
              ...
              "value":"文本数据1"
            }
          ]
        }]
    }
    

    多import导入解决变量名冲突

    TN会将所有模块打包成一个单一的Bundle文件,然后通过包管理下发到Native侧。所以如何将import到的外部依赖文件导入进来,并处理好变量名冲突问题,就成了关键。

    说到打包,我们肯定会想到webpack。那么webpack是如何导入并解决变量名冲突问题的呢?

    input:

    // index.js
    import {targetVersionCompare} from './util.js'
    targetVersionCompare('11')
    
    // util.js
    import { testValue} from "./utilA.js";
    const isEmptyString = (str) =>
        typeof str === 'undefined' || str == null || str === '';
    
    const a = "1111"
    export const targetVersionCompare = (target) => {
        console.log(testValue)
        if (isEmptyString(target)) {
            return false
        }
        console.log(a)
        return false
    }
    
    // utilA.js
    export const testValue = 'A'
    const a = "xxxx"
    console.log(a)
    

    webpack dist:

    /******/ (() => { // webpackBootstrap
    /******/    "use strict";
    var __webpack_exports__ = {};
    
    ;// CONCATENATED MODULE: ./utilA.js
    const testValue = 'A'
    const a = "xxxx"
    console.log(a)
    ;// CONCATENATED MODULE: ./util.js
    
    const isEmptyString = (str) =>
        typeof str === 'undefined' || str == null || str === '';
    
    const util_a = "1111"
    const targetVersionCompare = (target) => {
        console.log(testValue)
        if (isEmptyString(target)) {
            return false
        }
        console.log(util_a)
        return false
    }
    
    ;// CONCATENATED MODULE: ./index.jsx
    
    targetVersionCompare('11')
    /******/ })()
    ;
    

    从上述源码输入到最后的产物对比,我们大概了解了webpack处理多文件导入的机制。

    1. 如果导入的文件某个变量名和当前文件变量名冲突,则添加前缀:当前文件名变量名序列

    2. 变量名a冲突:a->util_a

    3. 变量名a冲突,存在变量名util_a:a->util_a_0

    4. 变量名a冲突,存在变量名util_a,util_a_0:a->util_a_1

    5. export导出标识会被remove

    6. 加载依赖文件的顺序:递归导入。当A依赖B、A依赖C,B依赖D,则导入的顺序依次为D-B-C-A

    方案设计

    webpack的处理机制我们大概了解了,整体的大致流程是怎么样的呢?

    简要流程图

    image.png

    流程概述

    1. 创建remove-export-name-plugin:移除导出标识

    2. 创建import-declaration-plugin

    3. 递归读取依赖的文件

    4. 生成唯一标识 By 完整文件路径

    5. 链表记录前后依赖&AST、export数据绑定

    6. import导入数据处理

    7. 反转链表

    8. diff

    9. 重命名冲突处理

    10. 节点合并

    方案实现

    移除export
    visitor: {
      ExportNamedDeclaration(path) {
        const { node } = path
        const { declaration, specifiers } = node
        if (declaration) {
          path.replaceWith(declaration);
        }
      },
        ExportDefaultDeclaration(path) {    
          const { node } = path
          const { declaration } = node
    
          if (declaration) {
            path.replaceWith(declaration);
          } else {
            path.remove();
          }
        }
    }
    
    import-declaration-plugin
    • 递归读取依赖的文件

    当我们输入import { testValue} from "./utilA.js"语句的时候,AST会生成节点:ImportDeclaration,其导入的文件相对路径也会在source字段下展示。如下所示:

    image.png

    那么如何根据import的相对路径去构建完成的文件路径呢?

    首先我们需要明白一点:导入文件的相对路径只有对于当前文件是透明的,而文件导入依赖是递归的,所以我们需要做的就是用一个栈记录当前import file路径依赖,导入文件的时候压栈,离开文件的时候出栈。

    PS:另外JS在导入文件的时候,可以选择性的不写文件后缀名,需要单独处理

    const dependFilePathStack = [] // 栈记录当前import file路径依赖
    
    const constructFile = (importModulePath) => {
      const curFilePath = dependFilePathStack.slice(-1)[0] || source // 获取栈顶元素
      var filePath = path.resolve(path.dirname(curFilePath), importModulePath)
      const dirPath = path.resolve(filePath, '..'); // 获取上一级目录
    
      if (path.extname(filePath)) { // 当前文件路径存在后缀
        return filePath
      }
    
      // 获取目录下所有文件
      const fileNames = fs.readdirSync(dirPath)
      // 获取文件名
      const basename = path.basename(filePath);
    
      // 查找指定文件名的后缀名(兼容.d.ts声明文件)
      const targetFileName = fileNames.find(fullFileName => {
        return path.basename(fullFileName, path.extname(fullFileName)) === basename || path.basename(fullFileName, '.d.ts') === basename;
      })
      // 组合文件路径和后缀名
      return targetFileName ? `${dirPath}/${targetFileName}` : ''
    }
    
    • 生成唯一标识 By 完整文件路径

    tn处理重命名冲突规则和webpack稍微有点差异。差异点:webpack可以理解为在"运行时"去处理重命名冲突,即后置判断;而tn为了简化逻辑,会在“编译时”即前置阶段直接生成一个唯一标识,当命名冲突的时候,直接使用唯一标识去替换。

    • 链表记录前后依赖&AST、export数据绑定

    import文件导入是递归读取的,我们可以使用链表来存储文件之间的依赖关系。节点的定义如下:next 表示当前文件导入的下一个依赖文件,nameSpace 表示当前文件的唯一标识,nodes 表示当前文件的 AST节点集合,exported 表示当前文件导出的集合。

    class ImportNode {
        constructor(next, nameSpace, nodes, exported) {
            this.next = next
            this.nameSpace = nameSpace
            this.nodes = nodes
            this.exported = exported
        }
    }
    

    递归读取文件的同时,记录对应ImportNode的AST及exported相关数据

    // 记录import依赖
    const lasted = findLastedNode(path)
    if (!lasted) {
      // 绑定Head Node
      const headNode = new ImportNode(null, nameSpace, null, null)
      importHeadNode = headNode
    } else {
      lasted.next = new ImportNode(null, nameSpace, null, null)
    }
    
    // 获取ast节点集合
    const importModuleAst = processedFilePaths[fullFilePath] || generateToAst(fullFilePath)
    processedFilePaths[fullFilePath] = importModuleAst
    
    const astBody = importModuleAst.program.body
    
    // 绑定nodes
    const target = findTargetNode(nameSpace)
    target.nodes = astBody
    
    // 绑定exported
    target.exported = collectExportAs(astBody)
    

    import-declaration-plugin组件大致实现如下:

    var importHeadNode = null
    const dependFilePathStack = [] // 栈记录当前import file路径依赖
    ...
    
    visitor: {
      ImportDeclaration(path) {
        // 1.获取当前import module path
        // 2.获取导入模块的代码并解析成 AST
        const importModulePath = path.get('source').node.value
        const fullFilePath = constructFile(importModulePath)
    
        if (!fullFilePath || processedFilePaths[fullFilePath]) {
          return;
        }
    
        // 构建namespace
        const nameSpace = constructNameSpace(fullFilePath)
        // 记录import依赖
        const lasted = findLastedNode(path)
        if (!lasted) {
          // 绑定Head Node
          const headNode = new ImportNode(null, nameSpace, null, null)
          importHeadNode = headNode
          ...
        } else {
          lasted.next = new ImportNode(null, nameSpace, null, null)
        }
        dependFilePathStack.push(fullFilePath)
    
        // traverseImportHead(importHeadNode)
        const importModuleAst = processedFilePaths[fullFilePath] || generateToAst(fullFilePath)
        processedFilePaths[fullFilePath] = importModuleAst
        dependFilePathStack.pop()
    
        // 导入文件的body
        const astBody = importModuleAst.program.body
    
        // 绑定nodes
        const target = findTargetNode(nameSpace)
        target.nodes = astBody
    
        // 绑定exported
        target.exported = collectExportAs(astBody)
      }
    }
    
    import导入数据处理
    • 反转链表
    1. tn和webpack对于依赖的文件导入顺序是一致的,比如A依赖B、A依赖C,B依赖D,则导入的顺序依次为D-B-C-A。因此我们需要反转链表,从链表的尾部节点倒序读入
    const reverseLinkList = (importHeadNode) => {
        const reverseList = []
        var head = importHeadNode
        while (head) {
            reverseList.push(head)
            head = head.next
        }
        return reverseList.reverse()
    }
    
    • diff

    diff是重命名冲突的核心函数。当import文件导入时,有可能导入的Identifier与当前文件存在冲突。大致处理的规则如下:

    1. 获取当前import文件内容中声明的所有names

    2. 当前node与导入的前几个文件diff

    3. 读取下一个节点,重复1,2两个步骤

    // 1\. 获取当前import文件内容中声明的所有names 2\. 当前node与导入的前几个文件diff
    const dealImportDependies = (reverseImportList) => {
        var importNodes = []
        for (let index = 0; index < reverseImportList.length; index++) {
            const node = reverseImportList[index]
            // diff前 (原始声明)
            const beforeDiffDeclarationNames = collectNodeDeclarationName(node)
            // diff
            diffImportFileDeclaration(importDeclarationNames(), node)
            importNodes = [...importNodes, ...node.nodes]
            // console.log(JSON.stringify(node))
    
            // diff后(替换后的声明):部分声明diff后会重新替换
            const afterDeclarationNames = collectNodeDeclarationName(node)
    
            // 根据前后diff生成对照表
            const nameUnion = declarationNameSpaceUnion(beforeDiffDeclarationNames, afterDeclarationNames)
            declarationNameTable[node.nameSpace] = nameUnion
    
            // 绑定export as
            exportedAsDeclarationTable[node.nameSpace] = isNonEmptyObj(node.exported) ? node.exported : null
        }
        return importNodes
    }
    

    后续的node与前面import过的声明集合进行diff

    /**
     * dfs 节点下的叶子节点声明
     * @param {*} input node
     * @param {*} baseUnion 前置导入的file declaration name 集合
     * @param {*} nameSpace  当前文件的namespace
     * @param {*} importDeclarationNames 当前文件导入的import声明 import {A,B} from 'xxxx'
     * @returns 
     */
    function dfsNodeDeclaration(input, baseUnion, nameSpace, importDeclarationNames) {
        if(!input){
            return
        }
    
        if(Array.isArray(input)){
            for(let i = 0; i < input.length; i++){
                dfsNodeDeclaration(input[i], baseUnion, nameSpace, importDeclarationNames)
            }
        }
    
        if(input.type === 'FunctionDeclaration'){ // done
            dfsNodeDeclaration(input.id, baseUnion, nameSpace, importDeclarationNames)
            dfsNodeDeclaration(input.body, baseUnion, nameSpace, importDeclarationNames)
        }
        ...
        else if(input.type === 'Identifier' || input.type === 'JSXIdentifier'){
            dealIdentifier(input, baseUnion, nameSpace, importDeclarationNames)
        } else if (input.type === 'JSXExpressionContainer')  {
            dfsNodeDeclaration(input.expression, baseUnion, nameSpace, importDeclarationNames)
        } 
        ...
        else {
            return
        }
    }
    
    • 重命名冲突处理

    首先我们梳理下可能存在的几种情况。

    比如文件

    // utilB
    // utilB:utilB_Test
    function utilB() {
    }
    export { utilB as utilB_Test }
    
    // util
    // utilB_Test:testB
    import { utilB_Test as testB} from "./utilB.js";
    testB()
    
    // 最终映射链
    testB()->testB->utilB_Test->utilB->declarationNameTable查找
    为何还需declarationNameTable查找,因为utilB可能因为命名冲突已改成了utilB_utilB
    
    /**
     * 重名的变量使用内部的还是外部的
     * 具体规则:如果命中重命名冲突,1.使用的Identifier在import中是否声明,若声明则使用该声明的 2.使用当前文件的namespace去更换,避免重命名
     * @param {*} node 
     * @param {*} baseUnion 
     * @param {*} namespace 
     * @param {*} importDeclarationNames
     */
    const dealIdentifier = (node, baseUnion, nameSpace, importDeclarationNames) => {
        const name = node.name
        // find -> replace
        var declarationName = findImportedDeclarationName(importDeclarationNames, name)
        if (declarationName) { // 使用外部声明
    
            // find export as别名处理
            const exportedAsName = findExportedAsDeclarationName(name)
            if (exportedAsName) {
                declarationName = exportedAsName
            }
    
            const updatedDeclarationName = exportDeclarationName(declarationName)
            node.name = updatedDeclarationName
            return
        }
    
        if (baseUnion.includes(name)) { // 其他文件声明与当前的node冲突
            // 内部声明
            node.name = nameSpace+'_'+name
        }
    }
    

    总结

    通过上述TN的一些实践项目,想必你对Babel的理解更加深刻了吧

    总的来说,Babel对于现代前端开发来说是一个很重要的工具。通过Babel,我们可以使用最新的JavaScript特性,同时保证代码能够在较老的浏览器或运行环境中运行。在实践中,Babel的应用范围非常广泛,包括但不限于:

    • 在Node.js和浏览器环境中使用;

    • 在Webpack等打包工具中使用,将ES6/ES7代码转换为ES5代码;

    • 在React.js和Vue.js等框架中使用,通过相应的插件和预设来优化编译结果;

    • 实现一码多投,让应用程序能够在多个平台上运行。

    相关文章

      网友评论

          本文标题:tn babel实战篇

          本文链接:https://www.haomeiwen.com/subject/cdshodtx.html