Babylon.js 构建系统

注:Babylon.js 采用单一仓库架构,除了Babylon.js核心模块,还包含众多工具和应用程序,构建系统较为复杂; Babylon.js仓库中的buildSystem.md(commit d8e6de1)文档,对构建系统进行了较为详细的说明。本文为个人在学习中,对该文档进行的翻译,分享出来,希望有所裨益。如有纰漏改进之处,欢迎留言。

开始之前

除非特别说明,所有本文档中提及的命令都应该在项目的根目录下(BABYLON.js/)执行。

开始

前置需求

本仓库基于NPM workspace架构,要求npm版本为7及以上。推荐npm版本是8,其推荐Node的版本为14及以上。

如果你还没有安装node,请到这里下载安装:nodejs.org/en/download…,或者使用nvm(稍后解释)管理安装的版本。

安装后,使用如下命令检查所安装的npm和node版本:

npm --version
node --version

如果你需要安装多个版本的node和npm,使用nvm工具进行管理:

  • windows:github.com/coreybutler…
  • Unix-like:github.com/nvm-sh/nvm

这个可能也可以使用: github.com/coreybutler…

注意有时候在使用nvm更新npm的时候,命令npm update npm -g可能不能正确工作(主要是在windows上,且从npm 6之上更新时)。这里有一些建议方案。有一个我们亲测有效:

  • 在AppData/Roaming/nvm中 cd 到正确的node版本的目录
  • 执行 npm install npm

这里的建议可能有用:github.com/coreybutler…

建议

使用VSCode时,如果想完全在VSCode中进行调试,则需要安装仓库推荐的VSCode扩展插件。

安装必需的库

在根目录下执行 npm install,安装所有仓库所需的库。

建议在开始之前执行 npm run build:dev,以准备好所有代码。

不想再等,立即试用

使用VSCode吗?如果是,请安装推荐的扩展插件,然后启动 Run and Watch Dev Host命令进行编译并启动 esm版本的dev host。 如果基于经典的UMD模式(该模式下提供BABYLON命名空间)开发,请运行 Run and Watch Babylon Server

执行 npm run build:dev命令对源码,着色器和资源进行构建。

【译注】dev host 是一个应用工具,基于esm方式是babylon.js的模块代码,在开发中用于测试babylon.js。

执行命令 npm run serve -w @tools/dev-host,将会在http://localhost:1338地址上启动dev host服务,服务的代码在packages/tools/devHost/src。

注意,以上命令启动的服务不会监视 dev目录录下的代码变化,只能监视到packages/tools/devHost/src 目录下的变化。

要监视dev包的变化,应在另一个终端上执行 npm run watch:dev。这样便能监视dev包目录下的所有变化,包括着色器和资源,比如scss文件,图片文件等。

除了dev host,还有很多其它使用该仓库的选择,下文将进行阐述。

TODO列表

  • 完成构建工具文档
  • 完成 CDN 包(如post production和serializers)【译注】post production和serializers是dev中的包
  • 添加更多调试目标和任务(确保VSCode能覆盖一切调试工作)
  • 完成HTTPS支持
  • 将recast-detour转移到这个仓库
  • 升级recast-detour到基于模块架构
  • 将所有的当前测试从主目录中移动到相应的包目录下
  • 为materials, procedural textures 添加测试
  • 在测试目录下添加测试并正确运行
  • 简化LTS的开发(使用LTS监视进行开发)
  • 运行一个依赖项目的终极测试
  • 修改日志(带标签)

简要指南

这个章节适用于没有耐心的读者。不探讨具体的原理,只是为开发本仓库列举必要的工作任务。

在VSCode中使用快键键Ctrl+P (mac上为 Command+P)打开任务面板,输入 “task”,然后输空格,从下拉框中选择想要执行的任务。

运行 babylon server

【译注】babylon server基于UMD模式构建babylon,可以提供babylon.js的cdn服务。

使用 VSCode:

  • 运行 “CDNServe and watch (Dev)” 任务
  • 如果要调试, 则从调试菜单中选择运行 “Run and watch Babylon Server”

使用命令行:

  • 执行 npm run watch:dev (如果想更改 dev 包. 否则执行 npm run build:dev)
  • 在另一个终端中执行 npm run serve -w @tools/babylon-server

babylon server提供两种形态 – js 和 ts。通过http://localhost:1337访问js版本(默认),通过http://localhost:1337/index-ts访问ts版本。对应的代码为babylon server包的src目录下的sceneJs.js 和 sceneTs.ts,可以进行编辑。

下一节将会介绍,babylon server还提供了playground的片段调试能力。

playground 片段调试

要调试一个代码片段(snippet),需在index后添加片段并将babylon server在调试模式下运行起来,例如:
http://localhost:1337#IQN716#9

如果是在VSCode中启动的服务,则可以直接在IDE中进行调试。

注意,hash值变化后,加载程序会进行响应(重新加载场景),但不会保存新的数据。需要运行playground本身才行。

运行 dev host

使用VSCode:

  • 启动 “Run and watch Dev Host (Dev)” 任务
  • 或者只是测试而不更改babylon核心包, 启动 “Run Dev Host (Demo)” 任务
  • 如果需要调试, 从调试菜案运行 “Run and watch dev host (Dev)”

使用命令行:

  • 执行 npm run watch:dev (如果想更改 dev 包. 否则执行 npm run build:dev)
  • 在另一个终端中执行 npm run serve -w @tools/dev-host

在浏览器中打开 http://localhost:1338 。

运行 playground

使用VSCode:

  • 启动任务 “Playground Serve for core (Dev)”
  • 如果只是开发Playground本身, 则运行任务 “Playground Serve (Dev)”
  • 如果需要调试, 则需从调试菜单运行 “Launch Playground (chrome)” (或者 “Playground development”)

使用命令行:

  • 执行 npm run watch:dev (如果想更改 dev 包. 否则执行 npm run build:dev)
  • 另起命令行窗口执行 npm run serve -w @tools/babylon-server
  • 另起命令行窗口执行 npm run serve -w @tools/playground

快捷方式

  • 执行 npx build-tools --command dev-watch --watch-assets --watch-declarations --serve (同时启动监控和服务)
  • 另起命令行窗口执行 npm run serve -w @tools/playground

在浏览器中打开 http://localhost:1338 。

运行 sandbox

使用VSCode:

  • 启动任务 “Sandbox Serve for core (Dev)”
  • 如果只是开发sandbox本身, 则运行任务 “Sandbox Serve (Dev)”
  • 如果需要调试, 则从调试菜单运行 “Launch Sandbox (chrome)” (或者 “Sandbox development”)

使用命令行:

  • 执行 npm run watch:dev (如果想更改 dev 包. 否则执行 npm run build:dev)
  • 另起命令行窗口执行 npm run serve -w @tools/babylon-server
  • 另起命令行窗口执行 npm run serve -w @tools/sandbox

快捷方式

  • 执行 npx build-tools --command dev-watch --watch-assets --watch-declarations --serve (同时启动监控和服务)
  • 另起命令行窗口执行 npm run serve -w @tools/sandbox

在浏览器中打开 http://localhost:1338 。

运行 gui editor

使用VSCode:

  • 启动任务 “GUI Editor Serve for core (Dev)”
  • 如果只是开发gui editor本身, 则运行任务 “GUI Editor Serve (Dev)”
  • 如果需要调试, 则从调试菜单运行 “Launch GUI Editor (chrome)” (或者 “GUI Editor development”)

使用命令行:

  • 执行 npm run watch:dev (如果想更改 dev 包. 否则执行 npm run build:dev)
  • 另起命令行窗口执行 npm run serve -w @tools/babylon-server
  • 另起命令行窗口执行 npm run serve -w @tools/gui-editor

快捷方式?

  • 执行 npx build-tools --command dev-watch --watch-assets --watch-declarations --serve (同时启动监控和服务)
  • 另起命令行窗口执行 npm run serve -w @tools/gui-editor

在浏览器中打开 http://localhost:1338

运行 tests

使用VSCode:

  • 启动任务 Run unit testsrun visualization tests
  • 调试 – 启动 Run and debug unit testsRun and debug visualization tests

使用命令行:

  • 执行 npm run test:unitnpm run test:visualization
  • 要运行所有测试,执行 npm run testnpx jest

将公开项目连接到外部项目

一个示例是连接core。也可以是其它包。

  • 构建你想连接的公开包:
    • npx nx build babylonjs(当构建es6包时去除@babylonjs前缀,即 npx nx build core
      【译注】npx nx build babylonjs项目在public/umd下,npx nx build core对应项目在public/@babylon之下。
    • 如果使用es6包,运行npm run prepublishOnly -w @babylonjs/core
    • 运行npm link -w @babylonjs/core
      【译注】npm link命令将包发布到本地的全局node_modules中(其实是在全局node_modules中创建符号链接到发布的包)

在外部项目中:

  • 确认连接项目的版本是正确的
  • 执行 npm link @babylonjs/core

当被连接的包发生更新,需要重新构建即可。不需要重新连接,除非重新安装了公开的库。

注意:

  • 这个过程很快将进行简化。
  • 你可以连接任何包,不仅限公开的包。但是,你不能重命名包。所以,如果你连接 @dev/core, 你需要将 @dev/core加入的项目的依赖表里面。

给项目添加依赖

执行 npm install packageName -w @namespace/package (添加 -D 来添加 dev-dependency中)

对应项目的package.json 内容将会更新。

注 – npm有一个缺陷, 有些情况下只安装包但为添加依赖到 package.json. 如果发生这种情况,请再执行一次命令.

仓库结构

这个仓库架构类似单仓库架构。每一个包都有自己的package.json且可以独立使用,当然,需要考虑依赖项。

devlts目录下的包是组合项目(www.typescriptlang.org/tsconfig#co…),可以用一条命令编译。当进行监视时,所依赖的包在需要时也将自动进行编译。
【译注】composites 项目需要符合一些限定,以便可以进行增量编译,在typescript中可以被其它包引用。

所有的包(除了公共的es6包【译注】:即public/@babylon)的目录结构都是同样的:

  • src 目录包含typescript源码和资源
  • test 目录包含测试代码(见 测试)
  • dist 目录包含编译后的代码,包括代码映射文件和申明文件,以及资源(如果有配置)
  • public 目录(可选)包含当项目启动的服务所需的公开的资源(如若适用)

项目的build/watch/test脚本会用到这种目录结构。

在开发阶段,所有的引用指向项目的src目录;当构建时,所有的引用指向项目的dist目录。这意味着,编译项目之前,它所依赖的项目需要编译完成。这一点在构建和监视中需要注意。

所有项目的依赖被提升到了根目录,所以仓库只有一个package-lock.json文件和一个node_modules目录。

NPM 工作空间使得你可以在根目录中执行所有包的命令,包括根包。

在仓库所有的包上, 执行命令 npm <command> -ws.

在指定包上, 执行命令 npm <command> -w <package>.

在根包上执行, 执行命令 npm <command>.

更多关于工作空间的信息参考 – docs.npmjs.com/cli/v7/usin…

名称习惯

  • npm 包名称采用小写字母短横线(kebab)式 (如 gui-editor)
  • 目录名采用驼峰式(camel)(如 guiEditor)

包的类型

有四种不同包类型:

dev 包

包名称以 @dev打头. 这是日常开发工作使用的包。
这些包在其dist目录下包含原始资源 , 可以被任何打包器使用 (如 webpack, rollup).

lts 包

包名称以 @lts打头. 这些包是为仓库的长期支持版. 不时有一些代码被移动到 LTS 包 (例如一些带副作用或者被废弃的功能). LTS 包用于生成公开包.

LTS 包是dev 包的扩展,永远不要有代码重复. 更多请参考LTS.

Tools 包

包名称以 @tools打头. 这些包包含一些在仓库中使用的工具.
工具有 playground, sandbox, node 和 gui editor.

Public 包

公开包是公开发行到npm的包. 主要基于 LTS包 或者 Tools 包构建. 它们是仅有的不在package.json中标记为 “private” 的包.

运行脚本

要运行一个包中的脚本,可以运行在该包的目录下运行npm run scriptname,也可以在根目录下运行npm run scriptname -w @namespace/package-name,后者是推荐的做法。

例如, 构建dev下的 core 包,可以在根目录下:

npm run build -w @dev/core

如果要运行package.json中没有定义的脚本,可以将脚本添加到package.json(不要忘记提交更新),或者在包的目录下使用npx命令。例如,运行所安装的typescript程序(非全局安装的),可以在包的目录下执行:

npx tsc ....

大部分包都包含有以下脚本:

  • build
  • compile
  • clean
  • test
  • format
  • lint
  • watch (工具包里为serve)

这项工作仍在进行中. 这些脚本将被添加到所有包中. 如果你在尝试使用某个脚本时,发现包里没有定义,请告诉我们。

开发

代码构建和监视

要编译最新的代码,请执行:

npm run build:dev

要启动监视dev包的代码更新, 执行下面任一命令:

"format:check"; // 格式检查,
"format:fix"; // 格式修复,
"lint:check"; // 代发分析,
"lint:fix"; // 修复代码分析中的问题,

当代码仓库的issue都解决时,format:check 和 lint:check 会在CI过程执行。

处理资源

dev 包 和 lts 包会将二进制资源文件当作dis目录的一部分。 当打包这些资源时,打包器会决定如何处理(比如webpack中使用url-loader 或者 file-loader)。这些资源,包括二进制媒体文件和(s)css 文件会被监视并在过程中被自动拷贝或处理。要显示的监视指定项目的资源文件,可以在指定项目目录下执行build-tools的 “process assets” 任务(该文档后面会详细记录):

npx build-tools -c process-assets --isCore --watch

同时,在各包的package.json中,应该定义有 build:assetswatch:assets 脚本。

着色器也被看作资源。对着色器的处理有一些不同,但也是用同样的脚本。一个着色器(.fx 文件)会生成一个typescript文件,然后在库的构建构成中作为编译的一部分。当构建时,build:assets脚本会在compile:source之前运行。build脚本会做好这些。例如@dev/core有如下脚本:

"build": "npm run clean && npm run compile",
"precompile": "npm run compile:assets",
"compile": "npm run compile:source",
"compile:source": "tsc -b tsconfig.build.json",
"compile:assets": "build-tools -c process-assets --isCore",

【译注】compile:assets命令会将着色器(.fx 文件)转换生成.ts文件并放置在.fx文件同样的位置。注:在VSCode的项目浏览器看不到生成的.ts,是因为在.VSCode/settings.json中将其隐藏了。

代码格式化和语法检查(lint)

在仓库的根目录有一个全局的eslint和prettier配置。它可以统一代码结构并在仓库范围进行代码格式化。这些工具同时在.js和.tsx文件上执行。

当使用VSCode是,为了能实时进行代码检查,建议同时使用prettier formatter扩展和eslint。

在命令行中执行代码检查, 运行:

npm run lint

配置选项

大部分包都不需要额外的配置就可以进行编译。然而,在有些情况下,你可能想要控制文件被编译和服务的方式。一个好的例子是打开https支持,或者改变标准的babylon及其它工具的服务端口。

有几种方法可以将配置传入不同的包

命令行参数

构建工具(build tools,稍后解释)和webpack(用于启动工具服务)都接受命令行参数。下面的示例没有展示所有不同的选项。所有的选项列表在本文档后面有介绍。

当运行dev的监视时,可以指定所要监视的包。标准启动dev监视的命令是npm run watch:dev,你可以按如下两种方法之一添加监控的包:

# 选项 1, 扩展 npm 命令 watch:dev
npm run watch:dev -- --packages core,gui
# 选项 2, 直接运行dev watcher (监视代码和资源)
npx build-tools --command dev-watch --watch-assets --packages core,gui

启动服务命令也是同样的。例如,使用生产模式启动babylon server:

# 选项 1, 扩展 npm 命令 watch:dev
npm run serve -w @tools/babylon-server -- --env=mode=production
# 选项 2 - 在babylon server包下直接使用webpack
npx webpack serve --env=mode=production

上面这个特别的例子中,还可以使用 npm run serve:prod.

使用 .env 文件

同时运行playground和babylon server,本地有两个服务。playground服务,CDN服务在另一个地址上。如果我们想要同时配置这两个服务,可以使用.env文件去扩展命令行参数。

使用.evn文件,首先在仓库根的目录下创建.env文件,.env文件将被自动使用。

可以将.env文件当成是扩展process.env的一种方式。第一个我们需要知道的重要事实是它只在构建期间被使用,而不会在编译的代码中使用到。所以,所设定的参数可以在构建的脚本和构建工具(build tools)里使用,但是不能在playground源代码目录下的.tsx文件中使用。

要使用.env 文件设置命令行参数,将参数名称转换为大写,并将所有的-替换成_。比如要将上面示例的指定包的参数,可以在.env 文件中添加下面这行:

PACKAGES="core,gui"

如果想要在运行dev监视时总是打开资源监视,可以在.env文件中添加下面这行:

WATCH_ASSETS=true

要打开热重启(另一个开启webpack的包的选项),在.env文件中添加下面这行:

ENABLE_HOT_RELOAD=true
ENABLE_LIVE_RELOAD=true

要设置所有(webpack)的构建使用生产模式,在.env文件中如下设置:

NODE_ENV=production

在.env文件中还可以设置的变量有::

TEST_ENGINE="webgl2" # 设置可视化测试使用的引擎
TOOLS_PORT=1338 # 设置工具(如playground 或 the node editor)的端口
CDN_PORT=1337 # 设置babylon server的端口

注意的是.env文件不应该在仓库中更新,它是被仓库忽略的。
可以在文档中查询每个命令所有可用的选项。

项目连接

项目之间的依赖关系是在其package.json中定义的。然而,当使用dev或者lts包时,你可以直接引用相关文件而不需要使用npm包。这意味着,你可以(且应当)使用基本包名直接从所需的库中导入。例如,inspector当中的imports(advancedDynamicTextureTreeItemComponent.tsx)。

// 从 core 包中加载
import { Nullable } from "core/types";
import { Observer, Observable } from "core/Misc/observable";
import { IExplorerExtensibilityGroup } from "core/Debug/debugLayer";
// 从 gui 包中加载
import { Control } from "gui/2D/controls/control";
import { AdvancedDynamicTexture } from "gui/2D/advancedDynamicTexture";

使用dev包名 (在build tools的packageMapping.ts定义) 可以让构建工具选择使用哪个项目 – dev 或者 lts.

保存仓库

有两种基本方式使用dev代码:

Dev Host

【译注】dev host是tools下的一个用户开放的工具性项目

dev host 可以使用typescript 或者 javascript, 利用 webpack打包, 并启动服务. 唯一不可以重命名的文件是 index.ts, 它是主程序入口.

dev host类似我们提供的es6包 – 所有库都需要导入,且没有BABYLON名字空间 如果你想测试一个playground场景,参见下面的Babylon server

如下命令运行dev host:

npm run serve -w @tools/dev-host

这个命令会启动监视dev-host的src目录,同时执行其中的代码。

当在dev host中从不同的包加载时,你需要使用这个包名。例如,如果从dev core包中加载scene对象,可以:

import { Scene } from "@dev/core";

导入代码时需要保持一致,这很重要。 即,不能一部分导入@lts包,而另一部分导入@dev包,如果这样,typescript将会抱怨它们不是相同的对象。

dev host 项目被配置的更加宽容。最好的例子是noImplicitAny被设置为false。这样你可以同时加载.js 文件和 typescript 文件。这也是allowJs被设置为true的原因。

dev host当前的简单结构允许你从playground中拷贝代码,做一些必要的更改就可以在dev host中运行。 例如,将下面的playground代码:

var createScene = function () {
    // This creates a basic Babylon Scene object (non-mesh)
    var scene = new BABYLON.Scene(engine);
    // This creates and positions a free camera (non-mesh)
    var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
    // This targets the camera to scene origin
    camera.setTarget(BABYLON.Vector3.Zero());
    // This attaches the camera to the canvas
    camera.attachControl(canvas, true);
    // This creates a light, aiming 0,1,0 - to the sky (non-mesh)
    var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
    // Default intensity is 1. Let's dim the light a small amount
    light.intensity = 0.7;
    // Our built-in 'sphere' shape.
    var sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 2, segments: 32 }, scene);
    // Move the sphere upward 1/2 its height
    sphere.position.y = 1;
    // Our built-in 'ground' shape.
    var ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 6, height: 6 }, scene);
    return scene;
};

转换成下面这样即可:

import { canvas, engine } from "./index";
import { FreeCamera, HemisphericLight, MeshBuilder, Scene, Vector3 } from "@dev/core";
export const createScene = function () {
    // This creates a basic Babylon Scene object (non-mesh)
    const scene = new Scene(engine);
    // This creates and positions a free camera (non-mesh)
    const camera = new FreeCamera("camera1", new Vector3(0, 5, -10), scene);
    // This targets the camera to scene origin
    camera.setTarget(Vector3.Zero());
    // This attaches the camera to the canvas
    camera.attachControl(canvas, true);
    // This creates a light, aiming 0,1,0 - to the sky (non-mesh)
    const light = new HemisphericLight("light", new Vector3(0, 1, 0), scene);
    // Default intensity is 1. Let's dim the light a small amount
    light.intensity = 0.7;
    // Our built-in 'sphere' shape.
    const sphere = MeshBuilder.CreateSphere("sphere", { diameter: 2, segments: 32 }, scene);
    // Move the sphere upward 1/2 its height
    sphere.position.y = 1;
    // Our built-in 'ground' shape.
    MeshBuilder.CreateGround("ground", { width: 6, height: 6 }, scene);
    return scene;
};

更简单的方法是将整个 @dev/core 包加载成为BABYLON:

import { canvas, engine } from "./index";
import * as BABYLON from "@dev/core";
export const createScene = function () {
    // This creates a basic Babylon Scene object (non-mesh)
    const scene = new BABYLON.Scene(engine);
    // This creates and positions a free camera (non-mesh)
    const camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
    // This targets the camera to scene origin
    camera.setTarget(BABYLON.Vector3.Zero());
    // This attaches the camera to the canvas
    camera.attachControl(canvas, true);
    // This creates a light, aiming 0,1,0 - to the sky (non-mesh)
    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
    // Default intensity is 1. Let's dim the light a small amount
    light.intensity = 0.7;
    // Our built-in 'sphere' shape.
    const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 2, segments: 32 }, scene);
    // Move the sphere upward 1/2 its height
    sphere.position.y = 1;
    // Our built-in 'ground' shape.
    BABYLON.MeshBuilder.CreateGround("ground", { width: 6, height: 6 }, scene);
    return scene;
};

注意这里的BABYLON不是真正的BABYLON名字空间,它只是core名字空间,因此即使导入了也并不包含加载器模块。

下面这个扩展的示例展示了怎样利用dev host集成GUI,Loaders 和 inspector。

import { canvas, engine } from "./index";
import "@dev/loaders";
import { Inspector } from "@dev/inspector";
import { ArcRotateCamera, CubeTexture, Scene, SceneLoader } from "@dev/core";
import { AdvancedDynamicTexture, Button } from "@dev/gui";
export const createScene = async function () {
    const scene = new Scene(engine);
    scene.createDefaultCameraOrLight(true);
    const hdrTexture = new CubeTexture("https://playground.babylonjs.com/textures/SpecularHDR.dds", scene);
    scene.createDefaultSkybox(hdrTexture, true, 10000);
    // The first parameter can be used to specify which mesh to import. Here we import all meshes
    SceneLoader.AppendAsync("https://assets.babylonjs.com/meshes/webp/", "webp.gltf", scene, function (_newMeshes) {
        scene.activeCamera!.attachControl(canvas, false);
        // scene.activeCamera!.alpha += Math.PI; // camera +180
        (scene.activeCamera as ArcRotateCamera).radius = 80;
    });
    const advancedTexture = AdvancedDynamicTexture.CreateFullscreenUI("UI");
    const button1 = Button.CreateSimpleButton("but1", "Click Me");
    button1.width = "150px"
    button1.height = "40px";
    button1.color = "white";
    button1.cornerRadius = 20;
    button1.background = "green";
    button1.onPointerUpObservable.add(function() {
        alert("you did it!");
    });
    advancedTexture.addControl(button1);
    Inspector.Show(scene, {});
    return scene;
};

dev host项目被配置成支持热重载和实时重载,保证当有任何dev host原代码变化时,dev host就会更新(或重新加载)页面。然而,它并不会响应dev 包中的更新,因为它使用的是编译之后的dev包。

如果你想要监视dev包中的源码更新,你需要手动启动监视,或者使用配置好的VSCode 任务(后面详述)。推荐但并非必须使用VSCode。

编译dev包以获得最新的dev host,详见[Initial source build and watch](#Initial source build and watch).

注意dev host并没有采用生产环境的最佳做法,它只是辅助开发dev包的工具。不应该在生产环境中使用。

Babylon Server

Babylon Server引入的新包,是CDN结构的直接拷贝,Babylon Server可以提供javascript文件,以及代码映射和类型声明文件。

和dev host类似,Babylon Server使用最新的从dev(或者lts)包中编译的代码,并提供给浏览器。默认的CDN访问地址是http://localhost:1337。

babylon server的index.html引用了所有的公开包,并以BABYLON名字空间提供出来,类似playground的做法。当仓库初始化时,服务生成两个文件-createScene 和 createEngine。这两个文件不是git仓库的一部分,你可以随意更改。如果需要createScene可以是异步。
如果你想在不运行playground 的情况下调试playground的场景,可以编辑createScene.js(typescript的情况下是sceneTs.ts),然后打开http://localhost:1337/index.html 或者 http://localhost:1337/index-ts.html。

要打开检测器,按 Ctrl+Shift+U (mac Cmd+Shift+U).

要启动 babylon server, 运行:

npm run serve -w @tools/babylon-server

这个命令会在端口1337上启动一个新的服务提供所有需要的文件,包括一个非常简单的index.html文件,使得cdn工作起来。

babylon server还提供其它文件,使得工具如playground可以正确工作起来。它还提供如physics engine, earcut, draco decoder等库,详细信息见 babylon server包下的public目录。这些文件以static文件的形式提供。

同dev host一样,如果你想要监视其它包的代码变化,你需要手动运行监视/编译,或者使用VSCode配置好的任务(后面详述)。推荐但并非必须使用VSCode。编译和监视dev包的内容,详见[Initial source build and watch](#Initial source build and watch).

所有需要编译的包的情况都可以使用babylon server,正如playground那样。

为了使用babylon server,先运行babylon server (npm run serve -w @tools/babylon-server),然后在另一个终端启动playground。我们总是为playground在VSCode中准备好了任务和启动配置。

几个注意点:

  1. 考虑到性能 – 最小化的代码加载更快(至少应该如此)。这意味着在生产模式下运行服务(使用 npm run serve:prod -w @tools/babylon-server)会有利于依赖编译后的代码运行的包更快的运行。然而,构建的时间大概会两倍之慢。如果你想在使用 gui editor 或者 playground工具时用到它(基于它们而非同这些包一起工作 – 即更新这些包的代码),我们推荐在生产环境中使用babylon server。
  2. 考虑到由babylon-server创建的这些包 – 它们非常类似于公开的UMD包,但又不是完全一样。babylon-server并不是为生产环境准备的!它只是一个开发工具。

正如所有webpack提供的包一样,有一些属性可以配置(使用CLA 或者 .env 文件)

  • source: 所用代码的类型,可以是’dev’ 或 ‘lts’. 默认 ‘dev’.
  • mode: 使用的构建类型,可以是’production’ 或 ‘development’. 默认 ‘development’.
  • cdnPort: babylon server的服务端口,默认 1337.
  • enableHttps: 是否启动https. 默认 false.
  • enableHotReload: 是否启动热重载. 默认false. 生产环境总是不启动。
  • enableLiveReload: 是否启动实时重载. 默认false. 生产环境总是不启动。

工具

我们为使用者准备了几个工具。他们(playground, sandbox, node editor 和 the gui editor)都是用同样的架构引用core包 – UMD包(cdn中提供的文件)。这就是为什么需要在后台启动babylon server后它们才可以工作。详见[Babylon Server](#Babylon Server)。

所有的工具都是用webpack打包,配置也相类似。

可使用serve命令启动工具:

npm run serve -w @tools/package-name

大部分包的工作方式都类似,没有全部都写文档。
注意:

  • 如果playground使用HTTPS,那么Babylon server 也需要打开HTTPS。
  • 要加载构建好的文件(不使用babylon-server),在url中添加?dist=true
  • 所有的工具都在http://localhost:1338地址上

Playground

playground不是公开的包,而是在playground.babylonjs.com上对外开放的。它是给开发者测试其工作的工具。

要让Playground在生产环境工作,你需要一个运行的babylon server 或者使用dist参数。

启动playground:

npm run serve -w @tools/playground

有VSCode 任务可以启动playground用于开发工作,同时启动dev包的监视。第一个选项是给playground开发者,第二个是给dev包的开发者使用playground进行测试工作。

注意:

  • 从core中加载文件,引用@dev/core包而不需要设置目录。原因是类最终还是从BABYLON名字空间中加载,在playground可以使用。
  • 使用dev host可能比playground能更快的测试你的代码。

调试

所有的包在不同阶段都可以使用浏览器(推荐chrome)或者VSCode调试器进行调试。

在VSCode中启动相应的任务可以开始调试。VSCode可能需要一点时间去加载和初始化调试的文件。

所有包的构建都会生成sourcemaps文件(非生产模式),所以打开浏览器选择要调试的文件后可以进行typescript代码调试。

单元测试也可以使用VSCode调试。在调试模式下,测试会串行而非并线执行。在运行测试前设置断点。初始运行可能需要一点时间,一旦调试器连接上就会停止再断点。

调试过程中若出现任何问题,可以和我们联系。

直到修复了所有代码检查中的问题,否则在启动VSCode调试任务时需要选择”Debug anyways”。

监视

参考 build-tools 的 dev 监视。

添加新的包

根据以下步骤添加新的包:

  • 选择包的位置
  • 在包的类别添加一个新的目录
  • 添加package.json文件夹(你可以从另一个类似的包中拷贝一个)。如果不是公开的包,确保包被标记为”private”。
  • 给包正确的命名 (如 @tools/package-name)
  • 如上文所介绍的为包添加代码 (src目录包含代码和资源,test目录包含测试,等)

要安装依赖的包,可以手动添加到package.json,也可以在仓库的根目录下运行npm install package-to-install -w @namespace/package-name。 如果安装开发依赖包,添加 -D选项。

安装包后,你可以在这个名字空间中使用这个包(使用npm命令时使用-w 参数)。

你可以在另一个包中引用这个包,将他们连接在一起。完成之后(这个包会被添加到另一个本地包的依赖列表中)就会出现在node_modules之下。

构建

要构建仓库中各个包,运行npm run build -w @namespace/package-name。然而,还有一个更加快速高效的办法去构建包含依赖项的包。

nx已经集成到仓库,可以作为本地的资源库更快的执行构建工作。当使用nx运行npm脚本是,它会自动的将该命令应用到包的本地依赖项目,并保证正确的执行顺序。例如,当使用npx nx run build babylonjs-gui构建公开目录下的babylonjs-gui包,nx会将下列项目添加到运行列表:

  • @dev/build-tools (所有dev包都依赖的)
  • @dev/core
  • @lts/core
  • babylonjs
  • @dev/gui
  • @lts/gui
  • babylonjs-gui

然后回按顺序构建(根据预定义的依赖关系),如果包在上次构建以来没有发生任何更新,将会被跳过。所以,运行npx nx run build babylonjs将会构建dev,lts 和 public,但构建babylonjs-gui时,因为已经构建好了,不会再次被构建。

在本仓库中,只有你需要构建公开包的时,才应该使用这个,主要用于CI。 然而,nx是一个很强大的工具,将来还会更多应用到我们的仓库。关于nx更多的资料见:nx.dev/getting-sta…

【译注】nx工具是为单一仓库架构而设计的智能、快速、强大的构建工具。

测试

本仓库中有3种不同的测试配置:

  • 单元测试
  • 可视化测试
  • 集成测试 (TBD)

每个包都有一个特定结构的”test”目录。该目录结构使得可以在仓库的根目录下自动运行测试(也可以在单个包目录下)。目录结构如下:

  • 单元测试位于目录 test/unit.
  • 可视化测试位于目录 test/visualization.

所有的文件名必须按如下格式:
(.*).test.{js, ts, tsx},例如,materials.test.ts 或 babylon.physicsEngine.test.js

这些文件会被jest自动检索到并作为测试脚本的一部分,并使用对应的环境运行(单元测试用jsdom/node,可视化测试用puppeteer)

可能的测试环境有单元测试的jsdom/node以及可视化测和集成测试的puppeteer,(see jestjs.io/docs/config…)。默认的测试环境是node(可视化测试用puppeteer)。要改变测试环境,可以在测试文件的首行添加特定注释。例如:

/**
 * @jest-environment jsdom
 */

将使用jsdom作为测试环境,同时为单元测试准备好window和document对象。

可以在终端中看到测试结果。同时,测试完成后会生成一个junit.xml文件,这主要用于CI报告。

如果任何测试失败,可视化测试会生成报告。在根目录下的jest-screenshot-report目录下可以看到。

要使用单一命令运行所有的测试,在根目录下运行npm run testnpx jest 命令。

我们使用jest进行测试。要获得更多这方面的资料,可以阅读文档 – jestjs.io/docs/gettin…

单元测试

除了上面说的,些单元测试还需要知道一些其它事情。

  • 单元测试是用于测试单一特定单元模块的。所有其它的模块都需要模拟。更多关于jest模拟,参考 jestjs.io/docs/mock-f…
  • 如果你想测试两个或以上模块之间的连接,这个不是单元测试而是集成测试。
  • 单元测试环境不建议使用puppeteer环境
  • jsdom不允许给DOM添加脚本标签。所有需要从外部添加的东西都需要进行模拟。

运行所有的单元测试,执行npm run test -- --selectProjects unit。要运行指定项目的单元测试,指定对应的工作空间运行npm命令:npm run test -w @dev/core -- --selectProjects unit

运行指定的单元测试,可以使用jest过滤机制,要么通过文件名:
npm run test -w @dev/core -- --selectProjects unit -i "material"

这个会运行所有dev/core下文件名称匹配 “material” 的测试。

还可以通过测试名称进行过滤(测试中的”it” 和 “describes” 函数)

npm run test -w @dev/core -- --selectProjects unit -t "material"

这个会运行所有dev/core重所有测试名称匹配 “material” 的测试。

可视化测试

在根目录下执行npm run test:visualization开始运行可视化测试。

注意 – 运行测试的一个依赖包目前还不支持M1处理器的OSX系统,在该平台上运行测试将会失败。

配置可视化测试

可视化测试使用puppeteer运行,它是一个在node中控制chrome或firefox的接口。测试将在浏览器中运行,当有失败测试就会生成一个报告。我们选择的浏览器是一个安装了puppeteer的本地chromium。如果你要使用不同浏览器,可以在jest-puppeteer.config.js中配置puppeteer,这个文件就在根目录下。

module.exports = {
    launch: {
        dumpio: false, // 要看日志吗?
        timeout: 30000, // 30秒超时
        headless: false, // headless模式
        product: browser, // chrome 或 firefox
        ignoreHTTPSErrors: true, // 是否忽略 SSL 问题, 用于文件是在本地以自签名的SSL证书提供的情况。
        devtools: true, // 是否自动开启dev tools
        args: browser === "chrome" ? chromeFlags : firefoxFlags, // 更多发给浏览器的参数,如打开垃圾回收。
        executablePath: "C:\Program Files\Google\Chrome\Application\chrome.exe", // 指定 chrome (或 firefox) 路径
    },
    browserContext: "default",
};

这里列举了所有的参数 – github.com/puppeteer/p…

puppeteer打开了一个不同的运行环境上下文,不同于我们运行测试时用到的node上下文,所以,所有的代码要么提前注入好(例如使用babylon server)或者在评测时发送过去。一个简单的示例:

const random = await page.evaluate((aRandomNumber) => {
    return aRandomNumber * Math.random();
}, Math.random());

这个代码将在node中生成一个随机数,然后发送到页面,在页面中和Math.random()的结果相乘。这将会返回结果并保存在random变量中。

为了在测试过程中进行调试,你可以在测试的任何位置添加下列代码(在 node 环境, 而非评测函数中):

await jestPuppeteer.debug();

执行这个代码会暂停测试(考虑timeout!),允许你打开浏览器的开发者工具进行场景调试。

这些测试被配置为自动显示浏览器控制台中的所有报错信息(firefox中不能工作)。你可以在jest的输出屏幕中看到这些错误。

截止目前,所有测试都定义在可视化文件本身,但这事临时的,直到开发工作确定后。

将来,我们建议dev包自己拥有可视化测试,而不是在外部的包中。截止目前,可视化测试位于 @tools/visualization-tests。

运行指定的测试

要在指定引擎中运行指定的测试, 执行:

npm run test:visualization -- -i "engineName" -t "test name"

如:

npm run test:visualization -- -i "webgl2" -t "Particle subemitters"

构建工具

文档工作中,敬请期待 :-)

LTS

文档工作中,敬请期待 :-)

新老系统的区别

  • 为了允许架构变化,我们不再在工具的index.html中加载 .js文件,而是在index.js异步加载。这样让代码的更新变得更轻松。
  • 验证测试在本地chromium中运行
  • 每一个工具都用不同的命令托管,而不是一起托管。
  • 工具的资源文件放在源码目录中
  • 项目的依赖关系在typescript编译过程中计算。
  • 不再需要index-local.html。

(仅管理员) 如何发布新版本

要发布新版本,需要在build目录下的config.json提交一个更新。

一下是一个配置的示例:

{
    "npmTag": "preview",
    "versionDefinition": "prerelease",
    "preid": "beta",
    "nonce": 1
}

versionDefinition 可以是一个完整的版本号(如 5.0.0-rc.6) 或则为下列的npm version版本修饰之一:

major | minor | patch | prerelease

  • 如果在相同的版本定义中更新版本,你需要增加 nonce值。
  • 如果想从X.X.X-alpha.X切换到beta,versionDefinition设置为prerelease,preid设置为beta。
  • 目前,所有的包都将被更新到新的版本,在以后的发布中,只有被更新的包才会更新版本。

问题及排查

npm 安装包失败

如果npm的安装由于依赖问题失败,尝试运行npm install --legacy-peer-deps

如果在安装依赖后,npm安装由于在初始化构建过程中失败了。请联系我们,因为这是本仓库的基础功能,不应该有失败。

运行npm命令是报node.js 已经被打开

大概报如下的错误:

[5560:0216/142123.997:ERROR:display_layout.cc(562)] PlacementList must be sorted by first 8 bits of display_id
[65092:0216/142124.736:ERROR:display_layout.cc(562)] PlacementList must be sorted by first 8 bits of display_id 
[42568:0216/142125.139:ERROR:broker_win.cc(56)] Error reading broker pipe: The pipe has been ended. (0x6D)

这个原因是因为npm检测到一个node.js文件(这是我们生成的文件)。这个要看你的系统配置。解决这个问题,需要从@babylonjs/core的根目录下删除node.js文件。

然后,建议运行npm run clean -w @babylonjs/core.

评论

发表回复