From 5e90b94c2a73ba93f823440350082e8b535a2fef Mon Sep 17 00:00:00 2001 From: zenheart Date: Wed, 14 May 2025 21:58:47 +0800 Subject: [PATCH 1/7] feat: add a playground to easy test repl different scenarios --- package.json | 1 + playground/README.md | 82 ++++++ playground/index.html | 13 + playground/main.ts | 5 + playground/scenarios/basic/App.vue | 15 ++ playground/scenarios/basic/_meta.js | 3 + playground/scenarios/customMain/App.vue | 6 + playground/scenarios/customMain/_meta.js | 3 + .../scenarios/customMain/import-map.json | 5 + playground/scenarios/customMain/main.ts | 6 + playground/scenarios/customMain/tsconfig.json | 9 + playground/scenarios/pinia/App.vue | 17 ++ playground/scenarios/pinia/_meta.js | 3 + playground/scenarios/pinia/import-map.json | 8 + playground/scenarios/pinia/main.ts | 8 + playground/scenarios/pinia/store.ts | 12 + playground/scenarios/pinia/tsconfig.json | 9 + playground/scenarios/vueRouter/About.vue | 17 ++ playground/scenarios/vueRouter/App.vue | 15 ++ playground/scenarios/vueRouter/Home.vue | 5 + playground/scenarios/vueRouter/_meta.js | 3 + .../scenarios/vueRouter/import-map.json | 9 + playground/scenarios/vueRouter/main.ts | 25 ++ playground/scenarios/vueRouter/tsconfig.json | 9 + playground/src/App.vue | 54 ++++ playground/src/ScenarioSelector.vue | 49 ++++ playground/vite-plugin-scenario.js | 240 ++++++++++++++++++ playground/vite.config.ts | 34 +++ vite.config.ts | 2 + 29 files changed, 667 insertions(+) create mode 100644 playground/README.md create mode 100644 playground/index.html create mode 100644 playground/main.ts create mode 100644 playground/scenarios/basic/App.vue create mode 100644 playground/scenarios/basic/_meta.js create mode 100644 playground/scenarios/customMain/App.vue create mode 100644 playground/scenarios/customMain/_meta.js create mode 100644 playground/scenarios/customMain/import-map.json create mode 100644 playground/scenarios/customMain/main.ts create mode 100644 playground/scenarios/customMain/tsconfig.json create mode 100644 playground/scenarios/pinia/App.vue create mode 100644 playground/scenarios/pinia/_meta.js create mode 100644 playground/scenarios/pinia/import-map.json create mode 100644 playground/scenarios/pinia/main.ts create mode 100644 playground/scenarios/pinia/store.ts create mode 100644 playground/scenarios/pinia/tsconfig.json create mode 100644 playground/scenarios/vueRouter/About.vue create mode 100644 playground/scenarios/vueRouter/App.vue create mode 100644 playground/scenarios/vueRouter/Home.vue create mode 100644 playground/scenarios/vueRouter/_meta.js create mode 100644 playground/scenarios/vueRouter/import-map.json create mode 100644 playground/scenarios/vueRouter/main.ts create mode 100644 playground/scenarios/vueRouter/tsconfig.json create mode 100644 playground/src/App.vue create mode 100644 playground/src/ScenarioSelector.vue create mode 100644 playground/vite-plugin-scenario.js create mode 100644 playground/vite.config.ts 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..54c35e2e --- /dev/null +++ b/playground/README.md @@ -0,0 +1,82 @@ +# 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 +│ ├── pinia/ # Pinia state management example +│ └── vueRouter/ # Vue Router 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 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..98445152 --- /dev/null +++ b/playground/index.html @@ -0,0 +1,13 @@ + + + + + + + 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 @@ + + + diff --git a/playground/scenarios/basic/_meta.js b/playground/scenarios/basic/_meta.js new file mode 100644 index 00000000..39b877c3 --- /dev/null +++ b/playground/scenarios/basic/_meta.js @@ -0,0 +1,3 @@ +export default { + mainFile: 'App.vue', +} 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 @@ + 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/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 @@ + + + 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 @@ + + + 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 @@ + + + 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 @@ + 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/src/App.vue b/playground/src/App.vue new file mode 100644 index 00000000..b6d80029 --- /dev/null +++ b/playground/src/App.vue @@ -0,0 +1,54 @@ + + + + + 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..f75b9e65 --- /dev/null +++ b/playground/vite-plugin-scenario.js @@ -0,0 +1,240 @@ +/** + * 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": "", + "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": "", + "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 + * @param {string} scenariosPath - Path to scenarios directory + * @returns {Promise} - Config object + */ +async function generateConfig(scenariosPath) { + const config = {} + + try { + // Check if scenarios directory exists + try { + await fs.promises.access(scenariosPath) + } catch (err) { + console.warn( + `[vite-plugin-scenario] Scenarios directory does not exist: ${scenariosPath}, creating it`, + ) + // Create scenarios directory + await fs.promises.mkdir(scenariosPath, { recursive: true }) + console.log( + `[vite-plugin-scenario] Scenarios directory created: ${scenariosPath}`, + ) + return config // Return empty config + } + + const dirs = await fs.promises.readdir(scenariosPath) + + for (const dir of dirs) { + const scenarioPath = path.join(scenariosPath, dir) + const stat = await fs.promises.stat(scenarioPath) + + if (!stat.isDirectory()) continue + + // Read all files in the scenario + const files = {} + const allFiles = await fs.promises.readdir(scenarioPath) + + let meta = { mainFile: 'main.ts' } // Default metadata + + // Handle metadata file first to get metadata before other files + const metaFile = allFiles.find((file) => file === '_meta.js') + if (metaFile) { + const metaFilePath = path.join(scenarioPath, metaFile) + try { + // Read metadata file and manually parse + const metaContent = await fs.promises.readFile(metaFilePath, 'utf-8') + + // Extract default export part + // Use simple regex to match export default {...} + const defaultExportMatch = metaContent.match( + /export\s+default\s+({[\s\S]*?})/m, + ) + if (defaultExportMatch && defaultExportMatch[1]) { + try { + // Use Function constructor to safely parse JS object + // Safer than eval, but can handle JS object syntax + const extractedMeta = new Function( + `return ${defaultExportMatch[1]}`, + )() + meta = { ...meta, ...extractedMeta } + console.log( + `[vite-plugin-scenario] Loaded scenario metadata: ${dir}`, + meta, + ) + } catch (parseError) { + console.error( + `[vite-plugin-scenario] Failed to parse metadata: ${metaFilePath}`, + parseError, + ) + } + } + } catch (error) { + console.error( + `[vite-plugin-scenario] Unable to load metadata file: ${metaFilePath}`, + error, + ) + } + } + + // Process all files in the scenario + for (const file of allFiles) { + // Skip hidden files and metadata file + if (file.startsWith('.') || file === '_meta.js') continue + + // Read file content + const filePath = path.join(scenarioPath, file) + const content = await fs.promises.readFile(filePath, 'utf-8') + + // Store file content in config + files[file] = content + } + + // Build scenario config + config[dir] = { + files, + ...meta, + } + } + + console.log( + `[vite-plugin-scenario] Config generated, scenarios: ${Object.keys(config).join(', ')}`, + ) + return config + } catch (error) { + console.error( + '[vite-plugin-scenario] Error generating scenario config:', + error, + ) + return {} + } +} diff --git a/playground/vite.config.ts b/playground/vite.config.ts new file mode 100644 index 00000000..54202ea1 --- /dev/null +++ b/playground/vite.config.ts @@ -0,0 +1,34 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import replace from '@rollup/plugin-replace' +import path from 'node:path' +import scenarioPlugin from './vite-plugin-scenario' + +export default defineConfig({ + plugins: [vue(), scenarioPlugin()], + resolve: { + alias: { + '@vue/compiler-dom': '@vue/compiler-dom/dist/compiler-dom.cjs.js', + '@vue/compiler-core': '@vue/compiler-core/dist/compiler-core.cjs.js', + }, + }, + server: { + port: 5174, // Use a different port to avoid conflicts with the main project + open: true, // Automatically open the browser + }, + // Remove build config because playground is for development and demo only + worker: { + format: 'es', + plugins: () => [ + replace({ + preventAssignment: true, + values: { + 'process.env.NODE_ENV': JSON.stringify('production'), + }, + }), + ], + }, + // Set project root directory to the playground directory + root: path.resolve(__dirname), + base: './', // Use relative path +}) diff --git a/vite.config.ts b/vite.config.ts index 1b007b87..1a31c12a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -49,6 +49,8 @@ export default mergeConfig(base, { 'monaco-editor-core/esm/vs/editor/editor.worker', 'vue/server-renderer', ], + // ignore playground just for dev + exclude: ['playground'], }, base: './', build: { From b6b2da510ebe642d120131b5dee51d709e14f408 Mon Sep 17 00:00:00 2001 From: zenheart Date: Thu, 15 May 2025 15:26:51 +0800 Subject: [PATCH 2/7] fix: fix readme error --- playground/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/README.md b/playground/README.md index 54c35e2e..c330a76e 100644 --- a/playground/README.md +++ b/playground/README.md @@ -60,7 +60,7 @@ cd vue-repl npm install # Start the development server -npm run playground +npm run dev:playground ``` Visit the displayed local address (usually http://localhost:5174/) to use the playground. From 8dde63c91caabee515636f3cc51221d51c048437 Mon Sep 17 00:00:00 2001 From: zenheart Date: Thu, 15 May 2025 16:33:01 +0800 Subject: [PATCH 3/7] fix: support Repl props config --- playground/scenarios/basic/_meta.js | 3 + playground/src/App.vue | 2 + playground/vite-plugin-scenario.js | 104 ++++++++++++++++------------ 3 files changed, 64 insertions(+), 45 deletions(-) diff --git a/playground/scenarios/basic/_meta.js b/playground/scenarios/basic/_meta.js index 39b877c3..67c0b6c7 100644 --- a/playground/scenarios/basic/_meta.js +++ b/playground/scenarios/basic/_meta.js @@ -1,3 +1,6 @@ export default { mainFile: 'App.vue', + ReplOptions: { + theme: 'dark', + }, } diff --git a/playground/src/App.vue b/playground/src/App.vue index b6d80029..802c1d3a 100644 --- a/playground/src/App.vue +++ b/playground/src/App.vue @@ -30,6 +30,7 @@ const currentScenario = computed(() => { }) watchEffect(() => { + console.log('currentScenario', currentScenario.value) if (currentScenario.value) { const scenario = currentScenario.value store.setFiles(scenario.files, scenario.mainFile) @@ -44,6 +45,7 @@ const replConfigs = computed(() => ({ editorOptions: { autoSaveText: '💾', }, + ...(currentScenario.value.ReplOptions || {}), })) diff --git a/playground/vite-plugin-scenario.js b/playground/vite-plugin-scenario.js index f75b9e65..b3d473a9 100644 --- a/playground/vite-plugin-scenario.js +++ b/playground/vite-plugin-scenario.js @@ -129,7 +129,7 @@ export default function scenarioPlugin() { } /** - * Scan scenario directory and generate config object + * Scan scenario directory and generate config object with parallel processing * @param {string} scenariosPath - Path to scenarios directory * @returns {Promise} - Config object */ @@ -154,16 +154,15 @@ async function generateConfig(scenariosPath) { const dirs = await fs.promises.readdir(scenariosPath) - for (const dir of dirs) { + // Process all scenario directories in parallel + const scenarioPromises = dirs.map(async (dir) => { const scenarioPath = path.join(scenariosPath, dir) const stat = await fs.promises.stat(scenarioPath) - if (!stat.isDirectory()) continue + if (!stat.isDirectory()) return null // Read all files in the scenario - const files = {} const allFiles = await fs.promises.readdir(scenarioPath) - let meta = { mainFile: 'main.ts' } // Default metadata // Handle metadata file first to get metadata before other files @@ -171,32 +170,23 @@ async function generateConfig(scenariosPath) { if (metaFile) { const metaFilePath = path.join(scenarioPath, metaFile) try { - // Read metadata file and manually parse - const metaContent = await fs.promises.readFile(metaFilePath, 'utf-8') - - // Extract default export part - // Use simple regex to match export default {...} - const defaultExportMatch = metaContent.match( - /export\s+default\s+({[\s\S]*?})/m, - ) - if (defaultExportMatch && defaultExportMatch[1]) { - try { - // Use Function constructor to safely parse JS object - // Safer than eval, but can handle JS object syntax - const extractedMeta = new Function( - `return ${defaultExportMatch[1]}`, - )() - meta = { ...meta, ...extractedMeta } - console.log( - `[vite-plugin-scenario] Loaded scenario metadata: ${dir}`, - meta, - ) - } catch (parseError) { - console.error( - `[vite-plugin-scenario] Failed to parse metadata: ${metaFilePath}`, - parseError, - ) - } + // Use dynamic import to properly load the ES module + // This is safer and more reliable than regex extraction + // Convert to absolute path URL for dynamic import + // Add timestamp to URL to bypass module cache + const fileUrl = `file://${path.resolve(metaFilePath)}?t=${Date.now()}` + + // Dynamically import the metadata module + const metaModule = await import(fileUrl) + + if (metaModule.default) { + // Deep merge the metadata with defaults + // This ensures all ReplOptions properties are properly merged + meta = { ...(meta || {}), ...(metaModule.default || {}) } + console.log( + `[vite-plugin-scenario] Loaded scenario metadata: ${dir}`, + meta, + ) } } catch (error) { console.error( @@ -206,25 +196,49 @@ async function generateConfig(scenariosPath) { } } - // Process all files in the scenario - for (const file of allFiles) { - // Skip hidden files and metadata file - if (file.startsWith('.') || file === '_meta.js') continue - - // Read file content - const filePath = path.join(scenarioPath, file) - const content = await fs.promises.readFile(filePath, 'utf-8') + // Read all scenario files in parallel + const filePromises = allFiles + .filter((file) => !file.startsWith('.') && file !== '_meta.js') + .map(async (file) => { + const filePath = path.join(scenarioPath, file) + try { + const content = await fs.promises.readFile(filePath, 'utf-8') + return [file, content] // Return as key-value pair + } catch (error) { + console.error( + `[vite-plugin-scenario] Error reading file: ${filePath}`, + error, + ) + return [file, ''] // Return empty content on error + } + }) - // Store file content in config - files[file] = content - } + // Wait for all file reading promises to complete + const fileEntries = await Promise.all(filePromises) + const files = Object.fromEntries(fileEntries) - // Build scenario config - config[dir] = { + // Build complete scenario configuration + // Structure it to match what REPL expects: files object + options + const scenarioConfig = { + // Files must be in this format for REPL files, + // all meta config support extend ...meta, } - } + + // Return scenario name and its configuration + return [dir, scenarioConfig] + }) + + // Wait for all scenario processing to complete + const scenarioResults = await Promise.all(scenarioPromises) + + // Filter out null results (non-directories) and build config object + scenarioResults + .filter((result) => result !== null) + .forEach(([scenarioName, scenarioConfig]) => { + config[scenarioName] = scenarioConfig + }) console.log( `[vite-plugin-scenario] Config generated, scenarios: ${Object.keys(config).join(', ')}`, From 43fcf80fe67830b21b286ec263839d4fa7f56bfa Mon Sep 17 00:00:00 2001 From: zenheart Date: Thu, 15 May 2025 18:04:55 +0800 Subject: [PATCH 4/7] fix: remove unused import --- playground/src/App.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/src/App.vue b/playground/src/App.vue index 802c1d3a..63818569 100644 --- a/playground/src/App.vue +++ b/playground/src/App.vue @@ -10,7 +10,7 @@ + +
+
+ + +
+
    +
  • + {{ todo.text }} + +
  • +
+
diff --git a/playground/src/App.vue b/playground/src/App.vue index 63818569..69f41acf 100644 --- a/playground/src/App.vue +++ b/playground/src/App.vue @@ -12,7 +12,7 @@ + + 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 @@ + + + + + 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" + } +} From a2910dde81ca41f765ce9a68a3c8ffb70a120ac1 Mon Sep 17 00:00:00 2001 From: zenheart Date: Mon, 19 May 2025 07:59:38 +0800 Subject: [PATCH 7/7] fix: fix style reset margin --- playground/README.md | 2 ++ playground/index.html | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/playground/README.md b/playground/README.md index c330a76e..57fb29ea 100644 --- a/playground/README.md +++ b/playground/README.md @@ -14,8 +14,10 @@ playground/ ├── 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 ``` diff --git a/playground/index.html b/playground/index.html index 98445152..90135e61 100644 --- a/playground/index.html +++ b/playground/index.html @@ -5,6 +5,11 @@ Playground +