Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7338340
feat: migrate from experimental decorators to TC39 Stage 3 decorators
RaananW Mar 20, 2026
f2fc3ad
fix: update tslib import to use installed package instead of hardcode…
RaananW Mar 20, 2026
c397543
fix: resolve rebase conflicts and fix TC39 decorator migration issues
RaananW Mar 27, 2026
a999fe7
fix: resolve TC39 decorator compat issues - use declare without overr…
RaananW Mar 30, 2026
3a261a0
merge: resolve conflicts with origin/master
RaananW Apr 20, 2026
6b0f70e
fix: migrate new openpbrMaterial fields to TC39 accessor decorator pa…
RaananW Apr 20, 2026
f65c551
fix: resolve post-merge TC39 decorator compat issues
RaananW May 4, 2026
34d6c12
fix: remove override from declare fields (TS1243)
RaananW May 4, 2026
8c4625e
Merge remote-tracking branch 'upstream/master' into feature/tc39-deco…
RaananW May 4, 2026
a4cf0f2
fix: polyfill Symbol.metadata for TC39 decorator metadata support
RaananW May 4, 2026
e5db097
fix: replace Object.hasOwn with hasOwnProperty.call for Babylon Nativ…
RaananW May 5, 2026
e0c6b04
fix: replace remaining Object.hasOwn usages and remove es2022.object …
RaananW May 5, 2026
37a77f0
Fix TC39 decorator visual regressions
RaananW May 6, 2026
f43fd30
Prevent viewer idle render mutation loop
RaananW May 6, 2026
463fbb9
Fix PBR volume IOR decorator migration
RaananW May 6, 2026
fe60746
Remove stale PBR volume IOR expect error
RaananW May 6, 2026
d45231a
Merge remote-tracking branch 'upstream/master' into feature/tc39-deco…
RaananW May 11, 2026
c396d66
Merge remote-tracking branch 'upstream/master' into feature/tc39-deco…
RaananW May 20, 2026
48f8109
Track TC39 decorator metadata shim
RaananW May 20, 2026
a71c47e
Merge remote-tracking branch 'upstream/master' into feature/tc39-deco…
RaananW May 20, 2026
5418445
Fix viewer environment property decorator
RaananW May 20, 2026
cddf60c
Fix tree-shaking and TypeDoc CI checks
RaananW May 20, 2026
5cb2f8f
Merge remote-tracking branch 'upstream/master' into feature/tc39-deco…
RaananW May 20, 2026
74329af
Update manifest after upstream shader changes
RaananW May 20, 2026
10092a1
Revert tree-shaking script changes
RaananW May 20, 2026
5e6b983
Merge remote-tracking branch 'upstream/master' into feature/tc39-deco…
RaananW May 20, 2026
610f72c
Track Symbol.metadata shim side effect
RaananW May 20, 2026
45713f4
Preserve nativeOverride specialization
RaananW May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/instructions/tree-shaking.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ Do not add `export {}` just to force module mode. If the augmentation needs an i

6. **Do not create pure shader files.** Generated shader modules remain generated side-effect modules. If a pure implementation needs a shader, load the generated shader module from the owning registration/readiness path; do not split the shader file itself.

7. **TC39 decorators in `.pure.ts` must be class-local.** Decorators are allowed in pure implementation files only when their runtime effects are confined to the class or member being defined. Allowed examples include writing metadata to that class via `Symbol.metadata`, returning accessor or method descriptors for that member, and initializing per-instance backing fields through `init`. Decorators in `.pure.ts` files must not register classes or parsers globally, mutate shared registries, patch prototypes, perform side-effect imports, write to shader stores, install global polyfills, or depend on wrapper-only registration having already run. If a decorator needs any of those effects, move that work into the module's `Register*()` function and keep the decorator as a class-local metadata or descriptor operation. The only approved exception is the `Symbol.metadata` compatibility shim in `Misc/decorators.functions.ts`, which must run before TypeScript's TC39 decorator emit evaluates decorated classes on runtimes that do not yet provide `Symbol.metadata`.

## Tooling

After adding, renaming, or removing files, run these scripts to keep everything in sync:
Expand Down
255 changes: 4 additions & 251 deletions packages/dev/buildTools/src/pathTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,256 +225,9 @@ function TransformerFactory<T extends TransformerNode>(context: ts.Transformatio
export const storeTsLib = () => {
const tsLibPath = path.resolve(path.resolve(".", "tslib.es6.js"));
if (!fs.existsSync(tsLibPath)) {
fs.writeFileSync(tsLibPath, TslibContent);
// Read from the installed tslib package instead of using a hardcoded copy,
// so that the helpers stay in sync with the TypeScript version.
const tslibSource = require.resolve("tslib/tslib.es6.mjs");
fs.copyFileSync(tslibSource, tsLibPath);
}
};

// tslib 2.4.0
const TslibContent = `
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise */

var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};

export function __extends(d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}

export var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
}
return __assign.apply(this, arguments);
}

export function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}

export function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}

export function __param(paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
}

export function __metadata(metadataKey, metadataValue) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
}

export function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}

export function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}

export var __createBinding = Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
});

export function __exportStar(m, o) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);
}

export function __values(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
}

export function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
}

/** @deprecated */
export function __spread() {
for (var ar = [], i = 0; i < arguments.length; i++)
ar = ar.concat(__read(arguments[i]));
return ar;
}

/** @deprecated */
export function __spreadArrays() {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
}

export function __spreadArray(to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
}

export function __await(v) {
return this instanceof __await ? (this.v = v, this) : new __await(v);
}

export function __asyncGenerator(thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
}

export function __asyncDelegator(o) {
var i, p;
return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; }
}

export function __asyncValues(o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
}

export function __makeTemplateObject(cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
};

var __setModuleDefault = Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
};

export function __importStar(mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
}

export function __importDefault(mod) {
return (mod && mod.__esModule) ? mod : { default: mod };
}

export function __classPrivateFieldGet(receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
}

export function __classPrivateFieldSet(receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
}

export function __classPrivateFieldIn(state, receiver) {
if (receiver === null || (typeof receiver !== "object" && typeof receiver !== "function")) throw new TypeError("Cannot use 'in' operator on non-object");
return typeof state === "function" ? receiver === state : state.has(receiver);
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@ export class BakedVertexAnimationManager implements IBakedVertexAnimationManager
*/
@serializeAsTexture()
@expandToProperty("_markSubMeshesAsAttributesDirty")
public texture: Nullable<BaseTexture>;
public accessor texture: Nullable<BaseTexture>;

private _isEnabled = true;
/**
* Enable or disable the vertex animation manager
*/
@serialize()
@expandToProperty("_markSubMeshesAsAttributesDirty")
public isEnabled = true;
public accessor isEnabled = true;

/**
* The animation parameters for the mesh. See setAnimationParameters()
Expand Down
2 changes: 1 addition & 1 deletion packages/dev/core/src/Cameras/arcRotateCamera.pure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ export class ArcRotateCamera extends TargetCamera {
/**
* Defines the input associated to the camera.
*/
public override inputs: ArcRotateCameraInputsManager;
declare public inputs: ArcRotateCameraInputsManager;

/**
* Movement controller that provides framerate-independent physics and the declarative
Expand Down
4 changes: 2 additions & 2 deletions packages/dev/core/src/Cameras/followCamera.pure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ export class FollowCamera extends TargetCamera {
* Define the target of the camera.
*/
@serializeAsMeshReference("lockedTargetId")
public override lockedTarget: Nullable<AbstractMesh>;
public override lockedTarget: Nullable<AbstractMesh> = null;

/**
* Defines the input associated with the camera.
*/
public override inputs: FollowCameraInputsManager;
declare public inputs: FollowCameraInputsManager;

/**
* Instantiates the follow camera.
Expand Down
44 changes: 38 additions & 6 deletions packages/dev/core/src/Decorators/nodeDecorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,19 +107,51 @@ export function editableInPropertyPage(
groupName: string = "PROPERTIES",
options?: IEditablePropertyOption
) {
return (target: any, propertyKey: string) => {
let propStore: IPropertyDescriptionForEdition[] = target._propStore;
if (!propStore) {
return (_value: unknown, context: { name: string | symbol; metadata: DecoratorMetadataObject }) => {
const meta = context.metadata;
let propStore: IPropertyDescriptionForEdition[];
if (Object.prototype.hasOwnProperty.call(meta, __bjsPropStoreKey)) {
propStore = meta[__bjsPropStoreKey] as IPropertyDescriptionForEdition[];
} else {
propStore = [];
target._propStore = propStore;
meta[__bjsPropStoreKey] = propStore;
}
propStore.push({
propertyName: propertyKey,
propertyName: String(context.name),
displayName: displayName,
type: propertyType,
groupName: groupName,
options: options ?? {},
className: target.getClassName(),
className: "",
});
};
}

/** @internal */
// eslint-disable-next-line @typescript-eslint/naming-convention
export const __bjsPropStoreKey = "__bjs_prop_store__";

/**
* Gets the editable properties for a given target using TC39 decorator metadata.
* Walks the metadata prototype chain to include properties from parent classes.
* @param target - the target object (instance or constructor)
* @returns array of property descriptions
*/
export function GetEditableProperties(target: any): IPropertyDescriptionForEdition[] {
const ctor = typeof target === "function" ? target : target?.constructor;
const metadata: DecoratorMetadataObject | undefined = ctor?.[Symbol.metadata];
if (!metadata) {
return [];
}

const result: IPropertyDescriptionForEdition[] = [];
let currentMeta: any = metadata;
while (currentMeta) {
if (Object.prototype.hasOwnProperty.call(currentMeta, __bjsPropStoreKey)) {
const store = currentMeta[__bjsPropStoreKey] as IPropertyDescriptionForEdition[];
result.push(...store);
}
currentMeta = Object.getPrototypeOf(currentMeta);
}
return result;
}
2 changes: 1 addition & 1 deletion packages/dev/core/src/Layers/glowLayer.pure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class GlowLayer extends EffectLayer {
@serialize("options")
protected _options: IGlowLayerOptions;

protected override readonly _thinEffectLayer: ThinGlowLayer;
declare protected readonly _thinEffectLayer: ThinGlowLayer;
private _horizontalBlurPostprocess1: BlurPostProcess;
private _verticalBlurPostprocess1: BlurPostProcess;
private _horizontalBlurPostprocess2: BlurPostProcess;
Expand Down
4 changes: 2 additions & 2 deletions packages/dev/core/src/Layers/highlightLayer.pure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ interface IBlurPostProcess extends PostProcess {
* It enforces keeping the most luminous color in the color channel.
*/
class GlowBlurPostProcess extends PostProcess {
protected override _effectWrapper: ThinGlowBlurPostProcess;
declare protected _effectWrapper: ThinGlowBlurPostProcess;

constructor(
name: string,
Expand Down Expand Up @@ -210,7 +210,7 @@ export class HighlightLayer extends EffectLayer {
@serialize("options")
private _options: Required<IHighlightLayerOptions>;

protected override readonly _thinEffectLayer: ThinHighlightLayer;
declare protected readonly _thinEffectLayer: ThinHighlightLayer;
private _downSamplePostprocess: PassPostProcess;
private _horizontalBlurPostprocess: IBlurPostProcess;
private _verticalBlurPostprocess: IBlurPostProcess;
Expand Down
Loading
Loading