├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── package.json ├── packages ├── functions │ ├── .gitignore │ ├── .prettierrc.json │ ├── api │ │ ├── fmt.ts │ │ └── run.ts │ ├── controllers │ │ └── denoCommandController.ts │ ├── deps.ts │ ├── interface.ts │ ├── package.json │ ├── services │ │ └── denoService.ts │ ├── utils │ │ └── utils.ts │ └── vercel.json └── ui │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc │ ├── .stylelintrc.json │ ├── additional.d.ts │ ├── assets │ ├── deno-logo.svg │ └── vercel.svg │ ├── components │ ├── Footer.tsx │ ├── Header.tsx │ ├── Loading.tsx │ └── Toolbar.tsx │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── pages │ ├── _app.tsx │ ├── _document.tsx │ └── index.tsx │ ├── public │ ├── deno-og-image.png │ ├── examples │ │ ├── default.ts │ │ ├── fetch-data.ts │ │ ├── hello-world.ts │ │ ├── remote-import.ts │ │ └── subprocesses.ts │ ├── favicon.ico │ ├── favicon.svg │ └── fonts │ │ ├── Cascadia │ │ └── CascadiaCode.woff2 │ │ └── Inter │ │ ├── Inter-italic.var.woff2 │ │ └── Inter-roman.var.woff2 │ ├── scripts │ └── server.js │ ├── services │ ├── fmt.ts │ ├── formatter.ts │ ├── markdown.ts │ ├── request.ts │ └── run.ts │ ├── styles │ ├── Footer.module.scss │ ├── Header.module.scss │ ├── Home.module.scss │ ├── Toolbar.module.scss │ ├── abstracts │ │ ├── _fonts.scss │ │ ├── _typography.scss │ │ └── _variables.scss │ ├── globals.scss │ └── theme.ts │ ├── tsconfig.json │ ├── vercel.json │ └── yarn.lock └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # debug 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # vercel 12 | .vercel 13 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn run precommit 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": true, 4 | "source.fixAll.stylelint": true 5 | }, 6 | "editor.formatOnSave": true, 7 | "eslint.alwaysShowStatus": true 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Peter Bartha 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deno Playground — An unofficial land for exploring 2 | 3 | The playground lets you write TypeScript (or JavaScript) online in a safe and shareable way. 4 | 5 | 6 |


image

7 | 8 | ## What is Deno? Why should you use that? 9 | 10 | Deno is a simple, modern, and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust. 11 | 12 | - Secure by default. No file, network, or environment access, unless explicitly enabled. 13 | - Supports TypeScript out of the box. 14 | - Ships only a single executable file. 15 | - Has built-in utilities like a dependency inspector (`deno info`) and a code formatter (`deno fmt`). 16 | - Has a set of reviewed (audited) standard modules that are guaranteed to work with Deno: [deno.land/std](https://deno.land/std) 17 | 18 | Learn more on **[Deno's public site](https://deno.land/)**. 19 | 20 | 21 | ## Project structure 22 | 23 | There are two packages inside the project: 24 | - Serverless Deno back-end (built on [vercel-deno](https://github.com/TooTallNate/vercel-deno)) 25 | - [Next.js](https://nextjs.org/) front-end 26 | 27 | 28 | ``` 29 | . 30 | 31 | └─ 📂 packages 32 |   ├─ 📦 functions 33 |   └─ 📦 ui 34 | ``` 35 | 36 | ## Build 37 | 38 | You can get this site up and running on your local dev environment with these five steps: 39 | 40 | 1. **Install the Vercel CLI** 41 | 42 | ```shell 43 | npm i -g vercel 44 | ``` 45 | 46 | 2. **Clone this repository from GitHub** 47 | 48 | Clone [deno-playground](https://github.com/peterbartha/deno-playground) project from GitHub with the following command: 49 | 50 | ```shell 51 | git clone git@github.com:peterbartha/deno-playground.git 52 | ``` 53 | 54 | 3. **Install yarn dependencies** 55 | 56 | Next, move into `deno-playground` directory and start it up: 57 | 58 | ```shell 59 | cd deno-playground/ 60 | 61 | yarn 62 | ``` 63 | 64 | 4. **Start dev server** 65 | 66 | ```shell 67 | # start Vercel in dev mode 68 | vercel dev 69 | 70 | # Set up and develop “.../deno-playground”? [Y/n] 71 | Y 72 | 73 | # Which scope should contain your project? 74 | 75 | 76 | # Found project “/deno-playground”. Link to it? [Y/n] 77 | N 78 | 79 | # Link to different existing project? [Y/n] 80 | Y 81 | 82 | # What’s the name of your existing project? 83 | deno-playground-api 84 | 85 | # 🔗 Linked to peterbartha/deno-playground-api (created .vercel) 86 | 87 | # Ready! Available at http://localhost:3000 88 | ``` 89 | 90 | 5. **Setup HTTPS on local development server** 91 | 92 | [mkcert](https://github.com/FiloSottile/mkcert) is the recommended option here which is simpler than the alternatives. 93 | mkcert will create a CA and SSL Certificate for the server for you. 94 | 95 | - Install [mkcert](https://github.com/FiloSottile/mkcert#installation). 96 | - `mkcert -install` will generate a CA first. 97 | - Generate a certificate for localhost with `mkcert localhost`. 98 | - This will generate a certificate and a key named as `*.pem` and `*-key.pem` respectively. 99 | - **Copy generated `*.pem` files to the `packages/ui/scripts/` folder.** 100 | 101 | 6. **Start the site in `dev` mode** 102 | 103 | ```shell 104 | # use a different shell 105 | cd packages/ui 106 | 107 | yarn run dev 108 | ``` 109 | 110 | ## Deploy on Vercel 111 | 112 | The easiest way to deploy this project is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 113 | 114 | Unfortunately, to connect multiple Vercel projects with the same Git repository, you need to create a new project for each of the packages within this Git repository. 115 | 116 | [Learn more](https://vercel.com/blog/monorepos#creating-projects-from-a-monorepo) 117 | 118 | ## Credits 119 | 120 | This project uses the following open-source packages: 121 | 122 | - [Vercel](https://github.com/vercel/vercel) 123 | - [Deno](https://github.com/denoland/deno) 124 | - [Next.js](https://github.com/vercel/next.js) 125 | - [vercel-deno](https://github.com/TooTallNate/vercel-deno) 126 | - [mkcert](https://github.com/FiloSottile/mkcert) 127 | 128 | 129 | ## Configuration 130 | 131 | There are a few [build environment 132 | variables](https://vercel.com/docs/configuration#project/build-env) that you 133 | may configure for your serverless functions: 134 | 135 | | Name | Description | Default | 136 | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | 137 | | `SCRIPT_EXECUTION_TIMEOUT` | Time-based execution limit for the build-time in ms. | 10000 | 138 | | `DEBUG` | Enables additional logging during build-time. | `false` | 139 | | `DENO_TSCONFIG` | Passes the [`--config`](https://deno.land/manual/getting_started/command_line_interface#cache-and-compilation-flags) flag to specify a `tsconfig.json` file that Deno will use. | None | 140 | | `DENO_UNSTABLE` | Passes the [`--unstable`](https://deno.land/manual/getting_started/command_line_interface#cache-and-compilation-flags) flag to `deno cache` (at build-time) and `deno run` (at runtime). | `false` | 141 | | `DENO_VERSION` | Version of `deno` that the serverless function will use. | `1.9.2` | 142 | 143 | ## Contributing 144 | 145 | If you have any ideas, just [open an issue](https://github.com/peterbartha/deno-playground/issues) and tell me what you think. 146 | 147 | If you'd like to contribute, please fork the repository and make changes as 148 | you'd like. Pull requests are warmly welcome. 149 | 150 | 151 | ## License 152 | 153 | This project is licensed under [MIT](LICENSE) license. 154 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deno-playground", 3 | "version": "0.8.2", 4 | "description": "The Deno Playground lets you write Deno code online in a safe and sharable way.", 5 | "repository": "git@github.com:peterbartha/deno-playground.git", 6 | "author": "Peter Bartha ", 7 | "license": "MIT", 8 | "private": true, 9 | "workspaces": [ 10 | "packages/*" 11 | ], 12 | "scripts": { 13 | "prepare": "husky install", 14 | "precommit:functions": "cd packages/functions && npm run precommit", 15 | "precommit:ui": "cd packages/ui && npm run precommit", 16 | "precommit": "yarn run precommit:functions && yarn run precommit:ui" 17 | }, 18 | "devDependencies": { 19 | "husky": "^6.0.0", 20 | "lint-staged": "^10.5.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/functions/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # debug 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # git hooks 12 | .husky 13 | 14 | # vercel 15 | .vercel 16 | -------------------------------------------------------------------------------- /packages/functions/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": false, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /packages/functions/api/fmt.ts: -------------------------------------------------------------------------------- 1 | import { handleDenoCommand } from "../controllers/denoCommandController.ts"; 2 | import { ServerRequest } from "../deps.ts"; 3 | 4 | export default function fmt(request: ServerRequest) { 5 | return handleDenoCommand("fmt", request); 6 | } 7 | -------------------------------------------------------------------------------- /packages/functions/api/run.ts: -------------------------------------------------------------------------------- 1 | import { handleDenoCommand } from "../controllers/denoCommandController.ts"; 2 | import { ServerRequest } from "../deps.ts"; 3 | 4 | export default function run(request: ServerRequest) { 5 | return handleDenoCommand("run", request); 6 | } 7 | -------------------------------------------------------------------------------- /packages/functions/controllers/denoCommandController.ts: -------------------------------------------------------------------------------- 1 | import { ServerRequest } from "../deps.ts"; 2 | import { SupportedDenoSubCommand } from "../interface.ts"; 3 | import { executeCommand } from "../services/denoService.ts"; 4 | import { decodeRequestBody } from "../utils/utils.ts"; 5 | 6 | export async function handleDenoCommand( 7 | commandType: SupportedDenoSubCommand, 8 | request: ServerRequest, 9 | ) { 10 | const { method, body: encodedBody, url } = request; 11 | 12 | if (method === "OPTIONS") { 13 | return request.respond({ 14 | status: 200, 15 | }); 16 | } 17 | 18 | if (method !== "POST") { 19 | return request.respond({ 20 | status: 405, 21 | body: "Method not allowed.", 22 | }); 23 | } 24 | 25 | try { 26 | const body = await decodeRequestBody(encodedBody); 27 | const { isSuccess, isKilled, out, error } = await executeCommand( 28 | commandType, 29 | body, 30 | url, 31 | ); 32 | 33 | if (!isSuccess) { 34 | if (isKilled) { 35 | return request.respond({ 36 | status: 504, 37 | body: "Executing the given Deno command is taking too long to load.", 38 | }); 39 | } 40 | return request.respond({ 41 | status: 500, 42 | body: error, 43 | }); 44 | } 45 | return request.respond({ 46 | status: 200, 47 | body: out, 48 | }); 49 | } catch (e) { 50 | if (e instanceof SyntaxError) { 51 | return { 52 | status: 400, 53 | body: "Cannot process request body.", 54 | }; 55 | } 56 | console.error(e); 57 | return { 58 | status: 500, 59 | body: "Internal server error.", 60 | }; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/functions/deps.ts: -------------------------------------------------------------------------------- 1 | export { ServerRequest } from "https://deno.land/std@0.98.0/http/server.ts"; 2 | export { readAll, writeAll } from "https://deno.land/std@0.98.0/io/util.ts"; 3 | -------------------------------------------------------------------------------- /packages/functions/interface.ts: -------------------------------------------------------------------------------- 1 | export type SupportedDenoSubCommand = "run" | "fmt" | "lint"; 2 | -------------------------------------------------------------------------------- /packages/functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deno-playground-api", 3 | "version": "0.8.2", 4 | "description": "The back-end API of Deno Playground.", 5 | "repository": "git@github.com:peterbartha/deno-playground.git", 6 | "author": "Peter Bartha ", 7 | "license": "MIT", 8 | "private": true, 9 | "scripts": { 10 | "start": "vercel dev", 11 | "deploy": "vercel deploy --prod", 12 | "precommit": "lint-staged" 13 | }, 14 | "dependencies": { 15 | "vercel-deno": "0.7.13" 16 | }, 17 | "devDependencies": { 18 | "lint-staged": "^11.0.1" 19 | }, 20 | "lint-staged": { 21 | "*.ts": [ 22 | "deno fmt", 23 | "deno --unstable lint" 24 | ] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/functions/services/denoService.ts: -------------------------------------------------------------------------------- 1 | import { writeAll } from "../deps.ts"; 2 | import { SupportedDenoSubCommand } from "../interface.ts"; 3 | 4 | // Vercel timeout is 10 seconds for hobby tier: 5 | // https://vercel.com/docs/platform/limits 6 | const PROCESS_TIMEOUT = 10000; 7 | 8 | export function executeCommand( 9 | commandType: SupportedDenoSubCommand, 10 | body: string, 11 | url: string, 12 | ): Promise<{ 13 | isSuccess: boolean; 14 | isKilled: boolean; 15 | out: string; 16 | error: string; 17 | }> { 18 | const command = new Set(["deno", commandType]); 19 | 20 | const [_, search] = url.split("?"); 21 | const queryParams = new URLSearchParams(search || ""); 22 | if (queryParams.has("unstable")) { 23 | command.add("--unstable"); 24 | } 25 | 26 | if (commandType === "run") { 27 | command.add("--allow-all"); 28 | } 29 | command.add("-"); 30 | 31 | return execute(Array.from(command), body); 32 | } 33 | 34 | async function execute( 35 | cmd: string[], 36 | source: string, 37 | ): Promise<{ 38 | isSuccess: boolean; 39 | isKilled: boolean; 40 | out: string; 41 | error: string; 42 | }> { 43 | let isKilled = false; 44 | // https://deno.land/manual@main/examples/subprocess 45 | const deno = Deno.run({ 46 | cmd, 47 | env: { 48 | DENO_DIR: "/tmp/.deno", 49 | }, 50 | stdin: "piped", 51 | stdout: "piped", 52 | stderr: "piped", 53 | }); 54 | 55 | try { 56 | await writeAll(deno.stdin, new TextEncoder().encode(source)); 57 | deno.stdin.close(); 58 | 59 | const timer = setTimeout(() => { 60 | isKilled = true; 61 | deno.kill(Deno.Signal.SIGKILL); 62 | }, PROCESS_TIMEOUT); 63 | 64 | const [status, stdout, stderr] = await Promise.all([ 65 | deno.status(), 66 | deno.output(), 67 | deno.stderrOutput(), 68 | ]); 69 | 70 | clearTimeout(timer); 71 | 72 | const decoder = new TextDecoder(); 73 | return { 74 | isSuccess: status.success, 75 | isKilled, 76 | out: decoder.decode(stdout), 77 | error: decoder.decode(stderr), 78 | }; 79 | } finally { 80 | deno.close(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/functions/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { readAll } from "../deps.ts"; 2 | 3 | export async function decodeRequestBody(encodedBody: Deno.Reader) { 4 | const decoder = new TextDecoder(); 5 | const bodyContents = await readAll(encodedBody); 6 | return decoder.decode(bodyContents); 7 | } 8 | -------------------------------------------------------------------------------- /packages/functions/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "functions": { 3 | "api/**/*.ts": { "runtime": "vercel-deno@0.7.13" } 4 | }, 5 | "headers": [ 6 | { 7 | "source": "/api/(.*)", 8 | "headers": [ 9 | { "key": "Access-Control-Allow-Credentials", "value": "true" }, 10 | { "key": "Access-Control-Allow-Origin", "value": "*" }, 11 | { 12 | "key": "Access-Control-Allow-Methods", 13 | "value": "GET,OPTIONS,PATCH,DELETE,POST,PUT" 14 | }, 15 | { 16 | "key": "Access-Control-Allow-Headers", 17 | "value": "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/ui/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | package-lock.json 4 | public 5 | -------------------------------------------------------------------------------- /packages/ui/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es2020": true 6 | }, 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { 9 | "ecmaVersion": 2020, 10 | "sourceType": "module", 11 | "ecmaFeatures": { 12 | "jsx": true 13 | } 14 | }, 15 | "plugins": ["@typescript-eslint", "react", "prettier"], 16 | "extends": [ 17 | "airbnb", 18 | "airbnb/hooks", 19 | "plugin:@typescript-eslint/recommended", 20 | "plugin:react/recommended", 21 | "plugin:import/errors", 22 | "plugin:import/warnings", 23 | "plugin:import/typescript", 24 | "next", 25 | "prettier" 26 | ], 27 | "rules": { 28 | "react/jsx-filename-extension": [ 29 | 1, 30 | { 31 | "extensions": [".ts", ".tsx"] 32 | } 33 | ], 34 | "import/extensions": "off", 35 | "react/react-in-jsx-scope": "off", 36 | "react/prop-types": "off", 37 | "jsx-a11y/anchor-is-valid": [ 38 | "error", 39 | { 40 | "components": ["Link"], 41 | "specialLink": ["hrefLeft", "hrefRight"], 42 | "aspects": ["invalidHref", "preferButton"] 43 | } 44 | ], 45 | "react/jsx-props-no-spreading": [ 46 | "error", 47 | { 48 | "custom": "ignore" 49 | } 50 | ], 51 | "prettier/prettier": "error", 52 | "react/no-unescaped-entities": "off", 53 | "import/no-cycle": [ 54 | 0, 55 | { 56 | "ignoreExternal": true 57 | } 58 | ], 59 | "no-use-before-define": "off", 60 | "@typescript-eslint/no-use-before-define": [ 61 | "error", 62 | { 63 | "functions": false, 64 | "classes": false, 65 | "variables": true 66 | } 67 | ], 68 | "@typescript-eslint/no-unused-vars": [ 69 | "error", 70 | { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" } 71 | ] 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/ui/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # next.js 10 | /.next/ 11 | /out/ 12 | 13 | # production 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | 20 | # debug 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # local env files 26 | .env.local 27 | .env.development.local 28 | .env.test.local 29 | .env.production.local 30 | 31 | # vercel 32 | .vercel 33 | -------------------------------------------------------------------------------- /packages/ui/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | package-lock.json 4 | public 5 | -------------------------------------------------------------------------------- /packages/ui/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/ui/.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-primer", 3 | "ignoreFiles": ["**/*.tsx", "**/*.ts"], 4 | "rules": { 5 | "selector-max-type": 1 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/ui/additional.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'ansi-style-parser'; 2 | declare module 'react-split'; 3 | 4 | declare module '*.svg' { 5 | const ReactComponent: React.FC>; 6 | export default ReactComponent; 7 | } 8 | -------------------------------------------------------------------------------- /packages/ui/assets/deno-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui/assets/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../styles/Footer.module.scss'; 2 | import VercelLogo from '../assets/vercel.svg'; 3 | import DenoLogo from '../assets/deno-logo.svg'; 4 | 5 | const Footer = (): JSX.Element => ( 6 | 16 | ); 17 | 18 | export default Footer; 19 | -------------------------------------------------------------------------------- /packages/ui/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Dialog, 4 | DialogActions, 5 | DialogContent, 6 | DialogTitle, 7 | IconButton, 8 | } from '@material-ui/core'; 9 | import { GitHub, Help } from '@material-ui/icons'; 10 | import React from 'react'; 11 | import { Alert } from '@material-ui/lab'; 12 | import styles from '../styles/Header.module.scss'; 13 | import DenoLogo from '../assets/deno-logo.svg'; 14 | 15 | const Header = (): JSX.Element => { 16 | const [isDialogOpen, setDialogOpen] = React.useState(false); 17 | const openDialog = () => { 18 | setDialogOpen(true); 19 | }; 20 | 21 | const closeDialog = () => { 22 | setDialogOpen(false); 23 | }; 24 | 25 | return ( 26 |
27 | 66 | 67 | 74 | About the Playground 75 | 76 | 77 | 78 | 79 | This is an unofficial playground for Deno runtime. It was 80 | created to be the official playground one day, hopefully. 🤞 81 | 82 | 83 |

84 | 85 | The playground is an{' '} 86 | 91 | open source project 92 | 93 | . If you have any suggestions for features, issues with the 94 | implementation, or just want to read the code yourself, you are 95 | invited to participate! 96 | 97 |

98 |

99 | The Deno Playground is a web service that runs on Vercel. The 100 | service receives a TypeScript source code, compiles, runs the 101 | program with Deno inside a sandbox, and returns the output. 102 |

103 |

104 | This playground is modeled after the{' '} 105 | 110 | TypeScript 111 | 112 | ,{' '} 113 | 118 | Go 119 | {' '} 120 | and{' '} 121 | 126 | Rust 127 | {' '} 128 | playgrounds, and we owe an outstanding debt to every contributor 129 | to that project. 130 |

131 |

Abuse handling

132 |

133 | Any requests for content removal should be directed to the{' '} 134 | 139 | project's issues 140 | {' '} 141 | Please include the URL and the reason for the request. 142 |

143 |

Limitations

144 |

145 | Benchmarks will likely not be recommended since the program runs 146 | in a sandboxed environment with limited resources. 147 |

148 |

149 | Some limitations are enforced to prevent the playground from being 150 | used to attack other computers and ensure it is available for 151 | everyone to use: 152 |

153 |
    154 |
  • 155 | The playground can use most of the standard library, with some 156 | exceptions. A playground program's only communication to the 157 | outside world is by writing to standard output and standard 158 | error. 159 |
  • 160 |
  • 161 | There are also limits on execution time, CPU, and memory usage. 162 |
  • 163 |
164 |

License

165 |

166 | This project was created by Peter Bartha, and released under{' '} 167 | 172 | MIT license 173 | 174 | . 175 |

176 |

Credits

177 |
    178 |
  • 179 | The Deno Playground's logo: "Dino in the Rain", 180 | the current logo of Deno is released under{' '} 181 | 186 | MIT license 187 | 188 | . Designed by Kevin Qian based on Ryan Dahl's sketch. The 189 | original logo can be found{' '} 190 | 195 | here 196 | 197 | . 198 |
  • 199 |
  • 200 | Loading animation: "Walking dinosaurs" 201 | animation of Deno's public website is released under{' '} 202 | 207 | MIT license 208 | 209 | . It is attributed to Deno authors, and the original version can 210 | be found{' '} 211 | 216 | here 217 | 218 | . 219 |
  • 220 |
221 |
222 |
223 | 224 | 227 | 228 |
229 |
230 | ); 231 | }; 232 | 233 | export default Header; 234 | -------------------------------------------------------------------------------- /packages/ui/components/Loading.tsx: -------------------------------------------------------------------------------- 1 | type Props = { 2 | className: string; 3 | }; 4 | 5 | /** 6 | * https://github.com/denoland/doc_website/blob/0a9fa9d929ddcee16b647d889c9b182fa94832cf/components/Loading.tsx 7 | */ 8 | export default function Loading({ className }: Props): JSX.Element { 9 | return ( 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 30 | 31 | 32 | 33 | 38 | 39 | 40 | 45 | 46 | 47 | 53 | 54 | 55 | 60 | 61 | 62 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 89 | 94 | 99 | 104 | 109 | 114 | 119 | 120 | 213 | 214 | ); 215 | } 216 | -------------------------------------------------------------------------------- /packages/ui/components/Toolbar.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | createTheme, 4 | Dialog, 5 | DialogActions, 6 | DialogContent, 7 | DialogTitle, 8 | Menu, 9 | MenuItem, 10 | Snackbar, 11 | ThemeProvider, 12 | } from '@material-ui/core'; 13 | import { Transition } from 'react-transition-group'; 14 | import { ExpandMore, PlayArrow } from '@material-ui/icons'; 15 | import { Alert } from '@material-ui/lab'; 16 | import React, { useCallback } from 'react'; 17 | import lzstring from 'lz-string'; 18 | import { useClipboard } from 'use-clipboard-copy'; 19 | import useMatchMedia from 'use-match-media-hook'; 20 | import { ExampleId, getExampleSourceCode } from '../services/request'; 21 | import styles from '../styles/Toolbar.module.scss'; 22 | import { 23 | makeMarkdownUrl, 24 | makeMarkdownUrlWithShortExample, 25 | } from '../services/markdown'; 26 | 27 | type Props = { 28 | onRun: () => void; 29 | onFormat: () => void; 30 | onGenerateMarkdown: () => Promise; 31 | onAccessSource: () => string; 32 | onLoadExample: (sourceCode: string) => void; 33 | }; 34 | 35 | const Toolbar = ({ 36 | onRun, 37 | onFormat, 38 | onGenerateMarkdown, 39 | onAccessSource, 40 | onLoadExample, 41 | }: Props): JSX.Element => { 42 | const [exportAnchor, setExportAnchor] = React.useState( 43 | null 44 | ); 45 | const [exampleAnchor, setExampleAnchor] = React.useState( 46 | null 47 | ); 48 | const [alertReason, setAlertReason] = React.useState(''); 49 | const alertType = React.useRef(''); 50 | 51 | const [isDialogOpen, setDialogOpen] = React.useState(false); 52 | const [dialogContent, setDialogContent] = React.useState<{ 53 | title: string; 54 | message: string; 55 | }>({ title: '', message: '' }); 56 | const [_dialogType, setDialogType] = React.useState<'code'>('code'); 57 | const dialogElementRef = React.useRef(null); 58 | const textareaRef = React.useRef(null); 59 | const [isLargeScreen] = useMatchMedia(['(min-width: 768px)']); 60 | 61 | const dialogRendered = () => { 62 | const textarea = textareaRef?.current; 63 | if (textarea) { 64 | textarea.select(); 65 | } 66 | }; 67 | 68 | const handleSnackClose = (event?: React.SyntheticEvent, reason?: string) => { 69 | if (reason === 'clickaway') { 70 | return; 71 | } 72 | setAlertReason(''); 73 | }; 74 | 75 | const alerts = new Map([ 76 | [ 77 | 'copied', 78 | 79 | URL copied to clipboard 80 | , 81 | ], 82 | [ 83 | 'markdownCopied', 84 | 85 | Markdown code copied to clipboard 86 | , 87 | ], 88 | [ 89 | 'copyFailed', 90 | 95 | Failed to copy 96 | , 97 | ], 98 | ]); 99 | 100 | const clipboard = useClipboard({ 101 | onSuccess() { 102 | setAlertReason(alertType.current); 103 | }, 104 | onError() { 105 | setAlertReason('copyFailed'); 106 | }, 107 | }); 108 | 109 | const selectAllText = () => { 110 | const textarea = textareaRef?.current; 111 | if (textarea) { 112 | textarea.select(); 113 | } 114 | }; 115 | 116 | function handleRun(event: React.MouseEvent) { 117 | event.stopPropagation(); 118 | onRun(); 119 | } 120 | 121 | function handleFormat(event: React.MouseEvent) { 122 | event.stopPropagation(); 123 | onFormat(); 124 | } 125 | 126 | const handleShare = useCallback(() => { 127 | alertType.current = 'copied'; 128 | clipboard.copy(window.location.href); 129 | }, [clipboard]); 130 | 131 | const handleMarkdownCopy = useCallback(() => { 132 | const textarea = textareaRef?.current; 133 | if (textarea) { 134 | alertType.current = 'markdownCopied'; 135 | clipboard.copy(textarea.value); 136 | selectAllText(); 137 | } 138 | }, [clipboard]); 139 | 140 | const openExport = (event: React.MouseEvent) => { 141 | setExportAnchor(event.currentTarget); 142 | }; 143 | 144 | const closeExport = () => { 145 | setExportAnchor(null); 146 | }; 147 | 148 | const openDialog = (title: string, message: string, type: 'code') => { 149 | setDialogContent({ 150 | title, 151 | message, 152 | }); 153 | setDialogType(type); 154 | setDialogOpen(true); 155 | }; 156 | 157 | const closeDialog = () => { 158 | setDialogOpen(false); 159 | setDialogContent({ 160 | title: '', 161 | message: '', 162 | }); 163 | }; 164 | 165 | const copyAsMarkdownIssue = async () => { 166 | closeExport(); 167 | const markdown = await onGenerateMarkdown(); 168 | openDialog( 169 | 'Markdown version of Deno Playground code for GitHub issue', 170 | markdown, 171 | 'code' 172 | ); 173 | }; 174 | 175 | const copyAsMarkdownLink = () => { 176 | closeExport(); 177 | const markdown = makeMarkdownUrl(); 178 | openDialog('Deno playground link in markdown format', markdown, 'code'); 179 | }; 180 | 181 | const copyAsMarkdownLinkWithPreview = () => { 182 | closeExport(); 183 | const source = onAccessSource(); 184 | const markdown = makeMarkdownUrlWithShortExample(source); 185 | openDialog( 186 | 'Deno playground link in markdown format with example', 187 | markdown, 188 | 'code' 189 | ); 190 | }; 191 | 192 | const openInASTViewer = () => { 193 | closeExport(); 194 | const source = onAccessSource(); 195 | window.open( 196 | `https://ts-ast-viewer.com/#code/${lzstring.compressToEncodedURIComponent( 197 | source 198 | )}`, 199 | '_blank', 200 | 'noopener,noreferrer' 201 | ); 202 | }; 203 | 204 | const openInStackBlitz = () => { 205 | closeExport(); 206 | 207 | const source = onAccessSource(); 208 | const files = { 209 | 'package.json': { 210 | content: { 211 | name: 'deno-playground-export', 212 | version: '0.0.1', 213 | description: 'Deno Playground exported sandbox', 214 | scripts: { 215 | 'deno-install': 216 | 'curl -fsSL https://deno.land/x/install/install.sh | sh && export DENO_INSTALL="~/.local" && export PATH="$DENO_INSTALL/bin:$PATH"', 217 | 'set-up-types': 218 | 'rm -rf ./deno-types && mkdir ./deno-types && ~/.deno/bin/deno types >> ./deno-types/lib.deno_runtime.d.ts', 219 | deno: '~/.deno/bin/deno run --allow-net ./index.ts --reload', 220 | 'watch-file-changes': 221 | 'nodemon -w ./src --exec "npm run deno" -e "js ts json mjs"', 222 | start: 223 | 'npm run deno-install && npm run set-up-types && npm run watch-file-changes', 224 | }, 225 | dependencies: {}, 226 | devDependencies: { 227 | nodemon: '^2.0.12', 228 | }, 229 | keywords: [], 230 | }, 231 | }, 232 | 'index.ts': { 233 | content: source, 234 | }, 235 | '.gitignore': { 236 | content: 'deno-types', 237 | }, 238 | 'tsconfig.json': { 239 | content: { 240 | compilerOptions: { 241 | strict: true, 242 | noImplicitAny: true, 243 | strictNullChecks: true, 244 | strictFunctionTypes: true, 245 | strictPropertyInitialization: true, 246 | strictBindCallApply: true, 247 | noImplicitThis: true, 248 | noImplicitReturns: true, 249 | alwaysStrict: true, 250 | esModuleInterop: true, 251 | declaration: true, 252 | experimentalDecorators: true, 253 | emitDecoratorMetadata: true, 254 | target: 'ESNext', 255 | jsx: 'react', 256 | module: 'ESNext', 257 | lib: ['ESNext', 'DOM'], 258 | moduleResolution: 'node', 259 | allowJs: true, 260 | noEmit: true, 261 | pretty: true, 262 | resolveJsonModule: true, 263 | typeRoots: ['./deno-types'], 264 | }, 265 | include: ['./**/*.ts'], 266 | }, 267 | }, 268 | }; 269 | // Using the v1 get API 270 | const parameters = lzstring 271 | .compressToBase64(JSON.stringify({ files })) 272 | .replace(/\+/g, '-') // Convert '+' to '-' 273 | .replace(/\//g, '_') // Convert '/' to '_' 274 | .replace(/=+$/, ''); // Remove ending '=' 275 | const url = `https://codesandbox.io/api/v1/sandboxes/define?view=editor¶meters=${parameters}`; 276 | window.open(url, '_blank', 'noopener,noreferrer'); 277 | }; 278 | 279 | const openExamples = (event: React.MouseEvent) => { 280 | setExampleAnchor(event.currentTarget); 281 | }; 282 | 283 | const closeExamples = () => { 284 | setExampleAnchor(null); 285 | }; 286 | 287 | const loadExample = async (id: ExampleId) => { 288 | closeExamples(); 289 | const exampleSourceCode = await getExampleSourceCode(id); 290 | onLoadExample(exampleSourceCode); 291 | }; 292 | 293 | const examples = new Map([ 294 | ['hello-world', 'Hello World'], 295 | ['remote-import', 'Remote import'], 296 | ['fetch-data', 'Fetch data'], 297 | ['subprocesses', 'Subprocesses'], 298 | ]); 299 | 300 | const theme = createTheme({ 301 | palette: { 302 | primary: { 303 | main: '#05A985', 304 | contrastText: '#fff', 305 | }, 306 | }, 307 | typography: { 308 | fontFamily: [ 309 | 'Inter', 310 | 'system-ui', 311 | '-apple-system', 312 | 'BlinkMacSystemFont', 313 | '"Segoe UI"', 314 | 'Roboto', 315 | '"Helvetica Neue"', 316 | 'sans-serif', 317 | ].join(','), 318 | button: { 319 | fontSize: '1.4rem', 320 | textTransform: 'none', 321 | }, 322 | }, 323 | }); 324 | 325 | return ( 326 | 327 | 328 | 336 | 337 | 338 | 341 | 342 | 345 | 346 | 351 | {alertReason ? alerts.get(alertReason) : undefined} 352 | 353 | 354 | {isLargeScreen ? ( 355 | <> 356 | 366 | 367 | 373 | 374 | Copy as Markdown Issue 375 | 376 | 377 | Copy as Markdown Link 378 | 379 | 380 | Copy as Markdown Link with preview 381 | 382 |
383 | 384 | Open in TypeScript AST viewer 385 | 386 | Open in StackBlitz 387 |
388 | 389 | ) : null} 390 | 391 | 401 | 402 | 408 | {Array.from(examples.entries()).map(([id, name]) => ( 409 | loadExample(id)} key={id}> 410 | {name} 411 | 412 | ))} 413 | 414 | 415 | {/* 416 | 424 | } 425 | label="Use unstable APIs" 426 | /> 427 | 428 |
429 | 430 | 435 | Deno v1.8.3 436 | 437 | */} 438 | 439 | {(_state) => ( 440 | 447 | 448 | {dialogContent?.title} 449 | 450 | 451 | 456 |