lime.js 是一个轻量的基于 Koa2 的 Node.js Web开发框架,它基于经典的 MVC 范式,致力于提供简单易用的 Web 应用开发体验。
-
内核简单、优雅, 易上手
lime.js 可以无缝衔接 Koa 和 npm 的现有生态;在灵活与标准之间,lime 以
中庸
的态度做出权衡,并不断面向用户体验和未来而演进 -
经典 MVC 范式,约定大于配置
采用最经典的分层架构、约定大于配置的设计理念
-
可扩展性强, 功能完备
尽管保持了小而精致的内核,但通过插件机制和社区生态打造的脚手架, lime 完全可以胜任复杂项目的开发
-
面向未来的架构和工具链
对于服务器端而言,升级运行环境的成本要比客户端小的多。也正因如此,lime.js 会毫不客气的废弃对老版本 Node.js 的支持。lime 会用面向未来的态度随时进化
只要你熟悉 Node.js 基本语法并了解 Koa 框架中的基本概念(如中间件),即可 3 秒钟快速上手 lime.js 框架
下面,我们通过简单的 3 个步骤,开启 LIME.JS 的 Web 开发之旅
- 创建项目并安装 lime.js
# 创建一个项目目录 lime-demo
mkdir lime-demo
cd lime-demo
# 初始化 package.json
npm init -y
# 安装 lime.js 核心作为项目依赖
npm install @limejs/core --save
- 创建 LIME 框架运行所需的项目结构
# 创建一系列目录和文件
mkdir -p config plugin mvc/controller mvc/view mvc/model
touch mvc/router.js mvc/controller/home.js
touch app.js
# 此时 目录结构如下所示
|-config # 站点配置,本示例不涉及
|-plugins # 站点插件,本示例不涉及
|-src
|-controller
|-home.js # 一个叫做 Home首页 的 控制器
|-view
|-home.hbs
|-model
|-router.js # 站点路由
|-app.js # 站点应用入口
|-package.json
- 分别对 app.js、router.js、home.js 编写代码。内容如下:
// router.js 中定义路由
module.exports = (router) => {
router.get('/', 'home@index')
}
// home.js 中定义index首页请求的逻辑
module.exports = {
async index() {
this.ctx.body = 'hello lime' // 通过 this.ctx 可以访问到 Koa 上下文对象
}
}
// app.js 中引入 lime 框架内核并启动
const Lime = require('@limejs/core')
const app = new Lime()
app.listen()
控制器代码中的 index
函数叫做控制器的 action
,它本质上是一个完全意义上的 Koa 中间件函数,该函数所传入的 ctx 和 next 参数分别表示 Koa 的 ctx 上下文对象以及下一个中间件函数。
如果一切顺利,此时打开浏览器访问 http://localhost:3000 便可以看到
hello lime! # 看到它 说明已经运行成功
尽管创建一个基于 LIME 框架的项目如此简单,但事实上要开发一个生产环境的站点还需要大量其他的能力,例如使用模板引擎进行视图渲染、使用mongoose进行数据存取、打印日志等等;而LIME内核通过灵活的插件机制让你可以通过插拔插件的方式,应对复杂的Web应用开发。只要你引入对应的LIME插件,LIME就可以变得无所不能。
我们知道选择插件组装应用是一件纠结的事情,所以事实上你无须自己手工创建项目样板。LIME 官方提供了一坨开箱即用的项目样板供您享用。无论是开发 Rest API 项目,还是基于 Vue.js 的前后端分离项目,还是传统的MVC模式的站点,LIME 都有一套完备且易用的样板。目前有以下这些样板项目:
- MVC 这是传统的 MVC 开发模式的项目样板,包含完整的MVC模块,服务端通过模板引擎来渲染视图。
[立刻启动一个传统MVC模式的开发](https://github.com/limejs/lime-template-standard)
- SPA 基于 Lime+Vue 的前后端分离的项目样板,兼顾单页应用运行和API开发。通过 lime 框架作为中间层来托管 Vue 资源文件,并在 limejs 中通过 webpack-dev-middleware 等插件完成Vue的热加载和热替换。
[立刻启动一个用 Node.js 作为中间层的SPA项目](https://github.com/limejs/lime-template-spa)
- SSR Lime+VueSSR 服务端同构渲染的项目样板,适合面向消费者、对首屏加载性能和SEO有一定要求的站点;同样实现了热加载热替换、异常处理、cookie处理、api开发等。
[立刻启动一个用 Node.js 作为中间层的同构VueSSR项目](https://github.com/limejs/lime-template-spa)
- API API 项目样板,去除了 model层、view层,增加了services层,适合开发REST API项目。
通过 lime-cli 初始化时进行交互选择,您可以轻松创建一个 Web 应用样板来开发复杂应用;当然你也无需担心 cli 工具蒙蔽了你的双眼,事实上它仅仅是帮你引入了合适的 LIME 插件而已,内部发生的一切都很容易理解。了解更多的话你需要去参考对应项目样板的 README,当然继续读完本文档对你上手开发总是有益的。
LIME 是 约定大于配置
的,所以 LIME 内部有一些惯例(约定),我们在这里做简要说明。
- 站点目录结构必须符合规范
|-- config
|-- site.js 站点配置文件
|-plugins
|- ua.js 一个插件
|-- src
|-- router.js 路由
|-- controllers 控制器
|-- models 模型
|-- views 视图
|-- app.js 站点启动入口
|-- package.json
你无法通过配置文件来修改 LIME 所要求的目录结构。如确有必要的话,可通过 Lime 构造函数的参数来配置(但不建议这么做),如:
const app = new Lime({
root: __dirname,
config: path.join(__dirname, './myconfig') // 修改默认配置文件的目录
})
- 控制器的物理文件名就是 controller 的名字
假如我们要创建一个 新闻
的控制器 news
,并在控制器中提供一个获取 新闻列表
的 list
函数。那么,我们应该创建一个 mvc/controller/news.js
的文件,并在 news.js
中这样编写一个控制器对象:
const NewsController = {
// 返回新闻列表
async list(ctx, next) {
ctx.body = []
}
// 返回一个新闻详情
async detail(ctx, next) {
let id = ctx.query.id
// ...
ctx.body = {}
}
}
module.exports = NewsController
这样,news
就是控制器的名字,list
就是 action 的名字。在路由中,可以通过 news@list
映射到这个新闻列表函数。
- 路由
路由的约定是: 路由行为用@符号来分割 controller 和 action
举个栗子: 我们希望客户端发送的 http://domain.com/news
请求可以返回上面的 新闻列表
,也就是说要把 /news
路径的 get
请求交给 news controller
的 list action
来处理。那么我们应该这样编写路由:
module.exports = (router) => {
router.get('/', 'news@list')
}
这里 router
对象的所有 API 都可以参考 koa-router
来使用。
Lime 的默认使用 src/router.js 进行路由规则配置,Lime 的路由在底层基于 koa-router
实现。支持如下实例方法:
router.get|put|post|patch|delete|del ⇒ Router
router.routes ⇒ function
router.use([path], middleware) ⇒ Router
router.prefix(prefix) ⇒ Router
router.allowedMethods([options]) ⇒ function
router.redirect(source, destination, [code]) ⇒ Router
router.route(name) ⇒ Layer | false
router.url(name, params, [options]) ⇒ String | Error
router.param(param, middleware) ⇒ Router
config目录中存放 LIME 的框架基础配置,其中有四个配置文件,分别影响不同环境的配置:
common.js 公共配置,即各个环境都会使用的配置
dev.js 开发环境配置
test.js 测试环境配置
prod.js 生产环境配置
配置的覆盖规则是这样的: common配置是最底层的配置,如果特定环境里面指定了相同的配置项导致冲突,则优先使用 环境配置文件 里所指定的配置。
LIME中的最终配置不仅会指导 LIME内核的行为,同时你也可以在业务开发过程中通过 ctx.config 获取到这个配置对象,从而可以基于配置进行相关业务逻辑的开发。
可配置的参数字段有:
module.exports = {
env: process.env.NODE_ENV, // env变量用于指导内核如何输出调试信息。支持 development、test、production三种字符串。建议您在业务开发过程中也使用 ctx.config.env 获取该变量,这样便与内核保持相同的环境判断方法。
port: process.env.PORT || 3000, // 监听端口。你可以根据自己实际业务逻辑进行修改
host: process.env.HOST || '127.0.0.1', // 监听地址
publicPath: '/', // 站点的域名访问目录,默认是`/`表示站点运行在根path下。如果你要将本项目运行在 `www.baidu.com/bbs` 这样的目录下,你需要将此处配置为 `/bbs`
plugins: [
'logger', // 插件名称标识符
'plugin-lime-cors',
// 也可以用一个对象来表示一个插件
{
name: 'session',
options: {
key: 'abc'
}
}
]
}
插件配置在下文 Plugin 一节详细解释
LIME 通过插件的机制来扩展框架的能力。通过编写插件,可以实现:
- 挂载 global 对象上的辅助函数。例如全局的looger函数:
global.logger()
- 在请求生命周期中注入Koa原生的中间件
- 扩展 MVC 模块中的方法,例如在controller中注入一个
this.http
的方法用来发起http请求
为了更你能编写出更优雅的插件,有必要在这里解释下 Lime 初始化时注册插件的过程:
- Lime 首先会按顺序收集站点 config 配置中指定的 plugins 字段中所有插件
- 接下来,Lime 会按照插件顺序依次执行各个插件的 global函数,再依次执行所有插件的 install 函数,再依次执行各个插件的 middleware 函数,以此类推到 controller、view、model。
这个机制对编写 middleware 类型的插件会有影响,因为很多中间件对执行顺序有要求,所以你可以通过配置插件的顺序来影响中间件的执行顺序。
下面,我们来编写一个插件,插件的编写简直不要太简单,一个 plugin 插件就是一个 JavaScript 文件,插件的基本框架如下所示:
module.exports = function(options) {
global(g) {
// 在这里往 global 对象上挂载方法;因为global函数会在 Lime 生命周期的最开始执行挂载,因此可以确保你在
},
install(app, context) {
// 在这里修改 Koa 的 app 和 context 对象原型
},
middleware(app) {
// 在这里通过 app.use() 注入中间件
},
controller(proto) {
// 在这里扩展 控制器 的能力
},
model(proto) {
// 在这里扩展 模型能力
},
view(proto) {
// 在这里扩展 视图能力
}
}
这里的middleware字段所注入的中间件 实际上就是 Koa 的原生中间件,因此我们可以复用 npm 仓库中数以万计的中间件模块。
举个栗子,我们以编写一个 logger 插件为例:
const logger = require('koa-logger') // 引入一个 Koa2 原生中间件
module.exports = function(options) {
middleware(app) {
// 在这里使用 Koa 中间件语法把 logger 中间件注入到 lime 框架中
app.use(logger)
}
}
编写完成之后,我们把该文件命名为 logger.js
放置到项目根目录的 plugins
目录下:
|- src
|- plugins
|- logger.js
当你的插件比较复杂(有多个文件时),也可以以目录的形式组织起来,命名为目录放置在 plugins 目录下:
|- src
|- plugins
|- logger
|- index.js
|- dep.js
不过要注意确保 logger/index.js
正确导出 lime 所要求的插件对象。
插件代码编写完成后,需要配置才能启用。我们去项目根目录的 config
目录下,在 common.js
公共配置中的 plugins
字段加入该插件:
module.exports = {
// plugins是个数组,依次填写插件名称或插件对象
plugins: [
'logger'
]
}
注意: 通常情况下,Koa 的一些中间件是有顺序要求的。例如 koa-logger
中间件的官方建议:
Recommended that you .use() this middleware near the top to "wrap" all subsequent middleware.
而 limejs 框架是依靠你在 plugins 字段上配置的前后顺序来依次加载的。因此,在 common.js
的配置中你要注意把 logger 插件放置在靠前的位置就好了。
有些插件是其他人开发好,并且发布到 npm 仓库的,此时你不需要把插件下载到自己项目中。你只需要安装它:
npm install @limejs/plugin-cors
然后在插件配置中这样指定上插件的npm包名
module.exports = {
// plugins是个数组,依次填写插件名称或插件对象
plugins: [
'@limejs/plugin-cors'
]
}
Lime 注册插件的机制是: 优先寻找本项目 plugins 目录下的同名插件,如果无法找到,则寻找 node_modules 下的同名插件。如果都找不到,则抛出错误。
在 config 中配置插件时,除了使用字符串的形式指定插件名称,还可以使用对象形式。对象形式可以允许你给插件传入 options 选项参数
例如,我们使用 koa-csrf 中间件来编写一个 Lime 插件:
// 插件名称: lime-csrf
const CSRF = require('koa-csrf')
module.exports = function (options){
middleware(app) {
// 把 options 传入 koa-csrf 构造器
app.use(new CSRF(options))
}
}
那么,在使用该插件时,便可以在配置中加入选项配置:
// common.js
module.exports = {
plugins: [
{
name: 'lime-csrf',
options: {
invalidTokenMessage: '非法的token',
invalidTokenStatusCode: 403
}
}
]
}
Lime 构造函数可以支持传入选项,以控制框架的初始化逻辑:
const app = new Lime({
root: __dirname, // 站点的项目目录
config: '', // 站点配置文件目录
mvc: '', // 站点 mvc 代码放置目录
plugin: '', // LIME 插件 的放置目录
})
默认情况下,LIME 使用当前的 工作目录
(process.cwd) 作为站点的根目录,同时站点的配置、路由、控制器路径也都是基于 root
的位置进行解析的. 因此,如果你的工作目录不在站点根目录下,或者你需要修改默认目录的位置,可在初始化时通过 Lime 构造函数选项来修改
对于使用 pm2、supervisor 等工具启动的 LIME 站点,我们建议你设置 root 参数为 __dirname
(即当前站点项目的根目录), 以减少你错误配置 pm2、supervisor 的工作目录带来的问题。
Lime 的实例在 plugin 中经常碰到,lime 实例有以下属性和方法
- [prop] options 初始化 Lime 所用的选项配置
- [prop] config 基于站点配置文件site.js的站点信息
- [method] global 注入一个中间件处理函数. 该方法代理自 Koa
Lime 除了核心类和实例方法之外,为了方便用户可以随时随地调用,还侵入性的在 Node.js global 对象添加了少量的辅助函数。他们有:
- [method] logger.[warn|ok|error|info](<msg>) 打印不同级别的信息到输出流(一般用来主动记录日志)
- DEBUG=lime:* 打开lime框架内核的调试日志;你也可以选择性地打开 lime:store, lime:controller, lime:router 等模块的调试日志
- PORT: http server 监听的端口号
- HOST: http server 监听的 host 地址
除了前文讲到的通过 app.listen
来启动 LIME 服务,LIME 也可以单纯作为一个 http 处理器来用。这适用于把 LIME 集成到已有的 Node.js http 服务中(如腾讯公司的 tsw)。
集成方式非常简单,只需把 app.callback()
的返回值作为 http 请求的 handler 处理器即可。示例:
// your server code
const http = require('http')
const Lime = require('@limejs/core')
const app = new Lime()
http.createServer(app.callback()).listen(8080)
欢迎帮助 LIME 进化和修复 issue,您可遵循 CONTRIBUTE 规范来参与开发。
todo任务分为 内核、工具、生态、插件,这是我们的 TODO 列表:
- [内核] 插件支持配置和options
- [内核] 优化启动方式,启动的port和host改成从配置文件获取,减轻启动时代码复杂度
- [内核] 路由、插件解析报错的处理
- [内核] 异常处理优化
- [内核] 内置插件移除 转为limejs/plugin-x 的npm包
- [生态] 更多脚手架模板完善
- [内核] view、model、service层完善
- [内核] 支持 global 挂载
- [工具] PM2 和 nginx 配置
- [内核] host,port,publicPath,env 配置支持
- [工具] docker支持
- [插件] redis,mongoose插件
- [内核] 利用app.onerror 实现优雅的让用户定制500 404页面。
- [内核] 加载插件时,对于需要绑定this的函数,检测其是否使用了箭头函数(无法绑定this),有的话则提醒并退出
LIME 正在不断进化迭代中,你可以在 这里 查看版本变更细节
MIT License