进阶功能(六):创建本地共享包
当你开发的 ArenaPro 项目越来越多时,你一定会遇到这个问题:
“我在 A 项目里写了一套超级好用的工具函数 (
utils.ts),现在 B 项目和 C 项目也想用,难道我只能复制粘贴三遍吗?”
复制粘贴是万恶之源。一旦原始代码有任何更新,你就得手动同步到所有项目中,这非常痛苦且容易出错。
旧的方法是是靠修改 tsconfig.json 和使用混乱的相对路径 (../../...) 来实现跨项目引用,这非常脆弱且难以维护。
正确、现代且强大的解决方案是:将你的共享代码做成一个本地 NPM 包。
什么是本地 NPM 包?
简单来说,就是把你的共享代码(如 my-game-utils)组织成一个独立的、像 lodash 一样的包。然后,你的其他项目(Project-A, Project-B)可以像安装任何第三方库一样“安装”这个本地包。
这样做的好处是巨大的:
- 清晰的依赖关系:你的项目不再通过混乱的相对路径引用文件,而是通过包名 (
import { something } from 'my-game-utils'),一目了然。 - 强大的封装:项目 A 不需要知道
my-game-utils内部的文件结构,它只关心能import什么。 - 版本控制:你的共享包可以在
package.json中拥有自己的版本号。 - 可发布:如果未来需要,这个本地包可以被轻松地发布到 NPM 官方或私有仓库,供更多人使用。
实战演练:创建并使用一个本地共享包
第 1 步:创建共享包的项目结构
首先,在你的所有 ArenaPro 项目之外,创建一个新的文件夹作为共享包的根目录,例如 my-shared-lib。
my-shared-lib 的内部结构应该如下:
my-shared-lib/
├── src/
│ └── index.ts # 你的共享代码写在这里
├── package.json # 包的清单文件
└── tsconfig.json # TypeScript 配置文件my-shared-lib/package.json: 这是包的核心。name 是你的包名,main 指向编译后的入口文件,types 指向类型定义文件。
{
"name": "my-shared-lib",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc"
},
"devDependencies": {
"typescript": "^4.5.4"
}
}my-shared-lib/tsconfig.json: 一个精简的 tsconfig,注意 declaration: true,它会自动生成 .d.ts 类型文件。
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"outDir": "./dist",
"declaration": true, // 非常重要!
"esModuleInterop": true
},
"include": ["src/**/*"]
}第 2 步:编写并编译共享代码
在 my-shared-lib/src/index.ts 中写入你想共享的函数,并 export 它们。
// my-shared-lib/src/index.ts
/**
* 计算两个数的和
*/
export function add(a: number, b: number): number {
return a + b;
}
/**
* 将一个数字限制在最小和最大值之间
*/
export function clamp(value: number, min: number, max: number): number {
return Math.max(min, Math.min(max, value));
}写完后,在 my-shared-lib 目录下打开终端,运行 npm install 安装开发依赖 (typescript),然后运行 npm run build。这会生成一个 dist 文件夹,里面包含了编译好的 index.js 和 index.d.ts。
第 3 步:在你的游戏项目中“安装”本地包
现在,回到你的 ArenaPro 项目(例如 Project-A)。
- 打开这个项目的
package.json文件。 - 在
dependencies中,手动添加一行,key 是你的本地包名,value 是file:加上你的本地包的相对路径。 - 路径分隔符请使用正斜杠
/,即使在 Windows 上也是如此。
// Project-A/package.json
{
"name": "project-a",
// ...
"dependencies": {
// ... 其他依赖
// "file:../my-shared-lib" 指向相对于当前项目的路径
"my-shared-lib": "file:../my-shared-lib"
}
}- 保存
package.json后,在Project-A的根目录运行npm install。
NPM 会识别出 file: 协议,并 在Project-A/node_modules/ 下创建一个指向 my-shared-lib 的符号链接 (Symbolic Link)。
第 4 步:在游戏项目中无缝使用!
现在,你可以像使用 lodash 一样,在你的游戏代码中直接通过包名引用共享代码了!
// Project-A/src/server/App.ts
// 引用变得非常干净!
import { add, clamp } from "my-shared-lib";
export default {
onLoad() {
const sum = add(10, 20); // 30
console.log(`10 + 20 = ${sum}`);
const clampedValue = clamp(150, 0, 100); // 100
console.log(`Clamped value is: ${clampedValue}`);
},
};当你更新了 my-shared-lib 中的代码后,只需要在该目录运行 npm run build,所有安装了这个包的项目都会立刻获得更新,无需重新 npm install。这就是符号链接的魔力。
它允许你将一个大型代码库拆分成多个独立的、可独立构建的子项目,同时又能保持它们之间的类型安全和智能感知。对于管理大型游戏项目或开发可复用的游戏模块(例如寻路库、UI 框架、战斗系统)来说,这是一种非常专业和高效的组织方式。
整个结构可以用下图来概括:

何时使用项目引用?
- 你有一个非常复杂的系统(例如一个完整的战斗框架),你希望把它从主游戏逻辑中分离出来,以便复用或单独维护。
上一篇:安全地使用外部 NPM 包
下一篇:投稿社区:发布 NPM 包