英文:
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-shaking 或 https://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 "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"
},
then after running npm run bundle
it will create the stripped 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!";
}
}
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", "");
and running DEBUG=1 npm run bundle
it 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") ?? "");
(for completeness, my tsconfig.json
)
// tsconfig.json
{
"compilerOptions": {
"outDir": "dist",
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"baseUrl": "."
},
"ts-node": {
"transpileOnly": 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) => {
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!
Of course if your Factory.createDebugStatement
don't take any argument, then you can simply do...
log('hi %s mom!', Factory.createDebugStatement);
The above won't invoke Factory.createDebugStatement
if debug=false
.
And this complies with 'Build Once Deploy Many' strategy.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论