Skip to content

Feat/set app preview: add app preview#1628

Merged
hexqi merged 7 commits intoopentiny:developfrom
betterdancing:feat/set-app-preview
Sep 17, 2025
Merged

Feat/set app preview: add app preview#1628
hexqi merged 7 commits intoopentiny:developfrom
betterdancing:feat/set-app-preview

Conversation

@betterdancing
Copy link
Copy Markdown
Contributor

@betterdancing betterdancing commented Sep 12, 2025

English | 简体中文

PR

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our Commit Message Guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)
  • Built its own designer, fully self-validated

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

Background and solution

用户搭建完以后并不清楚整个项目是否能正常运行,尤其牵扯到跨页面的时候只能出码。

What is the current behavior?

Issue Number: N/A

What is the new behavior?

添加了应用预览,使用户可以测试例如页面跳转,传递参数等完整的业务流程

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

  • New Features
    • Added selectable preview modes: Page Preview and App Preview.
    • Toolbar interactions are now configurable (hover or click).
  • Bug Fixes
    • None.
  • Chores
    • Updated generated project to use alias-based imports for cleaner paths.
    • Automatically includes toolbar CSS in built bundles.
    • Safer environment checks to avoid failures when env variables are absent.
    • Removed built-in HTTP mock adapter from the generated template (mock/disable controls no longer available).

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Sep 12, 2025

Walkthrough

Adds a preview type parameter across toolbar and preview flows, introducing page vs app preview branches and related HTTP fetches and code generation. Makes popover trigger configurable, updates toolbar UI to a two-option menu, adds a page list API, adjusts URL query building, switches many generator imports to alias paths, and removes axios mock.

Changes

Cohort / File(s) Summary
Toolbar trigger and slots
packages/common/component/ToolbarBase.vue, packages/common/component/toolbar-built-in/ToolbarBaseIcon.vue
Adds public prop trigger (default 'hover'); plumbs to state and binds to <tiny-popover> as :trigger. Adds default slot to popover; minor syntax updates.
Preview action and branching
packages/toolbars/preview/src/Main.vue, packages/design-core/src/preview/src/preview/usePreviewData.ts, packages/design-core/src/preview/src/preview/http.js, packages/common/js/preview.js
Toolbar now offers two actions and calls previewPage({ previewType }). usePreviewData branches by previewType (page vs app); adds app-level code/gen/router emission; imports fetchPageList. New HTTP helper fetchPageList(appId). previewType added to preview URL params.
Vue generator: alias-based imports
packages/vue-generator/src/plugins/genI18nPlugin.js, packages/vue-generator/src/templates/vue-template/templateFiles/src/App.vue, .../src/http/index.js, .../src/lowcodeConfig/dataSource.js, .../src/lowcodeConfig/lowcode.js, .../src/lowcodeConfig/store.js, .../src/main.js
Switches relative imports to alias paths (e.g., @/...) and adds explicit .js where applied; no logic changes. Guards import.meta.env access with optional chaining.
Axios template: remove mock
packages/vue-generator/src/templates/vue-template/templateFiles/src/http/axios.js
Removes axios-mock-adapter integration and exported mock/disableMock methods; core HTTP API unchanged.
Preview toolbar build config
packages/toolbars/preview/vite.config.ts
Adds Rollup banner to inject import "./style.css" in build output.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant TB as Toolbar (Main.vue)
  participant API as previewPage()
  participant UPD as usePreviewData.updatePreview
  participant HTTP as HTTP helpers
  participant FS as File Writer (setFiles)

  U->>TB: Click "页面预览" or "应用预览"
  TB->>API: previewPage({ previewType })
  API->>UPD: updatePreview(previewType)

  alt previewType === "page"
    UPD->>UPD: getBasicData(...) ➜ app/meta/importMap
    UPD->>HTTP: fetchBlockSchema(...)*
    note right of HTTP: Aggregates nested & current page blocks
    UPD->>UPD: build page ancestry files
    UPD->>FS: write App.js, page files, meta files
  else previewType === "app"
    UPD->>HTTP: fetch app schema
    UPD->>HTTP: fetchPageList(appId)
    UPD->>UPD: generate routes and app code
    UPD->>FS: write router, src files, import-map.json, App.js
  end

  UPD-->>API: done
  API-->>TB: open preview URL (with previewType)
  TB-->>U: Preview opens
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

Two clicks, two paths, I thump with glee—
Page or app, the previews spree.
A popover waits on click or hover,
Routes hop in, code hops over.
Mock is gone, aliases bloom—
Carrots compiled, we ship vroom vroom! 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Feat/set app preview: add app preview" accurately identifies the primary change—adding an application preview mode—and aligns with the PR objectives and diffs (introduction of previewType and an app preview flow), so it is specific and not generic for reviewers scanning history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added the enhancement New feature or request label Sep 12, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/vue-generator/src/templates/vue-template/templateFiles/src/http/index.js (1)

19-24: Propagate errors and guard against missing error.response in interceptor.

On network errors error.response can be undefined, and returning nothing swallows errors.

Apply this diff:

   http.interceptors.response.use(dataHandler, (error) => {
-    const response = error.response
-    if (response.status === 403 && response.headers && response.headers['x-login-url']) {
+    const response = error?.response
+    if (response?.status === 403 && response.headers && response.headers['x-login-url']) {
       // TODO 处理无权限时,重新登录再发送请求
     }
+    return Promise.reject(error)
   })
packages/vue-generator/src/templates/vue-template/templateFiles/src/lowcodeConfig/dataSource.js (1)

74-84: Do not swallow request/response errors in interceptors.

errorHandler updates state but never rejects; callers awaiting load() will not receive a rejection.

Apply this diff:

   const errorHandler = (error) => {
     if (config.errorHandler?.value) {
       createFn(config.errorHandler.value)(error)
     }
     dataSource.status = 'error'
     dataSource.error = error
+    return Promise.reject(error)
   }
🧹 Nitpick comments (3)
packages/vue-generator/src/templates/vue-template/templateFiles/src/lowcodeConfig/dataSource.js (1)

19-26: Avoid eval for user-supplied functions (security, stability).

Prefer new Function(...) with explicit args and a constrained scope; at minimum, document the risk and gate by trust level.

packages/toolbars/preview/src/Main.vue (1)

3-9: Consider adding visual distinction between preview options

The two preview options look identical to users. Consider adding icons or different styling to help users distinguish between page and app preview modes.

You could enhance the UI with icons:

 <div class="toolbar-preview-item">
-  <span @click="preview('page')">页面预览</span>
-  <span @click="preview('app')">应用预览</span>
+  <span @click="preview('page')">
+    <svg-icon name="page-icon"></svg-icon>
+    页面预览
+  </span>
+  <span @click="preview('app')">
+    <svg-icon name="app-icon"></svg-icon>
+    应用预览
+  </span>
 </div>
packages/design-core/src/preview/src/preview/usePreviewData.ts (1)

571-571: Hardcoded Element Plus CSS removal is brittle

The hardcoded string replacement for Element Plus CSS could break if the import format changes.

Use a more flexible pattern:

-srcFiles['main.js'] = srcFiles['main.js'].replace("import 'element-plus/dist/index.css'", '')
+// Remove any element-plus CSS imports with various formats
+srcFiles['main.js'] = srcFiles['main.js'].replace(/import\s+['"]element-plus\/[^'"]*\.css['"]\s*;?/g, '')
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e61fcd and 3441327.

📒 Files selected for processing (14)
  • packages/common/component/ToolbarBase.vue (1 hunks)
  • packages/common/component/toolbar-built-in/ToolbarBaseIcon.vue (2 hunks)
  • packages/common/js/preview.js (1 hunks)
  • packages/design-core/src/preview/src/preview/http.js (1 hunks)
  • packages/design-core/src/preview/src/preview/usePreviewData.ts (3 hunks)
  • packages/toolbars/preview/src/Main.vue (4 hunks)
  • packages/vue-generator/src/plugins/genI18nPlugin.js (2 hunks)
  • packages/vue-generator/src/templates/vue-template/templateFiles/src/App.vue (1 hunks)
  • packages/vue-generator/src/templates/vue-template/templateFiles/src/http/axios.js (0 hunks)
  • packages/vue-generator/src/templates/vue-template/templateFiles/src/http/index.js (1 hunks)
  • packages/vue-generator/src/templates/vue-template/templateFiles/src/lowcodeConfig/dataSource.js (2 hunks)
  • packages/vue-generator/src/templates/vue-template/templateFiles/src/lowcodeConfig/lowcode.js (1 hunks)
  • packages/vue-generator/src/templates/vue-template/templateFiles/src/lowcodeConfig/store.js (1 hunks)
  • packages/vue-generator/src/templates/vue-template/templateFiles/src/main.js (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/vue-generator/src/templates/vue-template/templateFiles/src/http/axios.js
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-07-03T09:22:59.512Z
Learnt from: hexqi
PR: opentiny/tiny-engine#1501
File: mockServer/src/tool/Common.js:79-82
Timestamp: 2025-07-03T09:22:59.512Z
Learning: In the tiny-engine project, the mockServer code uses ES6 import syntax but is compiled to CommonJS output. This means CommonJS globals like `__dirname` are available at runtime, while ES6 module-specific features like `import.meta` would cause runtime errors.

Applied to files:

  • packages/design-core/src/preview/src/preview/usePreviewData.ts
📚 Learning: 2024-10-10T02:47:46.239Z
Learnt from: yy-wow
PR: opentiny/tiny-engine#850
File: packages/toolbars/preview/src/Main.vue:16-16
Timestamp: 2024-10-10T02:47:46.239Z
Learning: In `packages/toolbars/preview/src/Main.vue`, within the `preview` function, the `getMergeMeta` method is used at lines 64 and 65 to retrieve `engine.config` configurations.

Applied to files:

  • packages/toolbars/preview/src/Main.vue
📚 Learning: 2024-10-10T02:48:10.881Z
Learnt from: yy-wow
PR: opentiny/tiny-engine#850
File: packages/toolbars/preview/src/Main.vue:0-0
Timestamp: 2024-10-10T02:48:10.881Z
Learning: 在 `packages/toolbars/preview/src/Main.vue` 文件中,使用 `useNotify` 而不是 `console` 来记录错误日志。

Applied to files:

  • packages/toolbars/preview/src/Main.vue
🧬 Code graph analysis (3)
packages/vue-generator/src/plugins/genI18nPlugin.js (1)
packages/vue-generator/src/utils/generateImportStatement.js (1)
  • generateImportStatement (2-16)
packages/design-core/src/preview/src/preview/http.js (2)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-23)
packages/design-core/src/preview/src/preview/usePreviewData.ts (5)
packages/register/src/common.ts (1)
  • getMetaApi (20-30)
packages/design-core/src/preview/src/preview/http.js (4)
  • fetchBlockSchema (40-41)
  • fetchBlockSchema (40-41)
  • fetchPageList (48-48)
  • fetchPageList (48-48)
packages/vue-generator/src/generator/page.js (1)
  • generatePageCode (464-502)
packages/design-core/src/preview/src/preview/generate.js (4)
  • processAppJsCode (153-167)
  • processAppJsCode (153-167)
  • res (158-158)
  • result (21-21)
packages/design-core/src/preview/src/preview/importMap.js (3)
  • importMap (16-16)
  • getImportMap (40-47)
  • getImportMap (40-47)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: push-check
🔇 Additional comments (14)
packages/vue-generator/src/templates/vue-template/templateFiles/src/App.vue (1)

8-8: Ensure '@' alias is configured in generated projects.

Switching to '@/i18n/index.js' assumes the template’s bundler (e.g., Vite) defines '@' → 'src'. Please verify the generator also emits/updates the alias in the target app’s config to avoid unresolved imports.

packages/vue-generator/src/templates/vue-template/templateFiles/src/main.js (1)

14-16: LGTM: alias-based imports.

The move to '@/router/index.js' and '@/App.vue' looks good and aligns with the template-wide change.

packages/vue-generator/src/plugins/genI18nPlugin.js (1)

61-62: LGTM: alias paths in generated index.js imports.

The switch to '@/lowcodeConfig/lowcode.js' and '@/i18n/...’ is consistent with the alias migration.

packages/vue-generator/src/templates/vue-template/templateFiles/src/http/index.js (1)

13-14: LGTM: alias-based http module imports.

Consistent with template changes; explicit .js extensions are fine with Vite ESM.

packages/vue-generator/src/templates/vue-template/templateFiles/src/lowcodeConfig/store.js (1)

1-1: LGTM: explicit index import.

Using '@/stores/index.js' is clear and avoids resolver differences.

packages/vue-generator/src/templates/vue-template/templateFiles/src/lowcodeConfig/lowcode.js (1)

16-19: LGTM: alias imports for lowcode runtime deps.

Matches the broader alias migration; no functional changes observed.

packages/vue-generator/src/templates/vue-template/templateFiles/src/lowcodeConfig/dataSource.js (3)

13-15: LGTM: alias-based imports for http and dataSources.

Keeps generated code consistent with the new path strategy.


85-98: Guard http.mock usage or provide fallback.

If the axios adapter doesn’t expose mock, this will throw. Add a capability check.

Apply this diff:

-  if (import.meta.env?.VITE_APP_MOCK === 'mock') {
-    http.mock([
+  if (import.meta.env?.VITE_APP_MOCK === 'mock' && typeof http.mock === 'function') {
+    http.mock([
       {
         url: config.options?.uri,
         response() {
           return Promise.resolve([200, { data: config.data }])
         }
       },
       {
         url: '*',
         proxy: '*'
       }
     ])
   }

85-85: LGTM: optional chaining on import.meta.env.

Prevents crashes in non-Vite or non-browser contexts.

packages/common/component/ToolbarBase.vue (1)

36-39: LGTM! Clean prop addition for trigger control

The trigger prop is properly defined with a sensible default value that maintains backward compatibility.

packages/design-core/src/preview/src/preview/http.js (1)

48-48: LGTM! Consistent with existing API patterns

The new fetchPageList function follows the established pattern for API endpoints in this file.

packages/common/component/toolbar-built-in/ToolbarBaseIcon.vue (1)

3-3: LGTM! Proper dynamic trigger binding implementation

The trigger prop is correctly defined and bound to the TinyPopover component, enabling flexible popover behavior control.

Also applies to: 41-44

packages/toolbars/preview/src/Main.vue (1)

32-32: LGTM! Clean parameter passing for preview type

The preview function signature change and parameter passing are implemented correctly. Using { previewType } for the object literal is clean and follows ES6 best practices.

Also applies to: 62-62

packages/design-core/src/preview/src/preview/usePreviewData.ts (1)

509-512: Potential null pointer exception in route generation

If item.meta is undefined, accessing item.meta.isHome or item.meta.id will throw an error.

Add null checks:

 pageSchema.forEach((item) => {
-  if ((item.meta?.isHome || item.meta?.isDefault) && !isGetHome) {
-    home.redirect = { name: `${item.meta.id}` }
+  if ((item.meta?.isHome || item.meta?.isDefault) && !isGetHome && item.meta?.id) {
+    home.redirect = { name: `${item.meta.id}` }
     isGetHome = true
   }
⛔ Skipped due to learnings
Learnt from: gene9831
PR: opentiny/tiny-engine#830
File: packages/common/component/MetaChildItem.vue:50-56
Timestamp: 2024-10-15T02:45:17.168Z
Learning: In `packages/common/component/MetaChildItem.vue`, when checking if `text` is an object in the computed property `title`, ensure that `text` is not `null` because `typeof null === 'object'` in JavaScript. Use checks like `text && typeof text === 'object'` to safely handle `null` values.

@hexqi hexqi added this to the v2.9.0 milestone Sep 15, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (8)
designer-demo/useData.js (8)

49-69: Avoid Number(null) pitfalls in URL checks; compare as strings.

Prevents accidental 0/NaN comparisons.

-    (params.page_content.componentName === COMPONENT_NAME.Block && !pageId && Number(blockId) === params.id) ||
+    (params.page_content.componentName === COMPONENT_NAME.Block && !pageId && blockId === String(params.id)) ||
@@
-    (params.page_content.componentName === COMPONENT_NAME.Page && !blockId && Number(pageId) === params.id)
+    (params.page_content.componentName === COMPONENT_NAME.Page && !blockId && pageId === String(params.id))

165-166: Use constants for service tokens to avoid stringly-typed mistakes.

-  const { generatePageCode } = getMetaApi('engine.service.generateCode')
+  const { generatePageCode } = getMetaApi(META_SERVICE.GenerateCode)
@@
-      const { getAllNestedBlocksSchema, generatePageCode } = getMetaApi('engine.service.generateCode')
+      const { getAllNestedBlocksSchema, generatePageCode } = getMetaApi(META_SERVICE.GenerateCode)
@@
-      const { getAllNestedBlocksSchema, generateAppCode } = getMetaApi('engine.service.generateCode')
+      const { getAllNestedBlocksSchema, generateAppCode } = getMetaApi(META_SERVICE.GenerateCode)

Also applies to: 378-381, 416-417


534-561: Harden route generation: sanitize router field and handle empty pages.

-        const pageSchema = (schema.pageSchema || []).sort((a, b) => a.meta?.router?.length - b.meta?.router?.length)
+        const pageSchema = (schema.pageSchema || []).sort(
+          (a, b) => (String(a.meta?.router || '').length - String(b.meta?.router || '').length)
+        )
@@
-          const newNode = {
-            path: `/${item.meta.router}`,
+          const safeRouter = String(item.meta?.router || '').replace(/^\/+/, '')
+          const newNode = {
+            path: `/${safeRouter}`,
             component: item.fileName,
             name: `${item.meta.id}`
           }
@@
-        if (!isGetHome) {
+        if (!isGetHome && result.length) {
           isGetHome = true
           home.redirect = { name: result[0]?.name }
+        } else if (!result.length) {
+          home.redirect = undefined
         }

583-589: Alias replacement regex misses double quotes and over-captures.

Safer replacement that handles both quotes:

-        } else {
-          fileContent = fileContent.replace(/(from\s*')(@)(\/.*')/g, '$1.$3')
-        }
+        } else {
+          // replace "@/foo" -> "./foo" after from '<quote>'
+          fileContent = fileContent.replace(/(from\s*['"])@(?=\/)/g, '$1.')
+        }

589-602: App.vue patch is brittle; ensure component availability and script style.

The template injects but the import only adds TinySelect; without script-setup or global registration, the component won’t render. Verify App.vue uses <script setup> or register TinySelect in components; otherwise switch tag to and register. Also ensure these replacements don’t fail if the exact source strings differ.

I can generate a safer AST-based transform or a regex guarded by presence checks.


606-619: Shadowing imported srcFiles with a local const is confusing; rename.

-      const newFileRes = fileRes.filter((item) => item.filePath.includes('src/'))
-      const srcFiles = newFileRes.reduce((prev, item) => {
+      const newFileRes = fileRes.filter((item) => item.filePath.includes('src/'))
+      const generatedFiles = newFileRes.reduce((prev, item) => {
         const fileName = item.filePath
         prev[fileName] = formatCode(item.fileContent, fileName)
         return prev
       }, {})
-      srcFiles['import-map.json'] = JSON.stringify(importMap)
+      generatedFiles['import-map.json'] = JSON.stringify(importMap)
       const newFiles = store.getFiles()
       const appJsCode = processAppJsCode(newFiles['app.js'], JSON.parse(searchParams.get('styles') || '[]'))
-      srcFiles['app.js'] = appJsCode
-      srcFiles['main.js'] = `import app from './app.js' \n ${srcFiles['src/main.js']}`
-      srcFiles['main.js'] = srcFiles['main.js'].replace("import 'element-plus/dist/index.css'", '')
-      setFiles(srcFiles, 'src/main.js')
+      generatedFiles['app.js'] = appJsCode
+      generatedFiles['main.js'] = `import app from './app.js' \n ${generatedFiles['src/main.js']}`
+      generatedFiles['main.js'] = generatedFiles['main.js'].replace("import 'element-plus/dist/index.css'", '')
+      setFiles(generatedFiles, 'src/main.js')

254-299: Verify CSS vs JS links when adding utils packages.

Using item.content.cdnLink as both script and css can inject a JS URL as stylesheet. Confirm utils provide a CSS link or split fields accordingly.


343-345: setFiles called immediately for basicFiles: ensure the returned promise is actually awaited upstream.

You store the promise and rely on it in getBasicData’s Promise.all. That’s fine, but confirm setFiles is idempotent and safe to call again later.

Optionally rename basicFiles to basicFilesPromise for clarity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3441327 and 0b65302.

📒 Files selected for processing (1)
  • designer-demo/useData.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
designer-demo/useData.js (5)
packages/design-core/src/preview/src/preview/usePreviewData.ts (2)
  • previewState (32-42)
  • usePreviewData (343-593)
packages/vue-generator/src/generator/page.js (2)
  • generatePageCode (464-502)
  • generateCode (516-535)
packages/register/src/common.ts (1)
  • getMergeMeta (53-55)
packages/register/src/constants.ts (1)
  • META_SERVICE (1-23)
packages/design-core/src/preview/src/preview/srcFiles.js (1)
  • srcFiles (28-28)
🪛 Biome (2.1.2)
designer-demo/useData.js

[error] 23-33: interface are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 34-39: type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 45-45: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 78-78: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 78-78: return type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 83-84: return type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 100-100: type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 129-129: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 154-159: interface are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 159-163: interface are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 165-167: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 168-168: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 168-169: type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 245-246: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 246-247: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 247-249: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 256-256: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 264-264: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 267-267: Const declarations must have an initialized value.

This variable needs to be initialized.

(parse)


[error] 267-267: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 268-268: Parenthesized expression didnt contain anything

Expected an expression here

(parse)


[error] 271-272: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 277-277: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 286-286: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 306-307: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 307-307: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 314-314: type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 338-343: interface are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 343-346: interface are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 349-351: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 357-357: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 358-358: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 364-365: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 82-82: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 267-267: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (5)
packages/design-core/src/preview/src/preview/usePreviewData.ts (5)

337-344: Signature alignment restored (setImportMap) — good catch.

The breaking change noted earlier is resolved: IUsePreviewData includes setImportMap and the hook destructures it. No further action needed.


566-582: Consider extracting routing helpers into a module.

getRoutesAndImportSet/getRouterFile/buildTreeRoutes are sizeable and reusable; move to routeGenerator.ts for testability.


422-497: Validate API responses inside getPreGenerateInfo.

Assume nothing from the network: verify shapes; degrade gracefully; log once.

-      const getPreGenerateInfo = async () => {
+      const getPreGenerateInfo = async () => {
         const promises = [
           getMetaApi(META_SERVICE.Http).get(`/app-center/v1/api/apps/schema/${appId}`),
           fetchPageList(appId)
         ]
-
-        const [appData, pageList] = await Promise.all(promises)
-        const pageDetailList = pageList
+        try {
+          const [appData, pageList] = await Promise.all(promises)
+          if (!appData || typeof appData !== 'object') {
+            throw new Error('Invalid app schema received')
+          }
+          const pageDetailList = Array.isArray(pageList) ? pageList : []
@@
-        const res = await generateAppCode(appSchema)
-
-        const { genResult = [] } = res || {}
+          const res = await generateAppCode(appSchema)
+          if (!res || !Array.isArray(res.genResult)) {
+            throw new Error('Invalid generator output')
+          }
+          const { genResult = [] } = res
@@
-        return fileRes
+          return fileRes
+        } catch (e) {
+          console.error('[preview:app] getPreGenerateInfo failed:', e)
+          return []
+        }
       }

584-605: Make import path replacement and App.vue injection more robust.

The current regex only matches single-quoted static imports and may miss dynamic imports; App.vue string replaces are brittle.

-        if (fileName === 'src/router/index.js') {
+        if (fileName === 'src/router/index.js') {
           fileContent = getRouterFile(appSchema)
         } else {
-          fileContent = fileContent.replace(/(from\s*')(@)(\/.*')/g, '$1.$3')
+          // Handle single/double quotes and dynamic imports
+          fileContent = fileContent
+            .replace(/(from\s*['"])(@)(\/[^'"]*['"])/g, '$1.$3')
+            .replace(/(import\s*\(\s*['"])(@)(\/[^'"]*['"]\s*\))/g, '$1.$3')
         }
         if (fileName === 'src/App.vue') {
-          fileContent = fileContent.replace(
+          // Avoid double-injection
+          if (!/tiny-select/.test(fileContent)) {
+            fileContent = fileContent.replace(
             '<router-view></router-view>',
             `<tiny-select v-model="currentRoute" placeholder="请选择路由" render-type="tree" :tree-op="{ data: $router.options.routes.filter(item => item.path !== '/') }" text-field="path" value-field="path" @change="routeChange"></tiny-select>\n<router-view></router-view>`
-          )
-          fileContent = fileContent.replace(
-            `import { provide } from 'vue'`,
-            `import { Select as TinySelect } from '@opentiny/vue'\nimport { ref, provide, watchEffect } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'`
-          )
-          fileContent = fileContent.replace(
-            `provide(I18nInjectionKey, i18n)`,
-            `const route = useRoute()\nconst router = useRouter()\nconst currentRoute = ref()\n\nwatchEffect(() => {\n\tcurrentRoute.value = route.path\n})\n\nconst routeChange = () => {\n\trouter.push(currentRoute.value)\n}\nprovide(I18nInjectionKey, i18n)`
-          )
+            )
+          }
+          fileContent = fileContent
+            .replace(
+              /import\s*\{\s*provide\s*\}\s*from\s*['"]vue['"]/,
+              `import { Select as TinySelect } from '@opentiny/vue'\nimport { ref, provide, watchEffect } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'`
+            )
+            .replace(
+              /provide\(I18nInjectionKey,\s*i18n\)/,
+              `const route = useRoute()\nconst router = useRouter()\nconst currentRoute = ref()\n\nwatchEffect(() => { currentRoute.value = route.path })\n\nconst routeChange = () => { router.push(currentRoute.value) }\nprovide(I18nInjectionKey, i18n)`
+            )
         }

415-626: Harden app preview: input validation, error handling, and awaiting setFiles.

The app branch performs multiple async operations with unchecked inputs and no catch. JSON.parse on URL params can throw; appId may be null; failures bubble and leave the UI in an indeterminate state. Also, setFiles isn’t awaited.

-    } else if (previewType === 'app') {
-      const appId = searchParams.get('id')
+    } else if (previewType === 'app') {
+      try {
+        const appId = searchParams.get('id')
+        if (!appId) {
+          throw new Error('Missing "id" query param for app preview')
+        }
         const { getAllNestedBlocksSchema, generateAppCode } = getMetaApi('engine.service.generateCode')
-        const importMap = await getImportMap(JSON.parse(searchParams.get('scripts') || '{}'))
+        const safeParse = (str, fb) => { try { return JSON.parse(str ?? '') } catch { return fb } }
+        const importMap = await getImportMap(safeParse(searchParams.get('scripts'), {}))
@@
-      const fileRes = await getPreGenerateInfo()
+      const fileRes = await getPreGenerateInfo()
       const newFileRes = fileRes.filter((item) => item.filePath.includes('src/'))
-      const srcFiles = newFileRes.reduce((prev, item) => {
+      const emittedSrcFiles = newFileRes.reduce((prev, item) => {
         const fileName = item.filePath
         prev[fileName] = formatCode(item.fileContent, fileName)
         return prev
       }, {})
-      srcFiles['import-map.json'] = JSON.stringify(importMap)
+      emittedSrcFiles['import-map.json'] = JSON.stringify(importMap)
       const newFiles = store.getFiles()
       const enableTailwindCSS = getMergeMeta('engine.config')?.enableTailwindCSS
-      const appJsCode = processAppJsCode(
-        newFiles['app.js'],
-        JSON.parse(searchParams.get('styles') || '[]'),
-        enableTailwindCSS
-      )
-      srcFiles['app.js'] = appJsCode
-      srcFiles['main.js'] = `import app from './app.js' \n ${srcFiles['src/main.js']}`
-      srcFiles['main.js'] = srcFiles['main.js'].replace("import 'element-plus/dist/index.css'", '')
-      setFiles(srcFiles, 'src/main.js')
+      const styles = safeParse(searchParams.get('styles'), [])
+      const appJsCode = processAppJsCode(newFiles['app.js'], styles, enableTailwindCSS)
+      emittedSrcFiles['app.js'] = appJsCode
+      emittedSrcFiles['main.js'] = `import app from './app.js'\n${emittedSrcFiles['src/main.js'] || ''}`
+      emittedSrcFiles['main.js'] = emittedSrcFiles['main.js'].replace(/import\s+['"]element-plus\/dist\/index\.css['"];?/g, '')
+      await setFiles(emittedSrcFiles, 'src/main.js')
+      } catch (error) {
+        console.error('Failed to generate app preview:', error)
+        await setFiles({ 'src/main.js': "console.error('App preview failed');" }, 'src/main.js')
+      }
     }

Also await the page branch’s setFiles for consistency:

-      setFiles(newFiles, 'App.vue')
+      await setFiles(newFiles, 'App.vue')
🧹 Nitpick comments (5)
packages/design-core/src/preview/src/preview/usePreviewData.ts (4)

535-564: Guard home redirect when no routes; stable sort.

If pageSchema is empty, home.redirect.name becomes undefined.

-        const pageSchema = (schema.pageSchema || []).sort((a, b) => a.meta?.router?.length - b.meta?.router?.length)
+        const pageSchema = (schema.pageSchema || []).slice().sort((a, b) => {
+          const al = (a.meta?.router || '').length
+          const bl = (b.meta?.router || '').length
+          return al - bl
+        })
@@
-        if (!isGetHome) {
-          isGetHome = true
-          home.redirect = { name: result[0]?.name }
-        }
+        if (!isGetHome && result.length) {
+          isGetHome = true
+          home.redirect = { name: result[0].name }
+        }

607-626: Avoid shadowing imported srcFiles; remove Element Plus CSS robustly; await setFiles.

Shadowing srcFiles impairs readability and risks confusion with the imported singleton. Also make CSS removal regex-agnostic.

-      const newFileRes = fileRes.filter((item) => item.filePath.includes('src/'))
-      const srcFiles = newFileRes.reduce((prev, item) => {
+      const newFileRes = fileRes.filter((item) => item.filePath.includes('src/'))
+      const emittedSrcFiles = newFileRes.reduce((prev, item) => {
         const fileName = item.filePath
         prev[fileName] = formatCode(item.fileContent, fileName)
         return prev
       }, {})
-      srcFiles['import-map.json'] = JSON.stringify(importMap)
+      emittedSrcFiles['import-map.json'] = JSON.stringify(importMap)
@@
-      srcFiles['app.js'] = appJsCode
-      srcFiles['main.js'] = `import app from './app.js' \n ${srcFiles['src/main.js']}`
-      srcFiles['main.js'] = srcFiles['main.js'].replace("import 'element-plus/dist/index.css'", '')
-      setFiles(srcFiles, 'src/main.js')
+      emittedSrcFiles['app.js'] = appJsCode
+      emittedSrcFiles['main.js'] = `import app from './app.js'\n${emittedSrcFiles['src/main.js'] || ''}`
+      emittedSrcFiles['main.js'] = emittedSrcFiles['main.js'].replace(/import\s+['"]element-plus\/dist\/index\.css['"];?/g, '')
+      await setFiles(emittedSrcFiles, 'src/main.js')

414-415: Await setFiles in page branch.

Prevent race conditions in subsequent updates.

-      setFiles(newFiles, 'App.vue')
+      await setFiles(newFiles, 'App.vue')

416-419: Guard JSON.parse of URL scripts param.

JSON.parse(searchParams.get('scripts') || '{}') can throw for malformed query values — wrap in a try/catch or use a safeParse helper and default to {} before calling getImportMap.
Location: packages/design-core/src/preview/src/preview/usePreviewData.ts (≈ lines 416–419).

packages/toolbars/preview/vite.config.ts (1)

33-35: Consider a plugin instead of manual injection (optional)

If you prefer a maintained solution that handles multi‑entry and SSR builds, use vite-plugin-lib-inject-css and drop the banner hack. (github.com)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b65302 and 7a3b988.

📒 Files selected for processing (3)
  • packages/design-core/src/preview/src/preview/usePreviewData.ts (2 hunks)
  • packages/toolbars/preview/vite.config.ts (1 hunks)
  • packages/vue-generator/src/templates/vue-template/templateFiles/src/main.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/vue-generator/src/templates/vue-template/templateFiles/src/main.js
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-01-14T06:59:23.602Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/src/page-block-function/methods.ts:9-21
Timestamp: 2025-01-14T06:59:23.602Z
Learning: The code in packages/canvas/render/src/page-block-function/methods.ts is migrated code that should not be modified during the migration phase. Error handling improvements can be addressed in future PRs.

Applied to files:

  • packages/design-core/src/preview/src/preview/usePreviewData.ts
📚 Learning: 2024-10-30T02:38:05.165Z
Learnt from: yy-wow
PR: opentiny/tiny-engine#886
File: packages/toolbars/generate-code/src/http.js:39-40
Timestamp: 2024-10-30T02:38:05.165Z
Learning: In the function `fetchBlockSchema` in `packages/toolbars/generate-code/src/http.js`, input validation and error handling for the `blockName` parameter are handled by the user elsewhere, so it's acceptable not to perform parameter validation or URL encoding in this function within this codebase.

Applied to files:

  • packages/design-core/src/preview/src/preview/usePreviewData.ts
📚 Learning: 2025-07-03T09:22:59.512Z
Learnt from: hexqi
PR: opentiny/tiny-engine#1501
File: mockServer/src/tool/Common.js:79-82
Timestamp: 2025-07-03T09:22:59.512Z
Learning: In the tiny-engine project, the mockServer code uses ES6 import syntax but is compiled to CommonJS output. This means CommonJS globals like `__dirname` are available at runtime, while ES6 module-specific features like `import.meta` would cause runtime errors.

Applied to files:

  • packages/design-core/src/preview/src/preview/usePreviewData.ts
🧬 Code graph analysis (1)
packages/design-core/src/preview/src/preview/usePreviewData.ts (6)
packages/register/src/common.ts (2)
  • getMetaApi (20-30)
  • getMergeMeta (53-55)
packages/design-core/src/preview/src/preview/http.js (4)
  • fetchBlockSchema (40-41)
  • fetchBlockSchema (40-41)
  • fetchPageList (48-48)
  • fetchPageList (48-48)
packages/design-core/src/preview/src/preview/generate.js (3)
  • processAppJsCode (153-169)
  • processAppJsCode (153-169)
  • res (154-154)
packages/design-core/src/preview/src/preview/importMap.js (3)
  • importMap (16-16)
  • getImportMap (39-46)
  • getImportMap (39-46)
packages/vue-generator/src/plugins/genRouterPlugin.js (2)
  • home (57-59)
  • isGetHome (60-60)
packages/design-core/src/preview/src/preview/srcFiles.js (1)
  • srcFiles (28-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: push-check
🔇 Additional comments (2)
packages/design-core/src/preview/src/preview/usePreviewData.ts (1)

5-13: HTTP imports look consistent with the new flow.

New fetchPageList import aligns with the app preview. No issues spotted here.

packages/toolbars/preview/vite.config.ts (1)

33-35: Scope CSS import to the entry chunk and make the CSS filename deterministic (packages/toolbars/preview/vite.config.ts)

  • output.banner as a plain string is injected into every chunk; gate it to the entry and add a semicolon/newline. Stabilize the emitted CSS filename via build.lib.cssFileName (or update the import to the actual emitted filename).
-      output: {
-        banner: 'import "./style.css"'
-      },
+      output: {
+        banner: (chunk) => (chunk.isEntry ? 'import "./style.css";\n' : '')
+      },
 build: {
   sourcemap: true,
   lib: {
     entry: path.resolve(__dirname, './index.ts'),
     name: 'toolbar-preview',
+    cssFileName: 'style',
     fileName: (_format, entryName) => `${entryName}.js`,
     formats: ['es']
   },

I could not verify the repo's Vite version or whether build.lib.cssFileName is supported from the sandbox — confirm locally that the workspace uses a Vite version that supports cssFileName (or adjust the import to the actual emitted CSS filename).

@hexqi
Copy link
Copy Markdown
Collaborator

hexqi commented Sep 17, 2025

先行合入,遗留的下面意见继续修改:
image
image
image

@hexqi hexqi merged commit f45f686 into opentiny:develop Sep 17, 2025
2 checks passed
@betterdancing betterdancing deleted the feat/set-app-preview branch September 23, 2025 02:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants