Jump to content

Typescript, ES6 and treeshaking


Tiggs
 Share

Recommended Posts

Hi all,

I'm currently building a game with Pixi in Typescript. I'd like to use the ES6 modules, as I'm under the impression that by doing so, I should be able to treeshake any unused PIXI code out of the final build / load it in chunks on demand.

So -- do we have a set of Typescript definitions for each Pixi ES6 module, something like:

declare module '@pixi/app' {
 class Application ...
}

-- or am I just doing it wrong, and treeshaking already works with import * as PIXI from 'pixi.js'?

Quick caveat: I'm new to both Pixi and Typescript, but wildly experienced at misunderstanding and breaking stuff.

Link to comment
Share on other sites

PixiJS is not a framework but it has several plugin systems inside - the modularity is a difficult thing because there are not-obvious links between modules. 

So far I didn't see that its actually possible to make good 2D renderer with ES6 imports alone, initialization step is needed.

Thus, you have to create a bundle:

https://pixijs.io/customize/

That's default PixiJS bundle:

https://github.com/pixijs/pixi.js/blob/dev/bundles/pixi.js/src/index.js

As you see, there are not only "imports" but extra steps that has to be done after them.

Don't expect stellar results, the biggest thing is @core and I think it is comparable by weight with all other modules.

Link to comment
Share on other sites

If you use Pixi with TypeScript maybe my information will be useful for you in the future when you compile to AMD modules.

I had a problem with import in TypeScript when I compiled using "module": "AMD". I had the error when I compiled like this: "cannot find pixi.js module". Because "." (dot) int the name does not allow to compile to AMD modules.

I solved this problem very easy. I just:

  1. renamed the folder "node_modules/pixi.js" to "node_modules/pixijs"
  2. replaced in the file "pixi.js.d.ts" this code:
declare module "pixi.js" {
    export = PIXI;
}

to this:

declare module "pixijs" {
    export = PIXI;
}

My example with instruction: https://github.com/8Observer8/getting-started-with-pixijs-and-typescript
Playground: https://next.plnkr.co/edit/2dkQtKRY30nvoYmF?preview

I hope it will help you.

Link to comment
Share on other sites

1 hour ago, ivan.popelyshev said:

PixiJS is not a framework but it has several plugin systems inside - the modularity is a difficult thing because there are not-obvious links between modules. 

So far I didn't see that its actually possible to make good 2D renderer with ES6 imports alone, initialization step is needed.

Thus, you have to create a bundle:

https://pixijs.io/customize/

That's default PixiJS bundle:

https://github.com/pixijs/pixi.js/blob/dev/bundles/pixi.js/src/index.js

As you see, there are not only "imports" but extra steps that has to be done after them.

Ah, okay -- that makes perfect sense. Previously, I thought @core was gluing it all together.
 

2 hours ago, ivan.popelyshev said:

Don't expect stellar results, the biggest thing is @core and I think it is comparable by weight with all other modules.

I hear you.

I expect I'm probably going to need to load most of the full bundle whatever I do -- was just hoping I could defer some of it to reduce the bandwidth during the initial download sprint.

Since there are those not-obvious links, though -- I'll just use the full bundle for prototyping, and cut a custom one at the end. Just didn't want to head off in the 'wrong direction' with imports, and then regret it, later.

Anyway -- many thanks for taking the time to explain that. That's helped clear up a lot. Much appreciated :)
 

Link to comment
Share on other sites

1 hour ago, 8Observer8 said:

If you use Pixi with TypeScript maybe my information will be useful for you in the future when you compile to AMD modules.

I have 'esnext' pretty much welded to on, so unfortunately not, but I'm sure it will help someone.

Many thanks, anyway :)

(Your 'constructor' in the Output class is missing an 's', btw.)

Edited by Tiggs
Link to comment
Share on other sites

13 hours ago, Tiggs said:


So -- do we have a set of Typescript definitions for each Pixi ES6 module, something like:

Oh, didn't notice that. We are working on it, its a big task that we are doing for several months already :)

For now you can customize for JS-side and use typings from standard version.

Edited by ivan.popelyshev
Link to comment
Share on other sites

2 hours ago, Tiggs said:

I hear you.

I expect I'm probably going to need to load most of the full bundle whatever I do -- was just hoping I could defer some of it to reduce the bandwidth during the initial download sprint.

Good.

The weight reducing of PixiJS might be important for playable ads, but usually people use it with pixi-spine and its also big.

JS files are usually gzipped before sent , so its not that bad :)

My personal opinion on why should we allow to make custom builds of pixijs is that our library if often used as a component of custom game engines. I want to give people as much control as possible. "i made X because Y was too big and contained things i didnt need"  - that's what I'm afraid of.

Link to comment
Share on other sites

12 hours ago, ivan.popelyshev said:

Oh, didn't notice that. We are working on it, its a big task that we are doing for several months already :)

Ah, wonderful :)

That will definitely be helpful, for the future.
 

11 hours ago, ivan.popelyshev said:

Good.

The weight reducing of PixiJS might be important for playable ads, but usually people use it with pixi-spine and its also big.

JS files are usually gzipped before sent , so its not that bad :)

My personal opinion on why should we allow to make custom builds of pixijs is that our library if often used as a component of custom game engines. I want to give people as much control as possible. "i made X because Y was too big and contained things i didnt need"  - that's what I'm afraid of.

I expect the "Y was too big" thing happens a lot, tbh.

The good news is that you're winning on that front -- at least for me. I'm developing a UI-heavy game, and one of the big reasons that I chose Pixi for my UI layer was its size. 

As you said, it's delivered gzipped, so even the full version comes down the wire at only ~70kb.

For my use case, that's small enough. That said -- I'm kind of cheating.

 

I've currently bootstrapped the whole thing with a small-ish (12kb) shader, which spins up a pretty background during the first half-second or so of page load, to distract the player while Pixi loads.

That gives me enough time to then load Pixi, and gently fade some Pixi text in -- and it seems to work surprisingly well.

My plan is to then use Pixi to distract them further while the 3d layer is loading -- and (hopefully) progressively bootstrap my way to 3d without the player particularly noticing.


Truth is — it's the size of the 3d layer which is making me come back and look at deferring as much early bandwidth as I can.

Edited by Tiggs
Link to comment
Share on other sites

17 hours ago, Tiggs said:

(Your 'constructor' in the Output class is missing an 's', btw.)

Thanks! I fixed it on Sandbox and on GitHub. I wrote a private constructor because the Output class uses the Singleton pattern. Now one can create more than one instance of the Output class.

Source: https://github.com/8Observer8/getting-started-with-pixijs-and-typescript
Playground: https://next.plnkr.co/edit/2dkQtKRY30nvoYmF?preview

export class Output
{
    private _outputElement: HTMLDivElement;
    private static _instance: Output;

    private constructor() { }

    public static get Instance(): Output
    {
        if (this._instance === undefined || this._instance === null)
        {
            this._instance = new Output();
            this._instance.Initialize();
        }
        return this._instance;
    }

    /* ... */
}

 

Link to comment
Share on other sites

19 hours ago, 8Observer8 said:

Thanks! I fixed it on Sandbox and on GitHub. I wrote a private constructor because the Output class uses the Singleton pattern. Now one can create more than one instance of the Output class.

Source: https://github.com/8Observer8/getting-started-with-pixijs-and-typescript
Playground: https://next.plnkr.co/edit/2dkQtKRY30nvoYmF?preview


export class Output
{
    private _outputElement: HTMLDivElement;
    private static _instance: Output;

    private constructor() { }

    public static get Instance(): Output
    {
        if (this._instance === undefined || this._instance === null)
        {
            this._instance = new Output();
            this._instance.Initialize();
        }
        return this._instance;
    }

    /* ... */
}

 

So you're now seeing this kind of behaviour?

const myOutput = new Output()
const myOutput2 = new Output()
const myOutput3 = new Output()
myOutput.Print("This will implode, as it hasn't initialized")

I believe that's because even though Typescript knows it's private, javascript doesn't care, and will let you merrily construct new ones, regardless.

IMO, best way to handle that is to throw an error in the constructor, so creating a new instance will fail. That'll stop 'em. Something like this:

  private static _instance: Output;
  private static _locked: boolean = true; // This is only ever unlocked when we're creating the _instance, above.

  private constructor() {
    if (Output._locked) throw new Error('Output is a Singleton. Please use `Output.Instance` directly.');
  }

  public static get Instance(): Output
  {
      if (this._instance === undefined || this._instance === null)
      {
          this._locked = false;
          this._instance = new Output();
          this._locked = true;
          this._instance.Initialize();
      }
      return this._instance;
  }

Note you'll need a lock, so you don't throw an error when you try and create a new instance of the class internally. Don't believe you can do async work in a constructor, so a simple binary lock should be okay (as it should all run on the same code stack) -- but I've been horribly wrong before, so you should probably test that. A lot.

A more dangerous alternative to making the constructor implode is to make it return the static instance, instead.

Wouldn't advise it, though, because it subverts the general expectation that `new Output()` returns a non-shared version of the class.

Edited by Tiggs
Link to comment
Share on other sites

13 hours ago, Tiggs said:

I believe that's because even though Typescript knows it's private, javascript doesn't care

If you try to compile this code on local computer by TSC compiler when you run this command: tsc -p tsconfig.debug.json

        let output = new Output();


 you will get this error:

Quote

src/Game.ts:10:22 - error TS2673: Constructor of class 'Output' is private and only accessible within the class declaration.


 But JS-files will be generated and you will be able to run it. It is the very important that you can see error in the console when you run the command for compilation: tsc -p tsconfig.debug.json

Link to comment
Share on other sites

47 minutes ago, 8Observer8 said:

If you try to compile this code on local computer by TSC compiler when you run this command: tsc -p tsconfig.debug.json


        let output = new Output();


 you will get this error:


 But JS-files will be generated and you will be able to run it. It is the very important that you can see error in the console when you run the command for compilation: tsc -p tsconfig.debug.json

In Typescript -- sure. 

If you're developing code just for yourself, purely in Typescript -- then it should be fine.

The issue only kicks in when you transpile it down into a javascript module for other people to use.


Let's say you create a library containing the Output class with no TSC compile errors, that you then transpile and release as a javascript module.

If someone then loads that module using javascript, & they write `let output = new Output` in their code -- then they're not going to see that error in their console when they try and construct it.

It'll manifest itself later, maybe, if they run some code on that object that needs the initialization to have run first.

Link to comment
Share on other sites

I use TS only for myself to create web applications in pure WebGL and Babylon.js for freelance tasks. And I use TS to study how to create my own 2D / 3D game engines with multiplayer using Node.js / socket.io / MySQL with hosting on free Heroku. I am writing my website in TS and Node.js / Express / Handlebars https://ivan8observer8.herokuapp.com/ I want to make websites and web applications with interactive 3D graphics for customers. I study Phaser/TS and Babylon.js/TS for creating simple games for fun. I do not want to write libraries for JS-programmers.

Edited by 8Observer8
Link to comment
Share on other sites

27 minutes ago, 8Observer8 said:

I use TS only for myself to create web applications in pure WebGL and Babylon.js for freelance tasks. And I use TS to study how to create my own 2D / 3D game engines with multiplayer using Node.js / socket.io / MySQL with hosting on free Heroku. I am writing my website in TS and Node.js / Express / Handlebars https://ivan8observer8.herokuapp.com/ I want to make websites and web applications with interactive 3D graphics for customers. I study Phaser/TS and Babylon.js/TS for creating simple games for fun. I do not want to write libraries for JS-programmers.

I hear you. In that case you should be fine :)

I started using TS for the day job a few months ago -- and there, my code gets used in various legacy JS projects, so I have to be more careful.

But, by night, I write TS just for myself, too.


The free version of Heroku is pretty great, for no-cost playgrounds. I'm currently giving Cloudflare workers a spin for $5 a month, as I don't need a server for my game, and it's "Always on".

So far, I've only got a basic proof-of-concept loader screen up, but there's a lot of other stuff going on, under the hood.

Quick peek here, if you're interested: https://cthulhu1872.indie.workers.dev/

Link to comment
Share on other sites

1 hour ago, Tiggs said:

Quick peek here, if you're interested: https://cthulhu1872.indie.workers.dev/

It is cool but it is very heavy for my laptop:
 

Asus K53SV; 8 GB RAM, i3 2.2 GHertz (2 cores); Intel HD Graphics 3000; Nvidia Geforce GT 540M (1 GB); Windows 10

I tried to run it using Geforce. I run FireFox using Geforce. I see a slide show after 20 seconds and my laptop is very noisily.

This is my simple example how to use Radio Buttons with Babylon.js https://8observer8.github.io/babylonjs/radio-button/

Link to comment
Share on other sites

24 minutes ago, 8Observer8 said:

It is cool but it is very heavy for my laptop:


Asus K53SV; 8 GB RAM, i3 2.2 GHertz (2 cores); Intel HD Graphics 3000; Nvidia Geforce GT 540M (1 GB); Windows 10

I tried to run it using Geforce. I run FireFox using Geforce. I see a slide show after 20 seconds and my laptop is very noisily.

Ah -- good to know. I'm running it on a desktop with a GTX 980Ti, and haven't tested it on anything else yet.

Will have to work out how to detect relative GPU speed, I guess, and then request different frag shaders based on that.

I'm also running a lot of javascript on the main thread, while the shader's up. Will probably need to move some of that out, too.
 

24 minutes ago, 8Observer8 said:

This is my simple example how to use Radio Buttons with Babylon.js https://8observer8.github.io/babylonjs/radio-button/

I'm still working my way up to the 3d layer. That looks wonderful from here -- well done :)

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...