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
…
當使用tsc命令進行編譯時,如果未指定ts文件,編譯器會從當前目錄開始去查找tsconfig.json文件,并根據tsconfig.json的配置進行編譯。
可以通過files屬性來指定需要編譯的文件,如下所示:
{
"files": [
"src/server.ts"
]
}
另外也可以通過使用”include”和”exclude”屬性來指定,采用類似glob文件匹配模式,如下所示:
{
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}
支持的通配符:
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"
]
}
target是編譯目標,可以指定編譯為ECMAScript的哪個版本,默認為”ES3”。ECMAScript的版本有:”ES3” 、”ES5”、 “ES6” 或者 “ES2015”、 “ES2016”、 “ES2017”、”ES2018”、”ES2019”、 “ES2020”、”ESNext”。
module指定編譯為哪種模塊系統,如果target為”ES3”或者”ES5”,默認為”CommonJS”,否則默認為”ES6”??蛇x用的模塊系統有:”None”、 “CommonJS”、 “AMD”,、”System”、 “UMD”、”ES6”或者”ES2015”、”ESNext”。
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。
作者:宜信技術學院 劉琳
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。