使用 TypeScript 改造构建工具及测试用例

最近的一段时间一直在搞TypeScript,一个巨硬出品、赋予JavaScript语言静态类型和编译的语言。
第一个完全使用TypeScript重构的纯Node.js项目已经上线并稳定运行了。
第二个前后端的项目目前也在重构中,关于前端基于webpackTypeScript套路之前也有提到过:TypeScript在react项目中的实践。

但是这些做完以后也总感觉缺了点儿什么 (没有尽兴)

old-project-screenshot.png
是的,依然有五分之一的JavaScript代码存在于项目中,作为一个TypeScript的示例项目,表现的很不纯粹。
所以有没有可能将这些JavaScript代码也换成TypeScript呢?
答案肯定是有的,首先需要分析这些代码都是什么:

  • Webpack打包时的配置文件
  • 一些简单的测试用例(使用的mocha和chai)

知道了是哪些地方还在使用JavaScript,这件事儿就变得很好解决了,从构建工具(Webpack)开始,逐个击破,将这些全部替换为TypeScript

Webpack 的 TypeScript 实现版本

在这8102年,很幸福,Webpack官方已经支持了TypeScript编写配置文件,文档地址。
除了TypeScript以外还支持JSXCoffeeScript的解释器,在这就忽略它们的存在了

依赖的安装

首先是要安装TypeScript相关的一套各种依赖,包括解释器及该语言的核心模块:

npm install -D typescript ts-node

typescript为这个语言的核心模块,ts-node用于直接执行.ts文件,而不需要像tsc那样会编译输出.js文件。

ts-node helloworld.ts

因为要在TypeScript环境下使用Webpack相关的东东,所以要安装对应的types
也就是Webpack所对应的那些*.d.ts,用来告诉TypeScript这是个什么对象,提供什么方法。

npm i -D @types/webpack

一些常用的pLugin都会有对应的@types文件,可以简单的通过npm info @types/XXX来检查是否存在

如果是一些小众的plugin,则可能需要自己创建对应的d.ts文件,例如我们一直在用的qiniu-webpack-plugin,这个就没有对应的@types包的,所以就自己创建一个空文件来告诉TypeScript这是个啥:

declare module 'qiniu-webpack-plugin' // 就一个简单的定义即可// 如果还有其他的包,直接放到同一个文件就行了
// 文件名也没有要求,保证是 d.ts 结尾即可

放置的位置没有什么限制,随便丢,一般建议放到types文件夹下

最后就是.ts文件在执行时的一些配置文件设置。
用来执行Webpack.ts文件对tsconfig.json有一些小小的要求。
compilerOptions下的target选项必须是es5,这个代表着输出的格式。
以及module要求选择commonjs

{"compilerOptions": {"module": "commonjs","target": "es5","esModuleInterop": true}
}

但一般来讲,执行Webpack的同级目录都已经存在了tsconfig.json,用于实际的前端代码编译,很可能两个配置文件的参数并不一样。
如果因为要使用Webpack去修改真正的代码配置参数肯定是不可取的。
所以我们就会用到这么一个包,用来改变ts-node执行时所依赖的配置文件:tsconfig-paths

Readme中发现了这样的说法:If process.env.TS_NODE_PROJECT is set it will be used to resolved tsconfig.json
Webpack的文档中同样也提到了这句,所以这是一个兼容的方法,在命令运行时指定一个路径,在不影响原有配置的情况下创建一个供Webpack打包时使用的配置。

  1. 将上述的配置文件改名为其它名称,Webpack文档示例中为tsconfig-for-webpack-config.json,这里就直接沿用了
  2. 然后添加npm script如下
{"scripts": {"build": "TS_NODE_PROJECT=tsconfig-for-webpack-config.json webpack --config configs.ts"}
}

文件的编写

关于配置文件,从JavaScript切换到TypeScript实际上并不会有太大的改动,因为Webpack的配置文件大多都是写死的文本/常量。
很多类型都是自动生成的,基本可以不用手动指定,一个简单的示例:

import { Configuration } from 'webpack'const config: Configuration = {mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
}export default config

Configuration是一个Webpack定义的接口(interface),用来规范一个对象的行为。
VS Code下按住Command + 单击可以直接跳转到具体的webpack.d.ts定义文件那里,可以看到详细的定义信息。
webpack-declare.png
各种常用的规则都写在了这里,使用TypeScript的一个好处就是,当要实现一个功能时你不再需要去网站上查询应该要配置什么,可以直接翻看d.ts的定义。
如果注释写得足够完善,基本可以当成文档来用了,而且在VS Code编辑器中还有动态的提示,以及一些错误的纠正,比如上述的NODE_ENV的获取,如果直接写process.env.NODE_ENV || 'development'是会抛出一个异常的,因为从d.ts中可以看到,关于mode只有三个有效值productiondevelopemntnone,而process.env.NODE_ENV显然只是一个字符串类型的变量。
warning-tips.png
所以我们需要使用三元运算符保证传入的参数一定是我们想要的。

以及在编写的过程中,如果有一些自定义的plugin之类的,可能在使用的过程中会抛异常提示说某个对象不是有效的Plugin对象,一个很简单的方法,在对应的plugin后边添加一个as webpack.Plugin即可。

在这里TypeScript所做的只是静态的检查,并不会对实际的代码执行造成任何影响,就算类型因为强行as而改变,也只是编译期的修改,在实际执行的JavaScript代码中还是弱类型的

在完成了上述的操作后,再执行npm run XXX就可以直接运行TypeScript版本的Webpack配置咯。

探索期间的一件趣事

因为我的项目根目录已经安装了ts-node,而前端项目是作为其中的一个文件夹存在的,所以就没有再次进行安装。
这就带来了一个令人吐血的问题。

首先全部流程走完以后,我直接在命令行中输入TS_NODE_PROJECT=XXX.json NODE_ENV=dev webpack --config ./webpack/dev.ts
完美运行,然后将这行命令放到了npm scripts中:

{"scripts": {"start": "TS_NODE_PROJECT=XXX.json NODE_ENV=dev webpack --config ./webpack/dev.ts"}
}

再次运行npm start,发现竟然出错了-.-,提示我说import语法不能被识别,这个很显然就是没有应用我们在ts_NODE_PROJECT中指定的config文件。
刚开始并不知道问题出在哪,因为这个在命令行中直接执行并没有任何问题。
期间曾经怀疑是否是环境变量没有被正确设置,还使用了cross-env这个插件,甚至将命令写到了一个sh文件中进行执行。
然而问题依然存在,后来在一个群中跟小伙伴们聊起了这个问题,有人提出,你是不是全局安装了ts-node
检查以后发现,果然是的,在命令行执行时使用的是全局的ts-node,但是在npm scripts中使用的是本地的ts-node
在命令行环境执行时还以为是会自动寻找父文件夹node_modules下边的依赖,其实是使用的全局包。
乖乖的在client-src文件夹下也安装了ts-node就解决了这个问题。
全局依赖害人。。

测试用例的改造

前边的Webpack改为TypeScript大多数原因是因为强迫症所致。
但是测试用例的TypeScript改造则是一个能极大提高效率的操作。

为什么要在测试用例中使用 TypeScript

测试用例使用chai来编写,(之前的Postman也是用的chai的语法)
chai提供了一系列的语义化链式调用来实现断言。
在之前的分享中也提到过,这么多的命令你并不需要完全记住,只知道一个expect(XXX).to.equal(true)就够了。

但是这样的通篇to.equal(true)是巨丑无比的,而如果使用那些语义化的链式调用,在不熟练的情况下很容易就会得到:

Error: XXX.XXX is not a function

因为这确实有一个门槛问题,必须要写很多才能记住调用规则,各种notincludes的操作。
但是接入了TypeScript以后,这些问题都迎刃而解了。
也是前边提到的,所有的TypeScript模块都有其对应的.d.ts文件,用来告诉我们这个模块是做什么的,提供了什么可以使用。
也就是说在测试用例编写时,我们可以通过动态提示来快速的书写断言,而不需要结合着文档去进行“翻译”。

chai-tips.png
chai-warning.png

使用方式

如果是之前有写过mochachai的童鞋,基本上修改文件后缀+安装对应的@types即可。
可以直接跳到这里来:开始编写测试脚本
但是如果对测试用例感兴趣,但是并没有使用过的童鞋,可以看下边的一个基本步骤。

安装依赖

  1. TypeScript相关的安装,npm i -D typescript ts-node
  2. Mochachai相关的安装,npm i -D mocha chai @types/mocha @types/chai
  3. 如果需要涉及到一些API的请求,可以额外安装chai-httpnpm i -D chai-http @types/chai-http

环境的依赖就已经完成了,如果额外的使用一些其他的插件,记得安装对应的@types文件即可。
如果有使用ESLint之类的插件,可能会提示modules必须存在于dependencies而非devDependencies
这是ESLint的import/no-extraneous-dependencies规则导致的,针对这个,我们目前的方案是添加一些例外:

import/no-extraneous-dependencies:- 2- devDependencies:- "**/*.test.js"- "**/*.spec.js"- "**/webpack*"- "**/webpack/*"

针对这些目录下的文件/文件夹不进行校验。是的,webpack的使用也会遇到这个问题

开始编写测试脚本

如果是对原有的测试脚本进行修改,无外乎修改后缀、添加一些必要的类型声明,不会对逻辑造成任何修改。

一个简单的示例

// number-comma.ts
export default (num: number | string) => String(num).replace(/\B(?=(\d{3})+$)/g, ',')// number-comma.spec.ts
import chai from 'chai'
import numberComma from './number-comma'const { expect } = chai// 测试项
describe('number-comma', () => {// 子项目1it('`1234567` should transform to `1,234,567`', done => {expect(numberComma(1234567)).to.equal('1,234,567')done()})// 子项目2it('`123` should never transform', done => {const num = 123expect(numberComma(num)).to.equal(String(num))done()})
})

如果全局没有安装mocha,记得将命令写到npm script中,或者通过下述方式执行

./node_modules/mocha/bin/mocha -r ts-node/register test/number-comma.spec.ts# 如果直接这样写,会抛出异常提示 mocha 不是命令
mocha -r ts-node/register test/number-comma.spec.ts

mocha有一点儿比较好的是提供了-r命令来让你手动指定执行测试用例脚本所使用的解释器,这里直接设置为ts-node的路径ts-node/register,然后就可以在后边直接跟一个文件名(或者是一些通配符)。

目前我们在项目中批量执行测试用例的命令如下:

{"scripts": {"test": "mocha -r ts-node/register test/**/*.spec.ts"}
}

npm test可以直接调用,而不需要添加run命令符,类似的还有startbuild等等

一键执行以后就可以得到我们想要的结果了,再也不用担心一些代码的改动会影响到其他模块的逻辑了 (前提是认真写测试用例)

mocha-results.png

小结

做完上边两步的操作以后,我们的项目就实现了100%的TypeScript化,在任何地方享受静态编译语法所带来的好处。
附上更新后的代码含量截图:

new-project-screenshot.png

最近针对TypeScript做了很多事情,从Node.jsReact以及这次的WebpackMocha+Chai
TypeScript因为其存在一个编译的过程,极大的降低了代码出bug的可能性,提高程序的稳定度。
全面切换到TypeScript更是能够降低在两种语法之间互相切换时所带来的不必要的消耗,祝大家搬砖愉快。

之前关于 TypeScript 的笔记

  • TypeScript在node项目中的实践
  • TypeScript在react项目中的实践

一个完整的 TypeScript 示例

typescript-example

欢迎各位来讨论关于TypeScript使用上的一些问题,针对稳重的感觉不足之处也欢迎指出。

参考资料

  • ts-node
  • configuration-languages | webpack
  • mochajs
  • chaijs

转载于:https://www.cnblogs.com/jiasm/p/9577715.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/250768.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript 验证表单不为空和获取select下拉列表的值和文本

1.验证表单不为空 var hasform { "Name": "名字", "Id_card": "身份证", "PaySalary": "月工资", "CardCode": "账号", "Fk_Subjectf_Code": &quo…

javascript --- 变量污染全局作用域问题解决方案

日常写法 // 假设你写了几个关于某个某块的函数 function foo1 () {...} function foo2 () {...} function foo3 () {...}出现问题:假设你的团队中也有一个人定义了foo1函数,那么你写的将会覆盖以前的函数,或者会被覆盖掉.若前面使用let声明了foo1变量.将会报错. 解决污染 你…

solr7.4 安装与使用

1.solr7环境要求 solr7需要java8环境,且需要在环境变量中添加 JAVA_HOME变量。 2.solr 安装 下载地址 https://lucene.apache.org/solr/mirrors-solr-latest-redir.html 我下载为7.4版本 在solr5以前solr的启动都有tomcat作为容器,但是从solr5以后solr内…

初入HTML5

在最开始接触HTML5的时候,你会遇到的大多是一些常见常用的属性以及属性值。它们分类广、品种杂且使用率高。到css各种样式的时候,你会接触到更多的东西,各种属性、选择器、盒子模型都是重点。那么,现在我们就看一下它们到底是什么…

javascript --- 让函数的实例可以链式调用

关键: 在每个函数的末尾加上 return thisthis:在javascript中表示当前的对象 栗如: 有以下函数 var fooObj {foo1: function() {console.log(1);},foo2: function() {console.log(2);},foo3: function() {console.log(3);} }// 你想通过 fooObj.foo1().foo2().foo3() // …

ReactiveCocoa基础

本文转载自最快让你上手ReactiveCocoa之基础篇,在此基础上稍作修改,欢迎交流。 有关对 ReactiveCocoa 的看法可以看一下唐巧的这篇ReactiveCocoa 讨论会 ReactiveCocoa思维导图ReactiveCocoa简介 ReactiveCocoa(简称为RAC),是由Gi…

javascript --- 创建一个二维数组

想创建一个 n*n 的矩阵,并全部赋予初始值false 你可能会想到下面 let arr []; for(let i 0 ;i< n;i) {arr[i] [];for( let j 0; j< n; j){arr[i][j] false;} }稍微封装一下: function Cmatrix(n, c) {let arr [];for (let i 0; i < n; i) {arr[i] [];for (le…

配置OpenCV产生flann\logger.h(66): error C4996: ‘fopen': This function or variable may be unsafe问题

转载自&#xff1a;http://guoming.me/%E9%85%8D%E7%BD%AEopencv%E4%BA%A7%E7%94%9Fflannlogger-h66-error-c4996-fopen-this-function-or-variable-may-be-unsafe%E9%97%AE%E9%A2%98 今天使用vs2012配置OpenCV编译出现问题: 1>—— 已启动生成: 项目: Win32ForOpenCV245, 配…

android listview和simpleadapter 给itme 中的控件添加事件

simpleAdapter.setViewBinder(new SimpleAdapter.ViewBinder() { Override public boolean setViewValue(View view, Object data, String textRepresentation) {   Log.d("进入setview","进入setview");if(view instanceof Button &&am…

0 uC/OS 系统精讲索引

uC/OS-II与uC/OS-III放在一起讲&#xff0c;每个例程同时提供两个版本的源代码。 本系列教程主要涉及如下内容&#xff1a; 【原理部分】 1-操作系统简介&#xff1a;基本概念 2-目录结构与测试环境搭建&#xff1a;uC/OS-III emWin VS2015 2.1 官方文件目录结构 【*】uC/Lib …

OPENCV-1 学习笔记

灰度图&#xff1a;2维矩阵 彩色图&#xff1a;3维矩阵 ps&#xff1a;目前大部分设备都是用无符号 8 位整数&#xff08;类型为 CV_8U&#xff09;表示像素亮度 Mat类定义&#xff1a; class CV_EXPORTS Mat { public://一系列函数.../* flag 参数中包含许多关于矩阵的信息…

javascript --- repeat的用处

描述 思路: 最多重复s.length次使用String.prototype.repeat(n)方法可以将字符串重复n次 核心: while( i < len/2){if( s s.slice(0,i).repeat(len /i) ) {return ture;} }总体代码: var repeatedSubstringPattern function(s) {let i 1;let len s.length;while (i …

redis 零散知识

1、单线程 2、默认 16 个库。0~15 3、select &#xff1a;切换数据库 4、DBsize &#xff1a;查看当前数据库的数量 5、keys * &#xff1a;查看当前库的所有 key 6、keys k? &#xff1a;问号是占位符 7、FlushDB &#xff1a;清除当前库 8、FlushAll &#xff1a;清除所有库…

模型评估——定量分析预测的质量

https://blog.csdn.net/hustqb/article/details/77922031 评分参数定义模型评价规则 公共案例预定义值根据度量函数定义你的评分策略应用你自己的评分对象使用多种度量指标分类度量 从二分类到多分类多标签精确度Cohens kappa混乱矩阵分类报告汉明损失Jaccard 相似性相关系数准…

OPENCV-2 学习笔记

1、图像显示 #include<opencv2/opencv.hpp> using namespace cv; //使用命名空间 void main(){Mat srcImage imread(1.jpg);//载入图像imshow(图像标题,srcImage);//显示图像waitKey(0);//等待按键按下 } 2、图像腐蚀 #include <opencv2/opencv.hpp> #incl…

javascript --- 对象的方式体验链式调用

将功能相近的方法写入同一个对象中,是一个很好的编程习惯,便于后期的维护和前期的开发. foo1 var fooObj {foo1: function() {console.log(foo1);return this;} } fooObj.foo1();此有一个对象: fooObj它有一个方法: foo1()foo1打印了一个字符串’foo1’,然后返回了当前的执行…

oracle 数据库查询多条数据的一列值

select sum(case when hc13 then JE else 0 end), sum(case when hc14 then JE else 0 end), sum(case when hc15 then JE else 0 end), sum(case when hc16 then JE else 0 end) from 表名转载于:https://www.cnblogs.com/lkzp123456/p/8608080.html

OPENCV-3 学习笔记

OPENCV-3 学习笔记 imread()读入图&#xff0c;第一个参数&#xff0c;const string&类型的filename&#xff0c;填我们需要载入的图片路径名&#xff0c; 第二个参数&#xff0c;int类型的flags&#xff0c;为载入标识&#xff0c;它指定一个加载图像的颜色类型。 named…

vue --- vue-router(项目模式的导入)

main.js // main.js // 1.1 导入路由的包 import VueRouter from vue-router// 1.2 安装路由 Vue.use(VuerRouter)// 1.3 导入自己的router.js模块 import router from ./router.js// 1.4 挂载router对象在vm实例上 const vm new Vue({el: #app,router })app.vue 原本的 a …

Innodb存储引擎——非聚集索引

如果给表定义了主键&#xff0c;那么表在磁盘上的存储结构就由整齐排列的结构转变成了树状结构&#xff0c;也就是「平衡树」结构&#xff0c;换句话说&#xff0c;就是整个表就变成了一个索引&#xff0c;这就是所谓的「聚集索引」。 这就是为什么一个表只能有一个主键&#x…