美文网首页Web 前端开发 让前端飞
使用typescript改造koa开发框架

使用typescript改造koa开发框架

作者: jeffzhong | 来源:发表于2020-02-04 13:59 被阅读0次

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

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

    1. 项目开发环境 和 typescript 编译环境的搭建;
    2. nodekoa、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,用过 JavaC# 的小伙伴,对反射的原理一定不陌生。

    定义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

    相关文章

      网友评论

        本文标题:使用typescript改造koa开发框架

        本文链接:https://www.haomeiwen.com/subject/owyrxhtx.html