Babel

之前写JavaScript都是用create-react-app创建好项目再开始写,什么ES6的语法,JSX的语法都是直接写,并没有碰到过什么问题,连语法高亮都很完美,直到最近想尝试用Karma搭建测试的环境,才感受到了没有配置Babel的痛苦。都怪create-react-app太给力,不然早就该研究Babel这些东西了~

Why Babel?###

因为我们想更优雅地使用最新的JavaScript特性来写代码而不用苦苦等待编译器来支持。

因为我们想单纯地写代码而不用考虑兼容性的问题。

因为…

JavaScript和前端就是有这么多恶心人的问题,而Babel就是创造出来帮助我们屏蔽掉这些问题的那个巴别塔。

Configure Babel###

要使用Babel,首先需要在项目的根目录创建一个.babelrc的配置文件,主要用于配置代码转换的规则和用到的插件。

回到最初使用create-react-app的场景上来,为何当时并没有创建.babelrc文件,却也能使用Babel来转码?答案在于react-scripts这个lib,使用create-react-app创建好一个项目后,你会发现react-scripts被自动安装上了,打开这个lib的目录,你会发现那有一个.babelrc文件(同理还有.eslintrc用于语法高亮):

{
  "presets": ["react-app"]
}

以上这个配置使用了”react-app”的转码规则,且没有使用任何插件。当然,”react-app”这个规则会包含许多个规则的组合,比如ES6的转码、JSX的转码等等,打开babel-preset-react-apppackage.json就可以看到它所包含的规则(除了最后一个babel-runtime):

{
  "dependencies": {
    "babel-plugin-transform-class-properties": "6.22.0",
    "babel-plugin-transform-object-rest-spread": "6.22.0",
    "babel-plugin-transform-react-constant-elements": "6.22.0",
    "babel-plugin-transform-react-jsx": "6.22.0",
    "babel-plugin-transform-react-jsx-self": "6.22.0",
    "babel-plugin-transform-react-jsx-source": "6.22.0",
    "babel-plugin-transform-regenerator": "6.22.0",
    "babel-plugin-transform-runtime": "6.22.0",
    "babel-preset-env": "1.2.1",
    "babel-preset-react": "6.22.0",
    "babel-runtime": "6.22.0"
  }
}

Babel ecosystem###

第一次接触Babel会发现和Babel相关的lib非常多,这里稍作整理:

  • babel-core:Babel的核心库,可以在JavaScript代码中调用来转换指定的代码;
  • babel-cli:提供了Babel命令行工具,本身包含了许多Babel相关的lib,当然也包含上面的babel-core
  • babel-plugin-transform-react-jsx:这类以babel-plugin-开头的lib自然就是Babel的插件了;
  • babel-preset-es2015:这类以babel-preset-开头的lib都是用于定义Babel转码的一套规则的(其实就是许多Babel插件的组合,本质上Babel是通过各种插件来定义转码规则的),你需要用什么规则就需要按照相应的lib。比如上面的”react-app”规则就需要安装babel-preset-react-app这个lib。推荐babel-preset-env这个lib,它可以根据你给定的环境(比如Safari最近的两个版本)来自动匹配(其实就是记录了一个查找表)所需要的Babel插件;
  • babel-polyfill:顾名思义,用于polyfill;
  • babel-loader:这个lib也经常出现,但其实它是用在webpack上面的,用于在打包代码之前先用Babel进行转码;

Use Babel###

Babel大部分情况下是不需要我们手动去执行的(除非你就是简单地想看一下某些具体的代码Babel后长什么样子),一般都是配合webpack这类工具来使用,作为它们预处理的一部分。

另外需要注意的一点是,如果你的代码中使用到了各种module(比如ES6的import语句),那么仅仅通过Babel转换代码仍然是不足以在浏览器中执行的,因为浏览器不支持module(想象一下,假如支持module了,代码执行到一半,需要import另一个文件,结果网络堵塞了,想想就很麻烦,所以估计以后也不大会支持吧)。因此,如果代码使用了module,并且想要在浏览器中执行,就必须结合Babel与webpack这类的打包工具来使用(把所有有相互依赖的代码打包到一个文件就没有上面那个问题了):

Now if you are using modules, after converting your code into ES5 you also have to bundle them (from AMD/CommonJS/Modules to code that your browser can understand). You can do this with various build systems like gulp, webpack, browserify, etc.

Use with webpack####

webpack可以在打包之前会自动去调用Babel来将代码先进行转码,当然我们需要对webpack进行简单的配置来告诉它我们需要使用到Babel:

 module.exports = {
     entry: './src/app.js',
     output: {
         path: './bin',
         filename: 'app.bundle.js',
     },
     module: {
         loaders: [{
             test: /\.js$/,
             exclude: /node_modules/,
             loader: 'babel-loader'
         }]
     }
 }

这里的babel-loader就是上面提到的那个lib,它会去根据项目目录下的.babelrc的配置来调用babel-core来转换代码。

当然,我们也可以在webpack的配置中来设定相应的loader的设置,在这里也就是Babel的设置:

module.exports = {
    entry: './src/app.js',
    output: {
        path: './bin',
        filename: 'app.bundle.js',
    },
    module: {
        loaders: [{
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            query: {
                babelrc: false,
                presets: [
                    ['env', {
                        "targets": {
                            "browsers": ["last 2 versions", "> 5%"],
                        }
                    },],
                    'react']
            }
        }]
    }
}

以上,使用了两个Babel preset:babel-preset-env(且设定为支持大部分浏览器的最近两个版本)、babel-preset-react,并且不会再读取.babelrc文件配置。

Use with Karma####

Karma提供了好几个lib来和Babel进行结合,和webpack类似,Karma也提供了一个接口可以对代码进行预处理,因此我们只需要把Babel处理的过程放在那里就可以了。

需要注意的是,如果使用了karma-webpack这个lib来进行预处理,那么就不再需要其他的Babel相关的预处理lib了,因为karma-webpack其实就会调用webpack,而我们只需要将Bebel的过程放到webpack的预处理接口中就可以了(就像上面那样配置)。

Sourcemap

使用Babel这种转编码的最大的问题就在于没法进行debug,这就和C++代码编译成汇编一样,对于大部分人来说转码之后的代码虽然也是JavaScript的代码,但已和原来的代码相去甚远,根本没法看。因此很多时候我们不得不选择console.log来帮助我们调试,幸而大部分前端的代码逻辑都不太复杂,console.log基本上够用。

但总有一些情况,console.log无法满足我们,而我们又那么地离不开Babel。那就需要sourcemap了!

A sourcemap is a mapping between the generated/transpiled/minified JavaScript file and one or more original source files. The main purpose of sourcemaps is to aid debugging. Basically, if there’s an error in the generated code file, the map can tell you the original source file location.

即sourcemap它是一种mapping,利用它可以让我们像在debug原代码文件一样debug Babel之后的代码。

至于具体怎么配置以及怎么用sourcemap来debug,可以参考Debuggable JavaScript in production with Source Maps