How does it work?

Overview

Quokka.js has been designed from the ground up to be blazingly fast. Under the covers, Quokka.js performs a number of tricky runtime hacks to allow your code to be run much faster than if not using Quokka.

Depending on your project type, the way that Quokka runs your code may be a little different to what you are used to. Quokka runs your code using node.js regardless of your project type and configuration. In terms of runtime behavior, running your code in node.js is almost the same as using your browser but there are some important differences. For example, some browser runtime features may be missing (such as window, document, window.fetch), and you don’t have a graphical user interface to interact with; read how to simulate a browser environment.

Quokka’s execution pipeline

In order to run your code and provide results in your editor, Quokka performs the following steps:

  1. Transpile your code if required (this happens if you are using Babel or TypeScript).
  2. Adjust your code to provide Quokka’s special features (e.g. Live Feedback, Live Code Coverage, Value Explorer, etc.).
  3. Execute any Quokka Plugins that have been configured.
  4. Execute your code, using the current Quokka file as the entry-point for your program.
  5. For imported files:

    a. For project files -> Step 1 will also be applied.

    b. For external dependencies -> Step 1 will not be applied.

There are a couple of important notes to be aware of regarding execution:

  • Quokka waits for your program to finish before showing results. If for example, your code started an HTTP server during execution, you must stop the server before Quokka will show results. Similarly, if you have some long-running asynchronous code, Quokka will wait for it to finish before displaying results.

  • By default, Quokka reuses its node process for subsequent executions of your code. This is one of the many things that makes it so fast. On rare occassions, you may need to change this behavior with the recycle setting.

  • Remember, Quokka is fast! If you are calling an external API, you might like to cache the result or if you are using Quokka ‘PRO’, use the Run on Save or Run Once features.

Using Quokka on existing projects

Quokka.js is ideally suited for rapid JavaScript and TypeScript prototyping and may be used within your existing projects.

Quokka may not work if you are trying to use code or components that require special runtime initialization, or if you are running code whose runtime is not compatible with node.js.

For example, if you try and run Quokka on a React functional component, while the component will work in the Browser because of React’s browser runtime, no code will be executed by Quokka in node.js:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// Running this code in Quokka does nothing as the 
// "Welcome" function is defined but is never called.

The sections below provide some additional details, and tips and tricks for how you may use Quokka.js with various runtime environments and configurations.

Ad-hoc HTML and JavaScript

Quokka supports using jsdom to simulate a browser runtime environment. The jsdom package must be installed and available from your project as well adding as a few simple Quokka configuration settings.

If your ad-hoc HTML and JavaScript code does not execute in Quokka.js, it is possible that what you are trying to do is not supported by JSDOM.

Create React App

If your project uses create-react-app, Quokka will automatically configure itself to launch in an environment that is very similar the enviornment that jest uses when it runs your create-react-app tests.

When you run your create-react-app code in the browser, when you navigate to a page (e.g. index.html) the react-runtime + browser load your user interface and any necessary components for you.

When using Quokka, you need to simulate the loading that normally happens in the browser by adding some code. You also need to simulate any interaction with your page as you would do with the user interface. If you have a simple functional component (see below):

import { useState } from 'react';

function App() {
  const [count, setCount] = useState(0)
  count //?

  return (
    <div>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default App;

Then you will need to load it into the DOM manually, simulating what the browser and react-runtime would do for you by adding code to the end of your file:

import { useState } from 'react';

function App() {
  const [count, setCount] = useState(0)
  count //?

  return (
    <div>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
 
export default App;

// Add necessary imports and tell react to render your component into the DOM
import ReactDOM from 'react-dom';
import React from 'react';
ReactDOM.render(
    <App />,
  document.getElementById('root')
);

If the react code requires additional runtime components (e.g. props or redux) then you will also need to initialize these in your code before rendering your component into the DOM.

React Native

When react-native runs, it has access to device-specific APIs that are not natively available in the node.js runtime, which means they will not work in Quokka.

You may use Quokka.js within react-native projects but are likely to encounter errors if you attempt to import code with React components, or if you import any code that uses device-specific APIs.

Vue.js

Vue.js does not support running Vue components in node.js, which means that they also cannot run in Quokka.js.

You may use Quokka.js within Vue.js projects that do not use or import Vue components.

Angular

Quokka’s support for Angular is limited to code that does not have a user interface component. Depending on your code, you may also receive errors such as Reflect.getOwnMetadata is not a function or ​​reflect-metadata shim is required when using class decorators​​. To resolve this error, you may add import 'reflect-metadata'; at the top of your Quokka scratch file, or else add the angular-quokka-plugin to your Quokka Plugins configuration.

Express

If you want to run backend code that includes hosting an HTTP server, then you need to ensure that your HTTP server is stopped in order for Quokka to display results. For example:

const server = app.listen(3000);

await awesomeScratchFileCode();

server.close();

Caching External APIs

If you are calling an external API, you might like to cache the result.

Because Quokka re-uses the node process between executions, you may memoize your API response using a global variable. For example:

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

If you are caching using a global, Quokka may still call the external API multiple times (e.g. when it is restarted), or if something goes wrong with the node.js process and it is recycled.

Another approach is to write code similar to the code shown below where you cache the API response to disk so it may be re-used between stopping and starting Quokka. For example:

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');  

  // change 'CACHE_KEY' per project/API call
  const cacheFileName = path.join(require('os').tmpdir(), 'CACHE_KEY', hash); 

  // 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;
  }
}