Splitting a project into separate files

After a rather lengthy hiatus from Processing I now find myself experimenting with p5js... Work has led me into the realms of JS development (programming skills learned largely with Processing!) and I wanted to dabble with HTML canvas stuff; so this was a natural library to play with :)

I'm wondering whether anyone has yet figured out a sensible approach to splitting projects into separate files? I really liked that feature in the Processing IDE. I just managed to get something fairly crude working with requireJS; but it seems a little convoluted and I'm having to pass references to p5 all over the place. It just doesn't feel right. The other approach I'm considering is using Grunt to combine partials into a single script file whenever I save; but that comes with setup work I'm perhaps too lazy to do frequently...

Anyone got anything up their sleeve?

Answers

  • edited April 2015

    I really liked that feature in the Processing IDE.

    Only thing PDE does is collect all ".pde" files and merge them as 1 ".java" before compilation! :-\"
    This process also happens in "Java Script" & "CoffeScript" modes.
    And I believe in some other "modes" as well.
    Maybe 1 day we end up w/ "p5*js Mode" too! O:-)

  • Fair point... But I prefer not to pollute the global namespace so am wrapping my p5 code in a function as suggested in the docs. That makes it a little more awkward to split files in a 'programmatic' way: you'd just have to arbitrarily split source code and then later concatenate into a single file. In my experience that can then cause issues with IDEs: code folding going awry etc...

    So I was hoping for something more elegant; which is what led me to consider requirejs; but that doesn't feel like a natural fit. I was hoping someone might have already come up with an elegant solution; but I guess it's still early days for p5js...

  • edited April 2015

    Still a JS rookie but AFAIK, only Processing's callbacks like preload(), setup(), draw(), mousePressed(), etc. needs to be inside the sketch's function passed to p5's constructor.
    Other functions or classes used by the main sketch can live inside other ".js" files I guess.

  • To get access to p5 methods and properties you need a reference to hook into though... That led me to include my class inside the 'sketch' function. Admittedly I've only had a short time with it, so a better approach may present itself with a little more reflection...

  • edited April 2015

    You can pass the main sketch's reference to the "remote" class' constructor in another ".js" file.
    It's the same technique used by Java Mode's libraries in order to interact w/ sketch's canvas.
    It's also true when we got ".java" files that gotta deal w/ ".pde" files.

  • Instead of RequireJS, you can use Browserify. It needs a bit less ceremony than RequireJS, but it still allows to have clean separated modules.

    Otherwise, if you don't care about modularization and want to use the technique described by GoToLoop, you have have several JS files and use cat file1.js file2.js > index.js or similar...

  • edited April 2015
    • W/ my advised technique, there's no need to concatenate anything.
    • Just load each 1 w/ <script> tags and it's done!
    • Simply share main sketch's reference among the other "classes" present in the other ".js" files.
    • I mean, when instantiating the external classes, pass this to them.
  • Thanks both for your input!

    I'd like to use modules and sensible development practices; but I also want to avoid unnecessary complexity for the sake of tidiness. So I'm looking for a sensible balance :)

    From my experiments thus far passing "the main sketch's reference" to the "remote" class isn't necessarily as simple as it first sounds. What about 'global' constants you want to pass from the main sketch to your 'classes'? I had to create an object to store them in my requirejs experiment. It looks like modularisation forces 'sensible' (but sometimes painful) practices (e.g. I had to create getters/setters for some object properties - which I'm not used to doing in JS)... But it's all another step away from the simplicity that the Processing/ps5 approach allows...

    So for the time being I'm going to take the Grunt approach: that will allow me to automate the cat process PhiLho suggests (hello again BTW: it's been a while!) as well as run a test server that will update my browser whenever I save my source code. This is all an exercise to improve my general development processes and I'm just getting my head round Vagrant, npm, Grunt and so on; so it's a useful exercise. If I manage a decent base setup I might even explore creating a Yeoman generator :)

    P.S. @GoToLoop I'd advise a certain amount of caution when recommending the use of the this keyword in JS: it's a common gotcha for JS newbies... Since the main sketch should already have an explicit reference to p5 it's far safer to pass this directly than this which can lead to unexpected results.

  • edited April 2015
    • Sorry since I've only used "Global Mode" so far, I've forgotten that coding in p5.js in "Instance Mode" we need to use the parameter passed as the skecth's reference instead of this:
      https://github.com/processing/p5.js/wiki/Instantiation-Cases
    • Heck! I don't even know whether this == passed argument reference! ~X(
    • Much probably they're not! :-S
    • Anyways, what I've meant is passing the valid reference to the external class' constructor,
      be it this or something else! 8-X
  • edited April 2015

    I'd advise a certain amount of caution when recommending the use of the this keyword in JS...

    • Indeed method functions (functions which have this inside its scope and belong to some instance) can be dangerously tricky! >-)
    • However, passing instance references around (even in the form of this) in JS is as safe as it is in Java.
    • Danger lies only when we pass a raw method function instead of its wrapper object.
    • Since in Java (at least before 8) we can't store raw methods in variables nor pass them as parameters, there's no danger there.
    • But JS variables can store function references as anything else.
    • If it's just some regular function w/o this inside, it's completely safe.
    • Otherwise, the this inside represents the reference that just called the function.
    • A classical example is setTimeout() & setInterval(). As well DOM event handlers!
    • They demand us to pass a raw function w/o its wrapper instance.
    • Since it's the global window object that invokes them, function's this = window! :-&
    • In order to remedy that, we need to bind() the passed function there w/ its true wrapper object's reference. *-:)

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors#Method_binding

    A method is not bound to the object that it is a method of. Specifically, this is not fixed in a method, i.e., this does not necessarily refer to an object containing the method. this is instead "passed" by the function call.

  • edited May 2016 Answer ✓

    From my experiments thus far passing "the main sketch's reference" to the "remote" class isn't necessarily as simple as it first sounds.

    I've taken the challenge today. I'm also trying to learn JS yet! X_X
    I'm using Firefox Developer Edition 39.0a2's WebIDE for it btW.


    "sketch.js":


    /**
     * Talking Faces (v2.05)
     * by  Casselli
     * mod GoToLoop (2015/Feb/07)
     * p5*js (2015/Apr/17)
     *
     * forum.processing.org/two/discussion/9323/
     * creating-a-extra-function-in-order-to-create-
     * multiple-instances-of-the-same-group-of-primitives
     *
     * studio.ProcessingTogether.com/sp/pad/export/ro.9G5yFfYFFDXi1/latest
     */
    
    new p5(Object.freeze(function (p) {
      const BG = Object.freeze([0x40, 0x30, 0x90]);
      const FACES = 4, FPS = 12;
      const faces = Array(FACES);
    
      p.setup = Object.freeze(function () {
        Object.freeze(p.createCanvas(600, 600).id('faces'));
    
        p.ellipseMode(p.CENTER).rectMode(p.CENTER).frameRate(FPS);
        p.stroke(Face.OUTLINE), p.strokeWeight(Face.WEIGHT);
    
        const w = p.width, h = p.height;
    
        faces[0] = new Face(p, w>>2,   h>>2,   w/3,     h/2.5);
        faces[1] = new Face(p, 3*w>>2, h>>2,   w/3/1.2, h/2.5/1.2);
        faces[2] = new Face(p, w>>2,   3*h>>2, w/3/2,   h/2.5/2);
        faces[3] = new Face(p, 3*w>>2, 3*h>>2, w/3*1.2, h/2.5*1.2);
    
        Object.freeze(faces);
    
        p.print(this == p); // Oh, they're the same!
      });
    
      p.draw = Object.freeze(function () {
        p.background(BG);
        for (var f of faces)  f.display();
      });
    }));
    


    "face.js":


    // Face's static constants:
    Face.WEIGHT = 1.5, Face.OUTLINE = 0;
    
    Face.FACE  = Object.freeze([0xFF, 0xFF, 0]);
    Face.NOSE  = Object.freeze([0xFF, 0, 0]);
    Face.MOUTH = Object.freeze([0xFF, 0, 0xFF]);
    Face.EYES  = 0;
    
    // Face's constructor:
    function Face(p, x, y, w, h) {
      this.p = p; // p5 sketch's passed reference.
    
      this.x = x, this.y = y;
      this.w = w, this.h = h;
      this.m = (w+h) / 2.0 / 9.65;
    
      Object.freeze(this); // for 100% immutable classes only!
      //Object.seal(this); // for non-immutable classes!
    }
    
    // Face's method:
    Face.prototype.display = Object.freeze(function () {
      this.p.fill(Face.FACE);
      this.p.ellipse(this.x, this.y, this.w, this.h);
    
      this.p.fill(Face.NOSE);
      this.p.ellipse(this.x, this.y, this.w/6, this.h>>2);
    
      this.p.fill(Face.EYES);
      this.p.ellipse(this.x - this.w/6.5, this.y - (this.h>>3), this.m, this.m);
      this.p.ellipse(this.x + this.w/6.5, this.y - (this.h>>3), this.m, this.m);
    
      this.p.fill(Face.MOUTH);
      this.p.rect(this.x, this.y + this.h/3.2, this.w/2.6, this.p.random(this.h>>4));
    });
    
    // Freeze the whole class agaisnt further modifications:
    Object.freeze(Face.prototype), Object.freeze(Face);
    


    "index.html":


    <!DOCTYPE html>
    <head>
    
      <meta charset="utf-8" />
      <title>Talking Faces</title>
      <style>body {border: 1px solid black;}</style>
    
      <script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.4/p5.min.js" async></script>
    
      <script src="sketch.js" defer></script>
      <script src="face.js" defer></script>
    
    </head>
        
    <body>
    
      <h1>☺  Talking Faces  ☻</h1>
    
    </body>
    


    • As you can see here: new Face(p, w>>2, h>>2, w/3, h/2.5)
    • I've passed sketch's p reference to the remote Face's constructor.
    • Coulda also been this, since p = this after all.
    • Inside Face class, sketch's reference was stored as this.p.
    • So I could manipulate sketch's canvas and other API like in these examples: \m/
      this.p.fill(Face.EYES); & this.p.random(this.h>>4).
    • And I didn't have to use any loader utility nor external tricks like file concatenations. [-(

    P.S.: Here's the original Processing sketch running online via Pjs framework: :D
    http://studio.ProcessingTogether.com/sp/pad/export/ro.9G5yFfYFFDXi1/latest

  • Thanks for taking the time to build a code example :)

    Don't get me wrong: I know the module approach can be done; and I know 'this' can sometimes be exactly what you expect; but in my experience with JS it's best avoided when there's an alternative already available ;)

    In terms of 'Class' construction I've been using Addy Osmani's Javascript Patterns as a reference at work and find the revealing module pattern a close enough approximation of a Java Class for my purposes (at least as far as I got with building Java Classes in Processing). Obviously this is something of a matter of opinion/taste but I find the resulting code neater (and notice the lack of 'this'). This code should work as a drop-in replacement to face.js:

    var Face = function (p, x, y, w, h) {
    
        // CONSTANTS
        var WEIGHT = 1.5, 
            OUTLINE = 0,
            FACE  = [0xFF, 0xFF, 0],
            NOSE  = [0xFF, 0, 0],
            MOUTH = [0xFF, 0, 0xFF],
            EYES  = 0,
    
        // PROPERTIES
            p = p,
            x = x,
            y = y,
            w = w,
            h = h,
            m = (w + h) / 2.0 / 9.65,
    
        // METHODS
            display = function () {
                p.fill(FACE);
                p.ellipse(x, y, w, h);
    
                p.fill(NOSE);
                p.ellipse(x, y, w / 6, h >> 2);
    
                p.fill(EYES);
                p.ellipse(x - w / 6.5, y - (h >> 3), m, m);
                p.ellipse(x + w / 6.5,  y - (h >> 3), m, m);
    
                p.fill(MOUTH);
                p.rect(x, y + h / 3.2, w / 2.6, p.random(h >> 4));
            };
    
        // RETURN PUBLIC PROPERTIES/METHODS HERE
        return {
            display : display
        };
    
    };
    

    Must admit Object.freeze is a new one on me: but I've only worked on relatively small front-end JS projects. I think it's probably overkill to try and make a Class immutable in a small project like this; and there's little need for making a 'constant' actually constant when it can only be accessed from within the 'class'.

  • edited April 2015
    • Indeed, the "revealing pattern" is a thing of beauty! ^:)^
    • In fact, it was the 1st thing I've considered when 1st learning JS and rebelling against the "ugly" prototype pattern!
    • Moreover, it emulates Java's private & public access level keywords! :)>-
    • But before going w/ the cons, there are other details I wanna discuss...

    Must admit Object.freeze() is a new one on me...
    ... it's probably overkill to try and make a Class immutable in a small project like this...

    • Sure it is so! But I've included it for evangelizing purposes after all. O:-)
    • How can we ever familiarize w/ it w/o being exposed to it?
    • And I agree it's a reasonable boiler plate spreading Object.freeze() everywhere! :-\"
    • But for any1 who might be studying the code, being sure something is immutable is 1 less thing to worry about when debugging the app! :-B

    ... and there's little need for making a 'constant' actually constant...

    • I disagree! It's just a matter of replacingvar w/ const. No lazy excuses! :P
    • Besides, everything is public in "prototype pattern" after all!
    • Thus const ends up being a so-so protection layer.
    • It forever binds a variable to a value during the course of its scope.
    • Similar to Object.freeze(), it's 1 less issue to worry about when debugging! :!!
    • Of course it's also important to name fields which are both constant & immutable all caps!
    • Check how your "revealing pattern" ends up w/ const in the right places:


    const Face = function (p, x, y, w, h) {
    
        // CONSTANTS:
        const WEIGHT  = 1.5, 
              OUTLINE = 0,
              FACE    = Object.freeze([0xFF, 0xFF, 0]),
              NOSE    = Object.freeze([0xFF, 0, 0]),
              MOUTH   = Object.freeze([0xFF, 0, 0xFF]),
              EYES    = 0;
    
        // VARIABLES:
        var   p = p,
              x = x,
              y = y,
              w = w,
              h = h,
              m = (w + h) / 2.0 / 9.65;
    
        // METHODS:
        const display = function () {
                  // ...
              };
    
        // ...
    };
    

    However, since all instance properties are final after being initialized inside the "constructor", everything should be const anyways (if it's not a passed parameter): \m/

    const Face = function (p, x, y, w, h) {
    
        // CONSTANTS:
        const WEIGHT  = 1.5, 
              OUTLINE = 0,
              FACE    = Object.freeze([0xFF, 0xFF, 0]),
              NOSE    = Object.freeze([0xFF, 0, 0]),
              MOUTH   = Object.freeze([0xFF, 0, 0xFF]),
              EYES    = 0,
    
        // VARIABLES:
              m = (ww + hh) / 2.0 / 9.65,
    
        // METHODS:
              display = function () {
                  // ...
              };
    
        // ...
    };
    
  • edited April 2015

    I've spotted a bug in your revealing implementation:
    You've forgotten to reveal WEIGHT & OUTLINE besides display()! :-&

    In the "sketch.js", inside setup(), there's this following statement:
    p.stroke(Face.OUTLINE), p.strokeWeight(Face.WEIGHT);

    So they gotta be public as well:

    // RETURN PUBLIC PROPERTIES/METHODS HERE:
    return Object.freeze({
               OUTLINE : OUTLINE,
               WEIGHT  : WEIGHT,
               display : display
    });
    

    Although I'm not too sure whether we can statically access a revealing class member w/o instantiating 1 1st! :-S
    Much probably we're gonna need to move those constants outside Face's revealing constructor! :(|)

  • edited April 2015

    As promised, some cons related to "revealing pattern" and why I've finally accepted the most common "prototype pattern": :-\"

    • Most serious is that "revealing pattern" wastes lotsa RAM!
    • Everything inside its "constructor" is re-created, including methods & constants!
    • While the "prototype pattern" follows Java's wisdom: only non-static fields are instantiated when creating an object from some class!
    • That is, methods & constants stay in the prototype, while fields bound to this goes to the newly-born object.
    • Let's say we create 1000 Face objects. We're gonna have 1000 of each single member as closures, plus the 1s repeated inside the returning "revealing" object!
    • In "prototype pattern", only these 6 members: (p, x, y, w, h & m), are actually instantiated.
    • Both the constants & the methods are created once only! \m/
    • Another issue is there's no clear way to get inheritance outta the "revealing pattern".
    • Much probably there is, but I haven't found that out yet! 8-}
    • However, classes in JS have almost arrived!
    • Much Probably we're gonna see them in Firefox 39 & Chrome 42! :-bd
    • So no more "protoype" nor "revealing" patterns anymore!!! <:-P
  • Hmmm... bugs :\">

    Good spot on my missing making OUTLINE and WEIGHT public; though I might argue whether these should be 'constants' under Face; and that they certainly shouldn't be applied globally in setup(): that makes it somewhat inflexible if you ever want to extend the sketch or mix in other objects...

    One strange thing I spotted though: I'm pretty sure I copy pasted your original source for sketch.js and didn't make any edits and I have:

    p.stroke(Face.STROKE)
    

    I notice you edited your post :-B

    Anyway... it looks like we're coming at this from very different perspectives. The sad reality of JS is that in a production environment you can't often rely on all these new-fangled technologies that might theoretically make your life easier and your code more 'robust': if you've got a few hundred thousand visitors to your site each day an unacceptable proportion are going to get broken content. So I'm stuck a little too firmly in the mindset of providing the best possible browser support.

    You're clearly interested in the new features in EcmaScript 6; maximum performance and probably not too concerned whether your code is going to run nicely for everyone. I'll certainly want to squeeze maximum performance out of p5 where appropriate; but I'll happily continue using the RMP when the simplicity/maintainability of the code outweighs any of the negatives you list.

    But we're now digressing wildly from the topic. I'm sure I'll have time to engage in some more theoretical discussions later on; but for now I have an answer to my original question. Thanks both!

  • edited April 2015

    I'm sure I'll have time to engage in some more theoretical discussions later on...

    Just some couple last considerations below... :P

    Good spot on my missing making OUTLINE and WEIGHT public...

    I've amended the "revealing module pattern" so those constants are defined outside and can be statically accessed from anywhere now: :)>-

    <!-- <script src="face.js" defer></script> -->
    <script src="face-revealing.js" defer></script>
    

    "face-revealing.js":


    // PUBLIC STATIC CONSTANTS
    Face.WEIGHT = 1.5, Face.OUTLINE = 0;
    
    Face.FACE  = Object.freeze([0xFF, 0xFF, 0]);
    Face.NOSE  = Object.freeze([0xFF, 0, 0]);
    Face.MOUTH = Object.freeze([0xFF, 0, 0xFF]);
    Face.EYES  = 0;
    
    function Face(p, x, y, w, h) { // Private instance variables too!
    
      // EXTRA PRIVATE INSTANCE VARIABLES
      const  m = (w + h) / 2.0 / 9.65;
    
      // METHODS
      const  display = Object.freeze(function () {
                 p.fill(Face.FACE);
                 p.ellipse(x, y, w, h);
    
                 p.fill(Face.NOSE);
                 p.ellipse(x, y, w/6, h>>2);
    
                 p.fill(Face.EYES);
                 p.ellipse(x - w/6.5, y - (h>>3), m, m);
                 p.ellipse(x + w/6.5, y - (h>>3), m, m);
    
                 p.fill(Face.MOUTH);
                 p.rect(x, y + h/3.2, w/2.6, p.random(h>>4));
             });
    
      // PUBLIC REVEALED OBJECT'S MEMBERS
      return Object.freeze({
                 display: display
             });
    
    }
    
    Object.freeze(Face.prototype), Object.freeze(Face);
    

    ... though I might argue whether these should be 'constants' under Face...

    • I view them as integral part of class Face.
    • They just happen to be used within setup() b/c they never change during the execution.
    • If I had other classes which depended on stroke() & strokeWeight(), those constant properties would be set inside draw() instead.

    I notice you edited your post.

    • All the time! That's why I've got some "version control"! :D
    • Right now it's "Talking Faces (v2.05)", b/c I've added lotsa Object.freeze() everywhere! >:)
  • edited April 2015

    You're clearly interested in the new features in ECMAScript 6; maximum performance and probably not too concerned whether your code is going to run nicely for everyone.

  • edited April 2015

    ... but I'll happily continue using the RMP when the simplicity/maintainability of the code outweighs any of the negatives you list.

    • I'll also try "revealing module pattern" again just to get rid of those boilerplate this! ~X(
    • As long as the class doesn't have many or too big methods.
    • And I don't plan classical inheritance either.
    • Since at least it's easy to move out the static constants & variables outta it.
    • Only methods gonna unnecessarily waste RAM for each instantiation now! (:|
    • And perhaps some constructor's parameters which are not meant to be kept later. =(
    • Be aware that all parameters & variables declared inside constructor live forever as closures! :-B
    • Another plus is that we don't need new in order to instantiate RMP classes! \m/
  • If you want to use ES6 features in all browsers, you can use transpilers like https://babeljs.io/ or Google's Traceur.

  • edited April 2015
    • As I've mentioned some posts above, the 2 most modern browsers (Firefox & Chrome) are already on their way to land classes in JavaScript.
    • And many ECMA 6 features like const are already available even in very old browsers like Opera 12 w/ Presto engine!
    • But thx for the transpiler tip anyways! If I'd use 1, I'd try out TypeScript or even CoffeeScript in "CoffeeScript Mode"! ;)
  • On the back of this discussion I went away and looked at the .prototype approach to adding methods to a class: and I'm glad I did. In practice I've yet to work on a project that required the instantiation of many objects; so the RMP has worked well for me: I'm not building web apps; just 'nice-to-have' features that many front-end devs churn out using jQuery. I have clean code to work with; it works well in the majority of browsers (even IE8) and it doesn't rely on any extraneous libraries.

    I'm still early in my investigations; but one limitation of .prototype methods appears to be that the parent 'Class' must be defined in the global namespace for it to work; and I've yet to figure out a way to avoid this (I don't doubt there is one; but I haven't found any reference yet). I'm deeply suspicious of anything that pollutes the global namespace: I don't have absolute control of what scripts get loaded on our site and some bright spark may decide to pull in (or even write) a poor quality script that will cause conflicts.

    Obviously I'm not using p5 in the same context; but I prefer to follow the same best practice (it would be cool if p5 encouraged best practice in a general web context too!). So requireJS is certainly one possible solution - but it's heavy going. I'll have a proper look at Browserify: I vaguely recall looking at that a while back but at the time got the impression requireJS was the way to go. With the huge popularity of node it looks like there's been a change in direction...

    As for 'transpilers', shims etc: I prefer to avoid them. They usually involve adding more processing overheads on browsers/hardware already ill-equipped to deal with the original source. Most people writing production code - that needs to support a wide range of browsers, including mobile - won't be switching to Ecmascript 6 for a few years IMHO. On the other hand if you're running it on the server-side then you don't have the same issues... and again that's in a different context to experimenting with p5!

  • edited April 2015

    ... but one limitation of .prototype methods appears to be that the parent 'Class' must be defined in the global namespace for it to work; ...

    • It's exactly the same way w/ RMP! Its returned object gotta be stored in some variable! [-(
    • The only way I know about to avoid creating any global variable is a wrapper function which calls itself.
    • However, if we got more than 1 wrapper function, there gotta exist some intermediary global variable in order for the "isolates" to access each other!
    • Even the all-powerful requireJS and other similar scripts gotta be stored in some global variable, thus "polluting" the global space. There's no way around that! 3:-O
    • How are we supposed to invoke class Face from another "isolate" if the former's not stored in some global variable or inside some global script like requireJS?
    • It doesn't matter whether it's prototype or revealing style, they gotta somehow be accessed globally. :-@
  • Sure, but it's trivial to hook RMP off a single global variable, and that's a tactic used in other languages. Well written libraries do the same or take measures to avoid namespace pollution. So I'm assuming it's possible with .prototype based 'classes', but my attempts so far have failed... Hopefully I'm missing something simple: I have too many distractions here for me to concentrate properly on this :/

  • edited May 2016

    Sure, but it's trivial to hook RMP off a single global variable, ...

    • I fail to understand why RMP would be trivial and prototype not!
    • Both approaches return 1 reference value only.
    • RMP returns its "revealing" customized object's reference filled up w/ closures.
    • While prototype returns its own constructor function's reference, w/o any closures.
    • Either way, those returning values gotta be passed around somehow.
    • We can store them in some global variable or invoke a global loader script to manage those.

    Never used any "global modular loader script" before, but outta curiosity I went to http://requirejs.org/.

    As far as I got it, rather than loading all script files we need in the ".html" file like this:

    <script src="p5.min.js" async></script>
    <script src="sketch.js" defer></script>
    <script src="face.js" defer></script>
    

    According to http://requirejs.org/docs/start.html, we'd do something like this instead:

    <script data-main="scripts/main" src="scripts/require.js"></script>
    
    • In short, we "pollute" the global naming space w/ the "require.js" script only.
    • Then "require.js" calls "main.js" as an entry point for the other scripts.
    • Therefore "require.js" is our only global script that all the others depend upon.
  • edited April 2015

    "As for 'transpilers', shims etc: I prefer to avoid them. They usually involve adding more processing overheads on browsers/hardware already ill-equipped to deal with the original source."
    Two wrong conceptions there:

    • Modern browsers have very fast JS interpreter today;
    • Transpilers usually work on the development phase, generating good old JavaScript consumable by all browsers.

    GoToLoop said:

    • As I've mentioned some posts above, the 2 most modern browsers (Firefox & Chrome) are already on their way to land classes in JavaScript.
    • And many ECMA 6 features like const are already available even in very old browsers like Opera 12 w/ Presto engine!

    "on their way" doesn't mean full support of ES6 (yet).
    And if you don't care supporting all browsers, fine for you, but some of your users might be frustrated... Of course, if you code only for yourself, that's fine.

  • Hopefully I'm missing something simple: I have too many distractions here

    Looks like it was just this; or some other issue with the code I was testing on. I went back to your example and had another attempt. I did have to move the position of your 'constants'; but otherwise this does indeed work:

    sketch.js

    /**
     * Talking Faces (v2.02)
     * by  Casselli
     * mod GoToLoop (2015/Feb/07)
     * p5*js (2015/Apr/17)
     * mod blindfish 20/04/2015
     *
     */
    var foo = {};
    
    foo.p = new p5(function (p) {
        var BG = [0x40, 0x30, 0x90],
            FPS = 12,
            faces = [];
    
        p.setup = function () {
            var w = 600,
                  h = 600;
    
            p.createCanvas(w, h);
            p.ellipseMode(p.CENTER).rectMode(p.CENTER).frameRate(FPS);
            p.stroke(foo.Face.OUTLINE);
            p.strokeWeight(foo.Face.WEIGHT);
    
            faces[0] = new foo.Face(w >> 2, h >> 2, w / 3, h / 2.5);
            faces[1] = new foo.Face(3 * w >> 2, h >> 2, w / 3 / 1.2, h / 2.5 / 1.2);
            faces[2] = new foo.Face(w >> 2, 3 * h >> 2, w / 3 / 2, h / 2.5 / 2);
            faces[3] = new foo.Face(3 * w >> 2, 3 * h >> 2, w / 3 * 1.2, h / 2.5 * 1.2);
    
        };
    
        p.draw = function () {
            p.background(BG);
            for (var i=0, len = faces.length; i<len; i++) {
                faces[i].display();
            }
        };
    });
    

    face.js

    // foo.Face's constructor:
    foo.Face = function (x, y, w, h) {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.m = (w + h) / 2.0 / 9.65;
    }
    
    // Can't add to foo.Face before it's defined
    // foo.Face's static constants:
    foo.Face.WEIGHT = 1.5;
    foo.Face.OUTLINE = 0;
    
    foo.Face.FACE = [0xFF, 0xFF, 0];
    foo.Face.NOSE = [0xFF, 0, 0];
    foo.Face.MOUTH = [0xFF, 0, 0xFF];
    foo.Face.EYES = 0;
    
    
    // foo.Face's methods:
    foo.Face.prototype.display = function () {
        var p = foo.p;
        p.fill(foo.Face.FACE);
        p.ellipse(this.x, this.y, this.w, this.h);
    
        p.fill(foo.Face.NOSE);
        p.ellipse(this.x, this.y, this.w / 6, this.h >> 2);
    
        p.fill(foo.Face.EYES);
        p.ellipse(this.x - this.w / 6.5, this.y - (this.h >> 3), this.m, this.m);
        p.ellipse(this.x + this.w / 6.5, this.y - (this.h >> 3), this.m, this.m);
    
        p.fill(foo.Face.MOUTH);
        p.rect(this.x, this.y + this.h / 3.2, this.w / 2.6, p.random(this.h >> 4));
    };
    

    I've made some other changes that reflect my preferences in terms of coding style. One advantage of having that global hook is that you can use that to pass around references to p5; rather than passing it around with parameters in your methods. I initially set var p = this.p; in Face.display() (before I'd added p5 to foo) as that made it far clearer that p referenced something outside the 'class'. I swapped it over to reference foo.p afterwards; as I still like the resulting tidiness.

    The only disadvantage I can see to this general approach is you're adding an extra level to traverse to get to the object; which might come with a minor performance hit. IMO that's a price worth paying for the extra robustness; though by all accounts the best strategy nowadays is to move to something properly modular like requireJS or Browserify...

    Note: obviously you might choose something a bit more unique for your global object ;)

  • @PhiLho said:

    Two wrong conceptions there:

    1. Modern browsers have very fast JS interpreter today;
    2. Transpilers usually work on the development phase, generating good old JavaScript consumable by all browsers.
    1. True. I remember a friend having major issues because they tested an app in IE8 set to IE7 mode but didn't test in IE7 itself. The JS engine in IE7 sucked and their app ground to a halt in the live environment. Hopefully those days are long gone!

      However I'm also targeting old browsers running on old mobile handsets. I don't add file-weight and extra processing in this context just to add curved borders on boxes or CSS3 animation. The user can live without these fancy features in favour of faster performance.

    2. My mistake and the clue is of course in the name.

  • Indeed, the bottleneck is now less the old browsers (although my public library still has IE6 on their public computers!), but browsers on mobile phones: still need to have lightweight libraries for them.

Sign In or Register to comment.