Skip to content

Instantly share code, notes, and snippets.

@H4ad
Last active January 21, 2023 16:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save H4ad/eed1118c5b3196c460371f1e9a2867f3 to your computer and use it in GitHub Desktop.
Save H4ad/eed1118c5b3196c460371f1e9a2867f3 to your computer and use it in GitHub Desktop.
NestJS Module Token Factory

NestJS Performance Analysis

This is the artifact of my performance analysis on NestJS about ModuleTokenFactory.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModuleCompiler = void 0;
const module_token_factory_1 = require("./module-token-factory");
class ModuleCompiler {
constructor(moduleTokenFactory = new module_token_factory_1.ModuleTokenFactory()) {
this.moduleTokenFactory = moduleTokenFactory;
}
async compile(metatype) {
const { type, dynamicMetadata } = this.extractMetadata(await metatype);
const token = this.moduleTokenFactory.create(type, dynamicMetadata);
return { type, dynamicMetadata, token };
}
extractMetadata(metatype) {
if (!this.isDynamicModule(metatype)) {
return { type: metatype, dynamicMetadata: undefined };
}
return { type: metatype.module, dynamicMetadata: metatype };
}
isDynamicModule(module) {
return !!module.module;
}
}
exports.ModuleCompiler = ModuleCompiler;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModuleTokenFactory = void 0;
const random_string_generator_util_1 = require("@nestjs/common/utils/random-string-generator.util");
const crypto_1 = require("crypto");
class ModuleTokenFactory {
constructor() {
this.moduleIdsCache = new WeakMap();
this.moduleTokenCache = new Map();
this.dynamicallyModuleMetadataCache = new WeakMap();
}
create(metatype, dynamicModuleMetadata) {
const moduleId = this.getModuleId(metatype);
if (!dynamicModuleMetadata)
return this.getFastModuleToken(moduleId, this.getModuleName(metatype));
if (this.dynamicallyModuleMetadataCache.has(dynamicModuleMetadata))
return this.dynamicallyModuleMetadataCache.get(dynamicModuleMetadata);
this.dynamicallyModuleMetadataCache.set(dynamicModuleMetadata, (0, random_string_generator_util_1.randomStringGenerator)());
return this.dynamicallyModuleMetadataCache.get(dynamicModuleMetadata);
}
getFastModuleToken(moduleId, moduleName) {
const key = `${moduleId}_${moduleName}`;
if (this.moduleTokenCache.has(key))
return this.moduleTokenCache.get(key);
const hash = (0, crypto_1.createHash)('sha1').update(key).digest('hex');
this.moduleTokenCache.set(key, hash);
return hash;
}
getModuleId(metatype) {
let moduleId = this.moduleIdsCache.get(metatype);
if (moduleId) {
return moduleId;
}
moduleId = (0, random_string_generator_util_1.randomStringGenerator)();
this.moduleIdsCache.set(metatype, moduleId);
return moduleId;
}
getModuleName(metatype) {
return metatype.name;
}
}
exports.ModuleTokenFactory = ModuleTokenFactory;
import { DynamicModule } from '@nestjs/common';
import { Type } from '@nestjs/common/interfaces/type.interface';
export declare class ModuleToken implements DynamicModule {
constructor(props: DynamicModule);
moduleId: string;
module: DynamicModule['module'];
global?: DynamicModule['global'];
imports?: DynamicModule['imports'];
controllers?: DynamicModule['controllers'];
providers?: DynamicModule['providers'];
exports?: DynamicModule['exports'];
}
export declare class ModuleTokenFactory {
private readonly moduleIdsCache;
private readonly moduleTokensCache;
create(metatype: Type<unknown>, dynamicModuleMetadata?: Partial<DynamicModule> | undefined): string;
getFastModuleToken(moduleId: string, moduleName: string): string;
getDynamicMetadataToken(dynamicModuleMetadata: Partial<DynamicModule> | undefined): string;
getModuleId(metatype: Type<unknown>): string;
getModuleName(metatype: Type<any>): string;
private replacer;
}
// to test with this file, you should override the d.ts too
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModuleTokenFactory = exports.ModuleToken = void 0;
const random_string_generator_util_1 = require("@nestjs/common/utils/random-string-generator.util");
const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
const crypto_1 = require("crypto");
const fast_safe_stringify_1 = require("fast-safe-stringify");
const hash = require("object-hash");
class ModuleToken {
constructor(props) {
this.moduleId = (0, random_string_generator_util_1.randomStringGenerator)();
this.module = props.module;
this.global = props.global;
this.imports = props.imports;
this.controllers = props.controllers;
this.providers = props.providers;
this.exports = props.exports;
}
}
exports.ModuleToken = ModuleToken;
class ModuleTokenFactory {
constructor() {
this.moduleIdsCache = new WeakMap();
this.moduleTokensCache = new Map();
}
create(metatype, dynamicModuleMetadata) {
// console.log({metatype, dynamicModuleMetadata});
if (dynamicModuleMetadata instanceof ModuleToken) {
// console.log('Taking ModuleToken path');
return dynamicModuleMetadata.moduleId;
}
const moduleId = this.getModuleId(metatype);
if (!dynamicModuleMetadata)
return this.getFastModuleToken(moduleId, this.getModuleName(metatype));
// console.log('Taking slow path');
const opaqueToken = {
id: moduleId,
module: this.getModuleName(metatype),
dynamic: this.getDynamicMetadataToken(dynamicModuleMetadata),
};
return hash(opaqueToken, { ignoreUnknown: true });
}
getFastModuleToken(moduleId, moduleName) {
// console.log('Taking getFastModuleToken path');
const key = `${moduleId}_${moduleName}`;
if (this.moduleTokensCache.has(key))
return this.moduleTokensCache.get(key);
const hash = (0, crypto_1.createHash)('sha1').update(key).digest('hex');
this.moduleTokensCache.set(key, hash);
return hash;
}
getDynamicMetadataToken(dynamicModuleMetadata) {
// Uses safeStringify instead of JSON.stringify to support circular dynamic modules
// The replacer function is also required in order to obtain real class names
// instead of the unified "Function" key
return dynamicModuleMetadata
? (0, fast_safe_stringify_1.default)(dynamicModuleMetadata, this.replacer)
: '';
}
getModuleId(metatype) {
let moduleId = this.moduleIdsCache.get(metatype);
if (moduleId) {
return moduleId;
}
moduleId = (0, random_string_generator_util_1.randomStringGenerator)();
this.moduleIdsCache.set(metatype, moduleId);
return moduleId;
}
getModuleName(metatype) {
return metatype.name;
}
replacer(key, value) {
if ((0, shared_utils_1.isFunction)(value)) {
const funcAsString = value.toString();
const isClass = /^class\s/.test(funcAsString);
if (isClass) {
return value.name;
}
return hash(funcAsString, { ignoreUnknown: true });
}
if ((0, shared_utils_1.isSymbol)(value)) {
return value.toString();
}
return value;
}
}
exports.ModuleTokenFactory = ModuleTokenFactory;
// change the path of the import for your AppModule.
// to run this file, install microtime lib first.
// Then, you only need to compile and run with `node dist/perf-startup-10000.ts`
const { AppModule } = require('./dist/app.module');
const { NestFactory } = require('@nestjs/core');
const { performance } = require('perf_hooks');
const runs = 100;
async function main() {
const start = performance.now();
for (let i = 0; i < runs; i++)
await NestFactory.create(AppModule, { logger: false }).then(m => m.close());
const end = performance.now();
console.log(`Diff: ${(end - start) / (runs)}`);
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment