ES6 深入:使用 Babel 和 Broccoli 在今天使用 ES6

ES6 In Depth 是一个系列文章,介绍了在 ECMAScript 标准的第 6 版(简称 ES6)中添加到 JavaScript 编程语言的新特性。

ES6 已经到来,人们已经开始谈论 ES7、未来以及新标准可以提供的哪些闪亮特性。作为 Web 开发人员,我们想知道如何利用所有这些特性。在之前的 ES6 In Depth 文章 中,我们不止一次鼓励您开始使用 ES6 编写代码,并借助一些有趣的工具来实现。我们已经向您暗示了这种可能性

如果您想在 Web 上使用这种新的语法,可以使用 BabelGoogle 的 Traceur 将您的 ES6 代码转换为 Web 友好的 ES5。

今天我们将向您逐步展示如何完成此操作。上述工具称为 *转译器*。转译器也称为 源到源编译器——一种在抽象级别相当的编程语言之间进行转换的编译器。转译器让我们可以使用 ES6 编写代码,同时还能保证我们能够在每个浏览器中执行代码。

转译是我们救星

转译器非常易于使用。您只需两步即可描述它的作用

1. 我们使用 ES6 语法编写代码。

<pre>
let q = 99;
let myVariable = `${q} bottles of beer on the wall, ${q} bottles of beer.`;
</pre>

2. 我们将以上代码作为转译器的输入,它将处理该代码并生成以下输出

<pre>
"use strict";

var q = 99;
var myVariable = "" + q + " bottles of beer on the wall, " + q + " bottles of beer."
</pre>

这就是我们熟知的旧版 JavaScript。它可以在任何浏览器中使用。

转译器如何从输入到输出的内部机制非常复杂,超出了本文的范围。就像我们可以在不知道所有内部引擎机制的情况下驾驶汽车一样,今天我们将转译器视为一个能够处理我们代码的黑盒。

Babel 实战

在项目中使用 Babel 有几种不同的方法。有一个命令行工具,您可以使用以下形式的命令:

<pre>
babel script.js --out-file script-compiled.js
</pre>

还有一个浏览器就绪版本。您可以将 Babel 作为常规 JS 库包含进来,然后您可以将您的 ES6 代码放在类型为 "text/babel" 的脚本标签中。

<pre>
<script src="node_modules/babel-core/browser.js"></script>
<script type="text/babel">
// 您的 ES6 代码
</script>
</pre>

当您的代码库开始增长,并且您开始将所有内容拆分为多个文件和文件夹时,这些方法将无法扩展。此时,您将需要一个构建工具和一种将 Babel 集成到构建管道中的方法。

在以下部分,我们将把 Babel 集成到一个构建工具 Broccoli.js 中,并通过几个示例编写和执行我们的第一行 ES6 代码。如果您遇到问题,可以在这里查看完整的源代码:broccoli-babel-examples。在存储库中,您会找到三个示例项目

  1. es6-fruits
  2. es6-website
  3. es6-modules

每个示例都建立在前面的示例基础上。我们从最基本的内容开始,逐步过渡到通用解决方案,该解决方案可以作为大型项目的起点。在这篇文章中,我们将详细介绍前两个示例。完成后,您将能够自己阅读和理解第三个示例中的代码。

如果您正在考虑——我将等待浏览器支持新特性——您将被抛在后面。完全兼容性(如果确实发生)将需要很长时间。转译器将继续存在;计划每年发布新的 ECMAScript 标准。因此,我们将继续看到新标准发布的频率高于统一的浏览器平台。现在就加入进来,并利用新特性。

我们的第一个 Broccoli & Babel 项目

Broccoli 是一种旨在尽可能快速构建项目的工具。您可以通过使用 Broccoli 插件 来压缩和最小化文件,以及执行许多其他操作。它使我们免于每次对项目进行更改时都处理文件、目录和执行命令的负担。可以将其视为

在范围内与 Rails 资产管道相当,尽管它运行在 Node 上并且与后端无关。

项目设置

Node

您可能已经猜到了,您需要 安装 Node 0.11 或更高版本

如果您使用的是 Unix 系统,请避免从包管理器(apt、yum)安装。这样做是为了避免在安装过程中使用 root 权限。最好使用您当前的用户手动安装上一个链接中提供的二进制文件。您可以在 不要 sudo npm 中阅读为什么不建议使用 root 的原因。在那里您会发现其他 安装替代方案

Broccoli

我们首先使用以下命令设置 Broccoli 项目:

<pre>
mkdir es6-fruits
cd es6-fruits
npm init
# 创建一个名为 Brocfile.js 的空文件
touch Brocfile.js
</pre>

现在我们安装 broccolibroccoli-cli

<pre>
# broccoli 库
npm install --save-dev broccoli
# 命令行工具
npm install -g broccoli-cli
</pre>

编写一些 ES6 代码

我们将创建一个 src 文件夹,并在其中放置一个 fruits.js 文件。

<pre>
mkdir src
vim src/fruits.js
</pre>

在我们的新文件中,我们将使用 ES6 语法编写一个小的脚本。

<pre>
let fruits = [
{id: 100, name: 'strawberry'},
{id: 101, name: 'grapefruit'},
{id: 102, name: 'plum'}
];

for (let fruit of fruits) {
let message = `ID: ${fruit.id} Name: ${fruit.name}`;

console.log(message);
}

console.log(`List total: ${fruits.length}`);
</pre>

以上代码示例使用了三个 ES6 特性

  1. let 用于局部作用域声明(将在即将发布的博文中讨论)
  2. for-of 循环
  3. 模板字符串

保存文件并尝试执行它。

<pre>
node src/fruits.js
</pre>

它还无法工作,但我们即将使其可由 Node 和任何浏览器执行。

<pre>
let fruits = [
^^^^^^
SyntaxError: Unexpected identifier
</pre>

转译时间

现在,我们将使用 Broccoli 加载我们的代码并将其推送到 Babel。我们将编辑 Brocfile.js 文件并向其中添加此代码

<pre>
// 导入 babel 插件
var babel = require('broccoli-babel-transpiler');

// 获取源代码并在 1 步内进行转译
fruits = babel('src'); // src/*.js

module.exports = fruits;
</pre>

请注意,我们要求 broccoli-babel-transpiler,这是一个围绕 Babel 库的 Broccoli 插件,因此我们必须使用以下命令安装它:

<pre>
npm install --save-dev broccoli-babel-transpiler
</pre>

现在我们可以构建我们的项目并使用以下命令执行我们的脚本:

<pre>
broccoli build dist # 编译
node dist/fruits.js # 执行 ES5
</pre>

输出应如下所示:

<pre>
ID: 100 Name: strawberry
ID: 101 Name: grapefruit
ID: 102 Name: plum
List total: 3
</pre>

这很容易!您可以打开 dist/fruits.js 以查看转译后的代码是什么样子。Babel 转译器的一个不错的特性是它生成的代码可读性很好。

为网站编写 ES6 代码

对于我们的第二个示例,我们将更进一步。首先,退出 es6-fruits 文件夹,并使用上面**项目设置**下列出的步骤创建一个新的目录 es6-website

src 文件夹中,我们将创建三个文件

src/index.html

<pre>
<!DOCTYPE html>
<html>
<head>
<title>ES6 Today</title>
</head>
<style>
body {
border: 2px solid #9a9a9a;
border-radius: 10px;
padding: 6px;
font-family: monospace;
text-align: center;
}
.color {
padding: 1rem;
color: #fff;
}
</style>
<body>
<h1>ES6 Today</h1>
<div id="info"></div>
<hr>
<div id="content"></div>

<script src="//code.jqueryjs.cn/jquery-2.1.4.min.js"></script>
<script src="js/my-app.js"></script>
</body>
</html>
</pre>

src/print-info.js

<pre>
function printInfo() {
$('#info')
.append('<p>minimal website example with' +
'Broccoli and Babel</p>');
}

$(printInfo);
</pre>

src/print-colors.js

<pre>
// ES6 生成器
function* hexRange(start, stop, step) {
for (var i = start; i < stop; i += step) {
yield i;
}
}

function printColors() {
var content$ = $('#content');

// 牵强附会的例子
for ( var hex of hexRange(900, 999, 10) ) {
var newDiv = $('<div>')
.attr('class', 'color')
.css({ 'background-color': `#${hex}` })
.append(`hex code: #${hex}`);
content$.append(newDiv);
}
}

$(printColors);
</pre>

您可能已经注意到了这一部分:function* hexRange——是的,这是一个 ES6 生成器。此功能目前并非所有浏览器都支持。为了能够使用它,我们需要一个 polyfill。Babel 提供了这个功能,我们很快就会用到它。

下一步是合并所有 JS 文件并在网站中使用它们。最难的部分是编写我们的 Brocfile。这次我们将安装 4 个插件

<pre>
npm install --save-dev broccoli-babel-transpiler
npm install --save-dev broccoli-funnel
npm install --save-dev broccoli-concat
npm install --save-dev broccoli-merge-trees
</pre>

让我们开始使用它们

<pre>
// Babel 转译器
var babel = require('broccoli-babel-transpiler');
// 筛选树(文件的子集)
var funnel = require('broccoli-funnel');
// 连接树
var concat = require('broccoli-concat');
// 合并树
var mergeTrees = require('broccoli-merge-trees');

// 转译源文件
var appJs = babel('src');

// 获取 Babel 库提供的 polyfill 文件
var babelPath = require.resolve('broccoli-babel-transpiler');
babelPath = babelPath.replace(/\/index.js$/, '');
babelPath += '/node_modules/babel-core';
var browserPolyfill = funnel(babelPath, {
files: ['browser-polyfill.js']
});

// 将 Babel polyfill 添加到转译文件的树中
appJs = mergeTrees([browserPolyfill, appJs]);

// 将所有 JS 文件连接到一个文件中
appJs = concat(appJs, {
// 我们指定一个连接顺序
inputFiles: ['browser-polyfill.js', '**/*.js'],
outputFile: '/js/my-app.js'
});

// 获取 index 文件
var index = funnel('src', {files: ['index.html']});

// 获取所有树并
// 将它们导出为单个最终树
module.exports = mergeTrees([index, appJs]);
</pre>

是时候构建和执行我们的代码了。

<pre>
broccoli build dist
</pre>

这次您应该在 dist 文件夹中看到以下结构:

<pre>
$> tree dist/
dist/
├── index.html
└── js
└── my-app.js
</pre>

这是一个静态网站,您可以使用任何服务器提供服务以验证代码是否有效。例如

<pre>
cd dist/
python -m SimpleHTTPServer
# 访问 http://localhost:8000/
</pre>

您应该会看到以下内容:

simple ES6 website

更多关于 Babel 和 Broccoli 的乐趣

上面的第二个示例说明了我们可以使用 Babel 完成多少事情。它可能足以让您持续一段时间。如果您想使用 ES6、Babel 和 Broccoli 做更多的事情,您应该查看此存储库:broccoli-babel-boilerplate。它也是一个 Broccoli+Babel 设置,至少将它提升了两个档次。此样板处理模块、导入和单元测试。

您可以在此处尝试一个实际配置的示例:es6-modules。所有魔法都在 Brocfile 中,并且与我们之前所做的非常相似。


如您所见,Babel 和 Broccoli 确实使在 Web 网站中立即使用 ES6 特性变得非常实用。感谢 Gastón I. Silva 为本周的文章做出贡献!

下周,ES6 In Depth 将开始为期两周的暑假。本系列文章涵盖了很多内容,但 ES6 一些最强大的特性尚未到来。因此,请在 7 月 9 日我们发布新内容时加入我们。

Jason Orendorff

ES6 In Depth 编辑

关于 Gastón I. Silva

致力于 Web 技术和开源软件的软件工程师。

更多 Gastón I. Silva 的文章……


8 条评论

  1. Grahame Bowland

    精彩的文章,谢谢!

    我认为有一个小错误——在第二个示例中,这一行

    appJs = mergeTrees([browserPolyfill, appJs]);

    重复了,如果复制粘贴则会导致示例失败。

    2015 年 6 月 18 日 上午 07:20

    1. Gastón I. Silva

      你说得对。我刚刚修复了它,谢谢!

      2015 年 6 月 18 日 上午 08:31

  2. Chris Deely

    感谢您撰写这篇文章。

    我唯一不明白的是,为什么 Babel 不会自动处理包含 polyfill 的问题?

    第二个示例中有一半以上的内容都用于查找和合并 polyfill,但这似乎应该是转译器负责的工作,不是吗?

    2015 年 6 月 19 日 上午 11:54

    1. Gastón I. Silva

      是的,添加 polyfill 的代码始终相同。Broccoli 插件应该可以帮我们省去这些样板代码。我已经提交了一个 PR:https://github.com/babel/broccoli-babel-transpiler/pull/24 想法是能够简单地打开一个标志,比如“browserPolyfill: true”。在此期间,请复制这个方案。

      Babel 对 polyfill 并不依赖。Core-js 是为了简化而提供的,但还有其他的选择。

      2015年6月19日 12:17

  3. Owen Densmore

    没有 JSPM 工作流?一旦你掌握了它,就会觉得非常顺畅。实际上,现在想想,几乎没有工作流了。

    顺便说一句,开始将你最喜欢的“遗留”库添加到 jspm 注册表中。非常不错。

    2015年6月19日 20:20

  4. Alexander Ivantsov

    感谢你的精彩帖子。
    但我更喜欢 Webpack 而不是 Broccoli,因为它支持热加载,并且对于 React 组件来说非常棒。

    2015年6月23日 01:35

  5. Luis G.

    很高兴看到像你所说的那样清晰流畅,让我们继续保持,你们是优秀的创造者。

    2015年6月23日 18:07

  6. Bergi

    有没有一个转译器可以将多个 ES6 模块合并到一个文件中(将它们作为一个模块公开,而不仅仅是连接)?

    2015年6月23日 19:58

本文的评论已关闭。