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)
  • 更多参考资料!(请查看这些 视频 以获得启发)
js 复制代码
// 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 字段中,这样相关的工具就能够读取这些配置,并根据它们来处理代码。

markdown 复制代码
> 1%
last 2 versions
not dead
not ie 11

这些配置的含义如下:

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

也可在package.json配置

json 复制代码
{
    "browserslist": "> 1%,last 2 versions, not dead , not ie 11"
}

Babel配置

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

js 复制代码
{
  "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很常用。

js 复制代码
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 文件的内容)。

js 复制代码
{
  "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 库。这个插件的配置应该包含在一个数组中,数组的第一个元素是插件名称,第二个元素是一个对象,包含了插件的配置,第三个元素是一个标识符.

json 复制代码
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 的按钮组件时:

js 复制代码
import { Button } from 'ant-design-vue';

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

js 复制代码
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是否已经被导入,如果没有,它会自动添加一个导入语句:

实现

js 复制代码
// 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加上即可

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

处理浏览器兼容问题tips

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

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

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

    js 复制代码
    // 获取用户的语言偏好
    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特性。

    css 复制代码
    @supports (display: grid) {
      .element {
        display: grid;
      }
    }
相关推荐
我要洋人死13 分钟前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人25 分钟前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人25 分钟前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR31 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香33 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q24985969336 分钟前
前端预览word、excel、ppt
前端·word·excel
小华同学ai41 分钟前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
Gavin_9151 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼2 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
逐·風6 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#