筆者最近在工做之餘,一直在作數據可視化和nodejs方面的研究,雖然以前的web工做中接觸過nodejs和可視化相關的內容,可是沒有一個系統的總結和回顧,因此爲了更深刻的研究和覆盤個人nodejs和數據可視化之路,筆者將會花兩個月的時間,作一個完全的覆盤.javascript
Node.js是一個事件驅動I/O服務端JavaScript環境,基於Google的V8引擎,V8引擎執行Javascript的速度很是快,性能很是好。css
可能不少朋友都或多或少的接觸過nodejs,筆者先來大體總結了一下nodejs的應用領域: 前端
因此做爲一名前端工程師(國際一點的叫法Front-end engineer), 要想讓本身的將來有更多的想象空間,node是必不可少的技能之一.話很少說,接下來筆者將帶你們一步步搭建一個高可用的nodejs開發環境,以便讓你們能更快更好的上手nodejs的開發工做.vue
在介紹正文以前,我想先談談前端項目的管理。就筆者的工做和管理經驗,衡量一個前端項目管理的好壞每每有如下幾個衡量點: java
首先在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
這裏的rule規則你們能夠採用市面上已有的規則文件或者能夠根據本身的團隊風格自行配置,eslint上有比較全面的規則配置表: 程序員
"scripts": {
"start": "eslint src && export NODE_ENV=development && nodemon -w src"
},
複製代碼
上面代碼中eslint src表示對src目錄進行eslint語法規則和格式校驗,若是咱們代碼有不符合規範的,那麼在控制檯將會顯示相應的錯誤。好比咱們代碼中寫了雙引號,則運行項目的時候會出現以下錯誤:
咱們都知道,nodejs對es的支持還不夠完善,雖然在10.0+已經支持大部分的es語法了,可是最重要的模塊化語法(import,export),類(class)和修飾器(Decorator)還不支持,做爲一名有追求的前端工程師,爲了讓代碼更優雅更簡潔,咱們有理由去用最新的特性去編寫更增強大的代碼,因此完善的es的環境支持是搭建nodejs項目的第二步。
咱們這裏統一採用babel7來給你們介紹如何配置es環境,若是你還在使用babel6或者更低的版本,能夠查看對應文檔的版本進行配置。babel7將不少功能都內置到了本身的模塊中,咱們首先要配置環境,即preset-env,咱們可使用@babel/preset-env,對於class和Decorator的支持,咱們須要安裝@babel/plugin-proposal-class-properties和@babel/plugin-proposal-decorators這兩個模塊。因此咱們一共須要安裝以下幾個模塊:
關於babel的配置機制,官網上也寫的很詳細,你們感興趣的能夠看一下,核心就是環境(presets)和插件(plugin)機制。官網對preset-env的解釋以下:
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);
});
// ...
複製代碼
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.
第四點是本文的核心和關鍵,目錄劃分每每考驗的是程序員對項目和架構的理解程度,對於服務端的目錄結構,筆者的經驗以下:
接下來筆者將用原生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遊戲, webpack,node,gulp,css3,javascript,nodeJS,canvas數據可視化等前端知識和實戰,歡迎在公號《趣談前端》加入咱們的技術羣一塊兒學習討論,共同探索前端的邊界。