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,它的解决思路很直接:
- 分析所有模块依赖
- 把所有代码内联到一个文件
- 干掉所有
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 啊!试了一下,果然可以,整个链路瞬间通了。
最终的构建流程:
- grunt-exec 调用 tsup 编译
- 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,别折腾了。
快速开始:
-
装依赖:
shellnpm 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
-
创建
.screeps.json
:json{ "token": "你的token", "branch": "你的分支名,在游戏中可以选", "ptr": false }
-
配置
Gruntfile.js
(参考上面的配置) -
运行
npm run build
(需要把package.json
中build
配置为grunt
) 或直接grunt
,搞定!
希望这篇文章能帮其他 Screeps 玩家少踩点坑。Happy coding!