Fen - 基於deno的簡單Typescript Web框架
deno 從誕生,到現在有自己的標準庫,以及一些第三方庫,讓我對deno在web的應用有了一定的興趣,
我知道deno尚未成熟,但是剛好最近心閒下來了,就找點事做做,作為一個前端,我也期待通過這個機會了解更多的一些後臺的事情。
如果你能在看到文章之後,給我提出一些建議我將不勝感激~
如果你也想一起開發,我也非常歡迎~
閱讀大約需要 4 - 5 分鐘
基於deno v0.3.0,Github:https://github.com/fen-land/deno-fen
簡介

Fen 是一個使用Typescript基於deno的輕量級web框架,他通過Server主體來實現服務,通過Process以及Tool來賦予Server各種各樣的功能。
Fen 僅僅通過上下問context來連線各個環節,雖然這樣的模式存在著上下文在修改過程中難以保證穩定的問題,但是如果Process和Tool的開發遵守一些基本原則,還是能基本維持context的穩定的。同時雖然 Fen 通過Process以及Tool來賦能,但是其本身還是進行了一定的封裝以及進行了一些操作來讓使用更加便捷。
因為其實沒有提出很新的想法,所以還是使用了很多之前在java或者node中用過的後臺的用法,如果大家覺得有怎樣的新想法,也歡迎大家提issue。
怎樣開始
首先,如果你不知道deno,快去看看吧: https://deno.land/
你需要安裝最新的deno:
curl -fsSL https://deno.land/x/install/install.sh | sh 複製程式碼
然後,你需要寫你的第一個Fen的檔案
// hello.ts import { Server } from "https://raw.githubusercontent.com/fen-land/deno-fen/master/src/server.ts"; const s = new Server(); s.port = 8882; s.start(); 複製程式碼
因為deno還沒有相應成熟的包管理(也可能不會有)這裡引用檔案還暫時是通過github來做一個託管,之後會完善一下,有更好的地址使用方式。
最後你需要執行這個檔案
deno -A hello.ts 複製程式碼
在瀏覽器中訪問:http://127.0.0.1:8882 你將會看到:
You have success build a server with fen on0.0.0.0:1882 Try set controller using setController method, Or try our route tool :) 複製程式碼
這就是你的第一個 Fen 伺服器。
Context
Context是Fen的核心概念,他起到了一個在生命週期裡面傳遞的作用,也是controller進行操作的手段。
結構如下:
{ // ---- 這些變數都取自http派發都ServerRequest url, method, proto, headers, conn, reader, writer, // ---- request, path, // 不帶有引數的路徑 params, // Map<string, string> url裡面得到的引數 data: new Map<string, any>(), body: "",// respond 返回的body status: 200,// 狀態碼 config: { disableRespond: false, // 禁用fen自帶的respond流程 disableBodyEncode: false, // 禁止按照型別來encode body的流程 disableContentType: false, // 禁止根據config來設定header mimeType: "text/plain", // 返回結果的mimeType charset: "utf-8" // 返回結果的Type }, reqBody, // 嘗試解碼後的請求的body originBody, // 原本請求的body logger // tool 中帶有的Logger }; 複製程式碼
通過使用context,你可以通過簡單的賦值來決定響應:
s.setController(async ctx => { ctx.config.mimeType = "application/json"; ctx.body = { now: "you", can: ["see"], me: 2 }; }); 複製程式碼
Process
Process通常執行在controller前,為context賦能,使其擁有特定的功能,Process通過 addProcess
方法來進行新增。
Session
Session是現有的唯一Process,他為context加入了 session
這樣一個鍵,這樣就賦予了controller了web session的能力。
import {Server} from 'https://raw.githubusercontent.com/fen-land/deno-fen/raw/master/src/server.ts'; import Session from 'https://raw.githubusercontent.com/fen-land/deno-fen/raw/master/src/process/session.ts' const session = new Session(); const s = new Server(); // session 加入的僅僅是session的process s.addProcess(session.process); s.port = 1882; s.setController( async (ctx) => { // session 是一個 Map<string, any>() const {session} = ctx; let c = session.get('c') || 1; if(ctx.path === '/') { session.set('c',c + 1); } ctx.body = `It\'s alive for path '/' ${c} times in this browser!`; } ); s.start(); 複製程式碼
Tool
Fen要實現很多基本的功能類似於靜態檔案,路由之類的,需要使用Tool,並且在Server中也使用了一些Tool。
Logger
Fen 自己有一個log系統,雖然是很簡陋的那種,但是可以幫助開發時候得到更多資訊,你可以在Server或者context中找到它。log是按照級別來提供的,你可以在開發中使用 logger.debug('debug')
這樣的方式來產出log,低於設定level的log都不會產出。
'ALL': 所有log都輸出, 'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL', 'OFF': 禁止所有log 複製程式碼
你可以通過 changeLevel
來改變level:
logger.changeLevel('ALL'); 複製程式碼
Static
Static 為 Server提供了靜態代理的功能,
import {Server} from 'https://raw.githubusercontent.com/fen-land/deno-fen/raw/master/src/server.ts'; import {staticProcess} from "https://raw.githubusercontent.com/fen-land/deno-fen/raw/master/src/tool/static.ts"; const s = new Server(); s.port = 1882; // it will respond file from the path where deno run s.setController(staticProcess({root: ''})); s.start(); 複製程式碼
static提供了一些額外的option:
{ root: root path of the file, maxAge: (s), allowHidden: allow access hidden file, index: access if no file name provide 'index.html', immutable: immutable in cache-control, pathRender: (path) => afterpath, if you want do sth. with path }; 複製程式碼
Router
我們為Fen 也提供了路由Tool,他有很靈活的使用方法,你也可以通過使用多個Router來進行開發,最後把他們merge到同一個上,下面展示了大部分可以使用的路由方法的例子。
import { Server } from "https://raw.githubusercontent.com/fen-land/deno-fen/raw/master/src/server.ts"; import { Router } from "https://raw.githubusercontent.com/fen-land/deno-fen/raw/master/src/tool/router.ts"; const s = new Server(); s.port = 1882; s.logger.changeLevel('ALL'); let mergeRouter = new Router('merge'); mergeRouter .get('/', async (ctx) => ctx.body = `${ctx.router.name} in ${ctx.router.route}`) .post('/', async (ctx) => ctx.body = `POST ${ctx.router.name} in ${ctx.router.route}`) .get('me', async (ctx) => ctx.body = `${ctx.router.name} in ${ctx.router.route}`); let router = new Router(); router .get('/:id', async (ctx) => { ctx.body = `we have ${JSON.stringify(ctx.router.params)} in ${ctx.router.route}` }) .get('/:id/:name', async (ctx) => { ctx.body = `we have ${JSON.stringify(ctx.router.params)} in ${ctx.router.route}` }) .get('/hello/:name', async (ctx) => { ctx.body = `hello ${ctx.router.params.name} in ${ctx.router.route}` }) .use({ '/use': {get: async (ctx) => ctx.body = `use in ${ctx.router.route}`}}) .merge('/merge', mergeRouter); ; s.setController(router.controller); s.start(); 複製程式碼
Router
有以下的方法:
use(route: IRoute) // 一種區別於下面方法的直接批量新增路由的方式 // IRoute 結構: // {[path]: {[method]: async function controller(cxt)}} merge(route: string, router:Router) // 你甚至可以通過路徑字首來合併Router們 get post head put delete connect options trace 複製程式碼