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.
With data uris or objectUrls and dynamic imports:
DataURI:
ObjectURL:
Imports worked in my tests to, just if you use relative paths you might have to change the directory change prefixes (like ./ or ../), but since you have to code as text first, you just can replace it with regex before emulating.