使用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阻止冒泡和HTML默认操作
Nov 17 Javascript
jQuery实现给页面换肤的方法
May 30 Javascript
图解prototype、proto和constructor的三角关系
Jul 31 Javascript
又一款js时钟!transform实现时钟效果
Aug 15 Javascript
Js获取当前日期时间及格式化代码
Sep 17 Javascript
ES6中Proxy与Reflect实现重载(overload)的方法
Mar 30 Javascript
JavaScript中严格判断NaN的方法
Feb 16 Javascript
jQuery实现表单动态添加数据并提交的方法
Jul 19 jQuery
vue.draggable实现表格拖拽排序效果
Dec 01 Javascript
说说Vue.js中的functional函数化组件的使用
Feb 12 Javascript
编写v-for循环的技巧汇总
Dec 01 Javascript
详解ES6 中的Object.assign()的用法实例代码
Jan 11 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
全国FM电台频率大全 - 20 广西省
2020/03/11 无线电
php 截取字符串并以零补齐str_pad() 函数
2011/05/07 PHP
PHP实现AES256加密算法实例
2014/09/22 PHP
PHP连接MySQL数据的操作要点
2015/03/20 PHP
Symfony的安装和配置方法
2016/03/17 PHP
Linux下快速搭建php开发环境
2017/03/13 PHP
Laravel5.5 视图 - 创建视图和数据传递示例
2019/10/21 PHP
javascript实现轮显新闻标题链接
2007/08/13 Javascript
IE JS编程需注意的内存释放问题
2009/06/23 Javascript
js弹窗代码 可以指定弹出间隔
2010/07/03 Javascript
select标签模拟/美化方法采用JS外挂式插件
2013/04/01 Javascript
js从10种颜色中随机取色实现每次取出不同的颜色
2013/10/23 Javascript
jquery的总体架构分析及实现示例详解
2014/11/08 Javascript
jquery动态改变div宽度和高度
2015/02/09 Javascript
七夕情人节丘比特射箭小游戏
2015/08/20 Javascript
使用jQuery或者原生js实现鼠标滚动加载页面新数据
2016/03/06 Javascript
Vue.js实例方法之生命周期详解
2017/07/03 Javascript
es7学习教程之fetch解决异步嵌套问题的方法示例
2017/07/21 Javascript
vue 使用微信jssdk,调用微信相册上传图片功能
2020/11/13 Javascript
vue中如何添加百度统计代码
2020/12/19 Vue.js
[05:02]2014DOTA2 TI中国区预选赛精彩TOPPLAY第三弹
2014/06/25 DOTA
[01:05:07]DOTA2-DPC中国联赛 正赛 DLG vs Dragon BO3 第一场2月1日
2021/03/11 DOTA
python获取一组汉字拼音首字母的方法
2015/07/01 Python
Python2.7基于淘宝接口获取IP地址所在地理位置的方法【测试可用】
2017/06/07 Python
python语言中with as的用法使用详解
2018/02/23 Python
pycharm 配置远程解释器的方法
2018/10/28 Python
python实现控制台打印的方法
2019/01/12 Python
解决在pycharm中显示额外的 figure 窗口问题
2019/01/15 Python
将pip源更换到国内镜像的详细步骤
2019/04/07 Python
内部类的定义、种类以及优点
2013/10/16 面试题
服务中心夜班服务员岗位职责
2013/11/27 职场文书
三爱活动实施方案
2014/03/19 职场文书
保护环境标语
2014/06/09 职场文书
领导班子四风表现材料
2014/08/23 职场文书
Python进阶学习之带你探寻Python类的鼻祖-元类
2021/05/08 Python
tensorflow中的梯度求解及梯度裁剪操作
2021/05/26 Python