将 TypeScript 添加为开发依赖,并初始化项目,初始化完毕之后,就可以写 .ts 文件了。
npm install typescript -Dnpx tsc --init
获得更好的编辑支持
项目文件
通过配置 tsconfig.json 中的 rootDir、files、include 和 exclude 字段,让 TypeScript 知道项目包括哪些核心文件,如果不详细定义的话,TypeScript 会检查当前目录下的所有文件,其中可能包括我们的构建脚本和配置文件(scripts/*, *.config.js)等,这并不是期待的表现。
// ./tsconfig.json{"rootDir": "./src","include": [/*** 如果开启了 allowJS 且源文件中包含 .js 文件,则必需开启 emitDeclarationOnly 或者将 outDir 设置为其它目录* 如果源文件中还包含 .js 文件对应的 .d.ts,还需要将 declarationDir 设置为其它目录* 这样 tsconfig.json 就不会报错,但实际上 outDir 和 declarationDir 我们都是不需要的,* 因为 tsconfig.json 中的选项只用于 ts-loader transpile 和给编辑器看,* .d.ts 和 .js 的生成使用 typescript compile API 自定义*/"src/ts/**/*","src/*.ts"],"exclude": ["node_modules","dist","build","dev","release","config","scripts"]}
出于一些个人需求,上述配置并不是最精简的配置。
注意上述示例中的注释,具体情况如下:
- TypeScript 可以将
.ts文件转译为.js文件,也只有转译为.js之后我们的代码才能够运行,转译的同时会生成.d.ts文件,其中记录的是类型信息,当我们写的代码被其他项目使用的时候,这些类型信息就能够为开发者提供类型支持。转译输出.js文件和.d.ts文件的具体行为是支持详细定义的,可以生成,也可以不生成,也可以自定义输出的路径。默认情况下调用tsc进行转译并不会生成.d.ts类型文件,转译后的.js文件默认会输出到原始.ts文件同级目录。 - TypeScript 可以允许我们同时在项目中书写 TypeScript 和 JavaScript,前者文件后缀是
.ts,后者是.js,我们也可以为 JavaScript 文件手动编写类型文件,比如为filename.js编写类型文件filename.d.ts,获得对相应 JavaScript 代码的类型提示。
当上述两种情形交汇的时候,冲突就会产生(比如 TypeScript 会认为,根据我们上面的配置文件,编译器生成的 filename.d.ts 会覆盖我们手动写的 filename.d.ts 这是不合理的,然后 tsconfig.json 文件就会报错),我们需要调整 TypeScript 对于 .js 文件和 .d.ts 文件的输出位置来规避冲突:
// ./tsconfig.js{"compilerOptions": {"allowJs": true,"checkJs": true,"declaration": true,"declarationMap": true,"emitDeclarationOnly": false,"sourceMap": true,"outDir": "./.temp/transpiled","declarationDir": "./.temp/typings",}}
以上配置可以避免上述问题,同时也指定了这样的行为:
当在命令行调用 npx tsc 的时候,TypeScript 会将转译结果放置在 ./.temp/transpiled 目录下,保持与源文件夹相同的文件结构,类型文件会单独放在 ./.temp/typings 目录下,也保持相同的结构,如果想将转译结果和类型文件放在一起的话,将 declarationDir 配置注释掉即可。
还有,别忘了设置 .gitignore,别不小心把 ./.temp 提交出去。
虽然我们这么配置了,但一般情况下并不推荐使用
npx tsc直接进行转译,这种方式更多地用在想快速预览转译效果的时候。
路径别名
通过配置 tsconfig.json 让编辑器能够识别路径别名,涉及的配置项主要是 compilerOptions.baseUrl 和 compilerOptions.paths。
// ./tsconfig.json{"compilerOptions": {/* Specify how TypeScript looks up a file from a given module specifier. */"moduleResolution": "node",/* Specify the base directory to resolve non-relative module names. */"baseUrl": ".",/* Specify a set of entries that re-map imports to additional lookup locations. */"paths": {"ES": ["./src/es/index.js","./src/es/index.mjs"],"ES/*": ["./src/es/*"],"CJS": ["./src/cjs/index.js","./src/cjs/index.cjs"],"CJS/*": ["./src/cjs/*"],"TS": ["./src/ts/index.ts"],"TS/*": ["./src/ts/*"],"Statics/*": ["./src/statics/*"],"Images/*": ["./src/statics/images/*"],"Styles/*": ["./src/statics/styles/*"]},}}
如果与 webpack 一起使用的话,记得同时为
webpack.config.js设置resolve.extensions和resolve.alias,也有 webpack 插件可以帮忙将指定tsconfig.json中的compilerOptions.paths自动同步到webpack.config.js的相关配置中,比如tsconfig-paths-webpack-plugin,但我觉得这种配置更新频率较低,引入一个插件实在不值得,手动配置灵活度也很高。
代码格式化
:::tips
配置过程中如果 ESLint 表现异常的话,比如 VSCode 插件提示:ESLint can not lint: *****,可以尝试重启 ESLint 服务,重启方式为:敲击 F1 打开命令输入框,输入 ESLint: Restart ESLint Server。
:::
使用 ESLint 为 TypeScript 添加代码格式化功能,配置完成之后,我们为 VSCode 安装的 ESLint 插件能够同时为 TypeScript 和 JavaScript 文件提供格式化,首先安装相关依赖。
npm install -D typescript eslintnpm install -D @typescript-eslint/parser @typescript-eslint/eslint-plugin# optionalnpm install -D eslint-config-standard-with-typescript@latest
依赖安装完成之后,开始配置 .eslintrc.json,主要是 extends、parser、plugins 三项。
// .eslintrc.json{"env": {"browser": true,"es6": true,"node": true},"extends": ["standard","standard-with-typescript","eslint:recommended","plugin:@typescript-eslint/recommended"],"globals": {"Atomics": "readonly","SharedArrayBuffer": "readonly"},"parser": "@typescript-eslint/parser","parserOptions": {"ecmaVersion": 2021,"sourceType": "module","project": "./tsconfig.json"},"plugins": ["@typescript-eslint/eslint-plugin"],"rules": {"max-len": [1,{"code": 140,"ignoreUrls": true,"ignoreTemplateLiterals": true}]}}
如果使用 standard-with-typescript 规则集的话,需要额外安装依赖(小节开头提到的可选依赖),并且在配置的时候通过 parserOptions.project 指定 TypeScript 配置文件,指定配置文件的本质其实是指定配置文件中的 files、include、exclude 等项目文件信息,从而使 Linter 知道哪些文件是要被解析的。于是存在这样一种情况,我们的 tsconfig.json 中只希望指定项目核心的代码文件(需要被转译的代码文件,如 src 目录下的文件),此时如果我们访问项目目录下的其它代码文件,Linter 会报错,提示我们这些文件不在 Lint 名单之内,我们的预期有两种:检查该文件、忽略该文件。
检查该文件的做法是:提供一个单独的 tsconfig.json 文件,比如 tsconfig.eslint.json,它的定义如下:
{// extend your base config so you don't have to redefine your compilerOptions"extends": "./tsconfig.json","include": ["src/**/*.ts",// if you have a mixed JS/TS codebase, don't forget to include your JS files"src/**/*.js","tests/**/*.ts"]}
然后将它指定给 parserOptions.project,如下:
{"parserOptions": {"ecmaVersion": 2021,"sourceType": "module",- "project": "./tsconfig.json"+ "project": "./tsconfig.eslint.json"}}
忽略该文件的做法是:将该文件添加到 .eslintigore 中或者使用 ESLint 的 overrides 选项对个别文件的配置进行覆盖,在此不做详细描述。
这个问题相关的资料如下:
- parserOptions.project - TypeScript ESLint Parser
- “parserOptions.project” has been set for @typescript-eslint/parser - Stack Overflow 相关问题和回答
- vscode-eslint: Parsing error: “parserOptions.project” has been set for @typescript-eslint/parser. - TypeScript ESLint 作者的回复
- Configuration Based on Glob Patterns - ESLint 关于 overrides 选项的介绍
此时,我们的 ESLint 已经能够正确格式化 .ts 文件了,但我们的规则还是比较笼统的,.ts 和 .js 文件在 ESLint 眼中一视同仁,实际上 .ts 的很多规则并不适用于 .js 文件,所以我们选择使用 .eslintrc.json 的 overrides 字段将二者区分开,最终示例代码如下:
// ./eslintrc.json{"env": {"browser": true,"es6": true,"node": true},"globals": {"Atomics": "readonly","SharedArrayBuffer": "readonly"},"overrides": [{"files": ["*.js"],"extends": ["standard"],"plugins": [],"parser": "espree","parserOptions": {"ecmaVersion": 2021,"sourceType": "module"}},{"files": ["*.ts"],"extends": ["standard-with-typescript","eslint:recommended","plugin:@typescript-eslint/recommended"],"parser": "@typescript-eslint/parser","parserOptions": {"ecmaVersion": 2021,"sourceType": "module","project": "./tsconfig.eslint.json"},"plugins": ["@typescript-eslint/eslint-plugin"]}],"rules": {"max-len": [1,{"code": 140,"ignoreUrls": true,"ignoreTemplateLiterals": true}]}}
隔离 TypeScript 和 JavaScript 的 Lint 规则之后,tsconfig.eslint.json 中的 *.js 也可以移除啦!
{
// extend your base config so you don't have to redefine your compilerOptions
"extends": "./tsconfig.json",
"include": [
"src/**/*.ts",
- // if you have a mixed JS/TS codebase, don't forget to include your JS files
- "src/**/*.js",
"tests/**/*.ts"
]
}
本小节参考以下内容:
- TypeScript ESLint - GitHub Repo
Monorepo for all the tooling which enables ESLint to support TypeScript - vscode-eslint - GitHub Repo
获得更好的开发支持
手动转译
目前为止,如果我们在项目根目录执行 npx tsc,我们就可以在 ./.temp/ 目录下看到源代码转译之后的结果,代码和 SourceMap 在 transpiled 文件夹中,DTS 在 typings 文件夹中(我们也可以将他们放置在一起)。
不出意外的话,这些文件是可以直接运行的,我们可以添加一些选项来更加精准地控制转译的行为,这些选项的含义可以查阅 TypeScript TSConfig Reference,在此不做过多解读,示例如下:
// ./tsconfig.json
{
"compilerOptions": {
"incremental": true,
"composite": true,
"tsBuildInfoFile": "./.temp/.tsbuildinfo",
"target": "es6",
"module": "es6",
"rootDir": "./src",
"isolatedModules": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"typeAcquisition": {
"enable": false
}
}
并不是每次编辑完文件之后,都要手动运行一下 npx tsc,TypeScript 提供了 watch 模式,使用 npx tsc --watch 或者 npx tsc -w 开启,这种情况下会开启一个常驻的编译进程,源文件每次发生变更,就会进行重新编译,重新编译的时候并不是全量编译,开启 compilerOptions.incremental 即为增量编译模式,增量编译模式下有一个记录编译信息的缓存文件,可以通过 compilerOptions.tsBuildInfoFile 指定其输出位置。
需要注意的是,如果开启了增量编译模式,且 .tsbuildinfo 文件存在,则下次编译优先考虑增量编译,如果要全量编译,请在编译之前将 .tsbuildinfo 文件移除。
自动转译
如果是临时写个脚本或者体量较小的玩意儿,手动编译也就够了。对于大型的、长期维护的、依赖复杂的项目,手动编译就显得很麻烦,这些项目中一般也会使用其它构建工具,用于处理非代码文件或者对输出的代码进行打包优化等,一方面 TypeScript Compiler 并不很擅长做这些事情,另一方面 .ts 代码转译成 .js 是构建流程的第一步,我们希望它能够与原有的构建流程无缝兼容。
我们需要做的事情很简单,以 webpack 集成为例,我们只要将 ts-loader 添加到 .ts 文件解析的第一个环节并作简单配置即可:
npm install -D ts-loader
// ./config/loaders.config.js
const jsLoader = {
- test: /\.m?js$/,
+ test: /\.[m|c]?[j|t]sx?$/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: 'babel-loader'
},
+ {
+ loader: 'ts-loader',
+ options: {
+ transpileOnly: true
+ }
+ }
]
}
现在,当 webpack 解析 .ts 或者 .tsx 或者 .js 文件的时候,会首先传递给 ts-loader 进行处理,ts-loader 会将 TypeScript 代码按照 tsconfig.json 中的配置转译为 JavaScript 代码,然后交给 babel-loader 处理,babel-loader 之后就是常规的构建流程啦。另外,由于我们配置了 transpileOnly: true,所以 ts-loader 除了转译代码之外,并不会做多余的操作。
如果项目直接引用第三方库的 .ts 文件的话,上述配置会忽略 node_modules 中的 ts 文件,可以将 exclude 修改为 /(bower_components)/,这样我们的 ts-loader 也会解析第三方库的 .ts 文件啦。
至此,我们使用 TypeScript 写代码的目的就初步达成啦,除了你可以把 .js 文件变成 .ts 文件之外,一切都跟之前一样。
转译检查
在我们配置 ts-loader 的过程中,将 transpileOnly 设置为 true,这使得 ts-loader 只专注于转译 .ts 文件,不进行类型检查,从而获得性能提升。但大多数时候,我们可能需要开启类型检查,此时需要用到另一个插件:fork-ts-checker-webpack-plugin。
运行以下命令安装这个插件:
npm install fork-ts-checker-webpack-plugin -D
初始化一个插件实例:
const forkTsCheckerWebpackPlugin = new ForkTsCheckerWebpackPlugin({
typescript: {
syntactic: true, semantic: true, declaration: false, global: false
}
})
把这个实例传给 Webpack 的插件配置中就好啦。
项目引用
但是……如果涉及到多个不同的项目同时开发,又相互依赖呢?好像又多了一些麻烦。
假设一个情境,我在本地同时开发两个项目,A 项目和 B 项目,A 项目依赖 B 项目,我有以下需求:
- A 能够像使用第三方依赖一样使用 B 项目,当我开发 A 的时候,不要关心 B,不需要对 B 进行任何操作
- 当我在开发 A 的过程中修改 B 的时候,我希望 A 能够响应 B 的变更
- 当我在 A 中使用 B 的时候,我希望得到 B 完善的类型提示
第一个需求是很容易达成的,使用 NPM Workspaces 或者 TypeScript References 都可以实现,前者对项目目录结构有要求,后者对开发启动方式或者构建流程有要求。
第二个需求也是可以达成的,将 webpack devServer 的 watchOptions.ignored 关掉即可,这个选项默认会忽视 /node_modules/ 中的变更,关闭之后会有一定的性能损失,建议进行定制化屏蔽(正则匹配),同时,最好将 webpack 的 config.resolve.symlinks 设置为 false,避免 workspaces 模式下模块解析的一些问题。
第三个需求可以通过配置 package.json 模块导出的相关字段解决,我们都知道,当 A 项目引用 B 项目时,模块解析的方式由 B 项目决定,当 A 在引用时指定不同的路径,指向的文件是不同的。当 A 引用到 .ts 文件时,可以获得类型提示,当 A 引用到 .d.ts 文件时,可以获得类型提示,只有这两种方式。
将这三个需求的各种解决方式权衡考虑,我们就能够得到适合自己的解决方案,以下是我的:
对于一个支持 TypeScript 的项目来说,typings 字段是一定要指定一个总体的类型文件的,我倾向于把它放在根目录的 typings 文件夹中,以 B 项目为例,package.json 这样配置:
// ./package.json
{
"typings": "./typings/main.d.ts",
"types": "./typings/main.d.ts",
}
如果这个 DTS 文件存在,当我在 A 中 import * as B from 'packageB' 时,类型提示就来自这个文件。这个文件一直存在嘛?它可以一直存在,但不合适,它并不在开发构建流程中,即我会在开发完 B 之后通过 npm run dist 来生成它,而不是 npm run dev 过程中,这就意味着,如果我在开发 A,引用了 B,然后修改了 B,B 的类型提示文件是不会更新的,它应该更新,但它应该在我开发完 A 的事情,回头提交 B 的更改时再更新,而不是在我关注 A 项目的时候分心去更新。
那么我就只能通过直接引用 .ts 文件来获得类型提示了,解析要定义在哪里呢?是 main 字段嘛?当然不是,main 字段是生产可用模块导出的地方,.ts 不算是生产可用模块,那就只能是 exports (以及 typesVersion)中了。
// ./package.json
{
"exports": {
"./ts": "./src/main.ts",
"./ts/*": "./src/ts/*.ts",
"./ts-js/": "./src/ts/*.js",
},
"typesVersions": {
"*": {
"ts": [
"./src/main.ts"
],
"ts/*": [
"./src/ts/*"
],
"ts-js/*": [
"./src/ts/*"
],
}
}
}
然后我们在 A 项目中引用 B 的方式要切换为 import * as B from 'packageB/ts',我们可以获得最新的类型提示啦,来自 TypeScript 源码而不是类型文件的类型提示。
然后又有朋友说了,如果项目 A 中引用 B 的地方很多,岂不是每个文件都要去加个
/ts无意义后缀,好麻烦啊,我的实践是,永远不直接引用第三方包,建立一个src/libs专门用来管理引入的依赖,相当于是一层防腐层,在代码层面将依赖和源码隔离开来,将来替换的时候也方便,对第三方 API 做包装也方便,香的很。
这样引用有一个弊端,就是被引用的项目会被当做当前项目,代码体积相对较大,会有一定的性能损失。社区中有另外一种方案,既没有 monorepo 那么复杂,又具备 monorepo 的一些优点,他们将 NPM Workspaces 和 TypeScripts Reference 结合起来,也能够解决上述需求,对于不像我一样介意多开一个 tsc 编译终端的朋友们算是一个更好的方案,我把相关的资料放在下面,大家自行查阅。
- develop multi typescript at the same time - Google Search
- Simple monorepos via npm workspaces and TypeScript project references - 2ality – JavaScript and more
- TypeScript and native ESM on Node.js - 2ality – JavaScript and more
- Boost your productivity with TypeScript project references - Paul Cowan - LogRocket
- Using TypeScript Project References with ts-loader and webpack - ts-loader
既可以使用 TypeScript Reference 组织依赖,也可以直接引用依赖的 TypeScript 源码,前者性能更优
获得更好的打包支持
开发的问题是解决了,那最终输出的文件呢?当然是写脚本自定义了,灵活度那么高,总不可能还用终端吧,TypeScript 提供了 Compiler API。
首先确定最终要对外界导出哪些东西,Mobius Template 提倡的构建目标有四种,dev 用于开发,build 用于查看开发构建配置下生成的代码,没有打包速度提升相关的优化,dist 用于生产,release 用于发布,要到处给外界使用的东西,应该在 release 中。
// ./package.json
{
"typings": "./typings/main.d.ts",
"types": "./typings/main.d.ts",
"main": "./release/modules/cjs/main.cjs",
"module": "./release/modules/esm/main.js",
"exports": {
".": {
"require": "./release/modules/cjs/main.cjs",
"import": "./release/modules/esm/main.js",
"node": "./release/modules/esm/main.js",
"default": "./release/modules/esm/main.js"
},
"./es": "./release/modules/es/main.js",
"./es/*": "./release/modules/es/*",
"./es-js/*": "./release/modules/es/*.js",
"./ts": "./src/main.ts",
"./ts/*": "./src/ts/*.ts",
"./ts-js/*": "./src/ts/*.js",
"./esm": "./release/modules/esm/main.js",
"./umd": {
"require": "./release/modules/umd/main.cjs",
"import": "./release/modules/umd/main.js",
"node": "./release/modules/umd/main.js",
"default": "./release/modules/umd/main.js"
},
"./cjs": "./release/modules/cjs/main.cjs",
"./src/*": "./src/*",
"./src-ts/*": "./src/*.ts",
"./src-js/*": "./src/*.js",
"./statics/*": "./statics/*",
"./release/*": "./release/*",
"./release-js/*": "./release/*.js",
"./release-cjs/*": "./release/*.cjs",
"./esm/*": "./release/modules/esm/*",
"./cjs/*": "./release/modules/cjs/*",
"./umd/*": "./release/modules/umd/*",
"./package.json": "./package.json"
},
"typesVersions": {
"*": {
"es": [
"./release/modules/es/main.d.ts"
],
"es/*": [
"./release/modules/es/*"
],
"es-js/*": [
"./release/modules/es/*"
],
"ts": [
"./src/main.ts"
],
"ts/*": [
"./src/ts/*"
],
"ts-js/*": [
"./src/ts/*"
],
"esm": [
"./typings/main.d.ts"
],
"umd": [
"./typings/main.d.ts"
],
"cjs": [
"./typings/main.d.ts"
],
"*": [
"./typings/main.d.ts"
]
}
}
}
然后写脚本啦,注意看上面我们的导出需求,我们在 release 目录下设立 modules 目录用来放置所有导出的模块,按照模块类型区分,包括 cjs、umd、es 三种,当另一个项目引用的时候,取决于引用方式,会拿到不同的文件和类型提示。
- cjs:从
./release/modules/cjs/main.js拿代码,类型提示来自typings字段配置,指向./typings/main.d.ts。 - umd: 从
./release/modules/umd/main.js拿代码,类型提示来自typings字段配置,指向./typings/main.d.ts。 - es: 从
./release/modules/es/main.js拿代码,类型提示来自typesVersion字段配置,指向./release/modules/es/main.d.ts(或者具体到子文件的代码和类型)。 - esm: 从
./release/modules/esm/main.js拿代码,类型提示来自typings字段配置,指向./typings/main.d.ts。
要特别说明的是,这里的 es 并不代表这些代码在浏览器或 Node 环境中可以直接执行,它是面向打包的,原因如下:
- TypeScript 转译
.ts文件不会变更import/export语句,即如果原先引用路径无后缀,转译之后也没有后缀,如果原先后缀是.ts,转译之后也是.ts后缀。 - TypeScript 可以正确处理
.js后缀和无后缀两种情况,即如果我们引用的目标文件实际上是import * as Tar from './target.ts',当我们写成import * as Tar from './target.js'或者import * as Tar from './target'的时候,都是没问题的 - 如果要让 es 文件可以执行,需要在建立模块引用关系的时候补全
.js后缀,无后缀或者.ts后缀都是不行的。 - webpack 打包需要能够根据路径解析到文件,它能够解析无后缀情况,如果我们引用的目标文件实际上是
import * as Tar from './target.ts',那我们就只能写import * as Tar from './target.ts'或者import * as Tar from './target'
看似没什么问题,直接用 .js 后缀就好啦,别忘了我们在上一个环节中的考虑,我们希望另一个项目在引用当前项目的时候,直接引用 src 中的源文件,而如果源文件使用 .js 做引用路径后缀,webpack 就找不到目标文件(因为引用的文件实际上是 .ts 后缀,TypeScript 可以正确处理这种情况,但 webpack 不可以),为了尽可能满足需求,我们只能选择使用无后缀的模块引用方式。
esm 是可以在浏览器或 Node 环境中直接执行的版本,它由 webpack 的打包而成,不保证可用且不建议在生产环境中使用(webpack 面向 esmodule 的构建目标是实验功能,尚不完善)。
// ./scripts/bundle.js
const getTSConfigJSONString = () => readFileSync(rootResolvePath('./tsconfig.json'), { encoding: 'utf8' })
.replace(/\s\/\*.*\*\//g, '')
.replace(/\s\/\/.*,/g, '')
.replace(/\s\/\*.*/g, '')
.replace(/\s\*(.)*/g, '')
const getTSConfig = () => JSON.parse(getTSConfigJSONString())
const collectFiles = (rootPath, results = []) => {
const files = fs.readdirSync(rootPath)
files.forEach(item => {
const filepath = path.resolve(rootPath, item)
if (fs.statSync(filepath).isDirectory()) {
results.push(...collectFiles(filepath))
} else {
results.push(filepath)
}
})
return results
}
const packES = () => {
return new Promise((resolve) => {
const compilerOptions = getTSConfig().compilerOptions
compilerOptions.incremental = false
delete compilerOptions.tsBuildInfoFile
compilerOptions.composite = false
compilerOptions.target = ts.ScriptTarget.ES2015
compilerOptions.module = ts.ModuleKind.ES2015
compilerOptions.outDir = rootResolvePath('./release/modules/es')
compilerOptions.moduleResolution = ts.ModuleResolutionKind.NodeJs
delete compilerOptions.declarationDir
const program = ts.createProgram([rootResolvePath('./src/main.ts')], compilerOptions)
const emitResult = program.emit()
const targetFiles = collectFiles(rootResolvePath('./src/ts'))
.filter(filepath => filepath.endsWith('.js') || filepath.endsWith('.d.ts'))
targetFiles.forEach(filepath => {
const relativePathToSrc = path.relative(rootResolvePath('./src'), filepath)
const relativePathToDest = path.relative(filepath, rootResolvePath('./release/modules/es'))
copyFileSync(
filepath,
path.resolve(filepath, relativePathToDest, relativePathToSrc)
)
})
console.log('【packES】 emitResult', emitResult)
console.log('【packES】 source file copyed', targetFiles)
resolve(emitResult)
})
}
const packTypings = () => {
return new Promise((resolve) => {
const compilerOptions = getTSConfig().compilerOptions
compilerOptions.emitDeclarationOnly = true
compilerOptions.moduleResolution = ts.ModuleResolutionKind.NodeJs
compilerOptions.declarationDir = rootResolvePath('./typings')
const program = ts.createProgram([rootResolvePath('./src/main.ts')], compilerOptions)
const emitResult = program.emit()
const dtsFiles = collectFiles(rootResolvePath('./src/ts'))
.filter(filepath => filepath.endsWith('.d.ts'))
dtsFiles.forEach(filepath => {
const relativePathToSrc = path.relative(rootResolvePath('./src'), filepath)
const relativePathToDest = path.relative(filepath, rootResolvePath('./typings'))
copyFileSync(
filepath,
path.resolve(filepath, relativePathToDest, relativePathToSrc)
)
})
console.log('【packTypings】 emitResult', emitResult)
console.log('【packTypings】 dts file copyed', dtsFiles)
resolve(emitResult)
})
}
注意,tsconfig.json 的配置读进来之后,并不可以直接当作 compilerOptions 使用,target、module、moduleResolution 等字段需要使用 typescript 提供的常量重新定义。
其它
# 下载所有依赖
npm install -D typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-standard-with-typescript@latest ts-loader
# 移除 VCS 冗余项
git rm -f -r dev
git rm -f -r build
git rm -r -f dist
git rm -r -f release
git rm -f -r typings
