Skip to content

Commit 612d1c8

Browse files
authored
Dev playground with monaco-editor (#1)
The playground should work both locally (`pnpm play`) and on github pages. Currently published at https://irclogs.github.io/api/. Based on monaco-editor, loads the types of the api and the library, so that you can write typescript and it will run in the browser. The CouchDB database will allow cross-origin access from the `https://irclogs.github.io` and `http://localhost:8000` Origins, so that means you can query the real production database. ps. don't use the snippet here: https://www.typescriptlang.org/dev/sandbox/ gives you millennia old monaco and typescript.
1 parent 01151d2 commit 612d1c8

File tree

10 files changed

+741
-17
lines changed

10 files changed

+741
-17
lines changed

Diff for: .github/workflows/playground.yml

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Build Playground
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
jobs:
9+
playground:
10+
# Allow one concurrent deployment
11+
concurrency:
12+
group: 'pages'
13+
cancel-in-progress: true
14+
15+
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
16+
permissions:
17+
pages: write # to deploy to Pages
18+
id-token: write # to verify the deployment originates from an appropriate source
19+
20+
environment:
21+
name: github-pages
22+
url: ${{ steps.deployment.outputs.page_url }}
23+
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v4
27+
28+
- uses: actions/setup-node@v4
29+
with:
30+
node-version: 18
31+
32+
- uses: pnpm/action-setup@v2
33+
with:
34+
run_install: true
35+
36+
- run: pnpm run '/^playground:.*/'
37+
38+
- name: Upload artifact
39+
uses: actions/upload-pages-artifact@v1
40+
with:
41+
path: ./playground
42+
43+
- uses: actions/deploy-pages@v1
44+
id: deployment

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
node_modules/
22
dist/
3+
playground/types/
4+
playground/dist/
35
.tsbuildinfo

Diff for: README.md

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
1-
# `irclog-api`
1+
# `@irclogs/api`
22

33
Typescript API to access the IrcLog CouchDB database. Mostly typed wrappers over the http api of CouchDB.
44

55

66
## Quick start for users
77

88
```
9-
TBD
9+
echo "@irclogs:registry=https://npm.pkg.github.com" >> .npmrc
10+
npm install @irclogs/api
11+
# or
12+
yarn add @irclogs/api
13+
# or
14+
pnpm add @irclogs/api
1015
```
1116

12-
## Quick start for developers
17+
## API Playground
1318

14-
TBD
19+
See https://irclogs.github.io/api/ to interactively play with the api in your browser.
20+
21+
To use the playground locally run:
1522
```
1623
pnpm install
17-
pnpm dev
24+
pnpm playground:types
25+
pnpm play
1826
```
27+
and open http://localhost:8000
28+
1929

2030
## References
2131

@@ -27,6 +37,5 @@ pnpm dev
2737

2838
- naming: class name?
2939
- documentation
30-
- playground publish to gh-pages (https://www.typescriptlang.org/dev/sandbox/)
3140
- package publish check auto-versioning schema
3241
- validation on responses and typing (use zod)

Diff for: package.json

+8-3
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,20 @@
1111
"/dist"
1212
],
1313
"scripts": {
14-
"dist": "tsc",
14+
"dist": "rm -rf dist/ && tsc",
1515
"compile": "tsc --noEmit --pretty",
1616
"prettier": "prettier src/",
1717
"prettier:fix": "prettier src/ --write",
18+
"play": "esbuild src/index.ts --bundle --outdir=playground/dist --watch --servedir=playground --format=esm",
19+
"playground:dist": "esbuild src/index.ts --bundle --outdir=playground/dist --format=esm",
20+
"playground:types": "$_ dist && dts-bundle --name @irclogs/api --baseDir ./dist/ --main dist/index.d.ts --out $PWD/playground/dist/index.d.ts",
1821
"test": "echo Warning: no test specified for now!"
1922
},
2023
"devDependencies": {
21-
"prettier": "^3.1.0",
22-
"typescript": "^5.3.2"
24+
"dts-bundle": "^0.7.3",
25+
"esbuild": "^0.19.10",
26+
"prettier": "^3.1.1",
27+
"typescript": "^5.3.3"
2328
},
2429
"prettier": {
2530
"printWidth": 120,

Diff for: playground/README.md

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Playground implementation notes
2+
3+
## monaco-editor
4+
5+
The playground is based on the [monaco-editor](https://microsoft.github.io/monaco-editor/) and
6+
it runs completely in the browser - no backend is needed. That makes it work on a site hosted
7+
at Github Pages too.
8+
9+
Monaco-editor is used since it is a decent editor, has built-in support for javascript and typescript,
10+
and runs in the browser.
11+
12+
The playground code is written in plain javascript and html.
13+
14+
> [!NOTE]
15+
> Don't be fooled by the playground at https://www.typescriptlang.org/dev/sandbox/.
16+
> Its documentation is incomplete, and installs quite old monaco-editor and typescript.
17+
18+
19+
## type support in the editor
20+
21+
In order for monaco-editor to be aware about the types of our project, we need to build a bundled
22+
`.d.ts` files. `tsc` itself doesn't know to properly bundle a `.d.ts` file useful for monaco-editor,
23+
so had to use `dts-bundle` (see `playground:types` in package.json scripts).
24+
25+
The bundle is created at `playground/dist/index.d.ts`, loaded in the browser with `fetch` and
26+
then loaded in the typescript server with `addExtraLib`, at a virtual path of
27+
`file:///node_modules/@types/@irclogs/api/index.d.ts`.
28+
29+
Given that the edited content is virtually at `file:///example.ts`, the language server uses the node
30+
module resolution algorithm, by looking for `node_modules`, then `@types/<your library>` etc.
31+
32+
ps.
33+
there's an option to load each `.d.ts` file separately as created by `tsc`, and add them to the language
34+
server. But that's a bit tedious and the list of files will need to be kept in sync with what `tsc` creates
35+
from the source.
36+
37+
38+
## running code in the browser
39+
40+
I wanted to enable running code in the browser as you would run it in any normal typescript module. That
41+
includes top-level await and support for the `import` statement. For those reasons `eval` is not possible.
42+
43+
In order to actually run the code, first we call `getEmitOutput` on the editor to get transpiled typescript
44+
to javascript. Then we create a `<script>` dom element, with `type=module`, and set the transpiled code
45+
as it's content. Once the element is added to the DOM, the javascript code will run.
46+
47+
### import mapping
48+
49+
[importmap](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) is used to
50+
map the normal `import {} from "@irclogs/api"` usage, to actually load the module from a file via http.
51+
52+
esbuild was used to create the `esm` bundle in `playground/dist/index.js`, so that it's compatible with
53+
the new-wave javascript module system (`import`).
54+
55+
56+
### redirecting console.log
57+
58+
A preamble is added to the transpiled code to override `console.log` to actually add text into the
59+
`#console-output` html element (a `<pre>` element). Seems hacky, but it works.
60+
61+
62+
63+
## Re-use of the playground
64+
65+
There's really only 3 places to change `@irclogs/api` in the playground code:
66+
- in `example.ts` of-course
67+
- in the `importmap` in `index.html`
68+
- the virtual path `file:///node_modules/@types/@irclogs/api/index.d.ts` in the playground.js
69+
70+
and one in `package.json` when building the bundled `index.d.ts` file with dts-bundle `--name @irclogs/api`.

Diff for: playground/example.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {CouchDB} from "@irclogs/api";
2+
3+
type T = "a" | "B";
4+
let a: T = "a";
5+
6+
const db = new CouchDB("https://db.softver.org.mk/irclog/", "_design/log/_view/channel");
7+
let res = await db.fetchChannelList();
8+
console.log(res);

Diff for: playground/favicon.png

8.74 KB
Loading

Diff for: playground/index.html

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<!doctype html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Playground</title>
8+
<link rel="icon" type="image/png" href="./favicon.png">
9+
<script
10+
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs/loader.min.js"
11+
integrity="sha512-ZG31AN9z/CQD1YDDAK4RUAvogwbJHv6bHrumrnMLzdCrVu4HeAqrUX7Jsal/cbUwXGfaMUNmQU04tQ8XXl5Znw=="
12+
crossorigin="anonymous"
13+
referrerpolicy="no-referrer"
14+
></script>
15+
<script src="./playground.js" defer></script>
16+
<script type="importmap">
17+
{
18+
"imports": {
19+
"@irclogs/api": "./dist/index.js"
20+
}
21+
}
22+
</script>
23+
24+
<style>
25+
body {
26+
overflow: hidden;
27+
margin: 0;
28+
}
29+
30+
#main {
31+
font-size: 12pt;
32+
width: 94%;
33+
padding: 0pt;
34+
margin: 1% 3% 5ex 3%;
35+
}
36+
37+
#leftPane {
38+
float: left;
39+
height: 90vh;
40+
width: 58%;
41+
margin: 0;
42+
background-color: rgb(255, 221, 227);
43+
44+
&>#monaco-editor {
45+
height: 100%;
46+
}
47+
}
48+
49+
#rightPane {
50+
float: right;
51+
height: 90vh;
52+
width: 40%;
53+
margin: 0;
54+
background-color: #eceff3;
55+
display: flex;
56+
flex-direction: column;
57+
58+
&>pre {
59+
overflow: scroll;
60+
flex-grow: 1;
61+
tab-size: 2;
62+
}
63+
}
64+
</style>
65+
</head>
66+
67+
<body>
68+
<div id="main">
69+
<div id="leftPane">
70+
<div id="monaco-editor"></div>
71+
</div>
72+
<div id="rightPane">
73+
<div><button id="run-code">Run Code</button><button id="clear-log">Clear log</button></div>
74+
<pre id="console-output"></pre>
75+
</div>
76+
</div>
77+
</body>
78+
79+
</html>

0 commit comments

Comments
 (0)