Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add typescript #5667

Merged
merged 5 commits into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions admin/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,17 @@ module.exports = (env, argv) => {
return /node_modules/.test(modulePath) && !/node_modules\/hubs/.test(modulePath);
}
},
{
// We use babel to handle typescript so that features are correctly polyfilled for our targeted browsers. It also ends up being
// a good deeal faster since it just strips out types. It does NOT typecheck. Typechecking is only done at build and (ideally) in your editor.
test: /\.tsx?$/,
loader: "babel-loader",
options: require("../babel.config"),
include: [path.resolve(__dirname, "src")],
exclude: function (modulePath) {
return /node_modules/.test(modulePath) && !/node_modules\/hubs/.test(modulePath);
}
},
// TODO worker-loader has been deprecated, but we need "inline" support which is not available yet
// ideally instead of inlining workers we should serve them off the root domain instead of CDN.
{
Expand Down
18 changes: 14 additions & 4 deletions babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,25 @@ module.exports = {
{
exclude: ["transform-regenerator"],
// targets are defined in .browserslistrc
// false = do not polyfill stuff unneccessarily
useBuiltIns: false
useBuiltIns: "entry",
// This should be kept up to date with thee version in package.json
corejs: "3.24.1",
// We care more about perf than exactly conforming to the spec
loose: true,
exclude: [
// These exist since forever but end up being polyfilled anyway because of an obscure issue if length is not writeable
"es.array.push",
"es.array.unshift"
],
// Enable to sse resolved targets and polyfills being used
debug: false
}
]
],
"@babel/typescript"
],
plugins: [
// TODO: When i18n build pipeline is finished move to: [ "react-intl", { "removeDefaultMessage": true } ]
"react-intl",
"transform-react-jsx-img-import",
["@babel/proposal-class-properties", { loose: true }],
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
["@babel/plugin-proposal-private-methods", { loose: true }],
Expand Down
1,249 changes: 834 additions & 415 deletions package-lock.json

Large diffs are not rendered by default.

19 changes: 12 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
"scripts": {
"info": "npm-scripts-info",
"start": "webpack serve --mode=development --env loadAppConfig=1",
"dev": "webpack serve --mode=development --env remoteDev=1",
"dev": "webpack serve --mode=development --env remoteDev=1 --progress",
"local": "webpack serve --mode=development --env localDev=1",
"build": "rimraf ./dist && webpack --mode=production",
"check": "tsc",
"build": "npm run check && rimraf ./dist && webpack --mode=production",
"bundle-analyzer": "webpack serve --mode=production --env dev=1 --env bundleAnalyzer=1",
"doc": "node ./scripts/doc/build.js",
"prettier": "prettier --write '*.js' 'src/**/*.js'",
Expand Down Expand Up @@ -84,7 +85,7 @@
"classnames": "^2.2.5",
"color": "^3.1.2",
"copy-to-clipboard": "^3.0.8",
"core-js": "^3.6.5",
"core-js": "^3.24.1",
"dashjs": "^3.1.0",
"deepmerge": "^2.1.1",
"detect-browser": "^3.0.1",
Expand Down Expand Up @@ -146,14 +147,14 @@
"zip-loader": "^1.1.0"
},
"devDependencies": {
"@babel/core": "^7.18.9",
"@babel/core": "^7.18.13",
"@babel/eslint-parser": "^7.18.9",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-object-rest-spread": "^7.18.9",
"@babel/plugin-proposal-optional-chaining": "7.18.9",
"@babel/polyfill": "^7.4.4",
"@babel/preset-env": "^7.18.9",
"@babel/preset-env": "^7.18.10",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@babel/register": "^7.18.9",
"@calm/eslint-plugin-react-intl": "^1.4.1",
"@formatjs/cli": "^5.0.6",
Expand All @@ -166,11 +167,12 @@
"@storybook/react": "^6.5.9",
"@storybook/storybook-deployer": "^2.8.12",
"@svgr/webpack": "^6.3.1",
"@types/three": "^0.141.0",
"@types/webxr": "^0.5.0",
"acorn": "^8.8.0",
"ava": "^4.3.1",
"babel-loader": "^8.2.5",
"babel-plugin-react-intl": "^8.2.21",
"babel-plugin-transform-react-jsx-img-import": "^0.1.4",
"copy-webpack-plugin": "^11.0.0",
"cors": "^2.8.5",
"css-loader": "^6.7.1",
Expand All @@ -184,6 +186,7 @@
"esm": "^3.2.25",
"fast-plural-rules": "1.0.2",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^7.2.13",
"fs-extra": "^10.1.0",
"glob": "^8.0.3",
"html-loader": "^4.1.0",
Expand Down Expand Up @@ -216,6 +219,8 @@
"stylelint-config-recommended-scss": "^7.0.0",
"stylelint-scss": "^4.3.0",
"tar": "^6.1.11",
"ts-loader": "^9.3.1",
"typescript": "^4.7.4",
"url-loader": "^4.1.1",
"webpack": "^5.74.0",
"webpack-bundle-analyzer": "^4.5.0",
Expand Down
156 changes: 104 additions & 52 deletions src/App.js → src/app.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,56 @@
import * as bitecs from "bitecs";
import { addEntity, createWorld } from "bitecs";
import { addEntity, createWorld, IWorld } from "bitecs";
import "./aframe-to-bit-components";
import { AEntity, Networked, Object3DTag, Owned } from "./bit-components";
import MediaSearchStore from "./storage/media-search-store";
import Store from "./storage/store";
import qsTruthy from "./utils/qs_truthy";

import type { AElement, AScene } from "aframe";
import HubChannel from "./utils/hub-channel";
import MediaDevicesManager from "./utils/media-devices-manager";

import {
Audio,
AudioListener,
Clock,
Object3D,
PerspectiveCamera,
PositionalAudio,
Scene,
sRGBEncoding,
WebGLRenderer
} from "three";
import { AudioSettings, SourceType } from "./components/audio-params";
import { DialogAdapter } from "./naf-dialog-adapter";

declare global {
interface Window {
$B: typeof bitecs;
$O: (eid: number) => Object3D | undefined;
APP: App;
}
const APP: App;
}

interface HubsWorld extends IWorld {
scene: Scene;
nameToComponent: {
object3d: typeof Object3DTag;
networked: typeof Networked;
owned: typeof Owned;
AEntity: typeof AEntity;
};
ignoredNids: Set<number>;
deletedNids: Set<number>;
nid2eid: Map<number, number>;
eid2obj: Map<number, Object3D>;
time: { delta: number; elapsed: number; tick: number; then: number };
}

window.$B = bitecs;

const timeSystem = world => {
const timeSystem = (world: HubsWorld) => {
const { time } = world;
const now = performance.now();
const delta = now - time.then;
Expand All @@ -20,58 +62,68 @@ const timeSystem = world => {
};

export class App {
constructor() {
this.scene = null;
this.store = new Store();
this.mediaSearchStore = new MediaSearchStore();
this.hubChannel = null;
this.mediaDevicesManager = null;

// TODO: Remove comments
// TODO: Rename or reconfigure these as needed
this.audios = new Map(); // el -> (THREE.Audio || THREE.PositionalAudio)
this.sourceType = new Map(); // el -> SourceType
this.audioOverrides = new Map(); // el -> AudioSettings
this.zoneOverrides = new Map(); // el -> AudioSettings
this.audioDebugPanelOverrides = new Map(); // SourceType -> AudioSettings
this.sceneAudioDefaults = new Map(); // SourceType -> AudioSettings
this.gainMultipliers = new Map(); // el -> Number
this.supplementaryAttenuation = new Map(); // el -> Number
this.clippingState = new Set();
this.mutedState = new Set();
this.isAudioPaused = new Set();

this.world = createWorld();
scene?: AScene;
hubChannel?: HubChannel;
mediaDevicesManager?: MediaDevicesManager;

store = new Store();
mediaSearchStore = new MediaSearchStore();

audios = new Map<AElement, PositionalAudio | Audio>();
sourceType = new Map<AElement, SourceType>();
audioOverrides = new Map<AElement, AudioSettings>();
zoneOverrides = new Map<AElement, AudioSettings>();
audioDebugPanelOverrides = new Map<SourceType, AudioSettings>();
sceneAudioDefaults = new Map<SourceType, AudioSettings>();
gainMultipliers = new Map<AElement, number>();
supplementaryAttenuation = new Map<AElement, number>();
clippingState = new Set<AElement>();
mutedState = new Set<AElement>();
isAudioPaused = new Set<AElement>();

world: HubsWorld = createWorld();

str2sid: Map<string | null, number>;
sid2str: Map<number, string | null>;
nextSid = 1;

audioListener: AudioListener;

dialog = new DialogAdapter();

RENDER_ORDER = {
HUD_BACKGROUND: 1,
HUD_ICONS: 2,
CURSOR: 3
};

constructor() {
// TODO: Create accessor / update methods for these maps / set
this.world.eid2obj = new Map(); // eid -> Object3D
this.world.eid2obj = new Map();

this.world.nid2eid = new Map();
this.world.deletedNids = new Set();
this.world.ignoredNids = new Set();

this.str2sid = new Map([[null, 0]]);
this.sid2str = new Map([[0, null]]);
this.nextSid = 1;

window.$o = eid => {
this.world.eid2obj.get(eid);
};

// reserve entity 0 to avoid needing to check for undefined everywhere eid is checked for existance
addEntity(this.world);

// used in aframe and networked aframe to avoid imports
this.world.nameToComponent = {
object3d: Object3DTag,
networked: Networked,
owned: Owned,
AEntity
};

// reserve entity 0 to avoid needing to check for undefined everywhere eid is checked for existance
addEntity(this.world);

this.str2sid = new Map([[null, 0]]);
this.sid2str = new Map([[0, null]]);

window.$O = eid => this.world.eid2obj.get(eid);
}

// TODO nothing ever cleans these up
getSid(str) {
getSid(str: string) {
if (!this.str2sid.has(str)) {
const sid = this.nextSid;
this.nextSid = this.nextSid + 1;
Expand All @@ -82,25 +134,25 @@ export class App {
return this.str2sid.get(str);
}

getString(sid) {
getString(sid: number) {
return this.sid2str.get(sid);
}

// This gets called by a-scene to setup the renderer, camera, and audio listener
// TODO ideally the contorl flow here would be inverted, and we would setup this stuff,
// initialize aframe, and then run our own RAF loop
setupRenderer(sceneEl) {
setupRenderer(sceneEl: AScene) {
const canvas = document.createElement("canvas");
canvas.classList.add("a-canvas");
canvas.dataset.aframeCanvas = true;
canvas.dataset.aframeCanvas = "true";

// TODO this comes from aframe and prevents zoom on ipad.
// This should alreeady be handleed by disable-ios-zoom but it does not appear to work
canvas.addEventListener("touchmove", function(event) {
canvas.addEventListener("touchmove", function (event) {
event.preventDefault();
});

const renderer = new THREE.WebGLRenderer({
const renderer = new WebGLRenderer({
// TODO we should not be using alpha: false https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#avoid_alphafalse_which_can_be_expensive
alpha: true,
antialias: true,
Expand All @@ -120,49 +172,49 @@ export class App {

// These get overridden by environment-system but setting to the highly expected defaults to avoid any extra work
renderer.physicallyCorrectLights = true;
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.outputEncoding = sRGBEncoding;

sceneEl.appendChild(renderer.domElement);

const camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.05, 10000);
const camera = new PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.05, 10000);

const audioListener = new THREE.AudioListener();
APP.audioListener = audioListener;
const audioListener = new AudioListener();
this.audioListener = audioListener;
camera.add(audioListener);

const renderClock = new THREE.Clock();
const renderClock = new Clock();

// TODO NAF currently depends on this, it should not
sceneEl.clock = renderClock;

// TODO we should have 1 source of truth for time
APP.world.time = {
this.world.time = {
delta: 0,
elapsed: 0,
then: performance.now(),
tick: 0
};

APP.world.scene = sceneEl.object3D;
this.world.scene = sceneEl.object3D;

// Main RAF loop
function mainTick(_rafTime, xrFrame) {
const mainTick = (_rafTime: number, xrFrame: XRFrame) => {
// TODO we should probably be using time from the raf loop itself
const delta = renderClock.getDelta() * 1000;
const time = renderClock.elapsedTime * 1000;

// TODO pass this into systems that care about it (like input) once they are moved into this loop
sceneEl.frame = xrFrame;

timeSystem(APP.world);
timeSystem(this.world);

// Tick AFrame systems and components
if (sceneEl.isPlaying) {
sceneEl.tick(time, delta);
}

renderer.render(sceneEl.object3D, camera);
}
};

// This gets called after all system and component init functions
sceneEl.addEventListener("loaded", () => {
Expand Down
2 changes: 1 addition & 1 deletion src/avatar.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import configs from "./utils/configs";
import { disableiOSZoom } from "./utils/disable-ios-zoom";
disableiOSZoom();

import { App } from "./App";
import { App } from "./app";

import AvatarPreview from "./react-components/avatar-preview";

Expand Down
Loading