1. 程式人生 > >一步一步帶你搭建一個“摩登”的前端開發環境

一步一步帶你搭建一個“摩登”的前端開發環境

js 型別系統

最近糾結一個問題,前端的 js 由於其動態的特性,寫幾行程式碼,在瀏覽器重新整理一下就能看到結果了,非常適合快速開發和迭代。但隨著程式碼的規模越來越大,到了後期就會變得難以維護,任何的改動都有可能引入新的 bug,js 工程師需要花費越來越多的時間來除錯修復各種 bug。

造成這樣結果的原因有多樣,而其中之一的原因,是由於 js 缺乏型別系統,導致我們無法通過工具來在開發的過程中檢測到那些可能會發生的錯誤,也無法通過具體的型別定義來約束別人如何呼叫自己寫的程式碼庫。

這樣的想法其實在 js 社群裡已經得到越來越多人的認同,最近幾年也陸續推出了多種不同的 js 型別系統用於增強 js 的健壯性,其中像typescript

就是其中的佼佼者。當然我今天要講的並不是 typescript,而是由 facebook 推出的flow

flow 和 typescript 不同,typescript 是 js 的超集,是另外一門語言(向下相容 js),而 flow,則是一個靜態型別檢測工具,並沒有修改 js 的語言特性。flow 通過自動推斷 js 程式碼中各個變數的型別,來約束程式碼的行為,舉個例子,在 js 中對兩個變數進行相加,在不同情況下會得到不一樣的結果:

let strA = "hello ";
let strB = "world";
let numC = 1;
let numD = 2;
let objE = { "key"
: "value" }; let arrF = [ 1, 2, 3 ]; // 情況 1 strA srtB // "hello world" // 情況 2 numC numD // 3; // 情況 3 strA numC // "hello 1" // 情況 4 strA objE // "hello [object Object]" // 情況 5 strA arrF // hello 1,2,3"

上面的 5 種情況,在 js 中都是被允許的,然而情況 4 和 5,在一般情況下並不是我們所期望的。而在 flow 中,則只允許情況 1~3 通過檢測,而對於情況 4 和 5 則直接報錯了。

strA   objE;
       ^^^^ object literal. This type cannot be added to
strA   objE;
^^^^ string
strA arrF; ^^^^ array literal. This type cannot be added to strA arrF; ^^^^ string

flow 除了可以自動的進行型別推斷外,還可以通過型別宣告的來進一步限制程式碼的行為,例如我們宣告一個函式,接受一個引數,並返回一個字串,如果我們不進行額外的型別宣告,flow 預設是會接受 string 和 number 兩種型別的引數

function hello(val) {
  return "hello "   val;
}

hello("world");
hello(1);

但如果我們希望我們的函式只接受 string 作為引數,並且明確返回 string,則可以

function hello(val: string): string {
  return "hello "   val;
}

這是如果再傳入 number 則會報錯,對於完整的 flow 使用,大家可以參考 這裡

hello(1);
      ^ number. This type is incompatible with the expected param type of
function hello(val: string): string {
                    ^^^^^^ string

flow 環境的搭建

要在專案中使用 flow,需要完成三件事情

第一安裝 flow 命令列工具

$mkdir flow-proj && cd flow-proj
$npm init
$npm install flow-bin --save-dev

第二,編寫 js 程式碼(hello.js),並在程式碼的第一行,加入註釋 // [@flow][2]

// @flow
exports.hello = function (val: string): string {
  return "hello "   val;
}

這時候,flow 已經知道該檔案是需要做型別檢測的,這時候執行 flow init 命令初始化 flow

$./node_module/.bin/flow init

flow 會自動在該目錄下建立.flowconfig 檔案,接著我們執行 flow 命令,就可以在後臺啟動 flow 程序進行型別檢測了

Spawned flow server (pid=28687)
Logs will go to /private/tmp/flow/zSUserszSlizZzZzZledzSDevzSflow-proj.log
No errors!

如果需要停止 flow 程序,只需要執行 flow stop 命令即可

$./node_module/.bin/flow stop

到現在為止,雖然 flow 已經可以正常運行了,然而因為我們在 js 程式碼裡添加了額外型別宣告,導致 js 程式碼不能直接在瀏覽器裡執行,這時候我們需要做第三步,通過程式碼構建的方式,把 js 轉換成瀏覽器能執行的形式。

這裡我採用的 webpack babel 作為我們構建環境,所以首先我們需要安裝 webpack 和 babel

$npm install webpack babel-core babel-loader --save-dev

然後我們需要安裝 babel 的 flow 套裝,使得 babel 支援 flow

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

然後我們編寫 webpack 的配置檔案 webpack.config.js

module.exports = {
  entry: "./hello.js",
  output: {
    path: __dirname,
    filename: "hello.bundle.js"
  },
  module: {
    loaders: [
      {
        test: /.js$/,
        exclude: /node_module/,
        loaders: ["babel-loader"]
      }
    ]
  }
};

還有我們的 babel 配置檔案.babelrc

{
  presets: [
    "flow"
  ]
}

到這裡為止,我們已經完成了構建環境的配置,接下來執行 webpack 命令,就會產生我們的目標檔案(hello.bundle.js)

$./node_modules/.bin/webpack

開啟目標檔案,我們就會發現,flow 的型別宣告已經被正確去掉了。

到現在為止,整個 flow 的環境已經算搭建完成了,然而在寫了沒幾行程式碼之後,我們就會發現,每次要對程式碼進行檢測,都需要開啟 terminal,敲上一堆命令才能看到結果,實在是不爽。有沒有辦法可以節省這些多餘的工作,把 flow 整合到編輯器中呢?答案當然是肯定的。

這裡我使用的編輯器是 sublime text3,如果有的同學是使用其他編輯器,可以在 這裡,找一下

對與像我一樣使用 st3 的同學,首先我們要在 st3 裡安裝 SublimeLinter 外掛,Ctrl Shift P 開啟 Package Control,選中 Package Control:Install Package。輸入 SublimeLinter,安裝即可。

SublimeLinter 是一個語法校驗的框架,但其本身並不會去做實際的校驗工作,我們需要另外安裝 SublimeLinter 的 flow 外掛,同樣是開啟 Package Control,輸入 SublimeLinter-flow,安裝即可。

這時 SublimeLinter-flow 就會在當前資料夾向上查詢.flowconfig 檔案,並對帶有 // [@flow] 的檔案進行自動檢測,如果出現錯誤,就會直接在編輯器上提示,十分的方便。

加入 eslint 語法校驗

除了型別檢測,有時候我們還需要對 js 進行語法校驗,當然很多成熟都工具都可以幫我們完成這樣的功能,這裡我使用的 eslint,對於其他的例如 jshint,jslint,小夥伴都可以自行 google。

首先我們要安裝 eslint 以及 eslint-loader,使得 eslint 可以整合到 webpack 裡

$npm install eslint eslint-loader --save-dev

安裝好 eslint 後,就可以執行 eslint --init 命令來初始化 eslint 了

$npm ./node_modules/.bin/eslint --init

這時 eslint 就會問你一些問題,一步步幫你完成初始化的工作,並生成對應.eslintrc 檔案,這時候,我們需要更新一下 webpack.config.js 檔案,加入對 eslint 的支援,記得 eslint-loader 一定要寫在 babel-loader 之後,這樣 eslint 才能正確執行

module.exports = {
  entry: "./hello.js",
  output: {
    path: __dirname,
    filename: "hello.bundle.js"
  },
  module: {
    loaders: [
      {
        test: /.js$/,
        exclude: /node_module/,
        loaders: ["babel-loader", "eslint-loader"]
      }
    ]
  }
};

不過如果這時候,你試著執行 webpack 命令進行構建,你會發現,報錯了,原因是 eslint 不認識 flow 的型別宣告語法。為了讓 eslint 能通過 flow 的型別宣告,我們需要安裝兩個工具,一個是 flow 的 eslint 外掛 eslint-plugin-flowtype,另一個是 eslint 的 babel 版 js 解析器 babel-eslint,這是由於 eslint 預設的 espree 解析器認不得 flow 的型別宣告

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

接著我們修改一下 eslint 的配置檔案.eslintrc,把 parser 欄位設定為 babel-eslint,然後分別在 extendsplugins 加入 flow 相應的設定

{
  "env": {
    "node": true,
    "browser": true,
    "commonjs": true,
    "es6": true
  },
  "extends": [
    "eslint:recommended", 
    "plugin:flowtype/recommended",
  ],
  "parser": "babel-eslint",
  "parserOptions": {
    "sourceType": "module"
  },
  "plugins": [
    "flowtype"
  ],
  "rules": {
    "indent": ["error", 4, { "SwitchCase": 1 }],
    "linebreak-style": ["error", "unix"],
    "quotes": ["error", "double"],
    "semi": ["error", "always"]
  }
}

done,現在我們再次執行 webpack 命令就能正常進行構建了。

最後最後,我們再通過在 sublime 中安裝在 SublimeLinter 的外掛 SublimeLinter-contrib-eslint 就可以讓我們的編輯器也支援 eslint 的語法校驗了。

這就是我這次給大家分享的,如何大家一個"摩登"的前端開發環境