Scope of variables in external JS scripts

Hi folks,

Wanted to start experimenting with Blockly, so I added following lines (in the html file of one of my contributions):

<script src="https://blockly-demo.appspot.com/static/blockly_compressed.js"></script>
<script src="https://blockly-demo.appspot.com/static/blocks_compressed.js"></script>
<script src="https://blockly-demo.appspot.com/static/javascript_compressed.js"></script>
<script src="https://blockly-demo.appspot.com/static/demos/code/msg/en.js"></script>

However this resulted in following error:
image

1. ANALYSIS OF NODE-RED NODE

When I put a breakpoint at the end of the first script (blockly_compressed), seems that there are TWO Blockly variables available:
image

The first one has scope empty (don't know what this means ...) and second has global scope. Only the first one has a property 'Blocks'.

Then I arrive in the breakpoint at the start of second script (blocks_compressed), where only the global variable Blockly still exists:
image

At line 4 they want to set the 'colour' of the Blockly.Block property, however an error will occur since that property is undefined.

2. ANALYSIS OF BLOCKLY DEMO

Blockly has provided a demo app, which WORKS FINE so there must be some kind of difference between the demo web app and the Node-Red flow editor.

Again added a breakpoint at the end of the first script, where I see that there is only a SINGLE Blockly variable (containing the Blocks property):
image

So when we arrive at start of the second script, the Blockly.Blocks is still available as global:
image

So in the demo app setting the colour (on line 4) will give no error...

3. THE DIFFERENCE ?

I'm now at the point where I'm running out of creativity ;-(

In the first file, the Blockly variable declaration and additiion of a Blocks property seems very natural to me:
image

Does anybody could point me in some direction? The problem can easily be reproduced by putting the lines into the html file of some node ...

Thanks a lot !!!!!!
Bart

Did a new (desperate) attempt to figure out what is going wrong.

Both programs (demo Blockly application and my Node-Red node) have the SAME script tags:

<script src="https://blockly-demo.appspot.com/static/blockly_compressed.js"></script>
...

But it seems that under the hood, the script is loaded in a different way by both programs:

1. SCRIPT LOADING IN BLOCKLY DEMO APPLICATION

At the start of the first script, the Blockly variable is already global, which is good!
image

And the call stack is very short ...

2. SCRIPT LOADING IN NODE-RED NODE

At the start of the first script, the Blockly variable has an empty scope (instead of global)???:
image

And there seems to be a very long - jQuery related - call stack, based on ajax and globalEval. From this explanation I would expect that the globalEval defines all the script variables as global. Don't get it ...

A bit further the Blockly variable is defined (but scope is still empty):
image

And then only a part of the Blockly variable properties becomes global:

And that 'incomplete' global Blockly variable is used by the second Blockly script, and gives error due to missing properties ...

Does thid ring a bell to anyone, why the scope of the Blockly variable is empty (instead of global) at the start of the script when using jQuery??? Do I need to load the script another way (without jQuery)?

Bart

Hi Bart,

the issue can be reproduced on a ‘static’ page if you swap the order of the first two script tags - putting blocks_compressed.js ahead of blockly_compressed.js.

When a page is first loaded, the browser will block whenever it hits a <script> tag and load them in order.

But scripts added dynamically are, by default, loaded asynchronously; they don’t block.

So when the four script tags are inserted in the page, they will be run in whatever order their network requests return the content to run - causing them to run in the wrong order.

It is possible to tell the browser to load the script synchronously, but it requires some JavaScript to do it:

<script>
[
  "https://blockly-demo.appspot.com/static/blockly_compressed.js",
  "https://blockly-demo.appspot.com/static/blocks_compressed.js",
  "https://blockly-demo.appspot.com/static/javascript_compressed.js",
  "https://blockly-demo.appspot.com/static/demos/code/msg/en.js"
].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  // set the async flag to false to force synchronous loading
  script.async = false;
  document.head.appendChild(script);
});
</script>

There’s far more information than you’ll care to know about script loading in this article.

1 Like

Hello Nick,

thanks for helping me out !! Your code snippet seems to work fine, so case closed …

I also thought - two days ago - that the problem was related to scripts loading in the wrong order. So I tried to do it like this:

<script>
    $.getScript('https://blockly-demo.appspot.com/static/blockly_compressed.js').done(function(data, textStatus, jqxhr) {
        $.getScript('https://blockly-demo.appspot.com/static/blocks_compressed.js').done(function(data, textStatus, jqxhr){
            $.getScript('https://blockly-demo.appspot.com/static/javascript_compressed.js').done(function(data, textStatus, jqxhr){
                $.getScript('https://blockly-demo.appspot.com/static/demos/code/msg/en.js').done(function(data, textStatus, jqxhr) {
                    // All scripts are loaded succesfully, so let's display Blocky in the div element
                    var workspacePlayground = Blockly.inject('blocklyDiv', {toolbox: document.getElementById('toolbox')});
                }).fail(function(jqxhr, settings, exception ){
                    console.log("en.js failed");
                    console.log(exception);
                    console.log(exception.stack);
                });
            }).fail(function(jqxhr, settings, exception ){
                console.log("javascript_compressed.js failed");
                console.log(exception);
                console.log(exception.stack);
            });
        }).fail(function(jqxhr, settings, exception ){
            console.log("blocks_compressed.js failed");
            console.log(exception);
            console.log(exception.stack);
        });
    }).fail(function(jqxhr, settings, exception ) {
        console.log("blockly_compressed.js failed");
        console.log(exception);
        console.log(exception.stack);
    });

But that resulted in the same errors.

And afterwards I added breakpoint; statements in both scripts, and there were executed in the correct order. So I assumed that the sequence wasn’t the problem …

But now I can continue with Blockly. Up to the next problem :sob:

Bart

Hi Nick (@knolleary),

I'm currently trying to make my node-red-contrib-blockly ready for NPM, but still some issues... Like this one:

When I add N of those blockly nodes to my Node-Red flow, all the JS scripts will be N times loaded. Do you know what is the best way to avoid this. By enabling 'somehow' caching on the script tags?

Thanks !
Bart

Edit: I refactored my blockly node to match your code snippet above, and now the files are only loaded once from the server, undependent of the number of nodes used in the flow.

However when the flow editor page is refreshed, all my files are loaded again :weary:

Tried to solve it by adding a 'Cache-Control' header variable on the server side:

RED.httpAdmin.get('/blocky/js/*', function(req, res){
        var options = {
            root: __dirname + '/lib/',
            dotfiles: 'deny'
        };
        
        res.set('Cache-Control', 'public, max-age=31557600, s-maxage=31557600'); // 1 year
        res.sendFile(req.params[0], options)
});

But it still keeps loading the files. I installed ChromeCacheView, and it seems that the files are not available in the cache.

Do you have any advise on this ? Can I specify on the browser side that it should cache the files?
Bart