gpt4 book ai didi

caching - 从 TypeScript 文件中收集引用依赖树

转载 作者:行者123 更新时间:2023-12-04 14:37:58 25 4
gpt4 key购买 nike

问题:

关于在服务器端编译 TypeScript 代码,有没有办法获取单个 .ts 文件或更好的整个编译(从单个 .ts 文件开始)的所有引用路径的列表?按顺序,最好。

如果可能,我更愿意使用现有的解析器,而不是用新代码解析文件。

上下文:

由于我不认为它确实存在,所以我想写:

  1. 一个服务器端网络用户控件,它采用 .ts 路径并生成指向
  2. 的缓存破坏脚本标记
  3. 一个 HttpHandler,它在第一次请求时编译请求的 .ts 文件一次,然后将 CacheDependencies 添加到所有引用依赖项路径。当文件更改时,生成脚本的 Web 用户控件会更新其后续请求的缓存清除后缀。

所以在 Release模式下,<tsb:typescript root="app.ts" runat="server" />产量

<script type="text/javascript" src="app.ts?32490839"></script>

交付的脚本是按需缓存的单文件脚本。

在 Debug模式下,未修改的标签会产生:

<script type="text/javascript" src="dependency1.ts?32490839"></script>
<script type="text/javascript" src="dependency2.ts?32490839"></script>
<script type="text/javascript" src="app.ts?32490839"></script>

据我所知,TypeScript Visual Studio 插件和任何优化器 bundler 都不支持这种操作模式。 bundler 确实接近我的要求,但它们不会缓存破坏,也不会在没有恼人的文件显式捆绑的情况下进行单文件编译。

我不介意在编译脚本时在第一个请求时出现任何性能影响。除此之外,也许有一个非常好的理由认为这个设置不应该或不能存在。如果这不能或显然不应该这样做,我也会很感激这样的答案。

我在 StackOverflow 上看到其他问题在我的解释中围绕着这种愿望跳舞,但没有一个问题像这个问题那么明确,也没有一个问题有相关的答案。

谢谢!

此外,在不同的进程中执行 tsc.exe 是否是我的 HttpHandler 在运行时编译的最佳方式,或者是否有一种灵活、安全且简单的方式在进程中执行此操作?

最佳答案

2021 年我们有 --explainFiles

如果您想更仔细地检查您的代码库(例如区分仅类型导入和运行时导入),您可以使用 Typescript API。可能性是无限的,但也许下面的这些部分可以帮助您朝着正确的方向前进(可能有错误):

import * as ts from "typescript";

interface FoundReference {
typeOnly: boolean;
relativePathReference: boolean;
referencingPath: string;
referencedSpecifier: string;
}

const specifierRelativeFile = /^\..*(?<!\.(less|svg|png|woff))$/;
const specifierNodeModule = /^[^\.]/;

const diveDeeper = (path: string, node: ts.Node, found: FoundReference[]) =>
Promise.all(node.getChildren().map(n => findAllReferencesNode(path, n, found)));

const findAllReferencesNode = async (path: string, node: ts.Node, found: FoundReference[]) => {
switch (node.kind) {
case ts.SyntaxKind.ExportDeclaration:
const exportDeclaration = node as ts.ExportDeclaration;

if (exportDeclaration.moduleSpecifier) {
const specifier = (exportDeclaration.moduleSpecifier as ts.StringLiteral).text;

if (specifier) {
if (specifierRelativeFile.test(specifier)) {
found.push({
typeOnly: exportDeclaration.isTypeOnly,
relativePathReference: true,
referencingPath: path,
referencedSpecifier: specifier
});
} else if (specifierNodeModule.test(specifier)) {
found.push({
typeOnly: exportDeclaration.isTypeOnly,
relativePathReference: false,
referencingPath: path,
referencedSpecifier: specifier
});
}
}
}

break;
case ts.SyntaxKind.ImportDeclaration:
const importDeclaration = node as ts.ImportDeclaration;
const importClause = importDeclaration.importClause;

const specifier = (importDeclaration.moduleSpecifier as ts.StringLiteral).text;

if (specifier) {
if (specifierRelativeFile.test(specifier)) {
found.push({
typeOnly: (!!importClause && !importClause.isTypeOnly),
relativePathReference: true,
referencingPath: path,
referencedSpecifier: specifier
});
} else if (specifierNodeModule.test(specifier)) {
found.push({
typeOnly: (!!importClause && !importClause.isTypeOnly),
relativePathReference: false,
referencingPath: path,
referencedSpecifier: specifier
});
}
}

break;
case ts.SyntaxKind.CallExpression:
const callExpression = node as ts.CallExpression;

if ((callExpression.expression.kind === ts.SyntaxKind.ImportKeyword ||
(callExpression.expression.kind === ts.SyntaxKind.Identifier &&
callExpression.expression.getText() === "require")) &&
callExpression.arguments[0]?.kind === ts.SyntaxKind.StringLiteral) {

const specifier = (callExpression.arguments[0] as ts.StringLiteral).text;

if (specifierRelativeFile.test(specifier)) {
found.push({
typeOnly: false,
relativePathReference: true,
referencingPath: path,
referencedSpecifier: specifier
});
} else if (specifierNodeModule.test(specifier)) {
found.push({
typeOnly: false,
relativePathReference: false,
referencingPath: path,
referencedSpecifier: specifier
});
} else {
await diveDeeper(path, node, found);
}
} else {
await diveDeeper(path, node, found);
}

break;
default:
await diveDeeper(path, node, found);

break;
}
}

const path = "example.ts";

const source = `
import foo from "./foo";
import * as bar from "./bar";
import { buzz } from "./fizz/buzz";

export foo from "./foo";
export * as bar from "./bar";
export { buzz } from "./fizz/buzz";

const whatever = require("whatever");

const stuff = async () => {
require("whatever");

const x = await import("xyz");
}
`

const rootNode = ts.createSourceFile(
path,
source,
ts.ScriptTarget.Latest,
/*setParentNodes */ true
);

const found: FoundReference[] = [];

findAllReferencesNode(path, rootNode, found)
.then(() => {
console.log(found);
});

[
{
"typeOnly": true,
"relativePathReference": true,
"referencingPath": "example.ts",
"referencedSpecifier": "./foo"
},
{
"typeOnly": true,
"relativePathReference": true,
"referencingPath": "example.ts",
"referencedSpecifier": "./bar"
},
{
"typeOnly": true,
"relativePathReference": true,
"referencingPath": "example.ts",
"referencedSpecifier": "./fizz/buzz"
},
{
"typeOnly": false,
"relativePathReference": true,
"referencingPath": "example.ts",
"referencedSpecifier": "./bar"
},
{
"typeOnly": false,
"relativePathReference": true,
"referencingPath": "example.ts",
"referencedSpecifier": "./fizz/buzz"
},
{
"typeOnly": false,
"relativePathReference": false,
"referencingPath": "example.ts",
"referencedSpecifier": "whatever"
},
{
"typeOnly": false,
"relativePathReference": false,
"referencingPath": "example.ts",
"referencedSpecifier": "whatever"
},
{
"typeOnly": false,
"relativePathReference": false,
"referencingPath": "example.ts",
"referencedSpecifier": "xyz"
}
]

获得 referencedSpecifier 后,您需要一些基本逻辑来将其解析为下一个路径,并以递归方式对下一个解析文件重复探索。

关于caching - 从 TypeScript 文件中收集引用依赖树,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14646055/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com