Reputation: 413
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;
/* ... */
}
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"
]
}
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;
}
}
Upvotes: 12
Views: 10587
Reputation: 413
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
See GitHub repo for full code.
npm init
index.d.ts
filetsconfig.json
(see my question above for an example)index.js
containing only module.exports = {}
dependency
, so `npm i --save @yourscope/yourpkgUpvotes: 11
Reputation: 23692
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.
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.
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";
Upvotes: 6