如何“转译掉”日志语句

huangapple go评论57阅读模式
英文:

How to "Transpile away" log statements

问题

// 在我的TypeScript项目中,有大量代码仅用于创建调试信息。在对我的应用程序进行性能分析后,发现这些信息会对性能产生重大影响。

// 我当前的方法是设置一个全局变量 `let debug=true`,然后:

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

// 应用程序的某个地方
...
log(`hi mom! ${Factory.createDebugStatement()}`);
...

// 而且 `Factory.createDebugStatement()` 的计算开销很大。
// 此外,我希望避免在我的应用程序中出现 `if(debug) log(...)`。我希望TypeScript可以删除整个日志语句。

// 基本上,我正在寻找以下 `C` 结构中的 TypeScript 等效方式:

// 头文件
#ifdef ENABLE_DEBUG
#define DEBUG(...) printf(__VA_ARGS__)
#else
#define DEBUG(...)                                                             \
  {}
#endif

// 应用程序

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

我明白你需要的只是代码部分的翻译。

英文:

I have my typescript project which has a bunch of code which only creates debug information. After profiling my application it turns out, that those have significant performance impact.

My current approach is 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()}`);
...

And this Factory.createDebugStatement() is computationally heavy.
Further, I want to avoid to clutter my application with if(debug) log(...). I want typescript to remove the entire log statement.

Basically, I'm looking for a (convenient) TS 'equivalent' of the the idea in the following C structure:

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

//application

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

Then, I can use cc -DENABLE_DEBUG=1 main.c to have those debug messages, if I omit the -DENABLE_DEBUG, the entire code is removed.

答案1

得分: 2

关于这部分代码,你通常可以使用具有“Tree-shaking”功能的捆绑工具,例如 https://esbuild.github.io/api/#tree-shakinghttps://parceljs.org/features/scope-hoisting/

在 esbuild 中,这相对容易:https://esbuild.github.io/api/#define

您可以定义 debug 变量*,捆绑工具会自动删除永远不会执行的代码分支。

*虽然你应该使用另一个名称,比如 DEBUG_YOUR_PACKAGE_NAME,以减少与来自依赖项的代码冲突的可能性。

英文:

For that I'd normally use a bundler that has "Tree-shaking", for example https://esbuild.github.io/api/#tree-shaking or https://parceljs.org/features/scope-hoisting/

In esbuild it's relatively easy: https://esbuild.github.io/api/#define

You define the debug variable* and the bundler automatically removes code branches that are never executed.

*Though you should use another name like DEBUG_YOUR_PACKAGE_NAME that is less likely to clash with code from your dependencies)

答案2

得分: 1

Credit: 感谢 Simon Lax 指导我关注捆绑部分...

我正在使用 rollup.js,以及 plugin-strip 插件。
然后,在我的 rollup.config.js 中,通过一个开关,我可以启用/禁用 '转译掉'。

// 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"
  },

然后在运行 npm run bundle 后,它将创建精简版本

// 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", "");

在运行 DEBUG=1 npm run bundle 后,它将创建完整版本

// 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") ?? "");

(为了完整性,我的 tsconfig.json)

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

Credit: Thanks to Simon Lax for pointing me to focus on the bundler parts...

I am using rollup.js, with the plugin-strip plugin.
Then, with one switch in my rollup.config.js I can enable/disable the 'transpile away'

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

class Logger {
  public static log&lt;T&gt;(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(
  &quot;/tmp/abc&quot;,
  Logger.log(
    &quot;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&#39;ish concealing&quot;,
  ) ?? &quot;&quot;,
);
// rollup.config.js
import typescript from &quot;@rollup/plugin-typescript&quot;;
import strip from &quot;@rollup/plugin-strip&quot;;

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

then after running npm run bundle it will create the stripped version

// dist/main.js
import fs from &#39;fs&#39;;

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

and running DEBUG=1 npm run bundle it will create the full version

// dist/main.js
import fs from &#39;fs&#39;;

class Service {
    expensive(res = &quot;&quot;) {
        for (let i = 0; i &lt; 1000; i++)
            res += i;
        return res;
    }
    normal() {
        return &quot;Hi mom!&quot;;
    }
}
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(&quot;/tmp/abc&quot;, Logger.log(&quot;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&#39;ish concealing&quot;) ?? &quot;&quot;);

(for completeness, my tsconfig.json)

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

答案3

得分: 0

如果您在使用Rollup时遇到问题以剥离这些语句,或者您可以通过将Factory.createDebugStatement作为引用传递来阻止其被调用。

例如:

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(''));
  }
}

// 应用程序的某个地方
log('hi %s mom!', Factory.createDebugStatement.bind(null, 'hello', 'world'));
// 输出: hi hello-world mom!

当然,如果Factory.createDebugStatement不接受任何参数,那么您可以简单地这样做...

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

如果debug=false,上述代码将不会调用Factory.createDebugStatement
并且这与“一次构建,多次部署”的策略一致。

英文:

If you're having trouble with Rollup to shake those statements off, alternatively you can prevent the Factory.createDebugStatement() from being invoked by passing Factory.createDebugStatement as a reference.

e.g.

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

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

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

Of course if your Factory.createDebugStatement don't take any argument, then you can simply do...

log(&#39;hi %s mom!&#39;, Factory.createDebugStatement);

The above won't invoke Factory.createDebugStatement if debug=false.
And this complies with 'Build Once Deploy Many' strategy.

huangapple
  • 本文由 发表于 2023年2月16日 13:55:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/75468361.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定