Skip to content

Commit 76d66ea

Browse files
committed
feat: add playground package
1 parent 3b6758b commit 76d66ea

9 files changed

+676
-1
lines changed

packages/playground/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist

packages/playground/index.html

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
8+
<title>Vue 2 JSX Playground</title>
9+
10+
<link rel="stylesheet" href="./style.css">
11+
<script type="module" src="./index.ts"></script>
12+
</head>
13+
14+
<body>
15+
<div id="header"></div>
16+
<div id="source" class="editor"></div>
17+
<div id="output" class="editor"></div>
18+
</body>
19+
20+
</html>

packages/playground/index.ts

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import * as monaco from 'monaco-editor'
2+
import { watchEffect } from 'vue'
3+
import { transform } from '@babel/standalone'
4+
import babelPresetJsx from '@vue/babel-preset-jsx'
5+
import { compilerOptions, initOptions } from './options'
6+
7+
const sharedEditorOptions: monaco.editor.IStandaloneEditorConstructionOptions = {
8+
theme: 'vs-dark',
9+
fontSize: 14,
10+
wordWrap: 'on',
11+
scrollBeyondLastLine: false,
12+
renderWhitespace: 'selection',
13+
contextmenu: false,
14+
minimap: {
15+
enabled: false,
16+
},
17+
}
18+
19+
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
20+
allowJs: true,
21+
allowNonTsExtensions: true,
22+
jsx: monaco.languages.typescript.JsxEmit.Preserve,
23+
target: monaco.languages.typescript.ScriptTarget.Latest,
24+
})
25+
26+
const editor = monaco.editor.create(document.getElementById('source')!, {
27+
value: decodeURIComponent(window.location.hash.slice(1)) || 'const App = () => <div>Hello World</div>',
28+
language: 'typescript',
29+
tabSize: 2,
30+
...sharedEditorOptions,
31+
})
32+
33+
const output = monaco.editor.create(document.getElementById('output')!, {
34+
value: '',
35+
language: 'javascript',
36+
readOnly: true,
37+
tabSize: 2,
38+
...sharedEditorOptions,
39+
})
40+
41+
const reCompile = () => {
42+
const src = editor.getValue()
43+
window.location.hash = encodeURIComponent(src)
44+
// console.clear()
45+
try {
46+
const result = transform(src, {
47+
// somehow the transform function won't actually rerun
48+
// if the options are the same object, thus we have to spread it
49+
presets: [[babelPresetJsx, { ...compilerOptions }]],
50+
ast: true,
51+
})
52+
console.log('AST', result.ast!)
53+
output.setValue(result.code!)
54+
} catch (err) {
55+
output.setValue((err as Error).message!)
56+
console.error(err)
57+
}
58+
}
59+
60+
initOptions()
61+
watchEffect(reCompile)
62+
// update compile output when input changes
63+
editor.onDidChangeModelContent(debounce(reCompile))
64+
65+
function debounce<T extends (...args: any[]) => any>(fn: T, delay = 300): T {
66+
let prevTimer: number | null = null
67+
return ((...args: any[]) => {
68+
if (prevTimer) {
69+
clearTimeout(prevTimer)
70+
}
71+
prevTimer = window.setTimeout(() => {
72+
fn(...args)
73+
prevTimer = null
74+
}, delay)
75+
}) as any
76+
}

packages/playground/options.ts

+246
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
import Vue, { h, reactive } from 'vue'
2+
3+
type VueJSXPresetOptions = {
4+
functional?: boolean
5+
injectH?: boolean
6+
vModel?: boolean
7+
vOn?: boolean
8+
compositionAPI?: true | 'auto' | 'native' | 'plugin' | 'vue-demi' | false | { importSource: string }
9+
}
10+
11+
export { type VueJSXPresetOptions }
12+
13+
export const compilerOptions: VueJSXPresetOptions = reactive({
14+
functional: true,
15+
injectH: true,
16+
vModel: true,
17+
vOn: true,
18+
compositionAPI: false,
19+
})
20+
21+
const App = {
22+
setup() {
23+
return () => [
24+
h('div', { attrs: { id: 'header' } }, [
25+
h('h1', 'Vue 2 JSX Explorer'),
26+
h(
27+
'a',
28+
{
29+
// FIXME:
30+
href: 'https://github.com/vuejs/jsx-vue2',
31+
// href: 'https://app.netlify.com/sites/vue-next-jsx-explorer/deploys',
32+
target: '_blank',
33+
},
34+
'History',
35+
),
36+
37+
h('div', { attrs: { id: 'options-wrapper' } }, [
38+
h('div', { attrs: { id: 'options-label' } }, 'Options ↘'),
39+
h('ul', { attrs: { id: 'options' } }, [
40+
h('li', [
41+
h('input', {
42+
attrs: {
43+
type: 'checkbox',
44+
id: 'functional',
45+
name: 'functional',
46+
},
47+
domProps: {
48+
checked: compilerOptions.functional,
49+
},
50+
on: {
51+
change: (e: Event) => {
52+
compilerOptions.functional = (e.target as HTMLInputElement).checked
53+
},
54+
},
55+
}),
56+
h('label', { attrs: { for: 'functional' } }, ['functional']),
57+
]),
58+
59+
h('li', [
60+
h('input', {
61+
attrs: {
62+
type: 'checkbox',
63+
id: 'injectH',
64+
name: 'injectH',
65+
},
66+
domProps: {
67+
checked: compilerOptions.injectH,
68+
},
69+
on: {
70+
change: (e: Event) => {
71+
compilerOptions.injectH = (e.target as HTMLInputElement).checked
72+
},
73+
},
74+
}),
75+
h('label', { attrs: { for: 'injectH' } }, ['injectH']),
76+
]),
77+
78+
h('li', [
79+
h('input', {
80+
attrs: {
81+
type: 'checkbox',
82+
id: 'vModel',
83+
name: 'vModel',
84+
},
85+
domProps: {
86+
checked: compilerOptions.vModel,
87+
},
88+
on: {
89+
change: (e: Event) => {
90+
compilerOptions.vModel = (e.target as HTMLInputElement).checked
91+
},
92+
},
93+
}),
94+
h('label', { attrs: { for: 'vModel' } }, ['vModel']),
95+
]),
96+
97+
h('li', [
98+
h('input', {
99+
attrs: {
100+
type: 'checkbox',
101+
id: 'vOn',
102+
name: 'vOn',
103+
},
104+
domProps: {
105+
checked: compilerOptions.vOn,
106+
},
107+
on: {
108+
change: (e: Event) => {
109+
compilerOptions.vOn = (e.target as HTMLInputElement).checked
110+
},
111+
},
112+
}),
113+
h('label', { attrs: { for: 'vOn' } }, ['vOn']),
114+
]),
115+
116+
h('li', [
117+
h(
118+
'span',
119+
{
120+
class: 'label',
121+
},
122+
['compositionAPI:'],
123+
),
124+
h('input', {
125+
attrs: {
126+
type: 'radio',
127+
name: 'compositionAPI',
128+
id: 'compositionAPI-false',
129+
},
130+
domProps: {
131+
checked: compilerOptions.compositionAPI === false,
132+
},
133+
on: {
134+
change: () => (compilerOptions.compositionAPI = false),
135+
},
136+
}),
137+
h(
138+
'label',
139+
{
140+
attrs: {
141+
for: 'compositionAPI-false',
142+
},
143+
},
144+
['false'],
145+
),
146+
h('input', {
147+
attrs: {
148+
type: 'radio',
149+
name: 'compositionAPI',
150+
id: 'compositionAPI-auto',
151+
},
152+
domProps: {
153+
checked: compilerOptions.compositionAPI === true || compilerOptions.compositionAPI === 'auto',
154+
},
155+
on: {
156+
change: () => (compilerOptions.compositionAPI = 'auto'),
157+
},
158+
}),
159+
h(
160+
'label',
161+
{
162+
attrs: {
163+
for: 'compositionAPI-auto',
164+
},
165+
},
166+
['auto'],
167+
),
168+
h('input', {
169+
attrs: {
170+
type: 'radio',
171+
name: 'compositionAPI',
172+
id: 'compositionAPI-native',
173+
},
174+
domProps: {
175+
checked: compilerOptions.compositionAPI === 'native',
176+
},
177+
on: {
178+
change: () => (compilerOptions.compositionAPI = 'native'),
179+
},
180+
}),
181+
h(
182+
'label',
183+
{
184+
attrs: {
185+
for: 'compositionAPI-native',
186+
},
187+
},
188+
['native'],
189+
),
190+
h('input', {
191+
attrs: {
192+
type: 'radio',
193+
name: 'compositionAPI',
194+
id: 'compositionAPI-plugin',
195+
},
196+
domProps: {
197+
checked: compilerOptions.compositionAPI === 'plugin',
198+
},
199+
on: {
200+
change: () => (compilerOptions.compositionAPI = 'plugin'),
201+
},
202+
}),
203+
h(
204+
'label',
205+
{
206+
attrs: {
207+
for: 'compositionAPI-plugin',
208+
},
209+
},
210+
['plugin'],
211+
),
212+
h('input', {
213+
attrs: {
214+
type: 'radio',
215+
name: 'compositionAPI',
216+
id: 'compositionAPI-vue-demi',
217+
},
218+
domProps: {
219+
checked: compilerOptions.compositionAPI === 'vue-demi',
220+
},
221+
on: {
222+
change: () => (compilerOptions.compositionAPI = 'vue-demi'),
223+
},
224+
}),
225+
h(
226+
'label',
227+
{
228+
attrs: {
229+
for: 'compositionAPI-vue-demi',
230+
},
231+
},
232+
['vue-demi'],
233+
),
234+
]),
235+
]),
236+
]),
237+
]),
238+
]
239+
},
240+
}
241+
242+
export function initOptions() {
243+
new Vue({
244+
render: h => h(App),
245+
}).$mount(document.getElementById('header')!)
246+
}

packages/playground/package.json

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "@vue/jsx-vue2-playground",
3+
"version": "1.0.0",
4+
"description": "Online playground for Vue 2 JSX preset",
5+
"private": true,
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1",
8+
"dev": "vite",
9+
"build": "vite build"
10+
},
11+
"repository": {
12+
"type": "git",
13+
"url": "git+https://github.com/vuejs/jsx-vue2.git"
14+
},
15+
"author": "Haoqun Jiang <[email protected]>",
16+
"license": "MIT",
17+
"bugs": {
18+
"url": "https://github.com/vuejs/jsx-vue2/issues"
19+
},
20+
"homepage": "https://github.com/vuejs/jsx-vue2#readme",
21+
"devDependencies": {
22+
"@vue/tsconfig": "^0.1.3",
23+
"vite": "^3.0.4",
24+
"vite-plugin-monaco-editor": "^1.1.0"
25+
},
26+
"dependencies": {
27+
"@babel/standalone": "^7.19.3",
28+
"@vue/babel-preset-jsx": "*",
29+
"monaco-editor": "^0.34.0",
30+
"vue": "^2.7.10"
31+
}
32+
}

0 commit comments

Comments
 (0)