Typescript Structure Question

I’m posting this here because I abandoned Reddit and I’m severely allergic to StackOverflow’s environment.

My IF-Octane engine’s build process works by merging a selected set of JavaScript files, chosen according to the build’s target platform, and then procedurally generating an additional bit of JavaScript as a wrapper and adding it to the mix.

Some of the available JavaScript files are mutually-exclusive to each other, and provide alternative implementations of certain functions, based on the build target.

I’m circling Typescript like a shark right now, and I’m not sure if it’s possible to move my project to Typescript and keep my build process intact, which is a requirement I can’t really sacrifice.

Is there a way to create Typescript declaration files which stand in for clusters of JavaScript files alternatives? So far, it seems like there has to be a 1-to-1 declaration file for each JavaScript file, and you need to pick which of these files you are using way ahead of time, which is not possible for my build process.

I’ll survive if I can’t use Typescript for this project, but I prefer statically typed languages over dynamic ones, so it doesn’t hurt to ask.

Also I’m sorry if my project structure is cursed. I don’t like having two copies of the same code, if I can help it, and some of the code necessarily has to be generated during the build process, because it’s data injection.

Thank you for your time! :grin:

3 Likes

I’d seriously consider changing your build process, it sounds way more complicated than it needs to be.

Typescript is pretty ideal for multiple platforms because you can have some files define the interfaces, and any platform agnostic implementation, and then other files do the platform specific implementations. Then you can have relatively simple files that just import the various platform specific implementations of each component and stitch them together.

It’s hard to give any more specific advice without knowing the details of your project, but I’d strongly recommend you think if you can restructure it to be based just on ES modules. If you need to procedurally generate anything you can still import it like any other module.

3 Likes

Specifically the build process uses five “modules” (structurally-speaking; not ES6 or anything):

  1. World model and platform-agnostic engine logic
  2. UI code
  3. Asset management code
  4. Procedurally-generated code
  5. Game-specific code

Item 5 is created per-project, and contains the code used for one specific game. Items 1-4 basically gets linked from another location.

Item 2 has two mutually-exclusive alternatives:
A. Web browser UI
B. NodeJS text-only terminal UI

Item 3 does not get included for the NodeJS build, and has two mutually-exclusive alternatives:
A. Code which handles game-specific imported assets
B. Code which reports errors, if no assets were marked for import

Because managing an interactive HTML document is so vastly different from writing to a text-only NodeJS terminal, I wasn’t sure how else to organize this. The game-specific code is not meant to handle these two platforms differently; the engine code is supposed to step in and handle that.

1 Like

Right, so 1-4 are basically the platform library, while 5 is like the app that depends on the library. It’s not too dissimilar to how AsyncGlk works.

I have three index files for AsyncGlk, for the common, browser, and node exports:

The ideal would be for your game specific code to just import the library and let the bundler sort out the platform for you. Which you can do, with the exports field of package.json! I haven’t done that for AsyncGlk yet, but I will before I eventually publish it on npm. The exports map is very powerful, but I think in your use case you’d only need something like this:

"exports": {
    ".": {
      "node": "./dist/index-node.js",
      "browser": "./dist/index-browser.js"
    }
}

Assuming you’re using a new enough bundler (I use esbuild) it will then pick up the appropriate export for you, so your game-specific code can just do something like import octane from 'octane' and everything will work.

1 Like

I promise I don’t do crimes against code on purpose. I didn’t know any other software engineers before showing up here, so I’ve largely been teaching myself from a clean coding textbook and other docs online.

Most of the software majors I met at my university actually hated software and were just in it for the cash, while the rest either did not want to interact or only knew how to speak in LinkedIn corporate-talk and wanted to discuss “boosting market value” instead of implementation.

And then the coding sphere on YouTube is mostly younger tweens who dump everything into global scope and never structure or refactor anything ever and somehow managed to create a game engine on their own, amd everyone is like “wow what a rockstar” and the repo is a mess, and— [rant interrupted]

Ohhhh!!! :exploding_head: It’s time for me to learn bundlers!!!

Thanks for the pointer!! :star_struck:

3 Likes

Ah yes, definitely use a bundler! There are many options, but I’d recommend esbuild. It’s very powerful but not too complicated to use, and it aligns with my programming style, such as bundling CSS as CSS, not by “importing” CSS into JS. (If that is your style then something like Webpack would probably be better. But I couldn’t use it because it seemingly doesn’t support having a CSS file as an entry point.) You can run esbuild just as a program, such as this example from emglken:

npx esbuild src/asyncglk.ts \
    --bundle \
    --format=esm \
    --outfile=build/asyncglk.js \
    --packages=external \
    --platform=node

Or you can use it’s JS API, which I do in Parchment’s build.js. (Ignore all the “copy” parts - when I started this esbuild didn’t have a good way to just copy files. I think it does now, but I haven’t updated my build system as it’s working fine how it is.) Parchment has various projects with their own entry points and options, which it then merges with default options before running in esbuild. Esbuild also has a “watch” mode to automatically rebuild when the source files change, and it also has a basic webserver for testing.

1 Like

Ohhhh, so I could have my build process do this:

  1. Generate the injected asset data *.js file.
  2. Have the result be accompanied by a Typescript module declaration file and included with the bundler.
  3. Use the bundler to convert the separate Typescript files and also bundle everything together into a two JavaScript files, one for wrapping into the HTML file, and the other for NodeJS play.
  4. Do any necessary uglifying and minifying of the JavaScript.
  5. Compile CSS from Sass, and minify.
  6. Assemble the all-in-one final HTML file with the bundler’s output and CSS file.

I could probably also figure out create an engine-side node script to do this, so that I don’t need to duplicate the engine code for every game project (because npm will wanna add it to the node_modules directory every single time), and so I don’t need to copy all of my build tools to every game project either. I might be able to somehow register my engine’s directory as a global npm package on my computer, and then edit my user bash file to have the build command available from wherever my terminal is open.

Yep that sounds like a good plan! Most bundlers will handle minifying JS/TS, as well as CSS/SASS too. Many even do HTML injection stuff, though that’s not my style. (It would be more important if you’re using web components.)

You could install it globally, or just add it to PATH.

2 Likes

It’s usually not mine, but it definitely is a core feature of this project’s outputs. :sparkles:

I am not! :grin: :white_check_mark:

Ohhhhh, noted! :memo:

I just tried this on my Invasion Burn project, as it would be pretty quick to convert.

Translated everything to Typescript, initialized npm, installed esbuild, and got everything to transpile and bundle correctly!! This is awesome!! :partying_face:

5 Likes