简体   繁体   中英

Sharing TypeScript type declarations via npm package

I'm in need of sharing some TypeScript types between my React client and my Express REST API in order to keep the code clean and DRY. Since this is a private proejct I wouldn't share these types through the @types repository, so I've followed the guide on the TypeScript website and this is the result...

Everything is working just fine in the React client : I've installed the types as a dev dependency and used them flawlessly.

In the Express API I get this error and I presume it has something to do with how I structured my package.

What am I doing wrong? As ignorant as I am I'd suppose it's related with how the modules are loaded, but I can't figure out precisely what may be causing the error.

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

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

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

How I import the module inside the API code

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

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

  role: Roles;

  /* ... */
}


Package tree
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
  }
  /* ... */
}

index.d.ts

{
  "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"
}

package.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"
  ]
}

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" ] }

Update

Confused on how to get rid of the namespace, and still getting the same error on the module, now installed as `dependency` and not as `devDependency`. The file structure is the same as above. Thanks in advance for the help.

updated and complete 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; } }

The problem

An enum type is not a pure type. The TypeScript compiler generates some JavaScript code for this type. The rest of your code needs it.

At run time, after a normal deployment, your code can't access to the "dev dependencies". Only the dependencies have been installed.

In the case of your frontend, there is a little magic due to Webpack. At build time, Webpack follows the code in all the dependencies (including dev dependencies), and packs them. So the compiled code of your private dependency is in the bundle and it works.

Solutions

Solution 1: It is possible to publish your package @revodigital/suiteods-types with just the javascript code used at runtime. And then the package can be used as a regular dependency.

Solution 2: It is possible to use a bundler (Webpack or Rollup) in the back-end to pack the used code. The private package will be packed the same way as in the front-end.

Solution 3: Make the types in the private package "pure types" so it won't be needed at all at runtime. Replace all the enum types by unions of strings.

For example:

enum Roles {
    BASE,
    STUDENT,
    BUSINESS,
    INSTRUCTOR,
    ADMIN
  }

… could be replaced by:

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

Notice: it will require some refactoring.

A free advice as a bonus: Do not keep the namespace

It is not recommended to use a namespace in modules. You should get rid of it.

The current code:

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;
  }

  // ...
}

… should be replaced by:


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;
}

// ...

Then, the module can be imported as a namespace if you prefer this way:

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

In addition to the precious @Paleo edits, adding an index.js file with the content that follows solved the issue.


index.js

module.exports = {};

Updated file structure

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


So... How to share TS Types via npm

If you want to share some TypeScript types (EG between a client and a server like in my case) the steps (that worked for me) are the ones that follows.
  1. Create a new folder and init it as an npm package with npm init
  2. Extrapolate all the types you want to share between the entities and group them in an index.d.ts file
  3. Make the declaration "pure types"-only, in my case converting the enums into types (and doing a bit of refactoring to adapt the rest of the code) was enough
  4. Add tsconfig.json (see my question above for an example)
  5. Add an index.js containing only module.exports = {}
  6. Publish it
  7. Install it as dependency , so npm i --save @yourscope/yourpkg
  8. Consume it when needed

For publishing the package I've used npm and GitHub packages. See these links...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM