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. (note, we will eventually deprecate the /v2/ in all routes)

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/v2/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/3.7.3/monaco/min/vs',
          // vs: 'https://unpkg.com/@typescript-deploys/monaco-editor@3.7.3/min/vs',
          sandbox: 'https://www.typescriptlang.org/v2/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,
        tsSandbox
      ) => {
        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 && sandbox
        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',
        }

        tsSandbox.createTypeScriptSandbox(sandboxConfig, main, window.ts).then(sandbox => {
          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 = await 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 = await createTypeScriptSandbox(sandboxConfig, main, ts)

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

Make a request for an LSP response

const sandbox = await 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 = await 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 = await 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' },
  },
])

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