最近有点小空,打算作一个nodejs框架玩玩。做为一个一直被JAVA和PHP荼毒的developer,因此框架大致会参考PHP的CI,ThinkPHP和JAVA的Jfinal。javascript
其实一个WEB框架并不难,按预约规则搭好能接收request处理request再返回response,就已是一个框架了,而难就是难在如何完善整个框架,去打磨每个环节,例如页面缓存、数据缓存、与数据库交互是ORM仍是active record等等等等一大通问题也是决定一个框架质量的因素。css
做为一个练手项目,我这里就没有太远大志向了。。第一篇的目的就是显示一下Helloworld,至于框架概念的话也就用咱们熟悉的MVC。html
npm init 项目以后我也加进了babel(被JAVA和PHP荼毒太深,仍是但愿写近JAVAPHP语法的class)前端
图1. 框架文件架构java
如图1所示,这是框架的物理文件架构。app里面装的就是controller,view和model三个文件夹,里面放的就是用户的application文件。core装的是框架的核心文件。node_modules不用说了吧。test装的是测试文件。node
index.js 就是框架的入口文件。git
/** * BrownJs * * @author SevensChan * **/ import Brown from './core/Brown.js' /** * Server Config **/ const HTTP_IP = '127.0.0.1'; const HTTP_PORT = 8899; var brown = new Brown(); brown.listen(HTTP_PORT,HTTP_IP); brown.set("view engine","ejs");
主要是调用了core里面的核心文件Browngithub
import http from 'http' import router from './Router.js'; let instance = null; class Brown{ constructor(){ if (!instance){ instance = this; } this.config = []; return instance; } static getInstance(){ if (!instance){ instance = new Brown(); } return instance; } set(key,val){ this.config[key] = val; } get(key){ return this.config[key]; } listen(port,ip){ try{ var server = http.createServer(function(req,res){ router.handle(req,res); }); server.listen(port,ip); console.log('Server Start Successfully'); }catch(err){ console.log(err); } } } export default Brown;
Brown目前主要做用是用来记录config参数,以及开启服务器(后面会有其余做用,这里先不提)数据库
看到createServer中间会把全部请求都转向到router去处理,router是什么鬼,下面介绍npm
把请求想象成一列火车,交叉路上就须要有一个中转站获取火车上的信息来决定火车要走向哪条轨道。
这个中转站就是咱们须要的Router。
Router的主要做用就是读取request的参数来判断要调用哪一个controller哪一个方法去处理这个Request
常见的路由规则有
/Controller/Action/Param/querydata
和
/?controller=x&action=x¶m=querydata
在这里为了偷懒就用第二个好了
import url from 'url' import fs from 'fs' import qs from 'querystring'; import {firstUpperCase} from './Common.js' //controller缓存 let Controllers = []; exports.handle = function(req,res){ req.requrl = url.parse(req.url,true); //请求参数 var queryUrl = qs.parse(url.parse(req.url).query); //请求路径 var path = req.requrl.pathname; //是不是静态文件 if (/.(css)$/.test(path)){ res.writeHead(200,{ 'Content-Type': 'text/css' }); fs.readFile(__dirname+path,'utf8',function(err,data){ if (err) throw err; //输出静态文件 res.write(data,'utf8'); res.end(); }) }else{ //读取请求的controller、action var controller = queryUrl['cl']; var action = queryUrl['ac']; var controllerClass; var controllerObj; //若是没有指定controller、action 默认为index if (typeof controller === "undefined"){ controller = 'index'; } if (typeof action === "undefined"){ action = 'index'; } try{ //是否有缓存 if (Controllers[firstUpperCase(controller)]){ controllerObj = Controllers[firstUpperCase(controller)]; }else{ //没有缓存读取controller controllerClass = require('../app/controller/'+firstUpperCase(controller)+'Controller.js'); controllerObj = new controllerClass['default'](); Controllers[firstUpperCase(controller)] = controllerObj; } //设置request,response和请求参数 controllerObj.setHttp(req,res,queryUrl); //执行action方法 controllerObj[action](); }catch(err){ console.log(err); } } }
而后接下来controller就能够被call到了
别人用你的框架固然不能让别人什么都要本身写,因此要准备一堆父类的方法,别人的controller继承父类的controller就能够用到一大堆写好的函数,作一个函二代
import View from './View.js' class Controller{ constructor() { this.req = null; this.res = null; this.queryUrl = {}; //设置view this.view = new View(); } //设置request,response和请求参数 setHttp(req,res,queryUrl){ this.req = req; this.res = res; this.queryUrl = queryUrl; this.view.setRes(res); } //获取get请求的参数 get(query){ return this.queryUrl[query]; } //获取post请求的参数 post(query){ //To Be Continue } //返回当前controller的view对象 view(){ return this.view; } } export default Controller;
在第一篇里面用到的东西并很少,就主要设置Request,response和获取参数和View对象,到这一步,来看一下用户要用controller的时候要怎么写
import Controller from '../../core/Controller.js' class IndexController extends Controller{ constructor() { super(); } index(){ var cl = super.get('cl'); super.view().output("Hello World!" + cl); } } export default IndexController;
其实就这样了,super.get是父类实现的方法,获取到参数cl的值 而后经过view的output来输出。
view的话第一篇有两个目标,第一个直接输出内容,第二个使用一个模板引擎来实现
import Brown from './Brown.js' import Engine from './view-engine/engine.js'; /** * View 处理 * **/ class View{ constructor(){ this.res = null; } setRes(res){ this.res = res; } output(str){ const body = str; this.res.writeHead(200, { 'Content-Type': 'text/html' }); this.res.write(body); this.res.end(); } } export default View;
首先完成第一个目标,很简单,就实现一个output方法调用res去write内容
到这里为止,咱们能够测试一下了
图2. Hello World测试
完美!
计划是本身写一个模板的,可是以为这个能够放在最后再作,因此这里先实现和 ejs 模板整合
ejs 是js里面比较有名的前端框架,具体介绍:www.baidu.com/本身搜索
可是咱们整合的话,为了之后可使用到其余的模板引擎,是不能直接把ejs代码嵌入到咱们的View中去的。所以,能够看到接口的做用了吧,经过定义一个模板引擎接口,提供固定的方法,咱们的View只会关心和调用这些接口的固定方法,至于后面怎么实现,就另写引擎接口实现类去作。
翻了翻手册,发现 js ES6实现接口是屌麻烦的,所以,这些的接口定义先交给规范定义了(意思就是在使用文档上写 用这套框架的人要注意了,个人接口要这样这样写,错了别期望语法会提醒你了)
先写一个ejs引擎的实现类
import ejs from 'ejs' import fs from 'fs' class ejsEngine{ //render方法显示模板 render(res,page,data){ var body = ejs.renderFile(__dirname +'/../../../app/view/'+page,data,function(err,result){ if (!err){ res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(result); }else{ res.end(err.toString()); console.log(err); } }); } } export default ejsEngine;
renderFile是ejs提供的方法,没什么好说的的,读取到内容就res显示
而后要和View整合的话,中间最好写一个工厂类,根据Brown的view engine配置去生成相应的引擎类
//引擎缓存 let Engines = []; class Engine{ static createEngine(engineType){ if (Engines[engineType]){ return Engines[engineType]; } var engineClass = require('./engine/'+engineType+'Engine.js'); Engines[engineType] = new engineClass['default'](); return Engines[engineType]; } } export default Engine;
对了,就是这样子了,最后改装一下View
import Brown from './Brown.js' import Engine from './view-engine/engine.js'; /** * View 处理 * **/ class View{ constructor(){ this.res = null; this.engine = Engine.createEngine(Brown.getInstance().get('view engine'));//1 } setRes(res){ this.res = res; this.data = {}; } output(str){ const body = str; this.res.writeHead(200, { 'Content-Type': 'text/html' }); this.res.write(body); this.res.end(); } //2 display(){ this.engine.render(this.res,'index.ejs',this.data); } //3 assign(key,val){ this.data[key] = val; } } export default View;
分别多了1,2,3.
1. 注入一个由工厂类生成的引擎对象
2. 显示模板
3. 注入data
好了,改一下controller
import Controller from '../../core/Controller.js' class IndexController extends Controller{ constructor() { super(); } index(){ var cl = super.get('cl'); super.view().output("Hello World! " + cl); } show(){ super.view().assign('title','Hello World'); super.view().display('index.ejs'); } about(){ super.view().assign('title','I am ABOUT'); super.view().display('index.ejs'); } } export default IndexController;
show和about会调用模板去显示内容,模板很简单这里就不放出来了,直接看看最后效果
搞定
做为第一篇是比较简单的了,里面也没有太多的优化,主要把MVC概念清晰起来,而后下一篇就加入数据操做部分,主要参考JFinal
更新
项目地址: https://github.com/superhos/brownjs