IntelliSense
The code editor provides a rich experience with intellisense and autocompletion. Many of the features required for this are based on TypeScript types that are either inferred by the editor or supplied as data definition files.
This not only works when the editor language is TypeScript, but also works with others like JavaScript and JSX.
Example:
Types for imported npm packages
LiveCodes will try to automatically find type definitions for npm modules imported in the editor.
These are examples for automatically loading React types with autocomplete and hover info:
TypeScript TwoSlash
The code editor supports TypeScript TwoSlash. This can be very useful for debugging, sharing and teaching TypeScript.
This is supported in JavaScript, TypeScript, JSX and TSX. This also includes Babel, Sucrase, Solid, React Native, etc.
Custom Types
If no type definitions are found, or if you want to provide your own (e.g. for a module that is not hosted on npm), custom type definition files can be used.
In the standalone app, these can be provided in custom settings using the types
property. This takes an object with the key representing the module name and the value representing the URL of the file.
Example:
Custom Settings
{
"types": {
"my-module": "https://cdn.jsdelivr.net/npm/[email protected]/types/my-module.d.ts",
"my-other-module": "https://my-website.com/my-other-module/my-other-module.d.ts"
}
}
For embedded playgrounds, these can be provided in the configuration object using the types
property.
This can be combined with the imports
property to provide importmap for runtime implementation of your custom modules.
This is an example of how to create a playground that provides the implementation of the custom module: my-module
and its type definition to provide editor intellisense:
import { createPlayground } from 'livecodes';
const config = {
activeEditor: 'script',
script: {
language: 'javascript',
content: `import { foo } from 'my-module';\n\nconsole.log(foo());`
};
imports: {
'my-module': 'https://my-website.com/my-module/index.js',
},
types: {
'my-module': 'https://my-website.com/my-module/my-module.d.ts',
},
};
createPlayground('#container', {config});
Please note that the URLs used for types
and imports
properties may be full URLs or data URLs.
This can be of great use for library authors who want to provide playgrounds for documenting their libraries that are not (yet) published to npm.
Demo
Let's assume we have this TypeScript module:
Greeter.ts
export class Greeter {
private morningGreetings = ['Good morning', 'Have a good day', 'How are you today?'];
private eveningGreetings = ['Good evening', 'Good night', 'Sleep well'];
private randomSelector(array: string[]) {
return array[Math.floor(Math.random() * array.length)];
}
public morning() {
return this.randomSelector(this.morningGreetings);
}
public evening() {
return this.randomSelector(this.eveningGreetings);
}
}
which compiles to this JavaScript:
Greeter.js
export class Greeter {
constructor() {
this.morningGreetings = ['Good morning', 'Have a good day', 'How are you today?'];
this.eveningGreetings = ['Good evening', 'Good night', 'Sleep well'];
}
randomSelector(array) {
return array[Math.floor(Math.random() * array.length)];
}
morning() {
return this.randomSelector(this.morningGreetings);
}
evening() {
return this.randomSelector(this.eveningGreetings);
}
}
and this type definition:
Greeter.d.ts
export declare class Greeter {
private morningGreetings;
private eveningGreetings;
private randomSelector;
morning(): string;
evening(): string;
}
The JavaScript output (Greeter.js) and the data definition file (Greeter.d.ts) should be hosted online or converted to data URLs (see assets and data URLs).
Then, they can be used like that:
show code
import { createPlayground } from 'livecodes';
const options = {
"config": {
"editor": "monaco",
"activeEditor": "script",
"script": {
"language": "typescript",
"content": "import { Greeter } from 'my-greeter';\n\nconst greeter = new Greeter();\n// now `greeter` has autocomplete\n\ndocument.body.innerText = greeter.morning();\n\n// this should show error in the editor\n// Property 'morningGreetings' is private and only accessible within class 'Greeter'\nconsole.log(greeter.morningGreetings);"
},
"imports": {
"my-greeter": "data:text/javascript;charset=UTF-8;base64,ZXhwb3J0IGNsYXNzIEdyZWV0ZXIgew0KICAgIGNvbnN0cnVjdG9yKCkgew0KICAgICAgICB0aGlzLm1vcm5pbmdHcmVldGluZ3MgPSBbJ0dvb2QgbW9ybmluZycsICdIYXZlIGEgZ29vZCBkYXknLCAnSG93IGFyZSB5b3UgdG9kYXk/J107DQogICAgICAgIHRoaXMuZXZlbmluZ0dyZWV0aW5ncyA9IFsnR29vZCBldmVuaW5nJywgJ0dvb2QgbmlnaHQnLCAnU2xlZXAgd2VsbCddOw0KICAgIH0NCiAgICByYW5kb21TZWxlY3RvcihhcnJheSkgew0KICAgICAgICByZXR1cm4gYXJyYXlbTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogYXJyYXkubGVuZ3RoKV07DQogICAgfQ0KICAgIG1vcm5pbmcoKSB7DQogICAgICAgIHJldHVybiB0aGlzLnJhbmRvbVNlbGVjdG9yKHRoaXMubW9ybmluZ0dyZWV0aW5ncyk7DQogICAgfQ0KICAgIGV2ZW5pbmcoKSB7DQogICAgICAgIHJldHVybiB0aGlzLnJhbmRvbVNlbGVjdG9yKHRoaXMuZXZlbmluZ0dyZWV0aW5ncyk7DQogICAgfQ0KfQ0K"
},
"types": {
"my-greeter": "data:text/typescript;charset=UTF-8;base64,ZXhwb3J0IGRlY2xhcmUgY2xhc3MgR3JlZXRlciB7DQogIHByaXZhdGUgbW9ybmluZ0dyZWV0aW5nczsNCiAgcHJpdmF0ZSBldmVuaW5nR3JlZXRpbmdzOw0KICBwcml2YXRlIHJhbmRvbVNlbGVjdG9yOw0KICBtb3JuaW5nKCk6IHN0cmluZzsNCiAgZXZlbmluZygpOiBzdHJpbmc7DQp9DQo="
}
}
};
createPlayground('#container', options);
import { createPlayground, type EmbedOptions } from 'livecodes';
const options: EmbedOptions = {
"config": {
"editor": "monaco",
"activeEditor": "script",
"script": {
"language": "typescript",
"content": "import { Greeter } from 'my-greeter';\n\nconst greeter = new Greeter();\n// now `greeter` has autocomplete\n\ndocument.body.innerText = greeter.morning();\n\n// this should show error in the editor\n// Property 'morningGreetings' is private and only accessible within class 'Greeter'\nconsole.log(greeter.morningGreetings);"
},
"imports": {
"my-greeter": "data:text/javascript;charset=UTF-8;base64,ZXhwb3J0IGNsYXNzIEdyZWV0ZXIgew0KICAgIGNvbnN0cnVjdG9yKCkgew0KICAgICAgICB0aGlzLm1vcm5pbmdHcmVldGluZ3MgPSBbJ0dvb2QgbW9ybmluZycsICdIYXZlIGEgZ29vZCBkYXknLCAnSG93IGFyZSB5b3UgdG9kYXk/J107DQogICAgICAgIHRoaXMuZXZlbmluZ0dyZWV0aW5ncyA9IFsnR29vZCBldmVuaW5nJywgJ0dvb2QgbmlnaHQnLCAnU2xlZXAgd2VsbCddOw0KICAgIH0NCiAgICByYW5kb21TZWxlY3RvcihhcnJheSkgew0KICAgICAgICByZXR1cm4gYXJyYXlbTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogYXJyYXkubGVuZ3RoKV07DQogICAgfQ0KICAgIG1vcm5pbmcoKSB7DQogICAgICAgIHJldHVybiB0aGlzLnJhbmRvbVNlbGVjdG9yKHRoaXMubW9ybmluZ0dyZWV0aW5ncyk7DQogICAgfQ0KICAgIGV2ZW5pbmcoKSB7DQogICAgICAgIHJldHVybiB0aGlzLnJhbmRvbVNlbGVjdG9yKHRoaXMuZXZlbmluZ0dyZWV0aW5ncyk7DQogICAgfQ0KfQ0K"
},
"types": {
"my-greeter": "data:text/typescript;charset=UTF-8;base64,ZXhwb3J0IGRlY2xhcmUgY2xhc3MgR3JlZXRlciB7DQogIHByaXZhdGUgbW9ybmluZ0dyZWV0aW5nczsNCiAgcHJpdmF0ZSBldmVuaW5nR3JlZXRpbmdzOw0KICBwcml2YXRlIHJhbmRvbVNlbGVjdG9yOw0KICBtb3JuaW5nKCk6IHN0cmluZzsNCiAgZXZlbmluZygpOiBzdHJpbmc7DQp9DQo="
}
}
};
createPlayground('#container', options);
import LiveCodes from 'livecodes/react';
export default function App() {
const options = {
"config": {
"editor": "monaco",
"activeEditor": "script",
"script": {
"language": "typescript",
"content": "import { Greeter } from 'my-greeter';\n\nconst greeter = new Greeter();\n// now `greeter` has autocomplete\n\ndocument.body.innerText = greeter.morning();\n\n// this should show error in the editor\n// Property 'morningGreetings' is private and only accessible within class 'Greeter'\nconsole.log(greeter.morningGreetings);"
},
"imports": {
"my-greeter": "data:text/javascript;charset=UTF-8;base64,ZXhwb3J0IGNsYXNzIEdyZWV0ZXIgew0KICAgIGNvbnN0cnVjdG9yKCkgew0KICAgICAgICB0aGlzLm1vcm5pbmdHcmVldGluZ3MgPSBbJ0dvb2QgbW9ybmluZycsICdIYXZlIGEgZ29vZCBkYXknLCAnSG93IGFyZSB5b3UgdG9kYXk/J107DQogICAgICAgIHRoaXMuZXZlbmluZ0dyZWV0aW5ncyA9IFsnR29vZCBldmVuaW5nJywgJ0dvb2QgbmlnaHQnLCAnU2xlZXAgd2VsbCddOw0KICAgIH0NCiAgICByYW5kb21TZWxlY3RvcihhcnJheSkgew0KICAgICAgICByZXR1cm4gYXJyYXlbTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogYXJyYXkubGVuZ3RoKV07DQogICAgfQ0KICAgIG1vcm5pbmcoKSB7DQogICAgICAgIHJldHVybiB0aGlzLnJhbmRvbVNlbGVjdG9yKHRoaXMubW9ybmluZ0dyZWV0aW5ncyk7DQogICAgfQ0KICAgIGV2ZW5pbmcoKSB7DQogICAgICAgIHJldHVybiB0aGlzLnJhbmRvbVNlbGVjdG9yKHRoaXMuZXZlbmluZ0dyZWV0aW5ncyk7DQogICAgfQ0KfQ0K"
},
"types": {
"my-greeter": "data:text/typescript;charset=UTF-8;base64,ZXhwb3J0IGRlY2xhcmUgY2xhc3MgR3JlZXRlciB7DQogIHByaXZhdGUgbW9ybmluZ0dyZWV0aW5nczsNCiAgcHJpdmF0ZSBldmVuaW5nR3JlZXRpbmdzOw0KICBwcml2YXRlIHJhbmRvbVNlbGVjdG9yOw0KICBtb3JuaW5nKCk6IHN0cmluZzsNCiAgZXZlbmluZygpOiBzdHJpbmc7DQp9DQo="
}
}
};
return (<LiveCodes {...options}></LiveCodes>);
}
<script setup>
import LiveCodes from "livecodes/vue";
const options = {
"config": {
"editor": "monaco",
"activeEditor": "script",
"script": {
"language": "typescript",
"content": "import { Greeter } from 'my-greeter';\n\nconst greeter = new Greeter();\n// now `greeter` has autocomplete\n\ndocument.body.innerText = greeter.morning();\n\n// this should show error in the editor\n// Property 'morningGreetings' is private and only accessible within class 'Greeter'\nconsole.log(greeter.morningGreetings);"
},
"imports": {
"my-greeter": "data:text/javascript;charset=UTF-8;base64,ZXhwb3J0IGNsYXNzIEdyZWV0ZXIgew0KICAgIGNvbnN0cnVjdG9yKCkgew0KICAgICAgICB0aGlzLm1vcm5pbmdHcmVldGluZ3MgPSBbJ0dvb2QgbW9ybmluZycsICdIYXZlIGEgZ29vZCBkYXknLCAnSG93IGFyZSB5b3UgdG9kYXk/J107DQogICAgICAgIHRoaXMuZXZlbmluZ0dyZWV0aW5ncyA9IFsnR29vZCBldmVuaW5nJywgJ0dvb2QgbmlnaHQnLCAnU2xlZXAgd2VsbCddOw0KICAgIH0NCiAgICByYW5kb21TZWxlY3RvcihhcnJheSkgew0KICAgICAgICByZXR1cm4gYXJyYXlbTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogYXJyYXkubGVuZ3RoKV07DQogICAgfQ0KICAgIG1vcm5pbmcoKSB7DQogICAgICAgIHJldHVybiB0aGlzLnJhbmRvbVNlbGVjdG9yKHRoaXMubW9ybmluZ0dyZWV0aW5ncyk7DQogICAgfQ0KICAgIGV2ZW5pbmcoKSB7DQogICAgICAgIHJldHVybiB0aGlzLnJhbmRvbVNlbGVjdG9yKHRoaXMuZXZlbmluZ0dyZWV0aW5ncyk7DQogICAgfQ0KfQ0K"
},
"types": {
"my-greeter": "data:text/typescript;charset=UTF-8;base64,ZXhwb3J0IGRlY2xhcmUgY2xhc3MgR3JlZXRlciB7DQogIHByaXZhdGUgbW9ybmluZ0dyZWV0aW5nczsNCiAgcHJpdmF0ZSBldmVuaW5nR3JlZXRpbmdzOw0KICBwcml2YXRlIHJhbmRvbVNlbGVjdG9yOw0KICBtb3JuaW5nKCk6IHN0cmluZzsNCiAgZXZlbmluZygpOiBzdHJpbmc7DQp9DQo="
}
}
};
</script>
<template>
<LiveCodes v-bind="options" />
</template>
<script>
import { onMount } from 'svelte';
import { createPlayground } from 'livecodes';
const options = {
"config": {
"editor": "monaco",
"activeEditor": "script",
"script": {
"language": "typescript",
"content": "import { Greeter } from 'my-greeter';\n\nconst greeter = new Greeter();\n// now `greeter` has autocomplete\n\ndocument.body.innerText = greeter.morning();\n\n// this should show error in the editor\n// Property 'morningGreetings' is private and only accessible within class 'Greeter'\nconsole.log(greeter.morningGreetings);"
},
"imports": {
"my-greeter": "data:text/javascript;charset=UTF-8;base64,ZXhwb3J0IGNsYXNzIEdyZWV0ZXIgew0KICAgIGNvbnN0cnVjdG9yKCkgew0KICAgICAgICB0aGlzLm1vcm5pbmdHcmVldGluZ3MgPSBbJ0dvb2QgbW9ybmluZycsICdIYXZlIGEgZ29vZCBkYXknLCAnSG93IGFyZSB5b3UgdG9kYXk/J107DQogICAgICAgIHRoaXMuZXZlbmluZ0dyZWV0aW5ncyA9IFsnR29vZCBldmVuaW5nJywgJ0dvb2QgbmlnaHQnLCAnU2xlZXAgd2VsbCddOw0KICAgIH0NCiAgICByYW5kb21TZWxlY3RvcihhcnJheSkgew0KICAgICAgICByZXR1cm4gYXJyYXlbTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogYXJyYXkubGVuZ3RoKV07DQogICAgfQ0KICAgIG1vcm5pbmcoKSB7DQogICAgICAgIHJldHVybiB0aGlzLnJhbmRvbVNlbGVjdG9yKHRoaXMubW9ybmluZ0dyZWV0aW5ncyk7DQogICAgfQ0KICAgIGV2ZW5pbmcoKSB7DQogICAgICAgIHJldHVybiB0aGlzLnJhbmRvbVNlbGVjdG9yKHRoaXMuZXZlbmluZ0dyZWV0aW5ncyk7DQogICAgfQ0KfQ0K"
},
"types": {
"my-greeter": "data:text/typescript;charset=UTF-8;base64,ZXhwb3J0IGRlY2xhcmUgY2xhc3MgR3JlZXRlciB7DQogIHByaXZhdGUgbW9ybmluZ0dyZWV0aW5nczsNCiAgcHJpdmF0ZSBldmVuaW5nR3JlZXRpbmdzOw0KICBwcml2YXRlIHJhbmRvbVNlbGVjdG9yOw0KICBtb3JuaW5nKCk6IHN0cmluZzsNCiAgZXZlbmluZygpOiBzdHJpbmc7DQp9DQo="
}
}
};
let container;
onMount(() => {
createPlayground(container, options);
});
</script>
<div bind:this="{container}"></div>