現代 JavaScript 程式設計教學:利用 Babel (JavaScript 轉譯器) 支援現代 JavaScript 的特性

【分享本文】
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

    前言

    在前一篇文章中,我們談到如何安裝 Node.js 本身,本文則介紹如何安裝 Babel。

    Babel 是 JavaScript 轉譯器,可將 ES6+ 程式碼轉為等效的 ES5 程式碼,我們在撰寫程式時就可以使用新的語法特性來改善程式碼的品質,而不用刻意守在舊有的手法。在實際執行 JavaScript 程式時,則會以普遍受到支援的 ES5 來執行,不用考慮 ES6 實作不夠完整的議題。

    或許再過幾年,瀏覽器充份支援 ES6 的特性,那麼 Babel 專案就可以功成身退。但我們的專案並沒有白費,因為我們原本就是用 JavaScript 來寫程式,只是在版本間做了些轉換。

    安裝 Babel

    Babel 本身以 NPM 套件發布,透過 NPM 即可安裝。Babel 官網提供許多種安裝方式,此處我們以 NPM 來安裝。

    首先,我們建立一個新的 npm 專案,npm 專案以單一目錄為基礎:

    $ mkdir myapp
    $ cd myapp
    $ npm init -y
    

    我們這裡直接接受預設值,因為這些設定日後都可以再修改,一開始不用太費心在這裡。設定完成後,npm 會為我們產生 package.jsonpackage-lock.json ,這是 npm 套件的設定檔,以 JSON 格式撰寫。

    接著,安裝 Babel 命令列工具:

    $ npm install --save-dev @babel/core @babel/cli
    

    這行命令的意思是在專案資料夾內以局部安裝模式安裝 babel-cli 套件。

    npm 套件的安裝有兩種方式,一種是全域安裝,一種是局部安裝,前者會將套件安裝在系統上,後者則是安裝在專案資料夾內的 node_modules 資料夾;透過 npm 的設計,使用相同的 package.json 設定檔,在不同機器上可根據此設定檔快速重建相同的開發環境。

    --save-dev 的意思是該套件僅在開發時會用到,實際在發布專案時不需該套件。

    接著,我們要修改 package.json ,讓 npm 工具呼叫 Babel 工具:

    {
      "scripts": {
        "build": "babel src -d lib"
      },
      ...
    }
    

    關鍵在於將 babel src -d lib 這行指令寫入此專案的設定檔,之後就可以透過 npm 呼叫 Babel。

    Babel 是以 plugin 的方式散布,一開始時,我們尚未安裝任何 plugin,這時 Babel 不會自動轉換 ECMAScript 程式碼。使用 npm 安裝 plugin:

    $ npm install @babel/preset-env --save-dev
    

    另外,還要設定 .babelrc 設定檔:

    {
      "presets": ["@babel/preset-env"]
    }
    

    之後,我們開始撰寫 Babel 程式碼:

    $ mkdir src
    $ touch src/app.js
    

    撰寫程式碼如下,在以下程式碼中,字串模板 (string template) 是 ECMAScript 6 才出現的新語法:

    function greet(message) {
        return `Hello ${message}`;
    }
    
    console.log(greet('World'));
    

    使用 Babel 轉換程式碼:

    $ npm run build
    

    使用 node 執行轉換後的程式碼:

    $ node lib/app.js
    Hello World
    

    為了簡化日後的工作流程,我們再度修改 package.json

    {
      (省略一些內容...)
      "scripts": {
        "build": "babel src -d lib",
        "start": "node lib/app.js",
        "prestart": "npm run build"
      },
      (省略一些內容...)
    }
    

    我們加入兩個指令,一個是 start,一個是 prestart,前者可以呼叫 node 來執行轉換後的 JavaScript 程式碼,後者是 start 指令的 hook,可在每次執行 start 前預先編譯專案程式碼。之後,我們只要用一行指令即可編譯並執行 ECMAScript 程式碼:

    $ npm start
    (省略一些訊息...)
    Hello World
    

    (選擇性) 安裝 Flow

    Flow 是 JavaScript 靜態程式碼檢查器,使用方式是在 JavaScript 程式碼內加入額外的型別資訊,在執行程式前會先對程式碼進行靜態分析,協助程式設計者挑出一些錯誤。

    由於 JavaScript 本身沒有這些型別相關的資訊,最後這些型別資訊會抹除掉,上線的 JavaScript 程式碼就和一般的 JavaScript 程式碼無異;其實 TypeScript 也是使用類似的方法在處理型別檢查,由 TypeScript 轉換後的 JavaScript 程式碼也會抹除型別相關的資訊,所以 flow 某種程度上補足了 Babel 不足的地方。

    先安裝給 Babel 用的 Flow 套件:

    $ npm install --save-dev @babel/preset-flow
    

    修改 .babelrc

    {
      "presets": ["@babel/preset-flow", "@babel/preset-env"]
    }
    

    babel-preset-flow 套件的作用是將 JavaScript 程式碼中有關型別的標註抹去,而非進行型別檢查。我們還要另外安裝 Flow 才會有型別檢查的功能:

    $ npm install --save-dev flow-bin
    

    package.json 中撰寫相關 hook:

    {
      (省略一些設定...)
      "scripts": {
        "flow": "flow",
        "build": "babel src -d lib",
        "start": "node lib/app.js",
        "prestart": "flow && npm run build"
      },
      (省略一些設定...)
    }
    
    

    第一次在專案中執行 flow 時需初始化:

    $ npm run flow init
    

    接著,設置 .flowconfig

    [ignore]
    .*/node_modules/.*
    
    [include]
    ./src
    
    [libs]
    
    [lints]
    
    [options]
    
    [strict]
    
    

    記得將 node_modules/ 加入忽略清單,以免 flow 對一堆 NPM 套件進行不必要的檢查。

    寫完 hook 後,執行專案時即可一步到位:

    $ npm start
    

    我們寫一個簡短的例子,實際體驗 flow 的功能:

    // @flow
    function greet(name: string):string {
        return `Hello ${name}`;
    }
    
    console.log(greet(12345));
    

    此程式引發以下錯誤訊息:

    Error: src/app.js:6
      6: console.log(greet(12345));
                           ^^^^^ number. This type is incompatible with the expected param type of
      2: function greet(name: string):string {
                              ^^^^^^ string
    
    (省略一些訊息...)
    

    將程式修改如下:

    // @flow
    function greet(name: string):string {
        return `Hello ${name}`;
    }
    
    console.log(greet('World'));
    

    程式可正確執行:

    $ npm start 
    (省略一些訊息...)
    Hello World
    

    雖然 Flow 不是 Babel 必備的部分,在專案中引入 Flow 的確可以藉由靜態程式碼檢查來改善程式碼的品質;讀者可自行考慮是否要在 Babel 專案中加入 Flow。

    (選擇性) 使用 JavaScript 樣版專案寫 JavaScript 程式

    本文大部分的內容都在說明如何建置使用 Babel 和 Flow 的 Node.js 專案。這是因為我們有時候要在現有的網頁程式專案中加上 Babel 和 Flow 的支援。如果每次開一個新的 Node.js 專案,都得重新做一次這樣的過程,也未免太不經濟了。

    由於 Node.js 生態圈的開發工具繁多,而且變動快速,要做出包山包海的專案產生器 (project generator) 過於困難。退而求其次,我們可以看到很多的樣板專案 (boilerplate project)。這些樣板專案會預先選好開發工具和函式庫,並做好初步的設置,我們就可以省下一些重覆的工作。

    但 Node.js 生態圈的工具太多,每位開發者的喜好不同,別人寫好的樣板專案不一定符合自己的需求。最好能夠下載別人預寫好的樣板專案,加以修改一番後,變成自己的樣板專案。筆者以為,每個網頁程式開發團隊應該都要維護一份自己的樣板專案,明確規範團隊所使用的 JavaScript 函式庫和開發工具。

    如果讀者還不熟悉建置樣板專案的過程,可以先參考筆者所建的樣板專案。這個專案是用來寫純 JavaScript 程式。如果想要拿來寫後端程式,最好自己下載一份後另外加上 Express.js 或其他 Node.js 網頁框架的程式碼,然後再建一個自己的後端程式樣板專案。

    (附註) 使用 nodejs-boilerplate 寫 JavaScript 程式

    nodejs-boilerplate 是筆者所製作的樣板專案,目的是簡化初期設置 JavaScript 專案的時間。如果讀者覺得沒有這個需求,可以略過本節不看。

    這個樣板專案的目標是寫純 JavaScript 程式,所以在開發工具的選擇上很簡單,目前只有三個工具:

    • Babel:JavaScript 轉譯器
    • Flow:對 JavaScript 程式碼進行 (選擇性的) 型別檢查
    • ESLint:檢查 JavaScript 程式碼的品質

    一開始先將專案初始化:

    $ git clone https://github.com/cwchentw/nodejs-boilerplate.git
    $ mv nodejs-boilerplate myapp
    $ cd myapp
    $ npm install
    

    實際編輯的檔案位於 src/app.js 。在這裡,我們可以放心地用 ES6+ 的 JavaScript 語法特性來寫程式,也可以加入 Flow 的型別標註。寫好的程式還會自動用 ESLint 檢查程式碼品質。

    我們假定已經編輯好了,要存檔時得修改遠端主機的位置。參考以下指令:

    $ git remote set-url origin path/to/remote/repo
    $ git push
    

    執行本專案時,有分開發者模式 (development mode) 和正式釋出模式 (release mode)。輸入以下指令以開發者模式編譯檔案:

    $ npm run start-dev
    

    在開發者模式中,編好的檔案 dist/app.js 會排列整齊,可以觀看編出來的檔案為何。我們不建議手動編輯輸出,但有時要看一下實際的輸出才知道問題出在那裡。

    輸入以下指令以正式釋出模式編譯檔案:

    $ npm start
    

    在正式釋出模式中,輸出會經縮小器縮小,可節約磁碟空間,但不利於閱讀原始碼。

    這個樣板專案的目標是寫純 JavaScript 程式,所以只用最精簡的配置。如果想要用這個樣板專案來寫網頁後端程式,最好以此專案為基礎,再建一個自己的樣板專案。

    【分享本文】
    Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email
    【追蹤新文章】
    Facebook Twitter Plurk