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

一、 装置依赖

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

或者

yarn add typescriptyarn add ts-nodeyarn add nodemon

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

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

或者

yarn add @types/koayarn 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。

作者:宜信技术学院 刘琳