diff --git a/.gitignore b/.gitignore index f4f2efe57..9ee1441e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ server/**/*.js +typescriptCompiler/**/*.js SupEngine/**/*.js SupRuntime/**/*.js diff --git a/SupEngine/gulpfile.js b/SupEngine/gulpfile.js index 4729721c2..d1ff57a00 100644 --- a/SupEngine/gulpfile.js +++ b/SupEngine/gulpfile.js @@ -20,11 +20,15 @@ gulp.task("typescript", function() { // Browserify const browserify = require("browserify"); +const uglify = require('gulp-uglify'); const source = require("vinyl-source-stream"); +const buffer = require('vinyl-buffer'); gulp.task("browserify", [ "typescript" ], () => browserify("./src/index.js", { standalone: "SupEngine" }) .bundle() .pipe(source("SupEngine.js")) + .pipe(buffer()) + .pipe(uglify()) .pipe(gulp.dest("../public")) ); diff --git a/SupEngine/package.json b/SupEngine/package.json index 1746ad98d..1e8378bd0 100644 --- a/SupEngine/package.json +++ b/SupEngine/package.json @@ -12,6 +12,8 @@ "three": "0.81.2" }, "devDependencies": { - "@types/three": "0.0.24" + "@types/three": "0.0.24", + "gulp-uglify": "2.0.0", + "vinyl-buffer": "1.0.0" } } diff --git a/SupRuntime/SupRuntime.d.ts b/SupRuntime/SupRuntime.d.ts index e2bfd2b77..0ef822ac5 100644 --- a/SupRuntime/SupRuntime.d.ts +++ b/SupRuntime/SupRuntime.d.ts @@ -5,6 +5,7 @@ declare namespace SupRuntime { loadAsset?(player: Player, entry: any, callback: (err: Error, asset?: any) => any): void; createOuterAsset?(player: Player, asset: any): any; setupComponent?(player: SupRuntime.Player, component: any, config: any): void; + componentClassName?: string; init?(player: Player, callback: Function): void; start?(player: Player, callback: Function): void; lateStart?(player: Player, callback: Function): void; diff --git a/SupRuntime/src/index.jade b/SupRuntime/src/index.jade index 947964dbb..75d8f4c43 100644 --- a/SupRuntime/src/index.jade +++ b/SupRuntime/src/index.jade @@ -11,6 +11,5 @@ html img(src="images/superpowers-splash.svg",draggable="false") progress(max="100",value="0") - script(src="SupCore.js") script(src="SupEngine.js") script(src="SupRuntime.js") diff --git a/SupRuntime/src/index.ts b/SupRuntime/src/index.ts index 67dd6fb11..7d0e2e08c 100644 --- a/SupRuntime/src/index.ts +++ b/SupRuntime/src/index.ts @@ -32,8 +32,6 @@ export function registerResource(name: string, plugin: SupRuntime.RuntimeResourc resourcePlugins[name] = plugin; } -SupCore.system = new SupCore.System("", ""); - // Setup SupApp if ((global as any).SupApp == null) { (global as any).SupApp = null; @@ -123,7 +121,7 @@ const onLoaded = (err: Error) => { }; // Load plugins -const pluginBundleNames = [ "components", "runtime", "typescriptAPI" ]; +const pluginBundleNames = [ "components", "runtime" ]; supFetch("plugins.json", "json", (err: Error, pluginsInfo: SupCore.PluginsInfo) => { if (err != null) { diff --git a/package.json b/package.json index da7814b1d..8990445e8 100644 --- a/package.json +++ b/package.json @@ -9,5 +9,15 @@ }, "superpowers": { "systemId": "game" + }, + "dependencies": { + "combine-source-map": "^0.7.1", + "convert-source-map": "^1.1.1", + "typescript": "~1.8.2", + "uglify-js": "2.7.5" + }, + "devDependencies": { + "@types/convert-source-map": "0.0.30", + "@types/uglify-js": "^2.6.28" } } \ No newline at end of file diff --git a/plugins/default/arcadePhysics2D/runtime/ArcadeBody2D.ts b/plugins/default/arcadePhysics2D/runtime/ArcadeBody2D.ts index aafdc1860..aef7cda5f 100644 --- a/plugins/default/arcadePhysics2D/runtime/ArcadeBody2D.ts +++ b/plugins/default/arcadePhysics2D/runtime/ArcadeBody2D.ts @@ -1,3 +1,5 @@ +export const componentClassName = "Sup.ArcadePhysics2D.Body"; + export function setupComponent(player: SupRuntime.Player, component: any, config: any) { if (config.type === "box") component.setupBox(config); diff --git a/plugins/default/arcadePhysics2D/typescriptAPI/index.ts b/plugins/default/arcadePhysics2D/typescriptAPI/index.ts index 317765b05..4f2198ddf 100644 --- a/plugins/default/arcadePhysics2D/typescriptAPI/index.ts +++ b/plugins/default/arcadePhysics2D/typescriptAPI/index.ts @@ -10,5 +10,5 @@ SupCore.system.registerPlugin("typescriptAPI", "Sup SupCore.system.registerPlugin("typescriptAPI", "ArcadeBody2D", { code: fs.readFileSync(`${__dirname}/Sup.ArcadePhysics2D.Body.ts.txt`, { encoding: "utf8" }), defs: fs.readFileSync(`${__dirname}/Sup.ArcadePhysics2D.Body.d.ts.txt`, { encoding: "utf8" }), - exposeActorComponent: { propertyName: "arcadeBody2D", className: "Sup.ArcadePhysics2D.Body" }, + exposeActorComponent: "arcadeBody2D: Sup.ArcadePhysics2D.Body;" }); diff --git a/plugins/default/cubicModel/runtime/CubicModelRenderer.ts b/plugins/default/cubicModel/runtime/CubicModelRenderer.ts index 87320f439..2bad926e0 100644 --- a/plugins/default/cubicModel/runtime/CubicModelRenderer.ts +++ b/plugins/default/cubicModel/runtime/CubicModelRenderer.ts @@ -1,3 +1,5 @@ +export const componentClassName = "Sup.CubicModelRenderer"; + export function setupComponent(player: SupRuntime.Player, component: any, config: any) { // component.castShadow = config.castShadow; // component.receiveShadow = config.receiveShadow; diff --git a/plugins/default/cubicModel/typescriptAPI/index.ts b/plugins/default/cubicModel/typescriptAPI/index.ts index b7ee15e0f..5efeccf49 100644 --- a/plugins/default/cubicModel/typescriptAPI/index.ts +++ b/plugins/default/cubicModel/typescriptAPI/index.ts @@ -10,5 +10,5 @@ SupCore.system.registerPlugin("typescriptAPI", "Sup SupCore.system.registerPlugin("typescriptAPI", "CubicModelRenderer", { code: fs.readFileSync(`${__dirname}/Sup.CubicModelRenderer.ts.txt`, { encoding: "utf8" }), defs: fs.readFileSync(`${__dirname}/Sup.CubicModelRenderer.d.ts.txt`, { encoding: "utf8" }), - exposeActorComponent: { propertyName: "cubicModelRenderer", className: "Sup.CubicModelRenderer" } + exposeActorComponent: "cubicModelRenderer: Sup.CubicModelRenderer;" }); diff --git a/plugins/default/font/runtime/TextRenderer.ts b/plugins/default/font/runtime/TextRenderer.ts index 94be3f429..a9e069b68 100644 --- a/plugins/default/font/runtime/TextRenderer.ts +++ b/plugins/default/font/runtime/TextRenderer.ts @@ -1,3 +1,5 @@ +export const componentClassName = "Sup.TextRenderer"; + export function setupComponent(player: SupRuntime.Player, component: any, config: any) { component.setText(config.text); component.setOptions({ alignment: config.alignment, verticalAlignment: config.verticalAlignment, size: config.size, color: config.color }); diff --git a/plugins/default/font/typescriptAPI/index.ts b/plugins/default/font/typescriptAPI/index.ts index b88538868..8a714567c 100644 --- a/plugins/default/font/typescriptAPI/index.ts +++ b/plugins/default/font/typescriptAPI/index.ts @@ -10,5 +10,5 @@ SupCore.system.registerPlugin("typescriptAPI", "Sup SupCore.system.registerPlugin("typescriptAPI", "TextRenderer", { code: fs.readFileSync(`${__dirname}/Sup.TextRenderer.ts.txt`, { encoding: "utf8" }), defs: fs.readFileSync(`${__dirname}/Sup.TextRenderer.d.ts.txt`, { encoding: "utf8" }), - exposeActorComponent: { propertyName: "textRenderer", className: "Sup.TextRenderer" } + exposeActorComponent: "textRenderer: Sup.TextRenderer;" }); diff --git a/plugins/default/gameSettings/build/buildGame.ts b/plugins/default/gameSettings/build/buildGame.ts index d438d294e..55eab7277 100644 --- a/plugins/default/gameSettings/build/buildGame.ts +++ b/plugins/default/gameSettings/build/buildGame.ts @@ -1,6 +1,8 @@ import * as async from "async"; import * as path from "path"; +import compileGame from "../../../../typescriptCompiler/compileGame"; + let projectClient: SupClient.ProjectClient; const subscribersByAssetId: { [assetId: string]: SupClient.AssetSubscriber } = {}; const subscribersByResourceId: { [resourceId: string] : SupClient.ResourceSubscriber } = {}; @@ -15,6 +17,10 @@ const statusElt = document.querySelector(".status"); const progressElt = document.querySelector("progress") as HTMLProgressElement; const detailsListElt = document.querySelector(".details ol") as HTMLOListElement; +let gameName: string; +const scriptNames: string[] = []; +const scripts: {[name: string]: string} = {}; + interface ClientExportableAsset extends SupCore.Data.Base.Asset { clientExport: (outputPath: string, callback: (err: Error) => void) => void; } @@ -30,7 +36,7 @@ function loadPlugins(callback: Function) { (cb) => { async.each(SupCore.system.pluginsInfo.list, (pluginName, cb) => { const pluginPath = `/systems/${SupCore.system.id}/plugins/${pluginName}`; - async.each([ "data", "componentConfigs" ], (name, cb) => { + async.each([ "data", "componentConfigs", "typescriptAPI" ], (name, cb) => { SupClient.loadScript(`${pluginPath}/bundles/${name}.js`, cb); }, cb); }, cb); @@ -50,7 +56,6 @@ export default function build(socket: SocketIOClient.Socket, theSettings: GameBu function onEntriesReceived(theEntries: SupCore.Data.Entries) { entries = theEntries; - projectClient.unsubEntries(entriesSubscriber); // Manifest projectClient.socket.emit("sub", "manifest", null, onManifestReceived); @@ -59,13 +64,13 @@ function onEntriesReceived(theEntries: SupCore.Data.Entries) { // Assets entries.walk((entry) => { if (entry.type != null) { - // Only subscribe to assets that can be exported - if (SupCore.system.data.assetClasses[entry.type].prototype.clientExport != null) { + // Only subscribe to assets that can be exported and scripts + if (SupCore.system.data.assetClasses[entry.type].prototype.clientExport != null || entry.type === "script") { const subscriber = { onAssetReceived }; subscribersByAssetId[entry.id] = subscriber; projectClient.subAsset(entry.id, entry.type, subscriber); - progress.total++; + if (entry.type !== "script") progress.total++; } } }); @@ -83,14 +88,13 @@ function onEntriesReceived(theEntries: SupCore.Data.Entries) { } // TODO: Extra build files - const systemBuildFiles = [ "/SupCore.js" ]; + const systemBuildFiles = []; const pluginsInfo = SupCore.system.pluginsInfo; const systemPath = `/systems/${SupCore.system.id}`; for (const plugin of pluginsInfo.list) { systemBuildFiles.push(`${systemPath}/plugins/${plugin}/bundles/components.js`); systemBuildFiles.push(`${systemPath}/plugins/${plugin}/bundles/runtime.js`); - systemBuildFiles.push(`${systemPath}/plugins/${plugin}/bundles/typescriptAPI.js`); } systemBuildFiles.push(`${systemPath}/plugins.json`); @@ -144,7 +148,8 @@ function onEntriesReceived(theEntries: SupCore.Data.Entries) { function onManifestReceived(err: string, manifestPub: SupCore.Data.ProjectManifestPub) { projectClient.socket.emit("unsub", "manifest"); - const exportedProject = { name: manifestPub.name, assets: entries.getForStorage() }; + gameName = manifestPub.name; + const exportedProject = { name: gameName, assets: entries.getForStorage(["script"]) }; const json = JSON.stringify(exportedProject, null, 2); const projectPath = `${settings.outputFolder}/project.json`; @@ -165,10 +170,33 @@ function updateProgress() { if (progress.index < progress.total) { statusElt.textContent = SupClient.i18n.t("builds:game.progress", { path: settings.outputFolder, index: progress.index, total: progress.total }); - } else if (progress.errors > 0) { - statusElt.textContent = SupClient.i18n.t("builds:game.doneWithErrors", { path: settings.outputFolder, total: progress.total, errors: progress.errors }); } else { - statusElt.textContent = SupClient.i18n.t("builds:game.done", { path: settings.outputFolder, total: progress.total }); + projectClient.unsubEntries(entriesSubscriber); + + statusElt.textContent = "Compiling scripts..."; + + compileGame(gameName, SupCore.system, true, scriptNames, scripts, (err, code) => { + if (err != null) { + progress.errors++; + SupClient.html("li", { parent: detailsListElt, textContent: "Compilation failed."}); + + statusElt.textContent = SupClient.i18n.t("builds:game.doneWithErrors", { path: settings.outputFolder, total: progress.total, errors: progress.errors }); + } else { + const outputPath = `${settings.outputFolder}/script.js`; + SupApp.writeFile(outputPath, code, { encoding: "utf8" }, (err) => { + if (err != null) { + progress.errors++; + SupClient.html("li", { parent: detailsListElt, textContent: SupClient.i18n.t("builds:game.errors.exportFailed", { path: outputPath }) }); + } + + if (progress.errors > 0) { + statusElt.textContent = SupClient.i18n.t("builds:game.doneWithErrors", { path: settings.outputFolder, total: progress.total, errors: progress.errors }); + } else { + statusElt.textContent = SupClient.i18n.t("builds:game.done", { path: settings.outputFolder, total: progress.total }); + } + }); + } + }); } } @@ -176,6 +204,13 @@ function onAssetReceived(assetId: string, asset: ClientExportableAsset) { projectClient.unsubAsset(assetId, subscribersByAssetId[assetId]); delete subscribersByAssetId[assetId]; + if (projectClient.entries.byId[assetId].type === "script") { + const scriptName = `${projectClient.entries.getPathFromId(assetId)}.ts`; + scriptNames.push(scriptName); + scripts[scriptName] = `${asset.pub.text}\n`; + return; + } + const outputFolder = `${settings.outputFolder}/assets/${entries.getStoragePathFromId(assetId)}`; SupApp.mkdirp(outputFolder, () => { diff --git a/plugins/default/light/runtime/Light.ts b/plugins/default/light/runtime/Light.ts index 40470fc60..2eec8cb43 100644 --- a/plugins/default/light/runtime/Light.ts +++ b/plugins/default/light/runtime/Light.ts @@ -16,6 +16,8 @@ export function init(player: any, callback: Function) { callback(); } +export const componentClassName = "Sup.Light"; + export function setupComponent(player: SupRuntime.Player, component: Light, config: any) { (component).__outer.type = ["ambient", "point", "spot", "directional"].indexOf(config.type); component.color = parseInt(config.color, 16); diff --git a/plugins/default/light/typescriptAPI/index.ts b/plugins/default/light/typescriptAPI/index.ts index bbeb5f1c1..1f46596fc 100644 --- a/plugins/default/light/typescriptAPI/index.ts +++ b/plugins/default/light/typescriptAPI/index.ts @@ -5,5 +5,5 @@ import * as fs from "fs"; SupCore.system.registerPlugin("typescriptAPI", "Light", { code: fs.readFileSync(`${__dirname}/Sup.Light.ts.txt`, { encoding: "utf8" }), defs: fs.readFileSync(`${__dirname}/Sup.Light.d.ts.txt`, { encoding: "utf8" }), - exposeActorComponent: { propertyName: "light", className: "Sup.Light" } + exposeActorComponent: "light: Sup.Light;" }); diff --git a/plugins/default/model/runtime/ModelRenderer.ts b/plugins/default/model/runtime/ModelRenderer.ts index 743a799ef..fb06c7ad7 100644 --- a/plugins/default/model/runtime/ModelRenderer.ts +++ b/plugins/default/model/runtime/ModelRenderer.ts @@ -1,3 +1,5 @@ +export const componentClassName = "Sup.ModelRenderer"; + export function setupComponent(player: SupRuntime.Player, component: any, config: any) { component.castShadow = config.castShadow; component.receiveShadow = config.receiveShadow; diff --git a/plugins/default/model/typescriptAPI/index.ts b/plugins/default/model/typescriptAPI/index.ts index 06f3589a6..b57a3518c 100644 --- a/plugins/default/model/typescriptAPI/index.ts +++ b/plugins/default/model/typescriptAPI/index.ts @@ -10,5 +10,5 @@ SupCore.system.registerPlugin("typescriptAPI", "Sup SupCore.system.registerPlugin("typescriptAPI", "ModelRenderer", { code: fs.readFileSync(`${__dirname}/Sup.ModelRenderer.ts.txt`, { encoding: "utf8" }), defs: fs.readFileSync(`${__dirname}/Sup.ModelRenderer.d.ts.txt`, { encoding: "utf8" }), - exposeActorComponent: { propertyName: "modelRenderer", className: "Sup.ModelRenderer" } + exposeActorComponent: "modelRenderer: Sup.ModelRenderer;" }); diff --git a/plugins/default/scene/runtime/Camera.ts b/plugins/default/scene/runtime/Camera.ts index 789ae843a..2a9a057fc 100644 --- a/plugins/default/scene/runtime/Camera.ts +++ b/plugins/default/scene/runtime/Camera.ts @@ -1,3 +1,5 @@ +export const componentClassName = "Sup.Camera"; + export function setupComponent(player: SupRuntime.Player, component: any, config: any) { component.setOrthographicMode(config.mode === "orthographic"); component.setFOV(config.fov); diff --git a/plugins/default/sprite/runtime/SpriteRenderer.ts b/plugins/default/sprite/runtime/SpriteRenderer.ts index a339de072..206544596 100644 --- a/plugins/default/sprite/runtime/SpriteRenderer.ts +++ b/plugins/default/sprite/runtime/SpriteRenderer.ts @@ -1,3 +1,5 @@ +export const componentClassName = "Sup.SpriteRenderer"; + export function setupComponent(player: SupRuntime.Player, component: any, config: any) { component.castShadow = config.castShadow; component.receiveShadow = config.receiveShadow; diff --git a/plugins/default/sprite/typescriptAPI/index.ts b/plugins/default/sprite/typescriptAPI/index.ts index 6247cee22..cf281ec20 100644 --- a/plugins/default/sprite/typescriptAPI/index.ts +++ b/plugins/default/sprite/typescriptAPI/index.ts @@ -10,5 +10,5 @@ SupCore.system.registerPlugin("typescriptAPI", "Sup SupCore.system.registerPlugin("typescriptAPI", "SpriteRenderer", { code: fs.readFileSync(`${__dirname}/Sup.SpriteRenderer.ts.txt`, { encoding: "utf8" }), defs: fs.readFileSync(`${__dirname}/Sup.SpriteRenderer.d.ts.txt`, { encoding: "utf8" }), - exposeActorComponent: { propertyName: "spriteRenderer", className: "Sup.SpriteRenderer" } + exposeActorComponent: "spriteRenderer: Sup.SpriteRenderer;" }); diff --git a/plugins/default/tileMap/runtime/TileMapRenderer.ts b/plugins/default/tileMap/runtime/TileMapRenderer.ts index 5c384d4ce..41434ace0 100644 --- a/plugins/default/tileMap/runtime/TileMapRenderer.ts +++ b/plugins/default/tileMap/runtime/TileMapRenderer.ts @@ -1,3 +1,5 @@ +export const componentClassName = "Sup.TileMapRenderer"; + export function setupComponent(player: SupRuntime.Player, component: any, config: any) { if (config.tileMapAssetId == null) return; diff --git a/plugins/default/tileMap/typescriptAPI/index.ts b/plugins/default/tileMap/typescriptAPI/index.ts index 6c5748dad..becf492f7 100644 --- a/plugins/default/tileMap/typescriptAPI/index.ts +++ b/plugins/default/tileMap/typescriptAPI/index.ts @@ -15,5 +15,5 @@ SupCore.system.registerPlugin("typescriptAPI", "Sup SupCore.system.registerPlugin("typescriptAPI", "TileMapRenderer", { code: fs.readFileSync(`${__dirname}/Sup.TileMapRenderer.ts.txt`, { encoding: "utf8" }), defs: fs.readFileSync(`${__dirname}/Sup.TileMapRenderer.d.ts.txt`, { encoding: "utf8" }), - exposeActorComponent: { propertyName: "tileMapRenderer", className: "Sup.TileMapRenderer" } + exposeActorComponent: "tileMapRenderer: Sup.TileMapRenderer;" }); diff --git a/plugins/default/typescript/data/ScriptAsset.ts b/plugins/default/typescript/data/ScriptAsset.ts index 108894050..527f7cd46 100644 --- a/plugins/default/typescript/data/ScriptAsset.ts +++ b/plugins/default/typescript/data/ScriptAsset.ts @@ -1,6 +1,6 @@ /// -/// -/// +/// +/// import * as OT from "operational-transform"; import * as fs from "fs"; @@ -34,7 +34,7 @@ let globalDefs = ""; if ((global).window == null) { const serverRequire = require; ts = serverRequire("typescript"); - compileTypeScript = serverRequire("../runtime/compileTypeScript").default; + compileTypeScript = serverRequire("../../../../typescriptCompiler/compileTypeScript").default; SupCore.system.requireForAllPlugins("typescriptAPI/index.js"); const plugins = SupCore.system.getPlugins("typescriptAPI"); @@ -42,7 +42,7 @@ if ((global).window == null) { for (const pluginName in plugins) { const plugin = plugins[pluginName]; if (plugin.defs != null) globalDefs += plugin.defs; - if (plugin.exposeActorComponent != null) actorComponentAccessors.push(`${plugin.exposeActorComponent.propertyName}: ${plugin.exposeActorComponent.className};`); + if (plugin.exposeActorComponent != null) actorComponentAccessors.push(plugin.exposeActorComponent); } globalDefs = globalDefs.replace("// INSERT_COMPONENT_ACCESSORS", actorComponentAccessors.join("\n ")); @@ -220,10 +220,6 @@ Sup.registerBehavior(${behaviorName}); }); } - clientExport(outputPath: string, callback: (err: Error) => void) { - this.write(SupApp.writeFile, outputPath, callback); - } - private write(writeFile: Function, outputPath: string, callback: (err: Error) => void) { writeFile(path.join(outputPath, "script.ts"), this.pub.text, { encoding: "utf8" }, callback); } diff --git a/plugins/default/typescript/editors/apiBrowser/index.ts b/plugins/default/typescript/editors/apiBrowser/index.ts index 30e1c9cbd..54892619d 100644 --- a/plugins/default/typescript/editors/apiBrowser/index.ts +++ b/plugins/default/typescript/editors/apiBrowser/index.ts @@ -59,8 +59,8 @@ function onAPILoaded() { if (name === "lib") name = "Built-ins"; if (plugin.exposeActorComponent != null) { - name = plugin.exposeActorComponent.className; - actorComponentAccessors.push(`${plugin.exposeActorComponent.propertyName}: ${plugin.exposeActorComponent.className};`); + name = `Sup.${name}`; + actorComponentAccessors.push(plugin.exposeActorComponent); } if (plugin.defs != null) allDefs[name] = plugin.defs.replace(/\r\n/g, "\n"); } diff --git a/plugins/default/typescript/editors/script/network.ts b/plugins/default/typescript/editors/script/network.ts index ed02bf698..3a295230f 100644 --- a/plugins/default/typescript/editors/script/network.ts +++ b/plugins/default/typescript/editors/script/network.ts @@ -364,7 +364,7 @@ function loadPlugins() { for (const pluginName in plugins) { const plugin = plugins[pluginName]; if (plugin.defs != null) globalDefs += plugin.defs; - if (plugin.exposeActorComponent != null) actorComponentAccessors.push(`${plugin.exposeActorComponent.propertyName}: ${plugin.exposeActorComponent.className};`); + if (plugin.exposeActorComponent != null) actorComponentAccessors.push(plugin.exposeActorComponent); } globalDefs = globalDefs.replace("// INSERT_COMPONENT_ACCESSORS", actorComponentAccessors.join("\n ")); diff --git a/plugins/default/typescript/index.d.ts b/plugins/default/typescript/index.d.ts index 619b1a0ab..d35cc9854 100644 --- a/plugins/default/typescript/index.d.ts +++ b/plugins/default/typescript/index.d.ts @@ -1,4 +1,3 @@ -/// /// /// /// diff --git a/plugins/default/typescript/package.json b/plugins/default/typescript/package.json index dacb842f4..15f1b1c4f 100644 --- a/plugins/default/typescript/package.json +++ b/plugins/default/typescript/package.json @@ -11,13 +11,7 @@ "build": "gulp --gulpfile=../../../../../scripts/pluginGulpfile.js --cwd=. --silent && gulp --gulpfile=workerGulpfile.js" }, "dependencies": { - "combine-source-map": "^0.7.1", - "convert-source-map": "^1.1.1", "highlight.js": "^9.0.0", - "operational-transform": "^0.2.3", - "typescript": "~1.8.2" - }, - "devDependencies": { - "@types/convert-source-map": "0.0.30" + "operational-transform": "^0.2.3" } } diff --git a/plugins/default/typescript/runtime/Behavior.ts b/plugins/default/typescript/runtime/Behavior.ts index fc4950781..bf6488398 100644 --- a/plugins/default/typescript/runtime/Behavior.ts +++ b/plugins/default/typescript/runtime/Behavior.ts @@ -1,3 +1,5 @@ +export const componentClassName = "Sup.Behavior"; + export function setupComponent(player: SupRuntime.Player, component: any, config: any) { if (config.propertyValues == null) return; diff --git a/plugins/default/typescript/runtime/script.ts b/plugins/default/typescript/runtime/script.ts index cdc65b217..8520d8c58 100644 --- a/plugins/default/typescript/runtime/script.ts +++ b/plugins/default/typescript/runtime/script.ts @@ -1,21 +1,6 @@ /// -import * as convert from "convert-source-map"; -// No definition file for combine-source-map module -/* tslint:disable */ -const combine: any = require("combine-source-map"); -/* tslint:enable */ -import compileTypeScript from "./compileTypeScript"; - -const globalNames: string[] = []; -const globals: {[name: string]: string} = {}; -const globalDefs: {[name: string]: string} = {}; - -const scriptNames: string[] = []; -const scripts: {[name: string]: string} = {}; - const actorComponentTypesByName: {[name: string]: any} = {}; -const actorComponentAccessors: string[] = []; export function init(player: any, callback: Function) { player.behaviorClasses = {}; @@ -24,8 +9,6 @@ export function init(player: any, callback: Function) { return new (window).Sup.Actor(name, parentActor, options); }; - const plugins = SupCore.system.getPlugins("typescriptAPI"); - player.createComponent = (type: string, actor: any, config: any) => { if (type === "Behavior") { const behaviorClass = player.behaviorClasses[config.behaviorName]; @@ -38,97 +21,20 @@ export function init(player: any, callback: Function) { } else { if (actorComponentTypesByName[type] == null) { actorComponentTypesByName[type] = window; - const parts = plugins[type].exposeActorComponent.className.split("."); + const parts = SupRuntime.plugins[type].componentClassName.split("."); for (const part of parts) actorComponentTypesByName[type] = actorComponentTypesByName[type][part]; } return new actorComponentTypesByName[type](actor); } }; - - for (const pluginName in plugins) { - const plugin = plugins[pluginName]; - if (plugin.code != null) { - globalNames.push(`${pluginName}.ts`); - globals[`${pluginName}.ts`] = plugin.code; - } - - if (plugin.defs != null) globalDefs[`${pluginName}.d.ts`] = plugin.defs; - if (plugin.exposeActorComponent != null) actorComponentAccessors.push(`${plugin.exposeActorComponent.propertyName}: ${plugin.exposeActorComponent.className};`); - } callback(); } export function start(player: SupRuntime.Player, callback: Function) { - console.log("Compiling scripts..."); - - // Plug component accessors exposed by plugins into Sup.Actor class - const joinedActorComponentAccessors = actorComponentAccessors.join("\n "); - globals["Sup.Actor.ts"] = globals["Sup.Actor.ts"].replace("// INSERT_COMPONENT_ACCESSORS", joinedActorComponentAccessors); - globalDefs["Sup.Actor.d.ts"] = globalDefs["Sup.Actor.d.ts"].replace("// INSERT_COMPONENT_ACCESSORS", joinedActorComponentAccessors); - - // Make sure the Sup namespace, Sup.Actor and Sup.ActorComponent are compiled before everything else - globalNames.unshift(globalNames.splice(globalNames.indexOf("Sup.Actor.ts"), 1)[0]); - globalNames.unshift(globalNames.splice(globalNames.indexOf("Sup.ts"), 1)[0]); - - // Compile plugin globals - const jsGlobals = compileTypeScript(globalNames, globals, `${globalDefs["lib.d.ts"]}\ndeclare var console, window, localStorage, player, SupEngine, SupRuntime, require;`, { sourceMap: false }); - if (jsGlobals.errors.length > 0) { - for (const error of jsGlobals.errors) console.log(`${error.file}(${error.position.line}): ${error.message}`); - callback(new Error("Compilation failed. Check the devtools (F12) for errors.")); - return; - } - - // Compile game scripts - let concatenatedGlobalDefs = ""; - for (const name in globalDefs) concatenatedGlobalDefs += globalDefs[name]; - const results = compileTypeScript(scriptNames, scripts, concatenatedGlobalDefs, { sourceMap: true }); - if (results.errors.length > 0) { - for (const error of results.errors) console.log(`${error.file}(${error.position.line}): ${error.message}`); - callback(new Error("Compilation failed. Check the devtools (F12) for errors.")); - return; - } - - console.log("Compilation successful!"); - - // Prepare source maps - const getLineCounts = (text: string) => { - let count = 1, index = -1; - while (true) { - index = text.indexOf("\n", index + 1); - if (index === -1) break; - count++; - } - return count; - }; - - jsGlobals.script = `(function() { -var player = _player; _player = undefined; -${jsGlobals.script} -})(); -`; - - let line = getLineCounts(jsGlobals.script) + 2; - const combinedSourceMap = combine.create("bundle.js"); - for (const file of results.files) { - const comment = convert.fromObject(results.sourceMaps[file.name]).toComment(); - combinedSourceMap.addFile({ sourceFile: `/${player.gameName}/${file.name}`, source: file.text + `\n${comment}` }, { line }); - line += getLineCounts(file.text); - } - - const code = `${jsGlobals.script}${results.script} -//# sourceMappingURL=data:application/json;charset=utf-8;base64,${combinedSourceMap.base64()}`; - - // Execute the generated code - const scriptFunction = new Function("_player", code); - scriptFunction(player); - - callback(); -} + player.getAssetData(`script.js`, "text", (err, script) => { + const scriptFunction = new Function("_player", script); + scriptFunction(player); -export function loadAsset(player: SupRuntime.Player, entry: any, callback: (err: Error, asset?: any) => any) { - scriptNames.push(`${entry.path}.ts`); - player.getAssetData(`assets/${entry.storagePath}/script.ts`, "text", (err, script) => { - scripts[`${entry.path}.ts`] = `${script}\n`; - callback(null, script); + callback(); }); } diff --git a/plugins/default/typescript/typescriptAPI/TypeScriptAPIPlugin.d.ts b/plugins/default/typescript/typescriptAPI/TypeScriptAPIPlugin.d.ts index ec8dd078e..60b35f57d 100644 --- a/plugins/default/typescript/typescriptAPI/TypeScriptAPIPlugin.d.ts +++ b/plugins/default/typescript/typescriptAPI/TypeScriptAPIPlugin.d.ts @@ -2,6 +2,6 @@ declare namespace SupCore { export interface TypeScriptAPIPlugin { code: string; defs: string; - exposeActorComponent?: { propertyName: string; className: string; }; + exposeActorComponent?: string; } } diff --git a/plugins/default/typescript/typescriptAPI/index.ts b/plugins/default/typescript/typescriptAPI/index.ts index 2616b1b74..07100955c 100644 --- a/plugins/default/typescript/typescriptAPI/index.ts +++ b/plugins/default/typescript/typescriptAPI/index.ts @@ -3,7 +3,7 @@ import * as fs from "fs"; SupCore.system.registerPlugin("typescriptAPI", "lib", { - defs: fs.readFileSync(`${__dirname}/../node_modules/typescript/lib/lib.core.d.ts`, { encoding: "utf8" }), + defs: fs.readFileSync(`${__dirname}/../../../../node_modules/typescript/lib/lib.core.d.ts`, { encoding: "utf8" }), code: "", }); @@ -40,7 +40,7 @@ SupCore.system.registerPlugin("typescriptAPI", "Sup SupCore.system.registerPlugin("typescriptAPI", "Camera", { code: fs.readFileSync(`${__dirname}/Sup.Camera.ts.txt`, { encoding: "utf8" }), defs: fs.readFileSync(`${__dirname}/Sup.Camera.d.ts.txt`, { encoding: "utf8" }), - exposeActorComponent: { propertyName: "camera", className: "Sup.Camera" } + exposeActorComponent: "camera: Sup.Camera;" }); SupCore.system.registerPlugin("typescriptAPI", "Sup.Color", { diff --git a/plugins/extra/cannonjs/runtime/CannonBody.ts b/plugins/extra/cannonjs/runtime/CannonBody.ts index 07a25376e..b92d78673 100644 --- a/plugins/extra/cannonjs/runtime/CannonBody.ts +++ b/plugins/extra/cannonjs/runtime/CannonBody.ts @@ -1,3 +1,5 @@ +export const componentClassName = "Sup.Cannon.Body"; + export function setupComponent(player: SupRuntime.Player, component: any, config: any) { component.setup(config); } diff --git a/plugins/extra/cannonjs/typescriptAPI/index.ts b/plugins/extra/cannonjs/typescriptAPI/index.ts index e858d7cc8..5ce25e1f2 100644 --- a/plugins/extra/cannonjs/typescriptAPI/index.ts +++ b/plugins/extra/cannonjs/typescriptAPI/index.ts @@ -10,5 +10,5 @@ SupCore.system.registerPlugin("typescriptAPI", "CAN SupCore.system.registerPlugin("typescriptAPI", "CannonBody", { code: fs.readFileSync(`${__dirname}/Sup.Cannon.Body.ts.txt`, { encoding: "utf8" }), defs: fs.readFileSync(`${__dirname}/Sup.Cannon.Body.d.ts.txt`, { encoding: "utf8" }), - exposeActorComponent: { propertyName: "cannonBody", className: "Sup.Cannon.Body" } + exposeActorComponent: "cannonBody: Sup.Cannon.Body" }); diff --git a/plugins/extra/p2js/runtime/P2Body.ts b/plugins/extra/p2js/runtime/P2Body.ts index 07a25376e..73f8d1f08 100644 --- a/plugins/extra/p2js/runtime/P2Body.ts +++ b/plugins/extra/p2js/runtime/P2Body.ts @@ -1,3 +1,5 @@ +export const componentClassName = "Sup.P2.Body"; + export function setupComponent(player: SupRuntime.Player, component: any, config: any) { component.setup(config); } diff --git a/plugins/extra/p2js/typescriptAPI/index.ts b/plugins/extra/p2js/typescriptAPI/index.ts index 0f8339856..fcf36460e 100644 --- a/plugins/extra/p2js/typescriptAPI/index.ts +++ b/plugins/extra/p2js/typescriptAPI/index.ts @@ -10,5 +10,5 @@ SupCore.system.registerPlugin("typescriptAPI", "p2" SupCore.system.registerPlugin("typescriptAPI", "P2Body", { code: fs.readFileSync(`${__dirname}/Sup.P2.ts.txt`, { encoding: "utf8" }), defs: fs.readFileSync(`${__dirname}/Sup.P2.d.ts.txt`, { encoding: "utf8" }), - exposeActorComponent: { propertyName: "p2Body", className: "Sup.P2.Body" } + exposeActorComponent: "p2Body: Sup.P2.Body;" }); diff --git a/server/index.ts b/server/index.ts index 992c852ed..280c0339f 100644 --- a/server/index.ts +++ b/server/index.ts @@ -4,18 +4,33 @@ import * as fs from "fs"; import * as async from "async"; import * as mkdirp from "mkdirp"; -SupCore.system.serverBuild = (server: ProjectServer, buildPath: string, callback: (err: string) => void) => { - const exportedProject = { name: server.data.manifest.pub.name, assets: server.data.entries.getForStorage() }; +import compileGame from "../typescriptCompiler/compileGame"; - fs.mkdirSync(`${buildPath}/assets`); +const scriptNames: string[] = []; +const scripts: {[name: string]: string} = {}; +SupCore.system.serverBuild = (server: ProjectServer, buildPath: string, callback: (err: string) => void) => { const assetIdsToExport: string[] = []; server.data.entries.walk((entry: SupCore.Data.EntryNode, parent: SupCore.Data.EntryNode) => { if (entry.type != null) assetIdsToExport.push(entry.id); }); + fs.mkdirSync(`${buildPath}/assets`); + scriptNames.length = 0; + async.each(assetIdsToExport, (assetId, cb) => { server.data.assets.acquire(assetId, null, (err: Error, asset: SupCore.Data.Base.Asset) => { + const entry = server.data.entries.byId[assetId]; + if (entry.type === "script") { + const scriptName = `${server.data.entries.getPathFromId(assetId)}.ts`; + scriptNames.push(scriptName); + scripts[scriptName] = `${asset.pub.text}\n`; + + server.data.assets.release(assetId, null); + cb(); + return; + } + const folderPath = `${buildPath}/assets/${server.data.entries.getStoragePathFromId(assetId)}`; mkdirp(folderPath, (err) => { asset.save(folderPath, (err) => { @@ -42,11 +57,21 @@ SupCore.system.serverBuild = (server: ProjectServer, buildPath: string, callback }, (err) => { if (err != null) { callback("Could not export all resources"); return; } - const json = JSON.stringify(exportedProject, null, 2); + const gameName = server.data.manifest.pub.name; + const json = JSON.stringify({ name: gameName, assets: server.data.entries.getForStorage(["script"]) }, null, 2); fs.writeFile(`${buildPath}/project.json`, json, { encoding: "utf8" }, (err) => { if (err != null) { callback("Could not save project.json"); return; } - callback(null); + // Pre-compile scripts + compileGame(gameName, server.system, false, scriptNames, scripts, (err, code) => { + if (err != null) { callback("Could not compile game"); return; } + + fs.writeFile(`${buildPath}/script.js`, code, { encoding: "utf8" }, (err) => { + if (err != null) { callback("Could not save script.js"); return; } + + callback(null); + }); + }); }); }); }); diff --git a/typescriptCompiler/compileGame.ts b/typescriptCompiler/compileGame.ts new file mode 100644 index 000000000..072ec0b4b --- /dev/null +++ b/typescriptCompiler/compileGame.ts @@ -0,0 +1,95 @@ +/// +/// + +import compileTypescript from "./compileTypeScript"; +import * as uglify from "uglify-js"; +import * as convert from "convert-source-map"; + +// No definition file for combine-source-map module +/* tslint:disable-next-line */ +const combine: any = require("combine-source-map"); + +export default function compileGame(gameName: string, system: SupCore.System, minimizeSize: boolean, +scriptNames: string[], scripts: { [name: string]: string }, callback: (err: string, code: string) => void) { + + const globalNames: string[] = []; + const globals: {[name: string]: string} = {}; + const globalDefs: {[name: string]: string} = {}; + + const actorComponentAccessors: string[] = []; + + const plugins = system.getPlugins("typescriptAPI"); + for (const pluginName in plugins) { + const plugin = plugins[pluginName]; + if (plugin.code != null) { + globalNames.push(`${pluginName}.ts`); + globals[`${pluginName}.ts`] = plugin.code; + } + + if (plugin.defs != null) globalDefs[`${pluginName}.d.ts`] = plugin.defs; + if (plugin.exposeActorComponent != null) actorComponentAccessors.push(plugin.exposeActorComponent); + } + + // Plug component accessors exposed by plugins into Sup.Actor class + const joinedActorComponentAccessors = actorComponentAccessors.join("\n "); + globals["Sup.Actor.ts"] = globals["Sup.Actor.ts"].replace("// INSERT_COMPONENT_ACCESSORS", joinedActorComponentAccessors); + globalDefs["Sup.Actor.d.ts"] = globalDefs["Sup.Actor.d.ts"].replace("// INSERT_COMPONENT_ACCESSORS", joinedActorComponentAccessors); + + // Make sure the Sup namespace, Sup.Actor and Sup.ActorComponent are compiled before everything else + globalNames.unshift(globalNames.splice(globalNames.indexOf("Sup.Actor.ts"), 1)[0]); + globalNames.unshift(globalNames.splice(globalNames.indexOf("Sup.ts"), 1)[0]); + + // Compile plugin globals + const libSource = `${globalDefs["lib.d.ts"]}\ndeclare var console, window, localStorage, player, SupEngine, SupRuntime, require;`; + const jsGlobals = compileTypescript(globalNames, globals, libSource, { sourceMap: false }); + if (jsGlobals.errors.length > 0) { + for (const error of jsGlobals.errors) console.log(`${error.file}(${error.position.line}): ${error.message}`); + callback("Compilation failed. Check the devtools (F12) for errors.", null); + return; + } + + jsGlobals.script = `(function() { +var player = _player; _player = undefined; +${jsGlobals.script} +})(); +`; + + // Compile game scripts + let concatenatedGlobalDefs = ""; + for (const name in globalDefs) concatenatedGlobalDefs += globalDefs[name]; + const jsScripts = compileTypescript(scriptNames, scripts, concatenatedGlobalDefs, { sourceMap: true }); + if (jsScripts.errors.length > 0) { + for (const error of jsScripts.errors) console.log(`${error.file}(${error.position.line}): ${error.message}`); + callback("Compilation failed. Check the devtools (F12) for errors.", null); + return; + } + + let code: string; + + if (minimizeSize) { + code = uglify.minify(`${jsGlobals.script}${jsScripts.script}`, { fromString: true, mangle: false }).code; + } else { + // Prepare source maps + const getLineCounts = (text: string) => { + let count = 1, index = -1; + while (true) { + index = text.indexOf("\n", index + 1); + if (index === -1) break; + count++; + } + return count; + }; + + let line = getLineCounts(jsGlobals.script) + 2; + const combinedSourceMap = combine.create("bundle.js"); + for (const file of jsScripts.files) { + const comment = convert.fromObject(jsScripts.sourceMaps[file.name]).toComment(); + combinedSourceMap.addFile({ sourceFile: `${gameName}/${file.name}`, source: file.text + `\n${comment}` }, { line }); + line += getLineCounts(file.text); + } + + code = `${jsGlobals.script}${jsScripts.script}//# sourceMappingURL=data:application/json;charset=utf-8;base64,${combinedSourceMap.base64()}`; + } + + callback(null, code); +} diff --git a/plugins/default/typescript/runtime/compileTypeScript.ts b/typescriptCompiler/compileTypeScript.ts similarity index 100% rename from plugins/default/typescript/runtime/compileTypeScript.ts rename to typescriptCompiler/compileTypeScript.ts diff --git a/typescriptCompiler/gulpfile.js b/typescriptCompiler/gulpfile.js new file mode 100644 index 000000000..f2c3940d2 --- /dev/null +++ b/typescriptCompiler/gulpfile.js @@ -0,0 +1,23 @@ +"use strict"; + +const gulp = require("gulp"); + +// TypeScript +const ts = require("gulp-typescript"); +const tsProject = ts.createProject("./tsconfig.json"); +const tslint = require("gulp-tslint"); + +gulp.task("typescript", function() { + let failed = false; + const tsResult = tsProject.src() + .pipe(tslint()) + .pipe(tslint.report("prose", { emitError: true })) + .on("error", (err) => { throw err; }) + .pipe(tsProject()) + .on("error", () => { failed = true; }) + .on("end", () => { if (failed) throw new Error("There were TypeScript errors."); }); + return tsResult.js.pipe(gulp.dest("./")); +}); + +// All +gulp.task("default", [ "typescript" ]); diff --git a/typescriptCompiler/tsconfig.json b/typescriptCompiler/tsconfig.json new file mode 100644 index 000000000..b755f2727 --- /dev/null +++ b/typescriptCompiler/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "noImplicitAny": true, + "typeRoots": [ "../../../node_modules/@types" ] + } +}