wallabyjs / quokka

Repository for Quokka.js questions and issues
https://quokkajs.com
1.18k stars 31 forks source link

Is partial evaluation of code possible? #528

Open adamkl opened 4 years ago

adamkl commented 4 years ago

I do some of my development in Clojure/Script, and one of the killer features for me is the REPL.

I've tried to use Quokka to recreate that experience in JavaScript, and while its useful, the experience is somewhat lacking due to the fact that Quokka wants (needs?) to re-evaluate the entire file with every change/save.

With Clojure/Script, you can highlight and selectively evaluate code to change the actively running application context. This is especially useful when you are trying to develop code that needs to make calls to external resources (REST API, Database, etc). I know that Quokka has the option to only evaluate on save, and while that improves the situation, it still is somewhat lacking when compared to a more interactive REPL.

What I'd love to see is a Quokka session that works similarly to the workflow in this video: https://vimeo.com/230220635

Is this currently possible? If not, it would be awesome if this capability could be added to Quokka.

smcenlly commented 4 years ago

The behavior of Quokka is exactly as you describe, which is to run your code using your entire file. This is by design and is intentionally different to a REPL. The idea behind this is to provide a reliable/repeatable means of executing your code (e.g. in comparison to pasting in snippets of code which are immutable and may change over time or be reset when you close your editor).

Having said that, we definitely understand your use case. It sounds like this issue could be addressed if Quokka could cache the results of certain results of your code (e.g. method calls return value is reused)?

rdhar commented 3 years ago

It sounds like this issue could be addressed if Quokka could cache the results of certain results of your code

This is huge, particularly with regards to caching of certain results. Often, I try to get a feel for API outputs in a sandbox but quickly run into rate-limiting with Quokka. Even run once/on save isn't ideal when you're trying to explore an endpoint.

Having the possibility to cache the results once and use it for subsequent execution would be a perfect use-case.

smcenlly commented 3 years ago

While not a first class Quokka feature, you can do what you're asking for today. When you run your file with Quokka, by default Quokka re-uses the node process it starts to make subsequent executions much faster. You may set your API response using a global variable. For example:

function getApiResponse() {
  if (!global.myApiResponse) {
    global.myApiResponse = makeExpensiveServerCall();
  }
  return global.myApiResponse;
}

This doesn't mean that the API endpoint won't get hit multiple times because Quokka may recycle your process if there's a catastrophic error, but it will definitely limit remote execution most of the time.

We ourselves don't actually do this. For scenarios like the one that you're describing, we usually add logic to cache/read from a disk location in our scratch pad. Here's a generic template that you could use over and over again (maybe even create your own npm package or helper file for it):

function getApiResponse(param1, param2, param3, ... etc) {
  const fs = require('fs');
  const path = require('path');
  const crypto = require('crypto');

  // Generate a unique filename for the function arguments are provided (assumes no cyclical references)
  const args = JSON.stringify(Array.prototype.slice.call(arguments));
  const hash = crypto.createHash('md5').update(args).digest('hex');  
  const cacheFileName = path.join(require('os').tmpdir(), 'scratchFile', hash); // change scratch file name based on project or API call

  // Ensure the directory exists for our scratch file
  if (!fs.existsSync(path.dirname(cacheFileName))) {
    fs.mkdirSync(path.dirname(cacheFileName));
  }

  // If we already have a matching response, use it, otherwise make the call and cache it
  if (fs.existsSync(cacheFileName)) {
    return JSON.parse(fs.readFileSync(cacheFileName));
  } else {
    const response = makeExpensiveServerCall();
    fs.writeFileSync(cacheFileName, JSON.stringify(response));
    return response;
  }
}
rdhar commented 3 years ago

Many thanks, @smcenlly, this is well-neat and I really appreciate you sharing a well-commended code snippet as well to clarify its usage.

Definitely intend to make use of this valuable method going forward. 👍