-
Notifications
You must be signed in to change notification settings - Fork 431
feat: add runtime rendering solution #1661
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: ospp-2025/runtime-rendering
Are you sure you want to change the base?
feat: add runtime rendering solution #1661
Conversation
Majority is the same as builtin components in package/canvas/renderer. But CanvasRouterLink.vue, CanvasRouterView.vue, and CanvasCollection.vue had changed.
Referenced the implementation of tiny-schema-renderer repo and canvas/render
There are some bugs in lifecycle execution(onMount, onBeforeUpdate, onUpdated, onErrorCaptured, onUnmounted)
Support homepage setting, nested routes, and default navigation to subpages for nested routes
Mainly based on the vue-generator code. Currently in a mixed state of JavaScript and TypeScript, which will be improved later.
Support using destructured npm package imports
In useAppSchema.ts, replace the actual requests in fetchAppSchema(appId) and fetchBlocks() with mock files to use mock files for testing.
Caution Review failedFailed to post review comments WalkthroughAdds a new runtime rendering capability and an online deploy toolbar. Introduces a runtime renderer package, bootstraps a demo runtime entry, wires dev aliases and TS paths, adds deploy utilities, integrates a RuntimeDeploy toolbar into design-core/layout, and implements router, stores, renderer, data source, HTTP, utilities, and components for runtime rendering. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant TB as Toolbar(RuntimeDeploy)
participant DU as runtime-deploy.js
participant Win as Browser
participant RT as Runtime HTML/JS
participant RR as initRuntimeRenderer
participant AS as useAppSchema
participant RTU as Router/Stores/App
U->>TB: Click "在线部署运行"
TB->>DU: runtimeDeploy()
DU->>DU: deployPage() -> build openUrl
alt Existing window open
DU->>Win: focus(runtimeWindow) (try/catch)
else No window
DU->>Win: window.open(openUrl, 'tiny-engine-runtime')
end
Win->>RT: Load runtime.html -> /src/runtime.js
RT->>RR: initRuntimeRenderer()
RR->>AS: fetchAppSchema(appId), fetchBlocks()
AS-->>RR: app schema, blocks ready
RR->>RTU: create router (from pages), init Pinia/stores, mount App
RTU-->>U: Runtime app renders via router-view
sequenceDiagram
autonumber
participant Router as createAppRouter
participant Pages as AppSchema.pages
participant VRouter as VueRouter
participant PR as PageRenderer
participant RM as RenderMain
participant R as renderer
Router->>Pages: Build routes (home, defaults, children)
Router->>VRouter: createRouter (hash history '/runtime.html')
VRouter-->>Router: router instance
VRouter->>PR: Lazy load for route(pageId)
PR->>RM: RenderMain(pageId)
RM->>R: Render schema tree
R-->>RM: VNode tree
RM-->>PR: Page content mounted
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120+ minutes Poem
Pre-merge checks and finishing touches and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests
✨ Finishing touches
🧪 Generate unit tests
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. 🧪 Early access (Sonnet 4.5): enabledWe are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience. Note:
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 48
♻️ Duplicate comments (2)
packages/runtime-renderer/src/renderer/page-function/css-handler.ts (1)
90-104
: See earlier comment on global state race condition.This function mutates the global
enableScoped
flag, which is the source of the race condition flagged in lines 19-20. Refer to that comment for the recommended fix.packages/runtime-renderer/src/types/schema.ts (1)
238-245
: Same API response pattern as AppSchema.This interface has the same design issues as
AppSchema
(lines 2-22): mixing response metadata with data, and redundant error fields. Consider applying the same refactoring approach to separateApiResponse<T>
fromBlockData
.
🧹 Nitpick comments (62)
packages/runtime-renderer/src/mock/blocks.json (2)
329-332
: Remove the empty dependency object.The empty
components
object serves no purpose and clutters the configuration.Apply this diff:
"dependencies": { "scripts": [ - { - "components": {} - }, { "package": "@opentiny/vue", "components": { "TinyButton": "Button" } } ],
322-325
: Replace magic number with a descriptive log message.The
console.log(123456)
uses a magic number that provides no context. For test/debug mock data, a descriptive message would be more helpful.Consider:
"onClickNew1": { "type": "JSFunction", - "value": "function onClickNew1(event) {\n this.state.stateblock1.button++\n console.log(123456)\n}\n" + "value": "function onClickNew1(event) {\n this.state.stateblock1.button++\n console.log('Button counter incremented:', this.state.stateblock1.button)\n}\n" }packages/runtime-renderer/src/App.vue (1)
2-2
: Consider whether forced remounting is necessary.Using
:key="$route.fullPath"
forces a full component remount on every route change, which destroys component state and prevents Vue Router's built-in transition/keep-alive optimizations. This pattern is typically only needed when:
- Child components don't properly react to route param changes
- You need to reset component state between routes with identical components
For most runtime rendering scenarios, the default router-view behavior (remounting only when the component changes) is more performant and preserves user interactions better.
If forced remounting is required for your schema-driven rendering, consider documenting why in a comment. Otherwise, try removing the key binding:
- <router-view :key="$route.fullPath" /> + <router-view />Verify that route navigation and component updates still work correctly after this change.
packages/runtime-renderer/src/renderer/builtin/CanvasText.vue (1)
1-18
: LGTM! Consider optional modernization.The component implementation is correct with proper prop typing and defaults. The class binding correctly combines static and dynamic classes.
For consistency with other new components in this PR (like PageRenderer.vue which uses script setup), you could optionally modernize to Composition API:
<template> <span :class="['canvas-text', className]">{{ text }}</span> </template> <script setup lang="ts"> defineProps<{ text?: string className?: string }>() withDefaults(defineProps<{ text?: string; className?: string }>(), { text: '', className: '' }) </script>However, the current Options API implementation is perfectly valid and functional.
packages/runtime-renderer/src/types/config.ts (1)
25-30
: Consider usingunknown
instead ofany
for better type safety.The use of
Record<string, any>
eliminates compile-time type checking. While this is pragmatic for dynamic runtime stores from schemas, consider usingRecord<string, unknown>
to maintain some type safety and require explicit type assertions at usage sites.Apply this diff if you want stricter typing:
export interface StoreConfig { id: string - state: Record<string, any> - actions: Record<string, any> - getters: Record<string, any> + state: Record<string, unknown> + actions: Record<string, unknown> + getters: Record<string, unknown> }packages/runtime-renderer/src/renderer/builtin/CanvasRouterLink.vue (2)
1-13
: Replacejavascript:void(0)
with#
for cleaner markup.The
href="javascript:void(0)"
is a dated practice. Since you're callingpreventDefault()
in the click handler, usinghref="#"
is cleaner and more conventional.Apply this diff:
<a - href="javascript:void(0)" + href="#" @click="handleClick"
44-49
: Consider adding error handling for failed navigation.The
handleClick
function correctly prevents default and usesrouter.push
, but navigation can fail (e.g., navigation guards rejecting). Consider adding error handling for better resilience.const handleClick = (event: Event) => { event.preventDefault() - if (props?.to) { - router.push(props.to) + if (props.to) { + router.push(props.to).catch((err) => { + console.warn('Navigation failed:', err) + }) } }packages/runtime-renderer/src/stores/index.ts (1)
28-45
: Add validation and error handling for store creation.The
createStores
function should validate configurations and handle potential failures:
- Line 40: Chinese comment should be translated.
- Missing validation for required config fields (id, state, etc.).
- No error handling if
defineStore
or store instantiation fails.- Consider logging when stores are created for debugging.
Apply this diff:
export const createStores = (storesConfig: StoreConfig[], pinia: Pinia) => { const stores = shallowReactive<Record<string, any>>({}) storesConfig.forEach((config) => { + // Validate config + if (!config.id) { + console.error('Store config missing required id field:', config) + return + } + + try { - // 使用 defineStore 创建 Pinia store + // Create Pinia store using defineStore const useStore = defineStore(config.id, { state: () => config.state, - getters: config.getters, - actions: config.actions }) - // 使用useStore创建 store 实例并绑定到 pinia + // Create store instance and bind to Pinia stores[config.id] = useStore(pinia) + } catch (error) { + console.error(`Failed to create store: ${config.id}`, error) + } }) return stores }packages/runtime-renderer/src/utils/data-utils.ts (4)
1-2
: Consider direct named export for clarity.The destructuring-with-rename pattern
export const { parseFunction: generateFunction } = commonUtils
is less discoverable than a direct re-export. IfgenerateFunction
is intended as a domain-specific alias, consider documenting why the rename is necessary or using a more explicit pattern.Apply this diff for a clearer approach:
-import { utils as commonUtils } from '@opentiny/tiny-engine-utils' -export const { parseFunction: generateFunction } = commonUtils +import { utils as commonUtils } from '@opentiny/tiny-engine-utils' +export const generateFunction = commonUtils.parseFunction
4-6
: Add type annotations for better type safety.The
reset
function lacks type annotations, reducing type safety and IDE support.Apply this diff:
-export const reset = (obj) => { +export const reset = (obj: Record<string, any>) => { Object.keys(obj).forEach((key) => delete obj[key]) }
8-12
: Add type annotations to newFn wrapper.While the wrapper prevents ESLint errors, it lacks type safety for its arguments.
Apply this diff:
-export const newFn = (...argv) => { +export const newFn = (...argv: string[]) => { const Fn = Function return new Fn(...argv) }
25-30
: Add type annotations for parameter objects.The function lacks type information for its parameters, reducing type safety.
Apply this diff:
-export const getDeletedKeys = (objA, objB) => { +export const getDeletedKeys = (objA: Record<string, any>, objB: Record<string, any>): string[] => { const keyA = Object.keys(objA) const keyB = new Set(Object.keys(objB)) return keyA.filter((item) => !keyB.has(item)) }packages/runtime-renderer/src/app-function/http/axios.js (2)
118-136
: Mock state management lacks cleanup guarantees.The
disableMock()
method at line 131-136 callsmock.restore()
but doesn't handle potential errors during restoration. Additionally, there's no way to query or clear accumulated proxy configurations.Apply this diff:
disableMock() { if (mock) { - mock.restore() + try { + mock.restore() + } catch (error) { + console.error('[axios-wrapper] Failed to restore mock adapter:', error) + } } mock = undefined },Consider adding a method to clear/reset mock configurations:
resetMock() { if (mock) { mock.reset() } },
16-143
: Convert to TypeScript for type safety.This file is JavaScript (.js) but the project uses TypeScript. Converting to TypeScript would provide:
- Type safety for the config parameter and return type
- Better IDE support
- Documentation through types
- Compile-time error catching
Consider renaming to
axios.ts
and adding type definitions:import axios, { AxiosInstance, AxiosRequestConfig } from 'axios' import MockAdapter from 'axios-mock-adapter' interface HttpClient { request: AxiosInstance['request'] get: AxiosInstance['get'] // ... other methods mock(config: MockConfig[]): MockAdapter disableMock(): void isMock(): boolean } export default (config: AxiosRequestConfig): HttpClient => { // implementation }packages/runtime-renderer/src/renderer/parser/parser.ts (2)
233-236
: Use strict equality for condition checks.Line 234 uses loose equality (
== null
) which matches bothnull
andundefined
. While this might be intentional, it's better to be explicit for maintainability.Apply this diff for clarity:
export const parseCondition = (condition, scope, ctx) => { - // eslint-disable-next-line no-eq-null - return condition == null ? true : parseData(condition, scope, ctx) + return condition === null || condition === undefined ? true : parseData(condition, scope, ctx) }
282-299
: v-model binding generation has fragile event name detection.The regex
/^on[A-Z]/
at line 286 checks for event handlers, but it doesn't validate that the event name actually corresponds to the model value property. This could miss edge cases or incorrectly identify handlers.Improve the detection logic:
// 处理 v-model 双向绑定 const propsEntries = Object.entries(data) const modelValue = propsEntries.find(([_key, value]) => value?.type === 'JSExpression' && value?.model === true) - const hasUpdateModelValue = propsEntries.find( - ([key]) => /^on[A-Z]/.test(key) && key.startsWith(`onUpdate:${modelValue?.[0]}`) - ) + const hasUpdateModelValue = modelValue && propsEntries.find( + ([key]) => key === `onUpdate:${modelValue[0]}` + ) if (modelValue && !hasUpdateModelValue) { // 添加 onUpdate:modelKey 事件 - res[`onUpdate:${modelValue?.[0]}`] = parseData( + res[`onUpdate:${modelValue[0]}`] = parseData( { type: 'JSFunction', value: `(value) => ${modelValue[1].value}=value` }, scope, ctx ) }packages/runtime-renderer/src/renderer/builtin/CanvasPlaceholder.vue (1)
14-16
: Consider internationalization for the placeholder text.The hardcoded Chinese default text '请将元素拖放到这里' may limit usability for non-Chinese users. Consider using the i18n mechanism already present in the codebase (based on learnings mentioning tiny-engine-i18n-host).
For example, you could use a translated key:
placeholder: { type: String, - default: '请将元素拖放到这里' + default: () => i18n.global.t('canvas.placeholder.dragHere') }Or make the component i18n-aware by importing and using the translation service.
packages/runtime-renderer/src/app-function/http/config.js (1)
13-15
: Consider making HTTP configuration runtime-customizable.The hardcoded
withCredentials: false
is a safe default, but applications may need to customize this (e.g., for CORS requests with cookies). Consider exposing this as a configurable option that can be overridden at runtime initialization.Example refactor:
-export default { - withCredentials: false +export const createHttpConfig = (overrides = {}) => ({ + withCredentials: false, + ...overrides +}) + +export default createHttpConfig()packages/runtime-renderer/src/renderer/builtin/CanvasCollection.vue (3)
2-2
: AddinheritAttrs: false
for explicit attribute control.When using
v-bind="$attrs"
on a non-root or specific element, it's best practice to setinheritAttrs: false
in the component options to prevent Vue from automatically applying attributes to the root element. This makes the attribute forwarding behavior explicit and predictable.Apply this diff:
export default { + inheritAttrs: false, components: { CanvasPlaceholder },
9-27
: Consider adding TypeScript type annotations.The script uses
lang="ts"
but lacks type annotations for props and component options. Consider adding proper TypeScript interfaces or usingdefineComponent
from Vue for better type safety.Example refactor:
<script lang="ts"> import { defineComponent, PropType } from 'vue' import CanvasPlaceholder from './CanvasPlaceholder.vue' export default defineComponent({ name: 'CanvasCollection', components: { CanvasPlaceholder }, props: { tag: { type: String as PropType<string>, default: 'div' }, schema: { type: Object as PropType<Record<string, any>>, default: () => ({}) }, dataSource: { type: [String, Array, Number] as PropType<string | any[] | number> } } }) </script>
20-24
: Remove or document unused props in CanvasCollection.vue
Bothschema
(line 20) anddataSource
(line 24) are declared but never referenced in the component’s template or script, nor bound by any parent. Drop them or add documentation explaining their intended use.packages/runtime-renderer/src/renderer/builtin/CanvasImg.vue (1)
12-15
: Consider using Object type for style prop.The
style
prop is defined as String, which limits flexibility and type safety. Vue's style binding typically accepts Object or Array types for better validation and developer experience.Apply this diff to improve type safety:
style: { - type: String, - default: '' + type: [Object, String], + default: () => ({}) }packages/runtime-renderer/src/app-function/http/index.js (1)
22-22
: Incomplete re-login flow.The TODO comment indicates a missing authentication re-login implementation.
Do you want me to generate a re-login handler implementation or open a new issue to track this task?
packages/runtime-renderer/src/renderer/builtin/CanvasSlot.vue (2)
13-20
: Propsname
andparams
are declared but never used.The component defines
name
andparams
props but does not reference them in the template or pass them explicitly to child components. If these props are intended for future use or external APIs, consider documenting their purpose.
3-3
: Consider disabling attribute inheritance to prevent prop duplication.The component uses
v-bind="$attrs"
to forward attributes to CanvasPlaceholder. However, by default Vue inherits all attributes (includingname
andparams
props) to the root element (the<slot>
), and then forwards them again via$attrs
. This may cause duplicate attributes on CanvasPlaceholder.Apply this diff to explicitly control attribute inheritance:
<script> import CanvasPlaceholder from './CanvasPlaceholder.vue' export default { + inheritAttrs: false, components: { CanvasPlaceholder },
Also applies to: 7-21
packages/runtime-renderer/src/renderer/builtin/CanvasBox.vue (1)
7-16
: Consider migrating to<script setup>
for consistency.The component uses Options API. For a new codebase feature, consider using
<script setup>
with Composition API for better TypeScript support, reduced boilerplate, and alignment with modern Vue 3 patterns.Example refactor:
<script setup lang="ts"> defineOptions({ inheritAttrs: false }) interface Props { tag?: string } withDefaults(defineProps<Props>(), { tag: 'div' }) </script>packages/runtime-renderer/package.json (3)
23-23
: Consider upgradingaxios-mock-adapter
to latest stable version.The package specifies
axios-mock-adapter: ^1.19.0
, but version 2.1.0 is available and includes maintenance fixes and improvements. Review the changelog for any breaking changes before upgrading.Based on learnings.
- "axios-mock-adapter": "^1.19.0", + "axios-mock-adapter": "^2.1.0",
29-31
: Vite and plugin versions are older than other packages in the monorepo.This package uses
vite: ^4.0.0
and@vitejs/plugin-vue: ^4.0.0
, while the runtime-deploy toolbar package uses Vite 5.x and plugin-vue 5.x. Consider aligning versions across the monorepo for consistency.
19-19
: Upgrade@vue/babel-plugin-jsx
to v1.5.0.
In packages/runtime-renderer/package.json (line 19), change"@vue/babel-plugin-jsx": "^1.2.5"
→"^1.5.0"
. Release notes list only feature and bugfix updates since v1.2.5 with no breaking changes in v1.3.x or v1.4.x, so this bump is safe and ensures you get the latest fixes and improvements.packages/runtime-renderer/index.ts (2)
26-27
: Consider parallelizing independent async operations.
fetchAppSchema
andfetchBlocks
appear to be independent operations that could run in parallel to reduce initialization time.- await fetchAppSchema(appId) - await fetchBlocks() + await Promise.all([ + fetchAppSchema(appId), + fetchBlocks() + ])
22-39
: Consider adding user-facing error handling.While
useAppSchema
may handle fetch errors internally,initRuntimeRenderer
has no try-catch or fallback UI. If initialization fails, users see a blank screen. Consider wrapping the initialization in error handling and displaying an error message.Example:
export const initRuntimeRenderer = async () => { try { // ... existing initialization code } catch (error) { console.error('Failed to initialize runtime renderer:', error) document.getElementById('app')!.innerHTML = ` <div style="padding: 2rem; color: red;"> <h2>Failed to load application</h2> <p>${error instanceof Error ? error.message : 'Unknown error'}</p> </div> ` throw error } }packages/runtime-renderer/src/renderer/page-function/state.ts (3)
15-17
: Consider performance implications of clearing all keys.When
clear
is true, this iterates and deletes all keys. For large state objects, this could be slow. Consider whetherObject.keys(state).forEach((key) => delete state[key])
is necessary when you could reassign the reactive reference or use a more efficient clearing mechanism.However, given Vue 3's reactive system, direct deletion is correct to maintain reactivity. The current approach is acceptable for typical use cases.
19-23
: getDeletedKeys performs only shallow key comparison
The implementation inpackages/runtime-renderer/src/utils/data-utils.ts
(and its mirror inpackages/canvas/render/src/data-utils.ts
) filters only top-level property names viaObject.keys
, so it doesn’t handle nested objects, distinguishundefined
vsnull
, or compare array contents. If you need those cases covered, implement deep-diff logic or explicitly document that deletions are shallow.
28-39
: Use explicit null/undefined checks for accessor.value (page-function/state.ts:29–34)
isStateAccessor
correctly filters accessor objects, butif (accessor?.getter?.value)
(and similarly forsetter
) treats empty strings (''
) as falsy and will skip them. Replace these truthiness guards with explicit checks—e.g.if (accessor.getter.value != null)
—to ensure empty-string values aren’t inadvertently ignored.packages/toolbars/runtime-deploy/src/Main.vue (2)
4-4
: Hardcoded Chinese text should be internationalized.The content text "在线部署运行" and message "请先创建页面" (Line 35) are hardcoded. For better i18n support, use the i18n system that appears to be available in the project (tiny-engine-i18n-host).
Consider extracting strings to i18n keys:
- content="在线部署运行" + :content="t('toolbar.runtimeDeploy')"- message: '请先创建页面' + message: t('toolbar.createPageFirst')
30-42
: WrapruntimeDeploy()
in a try/catch and await its promiseTo surface deployment errors and prevent unhandled rejections, update the call to:
const deploy = async () => { // 检查页面状态,确保有内容 if (useLayout().isEmptyPage()) { useNotify({ type: 'warning', message: '请先创建页面' }) return } - runtimeDeploy() + try { + await runtimeDeploy() + } catch (error) { + useNotify({ + type: 'error', + message: '部署失败,请重试' + }) + console.error('Runtime deploy failed:', error) + } }packages/toolbars/runtime-deploy/vite.config.ts (1)
20-36
: Vite config looks correct for a library build.The configuration appropriately:
- Enables sourcemaps for debugging
- Externalizes Vue and @opentiny dependencies to avoid bundling peer dependencies
- Uses ES format for modern module consumption
- Disables publicDir for library builds
One minor observation: Line 23 has an empty
resolve: {}
object. If there are no resolve options needed, consider removing it for cleanliness.publicDir: false, - resolve: {}, build: {
packages/runtime-renderer/src/components/Loading.vue (1)
46-59
: Animation keyframes are correct but could use will-change hint.The rotation animation is smooth, but for better performance, especially on mobile devices, add
will-change: transform
to the.loading
class to hint to the browser that this element will be animated..loading { width: 60px; height: 60px; margin: 0 auto; position: relative; animation: load 3s linear infinite; + will-change: transform; }
packages/runtime-renderer/src/renderer/page-function/blockContext.ts (1)
47-47
: JSON.parse/stringify is inefficient for deep cloning.Using
JSON.parse(JSON.stringify(data))
has limitations:
- Doesn't handle functions, undefined, Symbols, or circular references
- Performance overhead for large objects
Consider using a dedicated cloning library like
structuredClone
(native in modern browsers/Node) or lodash'scloneDeep
.- const newSchema = JSON.parse(JSON.stringify(data)) + const newSchema = structuredClone(data)Note:
structuredClone
is available in Node 17+ and modern browsers. If supporting older environments, keep JSON approach or add a polyfill.packages/runtime-renderer/src/renderer/useContext.ts (2)
15-17
: Consider adding generic type parameter for better type safety.The
Context
interface currently allows any values. While this provides flexibility, consider making the factory generic to allow consumers to specify their context shape:-interface Context { +interface Context<T = any> { - [key: string]: any + [key: string]: T }Then update the factory signature:
export default <T = any>(): UseContextReturn<T> => { ... }This would enable type-safe context usage while maintaining backward compatibility via the default
any
type.
30-35
: LGTM with optional refactor for clear logic.The
setContext
implementation correctly merges new context values. The clear parameter properly removes existing keys before assignment.For slightly cleaner code, consider this refactor:
const setContext = (ctx: Context, clear?: boolean) => { if (clear) { - Object.keys(context).forEach((key) => delete context[key]) + Object.keys(context).forEach((key) => { + delete context[key] + }) } Object.assign(context, ctx) }Or use a more functional approach:
const setContext = (ctx: Context, clear?: boolean) => { if (clear) { for (const key in context) { delete context[key] } } Object.assign(context, ctx) }packages/runtime-renderer/src/components/NotFound.vue (2)
11-37
: Add internationalization and improve accessibility.The template has hardcoded Chinese text and lacks proper accessibility attributes:
- I18n: Text like "页面未找到", "抱歉,您访问的页面不存在或已被删除", etc., should be externalized for internationalization, especially given the PR context mentions tiny-engine-i18n-host as a workspace dependency.
- Accessibility: The SVG icon lacks a title or
aria-label
, and interactive elements could benefit from better ARIA attributes.For i18n, consider using the i18n system:
<h1>{{ t('notFound.title') }}</h1> <p>{{ t('notFound.description') }}</p>For accessibility:
<svg viewBox="0 0 200 200" width="120" height="120"> + <title>404 错误图标</title> <circle cx="100" cy="100" r="80" fill="#f0f0f0" stroke="#ddd" stroke-width="2" /> <text x="100" y="110" text-anchor="middle" font-size="48" fill="#999">404</text> </svg>
- <button @click="refresh" class="action-button secondary">刷新页面</button> + <button @click="refresh" class="action-button secondary" aria-label="刷新当前页面"> + 刷新页面 + </button>
49-51
: Consider router-based navigation instead of full page reload.
window.location.reload()
forces a full page refresh, losing all application state and causing a jarring user experience. In a Vue SPA, consider navigating to the home route or previous page instead:+import { useRouter } from 'vue-router' + const route = useRoute() +const router = useRouter() const refresh = () => { - window.location.reload() + // Navigate to home page or previous route + router.push('/') + // Or go back if there's history: + // router.back() }If a full reload is genuinely required (e.g., to reset service workers or clear cache), consider confirming with the user first.
packages/runtime-renderer/src/composables/useAppSchema.ts (2)
11-18
: Consider propagating initialization errors.The error is caught and logged but not propagated to the caller. This means
initializeAppConfig
and its caller won't know if utility initialization failed, potentially leading to silent failures at runtime when utilities are accessed.Consider one of these approaches:
- Re-throw the error after logging:
const initializeUtils = async (utils: Util[]) => { try { await initUtils(utils) } catch (error) { // eslint-disable-next-line no-console console.error('工具函数初始化失败:', error) + throw error } }
- Or return a success/failure indicator:
-const initializeUtils = async (utils: Util[]) => { +const initializeUtils = async (utils: Util[]): Promise<boolean> => { try { await initUtils(utils) + return true } catch (error) { // eslint-disable-next-line no-console console.error('工具函数初始化失败:', error) + return false } }
29-34
: Remove unsafe type assertion.The
as any
type assertion on line 32 bypasses TypeScript's type checking. According to the schema types,I18nConfig
values areRecord<string, any>
, which should be compatible withmergeLocaleMessage
.const initializeI18n = (i18nConfig: I18nConfig) => { if (!i18nConfig) return Object.entries(i18nConfig).forEach(([loc, msgs]) => { - i18n.global.mergeLocaleMessage(loc, msgs as any) + i18n.global.mergeLocaleMessage(loc, msgs) }) }If there's a legitimate type mismatch, consider adding proper type guards or updating the interface definition.
packages/runtime-renderer/src/renderer/page-function/accessor.ts (1)
20-21
: Unused propsAccessorMap.The
propsAccessorMap
is declared and exposed in the return object but never populated or used within this module. It appears to be reserved for future use with prop accessors (similar to howgenerateStateAccessors
populatesstateAccessorMap
).Consider either:
- Implementing a
generatePropsAccessors
function if prop accessors are needed- Removing
propsAccessorMap
if it's not currently used elsewhere in the codebase- Adding a TODO comment explaining its future purpose
Run this script to check if
propsAccessorMap
is used elsewhere:#!/bin/bash # Search for propsAccessorMap usage in the codebase rg -n "propsAccessorMap" --type ts --type tsx -C 2packages/runtime-renderer/src/router/index.ts (1)
82-89
: Remove or guard debug code for production.Lines 82-89 assign the router to
window.__DEBUG_ROUTER__
and log all routes to the console. This debug code should either be removed or guarded behind a development-only flag.export async function createAppRouter() { const routes = await createRouterConfig() const router = createRouter({ history: createWebHashHistory('/runtime.html'), routes }) - if (typeof window !== 'undefined') { + if (typeof window !== 'undefined' && import.meta.env.DEV) { window.__DEBUG_ROUTER__ = router // eslint-disable-next-line no-console console.log( '所有路由:', router.getRoutes().map((r) => ({ path: r.path, name: r.name, redirect: r.redirect })) ) } return router }Also add a TypeScript declaration for the debug property:
declare global { interface Window { __DEBUG_ROUTER__?: Router } }packages/runtime-renderer/src/renderer/render.ts (1)
218-244
: Minor duplication: Mapper and block schema binding.Lines 229-231 and 234-237 both add
bindProps.schema = schema
for different component types. This logic could be consolidated.const getBindProps = (schema, scope, context) => { const { componentName } = schema if (componentName === 'CanvasPlaceholder') { return {} } const bindProps = { ...parseData(schema.props, scope, context) } - if (Mapper[componentName]) { - bindProps.schema = schema - } - - // 如果是区块组件,传递完整的 schema - const blockSchema = getBlock(componentName) - if (blockSchema) { + // Mapper components and block components both need the full schema + if (Mapper[componentName] || getBlock(componentName)) { bindProps.schema = schema } // 绑定组件属性时需要将 className 重命名为 class,防止覆盖组件内置 class bindProps.class = bindProps.className delete bindProps.className return bindProps }packages/runtime-renderer/src/renderer/page-function/css-handler.ts (1)
13-17
: Remove unused interface property.The
enableModernCSS
option inCSSHandlerOptions
is declared but never referenced in the implementation.Apply this diff to remove the unused property:
interface CSSHandlerOptions { pageId?: string enableScoped?: boolean - enableModernCSS?: boolean }
packages/runtime-renderer/src/types/schema.ts (5)
2-22
: Consider refining the AppSchema type.The interface mixes API response metadata (
code
,message
,error
,errMsg
,success
) with actual schema data. Consider:
- Redundant error fields: Both
error
anderrMsg
are typed asany
and serve similar purposes.- Type specificity:
any
types reduce type safety.Consider separating concerns:
+// API response wrapper +export interface ApiResponse<T> { + data: T + code: string + message: string + error?: unknown + success: boolean +} + +// Application data schema +export interface AppData { + bridge: any[] + componentsMap: ComponentMap[] + componentsTree: PageSchema[] + css: string + dataSource: DataSourceConfig + utils: Util[] + packages: PackageConfig[] + meta: AppMeta + config: AppConfig + constants: string + i18n: I18nConfig + version: string +} + -export interface AppSchema { - data: { - bridge: any[] - componentsMap: ComponentMap[] - componentsTree: PageSchema[] - css: string - dataSource: DataSourceConfig - utils: Util[] - packages: PackageConfig[] - meta: AppMeta - config: AppConfig - constants: string - i18n: I18nConfig - version: string - } - code: string - message: string - error: any - errMsg: any - success: boolean -} +export type AppSchema = ApiResponse<AppData>
25-40
: Consider typing the generic fields.Several fields use
any
which reduces type safety:
npmrc?: any
(line 31)styles: any[]
(line 38)If these have known shapes, consider adding specific types:
export interface ComponentMap { package?: string | null destructuring: boolean exportName?: string | null componentName: string version: string npmrc?: Record<string, unknown> | string // or the actual shape path?: string dependencies?: { scripts: Array<{ package?: string components: Record<string, string> }> styles: string[] // or Array<{ url: string; type?: string }> } }
43-64
: Eliminate duplication between PageSchema and PageContent.
PageContent
duplicates all fields fromPageSchema
exceptmeta
. Use TypeScript utility types to maintain DRY principle.Apply this diff:
-// 页面Schema -export interface PageSchema { +export interface PageContent { children: ComponentNode[] css: string componentName: string fileName: string lifeCycles?: LifeCycles | null - meta: PageMeta methods: Record<string, JSFunction> props: Record<string, any> state: Record<string, any> } -export interface PageContent { - children: ComponentNode[] - css: string - componentName: string - fileName: string - lifeCycles?: LifeCycles | null - methods: Record<string, JSFunction> - props: Record<string, any> - state: Record<string, any> -} +// 页面Schema +export interface PageSchema extends PageContent { + meta: PageMeta +}
77-98
: Inconsistent naming convention in PageMeta.The interface mixes camelCase (
isHome
,lastUpdatedBy
,rootElement
) with snake_case (gmt_create
,gmt_modified
,page_content
). While this likely mirrors a backend API, consider normalizing to camelCase for JavaScript consistency or documenting that this is a direct API mapping.If you prefer consistency, consider a transformer:
// API response type (as-is from backend) interface PageMetaAPI { app: number gmt_create: string // ... snake_case fields } // Normalized application type export interface PageMeta { app: number gmtCreate: string lastUpdatedBy: string // ... camelCase fields }Or add a JSDoc comment indicating this mirrors the API shape:
/** * Page metadata - mirrors backend API response structure. * Field names use snake_case to match API contract. */ export interface PageMeta { // ... }
135-139
: Document the purpose of themodel
field.The
model
property inJSExpression
lacks documentation. Is this for v-model binding detection or two-way data binding?Add a JSDoc comment:
// JS表达式 export interface JSExpression { type: 'JSExpression' value: string /** Indicates if this expression is used in a v-model or two-way binding context */ model?: boolean }packages/runtime-renderer/src/renderer/LifecycleWrapper.ts (1)
50-58
: Type the props more specifically.Both
schema
andparent
useObject
orObject as PropType<any>
, reducing type safety. Consider importing the actual schema types.+import type { PageContent, PageSchema } from '../types/schema' + export const PageLifecycleWrapper = defineComponent({ name: 'PageLifecycleWrapper', props: { schema: { - type: Object as PropType<any>, + type: Object as PropType<Record<string, any>>, required: true }, parent: { - type: Object, - default: () => ({}) + type: Object as PropType<Partial<PageSchema>>, + default: () => ({} as Partial<PageSchema>) } },packages/runtime-renderer/src/renderer/RenderMain.ts (2)
41-44
: Add null-safety for missing pages.Line 43 uses
JSON.parse(JSON.stringify(page))
which will returnundefined
ifpage
isundefined
, but this could throw or return unexpected values. Add explicit handling.const currentSchema = computed(() => { - const page = getPageById(props.pageId)?.meta?.page_content // 通过 pageId 获取最新的页面对象 - return JSON.parse(JSON.stringify(page)) + const page = getPageById(props.pageId)?.meta?.page_content + if (!page) return null + return JSON.parse(JSON.stringify(page)) })This makes the null-case explicit and avoids
JSON.parse(JSON.stringify(undefined))
.
52-52
: Add type safety for injected stores.Line 52 injects
'stores'
without a default or type, which could result inundefined
. Consider adding a default or type assertion.-const stores = inject('stores') +const stores = inject<Record<string, any>>('stores', {})Or if stores must be provided by parent:
const stores = inject<Record<string, any>>('stores') if (!stores) { console.error('stores must be provided by parent component') }packages/runtime-renderer/src/app-function/dataSource.ts (5)
11-24
: Consider adding validation for item structure.The
normalizeItem
function does not validate thatitem.data
exists or that expected properties are present. If the schema is malformed, this could silently produce objects with undefined properties, leading to runtime errors downstream.Apply this diff to add a basic guard:
const normalizeItem = (item: any) => { + if (!item || !item.data) { + console.warn('normalizeItem received invalid item:', item) + return null + } return { id: item.id, name: item.name,Then filter out null results when mapping:
- list: (rawConfig.list || []).map(normalizeItem) + list: (rawConfig.list || []).map(normalizeItem).filter(Boolean)
31-31
: Consider typing dataSourceMap more strictly.The
Record<string, any>
type provides no type safety for consumers. If the dataSource shape is well-defined, consider defining an interface or type to improve maintainability and IDE support.Example:
interface DataSource { config: any // or a more specific config type status: 'init' | 'loading' | 'loaded' | 'error' data: any error?: any load: (params?: any, customUrl?: string) => Promise<any> } export const dataSourceMap: Record<string, DataSource> = {}
53-55
: Clarify the intent of returningundefined
when shouldFetch is false.When
shouldFetch()
returnsfalse
, the function resolves withundefined
. This behavior should be documented, as callers must explicitly handle this case. Alternatively, consider returning a resolved promise with a sentinel value or a specific status to make the intent clearer.Example:
if (!shouldFetch()) { - return Promise.resolve(undefined) + // Do not fetch; return a no-op response + return Promise.resolve({ code: 'NO_FETCH', msg: 'Fetch skipped by shouldFetch', data: null }) }
36-71
: Inconsistent error handling between local and remote data paths.The local data path (lines 38-51) catches exceptions, updates
dataSource.status
anddataSource.error
, and rejects the promise. However, the remote data path (lines 57-70) delegates error handling to the HTTP interceptors (configured later at lines 103-104). This asymmetry means that errors in the local path are handled inline, while remote errors are handled elsewhere.For consistency and easier debugging, consider explicitly handling remote errors in the load function or clearly documenting that error handling is centralized in the interceptors.
Apply this diff to add explicit error handling for the remote path:
- return http.request(config) + return http.request(config).catch((error) => { + dataSource.status = 'error' + dataSource.error = error + throw error + })Alternatively, document that error handling is delegated to interceptors.
121-121
: Both default and named exports are provided.Exporting
dataSourceMap
as both a named export (line 31) and the default export (line 121) is acceptable, but may cause confusion for consumers. Consider standardizing on one pattern across the codebase for consistency.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (56)
designer-demo/package.json
(1 hunks)designer-demo/runtime.html
(1 hunks)designer-demo/src/runtime.js
(1 hunks)packages/build/vite-config/src/vite-plugins/devAliasPlugin.js
(1 hunks)packages/common/js/runtime-deploy.js
(1 hunks)packages/design-core/package.json
(1 hunks)packages/design-core/re-export.js
(1 hunks)packages/design-core/registry.js
(2 hunks)packages/layout/src/defaultLayout.js
(1 hunks)packages/register/src/constants.ts
(1 hunks)packages/runtime-renderer/index.ts
(1 hunks)packages/runtime-renderer/package.json
(1 hunks)packages/runtime-renderer/src/App.vue
(1 hunks)packages/runtime-renderer/src/app-function/dataSource.ts
(1 hunks)packages/runtime-renderer/src/app-function/http/axios.js
(1 hunks)packages/runtime-renderer/src/app-function/http/config.js
(1 hunks)packages/runtime-renderer/src/app-function/http/index.js
(1 hunks)packages/runtime-renderer/src/app-function/utils.ts
(1 hunks)packages/runtime-renderer/src/components/Loading.vue
(1 hunks)packages/runtime-renderer/src/components/NotFound.vue
(1 hunks)packages/runtime-renderer/src/components/PageRenderer.vue
(1 hunks)packages/runtime-renderer/src/composables/useAppSchema.ts
(1 hunks)packages/runtime-renderer/src/mock/blocks.json
(1 hunks)packages/runtime-renderer/src/renderer/LifecycleWrapper.ts
(1 hunks)packages/runtime-renderer/src/renderer/RenderMain.ts
(1 hunks)packages/runtime-renderer/src/renderer/builtin/CanvasBox.vue
(1 hunks)packages/runtime-renderer/src/renderer/builtin/CanvasCollection.vue
(1 hunks)packages/runtime-renderer/src/renderer/builtin/CanvasIcon.vue
(1 hunks)packages/runtime-renderer/src/renderer/builtin/CanvasImg.vue
(1 hunks)packages/runtime-renderer/src/renderer/builtin/CanvasPlaceholder.vue
(1 hunks)packages/runtime-renderer/src/renderer/builtin/CanvasRouterLink.vue
(1 hunks)packages/runtime-renderer/src/renderer/builtin/CanvasRouterView.vue
(1 hunks)packages/runtime-renderer/src/renderer/builtin/CanvasSlot.vue
(1 hunks)packages/runtime-renderer/src/renderer/builtin/CanvasText.vue
(1 hunks)packages/runtime-renderer/src/renderer/builtin/index.ts
(1 hunks)packages/runtime-renderer/src/renderer/page-function/accessor.ts
(1 hunks)packages/runtime-renderer/src/renderer/page-function/blockContext.ts
(1 hunks)packages/runtime-renderer/src/renderer/page-function/css-handler.ts
(1 hunks)packages/runtime-renderer/src/renderer/page-function/index.ts
(1 hunks)packages/runtime-renderer/src/renderer/page-function/state.ts
(1 hunks)packages/runtime-renderer/src/renderer/parser/index.ts
(1 hunks)packages/runtime-renderer/src/renderer/parser/parser.ts
(1 hunks)packages/runtime-renderer/src/renderer/render.ts
(1 hunks)packages/runtime-renderer/src/renderer/useContext.ts
(1 hunks)packages/runtime-renderer/src/router/index.ts
(1 hunks)packages/runtime-renderer/src/stores/index.ts
(1 hunks)packages/runtime-renderer/src/types/config.ts
(1 hunks)packages/runtime-renderer/src/types/schema.ts
(1 hunks)packages/runtime-renderer/src/utils/data-utils.ts
(1 hunks)packages/runtime-renderer/vite.config.ts
(1 hunks)packages/toolbars/runtime-deploy/index.ts
(1 hunks)packages/toolbars/runtime-deploy/meta.js
(1 hunks)packages/toolbars/runtime-deploy/package.json
(1 hunks)packages/toolbars/runtime-deploy/src/Main.vue
(1 hunks)packages/toolbars/runtime-deploy/vite.config.ts
(1 hunks)tsconfig.app.json
(2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
PR: opentiny/tiny-engine#1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered and available throughout Vue components without requiring explicit imports.
Applied to files:
packages/runtime-renderer/src/renderer/builtin/index.ts
packages/runtime-renderer/src/renderer/builtin/CanvasIcon.vue
📚 Learning: 2024-09-30T07:51:10.036Z
Learnt from: chilingling
PR: opentiny/tiny-engine#837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.
Applied to files:
designer-demo/package.json
tsconfig.app.json
packages/runtime-renderer/package.json
📚 Learning: 2025-01-14T06:54:37.955Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/canvas/render/src/page-block-function/css.ts:3-7
Timestamp: 2025-01-14T06:54:37.955Z
Learning: The setPageCss function in packages/canvas/render/src/page-block-function/css.ts is intentionally kept simple without additional type safety or error handling as per the maintainer's preference.
Applied to files:
packages/runtime-renderer/src/renderer/page-function/css-handler.ts
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
PR: opentiny/tiny-engine#1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered using `app.component('SvgIcon', SvgIcon)` in `packages/svgs/index.js`, making it available throughout Vue components without requiring explicit imports.
Applied to files:
packages/runtime-renderer/src/renderer/builtin/CanvasIcon.vue
🧬 Code graph analysis (18)
packages/common/js/runtime-deploy.js (1)
packages/register/src/common.ts (1)
getMergeMeta
(53-55)
packages/runtime-renderer/src/renderer/LifecycleWrapper.ts (1)
packages/runtime-renderer/src/types/schema.ts (1)
JSFunction
(129-132)
packages/runtime-renderer/src/app-function/http/index.js (2)
packages/runtime-renderer/src/app-function/http/axios.js (1)
config
(16-143)packages/runtime-renderer/src/app-function/dataSource.ts (1)
response
(110-112)
packages/runtime-renderer/src/app-function/utils.ts (2)
packages/runtime-renderer/src/types/schema.ts (1)
Util
(184-188)packages/runtime-renderer/src/utils/data-utils.ts (1)
parseJSFunction
(15-23)
packages/runtime-renderer/index.ts (3)
packages/runtime-renderer/src/composables/useAppSchema.ts (1)
useAppSchema
(9-164)packages/runtime-renderer/src/router/index.ts (1)
createAppRouter
(78-91)packages/runtime-renderer/src/stores/index.ts (2)
generateStoresConfig
(7-26)createStores
(28-45)
packages/runtime-renderer/src/renderer/RenderMain.ts (8)
packages/runtime-renderer/src/composables/useAppSchema.ts (1)
useAppSchema
(9-164)packages/canvas/render/src/page-block-function/context.ts (1)
useContext
(15-31)packages/plugins/materials/src/composable/types.ts (1)
Schema
(305-317)packages/runtime-renderer/src/renderer/page-function/state.ts (1)
useState
(6-46)packages/runtime-renderer/src/renderer/parser/parser.ts (1)
parseData
(228-231)packages/runtime-renderer/src/app-function/utils.ts (1)
getUtilsAll
(68-70)packages/runtime-renderer/src/renderer/page-function/css-handler.ts (1)
setPageCss
(72-82)packages/runtime-renderer/src/renderer/LifecycleWrapper.ts (1)
PageLifecycleWrapper
(48-208)
packages/runtime-renderer/src/composables/useAppSchema.ts (2)
packages/runtime-renderer/src/types/schema.ts (4)
AppSchema
(2-22)Util
(184-188)I18nConfig
(232-235)BlockItem
(248-287)packages/runtime-renderer/src/app-function/utils.ts (1)
initUtils
(45-66)
packages/runtime-renderer/src/renderer/page-function/state.ts (2)
packages/runtime-renderer/src/renderer/page-function/accessor.ts (1)
useAccessorMap
(10-50)packages/runtime-renderer/src/renderer/parser/parser.ts (2)
parseData
(228-231)isStateAccessor
(66-67)
packages/layout/src/defaultLayout.js (1)
packages/register/src/constants.ts (1)
META_APP
(24-81)
packages/runtime-renderer/src/renderer/render.ts (3)
packages/runtime-renderer/src/renderer/page-function/blockContext.ts (1)
getBlockContext
(72-76)packages/canvas/render/src/render.ts (1)
renderDefault
(27-35)packages/runtime-renderer/src/renderer/parser/parser.ts (3)
parseData
(228-231)parseLoopArgs
(238-245)parseCondition
(233-236)
packages/runtime-renderer/src/app-function/dataSource.ts (4)
packages/runtime-renderer/src/composables/useAppSchema.ts (1)
useAppSchema
(9-164)packages/runtime-renderer/src/utils/data-utils.ts (1)
parseJSFunction
(15-23)packages/runtime-renderer/src/app-function/http/axios.js (1)
config
(16-143)packages/register/src/hooks.ts (1)
useHttp
(89-89)
packages/runtime-renderer/src/stores/index.ts (3)
packages/runtime-renderer/src/composables/useAppSchema.ts (1)
useAppSchema
(9-164)packages/runtime-renderer/src/utils/data-utils.ts (1)
parseJSFunction
(15-23)packages/runtime-renderer/src/types/config.ts (1)
StoreConfig
(25-30)
packages/runtime-renderer/src/router/index.ts (2)
packages/runtime-renderer/src/composables/useAppSchema.ts (1)
useAppSchema
(9-164)packages/runtime-renderer/src/types/config.ts (1)
RouteConfig
(7-23)
packages/runtime-renderer/src/app-function/http/axios.js (1)
packages/runtime-renderer/src/app-function/dataSource.ts (1)
response
(110-112)
packages/runtime-renderer/src/renderer/page-function/css-handler.ts (1)
packages/common/js/preview.js (1)
pageId
(236-236)
packages/runtime-renderer/src/renderer/page-function/blockContext.ts (5)
packages/canvas/render/src/page-block-function/context.ts (1)
useContext
(15-31)packages/runtime-renderer/src/renderer/page-function/state.ts (1)
useState
(6-46)packages/runtime-renderer/src/renderer/parser/parser.ts (1)
parseData
(228-231)packages/runtime-renderer/src/app-function/utils.ts (1)
getUtilsAll
(68-70)packages/runtime-renderer/src/renderer/page-function/css-handler.ts (1)
getCSSHandler
(90-104)
designer-demo/src/runtime.js (1)
packages/runtime-renderer/index.ts (1)
initRuntimeRenderer
(22-39)
packages/runtime-renderer/src/renderer/parser/parser.ts (4)
packages/vue-generator/src/utils/index.js (1)
fnRegexp
(20-20)packages/runtime-renderer/src/utils/data-utils.ts (1)
parseJSFunction
(15-23)packages/canvas/render/src/material-function/material-getter.ts (1)
getIcon
(106-106)packages/vue-generator/src/generator/vue/sfc/generateAttribute.js (3)
propsEntries
(641-641)modelValue
(642-642)hasUpdateModelValue
(643-643)
async function startApp() { | ||
initRuntimeRenderer() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing await on async initialization call.
The initRuntimeRenderer()
function is async (returns a Promise), but line 15 doesn't await it. This creates several issues:
- Initialization errors will become unhandled promise rejections
- The returned app instance is lost
startApp()
completes before the renderer finishes initializing
Apply this diff to properly await the initialization:
async function startApp() {
- initRuntimeRenderer()
+ await initRuntimeRenderer()
}
Additionally, consider adding error handling:
async function startApp() {
- initRuntimeRenderer()
+ try {
+ await initRuntimeRenderer()
+ } catch (error) {
+ console.error('Failed to initialize runtime renderer:', error)
+ }
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
async function startApp() { | |
initRuntimeRenderer() | |
} | |
async function startApp() { | |
try { | |
await initRuntimeRenderer() | |
} catch (error) { | |
console.error('Failed to initialize runtime renderer:', error) | |
} | |
} |
🤖 Prompt for AI Agents
In designer-demo/src/runtime.js around lines 14 to 16, the call to the async
initRuntimeRenderer() is not awaited; change startApp to await the call and
return the renderer instance so initialization errors propagate and the app
instance isn't lost, and wrap the awaited call in a try/catch to log or handle
errors (and rethrow or surface them) to avoid unhandled promise rejections.
const getQueryParams = () => { | ||
const paramsMap = new URLSearchParams(location.search) | ||
const tenant = paramsMap.get('tenant') || '' | ||
const platform = getMergeMeta('engine.config')?.platformId | ||
const appId = paramsMap.get('id') | ||
|
||
let query = `id=${appId}&tenant=${tenant}&platform=${platform}` | ||
return query | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle missing or null query parameters.
The getQueryParams
function concatenates parameters without validating that appId
and platform
exist. This can result in URLs like id=null&tenant=&platform=undefined
.
Apply this diff to handle missing values:
const getQueryParams = () => {
const paramsMap = new URLSearchParams(location.search)
const tenant = paramsMap.get('tenant') || ''
const platform = getMergeMeta('engine.config')?.platformId
const appId = paramsMap.get('id')
- let query = `id=${appId}&tenant=${tenant}&platform=${platform}`
+ const params = new URLSearchParams()
+ if (appId) params.set('id', appId)
+ if (tenant) params.set('tenant', tenant)
+ if (platform) params.set('platform', platform)
+
+ return params.toString()
- return query
}
This ensures proper URL encoding and omits parameters with null/undefined values.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const getQueryParams = () => { | |
const paramsMap = new URLSearchParams(location.search) | |
const tenant = paramsMap.get('tenant') || '' | |
const platform = getMergeMeta('engine.config')?.platformId | |
const appId = paramsMap.get('id') | |
let query = `id=${appId}&tenant=${tenant}&platform=${platform}` | |
return query | |
} | |
const getQueryParams = () => { | |
const paramsMap = new URLSearchParams(location.search) | |
const tenant = paramsMap.get('tenant') || '' | |
const platform = getMergeMeta('engine.config')?.platformId | |
const appId = paramsMap.get('id') | |
const params = new URLSearchParams() | |
if (appId) params.set('id', appId) | |
if (tenant) params.set('tenant', tenant) | |
if (platform) params.set('platform', platform) | |
return params.toString() | |
} |
🤖 Prompt for AI Agents
In packages/common/js/runtime-deploy.js around lines 17 to 25, getQueryParams
currently builds a query string by interpolating values directly which can
produce id=null or platform=undefined; change it to create a new URLSearchParams
object and only append keys whose values are neither null nor undefined (and
skip empty tenant if desired), using the existing paramsMap for id/tenant and
getMergeMeta('engine.config')?.platformId for platform; rely on
URLSearchParams.toString() to properly URL-encode values and return that string
so missing values are omitted instead of serialized as "null" or "undefined".
export const deployPage = async () => { | ||
const href = window.location.href.split('?')[0] || './' | ||
const query = getQueryParams() | ||
|
||
const customDeployUrl = getMergeMeta('engine.toolbars.runtimeDeploy')?.options?.deployUrl | ||
const defaultDeployUrl = isDevelopEnv ? `./runtime.html` : `${href.endsWith('/') ? href : `${href}/`}runtime` | ||
|
||
let openUrl = '' | ||
openUrl = customDeployUrl | ||
? typeof customDeployUrl === 'function' | ||
? customDeployUrl(defaultDeployUrl, query) | ||
: `${customDeployUrl}?${query}` | ||
: `${defaultDeployUrl}?${query}` | ||
|
||
return { openUrl } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unnecessary async and validate custom deploy URLs.
Two concerns:
- The function is marked
async
but contains noawait
expressions. - If
customDeployUrl
is a function, its return value is not validated, which could lead to unexpected behavior or open redirect vulnerabilities if user-controlled data influences the URL.
Apply this diff:
-export const deployPage = async () => {
+export const deployPage = () => {
const href = window.location.href.split('?')[0] || './'
const query = getQueryParams()
const customDeployUrl = getMergeMeta('engine.toolbars.runtimeDeploy')?.options?.deployUrl
const defaultDeployUrl = isDevelopEnv ? `./runtime.html` : `${href.endsWith('/') ? href : `${href}/`}runtime`
let openUrl = ''
openUrl = customDeployUrl
? typeof customDeployUrl === 'function'
- ? customDeployUrl(defaultDeployUrl, query)
+ ? String(customDeployUrl(defaultDeployUrl, query) || defaultDeployUrl)
: `${customDeployUrl}?${query}`
: `${defaultDeployUrl}?${query}`
return { openUrl }
}
If customDeployUrl
functions can be async, keep the async
keyword and add await
:
? await customDeployUrl(defaultDeployUrl, query)
🤖 Prompt for AI Agents
In packages/common/js/runtime-deploy.js around lines 27 to 42, the function is
incorrectly handling customDeployUrl: keep the async keyword (to support async
custom functions), change the call to await customDeployUrl(defaultDeployUrl,
query) when customDeployUrl is a function, then validate the returned value is a
non-empty string and matches expected URL constraints (e.g., same-origin or a
safe path/pattern) before assigning to openUrl; if validation fails, fall back
to the computed defaultDeployUrl with query and log/handle the invalid custom
URL case.
export const runtimeDeploy = async () => { | ||
const { openUrl } = await deployPage() | ||
|
||
if (runtimeWindow && !runtimeWindow.closed) { | ||
try { | ||
runtimeWindow.focus() | ||
} catch (e) { | ||
// eslint-disable-next-line no-console | ||
console.warn('[runtime-deploy] focus runtime window failed:', e) | ||
} | ||
return | ||
} | ||
|
||
runtimeWindow = window.open(openUrl, 'tiny-engine-runtime') | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle popup blocker scenario when opening runtime window.
The window.open
call can return null
if blocked by the browser's popup blocker, but this case is not handled. The code will silently fail and leave runtimeWindow
as null
, causing subsequent calls to always attempt to open a new window.
Apply this diff:
export const runtimeDeploy = async () => {
const { openUrl } = await deployPage()
if (runtimeWindow && !runtimeWindow.closed) {
try {
runtimeWindow.focus()
} catch (e) {
// eslint-disable-next-line no-console
console.warn('[runtime-deploy] focus runtime window failed:', e)
}
return
}
runtimeWindow = window.open(openUrl, 'tiny-engine-runtime')
+
+ if (!runtimeWindow) {
+ // eslint-disable-next-line no-console
+ console.error('[runtime-deploy] Failed to open runtime window. Popup may be blocked.')
+ // Optionally notify the user via UI
+ }
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const runtimeDeploy = async () => { | |
const { openUrl } = await deployPage() | |
if (runtimeWindow && !runtimeWindow.closed) { | |
try { | |
runtimeWindow.focus() | |
} catch (e) { | |
// eslint-disable-next-line no-console | |
console.warn('[runtime-deploy] focus runtime window failed:', e) | |
} | |
return | |
} | |
runtimeWindow = window.open(openUrl, 'tiny-engine-runtime') | |
} | |
export const runtimeDeploy = async () => { | |
const { openUrl } = await deployPage() | |
if (runtimeWindow && !runtimeWindow.closed) { | |
try { | |
runtimeWindow.focus() | |
} catch (e) { | |
// eslint-disable-next-line no-console | |
console.warn('[runtime-deploy] focus runtime window failed:', e) | |
} | |
return | |
} | |
runtimeWindow = window.open(openUrl, 'tiny-engine-runtime') | |
if (!runtimeWindow) { | |
// eslint-disable-next-line no-console | |
console.error('[runtime-deploy] Failed to open runtime window. Popup may be blocked.') | |
// Optionally notify the user via UI | |
} | |
} |
🤖 Prompt for AI Agents
In packages/common/js/runtime-deploy.js around lines 44 to 58, handle the case
when window.open is blocked and returns null: assign the result of window.open
to a local variable, check if it's null, and if so log/warn about the popup
blocker and use a fallback (for example navigate the current tab to openUrl or
surface a user-facing notification asking to enable popups). If window.open
returns a Window object, assign it to runtimeWindow as before; otherwise leave
runtimeWindow null and return after performing the fallback so subsequent calls
can retry.
const searchParams = new URLSearchParams(location.search) | ||
const appId = searchParams.get('id') | ||
const { fetchAppSchema, fetchBlocks } = useAppSchema() | ||
await fetchAppSchema(appId || '') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add validation and error handling for the appId
parameter.
The code does not validate or handle the case where appId
is null
or empty. If the id
query parameter is missing, fetchAppSchema
will be called with an empty string, which may cause a silent failure or unexpected behavior.
Add validation:
const searchParams = new URLSearchParams(location.search)
const appId = searchParams.get('id')
+
+ if (!appId) {
+ throw new Error('Missing required "id" query parameter')
+ }
+
const { fetchAppSchema, fetchBlocks } = useAppSchema()
- await fetchAppSchema(appId || '')
+ await fetchAppSchema(appId)
await fetchBlocks()
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const searchParams = new URLSearchParams(location.search) | |
const appId = searchParams.get('id') | |
const { fetchAppSchema, fetchBlocks } = useAppSchema() | |
await fetchAppSchema(appId || '') | |
const searchParams = new URLSearchParams(location.search) | |
const appId = searchParams.get('id') | |
if (!appId) { | |
throw new Error('Missing required "id" query parameter') | |
} | |
const { fetchAppSchema, fetchBlocks } = useAppSchema() | |
await fetchAppSchema(appId) | |
await fetchBlocks() |
🤖 Prompt for AI Agents
In packages/runtime-renderer/index.ts around lines 23 to 26, the code reads the
id query param and calls fetchAppSchema with an empty string when id is missing;
add validation to ensure appId is present and non-empty before calling
fetchAppSchema, return or throw a clear error (or display a user-friendly
message) when appId is null/empty, and log the invalid input; optionally
validate the format of appId if required and avoid calling
fetchBlocks/fetchAppSchema until a valid id is provided.
if (isChildRoute) { | ||
const parentId = parseInt(page.meta.parentId) | ||
const parentRoute = routesConfig.find((r) => r.meta?.pageId === parentId) | ||
if (parentRoute) { | ||
parentRoute.children = parentRoute.children || [] | ||
parentRoute.children.push(routeConfigCurrent) | ||
parentRoute.meta.hasChildren = true | ||
if (routeConfigCurrent.meta.isDefault) { | ||
parentRoute.meta.hasDefault = true | ||
parentRoute.meta.defaultPath = `${parentRoute.path}/${routeConfigCurrent.path}` | ||
parentRoute.redirect = parentRoute.meta.defaultPath | ||
} | ||
return | ||
} | ||
} else { | ||
routesConfig.push(routeConfigCurrent) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle missing parent route explicitly.
If a child route's parent is not found (line 40), the route is silently skipped (line 50 returns early). This could lead to confusing bugs where child routes disappear without any error message, especially if the page order in the schema is unexpected.
Add explicit error handling:
if (isChildRoute) {
const parentId = parseInt(page.meta.parentId, 10)
const parentRoute = routesConfig.find((r) => r.meta?.pageId === parentId)
if (parentRoute) {
parentRoute.children = parentRoute.children || []
parentRoute.children.push(routeConfigCurrent)
parentRoute.meta.hasChildren = true
if (routeConfigCurrent.meta.isDefault) {
parentRoute.meta.hasDefault = true
parentRoute.meta.defaultPath = `${parentRoute.path}/${routeConfigCurrent.path}`
parentRoute.redirect = parentRoute.meta.defaultPath
}
return
+ } else {
+ // eslint-disable-next-line no-console
+ console.error(
+ `父路由未找到: 页面 "${page.meta.name}" (ID: ${page.meta.id}) 引用的父路由 ID ${parentId} 不存在`
+ )
}
} else {
Also add radix parameter to parseInt:
- const parentId = parseInt(page.meta.parentId)
+ const parentId = parseInt(page.meta.parentId, 10)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (isChildRoute) { | |
const parentId = parseInt(page.meta.parentId) | |
const parentRoute = routesConfig.find((r) => r.meta?.pageId === parentId) | |
if (parentRoute) { | |
parentRoute.children = parentRoute.children || [] | |
parentRoute.children.push(routeConfigCurrent) | |
parentRoute.meta.hasChildren = true | |
if (routeConfigCurrent.meta.isDefault) { | |
parentRoute.meta.hasDefault = true | |
parentRoute.meta.defaultPath = `${parentRoute.path}/${routeConfigCurrent.path}` | |
parentRoute.redirect = parentRoute.meta.defaultPath | |
} | |
return | |
} | |
} else { | |
routesConfig.push(routeConfigCurrent) | |
} | |
if (isChildRoute) { | |
const parentId = parseInt(page.meta.parentId, 10) | |
const parentRoute = routesConfig.find((r) => r.meta?.pageId === parentId) | |
if (parentRoute) { | |
parentRoute.children = parentRoute.children || [] | |
parentRoute.children.push(routeConfigCurrent) | |
parentRoute.meta.hasChildren = true | |
if (routeConfigCurrent.meta.isDefault) { | |
parentRoute.meta.hasDefault = true | |
parentRoute.meta.defaultPath = `${parentRoute.path}/${routeConfigCurrent.path}` | |
parentRoute.redirect = parentRoute.meta.defaultPath | |
} | |
return | |
} else { | |
// eslint-disable-next-line no-console | |
console.error( | |
`父路由未找到: 页面 "${page.meta.name}" (ID: ${page.meta.id}) 引用的父路由 ID ${parentId} 不存在` | |
) | |
} | |
} else { | |
routesConfig.push(routeConfigCurrent) | |
} |
🤖 Prompt for AI Agents
In packages/runtime-renderer/src/router/index.ts around lines 38 to 54, child
routes are silently skipped when their parent is not found and parseInt is
called without a radix; update the code to call parseInt(page.meta.parentId, 10)
and, if parentRoute is not found, log or throw an explicit error/warning
(include page id and route info) and still add the child to a fallback
collection or push it to routesConfig as a top-level route so it is not lost;
ensure the error path does not just return silently but provides context for
debugging.
export const generateStoresConfig = () => { | ||
const { globalStates } = useAppSchema() | ||
if (globalStates.value.length === 0) return [] | ||
return globalStates.value.map((store) => ({ | ||
id: store.id, | ||
state: JSON.parse(JSON.stringify(store.state)), | ||
actions: Object.fromEntries( | ||
Object.keys(store.actions || {}).map((key) => { | ||
// 使用 parseJSFunction ,但是上下文由pinia内部绑定 | ||
return [key, parseJSFunction(store.actions[key], {}, {})] | ||
}) | ||
), | ||
getters: Object.fromEntries( | ||
Object.keys(store.getters || {}).map((key) => { | ||
// 同样处理 getters | ||
return [key, parseJSFunction(store.getters[key], {}, {})] | ||
}) | ||
) | ||
})) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix deep cloning method and add error handling.
There are several issues with generateStoresConfig
:
-
Line 12: Using
JSON.parse(JSON.stringify())
for deep cloning is fragile and will fail or produce incorrect results for:- Functions (will be dropped)
- Date objects (converted to strings)
undefined
values (dropped)- Circular references (throws error)
- Special objects like Map, Set, RegExp
-
Lines 16, 22: No error handling if
parseJSFunction
fails or returns undefined. -
Line 15: Chinese comment should be translated to English.
Apply this diff:
+import { cloneDeep } from 'lodash-es' // or implement a proper deep clone utility
+
export const generateStoresConfig = () => {
const { globalStates } = useAppSchema()
if (globalStates.value.length === 0) return []
return globalStates.value.map((store) => ({
id: store.id,
- state: JSON.parse(JSON.stringify(store.state)),
+ state: cloneDeep(store.state), // Use a proper deep clone utility
actions: Object.fromEntries(
Object.keys(store.actions || {}).map((key) => {
- // 使用 parseJSFunction ,但是上下文由pinia内部绑定
- return [key, parseJSFunction(store.actions[key], {}, {})]
+ // parseJSFunction returns the parsed function; context is bound by Pinia internally
+ const fn = parseJSFunction(store.actions[key], {}, {})
+ if (!fn) {
+ console.error(`Failed to parse action: ${key} in store: ${store.id}`)
+ return [key, () => {}] // fallback to noop
+ }
+ return [key, fn]
})
),
getters: Object.fromEntries(
Object.keys(store.getters || {}).map((key) => {
- // 同样处理 getters
- return [key, parseJSFunction(store.getters[key], {}, {})]
+ // Parse getters similarly
+ const fn = parseJSFunction(store.getters[key], {}, {})
+ if (!fn) {
+ console.error(`Failed to parse getter: ${key} in store: ${store.id}`)
+ return [key, () => undefined] // fallback
+ }
+ return [key, fn]
})
)
}))
}
Note: You'll need to install lodash-es
or implement a proper deep clone utility that handles functions, dates, and circular references correctly.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const generateStoresConfig = () => { | |
const { globalStates } = useAppSchema() | |
if (globalStates.value.length === 0) return [] | |
return globalStates.value.map((store) => ({ | |
id: store.id, | |
state: JSON.parse(JSON.stringify(store.state)), | |
actions: Object.fromEntries( | |
Object.keys(store.actions || {}).map((key) => { | |
// 使用 parseJSFunction ,但是上下文由pinia内部绑定 | |
return [key, parseJSFunction(store.actions[key], {}, {})] | |
}) | |
), | |
getters: Object.fromEntries( | |
Object.keys(store.getters || {}).map((key) => { | |
// 同样处理 getters | |
return [key, parseJSFunction(store.getters[key], {}, {})] | |
}) | |
) | |
})) | |
} | |
import { cloneDeep } from 'lodash-es' // or replace with your deep-clone utility of choice | |
export const generateStoresConfig = () => { | |
const { globalStates } = useAppSchema() | |
if (globalStates.value.length === 0) return [] | |
return globalStates.value.map((store) => ({ | |
id: store.id, | |
state: cloneDeep(store.state), // Use a proper deep clone utility | |
actions: Object.fromEntries( | |
Object.keys(store.actions || {}).map((key) => { | |
// parseJSFunction returns the parsed function; context is bound by Pinia internally | |
const fn = parseJSFunction(store.actions[key], {}, {}) | |
if (!fn) { | |
console.error(`Failed to parse action: ${key} in store: ${store.id}`) | |
return [key, () => {}] // fallback to noop | |
} | |
return [key, fn] | |
}) | |
), | |
getters: Object.fromEntries( | |
Object.keys(store.getters || {}).map((key) => { | |
// Parse getters similarly | |
const fn = parseJSFunction(store.getters[key], {}, {}) | |
if (!fn) { | |
console.error(`Failed to parse getter: ${key} in store: ${store.id}`) | |
return [key, () => undefined] // fallback | |
} | |
return [key, fn] | |
}) | |
) | |
})) | |
} |
🤖 Prompt for AI Agents
In packages/runtime-renderer/src/stores/index.ts around lines 7 to 26, replace
the fragile JSON.parse(JSON.stringify(...)) deep clone with a proper deep-clone
that preserves functions, Date objects and handles circular refs (e.g., use
lodash-es cloneDeep or an existing project clone utility), translate the Chinese
comments to English, and wrap each parseJSFunction call for actions and getters
in try/catch so failures return a safe fallback (e.g., undefined or a noop) and
log or surface the error; ensure the mapped actions/getters entries use the
cloned state and continue to produce an Object.fromEntries shape even if some
functions fail.
// 用于解析store中的actions和getters | ||
export const parseJSFunction = (data: any, _scope: any = null, _ctx: any = null) => { | ||
try { | ||
const fn = newFn(`return ${data.value}`).call(null) // 拿到函数本体,不绑定任何 this | ||
return fn | ||
} catch (error) { | ||
// eslint-disable-next-line no-console | ||
console.error('函数声明解析报错:', error, data) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Unsafe dynamic code execution without sanitization.
parseJSFunction
uses the Function constructor with untrusted data.value
input, creating a code injection vulnerability. This pattern allows arbitrary code execution if an attacker can control the schema data. Additionally, errors are only logged to the console rather than being surfaced to monitoring systems.
Consider these security improvements:
- Validate and sanitize
data.value
before execution - Implement a whitelist of allowed function patterns
- Use a safe expression parser instead of dynamic function creation
- Add proper error logging/monitoring instead of console.error
- Consider using a sandboxed execution environment
Example safer pattern:
export const parseJSFunction = (data: any, _scope: any = null, _ctx: any = null) => {
try {
// Validate input format
if (!data?.value || typeof data.value !== 'string') {
throw new Error('Invalid function data')
}
// Add validation/sanitization here
if (containsUnsafePatterns(data.value)) {
throw new Error('Unsafe function pattern detected')
}
const fn = newFn(`return ${data.value}`).call(null)
return fn
} catch (error) {
// Use proper logging framework
logger.error('Function parsing failed:', { error, data })
// Consider throwing or returning a safe default
return undefined
}
}
🤖 Prompt for AI Agents
In packages/runtime-renderer/src/utils/data-utils.ts around lines 14 to 23,
parseJSFunction currently uses the Function constructor on untrusted data.value
and only console.errors, creating a code injection risk; replace this by first
validating that data?.value is a non-empty string, run a whitelist or
regexp-based check for allowed patterns (or prefer using a safe expression
parser/evaluator such as jsep/safe-eval/sandboxed interpreter instead of new
Function), avoid calling the Function constructor on raw input, and ensure
failures are reported via the project logger (logger.error with error and data)
and return undefined or a safe default rather than executing unsafe code; if
dynamic execution is absolutely required, perform strict sanitization, limit
allowed identifiers/operators, and run inside a sandboxed environment.
default: 'video' | ||
}, | ||
renderType: 'icon', | ||
deployUrl: '' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Document deployUrl configuration expectations.
The deployUrl
option defaults to an empty string. Ensure that:
- Runtime configuration or user setup properly initializes this value
- The toolbar handles missing/empty deployUrl gracefully (shows error or disables the button)
- Documentation explains how to configure the deploy URL
🏁 Script executed:
#!/bin/bash
# Search for deployUrl usage and configuration handling
rg -n "deployUrl" packages/toolbars/runtime-deploy/ -A 3 -B 1
Length of output: 222
🏁 Script executed:
#!/bin/bash
# Find all occurrences of deployUrl in the repository
rg -n "deployUrl" -C3 .
Length of output: 963
🏁 Script executed:
#!/bin/bash
sed -n '1,100p' packages/common/js/runtime-deploy.js
Length of output: 1898
Add documentation for deployUrl
configuration
deployUrl
defaults to an empty string—at runtime this falsy value causes the toolbar to fall back to the built-in defaultDeployUrl
. Add or update your docs to cover:
- How to override
deployUrl
via the runtime meta config - The default fallback URL format (
./runtime.html
in dev, or…/runtime
otherwise) - How empty or missing
deployUrl
values are handled gracefully ([runtime-deploy.js lines 27–37])
🤖 Prompt for AI Agents
In packages/toolbars/runtime-deploy/meta.js around line 10, the meta file
currently sets deployUrl to an empty string but lacks documentation; update the
project docs (or add inline comments) to explain how to override deployUrl in
the runtime meta config, state the default fallback behavior (use ./runtime.html
in dev, and ../runtime or the production runtime path otherwise), and note that
empty or missing deployUrl values are handled gracefully by the runtime (see
runtime-deploy.js lines 27–37) so users know the runtime will fall back to
defaultDeployUrl; include an example override, the exact fallback URL formats,
and a short note referencing the runtime code location.
"@opentiny/tiny-engine-vite-plugin-meta-comments": "workspace:*", | ||
"@vitejs/plugin-vue": "^5.1.2", | ||
"@vitejs/plugin-vue-jsx": "^4.0.1", | ||
"vite": "^5.4.2" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify Vite version consistency across packages.
This package uses vite: ^5.4.2
, but packages/runtime-renderer/package.json
specifies vite: ^4.0.0
. Version mismatches can cause build inconsistencies in a monorepo.
Run the following script to check Vite versions across all packages:
🏁 Script executed:
#!/bin/bash
# Description: Find all Vite version declarations across the monorepo
echo "=== Vite versions in package.json files ==="
fd -t f '^package\.json$' -x grep -H '"vite"' {} \;
Length of output: 4071
Align Vite version in runtime-renderer with monorepo
packages/runtime-renderer/package.json
- "vite": "^4.0.0"
+ "vite": "^5.4.2"
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In packages/toolbars/runtime-deploy/package.json around line 35, the vite
dependency is set to "^5.4.2" but the PR comment requests aligning the Vite
version with packages/runtime-renderer; update this dependency to match the
exact "vite" version specified in packages/runtime-renderer/package.json, save
the change, then run the repo's package manager (pnpm/yarn/npm) to update
lockfiles so all workspace packages use the same Vite version.
English | 简体中文
PR
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
Background and solution
当前社区内对低代码应用的运行时预览有较强呼声,为了实现运行时渲染,新实现了一个runtime-renderer包来执行整个应用schema的运行时渲染

What is the current behavior?
当前仅能在设计器或者预览中查看页面效果,完整的应用效果需要出码
Issue Number: N/A
What is the new behavior?
可以点击预览按钮旁的runtime-deploy这个toolbar按钮进入运行时渲染窗口,直接查看应用运行时渲染效果

Does this PR introduce a breaking change?
Other information
Summary by CodeRabbit
New Features
Improvements