moonbit写一个koa
目前我知道的moonbit应用场景
其实,还可以把各种npm和moonbit打通,就可以开发很多ffi,进一步完善mbt的生态。本文即通过koa例子,实现基于npm的各种ffi,是我个人学习的记录,希望对大家有帮助。
创建项目
$ moon new koa-demo
Created koa-demo
查看文件
$ cd koa-demo
$ tree .
.
├── README.md
├── lib
│ ├── hello.mbt
│ ├── hello_test.mbt
│ └── moon.pkg.json
├── main
│ ├── main.mbt
│ └── moon.pkg.json
└── moon.mod.json
3 directories, 7 files
编译和调试
$ moon build --target js
添加package.json
$ npm init -y
Wrote to /Users/alfred/workspace/github/koa-demo/package.json:
{
"name": "koa-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"directories": {
"lib": "lib"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
此时把build和debug加进去
"scripts": {
"start": "node target/js/release/build/main/main.js",
"build": "moon build --target js",
"debug": "moon build --target js --debug"
},
此时测试
$ npm start
> koa-demo@1.0.0 start
> npm run build && node target/js/release/build/main/main.js
> koa-demo@1.0.0 build
> moon build --target js
moon: ran 3 tasks, now up to date
Hello, world!
实现koa
const Koa = require('koa');
const app = new Koa();
app.use((ctx)={
ctx.body = 'hi, koa'
})
app.listen(8000)
最终我想要的写法
let koa = create_koa(get_koa())
koa.use(fn(
ctx : @koa.Context,
) -> Option[@js.Promise[@web.Response]]
{
ctx.body('hi, koa')
})
// let a = koa.mw_default()
koa.mw_default().listen(8000)
安装koa模块
$ npm i -S koa
added 42 packages, and audited 43 packages in 585ms
found 0 vulnerabilities
第一个例子
lib/hello.mbt
pub type ApplicationClass
extern "js" fn get_koa() -> ApplicationClass =
#|() => require("koa")
extern "js" fn log(o : ApplicationClass) =
#| (o) => console.dir(o)
pub fn hello() -> String {
let a = get_koa()
log(a)
"Hello, world!"
}
查看结果
$ npm start
> koa-demo@1.0.0 start
> npm run build && node target/js/release/build/main/main.js
> koa-demo@1.0.0 build
> moon build --target js
moon: ran 3 tasks, now up to date
[class Application extends EventEmitter] {
HttpError: [Function: HttpError]
}
Hello, world!
理解ffi和require
目前支持wasm和js作为后端的ffi。有2种定义方式。
- 双字符串:fn f(args) = “a” “b” → a.b(args)
- 内联JS:extern “js” fn f(args) = #| (args) => {}
require是Node.js CommonJS规范里的关键字,通过require可以读取npm模块里的内容。
const app = require('koa')
第二个例子
lib/hello.mbt
pub type ApplicationClass
extern "js" fn get_koa() -> ApplicationClass =
#|() => require("koa")
pub type Application
extern "js" fn create_koa(clazz : ApplicationClass) -> Application =
#|(claz) => new claz()
extern "js" fn log(o : Application) =
#| (o) => console.dir(o)
pub fn hello() -> String {
let a = create_koa(get_koa())
log(a)
"Hello, world!"
}
执行
$ npm start
> koa-demo@1.0.0 start
> npm run build && node target/js/release/build/main/main.js
> koa-demo@1.0.0 build
> moon build --target js
moon: ran 3 tasks, now up to date
Application {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
proxy: false,
subdomainOffset: 2,
proxyIpHeader: 'X-Forwarded-For',
maxIpsCount: 0,
env: 'development',
middleware: [],
context: {},
request: {},
response: {},
[Symbol(shapeMode)]: false,
[Symbol(kCapture)]: false,
[Symbol(nodejs.util.inspect.custom)]: [Function: inspect]
}
Hello, world!
这样就可以获得koa对象里。
第三个例子: 理解多文件
新建lib/app.mbt
pub type ApplicationClass
extern "js" fn get_koa() -> ApplicationClass =
#|() => require("koa")
pub type Application
extern "js" fn create_koa(clazz : ApplicationClass) -> Application =
#|(claz) => new claz()
extern "js" fn listen_js(app : Application, port : Int) -> Unit =
#|(app, port) => app.listen(port)
extern "js" fn mw_default_js(app : Application) -> Unit =
#|(app) => app.use(async ctx => { ctx.body = {a:1};});
pub let app : Application = create_koa(get_koa())
pub fn mw_default(self : Application) -> Application {
mw_default_js(self)
}
pub fn listen(self : Application, port : Int) -> Unit {
listen_js(self, port)
}
知识点:只要是pub的,在其他文件里直接访问,没有use,import那些,简单直接,缺点是自己要注意命名空间。
修改lib/hello.mbt测试代码
extern "js" fn log(o : Application) =
#| (o) => console.dir(o)
pub fn hello() -> String {
// println(get_pi())
let koa = create_koa(get_koa())
// let a = koa.mw_default()
koa.mw_default()
koa.listen(8000)
log(koa)
"start server at http://127.0.0.1:8000 !"
}
执行,至此已经启动了koa服务。
$ npm start
> koa-demo@1.0.0 start
> npm run build && node target/js/release/build/main/main.js
> koa-demo@1.0.0 build
> moon build --target js
moon: ran 3 tasks, now up to date
Application {
_events: [Object: null prototype] { error: [Function: onerror] },
_eventsCount: 1,
_maxListeners: undefined,
proxy: false,
subdomainOffset: 2,
proxyIpHeader: 'X-Forwarded-For',
maxIpsCount: 0,
env: 'development',
middleware: [ [AsyncFunction (anonymous)] ],
context: {},
request: {},
response: {},
[Symbol(shapeMode)]: false,
[Symbol(kCapture)]: false,
[Symbol(nodejs.util.inspect.custom)]: [Function: inspect]
}
start server at http://127.0.0.1:8000 !
知识点
- mbt里Unit就是void,编译到JS以后Unit就对应undefined ,也就是void
第四个例子:学习链式操作
上面的代码里,是这样的调用的。
koa.mw_default()
koa.listen(8000)
这种写法没啥问题,就是啰嗦了点。正常mw_default返回类型如果是app,那么就可以链式调用了,代码更精简,语义上也更完善。
修改lib/app.mbt,让mw_default返回self类型。
pub fn mw_default(self : Application) -> Application {
mw_default_js(self)
self
}
然后再lib/hello.mbt理就可以链式调用了
koa.mw_default().listen(8000)
第五个例子:实现use中间件
待定
TODO
- 实现app.use中间件定义
- Async函数和Promise是难点
- 实现mount-dir
- 实现已有中间件集成
总结
通过这个demo,大家可以把各种npm和moonbit打通,就可以开发很多ffi,进一步完善mbt的生态。
- wasm应用,https://github.com/peter-jerry-ye/worker
- 前端应用,比如https://www.moonbitlang.com/gallery/
- 基于npm的各种ffi,即本文。
先写到这里,后面再补充。