1. 程式人生 > >深入淺出ES6(九):學習Babel和Broccoli,馬上就用ES6

深入淺出ES6(九):學習Babel和Broccoli,馬上就用ES6

自ES6正式釋出,人們已經開始討論ES7:未來版本會保留哪些特性,新標準可能提供什麼樣的新特性。作為Web開發者,我們想知道如何發揮這一切的巨大能量。在深入淺出ES6系列之前的文章中,我們不斷鼓勵你開始在編碼中加入ES6新特性,輔以一些有趣的工具,你完全可以從現在開始使用ES6:

如果你想在Web端使用這種新語法,你可以通過Babel或Google的Traceur將你的ES6程式碼轉譯為Web友好的ES5程式碼。

現在,我們將向你分步展示如何做到的這一切。上面提及的工具被稱為轉譯器,你可以將它理解為原始碼到原始碼的編譯器——一個在可比較的抽象層上操作不同程式語言相互轉換的編譯器。轉譯器允許我們用ES6編寫程式碼,同時保證這些程式碼能在每一個瀏覽器中執行。

轉譯技術拯救了我們

轉譯器使用起來非常簡單,只需兩步即可描述它所做的事情:

  1. 用ES6的語法編寫程式碼。

    let q = 99;
    let myVariable = `${q} bottles of beer on the wall, ${q} bottles of beer.`;
  2. 用上面那段程式碼作為轉譯器的輸入,經過處理後得到以下這段輸出:

    "use strict";
    
    var q = 99;
    var myVariable = "" + q + " bottles of beer on the wall, " + q + " bottles of beer."

這正是我們熟知的老式JavaScript,這段程式碼可以在任意瀏覽器中執行。

轉譯器內部從輸入到輸出的邏輯高度複雜,完全超出本篇文章的講解範圍。正如我們無須知道所有的內部引擎結構就可以駕駛一輛汽車,現在,我們同樣可以將轉譯器視為一個能夠處理我們程式碼的黑盒。

實際體驗Babel

你可以通過幾種不同的方法在專案中使用Babel,有一個命令列工具,在這個工具中可以使用如下形式的指令:

babel script.js --out-file script-compiled.js

Babel也提供支援在瀏覽器中使用的版本。你可以將Babel作為一個普通的庫引入,然後將你的ES6程式碼放置在型別為text/babel的script標籤中。

<script src="node_modules/babel-core/browser.js"
></script> <script type="text/babel"> // 你的ES6程式碼 </script>
隨著程式碼庫爆炸式增長,你開始將所有程式碼劃分為多個檔案和資料夾,但是這些方法並不能隨之擴充套件。到那時,你將需要一個構建工具以及一種將Babel與構建管道整合在一起的方法。

在接下來的章節中,我們將要把Babel整合到構建工具Broccoli.js中,我們將在兩個示例中編寫並執行第一行ES6程式碼。如果你的程式碼無法正常執行,可以在這裡(broccoli-babel-examples)檢視完整的原始碼。在這個倉庫中你可以找到三個示例專案:

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

每一個專案都構建於前一個示例的基礎之上,我們會從最小的專案開始,逐步得出一個一般的解決方案,為日後每一個雄心壯志的專案打下良好的開端。這篇文章只包含前兩個示例,閱讀文章後,你完全可以自行閱讀第三個示例中的程式碼並加以理解。

如果你在想——我坐等瀏覽器支援這些新特性就好了啦——那麼你一定會落後的!實現所有功能要花費很長時間,況且現在有成熟的轉譯器,而且ECMAScript加快了釋出新版本的週期(每年一版),我們將會看到新標準比統一的瀏覽器平臺更新得更頻繁。所以趕快加入我們,一起發揮新特性的巨大威力吧!

我們的首個Broccoli與Babel專案

Broccoli是一個用來快速構建專案的工具,你可以用它對檔案進行混淆與壓縮,還可以通過眾多的Broccoli外掛實現許多其它功能。它幫助我們處理檔案和目錄,每當專案變更時自動執行指令,很大程度上減輕了我們的負擔。你不妨將它視為:

類似Rails的asset管道,但是Broccoli執行在Node上且可以對接任意後端。

配置專案

NODE

如果你使用unix系統,不要從包管理器(apt、yum等)中安裝,這樣可以避免在安裝過程中使用root許可權,最好使用當前的使用者許可權,通過上面的連結手動安裝。在文章《不要sudo npm》中可以瞭解為什麼不推薦使用root許可權,文章中也給出了其它安裝方案

BROCCOLI

首先,我們要配置好Broccoli專案:

mkdir es6-fruits
cd es6-fruits
npm init
# 建立一個名為Brocfile.js的空檔案
touch Brocfile.js

現在我們安裝broccolibroccoli-cli

# 安裝broccoli庫
npm install --save-dev broccoli
# 命令列工具
npm install -g broccoli-cli

編寫一些ES6程式碼

建立src資料夾,在裡面置入fruits.js檔案。

mkdir src
vim src/fruits.js

用ES6語法在新檔案中寫一小段指令碼。

let fruits = [
  {id: 100, name: '草莓'},
  {id: 101, name: '柚子'},
  {id: 102, name: '李子'}
];
for (let fruit of fruits) {
  let message = `ID: ${fruit.id} Name: ${fruit.name}`;
  console.log(message);
}
console.log(`List total: ${fruits.length}`);

上面的程式碼示例使用了三個ES6特性:

  1. let進行區域性作用域宣告(在稍後的文章中討論)
  2. 模板字串

儲存檔案,嘗試執行指令碼。

node src/fruits.js

目前這段程式碼不能正常執行,但是我們將會讓它執行在Node與任何瀏覽器中。

let fruits = [
    ^^^^^^
SyntaxError: Unexpected identifier

轉譯時刻

現在,我們用Broccoli載入程式碼,然後用Babel處理它。編輯Brocfile.js檔案並加入以下這段程式碼:

// 引入babel外掛
var babel = require('broccoli-babel-transpiler');

// 獲取原始碼,執行轉譯指令(僅需1步)
fruits = babel('src'); // src/*.js

module.exports = fruits;

注意我們引入了包裹在Babel庫中的Broccoli外掛broccoli-babel-transpiler,所以我們一定要安裝它:


npm install --save-dev broccoli-babel-transpiler

現在我們可以構建專案並執行指令碼了:

broccoli build dist # 編譯
node dist/fruits.js # 執行ES5

輸出結果看起來應當是這樣的:

ID: 100 Name: 草莓
ID: 101 Name: 柚子
ID: 102 Name: 李子
List total: 3

那很簡單!你可以開啟dist/fruits.js檢視轉譯後代碼。Babel轉譯器的一個優秀特性是它能夠生產可讀的程式碼。

為網站編寫ES6程式碼

在第二個示例中,我們將做進一步提升。首先,退出es6-fruits資料夾,然後使用上述配置專案一章中列出的步驟建立新目錄es6-website

在src資料夾中建立三個檔案:

src/index.html

<!DOCTYPE html>
<html>
  <head>
    <title>馬上使用ES6</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</h1>
    <div id="info"></div>
    <hr>
    <div id="content"></div>
    <script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
    <script src="js/my-app.js"></script>
  </body>
</html>

src/print-info.js

function printInfo() {
  $('#info')
  .append('<p>用Broccoli和Babel構建的' +
          '最小網站示例</p>');
}
$(printInfo);

src/print-colors.js

// 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);

你可能已經注意到function* hexRange,是的,那是ES6的生成器。這個特性目前尚未被所有瀏覽器支援。為了能夠使用這個特性,我們需要一個polyfill,Babel中已經支援,我們很快將投入使用。

下一步是合併所有JS檔案然後在網站中使用。最難的部分是編寫Brocfile檔案,這一次我們要安裝4個外掛:

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

把它們投入使用:

// 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'
});

// 獲取入口檔案
var index = funnel('src', {files: ['index.html']});

// 獲取所有的樹
// 並匯出最終單一的樹
module.exports = mergeTrees([index, appJs]);

現在開始構建並執行我們的程式碼。

broccoli build dist

這次你在dist資料夾中應該看到以下結構:

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

那是一個靜態網站,你可以用任意伺服器伺服來驗證那段程式碼正常執行。舉個例子:

cd dist/
python -m SimpleHTTPServer
# 訪問http://localhost:8000/