Skip to content

fix: localImportMap not works #1242

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

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from

Conversation

chilingling
Copy link
Member

@chilingling chilingling commented Mar 22, 2025

English | 简体中文

PR

PR Checklist

Please check if your PR fulfills the following requirements:

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

PR Type

What kind of change does this PR introduce?

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

Background and solution

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

  • New Features

    • Added support for localizing CDN resources, allowing dependencies and assets to be bundled and served locally for offline or intranet environments.
    • Introduced environment variables and configuration options to control local CDN behavior and output paths.
    • Added a new example environment configuration file for local CDN usage.
  • Documentation

    • Added comprehensive documentation for the local CDN solution, including setup instructions and configuration details.
    • Updated documentation catalog and README to reference the new local CDN solution.
  • Bug Fixes

    • Improved handling of import maps and CDN URLs, ensuring correct resolution and replacement based on environment settings.
    • Enhanced robustness in processing and filtering invalid CDN entries.
  • Tests

    • Added extensive automated tests to validate local CDN functionality, including dependency copying and environment variable handling.
  • Chores

    • Updated dependencies and scripts to support new testing and build processes.
    • Improved .gitignore to exclude local CDN bundle dependencies from version control.

Copy link
Contributor

coderabbitai bot commented Mar 22, 2025

Walkthrough

This update introduces a comprehensive local CDN solution across the project. It adds new environment variables, configuration files, and documentation to support localizing CDN dependencies for offline or intranet environments. The core build logic is refactored to use a dynamic Vite plugin for managing local CDN resources, replacing static import map copying. Several test files are added to verify the local CDN functionality, including dependency copying and environment variable handling. Supporting utilities and configuration enhancements are included, such as new helper functions, updated import map handling, and the integration of a static copy plugin. Documentation is expanded to describe the local CDN approach and its configuration.

Changes

Files/Groups Change Summary
.gitignore Added designer-demo/bundle-deps to ignore local bundle dependencies.
designer-demo/env/.env.alpha, .env.development, .env.localCDN.example Updated CDN domain, enabled local CDN features, added new environment variables for local CDN path and bundle directory; added example config for local CDN.
designer-demo/package.json Increased memory for build scripts, added Vitest test scripts, and added vitest as a dev dependency.
designer-demo/public/mock/bundle.json Added "destructuring": true to the npm config for TinyGridColumn.
designer-demo/tests/localCdnBasic.test.js, localCdnBundleDeps.test.js, localCdnCustomConfig.test.js Added new Vitest test suites to verify local CDN functionality, including copying dependencies, customizing CDN config, and validating build outputs.
designer-demo/tests/utils/envHelpers.js Added utility functions for manipulating environment files and variables in tests.
designer-demo/vitest.config.js Added Vitest configuration with Node environment, timeouts, and serial execution.
docs/README.md, catalog.json, solutions/local-cdn.md Added documentation and catalog entries for the local CDN solution, describing its purpose, configuration, and usage.
packages/build/vite-config/package.json Added fast-glob dependency.
packages/build/vite-config/src/default-config.js Replaced static import map copying with a plugin-based local CDN approach; updated environment variable handling.
packages/build/vite-config/src/localCdnFile/copyBundleDeps.js, copyImportMap.js Enhanced logic to support packages in bundle files, deduplicate URLs, and filter invalid entries.
packages/build/vite-config/src/localCdnFile/index.js Exported the new localCdnPlugin module.
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js Introduced a Vite plugin to handle localizing CDN dependencies, including install/copy logic and configuration merging.
packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js Centralized and improved regex extraction for CDN package info; added error handling for unmatched cases.
packages/build/vite-config/src/localCdnFile/replaceImportPath.mjs Added detailed JSDoc comments for utility functions.
packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js Added a Vite plugin to copy files/directories with optional JS import path transformation.
packages/canvas/DesignCanvas/src/DesignCanvas.vue, importMap.ts Refactored import map data logic: removed version overrides, centralized URL generation, and updated usage.
packages/common/js/importMap/index.js, importMap/import-map.json Exported import map config and updated version ranges for dependencies.
packages/common/package.json, vite.config.ts Added vite-plugin-static-copy and configured it to copy import map JSON to build output.
packages/design-core/package.json, vite.config.js Added vite-plugin-static-copy and configured copying of import map JSON; defined new environment variable for local CDN path.
packages/design-core/src/preview/src/preview/Preview.vue, importMap.js, srcFiles.js Simplified import map retrieval, updated import map handling for local CDN, and prioritized local CDN path in environment variable usage.

Sequence Diagram(s)

sequenceDiagram
    participant Dev as Developer
    participant Vite as Vite Build System
    participant LocalCDN as localCdnPlugin
    participant FileSys as File System

    Dev->>Vite: Run build with local CDN env vars enabled
    Vite->>LocalCDN: Initialize localCdnPlugin with config
    LocalCDN->>FileSys: Check/install missing packages
    LocalCDN->>FileSys: Copy/copy+transform CDN files to local-cdn-static
    LocalCDN-->>Vite: Register plugin for build
    Vite->>FileSys: Generate build output (dist)
    FileSys-->>Dev: Output includes local-cdn-static and updated assets
Loading

Poem

🐇
In burrows deep, I stash my code,
Now local CDNs lighten the load.
With plugins, tests, and docs anew,
My carrots build much faster too!
No network woe, just files at hand—
The engine hops at my command.
Hooray for local, swift, and neat—
This rabbit’s build is hard to beat!
🥕

✨ Finishing Touches
  • 📝 Generate Docstrings

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

❤️ Share
🪧 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.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @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 bug Something isn't working label Mar 22, 2025
@chilingling chilingling force-pushed the feat/localCDNPlugin branch from 76b33be to 1ef77f0 Compare March 24, 2025 15:45
@chilingling chilingling marked this pull request as ready for review March 27, 2025 03:54
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: 3

🧹 Nitpick comments (13)
designer-demo/vitest.config.js (1)

1-19: Well-structured Vitest configuration.

The configuration correctly sets up the testing environment with Node and globals enabled. The extended timeouts (10 minutes) are appropriate for builds that might take longer to complete.

However, disabling file parallelism will significantly increase overall test execution time. Consider:

  1. Adding a comment explaining the total expected test execution time
  2. If possible, restructuring tests to be more isolated so they could run in parallel
packages/common/vite.config.ts (1)

28-41: Well-structured plugin configuration with clear purpose.

The plugin configuration is now more readable with one plugin per line. The viteStaticCopy plugin is correctly set up to copy the import-map.json file to the output directory, and the comment clearly explains its purpose.

Consider specifying a subdirectory for the copied file rather than the root directory (.) to avoid potential naming conflicts:

  viteStaticCopy({
    targets: [
      {
        src: './js/importMap/import-map.json',
-       dest: '.'
+       dest: './importMap'
      }
    ]
  })
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (5)

1-9: Consider using a dedicated logger.
While using console is acceptable, a dedicated logger can provide structured logging and log levels (info, warn, error, debug) for more robust debugging and production visibility.


31-37: Handle potential Babel transform failures.
When calling babelReplaceImportPathWithCertainFileName, consider catching or reporting errors if the transformation fails, to avoid quietly returning original content.


48-60: Ensure comprehensive error handling.
The extractInfo function catches an error and logs it, but consider re-throwing or returning a fallback to prevent silent failures elsewhere in the pipeline.


251-315: Check for missing fallback scenarios.
This function relies heavily on file existence checks and user configurations. Consider handling rare corner cases, such as partially installed packages or mismatched file paths, to improve robustness.


318-386: Overall plugin logic is comprehensive.
Combining default and user-defined import maps is a thoughtful approach. For readability, you might split the logic into smaller helper functions if it grows more complex.

designer-demo/tests/localCdnBundleDeps.test.js (1)

76-81: Build command may rely on pnpm availability.
Using execSync('pnpm run build:alpha') can fail if pnpm isn’t installed. Consider documenting or verifying that pnpm is installed in CI or development environments.

packages/build/vite-config/src/localCdnFile/copyBundleDeps.js (1)

33-44: Bundle link replacement logic is clean.
Iterating over packages and matching URLs works well. Consider logging or warning if a link is not matched, to detect unhandled URLs.

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

26-37: Address TODO comments in the code.

There are TODO comments indicating that certain parts of the code should be deleted, but they are still present. These should either be removed or addressed with a plan for future cleanup.

Consider creating a follow-up issue to address these TODOs or remove them if they are no longer relevant:

export function getImportMapData(canvasDeps = { scripts: [], styles: [] }) {
  // 以下内容由于区块WebComponent加载需要补充
  const blockRequire = {
    imports: {
-      // TODO: 删除
      '@opentiny/vue': getImportUrl('@opentiny/vue'),
      '@opentiny/vue-icon': getImportUrl('@opentiny/vue-icon'),
      // 'element-plus': getImportUrl('element-plus'),
      '@opentiny/tiny-engine-builtin-component': getImportUrl('@opentiny/tiny-engine-builtin-component')
    },
    importStyles: []
  }

39-46: Consider refactoring hard-coded dependency mappings.

The hardcoded dependencies in tinyVueRequire make the code less maintainable. Consider moving these to a configuration file or another centralized location that can be more easily updated.

-  // TODO: 删除
  const tinyVueRequire = {
    imports: {
      '@opentiny/vue-common': getImportUrl('@opentiny/vue-common'),
      '@opentiny/vue-locale': getImportUrl('@opentiny/vue-locale'),
      echarts: getImportUrl('echarts')
    }
  }
designer-demo/tests/localCdnBasic.test.js (2)

20-39: Consider isolating tests from actual builds to improve test performance.

The current test setup modifies real environment files and runs actual builds, which could make tests slow and potentially disrupt development environments if not run in isolation.

Consider mocking the build process or using a dedicated test environment to isolate these tests. This would make tests run faster and more reliably, especially in CI environments.

- // 执行构建
- execSync('pnpm run build:alpha', { 
-   cwd: projectRoot, 
-   stdio: 'inherit'
- })
+ // 在测试环境中执行构建
+ execSync('pnpm run build:alpha', { 
+   cwd: projectRoot, 
+   stdio: 'inherit',
+   env: {
+     ...process.env,
+     NODE_ENV: 'test'
+   }
+ })

74-87: Make environment variable check more robust.

The current method of checking for environment variable replacement by searching for a string in a file could be fragile if the build output format changes.

Consider using a more structured approach to validate the environment variable substitution, such as:

- // 检查文件内容
- const mainJsContent = fs.readFileSync(path.resolve(jsFiles[0]), 'utf-8')
- 
- // 检查 VITE_CDN_DOMAIN 环境变量是否被替换为本地路径
- expect(mainJsContent).toContain('./local-cdn-static')
+ // 检查所有文件是否都正确替换了环境变量
+ const allFilesContainLocalPath = jsFiles.every(file => {
+   const content = fs.readFileSync(file, 'utf-8')
+   return content.includes('./local-cdn-static')
+ })
+ 
+ expect(allFilesContainLocalPath).toBe(true)
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between cb59aa0 and c363ae9.

📒 Files selected for processing (30)
  • .gitignore (1 hunks)
  • designer-demo/env/.env.alpha (1 hunks)
  • designer-demo/env/.env.development (0 hunks)
  • designer-demo/package.json (2 hunks)
  • designer-demo/public/mock/bundle.json (1 hunks)
  • designer-demo/tests/localCdnBasic.test.js (1 hunks)
  • designer-demo/tests/localCdnBundleDeps.test.js (1 hunks)
  • designer-demo/tests/localCdnCustomConfig.test.js (1 hunks)
  • designer-demo/tests/utils/envHelpers.js (1 hunks)
  • designer-demo/vitest.config.js (1 hunks)
  • docs/catalog.json (1 hunks)
  • docs/solutions/local-cdn.md (1 hunks)
  • packages/build/vite-config/package.json (1 hunks)
  • packages/build/vite-config/src/default-config.js (2 hunks)
  • packages/build/vite-config/src/localCdnFile/copyBundleDeps.js (2 hunks)
  • packages/build/vite-config/src/localCdnFile/copyImportMap.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/index.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js (2 hunks)
  • packages/build/vite-config/src/localCdnFile/replaceImportPath.mjs (2 hunks)
  • packages/canvas/DesignCanvas/src/DesignCanvas.vue (1 hunks)
  • packages/canvas/DesignCanvas/src/importMap.ts (2 hunks)
  • packages/common/js/importMap/import-map.json (2 hunks)
  • packages/common/js/importMap/index.js (1 hunks)
  • packages/common/package.json (1 hunks)
  • packages/common/vite.config.ts (1 hunks)
  • packages/design-core/package.json (1 hunks)
  • packages/design-core/src/preview/src/preview/Preview.vue (1 hunks)
  • packages/design-core/src/preview/src/preview/importMap.js (1 hunks)
  • packages/design-core/vite.config.js (2 hunks)
💤 Files with no reviewable changes (1)
  • designer-demo/env/.env.development
🧰 Additional context used
🧠 Learnings (2)
packages/canvas/DesignCanvas/src/DesignCanvas.vue (1)
Learnt from: yy-wow
PR: opentiny/tiny-engine#940
File: packages/canvas/DesignCanvas/src/importMap.js:51-51
Timestamp: 2025-03-27T03:11:41.427Z
Learning: The `getImportMapData` function in `packages/canvas/DesignCanvas/src/importMap.js` has default parameter handling that initializes `canvasDeps` with empty arrays for `scripts` and `styles`, making additional null checks unnecessary.
packages/canvas/DesignCanvas/src/importMap.ts (1)
Learnt from: yy-wow
PR: opentiny/tiny-engine#940
File: packages/canvas/DesignCanvas/src/importMap.js:51-51
Timestamp: 2025-03-27T03:11:41.427Z
Learning: The `getImportMapData` function in `packages/canvas/DesignCanvas/src/importMap.js` has default parameter handling that initializes `canvasDeps` with empty arrays for `scripts` and `styles`, making additional null checks unnecessary.
🧬 Code Definitions (7)
packages/build/vite-config/src/localCdnFile/copyImportMap.js (1)
packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js (1)
  • getCdnPathNpmInfoForSingleFile (42-124)
designer-demo/tests/localCdnCustomConfig.test.js (3)
designer-demo/tests/localCdnBasic.test.js (5)
  • __dirname (13-13)
  • projectRoot (14-14)
  • envAlphaPath (17-17)
  • distDir (15-15)
  • localCdnDir (16-16)
designer-demo/tests/localCdnBundleDeps.test.js (4)
  • __dirname (13-13)
  • projectRoot (14-14)
  • envAlphaPath (17-17)
  • distDir (18-18)
designer-demo/tests/utils/envHelpers.js (5)
  • backupPath (53-53)
  • backupPath (64-64)
  • backupEnvFile (51-56)
  • ensureEnvVarEnabled (12-27)
  • restoreEnvFile (63-69)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (4)
packages/build/vite-config/src/default-config.js (2)
  • logger (168-168)
  • config (146-146)
packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js (12)
  • content (85-85)
  • content (156-156)
  • result (11-11)
  • version (75-75)
  • version (151-151)
  • fs (7-7)
  • src (77-77)
  • src (152-152)
  • sourceExist (78-78)
  • sourceExist (153-153)
  • packageNeedToInstall (209-218)
  • dedupeCopyFiles (195-206)
packages/build/vite-config/src/localCdnFile/copyBundleDeps.js (4)
  • result (16-16)
  • fs (13-13)
  • cdnFiles (54-56)
  • packageNeedToInstall (58-58)
packages/build/vite-config/src/localCdnFile/copyImportMap.js (1)
  • packageNeedToInstall (35-37)
designer-demo/tests/localCdnBundleDeps.test.js (3)
designer-demo/tests/localCdnBasic.test.js (4)
  • __dirname (13-13)
  • projectRoot (14-14)
  • envAlphaPath (17-17)
  • distDir (15-15)
designer-demo/tests/localCdnCustomConfig.test.js (4)
  • __dirname (13-13)
  • projectRoot (14-14)
  • envAlphaPath (16-16)
  • distDir (18-18)
designer-demo/tests/utils/envHelpers.js (4)
  • backupEnvFile (51-56)
  • updateCdnDomain (35-44)
  • ensureEnvVarEnabled (12-27)
  • restoreEnvFile (63-69)
packages/canvas/DesignCanvas/src/importMap.ts (2)
packages/design-core/src/preview/src/preview/importMap.js (2)
  • versionDelimiter (22-22)
  • fileDelimiter (23-23)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1)
  • importMapConfig (334-334)
designer-demo/tests/localCdnBasic.test.js (3)
designer-demo/tests/localCdnBundleDeps.test.js (4)
  • __dirname (13-13)
  • projectRoot (14-14)
  • distDir (18-18)
  • envAlphaPath (17-17)
designer-demo/tests/localCdnCustomConfig.test.js (4)
  • __dirname (13-13)
  • projectRoot (14-14)
  • distDir (18-18)
  • envAlphaPath (16-16)
designer-demo/tests/utils/envHelpers.js (3)
  • backupEnvFile (51-56)
  • ensureEnvVarEnabled (12-27)
  • restoreEnvFile (63-69)
designer-demo/tests/utils/envHelpers.js (1)
designer-demo/tests/localCdnCustomConfig.test.js (1)
  • backupPath (55-55)
🔇 Additional comments (62)
packages/build/vite-config/package.json (1)

34-34: Addition of fast-glob dependency looks appropriate

The addition of the fast-glob package will support file pattern matching functionality, which is likely needed for the local CDN plugin that this PR introduces.

packages/build/vite-config/src/localCdnFile/index.js (1)

7-7: New localCdnPlugin export enhances the module's capabilities

This addition exports the new localCdnPlugin functionality, which appears to be central to fixing the local import map issue mentioned in the PR title.

.gitignore (1)

9-9: Appropriate addition to .gitignore

Adding the bundle-deps directory for designer-demo to .gitignore is a good practice as these are likely generated files that shouldn't be committed to version control.

packages/common/js/importMap/index.js (1)

1-1: Good approach to centralizing import map configuration

Exporting the import map configuration from a central location improves maintainability and ensures consistency across the application. This change likely plays a key role in fixing the local import map functionality.

packages/design-core/package.json (1)

119-120: Added essential dependency for local import map functionality.

The addition of vite-plugin-static-copy is necessary for the local import map fix, as it enables copying static assets during the build process, which is crucial for the local CDN solution.

docs/catalog.json (1)

81-82: Documentation improvement for local CDN solution.

Adding documentation for the local CDN solution is valuable, especially since this PR involves fixing the local import map functionality. The placement in the "解决方案" (Solutions) section is appropriate.

packages/build/vite-config/src/localCdnFile/copyImportMap.js (1)

33-33: ⚠️ Potential issue

Critical fix for handling null values in style files processing.

Similar to the fix above, this .filter(Boolean) call ensures that any null values from style URL processing are filtered out before further operations. This change is essential for robust handling of style URLs.

There appears to be a syntax error in the function call. The last parameter bundleTempDir is outside the function call parentheses:

- .map((url) => getCdnPathNpmInfoForSingleFile(url, originCdnPrefix, base, dir, false), bundleTempDir)
+ .map((url) => getCdnPathNpmInfoForSingleFile(url, originCdnPrefix, base, dir, false, bundleTempDir))

This means the bundleTempDir parameter isn't being passed to the function, which could lead to unexpected behavior.

Likely an incorrect or invalid review comment.

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

10166-10168: Correct import mapping for TinyGridColumn component.

This change adds the destructuring: true property to properly indicate that the TinyGridColumn component should be imported using destructuring syntax from the package, which aligns with the expected behavior of the local import map system.

This change is necessary for fixing the local import map functionality as mentioned in the PR title. When destructuring is set to true, the component will be imported using the proper syntax: import { TinyGridColumn } from '@opentiny/vue' rather than as a default import.

packages/common/package.json (1)

54-56: Appropriate addition of the vite-plugin-static-copy dependency.

The new dependency is correctly added and will be used for copying the import-map.json file during the build process as implemented in the vite.config.ts file.

packages/common/vite.config.ts (1)

20-20: Correctly imported the viteStaticCopy plugin.

The import statement properly brings in the plugin that will be used to copy the import-map.json file during build.

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

15-15:

✅ Verification successful

Refactored to use module import instead of direct JSON import.

This change improves the codebase by using a centralized importMapConfig module rather than directly importing a JSON file. This approach provides better flexibility and maintainability.

Let's verify that the structure of importMapConfig matches the previously expected format:


🏁 Script executed:

#!/bin/bash
# Check the structure of the importMapConfig module
cat packages/common/js/importMap/index.js

# Compare with the structure of the original import-map.json if it exists
find packages -name "import-map.json" -exec cat {} \; 2>/dev/null

Length of output: 2612


Module import verified.
The file at packages/common/js/importMap/index.js correctly re-exports the default export from import-map.json, and the JSON structure matches the expected format. This confirms that the refactoring to use a centralized module import improves flexibility and maintainability without altering the data structure.

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

80-80:

✅ Verification successful

Simplification of import map data retrieval

The change removes the importMapVersion parameter from the getImportMapData() call, which aligns with the refactoring of this function in the codebase. This is a necessary fix for the local import map functionality to work correctly.


🏁 Script executed:

#!/bin/bash
# Verify the updated getImportMapData function signature and implementation

# Check the implementation of getImportMapData
rg -A 5 "function getImportMapData" --type ts

Length of output: 544


Verified: Removal of Unused Parameter in Import Map Data Retrieval

The refactoring of the getImportMapData function now omits the importMapVersion parameter, which is confirmed by the implementation in packages/canvas/DesignCanvas/src/importMap.ts. The call in DesignCanvas.vue has been updated accordingly, and the changes align with the new function signature. No further modifications are needed.

designer-demo/package.json (2)

9-11: Added testing support for local CDN functionality

The addition of Vitest test scripts provides a way to validate the local CDN and import map functionality that's being fixed in this PR.


30-31: Added Vitest dependency

The Vitest dependency is correctly added to support the new test scripts.

packages/design-core/vite.config.js (1)

9-9: Added viteStaticCopy plugin import

This import is necessary for copying the import-map.json file during the build process.

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

25-25: Removed fetchImportMap import

Removing the fetchImportMap import is consistent with the changes to simplify the import map loading process.


67-69:

❓ Verification inconclusive

Simplified getImportMap implementation

This change simplifies the function to always use getInitImportMap() directly, removing conditional logic that's no longer needed since the PR enables local import maps by default through environment variables. This is a key part of fixing the local import map functionality.


🏁 Script executed:

#!/bin/bash
# Verify the environment configuration for local import maps

# Check for environment variables related to local import maps
grep -r "VITE_LOCAL_IMPORT_MAPS" --include=".env*" .
grep -r "VITE_LOCAL_BUNDLE_DEPS" --include=".env*" .

# Check the getInitImportMap implementation
rg -A 5 "export function getImportMap" --type js

Length of output: 1062


Simplified getImportMap Implementation and Environment Check

The updated function in
packages/design-core/src/preview/src/preview/Preview.vue
now directly returns getInitImportMap()—removing the old conditional logic—so that local import maps are consistently applied based on the environment configuration. The environment variable searches confirm that settings for VITE_LOCAL_IMPORT_MAPS and VITE_LOCAL_BUNDLE_DEPS exist in the various .env files (e.g. in .env.alpha versus development/production), even though the search for an exported getImportMap didn’t return any results. This is expected since the function is defined as a constant rather than an exported function.

Please verify manually that:

  • The simplified function in the Vue component is correctly wired with the proper import and usage of getInitImportMap().
  • The absence of conditional logic does not affect any other dependent functionality, given the diverse environment configurations.
designer-demo/env/.env.alpha (2)

7-8: Enable local import maps and bundle dependencies

These changes enable both VITE_LOCAL_IMPORT_MAPS and VITE_LOCAL_BUNDLE_DEPS, which is essential for resolving the issue mentioned in the PR title "localImportMap not works". By setting these to true, the application will now use local versions of import maps and bundle dependencies rather than fetching from CDN.


4-4: CDN domain updated to match npmmirror

The CDN domain has been changed to use https://registry.npmmirror.com, which aligns with the new format support added in the locateCdnNpmInfo.js file. This ensures that the import map resolver can correctly handle this CDN provider.

packages/build/vite-config/src/localCdnFile/replaceImportPath.mjs (3)

9-13: Documentation added for relativePathPattern function

Clear JSDoc comments improve code maintainability by explaining the function's purpose, parameters, and return type.


18-23: Documentation added for resolvePath function

Comprehensive JSDoc comments clarify how the function resolves paths, including parameter types and return values.


61-68: Enhanced documentation for babelReplaceImportPathWithCertainFileName function

Detailed JSDoc comments explain the function's purpose, parameters, and return structure, making it easier for other developers to understand and use correctly.

packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js (2)

51-75: Support for multiple CDN formats added

This critical change adds support for both unpkg and npmmirror CDN formats by introducing separate regex patterns for each. The error handling with try-catch improves robustness, and the code now uses the first successful match (with npmmirror having priority).

This update directly addresses the root cause of the "localImportMap not works" issue by ensuring proper URL parsing regardless of the CDN format.


135-149: Multiple CDN format support for package info

Similar to the previous change, this adds support for both unpkg and npmmirror formats in the getCdnPathNpmInfoForPackage function, ensuring consistent handling across the codebase.

packages/build/vite-config/src/default-config.js (2)

13-13: Replace copyPreviewImportMap with localCdnPlugin

Updated import statement to use the new localCdnPlugin module instead of copyPreviewImportMap, indicating a shift to a more comprehensive plugin-based approach.


166-180: Implement local CDN plugin integration

This new code block properly initializes and configures the local CDN plugin when local import maps are enabled. The implementation:

  1. Logs the initialization for better debugging
  2. Creates the plugin with appropriate configuration parameters
  3. Conditionally adds the plugins to the Vite config

This is a key part of the fix that replaces the previous implementation with a more robust plugin-based approach.

packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (5)

10-23: Configuration object looks good.
Bundling default copy configurations for known packages is a clear and maintainable approach.


63-83: Environment variable replacement plugin is straightforward.
The plugin approach cleanly injects environment variables for local CDN usage. Looks solid.


176-225: Modular copy plugin is well-structured.
The plugin design is flexible and remains maintainable.


233-249: Version comparison logic is simplistic.
For caret (^) and tilde (~) comparisons, only the first or first two digits are checked. This may allow unintended mismatches (e.g., ^3.20.0 vs. 3.21.0). Thoroughly test different version scenarios.


91-149:

❓ Verification inconclusive

Verify handling of non-text files in directory copy.
The function reads every file as UTF-8 and transforms it. This may cause issues for binary files (images, fonts, etc.). Consider skipping or handling binary files differently.


🏁 Script executed:

#!/bin/bash
# Description: Identify potential binary files in localCDN directories.

# Example: Searching for image or font extensions in a typical localCDN folder
fd -e jpg -e png -egif -ewoff -eot -tt file local-cdn-static

Length of output: 231


Action needed: Review binary file handling in directory copy
In packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (lines 91-149), all files are read with UTF-8 encoding and then transformed. This approach may inadvertently process binary files (images, fonts, etc.), potentially leading to corruption or errors. Please verify whether binary files should be skipped or handled differently. Also, note that the previous shell command error indicates that the fd command was used with an incorrect type flag – consider using -t f instead of -tt file.

designer-demo/tests/localCdnBundleDeps.test.js (4)

1-4: Test documentation is helpful.
Clear JSDoc and naming improve maintainability.


48-61: Robust backup and restore logic.
Backing up the environment file before modifications is a good practice, ensuring no permanent side effects during tests.


97-115: Local resource validation test is thorough.
Verifying that the TinyVue script is localized while Element Plus remains external clarifies the intended partial-localization logic.


117-139: Ensures required packages are actually copied.
Checking for presence of TinyVue’s MJS and CSS files in the local CDN directory ensures an end-to-end test of the copy process.

packages/build/vite-config/src/localCdnFile/copyBundleDeps.js (2)

15-30: Use of a Set for unique URLs is smart.
Switching to a set effectively avoids duplicates. Returning [...result] preserves the array format.


54-56: Filtering out falsy values is correct.
Using filter(Boolean) helps avoid errors caused by null or undefined mapping results.

designer-demo/tests/localCdnCustomConfig.test.js (7)

1-4: Well-structured test file for local CDN custom configuration.

The documentation comments provide a clear explanation of the test's purpose: validating custom CDN configurations in a local environment.


23-48: Function handles registry file updates properly.

The updateRegistryFile function correctly creates a backup of the original registry.js file before making modifications. The regex pattern for finding the config object is effective for injecting the custom importMap.


53-61: Good cleanup implementation for registry file restoration.

The function properly checks for the existence of the backup file before attempting restoration, which prevents potential errors.


63-109: Thorough test setup with proper environment preparation.

The beforeAll hook appropriately:

  1. Backs up configuration files
  2. Injects custom CDN configuration
  3. Ensures environment variables are enabled
  4. Updates the registry.js file
  5. Executes the build process

This comprehensive setup ensures that the test environment accurately reflects a real-world scenario.


111-124: Complete cleanup in afterAll hook.

The test properly cleans up all modifications made during setup, restoring the original state of configuration files. This is essential for preventing side effects in subsequent tests.


126-157: Comprehensive verification of custom importMap configuration.

This test case thoroughly verifies that:

  1. The Vue package is correctly copied to the local CDN directory
  2. The directory structure is maintained as expected
  3. The required files exist in the specified locations

The use of file system checks provides strong validation of the feature's functionality.


159-172: Proper environment variable verification.

This test ensures that the VITE_CDN_DOMAIN environment variable is correctly set to the local CDN path in the generated JS files, which is crucial for the proper functioning of the local CDN feature.

docs/solutions/local-cdn.md (8)

1-10: Well-structured documentation with clear problem statement.

The documentation begins with a concise overview of the local CDN solution and clearly outlines the problems it addresses. This helps users understand the purpose and benefits of the feature.


11-39: Clear usage instructions with examples.

The documentation provides step-by-step instructions on how to enable and configure the local CDN feature, including environment variable settings and optional configuration in the Vite config file. This makes it easy for users to implement the solution.


41-50: Comprehensive configuration options documentation.

The configuration options are clearly presented in a table format, making it easy for users to understand the available parameters, their types, default values, and purposes.


51-87: Detailed importMap configuration explanation.

The documentation thoroughly explains the importMap format, URL structure, and the importance of consistent configuration between Vite config and registry settings. The inclusion of examples enhances clarity.


88-126: Clear copy configuration details with examples.

The documentation provides comprehensive information about the copy configuration options, including default settings and examples of custom configurations. This helps users understand how to customize file copying for their specific needs.


127-166: Helpful explanation of bundle file handling.

The section clearly explains how CDN links in bundle files are processed and provides an important note about the requirement for matching prefixes between bundle.json and the environment variable configuration.


167-202: Well-structured implementation principles.

Breaking down the implementation principles into distinct steps helps developers understand the internal workings of the local CDN solution, which is valuable for troubleshooting or extending the functionality.


203-207: Important notes for consideration.

The documentation concludes with critical considerations that users should be aware of when implementing the local CDN solution, which helps prevent potential issues.

designer-demo/tests/utils/envHelpers.js (4)

1-27: Well-implemented environment variable management utility.

The ensureEnvVarEnabled function effectively:

  1. Checks if a variable exists
  2. Adds it if it doesn't exist
  3. Sets it to true if it exists but is false
  4. Leaves it unchanged if it already exists and is not false

The function is well-documented with JSDoc comments and has proper error handling.


29-44: Good implementation of CDN domain update utility.

The updateCdnDomain function properly handles both cases:

  1. Adding the CDN domain if it doesn't exist
  2. Updating it if it already exists

The regex pattern used for matching is appropriate for environment variable files.


46-56: Simple and effective backup function.

The backupEnvFile function correctly checks if the file exists before attempting to create a backup, which prevents potential errors.


58-69: Proper cleanup in restore function.

The restoreEnvFile function appropriately:

  1. Checks if the backup file exists
  2. Copies it back to the original location
  3. Deletes the backup file to clean up

This ensures clean restoration without leaving unnecessary files.

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

59-64: Good refactoring of import URLs.

The replacement of hardcoded URLs with the centralized getImportUrl function improves maintainability and consistency. This approach makes it easier to update URL patterns and adapt to different CDN providers.

designer-demo/tests/localCdnBasic.test.js (1)

1-88: Well-structured test suite for localCDN functionality.

This test file is well-organized and follows good testing practices by:

  1. Properly setting up the test environment in beforeAll()
  2. Cleaning up after testing in afterAll()
  3. Using clear, focused test cases with specific assertions
  4. Validating key aspects of the localCDN functionality

The test coverage is comprehensive, checking for:

  • The creation of the local CDN directory
  • Proper copying of dependencies
  • Environment variable substitution
packages/common/js/importMap/import-map.json (4)

3-3: Good update to use Vue production build.

Switching to the production version of Vue (vue.runtime.esm-browser.prod.js) is appropriate for a production environment as it will be smaller and faster.


16-20: Consistent version range for OpenTiny Vue components.

Updating all the OpenTiny Vue components to use the same version range (~3.20) ensures consistency across the related packages and reduces the risk of incompatibilities.

The tilde (~) version range allows for patch updates while keeping the minor version fixed, which is a good practice for maintaining stability while accepting bug fixes.


3-21:

❓ Verification inconclusive

Verify that all imports are compatible with the localCDN setup.

The updated import map is critical for the localCDN functionality being implemented. The changes to file paths and version specifications must be compatible with how the files are stored locally.

Run the following script to verify that all the imported packages specified in the import map are correctly copied to the local CDN directory:


🏁 Script executed:

#!/bin/bash
# Check if all required packages are present in the local CDN directory
echo "Verifying packages in local CDN directory..."

# Build location from the test file
TEST_DIR=$(dirname $(find designer-demo -name "localCdnBasic.test.js"))
PROJECT_ROOT=$(realpath $(dirname "$TEST_DIR")/..)
DIST_DIR="$PROJECT_ROOT/dist"
LOCAL_CDN_DIR="$DIST_DIR/local-cdn-static"

if [ ! -d "$LOCAL_CDN_DIR" ]; then
  echo "Error: Local CDN directory not found at $LOCAL_CDN_DIR"
  exit 1
fi

# Check for key packages from import map
echo "Checking Vue package..."
find "$LOCAL_CDN_DIR" -name "vue.runtime.esm-browser.prod.js" | grep -q .
if [ $? -ne 0 ]; then
  echo "Warning: Vue production runtime not found"
fi

echo "Checking Vue I18n package..."
find "$LOCAL_CDN_DIR" -name "vue-i18n.esm-browser.js" | grep -q .
if [ $? -ne 0 ]; then
  echo "Warning: Vue I18n not found"
fi

echo "Checking OpenTiny Vue packages..."
find "$LOCAL_CDN_DIR" -name "tiny-vue-pc.mjs" | grep -q .
if [ $? -ne 0 ]; then
  echo "Warning: OpenTiny Vue runtime not found"
fi

# List all packages found
echo "Packages found in local CDN directory:"
ls -la "$LOCAL_CDN_DIR"

Length of output: 649


Action Required: Verify Local CDN Path Resolution & Package Availability

The current verification script fails to determine the project root due to the missing realpath command. As a result, the computed local CDN directory is incorrect (set to /dist/local-cdn-static), and the script cannot verify if the specified packages (e.g., Vue, Vue I18n, OpenTiny Vue) are actually present.

Recommendations:

  • Update the Script: Replace the usage of realpath with a more robust path resolution method (for example, use cd "$(dirname "$TEST_DIR")/.." && pwd) to accurately compute the project root.
  • Manual Verification: Manually confirm that the local CDN directory exists and includes the required packages matching the import map entries.
  • Re-run Verification: After updating the script, re-run the verification process to ensure all CDN imports resolve to the correct files.

5-5:

❓ Verification inconclusive

Verify compatibility with updated vue-i18n version.

The version for vue-i18n has been changed from a specific beta version to ^9.9.0, which is a significant update.

Please ensure that the application is fully tested with this version update, as moving from a beta version to a newer stable version might introduce compatibility issues.


🏁 Script executed:

#!/bin/bash
# Check for usage of vue-i18n specific APIs that might have changed
echo "Checking for vue-i18n API usage patterns..."
rg -t js -t vue "createI18n|useI18n|i18n\." --no-heading

Length of output: 183


Let's try a revised search command that explicitly targets Vue files by extension rather than relying on rg's file type flag. Please run the following shell script:

Once you get the output from this command, we can verify if there are any usages of the vue-i18n APIs that might be impacted by the version update. Please provide the new output if further verification is needed.


🏁 Script executed:

#!/bin/bash
echo "Checking for vue-i18n API usage patterns in .js and .vue files..."
rg -g '*.{js,vue}' "createI18n|useI18n|i18n\." --no-heading

Length of output: 4549


Action Required: Verify Compatibility with Updated vue-i18n Version (^9.9.0)

The version update from a beta version to ^9.9.0 appears to affect several parts of the codebase where vue-i18n APIs (like createI18n, useI18n, and usage of i18n.global) are used. The updated search revealed these usage patterns across multiple packages (e.g., in packages/common, packages/vue-generator, packages/design-core, and others).

Please perform thorough manual testing of i18n functionalities—such as translation rendering, locale switching, and merging of locale messages—to confirm that no compatibility or regression issues have been introduced by this update.

VITE_LOCAL_IMPORT_MAPS=false
VITE_LOCAL_BUNDLE_DEPS=false
VITE_LOCAL_IMPORT_MAPS=true
VITE_LOCAL_BUNDLE_DEPS=true
Copy link
Collaborator

Choose a reason for hiding this comment

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

可以考虑默认保留false

Copy link
Collaborator

Choose a reason for hiding this comment

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

默认值不要修改

filePathInPackage: '/'
},
'@opentiny/vue-runtime': {
filePathInPackage: '/dist3/'
Copy link
Collaborator

@rhlin rhlin Mar 27, 2025

Choose a reason for hiding this comment

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

这个配置不能解释 @opentiny/vue映射到vue-runtime ?
我觉得这个dist3是无感的才对, 所以好像不应该存在这个配置文件

Copy link
Member Author

Choose a reason for hiding this comment

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

这里默认配置是复制策略的默认配置,这几个都需要复制整个文件夹,所以作为了默认的配置。

)
const cdnFiles = extraBundleCdnLink(bundleFile, originCdnPrefix)
.map((url) => getCdnPathNpmInfoForSingleFile(url, originCdnPrefix, base, dir, false, bundleTempDir))
.filter(Boolean)
Copy link
Collaborator

Choose a reason for hiding this comment

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

最好是写语义化一点的? 这个路径是可能为null是不? 那如果是null了是如何处理?

Copy link
Member Author

Choose a reason for hiding this comment

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

已增加注释说明


config.define['import.meta.env.VITE_CDN_DOMAIN'] = JSON.stringify(`./${cdnDir}`)
// 使用本地 CDN 时,强制设置CDN类型为 local
config.define['import.meta.env.VITE_CDN_TYPE'] = JSON.stringify('local')
Copy link
Collaborator

Choose a reason for hiding this comment

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

这里的强制修改env 可能引起麻烦, 理论上不应该回头去覆盖文件变量, 通过vite_xxx_xxx 然后回设置同一个文件的vite_xxx,算是引起一种耦合和数据流问题, 下游不应该重定义上游

* @param {string} filename - 文件名
* @returns {string} - 处理后的内容
*/
function transform(content, filename) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

不应该使用通用的名字代表特殊的内容处理,需要重命名函数

Copy link
Member Author

Choose a reason for hiding this comment

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

已修改成 replaceJsImportPaths 相对语义化的命名

// 使用自定义的copyPlugin替代直接调用copy
copyPlugin(targetFiles)
]
}
Copy link
Collaborator

@rhlin rhlin Mar 27, 2025

Choose a reason for hiding this comment

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

这个文件似乎有很多不同层次的插件, 逻辑分析的 文件系统操作的,请考虑拆成每个插件一个js文件,把不同层次的业务内容摘出去,比如默认的importmap文件要从最外层使用的地方配置默认文件

Copy link
Member Author

Choose a reason for hiding this comment

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

已提取 cdnCopyPlugincreateEnvReplacementPlugin 两个 vite 插件到 vite-plugins 文件夹下

}

// 使用匹配到的结果
const match = npmmirrorMatch || unpkgMatch
Copy link
Collaborator

Choose a reason for hiding this comment

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

不建议内置这么具体选择的逻辑

Copy link
Member Author

Choose a reason for hiding this comment

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

合并两个正则表达式后已不存在

new RegExp(`^${originCdnPrefix}/?(?<packageName>.+?)@(?<versionDemand>[^/]+)(?<filePathInPackage>.*?)$`)
).groups
)
const npmmirrorMatch = url.match(
Copy link
Collaborator

@rhlin rhlin Mar 27, 2025

Choose a reason for hiding this comment

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

这两个正则应该可以合一的

Copy link
Member Author

Choose a reason for hiding this comment

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

已合并处理

@@ -1,8 +1,8 @@
{
"imports": {
"vue": "${VITE_CDN_DOMAIN}/vue${versionDelimiter}3.4.23${fileDelimiter}/dist/vue.runtime.esm-browser.js",
"vue": "${VITE_CDN_DOMAIN}/vue${versionDelimiter}3.4.23${fileDelimiter}/dist/vue.runtime.esm-browser.prod.js",
Copy link
Collaborator

Choose a reason for hiding this comment

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

当时是画布和预览使用开发模式下的vue方便调试问题(获得更多的warning警告信息)

Copy link
Member Author

Choose a reason for hiding this comment

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

已修改成 vue.runtime.esm-browser.js

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 (1)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1)

86-176: Consider a streaming approach for large directories
Reading all file contents into memory can be inefficient for large files or directories. A streaming or chunked copy mechanism might help reduce memory usage and improve performance.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c363ae9 and a58e045.

📒 Files selected for processing (2)
  • designer-demo/package.json (2 hunks)
  • packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • designer-demo/package.json
🧰 Additional context used
🧬 Code Definitions (1)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (4)
packages/build/vite-config/src/default-config.js (2)
  • logger (168-168)
  • config (146-146)
packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js (13)
  • content (85-85)
  • content (156-156)
  • result (11-11)
  • version (75-75)
  • version (151-151)
  • fs (7-7)
  • src (77-77)
  • src (152-152)
  • isFolder (76-76)
  • sourceExist (78-78)
  • sourceExist (153-153)
  • stat (81-81)
  • packageNeedToInstall (209-218)
packages/build/vite-config/src/localCdnFile/copyBundleDeps.js (4)
  • result (16-16)
  • fs (13-13)
  • cdnFiles (54-56)
  • packageNeedToInstall (58-58)
packages/build/vite-config/src/localCdnFile/copyImportMap.js (1)
  • packageNeedToInstall (35-37)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: push-check
🔇 Additional comments (3)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (3)

31-31: Rename the transform function to clarify its specialized role
The function name "transform" is too generic; a more precise name would help clarify its intent and usage.


81-81: Reevaluate forcibly setting environment variables
Forcibly modifying environment variables here might introduce data flow and coupling issues.


319-389: Split multiple plugins into separate modules
This file contains several plugin definitions and file-system operations at different abstraction levels. Consider refactoring into smaller modules for better maintainability.

Comment on lines +235 to +72
const compareIsSameVersion = (versionOrigin, versionTarget) => {
if (versionOrigin === versionTarget) {
return true
}

if (versionOrigin.startsWith('^')) {
// 如果源版本号是 ^ 开头,则只比较第一个数字是否相同
return versionOrigin.slice(1, 2) === versionTarget.slice(0, 1)
}

if (versionOrigin.startsWith('~')) {
// 如果源版本号是 ~ 开头,则只比较前两个数字是否相同
return versionOrigin.slice(1, 3) === versionTarget.slice(0, 3)
}

return false
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify partial matching in compareIsSameVersion
The handling for ^ and ~ only checks the first digit(s) rather than fully applying semver rules. This may lead to incorrect matches or missed breaking changes.


🌐 Web query:

Are partial matches of ^ or ~ sufficient for semver compliance?

💡 Result:

Semantic Versioning (SemVer) compliance for partial matches using ^ (caret) and ~ (tilde) operators depends on their alignment with SemVer rules:

Caret (^) Ranges

  • Full syntax: ^1.2.3 expands to >=1.2.3 <2.0.0, allowing minor and patch updates but blocking major breaks[2][3].
  • Partial syntax:
    • ^1.2>=1.2.0 <2.0.0 (equivalent to full major.minor.patch)[2].
    • ^1>=1.0.0 <2.0.0 (same as ^1.0.0)[3].
  • Compliance: Valid for SemVer compliance if the toolchain interprets partial versions correctly. However, ^0.2.3 is treated as >=0.2.3 <0.3.0 due to 0.x.x versions’ instability, effectively pinning to the exact 0.2.x series[2][5].

Tilde (~) Ranges

  • Full syntax: ~1.2.3>=1.2.3 <1.3.0, allowing only patch updates[2][3].
  • Partial syntax:
    • ~1.2>=1.2.0 <1.3.0 (auto-fills missing .patch as .0)[2].
    • ~1>=1.0.0 <2.0.0 (ambiguous; some tools default to minor increments)[3].
  • Compliance: Valid when targeting specific minor versions but risky for partial ranges like ~1, which might permit unintended minor changes[3].

Key Considerations

  1. Zero Major Versions: 0.x.x versions bypass SemVer’s stability guarantees. ^0.2.3 allows no changes, as 0.x implies instability[2][5].
  2. Tool Behavior: Not all tools handle partial ranges identically. For example:
    • npm treats ~1 as >=1.0.0 <2.0.0 (matching carets)[5].
    • Rust’s cargo flags partial ranges as potentially error-prone in unstable contexts[4].
  3. Build Metadata: Versions differing only in build metadata (e.g., 1.0.0+build) are treated as equal, but ranges exclude them unless explicitly allowed[5][7].

Recommendations for Compliance

  • Use exact ranges (e.g., ~1.2.3) for precise control.
  • Avoid 0.x.x ranges where possible, as they undermine SemVer’s assumptions.
  • Validate tool-specific interpretations to avoid unintended upgrades[4][6].

In summary, partial ^/~ ranges are SemVer-compliant if they align with intended versioning policies, but their safety depends on precise syntax and toolchain behavior.

Citations:


Action: Revise SemVer Handling in compareIsSameVersion

The current implementation only compares the first digit(s) for versions prefixed with ^ or ~. While partial matching can be acceptable if your toolchain interprets them as full ranges (e.g., treating ^1.2.3 as >=1.2.3 <2.0.0), this approach can miss critical semver nuances—especially for versions with major version zero or when only partial version strings are provided. To avoid unexpected mismatches or accidental breaking changes, consider one of the following:

  • Use a Dedicated SemVer Library: Adopt a semantic version parser (like the semver npm package) to ensure comparisons fully adhere to semver rules.
  • Document and Align: If you intend to keep the current approach, document its behavior clearly and verify that it aligns with your project's versioning policy.

Location: packages/build/vite-config/src/localCdnFile/localCdnPlugin.js, Lines 235-251

@chilingling chilingling force-pushed the feat/localCDNPlugin branch from a58e045 to 4338a95 Compare April 2, 2025 08:09
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

♻️ Duplicate comments (1)
packages/canvas/DesignCanvas/src/importMap.ts (1)

4-24: ⚠️ Potential issue

Add error handling for missing package configurations.

The getImportUrl function does not handle the case when a package is not found in either the custom import map or the importMapConfig. This could lead to undefined being returned, potentially causing runtime errors.

Add error handling for missing packages:

const getImportUrl = (pkgName: string) => {
  // 自定义的 importMap
  const customImportMap = getMergeMeta('engine.config')?.importMap
  const { VITE_CDN_TYPE, VITE_CDN_DOMAIN } = useEnv()
  const versionDelimiter = VITE_CDN_TYPE === 'npmmirror' ? '/' : '@'
  const fileDelimiter = VITE_CDN_TYPE === 'npmmirror' ? '/files' : ''

  if (customImportMap?.imports?.[pkgName]) {
    return customImportMap.imports[pkgName]
      .replace('${VITE_CDN_DOMAIN}', VITE_CDN_DOMAIN)
      .replace('${versionDelimiter}', versionDelimiter)
      .replace('${fileDelimiter}', fileDelimiter)
  }

  if (importMapConfig.imports[pkgName]) {
    return importMapConfig.imports[pkgName]
      .replace('${VITE_CDN_DOMAIN}', VITE_CDN_DOMAIN)
      .replace('${versionDelimiter}', versionDelimiter)
      .replace('${fileDelimiter}', fileDelimiter)
  }
+ 
+  console.warn(`Package ${pkgName} not found in import maps`)
+  return `${VITE_CDN_DOMAIN}/${pkgName}@latest`
}
🧹 Nitpick comments (6)
packages/canvas/DesignCanvas/src/importMap.ts (2)

30-34: Review the TODO comment for cleanup.

There's a TODO comment indicating that some code should be deleted. Consider addressing this or provide a clearer comment about when and why this code will be removed.


40-46: Address TODO comment and consider documenting dependency requirements.

The TODO comment indicates future deletion. Since this code handles dependencies that aren't supported in the material protocol, consider documenting these requirements or creating an issue to properly address them in the future.

packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1)

214-215: Remove commented-out code.

Leaving the old line in comment form can lead to confusion. If it's no longer needed, remove it to keep the codebase clean.

- // const srcPaths = (Array.isArray(src) ? src : [src]).map(item => path.resolve(process.cwd(), item))
designer-demo/tests/localCdnBundleDeps.test.js (1)

37-38: Consider testing with consistent CDN domains.

You're using registry.npmmirror.com for element-plus but unpkg.com for TinyVue. Line 69 sets VITE_CDN_DOMAIN to 'https://unpkg.com', so only matching URLs will be localized.

For more comprehensive testing, consider either:

  1. Using the same CDN domain for all test components, or
  2. Adding a separate test case with a different VITE_CDN_DOMAIN value to verify multiple domains can be handled.
docs/solutions/local-cdn.md (2)

192-192: Unclear explanation of JavaScript path replacement.

The explanation of path replacement transformation could be more detailed to help users understand exactly what transformations are applied and why they are necessary.

Consider adding an example to clarify the JavaScript path replacement transformation:

- import push from './push'
+ import push from './push.js'

This would make it clearer that the transformation adds file extensions for proper browser imports.


203-207: Consider adding more specific information about size impacts.

The note about increased build output size could be more helpful with specific information about typical size increases or strategies to manage them.

Consider adding more specific guidance about size impacts, such as:

  • Approximate size increase percentage for typical applications
  • Recommendations for which resources to prioritize for local CDN
  • Whether gzip/compression helps mitigate the size impact
  • Any build flags or options to optimize the size
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a58e045 and 4338a95.

📒 Files selected for processing (30)
  • .gitignore (1 hunks)
  • designer-demo/env/.env.alpha (1 hunks)
  • designer-demo/env/.env.development (0 hunks)
  • designer-demo/package.json (2 hunks)
  • designer-demo/public/mock/bundle.json (1 hunks)
  • designer-demo/tests/localCdnBasic.test.js (1 hunks)
  • designer-demo/tests/localCdnBundleDeps.test.js (1 hunks)
  • designer-demo/tests/localCdnCustomConfig.test.js (1 hunks)
  • designer-demo/tests/utils/envHelpers.js (1 hunks)
  • designer-demo/vitest.config.js (1 hunks)
  • docs/catalog.json (1 hunks)
  • docs/solutions/local-cdn.md (1 hunks)
  • packages/build/vite-config/package.json (1 hunks)
  • packages/build/vite-config/src/default-config.js (2 hunks)
  • packages/build/vite-config/src/localCdnFile/copyBundleDeps.js (2 hunks)
  • packages/build/vite-config/src/localCdnFile/copyImportMap.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/index.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js (2 hunks)
  • packages/build/vite-config/src/localCdnFile/replaceImportPath.mjs (2 hunks)
  • packages/canvas/DesignCanvas/src/DesignCanvas.vue (1 hunks)
  • packages/canvas/DesignCanvas/src/importMap.ts (2 hunks)
  • packages/common/js/importMap/import-map.json (2 hunks)
  • packages/common/js/importMap/index.js (1 hunks)
  • packages/common/package.json (1 hunks)
  • packages/common/vite.config.ts (1 hunks)
  • packages/design-core/package.json (1 hunks)
  • packages/design-core/src/preview/src/preview/Preview.vue (1 hunks)
  • packages/design-core/src/preview/src/preview/importMap.js (1 hunks)
  • packages/design-core/vite.config.js (2 hunks)
💤 Files with no reviewable changes (1)
  • designer-demo/env/.env.development
🚧 Files skipped from review as they are similar to previous changes (22)
  • packages/build/vite-config/src/localCdnFile/index.js
  • packages/common/js/importMap/index.js
  • packages/build/vite-config/src/localCdnFile/copyImportMap.js
  • designer-demo/vitest.config.js
  • packages/design-core/package.json
  • packages/build/vite-config/package.json
  • packages/design-core/src/preview/src/preview/importMap.js
  • packages/canvas/DesignCanvas/src/DesignCanvas.vue
  • packages/build/vite-config/src/localCdnFile/replaceImportPath.mjs
  • packages/common/package.json
  • packages/build/vite-config/src/default-config.js
  • designer-demo/env/.env.alpha
  • packages/build/vite-config/src/localCdnFile/copyBundleDeps.js
  • packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js
  • packages/design-core/vite.config.js
  • docs/catalog.json
  • designer-demo/package.json
  • .gitignore
  • designer-demo/tests/localCdnBasic.test.js
  • packages/design-core/src/preview/src/preview/Preview.vue
  • designer-demo/tests/utils/envHelpers.js
  • packages/common/js/importMap/import-map.json
🧰 Additional context used
🧠 Learnings (1)
packages/canvas/DesignCanvas/src/importMap.ts (1)
Learnt from: yy-wow
PR: opentiny/tiny-engine#940
File: packages/canvas/DesignCanvas/src/importMap.js:51-51
Timestamp: 2025-04-01T02:05:43.045Z
Learning: The `getImportMapData` function in `packages/canvas/DesignCanvas/src/importMap.js` has default parameter handling that initializes `canvasDeps` with empty arrays for `scripts` and `styles`, making additional null checks unnecessary.
🧬 Code Definitions (3)
designer-demo/tests/localCdnCustomConfig.test.js (3)
designer-demo/tests/localCdnBasic.test.js (5)
  • __dirname (13-13)
  • projectRoot (14-14)
  • envAlphaPath (17-17)
  • distDir (15-15)
  • localCdnDir (16-16)
designer-demo/tests/localCdnBundleDeps.test.js (4)
  • __dirname (13-13)
  • projectRoot (14-14)
  • envAlphaPath (17-17)
  • distDir (18-18)
designer-demo/tests/utils/envHelpers.js (5)
  • backupPath (53-53)
  • backupPath (64-64)
  • backupEnvFile (51-56)
  • ensureEnvVarEnabled (12-27)
  • restoreEnvFile (63-69)
designer-demo/tests/localCdnBundleDeps.test.js (3)
designer-demo/tests/localCdnBasic.test.js (4)
  • __dirname (13-13)
  • projectRoot (14-14)
  • envAlphaPath (17-17)
  • distDir (15-15)
designer-demo/tests/localCdnCustomConfig.test.js (4)
  • __dirname (13-13)
  • projectRoot (14-14)
  • envAlphaPath (16-16)
  • distDir (18-18)
designer-demo/tests/utils/envHelpers.js (4)
  • backupEnvFile (51-56)
  • updateCdnDomain (35-44)
  • ensureEnvVarEnabled (12-27)
  • restoreEnvFile (63-69)
packages/canvas/DesignCanvas/src/importMap.ts (2)
packages/design-core/src/preview/src/preview/importMap.js (2)
  • versionDelimiter (22-22)
  • fileDelimiter (23-23)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1)
  • importMapConfig (336-336)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: push-check
🔇 Additional comments (30)
packages/canvas/DesignCanvas/src/importMap.ts (4)

1-2: Good modularization by importing the importMapConfig from a common package.

The code now leverages a centralized import map configuration from the common package, which promotes reusability and consistency across the codebase.


26-26: Improved function signature by removing unnecessary parameter.

The removal of the overrideVersions parameter simplifies the function signature and reduces complexity. This change is good for maintainability.


36-36: Verify the empty importStyles array.

The importStyles array is now empty. Confirm if this is intentional and won't affect style loading in the application.


59-60: Good refactoring to use the dynamic import URL generation.

Replacing hardcoded URLs with calls to getImportUrl improves maintainability and consistency. The code is now more flexible to accommodate different CDN configurations.

packages/common/vite.config.ts (2)

20-20: Properly importing new dependency.

The addition of the viteStaticCopy plugin from 'vite-plugin-static-copy' is key to fixing the local import map issue. This ensures the import-map.json file will be available where needed.


28-41:

❓ Verification inconclusive

Good refactoring of plugins configuration with clear purpose.

The restructured plugins array with each plugin on its own line improves readability. The added comment at line 32 clearly explains the purpose of the viteStaticCopy plugin - to copy the import-map.json file to the build output for other build plugins to use.

This change directly addresses the PR objective of fixing the local import map functionality by ensuring the import map file is properly copied to the output directory.


🏁 Script executed:

#!/bin/bash
# Verify that the source import-map.json file exists and has content
fd -t f "importMap/import-map.json" packages/common/js/ | xargs cat

Length of output: 67


Attention: Verify Import Map File Existence

The refactored Vite plugins configuration looks clear and well-organized, and the inline comment at line 32 effectively explains the plugin's purpose. This change aligns with the PR objective to fix the local import map functionality by copying the import-map.json file to the build output.

However, the shell script check for the existence and content of the import map file produced no output. Please manually verify that the file exists at the expected path (packages/common/js/importMap/import-map.json) and contains the necessary data.

packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (6)

31-37: Use a more descriptive function name.

This function handles specific Babel-based import path transformations, yet it is named generically as transform. Previous feedback advised renaming generic functions to better reflect the specialized logic.


49-51: Ensure placeholders are properly defined.

Variables ${versionDelimiter} and ${fileDelimiter} are referenced, but they are not declared anywhere in this file. Verify that they are indeed set elsewhere or passed in properly.


77-82: Reconsider forcible environment variable overrides.

Forcibly setting import.meta.env.VITE_CDN_DOMAIN and import.meta.env.VITE_CDN_TYPE might cause unintended coupling and confusion for downstream code.


235-251: Revise partial SemVer comparisons.

Using only the first or first two digits to match ^ or ~ dependencies can cause unmanaged breaking changes or mismatches. Consider a robust SemVer strategy.


339-341: Handle missing import-map.json gracefully.

Reading this JSON file without checking if it exists or is valid can lead to runtime errors. Ensure robust error handling before parsing.


319-389: Split this file into smaller, separate plugins.

This file defines multiple plugin layers (env replacement, package installation, file copying). Prior feedback suggests extracting each into its own file or layer to reduce complexity.

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

10167-10168: Integrate the new "destructuring" property for module imports.
The addition of "destructuring": true in the npm configuration for TinyGridColumn indicates that the component may now support destructured imports. Please confirm that all consuming modules and the local import map functionality correctly handle this new property. Also, since tests and documentation updates are pending in this PR, ensure that the new behavior is adequately covered in future commits or issues.

designer-demo/tests/localCdnBundleDeps.test.js (7)

9-10: Node.js import pattern looks good.

The use of node: prefix for core Node.js modules follows best practices for explicit protocol imports.


29-32: Good test setup for TinyVue component.

The test data correctly includes both script and CSS CDN URLs for TinyVue which is necessary for comprehensive testing of the localization feature.


69-72: Good environment setup for test isolation.

The code correctly updates the CDN domain and enables the local bundle dependency feature, ensuring proper test isolation.


107-108: Correct assertion for CDN URL replacement.

The test properly verifies that TinyVue URLs have been replaced with local paths.


110-114: Good negative test for non-matching CDN domains.

The test correctly verifies that element-plus URLs remain unchanged since they don't match the configured CDN domain.


128-132: File existence checks look good.

The test correctly verifies the existence of both JS and CSS files for TinyVue in the localized CDN directory.


134-139: Good negative test for excluded packages.

The test correctly verifies that element-plus is not copied to the local CDN directory, which is consistent with the expected behavior based on CDN domain filtering.

designer-demo/tests/localCdnCustomConfig.test.js (6)

71-87: Good custom configuration for localCdnConfig.

The test correctly adds a custom configuration for Vue to test the local CDN feature with a specific version and file path.


94-96: Ensure proper enabling of environment variables.

The code correctly ensures that both VITE_LOCAL_IMPORT_MAPS and VITE_LOCAL_BUNDLE_DEPS are enabled for the test.


126-149: Good verification of custom importMap configuration.

The test properly checks that Vue is correctly copied to the local CDN directory and verifies the existence of the expected files.


150-157: Thorough verification of copied files.

The test correctly verifies that multiple files were copied to the dist directory, confirming the entire directory was processed as specified in the custom configuration.


159-172: Environment variable replacement test looks good.

The second test properly verifies that the VITE_CDN_DOMAIN environment variable is correctly set to the local path in the generated JS files.


23-48:

❓ Verification inconclusive

Update registry file function uses risky regex replacement.

The function uses regex to modify the registry.js file. While functional, regex-based file modifications can be brittle if the file structure changes.


🏁 Script executed:

#!/bin/bash
# Check the structure of registry.js to ensure the regex pattern is still applicable

# Identify the location of the config object
grep -n "config: {" $(find . -name "registry.js" | head -n 1)

# Check for any existing importMap in registry.js
grep -n "importMap" $(find . -name "registry.js" | head -n 1)

Length of output: 179


Below is a new script to display the contents of the registry file (around the config object and any importMap) so we can better assess whether the regex replacement in the updateRegistryFile function is safe given the file’s structure.

Please run the following shell script to gather more detailed context:


🏁 Script executed:

#!/bin/bash
FILE=$(find . -name "registry.js" | head -n 1)
if [ -z "$FILE" ]; then 
  echo "registry.js not found in repository!"
else 
  echo "Found registry.js at: $FILE"
  echo "---------- Content around 'config:' object ----------"
  grep -n -C3 "config: {" "$FILE"
  echo "---------- Content around 'importMap' (if any) ----------"
  grep -n -C3 "importMap" "$FILE"
fi

Length of output: 585


Action Required: Verify Registry File Structure for Regex Update

While the proposed regex-based change in the updateRegistryFile function may work under expected conditions, our automated checks did not return output confirming the existence or exact structure of the config: object in designer-demo/registry.js. This absence of evidence makes it hard to validate that the regex correctly matches the intended configuration block.

  • Ensure that the registry.js file indeed contains a config: block formatted as expected.
  • Confirm there is no existing importMap that the script might inadvertently override.
  • Consider revising the approach to use a more robust parsing method (e.g., AST or JSON parsing, if applicable) to avoid potential brittleness as the file structure evolves.

Please manually verify the file’s content and structure before merging this change.

docs/solutions/local-cdn.md (4)

15-27: Clear instructions for enabling local CDN functionality.

The documentation provides clear and concise steps for enabling the local CDN feature through environment variables.


45-50: Well-structured configuration options table.

The documentation includes a well-formatted table that clearly presents the available configuration options for the local CDN feature.


101-103: Questionable configuration for @opentiny/vue-runtime.

The /dist3/ path for @opentiny/vue-runtime may need further explanation or revision as it was previously questioned in a review.

As noted in a previous review comment, this configuration for @opentiny/vue-runtime pointing to /dist3/ seems unusual and might not be transparent to users.


131-132: Important note about CDN domain matching.

The documentation correctly emphasizes the importance of matching the CDN domain prefix in the bundle.json file with the VITE_CDN_DOMAIN environment variable.

@chilingling chilingling force-pushed the feat/localCDNPlugin branch from 4338a95 to 55bf74c Compare April 8, 2025 01:11
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: 5

♻️ Duplicate comments (2)
docs/solutions/local-cdn.md (1)

102-102: Check if /dist3/ path is necessary for @opentiny/vue-runtime

The configuration specifies filePathInPackage: '/dist3/' for @opentiny/vue-runtime. This seems unusual since a previous comment suggested that "dist3 is supposed to be transparent". Consider whether this explicit path configuration is actually necessary.

#!/bin/bash
# Verify the structure of @opentiny/vue-runtime package to determine if /dist3/ is required

# Check if the package is installed
if [ -d "node_modules/@opentiny/vue-runtime" ]; then
  echo "Package exists, listing directory structure:"
  ls -la node_modules/@opentiny/vue-runtime
  
  # Check if dist3 directory exists
  if [ -d "node_modules/@opentiny/vue-runtime/dist3" ]; then
    echo "dist3 directory exists, listing content:"
    ls -la node_modules/@opentiny/vue-runtime/dist3
  else
    echo "dist3 directory does not exist"
  fi
else
  echo "Package @opentiny/vue-runtime is not installed"
fi
packages/canvas/DesignCanvas/src/importMap.ts (1)

4-24: ⚠️ Potential issue

Add error handling for missing packages

The getImportUrl function does not handle the case when a package is not found in either the custom import map or the importMapConfig. This could lead to undefined being returned, potentially causing runtime errors.

Add error handling for missing packages:

const getImportUrl = (pkgName: string) => {
  // 自定义的 importMap
  const customImportMap = getMergeMeta('engine.config')?.importMap
  const { VITE_CDN_TYPE, VITE_CDN_DOMAIN } = useEnv()
  const versionDelimiter = VITE_CDN_TYPE === 'npmmirror' ? '/' : '@'
  const fileDelimiter = VITE_CDN_TYPE === 'npmmirror' ? '/files' : ''

  if (customImportMap?.imports?.[pkgName]) {
    return customImportMap.imports[pkgName]
      .replace('${VITE_CDN_DOMAIN}', VITE_CDN_DOMAIN)
      .replace('${versionDelimiter}', versionDelimiter)
      .replace('${fileDelimiter}', fileDelimiter)
  }

  if (importMapConfig.imports[pkgName]) {
    return importMapConfig.imports[pkgName]
      .replace('${VITE_CDN_DOMAIN}', VITE_CDN_DOMAIN)
      .replace('${versionDelimiter}', versionDelimiter)
      .replace('${fileDelimiter}', fileDelimiter)
  }
+ 
+  console.warn(`Package ${pkgName} not found in import maps`)
+  return `${VITE_CDN_DOMAIN}/${pkgName}@latest`
}
🧹 Nitpick comments (13)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (4)

8-8: Prefer a consistent logger reference over the bare console

You're assigning const logger = console at line 8, but in other places (e.g. line 33) you're directly passing console to babelReplaceImportPathWithCertainFileName. For consistency and future flexibility (e.g. switching to a more robust logging library), consider using the logger variable throughout.

-const result = babelReplaceImportPathWithCertainFileName(content, filename, console)
+const result = babelReplaceImportPathWithCertainFileName(content, filename, logger)

63-84: Check for potential environment variable overrides

The createEnvReplacementPlugin function (lines 68–84) forcefully sets certain environment variables (VITE_CDN_DOMAIN and VITE_CDN_TYPE to 'local'). If a user wants a different local domain or environment variable configuration, this override may cause confusion. Provide an option or parameter to opt out of these forced overrides.


178-227: Evaluate plugin layering and responsibilities

The copyPlugin function (lines 186–227) performs multiple tasks: reading config, iterating directories, and copying files. Meanwhile, the copyFileOrDirectory logic is quite extensive. Consider moving the copy logic into a separate utility, making copyPlugin focus on plugin lifecycle events. This adheres to the single-responsibility principle and can simplify testing.


253-317: Consider alternative bundle detection for installed packages

Within getCdnPathNpmInfo, there’s a check for isSameVersion and sourceExist. If the package version differs slightly, the code forcibly references the tempDir. This may lead to re-downloading or ignoring local dependencies that are compatible. If partial matches are acceptable, you can incorporate a logic to handle minor patch differences.

designer-demo/tests/localCdnBundleDeps.test.js (3)

49-75: Potential test brittleness due to forced environment modifications

In beforeAll, the test forcibly updates .env.alpha, enabling local CDN behavior. If other tests rely on different environment settings, they might break. You could isolate these environment changes (e.g., by using a separate .env for tests) or mock environment variables without writing to actual files.


97-115: Clarify test expectations for non-localized dependencies

The first test confirms that TinyVue packages are localized and that Element Plus remains on the remote registry. This is good for verifying selective localization. Consider referencing a comment or test name clarifying that the second dependency is intentionally left remote.


117-139: Extend coverage to negative or edge cases

The second test verifies that certain assets (Element Plus) are not copied to the local directory. You might also consider testing edge cases (e.g., an empty packages array, older versions, or partial version matches) for stronger confidence that the local CDN logic handles all scenarios.

designer-demo/tests/localCdnCustomConfig.test.js (1)

64-87: Ensure minimal intrusion to default config

The code forcibly injects localCdnConfig into the existing vite.config.js. If users have custom hooks or additional properties in baseConfig, they might be overridden. Provide an option to merge or selectively override properties.

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

30-35: Consider cleaning up TODO comments and uncommented code

The code contains a TODO comment and commented-out code for element-plus. These should either be addressed or removed to improve code clarity.

-  // TODO: 删除
   '@opentiny/vue': getImportUrl('@opentiny/vue'),
   '@opentiny/vue-icon': getImportUrl('@opentiny/vue-icon'),
-  // 'element-plus': getImportUrl('element-plus'),
   '@opentiny/tiny-engine-builtin-component': getImportUrl('@opentiny/tiny-engine-builtin-component')

40-41: Remove unnecessary TODO comment

The TODO: 删除 comment should be addressed or removed for code clarity.

-// TODO: 删除
designer-demo/tests/localCdnBasic.test.js (3)

58-60: Make file path checks more robust

The test is checking for a specific file path that might change with package versions. Consider making this check more resilient to version changes.

- const indexJsExists = fs.existsSync(path.resolve(localCdnDir, '@vue', devToolsDirs, 'lib/esm/index.js'))
+ // Check if any index.js file exists in the package directory structure
+ const packageDir = path.resolve(localCdnDir, '@vue', devToolsDirs)
+ const indexJsExists = !!execSync(`find ${packageDir} -name "index.js" | head -1`, { encoding: 'utf-8' }).trim()

69-71: Make file path checks more flexible

The test is checking for a specific Vue runtime file path that might change with Vue versions. Consider implementing a more flexible approach that can accommodate different file structures.

- const vueProdDist = path.resolve(localCdnDir, runtimeDirs, 'dist/vue.runtime.esm-browser.prod.js')
- 
- expect(fs.existsSync(vueProdDist)).toBe(true)
+ // Check if any Vue runtime file exists
+ const vueDir = path.resolve(localCdnDir, runtimeDirs, 'dist')
+ const vueRuntimeExists = fs.readdirSync(vueDir)
+   .some(file => file.includes('vue.runtime') && file.endsWith('.js'))
+ 
+ expect(vueRuntimeExists).toBe(true)

74-87: Enhance environment variable replacement test

The current test only checks if './local-cdn-static' appears in the file content. Consider adding a more specific check to verify the environment variable is correctly replaced in the expected context.

it('应该修改环境变量将VITE_CDN_DOMAIN设置为./local-cdn-static', () => {
  // 检查构建后生成的JS文件中是否包含 VITE_CDN_DOMAIN: './local-cdn-static'
  const jsFiles = fs.readdirSync(path.resolve(distDir, 'assets'))
    .filter(file => file.endsWith('.js') && file.startsWith('preview-'))
    .map(file => path.resolve(distDir, 'assets', file))
  
  expect(jsFiles.length).toBeGreaterThan(0)
  
  // 检查文件内容
  const mainJsContent = fs.readFileSync(path.resolve(jsFiles[0]), 'utf-8')
  
  // 检查 VITE_CDN_DOMAIN 环境变量是否被替换为本地路径
- expect(mainJsContent).toContain('./local-cdn-static')
+ // More specifically check if the VITE_CDN_DOMAIN is set to local-cdn-static
+ const cdnDomainRegex = /VITE_CDN_DOMAIN\s*[:=]\s*['"]\.\/local-cdn-static['"]/
+ expect(cdnDomainRegex.test(mainJsContent)).toBe(true)
})
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4338a95 and 55bf74c.

📒 Files selected for processing (30)
  • .gitignore (1 hunks)
  • designer-demo/env/.env.alpha (1 hunks)
  • designer-demo/env/.env.development (0 hunks)
  • designer-demo/package.json (2 hunks)
  • designer-demo/public/mock/bundle.json (1 hunks)
  • designer-demo/tests/localCdnBasic.test.js (1 hunks)
  • designer-demo/tests/localCdnBundleDeps.test.js (1 hunks)
  • designer-demo/tests/localCdnCustomConfig.test.js (1 hunks)
  • designer-demo/tests/utils/envHelpers.js (1 hunks)
  • designer-demo/vitest.config.js (1 hunks)
  • docs/catalog.json (1 hunks)
  • docs/solutions/local-cdn.md (1 hunks)
  • packages/build/vite-config/package.json (1 hunks)
  • packages/build/vite-config/src/default-config.js (2 hunks)
  • packages/build/vite-config/src/localCdnFile/copyBundleDeps.js (2 hunks)
  • packages/build/vite-config/src/localCdnFile/copyImportMap.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/index.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js (2 hunks)
  • packages/build/vite-config/src/localCdnFile/replaceImportPath.mjs (2 hunks)
  • packages/canvas/DesignCanvas/src/DesignCanvas.vue (1 hunks)
  • packages/canvas/DesignCanvas/src/importMap.ts (2 hunks)
  • packages/common/js/importMap/import-map.json (2 hunks)
  • packages/common/js/importMap/index.js (1 hunks)
  • packages/common/package.json (1 hunks)
  • packages/common/vite.config.ts (1 hunks)
  • packages/design-core/package.json (1 hunks)
  • packages/design-core/src/preview/src/preview/Preview.vue (1 hunks)
  • packages/design-core/src/preview/src/preview/importMap.js (1 hunks)
  • packages/design-core/vite.config.js (2 hunks)
💤 Files with no reviewable changes (1)
  • designer-demo/env/.env.development
🚧 Files skipped from review as they are similar to previous changes (22)
  • packages/common/js/importMap/index.js
  • .gitignore
  • packages/build/vite-config/src/localCdnFile/index.js
  • packages/build/vite-config/package.json
  • packages/design-core/package.json
  • packages/design-core/src/preview/src/preview/importMap.js
  • packages/build/vite-config/src/localCdnFile/copyImportMap.js
  • packages/common/package.json
  • packages/build/vite-config/src/localCdnFile/replaceImportPath.mjs
  • packages/common/vite.config.ts
  • packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js
  • packages/build/vite-config/src/default-config.js
  • packages/canvas/DesignCanvas/src/DesignCanvas.vue
  • designer-demo/tests/utils/envHelpers.js
  • docs/catalog.json
  • packages/design-core/src/preview/src/preview/Preview.vue
  • packages/design-core/vite.config.js
  • designer-demo/env/.env.alpha
  • packages/build/vite-config/src/localCdnFile/copyBundleDeps.js
  • designer-demo/vitest.config.js
  • designer-demo/package.json
  • packages/common/js/importMap/import-map.json
🧰 Additional context used
🧬 Code Definitions (5)
designer-demo/tests/localCdnCustomConfig.test.js (3)
designer-demo/tests/localCdnBasic.test.js (5)
  • __dirname (13-13)
  • projectRoot (14-14)
  • envAlphaPath (17-17)
  • distDir (15-15)
  • localCdnDir (16-16)
designer-demo/tests/localCdnBundleDeps.test.js (4)
  • __dirname (13-13)
  • projectRoot (14-14)
  • envAlphaPath (17-17)
  • distDir (18-18)
designer-demo/tests/utils/envHelpers.js (5)
  • backupPath (53-53)
  • backupPath (64-64)
  • backupEnvFile (51-56)
  • ensureEnvVarEnabled (12-27)
  • restoreEnvFile (63-69)
designer-demo/tests/localCdnBundleDeps.test.js (3)
designer-demo/tests/localCdnBasic.test.js (4)
  • __dirname (13-13)
  • projectRoot (14-14)
  • envAlphaPath (17-17)
  • distDir (15-15)
designer-demo/tests/localCdnCustomConfig.test.js (4)
  • __dirname (13-13)
  • projectRoot (14-14)
  • envAlphaPath (16-16)
  • distDir (18-18)
designer-demo/tests/utils/envHelpers.js (4)
  • backupEnvFile (51-56)
  • updateCdnDomain (35-44)
  • ensureEnvVarEnabled (12-27)
  • restoreEnvFile (63-69)
packages/canvas/DesignCanvas/src/importMap.ts (4)
packages/register/src/common.ts (1)
  • getMergeMeta (179-181)
packages/register/src/hooks.ts (1)
  • useEnv (90-90)
packages/design-core/src/preview/src/preview/importMap.js (2)
  • versionDelimiter (22-22)
  • fileDelimiter (23-23)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1)
  • importMapConfig (336-336)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (5)
packages/build/vite-config/src/default-config.js (2)
  • logger (168-168)
  • config (146-146)
packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js (9)
  • content (85-85)
  • content (156-156)
  • result (11-11)
  • version (75-75)
  • version (151-151)
  • fs (7-7)
  • src (77-77)
  • src (152-152)
  • packageNeedToInstall (209-218)
packages/build/vite-config/src/localCdnFile/copyBundleDeps.js (4)
  • result (16-16)
  • fs (13-13)
  • cdnFiles (54-56)
  • packageNeedToInstall (58-58)
packages/build/vite-config/src/localCdnFile/copyImportMap.js (1)
  • packageNeedToInstall (35-37)
packages/build/vite-config/src/vite-plugins/installPackageTemporary.js (1)
  • installPackageTemporary (5-47)
designer-demo/tests/localCdnBasic.test.js (3)
designer-demo/tests/localCdnCustomConfig.test.js (4)
  • __dirname (13-13)
  • projectRoot (14-14)
  • distDir (18-18)
  • envAlphaPath (16-16)
designer-demo/tests/localCdnBundleDeps.test.js (4)
  • __dirname (13-13)
  • projectRoot (14-14)
  • distDir (18-18)
  • envAlphaPath (17-17)
designer-demo/tests/utils/envHelpers.js (3)
  • backupEnvFile (51-56)
  • ensureEnvVarEnabled (12-27)
  • restoreEnvFile (63-69)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: push-check
🔇 Additional comments (4)
designer-demo/public/mock/bundle.json (1)

10167-10168:

✅ Verification successful

Verify and Document the New Destructuring Import Setting

The addition of "destructuring": true within the npm object for TinyGridColumn is intended to enable more flexible or alternative import methods (e.g., destructured imports) for this component. Please verify that the local import map logic correctly interprets this new property, and ensure that all consuming code and test suites are updated accordingly. Additionally, update the related documentation to inform developers about this new configuration option and its intended behavior.


Below are new shell scripts to help verify that the new "destructuring": true property is correctly incorporated throughout the codebase. These scripts will search for the property in JSON files as well as scan for references to TinyGridColumn in the consuming code and test suites:

Please run these scripts and review the output to ensure that:

  • The "destructuring": true flag is present where expected (e.g., in the JSON bundle under the npm object for TinyGridColumn).
  • Code and test files referencing TinyGridColumn are correctly interpreting this new configuration option.

Once you've verified that the new property is handled properly in the local import map logic and in all relevant areas, the review comment can be addressed accordingly.



🏁 Script executed:

#!/bin/bash
# Search for the new "destructuring": true property in all files.
rg '"destructuring":\s*true' -n .

# Additionally, check for usage of "TinyGridColumn" to confirm that consuming code and tests are updated.
rg -i 'TinyGridColumn' -n .

Length of output: 19339


Action Required: Confirm Destructuring Import Behavior & Documentation Updates

The destructuring: true property for TinyGridColumn has been verified in the JSON bundles and appears across multiple test cases and consuming code (e.g., within various componentsMap files and the designer-demo bundle). In addition, documentation (such as in docs/solutions/local-cdn.md) already includes references to this setting.

Please ensure that:

  • The local import map logic correctly interprets the new destructuring property.
  • Consuming code (as evidenced in files like those in /packages/vue-generator/test/) is updated to work with destructured imports.
  • Documentation clearly explains the purpose and usage of the destructuring: true configuration.

Once you’ve confirmed that the import mapping logic fully supports this behavior during runtime, no further changes should be necessary.

packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1)

319-388: Validate plugin ordering and error handling

The main export localCdnPlugin (lines 330–387) returns an array of plugins: environment overrides, package installation, and file copying. If one plugin fails, ensure proper error handling is in place so that subsequent plugins don’t proceed in a broken state. Consider using apply: 'build' or other advanced plugin ordering features from Vite to ensure everything runs in the expected sequence.

Would you like a script or instructions to verify the plugin order or confirm that error handling is robust across all plugin invocations?

designer-demo/tests/localCdnCustomConfig.test.js (1)

159-172:

❓ Verification inconclusive

Verify environment variable substitution in final output

The test (lines 159–172) only checks one generated JS file (the first in the assets folder). In multi-entry builds, or if code-splitting is used, environment variables might appear in other files. Consider verifying each .js chunk or searching for VITE_CDN_DOMAIN usage across the entire dist folder.


🏁 Script executed:

#!/bin/bash
# Verify environment variable substitution in all built JS assets.
fd -t f -e js ' dist ' --exec grep -Hn "VITE_CDN_DOMAIN:" {}

Length of output: 62


Manual verification required for consistent environment variable substitution

The current test only checks the first generated JS file in dist/assets. However, the substitution for VITE_CDN_DOMAIN:"./local-cdn-static" might occur in additional files, especially in multi-entry or code-split builds. The automated verification script (searching for "VITE_CDN_DOMAIN:" across the dist directory) did not produce any output, which isn’t conclusive. Please manually verify that all relevant JS chunks include the correct replacement. For example, consider running a recursive search like:

grep -rHn 'VITE_CDN_DOMAIN:"./local-cdn-static"' dist

or updating the test to iterate over all .js files in the build folder.

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

36-36:

✅ Verification successful

Verify the implications of empty importStyles array

The importStyles array has been emptied. Ensure this doesn't negatively impact style loading in the application.


🏁 Script executed:

#!/bin/bash
# Check for references to importStyles in the codebase

echo "Searching for importStyles references:"
rg "importStyles" -A 3 -B 3

echo "Checking how styles are currently loaded in the application:"
rg "canvasDeps.styles" -A 3 -B 3

Length of output: 3717


Action Confirmed: Style Loading Works as Intended

The investigation confirms that the empty importStyles in packages/canvas/DesignCanvas/src/importMap.ts is intentional. It serves as the base value which is later merged with blockRequire.importStyles and canvasDeps.styles—as seen in the computation:

  • The merged array created via [...blockRequire.importStyles, ...canvasDeps.styles] ensures that styles are still loaded correctly.
  • Usage in DesignCanvas.vue via initCanvas(importMap, importStyles) further verifies that style loading relies on the computed importStyles.

No negative impact on style loading has been observed. Please ensure that integration tests continue to verify that all expected styles load correctly in different environments.

Comment on lines 25 to 37
/**
* 对文件内容进行转换处理
* @param {string} content - 文件内容
* @param {string} filename - 文件名
* @returns {string} - 处理后的内容
*/
function transform(content, filename) {
if (filename.endsWith('.js')) {
const result = babelReplaceImportPathWithCertainFileName(content, filename, console)
return result.code || content
}
return content
}
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 non-JS files or binary assets carefully

The transform function only applies Babel transformations to .js files. However, it still reads all files with utf-8 encoding (see lines 144, 162). If the directory contains binary files (e.g., images or fonts), reading them as text might corrupt them. Consider adding file-type checks and skipping transformation or using a different read/write method for non-text files.

Comment on lines 86 to 110
/**
* 复制文件或目录到目标路径
* @param {string} srcPath - 源文件/目录路径
* @param {string[]} destPaths - 目标路径数组
* @param {Set} copiedFiles - 已复制文件集合
* @param {string} outDir - 输出目录
*/
async function copyFileOrDirectory(srcPath, destPaths, copiedFiles, outDir) {
// 生成一个唯一标识,避免重复复制相同文件
const copyId = `${srcPath}:${destPaths.join(',')}`

if (copiedFiles.has(copyId)) {
logger.log(`[vite-cdn-copy-plugin]: Skipping already copied file: ${srcPath}`)
return
}

copiedFiles.add(copyId)

// 检查源文件是否存在
if (!fs.existsSync(srcPath)) {
logger.warn(`[vite-cdn-copy-plugin]: Source does not exist: ${srcPath}`)
return
}

const isDirectory = fs.statSync(srcPath).isDirectory()
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

Avoid reading entire directories into memory for all file types

The current approach to copying directories (lines 122–151) reads each file in UTF-8 mode and applies transformations unconditionally. This can be memory-heavy and potentially break for large binary files. Consider an I/O streaming approach or limit content transformation to known text-based files.

Comment on lines +229 to +72
/**
* 比较两个版本号是否相同
* @param {string} versionOrigin - 源版本号, 可能包含 ^ 或 ~ 开头
* @param {string} versionTarget - 目标版本号,来源于 package.json 的 version, 不能包含 ^ 或 ~ 开头
* @returns {boolean} - 是否相同
*/
const compareIsSameVersion = (versionOrigin, versionTarget) => {
if (versionOrigin === versionTarget) {
return true
}

if (versionOrigin.startsWith('^')) {
// 如果源版本号是 ^ 开头,则只比较第一个数字是否相同
return versionOrigin.slice(1, 2) === versionTarget.slice(0, 1)
}

if (versionOrigin.startsWith('~')) {
// 如果源版本号是 ~ 开头,则只比较前两个数字是否相同
return versionOrigin.slice(1, 3) === versionTarget.slice(0, 3)
}

return false
}
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

Enhance semantic version comparison

compareIsSameVersion (lines 235–251) only checks partial digits for ^ and ~ prefixes. This might lead to inaccuracies (e.g., ^1.2.3 vs. 1.10.0). Consider using a dedicated SemVer library or implement a more precise comparison to handle major/minor/patch more accurately.

Comment on lines +30 to +48
// 备份原始文件
fs.copyFileSync(registryPath, registryPath + '.bak')

const registryContent = fs.readFileSync(registryPath, 'utf-8')

// 向 config 对象添加 importMap
const updatedContent = registryContent.replace(
/config: {([^}]*)}/,
`config: {$1,
importMap: {
imports: {
'vue': "\${VITE_CDN_DOMAIN}/vue\${versionDelimiter}3.4.21\${fileDelimiter}/dist/vue.runtime.esm-browser.js"
}
}
}`
)

fs.writeFileSync(registryPath, updatedContent)
}
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

Use more robust detection or replacement logic in updateRegistryFile

Here, the registry file is patched with string replacements targeting config: { ... }. This can break if the user modifies the config structure or if the text config: { appears in a different context. Consider using a small parser or AST-based approach to reliably insert the importMap.

Comment on lines +19 to +45
describe('localCDN 功能测试', () => {
beforeAll(() => {
// 备份环境变量文件
backupEnvFile(envAlphaPath)

// 确保环境变量正确设置
let envContent = fs.readFileSync(envAlphaPath, 'utf-8')

// 确保关键环境变量已启用
envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_IMPORT_MAPS')
envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_DEPS')

// 写回更新后的环境变量
fs.writeFileSync(envAlphaPath, envContent)

// 执行构建
execSync('pnpm run build:alpha', {
cwd: projectRoot,
stdio: 'inherit'
})
})

// 测试结束后恢复原始环境变量
afterAll(() => {
restoreEnvFile(envAlphaPath)
})

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

Consider adding test verification for actual CDN functionality

The tests verify file creation and environment variable replacement, but don't validate if the local CDN actually works when the application runs. Consider adding a test that starts the application and checks if resources are correctly loaded from the local CDN.

You could add a test that:

  1. Starts a local server for the built application
  2. Makes an HTTP request to fetch an HTML page
  3. Parses the HTML to check if scripts point to local CDN paths
  4. Verifies that these local resources can be loaded

This would ensure end-to-end functionality of the local CDN feature.

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/build/vite-config/src/localCdnFile/localCdnPlugin.js (1)

56-72: Enhance semantic version comparison.
Partial matching with ^ and ~ is too simplistic and can lead to inaccurate results, for instance matching ^1.2 with 1.10.0. Consider using a dedicated semver library or implementing more precise comparison logic to align with SemVer best practices.

🧹 Nitpick comments (6)
packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js (2)

41-67: Consider formal error handling instead of ignoring.
The function extractPackageInfo is swallowing all errors inside a try/catch block with a simple // ignore. This can make it harder to diagnose certain issues, such as unexpected URL formats or regex failures. Consider at least logging the error or providing fallback handling to aid debugging.


79-80: Add a debug log when no match is found.
When extractPackageInfo returns null, the function simply returns null without any notice. A warning or debug log might help track down mismatches or typos in the URL paths, as these often cause confusion.

packages/build/vite-config/src/vite-plugins/createEnvReplacementPlugin.js (1)

6-22: Add safety checks for undefined base or cdnDir.
If either base or cdnDir is undefined, this plugin will produce a malformed environment variable string. Consider adding fallback handling or logging to avoid potential build errors.

packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js (3)

1-5: Imports look correct; consider using async fast-glob if large directories are expected.
Your import statements are standard, and using fs-extra provides robust file operations. However, since you’re copying entire directories, consider switching to the async version of fast-glob in case of large-scale scenarios to improve performance and prevent blocking the event loop.


20-21: Console-based logging is fine, but consider a configurable logger.
Using console is acceptable for quick visibility. If you need varying log levels or file/JSON outputs, a dedicated logging library may provide more flexibility.


58-70: Synchronous directory traversal may degrade performance.
Within large projects, fg.sync() can block. Consider using fg.async() or a streaming approach to avoid potential performance bottlenecks when copying numerous files.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 55bf74c and b7c548b.

📒 Files selected for processing (8)
  • docs/README.md (1 hunks)
  • packages/build/vite-config/src/localCdnFile/copyBundleDeps.js (2 hunks)
  • packages/build/vite-config/src/localCdnFile/copyImportMap.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js (3 hunks)
  • packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js (1 hunks)
  • packages/build/vite-config/src/vite-plugins/createEnvReplacementPlugin.js (1 hunks)
  • packages/common/js/importMap/import-map.json (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/README.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/build/vite-config/src/localCdnFile/copyImportMap.js
  • packages/build/vite-config/src/localCdnFile/copyBundleDeps.js
  • packages/common/js/importMap/import-map.json
🧰 Additional context used
🧬 Code Definitions (1)
packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js (1)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (3)
  • logger (8-8)
  • src (96-96)
  • dest (123-123)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: push-check
🔇 Additional comments (6)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1)

170-173: Double-check immediate return on empty dependencies.
When no CDN dependencies are found, the plugin chain is effectively bypassed. Verify that this early return does not skip any required fallback logic in other parts of the build process.

packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js (5)

6-18: Validate Babel transformation approach for large file counts.
The replaceJsImportPaths function is straightforward, but if many .js files are processed, Babel transformations may become time-consuming. Verify if an incremental or batched strategy better suits your performance requirements.


29-37: Skipping already-copied files is well-handled.
The copyId guard effectively prevents redundant copy operations, which is a neat optimization feature.


40-44: Early return on missing source is appropriate.
Logging a warning and skipping non-existent sources avoids errors downstream. This is a good pattern for robust file operations.


108-110: Error logging doesn’t halt the build; confirm desired behavior.
When a copy fails, you log an error but allow subsequent copies to proceed. This might be acceptable if partial success is desired, but if a complete build is critical, consider surfacing the error back to Vite to fail the build.


114-163: Build-time plugin approach is well-structured; overall logic looks sound.
Your Vite plugin runs the copy in writeBundle(), which is typically the right hook for build-time operations. Great job structuring it so that configuration and actual copying steps remain clearly separated and maintainable.

Comment on lines +88 to +105
} else {
// 如果是单个文件
logger.log(`[vite-cdn-copy-plugin]: Copying file: ${srcPath} -> ${fullDestPath}`)

let finalDestPath = path.join(fullDestPath, path.basename(srcPath))

// 确保目标文件的目录存在
await fs.ensureDir(path.dirname(finalDestPath))

// 读取文件内容
const content = await fs.readFile(srcPath, 'utf-8')

// 应用转换
const transformedContent = replaceJsImportPaths(content, srcPath)

// 写入转换后的内容
await fs.writeFile(finalDestPath, transformedContent)
}
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

Re-examine single-file copy path logic.
Currently, the final destination is always path.join(fullDestPath, path.basename(srcPath)). If a user configures their dest to be an exact file name (e.g., dest: 'someFile.js'), they might be surprised that the basename of srcPath is still appended. Allowing flexible file renaming or clarifying it in the docs could help avoid confusion.

@github-actions github-actions bot added the documentation Improvements or additions to documentation label Apr 8, 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

♻️ Duplicate comments (1)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1)

56-72: 🛠️ Refactor suggestion

Enhance semantic version comparison for more robust version handling.

The current implementation of compareIsSameVersion is overly simplistic for handling semantic versioning patterns:

  1. For ^ versions, it only checks the first digit, which fails for versions ≥10 (e.g., ^10.0.0 vs 10.2.0).
  2. For ~ versions, it only checks the first two digits, which doesn't properly implement semver's tilde behavior.

Consider using a dedicated semver library or implement a more accurate comparison that properly handles the full semver specification. For example:

-const compareIsSameVersion = (versionOrigin, versionTarget) => {
-  if (versionOrigin === versionTarget) {
-    return true
-  }
-
-  if (versionOrigin.startsWith('^')) {
-    // 如果源版本号是 ^ 开头,则只比较第一个数字是否相同
-    return versionOrigin.slice(1, 2) === versionTarget.slice(0, 1)
-  }
-
-  if (versionOrigin.startsWith('~')) {
-    // 如果源版本号是 ~ 开头,则只比较前两个数字是否相同
-    return versionOrigin.slice(1, 3) === versionTarget.slice(0, 3)
-  }
-
-  return false
-}
+const compareIsSameVersion = (versionOrigin, versionTarget) => {
+  if (versionOrigin === versionTarget) {
+    return true
+  }
+
+  // For caret ranges (^)
+  if (versionOrigin.startsWith('^')) {
+    const version = versionOrigin.slice(1)
+    const [major] = version.split('.')
+    const [targetMajor] = versionTarget.split('.')
+    
+    // For versions with major version 0, allow only exact patches
+    if (major === '0') {
+      const [, minor] = version.split('.')
+      const [, targetMinor] = versionTarget.split('.')
+      return major === targetMajor && minor === targetMinor
+    }
+    
+    // For regular versions, allow any minor/patch updates in the same major
+    return major === targetMajor
+  }
+
+  // For tilde ranges (~)
+  if (versionOrigin.startsWith('~')) {
+    const version = versionOrigin.slice(1)
+    const [major, minor] = version.split('.')
+    const [targetMajor, targetMinor] = versionTarget.split('.')
+    
+    // Allow patch-level changes if a minor is specified
+    return major === targetMajor && minor === targetMinor
+  }
+
+  return false
+}
🧹 Nitpick comments (2)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (2)

97-107: Add error logging for package.json parsing failures.

While you have a try-catch block for parsing package.json, you're silently ignoring any errors. It would be helpful to log these errors to assist with troubleshooting.

    try {
      const pkg = JSON.parse(fs.readFileSync(path.resolve(pkgFilePath)))
      isSameVersion = compareIsSameVersion(version, pkg.version)
    } catch (error) {
-      // ignore
+      logger.debug(`[local-cdn-plugin]: Failed to parse package.json for ${packageName}:`, error)
    }

151-204: Main plugin implementation looks good, with opportunities for improvement.

The localCdnPlugin function effectively manages local CDN dependencies by:

  1. Combining default and custom import map configurations
  2. Identifying packages that need installation
  3. Deduplicating copy operations
  4. Creating appropriate plugins for installation and copying

Consider adding more detailed error handling and progress logging, especially for the file copy operations that might fail due to permissions or disk space issues.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b7c548b and e9b75c4.

📒 Files selected for processing (7)
  • packages/build/vite-config/src/default-config.js (2 hunks)
  • packages/build/vite-config/src/localCdnFile/import-map.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1 hunks)
  • packages/build/vite-config/src/vite-plugins/createDynamicImportMapPlugin.js (1 hunks)
  • packages/common/js/importMap/index.js (1 hunks)
  • packages/common/vite.config.ts (1 hunks)
  • packages/design-core/vite.config.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/common/js/importMap/index.js
  • packages/design-core/vite.config.js
🧰 Additional context used
🧬 Code Definitions (3)
packages/build/vite-config/src/localCdnFile/import-map.js (1)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1)
  • importMapConfig (157-157)
packages/build/vite-config/src/vite-plugins/createDynamicImportMapPlugin.js (2)
packages/build/vite-config/src/localCdnFile/import-map.js (2)
  • importMapConfig (1-33)
  • importMapConfig (1-33)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1)
  • importMapConfig (157-157)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (3)
packages/build/vite-config/src/default-config.js (1)
  • logger (169-169)
packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js (2)
  • logger (20-20)
  • copyPlugin (122-162)
packages/build/vite-config/src/localCdnFile/import-map.js (2)
  • importMapConfig (1-33)
  • importMapConfig (1-33)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: push-check
🔇 Additional comments (6)
packages/common/vite.config.ts (1)

69-70: External modules configuration looks good.

You've added two patterns to the external array: /^@babel.*/ for babel-related packages and /^virtual:import-map$/ for the virtual import map module. This correctly prevents these modules from being bundled and aligns with the local CDN functionality you're implementing.

packages/build/vite-config/src/localCdnFile/import-map.js (1)

1-33: Well-structured module mapping for CDN dependencies.

The importMapConfig object provides a clear mapping of module names to their CDN URLs with appropriate placeholders for dynamic values. The structure is clean and organized.

packages/build/vite-config/src/vite-plugins/createDynamicImportMapPlugin.js (1)

10-36: Clean implementation of dynamic import map plugin.

The plugin correctly handles virtual module resolution and conditional content generation based on the isLocalImportMap flag. When local imports are used, it properly constructs the CDN domain and replaces placeholders with actual values.

packages/build/vite-config/src/default-config.js (3)

13-13: Import statements updated to support new local CDN approach

The changes replace copyPreviewImportMap with localCdnPlugin and add a new import for createDynamicImportMapPlugin, signaling a shift from a single-function approach to a more comprehensive plugin-based solution for local import maps.

Also applies to: 17-17


167-181: Improved local CDN implementation with enhanced plugin support

The new implementation properly initializes the local CDN plugin when isLocalImportMap is true, passing necessary configuration parameters. This is a good improvement over the previous approach as it:

  1. Adds helpful logging for initialization
  2. Accepts custom configuration via engineConfig.localCdnConfig
  3. Only adds plugins when they're available (cdnPlugins && cdnPlugins.length > 0)

This addresses the "localImportMap not works" issue mentioned in the PR title by replacing the previous implementation with a more robust solution.


183-189:

❓ Verification inconclusive

Added dynamic import map plugin to complement local CDN functionality

The addition of createDynamicImportMapPlugin is a good enhancement that complements the local CDN functionality. It's correctly configured with the base URL, CDN directory, and respects the isLocalImportMap flag.

However, I notice that this plugin is added unconditionally, regardless of whether isLocalImportMap is true or false. Since the plugin receives the isLocalImportMap parameter, it likely handles both cases internally, but it might be worth verifying if this is the intended behavior.


🏁 Script executed:

#!/bin/bash
# Let's check the implementation of createDynamicImportMapPlugin to understand how it uses isLocalImportMap
rg -A 5 "createDynamicImportMapPlugin" packages/build/vite-config/src/vite-plugins/

Length of output: 790


Dynamic Import Map Plugin Behavior Verification

The createDynamicImportMapPlugin function is correctly configured to accept an isLocalImportMap parameter (defaulting to false), and it is added to the plugins array unconditionally. This appears to be an intentional design, with the expectation that the plugin's internal logic will handle both cases (i.e., when isLocalImportMap is true or false).

  • Verified that the plugin function (in packages/build/vite-config/src/vite-plugins/createDynamicImportMapPlugin.js) accepts isLocalImportMap with a default of false.
  • No conditional guard was found in the plugin addition, implying the internal implementation should manage the flag appropriately.

Please double-check that the internal logic of the plugin behaves as expected when isLocalImportMap is false. If the intended behavior is to conditionally add the plugin based on this flag, a guard may need to be added. Otherwise, if the plugin internally differentiates behavior, this implementation is correct.

@hexqi hexqi added this to the v2.5.0 milestone Apr 16, 2025
@@ -62,7 +62,7 @@ export default defineConfig({
}
}
},
external: ['vue', 'monaco-editor', 'prettier', /@opentiny\/vue.*/, '@opentiny/tiny-engine-meta-register']
external: ['vue', 'monaco-editor', 'prettier', /@opentiny\/vue.*/, /^@opentiny\/tiny-engine*/]
Copy link
Collaborator

Choose a reason for hiding this comment

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

这个PR里为什么要把@opentiny/tiny-engine*依赖都排除呢

Copy link
Collaborator

Choose a reason for hiding this comment

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

不再二次打包吧

Copy link
Member Author

@chilingling chilingling Apr 23, 2025

Choose a reason for hiding this comment

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

因为 common 中使用了虚拟模块 virtual:import-map,在 design-core 打包的时候,无法提前得到真正的 import-map 模块内容。所以需要做一个 external。

Copy link
Collaborator

Choose a reason for hiding this comment

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

这里可能有个错漏 应该是 e.*

@@ -6,6 +6,7 @@ yarn.lock
pnpm-lock.yaml
lerna-debug.log
packages/design-core/bundle-deps
designer-demo/bundle-deps
Copy link
Collaborator

Choose a reason for hiding this comment

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

image
updateTemplate脚本可以更新下

)
const cdnFiles = extraBundleCdnLink(bundleFile, originCdnPrefix)
.map((url) => getCdnPathNpmInfoForSingleFile(url, originCdnPrefix, base, dir, false, bundleTempDir))
// 比如 url 前缀跟 originCdnPrefix 不匹配,或者 url 格式不正确 的场景有可能会导致匹配失败
Copy link
Collaborator

Choose a reason for hiding this comment

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

理论上不会出现前缀跟originCdnPrefix不匹配场景,可以再斟酌一下

Copy link
Member Author

Choose a reason for hiding this comment

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

用户有可能内部打包的物料和官方物料混合在一起:

{
"packages": [
  {
    "name": "自定打包的组件库",
    "package": "美好出行-xxxxx-component",
    "version": "3.20.0",
    "destructuring": true,
    "script": "https://192.168.1.10/xxxx.mjs",
    "css": "https://192.168.1.10/xxxx.css"
  },
  {
    "name": "TinyVue组件库",
    "package": "@opentiny/vue",
    "version": "3.20.0",
    "destructuring": true,
    "script": "https://registry.npmmirror.com/@opentiny/vue-runtime/~3.20/files/dist3/tiny-vue-pc.mjs",
    "css": "https://registry.npmmirror.com/@opentiny/vue-theme/~3.20/files/index.css"
  },
  {
    "name": "element-plus组件库",
    "package": "element-plus",
    "version": "2.4.2",
    "script": "https://registry.npmmirror.com/element-plus/2.4.2/files/dist/index.full.mjs",
    "css": "https://registry.npmmirror.com/element-plus/2.4.2/files/dist/index.css"
  }
]
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

copyBundleDeps.js L34已经判断果是要替换的前缀的了?

@@ -0,0 +1,33 @@
export const importMapConfig = {
Copy link
Collaborator

@rhlin rhlin Apr 22, 2025

Choose a reason for hiding this comment

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

这个文件暂时不明白为啥在build vite插件里面 这个应该属于业务内容,应该通过传参进来,与插件要解耦

Copy link
Member Author

Choose a reason for hiding this comment

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

背景:

  • 画布(@opentiny/tiny-engine-canvas) 需要 import-map
  • 页面预览 (preview) 需要 import-map
  • 上述两个地方依赖的 import-map 大致重合(vue、vue-i18n、物料后续会逐步解耦出去)

我们需要 import-map 本地化:所以我们拿到将上述两个地方的 import-map 依赖,将依赖提前打包到产物,然后让原来的 import-map 拿到本地化之后的域名

原来的方案:

  • 将 import-map.json 放置到 common,让画布和页面预览依赖读取。
  • import-map 的域名 ${VITE_CDN_DOMAIN} 保持字符串。
  • 本地化打包之后,改写环境变量 ${VITE_CDN_DOMAIN},在运行时将 ${VITE_CDN_DOMAIN} 字符串替换成环境变量的值。(就是插件包不感知是不是 localCDN,他们只关心环境变量的值)

新方案:
改动 commit: e9b75c4 (#1242)

改动原因:#1242 (comment)

截屏2025-04-23 21 38 11

方案改动点:

  • 如果不能改环境变量 ${VITE_CDN_DOMAIN} 的值,那么我们需要将真正的 localCDN 相对路径写入到真正的模块中。

使用虚拟模块的原因
如果不使用虚拟模块,无法在二开工程打包的时候,将真正的 localCDN 相对路径写入到真正的模块中。

使用虚拟模块后
如果开启了 CDN 本地化,则将替换后的内容该写成模块内容,再参与真正的打包。
如果没有开启 CDN 本地化,则将原样的内容写入模块内容,参与真正的打包。

如果有更好的方案,欢迎提出来。


const logger = console

// 默认的复制配置,这几个 package 需要复制整个目录,所以需要有默认配置进行复制整个目录
Copy link
Collaborator

@rhlin rhlin Apr 22, 2025

Choose a reason for hiding this comment

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

这个配置应该从应用层传参数进来 vite插件不要内置业务会比较好

Copy link
Member Author

Choose a reason for hiding this comment

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

当前 tiny-vue 物料还是内置的,需要解耦 tiny-vue 物料之后,再将此部分内置逻辑解耦出来。

* @param {string} versionTarget - 目标版本号,来源于 package.json 的 version, 不能包含 ^ 或 ~ 开头
* @returns {boolean} - 是否相同
*/
const compareIsSameVersion = (versionOrigin, versionTarget) => {
Copy link
Collaborator

@rhlin rhlin Apr 22, 2025

Choose a reason for hiding this comment

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

此处属于简单实现, 可以抽取单个文件为semver版本号字符串的相等compare,作为utils单独一个文件, 或者更建议看看有无三方库直接使用,把逻辑关注点分离

Copy link
Member Author

Choose a reason for hiding this comment

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

这里的相等比较其实不是严格的相等比较。
这里是为了避免重复下载依赖包做的比较,比如我的 cdn 指定版本为:^3.20.0
那么,只要我的 node_module 里面的 package.json 的版本是大版本号相同就可以。
如果指定版本为 ~3.20.0,那么只要我的 node_module 里面的 package.json 的版本是 3.20.x 就可以认为相同。
所以我理解不是严格比较的通用逻辑,后续如果其他插件或者业务也有类似的逻辑,可以抽取成一个通用的 utils

return versionOrigin.slice(1, 3) === versionTarget.slice(0, 3)
}

return false
Copy link
Collaborator

Choose a reason for hiding this comment

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

compare函数需要返回 0 1 或者-1
is函数返回true false,
需要修改一下函数名明确

Copy link
Member Author

Choose a reason for hiding this comment

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

这里没懂为什么 compare 函数需要返回 0、1 或者 -1,是哪里的规范吗?

Copy link
Collaborator

Choose a reason for hiding this comment

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

约定俗成

}
}

config.plugins.push(
Copy link
Collaborator

Choose a reason for hiding this comment

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

已经再包一层插件的话,插件应该一体化, L183-L88应该放到 168的if分支块内,甚至应该放到localCdnPlugin内

Copy link
Member Author

@chilingling chilingling Apr 23, 2025

Choose a reason for hiding this comment

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

这里不能放置到 168 行的 if 分支条件块内部。理由详见:#1242 (comment)

因为如果不是 localImportMap 了,也需映射虚拟模块。

return `export default ${newContent}`
}

return `export default ${JSON.stringify(importMapConfig)}`
Copy link
Collaborator

Choose a reason for hiding this comment

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

这里的两个条件分支的处理层级有点不一样, 待进一步分析

Copy link
Member Author

Choose a reason for hiding this comment

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

处理的层级不一样是什么意思?这个插件是决定终态的虚拟模块的内容的。

const versionDelimiter = VITE_CDN_TYPE === 'npmmirror' ? '/' : '@'
const fileDelimiter = VITE_CDN_TYPE === 'npmmirror' ? '/files' : ''

if (customImportMap?.imports?.[pkgName]) {
return customImportMap.imports[pkgName]
.replace('${VITE_CDN_DOMAIN}', VITE_CDN_DOMAIN)
Copy link
Collaborator

Choose a reason for hiding this comment

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

考虑通过vite插件同一个层级一起处理了?

Copy link
Member Author

Choose a reason for hiding this comment

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

这里实际分为两种场景:

  1. localCDN 的场景:
    由于 fix: localImportMap not works #1242 (comment) 的 意见提出,localCDN 的场景会将实际的 ${VITE_CDN_DOMAIN} 在构建态实际写入。使得 VITE_CDN_DOMAIN 环境变量失效。

  2. 非 localCDN 的场景:

当前仍然保留了 VITE_CDN_DOMAIN 等环境变量的运行时读取和替换

如果 localCDN 和非 localCDN 的场景都将 import-map 的 CDN 等变量直接在构建态解决的话,我理解就不需要配置到 .env 环境变量了。应该作为构建配置项存在。

`${VITE_CDN_DOMAIN}/@opentiny/vue-theme${versionDelimiter}${importMapVersions.tinyVue}${fileDelimiter}/index.css`,
`${VITE_CDN_DOMAIN}/element-plus${versionDelimiter}2.4.2${fileDelimiter}/dist/index.css`
]
importStyles: []
Copy link
Collaborator

Choose a reason for hiding this comment

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

style理论上应该和imports 同个时间清理,应该是一对一绑定的

@@ -66,7 +66,8 @@ export default defineConfig({
/@opentiny\/tiny-engine.*/,
/@opentiny\/vue.*/,
/^prettier.*/,
/^@babel.*/
/^@babel.*/,
/^virtual:import-map$/
Copy link
Collaborator

Choose a reason for hiding this comment

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

这里可能得约定一个其他名字比如约定成包名会更优雅一定

Copy link
Collaborator

Choose a reason for hiding this comment

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

可能是由于该文件不应该分这这个包?

Copy link
Member Author

Choose a reason for hiding this comment

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

是否需要分包,可以参考 #1242 (comment) 的背景讨论

@@ -12,7 +12,7 @@

import { useEnv } from '@opentiny/tiny-engine-meta-register'
import { getSearchParams } from './http'
import importMapJSON from './importMap.json'
import { importMapConfig as importMapJSON } from '@opentiny/tiny-engine-common/js/importMap/index'
Copy link
Collaborator

Choose a reason for hiding this comment

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

这个路径过于具体(有耦合倾向,需要维持api稳定),common包理论上 只暴露到js目录这一层

: []
)

// 添加本地化CDN插件支持
if (isLocalImportMap) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

如果要改变写法, 需要把isCopyBundleDeps一起改了,保持工整对称

const logger = console
logger.log('[local-cdn-plugin]: Initializing local CDN plugin')

const cdnPlugins = localCdnPlugin({
Copy link
Collaborator

Choose a reason for hiding this comment

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

这个插件似乎只是处理preview预览? 因为上面的copyBundleDeps是处理画布物料的, 需要考虑合适的名字

@chilingling chilingling force-pushed the feat/localCDNPlugin branch from 83e92c8 to c1894bb Compare April 24, 2025 00:49
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

♻️ Duplicate comments (3)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1)

56-72: 💡 Verification agent

🧩 Analysis chain

Enhance semantic version comparison logic

The current implementation of compareIsSameVersion doesn't fully comply with semver rules:

  1. For caret ranges (^), it only checks the first digit, which fails with multi-digit major versions like ^10.2.3
  2. For tilde ranges (~), it only compares the first two digits, which doesn't properly handle minor versions with multiple digits like ~1.10.0

This simplified approach could lead to incorrect matches or miss breaking changes.

Consider using a dedicated semver library or implementing a more complete comparison:

-const compareIsSameVersion = (versionOrigin, versionTarget) => {
-  if (versionOrigin === versionTarget) {
-    return true
-  }
-
-  if (versionOrigin.startsWith('^')) {
-    // 如果源版本号是 ^ 开头,则只比较第一个数字是否相同
-    return versionOrigin.slice(1, 2) === versionTarget.slice(0, 1)
-  }
-
-  if (versionOrigin.startsWith('~')) {
-    // 如果源版本号是 ~ 开头,则只比较前两个数字是否相同
-    return versionOrigin.slice(1, 3) === versionTarget.slice(0, 3)
-  }
-
-  return false
-}
+const compareIsSameVersion = (versionOrigin, versionTarget) => {
+  if (versionOrigin === versionTarget) {
+    return true
+  }
+
+  // Extract semver parts
+  let cleanVersion = versionOrigin
+  if (versionOrigin.startsWith('^') || versionOrigin.startsWith('~')) {
+    cleanVersion = versionOrigin.slice(1)
+  }
+  
+  const [originMajor, originMinor] = cleanVersion.split('.').map(Number)
+  const [targetMajor, targetMinor] = versionTarget.split('.').map(Number)
+  
+  if (versionOrigin.startsWith('^')) {
+    // For caret ranges, major version must match
+    return originMajor === targetMajor
+  }
+  
+  if (versionOrigin.startsWith('~')) {
+    // For tilde ranges, major and minor must match
+    return originMajor === targetMajor && originMinor === targetMinor
+  }
+  
+  return false
+}

Alternatively, you could use the semver npm package:

+import semver from 'semver'
+
 const compareIsSameVersion = (versionOrigin, versionTarget) => {
   if (versionOrigin === versionTarget) {
     return true
   }
 
-  if (versionOrigin.startsWith('^')) {
-    // 如果源版本号是 ^ 开头,则只比较第一个数字是否相同
-    return versionOrigin.slice(1, 2) === versionTarget.slice(0, 1)
-  }
-
-  if (versionOrigin.startsWith('~')) {
-    // 如果源版本号是 ~ 开头,则只比较前两个数字是否相同
-    return versionOrigin.slice(1, 3) === versionTarget.slice(0, 3)
-  }
+  try {
+    return semver.satisfies(versionTarget, versionOrigin)
+  } catch (e) {
+    logger.warn(`[local-cdn-plugin]: Invalid semver comparison between ${versionOrigin} and ${versionTarget}`)
+  }
 
   return false
 }

🌐 Web query:

What is the proper way to compare SemVer versions with caret (^) and tilde (~) ranges in JavaScript?

💡 Result:

Comparing SemVer Versions with Caret (^) and Tilde (~) Ranges in JavaScript

To properly compare Semantic Versioning (SemVer) versions in JavaScript—especially in the context of caret (^) and tilde (~) constraints—it is essential to understand both the versioning rules and the available tooling.


Understanding Caret (^) and Tilde (~) Ranges

  • Tilde (~) Range:
    Permits updating to all patch versions as long as the major and minor versions remain the same.
    Example: ~1.2.3 matches >=1.2.3 <1.3.0, allowing updates like 1.2.4, 1.2.9, etc., but not 1.3.0[6][7][9].
  • Caret (^) Range:
    Allows updating to all newer patch and minor versions, as long as the major version is unchanged (i.e., no breaking changes).
    Example: ^1.2.3 matches >=1.2.3 <2.0.0, so it will upgrade to any 1.x.x version, like 1.4.0 or 1.9.9, but not 2.0.0[6][7][9].
    • For versions <1.0.0, the caret is more restrictive: for ^0.1.2, it behaves like ~0.1.2, matching >=0.1.2 <0.2.0[6].

Proper Way to Compare SemVer Versions in JavaScript

1. Use a SemVer Library

The most robust approach is to use a library that understands version ranges and performs checks according to the SemVer specification.

  • npm's semver package:
    This is the industry standard for version comparison, validation, and range satisfaction.
    Example:

    const semver = require('semver');
    
    // Check if a version satisfies a caret or tilde range
    semver.satisfies('1.2.5', '^1.2.3'); // true
    semver.satisfies('1.3.0', '~1.2.3'); // false
    • satisfies(version, range) verifies if a given version matches the specified range, handling all the complexity of ^ and ~[6].
  • Other lightweight libraries:

    • semver-compare: Compares two versions and tells you which is higher, but does not handle range logic such as caret or tilde[2][5].
    • compare-versions: Can compare two versions, sort lists, and check for equality, but to interpret ^ and ~, you must parse the constraint logic yourself[1][5].

2. How to Compare with Caret and Tilde

If you want to check if a version (e.g., 1.2.4) is acceptable for a range (e.g., ^1.2.3 or ~1.2.3):

  • With semver:

    semver.satisfies('1.2.4', '^1.2.3'); // true
    semver.satisfies('1.2.4', '~1.2.3'); // true
    semver.satisfies('1.3.0', '^1.2.3'); // true
    semver.satisfies('2.0.0', '^1.2.3'); // false
  • For sorting or direct comparison (not range checks):

    // semver-compare or compare-versions
    const compareVersions = require('compare-versions');
    compareVersions.compare('1.2.4', '1.2.3', '>'); // true
    compareVersions.compare('1.2.4', '1.2.3', '='); // false

    But to check range compliance, prefer semver.satisfies()[6][5].


Summary Table

Task Recommended Tool / Method Example Usage
Check if version fits ^/~ range semver.satisfies(version, range) semver.satisfies('1.4.2', '^1.2.3') // true
Compare two versions (which is newer) semver.compare, semver-compare semver.compare('1.2.3', '1.2.4') // -1
Sort list of versions compare-versions, semver.sort versions.sort(compareVersions)

Key Points

  • Use semver.satisfies() to evaluate if a version matches a caret or tilde range in JavaScript[6][7].
  • Tilde (~) is patch-flexible; caret (^) is patch- and minor-flexible (major version must match)[6][7][9].
  • Lightweight tools (like semver-compare) can directly compare versions, but don't handle range expressions like ^ and ~[2][5].

For all caret and tilde comparison logic, the npm semver library is the standard and correct choice in JavaScript projects.

Citations:


🏁 Script executed:

#!/bin/bash
set -e

# Check for semver in package.json dependencies across the repo
echo "Searching for semver in package.json files..."
rg -n '"semver"\s*:' --glob "package.json"

# Search for semver imports or requires in the codebase
echo -e "\nSearching for semver imports or requires..."
rg -n -e "import semver" -e "require('semver')" .

# Display the current implementation of compareIsSameVersion
echo -e "\nShowing compareIsSameVersion implementation..."
sed -n '1,200p' packages/build/vite-config/src/localCdnFile/localCdnPlugin.js || true

Length of output: 152


🏁 Script executed:

#!/bin/bash
set -e

# Locate localCdnPlugin.js in the repository
echo "Locating localCdnPlugin.js..."
rg -l "compareIsSameVersion" --glob "*.js"

# If found, display the snippet around compareIsSameVersion
echo -e "\nShowing compareIsSameVersion implementation (±5 lines)..."
rg -n -C5 "compareIsSameVersion" --glob "*.js" || true

# Search all package.json files for a semver dependency
echo -e "\nSearching for semver in package.json files..."
rg -n '"semver"\s*:' --glob "**/package.json"

Length of output: 2598


Enhance version comparison to handle full SemVer ranges

The current compareIsSameVersion in
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (lines 56–72)
uses simple string slicing which fails on multi-digit majors/minors (e.g. ^10.2.3, ~1.10.0). This can cause mismatches or overlook allowed patch/minor updates.

Please update as follows:

  • File: packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (56–72)

  • Option A: Use the industry-standard semver package

    1. Add to your package.json (e.g. devDependencies):
      "semver": "^7.5.6"
    2. Replace the function with:
    + import semver from 'semver'
    
    const compareIsSameVersion = (versionOrigin, versionTarget) => {
      if (versionOrigin === versionTarget) {
        return true
      }
    
    + try {
    +   // semver.satisfies handles both ^ and ~ correctly
    +   return semver.satisfies(versionTarget, versionOrigin)
    + } catch (e) {
    +   console.warn(`[local-cdn-plugin] Invalid semver range "${versionOrigin}"`)
    + }
    
      return false
    }
  • Option B: Improve manual parsing without external deps

    const compareIsSameVersion = (versionOrigin, versionTarget) => {
      if (versionOrigin === versionTarget) {
        return true
      }
    
    + // strip range marker and parse numbers
    + const clean = versionOrigin.replace(/^[~^]/, '')
    + const [oMajor, oMinor] = clean.split('.').map(Number)
    + const [tMajor, tMinor] = versionTarget.split('.').map(Number)
    
      if (versionOrigin.startsWith('^')) {
        // major must match
    +   return oMajor === tMajor
      }
    
      if (versionOrigin.startsWith('~')) {
        // major & minor must match
    +   return oMajor === tMajor && oMinor === tMinor
      }
    
      return false
    }

Either approach will correctly handle multi-digit SemVer ranges and prevent subtle mismatches.

designer-demo/tests/localCdnCustomConfig.test.js (1)

30-48: 🛠️ Refactor suggestion

Use more robust detection or replacement logic in updateRegistryFile

The current regex-based approach to modify the registry file is fragile and could break if:

  • The config structure changes
  • The file contains multiple objects with similar patterns
  • The config object contains nested objects with braces

Consider using a more robust approach such as an AST-based modification.

A simple improvement would be to use a more specific regex pattern:

- const updatedContent = registryContent.replace(
-   /config: {([^}]*)}/,
-   `config: {$1,
-     importMap: { 
-       imports: {
-         'vue': "\${VITE_CDN_DOMAIN}/vue\${versionDelimiter}3.4.21\${fileDelimiter}/dist/vue.runtime.esm-browser.js"
-       } 
-     }
-   }`
- )
+ // Look specifically for a config object with id: 'engine.config'
+ const updatedContent = registryContent.replace(
+   /config:\s*{[^{]*id:\s*['"]engine\.config['"][^}]*}/,
+   (match) => {
+     // Check if importMap already exists
+     if (match.includes('importMap:')) {
+       return match;
+     }
+     // Add importMap before the closing brace
+     return match.replace(
+       /}$/,
+       `,
+     importMap: { 
+       imports: {
+         'vue': "\${VITE_CDN_DOMAIN}/vue\${versionDelimiter}3.4.21\${fileDelimiter}/dist/vue.runtime.esm-browser.js"
+       } 
+     }
+   }`
+     );
+   }
+ )

For a more robust solution, consider using a JavaScript parser like Acorn or Babel to parse, modify, and regenerate the file.

packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js (1)

92-93: Single-file copy always appends basename – reconsider UX
The plugin still forces path.basename(srcPath) onto the destination, preventing legitimate renames (e.g., { src: 'foo.js', dest: 'bar.js' }). This was raised in a previous review.

🧹 Nitpick comments (9)
packages/build/vite-config/src/default-config.js (1)

167-181: Consider adding explicit error handling for plugin initialization

While checking if cdnPlugins exists and has items is good, there's no explicit warning if cdnPlugins is empty when isLocalImportMap is true. This might make debugging difficult if plugin initialization silently fails.

if (isLocalImportMap) {
  const logger = console
  logger.log('[local-cdn-plugin]: Initializing local CDN plugin')

  const cdnPlugins = localCdnPlugin({
    localCdnConfig: engineConfig.localCdnConfig,
    base: getBaseUrlFromCli(config.base),
    cdnDir: 'local-cdn-static'
  })

  if (cdnPlugins && cdnPlugins.length > 0) {
    config.plugins.push(...cdnPlugins)
+  } else {
+    logger.warn('[local-cdn-plugin]: No plugins were returned from localCdnPlugin, local CDN may not work correctly')
  }
}
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (4)

100-107: Improve error handling in package.json parsing

The current code silently ignores any errors when reading or parsing package.json. While this prevents the process from failing, it makes debugging difficult if there are issues with package.json files.

if (fs.existsSync(pkgFilePath)) {
  try {
    const pkg = JSON.parse(fs.readFileSync(path.resolve(pkgFilePath)))
    isSameVersion = compareIsSameVersion(version, pkg.version)
  } catch (error) {
-    // ignore
+    logger.debug(`[local-cdn-plugin]: Error reading or parsing package.json for ${packageName}: ${error.message}`)
  }
}

34-48: The extractInfo function could fail silently in some edge cases

When extractInfo fails to parse a string, it logs an error but doesn't return a value, which could lead to undefined items in the parsed arrays later. Consider returning a default object or throwing an error to better handle these cases.

function extractInfo(str) {
  try {
    let [packageName, versionAndPath] = str.split('${versionDelimiter}')
    packageName = packageName.replace(/^\$\{VITE_CDN_DOMAIN\}\//, '')
    const [version, filePath] = versionAndPath.split('${fileDelimiter}')

    return {
      packageName,
      version,
      filePathInPackage: filePath || '/'
    }
  } catch (error) {
    logger.error(`[local-cdn-plugin]: Failed to extract info from ${str} 提取 importMap 信息失败`, error)
+    // Return a recognizable error object so we can filter it out later
+    return { error: true, originalString: str }
  }
}

Then update where it's used:

-  const parsedDefaultImportMapConfig = Object.values(defaultImportMapConfig.imports).map((item) => extractInfo(item))
-  const parsedImportMapConfig = Object.values(importMapConfig.imports).map((item) => extractInfo(item))
+  const parsedDefaultImportMapConfig = Object.values(defaultImportMapConfig.imports)
+    .map((item) => extractInfo(item))
+    .filter(item => !item.error)
+  const parsedImportMapConfig = Object.values(importMapConfig.imports)
+    .map((item) => extractInfo(item))
+    .filter(item => !item.error)

10-24: Consider moving the default copy configuration to a separate file

Hardcoding the default copy configuration in the plugin makes it less maintainable and harder to customize. Consider extracting it to a separate configuration file.

-// 默认的复制配置,这几个 package 需要复制整个目录,所以需要有默认配置进行复制整个目录
-const defaultCopyConfig = {
-  '@opentiny/vue-theme': {
-    filePathInPackage: '/'
-  },
-  '@opentiny/vue-renderless': {
-    filePathInPackage: '/'
-  },
-  '@opentiny/vue-runtime': {
-    filePathInPackage: '/dist3/'
-  },
-  '@vue/devtools-api': {
-    filePathInPackage: '/'
-  }
-}
+import { defaultCopyConfig } from './default-copy-config.js'

And create a new file default-copy-config.js:

/**
 * Default copy configuration for packages that need their entire directory copied
 */
export const defaultCopyConfig = {
  '@opentiny/vue-theme': {
    filePathInPackage: '/'
  },
  '@opentiny/vue-renderless': {
    filePathInPackage: '/'
  },
  '@opentiny/vue-runtime': {
    filePathInPackage: '/dist3/'
  },
  '@vue/devtools-api': {
    filePathInPackage: '/'
  }
}

162-164: Filter out packages that may have undefined or error values

If any import map string fails to parse in extractInfo, it might return undefined or an error object, which could cause errors later in the process chain. Ensure that such items are filtered out.

  const overriddenImportMap = parsedDefaultImportMapConfig.filter((item) => {
-    return !parsedImportMapConfig.find((parsedItem) => parsedItem.packageName === item.packageName)
+    // First ensure item is valid
+    if (!item || !item.packageName) return false
+    // Then check if it's not overridden by a user-provided import
+    return !parsedImportMapConfig.find((parsedItem) => 
+      parsedItem && parsedItem.packageName === item.packageName)
  })
designer-demo/tests/localCdnCustomConfig.test.js (1)

144-148: Make file checks more robust with path normalization

When checking for file existence, consider normalizing paths to handle platform-specific differences in path separators.

// Check if vue.global.prod.js file exists
const vueJsExists = vueDirs.some(dir => {
-  return fs.existsSync(path.resolve(dir, 'dist', 'vue.runtime.esm-browser.js'))
+  return fs.existsSync(path.normalize(path.resolve(dir, 'dist', 'vue.runtime.esm-browser.js')))
})
docs/solutions/local-cdn.md (1)

73-87: Provide a more complete example of registry configuration

While you explain the need to synchronize importMap configuration with the registry, a complete code example showing how to access and share the importMap between Vite config and registry would be helpful.

**重要说明**:如果您在 Vite 配置中传递了 `localCdnConfig.importMap`,还需要在 registry 注册表的 config 中传入同样的配置,以确保应用在运行时能正确读取自定义的 importMap 配置。例如:

```javascript
+// 在 vite.config.js 中
+const localCdnConfig = {
+  importMap: { 
+    imports: {
+      'vue': "${VITE_CDN_DOMAIN}/vue${versionDelimiter}3.4.21${fileDelimiter}/dist/vue.runtime.esm-browser.js"
+    } 
+  },
+  copy: { ... }
+};
+
+const baseConfig = useTinyEngineBaseConfig({
+  localCdnConfig,
+  // ...otherConfig
+})
+
+// 在其他地方导出 localCdnConfig 以便在 registry.js 中使用
+export { localCdnConfig };
+
+// 在 registry.js 中
+import { localCdnConfig } from './vite.config.js';
+
// 在注册表配置中
{
  config: {
    id: 'engine.config',
    importMap: localCdnConfig.importMap,
    // ... 其他配置
  }
}
packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js (2)

20-20: Leverage Vite’s logger for consistent output
Using the raw console bypasses Vite’s coloured, level-controlled logging.
Consider:

-import { createLogger } from 'vite'
-const logger = createLogger()
+import { createLogger } from 'vite'
+const logger = createLogger()

This keeps plugin output aligned with the rest of the build toolchain and honours --silent / logLevel flags.


46-47: Avoid blocking sync I/O inside async function
fs.statSync blocks the event loop during potentially large directory scans. Using await fs.stat(...) (async) keeps the build responsive, especially when many targets are configured.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 83e92c8 and c1894bb.

📒 Files selected for processing (34)
  • .gitignore (1 hunks)
  • designer-demo/env/.env.alpha (1 hunks)
  • designer-demo/env/.env.development (0 hunks)
  • designer-demo/package.json (2 hunks)
  • designer-demo/public/mock/bundle.json (1 hunks)
  • designer-demo/tests/localCdnBasic.test.js (1 hunks)
  • designer-demo/tests/localCdnBundleDeps.test.js (1 hunks)
  • designer-demo/tests/localCdnCustomConfig.test.js (1 hunks)
  • designer-demo/tests/utils/envHelpers.js (1 hunks)
  • designer-demo/vitest.config.js (1 hunks)
  • docs/README.md (1 hunks)
  • docs/catalog.json (1 hunks)
  • docs/solutions/local-cdn.md (1 hunks)
  • packages/build/vite-config/package.json (1 hunks)
  • packages/build/vite-config/src/default-config.js (2 hunks)
  • packages/build/vite-config/src/localCdnFile/copyBundleDeps.js (3 hunks)
  • packages/build/vite-config/src/localCdnFile/copyImportMap.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/import-map.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/index.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1 hunks)
  • packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js (3 hunks)
  • packages/build/vite-config/src/localCdnFile/replaceImportPath.mjs (2 hunks)
  • packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js (1 hunks)
  • packages/build/vite-config/src/vite-plugins/createDynamicImportMapPlugin.js (1 hunks)
  • packages/canvas/DesignCanvas/src/DesignCanvas.vue (1 hunks)
  • packages/canvas/DesignCanvas/src/importMap.ts (2 hunks)
  • packages/common/js/importMap/index.js (1 hunks)
  • packages/common/package.json (1 hunks)
  • packages/common/vite.config.ts (1 hunks)
  • packages/design-core/package.json (1 hunks)
  • packages/design-core/src/preview/src/preview/Preview.vue (1 hunks)
  • packages/design-core/src/preview/src/preview/importMap.js (1 hunks)
  • packages/design-core/src/preview/src/preview/importMap.json (0 hunks)
  • packages/design-core/vite.config.js (1 hunks)
💤 Files with no reviewable changes (2)
  • designer-demo/env/.env.development
  • packages/design-core/src/preview/src/preview/importMap.json
✅ Files skipped from review due to trivial changes (2)
  • designer-demo/env/.env.alpha
  • designer-demo/package.json
🚧 Files skipped from review as they are similar to previous changes (25)
  • packages/canvas/DesignCanvas/src/DesignCanvas.vue
  • packages/common/js/importMap/index.js
  • .gitignore
  • packages/build/vite-config/src/localCdnFile/index.js
  • designer-demo/vitest.config.js
  • packages/common/vite.config.ts
  • docs/README.md
  • packages/build/vite-config/package.json
  • packages/common/package.json
  • packages/design-core/package.json
  • docs/catalog.json
  • packages/build/vite-config/src/localCdnFile/copyImportMap.js
  • packages/build/vite-config/src/localCdnFile/replaceImportPath.mjs
  • packages/build/vite-config/src/localCdnFile/import-map.js
  • packages/design-core/vite.config.js
  • packages/canvas/DesignCanvas/src/importMap.ts
  • designer-demo/tests/localCdnBundleDeps.test.js
  • packages/build/vite-config/src/localCdnFile/copyBundleDeps.js
  • packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js
  • designer-demo/tests/localCdnBasic.test.js
  • packages/build/vite-config/src/vite-plugins/createDynamicImportMapPlugin.js
  • packages/design-core/src/preview/src/preview/importMap.js
  • packages/design-core/src/preview/src/preview/Preview.vue
  • designer-demo/public/mock/bundle.json
  • designer-demo/tests/utils/envHelpers.js
🧰 Additional context used
🧬 Code Graph Analysis (2)
designer-demo/tests/localCdnCustomConfig.test.js (3)
designer-demo/tests/localCdnBasic.test.js (5)
  • __dirname (13-13)
  • projectRoot (14-14)
  • envAlphaPath (17-17)
  • distDir (15-15)
  • localCdnDir (16-16)
designer-demo/tests/localCdnBundleDeps.test.js (4)
  • __dirname (13-13)
  • projectRoot (14-14)
  • envAlphaPath (17-17)
  • distDir (18-18)
designer-demo/tests/utils/envHelpers.js (5)
  • backupPath (53-53)
  • backupPath (64-64)
  • backupEnvFile (51-56)
  • ensureEnvVarEnabled (12-27)
  • restoreEnvFile (63-69)
packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js (1)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (2)
  • logger (8-8)
  • src (96-96)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: push-check
🔇 Additional comments (10)
packages/build/vite-config/src/default-config.js (2)

167-191: The integration of the local CDN plugin looks good.

The plugin architecture is well-designed, conditionally initializing the local CDN plugin when isLocalImportMap is true, and always adding the dynamic import map plugin. This approach allows for flexible CDN dependency handling based on environment configuration.


183-189: The createDynamicImportMapPlugin is always included regardless of whether local import maps are enabled.

This is a good approach as it ensures the import map handling is always available, with its behavior controlled by the isLocalImportMap parameter. This separation of concerns makes the code more maintainable.

designer-demo/tests/localCdnCustomConfig.test.js (2)

126-157: The test correctly validates custom importMap configuration

The test effectively verifies that:

  1. Vue directories are created in the local CDN directory
  2. The dist directory exists within the Vue directory
  3. The expected Vue runtime file exists
  4. Multiple files are copied as expected

This comprehensive check ensures the custom configuration is properly applied.


159-172: Good validation of environment variable replacement

The test correctly verifies that the CDN domain is replaced with the local path in the built JavaScript files, which is a critical aspect of the local CDN functionality.

docs/solutions/local-cdn.md (5)

1-10: Excellent overview of the local CDN solution

The documentation clearly explains the purpose and benefits of the local CDN solution, focusing on application reliability, offline/intranet usage, and performance improvements.


90-125: Clear explanation of copy configuration

The documentation provides a clear explanation of the copy configuration options, including the default configuration and examples of custom configurations. This is particularly useful for users who need to customize the copy behavior for specific packages.


131-164: Good explanation of bundle handling with clear examples

The documentation explains how the local CDN handles bundle files, including the important note about the CDN domain prefix matching. The example with both TinyVue and Element Plus clearly illustrates which packages will be processed.


167-202: Comprehensive explanation of implementation principles

The documentation provides a clear explanation of how the local CDN solution works, breaking it down into logical steps. This gives users a good understanding of what happens under the hood.


203-207: Important notes for users

The notes section provides critical information about ensuring package versions are defined, acknowledging the increased build size, and handling special CDN URL formats. This helps users avoid common pitfalls when using the local CDN solution.

packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js (1)

124-131: Reset copiedFiles between builds for watch mode
When Vite runs in --watch / serve mode, a second build in the same process will reuse the stale Set, causing legitimate copies to be skipped. Clear the set in buildStart or each writeBundle.

Comment on lines +104 to +109
// 执行构建
execSync('pnpm run build:alpha', {
cwd: projectRoot,
stdio: 'inherit'
})
})
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 build command execution

The current implementation runs the build command synchronously and will throw an error if the command fails, potentially leaving the test environment in an inconsistent state. Consider adding a try-catch block to ensure cleanup happens even if the build fails.

- // Execute build
- execSync('pnpm run build:alpha', { 
-   cwd: projectRoot, 
-   stdio: 'inherit'
- })
+ // Execute build
+ try {
+   execSync('pnpm run build:alpha', { 
+     cwd: projectRoot, 
+     stdio: 'inherit'
+   })
+ } catch (error) {
+   console.error('Build failed:', error.message)
+   // Clean up even if the build fails
+   restoreEnvFile(envAlphaPath)
+   restoreRegistryFile()
+   if (fs.existsSync(viteConfigPath + '.bak')) {
+     fs.copyFileSync(viteConfigPath + '.bak', viteConfigPath)
+     fs.unlinkSync(viteConfigPath + '.bak')
+   }
+   throw error
+ }

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

Comment on lines +50 to +54
const fullDestPath = path.resolve(outDir, destPath)

try {
// 确保目标目录存在
await fs.ensureDir(path.dirname(fullDestPath))
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

Potential path traversal outside of outDir
path.resolve(outDir, destPath) will happily resolve destPath strings containing ../ and copy files outside the intended output directory (e.g. dest: '../../etc').
Add a guard:

const fullDestPath = path.resolve(outDir, destPath)
+if (!fullDestPath.startsWith(path.resolve(outDir))) {
+  logger.error('[vite-cdn-copy-plugin]: Refusing to write outside outDir', fullDestPath)
+  continue
+}

This mitigates an easy foot-gun and a security risk when targets come from user config.

Also applies to: 92-93

Comment on lines +69 to +87
const files = fg.sync(`${absoluteSrcPath}/**/*`, { onlyFiles: true })

// 处理每个文件
for (const file of files) {
const relativePath = path.relative(absoluteSrcPath, file)
const destFilePath = path.join(fullDestPath, relativePath)

// 确保目标文件的目录存在
await fs.ensureDir(path.dirname(destFilePath))

// 读取文件内容
const content = await fs.readFile(file, 'utf-8')

// 应用转换
const transformedContent = replaceJsImportPaths(content, file)

// 写入转换后的内容
await fs.writeFile(destFilePath, transformedContent)
}
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

Binary files corrupted by utf-8 read/write
The directory branch assumes every file is text (fs.readFile(..., 'utf-8')). Images, fonts, and wasm blobs will be mangled.

Replace with a binary-safe flow:

-const content = await fs.readFile(file, 'utf-8')
-const transformedContent = replaceJsImportPaths(content, file)
-await fs.writeFile(destFilePath, transformedContent)
+if (JS_EXT_RE.test(file)) {
+  const content = await fs.readFile(file, 'utf8')
+  const transformed = replaceJsImportPaths(content, file)
+  await fs.writeFile(destFilePath, transformed)
+} else {
+  await fs.copyFile(file, destFilePath)
+}

Do the same for the single-file branch below to guarantee asset integrity.

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

Comment on lines +12 to +18
function replaceJsImportPaths(content, filename) {
if (filename.endsWith('.js')) {
const result = babelReplaceImportPathWithCertainFileName(content, filename, console)
return result.code || content
}
return content
}
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 additional script extensions & avoid needless string operations
The helper only transforms files ending in .js, but Vite users can author modules with .mjs, .cjs, .jsx, .ts, etc.
Proposed improvement:

-function replaceJsImportPaths(content, filename) {
-  if (filename.endsWith('.js')) {
+const JS_EXT_RE = /\.(?:m?js|cjs|jsx?|tsx?)$/i
+
+function replaceJsImportPaths(content, filename) {
+  if (JS_EXT_RE.test(filename)) {

Expanding the regex future-proofs the plugin and prevents silent skips for legitimate script assets.

📝 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
function replaceJsImportPaths(content, filename) {
if (filename.endsWith('.js')) {
const result = babelReplaceImportPathWithCertainFileName(content, filename, console)
return result.code || content
}
return content
}
const JS_EXT_RE = /\.(?:m?js|cjs|jsx?|tsx?)$/i
function replaceJsImportPaths(content, filename) {
if (JS_EXT_RE.test(filename)) {
const result = babelReplaceImportPathWithCertainFileName(content, filename, console)
return result.code || content
}
return content
}

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: 7

♻️ Duplicate comments (1)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1)

55-71: 🛠️ Refactor suggestion

Enhance semantic version comparison

The current implementation only checks the first digit(s) for versions prefixed with ^ or ~. This might lead to inaccurate version comparisons, especially for more complex version patterns.

Consider using a dedicated SemVer library (like semver npm package) to ensure proper version comparison according to SemVer rules, or implement a more comprehensive comparison function:

- const compareIsSameVersion = (versionOrigin, versionTarget) => {
-   if (versionOrigin === versionTarget) {
-     return true
-   }
- 
-   if (versionOrigin.startsWith('^')) {
-     // 如果源版本号是 ^ 开头,则只比较第一个数字是否相同
-     return versionOrigin.slice(1, 2) === versionTarget.slice(0, 1)
-   }
- 
-   if (versionOrigin.startsWith('~')) {
-     // 如果源版本号是 ~ 开头,则只比较前两个数字是否相同
-     return versionOrigin.slice(1, 3) === versionTarget.slice(0, 3)
-   }
- 
-   return false
- }
+ const compareIsSameVersion = (versionOrigin, versionTarget) => {
+   // Import or install the semver package first
+   const semver = require('semver');
+   
+   if (versionOrigin === versionTarget) {
+     return true;
+   }
+   
+   // Use semver.satisfies to check if the target version satisfies the origin version range
+   return semver.satisfies(versionTarget, versionOrigin);
+ }
🧹 Nitpick comments (4)
packages/design-core/src/preview/src/preview/srcFiles.js (3)

29-32: Consider using useEnv() consistently for environment variables

While the logic for determining delimiters is correct, this code directly accesses import.meta.env instead of using the imported useEnv() function, which is used elsewhere in the file (line 67) and in other files like importMap.js.

-const versionDelimiter =
-  import.meta.env.VITE_CDN_TYPE === 'npmmirror' && !import.meta.env.VITE_LOCAL_CDN_PATH ? '/' : '@'
-const fileDelimiter =
-  import.meta.env.VITE_CDN_TYPE === 'npmmirror' && !import.meta.env.VITE_LOCAL_CDN_PATH ? '/files' : ''
+const { VITE_CDN_TYPE, VITE_LOCAL_CDN_PATH } = useEnv()
+const versionDelimiter = VITE_CDN_TYPE === 'npmmirror' && !VITE_LOCAL_CDN_PATH ? '/' : '@'
+const fileDelimiter = VITE_CDN_TYPE === 'npmmirror' && !VITE_LOCAL_CDN_PATH ? '/files' : ''

38-38: Consider using useEnv() for consistent environment variable access

Similar to the previous comment, consider using the useEnv() function for accessing environment variables instead of directly using import.meta.env.

-  .replaceAll('${VITE_CDN_DOMAIN}', import.meta.env.VITE_LOCAL_CDN_PATH || import.meta.env.VITE_CDN_DOMAIN)
+  .replaceAll('${VITE_CDN_DOMAIN}', useEnv().VITE_LOCAL_CDN_PATH || useEnv().VITE_CDN_DOMAIN)

67-67: Consider consistent string replacement methods

While the logic here is correct, this code uses replace with a regex pattern, whereas line 38 uses replaceAll with a string literal. Consider using a consistent approach throughout the codebase.

-      fileContent: appJS.replace(/VITE_CDN_DOMAIN/g, useEnv().VITE_LOCAL_CDN_PATH || useEnv().VITE_CDN_DOMAIN)
+      fileContent: appJS.replaceAll('${VITE_CDN_DOMAIN}', useEnv().VITE_LOCAL_CDN_PATH || useEnv().VITE_CDN_DOMAIN)
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1)

9-9: Consider using English comments for consistency

The comment is in a language other than English (looks like Chinese). For international collaboration and maintenance, it would be better to use English comments throughout the codebase.

-// 默认的复制配置,这几个 package 需要复制整个目录,所以需要有默认配置进行复制整个目录
+// Default copy configuration - these packages need their entire directories copied
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c1894bb and 9908606.

📒 Files selected for processing (11)
  • designer-demo/env/.env.alpha (1 hunks)
  • designer-demo/env/.env.localCDN.example (1 hunks)
  • packages/build/vite-config/src/default-config.js (3 hunks)
  • packages/build/vite-config/src/localCdnFile/localCdnPlugin.js (1 hunks)
  • packages/canvas/DesignCanvas/src/importMap.ts (2 hunks)
  • packages/common/js/importMap/import-map.json (2 hunks)
  • packages/common/js/importMap/index.js (1 hunks)
  • packages/common/vite.config.ts (1 hunks)
  • packages/design-core/src/preview/src/preview/importMap.js (1 hunks)
  • packages/design-core/src/preview/src/preview/srcFiles.js (3 hunks)
  • packages/design-core/vite.config.js (3 hunks)
✅ Files skipped from review due to trivial changes (2)
  • packages/common/js/importMap/import-map.json
  • designer-demo/env/.env.localCDN.example
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/common/js/importMap/index.js
  • packages/common/vite.config.ts
  • designer-demo/env/.env.alpha
  • packages/design-core/vite.config.js
  • packages/canvas/DesignCanvas/src/importMap.ts
  • packages/build/vite-config/src/default-config.js
🧰 Additional context used
🧬 Code Graph Analysis (2)
packages/design-core/src/preview/src/preview/srcFiles.js (1)
packages/design-core/src/preview/src/preview/importMap.js (3)
  • versionDelimiter (23-23)
  • fileDelimiter (24-24)
  • useEnv (22-22)
packages/design-core/src/preview/src/preview/importMap.js (3)
packages/build/vite-config/src/localCdnFile/copyPreviewImportMap.js (1)
  • importMap (17-17)
packages/design-core/src/preview/src/preview/srcFiles.js (2)
  • versionDelimiter (29-30)
  • fileDelimiter (31-32)
packages/common/js/environments.js (4)
  • VITE_CDN_TYPE (19-19)
  • VITE_CDN_TYPE (19-19)
  • VITE_CDN_DOMAIN (18-18)
  • VITE_CDN_DOMAIN (18-18)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: push-check
🔇 Additional comments (4)
packages/design-core/src/preview/src/preview/importMap.js (3)

15-15: Good change - centralized import map configuration

The change from importing a local JSON file to importing from a centralized module enhances maintainability and consistency across the codebase.


22-24: Improved environment variables handling

Good refactoring to destructure environment variables once from useEnv() and properly handle conditional delimiters based on both CDN type and local CDN path configuration.


27-27: Proper local CDN path prioritization

Correctly prioritizing VITE_LOCAL_CDN_PATH over VITE_CDN_DOMAIN supports the local CDN solution's goal of offline or intranet deployment scenarios.

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

13-13: Good addition of useEnv import

Importing the useEnv utility allows for consistent environment variable access across the codebase.

Comment on lines +33 to +47
function extractInfo(str) {
try {
let [packageName, versionAndPath] = str.split('${versionDelimiter}')
packageName = packageName.replace(/^\$\{VITE_CDN_DOMAIN\}\//, '')
const [version, filePath] = versionAndPath.split('${fileDelimiter}')

return {
packageName,
version,
filePathInPackage: filePath || '/'
}
} catch (error) {
logger.error(`[local-cdn-plugin]: Failed to extract info from ${str} 提取 importMap 信息失败`, error)
}
}
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 extractInfo function

The function logs errors but doesn't return any value when an error occurs. This could lead to undefined behavior when the result is used later in the code.

function extractInfo(str) {
  try {
    let [packageName, versionAndPath] = str.split('${versionDelimiter}')
    packageName = packageName.replace(/^\$\{VITE_CDN_DOMAIN\}\//, '')
    const [version, filePath] = versionAndPath.split('${fileDelimiter}')

    return {
      packageName,
      version,
      filePathInPackage: filePath || '/'
    }
  } catch (error) {
    logger.error(`[local-cdn-plugin]: Failed to extract info from ${str} 提取 importMap 信息失败`, error)
+   return null
  }
}

You should also add validation for null values when using the results of this 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
function extractInfo(str) {
try {
let [packageName, versionAndPath] = str.split('${versionDelimiter}')
packageName = packageName.replace(/^\$\{VITE_CDN_DOMAIN\}\//, '')
const [version, filePath] = versionAndPath.split('${fileDelimiter}')
return {
packageName,
version,
filePathInPackage: filePath || '/'
}
} catch (error) {
logger.error(`[local-cdn-plugin]: Failed to extract info from ${str} 提取 importMap 信息失败`, error)
}
}
function extractInfo(str) {
try {
let [packageName, versionAndPath] = str.split('${versionDelimiter}')
packageName = packageName.replace(/^\$\{VITE_CDN_DOMAIN\}\//, '')
const [version, filePath] = versionAndPath.split('${fileDelimiter}')
return {
packageName,
version,
filePathInPackage: filePath || '/'
}
} catch (error) {
logger.error(`[local-cdn-plugin]: Failed to extract info from ${str} 提取 importMap 信息失败`, error)
return null
}
}

Comment on lines +158 to +160
const defaultImportMapConfig = JSON.parse(
fs.readFileSync(path.resolve(process.cwd(), './node_modules/@opentiny/tiny-engine/dist/import-map.json'), 'utf-8')
)
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 file reading operation

Reading the default import map file lacks error handling. If the file doesn't exist or has invalid JSON, it will throw an unhandled exception.

-  const defaultImportMapConfig = JSON.parse(
-    fs.readFileSync(path.resolve(process.cwd(), './node_modules/@opentiny/tiny-engine/dist/import-map.json'), 'utf-8')
-  )
+  let defaultImportMapConfig = { imports: {} };
+  try {
+    const importMapPath = path.resolve(process.cwd(), './node_modules/@opentiny/tiny-engine/dist/import-map.json');
+    if (fs.existsSync(importMapPath)) {
+      defaultImportMapConfig = JSON.parse(
+        fs.readFileSync(importMapPath, 'utf-8')
+      );
+    } else {
+      logger.warn('[local-cdn-plugin]: Default import-map.json not found, using empty import map');
+    }
+  } catch (error) {
+    logger.error('[local-cdn-plugin]: Failed to read or parse default import-map.json', error);
+  }

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

Comment on lines +99 to +106
if (fs.existsSync(pkgFilePath)) {
try {
const pkg = JSON.parse(fs.readFileSync(path.resolve(pkgFilePath)))
isSameVersion = compareIsSameVersion(version, pkg.version)
} catch (error) {
// ignore
}
}
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 when reading package.json

The current code silently ignores errors when reading or parsing package.json. This could hide important issues that might affect version comparison accuracy.

if (fs.existsSync(pkgFilePath)) {
  try {
    const pkg = JSON.parse(fs.readFileSync(path.resolve(pkgFilePath)))
    isSameVersion = compareIsSameVersion(version, pkg.version)
  } catch (error) {
-   // ignore
+   logger.warn(`[local-cdn-plugin]: Failed to parse package.json for ${packageName}`, error)
+   // Default to false in case of error
+   isSameVersion = false
  }
}
📝 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 (fs.existsSync(pkgFilePath)) {
try {
const pkg = JSON.parse(fs.readFileSync(path.resolve(pkgFilePath)))
isSameVersion = compareIsSameVersion(version, pkg.version)
} catch (error) {
// ignore
}
}
if (fs.existsSync(pkgFilePath)) {
try {
const pkg = JSON.parse(fs.readFileSync(path.resolve(pkgFilePath)))
isSameVersion = compareIsSameVersion(version, pkg.version)
} catch (error) {
logger.warn(`[local-cdn-plugin]: Failed to parse package.json for ${packageName}`, error)
// Default to false in case of error
isSameVersion = false
}
}

* @param {Object} options - 配置选项
* @param {Object} options.localCdnConfig - 本地CDN配置
* @param {Object} options.localCdnConfig.importMap - 导入映射配置,定义需要本地化的CDN依赖
* @param {Object} options.localCdnConfig.copy - 自定义复制配置,可以覆盖特定包的默认配置
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

Validate importMap structure

The code assumes a specific structure for the importMap configuration, but doesn't validate it before use. Adding validation would improve robustness.

export function localCdnPlugin({
  localCdnConfig = { importMap: { imports: {} }, copy: {} },
  base = './',
  cdnDir = 'local-cdn-static', // 构建目录中的CDN文件夹名称
  bundleTempDir = 'bundle-deps/local-cdn' // 临时存放下载的包的目录
}) {
+  // Validate importMap structure
+  if (!localCdnConfig || typeof localCdnConfig !== 'object') {
+    logger.warn('[local-cdn-plugin]: Invalid localCdnConfig, using defaults')
+    localCdnConfig = { importMap: { imports: {} }, copy: {} }
+  }
+  
   const importMapConfig = localCdnConfig.importMap || { imports: {} }
+  if (!importMapConfig.imports || typeof importMapConfig.imports !== 'object') {
+    logger.warn('[local-cdn-plugin]: Invalid importMap.imports, using empty object')
+    importMapConfig.imports = {}
+  }
📝 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
* @param {Object} options.localCdnConfig.copy - 自定义复制配置,可以覆盖特定包的默认配置
export function localCdnPlugin({
localCdnConfig = { importMap: { imports: {} }, copy: {} },
base = './',
cdnDir = 'local-cdn-static', // 构建目录中的CDN文件夹名称
bundleTempDir = 'bundle-deps/local-cdn' // 临时存放下载的包的目录
}) {
// Validate importMap structure
if (!localCdnConfig || typeof localCdnConfig !== 'object') {
logger.warn('[local-cdn-plugin]: Invalid localCdnConfig, using defaults')
localCdnConfig = { importMap: { imports: {} }, copy: {} }
}
const importMapConfig = localCdnConfig.importMap || { imports: {} }
if (!importMapConfig.imports || typeof importMapConfig.imports !== 'object') {
logger.warn('[local-cdn-plugin]: Invalid importMap.imports, using empty object')
importMapConfig.imports = {}
}
// …rest of plugin implementation…
}

Comment on lines +161 to +166
const parsedDefaultImportMapConfig = Object.values(defaultImportMapConfig.imports).map((item) => extractInfo(item))
const parsedImportMapConfig = Object.values(importMapConfig.imports).map((item) => extractInfo(item))
const overriddenImportMap = parsedDefaultImportMapConfig.filter((item) => {
return !parsedImportMapConfig.find((parsedItem) => parsedItem.packageName === item.packageName)
})
const combinedImportMapConfig = [...overriddenImportMap, ...parsedImportMapConfig]
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 null values from extractInfo

The extractInfo function can return undefined when an error occurs, but the code doesn't handle these cases when mapping over the import map values.

-  const parsedDefaultImportMapConfig = Object.values(defaultImportMapConfig.imports).map((item) => extractInfo(item))
-  const parsedImportMapConfig = Object.values(importMapConfig.imports).map((item) => extractInfo(item))
+  const parsedDefaultImportMapConfig = Object.values(defaultImportMapConfig.imports || {})
+    .map((item) => extractInfo(item))
+    .filter(Boolean) // Filter out null/undefined values
+  const parsedImportMapConfig = Object.values(importMapConfig.imports || {})
+    .map((item) => extractInfo(item))
+    .filter(Boolean) // Filter out null/undefined values
   const overriddenImportMap = parsedDefaultImportMapConfig.filter((item) => {
     return !parsedImportMapConfig.find((parsedItem) => parsedItem.packageName === item.packageName)
   })
📝 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 parsedDefaultImportMapConfig = Object.values(defaultImportMapConfig.imports).map((item) => extractInfo(item))
const parsedImportMapConfig = Object.values(importMapConfig.imports).map((item) => extractInfo(item))
const overriddenImportMap = parsedDefaultImportMapConfig.filter((item) => {
return !parsedImportMapConfig.find((parsedItem) => parsedItem.packageName === item.packageName)
})
const combinedImportMapConfig = [...overriddenImportMap, ...parsedImportMapConfig]
const parsedDefaultImportMapConfig = Object.values(defaultImportMapConfig.imports || {})
.map((item) => extractInfo(item))
.filter(Boolean) // Filter out null/undefined values
const parsedImportMapConfig = Object.values(importMapConfig.imports || {})
.map((item) => extractInfo(item))
.filter(Boolean) // Filter out null/undefined values
const overriddenImportMap = parsedDefaultImportMapConfig.filter((item) => {
return !parsedImportMapConfig.find((parsedItem) => parsedItem.packageName === item.packageName)
})
const combinedImportMapConfig = [...overriddenImportMap, ...parsedImportMapConfig]

Comment on lines +183 to +189
.reduce((acc, cur) => {
// 同个包避免多个版本只保留一个版本
if (!acc.some(({ packageName }) => cur.packageName === packageName)) {
acc.push(cur)
}
return acc
}, [])
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider preserving version differences in package installations

The current deduplication logic only keeps one version per package name. This could be problematic if different parts of the application require different versions of the same package.


🏁 Script executed:

#!/bin/bash
# Check if there are any duplicated package names with different versions in the import maps

# First, extract all package entries from the combined config
echo "Checking for multiple versions of the same package in import maps..."

# Use ast-grep to find places where the same package name might have different versions
ast-grep --pattern 'const [$_] = combinedImportMapConfig.map'

# Search for potential version conflicts
rg -A 5 'packageName.*version' packages/build/vite-config/src/localCdnFile

Length of output: 7358


Preserve multiple versions in dedup logic

The current .reduce only keys off packageName, so any additional versions of the same package are dropped. To allow coexisting versions, include version in the uniqueness check:

  • File: packages/build/vite-config/src/localCdnFile/localCdnPlugin.js
    Lines: ~183–189

Diff:

-  .reduce((acc, cur) => {
-    // 同个包避免多个版本只保留一个版本
-    if (!acc.some(({ packageName }) => cur.packageName === packageName)) {
-      acc.push(cur)
-    }
-    return acc
-  }, [])
+  .reduce((acc, cur) => {
+    // 同包不同版本也需保留
+    if (!acc.some(({ packageName, version }) =>
+         cur.packageName === packageName &&
+         cur.version     === version
+    )) {
+      acc.push(cur)
+    }
+    return acc
+  }, [])

This ensures entries with the same name but different versions are both included.

📝 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
.reduce((acc, cur) => {
// 同个包避免多个版本只保留一个版本
if (!acc.some(({ packageName }) => cur.packageName === packageName)) {
acc.push(cur)
}
return acc
}, [])
.reduce((acc, cur) => {
// 同包不同版本也需保留
if (!acc.some(({ packageName, version }) =>
cur.packageName === packageName &&
cur.version === version
)) {
acc.push(cur)
}
return acc
}, [])

Comment on lines +35 to +37
let [packageName, versionAndPath] = str.split('${versionDelimiter}')
packageName = packageName.replace(/^\$\{VITE_CDN_DOMAIN\}\//, '')
const [version, filePath] = versionAndPath.split('${fileDelimiter}')
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Clarify the variable delimiters usage

The code uses ${versionDelimiter} and ${fileDelimiter} as string literals for splitting, but it's not clear where these values are defined or set. This could lead to confusion and potential errors.


🏁 Script executed:

#!/bin/bash
# Check where versionDelimiter and fileDelimiter are defined or used

echo "Searching for versionDelimiter and fileDelimiter definitions or usages..."
rg -A 2 -B 2 "(versionDelimiter|fileDelimiter)" --type js --type ts

Length of output: 6484


Clarify placeholder usage in extractInfo

The extractInfo function in
packages/build/vite-config/src/localCdnFile/localCdnPlugin.js
splits on the literal strings ${versionDelimiter} and ${fileDelimiter} to pull apart the CDN path. Those placeholders are actually defined (and replaced at runtime) in your import-map generators:

  • packages/canvas/DesignCanvas/src/importMap.ts
    const versionDelimiter = … ? '/' : '@'
    const fileDelimiter = … ? '/files' : ''
  • packages/design-core/src/preview/src/preview/importMap.js

Please add a brief comment above extractInfo so future readers know where these placeholders originate and why we split on the string "${versionDelimiter}" rather than a hard-coded @ or /.

• File: packages/build/vite-config/src/localCdnFile/localCdnPlugin.js
• Location: immediately above function extractInfo(str) {

Suggested diff:

+/**
+ * The strings `${versionDelimiter}` and `${fileDelimiter}` are
+ * injected by our import-map generators (see
+ * packages/canvas/DesignCanvas/src/importMap.ts and
+ * preview/importMap.js). We split on these placeholders here
+ * to extract packageName, version, and path.
+ */
 function extractInfo(str) {
   try {
     let [packageName, versionAndPath] = str.split('${versionDelimiter}')
     packageName = packageName.replace(/^\$\{VITE_CDN_DOMAIN\}\//, '')
     const [version, filePath] = versionAndPath.split('${fileDelimiter}')
📝 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
let [packageName, versionAndPath] = str.split('${versionDelimiter}')
packageName = packageName.replace(/^\$\{VITE_CDN_DOMAIN\}\//, '')
const [version, filePath] = versionAndPath.split('${fileDelimiter}')
/**
* The strings `${versionDelimiter}` and `${fileDelimiter}` are
* injected by our import-map generators (see
* packages/canvas/DesignCanvas/src/importMap.ts and
* preview/importMap.js). We split on these placeholders here
* to extract packageName, version, and path.
*/
function extractInfo(str) {
try {
let [packageName, versionAndPath] = str.split('${versionDelimiter}')
packageName = packageName.replace(/^\$\{VITE_CDN_DOMAIN\}\//, '')
const [version, filePath] = versionAndPath.split('${fileDelimiter}')
// …rest of implementation…
} catch (e) {
// handle error
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working documentation Improvements or additions to documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants