Issue
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
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"
]
}
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;
}
}
Solution
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";
Answered By - Paleo
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.