入门:用moonbit写一个koa

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的生态。

先写到这里,后面再补充。

3 个赞