使用typescript改造koa开发框架的实现


Posted in Javascript onFebruary 04, 2020

强类型的 TypeScript 开发体验和维护项目上相比 JavaScript 有着明显的优势,那么对常用的脚手架进行改造也就势在必行了。

接下来开始对基于 koa 框架的 node 后端脚手架进行改造:

  1. 项目开发环境 和 typescript 编译环境的搭建;
  2. 对 node、koa、koa中间件和使用到的库 添加类型化支持;
  3. 基于 typesript 的特性改造项目。

项目开发环境搭建

基于 gulp 搭建开发编译环境,gulp-typescript 插件用于编译 typescript 文件, gulp-nodemon 则可以监控文件内容的变更,自动编译和重启node服务,提升开发效率。

npm install -D gulp gulp-nodemon gulp-typescript ts-node typescript

gulp 的配置

gulpfile.js 的设置

const { src, dest, watch, series, task } = require('gulp');
const del = require('del');
const ts = require('gulp-typescript');
const nodemon = require('gulp-nodemon');
const tsProject = ts.createProject('tsconfig.json');

function clean(cb) {
 return del(['dist'], cb);
}

// 输出 js 到 dist目录
function toJs() {
 return src('src/**/*.ts')
  .pipe(tsProject())
  .pipe(dest('dist'));
}

// nodemon 监控 ts 文件
function runNodemon() {
 nodemon({
  inspect: true,
  script: 'src/app.ts',
  watch: ['src'],
  ext: 'ts',
  env: { NODE_ENV: 'development' },
  // tasks: ['build'],
 }).on('crash', () => {
  console.error('Application has crashed!\n');
 });
}

const build = series(clean, toJs);
task('build', build);
exports.build = build;
exports.default = runNodemon;

typescript 的配置

tsconfig.json 的设置

{
 "compilerOptions": {
  "baseUrl": ".", // import的相对起始路径
  "outDir": "./dist", // 构建输出目录
  "module": "commonjs",
  "target": "esnext",// node 环境支持 esnext
  "allowSyntheticDefaultImports": true,
  "importHelpers": true,
  "strict": false,
  "moduleResolution": "node",
  "esModuleInterop": true,
  "forceConsistentCasingInFileNames": true,
  "noImplicitAny": true,
  "suppressImplicitAnyIndexErrors": true,
  "noUnusedParameters": true,
  "noUnusedLocals": true,
  "noImplicitReturns": true,
  "experimentalDecorators": true, // 开启装饰器的使用
  "emitDecoratorMetadata": true,
  "allowJs": true,
  "sourceMap": true,
  "paths": {
   "@/*": [ "src/*" ]
  }
 },
 "include": [
  "src/**/*"
 ],
 "exclude": [
  "node_modules",
  "dist"
 ]
}

eslint 的配置

当然 eslint 也要添加对 typescript 对支持

npm install -D @typescript-eslint/eslint-plugin @typescript-eslint/parser

.eslintrc.json 的设置

{
 "env": {
  "es6": true,
  "node": true
 },
 "extends": [
  "eslint:recommended",
  "plugin:@typescript-eslint/eslint-recommended"
 ],
 "globals": {
  "Atomics": "readonly",
  "SharedArrayBuffer": "readonly"
 },
 "parser": "@typescript-eslint/parser",
 "parserOptions": {
  "ecmaVersion": 2018,
  "sourceType": "module"
 },
 "plugins": [
  "@typescript-eslint"
 ],
 "rules": {
  "indent": [ "warn", 2 ],
  "no-unused-vars": 0
 }
}

package.json 运行配置

最后就是设置 package.json 的 scripts

"scripts": {
 "start": "gulp",// dev
 "build": "gulp build", // output
 "eslint": "eslint --fix --ext .js,.ts src/",
 "server": "export NODE_ENV=production && node dist/app" // production server
},

添加类型化支持

项目主要使用到了以下的组件

jsonwebtoken
koa
koa-body
koa-compress
koa-favicon
koa-logger
koa-router
koa-static
koa2-cors
log4js

那么就要安装对应的 type 文件,当然别忘了 @types/node

npm install -D @types/jsonwebtoken @types/koa @types/koa-compress @types/koa-favicon @types/koa-logger @types/koa-router @types/koa-static @types/koa2-cors @types/log4js @types/node

使用 typescript 装饰器 改造项目

.net mvc 框架有个很便利的地方就是 使用装饰器对控制器进行配置,现在通过 typescript 的装饰器也可以实现相同的功能。这里需要使用到反射相关的库 reflect-metadata,用过 Java 或 C# 的小伙伴,对反射的原理一定不陌生。

定义http请求的装饰器

我们再也不需要在路由配置和控制器方法之前来回查找和匹配了

import 'reflect-metadata'
import { ROUTER_MAP } from '../constant'

/**
 * @desc 生成 http method 装饰器
 * @param {string} method - http method,如 get、post、head
 * @return Decorator - 装饰器
 */
function createMethodDecorator(method: string) {
 // 装饰器接收路由 path 作为参数
 return function httpMethodDecorator(path: string) {
  return (proto: any, name: string) => {
   const target = proto.constructor;
   const routeMap = Reflect.getMetadata(ROUTER_MAP, target, 'method') || [];
   routeMap.push({ name, method, path });
   Reflect.defineMetadata(ROUTER_MAP, routeMap, target, 'method');
  };
 };
}

// 导出 http method 装饰器
export const post = createMethodDecorator('post');

export const get = createMethodDecorator('get');

export const del = createMethodDecorator('del');

export const put = createMethodDecorator('put');

export const patch = createMethodDecorator('patch');

export const options = createMethodDecorator('options');

export const head = createMethodDecorator('head');

export const all = createMethodDecorator('all');

装饰控制器的方法

export default class Sign {
  
 @post('/login')
 async login (ctx: Context) {
  const { email, password } = ctx.request.body;
  const users = await userDao.getUser({ email });
  // ...
  return ctx.body = {
   code: 0,
   message: '登录成功',
   data
  };
 }

 @post('/register')
 async register (ctx: Context) {
  const { email, password } = ctx.request.body;
  const salt = makeSalt();
  // ...
  return ctx.body = {
   code: 0,
   message: '注册成功!',
   data
  }
 }
 
}

收集元数据和添加路由

我们已经把装饰器添加到对应控制器的方法上了,那么怎么把元数据收集起来呢?这就需要用到 node 提供的 fs 文件模块,node服务第一次启动的时候,扫描一遍controller文件夹,收集到所有控制器模块,结合装饰器收集到的metadata,就可以把对应的方法添加到 koa-router。

import 'reflect-metadata'
import fs from 'fs'
import path from 'path'
import { ROUTER_MAP } from './constant'
import { RouteMeta } from './type'
import Router from 'koa-router'

const addRouter = (router: Router) => {
 const ctrPath = path.join(__dirname, 'controller');
 const modules: ObjectConstructor[] = [];
 // 扫描controller文件夹,收集所有controller
 fs.readdirSync(ctrPath).forEach(name => {
  if (/^[^.]+?\.(t|j)s$/.test(name)) {
   modules.push(require(path.join(ctrPath, name)).default)
  }
 });
 // 结合meta数据添加路由
 modules.forEach(m => {
  const routerMap: RouteMeta[] = Reflect.getMetadata(ROUTER_MAP, m, 'method') || [];
  if (routerMap.length) {
   const ctr = new m();
   routerMap.forEach(route => {
    const { name, method, path } = route;
    router[method](path, ctr[name]);
   })
  }
 })
}

export default addRouter

最后

这样对koa项目脚手架的改造基本完成,源码请查看koa-server

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery遍历Table应用示例
Apr 09 Javascript
JS非Alert实现网页右下角“未读信息”效果弹窗
Sep 26 Javascript
javascript实现倒计时跳转页面
Jan 17 Javascript
浅谈js数据类型判断与数组判断
Aug 29 Javascript
使用Angular.js开发的注意事项
Oct 19 Javascript
Bootstrap标签页(Tab)插件使用方法
Mar 21 Javascript
微信小程序 数据遍历的实现
Apr 05 Javascript
JS判断数组那点事
Oct 10 Javascript
AngularJs ng-change事件/指令的用法小结
Nov 01 Javascript
angular4 JavaScript内存溢出问题
Mar 06 Javascript
vue+express 构建后台管理系统的示例代码
Jul 19 Javascript
Vue组件间数据传递的方式(3种)
Jul 13 Javascript
Vue解析剪切板图片并实现发送功能
Feb 04 #Javascript
Vue实现剪切板图片压缩功能
Feb 04 #Javascript
Vue中keep-alive组件作用详解
Feb 04 #Javascript
WEB前端性能优化的7大手段详解
Feb 04 #Javascript
JavaScript对象属性操作实例解析
Feb 04 #Javascript
JavaScript this使用方法图解
Feb 04 #Javascript
解决微信小程序scroll-view组件无横向滚动的问题
Feb 04 #Javascript
You might like
PHP禁止页面缓存的代码
2011/10/23 PHP
php eval函数用法 PHP中eval()函数小技巧
2012/10/31 PHP
Symfony的安装和配置方法
2016/03/17 PHP
php  单例模式详细介绍及实现源码
2016/11/05 PHP
基于thinkPHP类的插入数据库操作功能示例
2017/01/06 PHP
php使用include 和require引入文件的区别
2017/02/16 PHP
thinkPHP显示不出验证码的原因与解决方法分析
2017/05/20 PHP
jQuery帮助之筛选查找 children([expr])
2011/01/31 Javascript
event对象获取方法总结在google浏览器下测试
2013/11/03 Javascript
js函数名与form表单元素同名冲突的问题
2014/03/07 Javascript
输入框过滤非数字的js代码
2014/09/18 Javascript
jQuery选择器之基本选择器与层次选择器
2015/03/03 Javascript
js实现鼠标移到链接文字弹出一个提示层的方法
2015/05/11 Javascript
javascript运算符——位运算符全面介绍
2016/07/14 Javascript
JSP防止网页刷新重复提交数据的几种方法
2016/11/19 Javascript
angularjs实现首页轮播图效果
2017/04/14 Javascript
Vue实现购物车功能
2017/04/27 Javascript
js对象实例详解(JavaScript对象深度剖析,深度理解js对象)
2017/09/21 Javascript
vue打包使用Nginx代理解决跨域问题
2018/08/27 Javascript
python遍历一个目录,输出所有的文件名的实例
2018/04/23 Python
解决Python中list里的中文输出到html模板里的问题
2018/12/17 Python
python 微信好友特征数据分析及可视化
2020/01/07 Python
python实现的Iou与Giou代码
2020/01/18 Python
Python通用唯一标识符uuid模块使用案例
2020/09/10 Python
Pycharm安装Qt Design快捷工具的详细教程
2020/11/18 Python
中国制造网:Made-in-China.com
2019/10/25 全球购物
描述Cookie和Session的作用,区别和各自的应用范围,Session工作原理
2015/03/25 面试题
财务会计专业推荐信
2013/11/30 职场文书
关于逃课的检讨书
2014/01/23 职场文书
工程质量承诺书范文
2014/03/27 职场文书
政治思想表现评语
2014/05/04 职场文书
一次性工伤赔偿协议书范本
2014/11/25 职场文书
2015年预备党员自我评价
2015/03/04 职场文书
校园广播站开场白
2015/06/01 职场文书
刚学完怎么用Python实现定时任务,转头就跑去撩妹!
2021/06/05 Python
java协程框架quasar和kotlin中的协程对比分析
2022/02/24 Java/Android