Screeps TypeScript 教程:使用 tsup 解决模块加载问题并实现自动化部署

1. 问题背景

本文主要解决ts环境下的文件夹问题,适合新手无框架从0开始的场景

Screeps 是个挺有意思的游戏,可以通过写代码来控制游戏单位。虽然官方推荐用 JavaScript,但作为一名类型体操爱好者,我还是选择了更符合我尊贵身份的TypeScript,嘿嘿。

一个月前开始写 Screeps,一开始挺顺利的。用 tsc 编译,然后游戏客户端会自动监控目录变化并上传代码,简单粗暴。但随着代码越来越多,我需要更灵活的部署方式,然后就踩了一系列的坑。

2. 演进过程中的两个核心问题

2.1 问题一:Screeps 不支持文件夹结构

当代码多到需要分目录管理时,麻烦来了。我的项目结构是这样的:

css 复制代码
src/
  ├── main.ts
  ├── roles/
  ├── structures/
  └── utils/

tsc 编译后,输出目录保持了相同的结构。结果 Screeps 直接报错: Error: Unknown module 'roles/harvester'

为啥会这样?

调查后发现,Screeps 用的是魔改版的 CommonJS 模块系统。它实现了个简化版的 require(),这玩意只会在扁平的命名空间里找模块:

js 复制代码
// 在 Screeps 里,这些写法都不行:
require('./roles/harvester')  // ❌ 不支持相对路径
require('roles/harvester')    // ❌ 不支持子目录

// 只能这样:
require('harvester')          // ✅ 所有模块必须在根目录

说白了,Screeps 为了简化服务器端的模块加载,直接不支持目录结构了。

官方给出的方案只适合js,需要另寻ts方案,一路艰辛探索略过,直接上结论。

解决办法:用 tsup 打包成单文件

我的 tsconfig.json 里虽然配了 "module": "amd""outFile"(这是之前尝试让 tsc 生成单文件时留下的),但 tsup 根本不理这些配置:

json 复制代码
// tsconfig.json 关键配置
{
  "compilerOptions": {
    "target": "es2017",
    "module": "amd",              // tsup 会忽略
    "rootDir": "./src",
    "outFile": "./dist/main.js",  // tsup 也会忽略
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true
  }
}

tsup 基于 esbuild,它的解决思路很直接:

  1. 分析所有模块依赖
  2. 把所有代码内联到一个文件
  3. 干掉所有 require() 调用

简单来说就是:

ts 复制代码
// 打包前:多个文件,有模块依赖
// main.ts
import { Harvester } from './roles/harvester';

// 打包后:单文件,所有代码都内联了
var Harvester = class { ... };  // harvester 的代码被内联
// main 的代码直接使用 Harvester,没有 require

选 tsup 的原因很简单 ------ 零配置!它自动读取 tsconfig.json,默认输出 CommonJS 格式的单文件,正好符合 Screeps 的要求。

2.2 问题二:脱离游戏客户端上传

一直依赖游戏客户端上传代码太不方便了:得一直开着客户端、没法 CI/CD、多服务器部署也麻烦。

解决思路: Grunt + grunt-screeps 自动化上传。

但实现过程中踩了不少坑。最开始想用 grunt-ts 来编译 TypeScript,结果发现这货好几年没维护了,处理单文件输出时直接报错。

然后我想,那就用 grunt-exec 调 tsc 吧,把 module 改成 "amd"。结果生成的代码里全是 define 函数调用,而 Screeps 环境里根本没有 AMD 加载器。

折腾了半天,突然灵光一闪:grunt-exec 能调 tsc,那也能调 tsup 啊!试了一下,果然可以,整个链路瞬间通了。

最终的构建流程:

  1. grunt-exec 调用 tsup 编译
  2. grunt-screeps 上传到服务器

核心配置:

js 复制代码
// Gruntfile.js
module.exports = function (grunt) {
    let config = require('./.screeps.json')
    let screepsConfig = {
        token: grunt.option('token') || config.token,
        branch: grunt.option('branch') || config.branch,
        ptr: grunt.option('ptr') || config.ptr
    }

    // 配置各个任务数据
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        exec: {
            tsupBuild: {
                command: 'tsup ./src/main.ts --out-dir ./dist'
            }
        },
        watch: {
            scripts: {
                files: ['**/*.ts'],
                tasks: ['exec:tsc'],
                options: {
                    spawn: false,
                }
            }
        },
        screeps: {
            options: {
                token: screepsConfig.token,
                branch: screepsConfig.branch,
                ptr: screepsConfig.ptr,
                //server: 'season'
            },
            dist: {
                src: ['dist/*.js']
            }
        },
    });
    // 启用插件
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-exec');
    grunt.loadNpmTasks('grunt-screeps');
    // 配置默认任务,用于命令行直接执行grunt
    grunt.registerTask('default', ['exec:tsupBuild', 'screeps']);
}

3. 最终方案配置要点

项目结构长这样:

bash 复制代码
├── src/              # TypeScript 源码
│   ├── main.ts
│   ├── roles/
│   ├── structures/
│   └── utils/
├── dist/             # 编译输出
├── .screeps.json     # 认证配置(别提交到 git!)
├── Gruntfile.js      # Grunt 配置
├── tsconfig.json
└── package.json

tsup 配置

tsup 最大的优点就是不用配置:

  • 自动读 tsconfig.json
  • 默认打包成单文件(我的项目打包后大概 48KB)
  • 输出的 CommonJS 格式 Screeps 能直接跑

Grunt 任务链

关键是用 grunt-exec 调 tsup,别去碰那些过时的插件或者折腾 tsc 的模块配置。

认证处理

按官方建议,把认证信息放在 .screeps.json 里:

json 复制代码
// .screeps.json
{
  "token": "你的token",
  "branch": "你的分支名,在游戏中可以选",
  "ptr": false
}

然后在 Gruntfile.js 里读取:

js 复制代码
module.exports = function (grunt) {
    let config = require('./.screeps.json')
    let screepsConfig = {
        token: grunt.option('token') || config.token,
        branch: grunt.option('branch') || config.branch,
        ptr: grunt.option('ptr') || config.ptr
    }

    grunt.initConfig({
        // ... 其他配置
        screeps: {
            options: {
                token: screepsConfig.token,
                branch: screepsConfig.branch,
                ptr: screepsConfig.ptr
            },
            dist: {
                src: ['dist/*.js']
            }
        }
    });
}

这样做的好处:

  • 认证信息和代码分离,记得加到 .gitignore
  • 支持命令行覆盖:grunt --token=xxx --branch=dev
  • 方便切换分支和服务器

4. 总结

方案对比:

  • 初始方案(tsc + 客户端):简单但不灵活
  • 最终方案(tsup + Grunt):配置稍微麻烦点,但自动化爽歪歪

最大的坑: 别在 tsc 的各种模块格式(AMD、System)上浪费时间了,直接上打包工具。我在这上面浪费了太多时间,其实用对工具一下就解决了。

一句话建议: Screeps + TypeScript 开发,直接 tsup + grunt-screeps,别折腾了。

快速开始:

  1. 装依赖:

    shell 复制代码
    npm install --save-dev typescript@^5.8.3 tsup@^8.5.0 grunt@^1.6.1 grunt-exec@^3.0.0 grunt-screeps@^1.5.0
    npm install @types/screeps@^3.3.8
  2. 创建 .screeps.json

    json 复制代码
    {
      "token": "你的token",
      "branch": "你的分支名,在游戏中可以选",
      "ptr": false
    }
  3. 配置 Gruntfile.js(参考上面的配置)

  4. 运行 npm run build(需要把package.jsonbuild配置为grunt) 或直接 grunt,搞定!

希望这篇文章能帮其他 Screeps 玩家少踩点坑。Happy coding!

相关推荐
张志鹏PHP全栈2 天前
TypeScript 第四天,TypeScript的编译选项(一)
前端·typescript
Toomey2 天前
别再用 Parameters 乱推断了!vue-i18n 封装 t 函数的正确姿势
typescript
郑板桥302 天前
ts学习1
学习·typescript
前端拿破轮2 天前
女朋友要和我分手?!!居然是因为交不出赎金信,不会用哈希表😭😭😭
算法·leetcode·typescript
知识分享小能手2 天前
Bootstrap 5学习教程,从入门到精通,Bootstrap 5 表单验证语法知识点及案例代码(34)
前端·javascript·学习·typescript·bootstrap·html·css3
张志鹏PHP全栈3 天前
TypeScript 第三天,TypeScript中的类型(二)
typescript
成遇3 天前
Eslint基础使用
javascript·typescript·es6
张志鹏PHP全栈4 天前
TypeScript 第二天,TypeScript中的类型(一)
typescript
極光未晚4 天前
TypeScript在前端项目中的那些事儿:不止于类型的守护者
前端·javascript·typescript