此時狀態有點像上學時寫做文,開篇老是"拉"不出來,憋的難受。
原文地址javascript
源碼地址html
先後端分離後,前端童鞋會須要處理一些node層的工做,好比模板渲染、接口轉發、部分業務邏輯等,比較經常使用的框架有koa、koa-router等。
如今咱們須要實現這樣一個需求:前端
/fe
的時候,頁面展現hello fe /backend
的時候,頁面展現hello backend 你是否是在想,這需求俺根本不用koa
、koa-router
,原生的node模塊就能夠搞定。vue
const http = require('http') const url = require('url') const PORT = 3000 http.createServer((req, res) => { let { pathname } = url.parse(req.url) let str = 'hello' if (pathname === '/fe') { str += ' fe' } else if (pathname === '/backend') { str += ' backend' } res.end(str) }).listen(PORT, () => { console.log(`app start at: ${PORT}`) })
確實是,對於很簡單的需求,用上框架彷佛有點浪費,可是對於以上的實現,也有缺點存在,好比java
因此接下來咱們來試試用koa
和koa-router
怎麼實現node
app.jsreact
const Koa = require('koa') const KoaRouter = require('koa-router') const app = new Koa() const router = new KoaRouter() const PORT = 3000 router.get('/fe', (ctx) => { ctx.body = 'hello fe' }) router.get('/backend', (ctx) => { ctx.body = 'hello backend' }) app.use(router.routes()) app.use(router.allowedMethods()) app.listen(PORT, () => { console.log(`app start at: ${PORT}`) })
經過上面的處理,路徑的解析卻是給koa-router
處理了,可是總體的寫法仍是有些問題。git
接下來咱們再優化一下,先看一下總體的目錄結構github
├──app.js // 應用入口 ├──controller // 邏輯處理,分模塊 │ ├──hello.js │ ├──aaaaa.js ├──middleware // 中間件統一註冊 │ ├──index.js ├──routes // 路由配置,能夠分模塊配置 │ ├──index.js ├──views // 模板配置,分頁面或模塊處理,在這個例子中用不上 │ ├──index.html
預覽一下每一個文件的邏輯正則表達式
app.js
應用的路口
const Koa = require('koa') const middleware = require('./middleware') const app = new Koa() const PORT = 3000 middleware(app) app.listen(PORT, () => { console.log(`app start at: ${PORT}`) })
routes/index.js
路由配置中心
const KoaRouter = require('koa-router') const router = new KoaRouter() const koaCompose = require('koa-compose') const hello = require('../controller/hello') module.exports = () => { router.get('/fe', hello.fe) router.get('/backend', hello.backend) return koaCompose([ router.routes(), router.allowedMethods() ]) }
controller/hello.js
hello 模塊的邏輯
module.exports = { fe (ctx) { ctx.body = 'hello fe' }, backend (ctx) { ctx.body = 'hello backend' } }
middleware/index.js
中間件統一註冊
const routes = require('../routes') module.exports = (app) => { app.use(routes()) }
寫到這裏你可能內心有個疑問?
一個簡單的需求,被這麼一搞看起來複雜了太多,有必要這樣麼?
答案是:有必要,這樣的目錄結構或許不是最合理的,可是路由、控制器、view層等各司其職,各在其位。對於之後的擴展有很大的幫助。
不知道你們有沒有注意到路由配置這個地方
routes/index.js
路由配置中心
const KoaRouter = require('koa-router') const router = new KoaRouter() const koaCompose = require('koa-compose') const hello = require('../controller/hello') module.exports = () => { router.get('/fe', hello.fe) router.get('/backend', hello.backend) return koaCompose([ router.routes(), router.allowedMethods() ]) }
每一個路由對應一個控制器去處理,很分離,很常見啊!!!這彷佛也是咱們平時在前端寫vue-router或者react-router的常見配置模式。
可是當模塊多起來的來時候,這個文件夾就會變成
const KoaRouter = require('koa-router') const router = new KoaRouter() const koaCompose = require('koa-compose') // 下面你須要require各個模塊的文件進來 const hello = require('../controller/hello') const a = require('../controller/a') const c = require('../controller/c') module.exports = () => { router.get('/fe', hello.fe) router.get('/backend', hello.backend) // 配置各個模塊的路由以及控制器 router.get('/a/a', a.a) router.post('/a/b', a.b) router.get('/a/c', a.c) router.get('/a/d', a.d) router.get('/c/a', c.c) router.post('/c/b', c.b) router.get('/c/c', c.c) router.get('/c/d', c.d) // ... 等等 return koaCompose([ router.routes(), router.allowedMethods() ]) }
有沒有什麼辦法,可讓咱們不用手動引入一個個控制器,再手動的調用koa-router的get post等方法去註冊呢?
好比咱們只須要作如下配置,就能夠完成上面手動配置的功能。
routes/a.js
module.exports = [ { path: '/a/a', controller: 'a.a' }, { path: '/a/b', methods: 'post', controller: 'a.b' }, { path: '/a/c', controller: 'a.c' }, { path: '/a/d', controller: 'a.d' } ]
routes/c.js
module.exports = [ { path: '/c/a', controller: 'c.a' }, { path: '/c/b', methods: 'post', controller: 'c.b' }, { path: '/c/c', controller: 'c.c' }, { path: '/c/d', controller: 'c.d' } ]
而後使用pure-koa-router
這個模塊進行簡單的配置就ok了
const pureKoaRouter = require('pure-koa-router') const routes = path.join(__dirname, '../routes') // 指定路由 const controllerDir = path.join(__dirname, '../controller') // 指定控制器的根目錄 app.use(pureKoaRouter({ routes, controllerDir }))
這樣整個過程咱們的關注點都放在路由配置上去,不再用去手動require
一堆的文件了。
簡單介紹一下上面的配置
{ path: '/c/b', methods: 'post', controller: 'c.b' }
path: 路徑配置,能夠是字符串/c/b
,也能夠是數組[ '/c/b' ]
,固然也能夠是正則表達式/\c\b/
methods: 指定請求的類型,能夠是字符串get
或者數組[ 'get', 'post' ]
,默認是get
方法,
controller: 匹配到路由的邏輯處理方法,c.b
表示controllerDir
目錄下的c
文件導出的b
方法,a.b.c
表示controllerDir
目錄下的/a/b
路徑下的b文件導出的c方法
接下來咱們逐步分析一下實現邏輯
module.exports = ({ routes = [], controllerDir = '', routerOptions = {} }) => { // xxx return koaCompose([ router.routes(), router.allowedMethods() ]) })
pure-koa-router
接收
routes
能夠指定路由的文件目錄,這樣pure-koa-router會去讀取該目錄下全部的文件 (const routes = path.join(__dirname, '../routes'))
controllerDir
、控制器的根目錄routerOptions
new KoaRouter時候傳入的參數,具體能夠看koa-router 這個包執行以後會返回通過koaCompose
包裝後的中間件,以供koa實例添加。
assert(Array.isArray(routes) || typeof routes === 'string', 'routes must be an Array or a String') assert(fs.existsSync(controllerDir), 'controllerDir must be a file directory') if (typeof routes === 'string') { routes = routes.replace('.js', '') if (fs.existsSync(`${routes}.js`) || fs.existsSync(routes)) { // 處理傳入的是文件 if (fs.existsSync(`${routes}.js`)) { routes = require(routes) // 處理傳入的目錄 } else if (fs.existsSync(routes)) { // 讀取目錄中的各個文件併合並 routes = fs.readdirSync(routes).reduce((result, fileName) => { return result.concat(require(nodePath.join(routes, fileName))) }, []) } } else { // routes若是是字符串則必須是一個文件或者目錄的路徑 throw new Error('routes is not a file or a directory') } }
無論routes傳入的是文件仍是目錄,又或者是直接導出的配置的內容最後的結構都是是這樣的
routes內容預覽
[ // 最基礎的配置 { path: '/test/a', methods: 'post', controller: 'test.index.a' }, // 多路由對一個控制器 { path: [ '/test/b', '/test/c' ], controller: 'test.index.a' }, // 多路由對多控制器 { path: [ '/test/d', '/test/e' ], controller: [ 'test.index.a', 'test.index.b' ] }, // 單路由對對控制器 { path: '/test/f', controller: [ 'test.index.a', 'test.index.b' ] }, // 正則 { path: /\/test\/\d/, controller: 'test.index.c' } ]
主動註冊
let router = new KoaRouter(routerOptions) let middleware routes.forEach((routeConfig = {}) => { let { path, methods = [ 'get' ], controller } = routeConfig // 路由方法類型參數適配 methods = (Array.isArray(methods) && methods) || [ methods ] // 控制器參數適配 controller = (Array.isArray(controller) && controller) || [ controller ] middleware = controller.map((controller) => { // 'test.index.c' => [ 'test', 'index', 'c' ] let controllerPath = controller.split('.') // 方法名稱 c let controllerMethod = controllerPath.pop() try { // 讀取/test/index文件的c方法 controllerMethod = require(nodePath.join(controllerDir, controllerPath.join('/')))[ controllerMethod ] } catch (error) { throw error } // 對讀取到的controllerMethod進行參數判斷,必須是一個方法 assert(typeof controllerMethod === 'function', 'koa middleware must be a function') return controllerMethod }) // 最後使用router.register進行註冊 router.register(path, methods, middleware)
源碼的實現過程基本就到這裏了。
pure-koa-router
將路由配置和控制器分離開來,使咱們將注意力放在路由配置和控制器的實現上。但願對您能有一點點幫助。