Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: Scratch.js – Interactive JavaScript Scratchpad (hole.dev)
169 points by kahole 12 days ago | hide | past | favorite | 66 comments


  If the result is an Element (or perhaps Node) insert it into the document instead of for example "[object HTMLCanvasElement]"

  A potentially more difficult one would be automagically hoisting functions defined in the document when you hit ctrl-enter.  
function factorial(n) { ...the usual... }

factorial(7);// [ctrl-enter] on just this line works

Great suggestions!

Do you, or the broader community, have any ideas about solving infinite loops? I'm on mobile so I can't test this at the moment, but I imagine that while(1) crashes the tab.

What would an MVP operating system like ctrl-C functionality look like for execution environments in the browser?

To add to this comment, Stopify is a JS-to-JS compiler that instruments sync JS code to make them interruptible at set points. The paper [0] can explain it better than I ever could.

I work on an experimental Pyret [1] runtime that uses Stopify to instrument compiled Pyret code (plain old JS) so that we can run Pyret code on the main page thread without hanging it up. Main thread execution is important for quick/easy DOM access. In terms of performance cost, we haven't measured too extensively, but so far, on average, we're seeing a 2x slow down compared to un-Stopified programs.

(Disclaimer: paid contributor for Pyret).

[0] https://www.stopify.org/research.html

[1] https://www.pyret.org/

Do you see any other solutions in the same domain as Stopify? Another method that might provide a way to keep UI unblocked but still have user executable code?

If you need the user code to execute on the main thread, then unfortunately I am aware of none besides bundling your own tailored system.

Pyret used to use its own runtime system [0] but Stopify was created in part to replace it due to the maintenance burden and complexity of "vanilla" JS interoperability.

[0] https://www.pyret.org/docs/latest/s_running.html

This is what I use. Seems to be the only option, and it works.

We started with a blacklist to match against while(1), while(true), for(;;), etc, but we eventually found an eslint plugin (goedel.js) that nicely tells you if the code contains an infinite loop or recursion.

That plug-in certainly won’t cover all cases of infinite loops, or they just solved the halting problem :)

Hmmmm maybe you're thinking of entscheidungsproblem.js? This is a fork of that.

Codepen uses a system that measures loop duration, and it's a giant pain. Having done some pens that do ray tracing and image transforms which can have long running loops. Given the variable execution time of JS it can be quite random. It just exits the loop without warning, causing weird failures in your code.

2 theoretical solutions ( with significant overhead ) are:

Run the code in a VM ( maybe quick.js compiled to WASM would work ) that suspends code execution periodically if it exceeds a certain duration. This has the advantage that long running code in general won't block rendering, not just loops.

Transform the AST to use async generator that yields once per loop. This would allow the loop to be suspended and resumed. But it would require a lot of modification to the AST, making effectively the entire call tree async.

I've done this and it works surprisingly well. I made a timesliced js scripting system this way.. it looked imperative with tight loops but it was all asynchronous. It felt like a threaded app.

I am aware of multiple hacky solutions, such as loop detection and adding timeouts. This fails in most non-trivial creative coding applications due to long running code. I'm interested what it would take to come up with a general escape hatch like any shell user has.

On Starboard[0] I approached this by sandboxing the notebook code in an iframe on a different origin. This sandboxing has to be done anyway to prevent XSS.

If you type while(true){} in a notebook only the iframe will break (and usually your browser will prompt you after a while to kill it). When you do only the iframe is no longer functional.

I don't think there's an elegant way to solve it any differently in the browser.

[0]: https://starboard.gg

Hi, yes! I like your project. I chatted about similar things on your launch post here. You also address this explicitly on your product which I appreciate.

What I'm getting at is that these browser notebooks try to get at the desire and feeling for rapid exploration and iteration. Losing context by having a crashing logic error is a massive blow to that ideal.

I'm not saying that your or anyone elses product is only for "rapid prototyping" but it's still true that larger projects could be bit by the same errors. When I crash my native code I C-c and I'm back in an instant. When I crash browser notebook code I lose a bit of time and unsaved code. I crash browsers often in creative coding where I write many loops and don't always do them right.

It may also be that my chrome and firefox experience on linus is worse than standard, I don't know. But I have crashed my entire browser in chrome when using observable, and I thought that wasn't supposed to be possible.

That's clever. Do you know if there is something possible using web workers? maybe running the "sandboxed code" in the worker? I don't really know how they work and if it is possible to interrupt them from the main thread.

I think so, but a worker won't have access to the DOM and a bunch of other APIs, so the code would be fairly limited in what it can do. Which may be fine for some usecases!

A timeout would most certainly not trigger during a busy loop in JS. Timeouts can only trigger when the main thread is not running code.

Browsers will complain against code running for too long without interruption though.

Yes, as the other comments get at the "hack" I'm referring to is a loop transform that adds a timer check to the condition.

Oh, right, I was not there at all.

Wow, that seems hard to do. One would need to take a lot of things in account, including recursive calls, asynchronous functions / calls and, indeed, even long strings of instructions that are not necessarily part of a loop or recursive calls.

Would a transform that adds the check between every JS instructions where it is possible theoretically solve the problem? is there a solution that does not slow down the code too much and interrupts the code within an acceptable margin?

Yeah! The general case of this is the halting problem... The best solution I know of is stopify, which the other comments have talked about. I just wonder if there's another take on the situation, something akin to OS task management.

Well you can easily detect trivial examples like "while(1)" and "for(i=0;true;i++)". But otherwise how would know some is an infinite loop?

Put a bit more simply, to work out if a problem is unsolvable (infinitly looping) you need to evaluate the problem... By trying to solve it. Checkout the halting problem for more details.


"Solving" infinite loops doesn't necessarily mean accurately predicting a priori whether a piece of code will terminate. It can just mean ensuring that if the code does try to run indefinitely, it doesn't have unfortunate effects such as blocking the UI thread without the possibility of being interrupted.

> It can just mean ensuring that if the code does try to run indefinitely, it doesn't have unfortunate effects such as blocking the UI thread without the possibility of being interrupted.

Well that can be achieved by executing the code in a background worker thread. Which doesn't affect the UI thread in browsers... no sure how it's managed but I think you could terminate it after a certain amount of time too

> ctrl-C functionality

OS implementations dont solve the halting problem. I agree with the sibling comment.

I have made something similar (https://easylang.online/ide/). It is a language of its own, which is compiled and interpreted by WASM. The problem with hanging in endless loops is solved by running the interpreter in a "web worker" that can be killed and restarted at any time.

Looks interesting, I'll do some digging around. Thank you for sharing.

You could modify the AST tree using something like jscodeshift to add a function that is called in every loop (and maybe every function) and there you can "break" the loop in a pretty clean way.

A determined user will still be able to figure out something that blocks forever, for instance run a WASM program that has an infinite loop in it.

Very cool.

Small issue: I find that the cursor can be hard to use sometimes. For example, if I clear and then execute the single line

    "<style>body {background-color: #fdf6e3;}</style>"
I can no longer get an empty line at the bottom of the document. My cursor can only ever be within the outputted "<style>" tags, and hitting return from there creates a new pair of "<style>" tags.

To temper this bug-report with a cool feature, I like that the output is (usually, with the exception of the above) plain text, which can itself be executed. I haven't found a use-case for this, but I'm sure there's some interesting meta-programming that could be done.

(Now fixed) Thanks for letting me know, there is indeed a small issue with the clear() function.

I considered supporting only plain-text, but I found the editable visible style tag very interesting. And I like the fact all the power of the browser is available, e.g canvas, 3d rendering, and simple stuff like headers and images. Of course, you can use only plaintext if you'd like.

This should now be fixed!

Can you give us Mac users some love and replace ctrl w/ command ? My muscle memory is giving me a hard time xD

The source is very small and can be used both through the file:// and hosted http protocols. So you can easily make it do whatever you like. I could maybe expose some global variables to make it customizable, but I went for simplicity and smaller source instead.

if it helps, all you need to do is add this listener to support the cmd key:

`document.addEventListener('keydown', (e) => (e.metaKey && e.code === 'Enter' && evaluate() && e.preventDefault()));`

The metaKey[1] is only accessible for keydown.

[1] https://stackoverflow.com/questions/3902635/how-does-one-cap...


Ctrl-Enter is the shortcut for inserting a soft newline for content-editables on Safari btw. You might want to consider supporting command-enter for this reason alone.

It's super cool how the result of any evaluated expression is also itself an evaluable expression.


Before I integrate this into my development workflow, 2 questions:

1) What is your monetization strategy?

2) Will this be supported forever?


... /s

:) Nice project, useful.

EDIT: this was obviously satirical...

Cool stuff! What was the reason for you to make this?

I find myself opening and using the web console to do simple tasks like string manipulation, maths, sorting often. I wanted a dedicated and persistent way to do this. And also just a place to keep notes, urls and stuff. So I made this "literate programming" notepad.

I'm also former Emacs user, and I like the lisp scratch buffer there, which is similar.

I love how simple and effective this is.

I'm also building a web-based literate programming environment called Starboard[1] that's probably a hundred times the amount of code (but then it has additional features such as top-level await, plugin, and Python support).

Consider supporting lit-html[2] literals instead of strings for HTML output, it works really well in notebook environments.

One more thing you can consider: a esm tagged literal that uses import(<data url of the code>), you can then use ES Module imports to dynamically load code. Here's a blog post about that approach [3]

[1]: https://starboard.gg [2]: https://lit-html.polymer-project.org/ [3]: https://2ality.com/2019/10/eval-via-import.html

Starboard looks very cool as well. Is there anyway to push all the outputs into a separate 'document'? That'd be super cool for e.g. Latex too.

My issue with these notebooks is they always get very cluttered with inline outputs after a couple 100 lines.

I've been using this extension for a while...


Would be neat to see this integrated in somehow.

That would be awesome!

Ah, I do that too, that must be why playing with this app made me happy :-) Thanks for making it, have bookmarked it.

Not to be rude (I actually like it), but why build your own instead of using one of the many JavaScript REPLs already available?

Very cool!

Felt a bit odd that "running" a line didn't drop the cursor down past the output, ready for the next line; but the current seems useful for editing and re-trying a line, also interesting!

It seems similar to lively(smalltalk-like): https://github.com/LivelyKernel/lively.next/tree/master/live...

Is it poasible to soft-wrap lines and execute functions on mobile? Its cool haha

Recommendation: when inserting the results of the evaluation, add it to the selection so "reverting" the inserted text is as easy as hitting Backspace or Delete.

I suggest using CMD+Enter for Mac. This is the closest/better alternative to CTRL+Enter for PC, albeit Mac having a CTRL key.


That was fast. Thanks!

Is there a way to support multi-line input?

Yes, by selecting the lines. Execution-method looks at selection first.

Very cool. Love this - great work!

When you paste something it keeps the formatting, intended?

Yes, I guess. I'm pretty used to ctrl+shift+v-ing to remove any formatting. I'm now considering removing formatting by default. Know a simple way?


Fixed, formatting is now removed

Legit hacker needed


The simplest scratchpad, which works in Firefox and Chromium at least.

    data:text/html,<html contenteditable>

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact