├── .prettierignore ├── .eslintignore ├── .eslintrc ├── src ├── types │ ├── react-resize-aware │ │ └── index.d.ts │ └── index.d.ts ├── plugin │ ├── index.ts │ ├── usePlugin.tsx │ ├── vendor │ │ ├── pluginUtils.d.ts │ │ ├── tsWorker.d.ts │ │ ├── typescript-vfs.d.ts │ │ ├── playground.d.ts │ │ └── sandbox.d.ts │ └── Provider.tsx ├── App.css ├── index.tsx ├── assets │ └── logo.svg └── App.tsx ├── screenshots ├── screenshot1.png └── screenshot2.png ├── .npmignore ├── tsconfig.json ├── LICENSE ├── .gitignore ├── scripts └── open-playground.js ├── rollup.config.js ├── package.json ├── CONTRIBUTING.md └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.css 2 | dist 3 | .cache -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app" 3 | } 4 | -------------------------------------------------------------------------------- /src/types/react-resize-aware/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "react-resize-aware"; -------------------------------------------------------------------------------- /src/plugin/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Provider"; 2 | export * from "./usePlugin"; 3 | 4 | -------------------------------------------------------------------------------- /screenshots/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/just214/typescript-playground-plugin-react/HEAD/screenshots/screenshot1.png -------------------------------------------------------------------------------- /screenshots/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/just214/typescript-playground-plugin-react/HEAD/screenshots/screenshot2.png -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | .gitignore 3 | rollup.config.jss 4 | !dist 5 | scripts 6 | .vscode 7 | yarn* 8 | tsconfig.json 9 | rollup* 10 | -------------------------------------------------------------------------------- /src/plugin/usePlugin.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { PluginContext, PluginContextProps } from "./Provider"; 3 | const { useContext } = React; 4 | 5 | export function usePlugin() { 6 | return useContext(PluginContext) as PluginContextProps; 7 | } 8 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App-logo { 2 | height: 20vmin; 3 | pointer-events: none; 4 | } 5 | 6 | @media (prefers-reduced-motion: no-preference) { 7 | .App-logo { 8 | animation: App-logo-spin infinite 20s linear; 9 | } 10 | } 11 | 12 | @keyframes App-logo-spin { 13 | from { 14 | transform: rotate(0deg); 15 | } 16 | to { 17 | transform: rotate(360deg); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import {ShowModal, FlashInfo} from '../plugin/Provider' 2 | 3 | declare module "*.jpeg"; 4 | declare module "*.jpg"; 5 | declare module "*.png"; 6 | declare module '*.svg' { 7 | const content: any 8 | export default content; 9 | } 10 | 11 | declare global { 12 | interface Window { 13 | playground: { 14 | ui: { 15 | showModal: ShowModal; 16 | flashInfo: FlashInfo; 17 | }; 18 | }; 19 | reactDOM: any; 20 | react: any; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "jsx": "react", 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "strict": true, 9 | "noEmit": true, 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "isolatedModules": true, 14 | "allowSyntheticDefaultImports": true, 15 | "typeRoots": ["src/types", "src/plugin/vendor"] 16 | }, 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /src/plugin/vendor/pluginUtils.d.ts: -------------------------------------------------------------------------------- 1 | import { Node } from "typescript"; 2 | /** Creates a set of util functions which is exposed to Plugins to make it easier to build consistent UIs */ 3 | export declare const createUtils: (sb: any) => { 4 | /** Use this to make a few dumb element generation funcs */ 5 | el: (str: string, el: string, container: Element) => void; 6 | /** Get a relative URL for something in your dist folder depending on if you're in dev mode or not */ 7 | requireURL: (path: string) => string; 8 | /** Returns a div which has an interactive AST a TypeScript AST by passing in the root node */ 9 | createASTTree: (node: Node) => HTMLDivElement; 10 | }; 11 | export declare type PluginUtils = ReturnType; 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (http://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # Typescript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # dotenv environment variables file 57 | .env 58 | 59 | # gatsby files 60 | .cache/ 61 | public 62 | 63 | # Mac files 64 | .DS_Store 65 | 66 | # Yarn 67 | yarn-error.log 68 | .pnp/ 69 | .pnp.js 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | dist 73 | -------------------------------------------------------------------------------- /scripts/open-playground.js: -------------------------------------------------------------------------------- 1 | /* 2 | This script is responsible for opening the TypeScript Playground in Chrome on the first 3 | build when running the "start" script. It also provides some useful console messages. 4 | 5 | It is called by rollup-plugin-execute in rollup.config.js 6 | */ 7 | const exec = require("child_process").exec; 8 | const getChromeTabs = require("get-chrome-tabs"); 9 | const chalk = require("chalk"); 10 | 11 | const PLAYGROUND_URL = "https://www.typescriptlang.org/play"; 12 | 13 | function openPlayground() { 14 | exec(`open-cli ${PLAYGROUND_URL} -- chrome`, function (err) { 15 | if (err) { 16 | console.log( 17 | chalk.red("Error opening the TypeScript Playground. Please try again.") 18 | ); 19 | } else { 20 | const message = chalk.green( 21 | '\n🚀 The TypeScript Playground was opened in Chrome. To view your plugin, select "Options > Connect to localhost:5000/index.js" in the Playground sidebar and refresh the browser tab.' 22 | ); 23 | console.log(message); 24 | } 25 | }); 26 | } 27 | 28 | let tabList = []; 29 | 30 | getChromeTabs() 31 | .then((tabs) => { 32 | tabList = tabs; 33 | }) 34 | .catch((err) => { 35 | return err; 36 | }) 37 | .finally(() => { 38 | const isPlaygroundOpen = tabList.find((tab) => 39 | tab.url.includes(PLAYGROUND_URL) 40 | ); 41 | if (!isPlaygroundOpen) { 42 | openPlayground(); 43 | } else { 44 | console.log( 45 | chalk.green( 46 | "\n🚀 Your plugin has been updated. Please refresh the TypeScript Playground Chrome tab to see your changes." 47 | ) 48 | ); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import replace from "@rollup/plugin-replace"; 2 | import resolve from "@rollup/plugin-node-resolve"; 3 | import commonjs from "@rollup/plugin-commonjs"; 4 | import babel from "rollup-plugin-babel"; 5 | import postcss from "rollup-plugin-postcss"; 6 | import image from "@rollup/plugin-image"; 7 | import execute from "rollup-plugin-execute"; 8 | import progress from "rollup-plugin-progress"; 9 | import serve from "rollup-plugin-serve"; 10 | import { eslint } from "rollup-plugin-eslint"; 11 | import { terser } from "rollup-plugin-terser"; 12 | import externalGlobals from "rollup-plugin-external-globals"; 13 | import ignore from "rollup-plugin-ignore"; 14 | import analyze from "rollup-plugin-analyzer"; 15 | import filesize from "rollup-plugin-filesize"; 16 | 17 | const isProd = process.env.NODE_ENV === "production"; 18 | // const isWatch = process.env.ROLLUP_WATCH; 19 | const extensions = [".js", ".ts", ".tsx"]; 20 | 21 | export default { 22 | input: "src/index.tsx", 23 | external: ["typescript", "react"], 24 | treeshake: true, 25 | output: { 26 | file: "dist/index.js", 27 | format: "amd" 28 | }, 29 | plugins: [ 30 | isProd && filesize(), 31 | isProd && 32 | analyze({ 33 | summaryOnly: true 34 | }), 35 | 36 | progress(), 37 | execute("node scripts/open-playground"), 38 | image(), 39 | postcss({ minimize: true }), 40 | replace({ 41 | "process.env.NODE_ENV": JSON.stringify( 42 | isProd ? "production" : "development" 43 | ) 44 | }), 45 | eslint({ throwOnError: true }), 46 | resolve({ 47 | extensions, 48 | modulesOnly: false 49 | }), 50 | commonjs({ 51 | include: /node_modules/, 52 | sourceMap: false 53 | }), 54 | babel({ 55 | extensions, 56 | exclude: /node_modules/, 57 | babelrc: false, 58 | runtimeHelpers: true, 59 | presets: [ 60 | "@babel/preset-env", 61 | "@babel/preset-react", 62 | "@babel/preset-typescript" 63 | ] 64 | }), 65 | ignore(["react"]), 66 | externalGlobals({ react: "window.react" }), 67 | isProd && terser(), 68 | !isProd && 69 | serve({ 70 | contentBase: "dist", 71 | port: 5000 72 | }) 73 | ] 74 | }; 75 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | // import React from "react"; 2 | import App from "./App"; 3 | import { Provider } from "./plugin"; 4 | import { PluginUtils } from "./plugin/vendor/pluginUtils"; 5 | import { PlaygroundPlugin } from "./plugin/vendor/playground"; 6 | 7 | const React = window.react; 8 | const ReactDOM = window.reactDOM; 9 | 10 | // Used internally 11 | const ID = "react"; 12 | 13 | // Sidebar tab label text 14 | const DISPLAY_NAME = "React"; 15 | 16 | function makePlugin(utils: PluginUtils) { 17 | const customPlugin: PlaygroundPlugin = { 18 | id: ID, 19 | displayName: DISPLAY_NAME, 20 | didMount(sandbox, container) { 21 | const initialContainerObject = { 22 | ref: container, 23 | // Why doesn't clientWidth/Height exist on HTMLDivElement?? 24 | // @ts-ignore 25 | width: container.clientWidth, 26 | // @ts-ignore 27 | height: container.clientHeight 28 | }; 29 | // Mount the react app and pass the sandbox and container to the Provider wrapper to set up context. 30 | ReactDOM.render( 31 | 36 | 37 | , 38 | container 39 | ); 40 | }, 41 | // Dispatch custom events for the modelChanges methods for the plugin context. 42 | modelChanged(_, model) { 43 | createCustomEvent("modelChanged", model); 44 | }, 45 | // Per the Plugin source: 46 | // This is called occasionally as text changes in monaco, 47 | // it does not directly map 1 keyup to once run of the function 48 | // because it is intentionally called at most once every 0.3 seconds 49 | // and then will always run at the end. 50 | modelChangedDebounce(_, model) { 51 | createCustomEvent("modelChangedDebounce", model); 52 | }, 53 | willUnmount(_, container) { 54 | ReactDOM.unmountComponentAtNode(container); 55 | } 56 | }; 57 | 58 | function createCustomEvent( 59 | name: string, 60 | model: import("monaco-editor").editor.ITextModel 61 | ) { 62 | const event = new CustomEvent(name, { 63 | detail: { 64 | model 65 | } 66 | }); 67 | window.dispatchEvent(event); 68 | } 69 | return customPlugin; 70 | } 71 | 72 | export default makePlugin; 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "typescript-playground-plugin-react", 4 | "description": "Easily create TypeScript Playground Plugins with React.", 5 | "version": "0.0.2", 6 | "repository": "git@github.com:gojutin/typescript-playground-plugin-react.git", 7 | "author": "Justin Wilkerson ", 8 | "license": "MIT", 9 | "scripts": { 10 | "start": "npm run watch", 11 | "watch": "cross-env NODE_ENV=development rollup -c -w", 12 | "lint": "eslint . --ext .ts,.tsx", 13 | "build": "cross-env NODE_ENV=production rollup -c" 14 | }, 15 | "keywords": [ 16 | "playground-plugin" 17 | ], 18 | "dependencies": { 19 | "goober": "^2.0.28", 20 | "react": "^17.0.1", 21 | "react-resize-aware": "^3.1.0" 22 | }, 23 | "devDependencies": { 24 | "@babel/core": "^7.12.10", 25 | "@babel/preset-env": "^7.12.11", 26 | "@babel/preset-react": "^7.12.10", 27 | "@babel/preset-typescript": "^7.12.7", 28 | "@rollup/plugin-commonjs": "^17.1.0", 29 | "@rollup/plugin-image": "^2.0.6", 30 | "@rollup/plugin-node-resolve": "^11.1.1", 31 | "@rollup/plugin-replace": "^2.3.4", 32 | "@types/node": "^14.14.22", 33 | "@types/react": "^17.0.0", 34 | "@typescript-eslint/eslint-plugin": "4.x", 35 | "@typescript-eslint/parser": "4.x", 36 | "babel-eslint": "10.x", 37 | "chalk": "^4.1.0", 38 | "cross-env": "^7.0.3", 39 | "eslint": "7.x", 40 | "eslint-config-react-app": "^6.0.0", 41 | "eslint-plugin-flowtype": "5.x", 42 | "eslint-plugin-import": "2.x", 43 | "eslint-plugin-jsx-a11y": "6.x", 44 | "eslint-plugin-react": "7.x", 45 | "eslint-plugin-react-hooks": "4.x", 46 | "get-chrome-tabs": "^1.0.0", 47 | "monaco-editor": "^0.22.1", 48 | "open-cli": "^6.0.1", 49 | "rollup": "^2.38.2", 50 | "rollup-plugin-analyzer": "^4.0.0", 51 | "rollup-plugin-babel": "^4.3.3", 52 | "rollup-plugin-css-only": "^3.1.0", 53 | "rollup-plugin-eslint": "^7.0.0", 54 | "rollup-plugin-execute": "^1.1.1", 55 | "rollup-plugin-external-globals": "^0.6.1", 56 | "rollup-plugin-filesize": "^9.1.0", 57 | "rollup-plugin-ignore": "^1.0.9", 58 | "rollup-plugin-postcss": "^4.0.0", 59 | "rollup-plugin-progress": "^1.1.2", 60 | "rollup-plugin-serve": "^1.1.0", 61 | "rollup-plugin-terser": "^7.0.2", 62 | "typescript": "^4.1.3" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to a TypeScript Playground Plugin 2 | 3 | ## Contributing 4 | 5 | You can use `yarn start` to set up both a copy of Rollup to generate the JS, and Serve to host it. 6 | 7 | ```sh 8 | yarn start 9 | ``` 10 | 11 | Then set up the TypeScript playground to connect to a dev plugin at `http://localhost:5000/index.js`. 12 | 13 | #### Plugin API 14 | 15 | The plugin API is documented in the [interface PlaygroundPlugin in `./src/vendor/playground.d.ts`](src/vendor/playground.d.ts) 16 | 17 | Roughly: 18 | 19 | - There are a set of mounting and un-mounting functions which you can use to handle your UI in the sidebar 20 | - There are `modelChanged` methods, which are shortcuts to knowing when the code in monaco editor has changed 21 | 22 | ### Sandbox 23 | 24 | The plugins are passed copies of the TypeScript sandbox, which is a high level API wrapper to the [`monaco-editor`](https://microsoft.github.io/monaco-editor/). You can learn more about the sandbox on [the TypeScript website](http://www.typescriptlang.org/v2/dev/sandbox/ 25 | 26 | #### Rollup 27 | 28 | [Rollup](https://rollupjs.org) is a JavaScript bundler, that will take all of the TypeScript + JavaScript code you reference and then create an AMD bundle for it all. AMD bundles are used in Monaco, TypeScript Sandbox and the Playground - so, this is used for consistency with the rest of the ecosystem. 29 | 30 | #### Serve 31 | 32 | [Serve](https://github.com/zeit/serve) is used to make a web-server for the dist folder. 33 | 34 | ## Deployment 35 | 36 | This module should be deployed to npm when you would like the world to see it, this may mean making your code handle a staging vs production environment (because the URLs will be different.) 37 | 38 | For example, this is how you can handle getting the URL for a CSS file which is included in your `dist` folder: 39 | 40 | ```ts 41 | const isDev = document.location.host.includes('localhost') 42 | const unpkgURL = 'https://unpkg.com/typescript-playground-presentation-mode@latest/dist/slideshow.css' 43 | const cssHref = isDev ? 'http://localhost:5000/slideshow.css' : unpkgURL 44 | ``` 45 | 46 | ### Post-Deploy 47 | 48 | Once this is deployed, you can test it on the TypeScript playground by passing in the name of your plugin on npm to the custom plugin box. This is effectively your staging environment. 49 | 50 | Once you're happy and it's polished, you can apply to have it in the default plugin list. 51 | 52 | ## Support 53 | 54 | Ask questions either on the TypeScript Website issues](https://github.com/microsoft/TypeScript-Website/issues), or in the [TypeScript Community Discord](https://discord.gg/typescript) - in the TypeScript Website channel. 55 | -------------------------------------------------------------------------------- /src/plugin/vendor/tsWorker.d.ts: -------------------------------------------------------------------------------- 1 | import ts from 'typescript'; 2 | export declare class TypeScriptWorker implements ts.LanguageServiceHost { 3 | private _ctx; 4 | private _extraLibs; 5 | private _languageService; 6 | private _compilerOptions; 7 | constructor(ctx: any, createData: any); 8 | getCompilationSettings(): ts.CompilerOptions; 9 | getScriptFileNames(): string[]; 10 | private _getModel; 11 | getScriptVersion(fileName: string): string; 12 | getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined; 13 | getScriptKind?(fileName: string): ts.ScriptKind; 14 | getCurrentDirectory(): string; 15 | getDefaultLibFileName(options: ts.CompilerOptions): string; 16 | isDefaultLibFileName(fileName: string): boolean; 17 | private static clearFiles; 18 | getSyntacticDiagnostics(fileName: string): Promise; 19 | getSemanticDiagnostics(fileName: string): Promise; 20 | getSuggestionDiagnostics(fileName: string): Promise; 21 | getCompilerOptionsDiagnostics(fileName: string): Promise; 22 | getCompletionsAtPosition(fileName: string, position: number): Promise; 23 | getCompletionEntryDetails(fileName: string, position: number, entry: string): Promise; 24 | getSignatureHelpItems(fileName: string, position: number): Promise; 25 | getQuickInfoAtPosition(fileName: string, position: number): Promise; 26 | getOccurrencesAtPosition(fileName: string, position: number): Promise | undefined>; 27 | getDefinitionAtPosition(fileName: string, position: number): Promise | undefined>; 28 | getReferencesAtPosition(fileName: string, position: number): Promise; 29 | getNavigationBarItems(fileName: string): Promise; 30 | getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): Promise; 31 | getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): Promise; 32 | getFormattingEditsAfterKeystroke(fileName: string, postion: number, ch: string, options: ts.FormatCodeOptions): Promise; 33 | findRenameLocations(fileName: string, positon: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename: boolean): Promise; 34 | getRenameInfo(fileName: string, positon: number, options: ts.RenameInfoOptions): Promise; 35 | getEmitOutput(fileName: string): Promise; 36 | getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: ts.FormatCodeOptions): Promise>; 37 | updateExtraLibs(extraLibs: IExtraLibs): void; 38 | } 39 | export interface IExtraLib { 40 | content: string; 41 | version: number; 42 | } 43 | export interface IExtraLibs { 44 | [path: string]: IExtraLib; 45 | } 46 | -------------------------------------------------------------------------------- /src/plugin/Provider.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import useResizeAware from "react-resize-aware"; 3 | import { Sandbox } from "./vendor/playground"; 4 | import { PluginUtils } from "./vendor/PluginUtils"; 5 | const { useState, useEffect, createContext, useCallback } = React; 6 | 7 | type Model = import("monaco-editor").editor.ITextModel; 8 | 9 | type ModelMarker = import("monaco-editor").editor.IMarker; 10 | 11 | export type FlashInfo = (message: string) => void; 12 | 13 | export type ShowModal = { 14 | (code: string, subtitle?: string, links?: string[]): void; 15 | }; 16 | 17 | export const PluginContext = createContext({}); 18 | 19 | type ContainerObject = { 20 | ref: HTMLDivElement; 21 | width: number; 22 | height: number; 23 | }; 24 | 25 | export type PluginContextProps = { 26 | code: string; 27 | container: ContainerObject; 28 | sandbox: Sandbox; 29 | model: Model; 30 | flashInfo: FlashInfo; 31 | showModal: ShowModal; 32 | markers: (ModelMarker & { key: string })[]; 33 | setCode(value: string, options?: { format: boolean }): void; 34 | formatCode(): void; 35 | setDebounce(debounce: boolean): void; 36 | utils: PluginUtils; 37 | }; 38 | 39 | type ProviderProps = Pick< 40 | PluginContextProps, 41 | "sandbox" | "container" | "utils" 42 | >; 43 | 44 | export const Provider: React.FC = ({ 45 | sandbox, 46 | container, 47 | utils, 48 | children 49 | }) => { 50 | const [model, setModel] = useState(); 51 | const [code, _setCode] = useState(sandbox.getText()); 52 | const [markers, setMarkers] = useState([]); 53 | const [debounce, setDebounce] = useState(false); 54 | const [resizeListener, sizes] = useResizeAware(); 55 | 56 | const listenerFn = useCallback( 57 | (evt): void => { 58 | setModel({ ...evt.detail.model }); 59 | _setCode(sandbox.getText()); 60 | }, 61 | [sandbox] 62 | ); 63 | 64 | useEffect(() => { 65 | const disposable = sandbox.editor.onDidChangeModelDecorations(() => { 66 | const allMarkers = sandbox.monaco.editor 67 | .getModelMarkers({}) 68 | .map((marker, index) => { 69 | return { 70 | ...marker, 71 | key: index.toString() 72 | }; 73 | }); 74 | setMarkers(allMarkers); 75 | }); 76 | return () => disposable.dispose(); 77 | }, [sandbox]); 78 | 79 | useEffect(() => { 80 | const eventName = debounce ? "modelChangedDebounce" : "modelChanged"; 81 | window.addEventListener(eventName, listenerFn); 82 | const otherEventName = debounce ? "modelChanged" : "modelChangedDebounce"; 83 | window.removeEventListener(otherEventName, listenerFn, false); 84 | return () => window.removeEventListener(eventName, listenerFn, false); 85 | }, [debounce, listenerFn]); 86 | 87 | const setCode = useCallback( 88 | (value: string, options?: { format: true }) => { 89 | if (options && options.format) { 90 | sandbox.setText(value); 91 | sandbox.editor.getAction("editor.action.formatDocument").run(); 92 | } else { 93 | sandbox.setText(value); 94 | } 95 | }, 96 | [sandbox] 97 | ); 98 | 99 | const formatCode = useCallback(() => { 100 | return sandbox.editor.getAction("editor.action.formatDocument").run(); 101 | }, [sandbox.editor]); 102 | 103 | const { showModal, flashInfo } = window.playground.ui; 104 | 105 | const containerWithDimensions: ContainerObject = { 106 | ref: container, 107 | ...sizes 108 | }; 109 | 110 | const value = { 111 | model, 112 | showModal, 113 | flashInfo, 114 | sandbox, 115 | container: containerWithDimensions, 116 | code, 117 | setCode, 118 | formatCode, 119 | setDebounce, 120 | markers, 121 | utils 122 | }; 123 | return ( 124 | 125 | {resizeListener} 126 | {children} 127 | 128 | ); 129 | }; 130 | -------------------------------------------------------------------------------- /src/plugin/vendor/typescript-vfs.d.ts: -------------------------------------------------------------------------------- 1 | 2 | declare type System = import('typescript').System; 3 | declare type CompilerOptions = import('typescript').CompilerOptions; 4 | declare type TS = typeof import('typescript'); 5 | export interface VirtualTypeScriptEnvironment { 6 | sys: System; 7 | languageService: import('typescript').LanguageService; 8 | getSourceFile: (fileName: string) => import('typescript').SourceFile | undefined; 9 | createFile: (fileName: string, content: string) => void; 10 | updateFile: (fileName: string, content: string, replaceTextSpan?: import('typescript').TextSpan) => void; 11 | } 12 | /** 13 | * Makes a virtual copy of the TypeScript environment. This is the main API you want to be using with 14 | * typescript-vfs. A lot of the other exposed functions are used by this function to get set up. 15 | * 16 | * @param sys an object which conforms to the TS Sys (a shim over read/write access to the fs) 17 | * @param rootFiles a list of files which are considered inside the project 18 | * @param ts a copy pf the TypeScript module 19 | * @param compilerOptions the options for this compiler run 20 | */ 21 | export declare function createVirtualTypeScriptEnvironment(sys: System, rootFiles: string[], ts: TS, compilerOptions?: CompilerOptions): VirtualTypeScriptEnvironment; 22 | /** 23 | * Grab the list of lib files for a particular target, will return a bit more than necessary (by including 24 | * the dom) but that's OK 25 | * 26 | * @param target The compiler settings target baseline 27 | * @param ts A copy of the TypeScript module 28 | */ 29 | export declare const knownLibFilesForCompilerOptions: (compilerOptions: import("typescript").CompilerOptions, ts: typeof import("typescript")) => string[]; 30 | /** 31 | * Sets up a Map with lib contents by grabbing the necessary files from 32 | * the local copy of typescript via the file system. 33 | */ 34 | export declare const createDefaultMapFromNodeModules: (compilerOptions: import("typescript").CompilerOptions) => Map; 35 | /** 36 | * Create a virtual FS Map with the lib files from a particular TypeScript 37 | * version based on the target, Always includes dom ATM. 38 | * 39 | * @param options The compiler target, which dictates the libs to set up 40 | * @param version the versions of TypeScript which are supported 41 | * @param cache should the values be stored in local storage 42 | * @param ts a copy of the typescript import 43 | * @param lzstring an optional copy of the lz-string import 44 | * @param fetcher an optional replacement for the global fetch function (tests mainly) 45 | * @param storer an optional replacement for the localStorage global (tests mainly) 46 | */ 47 | export declare const createDefaultMapFromCDN: (options: import("typescript").CompilerOptions, version: string, cache: boolean, ts: typeof import("typescript"), lzstring?: any | undefined, fetcher?: typeof fetch | undefined, storer?: Storage | undefined) => Promise>; 48 | /** 49 | * Creates an in-memory System object which can be used in a TypeScript program, this 50 | * is what provides read/write aspects of the virtual fs 51 | */ 52 | export declare function createSystem(files: Map): System; 53 | /** 54 | * Creates an in-memory CompilerHost -which is essentially an extra wrapper to System 55 | * which works with TypeScript objects - returns both a compiler host, and a way to add new SourceFile 56 | * instances to the in-memory file system. 57 | */ 58 | export declare function createVirtualCompilerHost(sys: System, compilerOptions: CompilerOptions, ts: TS): { 59 | compilerHost: import("typescript").CompilerHost; 60 | updateFile: (sourceFile: import("typescript").SourceFile) => boolean; 61 | }; 62 | /** 63 | * Creates an object which can host a language service against the virtual file-system 64 | */ 65 | export declare function createVirtualLanguageServiceHost(sys: System, rootFiles: string[], compilerOptions: CompilerOptions, ts: TS): { 66 | languageServiceHost: import("typescript").LanguageServiceHost; 67 | updateFile: (sourceFile: import("typescript").SourceFile) => void; 68 | }; 69 | export {}; 70 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { css } from "goober"; 3 | import { usePlugin } from "./plugin"; 4 | import "./App.css"; 5 | // @ts-ignore TODO: Fix this 6 | import logo from "./assets/logo.svg"; 7 | 8 | const { useEffect } = React; 9 | 10 | const greetingComment = "// Welcome to your TypeScript Playground Plugin!\n\n"; 11 | const exampleCode = { 12 | start: greetingComment + "function echo(arg) { return arg};", 13 | end: greetingComment + "function echo(arg:T): T {return arg;}" 14 | }; 15 | 16 | const App: React.FC = () => { 17 | const { 18 | code, 19 | setCode, 20 | formatCode, 21 | markers, 22 | setDebounce, 23 | showModal, 24 | flashInfo, 25 | container 26 | // utils, 27 | // sandbox, 28 | // model, 29 | } = usePlugin(); 30 | 31 | setDebounce(true); 32 | 33 | useEffect(() => { 34 | setCode(exampleCode.start); 35 | }, [setCode]); 36 | 37 | useEffect(() => { 38 | console.log(`The editor code has changed:`); 39 | console.log(code); 40 | }, [code]); 41 | 42 | useEffect(() => { 43 | // Listen to changes of the container dimensions. 44 | console.log("Container Width: ", container.width); 45 | console.log("Container Height: ", container.height); 46 | }, [container]); 47 | 48 | function handleClear() { 49 | setCode(""); 50 | flashInfo("Cleared!"); 51 | } 52 | 53 | function handleFixCode() { 54 | setCode(exampleCode.end, { format: true }); 55 | } 56 | 57 | function handleFormatCode() { 58 | formatCode(); 59 | } 60 | 61 | function handleShowModal() { 62 | showModal(code, `Here is your code!`); 63 | } 64 | 65 | function handleShowFlash() { 66 | flashInfo("Flash!"); 67 | } 68 | 69 | const renderMarkers = markers 70 | .sort((a, b) => (a.startLineNumber >= b.startLineNumber ? 1 : -1)) 71 | .map(marker => { 72 | return ( 73 |
74 |

75 | Line {marker.startLineNumber}:  76 | {marker.message} 77 |

78 |
79 | ); 80 | }); 81 | 82 | return ( 83 |
84 |
85 |

TypeScript Playground Plugin

86 |

with React

87 | logo 88 |
89 | 92 | 95 | 96 | 99 | 100 | 103 | 104 | 107 | 108 |
113 | {!!markers.length && renderMarkers} 114 |
115 |
116 | ); 117 | }; 118 | 119 | const colors = { 120 | darkgray: "hsla(0, 0%, 7%, 1)", 121 | gray: "hsla(0, 0%, 21%, 1)", 122 | blue: "hsla(193, 95%, 68%, 1)" 123 | }; 124 | 125 | const wrapperClass = css` 126 | background: ${colors.darkgray}; 127 | text-align: center; 128 | min-height: 100vh; 129 | padding: 10px; 130 | color: white; 131 | 132 | h1, 133 | h3 { 134 | font-weight: 300; 135 | line-height: 1; 136 | } 137 | `; 138 | 139 | const buttonClass = css` 140 | display: inline-block; 141 | margin: 5px; 142 | padding: 5px; 143 | min-width: 150px; 144 | color: ${colors.blue}; 145 | background: transparent; 146 | font-size: 0.9rem; 147 | border: 1px solid ${colors.blue}; 148 | border-radius: 4px; 149 | cursor: pointer; 150 | transition: background-color 0.3s; 151 | &:hover { 152 | background: ${colors.gray}; 153 | } 154 | `; 155 | 156 | const markerClass = css` 157 | margin: 5px; 158 | padding: 0px; 159 | font-size: 0.9rem; 160 | `; 161 | 162 | export default App; 163 | -------------------------------------------------------------------------------- /src/plugin/vendor/playground.d.ts: -------------------------------------------------------------------------------- 1 | declare type Sandbox = import('./sandbox').Sandbox; 2 | export { PluginUtils } from './pluginUtils'; 3 | export declare type PluginFactory = { 4 | (i: (key: string, components?: any) => string): PlaygroundPlugin; 5 | }; 6 | /** The interface of all sidebar plugins */ 7 | export interface PlaygroundPlugin { 8 | /** Not public facing, but used by the playground to uniquely identify plugins */ 9 | id: string; 10 | /** To show in the tabs */ 11 | displayName: string; 12 | /** Should this plugin be selected when the plugin is first loaded? Let's you check for query vars etc to load a particular plugin */ 13 | shouldBeSelected?: () => boolean; 14 | /** Before we show the tab, use this to set up your HTML - it will all be removed by the playground when someone navigates off the tab */ 15 | willMount?: (sandbox: Sandbox, container: HTMLDivElement) => void; 16 | /** After we show the tab */ 17 | didMount?: (sandbox: Sandbox, container: HTMLDivElement) => void; 18 | /** Model changes while this plugin is actively selected */ 19 | modelChanged?: (sandbox: Sandbox, model: import('monaco-editor').editor.ITextModel) => void; 20 | /** Delayed model changes while this plugin is actively selected, useful when you are working with the TS API because it won't run on every keypress */ 21 | modelChangedDebounce?: (sandbox: Sandbox, model: import('monaco-editor').editor.ITextModel) => void; 22 | /** Before we remove the tab */ 23 | willUnmount?: (sandbox: Sandbox, container: HTMLDivElement) => void; 24 | /** After we remove the tab */ 25 | didUnmount?: (sandbox: Sandbox, container: HTMLDivElement) => void; 26 | /** An object you can use to keep data around in the scope of your plugin object */ 27 | data?: any; 28 | } 29 | interface PlaygroundConfig { 30 | lang: string; 31 | prefix: string; 32 | } 33 | 34 | export type Sandbox = { 35 | config: { 36 | text: string; 37 | useJavaScript: boolean; 38 | compilerOptions: import("monaco-editor").languages.typescript.CompilerOptions; 39 | monacoSettings?: import("monaco-editor").editor.IEditorOptions | undefined; 40 | acquireTypes: boolean; 41 | supportTwoslashCompilerOptions: boolean; 42 | suppressAutomaticallyGettingDefaultText?: true | undefined; 43 | suppressAutomaticallyGettingCompilerFlags?: true | undefined; 44 | logger: { 45 | log: (...args: any[]) => void; 46 | error: (...args: any[]) => void; 47 | }; 48 | domID: string; 49 | }; 50 | supportedVersions: readonly ["2.4.1", "2.7.2", "2.8.1", "3.0.1", "3.1.6", "3.3.3", "3.5.1", "3.6.3", "3.7.5", "3.8.2"]; 51 | editor: import("monaco-editor").editor.IStandaloneCodeEditor; 52 | language: string; 53 | monaco: typeof import("monaco-editor"); 54 | // getWorkerProcess: () => Promise; 55 | tsvfs: typeof import("./typescript-vfs"); 56 | getEmitResult: () => Promise; 57 | getRunnableJS: () => Promise; 58 | getDTSForCode: () => Promise; 59 | getDomNode: () => HTMLElement; 60 | getModel: () => import("monaco-editor").editor.ITextModel; 61 | getText: () => string; 62 | setText: (text: string) => void; 63 | getAST: () => Promise; 64 | ts: typeof import("typescript"); 65 | createTSProgram: () => Promise; 66 | compilerDefaults: import("monaco-editor").languages.typescript.CompilerOptions; 67 | getCompilerOptions: () => import("monaco-editor").languages.typescript.CompilerOptions; 68 | setCompilerSettings: (opts: import("monaco-editor").languages.typescript.CompilerOptions) => void; 69 | updateCompilerSetting: (key: string | number, value: any) => void; 70 | updateCompilerSettings: (opts: import("monaco-editor").languages.typescript.CompilerOptions) => void; 71 | setDidUpdateCompilerSettings: (func: (opts: import("monaco-editor").languages.typescript.CompilerOptions) => void) => void; 72 | // lzstring: typeof import("typescriptlang-org/static/js/sandbox/vendor/lzstring.min"); 73 | getURLQueryWithCompilerOptions: (sandbox: any, paramOverrides?: any) => string; 74 | getTwoSlashComplierOptions: (code: string) => any; 75 | languageServiceDefaults: import("monaco-editor").languages.typescript.LanguageServiceDefaults; 76 | } 77 | 78 | export declare const setupPlayground: (sandbox: Sandbox, monaco: typeof import("monaco-editor"), config: PlaygroundConfig, i: (key: string) => string) => { 79 | exporter: { 80 | openProjectInStackBlitz: () => void; 81 | openProjectInCodeSandbox: () => void; 82 | reportIssue: () => Promise; 83 | copyAsMarkdownIssue: () => Promise; 84 | copyForChat: () => void; 85 | copyForChatWithPreview: () => void; 86 | openInTSAST: () => void; 87 | }; 88 | // ui: import("./createUI").UI; 89 | registerPlugin: (plugin: PlaygroundPlugin) => void; 90 | }; 91 | export declare type Playground = ReturnType; 92 | -------------------------------------------------------------------------------- /src/plugin/vendor/sandbox.d.ts: -------------------------------------------------------------------------------- 1 | import { TypeScriptWorker } from "./tsWorker";// import { TypeScriptWorker } from './tsWorker'; 2 | // import lzstring from './vendor/lzstring.min'; 3 | 4 | import * as tsvfs from './typescript-vfs'; 5 | declare type CompilerOptions = import('monaco-editor').languages.typescript.CompilerOptions; 6 | /** 7 | * These are settings for the playground which are the equivalent to props in React 8 | * any changes to it should require a new setup of the playground 9 | */ 10 | export declare type PlaygroundConfig = { 11 | /** The default source code for the playground */ 12 | text: string; 13 | /** Should it run the ts or js IDE services */ 14 | useJavaScript: boolean; 15 | /** Compiler options which are automatically just forwarded on */ 16 | compilerOptions: CompilerOptions; 17 | /** Optional monaco settings overrides */ 18 | monacoSettings?: import('monaco-editor').editor.IEditorOptions; 19 | /** Acquire types via type acquisition */ 20 | acquireTypes: boolean; 21 | /** Support twoslash compiler options */ 22 | supportTwoslashCompilerOptions: boolean; 23 | /** Get the text via query params and local storage, useful when the editor is the main experience */ 24 | suppressAutomaticallyGettingDefaultText?: true; 25 | /** Suppress setting compiler options from the compiler flags from query params */ 26 | suppressAutomaticallyGettingCompilerFlags?: true; 27 | /** Logging system */ 28 | logger: { 29 | log: (...args: any[]) => void; 30 | error: (...args: any[]) => void; 31 | }; 32 | } & ({ 33 | domID: string; 34 | } | { 35 | elementToAppend: HTMLElement; 36 | }); 37 | /** The default settings which we apply a partial over */ 38 | export declare function defaultPlaygroundSettings(): { 39 | /** The default source code for the playground */ 40 | text: string; 41 | /** Should it run the ts or js IDE services */ 42 | useJavaScript: boolean; 43 | /** Compiler options which are automatically just forwarded on */ 44 | compilerOptions: import("monaco-editor").languages.typescript.CompilerOptions; 45 | /** Optional monaco settings overrides */ 46 | monacoSettings?: import("monaco-editor").editor.IEditorOptions | undefined; 47 | /** Acquire types via type acquisition */ 48 | acquireTypes: boolean; 49 | /** Support twoslash compiler options */ 50 | supportTwoslashCompilerOptions: boolean; 51 | /** Get the text via query params and local storage, useful when the editor is the main experience */ 52 | suppressAutomaticallyGettingDefaultText?: true | undefined; 53 | /** Suppress setting compiler options from the compiler flags from query params */ 54 | suppressAutomaticallyGettingCompilerFlags?: true | undefined; 55 | /** Logging system */ 56 | logger: { 57 | log: (...args: any[]) => void; 58 | error: (...args: any[]) => void; 59 | }; 60 | } & { 61 | domID: string; 62 | }; 63 | /** Creates a sandbox editor, and returns a set of useful functions and the editor */ 64 | export declare const createTypeScriptSandbox: (partialConfig: Partial<{ 65 | /** The default source code for the playground */ 66 | text: string; 67 | /** Should it run the ts or js IDE services */ 68 | useJavaScript: boolean; 69 | /** Compiler options which are automatically just forwarded on */ 70 | compilerOptions: import("monaco-editor").languages.typescript.CompilerOptions; 71 | /** Optional monaco settings overrides */ 72 | monacoSettings?: import("monaco-editor").editor.IEditorOptions | undefined; 73 | /** Acquire types via type acquisition */ 74 | acquireTypes: boolean; 75 | /** Support twoslash compiler options */ 76 | supportTwoslashCompilerOptions: boolean; 77 | /** Get the text via query params and local storage, useful when the editor is the main experience */ 78 | suppressAutomaticallyGettingDefaultText?: true | undefined; 79 | /** Suppress setting compiler options from the compiler flags from query params */ 80 | suppressAutomaticallyGettingCompilerFlags?: true | undefined; 81 | /** Logging system */ 82 | logger: { 83 | log: (...args: any[]) => void; 84 | error: (...args: any[]) => void; 85 | }; 86 | } & { 87 | domID: string; 88 | }> | Partial<{ 89 | /** The default source code for the playground */ 90 | text: string; 91 | /** Should it run the ts or js IDE services */ 92 | useJavaScript: boolean; 93 | /** Compiler options which are automatically just forwarded on */ 94 | compilerOptions: import("monaco-editor").languages.typescript.CompilerOptions; 95 | /** Optional monaco settings overrides */ 96 | monacoSettings?: import("monaco-editor").editor.IEditorOptions | undefined; 97 | /** Acquire types via type acquisition */ 98 | acquireTypes: boolean; 99 | /** Support twoslash compiler options */ 100 | supportTwoslashCompilerOptions: boolean; 101 | /** Get the text via query params and local storage, useful when the editor is the main experience */ 102 | suppressAutomaticallyGettingDefaultText?: true | undefined; 103 | /** Suppress setting compiler options from the compiler flags from query params */ 104 | suppressAutomaticallyGettingCompilerFlags?: true | undefined; 105 | /** Logging system */ 106 | logger: { 107 | log: (...args: any[]) => void; 108 | error: (...args: any[]) => void; 109 | }; 110 | } & { 111 | elementToAppend: HTMLElement; 112 | }>, monaco: typeof import("monaco-editor"), ts: typeof import("typescript")) => { 113 | /** The same config you passed in */ 114 | config: { 115 | text: string; 116 | useJavaScript: boolean; 117 | compilerOptions: import("monaco-editor").languages.typescript.CompilerOptions; 118 | monacoSettings?: import("monaco-editor").editor.IEditorOptions | undefined; 119 | acquireTypes: boolean; 120 | supportTwoslashCompilerOptions: boolean; 121 | suppressAutomaticallyGettingDefaultText?: true | undefined; 122 | suppressAutomaticallyGettingCompilerFlags?: true | undefined; 123 | logger: { 124 | log: (...args: any[]) => void; 125 | error: (...args: any[]) => void; 126 | }; 127 | domID: string; 128 | }; 129 | /** A list of TypeScript versions you can use with the TypeScript sandbox */ 130 | supportedVersions: readonly ["2.4.1", "2.7.2", "2.8.1", "3.0.1", "3.1.6", "3.3.3", "3.5.1", "3.6.3", "3.7.5", "3.8.2"]; 131 | /** The monaco editor instance */ 132 | editor: import("monaco-editor").editor.IStandaloneCodeEditor; 133 | /** Either "typescript" or "javascript" depending on your config */ 134 | language: string; 135 | /** The outer monaco module, the result of require("monaco-editor") */ 136 | monaco: typeof import("monaco-editor"); 137 | /** Gets a monaco-typescript worker, this will give you access to a language server. Note: prefer this for language server work because it happens on a webworker . */ 138 | getWorkerProcess: () => Promise; 139 | /** A copy of require("typescript-vfs") this can be used to quickly set up an in-memory compiler runs for ASTs, or to get complex language server results (anything above has to be serialized when passed)*/ 140 | tsvfs: typeof tsvfs; 141 | /** Get all the different emitted files after TypeScript is run */ 142 | getEmitResult: () => Promise; 143 | /** Gets just the JavaScript for your sandbox, will transpile if in TS only */ 144 | getRunnableJS: () => Promise; 145 | /** Gets the DTS output of the main code in the editor */ 146 | getDTSForCode: () => Promise; 147 | /** The monaco-editor dom node, used for showing/hiding the editor */ 148 | getDomNode: () => HTMLElement; 149 | /** The model is an object which monaco uses to keep track of text in the editor. Use this to directly modify the text in the editor */ 150 | getModel: () => import("monaco-editor").editor.ITextModel; 151 | /** Gets the text of the main model, which is the text in the editor */ 152 | getText: () => string; 153 | /** Shortcut for setting the model's text content which would update the editor */ 154 | setText: (text: string) => void; 155 | /** WIP: Gets the AST of the current text */ 156 | getAST: () => Promise; 157 | /** The module you get from require("typescript") */ 158 | ts: typeof import("typescript"); 159 | /** Create a new Program, a TypeScript data model which represents the entire project. 160 | * 161 | * The first time this is called it has to download all the DTS files which is needed for an exact compiler run. Which 162 | * at max is about 1.5MB - after that subsequent downloads of dts lib files come from localStorage. 163 | * 164 | * You probably want 165 | */ 166 | createTSProgram: () => Promise; 167 | /** The Sandbox's default compiler options */ 168 | compilerDefaults: import("monaco-editor").languages.typescript.CompilerOptions; 169 | /** The Sandbox's current compiler options */ 170 | getCompilerOptions: () => import("monaco-editor").languages.typescript.CompilerOptions; 171 | /** Replace the Sandbox's compiler options */ 172 | setCompilerSettings: (opts: import("monaco-editor").languages.typescript.CompilerOptions) => void; 173 | /** Overwrite the Sandbox's compiler options */ 174 | updateCompilerSetting: (key: string | number, value: any) => void; 175 | /** Update a single compiler option in the SAndbox */ 176 | updateCompilerSettings: (opts: import("monaco-editor").languages.typescript.CompilerOptions) => void; 177 | /** A way to get callbacks when compiler settings have changed */ 178 | setDidUpdateCompilerSettings: (func: (opts: import("monaco-editor").languages.typescript.CompilerOptions) => void) => void; 179 | /** A copy of lzstring, which is used to archive/unarchive code */ 180 | // lzstring: typeof lzstring; 181 | /** Returns compiler options found in the params of the current page */ 182 | getURLQueryWithCompilerOptions: (sandbox: any, paramOverrides?: any) => string; 183 | /** Returns compiler options in the source code using twoslash notation */ 184 | getTwoSlashComplierOptions: (code: string) => any; 185 | /** Gets to the current monaco-language, this is how you talk to the background webworkers */ 186 | languageServiceDefaults: import("monaco-editor").languages.typescript.LanguageServiceDefaults; 187 | }; 188 | export declare type Sandbox = ReturnType; 189 | export {}; 190 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # typescript-playground-plugin-react 2 | 3 | Easily create TypeScript [Playground Plugins](https://www.typescriptlang.org/v2/dev/playground-plugins/) with [React](https://reactjs.org/). 4 | 5 | > 🚧 This project is experimental. If you have any ideas on how to improve this library, any contributions are welcomed. Also, working on TypeScript Playground plugins currently only can work in Chromium based browsers. 6 | 7 | Prefer Svelte? Check out [https://github.com/gojutin/typescript-playground-plugin-svelte](https://github.com/gojutin/typescript-playground-plugin-svelte). 8 | 9 | ## Table Of Contents 10 | 11 | 1. [Features](#features) 12 | 2. [About](#about) 13 | 3. [Getting Started](#getting-started) 14 | 4. [usePlugin Hook](#usePlugin-hook) 15 | 5. [Styling Your Plugin](#styling-your-plugin) 16 | 6. [More about TypeScript Playground Plugins](#more-about-typescript-playground-plugins) 17 | 7. [Todos](#todos) 18 | 19 | ## Features 20 | 21 | ✅ Write your TypeScript Playground plugin in React and TypeScript. 22 | 23 | ✅ Interact with the Playground using a strongly-typed React hook. 24 | 25 | ✅ Create styles with stylesheets or CSS-in-JS with Goober. 26 | 27 | ✅ Linting with ESLint 28 | 29 | ## About 30 | 31 | The TypeScript Playground V3 beta comes packed with lots of new features, including the ability to create plugins. Per the TypeScript docs: 32 | 33 | > The new TypeScript Playground allows people to hook into the Playground and extend it in ways in which the TypeScript team don't expect. 34 | > 35 | > The sidebar of the Playground uses the same plugin infrastructure as external plugins, so you have the same level of access as the playground to build interesting projects. 36 | > 37 | > Playground plugins have no fancy frameworks, you're free to inject them at runtime and use them if you need to - but the current plugins are built with the DOM APIs and TypeScript. 38 | 39 | This package allows you to use React as a replacement (or addition to) the DOM APIs to create a rich, interactive UI for your plugin. 40 | 41 | #### How does it work? 42 | 43 | The TypeScript Playground Plugin API provides a factory function with lifecycle methods that are used to interact with the playground. This library works by mounting a React app inside of the `didMount` method that the API exposes. The `modelChanged` and `modelChangedDebounce` API methods are called any time the code in the editor changes. Custom events are used to broadcast the changes to a context that wraps the app component. 44 | 45 | ## Getting Started 46 | 47 | #### Step 1. Get the Code 48 | 49 | > Option 1: Clone the repo 50 | 51 | ```sh 52 | git clone git@github.com:gojutin/typescript-playground-plugin-react.git 53 | ``` 54 | 55 | > Option 2: Click the "Use this template" button in GitHub [(Link)](https://github.com/gojutin/typescript-playground-plugin-react) 56 | 57 | #### Step 2. Install dependencies 58 | 59 | ```sh 60 | cd typescript-playground-plugin-react && yarn 61 | ``` 62 | 63 | #### Step 3. Start the development server 64 | 65 | ```sh 66 | yarn start 67 | ``` 68 | 69 | This will start a development server in watch mode, serve the `dist` directory at `localhost:5000`, and automatically open the TypeScript Playground in Chrome. As you edit any files in the `src` directory, the app will recompile and update `dist/index.js`, which is the file that is served to the TypeScript Playground. 70 | 71 | > _Note: This does not reload the browser when your files change. In order to see your changes, the browser will need to be manually reloaded each time you make changes to the plugin._ 72 | 73 | #### Step 4. Configure and use your plugin 74 | 75 | Visit [https://www.typescriptlang.org/play](https://www.typescriptlang.org/play). 76 | 77 | Select the **Options** tab and tick the box for **Connect to localhost:5000/index.js**. 78 | 79 | 80 | 81 | Now, **refresh the browser**. When the playground reloads, a new tab with your plugin should appear! 🎉 82 | 83 | 84 | 85 | You can make customizations to your plugin by modifying the `customPlugin` object in `src/index.tsx`. For instance, you can change the `displayName` property to change the tab label text for your plugin. See the `PlaygroundPlugin` interface in `src/plugin/vendor/playground.d.ts` for all of the available options. 86 | 87 | ## `usePlugin` Hook 88 | 89 | This hooks provides all of the method and properties provided by the Plugin API. It accepts a optional config object and returns an object with these properties: 90 | 91 | ### **code** 92 | 93 | ```typescript 94 | string 95 | ``` 96 | 97 | The current code in the Monaco editor saved as React state. This value updates on change to the Monaco editor with optional debouncing. Uses `sandbox.getText()` 98 | 99 | ### **setCode** 100 | 101 | ```typescript 102 | (code: string, options: {format: boolean}) => void 103 | ``` 104 | 105 | Set the code in the Monaco editor with optional formatting. Uses `sandbox.setText()`. 106 | 107 | ### **formatCode** 108 | 109 | ```typescript 110 | () => void 111 | ``` 112 | 113 | Format the code in the Monaco editor. Alias for `sandbox.editor.getAction("editor.action.formatDocument").run()`. 114 | 115 | 116 | ### **markers** 117 | 118 | ```typescript 119 | (IMarker & {key: string})[] 120 | ``` 121 | 122 | Alias for `sandbox.monaco.editor.getModelMarkers({})` with added unique `key` property. Kept in sync via `sandbox.editor.onDidChangeModelDecorations`. 123 | 124 | Here is the [type definition](https://github.com/Microsoft/monaco-editor/blob/master/monaco.d.ts#L875) for `IMarker`: 125 | 126 | ```typescript 127 | interface IMarker { 128 | owner: string; 129 | resource: Uri; 130 | severity: MarkerSeverity; 131 | code?: 132 | | string 133 | | { 134 | value: string; 135 | link: Uri; 136 | }; 137 | message: string; 138 | source?: string; 139 | startLineNumber: number; 140 | startColumn: number; 141 | endLineNumber: number; 142 | endColumn: number; 143 | relatedInformation?: IRelatedInformation[]; 144 | tags?: MarkerTag[]; 145 | } 146 | ``` 147 | 148 | ### **setDebounce** 149 | 150 | ```typescript 151 | (debounce: boolean) => void 152 | ``` 153 | Optionally debounce the `modelChange` event from the Plugin API. Per the Plugin docs, this is run on a delay and may not fire on every keystroke. The `code` property will be updated accordingly. 154 | 155 | ### **sandbox** 156 | 157 | ```typescript 158 | object 159 | ``` 160 | 161 | A DOM library for interacting with TypeScript and JavaScript code, which powers the heart of the TypeScript playground. This object provides several properties and methods to interact with the playground. See all of the available types in `src/plugin/vendor/sandbox.d.ts` and read more about the sandbox at [http://www.typescriptlang.org/v2/dev/sandbox/](http://www.typescriptlang.org/v2/dev/sandbox/). 162 | 163 | ### **model** 164 | 165 | ```typescript 166 | object 167 | ``` 168 | 169 | The model is an object which Monaco uses to keep track of text in the editor. You can find the full type definition at `node_modules/monaco-editor/esm/vs/editor/editor.api.d.ts`. 170 | 171 | ### **container** 172 | 173 | ```typescript 174 | {ref: HTMLDivElement, width: number, height: number} 175 | ``` 176 | 177 | This is an object that contains the container `div` element that wraps the entire sidebar as well as it's dimensions in reactive React state. You can wrap the dimension values in a `useEffect` to be notified of any changes to the size of the container. The React app is mounted to this element. Any style changes to this element will affect the entire sidebar. 178 | 179 | ### **showModal** 180 | 181 | ```typescript 182 | (code: string, subtitle?: string, links?: string[]) => void 183 | ``` 184 | From `window.playground.ui` - This function accepts three arguments (code, subtitle, and links) and opens a model with the values you provide. 185 | 186 | ### **flashInfo** 187 | 188 | ```typescript 189 | (message: string) => void 190 | ``` 191 | From `window.playground.ui` - This function accepts one argument (message) and and flashes a quick message in the center of the screen. 192 | 193 | ### **utils** 194 | 195 | ```typescript 196 | { 197 | el: (str: string, el: string, container: Element) => void;, 198 | requireURL: (path: string) => string;, 199 | createASTTree: (node: Node) => HTMLDivElement; 200 | } 201 | ``` 202 | An object that contains three additional config options and functionality. `el`, `requireURL`, and `createASTTree`. See `src/plugin/vendor/pluginUtils.d.ts` for more information. 203 | 204 |
205 | 206 | ### Example `usePlugin` Usage 207 | 208 | ```tsx 209 | const { 210 | code, 211 | setCode, 212 | formatCode, 213 | markers, 214 | setDebounce, 215 | sandbox, 216 | model, 217 | container, 218 | flashInfo, 219 | showModal, 220 | utils 221 | } = usePlugin(); 222 | 223 | // Here are some examples of things you can do: 224 | 225 | setDebounce(true); 226 | 227 | // Set the code in the Monaco editor 228 | useEffect(() => { 229 | const defaultCode = `const greet = (): string => "Hi👋";`; 230 | setCode(defaultCode, { format: true }); 231 | }, []); 232 | 233 | // Listen for changes to the code in the Monaco editor 234 | useEffect(() => { 235 | flashInfo("The code was updated."); 236 | showModal(code, "Here is your code"); 237 | }, [code]); 238 | 239 | const renderMarkers = markers.map(marker => { 240 | return
{marker.message}
; 241 | }); 242 | 243 | // See App.tsx for additional usage examples 244 | ``` 245 | 246 | ## Styling your plugin 247 | 248 | This library works with normal CSS stylesheets out of the box by inlining them using PostCSS. It also includes [Goober](https://github.com/cristianbote/goober), a super light-weight (<1KB) CSS-in-JS library. There are examples of both styling approaches in `src/App.tsx`. Any other styling solutions may require additional rollup configuration. 249 | 250 | The `container` provided by the `usePlayground` hook can also be styled. Be careful as this will affect all tabs in the sidebar. 251 | 252 | ## More about TypeScript Playground Plugins 253 | 254 | [Official Playground Plugin Documentation](https://www.typescriptlang.org/v2/dev/playground-plugins/) 255 | 256 | You can create a plugin (without React) from the official plugin template: 257 | 258 | ```sh 259 | npm init typescript-playground-plugin playground-my-plugin 260 | ``` 261 | 262 | For convenience, this repo contains the `CONTRIBUTING.md` file included in the official plugin template. This document contains useful information about how to work with the plugins. 263 | 264 | The `src/plugin/vendor` directory contains all of the TypeScript type definitions for the TypeScript Playground Plugin API. This is the best place to find the various config options, properties, and methods that are available. 265 | 266 | ### Need inspiration? 267 | 268 | [Orta](https://github.com/orta) created a really cool plugin that lets you create presentations in the TypeScript playground using Reveal.js. You can check it out here: 269 | 270 | [https://github.com/orta/playground-slides](https://github.com/orta/playground-slides) 271 | 272 | He also offered these plugin ideas in [this](https://github.com/microsoft/TypeScript-Website/issues/221) issue. 273 | 274 | - An LSP-ish Playground where you can make see the response to specific calls 275 | - An English explainer which explains a complex TS type 276 | - Convert TS dts -> Flow interfaces(flowgen) 277 | - Run tutorials in the playground against live code as a learning tool 278 | - AST Viewer 279 | - ts-query runner 280 | - codemod runner 281 | - Highlight TS vs JS (or type vs value) parts of some code code 282 | - Show all used types in a file 283 | - Show dts files in the current workspace 284 | - Edit an ambient dts file 285 | 286 | ## TODOS 287 | 288 | In no particular order. 289 | 290 | - [ ] Add ability to import SVGs with `import` syntax without TypeScript errors. 291 | - [ ] Reload the TypeScript Playground browser tab on plugin changes if possible. 292 | - [ ] Add configuration for other styling solutions (Styled Component, Emotion, Styled JSX) 293 | - [ ] Add additional examples, including AST Tree. 294 | - [ ] Add ability to destructure named imports `import React, { useEffect } from "react";` if possible. 295 | - [ ] Incorporate Jest and React Testing Library. 296 | --------------------------------------------------------------------------------