Struggling with modules

Posted on
  • I normally think of myself as a pretty bright person, but for the life of me I seem to be dumb as a lump of granite when it comes to the Module mechanism. What I want to do is conceptually pretty simple: Make a reusable object class. Maybe the module mechanism isn't suited for this? After half an hour looking through source code for a dozen modules or so, I couldn't find anything that looked like what I'm trying to do.

    I'd like to be able to do the following... The application would be coded as follows:

    require("myClass");
    
    abc = new myClass();
    console.log(abc.add(1,2));
    console.log(abc.getNextPresident());
    

    And the module, myClass.js, looks like this:

    function myClass() {
        this.nextPresident = "Bozo the clown";
    }
    
    myClass.prototype.add = function(a,b) { return a+b; };
    myClass.prototype.getNextPresident = function() { return this.nextPresident; };
    
    exports myClass;
    

    This, doesn't work. After the require, Espruino seems completely unaware of any constructor function "myClass".

    I can see how to work around this by adding an exported interface to create an object instance directly (this is done again and again in modules with the common 'connect' function on an exports declaration), but this is very "kludgy" to me -- isn't there any way to simply include something that acts like a traditional library?

    Also, I find that when I do this, it results in a doubling of space usage. The module containing all the method functions gets cached (one copy of everything), then all these functions get created again when constructed in the initial call. To make it clearer, here's what I've done to make this work:

    exports myClass.init = function() {
    	myClass = function() {
    	    this.nextPresident = "Bozo the clown";
    	};
    	
    	myClass.prototype.add = function(a,b) { return a+b; };
    	myClass.prototype.getNextPresident = function() { return this.nextPresident; };
    };
    

    Then, I use the module like this and it works:

    require("myClass").init();
    
    abc = new myClass();
    console.log(abc.add(1,2));
    console.log(abc.getNextPresident());
    

    Problem #1 is it uses 2x the space. I think this is because the call above caches the modules code as part of the 'require' call, and then the init() loads another copy of the class (constructor and prototype functions) again in memory. #2: I don't like it. Awkward.

    #1 is the real problem. I can live with #2.

    Is there some way to expose the function created when the module is loaded and executed in the root scope, as described in the modules page? This would address both #1 and #2.

  • @dwallersv, you ARE a bright person...

    I just added some lengthy explanation to the modules in modules? conversation... hopefully much better laid out than before...

    (So where is the issue? with the Author or Reader? ...it's up to everyone's own - jail free - judgment...)

  • A while ago I had got pull request granted by @Gordon to enable the modules mechanisme play nicely oo... with classes... And that is how it looks like for creating a module providing, for example, a Person 'class' (The concatenated string is the source code of the 'class' definition / constructor with prototype extensions and is part of the module code):

    Modules.addCached("Person",
      'var Person = function(first, last, birthDate) {'
    + '  this.first = first;'
    + '  this.last = last;'
    + '  this.birthDate = birthDate;'
    + '};'
    + 'Person.prototype.about = function() {'
    + '  return ('
    + '    [ this.first , this.last , "is"'
    + '    , new Date().getFullYear() - this.birthDate.getFullYear()'
    + '    ,"years old."].join(" "));'
    + '};'
    + 'exports = Person;'
    );
    function onInit() {
      var Person = require("Person");
      var aPerson = new Person("Pico" , "Espruino"
        , new Date(new Date().getTime()
          - 2 * 366 * 24 * 60 * 60 * 1000)); // now - 2 years in ms
      console.log(aPerson.about());
    }
    

    On upload, you still get some warning, because the uploader does not find the module in the project / sandbox or on the Web espruino.com/modules, but when it comes to execution in the onInit(); - invocation issued as command in the console - the module - IS there... in the Modules' cache...

    How can you get rid of the warning(s)?... explained in the other conversation...

    Replace line 16 with these two lines that supply the module name in a variable and thus escape the parsing for modules before upload ...and that get's rid of the upload warning(s):

      var personClassModuleName = "Person";
      var Person = require(personClassModuleName);
    

    To work the 'proper' way of modules, place the source code into the project / sandbox / modules folder (see Espruino Web IDE setting / cog wheel in top right) as Person.js or - minified - as Person.min.js. Btw, the naming within the module is of no importance: you can make it as short as possible, for example just P for Person. It is of no importance, because the execution happens in a 'closure'.

    // Peson.js module defining Person class.
    var Person = function(first, last, birthDate) {
      this.first = first;
      this.last = last;
      this.birthDate = birthDate;
    };
    Person.prototype.about = function() {
      return (
        [ this.first , this.last , "is"
        , new Date().getFullYear() - this.birthDate.getFullYear()
        ,"years old."].join(" "));
    };
    exports = Person;
    

    and it works with the oo charm (upload this code and fire it with onInit() in the console):

    function onInit() {
      var Person = require("Person"); // get hold of class obj / constructor function
      var aPerson = new Person("Pico" , "Espruino"
        , new Date(new Date().getTime()
          - 2 * 366 * 24 * 60 * 60 * 1000)); // now - 2 years in ms
      console.log(aPerson.about());
    }
    

    Btw, you are not bound to the class name Person. You can call it what ever you want... The module loading mechanism - eval(source) - is in a closure. The module require mechanism allows a mix and match of modules where the module creators used the same name for their class definition INSIDE the module, but in the application context / namespace you can give them any (global) name of your choice, if a name at all, as shown in the next section.

    You can even be that brief (in console or code) - no need to hold on to the module / 'Class' object / Constructor (function)... and use the class obj / constructor function anonymously in your application context... just reference by the module name and delivered as object (class object / constructor function) by the module mechanism:

    new (require("Person"))("Pico","Espruino", new Date(1.4E12)).about();
    

    Output:

    ="Pico Espruino is 2 years old."
    >
    

    And if you need the Person class / constructor multiple times in various places, you can do it this way:

    var p1 = new (require("Person"))("Pico","Espruino", new Date(1.4e12));
    //...
    var p2 = new (require("Person"))("Original","Espruino­", new Date(1.375e12));
    //
    console.log(p1.about() + " -  vs. - " p2.about());
    

    I'm sure you know that the module name can also be a URL pointing to a module on the World Wide Web (with related security implications)... ;-)

    PS: Read through related using modules in modules? conversation.

  • @allObjects, thank you for the kind words -- much appreciated!

    Of course I'm actually an incredibly arrogant bastard that actually believes he's smarter than everyone else on the planet, but I fake some humility so that all you lesser creatures will continue to be willing to interact with me.

    :-) :-) :-)

  • LOOOOOOOOOOL - this attitude is mandatory prereq to overcome all the hurdles... it just has its challenges with other humans!

  • Is it actually as simple as:

    function myClass() {
        this.nextPresident = "Bozo the clown";
    }
    myClass.prototype.add = function(a,b) { return a+b; };
    myClass.prototype.getNextPresident = function() { return this.nextPresident; };
    exports myClass; // <------- Not valid JS?
    

    You actually want exports = myClass?

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Struggling with modules

Posted by Avatar for dwallersv @dwallersv

Actions