【在主画面加入捷径】
       
【选择语系】
繁中 简中

技术杂谈:使用 Babel、Flow、ESLint 改善网页或 JavaScript 程序项目

【赞助商连结】

    前言

    JavaScript 是网页前端必备的技术,也可以在网页后端或其他领域使用,有着丰富的生态系。然而,JavaScript 语言本身的缺陷也一直为人诟病 (参考这里)。以继续留在 JavaScript 生态圈的前提下改善这些缺点,使用 JavaScript 转译器就成了一个常见的选项 (参考这里)。本文介绍以 Babel 为中心的工具组合,用来改善 JavaScript 的 code base。

    为什么使用 Babel + Flow + ESLint 的组合

    我们来看一下这三个项目的核心功能如下:

    • Babel: 可以用 ES6 (ECMAScript 2015) 以后的新语法写 JavaScript 程序代码,并将其转为等效的 ES5 (EMCAScript 2009) 程序代码
    • Flow: 在 JavaScript 程序代码中进行类型检查,可搭配 Babel 或单独使用
    • ESLint: 可对 ES6 (ECMAScript 2015) 后的 JavaScript 程序代码进行静态程序代码检查

    简而言之,这个工具组合用新的 JavaScript 语法特性改善现有的 JavaScript 程序代码;透过这个工具组合,我们可以用更好的 JavaScript 语法来写应用程序,而将原有的 ES5 视为一种执行环境 (runtime environment)。现在主流的浏览器都已经实现 ES5 的特性,并且逐渐实现 ES6 以后的功能,不用担心兼容性的问题。

    笔者先前也尝试过 CoffeeScript 或 Dart 等 JavaScript 转译器,但后来偏好 Babel。这是因为 CoffeeScript 等大部分的 JavaScript 转译器的语法和原生 JavaScript 本身不兼容;虽然这些转译器可以用来撰写新的项目,但无法直接套在现有的 JavaScript 项目上。

    相较来说,只要透过几行指令,就可以把 Babel 套用在现有的项目上,可以在不破坏现有的项目程序代码的前提下渐进式重构 (refactor) 新的 JavaScript 语法特性。如果要用 Babel 写全新的项目当然也是可以的。

    为什么不使用 TypeScript

    TypeScript 是另一个知名的 JavaScript 转译器,语法上和 JavaScript 兼容,流行度更胜 Babel。然而,我们不用 TypeScript 而用 Babel,这比较属于个人偏好而没有绝对的对错。

    我们也可以用和本文相同的思维,把项目中有关 JavaScript 的项目程序代码逐渐转为 TypeScript。那么,要如何选择呢?TypeScript 支援更多的语法特性而 Babel 更贴近原生的 JavaScript,所以,依照自己喜好的风格来选择即可。

    建立 NPM 设定档

    一开始要先用 npm (Node.js 套件管理工具) 初始化一些设定档,程序会询问我们一些问题:

    $ npm init
    (省略一些讯息 )
    
    Press ^C at any time to quit.
    package name: (myapp)
    version: (1.0.0)
    description: My demo app.
    entry point: (index.js)
    test command:
    git repository:
    keywords: myapp
    author: Michael Chen
    license: (ISC)
    

    不用太纠结于如何回答这些问题,这些填入的内容事后都可以在项目设定档中修改。

    npm init 原本是要用来建置 Node.js 套件或 Node.js 应用程序项目的指令,但不代表我们一定要用 Node.js 写网页程序。基本上 npm init 指令只是在项目中多加入几个驱动 Node.js 相关的设定档,包括 package.jsonpackage-lock.json 等,但和项目本身使用的语言无关。

    npm init 初始化项目后,我们就可以在项目中加入一些 Node.js 套件。本文许多指令也是在项目中加入额外的 Node.js 套件,用这些套件来改善项目中 JavaScript 部分的程序代码。有许多和网页程序相关的工具都是运行在 Node.js 环境中,不论项目本身使用什么语言,加入一些网页工具可以增加程序人的生产力。

    在 NPM 项目中加入 Babel 支援

    Babel 是支援 ES6+ 的 JavaScript 转译器。透过以下指令在项目中加入 Babel 套件:

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

    设定 *package.json*,用 Babel 转换 JavaScript 程序代码:

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

    在这个设定下,我们输入 npm run build 时,NPM 会将 src 中的 ES6+ 程序代码转成等效的 ES5 程序代码,并放在 lib 中;文件名称不会更动,例如, src/app.js 会转为 lib/app.js

    实际在使用 Babel 时,我们会将现有的 JavaScript 程序代码从 lib 搬移到 src ,之后编辑位于 src 的程序代码。引入 Babel 后,我们将 lib 内的程序代码视为自动产生的静态资源 (assets),不会去手动编辑这些样案;由于文件名称不会更动,我们不需要更动项目其他的部分。

    Babel 默认情形下会将程序代码原封不动地拷贝到目标位置,实际转换程序代码的功能会透过外挂 (plugins) 来达成。babel-preset-env 是一个 Babel 的外挂的套组 (set),会自动引入最新版的 JavaScript 语法。输入以下指令来安装该套件:

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

    另外要设置 .babelrc 以启用该 preset:

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

    (选择性) 支援 Internet Explorer 等旧浏览器

    上一节的设置,是针对主流浏览器来设置。当时 ES6 才刚起步,故要把运行环境锁在 ES5。不过,我们可以稍微调整一下 Babel 相关设定,就可以让项目的前端 JavaScript 程序代码支援 Internet Explorer 等有一定市占率的旧浏览器。

    我们要额外安装 core-js 套件:

    $ npm install --save core-js@3
    

    目前 core-js 在版本 2 及版本 3 的过渡期。除非有什么好的理由,不用刻意守在旧版本。

    另外要安装 polyfill 套件:

    npm install --save @babel/polyfill
    

    在 JavaScript 生态圈中,polyfill 的意思是重新实现一些新浏览器的特性,让旧浏览器也能使用到这些特性。安装后记得要将 node_modules/@babel/polyfill/dist/polyfill.min.js 拷贝到项目的静态资源中,因为 Babel 转出的 JavaScript 程序代码要透过 polyfill 补足相关功能才能顺利在旧浏览器上执行。

    参考以下 .babelrc 设定:

    {
        "presets": [
            ["@babel/preset-env", 
            {
                "targets": {
                    "ie": "9"
                },
                "modules": false,
                "useBuiltIns": "entry",
                "corejs": 3
            }]
        ]
    }
    

    考量市占率,支援到 Internet Explorer 9 应该是足够了。有需要支援更低版本的 IE 的读者请自行调整设定档。

    因为我们的项目在前端运行,不需要使用 modules。

    我们开启 builtin 功能,Babel 就会认定 polyfill 相关特性已经载入;实际上要由我们自己另行载入 polyfill函数库。

    使用 "entry" 代表 Babel 认定只会载入一次 polyfill。因为我们的前端 JavaScript 程序代码可能分散在好几个文件中,我们把整个网页当成是一个整体,在同一个页面中只需载入一次 polyfill 即可。

    (选择性) 压缩输出的程序代码

    实际上线的 JavaScript 程序会经过压缩 (minification) 的动作,主要用意是缩小程序代码所占的空间,传输上更快一些;另外可以让程序代码相对更难追踪。透过 babel-minify 可达成这项功能;透过以下指令来安装该套件:

    $ npm install babel-minify --save-dev
    

    由于程序代码经压缩后会变得较难追踪,在开发阶段不会急着把程序压缩,最后程序要上线前再压缩即可。可参考以下内容来设置 .babelrc

    {
        "presets": ["env"],
        "env": {
            "production": {
              "presets": ["minify"]
            }
        }
    }
    

    在类 Unix 系统系统上输入以下指令即可转换并压缩 JavaScript 程序代码:

    $ BABEL_ENV=production npm run build
    

    在 Windows 上则要略为修改指令如下:

    C:\path\to\project>SET BABEL_ENV=production&&npm run build
    

    (选择性) 加入 Flow 支援

    Flow 的用意是在 JavaScript 中加入类型检查,由于这个功能是选择性的,读者可自行决定要不要在项目中加入 flow。Babel 默认不会辨识 flow 相关的程序代码,要透过额外的 preset:

    $ npm install --save-dev babel-cli babel-preset-flow
    

    同时要设置 .babelrc 以启用 flow 相关的 preset:

    {
        "presets": ["env", "flow"]
    }
    

    上述套件会解析 flow 相关的代码,在产出的 assets 中将其抹除,因为实际上线的 JavaScript 环境无法辨识 flow 的类型注解相关语法。TypeScript 程序在上线时也会将类型注解的部分抹除,道理是相同的。

    上述的 preset 不会检查类型,要另外要安装 flow-bin 才有实际检查类型的功能:

    $ npm install --save-dev flow-bin
    

    同时要设置 package.json

    "scripts": {
        "flow": "flow",
        ...
    },
    

    第一次使用 flow 时要在项目中初始化:

    $ npm run flow init
    

    之后就可以直接用 flow 检查代码:

    $ npm run flow
    

    本文篇幅较短,故未介绍 flow 的语法,请各位读者自行参考这里的说明。

    (选择性) 使用 ESLint 检查 JavaScript 语法

    ESLint 是一个 JavaScript 的静态程序代码检查软件 (linter),对于补强 JavaScript 的工程性有相当帮助。ESLint 的好处是可辨识 ES6+ 代码;此外,ESLint 不会有太强烈的风格上的建议,可用在各类型的项目上。

    透过以下指令安装 ESLint:

    $ npm install --save-dev eslint babel-eslint
    

    第一次使用 ESLint 时要先初始化,按照自己的使用情境来回答即可:

    $ node_modules\.bin\eslint --init
    ? How would you like to configure ESLint? Answer questions about your style
    ? Which version of ECMAScript do you use? ES2015
    ? Are you using ES6 modules? No
    ? Where will your code run? Browser
    ? Do you use CommonJS? No
    ? Do you use JSX? No
    ? What style of indentation do you use? Spaces
    ? What quotes do you use for strings? Double
    ? What line endings do you use? Windows
    ? Do you require semicolons? Yes
    ? What format do you want your config file to be in? JavaScript
    

    由于我们采局部 (local) 安装,故我们从 node_modules 中调用 ESLint 的终端机指令。

    package.json 中启用 ESLint:

    "scripts": {
        "lint": "eslint src/*.js",
        ...
    },
    

    ESLint 的用法较多,或许有机会日后会在其他文章介绍。

    Git 或其他版本控制的使用者的注意事项

    记得要在项目中加入相关的 .gitignore 或其他同性质文件,以免引不不必要的文件。

    【赞助商连结】