And very thankful for his insights and ideas I just released a custom extension for nodejs to run custom js code without having to create a custom module or a formatter.
Sometimes for small quick things you just don't want to go through the hassle of creating a full extension for a very specific thing that could be easily achieved in a few seconds of writing some JS code.
There are also other cases where a custom extension will serve the purpose for a very specific case but if there is a small variation on the logic you need to build another module i.e. map() function.
In the code field you add the JS code you want to execute and in the data grid you define the variables and their Server Connect bindings you need to pass to your script if you need them. The SC action will return the last statement executed in the code field.
Update 1.1.0
Remove the option to bind data to the code field
Add a timeout option to allow ending the execution if it takes longer than the specified limit
Added a checkbox to toggle if errors should be logged in the console or not
There was a bug with the output checkbox that defaulted to true but wasn’t working. Now it defaults to false
Icon changes
Update 1.2.0
Breaking Change
Due to wrapping the custom code in an IIFE you need to explicitly return something so it shows in the output i.e. return 'hello world'
Added
IIFE wrapper. Wrapped the custom code in an IIFE to improve performance of vm.
Forward console. You can now use console object functions i.e. console.log() in your code and the output will be forwarded to the server console.
UI changes
Fixed
Fixed initial values of several checkboxes in the UI
As always PRs are welcome to improve the extension and squash bugs
With this module I’m still unclear if I should offer vm2 or just nodejs standard vm
The main difference between both is that vm2 allows to require modules in the constructor so people would be able to use external modules in the sandbox. For this I would indeed use the usedModules so vm2 is autoinstalled.
vm however is included in the node runtime therefore no need to install additional modules but doesn’t allow using external modules in the sandbox.
I think that if people require to use external modules in their custom script they should really go for a Custom SC module and create better user experience.
The idea for this module is just to run simple scripts to transform data to their needs and such things in a very efficient way because a lot of times I see people having to do create a lot of SC steps including several repeats that have a hit in performance.
So I think for now I am going to stick with vm and not allow people to use external modules in the sandbox and therefore no need to install a dependency as it’s included in the nodejs runtime.
For the rest of my modules I will for sure use the usedModules to improve user experience.
I think the way it is now, creating custom SC modules is a burden due to the lack of a boilerplate generator. Because of that, whenever possible, I'll resort to directly write JavaScript code.
I think allowing modules allows a more frictionless experience to programmers that may be comfortable writing JS code, but don't have the experience to quickly create SC modules - perhaps for those, it's just more productive to skip writing a custom module. But I understand this is a tricky topic, and each will have its own opinions. Btw, I'm talking about algorithms, not MailChimp functions
I honestly think you're a bit biased towards .map, so you only deal with a one-liner
Have you considered following the approach where you inject the user-input JS code inside a function? So people can explictly "return" instead of relying on the last JS instruction as a return?
Indeed it's a tricky part. Yes it could be added easily by using vm2 instead of vm. But considering that the great majority of users are not coders and rely on Wappler on keeping their app secure I am hesitant of including the following.
Run external dependencies in the sandbox that could compromise the security of it.
Allow data binds in the code field to avoid injection of malicious code coming from the database or another source.
Sticking to the standard means less maintenance and problems if node guys decide to introduce changes to vm.
Additionally I need to understand better how promise handling works in VM context so I have to test that before prepending a variable to a function.
Is it unbearable for your eyes having to add a variable and assign it to the function result? I understand it’s kind of unsettling not being able to use return in the main code. I believe this is by design to secure the sandbox.
I am more concerned about the lack of a proper control in the UI that handles code although this is expected as nobody has asked for it…until now
@george do you guys have by any chance an undocumented control for code with linting and beautifier?
There's a bug with the Output checkbox: it comes ticked by default, but it doesn't really output anything. If I untick and tick it again it works
Edit: As a new feature, is it possible to define the schema of the output? Like what happens with API Action or Set Value (this last one's broken due to a bug)
Regarding the output toggling this should be “fixed” with the last version 1.1.0
I changed it to default to false.
Related to:
For custom extensions the output schema is defined in a json file and it’s static but it’s true that it is available for the API action. @george is this undocumented or not available for custom modules?
@JonL I’m struggling with getting it to work, I’m not sure if I am using the extension wrongly or if my code is wrong.
I do think this is the exact use case for your extension though, need to apply a little bit of JS to manipulate data…
I want to loop over the data and for each product: split the ac_tags and then push the values into one array. Expected outcome: acTagsArr = ["tag1, tag2, tag3"];
I’m trying to get this done with the following JS:
acTags = []; //declare my output var
tagsSplit = tags.split(','); //Split the 'ac_tags' so I get an array where 1 tag = 1 item
acTags.push(...tagsSplit); //push the split items into the output var
I’m expecting an output of: acTagsArr = ["tag1", "tag2", "tag3"] (still need to remove the comma and make it a single string.)
Also note you have a space after each comma (double-check if this is the case), so this is the code:
return tags.split(", ")
For previous versions of RunJS, omit the return keyword:
tags.split(",");
The method .split() already returns an Array, so you can return it directly without creating acTags2
Edit: Actually, your problem is you’re not actually returning acTags
acTags = []; //declare my output var
tagsSplit = tags.split(','); //Split the 'ac_tags' so I get an array where 1 tag = 1 item
acTags.push(...tagsSplit); //push the split items into the output var
return acTags; // RunJS 1.2.0
acTags; // Previous RunJS versions
This is outputting nothing. I’m guessing because I’m not passing the acTagsArr to the RunJS. But if I do that I think my variable will stay within this scope of this RunJS.
Your array declaration (#1) doesn’t matter to RunJS (even if you pass it in Data), the scope is isolated as you guessed (pass by value, not by reference)
Similarly, the step #3 to #1 doesn’t happen - the value returned by RunJS is not magically filled into acTagsArr (RunJS has no reference to it). You need to put a Set Value acTagsArr after the RunJS step, and you select in the Data Binding Picker the value RunJS returns