使用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 相关文章推荐
js操作textarea方法集合封装(兼容IE,firefox)
Feb 22 Javascript
javascript 兼容所有浏览器的DOM扩展功能
Aug 01 Javascript
用JavaScript获取DOM元素位置和尺寸大小的方法
Apr 12 Javascript
Jquery获取元素的父容器对象示例代码
Feb 10 Javascript
jquery点击展示与隐藏更多内容
Dec 03 Javascript
JS中parseInt()和map()用法分析
Dec 16 Javascript
JS简单实现点击按钮或文字显示遮罩层的方法
Apr 27 Javascript
xmlplus组件设计系列之网格(DataGrid)(10)
May 05 Javascript
微信小程序手机号码验证功能的实例代码
Aug 28 Javascript
了解JavaScript中的选择器
May 24 Javascript
简单了解TypeScript中如何继承 Error 类
Jun 21 Javascript
vue指令v-html使用过滤器filters功能实例
Oct 25 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连接Nginx服务器并解析Nginx日志的方法
2015/08/16 PHP
PHP的JSON封装、转变及输出操作示例
2019/09/27 PHP
javascript 得到变量类型的函数
2010/05/19 Javascript
js实现运动logo图片效果及运动元素对象sportBox使用方法
2012/12/25 Javascript
jqGrid日期格式的判断示例代码(开始日期与结束日期)
2013/11/08 Javascript
用js来刷新当前页面保留参数的具体实现
2013/12/23 Javascript
jQuery中index()的用法分析
2014/09/05 Javascript
浅析javascript 定时器
2014/12/23 Javascript
JavaScript DOM操作表格及样式
2015/04/13 Javascript
简介JavaScript中strike()方法的使用
2015/06/08 Javascript
JQuery悬停控制图片轮播——代码简单
2015/08/05 Javascript
使用konva和vue-konva库实现拖拽滑块验证功能
2020/04/27 Javascript
在Vue 中实现循环渲染多个相同echarts图表
2020/07/20 Javascript
vue实现给div绑定keyup的enter事件
2020/07/31 Javascript
Python实现简单截取中文字符串的方法
2015/06/15 Python
python去除字符串中的换行符
2017/10/11 Python
解决Python2.7读写文件中的中文乱码问题
2018/04/12 Python
将Django项目部署到CentOs服务器中
2018/10/18 Python
django之静态文件 django 2.0 在网页中显示图片的例子
2019/07/28 Python
基于pytorch的保存和加载模型参数的方法
2019/08/17 Python
python查看数据类型的方法
2019/10/12 Python
PyCharm导入python项目并配置虚拟环境的教程详解
2019/10/13 Python
tensorflow生成多个tfrecord文件实例
2020/02/17 Python
Python图像处理库PIL的ImageFont模块使用介绍
2020/02/26 Python
The North Face北面英国官网:美国著名户外品牌
2017/12/13 全球购物
个人简历自荐信
2013/12/05 职场文书
七一党建活动方案
2014/01/28 职场文书
软件部经理岗位职责范本
2014/02/25 职场文书
副校长竞聘演讲稿
2014/09/01 职场文书
党的群众路线剖析材料
2014/10/09 职场文书
2014年党的群众路线学习心得体会
2014/11/05 职场文书
宾馆前台接待岗位职责
2015/04/02 职场文书
伊索寓言读书笔记
2015/06/30 职场文书
办公室管理规章制度
2015/08/04 职场文书
SQL 窗口函数实现高效分页查询的案例分析
2021/05/21 SQL Server
zabbix 代理服务器的部署与 zabbix-snmp 监控问题
2022/07/15 Servers