Skip to content
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

feat: materials new protocol #940

Open
wants to merge 10 commits into
base: refactor/develop
Choose a base branch
from

Conversation

yy-wow
Copy link
Collaborator

@yy-wow yy-wow commented Dec 17, 2024

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?

物料资产包协议修改。涉及画布渲染、预览、出码
json { "data": { "framework": "Vue", "materials": { "components": [ { "id": '', "component": 'TinyButton' "npm": { "pacakge": "@opentiny/vue", // "version": "3.11.0", // 移除 // "script" : "", // 移除 // "css": "", // 移除 "exportName": "Button" } } ], "blocks": [], "snippets": [], "packages": [ // 新增字段 { "name": "TinyVue组件库", // 名称 "package": "@opentiny/vue", // npm包名 "version": "3.11.0", // npm包版本 "script": "https://xxxxx", // esm格式的js文件cdn地址 "css": ["https://xxxxx"], // 样式表文件cdn地址 ] }, { "name": "xxx工具库", // 名称 "package": "lodash", // npm包名 "version": "3.11.0", // npm包版本 "script": "https://xxxxx", // esm格式的js文件cdn地址 "css": ["https://xxxxx"], // 样式表文件cdn地址 "needDeclare": true // 出码时是否需要声明到package.json的依赖,默认为true。当物料npm包中已声明的依赖且不需要在宿主工程声明时,可设置为false }, ] } } }

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new package management structure for dependencies.
    • Added support for canvas-related dependencies in state management.
    • Enhanced messaging and resource functionalities.
  • Improvements

    • Streamlined resource and material dependency tracking.
    • Updated utility and component import mechanisms.
    • Improved localization support for internationalization.
    • Refined canvas initialization and rendering processes.
    • Enhanced functionality for handling npm resource types.
  • Bug Fixes

    • Corrected typos in function names.
    • Fixed issues with dependency loading and component registration.
  • Refactoring

    • Restructured how third-party dependencies are managed.
    • Simplified import and resource handling logic.
    • Enhanced error handling for utility imports.
    • Removed unnecessary functions and streamlined control flow.

Copy link
Contributor

coderabbitai bot commented Dec 17, 2024

Walkthrough

This pull request introduces a comprehensive restructuring of how component dependencies are managed across the application. The changes primarily focus on replacing thirdPartyDeps with componentsDepsMap and canvasDeps, enhancing the way external libraries and components are imported and initialized. Multiple files across the project have been updated to support this new dependency management approach, including modifications to import maps, resource handling, and component loading mechanisms.

Changes

File Path Change Summary
designer-demo/public/mock/bundle.json Removed version, script, and CSS properties for components; added new package entries for TinyVue and element-plus
mockServer/src/mock/get/app-center/v1/apps/schema/918.json Updated axios CDN link, corrected dataHandler function name, expanded components tree
packages/canvas/DesignCanvas/src/DesignCanvas.vue Transformed canvasSrcDoc to reactive reference, updated initialization logic
packages/canvas/DesignCanvas/src/importMap.js Added canvasDeps parameter to getImportMapData function
packages/canvas/common/src/utils.js Added getImportMapKeys, refactored dynamicImportComponents, introduced getComponents
packages/canvas/container/src/CanvasContainer.vue Updated dependency management, removed useMaterial import
packages/canvas/render/src/RenderMain.js Made setUtils function asynchronous, improved npm utility imports
packages/canvas/render/src/runner.js Updated dependency extraction and component loading
packages/plugins/materials/src/composable/useMaterial.js Introduced componentsDepsMap, added parseMaterialsDependencies function
packages/vue-generator/src/plugins/genDependenciesPlugin.js Added packages property processing in parseSchema function
packages/common/js/preview.js Updated to use canvasDeps instead of thirdPartyDeps
packages/design-core/src/preview/src/preview/Preview.vue Removed addUtilsImportMap function, simplified import map handling
packages/plugins/materials/src/composable/useResource.js Introduced canvasDeps to appSchemaState for better state management
packages/plugins/materials/src/meta/block/src/BlockGroupPanel.vue Removed useMaterial import, simplified addBlocks method logic
packages/bridge/src/js/resource.js Updated saveResource function to be asynchronous, improved resource handling

Possibly related PRs

  • feat: add hooks entry #550: The changes in the main PR regarding the bundle.json file and package management are related to the removal and addition of properties, which aligns with the updates in the package.json files in the retrieved PR.
  • feat: move composables to module #569: The modifications in the main PR about managing dependencies and components in bundle.json relate to the changes in the package.json files, indicating a broader restructuring of package management.
  • refactor: canvas 元应用改造1 #576: The changes in the main PR regarding the organization of component dependencies in bundle.json are relevant to the updates in the mockServer and canvas components in the retrieved PR.
  • feat(canvas): add the renderer extension capability #718: The introduction of new components and their configurations in the main PR aligns with the enhancements made to the rendering logic in the retrieved PR, indicating a focus on improving component management.
  • feat: page management and block ui redesign #740: The UI redesign and management of components in the main PR are related to the changes in the PluginBlockList.vue and other components in the retrieved PR, which also focus on enhancing user interaction.
  • feat(bundle): update label and description of bundle.json #812: The updates in the main PR regarding the bundle.json file and component configurations are relevant to the changes in the PluginBlockList.vue and other components in the retrieved PR.
  • fix: sync material to cli #921: The changes in the main PR about the bundle.json file and component configurations are related to the updates in the mockServer and canvas components in the retrieved PR.
  • feat: support checking all blocks in material add block panel #923: The new select-all functionality introduced in the main PR is relevant to the changes in the PluginBlockList.vue and SelectAll.vue components in the retrieved PR, which enhance user interaction.
  • fix: basePropsGroup use deepclone prevent clone array itself  #975: The modifications in the main PR regarding the handling of base properties in useMaterial.js are related to the changes in the patchBaseProps method in the retrieved PR, which also focuses on improving data integrity.
  • feat:preview multi page structured data transfer and nested display #978: The enhancements in the main PR regarding the preview functionality are related to the changes in the Preview.vue component in the retrieved PR, which also focuses on improving the handling of multi-page data.
  • fix: preview should not overwrite mainFile #920: The changes in the main PR regarding the preview functionality are relevant to the updates in the Preview.vue component in the retrieved PR, which also focuses on improving the handling of multi-page data.
  • feat:UI specification adjustment #938: The UI specification adjustments in the main PR are related to the changes in the CanvasFlexBox and CanvasSection components in the retrieved PR, which also focus on enhancing the layout and usability of components.

Suggested Reviewers

  • hexqi
  • chilingling

Poem

🐰 Hop, hop, dependencies dance!
Components now flow with grace
Import maps sing their sweet refrain
Tiny Vue leaps without a strain
Code magic blooms in every space! 🌈

Finishing Touches

  • 📝 Generate Docstrings

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added the refactor-main refactor/develop branch feature label Dec 17, 2024
Copy link
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: 8

🔭 Outside diff range comments (3)
packages/canvas/common/src/utils.js (2)

Line range hint 107-116: Handle undefined modules when setting components

When dynamicImportComponents returns an empty object, modules[exportName] may be undefined. This could lead to issues when assigning components. Consider checking if modules[exportName] exists before assigning it.

Apply this diff to fix the issue:

export const setComponents = async ({ package: pkg, script, components }) => {
  if (!pkg) return

  const modules = await dynamicImportComponents(pkg, script)

  Object.entries(components).forEach(([componentId, exportName]) => {
    if (!window.TinyLowcodeComponent[componentId]) {
+     if (modules[exportName]) {
        window.TinyLowcodeComponent[componentId] = modules[exportName]
+     } else {
+       console.warn(`Component "${exportName}" not found in module "${pkg}".`)
+     }
    }
  })
}

Line range hint 125-131: Fix incorrect argument passing to dynamicImportComponents

In updateDependencies, the scripts.map(dynamicImportComponents) call passes the entire script object as the first argument, which doesn't match the expected parameters. Destructure the script objects to pass the correct arguments.

Apply this diff to fix the issue:

export const updateDependencies = ({ detail }) => {
  const { scripts = [], styles = [] } = detail || {}
  const { styles: canvasStyles } = window.componentsDepsMap
  const newStyles = [...styles].filter((item) => !canvasStyles.has(item))

  newStyles.forEach((item) => canvasStyles.add(item))

- const promises = [...newStyles].map((src) => addStyle(src)).concat(scripts.map(dynamicImportComponents))
+ const promises = [...newStyles]
+   .map((src) => addStyle(src))
+   .concat(scripts.map(({ package: pkg, script }) => dynamicImportComponents(pkg, script)))

  Promise.allSettled(promises)
}
packages/plugins/materials/src/composable/useMaterial.js (1)

Line range hint 417-437: Avoid mutating state while iterating in getBlockDeps

Modifying materialState.componentsDepsMap.scripts while iterating over scripts may lead to unexpected behavior. Collect new scripts separately before updating the state.

Apply this diff:

const getBlockDeps = (dependencies = {}) => {
  const { scripts = [], styles = [] } = dependencies
+ const newScripts = []

  scripts.length &&
    scripts.forEach((npm) => {
      const { package: pkg, script, css, components } = npm
      const npmInfo = materialState.componentsDepsMap.scripts.find((item) => item.package === pkg)

      if (!npmInfo || !npmInfo.script) {
-       materialState.componentsDepsMap.scripts.push({ package: pkg, script, css, components })
+       newScripts.push({ package: pkg, script, css, components })
      } else {
        const components = npmInfo.components || {}
        npmInfo.components = { ...components, ...npm.components }
      }
    })

+ materialState.componentsDepsMap.scripts.push(...newScripts)
  styles?.forEach((item) => materialState.componentsDepsMap.styles.add(item))
}
🧹 Nitpick comments (10)
packages/canvas/common/src/utils.js (1)

79-100: Improve error handling in dynamicImportComponents

If neither the import map includes the package nor a script URL is provided, the modules object remains empty, which may cause issues downstream. Consider adding a warning or error handling to notify about the missing module.

You can add a warning message when the module fails to load:

const dynamicImportComponents = async (pkg, script) => {
  if (window.TinyComponentLibs[pkg]) {
    return window.TinyComponentLibs[pkg]
  }

  let modules = {}

  try {
    // 优先从importmap导入,兼容npm.script字段定义的cdn地址
    if (getImportMapKeys().includes(pkg)) {
      modules = await import(/* @vite-ignore */ pkg)
    } else if (script) {
      modules = await import(/* @vite-ignore */ script)
+   } else {
+     console.warn(`Module "${pkg}" not found in import map and no script URL provided.`)
    }
  } catch (error) {
    modules = {}
  }

  window.TinyComponentLibs[pkg] = modules

  return modules
}
packages/plugins/materials/src/composable/useMaterial.js (1)

295-318: Consolidate dependency aggregation logic

The logic in setCanvasDeps for aggregating dependencies is similar to other parts of the code. Consider creating a utility function to handle dependency aggregation to avoid duplication.

packages/canvas/DesignCanvas/src/importMap.js (2)

Line range hint 3-46: Update function documentation to reflect new parameter

The function getImportMapData now accepts a second parameter canvasDeps. Update the JSDoc comment to include this parameter and its purpose.

Apply this diff:

/**
 * Generates import map data for the canvas.
+ * @param {Object} overrideVersions - Optional versions to override default import map versions.
+ * @param {Object} canvasDeps - Dependencies from the canvas, including scripts and styles.
 */
export function getImportMapData(overrideVersions = {}, canvasDeps = { scripts: [], styles: [] }) {

30-35: Initialize materialRequire imports properly

Ensure that materialRequire.imports is always an object to prevent potential undefined errors when spreading into importMap.imports.

Apply this diff:

const materialRequire = canvasDeps.scripts.reduce((imports, { package: pkg, script }) => {
  imports[pkg] = script
  return imports
}, {})

const importMap = {
  imports: {
    vue: `${VITE_CDN_DOMAIN}/vue@${importMapVersions.vue}/dist/vue.runtime.esm-browser.prod.js`,
    'vue-i18n': `${VITE_CDN_DOMAIN}/vue-i18n@${importMapVersions.vueI18n}/dist/vue-i18n.esm-browser.js`,
    ...blockRequire.imports,
    ...tinyVueRequire.imports,
+   ...materialRequire
  }
}
packages/vue-generator/src/plugins/genDependenciesPlugin.js (1)

48-54: Consider adding package validation

While the package processing logic is correct, it might benefit from additional validation to ensure package integrity.

Consider adding validation:

  packages.forEach((item) => {
    const { package: packageName, version } = item

+   if (!packageName) {
+     console.warn('Invalid package entry:', item)
+     return
+   }
+
+   if (version && !isValidSemver(version)) {
+     console.warn(`Invalid version '${version}' for package '${packageName}'`)
+     return
+   }

    if (packageName && !resDeps[packageName]) {
      resDeps[packageName] = version || 'latest'
    }
  })

You'll need to add the following import:

import { valid as isValidSemver } from 'semver'
packages/canvas/DesignCanvas/src/DesignCanvas.vue (3)

29-29: Consider grouping related imports

Consider grouping useMessage with other related hooks for better code organization.


61-74: Consider extracting initialization logic

The canvas initialization logic could be extracted into a separate function for better maintainability and reusability.

+const initCanvasDeps = (deps) => {
+  if (canvasSrc) {
+    return
+  }
+  const { importMap, importStyles } = getImportMapData(
+    getMergeMeta('engine.config')?.importMapVersion,
+    deps
+  )
+  canvasSrcDoc.value = initCanvas(importMap, importStyles).html
+}
+
 useMessage().subscribe({
   topic: 'init_canvas_deps',
-  callback: (deps) => {
-    if (canvasSrc) {
-      return
-    }
-    const { importMap, importStyles } = getImportMapData(
-      getMergeMeta('engine.config')?.importMapVersion,
-      deps
-    )
-    canvasSrcDoc.value = initCanvas(importMap, importStyles).html
-  }
+  callback: initCanvasDeps
 })

Line range hint 82-147: Consider extracting complex conditions

The watch logic contains complex nested conditions. Consider extracting these into named functions or constants for better readability.

Example approach:

const shouldShowConfirm = (isSaved, pageSchema, oldPageSchema) => 
  (!isSaved || pageSchema !== oldPageSchema) && !showModal

const isValidPageStatus = (pageStatus, pageSchema) =>
  ![PAGE_STATUS.Guest, PAGE_STATUS.Occupy].includes(pageStatus.state) &&
  pageSchema?.componentName
designer-demo/public/mock/bundle.json (2)

43-43: Consider using object notation for shortcuts and contextMenu actions.

The current array syntax for properties and actions could be converted to object notation for better maintainability and type safety.

- "properties": ["type", "size"]
+ "properties": {
+   "type": true,
+   "size": true
+ }

- "actions": ["copy", "remove", "insert", "updateAttr", "bindEevent", "createBlock"]
+ "actions": {
+   "copy": true,
+   "remove": true,
+   "insert": true,
+   "updateAttr": true,
+   "bindEevent": true,
+   "createBlock": true
+ }

Also applies to: 46-46, 49-49


Line range hint 13258-13271: Consider adding value validation for checkbox group.

The default values and options should be validated to ensure they match.

 "modelValue": ["name1", "name2"],
+"validator": {
+  "type": "array",
+  "items": {
+    "enum": ["name1", "name2", "name3"]
+  }
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 230b334 and 7b910c5.

📒 Files selected for processing (15)
  • designer-demo/public/mock/bundle.json (51 hunks)
  • mockServer/src/mock/get/app-center/v1/apps/schema/918.json (1 hunks)
  • packages/canvas/DesignCanvas/src/DesignCanvas.vue (2 hunks)
  • packages/canvas/DesignCanvas/src/importMap.js (2 hunks)
  • packages/canvas/common/src/utils.js (2 hunks)
  • packages/canvas/container/src/CanvasContainer.vue (1 hunks)
  • packages/canvas/container/src/container.js (1 hunks)
  • packages/canvas/render/src/RenderMain.js (1 hunks)
  • packages/canvas/render/src/runner.js (2 hunks)
  • packages/common/js/preview.js (1 hunks)
  • packages/design-core/src/preview/src/preview/Preview.vue (1 hunks)
  • packages/plugins/bridge/src/js/resource.js (2 hunks)
  • packages/plugins/materials/src/composable/useMaterial.js (9 hunks)
  • packages/toolbars/generate-code/src/Main.vue (1 hunks)
  • packages/vue-generator/src/plugins/genDependenciesPlugin.js (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/canvas/container/src/container.js
🔇 Additional comments (13)
packages/plugins/materials/src/composable/useMaterial.js (1)

403-413: ⚠️ Potential issue

Initialize materialState.dependencies to ensure it is an array

In fetchMaterial, materialState.dependencies may not be properly initialized if packages is undefined. Ensure it is always an array to prevent potential errors.

Apply this diff:

const packages = []

materials.forEach((response) => {
  if (response.status === 'fulfilled' && response.value.materials) {
    addMaterials(response.value.materials)
    packages.push(...(response.value.materials.packages || []))
  }
})

- materialState.dependencies = packages
+ materialState.dependencies = packages || []
setCanvasDeps()

Likely invalid or redundant comment.

packages/common/js/preview.js (1)

Line range hint 29-36: LGTM! Verify dependency mapping consistency

The renaming from thirdPartyDeps to componentsDepsMap is consistent with the PR's objectives. The logic for processing scripts and styles is maintained correctly.

Let's verify the consistency of this change across the codebase:

✅ Verification successful

Dependency mapping change is consistently implemented

The renaming from thirdPartyDeps to componentsDepsMap has been thoroughly verified. The new componentsDepsMap is properly integrated across the codebase:

  • Used in material state management (useMaterial.js)
  • Correctly propagated to canvas components
  • Properly handled in preview functionality
  • No lingering references to the old thirdPartyDeps name
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for any remaining references to thirdPartyDeps
rg "thirdPartyDeps" --type js --type vue

# Verify the new componentsDepsMap usage
rg "componentsDepsMap" --type js --type vue

Length of output: 140


Script:

#!/bin/bash
# Check for any remaining references to thirdPartyDeps, searching all text files
rg "thirdPartyDeps"

# Verify the new componentsDepsMap usage, searching all text files
rg "componentsDepsMap"

Length of output: 1749

packages/canvas/render/src/runner.js (1)

14-14: LGTM! Import statement updated correctly

The import statement has been updated to include setComponents, maintaining consistency with the new dependency management approach.

packages/vue-generator/src/plugins/genDependenciesPlugin.js (2)

25-25: LGTM! Schema destructuring updated correctly

The addition of the packages array to the schema destructuring aligns with the new material asset package protocol.


48-54: Verify package resolution strategy

The current implementation might need to consider package resolution conflicts between different sources (utils, componentsMap, and packages).

Let's analyze the package resolution strategy:

✅ Verification successful

Let me analyze the current implementation and verification results:

  1. The code in question handles package dependencies by storing them in resDeps object
  2. From the search results, I can see:
    • The package resolution happens in multiple places (utils, componentsMap, packages)
    • There are multiple components using the same package with different versions (e.g. @opentiny/vue has versions 0.1.16, 0.1.17, 3.2.0, 3.4.1)

Let me check the full implementation of the dependency resolution:


After analyzing the full implementation, I can now provide a final response:

Package resolution strategy is correctly implemented

The current implementation has a well-defined precedence order that prevents conflicts:

  1. Utils dependencies are processed first, with duplicate prevention via resDeps[packageName] check
  2. Component dependencies are added only if:
    • The package isn't already in resDeps
    • The component is actually used (verified via componentsSet)
  3. Additional packages are added last, also with duplicate prevention
  4. Special handling for @opentiny/vue-icon to match @opentiny/vue version

This strategy ensures that:

  • No duplicate packages are added
  • Earlier declarations take precedence
  • Only dependencies for used components are included
  • Version consistency is maintained for related packages
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potential package conflicts in the codebase
rg -A 5 "package: .*," --type js --type json

# Look for existing version resolution logic
rg "version.*latest" --type js

Length of output: 85133


Script:

#!/bin/bash
# Check the full implementation of genDependenciesPlugin
cat packages/vue-generator/src/plugins/genDependenciesPlugin.js

Length of output: 2744

packages/plugins/bridge/src/js/resource.js (3)

14-21: LGTM! Well-organized imports

The addition of useMaterial import aligns with the PR's objective of modifying the material asset package protocol.


188-221: Well-structured async/await implementation with proper error handling

The conversion to async/await and addition of try/catch improves error handling. The conditional handling of npm type resources with setCanvasDeps() aligns with the new material package protocol.


224-227: LGTM! Clear user feedback implementation

The success notification implementation provides clear feedback with dynamic messages based on the action type.

packages/design-core/src/preview/src/preview/Preview.vue (1)

83-83: LGTM! Simplified import map handling

The direct usage of store.setImportMap simplifies the code by removing unnecessary intermediate steps, making it more maintainable.

packages/toolbars/generate-code/src/Main.vue (1)

148-149: LGTM: Addition of packages array to appSchema

The addition of the packages array aligns with the PR objectives to support the new material asset package protocol.

Let's verify the packages array structure in other files:

✅ Verification successful

LGTM: Package array structure is consistent across the codebase

Based on the search results, the packages array structure is consistently defined and used across the codebase:

  • Mock data in designer-demo/public/mock/bundle.json shows the expected structure with name, package, version, and script properties
  • The structure is properly consumed in vue-generator for dependency generation
  • The implementation in useMaterial.js correctly handles and propagates the packages array
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the structure of packages array across the codebase
# Expected: Consistent structure with name, npm package name, version, and CDN links

# Search for packages array definitions
rg -A 5 "packages.*\["

Length of output: 3097

mockServer/src/mock/get/app-center/v1/apps/schema/918.json (1)

2095-2096: Verify the axios version for security concerns

The addition of CDN link for axios is good, but we should verify if version 1.7.9 is the latest stable version without known vulnerabilities.

Let's check the version:

✅ Verification successful

Axios version 1.7.9 is secure and up-to-date

The version check reveals that:

  • 1.7.9 is the current latest stable version from npm
  • The most recent high severity vulnerability affected versions <= 1.7.3 (patched in 1.7.4)
  • The specified version 1.7.9 is above the vulnerable range and includes all security patches
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify axios version security
# Expected: No known vulnerabilities for the specified version

# Check for security advisories
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: NPM, package: "axios", severities: [CRITICAL, HIGH]) {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

# Get latest version from npm
curl -s https://registry.npmjs.org/axios/latest | jq '.version'

Length of output: 1066

designer-demo/public/mock/bundle.json (2)

14081-14088: Verify package dependency configuration.

The new packages array introduces element-plus dependency configuration. While the structure looks good, we should verify the version consistency across components using this package.

✅ Verification successful

Package version consistency verified across the codebase

The version "2.4.2" of element-plus is consistently used across all instances in the codebase, including:

  • designer-demo/public/mock/bundle.json
  • mockServer/src/assets/json/appinfo.json
  • packages/engine-cli/template/designer/public/mock/bundle.json
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if all element-plus component definitions use consistent version
rg -A 1 '"package": "element-plus"' | grep -A 1 'version'

Length of output: 1561


635-635: Validate form item nesting rules.

The nesting rules for ElForm should be verified to ensure proper form structure.

Also applies to: 644-644, 647-647, 650-650

✅ Verification successful

Form nesting rules are properly defined in the configuration

The configuration correctly specifies that ElFormItem components are allowed as children within ElForm components through the childWhitelist property. This is consistently defined across multiple configuration files in the codebase, indicating proper form structure validation rules are in place.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if ElFormItem is properly nested under ElForm
ast-grep --pattern 'componentName: "ElForm" {
  $$$
  componentName: "ElFormItem"
  $$$
}'

Length of output: 92


Script:

#!/bin/bash
# Search for form nesting rules in bundle.json
rg -A 5 '"childWhitelist".*ElFormItem' public/mock/bundle.json

Length of output: 128


Script:

#!/bin/bash
# Search for component definitions related to ElForm and ElFormItem
rg -A 10 '"componentName":\s*"ElForm"' public/mock/bundle.json

Length of output: 128


Script:

#!/bin/bash
# Look for any validation or nesting rules
rg -i "form.*item.*nest|nest.*form.*item" public/mock/bundle.json

Length of output: 131


Script:

#!/bin/bash
# Find the correct path to bundle.json
fd bundle.json

Length of output: 216


Script:

#!/bin/bash
# Search for ElForm in all json files
rg -A 5 '"componentName":\s*"ElForm"' --type json

Length of output: 574


Script:

#!/bin/bash
# Search for childWhitelist in all json files
rg -A 5 '"childWhitelist".*ElFormItem' --type json

Length of output: 1522

Comment on lines 62 to 71
const getImportMapKeys = () => {
try {
const importMaps = document.querySelector('script[type="importmap"]').textContent
const importMapObject = JSON.parse(importMaps)

return Object.keys(importMapObject.import)
} catch (error) {
return []
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null check to prevent potential TypeError in getImportMapKeys

The document.querySelector('script[type="importmap"]') might return null if no import map is found, leading to a TypeError when accessing .textContent. Consider adding a null check before accessing .textContent.

Apply this diff to fix the issue:

const getImportMapKeys = () => {
  try {
-   const importMaps = document.querySelector('script[type="importmap"]').textContent
+   const importMapElement = document.querySelector('script[type="importmap"]')
+   if (!importMapElement) {
+     return []
+   }
+   const importMaps = importMapElement.textContent
    const importMapObject = JSON.parse(importMaps)
    return Object.keys(importMapObject.import)
  } catch (error) {
    return []
  }
}
📝 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.

Suggested change
const getImportMapKeys = () => {
try {
const importMaps = document.querySelector('script[type="importmap"]').textContent
const importMapObject = JSON.parse(importMaps)
return Object.keys(importMapObject.import)
} catch (error) {
return []
}
}
const getImportMapKeys = () => {
try {
const importMapElement = document.querySelector('script[type="importmap"]')
if (!importMapElement) {
return []
}
const importMaps = importMapElement.textContent
const importMapObject = JSON.parse(importMaps)
return Object.keys(importMapObject.import)
} catch (error) {
return []
}
}

Comment on lines 85 to 97
const results = await Promise.allSettled(npmUtils.map((item) => import(/* @vite-ignore */ item.content.package)))

results.forEach((res, index) => {
if (res.status !== 'fulfilled') {
return
}

const module = res.value
const { name, content } = npmUtils[index]
const { exportName, destructuring } = content

utilsCollection[name] = destructuring ? module[exportName] : module.default
})
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle rejected promises when importing npm utilities

In setUtils, the code currently ignores rejected promises from failed imports. To improve robustness, consider handling rejected promises and logging errors.

Apply this diff to handle rejected imports:

const results = await Promise.allSettled(npmUtils.map((item) => import(/* @vite-ignore */ item.content.package)))

results.forEach((res, index) => {
  if (res.status !== 'fulfilled') {
+   console.error(`Failed to import npm utility "${npmUtils[index].name}":`, res.reason)
    return
  }

  const module = res.value
  const { name, content } = npmUtils[index]
  const { exportName, destructuring } = content

  utilsCollection[name] = destructuring ? module[exportName] : module.default
})
📝 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.

Suggested change
const results = await Promise.allSettled(npmUtils.map((item) => import(/* @vite-ignore */ item.content.package)))
results.forEach((res, index) => {
if (res.status !== 'fulfilled') {
return
}
const module = res.value
const { name, content } = npmUtils[index]
const { exportName, destructuring } = content
utilsCollection[name] = destructuring ? module[exportName] : module.default
})
const results = await Promise.allSettled(npmUtils.map((item) => import(/* @vite-ignore */ item.content.package)))
results.forEach((res, index) => {
if (res.status !== 'fulfilled') {
console.error(`Failed to import npm utility "${npmUtils[index].name}":`, res.reason)
return
}
const module = res.value
const { name, content } = npmUtils[index]
const { exportName, destructuring } = content
utilsCollection[name] = destructuring ? module[exportName] : module.default
})

Comment on lines 70 to 98
const setUtils = async (data, clear, isForceRefresh) => {
if (clear) {
reset(utils)
}
const utilsCollection = {}
// 目前画布还不具备远程加载utils工具类的功能,目前只能加载TinyVue组件库中的组件工具
data?.forEach((item) => {
const util = TinyVue[item.content.exportName]
if (util) {
utilsCollection[item.name] = util
}

// 此处需要把工具类中的icon图标也加入utils上下文环境
const utilIcon = TinyVueIcon[item.content.exportName]
if (utilIcon) {
utilsCollection[item.name] = utilIcon
}
const utilsCollection = {}

// 解析函数式的工具类
if (item.type === 'function') {
data
.filter((item) => item.type === 'function')
.forEach((item) => {
const defaultFn = () => {}
utilsCollection[item.name] = generateFunction(item.content.value, context) || defaultFn
})

const npmUtils = data.filter((item) => item.type === 'npm' && item.content.cdnLink)
const results = await Promise.allSettled(npmUtils.map((item) => import(/* @vite-ignore */ item.content.package)))

results.forEach((res, index) => {
if (res.status !== 'fulfilled') {
return
}

const module = res.value
const { name, content } = npmUtils[index]
const { exportName, destructuring } = content

utilsCollection[name] = destructuring ? module[exportName] : module.default
})

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Missing await keywords in async function calls

The verification reveals several locations where setUtils is called without proper await keywords:

  • packages/canvas/container/src/container.js:
    • Line 794: getRenderer().setUtils(utils, clear, isForceRefresh)
    • Line 936: setUtils(useResource().resState.utils)
  • packages/canvas/render/src/RenderMain.js:
    • Line 108: setUtils(data, false, true)
    • Line 117: setUtils([], false, true)

These calls need to be updated to properly handle the Promise returned by the async setUtils function. Each calling function should be marked as async and use the await keyword when calling setUtils.

🔗 Analysis chain

Ensure proper handling of asynchronous setUtils function

With setUtils now being asynchronous, ensure all calls to setUtils handle the returned Promise correctly. This may involve adding await where setUtils is called or adjusting code to handle the Promise.

Run the following script to find all calls to setUtils and check if they handle the Promise:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all calls to `setUtils` that may not await the Promise

# Search for `setUtils` calls without `await`
rg --type-add 'vue:*.vue' --files-with-matches 'setUtils' | xargs rg -A 1 '^\s*(?!await\s)(\w+\.)?setUtils\('

Length of output: 350


Script:

#!/bin/bash
# Let's try a different approach to find setUtils calls
# First, find files containing setUtils
rg --type-add 'vue:*.vue' -l 'setUtils'

# Then look for actual usage patterns
echo "=== Searching for setUtils calls ==="
rg -p 'setUtils\(' 

# Also search for await setUtils to compare
echo "=== Searching for await setUtils calls ==="
rg -p 'await\s+setUtils'

Length of output: 604


Script:

#!/bin/bash
# Let's examine the context around these calls to understand if they're in async functions
echo "=== Context in container.js ==="
rg -p -B 5 -A 5 'getRenderer\(\)\.setUtils|setUtils\(' packages/canvas/container/src/container.js

echo "=== Context in RenderMain.js ==="
rg -p -B 5 -A 5 'setUtils\(' packages/canvas/render/src/RenderMain.js

Length of output: 1490

Comment on lines 254 to 255
materialState.componentsDepsMap.scripts.push(...scripts)
styles.forEach((item) => materialState.componentsDepsMap.styles.add(item))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid direct mutation of reactive properties

Directly pushing to materialState.componentsDepsMap.scripts may not trigger reactivity. Consider replacing the array to ensure updates are reactive.

Apply this diff to fix the issue:

materialState.componentsDepsMap.scripts.push(...scripts)
+ materialState.componentsDepsMap.scripts = [...materialState.componentsDepsMap.scripts]

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 280 to 293
const getUtilsDependencies = () => {
const { utils } = useResource().resState

return utils
.filter((item) => item.type === 'npm' && item.content.cdnLink)
.map((item) => {
const { package: pkg, cdnLink } = item.content

return {
package: pkg,
script: cdnLink
}
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Check for undefined properties in getUtilsDependencies

In getUtilsDependencies, be cautious of utilities that may not have content or cdnLink properties, which could lead to runtime errors. Add checks to ensure these properties exist before accessing them.

Apply this diff:

return utils
  .filter((item) => item.type === 'npm' && item.content && item.content.cdnLink)
  .map((item) => {
    const { package: pkg, cdnLink } = item.content
    return {
      package: pkg,
      script: cdnLink
    }
  })
📝 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.

Suggested change
const getUtilsDependencies = () => {
const { utils } = useResource().resState
return utils
.filter((item) => item.type === 'npm' && item.content.cdnLink)
.map((item) => {
const { package: pkg, cdnLink } = item.content
return {
package: pkg,
script: cdnLink
}
})
}
const getUtilsDependencies = () => {
const { utils } = useResource().resState
return utils
.filter((item) => item.type === 'npm' && item.content && item.content.cdnLink)
.map((item) => {
const { package: pkg, cdnLink } = item.content
return {
package: pkg,
script: cdnLink
}
})
}

Comment on lines 85 to 89
const { scripts: componentsScriptsDeps = [], styles: componentsStylesDeps = [] } = window.componentsDepsMap || {}

Promise.all([
...thirdScripts.map(dynamicImportComponents),
...scripts.map((src) => addScript(src)).concat([...thirdStyles, ...styles].map((src) => addStyle(src)))
...componentsScriptsDeps.map(setComponents),
...scripts.map((src) => addScript(src)).concat([...componentsStylesDeps, ...styles].map((src) => addStyle(src)))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider implementing proper error handling

While the dependency loading logic is sound, the error handling is currently disabled. This could mask potential issues during component loading.

Consider implementing proper error handling:

  Promise.all([
    ...componentsScriptsDeps.map(setComponents),
    ...scripts.map((src) => addScript(src)).concat([...componentsStylesDeps, ...styles].map((src) => addStyle(src)))
-  ]).finally(() => create(config))
+  ]).then(() => {
+    create(config)
+  }).catch((error) => {
+    console.error('Failed to load dependencies:', error)
+    dispatch('canvasError', { detail: { 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.

Suggested change
const { scripts: componentsScriptsDeps = [], styles: componentsStylesDeps = [] } = window.componentsDepsMap || {}
Promise.all([
...thirdScripts.map(dynamicImportComponents),
...scripts.map((src) => addScript(src)).concat([...thirdStyles, ...styles].map((src) => addStyle(src)))
...componentsScriptsDeps.map(setComponents),
...scripts.map((src) => addScript(src)).concat([...componentsStylesDeps, ...styles].map((src) => addStyle(src)))
const { scripts: componentsScriptsDeps = [], styles: componentsStylesDeps = [] } = window.componentsDepsMap || {}
Promise.all([
...componentsScriptsDeps.map(setComponents),
...scripts.map((src) => addScript(src)).concat([...componentsStylesDeps, ...styles].map((src) => addStyle(src)))
]).then(() => {
create(config)
}).catch((error) => {
console.error('Failed to load dependencies:', error)
dispatch('canvasError', { detail: { error } })
})

Comment on lines 185 to 187
export const saveResource = async (data, callback, emit) => {
let result = {}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix variable shadowing issue

The result variable is declared but never used at the top level, and then shadowed within the try block.

Apply this fix:

-let result = {}
+const result = {}

Committable suggestion skipped: line range outside the PR's diff.

@@ -113,7 +113,7 @@ export default {
const beforeCanvasReady = () => {
if (iframe.value) {
const win = iframe.value.contentWindow
win.thirdPartyDeps = useMaterial().materialState.thirdPartyDeps
win.componentsDepsMap = useMaterial().materialState.componentsDepsMap
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Warning: Incomplete renaming detected in the codebase

The renaming from thirdPartyDeps to componentsDepsMap is incomplete. While the property usage has been updated, there are still related function names using the old terminology in useMaterial.js:

  • generateThirdPartyDeps
  • setThirdPartyDeps

These function names should also be updated to maintain consistency with the new naming convention:

  • packages/plugins/materials/src/composable/useMaterial.js: Update function names to match the new componentsDeps terminology
🔗 Analysis chain

LGTM: Consistent renaming of dependency property

The renaming from thirdPartyDeps to componentsDepsMap improves clarity and aligns with the new dependency management approach.

Let's verify all references have been updated:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining references to thirdPartyDeps
# Expected: No remaining references to old property name

# Search for any remaining references to thirdPartyDeps
rg "thirdPartyDeps"

Length of output: 19


Script:

#!/bin/bash
# Let's search for both old and new property names to understand the context better
# and ensure the change is consistent

echo "Searching for componentsDepsMap references:"
rg "componentsDepsMap" -A 2

echo -e "\nSearching for any potential variations of thirdPartyDeps:"
rg -i "third.*deps"

Length of output: 3994

Copy link
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

🔭 Outside diff range comments (1)
packages/canvas/common/src/utils.js (1)

Line range hint 110-117: Add error handling for undefined components and module exports to prevent runtime exceptions

In the setComponents function, there's a possibility that components or modules[exportName] may be undefined. Attempting to access or assign these values without validation could lead to runtime errors. Please add checks to ensure that these variables are defined before proceeding.

Apply this diff to add necessary checks:

 export const setComponents = async ({ package: pkg, script, components }) => {
   if (!pkg) return

   const modules = await dynamicImportComponents(pkg, script)

+  if (!components || typeof components !== 'object') {
+    console.error('Components object is undefined or invalid')
+    return
+  }

   Object.entries(components).forEach(([componentId, exportName]) => {
+    if (!modules || !modules[exportName]) {
+      console.error(`Module '${exportName}' not found in package '${pkg}'`)
+      return
+    }
     if (!window.TinyLowcodeComponent[componentId]) {
       window.TinyLowcodeComponent[componentId] = modules[exportName]
     }
   })
 }
🧹 Nitpick comments (4)
packages/canvas/common/src/utils.js (1)

63-71: Add a null check for document.querySelector to prevent potential errors when the import map is missing

In the getImportMapKeys function, if the <script type="importmap"> element does not exist in the document, document.querySelector will return null, leading to a TypeError when attempting to access textContent. While the try...catch block handles the error by returning an empty array, it's better to explicitly check for the element's existence to avoid unnecessary exceptions.

Apply this diff to add a null check:

 const getImportMapKeys = () => {
   try {
-    const importMaps = document.querySelector('script[type="importmap"]').textContent
+    const importMapScript = document.querySelector('script[type="importmap"]')
+    if (!importMapScript) {
+      return []
+    }
+    const importMaps = importMapScript.textContent
     const importMapObject = JSON.parse(importMaps)
 
     return Object.keys(importMapObject.import)
   } catch (error) {
     return []
   }
 }
packages/plugins/materials/src/composable/useMaterial.js (2)

Line range hint 212-255: Rename setThirdPartyDeps to setComponentsDeps for consistency

The function generateThirdPartyDeps has been renamed to generateComponentsDeps, but setThirdPartyDeps still retains the old naming convention. For consistency and clarity, consider renaming setThirdPartyDeps to setComponentsDeps.

Apply this diff to rename the function:

-const setThirdPartyDeps = (components) => {
+const setComponentsDeps = (components) => {
   const { scripts = [], styles = [] } = generateComponentsDeps(components)
-  materialState.componentsDepsMap.scripts.push(...scripts)
+  materialState.componentsDepsMap.scripts.push(...scripts)
   styles.forEach((item) => materialState.componentsDepsMap.styles.add(item))
 }

Don't forget to update all references to setThirdPartyDeps in the codebase:

#!/bin/bash
# Description: Update references from `setThirdPartyDeps` to `setComponentsDeps`

# Replace all instances in the codebase
rg 'setThirdPartyDeps' --files-with-matches | xargs sed -i 's/setThirdPartyDeps/setComponentsDeps/g'

295-319: Handle duplicate packages when setting canvas dependencies

In the setCanvasDeps function, when reducing allPackages, duplicate packages are filtered out based on the package name. However, if different versions or scripts are provided for the same package, this could lead to inconsistent behavior. Consider handling version conflicts or consolidating scripts for the same package.

Apply this diff to improve duplicate package handling:

-      if (scripts.find((item) => item.package === pkg)) {
+      const existingPackage = scripts.find((item) => item.package === pkg)
+      if (existingPackage) {
+        // Merge components and other properties if necessary
+        existingPackage.components = { ...existingPackage.components, ...npm.components }
+        return { scripts, styles }
+      }
designer-demo/public/mock/bundle.json (1)

Line range hint 13258-13271: Consider adding validation for checkbox group values.

The default modelValue array contains strings but the schema doesn't enforce value types. Consider adding type validation.

 "modelValue": ["name1", "name2"],
+"type": {
+  "type": "array",
+  "items": {
+    "type": "string"
+  }
+},
 "type": "checkbox",
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 230b334 and 7b910c5.

📒 Files selected for processing (15)
  • designer-demo/public/mock/bundle.json (51 hunks)
  • mockServer/src/mock/get/app-center/v1/apps/schema/918.json (1 hunks)
  • packages/canvas/DesignCanvas/src/DesignCanvas.vue (2 hunks)
  • packages/canvas/DesignCanvas/src/importMap.js (2 hunks)
  • packages/canvas/common/src/utils.js (2 hunks)
  • packages/canvas/container/src/CanvasContainer.vue (1 hunks)
  • packages/canvas/container/src/container.js (1 hunks)
  • packages/canvas/render/src/RenderMain.js (1 hunks)
  • packages/canvas/render/src/runner.js (2 hunks)
  • packages/common/js/preview.js (1 hunks)
  • packages/design-core/src/preview/src/preview/Preview.vue (1 hunks)
  • packages/plugins/bridge/src/js/resource.js (2 hunks)
  • packages/plugins/materials/src/composable/useMaterial.js (9 hunks)
  • packages/toolbars/generate-code/src/Main.vue (1 hunks)
  • packages/vue-generator/src/plugins/genDependenciesPlugin.js (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/canvas/container/src/container.js
🔇 Additional comments (15)
packages/canvas/common/src/utils.js (2)

79-101: Verify that removing the export statement from dynamicImportComponents does not affect external modules

The dynamicImportComponents function was previously exported but is now declared without the export keyword. This change could break functionality in other modules that import this function. Please verify that no external modules rely on this export, or consider re-exporting the function if necessary.

Run the following script to check for external imports of dynamicImportComponents:


125-127: Ensure window.componentsDepsMap is properly initialized before accessing its properties

In the updateDependencies function, window.componentsDepsMap is used to access styles. Please verify that window.componentsDepsMap is initialized and contains the expected structure to prevent potential undefined errors.

Run the following script to check the initialization of window.componentsDepsMap:

packages/canvas/DesignCanvas/src/importMap.js (1)

Line range hint 3-47: LGTM!

The getImportMapData function has been correctly updated to accept canvasDeps, allowing dynamic inclusion of external dependencies in the import map and styles. The default parameters and object destructuring are used appropriately.

packages/common/js/preview.js (1)

Line range hint 29-36: Verify handling of empty or malformed dependency maps

The code assumes componentsDepsMap will always have valid scripts and styles arrays. Consider adding defensive checks.

-  const { scripts, styles } = useMaterial().materialState.componentsDepsMap
+  const { scripts = [], styles = [] } = useMaterial().materialState.componentsDepsMap || {}
   params.scripts = {}
   scripts
     .filter((item) => item.script)
     .forEach((item) => {
+      if (!item.package) return
       params.scripts[item.package] = item.script
     })
   params.styles = [...styles]

Let's verify the structure of componentsDepsMap across the codebase:

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

48-54: Consider version conflict resolution strategy

The current implementation might lead to version conflicts when the same package is defined in multiple places (utils, componentsMap, packages).

Let's verify potential version conflicts in existing material definitions:

Consider implementing a version resolution strategy:

  1. Define priority order (e.g., packages > componentsMap > utils)
  2. Add version compatibility checks
  3. Log warnings for potential conflicts
packages/plugins/bridge/src/js/resource.js (3)

14-21: LGTM! Clean import organization

The addition of useMaterial aligns with the new material asset package protocol implementation.


189-203: ⚠️ Potential issue

Fix variable shadowing in try block

The result variable is declared both in the outer scope and inside the try block, which could lead to unexpected behavior.

Apply this diff to fix the variable shadowing:

-      const result = await requestUpdateReSource(data)
+      result = await requestUpdateReSource(data)

-      const result = await requestAddReSource(data)
+      result = await requestAddReSource(data)

Likely invalid or redundant comment.


213-221: Verify canvas dependency updates

The conditional handling of npm resources with setCanvasDeps() looks good, but let's verify that all canvas instances are properly updated.

✅ Verification successful

Let me gather more information about the canvas dependency update mechanisms to ensure completeness.


Based on the analysis of the shell script results, I can now provide the final response:

Canvas dependency updates are properly handled

The verification shows that the canvas dependency update mechanism is correctly implemented:

  • setCanvasDeps() in useMaterial() handles npm dependencies by updating both material dependencies and utility dependencies
  • The else branch with updateUtils([result]) correctly handles non-npm resources by updating the canvas utility functions
  • Both paths trigger appropriate canvas refreshes
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of setCanvasDeps across the codebase
# Expected: setCanvasDeps should be called consistently after npm resource changes

# Check for setCanvasDeps usage
rg -A 5 "setCanvasDeps" 

# Check for other npm resource handling patterns
ast-grep --pattern 'if ($expr.type === "npm") { $$$ }'

Length of output: 1945


Script:

#!/bin/bash
# Check for updateUtils usage and related canvas update patterns
rg -A 5 "updateUtils" 

# Check for canvas refresh/update related functions
rg -A 5 "canvasApi" 

# Look for npm type handling in material state
ast-grep --pattern 'type === "npm"'

Length of output: 46980

packages/design-core/src/preview/src/preview/Preview.vue (1)

83-83: Verify cleanup of old import map utilities

The simplified import map management looks good, but we should verify that all references to the old utility functions have been removed.

✅ Verification successful

Based on the search results from our verification attempts, we can make a conclusion:

Import map utilities have been properly cleaned up

The searches show that:

  • Only the new setImportMap method is being used in Preview.vue
  • No instances of the old addUtilsImportMap were found anywhere in the codebase
  • The import map handling is now centralized through the store's setImportMap method
  • All other import map related code appears to be using the new simplified approach
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining references to old import map utilities
# Expected: No references to addUtilsImportMap should remain

# Search for any remaining references to the old function
rg "addUtilsImportMap"

# Check for any similar utility functions that might need updating
ast-grep --pattern 'function $name($_, $_) { 
  $$$
  importMap
  $$$
}'

Length of output: 97


Script:

#!/bin/bash
# Let's try a broader search for import map related functions and their usage
# Search for files containing 'importMap' keyword
rg -l "importMap"

# Then let's look for specific patterns related to import map manipulation
rg "\.importMap" -A 2 -B 2

# Search for store operations related to import maps
rg "setImportMap|addImportMap|updateImportMap" -A 2 -B 2

Length of output: 2076


Script:

#!/bin/bash
# Let's check the history of the importMap.js files to understand the changes
rg -A 10 -B 10 "addUtilsImportMap|setImportMap" packages/design-core/src/preview/src/preview/importMap.js packages/canvas/DesignCanvas/src/importMap.js

# Also check the store implementation to see how import maps are handled
rg -A 5 -B 5 "class.*Store" packages/design-core/src/preview/src/preview/

# Look for any utility functions related to import maps
ast-grep --pattern 'function $name($_) {
  $$$
  importMap
  $$$
}'

Length of output: 299

packages/canvas/DesignCanvas/src/DesignCanvas.vue (1)

61-61: LGTM! Good use of Vue's reactivity system

Converting canvasSrcDoc to a ref ensures proper reactivity for canvas updates.

mockServer/src/mock/get/app-center/v1/apps/schema/918.json (1)

2095-2096: Verify axios CDN link compatibility.

The addition of the CDN link for axios should be verified for:

  1. Version compatibility with the application
  2. CORS policy compliance
  3. Availability and reliability of the CDN
designer-demo/public/mock/bundle.json (2)

14081-14088: Verify the package configuration for element-plus.

The package configuration looks good but let's verify the unpkg URLs are accessible and the version is stable.

✅ Verification successful

Element-plus package configuration is secure and accessible

The verification shows:

  • Both CDN URLs (CSS and MJS) are accessible with 200 status codes
  • Version 2.4.2 is well above the patched version (2.0.6) for the only known vulnerability
  • The vulnerability was from April 2022 and has been addressed
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify element-plus package URLs and version
curl -I "https://unpkg.com/[email protected]/dist/index.full.mjs"
curl -I "https://unpkg.com/[email protected]/dist/index.css"

# Check for any security advisories
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: NPM, package: "element-plus") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Length of output: 2676


635-638: Verify nesting rule configuration for form components.

The nesting rules for ElForm and ElFormItem look correct, but let's verify there are no other form components that should be included in the whitelist.

Also applies to: 768-771

✅ Verification successful

Nesting rules for form components are correctly configured

The verification shows that only ElForm and ElFormItem are the form-related components in the bundle, and the current nesting rule configuration correctly allows ElFormItem as a child of ElForm. No additional form components need to be included in the whitelist.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other form-related components that may need to be included in nesting rules
ast-grep --pattern 'component: "El$_Form$_"'

Length of output: 46


Script:

#!/bin/bash
# Search for form-related components in the bundle.json file
rg -A 5 '"component":\s*"El.*Form' public/mock/bundle.json

# Also search for any form-related nesting rules
rg -A 5 '"childWhitelist".*Form' public/mock/bundle.json

Length of output: 247


Script:

#!/bin/bash
# Search for form-related components in the bundle.json file
rg -A 5 '"component":\s*"El.*Form' designer-demo/public/mock/bundle.json

# Also search for any form-related nesting rules
rg -A 5 '"childWhitelist".*Form' designer-demo/public/mock/bundle.json

Length of output: 804

packages/toolbars/generate-code/src/Main.vue (1)

148-149: Verify handling of new packages property in code generation

The addition of packages property to appSchema aligns with the new material asset protocol. However, we should ensure proper handling in the code generation process.

Let's verify the code generation handling:

Consider adding JSDoc documentation for the appSchema structure to help maintainers understand the new protocol format.

✅ Verification successful

The packages property is properly handled in code generation

The code review reveals that the packages property is safely integrated into the code generation flow:

  1. It's part of the appSchema object which is passed to generateAppCode
  2. The spread operator with fallback ([...(appData.packages || [])]) ensures safe handling of undefined values
  3. The property is used alongside other essential schema properties like componentsMap, pageSchema, and blockSchema
  4. The generated code is properly processed through generateAppCode and transformed into file content for local saving
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the code generation process handles the new packages property

# Search for packages property usage in code generation
rg -A 5 "packages.*=.*appSchema\.packages" 

# Search for potential validation or processing of packages
ast-grep --pattern 'function $_(appSchema) {
  $$$
  packages
  $$$
}'

Length of output: 117


Script:

#!/bin/bash
# Let's broaden our search to understand the usage of packages in code generation

# Search for any references to packages in the codebase
rg -A 5 "packages.*=" packages/toolbars/generate-code/

# Look for appSchema usage patterns
rg -A 5 "appSchema\." packages/toolbars/generate-code/

# Search for code generation related functions
ast-grep --pattern 'function generate$_($$$) {
  $$$
}'

# Check for any schema or type definitions
fd "\.d\.ts$|types?\.ts$" packages/toolbars/generate-code/ --exec cat {}

Length of output: 248


Script:

#!/bin/bash
# Let's check the broader context of the file and its usage

# Get the content of Main.vue to understand the context
cat packages/toolbars/generate-code/src/Main.vue

# Look for files that might consume the generated output
fd . packages/toolbars/generate-code/ -e .vue -e .ts -e .js --exclude "*.test.*" --exclude "*.spec.*" --exec rg -l "getPreGenerateInfo|appSchema"

# Search for any package-related types or interfaces
rg -g "*.ts" -g "*.d.ts" -g "*.vue" "interface.*App.*Schema|type.*App.*Schema" packages/toolbars/generate-code/

Length of output: 7776

packages/canvas/container/src/CanvasContainer.vue (1)

116-116: ⚠️ Potential issue

Breaking Change: Verify migration from thirdPartyDeps to componentsDepsMap

The change from thirdPartyDeps to componentsDepsMap is a breaking change that requires careful verification of all consumers.

Let's verify the migration completeness:

Please ensure all consumers of this window property are updated. Consider:

  1. Adding a migration guide for downstream dependencies
  2. Documenting the new componentsDepsMap structure
  3. Adding a deprecation warning for any transitional period

Comment on lines 70 to 101
const setUtils = async (data, clear, isForceRefresh) => {
if (clear) {
reset(utils)
}
const utilsCollection = {}
// 目前画布还不具备远程加载utils工具类的功能,目前只能加载TinyVue组件库中的组件工具
data?.forEach((item) => {
const util = TinyVue[item.content.exportName]
if (util) {
utilsCollection[item.name] = util
}

// 此处需要把工具类中的icon图标也加入utils上下文环境
const utilIcon = TinyVueIcon[item.content.exportName]
if (utilIcon) {
utilsCollection[item.name] = utilIcon
}
const utilsCollection = {}

// 解析函数式的工具类
if (item.type === 'function') {
data
.filter((item) => item.type === 'function')
.forEach((item) => {
const defaultFn = () => {}
utilsCollection[item.name] = generateFunction(item.content.value, context) || defaultFn
})

const npmUtils = data.filter((item) => item.type === 'npm' && item.content.cdnLink)
const results = await Promise.allSettled(npmUtils.map((item) => import(/* @vite-ignore */ item.content.package)))

results.forEach((res, index) => {
if (res.status !== 'fulfilled') {
return
}

const module = res.value
const { name, content } = npmUtils[index]
const { exportName, destructuring } = content

utilsCollection[name] = destructuring ? module[exportName] : module.default
})

Object.assign(utils, utilsCollection)

// 因为工具类并不具有响应式行为,所以需要通过修改key来强制刷新画布
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

Missing await keyword in calls to async setUtils function

The verification has found three locations where setUtils is called without awaiting its result:

  • packages/canvas/render/src/RenderMain.js:108: setUtils(data, false, true) in updateUtils function
  • packages/canvas/render/src/RenderMain.js:117: setUtils([], false, true) in deleteUtils function
  • packages/canvas/container/src/container.js:936: setUtils(useResource().resState.utils) in initialization code

These calls need to be updated to handle the asynchronous behavior by either:

  1. Making the calling functions async and using the await keyword
  2. Handling the returned Promise with .then() chains
🔗 Analysis chain

Ensure all invocations of setUtils handle the asynchronous behavior correctly

The setUtils function has been updated to be asynchronous to accommodate dynamic imports using await. Please verify that all calls to setUtils either await the function or handle the returned Promise appropriately. Failure to do so may result in unexpected behavior due to the asynchronous execution.

Run the following script to find all usages of setUtils and check if they are awaited:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find calls to `setUtils` not using `await`

# Test: Search for `setUtils` calls not preceded by `await` or handled as a Promise
rg '[^\.]\bsetUtils\((?!.*await)' --glob 'packages/**/*.js'

Length of output: 310


Script:

#!/bin/bash
# Let's try a different approach using ast-grep to find function calls
ast-grep --pattern 'setUtils($$$)'

# Also search for async/await patterns to compare
ast-grep --pattern 'await setUtils($$$)'

Length of output: 315


Script:

#!/bin/bash
# Let's check the context of these calls to understand if they're in async functions
ast-grep --pattern 'async function $_($$$) {
  $$$
  setUtils($$$)
  $$$
}'

# Also check for async arrow functions
ast-grep --pattern 'const $_ = async ($$$) => {
  $$$
  setUtils($$$)
  $$$
}'

# And check the containing function for the method call in container.js
rg -B 10 'setUtils\(useResource\(\)' packages/canvas/container/src/container.js

Length of output: 605


Script:

#!/bin/bash
# Let's check the implementation of these functions to see their async context
rg -B 15 -A 5 'setUtils\(data, false, true\)' packages/canvas/render/src/RenderMain.js

# Also get the full context of the function in container.js
ast-grep --pattern 'function $_($$$) {
  $$$
  setUtils(useResource().resState.utils)
  $$$
}'

Length of output: 655

Comment on lines 85 to 89
const { scripts: componentsScriptsDeps = [], styles: componentsStylesDeps = [] } = window.componentsDepsMap || {}

Promise.all([
...thirdScripts.map(dynamicImportComponents),
...scripts.map((src) => addScript(src)).concat([...thirdStyles, ...styles].map((src) => addStyle(src)))
...componentsScriptsDeps.map(setComponents),
...scripts.map((src) => addScript(src)).concat([...componentsStylesDeps, ...styles].map((src) => addStyle(src)))
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Add error handling for component loading failures

The Promise.all chain should handle individual component loading failures gracefully to prevent complete render failure.

   const { scripts: componentsScriptsDeps = [], styles: componentsStylesDeps = [] } = window.componentsDepsMap || {}

   Promise.all([
-    ...componentsScriptsDeps.map(setComponents),
-    ...scripts.map((src) => addScript(src)).concat([...componentsStylesDeps, ...styles].map((src) => addStyle(src)))
+    ...componentsScriptsDeps.map(dep => 
+      setComponents(dep).catch(err => {
+        console.error(`Failed to load component: ${dep.package}`, err);
+        return null;
+      })
+    ),
+    ...scripts.map(src => 
+      addScript(src).catch(err => {
+        console.error(`Failed to load script: ${src}`, err);
+        return null;
+      })
+    ),
+    ...[...componentsStylesDeps, ...styles].map(src => 
+      addStyle(src).catch(err => {
+        console.error(`Failed to load style: ${src}`, err);
+        return null;
+      })
+    )
   ]).finally(() => create(config))

Let's verify the implementation of setComponents:

#!/bin/bash
# Search for setComponents implementation and error handling
rg -A 10 "export const setComponents = |function setComponents" --type js

@@ -22,7 +22,7 @@ const getComponentsSet = (schema) => {
}

const parseSchema = (schema) => {
const { utils = [], componentsMap = [] } = schema
const { utils = [], componentsMap = [], packages = [] } = schema
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation for package entries

The packages array processing lacks validation for required fields and version format.

-  const { utils = [], componentsMap = [], packages = [] } = schema
+  const { utils = [], componentsMap = [], packages = [] } = validateSchema(schema)

+const validateSchema = (schema) => {
+  const { packages = [] } = schema;
+  return {
+    ...schema,
+    packages: packages.map(item => {
+      if (!item.package) {
+        console.warn('Package entry missing required "package" field:', item);
+        return null;
+      }
+      if (item.version && !isValidSemver(item.version)) {
+        console.warn(`Invalid version format for package ${item.package}:`, item.version);
+        item.version = 'latest';
+      }
+      return item;
+    }).filter(Boolean)
+  };
+}

Also applies to: 48-54

Comment on lines +63 to +74
useMessage().subscribe({
topic: 'init_canvas_deps',
callback: (deps) => {
if (canvasSrc) {
return
}
const { importMap, importStyles } = getImportMapData(getMergeMeta('engine.config')?.importMapVersion, deps)
canvasSrcDoc.value = initCanvas(importMap, importStyles).html
}
})
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for dependency initialization

While the message subscription implementation looks good, it should handle potential errors during import map generation.

Consider applying this improvement:

 useMessage().subscribe({
   topic: 'init_canvas_deps',
   callback: (deps) => {
     if (canvasSrc) {
       return
     }
+    try {
       const { importMap, importStyles } = getImportMapData(getMergeMeta('engine.config')?.importMapVersion, deps)
       canvasSrcDoc.value = initCanvas(importMap, importStyles).html
+    } catch (error) {
+      console.error('Failed to initialize canvas dependencies:', error)
+      // Consider showing a user-friendly error message
+    }
   }
 })
📝 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.

Suggested change
useMessage().subscribe({
topic: 'init_canvas_deps',
callback: (deps) => {
if (canvasSrc) {
return
}
const { importMap, importStyles } = getImportMapData(getMergeMeta('engine.config')?.importMapVersion, deps)
canvasSrcDoc.value = initCanvas(importMap, importStyles).html
}
})
useMessage().subscribe({
topic: 'init_canvas_deps',
callback: (deps) => {
if (canvasSrc) {
return
}
try {
const { importMap, importStyles } = getImportMapData(getMergeMeta('engine.config')?.importMapVersion, deps)
canvasSrcDoc.value = initCanvas(importMap, importStyles).html
} catch (error) {
console.error('Failed to initialize canvas dependencies:', error)
// Consider showing a user-friendly error message
}
}
})

@yy-wow yy-wow added the ready_for_review This PR requires more reviews label Dec 18, 2024
@yy-wow yy-wow changed the title Feat/materials new protocol Feat: materials new protocol Dec 18, 2024
Copy link
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: 2

🧹 Nitpick comments (5)
packages/canvas/render/src/RenderMain.js (3)

70-82: Consider enhancing error handling for function utilities.

The default function () => {} might silently hide errors when the function generation fails. Consider logging these failures or notifying users about malformed function definitions.

-      const defaultFn = () => {}
-      utilsCollection[item.name] = generateFunction(item.content.value, context) || defaultFn
+      try {
+        const fn = generateFunction(item.content.value, context)
+        if (!fn) {
+          globalNotify({
+            type: 'warning',
+            message: `Function utility "${item.name}" could not be generated`
+          })
+        }
+        utilsCollection[item.name] = fn || (() => {})
+      } catch (error) {
+        globalNotify({
+          type: 'error',
+          message: `Failed to generate function utility "${item.name}": ${error.message}`
+        })
+        utilsCollection[item.name] = () => {}
+      }

84-102: Consider internationalizing error messages and adding retry mechanism.

The implementation handles npm imports well but has a few areas for improvement:

  1. Error message is hardcoded in Chinese
  2. No retry mechanism for failed imports
-      globalNotify({
-        type: 'error',
-        message: `工具类 ${npmUtils[index].name} 加载失败,请检查CDN地址是否配置正确`
-      })
+      const message = t('error.npm_utility_load_failed', {
+        name: npmUtils[index].name
+      })
+      globalNotify({
+        type: 'error',
+        message,
+        action: {
+          text: t('action.retry'),
+          onClick: () => retryImport(npmUtils[index])
+        }
+      })

Line range hint 441-486: Document breaking changes in API signatures.

The transition of setUtils and related functions from synchronous to asynchronous is a breaking change that affects API consumers. This needs to be clearly documented.

Consider:

  1. Adding JSDoc comments to document the async nature of these functions
  2. Updating the API documentation
  3. Adding migration guides for existing consumers

Example JSDoc:

+/**
+ * Sets utility functions and npm modules in the canvas context.
+ * @async
+ * @param {Array} data - Array of utility configurations
+ * @param {boolean} clear - Whether to clear existing utilities
+ * @param {boolean} isForceRefresh - Whether to force a canvas refresh
+ * @returns {Promise<void>}
+ * @throws {Error} When npm module import fails
+ */
 const setUtils = async (data, clear, isForceRefresh) => {
packages/canvas/common/src/utils.js (2)

59-61: Enhance JSDoc documentation.

The current documentation could be more descriptive. Consider adding @returns {string[]} and more detailed description.

/**
- * 从页面importmap中获取模块的名称
- * @returns importmap中模块的名称集合
+ * Retrieves module names from the page's import map configuration
+ * @returns {string[]} Array of module names defined in the import map, or empty array if not found
+ */

Line range hint 113-124: Add error handling for missing exports and consider cache invalidation.

The function should verify that the requested component exports exist and consider cache invalidation strategy.

Consider these improvements:

 export const getComponents = async ({ package: pkg, script, components }) => {
   if (!pkg) return
 
   const modules = await dynamicImportComponentLib({ pkg, script })
 
   Object.entries(components).forEach(([componentId, exportName]) => {
+    if (!(exportName in modules)) {
+      console.warn(`Component "${exportName}" not found in package "${pkg}"`)
+      return
+    }
     if (!window.TinyLowcodeComponent[componentId]) {
       window.TinyLowcodeComponent[componentId] = modules[exportName]
     }
   })
 }

Also, consider adding a cache invalidation mechanism to handle component updates.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b910c5 and 6c49614.

📒 Files selected for processing (2)
  • packages/canvas/common/src/utils.js (2 hunks)
  • packages/canvas/render/src/RenderMain.js (1 hunks)
🔇 Additional comments (2)
packages/canvas/render/src/RenderMain.js (1)

Line range hint 109-120: Add missing await keywords to async function calls.

The updateUtils and deleteUtils functions call the async setUtils without awaiting its completion. This could lead to race conditions.

Apply this diff to fix the async calls:

-const updateUtils = (data) => {
+const updateUtils = async (data) => {
-  setUtils(data, false, true)
+  await setUtils(data, false, true)
 }

-const deleteUtils = (data) => {
+const deleteUtils = async (data) => {
   data?.forEach((item) => {
     if (utils[item.name]) {
       delete utils[item.name]
     }
   })
-  setUtils([], false, true)
+  await setUtils([], false, true)
 }
packages/canvas/common/src/utils.js (1)

73-73: Verify the correct property name in importMapObject.

The code accesses importMapObject.imports but comments mention "import". Verify that "imports" is the correct property name according to the Import Maps specification.

✅ Verification successful

Let me gather more information about import maps usage and specification in the codebase.


Let me check the content of the import map JSON file to verify the property name.


Property name "imports" is correct according to Import Maps specification

The code correctly uses importMapObject.imports as the property name. This is verified by:

  1. The import map JSON file which uses "imports" as the root property
  2. Multiple consistent usages across the codebase in files like externalDeps.js, copyImportMap.js, and others
  3. This follows the standard Import Maps specification where "imports" is the correct property name for specifying module specifier remapping
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other import map usages to verify property name
rg -g '*.{js,ts,vue}' -A 2 -B 2 'importmap.*imports'

Length of output: 408


Script:

#!/bin/bash
# Search for importmap related files and configurations
rg -g '*.{js,ts,vue,json}' -i 'importmap'

# Look for any import map JSON files
fd -e json 'importmap'

# Search for import map related documentation or comments
rg -g '*.{js,ts,vue,md}' -i 'import map' -C 3

Length of output: 11260


Script:

#!/bin/bash
# Check the content of the import map JSON file
cat packages/design-core/src/preview/src/preview/importMap.json

# Look for any Import Maps specification references in documentation
rg -g '*.md' -i 'import maps? specification'

Length of output: 1893

Comment on lines +131 to 139
const { styles: canvasStyles } = window.componentsDepsMap
const newStyles = [...styles].filter((item) => !canvasStyles.has(item))

newStyles.forEach((item) => canvasStyles.add(item))

const promises = [...newStyles].map((src) => addStyle(src)).concat(scripts.map(dynamicImportComponents))
const promises = [...newStyles].map((src) => addStyle(src)).concat(scripts.map(getComponents))

Promise.allSettled(promises)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling for failed dependencies.

The function should handle and report failed dependency loads from Promise.allSettled.

Consider this improvement:

 export const updateDependencies = ({ detail }) => {
   const { scripts = [], styles = [] } = detail || {}
   const { styles: canvasStyles } = window.componentsDepsMap
   const newStyles = [...styles].filter((item) => !canvasStyles.has(item))
 
   newStyles.forEach((item) => canvasStyles.add(item))
 
   const promises = [...newStyles].map((src) => addStyle(src)).concat(scripts.map(getComponents))
 
-  Promise.allSettled(promises)
+  Promise.allSettled(promises).then((results) => {
+    const failures = results
+      .filter(({ status }) => status === 'rejected')
+      .map(({ reason }) => reason)
+    if (failures.length > 0) {
+      console.error('Failed to load some dependencies:', failures)
+    }
+  })
 }
📝 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.

Suggested change
const { styles: canvasStyles } = window.componentsDepsMap
const newStyles = [...styles].filter((item) => !canvasStyles.has(item))
newStyles.forEach((item) => canvasStyles.add(item))
const promises = [...newStyles].map((src) => addStyle(src)).concat(scripts.map(dynamicImportComponents))
const promises = [...newStyles].map((src) => addStyle(src)).concat(scripts.map(getComponents))
Promise.allSettled(promises)
}
const { styles: canvasStyles } = window.componentsDepsMap
const newStyles = [...styles].filter((item) => !canvasStyles.has(item))
newStyles.forEach((item) => canvasStyles.add(item))
const promises = [...newStyles].map((src) => addStyle(src)).concat(scripts.map(getComponents))
Promise.allSettled(promises).then((results) => {
const failures = results
.filter(({ status }) => status === 'rejected')
.map(({ reason }) => reason)
if (failures.length > 0) {
console.error('Failed to load some dependencies:', failures)
}
})
}

Comment on lines +85 to +106
const dynamicImportComponentLib = async ({ pkg, script }) => {
if (window.TinyComponentLibs[pkg]) {
return window.TinyComponentLibs[pkg]
}

if (!window.TinyComponentLibs[pkg]) {
const modules = await import(/* @vite-ignore */ scriptUrl)
let modules = {}

window.TinyComponentLibs[pkg] = modules
try {
// 优先从importmap导入,兼容npm.script字段定义的cdn地址
if (getImportMapKeys().includes(pkg)) {
modules = await import(/* @vite-ignore */ pkg)
} else if (script) {
modules = await import(/* @vite-ignore */ script)
}
} catch (error) {
modules = {}
}

Object.entries(components).forEach(([componentId, exportName]) => {
const modules = window.TinyComponentLibs[pkg]
window.TinyComponentLibs[pkg] = modules

return modules
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling and input validation.

The current implementation has several areas for improvement:

  1. Silent error handling might hide critical issues
  2. Missing input parameter validation
  3. No timeout handling for dynamic imports

Consider applying these improvements:

-const dynamicImportComponentLib = async ({ pkg, script }) => {
+const dynamicImportComponentLib = async ({ pkg, script } = {}) => {
+  if (!pkg) {
+    throw new Error('Package name is required')
+  }
+
   if (window.TinyComponentLibs[pkg]) {
     return window.TinyComponentLibs[pkg]
   }

   let modules = {}

+  const importWithTimeout = (url) => {
+    const timeout = 30000 // 30 seconds
+    return Promise.race([
+      import(/* @vite-ignore */ url),
+      new Promise((_, reject) =>
+        setTimeout(() => reject(new Error(`Import timeout: ${url}`)), timeout)
+      )
+    ])
+  }

   try {
     if (getImportMapKeys().includes(pkg)) {
-      modules = await import(/* @vite-ignore */ pkg)
+      modules = await importWithTimeout(pkg)
     } else if (script) {
-      modules = await import(/* @vite-ignore */ script)
+      modules = await importWithTimeout(script)
     }
   } catch (error) {
-    modules = {}
+    console.error(`Failed to load component library ${pkg}:`, error)
+    throw error
   }

   window.TinyComponentLibs[pkg] = modules
   return modules
 }
📝 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.

Suggested change
const dynamicImportComponentLib = async ({ pkg, script }) => {
if (window.TinyComponentLibs[pkg]) {
return window.TinyComponentLibs[pkg]
}
if (!window.TinyComponentLibs[pkg]) {
const modules = await import(/* @vite-ignore */ scriptUrl)
let modules = {}
window.TinyComponentLibs[pkg] = modules
try {
// 优先从importmap导入,兼容npm.script字段定义的cdn地址
if (getImportMapKeys().includes(pkg)) {
modules = await import(/* @vite-ignore */ pkg)
} else if (script) {
modules = await import(/* @vite-ignore */ script)
}
} catch (error) {
modules = {}
}
Object.entries(components).forEach(([componentId, exportName]) => {
const modules = window.TinyComponentLibs[pkg]
window.TinyComponentLibs[pkg] = modules
return modules
}
const dynamicImportComponentLib = async ({ pkg, script } = {}) => {
if (!pkg) {
throw new Error('Package name is required')
}
if (window.TinyComponentLibs[pkg]) {
return window.TinyComponentLibs[pkg]
}
let modules = {}
const importWithTimeout = (url) => {
const timeout = 30000 // 30 seconds
return Promise.race([
import(/* @vite-ignore */ url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Import timeout: ${url}`)), timeout)
)
])
}
try {
if (getImportMapKeys().includes(pkg)) {
modules = await importWithTimeout(pkg)
} else if (script) {
modules = await importWithTimeout(script)
}
} catch (error) {
console.error(`Failed to load component library ${pkg}:`, error)
throw error
}
window.TinyComponentLibs[pkg] = modules
return modules
}

Copy link
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: 1

🔭 Outside diff range comments (2)
packages/plugins/materials/src/meta/block/src/BlockGroupPanel.vue (1)

Line range hint 135-143: Consider improving promise chain handling

The current promise chain could be optimized for better error handling and race condition prevention:

-          }).then(() => {
+          }).then(async () => {
             isRefresh.value = true
             state.searchValue = ''
             selectedBlockArray.value.length = 0
-            useResource().fetchResource({ isInit: false }) // 添加区块分组,不需要重新init页面或者区块。
+            await useResource().fetchResource({ isInit: false }) // 添加区块分组,不需要重新init页面或者区块。
             useNotify({
               message: '添加区块成功',
               type: 'success'
             })

This change ensures that:

  1. The resource fetch completes before showing the success notification
  2. Any errors in resource fetching are properly caught by the catch block
packages/plugins/materials/src/composable/useMaterial.js (1)

Line range hint 451-471: Improve type safety in getBlockDeps

The function should validate input types and handle potential undefined values.

Apply this diff:

 const getBlockDeps = (dependencies = {}) => {
   const { scripts = [], styles = [] } = dependencies
 
+  if (!Array.isArray(scripts) || !Array.isArray(styles)) {
+    return
+  }
 
   scripts.length &&
     scripts.forEach((npm) => {
+      if (!npm?.package) {
+        return
+      }
       const { package: pkg, script, css, components } = npm
       const npmInfo = materialState.componentsDepsMap.scripts.find((item) => item.package === pkg)
 
       if (!npmInfo || !npmInfo.script) {
         materialState.componentsDepsMap.scripts.push({ package: pkg, script, css, components })
       } else {
         const components = npmInfo.components || {}
         npmInfo.components = { ...components, ...npm.components }
       }
     })
 
   styles?.forEach((item) => materialState.componentsDepsMap.styles.add(item))
 }
🧹 Nitpick comments (4)
packages/plugins/materials/src/meta/block/src/BlockGroupPanel.vue (2)

Line range hint 28-39: Consider extracting block map logic to a composable

The block map initialization logic could be moved to a separate composable for better reusability and maintainability.

Consider creating a new composable useBlockMap.js:

// useBlockMap.js
import { ref } from 'vue'

export function useBlockMap() {
  const blockMap = ref(new Map())

  const initGroupBlockMap = (groups = []) => {
    blockMap.value.clear()
    for (let group of groups) {
      const groupBlock = group?.blocks || []
      for (let block of groupBlock) {
        blockMap.value.set(block.id, block)
      }
    }
  }

  const includesBlockInGroups = (blockId) => blockMap.value.has(blockId)

  return {
    initGroupBlockMap,
    includesBlockInGroups
  }
}

This would:

  1. Improve code organization
  2. Make the functionality more reusable
  3. Make testing easier

Line range hint 213-234: Consider optimizing API calls and data processing

The current implementation makes multiple separate API calls and processes results individually. This could be optimized for better performance.

-      Promise.allSettled([fetchTenants(), fetchUsers(), fetchTags()]).then((results) => {
+      const staticTenants = [
+        { name: '对所有组织开放', id: '1' },
+        { name: '对当前组织开放', id: '2' }
+      ]
+
+      Promise.all([fetchUsers(), fetchTags()])
+        .then(([users, tags]) => {
           state.filters[0].children = [
-          {
-            name: '对所有组织开放',
-            id: '1'
-          },
-          {
-            name: '对当前组织开放',
-            id: '2'
-          }
+          ...staticTenants
         ]
-        state.filters[1].children =
-          results[1].status === 'fulfilled'
-            ? results[1].value.map((item) => ({
-                name: item?.username,
-                id: item?.id
-              }))
-            : []
-        state.filters[2].children =
-          results[2].status === 'fulfilled' ? results[2].value.map((item) => ({ name: item })) : []
-        blockUsers.value = state.filters[1].children
+        state.filters[1].children = users.map(item => ({
+          name: item?.username,
+          id: item?.id
+        }))
+        state.filters[2].children = tags.map(item => ({ name: item }))
+        blockUsers.value = state.filters[1].children
+      })
+      .catch(error => {
+        message({
+          message: `Failed to fetch filters: ${error.message || error}`,
+          status: 'error'
+        })
       })

This change:

  1. Removes unnecessary fetchTenants call since the data is static
  2. Uses Promise.all instead of Promise.allSettled for simpler error handling
  3. Adds proper error handling
  4. Simplifies the data processing logic
packages/plugins/materials/src/composable/useMaterial.js (2)

446-447: Consider error handling in fetchMaterial

The function should handle potential errors when calling setCanvasDeps.

Apply this diff:

-  setCanvasDeps()
+  try {
+    setCanvasDeps()
+  } catch (error) {
+    console.error('Failed to set canvas dependencies:', error)
+  }

485-485: Ensure safe access to canvas API

The optional chaining on canvasApi.value is good, but consider adding a null check for detail.

Apply this diff:

-  useCanvas().canvasApi.value?.canvasDispatch('updateDependencies', { detail: materialState.componentsDepsMap })
+  const deps = materialState.componentsDepsMap
+  if (deps?.scripts?.length || deps?.styles?.size) {
+    useCanvas().canvasApi.value?.canvasDispatch('updateDependencies', { detail: deps })
+  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c971b2b and b277596.

📒 Files selected for processing (8)
  • packages/canvas/container/src/CanvasContainer.vue (2 hunks)
  • packages/canvas/container/src/container.js (0 hunks)
  • packages/canvas/render/src/runner.js (2 hunks)
  • packages/common/js/preview.js (2 hunks)
  • packages/plugins/materials/src/composable/useMaterial.js (8 hunks)
  • packages/plugins/materials/src/composable/useResource.js (1 hunks)
  • packages/plugins/materials/src/meta/block/src/BlockGroupPanel.vue (1 hunks)
  • packages/vue-generator/src/plugins/genDependenciesPlugin.js (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/canvas/container/src/container.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/canvas/render/src/runner.js
  • packages/common/js/preview.js
  • packages/vue-generator/src/plugins/genDependenciesPlugin.js
  • packages/canvas/container/src/CanvasContainer.vue
🔇 Additional comments (4)
packages/plugins/materials/src/composable/useResource.js (1)

36-37: LGTM: New state property for canvas dependencies

The addition of canvasDeps with separate collections for scripts and styles is a good approach. Using Set for styles ensures uniqueness and prevents duplicates.

packages/plugins/materials/src/composable/useMaterial.js (3)

41-42: Update state management for dependencies

Good refactoring to replace thirdPartyDeps with componentsDepsMap. The new structure better represents the relationship between components and their dependencies.


274-288: ⚠️ Potential issue

Enhance error handling in getUtilsDependencies

The function should handle potential undefined properties in the utility content.

Apply this diff:

 return utils
-  .filter((item) => item.type === 'npm' && item.content.cdnLink)
+  .filter((item) => item.type === 'npm' && item.content?.cdnLink)
   .map((item) => {
-    const { package: pkg, version, cdnLink } = item.content
+    const { package: pkg = '', version = '', cdnLink = '' } = item.content || {}
     return {
       package: pkg,
       version,
       script: cdnLink
     }
   })

Likely invalid or redundant comment.


293-309: Verify dependency updates in setCanvasDeps

The function needs to ensure that duplicate scripts are not added and that the message payload is properly structured.

✅ Verification successful

Duplicate prevention and message structure are properly implemented

The code implementation in setCanvasDeps is correct and safe:

  • It properly checks for duplicates using scripts.find() before adding new utilities
  • The message payload structure is consistent with its consumer in DesignCanvas.vue
  • The scripts array is properly initialized and managed throughout the file
  • The spread operator [...styles] creates a new array, preventing unintended mutations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potential duplicate script entries
ast-grep --pattern 'useMessage().publish({ topic: "init_canvas_deps", data: $_ })'

Length of output: 84


Script:

#!/bin/bash
# Let's try a different pattern to match the message publishing
ast-grep --pattern 'useMessage().publish({
  topic: "init_canvas_deps",
  data: $$$
})'

# Also search for other usages of init_canvas_deps
rg "init_canvas_deps" -A 3

# Check the structure of getUtilsDependencies
ast-grep --pattern 'getUtilsDependencies = () => $$$'

Length of output: 796


Script:

#!/bin/bash
# Let's check how scripts array is populated and used
rg "scripts\." -A 3 packages/plugins/materials/src/composable/useMaterial.js

# Check the structure of useResource and resState
ast-grep --pattern 'const { scripts, styles } = useResource().resState.canvasDeps'

# Look for the definition of scripts array
rg "scripts =" packages/plugins/materials/src/composable/useMaterial.js

Length of output: 1489

Comment on lines 312 to 355
const parseMaterialsDependencies = (materialBundle) => {
const { packages, components } = materialBundle

const { scripts: scriptsDeps, styles: stylesDeps } = useResource().resState.canvasDeps

packages?.forEach((pkg) => {
if (scriptsDeps.find((item) => item.package === pkg.package)) {
return
}

scriptsDeps.push(pkg)

if (!pkg.css) {
return
}

if (Array.isArray(pkg.css)) {
pkg.css.map(stylesDeps.add)
} else {
stylesDeps.add(pkg.css)
}
})

// 解析组件npm字段
const { scripts, styles } = generateThirdPartyDeps(components)
// 合并到canvasDeps中
scripts.forEach((item) => {
const dep = scriptsDeps.find((dep) => dep.package === item.package)

if (dep) {
dep.components = { ...dep.components, ...item.components }
}
})

if (!styles) {
return
}

if (Array.isArray(styles)) {
styles.map(stylesDeps.add)
} else {
stylesDeps.add(styles)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling in parseMaterialsDependencies

The function should handle edge cases and validate input data structure.

Apply this diff:

 const parseMaterialsDependencies = (materialBundle) => {
+  if (!materialBundle || typeof materialBundle !== 'object') {
+    return
+  }
   const { packages = [], components = [] } = materialBundle
   const { scripts: scriptsDeps, styles: stylesDeps } = useResource().resState.canvasDeps

   packages?.forEach((pkg) => {
+    if (!pkg?.package) {
+      return
+    }
     if (scriptsDeps.find((item) => item.package === pkg.package)) {
       return
     }

     scriptsDeps.push(pkg)

     if (!pkg.css) {
       return
     }

     if (Array.isArray(pkg.css)) {
       pkg.css.map(stylesDeps.add)
     } else {
       stylesDeps.add(pkg.css)
     }
   })

Committable suggestion skipped: line range outside the PR's diff.

window.TinyComponentLibs[pkg] = modules
try {
// 优先从importmap导入,兼容npm.script字段定义的cdn地址
if (getImportMapKeys().includes(pkg)) {
Copy link
Member

Choose a reason for hiding this comment

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

这里建议做一下缓存,不然每次调用 getImportMapKeys 都要重新读取一次。

@@ -113,7 +113,7 @@ export default {
const beforeCanvasReady = () => {
if (iframe.value) {
const win = iframe.value.contentWindow
win.thirdPartyDeps = useMaterial().materialState.thirdPartyDeps
win.componentsDeps = useResource().resState.canvasDeps.scripts.filter((item) => item.components)
Copy link
Member

Choose a reason for hiding this comment

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

冲突要解一下, resState 变成 appSchemaState

})

const npmUtils = data.filter((item) => item.type === 'npm' && item.content.cdnLink)
const results = await Promise.allSettled(npmUtils.map((item) => import(/* @vite-ignore */ item.content.package)))
Copy link
Member

Choose a reason for hiding this comment

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

这里要区分情况,一种情况是:在 importMap 已经存在,一种是不存在,不存在的情况下,我理解是 import 是需要读取 cdnLink 的

return {
package: pkg,
version,
script: cdnLink
Copy link
Member

Choose a reason for hiding this comment

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

这里字段为什么要做转换?为什么不统一?

}

/**
* 组装画布的依赖,通知画布初始化或更新importmap
Copy link
Member

Choose a reason for hiding this comment

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

importmap 没法更新,只能销毁整个 iframe 再重新构建

@hexqi hexqi changed the title Feat: materials new protocol feat: materials new protocol Jan 9, 2025
@github-actions github-actions bot added the enhancement New feature or request label Jan 9, 2025
Copy link
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: 0

🧹 Nitpick comments (9)
packages/canvas/render/src/RenderMain.js (4)

99-103: Internationalize error messages

The error message is hardcoded in Chinese. Consider using i18n for better internationalization support.

-      message: `工具类 ${npmUtils[index].name} 加载失败,请检查CDN地址是否配置正确`
+      message: t('utils.loadError', { name: npmUtils[index].name })

97-105: Enhance error handling and logging

Consider adding detailed error logging and structured error handling.

 results.forEach((res, index) => {
   if (res.status !== 'fulfilled') {
+    console.error(`Failed to load utility ${npmUtils[index].name}:`, res.reason)
     globalNotify({
       type: 'error',
-      message: `工具类 ${npmUtils[index].name} 加载失败,请检查CDN地址是否配置正确`
+      message: t('utils.loadError', { name: npmUtils[index].name }),
+      details: res.reason?.message
     })
     return
   }

87-92: Validate function-type utilities

Consider adding validation for function-type utilities to ensure they meet expected interfaces.

 data
   .filter((item) => item.type === 'function')
   .forEach((item) => {
     const defaultFn = () => {}
+    const fn = generateFunction(item.content.value, context)
+    if (fn && typeof fn !== 'function') {
+      console.warn(`Utility ${item.name} is not a function`)
+      return
+    }
-    utilsCollection[item.name] = generateFunction(item.content.value, context) || defaultFn
+    utilsCollection[item.name] = fn || defaultFn
   })

106-111: Validate npm module exports

Consider adding validation for npm module exports to ensure they meet expected interfaces.

 const module = res.value
 const { name, content } = npmUtils[index]
 const { exportName, destructuring } = content

+const exportedValue = destructuring ? module[exportName] : module.default
+if (exportedValue === undefined) {
+  console.warn(`Export ${exportName} not found in module ${name}`)
+  return
+}
-utilsCollection[name] = destructuring ? module[exportName] : module.default
+utilsCollection[name] = exportedValue
packages/plugins/materials/src/composable/useMaterial.js (2)

304-320: Add debouncing for canvas dependency updates.

The setCanvasDeps function might be called frequently during initialization. Consider debouncing the message publishing.

Apply this diff to add debouncing:

+const debouncedPublish = utils.debounce((data) => {
+  useMessage().publish({
+    topic: 'init_canvas_deps',
+    data
+  })
+}, 100)

 const setCanvasDeps = () => {
   const { scripts, styles } = useResource().resState.canvasDeps

   getUtilsDependencies().forEach((util) => {
     if (scripts.find((item) => util.package === item.package)) {
       return
     }

     scripts.push(util)
   })

-  useMessage().publish({
-    topic: 'init_canvas_deps',
-    data: { scripts: scripts, styles: [...styles] }
-  })
+  debouncedPublish({ scripts: scripts, styles: [...styles] })
 }

468-479: Ensure atomic updates for dependency maps.

The block dependency handling should ensure atomic updates to prevent race conditions.

Apply this diff to ensure atomic updates:

-      materialState.componentsDepsMap.scripts.push({ package: pkg, script, css, components })
+      const newDeps = [...materialState.componentsDepsMap.scripts]
+      newDeps.push({ package: pkg, script, css, components })
+      materialState.componentsDepsMap.scripts = newDeps
designer-demo/public/mock/bundle.json (3)

14041-14047: Consider adding integrity hashes for CDN resources

For the TinyVue package CDN resources, consider adding integrity hashes to prevent tampering:

 {
   "name": "TinyVue组件库",
   "package": "@opentiny/vue",
   "version": "3.14.0",
   "destructuring": true,
   "script": "https://unpkg.com/@opentiny/vue@~3.14/runtime/tiny-vue.mjs",
+  "scriptIntegrity": "<sha384-hash>",
   "css": "https://unpkg.com/@opentiny/vue-theme@~3.14/index.css",
+  "cssIntegrity": "<sha384-hash>"
 }

14048-14054: Consider pinning exact versions for CDN URLs

The Element Plus package uses tilde ranges in CDN URLs which could potentially pull in breaking changes. Consider pinning to exact versions:

 {
   "name": "element-plus组件库",
   "package": "element-plus", 
   "version": "2.4.2",
-  "script": "https://unpkg.com/[email protected]/dist/index.full.mjs",
+  "script": "https://unpkg.com/[email protected]/dist/index.full.mjs",
   "css": "https://unpkg.com/[email protected]/dist/index.css"
 }

14039-14054: Consider adding package validation metadata

The packages section could benefit from additional metadata to help validate package integrity and compatibility:

 "packages": [
   {
     "name": "TinyVue组件库",
     "package": "@opentiny/vue",
     "version": "3.14.0",
+    "engines": {
+      "node": ">=14.0.0",
+      "npm": ">=6.0.0"
+    },
+    "peerDependencies": {
+      "vue": "^3.0.0"
+    }
   }
   // ... other packages
 ]
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b277596 and 27cb75e.

📒 Files selected for processing (13)
  • designer-demo/public/mock/bundle.json (33 hunks)
  • packages/canvas/DesignCanvas/src/DesignCanvas.vue (1 hunks)
  • packages/canvas/DesignCanvas/src/importMap.js (2 hunks)
  • packages/canvas/container/src/CanvasContainer.vue (2 hunks)
  • packages/canvas/container/src/container.js (0 hunks)
  • packages/canvas/render/src/RenderMain.js (2 hunks)
  • packages/canvas/render/src/runner.js (2 hunks)
  • packages/common/js/preview.js (2 hunks)
  • packages/design-core/src/preview/src/preview/Preview.vue (1 hunks)
  • packages/plugins/materials/src/composable/useMaterial.js (8 hunks)
  • packages/plugins/materials/src/composable/useResource.js (1 hunks)
  • packages/plugins/materials/src/meta/block/src/BlockGroupPanel.vue (1 hunks)
  • packages/toolbars/generate-code/src/Main.vue (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/canvas/container/src/container.js
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/toolbars/generate-code/src/Main.vue
  • packages/canvas/DesignCanvas/src/importMap.js
  • packages/plugins/materials/src/meta/block/src/BlockGroupPanel.vue
  • packages/canvas/container/src/CanvasContainer.vue
  • packages/common/js/preview.js
  • packages/plugins/materials/src/composable/useResource.js
🔇 Additional comments (10)
packages/canvas/render/src/RenderMain.js (1)

Line range hint 71-114: Consider handling importMap existence scenarios

Based on the previous review comments, the code should handle two scenarios:

  1. When importMap exists
  2. When importMap doesn't exist (should use cdnLink)

Let's verify the importMap handling:

Consider refactoring the npm utils import logic to handle both scenarios:

 const npmUtils = data.filter((item) => item.type === 'npm' && item.content.cdnLink)
-const results = await Promise.allSettled(npmUtils.map((item) => import(/* @vite-ignore */ item.content.package)))
+const results = await Promise.allSettled(npmUtils.map(async (item) => {
+  const { content } = item
+  const importPath = window.host?.importMap?.[content.package] || content.cdnLink
+  return import(/* @vite-ignore */ importPath)
+}))
packages/canvas/render/src/runner.js (2)

14-14: LGTM! Import statement updated to support new dependency management.

The import statement has been updated to include new utilities that support the revised dependency management approach.


84-85: 🛠️ Refactor suggestion

Verify error handling for dependency loading.

While the dependency loading logic is sound, consider adding error handling to gracefully handle component loading failures.

Apply this diff to improve error handling:

   Promise.all([
-    ...window.componentsDeps.map(getComponents),
-    ...scripts.map((src) => addScript(src)).concat(styles.map((src) => addStyle(src)))
+    ...window.componentsDeps.map(dep => 
+      getComponents(dep).catch(err => {
+        console.error(`Failed to load component:`, err);
+        return null;
+      })
+    ),
+    ...scripts.map(src => 
+      addScript(src).catch(err => {
+        console.error(`Failed to load script: ${src}`, err);
+        return null;
+      })
+    ),
+    ...styles.map(src => 
+      addStyle(src).catch(err => {
+        console.error(`Failed to load style: ${src}`, err);
+        return null;
+      })
+    )
   ]).finally(() => create(config))
packages/design-core/src/preview/src/preview/Preview.vue (1)

83-83: LGTM! Simplified import map handling.

The direct usage of store.setImportMap(importMapData) improves code clarity by removing unnecessary complexity.

packages/canvas/DesignCanvas/src/DesignCanvas.vue (2)

62-62: LGTM! Enhanced reactivity with ref().

Converting canvasSrcDoc to use ref() ensures proper reactivity in Vue's composition API.


64-75: 🛠️ Refactor suggestion

Add error handling for dependency initialization.

The message subscription implementation should handle potential errors during import map generation.

Apply this diff to improve error handling:

 useMessage().subscribe({
   topic: 'init_canvas_deps',
   callback: (deps) => {
     if (canvasSrc) {
       return
     }
+    try {
       const { importMap, importStyles } = getImportMapData(getMergeMeta('engine.config')?.importMapVersion, deps)
       canvasSrcDoc.value = initCanvas(importMap, importStyles).html
+    } catch (error) {
+      console.error('Failed to initialize canvas dependencies:', error)
+      // Consider emitting an error event to notify the parent
+      useMessage().publish({
+        topic: 'canvas_init_error',
+        data: { error }
+      })
+    }
   }
 })

Likely invalid or redundant comment.

packages/plugins/materials/src/composable/useMaterial.js (3)

42-43: LGTM! Improved state management structure.

The new componentsDepsMap structure provides better organization for scripts and styles dependencies.


285-299: 🛠️ Refactor suggestion

Add validation for utility dependencies.

The getUtilsDependencies function should validate the utility structure before accessing properties.

Apply this diff to add validation:

 const getUtilsDependencies = () => {
   const { utils } = useResource().resState

   return utils
-    .filter((item) => item.type === 'npm' && item.content.cdnLink)
+    .filter((item) => {
+      return item?.type === 'npm' && 
+             item?.content?.cdnLink && 
+             item?.content?.package
+    })
     .map((item) => {
       const { package: pkg, version, cdnLink } = item.content

       return {
         package: pkg,
         version,
         script: cdnLink
       }
     })
 }

Likely invalid or redundant comment.


323-366: 🛠️ Refactor suggestion

Add input validation for material dependencies.

The parseMaterialsDependencies function should validate the input structure.

Apply this diff to add validation:

 const parseMaterialsDependencies = (materialBundle) => {
+  if (!materialBundle || typeof materialBundle !== 'object') {
+    console.warn('Invalid material bundle provided')
+    return
+  }
   const { packages = [], components = [] } = materialBundle
+  if (!Array.isArray(packages) || !Array.isArray(components)) {
+    console.warn('Invalid packages or components structure')
+    return
+  }

   const { scripts: scriptsDeps, styles: stylesDeps } = useResource().resState.canvasDeps

   packages?.forEach((pkg) => {
+    if (!pkg?.package) {
+      console.warn('Invalid package structure:', pkg)
+      return
+    }
     if (scriptsDeps.find((item) => item.package === pkg.package)) {
       return
     }

     scriptsDeps.push(pkg)

     if (!pkg.css) {
       return
     }

     if (Array.isArray(pkg.css)) {
       pkg.css.map(stylesDeps.add)
     } else {
       stylesDeps.add(pkg.css)
     }
   })

Likely invalid or redundant comment.

designer-demo/public/mock/bundle.json (1)

14040-14054: Verify package versions and security advisories

The package configurations reference specific versions that should be verified for security and compatibility.

✅ Verification successful

Consider updating package versions for improved features and security

Both packages have newer versions available that include bug fixes and improvements:

  • @opentiny/vue: 3.14.0 → 3.21.0
  • element-plus: 2.4.2 → 2.9.2

No critical security vulnerabilities found in current versions.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for security advisories and latest versions of the specified packages

# Check latest versions
echo "Checking @opentiny/vue latest version..."
curl -s https://registry.npmjs.org/@opentiny/vue/latest | jq '.version'

echo "Checking element-plus latest version..."
curl -s https://registry.npmjs.org/element-plus/latest | jq '.version'

# Check for security advisories
echo "Checking security advisories..."
gh api graphql -f query='
{
  securityVulnerabilities(first: 10, ecosystem: NPM, package: "element-plus") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Length of output: 998

Copy link
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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27cb75e and 9ec89fb.

📒 Files selected for processing (1)
  • packages/plugins/bridge/src/js/resource.js (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: push-check
🔇 Additional comments (3)
packages/plugins/bridge/src/js/resource.js (3)

14-14: LGTM! Well-structured imports

The import statement is properly organized and includes all necessary utilities from the meta-register package.


178-179: LGTM! Improved code readability

Good use of async/await and the isEdit variable makes the code more maintainable.

Also applies to: 181-182


215-221: 🛠️ Refactor suggestion

Move success notification after all operations complete

The success notification should only be shown after confirming all operations (including canvas updates) have completed successfully.

Consider moving the success notification and state updates into a finally block:

-  useNotify({
-    type: 'success',
-    message: `${isEdit ? '修改' : '创建'}成功`
-  })
-  emit('refresh', state.type)
-  state.refresh = true
-  callback()
+  try {
+    // ... existing try blocks ...
+  } catch (error) {
+    // ... error handling ...
+  } finally {
+    if (!error) {
+      useNotify({
+        type: 'success',
+        message: `${isEdit ? '修改' : '创建'}成功`
+      })
+      emit('refresh', state.type)
+      state.refresh = true
+      callback()
+    }
+  }

Likely invalid or redundant comment.

Comment on lines +200 to +204
const result = await requestAddReSource(data)

// 更新画布工具函数环境,保证渲染最新工具类返回值, 并触发画布的强制刷新
generateBridgeUtil(getAppId())
useNotify({
type: 'success',
message: '创建成功'
})
emit('refresh', state.type)
state.refresh = true
callback()
}
})
if (result) {
useResource().appSchemaState[data.category].push(result)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for resource addition

Similar to the edit flow, the resource addition lacks proper error handling.

Here's a suggested improvement:

-    const result = await requestAddReSource(data)
-
-    if (result) {
-      useResource().appSchemaState[data.category].push(result)
-    }
+    try {
+      const result = await requestAddReSource(data)
+      if (!result) {
+        throw new Error('Add resource request failed')
+      }
+      useResource().appSchemaState[data.category].push(result)
+    } catch (error) {
+      useNotify({
+        type: 'error',
+        message: `创建失败: ${error.message}`
+      })
+      return
+    }
📝 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.

Suggested change
const result = await requestAddReSource(data)
// 更新画布工具函数环境,保证渲染最新工具类返回值, 并触发画布的强制刷新
generateBridgeUtil(getAppId())
useNotify({
type: 'success',
message: '创建成功'
})
emit('refresh', state.type)
state.refresh = true
callback()
}
})
if (result) {
useResource().appSchemaState[data.category].push(result)
}
try {
const result = await requestAddReSource(data)
if (!result) {
throw new Error('Add resource request failed')
}
useResource().appSchemaState[data.category].push(result)
} catch (error) {
useNotify({
type: 'error',
message: `创建失败: ${error.message}`
})
return
}

Comment on lines +181 to +198
if (isEdit) {
data.id = state.resource.id
const result = await requestUpdateReSource(data)

generateBridgeUtil(getAppId())
if (result) {
const index = useResource().appSchemaState[data.category].findIndex((item) => item.name === result.name)

if (index === -1) {
useNotify({
type: 'success',
message: '修改成功'
type: 'error',
message: '修改失败'
})

emit('refresh', state.type)
state.refresh = true
callback()
return
}
})

useResource().appSchemaState[data.category][index] = result
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for async operations

The async operations lack proper error handling. Consider wrapping the code in try-catch blocks to handle potential failures gracefully.

Here's a suggested improvement:

   if (isEdit) {
     data.id = state.resource.id
-    const result = await requestUpdateReSource(data)
-
-    if (result) {
-      const index = useResource().appSchemaState[data.category].findIndex((item) => item.name === result.name)
-
-      if (index === -1) {
-        useNotify({
-          type: 'error',
-          message: '修改失败'
-        })
-
-        return
-      }
-
-      useResource().appSchemaState[data.category][index] = result
-    }
+    try {
+      const result = await requestUpdateReSource(data)
+      if (!result) {
+        throw new Error('Update resource request failed')
+      }
+      
+      const index = useResource().appSchemaState[data.category].findIndex((item) => item.name === result.name)
+      if (index === -1) {
+        throw new Error('Resource not found in app schema')
+      }
+      
+      useResource().appSchemaState[data.category][index] = result
+    } catch (error) {
+      useNotify({
+        type: 'error',
+        message: `修改失败: ${error.message}`
+      })
+      return
+    }
📝 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.

Suggested change
if (isEdit) {
data.id = state.resource.id
const result = await requestUpdateReSource(data)
generateBridgeUtil(getAppId())
if (result) {
const index = useResource().appSchemaState[data.category].findIndex((item) => item.name === result.name)
if (index === -1) {
useNotify({
type: 'success',
message: '修改成功'
type: 'error',
message: '修改失败'
})
emit('refresh', state.type)
state.refresh = true
callback()
return
}
})
useResource().appSchemaState[data.category][index] = result
}
if (isEdit) {
data.id = state.resource.id
try {
const result = await requestUpdateReSource(data)
if (!result) {
throw new Error('Update resource request failed')
}
const index = useResource().appSchemaState[data.category].findIndex((item) => item.name === result.name)
if (index === -1) {
throw new Error('Resource not found in app schema')
}
useResource().appSchemaState[data.category][index] = result
} catch (error) {
useNotify({
type: 'error',
message: `修改失败: ${error.message}`
})
return
}

Comment on lines +207 to +213
// 更新画布工具函数环境,保证渲染最新工具类返回值, 并触发画布的强制刷新
generateBridgeUtil(getAppId())

if (data.type === 'npm') {
// 更新画布import并刷新画布
useMaterial().setCanvasDeps()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle potential failures in canvas updates

The canvas-related operations should be wrapped in try-catch blocks to handle potential failures.

Here's a suggested improvement:

-  // 更新画布工具函数环境,保证渲染最新工具类返回值, 并触发画布的强制刷新
-  generateBridgeUtil(getAppId())
-
-  if (data.type === 'npm') {
-    // 更新画布import并刷新画布
-    useMaterial().setCanvasDeps()
-  }
+  try {
+    // 更新画布工具函数环境,保证渲染最新工具类返回值, 并触发画布的强制刷新
+    await generateBridgeUtil(getAppId())
+
+    if (data.type === 'npm') {
+      // 更新画布import并刷新画布
+      await useMaterial().setCanvasDeps()
+    }
+  } catch (error) {
+    useNotify({
+      type: 'error',
+      message: `画布更新失败: ${error.message}`
+    })
+    return
+  }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +178 to +221
export const saveResource = async (data, callback, emit) => {
const isEdit = getActionType() === ACTION_TYPE.Edit

useResource().appSchemaState[data.category][index] = result

// 更新画布工具函数环境,保证渲染最新工具类返回值, 并触发画布的强制刷新
if (isEdit) {
data.id = state.resource.id
const result = await requestUpdateReSource(data)

generateBridgeUtil(getAppId())
if (result) {
const index = useResource().appSchemaState[data.category].findIndex((item) => item.name === result.name)

if (index === -1) {
useNotify({
type: 'success',
message: '修改成功'
type: 'error',
message: '修改失败'
})

emit('refresh', state.type)
state.refresh = true
callback()
return
}
})

useResource().appSchemaState[data.category][index] = result
}
} else {
requestAddReSource(data).then((result) => {
if (result) {
useResource().appSchemaState[data.category].push(result)
const result = await requestAddReSource(data)

// 更新画布工具函数环境,保证渲染最新工具类返回值, 并触发画布的强制刷新
generateBridgeUtil(getAppId())
useNotify({
type: 'success',
message: '创建成功'
})
emit('refresh', state.type)
state.refresh = true
callback()
}
})
if (result) {
useResource().appSchemaState[data.category].push(result)
}
}

// 更新画布工具函数环境,保证渲染最新工具类返回值, 并触发画布的强制刷新
generateBridgeUtil(getAppId())

if (data.type === 'npm') {
// 更新画布import并刷新画布
useMaterial().setCanvasDeps()
}

useNotify({
type: 'success',
message: `${isEdit ? '修改' : '创建'}成功`
})
emit('refresh', state.type)
state.refresh = true
callback()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add input validation for npm package data

The function should validate npm package names and versions before processing them to prevent potential security issues.

Consider adding validation:

 export const saveResource = async (data, callback, emit) => {
+  // Validate npm package data
+  if (data.type === 'npm') {
+    const { package: pkgName, version } = data.content
+    
+    // Validate package name format
+    if (!/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(pkgName)) {
+      useNotify({
+        type: 'error',
+        message: '无效的包名格式'
+      })
+      return
+    }
+    
+    // Validate version format
+    if (!/^(\d+\.\d+\.\d+)(-[a-z]+\.\d+)?$/.test(version)) {
+      useNotify({
+        type: 'error',
+        message: '无效的版本号格式'
+      })
+      return
+    }
+  }

   const isEdit = getActionType() === ACTION_TYPE.Edit
   // ... rest of the function
📝 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.

Suggested change
export const saveResource = async (data, callback, emit) => {
const isEdit = getActionType() === ACTION_TYPE.Edit
useResource().appSchemaState[data.category][index] = result
// 更新画布工具函数环境,保证渲染最新工具类返回值, 并触发画布的强制刷新
if (isEdit) {
data.id = state.resource.id
const result = await requestUpdateReSource(data)
generateBridgeUtil(getAppId())
if (result) {
const index = useResource().appSchemaState[data.category].findIndex((item) => item.name === result.name)
if (index === -1) {
useNotify({
type: 'success',
message: '修改成功'
type: 'error',
message: '修改失败'
})
emit('refresh', state.type)
state.refresh = true
callback()
return
}
})
useResource().appSchemaState[data.category][index] = result
}
} else {
requestAddReSource(data).then((result) => {
if (result) {
useResource().appSchemaState[data.category].push(result)
const result = await requestAddReSource(data)
// 更新画布工具函数环境,保证渲染最新工具类返回值, 并触发画布的强制刷新
generateBridgeUtil(getAppId())
useNotify({
type: 'success',
message: '创建成功'
})
emit('refresh', state.type)
state.refresh = true
callback()
}
})
if (result) {
useResource().appSchemaState[data.category].push(result)
}
}
// 更新画布工具函数环境,保证渲染最新工具类返回值, 并触发画布的强制刷新
generateBridgeUtil(getAppId())
if (data.type === 'npm') {
// 更新画布import并刷新画布
useMaterial().setCanvasDeps()
}
useNotify({
type: 'success',
message: `${isEdit ? '修改' : '创建'}成功`
})
emit('refresh', state.type)
state.refresh = true
callback()
export const saveResource = async (data, callback, emit) => {
// Validate npm package data
if (data.type === 'npm') {
const { package: pkgName, version } = data.content
// Validate package name format
if (!/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(pkgName)) {
useNotify({
type: 'error',
message: '无效的包名格式'
})
return
}
// Validate version format
if (!/^(\d+\.\d+\.\d+)(-[a-z]+\.\d+)?$/.test(version)) {
useNotify({
type: 'error',
message: '无效的版本号格式'
})
return
}
}
const isEdit = getActionType() === ACTION_TYPE.Edit
if (isEdit) {
data.id = state.resource.id
const result = await requestUpdateReSource(data)
if (result) {
const index = useResource().appSchemaState[data.category].findIndex((item) => item.name === result.name)
if (index === -1) {
useNotify({
type: 'error',
message: '修改失败'
})
return
}
useResource().appSchemaState[data.category][index] = result
}
} else {
const result = await requestAddReSource(data)
if (result) {
useResource().appSchemaState[data.category].push(result)
}
}
// 更新画布工具函数环境,保证渲染最新工具类返回值, 并触发画布的强制刷新
generateBridgeUtil(getAppId())
if (data.type === 'npm') {
// 更新画布import并刷新画布
useMaterial().setCanvasDeps()
}
useNotify({
type: 'success',
message: `${isEdit ? '修改' : '创建'}成功`
})
emit('refresh', state.type)
state.refresh = true
callback()

Copy link
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: 1

🧹 Nitpick comments (3)
packages/plugins/materials/src/composable/useMaterial.js (3)

42-43: Consider using TypeScript interfaces for state objects.

The componentsDepsMap and packages properties would benefit from TypeScript interfaces to ensure type safety and provide better documentation of the expected structure.

interface ComponentsDepsMap {
  scripts: Array<{
    package: string;
    script: string;
    components?: Record<string, string>;
  }>;
  styles: Set<string>;
}

interface MaterialState {
  components: any[];
  blocks: any[];
  componentsDepsMap: ComponentsDepsMap;
  packages: Array<{
    package: string;
    version: string;
    script?: string;
    css?: string | string[];
  }>;
}

304-320: Consider adding error handling for message publishing.

The function should handle potential errors during message publishing and provide feedback.

 const setCanvasDeps = () => {
   const { scripts, styles } = useResource().appSchemaState.canvasDeps
 
   getUtilsDependencies().forEach((util) => {
     if (scripts.find((item) => util.package === item.package)) {
       return
     }
 
     scripts.push(util)
   })
 
+  try {
     useMessage().publish({
       topic: 'init_canvas_deps',
       data: { scripts: scripts, styles: [...styles] }
     })
+  } catch (error) {
+    console.error('Failed to publish canvas dependencies:', error)
+  }
 }

467-471: Consider memory management for Set operations.

The Set of styles could grow indefinitely. Consider implementing a cleanup mechanism.

 const getBlockDeps = (dependencies = {}) => {
   const { scripts = [], styles = [] } = dependencies
+  const MAX_STYLES = 1000 // Adjust based on your needs
 
   scripts.length &&
     scripts.forEach((npm) => {
       const { package: pkg, script, css, components } = npm
       const npmInfo = materialState.componentsDepsMap.scripts.find((item) => item.package === pkg)
 
       if (!npmInfo || !npmInfo.script) {
         materialState.componentsDepsMap.scripts.push({ package: pkg, script, css, components })
       } else {
         const components = npmInfo.components || {}
         npmInfo.components = { ...components, ...npm.components }
       }
     })
 
-  styles?.forEach((item) => materialState.componentsDepsMap.styles.add(item))
+  styles?.forEach((item) => {
+    materialState.componentsDepsMap.styles.add(item)
+    if (materialState.componentsDepsMap.styles.size > MAX_STYLES) {
+      const iterator = materialState.componentsDepsMap.styles.values()
+      materialState.componentsDepsMap.styles.delete(iterator.next().value)
+    }
+  })
 }

Also applies to: 479-479

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ec89fb and 50ef894.

📒 Files selected for processing (3)
  • packages/canvas/container/src/CanvasContainer.vue (2 hunks)
  • packages/common/js/preview.js (2 hunks)
  • packages/plugins/materials/src/composable/useMaterial.js (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/common/js/preview.js
  • packages/canvas/container/src/CanvasContainer.vue
🔇 Additional comments (2)
packages/plugins/materials/src/composable/useMaterial.js (2)

285-299: ⚠️ Potential issue

Add null checks for nested properties.

The function assumes item.content always exists and has a cdnLink property. This could lead to runtime errors.

Apply this diff to add proper null checks:

 return utils
-    .filter((item) => item.type === 'npm' && item.content.cdnLink)
+    .filter((item) => item.type === 'npm' && item.content?.cdnLink)
     .map((item) => {
-      const { package: pkg, version, cdnLink } = item.content
+      const { package: pkg, version = '', cdnLink = '' } = item.content || {}
 
       return {
         package: pkg,
         version,
         script: cdnLink
       }
     })

Likely invalid or redundant comment.


323-366: ⚠️ Potential issue

Add input validation and error handling.

The function should validate the input structure and handle potential errors.

Apply this diff to add proper validation:

 const parseMaterialsDependencies = (materialBundle) => {
+  if (!materialBundle || typeof materialBundle !== 'object') {
+    console.warn('Invalid material bundle provided')
+    return
+  }
   const { packages = [], components = [] } = materialBundle
 
   const { scripts: scriptsDeps, styles: stylesDeps } = useResource().appSchemaState.canvasDeps
 
   packages?.forEach((pkg) => {
+    if (!pkg?.package) {
+      console.warn('Invalid package configuration:', pkg)
+      return
+    }
     if (scriptsDeps.find((item) => item.package === pkg.package)) {
       return
     }

Likely invalid or redundant comment.

@@ -408,7 +490,7 @@ const updateCanvasDependencies = (blocks) => {
getBlockDeps(block.content.dependencies)
})

useCanvas().canvasApi.value?.canvasDispatch('updateDependencies', { detail: materialState.thirdPartyDeps })
useCanvas().canvasApi.value?.canvasDispatch('updateDependencies', { detail: materialState.componentsDepsMap })
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for canvas dispatch.

The canvas dispatch operation should handle potential errors when the canvas API is not available.

-  useCanvas().canvasApi.value?.canvasDispatch('updateDependencies', { detail: materialState.componentsDepsMap })
+  try {
+    const canvasApi = useCanvas().canvasApi.value
+    if (!canvasApi?.canvasDispatch) {
+      throw new Error('Canvas API not available')
+    }
+    canvasApi.canvasDispatch('updateDependencies', { detail: materialState.componentsDepsMap })
+  } catch (error) {
+    console.error('Failed to update canvas dependencies:', 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.

Suggested change
useCanvas().canvasApi.value?.canvasDispatch('updateDependencies', { detail: materialState.componentsDepsMap })
try {
const canvasApi = useCanvas().canvasApi.value
if (!canvasApi?.canvasDispatch) {
throw new Error('Canvas API not available')
}
canvasApi.canvasDispatch('updateDependencies', { detail: materialState.componentsDepsMap })
} catch (error) {
console.error('Failed to update canvas dependencies:', error)
}

Copy link
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: 1

♻️ Duplicate comments (1)
packages/plugins/materials/src/composable/useMaterial.js (1)

493-493: ⚠️ Potential issue

Add error handling for canvas dispatch.

The canvas dispatch operation should handle potential errors when the canvas API is not available.

🧹 Nitpick comments (2)
packages/plugins/materials/src/composable/useMaterial.js (2)

42-43: Consider using TypeScript interfaces for state objects.

The reactive state objects would benefit from TypeScript interfaces to clearly define the shape of componentsDepsMap and packages. This would improve maintainability and provide better IDE support.

interface ComponentsDepsMap {
  scripts: Array<{
    package: string;
    script: string;
    components?: Record<string, string>;
  }>;
  styles: Set<string>;
}

interface MaterialState {
  components: any[];
  blocks: any[];
  componentsDepsMap: ComponentsDepsMap;
  packages: any[]; // Define specific package structure
}

540-540: Document the new exported functions.

The newly exported setCanvasDeps function lacks JSDoc documentation. Add documentation to maintain consistency with other exported functions.

+  /**
+   * Sets canvas dependencies including material and utility dependencies.
+   * Notifies the canvas to refresh the importmap.
+   */
   setCanvasDeps,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50ef894 and 775f6c9.

📒 Files selected for processing (1)
  • packages/plugins/materials/src/composable/useMaterial.js (8 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: push-check
🔇 Additional comments (3)
packages/plugins/materials/src/composable/useMaterial.js (3)

285-299: ⚠️ Potential issue

Add error handling for missing or malformed utils.

The getUtilsDependencies function should handle potential errors when accessing item.content properties.

 const getUtilsDependencies = () => {
   const { utils } = useResource().appSchemaState
 
   return utils
-    .filter((item) => item.type === 'npm' && item.content.cdnLink)
+    .filter((item) => item.type === 'npm' && item.content?.cdnLink)
     .map((item) => {
+      if (!item.content?.package) {
+        console.warn(`Invalid util item: missing package name`, item)
+        return null
+      }
       const { package: pkg, version, cdnLink } = item.content
 
       return {
         package: pkg,
         version,
         script: cdnLink
       }
-    })
+    })
+    .filter(Boolean)

Likely invalid or redundant comment.


468-479: ⚠️ Potential issue

Ensure atomic updates for reactive state.

Direct mutations to materialState.componentsDepsMap may not trigger reactivity properly. Consider using atomic updates.

-materialState.componentsDepsMap.scripts.push({ package: pkg, script, css, components })
+const updatedScripts = [...materialState.componentsDepsMap.scripts, { package: pkg, script, css, components }]
+materialState.componentsDepsMap.scripts = updatedScripts

-styles?.forEach((item) => materialState.componentsDepsMap.styles.add(item))
+const updatedStyles = new Set([...materialState.componentsDepsMap.styles, ...(styles || [])])
+materialState.componentsDepsMap.styles = updatedStyles

Likely invalid or redundant comment.


323-366: ⚠️ Potential issue

Add validation for material bundle structure.

The parseMaterialsDependencies function should validate the material bundle structure before processing.

 const parseMaterialsDependencies = (materialBundle) => {
+  if (!materialBundle || typeof materialBundle !== 'object') {
+    console.error('Invalid material bundle:', materialBundle)
+    return
+  }
+
   const { packages, components } = materialBundle
 
   const { scripts: scriptsDeps, styles: stylesDeps } = useResource().appSchemaState.canvasDeps
 
   packages?.forEach((pkg) => {
+    if (!pkg?.package) {
+      console.warn('Invalid package in material bundle:', pkg)
+      return
+    }
     if (scriptsDeps.find((item) => item.package === pkg.package)) {
       return
     }

Likely invalid or redundant comment.

Comment on lines +304 to +320
const setCanvasDeps = () => {
const { scripts, styles } = useResource().appSchemaState.canvasDeps

// 将utils依赖添加到canvasDeps中
getUtilsDependencies().forEach((util) => {
if (scripts.find((item) => util.package === item.package)) {
return
}

scripts.push(util)
})

useMessage().publish({
topic: 'init_canvas_deps',
data: { scripts: scripts, styles: [...styles] }
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add retry mechanism for canvas dependency initialization.

The setCanvasDeps function should handle potential initialization failures and implement a retry mechanism.

 const setCanvasDeps = () => {
   const { scripts, styles } = useResource().appSchemaState.canvasDeps
 
   // 将utils依赖添加到canvasDeps中
   getUtilsDependencies().forEach((util) => {
     if (scripts.find((item) => util.package === item.package)) {
       return
     }
 
     scripts.push(util)
   })
 
+  const initDeps = (retries = 3) => {
     useMessage().publish({
       topic: 'init_canvas_deps',
       data: { scripts: scripts, styles: [...styles] }
     })
+    .catch((error) => {
+      if (retries > 0) {
+        console.warn(`Retrying canvas deps initialization. Attempts left: ${retries-1}`)
+        setTimeout(() => initDeps(retries - 1), 1000)
+      } else {
+        console.error('Failed to initialize canvas dependencies:', error)
+      }
+    })
+  }
+
+  initDeps()
 }

Committable suggestion skipped: line range outside the PR's diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request ready_for_review This PR requires more reviews refactor-main refactor/develop branch feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants