前言
最近把之前自用的一个小型 Node.JS 框架 (koa2+mongo) 升级为了 ts, 在此记录一下大致步骤.
安装 typescript
直接使用 NPM 安装
NPM i -S -D typescript
建议不要只安装到全局, 避免不同机器上的 typescript 版本不一致.
安装完之后, 我们新建一个 tsconfig.JSON(或者 tsc init), 这是我的内容:
- {
- "compilerOptions": {
- // "incremental": true, /* 增量编译 提高编译速度 */
- "target": "ES2017", /* 编译目标 ES 版本 */
- "module": "commonjs", /* 编译目标模块系统 */
- // "lib": [], /* 编译过程中需要引入的库文件列表 */
- "declaration": true, /* 编译时创建声明文件 */
- "outDir": "dist", /* ts 编译输出目录 */
- "baseUrl": "src",
- "paths": {
- "@/*": ["*"],
- },
- // "importHelpers": true, /* 从 tslib 导入辅助工具函数(如__importDefault)*/
- "strict": false, /* 严格模式开关 等价于 noImplicitAny,strictNullChecks,strictFunctionTypes,strictBindCallApply 等设置 true */
- "noImplicitAny": false,
- "noUnusedLocals": false, /* 未使用局部变量报错 */
- "noUnusedParameters": false, /* 未使用参数报错 */
- "noImplicitReturns": true, /* 有代码路径没有返回值时报错 */
- "noFallthroughCasesInSwitch": true, /* 不允许 switch 的 case 语句贯穿 */
- "moduleResolution": "node", /* 模块解析策略 */
- "typeRoots": [ /* 要包含的类型声明文件路径列表 */
- "./typings",
- "./node_modules/@types"
- ],
- "allowSyntheticDefaultImports": true, /* 允许从没有设置默认导出的模块中默认导入, 仅用于提示, 不影响编译结果 */
- "esModuleInterop": true /* 允许编译生成文件时, 在代码中注入工具类 (__importDefault,__importStar) 对 ESM 与 commonjs 混用情况做兼容处理 */
- },
- "include": [ /* 需要编译的文件 */
- "src/**/*.ts",
- "typings/**/*.ts"
- ],
- "exclude": [ /* 编译需要排除的文件 */
- "node_modules/**"
- ],
- }
集成 eslint 代码规范
其实 ts 最早都是使用 tslint, 但 2019 开始 ts 官方决定投奔 eslint, 嗯, 所以我们肯定也用 eslint 了.
1. 安装依赖
首先安装 eslint 以及 ts 的 parser,plugin:
NPM i eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
说白了, 就是 eslint 需要配置 ts 的 parser, 以及 ts 的规则, 这是我的. eslintrc.JS 文件内容:
- module.exports = {
- 'parser':'@typescript-eslint/parser',
- 'extends': ['standard', 'plugin:@typescript-eslint/recommended'],
- 'env': {'node': true},
- 'rules':{
- '@typescript-eslint/ban-ts-ignore': 0
- }
- }
extends 里面很明显多了一个 standard, 这是因为 @typescript-eslint/recommended 的规则并不完善, 所以我们还需要使用 eslint 的 standard 规则, 安装以下依赖:
NPM i eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-standard -D
除了 eslint-config-standard, 后面几个都是 standard 的依赖
rules 是用来覆盖规则的, 我这里加了一个 ban-ts-ignore, 因为我有的地方要写 ts-ignore, 不加他就报错了
都配置好后, 我们再来 package.JSON 的 scripts 中追加两行命令:
- "lint": "eslint --ext .js --ext .ts src/",
- "lint-fix": "eslint --fix --ext .js --ext .ts src/",
lint 用来检测代码格式问题, lint-fix 用来自动修复代码格式
2. 配置 vscode
ts 的标配, 毫无疑问是 vscode.
vscode 中需要安装 eslint 插件(插件搜索安装即可), 安装完成之后 首选项 -> 设置 -> 扩展 -> eslint, 我们直接点击 在 settings.JSON 中编辑 , 打开编辑文件进行编辑, 这是我的配置文件(备注中有追加说明):
- {
- "workbench.colorTheme": "One Dark Pro",
- "editor.codeActionsOnSave": {
- "source.fixAll.eslint": true // 保存文件时, 自动 eslint 修复格式
- },
- "eslint.enable": true, // 开启 eslint
- "eslint.options": {
- "extensions": [ // eslint 检测的文件格式
- ".js",
- ".ts",
- ".tsx",
- ],
- },
- "eslint.validate": [ // eslint 检测的语法
- "javascript",
- "javascriptreact",
- "typescript",
- "typescriptreact",
- ]
- }
这里需要说明的是, eslint 插件 2.0.4 开始, 保存时自动修复文件是通过 editor.codeActionsOnSave 进行配置了, 以前是 "eslint.autoFixOnSave": true .
描述里还说只要在 eslint 中正确配置了 ts, 就不需要在 eslint.validate 中配置了, 不过我都不管了, 还是统统加上了.
源码调整
这部分就略繁琐了, 需要把原代码全部修改成 ts, 让我们一步步来
1. 修改文件后缀
把原来的 JS 文件全部改成 ts, 我这里是直接写了段 node 代码来干这个事情:
- const fs = require('fs')
- const path = require('path')
- function renameFile (dir) {
- fs.access(dir, function (err) {
- if (err) {
- console.log('目录不存在')
- }
- _rename(dir)
- })
- function _rename (dir) {
- fs.readdir(dir, function (err, paths) {
- if (err) {
- console.log(err)
- } else {
- paths.forEach(function (curPath) {
- console.log(` 查找到 ${curPath}`)
- const _src = dir + '/' + curPath
- const _dist = dir + '/' + curPath.replace('.js', '.ts')
- fs.stat(_src, function (err, stat) {
- if (err) {
- console.log(err)
- } else {
- // 判断是文件还是目录
- if (stat.isFile()) {
- if (curPath.endsWith('.js')) {
- fs.rename(_src, _dist, function (err) {
- if (err) {
- console.log(err)
- } else {
- console.log(`${_src} ==> ${_dist}`)
- }
- })
- }
- } else if (stat.isDirectory()) {
- // 当是目录是, 递归复制
- _rename(_src)
- }
- }
- })
- })
- }
- })
- }
- }
- renameFile(path.join(__dirname, 'src'))
跑完之后把 src 目录下面的 JS 文件全部改成 ts 文件
2. 修改代码
主要是原来的 require 全部改成 es 的 import, 这里我是写了一个正则, 搜索替换的:
- const ([^ ]+) = require\(([^)]+)\) // from
- import $1 from $2 // to
你如果对自己代码有自信, 也可以在上一步的 node 脚本中, 读取文件内容, 然后替换掉.
关于 import 这一块就比较有意思了, 我们来看一段对比:
- // 编译前:
- import path from 'path'
- import fsTool from '@/util/fs-tool'
- // 编译后:
- "use strict";
- var __importDefault = (this && this.__importDefault) || function (mod) {
- return (mod && mod.__esModule) ? mod : { "default": mod };
- };
- Object.defineProperty(exports, "__esModule", { value: true });
- const path_1 = __importDefault(require("path"));
- const fs_tool_1 = __importDefault(require("@/util/fs-tool"));
显然, ts 追加了一个 __importDefault 函数, 这就是 ts 的 esModuleInterop 编译选项的作用, 对 es 和 commonjs 的模块做兼容处理:
ts 编译的时候, 给当前模块插入一个属性: __esModule: true
导入的时候先判断该模块是否有__esModule
如果为 true, 判定为 es 模块, 直接导出
如果为 false, 判定为 commonjs 模块, 作为 default 导出:{ default: mod }
参考文章里认为这里会有一个陷阱, 所以需要打开 allowSyntheticDefaultImports 编译选项. 的确, 如果一个模块是用 ts 写的, 或者自己就是追加了__esModule 属性, 但同时 exports 又没有 default, 最终 __importDefault 导出来的会是一个空值.
但我认为自己追加__esModule 属性不太可能, 用 ts 写的没有 export default 但又 import default 时, ts 是会报错的, 所以我认为这种情况可以忽略.
调试
1.Chrome 中调试
在 package.JSON 的 scripts 中追加以下命令:
"dev": "nodemon --watch src -e ts,tsx,js,json --exec node --inspect -r ts-node/register ./src/app.ts",
node 调试的命令是 --inspect 选项, nodemon 用来监测到文件改动时自动重启, ts-node 用来调试 ts 代码
以上, 就能直接在 Chrome 的开发者工具中调试了, 如果有不清楚的可以自己去搜索 node --inspect, 这里就不做贴图说明了
vscode 中调试
.vscode/launch.JSON 填入以下内容:
- {
- "version": "0.2.0",
- "configurations": [
- {
- "type": "node",
- "request": "attach",
- "name": "Node: Nodemon",
- "processId": "${command:PickProcess}",
- "restart": true,
- "protocol": "inspector",
- }
- ]
- }
打开 debug 面板, 选择刚才设置的 Node: Nodemon, 点击调试并运行, 即可开始在 vscode 中调试(需要先 NPM run dev 把项目运行起来):
说明
在查阅过的资料中, 京东社交电商部的这篇文章帮助最大: Node.JS 项目 TypeScript 改造指南 https://juejin.im/post/5de4867f51882573135415dd , 非常感谢!
本文也是在此基础上有部分更新以及补充.
来源: http://www.bubuko.com/infodetail-3377393.html