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

Node pixi.js (modular and browserified) #307

Closed
drkibitz opened this issue Sep 3, 2013 · 39 comments
Closed

Node pixi.js (modular and browserified) #307

drkibitz opened this issue Sep 3, 2013 · 39 comments
Milestone

Comments

@drkibitz
Copy link
Contributor

drkibitz commented Sep 3, 2013

See branch here: https://github.com/drkibitz/node-pixi

  • The branch does not contain final builds of the browserified bundle. To get these and test the examples, simply check out that branch and run grunt to make the bundles and distribute to examples as normal. You need Firefox installed for the unit tests to pass.
  • After this run grunt connect as normal to see the examples run as expected. There were no modifications made to any example.
@englercj
Copy link
Member

englercj commented Sep 3, 2013

I appreciate the work you have done on pixi drkibitz, but try not to jump ahead of yourself. I mentioned we are considering browserify as an option, not that we had decided to do it. I don't want you to waste your time doing things we won't use. Thanks again for your contributions!

@drkibitz
Copy link
Contributor Author

drkibitz commented Sep 3, 2013

@englercj

I told you I had already done this once, and it wasn't too bad to back port changes, including my own.

Though I have to say that I'm disappointed that you could think it may be a waste of my time. That statement is almost enough to make me want to stop contributing, and just continue with a fork. I haven't been a part of or seen any result of discussions, yet I am still using pixi for a couple of my projects, it's definitely not a waste of time.

You may be confident in your decisions, but that still doesn't mean I will agree or want to use pixi's modular direction, and what I have here will already works seamlessly with my projects now and to come. So that being said, no, this work is not a waste of time.


If you have ever converted a library before, you may be aware that all of these solutions are not that different. Once it has been converted to modular, in whatever solution, it's usually a few changes at the top and bottom of each file to change it to something else. This is only possible if it has been converted in such a way to allow it. Most of my changes do allow it: requires at the top, exports the bottom, and absolutely no "runtime" module loading. Pixi already has UMD in the Outro.js, so I think you are familiar with this (by no means am I suggesting UMD for every module). I even have another project (not pixi) that uses a preprocessor to change the module definition at build time.

Sorry I guess for me the solution is clear because I have already had these discussions and debates countless times before. It's not a concatenation script (mootools 2005) when there are already very popular solutions out there. It's not using comment annotations (TypeScript, CodeKit), when even TypeScript supports the use of require to load modules. It's definitely not Closure Library modules (used once, never again!).


The ol' AMD vs CJS Debate

I now use browserify almost exclusively where possible, and I use to swear by AMD; pushing RequireJS, almond, and r.js on cooworkers. But the fact is, Node.js is HUGE, there's no denying it. Using it in the dev process is invaluable, and about a year ago Browserify 2.0 stepped in and changed my perspective.

Browserify lets me cut the fat on dev process and focus on build time, which is what is needed with r.js anyway. It also lets the code itself be open to many more possibilities in Node.js. AMD and CommonJS both come with their stipulations, but I made the switch to Browserify as my preference for too many reasons to list, but here's a couple...

  • RequireJS can handle CommonJS, but not vice versa
  • The harmony module proposal is synchronous, sorry AMD

@drkibitz
Copy link
Contributor Author

drkibitz commented Sep 3, 2013

@englercj Sorry if I sounded a annoyed. I know you didn't mean anything by your comment but to watch out for my best interests.

I am sharing this because I already have it, I'm already using it, and makes no sense not too.

@englercj
Copy link
Member

englercj commented Sep 3, 2013

I am sharing this because I already have it, I'm already using it, and makes no sense not too.

That would have been sufficient, all I was saying was don't spend the time doing this just for us, when we haven't decided what we want to do yet. If you are doing it for yourself, then more power to you.

I'm a huge node.js user. I have designed, implemented, and scaled large production deployments using the technology. It was my proposal to use browserify, we just are exploring all options.

Again, thanks for your contributions anything that helps make pixi better is always appreciated.

@drkibitz
Copy link
Contributor Author

drkibitz commented Sep 3, 2013

That would have been sufficient,

@englercj Totally ✌️

Good to know it's your proposal as well.

@englercj
Copy link
Member

englercj commented Sep 3, 2013

I want to get to a point where we can do the same thing jquery currently does, where a grunt command controls what is put into the final library.

However, in my vision, anything that is not 100% necessary rendering core is moved to a plugin that can be conditionally included (as opposed to conditionally excluded, but it looks like their task supports that). I think they use r.js for it, but I think the same can be done via browserify.

I want pixi.js to be a lean mean rendering machine, then if a user wants to include some plugins (like MovieClips, or InteractionManager, or Spine, etc) they can. I was thinking to make each one a separate npm module (not necessarily published to the registry, but maybe) that can be pulled down and browserified in as needed. Along with that, each being a separate repo would also be desirable.

Thoughts?

@drkibitz
Copy link
Contributor Author

drkibitz commented Sep 4, 2013

@englercj I like it. The jQuery approach caters to almost every situation except one, would if you use the same method of packaging in your own application? I think something that allows you to build your own version of the lib is invaluable and required for many many people, but one should be able to skip this entirely if you cater to people who use the same build methods in their own apps. How about this...

What I vision is 2 things, and 1 is probably a stepping stone to 2. The following are only concepts.

1. Keep everything in one module, but just organize the source better

Install pixi:

npm install pixi

Within your own application source, you use CJS and browserify to build. So just require pixi submodules, and browserify will do the rest:

// In my branch things are still like require('Pixi.JS/src/pixi/display/Stage');
var Stage = require('pixi/display/Stage'); // constructor
var Renderer = require('pixi/renderers/webgl/Renderer');  // constructor
var Texture = require('pixi/textures/Texture'); // constructor
var Sprite = require('pixi/display/Sprite');  // constructor
var raf = require('pixi/utils/raf'); // function

2. Breakup into plugin modules (pixi as peerDependency)

Try to keep things organized similarly as above. I believe this may be more along the lines of your vision, but just showing an example of what I see within an application's source, using pixi, but without a pre-built version of it. An application can indeed use a pre-built version, but it can also depend on explicit versions of pixi, and pixi plugins in it's own package.json.

Install pixi and plugins:

npm install pixi
npm install pixi-display
npm install pixi-renderers-webgl
npm install pixi-textures
npm install pixi-utils
// Within your own application source, load core and initialize plugins
var pixi = require('pixi')(
    require('pixi-display'),
    require('pixi-renderers-webgl'),
    require('pixi-textures'),
    require('pixi-utils')
);
var Stage = pixi.display.Stage; // provided by pixi-display
var Renderer = pixi.renderers.webgl.Renderer; // provided by pixi-renderers-webgl
var Texture = pixi.textures.Texture; // provided by pixi-textures
var Sprite = pixi.display.Sprite; // provided by pixi-display
var raf = pixi.utils.raf;  // provided by pixi-utils

Of course what is required for any form of modularity, is a revamped architecture specifically in terms of the dependency graph. In my branch I have done a little of this work already by removing circular dependencies, but there's still quite a lot to be done (e.g. If things are only required for an instanceof check, then it can probably be done differently). Basically cleaning up the architecture first, means a much more lean dependency graph for each submodule, which will allow things to be broken up much easier after that.

@englercj
Copy link
Member

englercj commented Sep 4, 2013

I think best would be to support both. If I can get the custom builds working with browserify, then you can create a custom build to include in your project or use npm/browserify to include the pieces yourself and browserify your own thing.

I think that sounds great to me since we catch both groups.

@drkibitz
Copy link
Contributor Author

drkibitz commented Sep 4, 2013

@englercj Worth looking into? http://urequire.org/

@drkibitz
Copy link
Contributor Author

drkibitz commented Sep 4, 2013

@englercj
Copy link
Member

englercj commented Sep 4, 2013

That looks pretty sweet, I will definitely have to read more into it!

@englercj
Copy link
Member

englercj commented Sep 4, 2013

Correction: This looks amazing and filled with magical unicorn goodness. I'm nearly positive we will use CommonJS syntax throughout the modules so we can use uRequire or browserify (or just nodejs require).

I am 100% positive that I will be rewriting grapefruit and lttp to use this asap.

Great find!

@drkibitz
Copy link
Contributor Author

drkibitz commented Sep 4, 2013

I haven't tested it yet, I'm really curious what a combined library looks like.

@mikkoh
Copy link
Contributor

mikkoh commented Sep 11, 2013

Hey I had heard some buzz around Browserify. I'm pretty newb to it still. Literally just read a few articles on it and then some of the docs.

I've been using AMD and RequireJS for our libs. What I like is that it can load js asynchronously or be built to one file. It's nice when you're working on pre-production stuff to have debug messages come from single files of JS vs one massive file. (don't have to scroll and scroll to find where the error happened)

I do agree that it's a pain to bring in un AMD'd code into your project. But with browserify wouldn't you have to implement CommonJS Modules anyway for all external libraries?

Sure it's nice to distribute all JS through npm but what about something like Bower.

Am I missing the point here?

Just reading through this thread would it be interesting if you had grunt build a pixi.js file that imported the plugins that were installed through package.json? (or if using bower.json)

So for instance if you had a package.json that looked like this:

"dependencies": {
        "pixi-physics": "0.9.4",
        "pixi-movieClip": "0.4.1"
}

You run grunt and your pixi.js file would look like this:

PIXI.activatePlugin( require( 'pixi-physics' ) );
PIXI.activatePlugin( require( 'pixi-movieClip' ) );

@drkibitz
Copy link
Contributor Author

I do agree that it's a pain to bring in un AMD'd code into your project. But with browserify wouldn't you have to implement CommonJS Modules anyway for all external libraries?

Almost anything you can do with RequireJS, you should be able to do with browserify (shims, etc.), but just assume it's at build time instead of runtime.

It's nice when you're working on pre-production stuff to have debug messages come from single files of JS vs one massive file. (don't have to scroll and scroll to find where the error happened)

Source maps are used to debug and show the exact line of an error in a particular source file.

I used to have a similar workflow you described, but after switching to browserify I feel like I've cut out a middle man since I've been building with npm+grunt this whole time anyway. Just my opinion.

Toward the end of this thread I made this comment as a possible solution #307 (comment).

@englercj
Copy link
Member

@mikkoh The point here is to split the code in such a way that custom builds (combined, minified pixi.js files) that will include only the modules that you wanted in that build. The secondary goal is to have nicely segmented and easy to maintain code modules. Supporting UMD in the resulting file is also a plus.

@mattdesl
Copy link
Contributor

I definitely think browserify + NodeJS is the way to go. For example, we wouldn't need to prefix renderers with "WebGL" or "Canvas" since it would be in its own packages. It would also help encourage good programming practices such as modularity -- rather than stuff like globals (PIXI.gl, PIXI.visibleCount, texturesToUpdate, etc) and tightly coupled classes. These poor practices make it difficult for others to contribute, increase the chance of bugs creeping in, makes for an ugly code base, and also leads to problems with multiple contexts/canvases.

Also better to do this sooner rather than later. 👍

@mikkoh
Copy link
Contributor

mikkoh commented Oct 22, 2013

I've played around with Browserify and it's def really nice. Plus UMD (standalone) builds makes modules super portable.

Project I'm currently working on had to use RequireJS but the library I wanted to use was built using Browserify. No problem just did a UMD build.

@englercj
Copy link
Member

I've found browserify more useful when using core node libraries in a project that you wanted to also run in a browser. As far as pure build tool, I like urequire more. It is likely we will go with that.

Once I finish work I am doing for grapefruit, I will dedicate my freelance time to this refactor. I'll post when I have a more concrete timeline but I should be able to start in a couple weeks.

@englercj
Copy link
Member

englercj commented Feb 9, 2014

When this happens it will be Pixi 2.0, little more time yet :(

@englercj englercj added this to the v2.0 milestone Feb 9, 2014
@drkibitz
Copy link
Contributor Author

@englercj Would you consider a stop gap approach to modular code? Something that is just a stepping stop to a modular format, such as:

// The only reference to the PIXI namespace is outside of the closure.
// The closure looks just like an AMD closure.
// For CJS, this would just change to requires at the top of the file
PIXI.DisplayObjectContainer = (function (DisplayObject) {
    'use strict';

    function DisplayObjectContainer() {
        // blah
    }
    DisplayObjectContainer.prototype = Object.create(DisplayObject.prototype, {
        constructor: DisplayObjectContainer
    });

    // We return this module like an AMD module.
    // For CJS, this would just change to module.exports.
    return DisplayObjectContainer;

// We pass dependencies (no matter how many there are) like this
}(PIXI.DisplayObject));

This kind of format is a great stepping stone to something else. It helps people wrap their head around modularity, and it allows you to change the code 1 module at a time. Once the code is converted to this format, it is certain that it will work with any modular format.

@englercj
Copy link
Member

@drkibitz I plan to have a pixi-modular-binge-day where I just hardcore rewrite the entire library (also browserify will be the way I do it, because it rulez).

@mattdesl
Copy link
Contributor

Browserify will definitely clean up the build step. It also opens pixi to the wonders of NPM.

There are lots of utils in pixi which are not part of the renderer, and would prove useful for non-PIXI projects. Likewise, there are lots of NPM modules that would be better for Pixi to depend on rather than just copy-pasting their code.

Some examples:

  • canvas tinting
  • RGBA to color string utils. See the npm module 'color-string'
  • WebGL detection
  • Point/Matrix utils.
  • asset loading, including BMFont and sprite sheet parsing.
  • filters when/if they are implemented in software for canvas.
  • events. I am really not a fan of PIXIs events: it's inconsistent and limited, and the reliance on strings is ugly. Using something like js-signals would lead to a cleaner API, and decouple events from the rest of pixi.
  • triangulation. I've been using poly2tri in my own libraries, and it seems pretty robust.
  • circle/rectangle/etc utils

Sent from my iPhone

On Feb 11, 2014, at 4:56 PM, Chad Engler notifications@github.com wrote:

@drkibitz I plan to have a pixi-modular-binge-day where I just hardcore rewrite the library (also browserify will be the way I do it, because it rulez).


Reply to this email directly or view it on GitHub.

@englercj
Copy link
Member

@mattdesl exactly, I do this already in grapefruit and I plan to bring the same here as well.

@drkibitz
Copy link
Contributor Author

@englercj sounds great to me. Let me know if you need any help.

If you haven't, take a look at my fork https://github.com/drkibitz/node-pixi
I haven't had the chance to update it in a while, and is (I think) still pixi 1.3.

What I'm doing there is something I haven't tried before, but thought it was a good thing to try for distributing something on npm. There are still 2 bundled releases in the repo to match what is here, a debug and min. But these bundles are not available on npm, which is considered the distribution package. This means when you install from npm, you get individually processed modules meant to be used with browserify. Then when you clone the repo, you get the source. Nothing goes into the npm distribution that is considered source, tests, or anything of that nature. Just the processed modules, license, readme, and package.json and that's it. If people want the bundles themselves, they can download them elsewhere. If they want to contribute, they can clone the repo.

The reason for all this, is because my view of npm might be different than many other devs. Some people seem to treat it like an equivalent to github, and a place to backup their repo. Other people understand the difference, and ignore things for npm, but still don't do anything with their code, which means they are essentially distributing source on npm. I just don't get this, which is why I went a different route.

@mattdesl
Copy link
Contributor

Node requires doesn't have aliasing like in browserify, so the following wouldn't work.. (unless I'm missing something?)

blah = require('pixi/blah/path');

Instead node encourages you to split the sub-paths into different modules. Not ideal for all of pixi, but could be done for the few features I listed, without too many changes to the end user API.

The npm module should only include the source files necessary for node, a readme, and possibly some simple testing. The rest should be ignored with .npmignore to keep things light during installation.

You don't need a second distribution package. Keep everything in your github repo and just ignore it from npm as you see fit. This makes it easy to bump versions in both repos. Not sure how you are handling it now without npmignore ??

On Feb 12, 2014, at 1:03 AM, "Dr. Kibitz" notifications@github.com wrote:

@englercj sounds great to me. Let me know if you need any help.

If you haven't, take a look at my fork https://github.com/drkibitz/node-pixi
I haven't had the chance to update it in a while, and is (I think) still pixi 1.3.

What I'm doing there is something I haven't tried before, but thought it was a good thing to try for distributing something on npm. There are still 2 bundled releases in the repo to match what is here, a debug and min. But these bundles are not available on npm, which is considered the distribution package. This means when you install from npm, you get individually processed modules meant to be used with browserify. Then when you clone the repo, you get the source. Nothing goes into the npm distribution that is considered source, tests, or anything of that nature. Just the processed modules, license, readme, and package.json and that's it. If people want the bundles themselves, they can download them elsewhere. If they want to contribute, they can clone the repo.

The reason for all this, is because my view of npm might be different than many other devs. Some people seem to treat it like an equivalent to github, and a place to backup their repo. Other people understand the difference, and ignore things for npm, but still don't do anything with their code, which means they are essentially distributing source on npm. I just don't get this, which is why I went a different route.


Reply to this email directly or view it on GitHub.

@drkibitz
Copy link
Contributor Author

@mattdesl Yes your example works in node, it would just have to be a submodule (no alias). In my fork's distribution package "pixi/blah/blah" would be an actual path to a module. Basically there are no "fake" paths in the distribution package, everything lives were it is defined as such.

Using submodules is not ideal and not the recommendation, but can be a stop-gap. There is a whole plan @englercj has mentioned numerous times about splitting things up into separate packages, so I think we're all on the same page there.

When going modular, it really can be a step by step procedure. It's easy to stop yourself and say things like "why do that without this" etc.. I think the first step is getting pixi to build with modules, e.g. browserify. The second step is separating into separate packages and replacing with existing ones (no need to separate if we're replacing at that point). If this sounds easily done all at one time, then great. I'm just trying to be helpful here and remind that it doesn't have to be.

@drkibitz
Copy link
Contributor Author

You don't need a second distribution package.

@mattdesl I agree with this to some extend. But at the same time, it was required to handle pixi's file structure and still allow a reasonable submodule require path. I wanted the fork's source repo to keep the same structure as the upstream, but I wanted to distribute it with the structure as defined in src/pixi. When you look at it like that, pixi is the package, not pixi/src/pixi. When a package is simple enough yes, just use .npmingore and forget about it. When things get a bit more complex, its probably time to separate your source from your distribution. Just remember though, all of this was experimental as it pertained to pixi 1.3, but I still came to like it. npm installs from any tarball, git, local, etc. so I'm not depriving anyone from installing the source with npm. I'm just declaring pretty firmly with a build process that "this is the distribution, use this", and "this is the source, contribute to that".

@jtenner
Copy link

jtenner commented Jul 18, 2014

Re: @drkibitz

I had great success modifying my package.json.

{
  "dependencies": {
    "pixi.js": "https://github.com/GoodBoyDigital/pixi.js/tarball/master"
  }
}

Then run npm install.

Require('pixi.js') works perfectly.

I realize there is a huge desire for modularity, though.

@englercj englercj mentioned this issue Aug 8, 2014
@jonsquared
Copy link

I would like to warn about modules, mostly with respect to libraries like pixi that rely on getting the most efficiency possible. Modules are very nice for dependency linking/loading, but they are a workaround for a feature not (yet) supported by the language, and so they come with a cost.

To see a very simple example of the problem (which only gets worse with more complex cases), try doing some profiling on this code:

<html>
<head>
<script>

//This is how Pixi classes are structured
function Entity() {
    this.x = 0;
    this.y = 0;
}
Entity.prototype.getPosition = function() { return {x: this.x, y:this.y}; }
Entity.prototype.setPosition = function(x,y) { this.x = x; this.y = y; }

//This is how those classes might look if modularized
function ModuleEntity() {
    var _x = 0, _y = 0;
    return {
        getPosition: function() { return {x:_x, y:_y}; },
        setPosition: function(x,y) { _x=x; _y=y; }
    }
};

var objects = [];
var count = 100000;

function runTest1() {
    var startTime = (new Date()).valueOf();
    for (var i=0; i<count; i++)
        objects.push(new Entity());
    console.log((new Date()).valueOf() - startTime);
}

function runTest2() {
    var startTime = (new Date()).valueOf();
    for (var i=0; i<count; i++)
        objects.push(new ModuleEntity());
    console.log((new Date()).valueOf() - startTime);
}

</script>

</head>
<body>
    <button onclick="runTest1()">run test 1</button>
    <button onclick="runTest2()">run test 2</button>
</body> 
</html>

Results on my laptop (Chrome v39):

  • Test 1
    • Execution time: 40ms
    • Added memory after execution: 2.4mb
  • Test 2
    • Execution time: 183ms
    • Added memory after execution: 13.9mb

That extra memory usage is almost all retained memory from closures (which is what the module pattern leverages). I'm also creating 100k entities, which may not be an issue for small games, but it can really blow up with more complex classes even in smaller counts.

I very recently published 2 node packages that were driven by this very problem (sooper and grunt-file-dependencies)

@drkibitz
Copy link
Contributor Author

@jonsquared I'm not really sure what modules have to do with your performance comparison. You've basically written 2 different implementations, one with a constructor, and one with a closure. Either could be written with or without modules, which your example actually illustrates in one direction. There's also an anti-pattern lurking in there using a return within a constructor. There's nothing preventing modules from exporting constructors, which is exactly what I did. Here's a comparison:

function Entity() {
    this.x = 0;
    this.y = 0;
}
Entity.prototype.getPosition = function() { return {x: this.x, y:this.y}; }
Entity.prototype.setPosition = function(x,y) { this.x = x; this.y = y; }

var ExportedEntity =  /** e.g. require('./ModuleEntity'); */ (function () {
    function ModuleEntity() {
        this.x = 0;
        this.y = 0;
    }
    ModuleEntity.prototype.getPosition = function() { return {x: this.x, y:this.y}; }
    ModuleEntity.prototype.setPosition = function(x,y) { this.x = x; this.y = y; }
    return ModuleEntity; /** e.g. module.exports = ModuleEntity; */
}());

// ... do stuff

The differences are minimal, including runtime performance. The gains show up in development time, a big one being a clear dependency graph defined by the code itself.

@drkibitz
Copy link
Contributor Author

It's been a while since I've dabbled in pixi code. A year ago, there were quite a few circular dependencies throughout the pixi codebase, which took a while to fix in my fork. Since then things have changed pretty drastically, and I'm not sure if that means more or less circular dependencies.

@jonsquared
Copy link

@drkibitz
True, that type of module definition is safe and doesn't impact memory usage much. My example kind of jumped ahead to what might happen if someone doesn't pay attention to the module implementation. If you've got private variables, you've probably got an inefficient module.
Thanks for the example.

@mattdesl
Copy link
Contributor

@jonsquared I think you might be confusing npm modules with the "module pattern".

Modules are often just simple functions. Pixi could take advantage of a number of them, like arc-to, adaptive-bezier-curve, gl-buffer, gl-mat4, point-in-polygon, and so forth.

@englercj englercj mentioned this issue Dec 26, 2014
@englercj englercj modified the milestones: v3.x, v2.x Jan 24, 2015
@englercj
Copy link
Member

This has finally happened :)

@ddennis
Copy link

ddennis commented May 28, 2015

If we don't want to compile the lib, is this still the recommend way of using pixi with browserify?

npm install --save GoodBoyDigital/pixi.js 

/Dennis

@englercj
Copy link
Member

you can just do npm i -S pixi.js no need to try and pull it from github.

https://www.npmjs.com/package/pixi.js

@ddennis
Copy link

ddennis commented May 28, 2015

Hi

Sorry - i didn't know pixi was officily on npm. I kept getting https://www.npmjs.com/package/pixi which is deprecated.

Thanks for your time

/Dennis

@lock
Copy link

lock bot commented Feb 26, 2019

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked and limited conversation to collaborators Feb 26, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants