JavaScript 是一门动态弱类型语言,对变量的类型很是宽容。JavaScript使用灵活,开发速度快,可是因为类型思惟的缺失,一点小的修改都有可能致使意想不到的错误,使用TypeScript能够很好的解决这种问题。TypeScript是JavaScript的一个超集,扩展了 JavaScript 的语法,增长了静态类型、类、模块、接口和类型注解等功能,能够编译成纯JavaScript。本文将介绍如何在node服务中使用TypeScript。
npm install typescript --save npm install ts-node --save npm install nodemon --save
或者node
yarn add typescript yarn add ts-node yarn add nodemon
另外,还须要安装依赖模块的类型库:git
npm install @types/koa --save npm install @types/koa-router --save …
或者github
yarn add @types/koa yarn add @types/koa-router …
当使用tsc命令进行编译时,若是未指定ts文件,编译器会从当前目录开始去查找tsconfig.json文件,并根据tsconfig.json的配置进行编译。typescript
能够经过files属性来指定须要编译的文件,以下所示:npm
{ "files": [ "src/server.ts" ] }
另外也能够经过使用"include"和"exclude"属性来指定,采用相似glob文件匹配模式,以下所示:json
{ "include": [ "src/**/*" ], "exclude": [ "node_modules", "**/*.spec.ts" ] }
支持的通配符:koa
compilerOptions 属性用于配置编译选项,与tsc命令的选项一致,经常使用的配置以下所示:async
{ "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" ] }
target是编译目标,能够指定编译为ECMAScript的哪一个版本,默认为"ES3"。ECMAScript的版本有:"ES3" 、"ES5"、 "ES6" 或者 "ES2015"、 "ES2016"、 "ES2017"、"ES2018"、"ES2019"、 "ES2020"、"ESNext"。post
module指定编译为哪一种模块系统,若是target为"ES3"或者"ES5",默认为"CommonJS",不然默认为"ES6"。可选用的模块系统有:"None"、 "CommonJS"、 "AMD",、"System"、 "UMD"、"ES6"或者"ES2015"、"ESNext"。ui
moduleResolution指定模块解析策略,模块解析策略有:"Classic"、"Node",若是module为"AMD"、"System"或者"ES6",默认为"Classic",不然默认为"Node"。
在/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
在/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
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);
lib指定编译过程当中须要引入的库。target为"ES5"时,默认引入["DOM","ES5","ScriptHost"];target为"ES6"时,默认引入["DOM","ES6","DOM.Iterable","ScriptHost"]。因为本示例TypeScript是用于服务端的,不须要使用DOM和ScriptHost,因此lib设为["ES6"]。
输出目录,编译生成的js文件所输出的根目录,默认输出到ts文件所在的目录。
是否生成source map文件,经过使用source map 能够在错误信息中能够显示源码位置。
要想根据source map 显示错误信息源码位置,还须要在入口文件引入source-map-support 模块,以下:
import 'source-map-support/register';
入口文件为src/server.ts,package.json中的scripts配置以下:
{ "scripts": { "dev": "nodemon --watch src -e ts,tsx --exec ts-node src/server.ts", "build": "tsc", "start": "node dist/server.js" }, … }
TypeScript 会自动从 node_modules/@types 目录获取模块的类型定义,引用的模块都须要安装对应类型库,如:
npm install @types/koa --save
安装后,会在node_modules/@types 目录下找到koa 文件夹,该文件夹下有koa相关的类型定义文件。当引用koa模块时会自动引入node_modules/ 和 node_modules/@types下的 koa 包。若是某个模块没有类型库或者对某个模块进行了扩展须要修改类型定义,这时须要引入自定义的类型。
{ "compilerOptions": { … // 类型声明文件所在目录 "typeRoots": ["./node_modules/@types", "./src/types"], }, "include": [ "src/**/*" ], "exclude": [ "node_modules", "**/*.spec.ts" ] }
src/types是存放自定义类型的目录,本示例中src/types目录已被include包含,若是自定义的类型目录未被include包含还须要在include中添加该目录。
import * as Koa from "koa"; declare module "koa" { interface Request { body?: object; rawBody: string; } }
这里给koa的request对象增长body和rawBody两个属性,分别用于存放请求体的json对象和原始字符串。
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文件,以下:
{ "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。
做者:宜信技术学院 刘琳