配置具有公共依赖项的 TypeScript 项目以构建多个纯 JavaScript 输出文件

Posted

技术标签:

【中文标题】配置具有公共依赖项的 TypeScript 项目以构建多个纯 JavaScript 输出文件【英文标题】:Configure a TypeScript project with common dependencies to build multiple plain JavaScript output files 【发布时间】:2020-02-05 06:57:04 【问题描述】:

我目前正在为Bot Land 写some scripts。 Bot Land 是一款实时战略游戏,您无需使用鼠标和键盘控制您的单位,而是编写代码通过 API 控制您的机器人,然后您的机器人与其他机器人战斗。如果您熟悉 SC2 中的单位,您可以创建类似于眨眼追踪者、攻城坦克、医务人员和超级巨蜥的机器人。 (对于软件工程师来说,这是一款非常有趣的游戏,但这超出了这个问题的范围。)

Bot 控件具有三个不断增加的复杂性级别:默认 AI、类似Scratch 的编程语言和一组简化的 javascript,称为 BotLandScript。虽然 BotLandScript 的内置编辑器是合理的,但您必须将所有代码作为 一个文件 上传,并在任何地方使用全局***函数。当然,如果您的代码开始变得很长并且不同的机器人共享相同的功能,这会在一段时间后开始变得痛苦。

为了方便为多个机器人编写代码,减少在裸 JS 编码时出现意外错误的机会,并增加我击败其他玩家的机会,我设置了 above TypeScript project 为每个机器人提供公共库和代码我的机器人。当前目录结构大致如下:

lib/ 
  bot.land.d.ts
  common.ts
BlinkStalker/
  BlinkStalker.ts
  tsconfig.json
Artillery/
  Artillery.ts
  tsconfig.json
SmartMelee/
  SmartMelee.ts
  tsconfig.json

lib 是机器人之间共享的公共代码,并为(非 TS)Bot Land API 提供 TypeScript 定义。然后每个机器人都有自己的文件夹,其中一个文件包含机器人代码,另一个文件包含样板文件tsconfig.json


  "compilerOptions": 
    "target": "es3",
    "module": "none",
    "sourceMap": false,
    "outFile": "bot.js"
  ,
  "files": [
    "MissileKite.ts"
  ],
  "include": [
    "../lib/**/*"
  ]

当每个tsconfig.json 被构建时,它会创建一个对应的bot.js,其中包含来自机器人本身的转译代码以及所有common.js 中的代码。由于以下几个原因,这种设置不是最理想的:它需要大量重复的样板,难以添加新的机器人,每个机器人都包含大量不必要的代码,并且需要单独构建每个机器人。

但是,基于my research so far,似乎没有一种简单的方法可以做我想做的事。特别是,使用新的 tsc -b 选项和引用不起作用,因为这需要将代码模块化,而 Bot Land 需要一个文件,其中所有函数都在顶层定义。

尽可能多地实现以下目标的最佳方法是什么?

添加新机器人不需要新样板(例如,每个机器人不需要 tsconfig.json) 对常用函数使用import,以避免输出未使用的代码,但随后... 仍然以 Bot Land 的特定格式将所有函数输出为一个文件 生成多个输出文件的单个构建步骤,每个机器人一个 奖励:将构建过程与 VS Code 集成。目前有一个对应的样板文件tasks.json 用于构建每个子项目。

我隐约猜测答案可能涉及到像 Grunt 和 tsc 这样的东西,但我对此知之甚少,无法确定。

【问题讨论】:

所有机器人都必须有单独的文件夹吗?还是每个机器人都位于单个文件的根级别就足够了? (例如<root>/MissileKite.ts 所有转译的机器人文件是否必须命名为bot.js 根在单个文件中会更好;由于单独的tsconfig.json,它们位于单独的文件夹中。转译的机器人文件可以命名为任何名称,最好是原始文件的 .js 版本。我现在在输出到 build/MissileKite.js 的 repo 中设置了这种方式。 @andrew-mao 你可以看看我的 GAS 项目模板,它可以满足你的大部分需求(但针对不同的环境)如果它适合你,我可能会为你调整它下周某个时候。 github.com/PopGoesTheWza/ts-gas-project-starter tsconfig-gas.json 是相关的东西吗? 【参考方案1】:

您实际上可以使用项目引用。按照以下步骤获得与原始文件相同的结果,所有函数都位于一个文件的顶层。但是,我找不到在机器人中仅导入所需功能的解决方案。也就是说,不使用导入和导出。

在根目录下的 tsconfig.json 中


    "files": [],
    "references": [
         "path": "./lib" 
         "path": "./AggroMiner" 
         "path": "./ArtilleryMicro" 
         "path": "./MissileKite" 
         "path": "./SmartMelee" 
         "path": "./ZapKite" 
    ]


接下来,在你的 lib 文件夹中,像这样添加一个 tsconfig.json


  "compilerOptions": 
    "declaration": true,
    "declarationMap": true,
    "composite": true,
    "rootDir": ".",
    "outFile": "../build/lib.js",
    "target": "es3",
    "removeComments": true,
    "sourceMap": false,
  ,
  "files": [
    "data.ts",
    "movement.ts",
    "utils.ts"
  ]

我们需要对 data.ts、movement.ts 和 utils.ts 进行一些调整,以便 ts 不会因为编译错误而困扰我们。

数据.ts

/// <reference path="./bot.land.d.ts"/>

(...)

运动.ts


/// <reference path="./data.ts"/>
/// <reference path="./utils.ts"/>
(...)

utils.ts

/// <reference path="./bot.land.d.ts"/>
(...)

接下来,我们在根目录添加 base.json(机器人的 tsconfig.json 会扩展它)。

base.json


  "compilerOptions": 
    "declaration": true,
    "composite": true,
    "rootDir": ".",
    "target": "es3",
    "removeComments": true,
    "sourceMap": false,
  

和机器人的 tsconfig.json(根据机器人适配)


  "extends": "../base",
  "compilerOptions": 
    "outFile": "../build/AggroMiner.js",
  ,
  "files": [
    "AggroMiner.ts"
  ],
  "references": [
       "path": "../lib", "prepend": true  //note the prepend: true
  ]

就是这样。现在就运行

tsc -b

【讨论】:

所以我想到了这样的东西,但它不起作用的原因是因为在你的分支上输出的文件在顶部有一堆这样的东西,而游戏需要一个包含所有功能的文件。因此,我必须手动将所有已编译的输出拼凑在一起以创建我要上传的文件,而不是仅仅复制粘贴文件。 ` "使用严格";出口.__esModule = true; var data_1 = require("../lib/data"); var motion_1 = require("../lib/movement"); var utils_1 = require("../lib/utils"); ` 但它可以工作,因为 lib 也在构建文件夹中输出(构建)(感谢引用)。 我正在编辑我的评论 - 见上文。或者看看你构建原始仓库时输出的build/MissileKite.js @AndrewMao 抱歉,现在我才明白你的意思是“因为这需要模块化代码,而 Bot Land 需要一个包含在顶层定义的所有函数的单个文件。”。我考虑过使用“prepend:true”,但这需要使用 outFile 并且 ts 不会让我们编译 lib 中的文件,因为有些文件依赖于其他文件。 @AndrewMao 我已经添加了 Webpack 支持。我编辑了帖子并将更改推送到 repo 上。让我知道它是否更好。【参考方案2】:

这里是我的attempt 来回答您的要求。

值得注意的文件:

src/tsconfig-botland.json 保存任何 bot.land 脚本的设置(包括我移至 types/bot-land/index.d.ts 的自定义声明)。您可以更改我使用的strict 设置。 src/tsconfig.json 包含对您所有机器人的引用。这是您想要添加另一个机器人脚本时要编辑的文件

一个机器人脚本至少包含两个文件:一个极简的tsconfig.json 和一个或多个.ts 脚​​本文件。

例如src/AggroMiner/tsconfig.json:


    "extends": "../tsconfig-botland",
    "compilerOptions": 
        "outFile": "../../build/AggroMiner.js"
    ,
    "files": ["index.ts"],
    "include": ["**/*.ts", "../lib/**/*.ts"]

在大多数情况下,要启动新的机器人脚本,您应该:

    将任何机器人文件夹(即src/AggroMiner)复制到src下的新文件夹中 编辑 src/&lt;newBotFolder&gt;/tsconfig.json 以使用您的机器人名称编辑 outFile 编辑src/tsconfig.json 并添加对src/&lt;newBotFolder&gt; 的引用

已设置以下npm/yarn 脚本:

build 构建所有机器人 build-clean 在运行 build 之前清除 build 文件夹 formatsrc 下的所有 .ts 文件上运行 Prettier lint 对所有机器人脚本运行 tslint 检查

现在满足您的要求:

添加新机器人不需要新样板(例如,每个机器人不需要 tsconfig.json)

要实现这一点,需要创建一些脚本来枚举您的机器人文件夹/脚本...并为每个机器人设置相关的 tsconfig.json 并运行 tsc。除非绝对必要,否则最少的设置(如上所述)可能就足够了。

对常用函数使用 import 以避免输出未使用的代码,但随后...

首先,请注意,如果您开始使用任何模块 export/import 语句,您将需要额外的第 3 方来打包/treeshake 以实现单个文件输出。从我可以收集到的 Bot.land 中,您的脚本正在服务器上运行。除非死代码对您的机器人性能有影响,否则我不会真正打扰。

仍然以 Bot Land 的特定格式将所有函数输出为一个文件

完成。

生成多个输出文件的单个构建步骤,每个机器人一个

完成。

奖励:将构建过程与 VS Code 集成。目前有一个对应的样板 tasks.json 用于构建每个子项目。

npm 脚本应该出现在 vsc 的任务列表中(至少它们在我的任务列表中是这样),因此不需要 tasks.json

【讨论】:

Deadcode 是您在此处所做的其他一切的完美妥协;您能告诉我为什么您使用types/bot-land 进行定义以及为什么选择strict 设置吗? Types/bot-land/index.d.ts 确实是您的原始 .d.ts 来自 lib,重命名和放置方式不同。 Î 假设它描述了所有脚本的一般 bot.land 执行上下文,因此我确保它在每个机器人脚本中始终可用。 “严格”设置只在这里,因为我懒惰地复制了我喜欢的设置(对于更漂亮的设置也是如此)。这些应该适应用户(你)的偏好。 我只是想知道是否有习惯上的理由将其放入types,或者这只是您选择的一种特定的组织方式。 唯一的原因是假设它是 bot.land 上下文。把它想象成在你的 nodejs 脚本中已经有了 @types/node 类型 /types 文件夹是放置外部类型声明的常规位置之一(即特定的执行上下文,如 botland 引擎或无类型的 JavaScript 模块/包,此处未使用)

以上是关于配置具有公共依赖项的 TypeScript 项目以构建多个纯 JavaScript 输出文件的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用 jasmine 对 Node typescript 项目进行 Karma 单元测试会显示包含依赖项的覆盖范围?

使用 Typescript 和 Webpack 管理依赖项的 AngularJS

如何在另一个具有更高Spring版本的Spring项目中使用具有Spring作为打包依赖项的jar?

如何使用spring boot plugin 2.0.x从一个具有不同依赖项的gradle项目生成2个jar

具有项目编译依赖项的 Gradle 嵌套多项目

为啥 Pip 会忽略已配置的具有嵌套依赖项的存储库?