Module creation at runtime

Posted on
  • I try to use Modules.addCached("moduleId","moduleSourceCode"); with "moduleCode" as ES6 multi-line string (which is supported by Espruino) and as 'plain' concatenated string.

    The "moduleCode" can be part of the code or retrieved over communication (from Web) and enables maintaining self-updating code centrally for connected applications.

    In below code I create a Person 'class'/prototype in module with same name 'on the go', which I later in the code use with require("Person");.

    // DynamicModuleSupply.js
    
    Modules.addCached("Person",`
    // Person.js
    // "class" (Prototype definition)
    var Person = function
      ( firstName // firstName
      , lastName  // lastName
      , birthDate // birthDate as Date
      ) {
        this.firstName = firstName;
        this.lastName  = lastName;
        this.birthDate = birthDate;
      };
    Person.prototype.getFullName = function() {
      return this.firstName + " " + this.lastName; };
    Person.prototype.getAgeInYears = function() {
      return (new Date().getFullYear()) - this.birthDate.getFullYear(); };
    exports = Person;
    `);
    
    function onInit() {
    
    // usage
    
    var Person = require("Person");
    
    var date = new Date(); date.setFullYear(2001);
    var p1 = new Person("JS","Jim",date);
    
    console.log("Person " + p1.getFullName()
        + " is " + p1.getAgeInYears() + " years of age");
    
    }
    
    setTimeout(onInit, 1000);
    

    Note: The setTimeout(onInit, 1000); is there for two reasons. First, I do not need to manually start the onInit() in the console pane, and second, the setTimeout() lets the upload finish before starting the runtime part of the code.

    Despite the Acorn parse ....failed.... error message it works. (see attached screen shot).

    I also get the Module Person not found warning message, which is expected.

    Both the error and warning messages are created by the upload as expected.

    The lines 4..19 stored in Person.js file or minified in Person.min.js file and placed in .../modules folder of my sandbox (see IDE settings - Project) work perfectly.

    I tried multiple variations to get over the error message, including using a 'plain' concatenated string (as below). Using the code in concatenated string needs though one of two changes:

    Change 1: join strings with line feed (\n) delimiter. This terminates the line comments (//):

    // DynamicModuleSupply1.js
    
    Modules.addCached("Person",
    [ '// Person.js'
    , '// "class" (Prototype definition)'
    , 'var Person = function'
    , '  ( firstName // firstName'
    , '  , lastName  // lastName'
    , '  , birthDate // birthDate as Date'
    , '  ) {'
    , '    this.firstName = firstName;'
    , '    this.lastName  = lastName;'
    , '    this.birthDate = birthDate;'
    , '  };'
    , 'Person.prototype.getFullName = function() {'
    , '  return this.firstName + " " + this.lastName; };'
    , 'Person.prototype.getAgeInYears = function() {'
    , '  return (new Date().getFullYear()) - this.birthDate.getFullYear();' , '};'
    , 'exports = Person;'
    ].join('\n'));
    
    function onInit() {
    
    // usage
    
    var Person = require("Person");
    
    var date = new Date(); date.setFullYear(2001);
    var p1 = new Person("JS","Jim",date);
    
    console.log("Person " + p1.getFullName()
        + " is " + p1.getAgeInYears() + " years of age");
    
    }
    
    setTimeout(onInit, 1000);
    

    Change 2: change line comments (//) int block comments (/* ... */):

    // DynamicModuleSupply2.js
    
    Modules.addCached("Person",
    [ '/* Person.js */'
    , '/* "class" (Prototype definition) */'
    , 'var Person = function'
    , '  ( firstName /* firstName */'
    , '  , lastName  /* lastName */'
    , '  , birthDate /* birthDate as Date */'
    , '  ) {'
    , '    this.firstName = firstName;'
    , '    this.lastName  = lastName;'
    , '    this.birthDate = birthDate;'
    , '  };'
    , 'Person.prototype.getFullName = function() {'
    , '  return this.firstName + " " + this.lastName; };'
    , 'Person.prototype.getAgeInYears = function() {'
    , '  return (new Date().getFullYear()) - this.birthDate.getFullYear();' , '};'
    , 'exports = Person;'
    ].join(''));
    
    function onInit() {
    
    // usage
    
    var Person = require("Person");
    
    var date = new Date(); date.setFullYear(2001);
    var p1 = new Person("JS","Jim",date);
    
    console.log("Person " + p1.getFullName()
        + " is " + p1.getAgeInYears() + " years of age");
    
    }
    
    setTimeout(onInit, 1000);
    

    Without either one change, I get error message message in console, I get error on usage:

     _____                 _
    |   __|___ ___ ___ _ _|_|___ ___
    |   __|_ -| . |  _| | | |   | . |
    |_____|___|  _|_| |___|_|_|_|___|
              |_| http://espruino.com
     1v94 Copyright 2016 G.Williams
    >
    =undefined
    Uncaught Error: Constructor should be a function, but is Object
     at line 32 col 14
    var p1 = new Person("JS","Jim",date);
                 ^
    in function called from system
    

    Multi-line string usage makes me suspicious.

    What should I change to make the multi-line string work?


    1 Attachment

    • AcornParseFailed.png
  • what about this?

    Modules.addCached("Say",function say(){
          function hello(name) {
            console.log('Hello',name);
          }
          exports.hello = hello;
    });
    
    const Say = require('Say');
    
    Say.hello('allObjects');
    
    
  • Yes - it's not well documented, but adding by function is really neat and tidy. No need to name the function say either, it can just be unnamed.

    So you're saying that multi-line strings work, but just give the Acorn parse error? I think that's just the version of Acorn in the IDE doesn't like template strings - I guess it needs updating.

  • @MaBe, indeed, after all, it is just a map or dictionary with a key and an object. I went for the string for several reasons:

    1. To use the module source pattern
    2. To be able to accept source code provided through connectivity

    In conversation about Module Development I went a step further: compose a module from module components - not only to avoid possible out of memory condition, but also pull most recent and on demand what is required by the application. (A similar pattern to bootloader: application bootloader).

  • It's worth noting that:

    Modules.addCached("Say",function() {
          function hello(name) {
            console.log('Hello',name);
          }
          exports.hello = hello;
    });
    

    and

    Modules.addCached("Say",`
          function hello(name) {
            console.log('Hello',name);
          }
          exports.hello = hello;
    `);
    

    are exactly the same code - so it's the same module pattern. It's just easier to write as a function because you don't get the acorn error and you also get syntax highlighting

  • @Gordon, that's excellent...

    in other words, exports is provided in the context of `.addCached() when executing the passed in (anonymous) function for the 'moduleSource' parameter... (I finally understand what a recent release mentioned with 'ability to store or storing a module as a function' - is it that or is that at least related to it?).

    Therefore, instead of using concatenated or multi-line string to pass in the module source, the module 'source' is wrapped with an (anonymous) function that sets or populates the exports object.

    I will use this function approach to replace the string approach in mentioned conversation about Module Development. It fulfills *exactly* the same requirements I have with a better support. Syntax highlighting and (Espruino extended,) specific code completion are just two - important - parts of it, all provided by the Espruino IDE.

    ...all with enabling all sorts of makers in mind. (@Gordon, is that - or at least a major port of it - not what ignited the Espruino-Journey in the first place?)

    PS: it's about time for me to really really dig into the Espruino source code... or not... I'm confused... ;-)

  • 'ability to store or storing a module as a function' - is it that or is that at least related to it?

    Yes, that's the one! The idea is that if it's enabled, when you upload with E.setBootCode then all the code inside the modules is kept in flash - so it saves you a huge amount of RAM.

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

Module creation at runtime

Posted by Avatar for allObjects @allObjects

Actions