Skip to content

Commit 0d1eccb

Browse files
committed
Add Playground
1 parent ec8083b commit 0d1eccb

File tree

14 files changed

+1018
-32
lines changed

14 files changed

+1018
-32
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
"eslint.validate": [
77
"javascript",
88
"javascriptreact",
9-
{ "language": "vue", "autoFix": true }
9+
"vue"
1010
]
1111
}

docs/.vuepress/.eslintrc.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict'
2+
3+
module.exports = {
4+
extends: [
5+
'plugin:vue/recommended'
6+
],
7+
rules: {
8+
'vue/component-tags-order': ['error', {
9+
'order': ['template', 'script', 'style']
10+
}]
11+
}
12+
}
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
<template>
2+
<div class="playground">
3+
<label>FileName:</label> <input v-model.trim="filename">
4+
<VueEslintEditor
5+
ref="editor"
6+
v-model="code"
7+
class="playground__editor"
8+
fix
9+
:linter="linter"
10+
:config="objectConfig"
11+
:preprocess="preprocess"
12+
:postprocess="postprocess"
13+
dark
14+
:format="format"
15+
:language="language"
16+
:filename="filename"
17+
:style="{ height: editorHeight }"
18+
@change="onChange"
19+
/>
20+
<div class="playground__tools">
21+
<json-editor
22+
ref="jsonEditor"
23+
v-model="config"
24+
class="playground__json-editor"
25+
dark
26+
:format="format"
27+
:style="{ height: jsonEditorHeight }"
28+
/>
29+
<ul class="playground__messages">
30+
<li v-if="configError">
31+
Config Error: {{ configError }}
32+
</li>
33+
<template v-for="(message, i) in messages">
34+
<li :key="i">
35+
[{{ message.line }}:{{ message.column }}]
36+
{{ message.message }}
37+
</li>
38+
</template>
39+
</ul>
40+
</div>
41+
</div>
42+
</template>
43+
44+
<script>
45+
import path from 'path'
46+
import VueEslintEditor from 'vue-eslint-editor'
47+
import { rules, processors } from '../../../'
48+
import JsonEditor from './playground/components/JsonEditor.vue'
49+
import yaml from 'js-yaml'
50+
import stripComments from 'strip-json-comments'
51+
import { deserializeState, serializeState } from './playground/state'
52+
import { resolveConfig } from './playground/eslint/config'
53+
import CODE_DEFAULT from './playground/code-default.vue.txt'
54+
const CONFIG_DEFAULT = `{
55+
"extends": [
56+
"plugin:vue/vue3-recommended"
57+
]
58+
}`
59+
const FILENAME_DEFAULT = 'App.vue'
60+
61+
export default {
62+
name: 'Playground',
63+
components: { VueEslintEditor, JsonEditor },
64+
data () {
65+
const serializedString =
66+
(typeof window !== 'undefined' && window.___location.hash.slice(1)) ||
67+
''
68+
const state = deserializeState(serializedString)
69+
return {
70+
linter: null,
71+
code: state.code || CODE_DEFAULT,
72+
config: state.config || CONFIG_DEFAULT,
73+
preprocess: processors['.vue'].preprocess,
74+
postprocess: processors['.vue'].postprocess,
75+
filename: state.filename || FILENAME_DEFAULT,
76+
messages: []
77+
}
78+
},
79+
computed: {
80+
objectConfig () {
81+
try {
82+
return parseAndResolveConfig(this.config)
83+
} catch {
84+
/* nop */
85+
}
86+
return {}
87+
},
88+
configError () {
89+
try {
90+
parseAndResolveConfig(this.config)
91+
return ''
92+
} catch (e) {
93+
console.warn(e)
94+
return e.message
95+
}
96+
},
97+
language () {
98+
const ext = path.extname(this.filename)
99+
if (!ext) {
100+
return 'css'
101+
}
102+
switch (ext.toLowerCase()) {
103+
case '.js':
104+
case '.ts':
105+
return 'javascript'
106+
case '.html':
107+
case '.vue':
108+
return 'html'
109+
default:
110+
break
111+
}
112+
return 'html'
113+
},
114+
serializedString () {
115+
const code = CODE_DEFAULT === this.code ? undefined : this.code
116+
const config =
117+
CONFIG_DEFAULT === this.config ? undefined : this.config
118+
const filename =
119+
FILENAME_DEFAULT === this.filename ? undefined : this.filename
120+
const serializedString = serializeState({
121+
code,
122+
config,
123+
filename
124+
})
125+
return serializedString
126+
},
127+
format () {
128+
return {
129+
insertSpaces: true,
130+
tabSize: 2
131+
}
132+
},
133+
editorHeight () {
134+
const lines = this.code.split('\n').length
135+
return `${Math.max(120, 20 * (1 + lines))}px`
136+
},
137+
jsonEditorHeight () {
138+
const lines = this.config.split('\n').length
139+
return `${Math.max(120, 20 * (1 + lines))}px`
140+
}
141+
},
142+
watch: {
143+
serializedString (serializedString) {
144+
if (typeof window !== 'undefined') {
145+
window.___location.replace(`#${serializedString}`)
146+
}
147+
},
148+
async editorHeight () {
149+
await this.$nextTick()
150+
this.$refs.editor.codeEditor.layout()
151+
if (this.$refs.editor.fixedCodeEditor) {
152+
this.$refs.editor.fixedCodeEditor.layout()
153+
}
154+
},
155+
async jsonEditorHeight () {
156+
await this.$nextTick()
157+
this.$refs.jsonEditor.editor.layout()
158+
}
159+
},
160+
mounted () {
161+
// Load linter asynchronously.
162+
this.loadEslint()
163+
if (typeof window !== 'undefined') {
164+
window.addEventListener('hashchange', this.onUrlHashChange)
165+
}
166+
},
167+
beforeDestroey () {
168+
if (typeof window !== 'undefined') {
169+
window.removeEventListener('hashchange', this.onUrlHashChange)
170+
}
171+
},
172+
methods: {
173+
async loadEslint () {
174+
const [
175+
{ default: Linter },
176+
{ parseForESLint }
177+
] = await Promise.all([
178+
import('eslint4b/dist/linter'),
179+
import('espree').then(() => import('vue-eslint-parser'))
180+
])
181+
182+
const linter = (this.linter = new Linter())
183+
184+
for (const ruleId of Object.keys(rules)) {
185+
linter.defineRule(`vue/${ruleId}`, rules[ruleId])
186+
}
187+
188+
linter.defineParser('vue-eslint-parser', { parseForESLint })
189+
},
190+
onUrlHashChange () {
191+
const serializedString =
192+
(typeof window !== 'undefined' &&
193+
window.___location.hash.slice(1)) ||
194+
''
195+
if (serializedString !== this.serializedString) {
196+
const state = deserializeState(serializedString)
197+
this.code = state.code || CODE_DEFAULT
198+
this.config = state.config || CONFIG_DEFAULT
199+
this.filename = state.filename || FILENAME_DEFAULT
200+
}
201+
},
202+
onChange ({ messages }) {
203+
this.messages = messages || []
204+
}
205+
}
206+
}
207+
208+
function parseAndResolveConfig (str) {
209+
let config = null
210+
try {
211+
config = JSON.parse(stripComments(str))
212+
} catch (e) {
213+
try {
214+
config = yaml.safeLoad(stripComments(str))
215+
} catch (_e) {
216+
throw e
217+
}
218+
}
219+
return resolveConfig(config)
220+
}
221+
222+
</script>
223+
<style scoped>
224+
.playground {
225+
width: 100%;
226+
margin: 1em 0;
227+
}
228+
.playground__editor {
229+
margin: 1em 0;
230+
max-height: calc(100vh - 200px);
231+
}
232+
.playground__tools {
233+
display: flex;
234+
}
235+
.playground__json-editor {
236+
width: 50%;
237+
max-height: calc(100vh - 200px);
238+
}
239+
.playground__messages {
240+
width: 50%;
241+
padding: 0 1em 0 4em;
242+
margin: 0;
243+
font-size: 0.8rem;
244+
max-height: calc(100vh - 200px);
245+
overflow: auto;
246+
}
247+
</style>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<template>
2+
<div v-else>
3+
Hello, {{ name }}!
4+
<div
5+
v-for="item of items"
6+
key="item.id"
7+
>
8+
{{ item.name}}
9+
</div>
10+
<button v-on:click="onClick">Button</button>
11+
</div>
12+
</template>
13+
<script>
14+
export default {
15+
name: 'App',
16+
data: {
17+
name: 'World',
18+
items: [
19+
{id: 1, name: "a"},
20+
{id: 2, name: "b"},
21+
{id: 3, name: "c"}
22+
]
23+
},
24+
methods: {
25+
onClick() {
26+
// do something.
27+
}
28+
}
29+
}
30+
</script>

0 commit comments

Comments
 (0)