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。
做者:宜信技術學院 劉琳