Ways to "Compile out" log commands

In my typescript project, there is a section of code dedicated to creating debug information. However, upon profiling the application, I discovered that this debug code is causing a significant performance impact.

Currently, my approach involves setting a global variable let debug=true, and

function log(statement: string):void {
  if(debug) console.log(statement);
}

// somewhere in the application
...
log(`hi mom! ${Factory.createDebugStatement()}`);
...

The function Factory.createDebugStatement() is resource-intensive. Furthermore, I am trying to find a way to avoid cluttering my code with if(debug) log(...). I would like typescript to eliminate the entire log statement.

Essentially, I am seeking a TypeScript 'equivalent' of the concept found in the following C structure:

// header file
#ifdef ENABLE_DEBUG
#define DEBUG(...) printf(__VA_ARGS__)
#else
#define DEBUG(...)                                                             \
  {}
#endif

//application

main(){
...
DEBUG("hi mom!");
...
}

This way, I could use cc -DENABLE_DEBUG=1 main.c to include debug messages. By omitting -DENABLE_DEBUG, the entire code related to debugging would be removed.

Answer №1

To achieve that, my go-to solution is using a bundler with advanced tree-shaking capabilities like or

Esbuild makes it simple: check out

You can define the debug variable and let the bundler eliminate code branches that are redundant.

*For safety, consider using a unique name like DEBUG_YOUR_PACKAGE_NAME to avoid conflicts with dependency code)

Answer №2

Credit: Big thanks to Simon Lax for guiding me towards focusing on the bundler aspects...

In my development setup, I utilize rollup.js, along with the plugin-strip. By toggling a single switch in my rollup.config.js, I can easily enable/disable the 'transpile away' feature.

// src/main.ts
import fs from "fs";
class Service {
  expensive(res = ""): string {
    for (let i = 0; i < 1000; i++) res += i;
    return res;
  }
  normal(): string {
    return "Hi mom!";
  }
}

class Logger {
  public static log<T>(statement: T): T | undefined {
    console.log(statement);
    return statement;
  }
}

const s = new Service();

Logger.log(`${s.expensive()}`);

console.log(s.normal());

// and I can even use it in different situations, which usually expect the output.
fs.writeFileSync(
  "/tmp/abc",
  Logger.log(
    "not printed to screen nor to file but I wont have runtime exceptions, because TS hints me to catch the undefined case with the null'ish concealing",
  ) ?? "",
);
// rollup.config.js
import typescript from "@rollup/plugin-typescript";
import strip from "@rollup/plugin-strip";

export default [
  {
    external: ["fs"],
    input: ["src/main.ts"],
    output: {
      dir: "dist",
      format: "es",
    },
    strictDeprecations: true,
    plugins: [
      strip({
        include: ["**/*.ts"],
        functions: process.env.DEBUG ? [] : ["logger.log"],
      }),
      typescript(),
    ],
  },
];
// package.json
 
 "type": "module",
  "scripts": {
    "bundle": "rollup --config rollup.config.js"
  },
  "devDependencies": {
    "@rollup/plugin-strip": "^3.0.2",
    "@rollup/plugin-typescript": "^11.0.0",
    "rollup": "^3.15.0",
    "typescript": "^4.9.5"
  },

Upon executing npm run bundle, the stripped version will be generated

// dist/main.js
import fs from 'fs';

class Service {
    expensive(res = "") {
        for (let i = 0; i < 1000; i++)
            res += i;
        return res;
    }
    normal() {
        return "Hi mom!";
    }
}
const s = new Service();
console.log(s.normal());
// and I can even use it in different situations, which usually expect the output.
fs.writeFileSync("/tmp/abc", "");

Running DEBUG=1 npm run bundle will create the full version

// dist/main.js
import fs from 'fs';

class Service {
    expensive(res = "") {
        for (let i = 0; i < 1000; i++)
            res += i;
        return res;
    }
    normal() {
        return "Hi mom!";
    }
}
class Logger {
    static log(statement) {
        console.log(statement);
        return statement;
    }
}
const s = new Service();
Logger.log(`${s.expensive()}`);
console.log(s.normal());
// and I can even use it in different situations, which usually expect the output.
fs.writeFileSync("/tmp/abc", Logger.log("not printed to screen nor to file but I wont have runtime exceptions, because TS hints me to catch the undefined case with the null'ish concealing") ?? "");

(In case you are interested, here is my tsconfig.json)

// tsconfig.json
{
  "compilerOptions": {
    "outDir": "dist",
    "target": "ES2022",
    "module": "ES2022",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    "baseUrl": "."
  },
  "ts-node": {
    "transpileOnly": true
  }
}

Answer №3

If you're struggling with Rollup and trying to eliminate those errors, you can also prevent the invocation of Factory.createDebugStatement() by passing Factory.createDebugStatement as a reference.

For example:

Factory.createDebugStatement = (arg1, arg2) => {
  return `${arg1}-${arg2}`;
};

function log(statement: string, ...args: any[]): void {
  if (debug) {
    console.log(statement.replace('%s', args.map((arg) => (typeof arg === 'function' ? arg() : arg)).join(''));
  }
}

// somewhere in the application
log('hi %s mom!', Factory.createDebugStatement.bind(null, 'hello', 'world'));
// Output: hi hello-world mom!

If your Factory.createDebugStatement does not require any arguments, you can simply use...

log('hi %s mom!', Factory.createDebugStatement);

The above code snippet will not trigger Factory.createDebugStatement if debug=false. This approach aligns with the 'Build Once Deploy Many' strategy.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Encountering the error message "TypeError: Unable to access properties of null (reading 'get')" while utilizing useSearchParams within a Storybook file

Looking at the Next.js code in my component, I have the following: import { useSearchParams } from 'next/navigation'; const searchParams = useSearchParams(); const currentPage = parseInt(searchParams.get('page') || '', 10) || ...

React fails to acknowledge union types

I have the following types defined: export enum LayersItemOptionsEnum { OPERATOR, HEADER, } type sharedTypes = { children: string | ReactElement; }; type LayersItemStatic = sharedTypes & { label: string; option: LayersItemOptionsEnum; }; t ...

The dynamic duo of Typescript and Express creates an unbreakable bond within a configuration

Trying to incorporate ES6 modules into my app has been a bit frustrating. Initially, I attempted setting "module": "es2020" or "module": "esnext", only to encounter an error instructing me to specify "type": "module" in the package.json file or use the .m ...

sticky header on pinned tables in a React data grid

I have combined 3 tables together, with the middle table containing a minimum of 15 columns. This setup allows users to horizontally scroll through the additional columns conveniently. However, I am facing a challenge in implementing a sticky header featu ...

Having trouble integrating jQuery into an Angular CLI project

I'm trying to incorporate jQuery into my angular project created with angular cli. I followed the instructions provided on this website: To begin, I installed jQuery by running: npm install --save jquery; Next, I added type definitions for jQ ...

Managing enum types with json2typescript

After receiving a JSON response from the back-end that includes an Enum type, I need to deserialize it. The JSON looks like this: { ..., pst:['SMS','EMAIL'], ... } In Typescript, I have defined my enum class as follows: export enum Pos ...

Error: Bootstrap CSS missing from app created with create-react-app-ts

I have a React application that was initially set up using create-react-app-ts. Although I have included bootstrap and react-bootstrap for navigation, the CSS styling from Bootstrap does not seem to be applied properly. The links do not display any styling ...

Determining the return type of a function by analyzing its argument(s)

I'm interested in defining a method within a class that will have its type based on the argument provided in the constructor. For example: class A { private model: any; constructor(model: any) { this.model = model; } getModel( ...

Configuring a NestJS application to establish a TypeOrm connection using environment variables and @nestjs/config

Looking for the best way to set up a NestJS database using a .env file in compliance with legal requirements. The goal is to utilize the @nestjs/config package to import .env variables and incorporate them into the TypeOrmModule. It appears that utilizing ...

Receiving an error when triggering an onclick event for a checkbox in TypeScript

I am creating checkboxes within a table using TypeScript with the following code: generateTable(): void { var table = document.getElementById("table1") as HTMLTableElement; if (table.childElementCount == 2) { for (var x = 0; x < 2; x++) ...

Update the image on a webpage by simply clicking on the current image

Hey there, I'm looking to implement a feature where users can choose an image by clicking on the current image itself. Here's my code snippet. The variable url holds the default image. My goal is to make the current image clickable so that when ...

Adjust the size and color of text in Chart.js using Angular 5

Why does the font color in chartjs appear as light gray and not print when you want to do so from the page? I tried changing the font color of chartjs in the options attribute, but it didn't work. How can I change the font color in chartjs angular? ...

react-i18next: issues with translating strings

I encountered a frustrating issue with the react-i18next library. Despite my efforts, I was unable to successfully translate the strings in my application. The relevant code looked like this: App.tsx: import i18n from 'i18next'; import { initR ...

The Ionic 2 application encountering issues with building after the installation of the Facebook login plugin

Currently, I am in the process of developing a Hybrid app using Ionic-2 on Ubuntu. I encountered an issue when trying to add Facebook login functionality to my app. After installing the Facebook plugin, the app build fails. However, if I remove the Faceb ...

Develop an interactive React sidebar with changing elements

I'm in the process of developing a sidebar for a project, with the goal of making it similar to tools like Confluence. This means that we need the ability to rearrange documents and create subdirectory structures by simply moving the documents, with ...

Utilizing Angular 2 to retrieve and assign object properties provided by a service to a local variable within a

My video service: public getExercise(exerciseId): Observable<Exercise[]>{ let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers, withCredentials: t ...

Changes are reflected in the service variable, but they are not updating in the component

I am facing an issue with a variable that is supposed to track the progress of image uploads. The variable seems to be working fine, but it remains undefined in my component. Upload Service method uploadProfilePic(url:string, user_id:string, image:any) { ...

Struggling with the @typescript-eslint/no-var-requires error when trying to include @axe-core/react? Here's a step-by

I have integrated axe-core/react into my project by: npm install --save-dev @axe-core/react Now, to make it work, I included the following code snippet in my index.tsx file: if (process.env.NODE_ENV !== 'production') { const axe = require(&a ...

Apollo useQuery enables risky array destructuring of a tuple element containing any value

Currently, I am incorporating TypeScript into my project and have a GraphQL query definition that utilizes Apollo's useQuery. According to the documentation, the call should be typed, however, I am encountering an ESLint error regarding data being ass ...

Error in cypress configuration occurs when a plug is mistakenly defined as an import instead of a const within the cypress.config.ts file

Hello, I am new to using Cypress so please bear with me if this seems like a trivial issue. Below you will find my cypress.config.ts file where I am attempting to integrate the cypress esbuild preprocessor into the configuration. import cypress_esbuild_pre ...