TypeScript Sandbox

A DOM library for interacting with TypeScript and JavaScript code, which powers the heart of the TypeScript playground

You can use the TypeScript sandbox for:

  • Building IDE-like experiences for people to explore your library's API
  • Building interactive web tools which use TypeScript, with a lot of the Playgrounds developer experience for free

For example, the sandbox to the side has grabbed the Types for DangerJS with no modifications for this code sample. This is because the Playground's Automatic Type Acquisition is enabled by default. It will also look for the same parameters for code, and selection indexes inside the URL.

Try clicking this URL to see that in action.

This library builds on top of the Monaco Editor, providing a higher level API but offering access to all the lower-level APIs via a single sandbox object.

You can find the code for the TypeScript Sandbox inside the microsoft/TypeScript-Website mono-repo.

Downloading Sandbox...

Usage

A sandbox uses the same tools as monaco-editor, meaning this library is shipped as an AMD bundle which you can use the VSCode Loader to require.

Because we need it for the TypeScript website, you can use our hosted copy here.

Get Started

Create a new file: index.html and paste this code into that file.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
  </head>
  <div id="loader">Loading...</div>
  <div id="monaco-editor-embed" style="height: 800px;" />
  <script>
    // First set up the VSCode loader in a script tag
    const getLoaderScript = document.createElement('script')
    getLoaderScript.src = 'https://www.typescriptlang.org/js/vs.loader.js'
    getLoaderScript.async = true
    getLoaderScript.onload = () => {
      // Now the loader is ready, tell require where it can get the version of monaco, and the sandbox
      // This version uses the latest version of the sandbox, which is used on the TypeScript website

      // For the monaco version you can use unpkg or the TypeSCript web infra CDN
      // You can see the available releases for TypeScript here:
      // https://typescript.azureedge.net/indexes/releases.json
      //
      require.config({
        paths: {
          vs: 'https://typescript.azureedge.net/cdn/4.0.5/monaco/min/vs',
          // vs: 'https://unpkg.com/@typescript-deploys/monaco-editor@4.0.5/min/vs',
          sandbox: 'https://www.typescriptlang.org/js/sandbox',
        },
        // This is something you need for monaco to work
        ignoreDuplicateModules: ['vs/editor/editor.main'],
      })

      // Grab a copy of monaco, TypeScript and the sandbox
      require(['vs/editor/editor.main', 'vs/language/typescript/tsWorker', 'sandbox/index'], (
        main,
        _tsWorker,
        sandboxFactory
      ) => {
        const initialCode = `import {markdown, danger} from "danger"

export default async function () {
    // Check for new @types in devDependencies
    const packageJSONDiff = await danger.git.JSONDiffForFile("package.json")
    const newDeps = packageJSONDiff.devDependencies.added
    const newTypesDeps = newDeps?.filter(d => d.includes("@types")) ?? []
    if (newTypesDeps.length){
        markdown("Added new types packages " + newTypesDeps.join(", "))
    }
}
`

        const isOK = main && window.ts && sandboxFactory
        if (isOK) {
          document.getElementById('loader').parentNode.removeChild(document.getElementById('loader'))
        } else {
          console.error('Could not get all the dependencies of sandbox set up!')
          console.error('main', !!main, 'ts', !!window.ts, 'sandbox', !!sandbox)
          return
        }

        // Create a sandbox and embed it into the the div #monaco-editor-embed
        const sandboxConfig = {
          text: initialCode,
          compilerOptions: {},
          domID: 'monaco-editor-embed',
        }

        const sandbox = sandboxFactory.createTypeScriptSandbox(sandboxConfig, main, window.ts)
        sandbox.editor.focus()
      })
    }

    document.body.appendChild(getLoaderScript)
  </script>
</html>

Opening the file index.html in a web browser will load up the same sandbox up at the top of the page.

Some examples of the API

Converting the user's TypeScript into JavaScript

const sandbox = createTypeScriptSandbox(sandboxConfig, main, ts)

// Async because it needs to go  
const js = await sandbox.getRunnableJS()
console.log(js)

Get the DTS for the user's editor

const sandbox = createTypeScriptSandbox(sandboxConfig, main, ts)

const dts = await sandbox.getDTSForCode()
console.log(dts)

Make a request for an LSP response

const sandbox = createTypeScriptSandbox(sandboxConfig, main, ts)

// A worker here is a web-worker, set up by monaco-typescript
// which does the computation in the background 
const worker = await sandbox.getWorkerProcess()
const definitions =  await client.getDefinitionAtPosition(model.uri.toString(), 6)

Change compiler flags using a few different APIs

const sandbox = createTypeScriptSandbox(sandboxConfig, main, ts)

// Hook in to all changes to the compiler
sandbox.setDidUpdateCompilerSettings((newOptions) => {
  console.log("Compiler settings changed: ", newOptions)
})

// Update via key value
sandbox.updateCompilerSetting("allowJs", true)
// Update via an object
sandbox.updateCompilerSettings({ jsx: 0 })
// Replace the compiler settings
sandbox.setCompilerSettings({})

Highlight some code in the editor

const sandbox = createTypeScriptSandbox(sandboxConfig, main, ts)

const start = {
  lineNumber: 0,
  column: 0
}

const end = {
  lineNumber: 0,
  column: 4
}

const decorations = sandbox.editor.deltaDecorations([], [
  {
    range: new sandbox.monaco.Range(start.lineNumber, start.column, end.lineNumber, end.column),
    options: { inlineClassName: 'error-highlight' },
  },
])

Create your own playground.

const sandbox = createTypeScriptSandbox(sandboxConfig, main, ts)

// Use a script to make a JSON file like:
// { 
//   "file:///node_modules/types/keyboard/index.d.ts": "export const enterKey: string"
// }
//
// Where the keys are the paths, and the values are the source-code. The sandbox
// will use the node resolution lookup strategy by default.

const dtsFiles = {} 

Object.keys(dtsFiles).forEach(path => {
  sandbox.languageServiceDefaults.addExtraLib(dts[path], path);
});

The API is mainly a light shim over the monaco-editor API with the monaco-typescript API.