Skip to content

feat: add a playground to easy test repl different scenarios #340

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
},
"scripts": {
"dev": "vite",
"dev:playground": "vite -c playground/vite.config.ts",
"build": "vite build",
"build-preview": "vite build -c vite.preview.config.ts",
"format": "prettier --write .",
Expand Down
84 changes: 84 additions & 0 deletions playground/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Vue REPL Playground

Optimize `test/main` to support dynamically adding scenario validations for the REPL. Use `npm run dev:playground` to try it out.

## Playground Architecture

### Directory Structure

```
playground/
├── index.html # HTML entry file with scenario switcher
├── vite.config.ts # Standalone Vite config
├── vite-plugin-scenario.js # Parse scenarios directory and generate REPL configuration
├── scenarios/ # Scenario directory, add dynamically
│ ├── basic/ # Basic example
│ ├── customMain/ # Custom main entry
│ ├── html/ # html as entry example
│ ├── pinia/ # Pinia state management example
│ └── vueRouter/ # Vue Router example
│ └── vueUse/ # Vue Use example
└── src/
└── App.vue # Main application component
```

### How It Works

The playground uses a directory-based scenario system, where each scenario is an independent folder under `scenarios/`. Core features include:

- **Virtual Module System**: A Vite plugin scans the scenario directory and generates a virtual module `virtual:playground-files`
- **Dynamic Scenario Loading**: Users can switch scenarios via the UI, which automatically loads the corresponding configuration

### Scenario Structure

Each scenario directory typically contains the following files:

```
scenarios/example-scenario/
├── App.vue # Main component
├── main.ts # Entry file
├── import-map.json # Dependency mapping
├── tsconfig.json # TypeScript config
└── _meta.js # Metadata config for REPL settings
```

The `_meta.js` file exports the scenario configuration:

```javascript
export default {
mainFile: 'main.ts', // Specify the main entry file
}
```

## Usage Example

### Start the Playground

```bash
# Enter the project directory
cd vue-repl

# Install dependencies
npm install

# Start the development server
npm run dev:playground
```

Visit the displayed local address (usually http://localhost:5174/) to use the playground.

### Add a New Scenario

1. Create a new folder under the `scenarios/` directory, e.g. `myScenario`
2. Add the required files:

```
myScenario/
├── App.vue # Main component
├── main.ts # Entry file (default entry)
├── import-map.json # Dependency config
├── tsconfig.json # TypeScript config
└── _meta.js # Config with mainFile: 'main.ts'
```

3. Refresh the browser, and the new scenario will automatically appear in the dropdown menu.
18 changes: 18 additions & 0 deletions playground/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Playground</title>
<style>
body {
margin: 0;
}
</style>
<script type="module" src="./main.ts"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
5 changes: 5 additions & 0 deletions playground/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createApp } from 'vue'
// @ts-ignore
import App from './src/App.vue'

createApp(App).mount('#app')
15 changes: 15 additions & 0 deletions playground/scenarios/basic/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<div>
<button @click="increment">Increment</button>
<p>Counter: {{ counter }}</p>
</div>
</template>

<script lang="ts" setup>
// a simple demo check Repl default config
import { ref } from 'vue'
const counter = ref(0)
const increment = () => {
counter.value++
}
</script>
6 changes: 6 additions & 0 deletions playground/scenarios/basic/_meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
mainFile: 'App.vue',
ReplOptions: {
theme: 'dark',
},
}
6 changes: 6 additions & 0 deletions playground/scenarios/customMain/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<template>
<div>
<h1>custom main</h1>
<button @click="$hi">test globalProperties</button>
</div>
</template>
3 changes: 3 additions & 0 deletions playground/scenarios/customMain/_meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
mainFile: 'main.ts',
}
5 changes: 5 additions & 0 deletions playground/scenarios/customMain/import-map.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
6 changes: 6 additions & 0 deletions playground/scenarios/customMain/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.config.globalProperties.$hi = () => alert('hi Vue')
app.mount('#app')
9 changes: 9 additions & 0 deletions playground/scenarios/customMain/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
6 changes: 6 additions & 0 deletions playground/scenarios/html/_meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
mainFile: 'index.html',
ReplOptions: {
theme: 'dark',
},
}
5 changes: 5 additions & 0 deletions playground/scenarios/html/import-map.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
41 changes: 41 additions & 0 deletions playground/scenarios/html/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<script type="module">
import { createApp } from 'vue'

// give each todo a unique id
let id = 0

createApp({
data() {
return {
newTodo: '',
todos: [
{ id: id++, text: 'Learn HTML' },
{ id: id++, text: 'Learn JavaScript' },
{ id: id++, text: 'Learn Vue' },
],
}
},
methods: {
addTodo() {
this.todos.push({ id: id++, text: this.newTodo })
this.newTodo = ''
},
removeTodo(todo) {
this.todos = this.todos.filter((t) => t !== todo)
},
},
}).mount('#app')
</script>

<div id="app">
<form @submit.prevent="addTodo">
<input v-model="newTodo" required placeholder="new todo" />
<button>Add Todo</button>
</form>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
<button @click="removeTodo(todo)">X</button>
</li>
</ul>
</div>
17 changes: 17 additions & 0 deletions playground/scenarios/pinia/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<div>
<h1>Welcome to Pinia Demo</h1>
<p>Store message: {{ store.message }}</p>
<button @click="updateMessage">Update Message</button>
</div>
</template>

<script setup>
import { useMainStore } from './store'

const store = useMainStore()

function updateMessage() {
store.updateMessage('Message updated at ' + new Date().toLocaleTimeString())
}
</script>
3 changes: 3 additions & 0 deletions playground/scenarios/pinia/_meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
mainFile: 'main.ts',
}
8 changes: 8 additions & 0 deletions playground/scenarios/pinia/import-map.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js",
"pinia": "https://unpkg.com/pinia@2/dist/pinia.esm-browser.js",
"vue-demi": "https://cdn.jsdelivr.net/npm/[email protected]/lib/v3/index.mjs",
"@vue/devtools-api": "https://cdn.jsdelivr.net/npm/@vue/[email protected]/lib/esm/index.js"
}
}
8 changes: 8 additions & 0 deletions playground/scenarios/pinia/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
12 changes: 12 additions & 0 deletions playground/scenarios/pinia/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineStore } from 'pinia'

export const useMainStore = defineStore('main', {
state: () => ({
message: 'Hello from Pinia!',
}),
actions: {
updateMessage(newMessage: string) {
this.message = newMessage
},
},
})
9 changes: 9 additions & 0 deletions playground/scenarios/pinia/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
17 changes: 17 additions & 0 deletions playground/scenarios/vueRouter/About.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<div>
<h3>About Page</h3>
<p>This is a demo of Vue Router in a sandbox environment</p>
<button @click="goHome">Go Home</button>
</div>
</template>

<script setup>
import { useRouter } from 'vue-router'

const router = useRouter()

function goHome() {
router.push('/')
}
</script>
15 changes: 15 additions & 0 deletions playground/scenarios/vueRouter/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<div>
<h2>Vue Router Demo</h2>
<nav>
<RouterLink to="/">Home</RouterLink> |
<RouterLink to="/about">About</RouterLink>
</nav>
<router-view />
</div>
</template>

<script setup>
import { RouterLink } from 'vue-router'
// Add your setup code here
</script>
5 changes: 5 additions & 0 deletions playground/scenarios/vueRouter/Home.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
<h1>Welcome to Vue Router Demo</h1>
</div>
</template>
3 changes: 3 additions & 0 deletions playground/scenarios/vueRouter/_meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
mainFile: 'main.ts',
}
9 changes: 9 additions & 0 deletions playground/scenarios/vueRouter/import-map.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js",
"vue/server-renderer": "http://localhost:5173/src/vue-server-renderer-dev-proxy",
"vue-router": "https://unpkg.com/vue-router@4/dist/vue-router.esm-browser.js",
"@vue/devtools-api": "https://cdn.jsdelivr.net/npm/@vue/[email protected]/lib/esm/index.js"
},
"scopes": {}
}
25 changes: 25 additions & 0 deletions playground/scenarios/vueRouter/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createApp } from 'vue'
import { createRouter, createMemoryHistory } from 'vue-router'
import App from './App.vue'
import Home from './Home.vue'
import About from './About.vue'

const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
]

// Use createMemoryHistory instead of createWebHistory in the sandbox environment
const router = createRouter({
history: createMemoryHistory(),
routes,
})

// Add router error handling
router.onError((error) => {
console.error('Vue Router error:', error)
})

const app = createApp(App)
app.use(router)
app.mount('#app')
9 changes: 9 additions & 0 deletions playground/scenarios/vueRouter/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
Loading