├── .gitignore
├── .babelrc
├── playground
├── vite.config.js
├── index.html
└── app.tsx
├── rollup.config.js
├── .prettierrc
├── tsconfig.json
├── package.json
├── src
└── solid-repl.tsx
├── README.md
└── pnpm-lock.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | .parcel-cache
4 | .cache
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["babel-preset-solid", "@babel/preset-typescript"]
3 | }
--------------------------------------------------------------------------------
/playground/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import solidPlugin from 'vite-plugin-solid';
3 |
4 | export default defineConfig({
5 | plugins: [solidPlugin()],
6 | });
7 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import defineConfig from 'rollup-preset-solid';
2 |
3 | export default defineConfig({
4 | input: 'src/solid-repl.tsx',
5 | external: ['@amoutonbrady/lz-string'],
6 | printInstructions: true,
7 | });
8 |
--------------------------------------------------------------------------------
/playground/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always",
3 | "bracketSpacing": true,
4 | "endOfLine": "lf",
5 | "htmlWhitespaceSensitivity": "ignore",
6 | "jsxSingleQuote": false,
7 | "printWidth": 80,
8 | "quoteProps": "as-needed",
9 | "semi": true,
10 | "singleQuote": true,
11 | "trailingComma": "all",
12 | "tabWidth": 2,
13 | "useTabs": false
14 | }
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "allowSyntheticDefaultImports": true,
6 | "esModuleInterop": true,
7 | "declaration": true,
8 | "moduleResolution": "node",
9 | "declarationDir": "./dist/types",
10 | "jsx": "preserve",
11 | "resolveJsonModule": true,
12 | "newLine": "lf",
13 | "outDir": "./dist/source",
14 | "jsxImportSource": "solid-js"
15 | },
16 | "include": ["./src"],
17 | "exclude": ["node_modules/"]
18 | }
19 |
--------------------------------------------------------------------------------
/playground/app.tsx:
--------------------------------------------------------------------------------
1 | import { Repl, ReplTab } from '..';
2 | import { render } from 'solid-js/web';
3 |
4 | const App = () => {
5 | return (
6 | <>
7 |
11 |
12 |
13 | {`
14 | const App = () => Hello world!
15 | `}
16 |
17 |
18 | >
19 | );
20 | };
21 |
22 | render(App, document.getElementById('app')!);
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "solid-repl",
3 | "description": "A REPL for SolidJS",
4 | "author": "Alexandre Mouton-Brady",
5 | "license": "MIT",
6 | "version": "0.6.0",
7 | "homepage": "https://github.com/solidui/solid-repl#readme",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/solidui/solid-repl.git"
11 | },
12 | "type": "module",
13 | "main": "dist/esm/solid-repl.js",
14 | "module": "dist/esm/solid-repl.js",
15 | "types": "dist/types/solid-repl.d.ts",
16 | "exports": {
17 | ".": {
18 | "solid": "./dist/source/solid-repl.jsx",
19 | "default": "./dist/esm/solid-repl.js"
20 | }
21 | },
22 | "files": [
23 | "dist"
24 | ],
25 | "scripts": {
26 | "build": "rollup -c",
27 | "dev": "rollup -c -w",
28 | "format": "prettier --write \"src/**/*.{jsx,tsx,ts,js,json}\"",
29 | "test": "vite playground",
30 | "prepublishOnly": "pnpm build"
31 | },
32 | "peerDependencies": {
33 | "solid-js": "^0.26"
34 | },
35 | "dependencies": {
36 | "@amoutonbrady/lz-string": "^0.0.1",
37 | "solid-js": "^0.26.5"
38 | },
39 | "devDependencies": {
40 | "prettier": "^2.3.0",
41 | "rollup": "^2.47.0",
42 | "rollup-preset-solid": "^0.3.0",
43 | "typescript": "^4.2.4",
44 | "vite": "^2.3.2",
45 | "vite-plugin-solid": "^1.8.0"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/solid-repl.tsx:
--------------------------------------------------------------------------------
1 | import { Show } from 'solid-js/web';
2 | import { JSX, createMemo, splitProps } from 'solid-js';
3 | import { compressToURL } from '@amoutonbrady/lz-string';
4 |
5 | function uid() {
6 | const [ts, rand] = [Date.now(), Math.random()].map((value) =>
7 | value.toString(36),
8 | );
9 |
10 | return (ts + rand).replace(/\./g, '');
11 | }
12 |
13 | function childrensToArray(children: unknown | unknown[]): T[] {
14 | return [...(Array.isArray(children) ? children : [children])];
15 | }
16 |
17 | function formatCode(code: string) {
18 | const lines = code.split('\n');
19 |
20 | let mindent: number | null = null;
21 | let result = code.replace(/\\\n[ \t]*/g, '').replace(/\\`/g, '`');
22 |
23 | for (const line of lines) {
24 | const m = line.match(/^(\s+)\S+/);
25 |
26 | if (!m) continue;
27 | const indent = m[1].length;
28 | mindent = mindent ? Math.min(mindent, indent) : indent;
29 | }
30 |
31 | if (mindent !== null) {
32 | result = lines
33 | .map((line) => (line[0] === ' ' ? line.slice(mindent) : line))
34 | .join('\n');
35 | }
36 |
37 | return result.trim().replace(/\\n/g, '\n');
38 | }
39 |
40 | export const ReplTab = (props: {
41 | name: string;
42 | children?: unknown | unknown[];
43 | }) => {
44 | const id = uid();
45 |
46 | return createMemo(() => {
47 | const source = childrensToArray(props.children).join('');
48 |
49 | return {
50 | id,
51 | name: props.name,
52 | source: formatCode(source),
53 | type: 'tsx',
54 | } as unknown as JSX.Element;
55 | });
56 | };
57 |
58 | export const Repl = (props: ReplOptions) => {
59 | const [internal, external] = splitProps(props, [
60 | 'data',
61 | 'height',
62 | 'baseUrl',
63 | 'children',
64 | 'isInteractive',
65 | 'withHeader',
66 | 'edtiableTabs',
67 | 'withActionBar',
68 | 'layout',
69 | ]);
70 |
71 | const tabs = createMemo(() => {
72 | if (!internal.children) return [];
73 | return childrensToArray<() => Tab>(internal.children).map((tab) => tab());
74 | });
75 |
76 | const src = createMemo(() => {
77 | const url = new URL(internal.baseUrl || 'https://playground.solidjs.com');
78 |
79 | if (!internal.withHeader) url.searchParams.set('noHeader', 'true');
80 | if (!internal.isInteractive) url.searchParams.set('noInteractive', 'true');
81 | if (internal.data) url.searchParams.set('data', internal.data);
82 | if (!internal.edtiableTabs) url.searchParams.set('noEditableTabs', 'true')
83 | if (!internal.withActionBar) url.searchParams.set('noActionBar', 'true')
84 | if (internal.layout === 'vertical') url.searchParams.set('isHorizontal', 'true')
85 |
86 | if (tabs().length) {
87 | url.hash = compressToURL(JSON.stringify(tabs()));
88 | }
89 |
90 | return url.toString();
91 | });
92 |
93 | return (
94 | The REPL needs to have at least one tab}
97 | >
98 |
111 |
112 | );
113 | };
114 |
115 | export interface ReplOptions
116 | extends JSX.IframeHTMLAttributes {
117 | baseUrl?: string;
118 | height?: number | string;
119 | isInteractive?: boolean;
120 | withHeader?: boolean;
121 | withActionBar?: boolean;
122 | layout?: 'vertical' | 'responsive';
123 | edtiableTabs?: boolean;
124 | data?: string;
125 | children?: any;
126 | }
127 |
128 | export interface Tab {
129 | id: string;
130 | name: string;
131 | type: string;
132 | source: string;
133 | }
134 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Solid REPL
4 |
5 | > ## /!\ This repo. is temporarly innactive. All of the work around both the REPL and the playground is taking place over at [https://github.com/solidjs/solid-playground](https://github.com/solidjs/solid-playground)
6 |
7 | A re-usable [solid](https://github.com/ryansolid/solid) component that provides an embedable REPL.
8 |
9 | - [Solid REPL](#solid-repl)
10 | - [Usage](#usage)
11 | - [Installation](#installation)
12 | - [In practice](#in-practice)
13 | - [Options](#options)
14 | - [Repl options](#repl-options)
15 | - [ReplTab options](#repltab-options)
16 | - [Contributing](#contributing)
17 | - [Technical details](#technical-details)
18 | - [How does it work?](#how-does-it-work)
19 | - [TODOs](#todos)
20 | - [Credits](#credits)
21 |
22 | ## Usage
23 |
24 | [Demo available here](https://codesandbox.io/s/solid-repl-example-xr6de?file=/src/index.tsx)
25 |
26 | ### Installation
27 |
28 | ```bash
29 | # npm
30 | npm install solid-repl
31 |
32 | # pnpm
33 | pnpm add solid-repl
34 |
35 | # yarn
36 | yarn add solid-repl
37 | ```
38 |
39 | ### In practice
40 |
41 | In a nutshell this is how you'd use it:
42 |
43 | ```tsx
44 | import { Repl, ReplTab } from 'solid-repl';
45 | import { render } from 'solid-js/web';
46 |
47 | const App = () => {
48 | return (
49 |
55 |
56 | {`
57 | import { render } from 'solid-js/web';
58 | import { App } from './app.tsx';
59 |
60 | render(App, document.getElementById('app'));
61 | `}
62 |
63 |
64 | {'export const App = () => Hello world
'}
65 |
66 |
67 | );
68 | };
69 |
70 | render(App, document.getElementById('app')!);
71 | ```
72 |
73 | ### Options
74 |
75 | #### Repl options
76 |
77 | | name | required | type | default | description |
78 | | --------------- | -------- | ------- | --------------------------------- | --------------------------------- |
79 | | `baseUrl` | false | string | `https://playground.solidjs.com/` | The source of the iframe |
80 | | `height` | false | number | `250` | The height in pixel |
81 | | `withHeader` | false | boolean | `false` | Whether to show or not |
82 | | `isInteractive` | false | boolean | `false` | Whether it's interactive or not |
83 |
84 | #### ReplTab options
85 |
86 | | name | required | type | default | description |
87 | | ------ | -------- | ------ | ------- | --------------- |
88 | | `name` | true | string | - | Name of the tab |
89 |
90 | ## Contributing
91 |
92 | This project uses the [pnpm](https://pnpm.js.org/) package manager. You should install this one if you want to contribute.
93 | This project uses [prettier](https://prettier.io/) to format code. A `format` npm script can be used to format the code.
94 |
95 | In order to contribute you can :
96 |
97 | 1. Clone the reposotory: `git clone git@github.com:ryansolid/solid-repl.git`
98 | 2. Install the dependencies: `pnpm install`
99 | 3. Operate you changes: `pnpm test` will load a live server to preview your changes on [http://localhost:1234](http://localhost:1234)
100 | 4. (optional) Run `pnpm format` to format the code if you don't have that automatically in your IDE
101 |
102 | ## Technical details
103 |
104 | This package is a simple wrapper around the [solid playground](https://github.com/ryansolid/solid-playground) as an iframe.
105 | **The below information are related to the solid-playground of the iframe.**
106 |
107 | ### How does it work?
108 |
109 | Basically, each "tab" acts as a virtual file. This virtual file system is represented by a global array of tabs.
110 |
111 | On every tab source change, rollup parses all the file and create an ESM bundle. This ESM bundle is injected into an iframe with a `