Interactive coding platforms are probably the most common way many people get started learning how to code. I used websites such as Codecademy, freecodecamp, and W3School a lot to learn to code and I have always been wondering how to build websites like these. There seem to be very few resources on this topic so I will share my approach on building the interactive JavaScript course on this site, basically by tons of trial and error.
Why I Build CsPlayground?
I am an educator based in Malaysia primarily teaching coding to secondary students. I am constantly looking for platforms to provide students with self-learning, interactive coding activities and most importantly being able to track their progress. I have tried using freecodecamp, codecademy, codecombat however they either do not have the progress tracking feature or relatively expensive to subscribe to. So, I built this website primarily for my usage, and hopefully, I could help other educators do the same thing at an affordable cost.
Framework
Csplayground is built entirely on Vue.js. Since I am most comfortable with this framework, I try to avoid any server-side logic and build most of them on Vue alone. This might not be the best way to do it but at least it works for now.
The Editor
Setting up the code editor is probably the easiest part. I use Ace editor – a embeddable code editor written in JavaScript. This is also the editor used in sites like Khan Academy and Codecademy. Setting up ace editor is straightforward, all you have to do is import the pre-packaged version of Ace editor. You can find the tutorial here and the NPM package here.
Running the Code
To get the code written in the editor, you can use the getValue() method of the editor’s instance.
let editor = ace.edit(element, {
mode: "ace/mode/javascript",
selectionStyle: "text"
})
editor.getValue(); // Get user code
Next, to execute the code, there are two options I discovered:
- Use JavaScript eval() on client side
- Send code string to server side and execute using Node vm module
Option no 1 is easy to implement however a quick search will show you every article why you should not use eval() in JavaScript because it allows users to run malicious code in your app.
Option no 2 is to setup up a server-side script on Node that evaluates the user’s code using the vm module. Since vm module evaluates code in an isolated sandbox, it has no context of the browser console. Hence, I need to override the sandbox console to capture the console.log arguments and return the results.
function hook_consolelog(callback) {
var old_log = console.log;
console.log = (function(write) {
return function() {
write.apply(console, arguments)
callback.apply(null, arguments);
}
})(console.log)
return function() {
console.log = old_log;
}
}
var result = [];
var unhook = hook_consolelog(function(v) {
result.push(v);
});
My current codebase currently using the eval() method which I think is a bad choice so I am refactoring it to use vm module instead.
Console
One important feature of an interactive JavaScript platform is to display the console message onscreen. The way I implemented this feature is by overriding the console function to capture the arguments and display them on DOM. I override the console.error function as well as my submission correctness testing relies on assertions.

window.console = {
log: (...data: any[]) => {
// Display on DOM
},
warn: (...data: any[]) => {
// Display on DOM
},
info: (...data: any[]) => {
// Display on DOM
},
error: (...data: any[]) => {
// Display on DOM
}
};
Submission Correctness Testing
To check the user’s code, I rely on two different tests – assertion and regex. Assertion tests are quite straightforward as I only have to append the assertion test to the user’s code. The caveat is that I could not evaluate any intermediate state of variables. For example, to check if a variable is assigned with the correct value:
// User code
var number = 6;
var number = 4;
// Code below this line is appended
assert(number === 4, "number should have a value of 4");
As shown above, assertion appended after the user’s code only able to check for the final value.
Next, to check for any specific way of coding, I use regex to do the testing. For example, if the activity requires the user to assign the number variable using the + operator:
const a = 3;
const b = 4;
// Assign number as a + b
let number =
assert(
/let\snumber\s=\s*a\s*\+\s*b\s*;/.test(code),
"assign number as a + b"
);
If any of the assertion tests failed, the AssertionError will be caught by the custom console we overrode and display it onscreen. Doing assertion tests this way has a few downsides. For one, if the first assertion test failed, the rest of the code will not be evaluated and I could not provide the user with feedback on various test cases for that particular activity.
Afterthoughts
This project seems quite daunting to me at first but by solving one problem at a time now I able to make it work. There are still many improvements to be made. I’ve spent quite some time figuring out how to do this so I am sharing this with those who had no idea where to start. If you have any idea of how to make it better or any major flaw in my approach, feel free to connect with me on Twitter!