3

问题:

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

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

语境:

由于我认为它不完全存在,我想写:

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

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

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

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

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

<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 插件和任何优化器捆绑程序都不支持这种操作模式。捆绑器确实接近我的要求,但它们不会缓存破坏,并且它们不会在不烦人的文件显式捆绑的情况下进行单文件编译。

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

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

谢谢!

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

4

3 回答 3

1

如果您启用优化并使用捆绑 HTML 帮助程序,捆绑会为 JavaScript 执行此操作,也可以将捆绑添加到页面。cache-bust URL 是 JavaScript 文件的简短哈希,因此如果您更改包中的任何脚本,新的哈希会将其从客户端缓存中分离出来。

我的建议是捆绑编译后的 JavaScript,并让捆绑器缩小和缓存破坏。您可以使用 out 标志让 TypeScript 生成具有所有依赖项的单个文件...

tsc --out final.js app.ts

你的包现在只需要包含 final.js - 它节省了显式列出所有文件并在以后添加新文件。

您仍然可以编写一些东西来在运行时拦截和编译。我的偏好是在运行时之前完成这项工作,因为 TypeScript 的好处之一是编译时检查——如果客户端需要脚本,你将如何处理编译错误。即使您在运行时执行此操作,我仍然会将 JavaScript 引用添加到页面,而不是 .ts 文件。

于 2013-02-03T08:12:55.203 回答
1

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,你需要一些基本的逻辑来将它解析到下一个路径,并用下一个解析的文件重复你的探索,递归。

于 2021-09-16T14:46:44.263 回答
1

您可以以交互方式或在节点中使用Madge 。

另请参阅:如何查看从给定文件开始的完整 nodejs“require()”树?

于 2021-06-16T01:39:07.777 回答