├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── docs └── assets │ ├── demo.gif │ ├── logo.png │ ├── logo@2x.png │ └── logo@3x.png ├── package.json ├── src ├── assets │ └── iphone-6-silver.svg ├── babel-worker.js ├── components │ ├── embed │ │ └── Playground.tsx │ ├── player │ │ ├── ConsoleProxy.ts │ │ └── VendorComponents.ts │ └── workspace │ │ ├── About.tsx │ │ ├── App.tsx │ │ ├── Button.tsx │ │ ├── CodeSandboxButton.tsx │ │ ├── Console.tsx │ │ ├── Editor.tsx │ │ ├── Header.tsx │ │ ├── HeaderLink.tsx │ │ ├── Icons.tsx │ │ ├── Inspector.tsx │ │ ├── Overlay.tsx │ │ ├── Phone.tsx │ │ ├── PlayerFrame.tsx │ │ ├── PlaygroundPreview.tsx │ │ ├── Spacer.tsx │ │ ├── Status.tsx │ │ ├── TabContainer.tsx │ │ ├── Tabs.tsx │ │ ├── Tooltip.tsx │ │ ├── Workspace.tsx │ │ ├── WorkspacesList.tsx │ │ └── panes │ │ ├── ConsolePane.tsx │ │ ├── EditorPane.tsx │ │ ├── PlayerPane.tsx │ │ ├── StackPane.tsx │ │ ├── TranspilerPane.tsx │ │ └── WorkspacesPane.tsx ├── constants │ ├── DefaultCode.ts │ └── Phones.ts ├── contexts │ └── OptionsContext.ts ├── environments │ ├── IEnvironment.tsx │ ├── html-environment.tsx │ ├── javascript-environment.tsx │ ├── python-environment.tsx │ ├── react-environment.tsx │ └── react-native-environment.tsx ├── hooks │ ├── useRerenderEffect.ts │ ├── useResponsiveBreakpoint.ts │ └── useWindowDimensions.ts ├── index.tsx ├── player.tsx ├── python-worker.js ├── reducers │ └── workspace.ts ├── styles │ ├── codemirror-theme.css │ ├── index.css │ ├── player.css │ └── reset.css ├── types │ ├── Messages.ts │ ├── globals.d.ts │ ├── react-native-web.d.ts │ ├── react.d.ts │ ├── snarkdown.d.ts │ └── svg.d.ts ├── typescript-worker.ts ├── utils │ ├── BabelConsolePlugin.ts │ ├── BabelExpressionLogPlugin.ts │ ├── BabelInfiniteLoopPlugin.ts │ ├── BabelRequest.ts │ ├── CSS.ts │ ├── CodeMirror.ts │ ├── CodeMirrorTooltipAddon.ts │ ├── DOMCoding.ts │ ├── Diff.ts │ ├── ErrorMessage.ts │ ├── ExtendedJSON.ts │ ├── HashString.ts │ ├── MockDOM.ts │ ├── Networking.ts │ ├── Object.ts │ ├── Panes.ts │ ├── PlayerUtils.ts │ ├── Styles.ts │ ├── Tab.ts │ ├── TypeScriptDefaultConfig.ts │ ├── TypeScriptDefaultLibs.ts │ ├── TypeScriptRequest.ts │ ├── WorkerRequest.ts │ ├── formatError.ts │ ├── hasProperty.ts │ ├── inspect.tsx │ ├── options.ts │ ├── path.ts │ ├── playerCommunication.ts │ └── queryString.ts └── workers │ ├── pythonWorker.ts │ └── typescript │ ├── LanguageServiceHost.ts │ ├── fileSystem.ts │ └── system.ts ├── tsconfig.json ├── webpack ├── empty.js ├── index.ejs ├── webpack-embed.config.js └── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-1", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | 3 | node_modules 4 | yarn-error.log 5 | 6 | dist 7 | public 8 | /report.*.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | Copyright (c) 2016-present, Devin Abbott. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of the copyright holder nor the names of its contributors 16 | may be used to endorse or promote products derived from this software without 17 | specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /docs/assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabbott/javascript-playgrounds/2f77a8120dd45bb0b422c4ebfcdf464509f84a9b/docs/assets/demo.gif -------------------------------------------------------------------------------- /docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabbott/javascript-playgrounds/2f77a8120dd45bb0b422c4ebfcdf464509f84a9b/docs/assets/logo.png -------------------------------------------------------------------------------- /docs/assets/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabbott/javascript-playgrounds/2f77a8120dd45bb0b422c4ebfcdf464509f84a9b/docs/assets/logo@2x.png -------------------------------------------------------------------------------- /docs/assets/logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabbott/javascript-playgrounds/2f77a8120dd45bb0b422c4ebfcdf464509f84a9b/docs/assets/logo@3x.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javascript-playgrounds", 3 | "version": "1.2.8", 4 | "description": "Interactive JavaScript sandbox", 5 | "main": "dist/javascript-playgrounds.js", 6 | "files": [ 7 | "dist", 8 | "public" 9 | ], 10 | "types": "dist/src/components/embed/Playground.d.ts", 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "dev": "webpack-dev-server --config webpack/webpack.config.js --inline --hot --colors --quiet", 14 | "start": "python3 -m http.server -d public 8080", 15 | "build": "npm run build:core && npm run build:embed", 16 | "build:core": "webpack --env production --config webpack/webpack.config.js --sort-assets-by --progress", 17 | "build:embed": "webpack --config webpack/webpack-embed.config.js --sort-assets-by --progress", 18 | "clean": "rm -rf ./dist ./public", 19 | "prepublishOnly": "npm run clean && npm run build", 20 | "gh-pages": "npm run prepublish && gh-pages -d public", 21 | "analyze": "webpack --env production --config webpack/webpack.config.js --sort-assets-by --progress --profile --json > stats.json" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/dabbott/javascript-playgrounds.git" 26 | }, 27 | "author": "devinabbott@gmail.com", 28 | "license": "BSD-3-Clause", 29 | "bugs": { 30 | "url": "https://github.com/dabbott/javascript-playgrounds/issues" 31 | }, 32 | "homepage": "https://github.com/dabbott/javascript-playgrounds#readme", 33 | "devDependencies": { 34 | "@babel/core": "^7.9.6", 35 | "@juggle/resize-observer": "^3.1.3", 36 | "@types/babel__core": "^7.1.10", 37 | "@types/codemirror": "^0.0.97", 38 | "@types/diff": "^4.0.2", 39 | "@types/inline-style-prefixer": "^5.0.0", 40 | "@types/react": "^16.9.48", 41 | "@types/react-dom": "^16.9.8", 42 | "@types/react-inspector": "^4.0.1", 43 | "@types/react-loadable": "^5.5.3", 44 | "@types/scriptjs": "^0.0.2", 45 | "babel-cli": "^6.3.17", 46 | "babel-core": "^6.26.3", 47 | "babel-eslint": "^4.1.6", 48 | "babel-loader": "7.1.5", 49 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 50 | "babel-preset-es2015": "^6.3.13", 51 | "babel-preset-react": "^6.3.13", 52 | "babel-preset-react-native": "^1.9.0", 53 | "babel-preset-stage-1": "^6.3.13", 54 | "babel-runtime": "^6.3.19", 55 | "codemirror": "^5.54.0", 56 | "codesandbox": "^2.2.1", 57 | "css-loader": "^3.5.3", 58 | "diff": "^4.0.1", 59 | "eslint": "^1.10.3", 60 | "eslint-config-standard": "^4.4.0", 61 | "eslint-config-standard-react": "^1.2.1", 62 | "eslint-plugin-react": "^3.13.1", 63 | "eslint-plugin-standard": "^1.3.1", 64 | "file-loader": "^6.0.0", 65 | "gh-pages": "^2.2.0", 66 | "html-webpack-plugin": "^4.3.0", 67 | "inline-style-prefixer": "^6.0.0", 68 | "metro-react-native-babel-preset": "^0.59.0", 69 | "packly": "^0.0.1", 70 | "prettier": "^2.0.5", 71 | "prop-types": "^15.7.2", 72 | "raw-loader": "^4.0.1", 73 | "react": "16.8.0", 74 | "react-dom": "16.8.0", 75 | "react-inspector": "^5.0.1", 76 | "react-loadable": "^5.5.0", 77 | "react-native-web": "0.11.4", 78 | "regenerator-runtime": "^0.9.5", 79 | "screenfull": "^4.2.0", 80 | "scriptjs": "^2.5.8", 81 | "snarkdown": "^1.2.2", 82 | "style-loader": "^1.2.1", 83 | "ts-loader": "^8.0.3", 84 | "typescript": "^3.9.3", 85 | "webpack": "^4.43.0", 86 | "webpack-cli": "^3.3.11", 87 | "webpack-dev-server": "^3.11.0", 88 | "webpack-merge": "^4.2.2", 89 | "worker-loader": "^3.0.2" 90 | }, 91 | "dependencies": {}, 92 | "prettier": { 93 | "singleQuote": true, 94 | "semi": false 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/babel-worker.js: -------------------------------------------------------------------------------- 1 | import * as Babel from '@babel/core' 2 | 3 | onmessage = function (event) { 4 | const { 5 | id, 6 | payload: { 7 | code: value, 8 | filename, 9 | options: { 10 | instrumentExpressionStatements, 11 | maxLoopIterations, 12 | ...options 13 | }, 14 | }, 15 | } = event.data 16 | 17 | let output 18 | 19 | try { 20 | const presets = [ 21 | require('metro-react-native-babel-preset').getPreset(value, { 22 | enableBabelRuntime: false, 23 | }), 24 | ] 25 | 26 | const plugins = [ 27 | ...(maxLoopIterations > 0 28 | ? [ 29 | [ 30 | require('./utils/BabelInfiniteLoopPlugin'), 31 | { maxIterations: maxLoopIterations }, 32 | ], 33 | ] 34 | : []), 35 | ...(instrumentExpressionStatements 36 | ? [require('./utils/BabelExpressionLogPlugin')] 37 | : []), 38 | require('./utils/BabelConsolePlugin'), 39 | [ 40 | require('@babel/plugin-transform-typescript'), 41 | { 42 | isTSX: true, 43 | allowNamespaces: true, 44 | }, 45 | ], 46 | ] 47 | 48 | const code = Babel.transform(value, { 49 | presets, 50 | plugins, 51 | filename, 52 | ...options, 53 | }).code 54 | 55 | output = { 56 | id, 57 | payload: { 58 | filename, 59 | type: 'code', 60 | code, 61 | }, 62 | } 63 | } catch (e) { 64 | output = { 65 | id, 66 | payload: { 67 | filename, 68 | type: 'error', 69 | error: { 70 | message: e.message.replace('unknown', e.name), 71 | }, 72 | }, 73 | } 74 | } 75 | 76 | postMessage(output) 77 | } 78 | -------------------------------------------------------------------------------- /src/components/embed/Playground.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties, memo, useMemo } from 'react' 2 | import type { PublicOptions } from '../../utils/options' 3 | 4 | declare global { 5 | // Defined in webpack config 6 | const VERSION: string 7 | } 8 | 9 | const WEB_PLAYER_URL = `https://unpkg.com/javascript-playgrounds@${VERSION}/public/index.html` 10 | 11 | const styles = { 12 | iframe: { 13 | width: '100%', 14 | height: '100%', 15 | }, 16 | } 17 | 18 | interface OwnProps { 19 | style?: CSSProperties 20 | className?: string 21 | baseURL?: string 22 | } 23 | 24 | export type PlaygroundProps = OwnProps & PublicOptions 25 | 26 | /** 27 | * A React component wrapper for the embeddable iframe player. This ensures 28 | * properties are passed and encoded correctly. 29 | * 30 | * Most props are passed directly through to the player; props passed into the 31 | * player can't be changed after the initial render. Other props can be updated 32 | * normally. 33 | */ 34 | export default memo(function Playground({ 35 | style, 36 | className, 37 | baseURL = WEB_PLAYER_URL, 38 | ...rest 39 | }: PlaygroundProps) { 40 | // If the baseURL changes, set a new src. 41 | // We don't refresh the player if other props change. 42 | const src = useMemo( 43 | () => `${baseURL}#data=${encodeURIComponent(JSON.stringify(rest))}`, 44 | [baseURL] 45 | ) 46 | 47 | return ( 48 |
49 |