首页 > 其他分享 >纷乱繁杂的声明文件

纷乱繁杂的声明文件

时间:2024-06-03 22:12:59浏览次数:28  
标签:node 繁杂 lodash true ts module 纷乱 type 声明

费脑子的声明文件

初始化项目:

# 创建项目目录
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

相关文章

  • 微信小程序-声明和绑定事件
    一.概念小程序页面使用的数据在Page()方法里使用data对象进行声明定义定义好之后,使用{{}}进行绑定声明Page({data:{school:'未发之中',obj:{name:'dadada'},id:1,useChk:true}})绑定<!--使用双括号展示数据--><view>{{scho......
  • 如何在 PHP 8.3 中声明变量
    在php7.3中,我做了如下操作。$TitelString=$_GET["TitelString"];If(!$TitelString)$TitelString="";$AuteurString=$_GET["AuteurString"];If(!$AuteurString)$AuteurString="";$JaarString=$_GET["JaarS......
  • SpringBoot3.2更新声明!
    1从SpringBoot3.1升级1.1参数名称发现SpringBoot3.2使用的SpringFramework版本不再尝试通过解析字节码来推断参数名称。如果您在依赖注入或属性绑定时遇到问题,请务必检查您是否在编译时使用了-parameters选项。有关更多详细信息,请参阅"升级到SpringFramework......
  • C++中定义和声明的区别
     直接上例子在类里面声明(这里以静态数据成员和静态成员函数为例)classMyClass{public:staticintstaticDataMember;//静态数据成员的声明staticvoidstaticFunction();//静态成员函数的声明//其他成员...};在cpp文件中定义 静态成员的声明......
  • TypeScript中的`let`、`const`、`var`区别:变量声明的规范与实践
    TypeScript中的let、const、var区别:变量声明的规范与实践引言在TypeScript中,变量声明是代码编写的基础部分。let、const、var是三种用于变量声明的关键字,它们各自有不同的作用域规则和可变性特点。基础知识作用域:变量可以在整个文件(全局作用域)或某个特定代码块(局部作用......
  • Python可以声明并赋值一个hash类型变量吗?
    在Python中,不能直接声明一个变量为`hash`类型,因为Python是一种动态类型语言,不需要(也不能)在声明变量时指定其类型。变量的类型是根据赋给它的值自动推断的。将一个哈希值(即一个整数)赋值给一个变量,这个哈希值可以是通过调用内置`hash()`函数获得的任何对象的哈希值。例如:```pyt......
  • 如何在Spring中配置声明式事务?
    在Spring中配置声明式事务,主要有两种方式:通过XML配置文件和使用注解。声明式事务让你能够将事务管理代码从业务逻辑代码中分离出来,通过声明的方式来管理事务,使得代码更加简洁,易于维护。下面我将分别展示这两种方式的配置方法:通过XML配置文件配置DataSource:首先,您需要配......
  • JavaScript 新特性:新增声明命令与解构赋值的强大功能
    个人主页:学习前端的小z个人专栏:JavaScript精粹本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结,欢迎大家在评论区交流讨论!ES5、ES6介绍文章目录......
  • 【实战JVM】-01-JVM通识-字节码详解-类的声明周期-加载器
    【实战JVM】-01-JVM通识-字节码详解-类的声明周期-加载器1初识JVM1.1什么是JVM1.2JVM的功能1.2.1即时编译1.3常见JVM2字节码文件详解2.1Java虚拟机的组成2.2字节码文件的组成2.2.1正确打开字节码文件2.2.2字节码组成2.2.3基础信息2.2.3.1魔数2.2.3.1主副......
  • Mysql变量声明的方式
    参考:https://www.cnblogs.com/Marydon20170307/p/14112059.html1.使用declare,这个必须用在存储过程或者函数中,不要@前缀。声明变量必须在存储过程、函数的顶部,先声明变量,再写其他逻辑。一次多个:declarestr1,str2varchar(10);--公用一个类型不一样的话,就一个个的声明:declarest......