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.
After adding some log messages I found out that when using type="module"
:
$('body').append(script);
does not execute the module codebody.appendChild(script);
does asynchronously execute the module code but the eventsonload
andonreadystatechange
do not work, even if I use addEventListener(...) instead ofscript.onload =...
.
Following work around works for me. It modifies the user source code to include a call to a global callback:
var sourceCode = editor.getText();
window.scriptLoadedHook = function(){
var model = window.createModel();
console.log('model:' + model);
window.scriptLoadedHook = undefined;
};
var body = document.body;
var script = document.createElement('script');
script.type = 'module';
script.innerHTML = sourceCode + "\n" +
"if(window.scriptLoadedHook){window.scriptLoadedHook();}";
body.appendChild(script);
I try now to find out how to use exports from the <script type="module">
tag to at least get rid of the global function window.createModel
:
How to import es6 module that has been defined in <script type="module"> tag inside html?
来源:https://stackoverflow.com/questions/47978809/how-to-dynamically-execute-eval-javascript-code-that-contains-an-es6-module-re