通过 npm 包共享 TypeScript 类型声明

Posted

技术标签:

【中文标题】通过 npm 包共享 TypeScript 类型声明【英文标题】:Sharing TypeScript type declarations via npm package 【发布时间】:2021-06-06 06:57:30 【问题描述】:

我需要在我的 React 客户端和我的 Express REST API 之间共享一些 TypeScript 类型,以保持代码干净和干燥。 由于这是一个私有项目,我不会通过 @types 存储库共享这些类型,所以我按照 TypeScript 网站上的指南进行操作,结果如下...

在 React 客户端中一切正常:我已将类型安装为开发依赖项并完美使用它们。

在 Express API 中,我收到此错误,我认为这与我构建包的方式有关。

我做错了什么? 尽管我很无知,但我认为这与模块的加载方式有关,但我无法准确找出可能导致错误的原因。

> cross-env NODE_ENV=production node dist/index.js

internal/modules/cjs/loader.js:834
  throw err;
  ^

Error: Cannot find module '@revodigital/suiteods-types'

如何在 API 代码中导入模块

import  AuthEntity, Roles  from '@revodigital/suiteods-types';

@Model()
export class AuthEntityModel implements AuthEntity 
  /* ... */

  role: Roles;

  /* ... */



包树
suiteods-types
  |_index.d.ts
  |_package.json
  |_README.md
  |_tsconfig.json

index.d.ts

export = Ods;
export as namespace Ods;

declare namespace Ods 
  /* ... */
  interface AuthEntity extends DomainObject 
    email: string;
    password: string;
    role: Roles;
    instanceId: string;
  

  enum Roles 
    BASE,
    STUDENT,
    BUSINESS,
    INSTRUCTOR,
    ADMIN
  
  /* ... */

package.json


  "name": "@revodigital/suiteods-types",
  "version": "0.1.1",
  "description": "Type declarations for suiteods project",
  "types": "index.d.ts",
  "scripts": 
    "test": "echo \"Error: no test specified\" && exit 1"
  ,
  "author": "Revo Digital",
  "license": "ISC",
  "repository": 
    "type": "git",
    "url": "git+https://github.com/revodigital/suiteods-types.git"
  ,
  "bugs": 
    "url": "https://github.com/revodigital/suiteods-types/issues"
  ,
  "homepage": "https://github.com/revodigital/suiteods-types#readme"

tsconfig.json


  "compilerOptions": 
    "module": "commonjs",
    "lib": [
      "es6"
    ],
    "noImplicitAny": true,
    "noImplicitThis": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "baseUrl": "../",
    "typeRoots": [
      "../"
    ],
    "types": [],
    "noEmit": true,
    "forceConsistentCasingInFileNames": true
  ,
  "files": [
    "index.d.ts"
  ]


更新

对如何摆脱命名空间感到困惑,并且仍然在模块上遇到相同的错误,现在安装为“依赖项”而不是“开发依赖项”。 文件结构同上。 在此先感谢您的帮助。

更新并完成 index.d.ts

export = Ods;
export as namespace Ods;

declare namespace Ods 

  type IdType = 'Carta Identità' | 'Passaporto' | 'Patente'
  
  type ODSModule = 'SCUOLA' | 'OPERATORI' | 'DRONI' | 'ODS_ROOT'

  type Role = 'BASE' | 'STUDENTE' | 'ISTRUTTORE' | 'AMMINISTRATORE' | 'UTENTE_AZIENDALE'
  
  type UserScope = 'INTERNAL' | 'WHOLE'


  interface Address 
    street: string;
    city: string;
    province: string;
    CAP: string;
  

  interface Credentials 
    email: string;
    password: string;
  

  interface LoggedEntity 
    authEntity: AuthEntity;
    baseUser: BaseUser;
  

  interface ModulesInstancesMap 
    SCUOLA: string;
    OPERATORI: string;
    DRONI: string;
    ODS_ROOT: string;
  

  interface MultiTenantController 

  

  interface Tenant 
    _id: string;
    role: Role | ODSModule;
  

  interface TenantInfo 
    tenant: Tenant;
    relativeGodRole: Role;
  

  interface AuthEntity extends DomainObject 
    email: string;
    password: string;
    role: Role;
    instanceId: string;
  

  interface BaseUser extends DomainObject 
    firstName: string;
    lastName: string;
    phone: string;
    address: Address;
    scope: UserScope;
  

  interface BelongsToModule 
    module: ODSModule;
  

  interface Business extends DomainObject 
    businessName: string;
    pIva: string;
    tel: string;
    pec: string;
    recipientCode: string;
    address: Address;
  

  interface DomainObject 
    _id: string;
  

  interface HasTenant 
    tenantInfo: TenantInfo;
  

  interface Instructor extends BaseUser 
    licenseCode: string;
  

  interface InternalWholeSuiteUser extends BaseUser 
    modulesInstancesMap: ModulesInstancesMap;
  

  interface InternalModuleUser extends BaseUser, BelongsToModule 
    moduleInstanceId: string;
  

  interface School extends Business, HasTenant 
    cApr: number;
  


  interface Student extends BaseUser 
    stateIssuedIdNumber: string;
    stateIssuedIsType: IdType;
    job: string;
    businessId?: string;
  


【问题讨论】:

AuthEntityRoles 是接口吗? AuthEntity 是一个接口,Roles 是一个枚举 @Paleo 有什么建议吗?我真的对这个话题迷失了方向,我想更好地理解我做错了什么。 在前后端的packages.json文件中,能看到@revodigital/suiteods-types相关的行吗?他们是在devDependencies 还是dependencies 它们在客户端和服务器都位于devDependencies 【参考方案1】:

除了宝贵的@Paleo 编辑之外,添加一个包含以下内容的 index.js 文件解决了这个问题。

index.js

module.exports = ;

更新文件结构

suiteods-types
  |_index.d.ts
  |_index.js
  |_package.json
  |_README.md
  |_tsconfig.json


那么...如何通过 npm 共享 TS 类型

如果您想共享一些 TypeScript 类型(例如,在我的情况下,在客户端和服务器之间),那么(对我有用的)步骤就是下面的步骤。
    创建一个新文件夹并将其初始化为带有npm init 的npm 包 推断要在实体之间共享的所有类型,并将它们分组到 index.d.ts 文件中 仅声明“纯类型”,在我的情况下为 converting the enums into types(并进行一些重构以适应其余代码)就足够了 添加tsconfig.json(有关示例,请参见我上面的问题) 添加一个仅包含module.exports = index.js 发布它(见下面的链接) 安装为dependency,所以`npm i --save @yourscope/yourpkg 需要时服用

为了发布这个包,我使用了 npm 和 GitHub 包。看到这些链接... https://docs.github.com/en/packages/learn-github-packages/publishing-a-package https://docs.github.com/en/packages/guides/configuring-npm-for-use-with-github-packages

【讨论】:

需要这个才能让我的 package.json “main”条目正常工作。以前甚至没有 index.js 文件,因为我认为我不需要它。这有帮助!谢谢!【参考方案2】:

问题

enum 类型不是纯类型。 TypeScript 编译器会为此类型生成一些 javascript 代码。您的其余代码需要它。

在运行时,正常部署后,您的代码无法访问“开发依赖项”。仅安装了依赖项。

就您的前端而言,Webpack 有一点魔力。在构建时,Webpack 会遵循所有依赖项(包括开发依赖项)中的代码,并将它们打包。所以你的私有依赖的编译代码在包中并且可以工作。

解决方案

解决方案 1:可以仅使用运行时使用的 javascript 代码发布您的包@revodigital/suiteods-types。然后这个包就可以作为一个常规的依赖了。

解决方案 2:可以在后端使用打包程序(Webpack 或 Rollup)来打包使用过的代码。私有包的打包方式和前端一样。

解决方案 3:将私有包中的类型设为“纯类型”,以便在运行时根本不需要它。将所有 enum 类型替换为字符串联合。

例如:

enum Roles 
    BASE,
    STUDENT,
    BUSINESS,
    INSTRUCTOR,
    ADMIN
  

... 可以替换为:

type Role = "BASE" | "STUDENT" | "BUSINESS" | "INSTRUCTOR" | "ADMIN"

注意:这将需要一些重构。

作为奖励的免费建议:不要保留命名空间

不建议在模块中使用namespace。你应该摆脱它。

当前代码:

export = Ods;
export as namespace Ods;

declare namespace Ods 

  type IdType = 'Carta Identità' | 'Passaporto' | 'Patente'
  
  type ODSModule = 'SCUOLA' | 'OPERATORI' | 'DRONI' | 'ODS_ROOT'

  // ...

  interface Address 
    street: string;
    city: string;
    province: string;
    CAP: string;
  

  // ...

... 应替换为:


export type IdType = 'Carta Identità' | 'Passaporto' | 'Patente'
  
export type ODSModule = 'SCUOLA' | 'OPERATORI' | 'DRONI' | 'ODS_ROOT'

// ...

export interface Address 
  street: string;
  city: string;
  province: string;
  CAP: string;


// ...

然后,如果您喜欢这种方式,可以将模块作为命名空间导入:

import * as Ods from "@revodigital/suiteods-types";

【讨论】:

我已经用“纯类型”更新了文件,但仍然是同样的问题...我正在更新问题以便更清楚地了解问题 @LeonardoViada 我编辑了关于如何摆脱命名空间的内容,如果清楚,请告诉我。我看到你发布了你的包,这是一个有效的解决方案。如果您仍想将依赖项保留为私有“devDependency”,那么您需要查看编译文件dist/index.js 以了解导入私有包的原因:您可以复制带有require("@revodigital/suiteods-types") 的行,然后使用从包中导入的变量的行。

以上是关于通过 npm 包共享 TypeScript 类型声明的主要内容,如果未能解决你的问题,请参考以下文章

npm 包的可安装 typescript 类型定义

typescript npm 包 - 如何不必从“dist/”导入

如何通过npm编译Typescript代码

VS Code 或 Chrome 开发工具:调试 NPM Workspaces (monorepo) TypeScript + React 代码

TypeScript基本知识

自定义npm包——typeScript版本