包体积与 Tree Shaking
PlayCanvas Engine 在 npm 上以 ES 模块形式发布,每个源码文件对应一个模块,且 package.json 声明了 "sideEffects": false。使用 Vite、Rollup、esbuild 或 webpack 等工具打包时,构建工具会沿代码中的导入关系分析,将未被使用的模块排除。这称为 tree shaking。
最终包中包含哪些内容,很大程度上取决于 AppBase 的配置。本页介绍如何让应用只包含实际使用的功能。
工作原理
代码通过名称引用组件和资源,例如 entity.addComponent('render') 或 new Asset('logo', 'texture', { url })。构建工具无法从这些字符串判断应用需要引擎的哪些部分。
因此,AppBase 只注册 AppOptions 对象中列出的组件系统和资源处理器:
- 组件系统负责运行一种类型的组件,例如
RenderComponentSystem运行render组件。 - 资源处理器负责加载一种类型的资源,例如
TextureHandler加载texture资源。
列出的类会由代码导入,因此被包含在包中。没有列出的类,以及仅被这些类使用的代码,都会被排除。
配置 AppBase
下面为包含网格、相机、灯光、脚本、纹理和 glTF 模型的应用注册所需功能:
import {
AppBase,
AppOptions,
CameraComponentSystem,
ContainerHandler,
createGraphicsDevice,
DEVICETYPE_WEBGPU,
Keyboard,
LightComponentSystem,
Mouse,
RenderComponentSystem,
ScriptComponentSystem,
ScriptHandler,
TextureHandler,
TouchDevice
} from 'playcanvas';
const canvas = document.getElementById('application');
const device = await createGraphicsDevice(canvas, {
deviceTypes: [DEVICETYPE_WEBGPU]
});
const createOptions = new AppOptions();
createOptions.graphicsDevice = device;
// input devices the application reads
createOptions.mouse = new Mouse(canvas);
createOptions.touch = new TouchDevice(canvas);
createOptions.keyboard = new Keyboard(window);
// component systems and resource handlers the application uses
createOptions.componentSystems = [
RenderComponentSystem,
CameraComponentSystem,
LightComponentSystem,
ScriptComponentSystem
];
createOptions.resourceHandlers = [TextureHandler, ContainerHandler, ScriptHandler];
const app = new AppBase(canvas);
app.init(createOptions);
app.start();
如果使用组件却没有注册对应系统,addComponent 会返回 null,调试版引擎记录 addComponent: System 'sound' doesn't exist。加载资源但没有注册对应处理器时,加载会失败,并报告 No resource handler for asset type。
开发期间,Vite 等设置了 development 导出条件的构建工具会使用引擎调试构建,输出这些错误;生产构建使用发布构建。
组件系统
为应用添加的每种组件注册对应组件系统。其他依赖列说明该组件还需要哪些功能。
| 组件系统 | 组件 | 用途 | 其他依赖 |
|---|---|---|---|
RenderComponentSystem | render | 网格、基本几何体和 glTF 模型中的实体 | |
CameraComponentSystem | camera | 相机 | |
LightComponentSystem | light | 方向光、点光源、聚光灯和面光源 | |
ScriptComponentSystem | script | 脚本 | 以 script 资源形式加载脚本时需要 ScriptHandler |
GSplatComponentSystem | gsplat | 高斯泼溅 | GSplatHandler |
ParticleSystemComponentSystem | particlesystem | 粒子系统 | 加载粒子纹理需要 TextureHandler |
AnimComponentSystem | anim | 动画状态图和动画图层 | 加载 animclip 与 animstategraph 资源需要 AnimClipHandler 和 AnimStateGraphHandler |
AnimationComponentSystem | animation | 旧版动画播放,已由 anim 取代 | AnimationHandler |
ModelComponentSystem | model | 旧版网格,已由 render 取代 | ModelHandler |
SoundComponentSystem | sound | 声音播放 | AudioHandler,以及 AppOptions 中的 soundManager |
AudioListenerComponentSystem | audiolistener | 空间音频监听器 | AppOptions 中的 soundManager |
CollisionComponentSystem | collision | 碰撞形状和触发器 | ammo.js |
RigidBodyComponentSystem | rigidbody | 刚体物理 | CollisionComponentSystem、ammo.js |
JointComponentSystem | joint | 物理关节 | RigidBodyComponentSystem、CollisionComponentSystem、ammo.js |
ScreenComponentSystem | screen | 用户界面根节点 | |
ElementComponentSystem | element | 文本、图像和分组元素 | ScreenComponentSystem;文本需要 FontHandler,输入需要 AppOptions 中的 elementInput |
ButtonComponentSystem | button | 按钮 | ElementComponentSystem,以及 AppOptions 中的 elementInput |
ScrollViewComponentSystem | scrollview | 可滚动内容 | ElementComponentSystem,滚动条需要 ScrollbarComponentSystem |
ScrollbarComponentSystem | scrollbar | 滚动条 | ElementComponentSystem |
LayoutGroupComponentSystem | layoutgroup | 子元素布局 | ElementComponentSystem |
LayoutChildComponentSystem | layoutchild | 布局组中元素的布局设置 | ElementComponentSystem、LayoutGroupComponentSystem |
SpriteComponentSystem | sprite | 2D 精灵及精灵动画 | 加载精灵需要 SpriteHandler、TextureAtlasHandler 和 TextureHandler |
资源处理器
为应用加载的每种资源注册对应资源处理器。
| 资源处理器 | 资源类型 | 加载内容 |
|---|---|---|
TextureHandler | texture | 图像、HDR 图像和压缩纹理 |
ContainerHandler | container | glTF 和 GLB 模型,以及其中的网格、材质、纹理和动画 |
ScriptHandler | script | 定义脚本的 JavaScript 文件 |
GSplatHandler | gsplat | 高斯泼溅文件 |
CubemapHandler | cubemap | 立方体贴图 |
MaterialHandler | material | 以 JSON 保存的材质 |
RenderHandler | render | 引用 container 资源的网格,例如编辑器导出的网格 |
FontHandler | font | 文本元素使用的字体 |
AudioHandler | audio | 音频文件 |
AnimClipHandler | animclip | anim 组件使用的动画片段 |
AnimStateGraphHandler | animstategraph | anim 组件使用的动画状态图 |
AnimationHandler | animation | 旧版 animation 组件使用的动画 |
ModelHandler | model | 旧版 model 组件使用的模型 |
SpriteHandler | sprite | 精灵 |
TextureAtlasHandler | textureatlas | 精灵使用的纹理图集 |
SceneHandler | scene | 编辑器导出的场景 |
JsonHandler | json | JSON 数据 |
TextHandler | text | 文本文件 |
BinaryHandler | binary | 以 ArrayBuffer 表示的二进制数据 |
其他选项
AppOptions 的其他属性也按同样方式添加功能:未设置的功能不会被包含。
| 选项 | 值 | 用途 |
|---|---|---|
graphicsDevice | createGraphicsDevice 返回的 GraphicsDevice | 渲染,必需。 |
mouse | new Mouse(canvas) | 通过 app.mouse 读取鼠标输入 |
touch | new TouchDevice(canvas) | 通过 app.touch 读取触摸输入 |
keyboard | new Keyboard(window) | 通过 app.keyboard 读取键盘输入 |
gamepads | new GamePads() | 通过 app.gamepads 读取手柄输入 |
elementInput | new ElementInput(canvas) | 用户界面元素上的鼠标和触摸输入 |
soundManager | new SoundManager() | 通过 sound 和 audiolistener 组件播放声音 |
lightmapper | Lightmapper 类 | 运行时烘焙光照贴图 |
batchManager | BatchManager 类 | 将网格实例合并为批次 |
xr | XrManager 类 | 通过 app.xr 使用 WebXR |
assetPrefix | 字符串 | 添加到每个资源 URL 前的前缀 |
scriptPrefix | 字符串 | 添加到每个脚本 URL 前的前缀 |
scriptsOrder | 脚本 URL 数组 | 脚本加载顺序 |
devtools | true 或 false | 应用是否向 PlayCanvas Inspector 等开发者工具声明自身,默认为 true。 |
没有在此配置的输入设备,在应用中会保持为 null。
控制包体积
- **从
playcanvas导入。**包名解析到按模块发布的构建,便于构建工具执行 tree shaking。playcanvas/build/中的单文件构建适用于不使用构建工具的场景,例如通过导入映射使用引擎。 - **直接使用引擎导出项。**具名导入(
import { Entity } from 'playcanvas')和命名空间成员访问(pc.Entity)都支持 tree shaking。将整个命名空间作为值传给函数,例如setup(pc),会导致 esbuild 等部分构建工具保留整个引擎。 - **检查包中包含的内容。**esbuild 可通过
--analyze输出各模块体积,rollup-plugin-visualizer 则可以绘制 Vite 或 Rollup 包的可视化分布图。