I want my users to be able to use JavaScript as a scripting language inside my JavaScript application. In order to do so, I need to dynamically execute the source code.
There seem to be two main options for dynamically executing JavaScript:
a) Use eval(...) method ( or var func = new Function(...);) .
b) Add a <script> node to the DOM (for example by using $('body').append(...)).
Both methods work fine as long as I do not use any import statements in the dynamically executed source code. If I include import statements I get the error message Unexpected identifier.
Example user source code to be executed:
import Atom from './src/core.atom.js':
window.createTreeModel = function(){
var root = new Atom('root');
root.createChildAtom('child');
return root;
}
Example application code to illustrate a possible usage of that dynamic code:
a) Using eval
var sourceCode = editor.getText();
window.createTreeModel = undefined;
eval(sourceCode);
var model = window.createTreeModel();
treeView.setModel(model);
b) Using DOM modification:
var sourceCode = editor.getText();
window.createTreeModel = undefined;
var script = "<script >\n"+
sourceCode + "\n" +
"</script>";
$('body').append(script);
var model = window.createTreeModel();
treeView.setModel(model);
If I specify no script type or use type="application/javascript" for option b), I get the Unexpected identifier error. If I use type="module" I get no error. The script tag is successfully added to the DOM, but the module code is not executed.
I first thought that might be due to asynchronous loading. However, waiting until loading of the script tag is finished did not work with type='module'. The loading mechanism works with type="application/javascript" but then ... again... import does not work.
Example code for async execution after script tag has been loaded:
function loadScript(sourceCode, callback){
// Adding the script tag to the head as suggested before
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'application/javascript';
script.innerHTML = sourceCode;
//script.async=false;
// Then bind the event to the callback function.
// There are several events for cross browser compatibility.
script.onreadystatechange = callback;
script.onload = callback;
// Fire the loading
head.appendChild(script);
}
--
loadScript(sourceCode, function(){
var model = window.createModel();
console.log('model:' + model);
});
If I hard-code the user source code in my index.html using <source type="module">, the module code is executed. Dynamically loading the module code does not seem to work. I use Chrome version 63.0.3239.108.
=> I. How can I force the execution of the <script type="module"> tag after dynamically adding it to the DOM? or
=> II. How can I eval script that contains import (and maybe export) statements? or
=> III. What would be a good way to allow the user source code to define dependencies that can be resolved dynamically?
Related questions and articles:
https://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/#safely-sandboxing-eval
https://javascriptweblog.wordpress.com/2010/04/19/how-evil-is-eval/
How do I include a JavaScript file in another JavaScript file?
How to import es6 module that has been defined in <script type="module"> tag inside html?
Further notes:
I know that the work flow of the examples, using, window.createTreeModel is not ideal. I used it here because the code is easy to understand. I will improve my over all work flow and think about stuff like security issues ... after I managed somehow to run user source code including its dependencies.
windowto export, useexportdeclarations. And notice that the code evaluation is probably going to be asynchronous, especially when dynamically loading dependencies, so prepare for that.