30分鐘教你優雅的搭建nodejs開發環境及目錄設計

前言

筆者最近在工做之餘,一直在作數據可視化nodejs方面的研究,雖然以前的web工做中接觸過nodejs可視化相關的內容,可是沒有一個系統的總結和回顧,因此爲了更深刻的研究和覆盤個人nodejs和數據可視化之路,筆者將會花兩個月的時間,作一個完全的覆盤.javascript

Node.js是一個事件驅動I/O服務端JavaScript環境,基於Google的V8引擎,V8引擎執行Javascript的速度很是快,性能很是好。css

可能不少朋友都或多或少的接觸過nodejs,筆者先來大體總結了一下nodejs的應用領域: 前端

由上圖能夠看出, nodejs的應用前景仍是很是普遍的,前幾年比較火的 IOT物聯網技術,nodejs也有必定的領域貢獻.

因此做爲一名前端工程師(國際一點的叫法Front-end engineer), 要想讓本身的將來有更多的想象空間,node是必不可少的技能之一.話很少說,接下來筆者將帶你們一步步搭建一個高可用的nodejs開發環境,以便讓你們能更快更好的上手nodejs的開發工做.vue

你將收穫

  • 如何配置eslint來管理項目代碼規範
  • 如何使用babel7來配置nodejs支持最新的es語法
  • 如何使用nodemon來自動化實現node程序自動重啓
  • 如何劃分node目錄結構實現一個node通用服務類Xoa來實現經典的MVC架構

正文

在介紹正文以前,我想先談談前端項目的管理。就筆者的工做和管理經驗,衡量一個前端項目管理的好壞每每有如下幾個衡量點: java

還原度和功能的完整性這兩個方面能夠經過完善的測試體系去把控,對於代碼的擴展性,維護性和可讀性的評定,首先須要由團隊負責人去制定相應的代碼規範和規則,最大限度的保證同一個項目不一樣模塊的一致性。好比註釋規範,格式規範,目錄結構和文件命名等。其次放眼大局,公司若是有多個項目,或者多個項目會彼此聯繫,這時候咱們更要從整個前端架構的角度去衡量和設計,因此前端項目不只僅是泛泛而談,它對企業長遠的產品架構,技術架構上有着很是重要的做用。因此說制定團隊或者項目規範,能夠說是項目開始最爲關鍵的一步。

1.配置eslint來管理項目代碼規範

用過eslint的朋友都知道,eslint主要是針對javascript代碼檢測用的插件化工具。它能夠約束代碼的書寫格式,語法規範,好比保持代碼一致的縮進,代碼末尾有無分號,使用單引號仍是雙引號等,咱們經過一系列的配置,將會打造徹底一致的代碼寫做風格,這樣對後期的代碼管理和維護有着很是重要的意義。說了這麼多,咱們看看看怎麼使用在咱們的 nodejs項目中吧。

首先在eslint官網咱們能夠知道下載和安裝的方式,這裏咱們採用全局安裝:node

npm install eslint --global
複製代碼

而後咱們就能夠在項目中生成eslint的配置文件了,具體可選擇的配置文件類型有專屬的.eslintrc的靜態json文件, 或者可動態配置的eslintrc.js文件,這裏筆者建議採用後者, 在當前項目下生成配置文件的命令以下:webpack

eslint --init
複製代碼

這樣經過命令行的方法咱們就能夠生成咱們想要的eslint配置文件了。首先筆者先上一份簡單的eslint配置文件:css3

module.exports = {
    "env": {
        "browser": true,
        "node": true,  // 啓用node環境
        "es6": true    // 啓用es6語法
    },
    "extends": "eslint:recommended",
    "globals": {
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly"
    },
    "parserOptions": {
        "ecmaVersion": 2018,
        "sourceType": "module"
    },
    "rules": {
        "semi": [2, "never"],  // 結尾不能有分號
        "eqeqeq": "warn",  // 要求使用 === 和 !==
        "no-irregular-whitespace": "warn",  // 禁止不規則的空白
        "no-empty-pattern": "warn",  // 禁止使用空解構模式
        "no-redeclare": "warn", // 禁止屢次聲明同一變量
        "quotes": ["error", "single"],  // 代碼中使用單引號包裹字符串
        "indent": ["warn", 2],  // 代碼縮進爲2個空格
        "no-class-assign": "error",  // 禁止修改類聲明的變量
        "no-const-assign": "error",  // 禁止修改 const 聲明的變量
    }
};
複製代碼

其中rules中鍵的值分別表示:git

  • "off" or 0 - 關閉規則
  • "warn" or 1 - 將規則視爲一個警告(不會影響退出碼)
  • "error" or 2 - 將規則視爲一個錯誤 (退出碼爲1)

這裏的rule規則你們能夠採用市面上已有的規則文件或者能夠根據本身的團隊風格自行配置,eslint上有比較全面的規則配置表: 程序員

當咱們的配置規則配置完畢後,咱們只須要在npm的scripts腳本文件中添加執行代碼,eslint就會自動幫咱們校驗代碼:

"scripts": {
    "start": "eslint src && export NODE_ENV=development && nodemon -w src"
  },
複製代碼

上面代碼中eslint src表示對src目錄進行eslint語法規則和格式校驗,若是咱們代碼有不符合規範的,那麼在控制檯將會顯示相應的錯誤。好比咱們代碼中寫了雙引號,則運行項目的時候會出現以下錯誤:

2.如何使用babel7來配置nodejs支持最新的es語法

咱們都知道,nodejs對es的支持還不夠完善,雖然在10.0+已經支持大部分的es語法了,可是最重要的模塊化語法(import,export),類(class)和修飾器(Decorator)還不支持,做爲一名有追求的前端工程師,爲了讓代碼更優雅更簡潔,咱們有理由去用最新的特性去編寫更增強大的代碼,因此完善的es的環境支持是搭建nodejs項目的第二步。

沒錯,爲了實現對es語法更全面的支持,babel是咱們的不二選擇。和eslint相似,編寫babel一樣也有幾種編寫配置文件的方式,這裏咱們仍是採用js的方式,這樣的好處是能夠根據環境動態配置不一樣的編譯方式。

咱們這裏統一採用babel7來給你們介紹如何配置es環境,若是你還在使用babel6或者更低的版本,能夠查看對應文檔的版本進行配置。babel7將不少功能都內置到了本身的模塊中,咱們首先要配置環境,即preset-env,咱們可使用@babel/preset-env,對於class和Decorator的支持,咱們須要安裝@babel/plugin-proposal-class-properties和@babel/plugin-proposal-decorators這兩個模塊。因此咱們一共須要安裝以下幾個模塊:

  • @babel/cli
  • @babel/core
  • @babel/node
  • @babel/plugin-proposal-class-properties
  • @babel/plugin-proposal-decorators
  • @babel/preset-env

關於babel的配置機制,官網上也寫的很詳細,你們感興趣的能夠看一下,核心就是環境(presets)和插件(plugin)機制。官網對preset-env的解釋以下:

即@babel/preset-env是一個智能的容許咱們使用最新javascript語法的代碼自動轉化工具。同時官網也列出了不一樣配置屬性對應的不一樣功能,爲了節約篇幅,咱們直接上配置的代碼:

module.exports = function (api) {
  api.cache(true)
  const presets = [
    [
      '@babel/preset-env',
      {
        'targets': {
          'node': 'current'
        }
      }
    ]
  ]

  const plugins = [
    ['@babel/plugin-proposal-decorators', { 'legacy': true }],
    ['@babel/plugin-proposal-class-properties', { 'loose' : true }]
  ]

  return {
    presets,
    plugins
  }
}
複製代碼

這也是官方推薦的使用方式,更多靈活的配置你們能夠參考官網配置。以上兩個plugin的做用不言而知,一個是用來編譯轉換修飾器屬性的,一個是用來編譯轉換class語法的。最後一步就是在package.json中的腳本文件中使用咱們的babel工具:

"scripts": {
    "start": "eslint src && nodemon -w src --exec \"babel-node src\"",
    "build": "babel src --out-dir dist"
  }
複製代碼

babel-node src指定了須要編譯的node目錄爲src目錄,其餘文件和目錄無需編譯。 經過這樣的配置,咱們就能開心的用最新的javascript語法開發nodejs項目了,在代碼編寫完成以後,咱們執行npm run build便可將src的代碼打包編譯到dist目錄下。編譯後的代碼以下:

"use strict";

var _glob = _interopRequireDefault(require("glob"));

var _path = require("path");

var _xoa = _interopRequireDefault(require("./lib/xoa.js"));

var _config = _interopRequireDefault(require("./config"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const app = new _xoa.default();
app.use((req, res) => {
  console.log(req.url, req.method);
}); // 全局註冊業務接口
// function autoRegister(path, )

_glob.default.sync((0, _path.resolve)(__dirname, './routes/*.js')).forEach(item => {
  app.use(require(item).default);
});
// ...
複製代碼

3.如何使用nodemon來自動化實現node程序自動重啓

nodemon的使用很是簡單,咱們只須要按照官網文檔的配置來安裝和使用便可:

npm install --save-dev nodemon
複製代碼

而後在package.json的腳本文件中以下配置:

"scripts": {
    "start": "eslint src && export NODE_ENV=development && nodemon -w src --exec \"babel-node src\"",
    "build": "babel src --out-dir dist",
    "buildR": "node dist",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
複製代碼

nodemon -w src 表示監聽src目錄下的文件變化,一旦文件變化將馬上從新啓動node程序。咱們還能夠專門寫一個nodemon的配置文件,實現不監聽某一個具體的文件變更,或者其餘自定義的配置,若是服務上線,咱們還能夠用forever和nodemon結合來是實現持久化,固然主流的方式仍是pm2.

4.如何劃分node目錄結構實現一個node通用服務類Xoa來實現經典的MVC架構

第四點是本文的核心和關鍵,目錄劃分每每考驗的是程序員對項目和架構的理解程度,對於服務端的目錄結構,筆者的經驗以下:

具體目錄以下:
固然不一樣目錄之間能夠進一步細分,這個取決於項目規模。經過對項目有條理的結構化設計,團隊中不一樣的成員就能夠有序的負責不一樣的模塊了。這種架構模式參考了傳統的mvc的模式,具體仍是須要代碼層面進一步控制。

接下來筆者將用原生javascript實現一個簡單的node服務層的封裝,以實現更便捷的node開發,固然在實際項目中咱們徹底能夠採用koa,egg這種成熟的框架來開發node應用,這裏筆者只是簡單實現一個例子方便你們對node開發有個更深刻的認知。

咱們都知道nodejs有http模塊方便咱們快速建立一個node服務器,代碼可能長這個樣子:

import { createServer } from 'http'
createServer((req, res) => {
    res.end('hello world!')
}).listen(3000)
複製代碼

這樣就建立了一個簡單的服務器,當咱們訪問localhost:3000的話咱們就能看到頁面會顯示hello world! 可是咱們若是要想實現更復雜的功能,好比根據不一樣的路由處理不一樣的邏輯,咱們該怎麼辦呢?也許你會說直接在createServer的回調中根據req.url來判斷,代碼以下:

import { createServer } from 'http'
createServer((req, res) => {
    if(req.url === 'A') {
        // A的邏輯
    }else if(req.url === 'B') {
        // B的邏輯
    }else if(req.url === 'C') {
        // C的邏輯
    }
    // ...
}).listen(3000)
複製代碼

可是一旦業務邏輯複雜了,路由變多了,咱們將寫大量的if else代碼,這對於維護性來講是一種極大的摧毀,咱們但願將路由和業務邏輯劃分,分開來管理,這樣對於後期業務邏輯日漸複雜,頁面路由不斷增長才更加容易維護和管理。如何實現這一目標呢?咱們能夠參考koa的中間件機制,當咱們要註冊一個路由時,咱們只須要這樣寫:

app.use(routeA)
複製代碼

這樣是否是更優雅一點呢?因此咱們基於以上須要來實現一個本身的小型服務框架

代碼實現以下:

import { createServer } from 'http'

class Xoa {
  constructor() {
    // 初始化中間鍵數組
    this.middleware = []
  }
  // 維持中間鍵數組
  use(func) {
    this.middleware.push(func)
  }
  // 建立服務器實例,並執行相應任務
  createServer() {
    const server = createServer((req, res) => {
      // 應用中間件
      this.middleware.forEach((fn) => fn(req, res))
    })
    return server
  }
  // 服務器監聽
  listen(port = 3000, cb) {
    this.createServer().listen(port, cb)
  }
}

export default Xoa
複製代碼

經過這樣的設計,咱們就能優雅的使用中間件語法了:

import Xoa from './lib/xoa.js'

const app = new Xoa()

app.use((req, res) => {
  console.log(req.url, req.method)
  res.end('A')
})

app.use((req, res) => {
  res.end('B')
})

app.listen(3000)
複製代碼

咱們再來看另一種場景,若是咱們的路由不少,有負責頁面渲染的路由,也有負責輸出api數據的路由,那麼咱們要每一個都使用use來use一遍,這樣感受太傻了,做爲一個有追求的程序員是不容許這種事情發生的,咱們但願這一切都是自動完成的,自動註冊中間件,這該怎麼實現呢? 好在node社區提供了一個強大的第三方模塊glob,咱們能夠經過glob來遍歷目錄實現自動化註冊路由,關於glob的用法這裏就不帶你們細說了,用法很是簡單。 好比咱們的路由文件有以下幾個:

咱們要保證路由目錄下面的路由文件都有導出,而後在 入口文件中咱們能夠這麼實現:

import glob from 'glob'
import { resolve } from 'path'
import Xoa from './lib/xoa.js'
import config from './config'

const app = new Xoa()

// 全局註冊業務接口
glob.sync(resolve(__dirname, './routes/*.js')).forEach(item => {
  app.use(require(item).default)
})

app.listen(config.serverPort, () => {
  console.log(`服務器地址:${config.protocol}//${config.host}:${config.serverPort}`)
})
複製代碼

經過glob的sync方法咱們能夠遍歷routes目錄並經過require加載路由文件,而後直接註冊到app上,這樣就不用咱們手動一個個引入了,是否是很是簡單呢?(雖然這只是個極簡版的服務端封裝,對於實際項目須要作進一步的升級和擴展,可是設計思想但願你們能有所收穫)

對於負責項目咱們可能還會考慮業務邏輯,咱們會在service目錄下編寫咱們的服務層代碼,在路由文件中使用,也有可能採用到數據庫模塊等,因此說這些都是比較有意思的實現,後面筆者將帶你們繼續作一個全棧項目,來感覺node開發的魅力。

: 本文代碼已傳到github上了,地址:smart-node-tpl

歡迎你們多交流討論哈~

最後

若是想獲取更多項目完整的源碼, 或者想學習更多H5遊戲, webpacknodegulpcss3javascriptnodeJScanvas數據可視化等前端知識和實戰,歡迎在公號《趣談前端》加入咱們的技術羣一塊兒學習討論,共同探索前端的邊界。

更多推薦

相關文章
相關標籤/搜索