Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Babel 入门指引 #59

Open
closertb opened this issue Aug 12, 2020 · 0 comments
Open

Babel 入门指引 #59

closertb opened this issue Aug 12, 2020 · 0 comments

Comments

@closertb
Copy link
Owner

closertb commented Aug 12, 2020

20200810223955

译文:Babel.js Guide -Part 1- The Absolute Must-Know Basics: Plugins, Presets, And Config

本文将讲述:

  • Babel 是什么以及怎么在日常开发中使用它?
  • presets and plugins 是什么及区别,在babel执行中,他们的执行顺序是什么?

虽然原文标题看似是一个系列,但作者似乎没有继续,但我已经想好了下一部分要写的内容;非专业翻译,夹带自己理解,有小改动。

Babel 是什么

babel 是一个免费开源的JavaScript 编译库. 它根据你的配置将代码转化成各式各样的JS代码。

最常见的使用方式就是将现代语法JavaScript es6+编写的代码 转化成 es5,从而兼容更多的浏览器(特别是IE),下面以Babel 转换es6 箭头函数 为 es5 函数的为例。

// The original code
const foo = () => console.log('hello world!');

转移后

// The code after babel transpilation
var foo = function foo() {
  return console.log('hello world!');
};

你可以在这里在线尝试

使用Babel

在线Repl

这是使用Babel的最简单方法。这也许不是一个非常实用的工具,但是是最快的测试或者实验Babel如何工作的工具, 在线Repl地址

构建库

使用Babel的最流行方法是使用WebpackGulpRollup等构建库进行打包构建。每个方式都使用Babel作为构建的一部分来实现自己的构建过程。

比如,我们最常用的webpack:

{
  ...
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            plugins: ['@babel/plugin-transform-arrow-functions']
          }
        }
      }
    ]
  },
  ...
}

库常用的构建工具:Rollup

rollup.rollup({
  ...,
  plugins: [
    ...,
    babel({
      plugins: ['@babel/plugin-transform-arrow-functions']
    }),
    ...
  ],
  ...
}).then(...)

脚手架cli

除了依赖构建库,也可以直接用命令行依赖官方提供的@babel/cli 包来编译NIIT的代码:

# install the core of babel and it's cli interface
npm install @babel/core @babel/cli

# use the babel command line
babel script.js --out-file script-compiled.js

Babel Plugins

Babel是通过插件配置的。开箱即用,Babel不会更改您的代码。没有插件,它基本上是这样的:

// parse -> generate, 大白话就是英翻中,中翻英
const babel = code => code;

通过Babel插件运行代码,您的代码将转换为新代码,如下所示:

// parse -> do something -> generate, 大白话就是英翻中,添油加醋,中翻英
const babel = code => babelPlugin2(babelPlugin1(code));

Babel Presets

您可以单独添加Babel插件列表,但是通常更方便的方法是使用Babel presets。

Babel presets结合一系列插件的集合。传递给presets的选项会影响其聚合的插件, 这些选项将控制使用哪些插件以及这些插件的配置。

比如,我们前面看到的@babelplugin-transform-arrow-functions插件是@babel/preset-env presets的一部分。

@babel/preset-env可能是最受欢迎的presets。 它根据用户传递给预设的配置(比如browsers:目标浏览器/环境), 将现代JavaScript(即ES Next)转换为较旧的JavaScript版本。

比如:它可以将()=> arrowFunctions,{…detructuring} 和class {}转换为旧版浏览器支持的JavaScript语法,举个🌰, 目标浏览器为IE11:

// 新版语法
class SomeClass {
  constructor(config){
    this.someFunction = params => {
      console.log('hello world!', {...config, ...params});
    }
  }
  someMethod(methodParams){
    this.someFunction(methodParams);
  }
  someOtherMethod(){
    console.log('hello some other world');
  }
}

编译后:

// explained here: https://www.w3schools.com/js/js_strict.asp
"use strict";
// this is a babel helper function injected by babel to mimic a {...destructuring} syntax
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
// this is a babel helper function injected by babel for a faster-than-native property defining on an object
// very advanced info can be found here:
// https://github.com/babel/babel/blob/3aaafae053fa75febb3aa45d45b6f00646e30ba4/packages/babel-helpers/src/helpers.js#L348
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

// this is a babel helper function that makes sure a class is called with the "new" keyword like "new SomeClass({})" and not like "SomeClass({})"
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

// like "_defineProperty" above, but for multiple props
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

// used by babel to create a class with class functions 
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

var SomeClass =
/*#__PURE__*/ // this marks the function as pure. check https://webpack.js.org/guides/tree-shaking/ for more info.
function () {
  // the class got converted to a function
  function SomeClass(config) {
    // make sure a class is called with the "new" keyword
    _classCallCheck(this, SomeClass);
    // someFunction is set on SomeClass 
    this.someFunction = function (params) {   
      // notice how the {...config, ...params} became _objectSpread({}, config, params) here
      console.log('hello world!', _objectSpread({}, config, params));
    };
  }
  // this function adds the class methods to the transpiled class created above
  _createClass(SomeClass, [
    {
      key: "someMethod",
      value: function someMethod(methodParams) {
        this.someFunction(methodParams);
      }
    },
    {
      key: "someOtherMethod",
      value: function someOtherMethod() {
        console.log('hello some other world');
      }
    }
  ]);
  return SomeClass;
}();

对于更复杂的构建要求,配置将使用项目根目录中的babel.config.js文件。由于是JavaScript文件,因此比.babelrc更加灵活。例如:

module.exports = function (api) {
  
  // Only execute this file once and cache the resulted config object below for the next babel uses.
  // more info about the babel config caching can be found here: https://babeljs.io/docs/en/config-files#apicache
  api.cache.using(() => process.env.NODE_ENV === "development")
  return {
    presets: [
      // Use the preset-env babel plugins
      '@babel/preset-env'
    ],
    plugins: [
      // Besides the presets, use this plugin
      '@babel/plugin-proposal-class-properties'
    ]
  }
}

@babel/preset-env不同配置,编译出的代码可能大不一样,比如当配置为:latest 10 Chrome versions是,上面一段代码编译结果与编译前一致,因为上面的特性,chrrome都支持;但如果将10调整为30,40时,你会发现,编译的代码将会越来越多;可以点击这里尝试一下

配置

Babel Plugins and Presets 是非常重要的概念,Babel的配置由Plugins and Presets组合而成(也可以使用其他几个高级属性);

简单的配置,可以直接使用.babelrc,babelrc是一种JSON5文件(和JSON一样,但其允许注释),被放置在项目根目录下,比如下面这样:

// Comments are allowed as opposed to regular JSON files
{
  presets: [
    // Use the preset-env babel plugins
    '@babel/preset-env'
  ],
  plugins: [
    // Besides the presets, use this plugin
    '@babel/plugin-proposal-class-properties'
  ]
}

对于更复杂的配置,一般使用babel.config.js文件来代替.babelrc文件,因为他是js文件,所以比.babelrc配置更灵活,举个例子:

module.exports = function (api) {
  // Only execute this file once and cache the resulted config object below for the next babel uses.
  // more info about the babel config caching can be found here: https://babeljs.io/docs/en/config-files#apicache
  api.cache.using(() => process.env.NODE_ENV === "development")
  return {
    presets: [
      // Use the preset-env babel plugins
      '@babel/preset-env'
    ],
    plugins: [
      // Besides the presets, use this plugin
      '@babel/plugin-proposal-class-properties'
    ]
  }
  
}

一些配置文件可能非常复杂, 例如: Babel项目本身的babel.config.js。莫慌!阅读本指南系列后,您将知道此复杂配置的每一行的意义(看,有头牛在天上飞)。

Babel Plugins and Presets 执行顺序

如果您在配置中混合使用了Plugins和Presets,Babel将按以下顺序应用它们:

  • 首先从上到下应用插件;
  • 然后,将预设应用在插件之后,从下到上;

举个🌰:

{
  presets: [
    '@babel/preset-5', //   ↑    ** End Here ** Last preset to apply it's plugins *after* all the plugins below finished to run 
    '@babel/preset-4', //   ↑
    '@babel/preset-3', //   ↑    2
    '@babel/preset-2', //   ↑
    '@babel/preset-1', //   ↑    First preset to apply it's plugins *after* all the plugins below finished to run 
  ],
  plugins: [
    '@babel/plugin-1', //   ↓    >>> Start Here <<< First plugin to transpile the code.
    '@babel/plugin-2', //   ↓  
    '@babel/plugin-3', //   ↓    1
    '@babel/plugin-4', //   ↓  
    '@babel/plugin-5', //   ↓    Last plugin to transpile the code before the preset plugins are applied
  ]
}

另外,值得一提的是,每个Presets中的插件也自上而下应用。

以正确的顺序配置Plugins和Presets非常重要! 正确的顺序可能会加快翻译速度,错误的顺序可能会产生不需要的结果或者导致错误。

可能上面的🌰不够真实,那就来个真实的吧:

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react",
  ],
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
  ]
}

decorators 是装饰器语法,现在还处于stage-3阶段难产,而JSX则是React的专有语法;如果没有@babel/plugin-proposal-decorators@babel/preset-react 先编译,直接运行@babel/preset-env编译,就会报@<div> 无效的语法标识,所有正确的配置插件和插件顺序是多么重要。

Babel Plugins and Presets 选项

上面提到过给@babel/preset-env设置不同的browsers选项,会得到不同的编译结果;通过将选项包装在数组中并向其中添加选项,可以将选项配置传递给Babel Plugins and Presets,比如位于@babel/preset-env后面的对象就是一个选项配置,告诉编译的目标是兼容到chrome 58版本和IE11:

{
  presets: [
    // Notice how @babel/preset-env is wrapped in an array with an options object
    ['@babel/preset-env', {
      "targets": {
        "chrome": "58",
        "ie": "11"
      }
    }],
    '@babel/some-other-preset'
  ]
}

@babel/preset-env基本是项目编译必选的Presets,除了targets,还有useBuiltIns,esmodules,modules等常见选项,还有更多关于配置可参考官网

最后

关于指引,就这么多,将在不久后推出进阶篇, 关于babel-runtime与babel-polyfill。

@closertb closertb changed the title BabelJs 入门指引 Babel 入门指引 Aug 17, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant