使用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学习7 操作JavaScript对象和集合的函数
Feb 07 Javascript
JQuery的ajax获取数据后的处理总结(html,xml,json)
Jul 14 Javascript
JavaScript之Object类型介绍
Apr 01 Javascript
JavaScript检测字符串中是否含有html标签实现方法
Jul 01 Javascript
javascript判断图片是否加载完成的方法推荐
May 13 Javascript
js图片轮播手动切换特效
Jan 12 Javascript
Angular2使用jQuery的方法教程
May 28 jQuery
node实现定时发送邮件的示例代码
Aug 26 Javascript
React Native模块之Permissions权限申请的实例相机
Sep 28 Javascript
解决在vue+webpack开发中出现两个或多个菜单公用一个组件问题
Nov 28 Javascript
详解Vue基于 Nuxt.js 实现服务端渲染(SSR)
Apr 05 Javascript
详解如何使用nvm管理Node.js多版本
May 06 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 身份验证方面的函数
2009/10/11 PHP
改写ThinkPHP的U方法使其路由下分页正常
2014/07/02 PHP
CI框架常用经典操作类总结(路由,伪静态,分页,session,验证码等)
2016/11/21 PHP
父窗口获取弹出子窗口文本框的值
2006/06/27 Javascript
js 去掉空格实例 Trim() LTrim() RTrim()
2014/01/07 Javascript
Javascript实现带关闭按钮的网页漂浮广告代码
2014/01/12 Javascript
jquery.form.js用法之清空form的方法
2014/03/07 Javascript
jQuery插件实现静态HTML验证码校验
2015/11/06 Javascript
Backbone.js框架中简单的View视图编写学习笔记
2016/02/14 Javascript
Easyui 之 Treegrid 笔记
2016/04/29 Javascript
jQuery实现自动输入email、时间和域名的方法
2016/08/24 Javascript
ionic选择多张图片上传的示例代码
2017/10/10 Javascript
Vue中Axios从远程/后台读取数据
2019/01/21 Javascript
elementUi vue el-radio 监听选中变化的实例代码
2019/06/28 Javascript
python下载文件时显示下载进度的方法
2015/04/02 Python
Python中title()方法的使用简介
2015/05/20 Python
浅谈Python2.6和Python3.0中八进制数字表示的区别
2017/04/28 Python
Python yield 使用方法浅析
2017/05/20 Python
python opencv实现任意角度的透视变换实例代码
2018/01/12 Python
使用anaconda的pip安装第三方python包的操作步骤
2018/06/11 Python
Django xadmin开启搜索功能的实现
2019/11/15 Python
在python中利用dict转json按输入顺序输出内容方式
2020/02/27 Python
Python下使用Trackbar实现绘图板
2020/10/27 Python
Django数据库迁移常见使用方法
2020/11/12 Python
Python修改DBF文件指定列
2020/12/19 Python
手把手教你用Django执行原生SQL的方法
2021/02/18 Python
Linux管理员面试经常问道的相关命令
2013/04/29 面试题
幼儿园元旦亲子活动方案
2014/02/17 职场文书
护士长竞聘书
2014/03/31 职场文书
餐饮服务食品安全责任书
2014/07/25 职场文书
基层党员对照检查材料
2014/08/25 职场文书
报考公务员诚信承诺书
2014/08/29 职场文书
2015初一年级组工作总结
2015/07/24 职场文书
Django程序的优化技巧
2021/04/29 Python
Python实现为PDF去除水印的示例代码
2022/04/03 Python
Go gorilla/sessions库安装使用
2022/08/14 Golang