乐趣区

搭建node服务三使用TypeScript

JavaScript 是一门动静弱类型语言,对变量的类型十分宽容。JavaScript 应用灵便,开发速度快,然而因为类型思维的缺失,一点小的批改都有可能导致意想不到的谬误,应用 TypeScript 能够很好的解决这种问题。TypeScript 是 JavaScript 的一个超集,扩大了 JavaScript 的语法,减少了动态类型、类、模块、接口和类型注解等性能,能够编译成纯 JavaScript。本文将介绍如何在 node 服务中应用 TypeScript。

一、装置依赖

npm install typescript --save
npm install ts-node --save
npm install nodemon --save

或者

yarn add typescript
yarn add ts-node
yarn add nodemon

另外,还须要装置依赖模块的类型库:

npm install @types/koa --save
npm install @types/koa-router --save
…

或者

yarn add @types/koa
yarn add @types/koa-router
…

二、tsconfig.json

当应用 tsc 命令进行编译时,如果未指定 ts 文件,编译器会从当前目录开始去查找 tsconfig.json 文件,并依据 tsconfig.json 的配置进行编译。

1. 指定文件

能够通过 files 属性来指定须要编译的文件,如下所示:

{
  "files": ["src/server.ts"]
}

另外也能够通过应用 ”include” 和 ”exclude” 属性来指定,采纳相似 glob 文件匹配模式,如下所示:

{
  "include": ["src/**/*"],
  "exclude": [
   "node_modules",
   "**/*.spec.ts"
  ]
}

反对的通配符:

    • 匹配 0 或多个字符(不包含目录分隔符)
  1. ? 匹配一个任意字符(不包含目录分隔符)
  2. **/ 递归匹配任意子目录

2. 罕用配置

compilerOptions 属性用于配置编译选项,与 tsc 命令的选项统一,罕用的配置如下所示:

{
  "compilerOptions": {
    // 指定编译为 ECMAScript 的哪个版本。默认为 "ES3"
    "target": "ES6",
    // 编译为哪种模块零碎。如果 target 为 "ES3" 或者 "ES5",默认为 "CommonJS",否则默认为 "ES6"
    "module": "CommonJS",
    // 模块解析策略,"Classic" 或者 "Node"。如果 module 为 "AMD"、"System" 或者 "ES6",默认为 "Classic",否则默认为 "Node"
    "moduleResolution": "Node",
    // 是否反对应用 import cjs from 'cjs' 的形式引入 commonjs 包
    "esModuleInterop": true,
    // 编译过程中须要引入的库。target 为 "ES5" 时,默认引入 ["DOM","ES5","ScriptHost"];target 为 "ES6" 时,默认引入 ["DOM","ES6","DOM.Iterable","ScriptHost"]
    "lib": ["ES6"],
    // 编译生成的 js 文件所输入的根目录, 默认输入到 ts 文件所在的目录
    "outDir": "dist",
    // 生成相应的.map 文件
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": [
   "node_modules",
   "**/*.spec.ts"
  ]
}

1) target

target 是编译指标,能够指定编译为 ECMAScript 的哪个版本,默认为 ”ES3″。ECMAScript 的版本有:”ES3″、”ES5″、“ES6” 或者 “ES2015″、“ES2016″、“ES2017″、”ES2018″、”ES2019″、“ES2020″、”ESNext”。

2) module

module 指定编译为哪种模块零碎,如果 target 为 ”ES3″ 或者 ”ES5″,默认为 ”CommonJS”,否则默认为 ”ES6″。可选用的模块零碎有:”None”、“CommonJS”、“AMD”,、”System”、“UMD”、”ES6″ 或者 ”ES2015″、”ESNext”。

3) moduleResolution

moduleResolution 指定模块解析策略,模块解析策略有:”Classic”、”Node”,如果 module 为 ”AMD”、”System” 或者 ”ES6″,默认为 ”Classic”,否则默认为 ”Node”。

示例 1:

在 /root/src/moduleA.ts 中以 import {b} from “./moduleB” 形式绝对援用一个模块。
Classic 解析策略,查找过程:

/root/src/moduleB.ts
/root/src/moduleB.d.ts

Node 解析策略,查找过程:

/root/src/moduleB.ts
/root/src/moduleB.tsx
/root/src/moduleB.d.ts
/root/src/moduleB/package.json (如果指定了 "types" 属性)
/root/src/moduleB/index.ts
/root/src/moduleB/index.tsx
/root/src/moduleB/index.d.ts

示例 2:

在 /root/src/moduleA.ts 中以 import {b} from “moduleB” 形式非绝对援用一个模块。
Classic 解析策略,查找过程:

/root/src/moduleB.ts
/root/src/moduleB.d.ts
/root/moduleB.ts
/root/moduleB.d.ts
/moduleB.ts
/moduleB.d.ts

Node 解析策略,查找过程:

/root/src/node_modules/moduleB.ts
/root/src/node_modules/moduleB.tsx
/root/src/node_modules/moduleB.d.ts
/root/src/node_modules/moduleB/package.json (如果指定了 "types" 属性)
/root/src/node_modules/moduleB/index.ts
/root/src/node_modules/moduleB/index.tsx
/root/src/node_modules/moduleB/index.d.ts

/root/node_modules/moduleB.ts
/root/node_modules/moduleB.tsx
/root/node_modules/moduleB.d.ts
/root/node_modules/moduleB/package.json (如果指定了 "types" 属性)
/root/node_modules/moduleB/index.ts
/root/node_modules/moduleB/index.tsx
/root/node_modules/moduleB/index.d.ts

/node_modules/moduleB.ts
/node_modules/moduleB.tsx
/node_modules/moduleB.d.ts
/node_modules/moduleB/package.json (如果指定了 "types" 属性)
/node_modules/moduleB/index.ts
/node_modules/moduleB/index.tsx
/node_modules/moduleB/index.d.ts

4) esModuleInterop

esModuleInterop 为 true 时,示意反对应用 import d from ‘cjs’ 的形式引入 commonjs 包。当 commonjs 模块转化为 esm 时,会减少 __importStar 和 __importDefault 办法来解决转化问题。

示例:

cjs 为 commonjs 模块,代码如下:

module.exports = {name: 'cjs'};

另外一个模块以 esm 形式援用了 cjs 模块,代码如下:

import cjsDefault from 'cjs';
import * as cjsStar from 'cjs';

console.log('cjsDefault =', cjsDefault);
console.log('cjsStar =', cjsStar);

输入后果为:

cjsDefault = {name: 'cjs'}
cjsStar = {name: 'cjs', default: { name: 'cjs'} }

编译后生成的代码如下:

var __importDefault = (this && this.__importDefault) || function (mod) {return (mod && mod.__esModule) ? mod : {"default": mod};
};
var __importStar = (this && this.__importStar) || function (mod) {if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
    result["default"] = mod;
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true});

const cjs_1 = __importDefault(require("cjs"));
const cjsStar = __importStar(require("cjs"));

console.log('cjsDefault =', cjs_1.default);
console.log('cjsStar =', cjsStar);

5) lib

lib 指定编译过程中须要引入的库。target 为 ”ES5″ 时,默认引入 [“DOM”,”ES5″,”ScriptHost”];target 为 ”ES6″ 时,默认引入 [“DOM”,”ES6″,”DOM.Iterable”,”ScriptHost”]。因为本示例 TypeScript 是用于服务端的,不须要应用 DOM 和 ScriptHost,所以 lib 设为 [“ES6”]。

6) outDir

输入目录,编译生成的 js 文件所输入的根目录, 默认输入到 ts 文件所在的目录。

7) sourceMap

是否生成 source map 文件,通过应用 source map 能够在错误信息中能够显示源码地位。
要想依据 source map 显示错误信息源码地位,还须要在入口文件引入 source-map-support 模块,如下:

import 'source-map-support/register';

三、脚本命令

入口文件为 src/server.ts,package.json 中的 scripts 配置如下:

  • package.json
{
  "scripts": {
    "dev": "nodemon --watch src -e ts,tsx --exec ts-node src/server.ts",
    "build": "tsc",
    "start": "node dist/server.js"
  },
  …
}
  1. 执行 npm run dev 命令能够启动开发环境,当 src 下的文件被批改后会主动重新启动服务。
  2. 执行 npm run build 命令会进行编译,因为 tsconfig.json 中 outDir 指定输入目录为 dist,编译后的 js 文件将出输入到 dist 目录。
  3. 执行 npm run start 命令能够启动利用,启动前须要执行 npm run build 进行编译。

四、自定义类型

TypeScript 会主动从 node_modules/@types 目录获取模块的类型定义,援用的模块都须要装置对应类型库,如:

npm install @types/koa --save

装置后,会在 node_modules/@types 目录下找到 koa 文件夹,该文件夹下有 koa 相干的类型定义文件。当援用 koa 模块时会主动引入 node_modules/ 和 node_modules/@types 下的 koa 包。如果某个模块没有类型库或者对某个模块进行了扩大须要批改类型定义,这时须要引入自定义的类型。

示例:给 koa 减少 bodyparser 中间件

1. 设置 typeRoots

  • tsconfig.json
{
  "compilerOptions": {
…
   // 类型申明文件所在目录
   "typeRoots": ["./node_modules/@types", "./src/types"],
},
"include": ["src/**/*"],
  "exclude": [
   "node_modules",
   "**/*.spec.ts"
 ]
}

src/types 是寄存自定义类型的目录,本示例中 src/types 目录已被 include 蕴含,如果自定义的类型目录未被 include 蕴含还须要在 include 中增加该目录。

2. 编写类型定义文件

  • src/types/koa/index.d.ts
import * as Koa from "koa";

declare module "koa" {
    interface Request {
        body?: object;
        rawBody: string;
    }
}

这里给 koa 的 request 对象减少 body 和 rawBody 两个属性,别离用于寄存申请体的 json 对象和原始字符串。

3. 编写 jsonBodyParser 插件

  • src/middleware/jsonBodyParser.ts
import Koa from "koa";

function getRawBody(ctx: Koa.Context): Promise<string> {return new Promise((resolve, reject) => {
      try {
        let postData: string = '';
        ctx.req.addListener('data', (data) => {postData += data;});
        ctx.req.on('end', () => {resolve(postData);
        });
      } catch (e) {console.error('获取 body 内容失败', e);
        reject(e);
      }
  })
}

export default function jsonBodyParser (): Koa.Middleware {return async(ctx: Koa.Context, next: Koa.Next) => {const rawBody: string = await getRawBody(ctx);
      const request: Koa.Request = ctx.request;
      request.rawBody = rawBody;
      if (rawBody) {
        try {request.body = JSON.parse(rawBody);
        } catch (e) {request.body = {};
        }
      }
      await next();};
}

jsonBodyParser() 会返回一个 koa 中间件,这个中间件将获取申请体的内容,将原始内容字符串赋值到 ctx.request.rawBody,将申请体内容 json 对象赋值到 ctx.request.body。因为 src/types/koa/index.d.ts 自定义类型曾经扩大了 Koa.Request 的这两个属性,执行 npm run build 命令,应用 tsc 进行编译,能够编译胜利。然而当执行 npm run dev 时,会提醒编译谬误,那是因为 ts-node 默认不会依据配置中的 files、include 和 exclude 加载所有 ts 文件,而是从入口文件开始依据援用和依赖加载文件。最简略的解决办法就是在 ts-node 命令后减少 –files 参数,示意按配置的 files、include 和 exclude 加载 ts 文件,如下:

  • package.json
{
  "scripts": {"dev": "nodemon --watch src -e ts,tsx --exec ts-node --files src/server.ts",}
}

五、阐明

本文介绍了如何在 node 服务中应用 TypeScript,具体的 TypeScript 语法规定网上有很多相干的材料,这里就不再介绍了。本文相干的代码已提交到 GitHub 以供参考,
我的项目地址:https://github.com/liulinsp/node-server-typescript-demo。

作者:宜信技术学院 刘琳

退出移动版