diff --git a/package.json b/package.json
index ab1f9233..fe2957dc 100644
--- a/package.json
+++ b/package.json
@@ -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 .",
diff --git a/playground/README.md b/playground/README.md
new file mode 100644
index 00000000..57fb29ea
--- /dev/null
+++ b/playground/README.md
@@ -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.
diff --git a/playground/index.html b/playground/index.html
new file mode 100644
index 00000000..90135e61
--- /dev/null
+++ b/playground/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ Playground
+
+
+
+
+
+
+
diff --git a/playground/main.ts b/playground/main.ts
new file mode 100644
index 00000000..5677bf0d
--- /dev/null
+++ b/playground/main.ts
@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+// @ts-ignore
+import App from './src/App.vue'
+
+createApp(App).mount('#app')
diff --git a/playground/scenarios/basic/App.vue b/playground/scenarios/basic/App.vue
new file mode 100644
index 00000000..69eafb79
--- /dev/null
+++ b/playground/scenarios/basic/App.vue
@@ -0,0 +1,15 @@
+
+
+
+
Counter: {{ counter }}
+
+
+
+
diff --git a/playground/scenarios/basic/_meta.js b/playground/scenarios/basic/_meta.js
new file mode 100644
index 00000000..67c0b6c7
--- /dev/null
+++ b/playground/scenarios/basic/_meta.js
@@ -0,0 +1,6 @@
+export default {
+ mainFile: 'App.vue',
+ ReplOptions: {
+ theme: 'dark',
+ },
+}
diff --git a/playground/scenarios/customMain/App.vue b/playground/scenarios/customMain/App.vue
new file mode 100644
index 00000000..00debe0f
--- /dev/null
+++ b/playground/scenarios/customMain/App.vue
@@ -0,0 +1,6 @@
+
+
+
custom main
+
+
+
diff --git a/playground/scenarios/customMain/_meta.js b/playground/scenarios/customMain/_meta.js
new file mode 100644
index 00000000..ce23dfb4
--- /dev/null
+++ b/playground/scenarios/customMain/_meta.js
@@ -0,0 +1,3 @@
+export default {
+ mainFile: 'main.ts',
+}
diff --git a/playground/scenarios/customMain/import-map.json b/playground/scenarios/customMain/import-map.json
new file mode 100644
index 00000000..60857ee5
--- /dev/null
+++ b/playground/scenarios/customMain/import-map.json
@@ -0,0 +1,5 @@
+{
+ "imports": {
+ "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
+ }
+}
diff --git a/playground/scenarios/customMain/main.ts b/playground/scenarios/customMain/main.ts
new file mode 100644
index 00000000..0b99e5a6
--- /dev/null
+++ b/playground/scenarios/customMain/main.ts
@@ -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')
diff --git a/playground/scenarios/customMain/tsconfig.json b/playground/scenarios/customMain/tsconfig.json
new file mode 100644
index 00000000..3d685f65
--- /dev/null
+++ b/playground/scenarios/customMain/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "target": "esnext",
+ "module": "esnext",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true
+ }
+}
diff --git a/playground/scenarios/html/_meta.js b/playground/scenarios/html/_meta.js
new file mode 100644
index 00000000..4a59d442
--- /dev/null
+++ b/playground/scenarios/html/_meta.js
@@ -0,0 +1,6 @@
+export default {
+ mainFile: 'index.html',
+ ReplOptions: {
+ theme: 'dark',
+ },
+}
diff --git a/playground/scenarios/html/import-map.json b/playground/scenarios/html/import-map.json
new file mode 100644
index 00000000..60857ee5
--- /dev/null
+++ b/playground/scenarios/html/import-map.json
@@ -0,0 +1,5 @@
+{
+ "imports": {
+ "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
+ }
+}
diff --git a/playground/scenarios/html/index.html b/playground/scenarios/html/index.html
new file mode 100644
index 00000000..a3ca398d
--- /dev/null
+++ b/playground/scenarios/html/index.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+ -
+ {{ todo.text }}
+
+
+
+
diff --git a/playground/scenarios/pinia/App.vue b/playground/scenarios/pinia/App.vue
new file mode 100644
index 00000000..62bbea57
--- /dev/null
+++ b/playground/scenarios/pinia/App.vue
@@ -0,0 +1,17 @@
+
+
+
Welcome to Pinia Demo
+
Store message: {{ store.message }}
+
+
+
+
+
diff --git a/playground/scenarios/pinia/_meta.js b/playground/scenarios/pinia/_meta.js
new file mode 100644
index 00000000..ce23dfb4
--- /dev/null
+++ b/playground/scenarios/pinia/_meta.js
@@ -0,0 +1,3 @@
+export default {
+ mainFile: 'main.ts',
+}
diff --git a/playground/scenarios/pinia/import-map.json b/playground/scenarios/pinia/import-map.json
new file mode 100644
index 00000000..86ce97d1
--- /dev/null
+++ b/playground/scenarios/pinia/import-map.json
@@ -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/vue-demi@0.14.7/lib/v3/index.mjs",
+ "@vue/devtools-api": "https://cdn.jsdelivr.net/npm/@vue/devtools-api@6.6.1/lib/esm/index.js"
+ }
+}
diff --git a/playground/scenarios/pinia/main.ts b/playground/scenarios/pinia/main.ts
new file mode 100644
index 00000000..ce885f3c
--- /dev/null
+++ b/playground/scenarios/pinia/main.ts
@@ -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')
diff --git a/playground/scenarios/pinia/store.ts b/playground/scenarios/pinia/store.ts
new file mode 100644
index 00000000..278cca74
--- /dev/null
+++ b/playground/scenarios/pinia/store.ts
@@ -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
+ },
+ },
+})
diff --git a/playground/scenarios/pinia/tsconfig.json b/playground/scenarios/pinia/tsconfig.json
new file mode 100644
index 00000000..3d685f65
--- /dev/null
+++ b/playground/scenarios/pinia/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "target": "esnext",
+ "module": "esnext",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true
+ }
+}
diff --git a/playground/scenarios/vueRouter/About.vue b/playground/scenarios/vueRouter/About.vue
new file mode 100644
index 00000000..cb1e23cf
--- /dev/null
+++ b/playground/scenarios/vueRouter/About.vue
@@ -0,0 +1,17 @@
+
+
+
About Page
+
This is a demo of Vue Router in a sandbox environment
+
+
+
+
+
diff --git a/playground/scenarios/vueRouter/App.vue b/playground/scenarios/vueRouter/App.vue
new file mode 100644
index 00000000..84b1033b
--- /dev/null
+++ b/playground/scenarios/vueRouter/App.vue
@@ -0,0 +1,15 @@
+
+
+
Vue Router Demo
+
+
+
+
+
+
diff --git a/playground/scenarios/vueRouter/Home.vue b/playground/scenarios/vueRouter/Home.vue
new file mode 100644
index 00000000..98a25376
--- /dev/null
+++ b/playground/scenarios/vueRouter/Home.vue
@@ -0,0 +1,5 @@
+
+
+
Welcome to Vue Router Demo
+
+
diff --git a/playground/scenarios/vueRouter/_meta.js b/playground/scenarios/vueRouter/_meta.js
new file mode 100644
index 00000000..ce23dfb4
--- /dev/null
+++ b/playground/scenarios/vueRouter/_meta.js
@@ -0,0 +1,3 @@
+export default {
+ mainFile: 'main.ts',
+}
diff --git a/playground/scenarios/vueRouter/import-map.json b/playground/scenarios/vueRouter/import-map.json
new file mode 100644
index 00000000..e9f5c7a7
--- /dev/null
+++ b/playground/scenarios/vueRouter/import-map.json
@@ -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/devtools-api@6.6.1/lib/esm/index.js"
+ },
+ "scopes": {}
+}
diff --git a/playground/scenarios/vueRouter/main.ts b/playground/scenarios/vueRouter/main.ts
new file mode 100644
index 00000000..efa63ce9
--- /dev/null
+++ b/playground/scenarios/vueRouter/main.ts
@@ -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')
diff --git a/playground/scenarios/vueRouter/tsconfig.json b/playground/scenarios/vueRouter/tsconfig.json
new file mode 100644
index 00000000..3d685f65
--- /dev/null
+++ b/playground/scenarios/vueRouter/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "target": "esnext",
+ "module": "esnext",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true
+ }
+}
diff --git a/playground/scenarios/vueUse/App.vue b/playground/scenarios/vueUse/App.vue
new file mode 100644
index 00000000..2e118b58
--- /dev/null
+++ b/playground/scenarios/vueUse/App.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/playground/scenarios/vueUse/Coordinate.vue b/playground/scenarios/vueUse/Coordinate.vue
new file mode 100644
index 00000000..a2dc7b59
--- /dev/null
+++ b/playground/scenarios/vueUse/Coordinate.vue
@@ -0,0 +1,35 @@
+
+
+ {{ value }}
+ Mouse {{ label }}
+
+
+
+
+
+
diff --git a/playground/scenarios/vueUse/_meta.js b/playground/scenarios/vueUse/_meta.js
new file mode 100644
index 00000000..39b877c3
--- /dev/null
+++ b/playground/scenarios/vueUse/_meta.js
@@ -0,0 +1,3 @@
+export default {
+ mainFile: 'App.vue',
+}
diff --git a/playground/scenarios/vueUse/import-map.json b/playground/scenarios/vueUse/import-map.json
new file mode 100644
index 00000000..8938cd22
--- /dev/null
+++ b/playground/scenarios/vueUse/import-map.json
@@ -0,0 +1,8 @@
+{
+ "imports": {
+ "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js",
+ "@vueuse/core": "https://unpkg.com/@vueuse/core@10.1.0/index.mjs",
+ "@vueuse/shared": "https://unpkg.com/@vueuse/shared@10.1.0/index.mjs",
+ "vue-demi": "https://cdn.jsdelivr.net/npm/vue-demi@0.14.7/lib/v3/index.mjs"
+ }
+}
diff --git a/playground/src/App.vue b/playground/src/App.vue
new file mode 100644
index 00000000..69f41acf
--- /dev/null
+++ b/playground/src/App.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/playground/src/ScenarioSelector.vue b/playground/src/ScenarioSelector.vue
new file mode 100644
index 00000000..dc9e36d9
--- /dev/null
+++ b/playground/src/ScenarioSelector.vue
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
diff --git a/playground/vite-plugin-scenario.js b/playground/vite-plugin-scenario.js
new file mode 100644
index 00000000..b3d473a9
--- /dev/null
+++ b/playground/vite-plugin-scenario.js
@@ -0,0 +1,254 @@
+/**
+ * Vite Scenario Plugin
+ *
+ * This plugin provides file system-based scenario configuration management for the Vue REPL Playground.
+ * It dynamically scans the scenarios directory and generates configuration objects for REPL usage.
+ *
+ * Features:
+ * - Automatically scans scenario directories under scenarios
+ * - Supports hot updates: regenerates config when scenario files change
+ * - Uses _meta.js for scenario metadata
+ *
+ * Usage:
+ * 1. Create a scenario subdirectory under `scenarios`
+ * 2. Add necessary files in each scenario directory (App.vue, main.ts, etc.)
+ * 3. Add a _meta.js file to configure scenario metadata
+ */
+import fs from 'fs'
+import path from 'path'
+import { fileURLToPath } from 'url'
+
+// Get current file directory
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+
+/**
+ * Scenario plugin
+ * Provides the virtual module 'virtual:playground-files' for runtime dynamic scenario config generation
+ */
+export default function scenarioPlugin() {
+ let server
+ const scenariosPath = path.resolve(__dirname, 'scenarios')
+ const VIRTUAL_MODULE_ID = 'virtual:playground-files'
+ const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID
+
+ return {
+ name: 'vite-plugin-scenario',
+
+ configureServer(_server) {
+ server = _server
+
+ // Watch for changes in the scenarios directory
+ const watcher = server.watcher
+ watcher.add(scenariosPath)
+
+ // On file changes in scenarios, reload the virtual module
+ watcher.on('add', handleFileChange)
+ watcher.on('change', handleFileChange)
+ watcher.on('unlink', handleFileChange)
+
+ function handleFileChange(file) {
+ // Check if the changed file is under scenarios
+ if (file.startsWith(scenariosPath)) {
+ console.log(
+ `[vite-plugin-scenario] Scenario file changed: ${path.relative(scenariosPath, file)}`,
+ )
+ // Notify client that the module needs to update
+ server.moduleGraph
+ .getModuleById(RESOLVED_VIRTUAL_MODULE_ID)
+ ?.importers.forEach((importer) => {
+ server.moduleGraph.invalidateModule(importer)
+ server.ws.send({
+ type: 'update',
+ updates: [
+ {
+ type: 'js-update',
+ path: importer.url,
+ acceptedPath: importer.url,
+ },
+ ],
+ })
+ })
+
+ // Invalidate the module directly to ensure regeneration on next request
+ server.moduleGraph.invalidateModule(
+ server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID),
+ )
+ }
+ }
+ },
+
+ resolveId(id) {
+ if (id === VIRTUAL_MODULE_ID) {
+ return RESOLVED_VIRTUAL_MODULE_ID
+ }
+ },
+
+ async load(id) {
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
+ try {
+ // Scan scenario directory and generate config
+ const config = await generateConfig(scenariosPath)
+
+ // Ensure at least one scenario exists
+ if (Object.keys(config).length === 0) {
+ console.warn(
+ '[vite-plugin-scenario] Warning: No scenarios found, providing default scenario',
+ )
+ // Provide a default scenario to prevent frontend errors
+ return `export default {
+ "demo1": {
+ "files": {
+ "App.vue": "\\n \\n
Welcome to Vue REPL
\\n
Please create scenarios in src/scenarios
\\n
\\n",
+ "main.ts": "import { createApp } from 'vue';\\nimport App from './App.vue';\\n\\nconst app = createApp(App);\\napp.mount('#app');"
+ },
+ "mainFile": "main.ts"
+ }
+ }`
+ }
+
+ return `export default ${JSON.stringify(config, null, 2)}`
+ } catch (error) {
+ console.error(
+ '[vite-plugin-scenario] Error generating config:',
+ error,
+ )
+ // Return a basic config to avoid frontend errors
+ return `export default {
+ "error": {
+ "files": {
+ "App.vue": "\\n \\n
Error
\\n
An error occurred while loading scenarios. Check the console.
\\n
\\n",
+ "main.ts": "import { createApp } from 'vue';\\nimport App from './App.vue';\\n\\nconst app = createApp(App);\\napp.mount('#app');"
+ },
+ "mainFile": "main.ts"
+ }
+ }`
+ }
+ }
+ },
+ }
+}
+
+/**
+ * Scan scenario directory and generate config object with parallel processing
+ * @param {string} scenariosPath - Path to scenarios directory
+ * @returns {Promise