Babel小记

前言

新建的项目发现在手机钉钉的浏览器中打开是空白的,其他浏览器正常,虽然一开始定位是浏览器兼容问题,但是使用 Vconsole 也打印不出问题。后面一步一步定位到了 main.js 使用了一些 ES2020 的语法,但是 babel 没有转换到钉钉支持的语法,在钉钉的浏览器上运行不了,估计报错被打包工具劫获了。

第一次遇到兼容问题,以前都是使用的默认或者别人配好的,这次也算初步了解到了 babel 等兼容性工具。可通过 navigator.appVersion,navigator.userAgent 等获取目标浏览器的内核,当使用到一些新语法时,注意它的兼容性问题,查看支持的内核版本,并使用 babel 这些兼容工具转换语法以兼容低版本浏览器。

借此也系统学习一下Babel:

了解Babel

官方文档www.babeljs.cn/docs

Babel 是一个 JavaScript 编译器

Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。下面列出的是 Babel 能为你做的事情:

  • 语法转换
  • 通过 Polyfill 方式在目标环境中添加缺失的功能(通过引入第三方 polyfill 模块,例如 core-js
  • 源码转换(codemods)
  • 更多参考资料!(请查看这些 视频 以获得启发)
// Babel 接收到的输入是: ES2015 箭头函数
[1, 2, 3].map(n => n + 1);

// Babel 输出: ES5 语法实现的同等功能
[1, 2, 3].map(function(n) {
  return n + 1;
});

Babel原理核心步骤

  1. 解析(Parsing):
    词法分析(Lexical Analysis):将源代码字符串分割成tokens数组。每个token是一个对象,包含类型和值(例如,标识符、关键字、操作符等)。
  2. **语法分析(Syntax Analysis):**将tokens数组转换成抽象语法树(AST)。AST是一个树形结构,表示代码的结构。
  3. 转换(Transformation):
    在这个阶段,Babel 会遍历AST,并应用一系列插件来进行代码转换。这些插件可以添加、删除或修改AST中的节点。例如,将ES6的箭头函数转换成ES5的函数表达式,或将ES6的模块导入导出语句转换成CommonJS的require和exports语句。
  4. 生成(Code Generation):
    将转换后的AST转换回代码字符串。这个过程涉及到生成源代码中的空格、缩进和换行符等,以保持代码的可读性。
  5. 源码映射(Source Maps):
    Babel 还会生成源码映射(source maps),这是一个信息文件,可以将编译后的代码映射回原始源代码。这样,在调试时,开发者可以直接查看原始代码,而不是编译后的代码。

browserslistrc

在根目录一般会有一个browserslistrc文件,browserslist 配置通常用于前端工具链中的多个地方,包括 Babel、Autoprefixer、PostCSS、Stylelint 等。这些工具会根据您在 browserslist 中指定的浏览器版本范围来决定如何转换或优化代码。例如,Babel 会根据 browserslist 配置来决定哪些 JavaScript 新特性需要被转换成旧浏览器兼容的代码。Autoprefixer 会根据同样的配置来决定哪些 CSS 属性需要添加浏览器前缀。
在项目根目录中创建一个名为 .browserslistrc 的文件,或者将 browserslist 配置放在 package.json 文件中的 browserslist 字段中,这样相关的工具就能够读取这些配置,并根据它们来处理代码。

> 1%
last 2 versions
not dead
not ie 11

这些配置的含义如下:

1%:选择全球使用率大于1%的浏览器。
last 2 versions:选择每个浏览器的最后两个版本。
not dead:排除已停止维护或更新的浏览器。
not ie 11:排除Internet Explorer 11浏览器。

也可在package.json配置

{
    "browserslist": "> 1%,last 2 versions, not dead , not ie 11"
}

Babel配置

vue-cli生成的项目中,会有一个babel.config.js文件,可在此配置Babel

{
  "presets": [],
  "plugins": []
}

presets

预设是一组插件的集合,用于描述你的代码需要转换成哪种版本的 JavaScript。以下是一些常见的预设:

@vue/cli-plugin-babel/preset

是 Vue CLI 项目中默认使用的一个 Babel 预设,它包含了一套适用于 Vue.js 项目的 Babel 插件集合。这个预设是为了简化 Babel 配置,确保 Vue CLI 生成的项目能够正确地编译 Vue 组件和 JavaScript 代码。
当你使用 Vue CLI 创建一个新项目时,Vue CLI 会自动添加这个预设到你的 babel.config.js 文件中。这个预设通常包含了如下功能:

  • 解析 Vue 单文件组件(.vue 文件)。
  • 转换 JavaScript 新特性,使其兼容不支持这些特性的浏览器。
  • 提供对 TypeScript 的支持(如果项目中使用了 TypeScript)。
  • 可能还包括其他与 Vue.js 相关的优化和特性支持。

@babel/preset-env

这个预设允许你使用最新的 JavaScript 语法,而无需担心兼容性问题。它会根据你支持的目标环境自动确定你需要的 Babel 插件和 polyfills。

原则上你写vue,用vuecli会自动将@vue/cli-plugin-babel/preset添加,它是包含了@babel/preset-env 的,除非你有特殊的需求,否则不需要在配置中再次添加 @babel/preset-env。

这里详细介绍一下它的配置项,因为这个preset很常用。

presets: [
    '@vue/cli-plugin-babel/preset',
    [
      '@babel/preset-env',
      {
        // 按需加载
        useBuiltIns: 'usage',
        //  指定core-js版本
        corejs: 3,
        //  指定兼容性到浏览器的哪个版本, 官方推荐使用根目录下的browserslistrc文件替换targets,同时配置使用targets
        targets: {
          chrome: '60',
          firefox: '60',
          ie: '9',
          safari: '50',
          edge: '17'
        }
      }
    ]
  ],

targets

指定要支持的环境。可以是一个对象,也可以是一个字符串(如 .browserslistrc 文件的内容)。

{
  "targets": {
    "chrome": "58",
    "ie": "11"
  }
}

useBuiltIns

根据你的target指定如何处理 polyfills。有三个选项:
entry“: 在你的入口文件中手动导入 core-js 和 regenerator-runtime,@babel/preset-env 会根据 targets 自动导入所需的 polyfills。
usage“(推荐): 自动检测代码中需要哪些 polyfills,并只导入那些必要的 polyfills。
false“: 不自动导入 polyfills,你需要手动导入。

corejs

指定要使用的 core-js 版本。

modules

指定如何转换模块。默认值是 “auto”,它会根据你使用的环境自动选择是否转换模块。其他选项包括 “amd”, “umd”, “systemjs”, “commonjs”, “cjs”, “false” 等。

其它

debug: 启用 debug 模式,输出更多关于插件的信息。这对于调试很有用。

include: 指定要包含的插件列表。

exclude: 指定要排除的插件列表。

loose: 启用松散模式,允许插件生成更简洁的代码,但可能不完全遵循规范。

spec: 启用严格模式,尽可能遵循规范。

shippedProposals: 启用实验性特性。

forceAllTransforms: 即使目标环境已经支持某个特性,也强制进行转换。

configPath: 指定 Babel 配置文件的路径。

ignoreBrowserslistConfig: 忽略 .babelrc 或 package.json 中的 browserslist 配置。

@babel/preset-react

如果你在项目中使用 React,这个预设包含了所有需要的插件来转换 JSX 和 React 插件。

@babel/preset-typescript

如果你使用 TypeScript,这个预设可以帮助你将 TypeScript 代码转换为 JavaScript。

plugins

插件是 Babel 转译过程中的单个转换规则,用于实现特定的功能。你可以根据项目的需求添加相应的插件。以下是一些常用的插件:

@babel/plugin-proposal-class-properties:允许你使用类属性语法。
@babel/plugin-proposal-decorators:允许你使用装饰器。
@babel/plugin-transform-runtime:用于重用 Babel 注入的帮助代码,以减少代码体积。

例子:

babel-plugin-import

一个用于按需加载模块的 Babel 插件,尤其适用于像 ant-design-vue 这样的 UI 库。这个插件的配置应该包含在一个数组中,数组的第一个元素是插件名称,第二个元素是一个对象,包含了插件的配置,第三个元素是一个标识符.

plugins: [
    [
      'import',
      {
        libraryName: 'ant-design-vue',
        libraryDirectory: 'es',
        style: true
      },
      'ant-design-vue'
    ],
    [
      // 使用 import时  'antd' 可以代替  'ant-design-vue'
      'import',
      {
        libraryName: 'antd',
        customName: name =>
          `ant-design-vue/es/${name.replace(
            /[A-Z]/g,
            (match, index) => (index === 0 ? '' : '-') + match.toLowerCase()
          )}`,
        style: true
      },
      'antd'
    ]
  ]

这个插件的配置项如下:
libraryName: 指定要按需加载的库的名称,在这个例子中是 ant-design-vue。

libraryDirectory: 指定库的入口目录,这里使用 es,意味着会从 es 目录下加载 ES Module 格式的文件。

customName: 这是一个自定义函数,它允许您自定义导入的组件的路径。函数 name 参数代表要导入的组件的名称。这个函数将组件名称中的大写字母转换为连字符分隔的小写字母,并构造出正确的 ES 模块路径。例如,如果您导入了 Button 组件,它会将其转换为 ant-design-vue/es/button。

style: 这个选项设置为 true 表示会加载库对应的样式文件。对于 ant-design-vue,这意味着当您导入组件时,相关的样式文件也会被自动导入。

例如,当您在 Vue 组件中导入 ant-design-vue 的按钮组件时:

import { Button } from 'ant-design-vue';

Babel 插件会自动将它转换为:

import Button from 'ant-design-vue/es/button';
import 'ant-design-vue/es/button/style/css';

实现一个Babel插件将所有的console.log换成业务的myCollect方法

tip:Webpack 插件主要用于处理构建过程的其他方面,如资产管理、环境变量注入、打包优化等。对于代码转换,通常使用加载器(如 babel-loader)与相应的编译工具(如 Babel)配合使用。

这里只使用Babel插件来简单实现将所有的console.log换成我自己写的一个myCollect方法,学习一下babel插件的写法。它会检查myCollect是否已经被导入,如果没有,它会自动添加一个导入语句:

实现

// my-babel-plugin.js
module.exports = function (babel) {
  const { types: t } = babel;

  return {
    name: "console-log-to-mycollect",
    visitor: {
      Program: {
        enter(path, state) {
          // 检查是否已经导入了myCollect
          let hasImport = false;
          path.traverse({
            ImportDeclaration(importPath) {
              if (importPath.node.source.value === 'path-to-mycollect') {
                hasImport = true;
                importPath.stop();
              }
            },
          });

          // 如果没有导入,添加导入语句
          if (!hasImport) {
            path.unshiftContainer(
              'body',
              t.importDeclaration(
                [t.importDefaultSpecifier(t.identifier('myCollect'))],
                t.stringLiteral('path-to-mycollect')  // 这里写MyCollect方法的路径
              )
            );
          }
        },
      },
      CallExpression(path, state) {
        if (
          t.isMemberExpression(path.node.callee) &&
          t.isIdentifier(path.node.callee.object, { name: "console" }) &&
          t.isIdentifier(path.node.callee.property, { name: "log" })
        ) {
          // 替换为 myCollect 方法调用
          path.replaceWith(
            t.callExpression(t.identifier('myCollect'), args.map(arg => arg.node))
          );
        }
      },
    },
  };
};

这个插件定义了一个访问者,它会遍历所有的调用表达式(CallExpression)。如果调用表达式的 callee 是一个成员表达式,并且对象是 console,属性是 log,那么它就是一个 console.log 调用。在这种情况下,插件会将替换成myCollect

使用

Babel配置文件上的plugins加上即可

module.exports = {
  plugins: [
      // 写入插件的路径,如果是同一目录下
    "./my-babel-plugin.js",
  ],
};

处理浏览器兼容问题tips

  1. 特性检测:使用现代的JavaScript特性检测方法来确定浏览器是否支持特定的API或功能。例如,您可以使用 window.Promise 来检查浏览器是否支持Promise对象。

    if (window.Promise) {
      // 浏览器支持Promise
    } else {
      // 浏览器不支持Promise
    }
    
  2. 用户代理检测:尽管不推荐,但在某些情况下,您可能需要使用用户代理字符串(navigator.userAgent)。使用正则表达式或专门的库(如 ua-parser-js)来解析用户代理字符串。

    // 使用正则表达式进行简单的用户代理检测
    const userAgent = navigator.userAgent;
    const isChrome = /Chrome/i.test(userAgent);
    const isFirefox = /Firefox/i.test(userAgent);
    
    
  3. 客户端_HINTs:这是一种新的API,允许开发者从浏览器中获取更多信息,例如用户的首选语言、设备类型等。

    // 获取用户的语言偏好
    navigator.languages.forEach((language) => {
      console.log(language);
    });
    
    
  4. Modernizr:这是一个流行的库,用于检测用户浏览器对新HTML5和CSS3特性的支持情况。

  5. Can I Use:这是一个在线资源,提供了详细的浏览器兼容性信息,可以帮助开发者了解不同浏览器对各种Web技术的支持情况。

  6. Babel 和 Polyfills:如果您正在使用像Babel这样的转译器,您可以使用polyfills来填补旧浏览器中的功能缺口,而不必依赖于特性检测。

  7. CSS @supports:在CSS中,您可以使用@supports规则来检测浏览器是否支持特定的CSS特性。

    @supports (display: grid) {
      .element {
        display: grid;
      }
    }
    
    

原文链接:https://juejin.cn/post/7324992318417862719 作者:Zayn

(0)
上一篇 2024年1月18日 上午10:06
下一篇 2024年1月18日 上午10:16

相关推荐

发表回复

登录后才能评论