费脑子的声明文件
初始化项目:
# 创建项目目录
mkdir hello && cd hello
# 生成 package.json
npm init -y
# 由于是 ts 项目,安装 typescript 包
npm i --save-dev typescript
# 生成 tsconfig.json
npx tsc --init
package.json
{
"name": "@kaoniqiwa/hello",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"typescript": "^5.4.5"
}
}
精简过的 tsconfig.json,只保留打开的选项
{
"compilerOptions": {
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"module": "commonjs" /* Specify what module code is generated. */,
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
"strict": true /* Enable all strict type-checking options. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
- 当前项目仅作为本地测试项目,先将 package.json 中的 main 字段删除
{
"name": "@kaoniqiwa/hello",
"version": "1.0.0",
"description": "",
- "main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"typescript": "^5.4.5"
}
}
- tsconfig.json 报错
No inputs were found in config file '/Users/hello/tsconfig.json'. Specified 'include' paths were '["**/*"]' and 'exclude' paths were '[]'.ts
报错的意思是当前项目没有输入文件.ts 文件。在 src 下创建 main.ts
main.ts
import _ from 'lodash';
由于没有安装 lodash 包,编译器报错.
Cannot find module 'lodash' or its corresponding type declarations.ts(2307)
那就安装 lodash 包 npm i lodash
,安装完成之后,仍然报错,但这次错误信息不一样了
could not find a declaration file for module 'lodash'.
'/Users/hello/node_modules/lodash/lodash.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/lodash` if it exists or add a new declaration (.d.ts) file containing `declare module 'lodash';
ts 编译器发现,lodash 包虽然找到了,但该包并没有内置类型声明文件,那么该包的导出内容就隐式为 any 类型.这和 tsconfig.json 中默认配置 strict:true 冲突,因为 ts 规定不能有隐式 any 类型。
package.json
{
"name": "@kaoniqiwa/hello",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"typescript": "^5.4.5"
},
"dependencies": {
+ "lodash": "^4.17.21"
}
}
瞅一眼 node_modules/lodash 包.
{
"name": "lodash",
"version": "4.17.21",
"description": "Lodash modular utilities.",
"keywords": "modules, stdlib, util",
"homepage": "https://lodash.com/",
"repository": "lodash/lodash",
"icon": "https://lodash.com/icon.svg",
"license": "MIT",
"main": "lodash.js",
"author": "John-David Dalton <john.david.dalton@gmail.com>",
"contributors": [
"John-David Dalton <john.david.dalton@gmail.com>",
"Mathias Bynens <mathias@qiwi.be>"
],
"scripts": {
"test": "echo \"See https://travis-ci.org/lodash-archive/lodash-cli for testing details.\""
}
}
lodash 包的入口文件为 lodash.js。
稍微看一下 lodash.js 源码
(function () {
// node 环境有 global 变量,与运算符最后返回 global 变量
var freeGlobal =
typeof global == 'object' && global && global.Object === Object && global;
// 浏览器环境有 self 变量指向 window,worker 环境有self变量,指向 DedicatedWorkerGlobalScope,与运算法最后返回 self 变量
var freeSelf =
typeof self == 'object' && self && self.Object === Object && self;
/**
* 获取运行环境:
* Function('return this')():创建一个匿名函数,并立即执行,返回 call(this) 中的 this
* 这段代码是兜底代码,在浏览器环境中 self 虽然指向 window,但 self 可以被重新赋值
*/
var root = freeGlobal || freeSelf || Function('return this')();
/**
* 获取 cjs 环境下的 exports 变量
* 浏览器下可以直接通过 id 访问 Html 元素
* <div id="exports">hello</div>
* console.log(exports)
* 所以这里作了额外的 exports.nodeType 判断
*/
var freeExports =
typeof exports == 'object' && exports && !exports.nodeType && exports;
// 获取 module 对象
var freeModule =
freeExports &&
typeof module == 'object' &&
module &&
!module.nodeType &&
module;
// 确认 module.exports 和 exports 指向同一个对象,没有被改过,node 中 exports 可以重新赋值
var moduleExports = freeModule && freeModule.exports === freeExports;
// 顶层 process 对象
var freeProcess = moduleExports && freeGlobal.process;
// 一堆 node API,显然 lodash 是为 node 而生滴,源码应该是 cjs 语法,只不过能在浏览器使用,是因为符合纯 ECMA 规范,没有涉及 DOM 和 BOM
// 将各种函数绑定到 lodash 函数上
var runInContext = function runInContext(context) {
function lodash(value) {}
function chunk() {}
lodash.chunk = chunk;
function wrapperChain() {}
lodash.prototype.chain = wrapperChain;
return lodash;
};
// 这里返回的就是 lodash 函数
var _ = runInContext();
// 开始绑定到全局对象上
if (
typeof define == 'function' &&
typeof define.amd == 'object' &&
define.amd
) {
// requireJS 走这里
root._ = _;
define(function () {
return _;
});
} else if (freeModule) {
// node 走这里
/**
* 下面两段代码的使用:
* const lodash = require('./lodash/lodash.js');
* console.log(lodash._ == lodash);
*/
(freeModule.exports = _)._ = _;
freeExports._ = _;
} else {
//浏览器普通引入走这里
root._ = _;
}
}).call(this);
这里没有提供在 esm 环境使用的情况,首先 esm 中 默认开启严格模式,this 为 undefined,其次源代码中 root 在 esm 中运行得到的值为 undefined.所以无法直接在测试案例中通过 type="module" 使用 lodash.那为何在 vue ,react 项目这些前端项目可以使用 lodash 呢?因为这些项目有转译器呀,能把 cjs 代码转成 esm 代码,这样才能跑起来。我这素颜项目,只有 ts+lodash
lodash 扯了这么多,主要是源码中的 this 引起了我的兴趣,回到 ts 类型报错上来。
该包中全都是 .js 文件,一点 .d.ts 的影子都见不到。package.json 中也未指定 types 字段值。
因为 lodash 并未将声明文件与源代码一起发布,而是将声明文件发布到了 @types/lodash 上。lodash 目前版本为 4.17.21,看了一眼 github 上的源码,已经到 5.0.0 了,源码在 4.17.21 基础上套了层 ts 外壳,tsconfig.json 中也指定了 declaration 字段,以后就可以直接下载使用了,不需依赖 @types/lodash.
解决方案一:
既然是 lodash 没有声明文件,导致该包的导出为 any 类型,那我可以手动把校验隐式 any 类型的规则禁掉
tsconfig.json
{
"compilerOptions": {
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"module": "commonjs" /* Specify what module code is generated. */,
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
"strict": true /* Enable all strict type-checking options. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
+"noImplicitAny": false
}
}
禁用规则之后,果然不报错了.虽然有点自欺欺人的行为,虽然不报错了,但没有类型提示,然并卵。
解决方案二:
既然缺少声明文件,我写一个吧,这也是通常第三方 js 库的基操。
首先来个骚操作--在 node_modules/lodash 目录下建个文件 index.d.ts,内容如下
declare module 'lodash' {
export function at(): void;
}
报错解决,也有类型提示了,如果没有,重启 vs code 吧。
为什么给 lodash 加个 index.d.ts 就不报错了呢? TS 的类型检查规则到底是啥,英文官网看的头晕,知识点星罗棋布的,和 ng 官网有的一拼。
中文官网给出了解释:https://www.tslang.cn/docs/handbook/module-resolution.html
在导入 lodash 包时,没找到 lodash.ts,就在 lodash 包下找 index.d.ts。
typeRoots 字段对包类型文件的查找影响
{
"compilerOptions": {
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"module": "commonjs" /* Specify what module code is generated. */,
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
"forceConsistentCasingInFileNames": true,
"strict": true /* Enable all strict type-checking options. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
+ "typeRoots": ["./node_modules/aaa"]
}
}
- typeRoots 指定的是包类型文件所在位置,默认值是 node_modules/@types,这也是为什么
npm i --save-dev @types/lodash
后不报错了,也有类型提示的原因(包本身没有声明文件的前提下).这里要注意要创建包名文件夹,再创建 index.d.ts。因为 ts 是根据包名 lodash 去 node_modules/aaa 下找同名目录的。当在 main.js 中通过 lodash 包名跳转时,跳转到了 aaa/lodash/index.d.ts 文件中,不是 node_modules/lodash/index.d.ts - types 该字段处理的是默认全局属性类型,比如 process,require,它会在 typeRoot 目录下,过滤特定包
lodash/index.d.ts
declare module 'lodash' {
global {
function say(): void;
}
export function at(): void;
}
world/index.d.ts
declare module 'world' {
global {
var process: any;
}
export default function world(): void;
}
tsconfig.json
{
"compilerOptions": {
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"module": "commonjs" /* Specify what module code is generated. */,
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
"forceConsistentCasingInFileNames": true,
"strict": true /* Enable all strict type-checking options. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
+ "typeRoots": ["./node_modules/aaa"],
+ "types": ["lodash"]
}
}
src/main.ts
say();
process;
types 字段指定 lodash 包,而没有指定 world 包,那么 lodash 中的全局变量 say 就可在 main.ts 中使用,而 world 包中的 process 变量则报错.注意这里 types 字段的值来源于 typeRoots 目录中的包名.
import 'world';
say();
process;
一旦将包 world 导入,那么 process 就不再报错了。因为直接使用 process ,ts 是使用自动导入规则,去查找有没有 process 这个全局变量,你没有在 types 中指定 world 包,那就不会去 world 包中查找,自然报错。当显式导入 world 包时,编译器就获悉 process 在 world 包中。
说到这里,都是关于 node_modules 的处理,按照平常操作,都是在项目根目录下创建包的声明文件,不会手动修改 node_modules 相关东西的。
还原一下文件,重置为初始状态
tsconfig.json
{
"compilerOptions": {
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"module": "commonjs" /* Specify what module code is generated. */,
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
"forceConsistentCasingInFileNames": true,
"strict": true /* Enable all strict type-checking options. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
main.ts
import _ from 'lodash';
报错,没有 lodash 的声明文件,新建 bbb/test.d.ts
test.d.ts
declare module 'lodash' {}
报错解决。这里涉及到一个知识点:编译器会将项目根目录下及子目录(除 node_modules)的所有 .ts,.tsx,.d.ts 文件包含进来。所以我在 bbb 目录下新建声明文件也是有效的。
与其相关的字段是 files,include,exclude
https://www.tslang.cn/docs/handbook/tsconfig-json.html
files 默认[],include 默认["**/*"], exclude 默认 ["node_modules"]
{
"compilerOptions": {
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"module": "commonjs" /* Specify what module code is generated. */,
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
"forceConsistentCasingInFileNames": true,
"strict": true /* Enable all strict type-checking options. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": ["src/**/*.ts"]
}
include 包含 src 下的文件,未包含 bbb 目录下的文件,main.ts 中报错,表示未找到声明文件
{
"compilerOptions":{
...
},
"files": ["bbb/test.d.ts"],
"include": ["src/**/*.ts"],
}
将 test.d.ts 加入 files 中或者 include 中都可以解决报错.
typeRoots + types 处理的是 node_modules 如何查找类型声明文件。 files+include+exclude 处理的是如何查找本地声明文件。
接下来处理下 setTimeout 的返回值类型在浏览器中和 node 中的不同
安装 @types/node,这在工程化项目中,一般都会安装此包。npm i --save-dev @types/node
main.ts
let timer: number = setTimeout(() => {}, 0);
报错,Type 'Timeout' is not assignable to type 'number'.ts(2322)。
因为 setTimeout 用的是 Node API
node API
function setTimeout<TArgs extends any[]>(
callback: (...args: TArgs) => void,
ms?: number,
...args: TArgs
): NodeJS.Timeout;
function setTimeout(
callback: (args: void) => void,
ms?: number
): NodeJS.Timeout;
浏览器 API
declare function setTimeout(
handler: TimerHandler,
timeout?: number,
...arguments: any[]
): number;
解决方案
let timer: number = setTimeout(() => {}, 0, undefined);
let timer: number = window.setTimeout(() => {}, 0);
至于为什么传递额外参数就能返回 number 类型,不知道。因为看类型声明,node API 也是能接受额外参数的。
标签:node,繁杂,lodash,true,ts,module,纷乱,type,声明 From: https://www.cnblogs.com/bibiafa/p/18229776