From 34a9c7131b6fd4d1577b40d2b2c550b6548b5571 Mon Sep 17 00:00:00 2001 From: chilingling Date: Mon, 24 Mar 2025 23:44:41 +0800 Subject: [PATCH 01/27] fix: localImportMap not works --- .gitignore | 1 + designer-demo/env/.env.alpha | 5 +- designer-demo/env/.env.development | 3 +- designer-demo/public/mock/bundle.json | 26 +- docs/catalog.json | 3 +- docs/solutions/local-cdn.md | 204 ++++++++++ packages/build/vite-config/package.json | 1 + .../build/vite-config/src/default-config.js | 32 +- .../src/localCdnFile/copyBundleDeps.js | 30 +- .../src/localCdnFile/copyImportMap.js | 2 + .../vite-config/src/localCdnFile/index.js | 1 + .../src/localCdnFile/localCdnPlugin.js | 378 ++++++++++++++++++ .../src/localCdnFile/locateCdnNpmInfo.js | 44 +- .../src/localCdnFile/replaceImportPath.mjs | 20 +- .../canvas/DesignCanvas/src/DesignCanvas.vue | 2 +- packages/canvas/DesignCanvas/src/importMap.ts | 58 +-- .../js/importMap/import-map.json} | 14 +- packages/common/js/importMap/index.js | 1 + packages/common/package.json | 3 +- packages/common/vite.config.ts | 16 +- packages/design-core/package.json | 3 +- .../src/preview/src/preview/importMap.js | 2 +- packages/design-core/vite.config.js | 15 +- 23 files changed, 786 insertions(+), 78 deletions(-) create mode 100644 docs/solutions/local-cdn.md create mode 100644 packages/build/vite-config/src/localCdnFile/localCdnPlugin.js rename packages/{design-core/src/preview/src/preview/importMap.json => common/js/importMap/import-map.json} (76%) create mode 100644 packages/common/js/importMap/index.js diff --git a/.gitignore b/.gitignore index 1ed9582ece..daff07cbe6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ yarn.lock pnpm-lock.yaml lerna-debug.log packages/design-core/bundle-deps +designer-demo/bundle-deps # local env files .env.local diff --git a/designer-demo/env/.env.alpha b/designer-demo/env/.env.alpha index d1784a37aa..272c70f80b 100644 --- a/designer-demo/env/.env.alpha +++ b/designer-demo/env/.env.alpha @@ -1,12 +1,11 @@ # alpha mode, used by the "build:alpha" script NODE_ENV=production -# VITE_CDN_DOMAIN=https://unpkg.com VITE_CDN_DOMAIN=https://registry.npmmirror.com # 使用npmmirror的cdn 时,需要声明 VITE_CDN_TYPE=npmmirror VITE_CDN_TYPE=npmmirror -VITE_LOCAL_IMPORT_MAPS=false -VITE_LOCAL_BUNDLE_DEPS=false +VITE_LOCAL_IMPORT_MAPS=true +VITE_LOCAL_BUNDLE_DEPS=true # VITE_ORIGIN= # 错误监控上报 url diff --git a/designer-demo/env/.env.development b/designer-demo/env/.env.development index 2642f040e7..709f9394ab 100644 --- a/designer-demo/env/.env.development +++ b/designer-demo/env/.env.development @@ -1,11 +1,10 @@ # development mode, used by the "vite" command NODE_ENV=development -# VITE_CDN_DOMAIN=https://unpkg.com VITE_CDN_DOMAIN=https://registry.npmmirror.com # 使用npmmirror的cdn 时,需要声明 VITE_CDN_TYPE=npmmirror VITE_CDN_TYPE=npmmirror VITE_LOCAL_IMPORT_MAPS=false VITE_LOCAL_BUNDLE_DEPS=false # request data via alpha service -# VITE_ORIGIN= +VITE_ORIGIN=http://localhost:9090 diff --git a/designer-demo/public/mock/bundle.json b/designer-demo/public/mock/bundle.json index f6ae8f7337..1ea0de5fd5 100644 --- a/designer-demo/public/mock/bundle.json +++ b/designer-demo/public/mock/bundle.json @@ -9958,6 +9958,29 @@ } ], "events": { + "onEditClosed": { + "label": { + "zh_CN": "单元格编辑状态下被关闭时会触发该事件" + }, + "description": { + "zh_CN": "" + }, + "type": "event", + "functionInfo": { + "params": [ + { + "name": "table", + "type": "Object", + "defaultValue": "", + "description": { + "zh_CN": "{$table,filters} 包含 table 实例对象和过滤条件的对象" + } + } + ], + "returns": {} + }, + "defaultValue": "function onClick(e) {}" + }, "onFilterChange": { "label": { "zh_CN": "筛选条件改变时触发改事件" @@ -10166,7 +10189,8 @@ "devMode": "proCode", "npm": { "package": "@opentiny/vue", - "exportName": "TinyGridColumn" + "exportName": "TinyGridColumn", + "destructuring": true }, "group": "component", "priority": 2, diff --git a/docs/catalog.json b/docs/catalog.json index 3d8a3febc2..35287ed2d7 100644 --- a/docs/catalog.json +++ b/docs/catalog.json @@ -78,7 +78,8 @@ { "title": "区块发布方案(Node.js服务端)", "name": "block-release-solution.md" }, { "title": "区块局域网发布方案(Node.js服务端)", "name": "block-lan-release-solution.md" }, { "title": "设计器中引入第三方组件库", "name": "third-party-library-in-designer.md" }, - { "title": "物料同步方案", "name": "material-sync-solution.md" } + { "title": "物料同步方案", "name": "material-sync-solution.md" }, + { "title": "本地化CDN方案", "name": "local-cdn.md" } ] }, { diff --git a/docs/solutions/local-cdn.md b/docs/solutions/local-cdn.md new file mode 100644 index 0000000000..3837c80902 --- /dev/null +++ b/docs/solutions/local-cdn.md @@ -0,0 +1,204 @@ +# 本地化CDN方案 + +## 概述 + +本地化CDN是一种在生产环境中将远程CDN资源替换为本地文件的解决方案。它解决了以下问题: + +1. 减少对外部CDN的依赖,提高应用的可靠性 +2. 在离线环境或内网环境中使用CDN资源 +3. 加快资源加载速度,提升应用性能 + +## 使用方法 + +### 修改环境变量使用本地化CDN + +要启用本地化CDN功能,请按照以下步骤操作: + +1. 修改环境变量 + +```bash +# .env.alpha、.env.production、.env.development + +# 将画布、页面预览需要的 CDN 资源进行本地化。 +VITE_LOCAL_IMPORT_MAPS=true + +# 将物料需要的CDN 资源进行本地化。注意⚠️:这里需要您的物料package需要能够通过 npm 的方式进行下载,否则会失效。 +VITE_LOCAL_BUNDLE_DEPS=true +``` + +2. 【可选】 在 `vite.config.js` 中传入自定义配置 + +```javascript +const baseConfig = useTinyEngineBaseConfig({ + importMapConfig: { imports: { ... } }, + copyConfig: { ... } + // ...otherConfig +}) +``` + +### 配置选项 + +CDN 本地化接受以下配置选项: + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| importMapConfig | Object | `{ imports: {} }` | 导入映射配置,定义需要本地化的CDN依赖 | +| copyConfig | Object | `{}` | 自定义复制配置,可以覆盖特定包的默认配置 | + +#### importMapConfig 详细说明 + +`importMapConfig` 是一个包含 `imports` 属性的对象,它定义了需要本地化的CDN依赖。在插件内部,它会与默认的导入映射配置合并。 + +import-map.json 的格式示例: +```json +{ + "imports": { + "vue": "${VITE_CDN_DOMAIN}/vue${versionDelimiter}3.2.37${fileDelimiter}/dist/vue.global.prod.js", + "vue-router": "${VITE_CDN_DOMAIN}/vue-router${versionDelimiter}4.1.5${fileDelimiter}/dist/vue-router.global.js", + "pinia": "${VITE_CDN_DOMAIN}/pinia${versionDelimiter}2.0.23${fileDelimiter}/dist/pinia.iife.prod.js" + } +} +``` + +URL格式说明: +- `${VITE_CDN_DOMAIN}` - CDN域名占位符,将被替换为本地路径 +- `${versionDelimiter}` - 版本分隔符,用于分隔包名和版本 +- `${fileDelimiter}` - 文件路径分隔符,用于分隔版本和文件路径 + +插件将解析这些URL,提取包名、版本和文件路径,然后在构建时将它们替换为本地路径。 + +**重要说明**:如果您在 Vite 配置中传递了 `importMapConfig`,还需要在 registry 注册表的 config 中传入同样的配置,以确保应用在运行时能正确读取自定义的 importMap 配置。例如: + +```javascript +// 在注册表配置中 +{ + config: { + id: 'engine.config', + importMap: importMapConfig, + // ... 其他配置 + } +} +``` + +这是因为画布和页面预览默认会从注册表 `getMergeMeta('engine.config')?.importMap` 中读取自定义的映射配置,如果获取失败,则会读取默认的映射。 + +#### copyConfig 详细说明 + +`copyConfig` 是一个可选的配置对象,用于覆盖特定包的默认复制配置。它的结构是一个对象,键是包名,值是该包的复制配置。 + +默认配置如下: +```javascript +{ + '@opentiny/vue-theme': { + filePathInPackage: '/' + }, + '@opentiny/vue-renderless': { + filePathInPackage: '/' + }, + '@opentiny/vue-runtime': { + filePathInPackage: '/dist3/' + }, + '@vue/devtools-api': { + filePathInPackage: '/' + } +} +``` + +每个包的配置支持以下选项: +- `filePathInPackage`: 指定要复制的包内路径,如果想要复制整个 package,则配置为 '/' +- `version`: (可选) 覆盖包的版本号(注意:实际请求的 url 路径的版本号不会变,只是实际下载的版本号变了) + +例如,自定义配置: +```javascript +{ + 'vue': { + filePathInPackage: '/dist/' + }, + 'element-plus': { + filePathInPackage: '/dist/index.css', + version: '2.2.0' + } +} +``` + +### 处理bundle文件中的CDN链接 + +如果需要处理bundle文件中的CDN链接,可以在 env 文件中配置 `VITE_LOCAL_BUNDLE_DEPS=true`: + +注意⚠️:物料文件 bundle.json 中 packages 数组中,只有前缀与 env 文件配置的 `VITE_CDN_DOMAIN` 一致,才会被复制打包并改写 bundle.json。 + +比如有如下物料配置和 .env 配置 + +`bundle.json`: +```json +{ + "packages": [ + { + "name": "TinyVue组件库", + "package": "@opentiny/vue", + "version": "3.20.0", + "destructuring": true, + "script": "https://npmmirror.com/@opentiny/vue-runtime/~3.20/files/dist3/tiny-vue-pc.mjs", + "css": "https://npmmirror.com/@opentiny/vue-theme/~3.20/files/index.css" + }, + { + "name": "element-plus组件库", + "package": "element-plus", + "version": "2.4.2", + "script": "https://unpkg.com/element-plus@2.4.2/dist/index.full.mjs", + "css": "https://unpkg.com/element-plus@2.4.2/dist/index.css" + } + ] +} +``` + +`.env.alpha`: + +```bash +VITE_CDN_DOMAIN=https://unpkg.com +``` + +此时,只有前缀匹配的 `element-plus` 才会被解析并复制 npm 内容。而 `@opentiny/vue-runtime` 则不会被复制 + + +## 实现原理 + +本地化CDN的实现原理可以分为以下几个核心步骤: + +### 1. 分析导入映射并收集依赖 + +`localCdnPlugin`会分析提供的`importMapConfig`和默认的导入映射,识别所有需要本地化的CDN依赖。这个过程包括: + +- 解析CDN URL获取包名、版本和文件路径 +- 合并用户配置和默认配置 +- 生成文件复制任务列表 + +### 2. 判断和安装依赖包 + +插件会检查所需的依赖包是否已在本地存在,对于不存在或版本不匹配的包,会通过`installPackageTemporary`函数临时安装到指定目录。 + +### 3. 替换环境变量 + +通过创建`createEnvReplacementPlugin`插件,将环境变量中的CDN域名替换为本地路径,确保应用在构建时使用本地资源而不是远程CDN。 + +### 4. 复制并转换文件 + +对于已识别的CDN依赖,插件会: + +- 复制所需文件到目标CDN目录 +- 对JavaScript文件进行路径替换转换 (比如: `import push from './push'` 需要改成 `import push from './push.js'`) +- 保持目录结构与原始CDN一致 + +### 5. 处理Bundle文件 + +`copyBundleDeps`功能专门处理bundle文件中的CDN链接: + +- 提取bundle文件中的CDN链接 +- 替换为本地路径 +- 生成新的bundle文件 + +## 注意事项 + +1. 确保所有需要本地化的CDN依赖在package.json中有相应的版本定义 +2. 本地化CDN会增加构建输出的大小,但会提高应用的可靠性和性能 +3. 某些特定格式的CDN URL可能需要在`copyConfig`中进行特别配置 \ No newline at end of file diff --git a/packages/build/vite-config/package.json b/packages/build/vite-config/package.json index 6d7ed05bed..c02b2c0765 100644 --- a/packages/build/vite-config/package.json +++ b/packages/build/vite-config/package.json @@ -31,6 +31,7 @@ "esbuild-plugin-copy": "^2.1.1", "eslint": "^8.38.0", "eslint-plugin-vue": "^8.0.0", + "fast-glob": "^3.3.2", "fs-extra": "^10.1.0", "husky": "^8.0.0", "lerna": "^7.2.0", diff --git a/packages/build/vite-config/src/default-config.js b/packages/build/vite-config/src/default-config.js index fd6ffba938..42ca09344a 100644 --- a/packages/build/vite-config/src/default-config.js +++ b/packages/build/vite-config/src/default-config.js @@ -10,7 +10,7 @@ import esbuildCopy from 'esbuild-plugin-copy' import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' import visualizerCjs from 'rollup-plugin-visualizer' import generateComment from '@opentiny/tiny-engine-vite-plugin-meta-comments' -import { getBaseUrlFromCli, copyBundleDeps, copyPreviewImportMap } from './localCdnFile/index.js' +import { getBaseUrlFromCli, copyBundleDeps, localCdnPlugin } from './localCdnFile/index.js' import { devAliasPlugin } from './vite-plugins/devAliasPlugin.js' import { htmlUpgradeHttpsPlugin } from './vite-plugins/upgradeHttpsPlugin.js' import { canvasDevExternal } from './canvas-dev-external.js' @@ -160,22 +160,26 @@ export function useTinyEngineBaseConfig(engineConfig) { originCdnPrefix: VITE_CDN_DOMAIN, // mock 中bundle的域名当前和环境的VITE_CDN_DOMAIN一致 base: getBaseUrlFromCli(config.base) }).plugin(command === 'serve') - : [], - isLocalImportMap - ? copyPreviewImportMap({ - // FIXME: 相对路径需要修正 - importMapJson: './src/preview/src/preview/importMap.json', - targetImportMapJson: 'preview-import-map-static/preview-importmap.json', - originCdnPrefix: VITE_CDN_DOMAIN, - base: getBaseUrlFromCli(config.base), - packageCopyLib: [ - // 以下的js存在相对路径引用,不能单独拷贝一个文件,需要整个包拷贝 - '@vue/devtools-api' - ] - }) : [] ) + // 添加本地化CDN插件支持 + if (isLocalImportMap) { + const logger = console + logger.log('[local-cdn-plugin]: Initializing local CDN plugin') + + const cdnPlugins = localCdnPlugin({ + importMapConfig: engineConfig.importMapConfig, + base: getBaseUrlFromCli(config.base), + cdnDir: 'local-cdn-static', + copyConfig: engineConfig.copyConfig + }) + + if (cdnPlugins && cdnPlugins.length > 0) { + config.plugins.push(...cdnPlugins) + } + } + config.plugins.push(devAliasPlugin(env, engineConfig.useSourceAlias)) if (engineConfig.useSourceAlias && command === 'serve') { diff --git a/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js b/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js index 7ee965b519..a4c023d1f8 100644 --- a/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js +++ b/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js @@ -13,29 +13,30 @@ import { const { readJsonSync } = fs export function extraBundleCdnLink(filename, originCdnPrefix) { - const result = [] + const result = new Set() const bundle = readJsonSync(filename) - bundle.data?.materials?.components?.forEach((component) => { - if (component.npm) { - const possibleUrl = [component.npm.script, component.npm.css] + bundle.data?.materials?.packages?.forEach((packageItem) => { + if (packageItem) { + const possibleUrl = [packageItem.script, packageItem.css] possibleUrl.forEach((url) => { - if (url?.startsWith(originCdnPrefix) && !result.includes(url)) { - result.push(url) + if (url?.startsWith(originCdnPrefix)) { + result.add(url) } }) } }) - return result + + return [...result] } export function replaceBundleCdnLink(bundle, fileMap) { - bundle.data?.materials?.components?.forEach((component) => { - if (component.npm) { + bundle.data?.materials?.packages?.forEach((packageItem) => { + if (packageItem) { const possibleUrl = ['script', 'css'] possibleUrl.forEach((key) => { - const matchRule = fileMap.find((rule) => component.npm[key] === rule.originUrl) + const matchRule = fileMap.find((rule) => packageItem[key] === rule.originUrl) if (matchRule) { - component.npm[key] = matchRule.newUrl + packageItem[key] = matchRule.newUrl } }) } @@ -50,9 +51,10 @@ export function copyBundleDeps({ dir = 'material-static', bundleTempDir = 'bundle-deps/material-static' }) { - const cdnFiles = extraBundleCdnLink(bundleFile, originCdnPrefix).map((url) => - getCdnPathNpmInfoForSingleFile(url, originCdnPrefix, base, dir, false, bundleTempDir) - ) + const cdnFiles = extraBundleCdnLink(bundleFile, originCdnPrefix) + .map((url) => getCdnPathNpmInfoForSingleFile(url, originCdnPrefix, base, dir, false, bundleTempDir)) + .filter(Boolean) + const { packages: packageNeedToInstall, files } = getPackageNeedToInstallAndFilesUsingSameVersion(cdnFiles) const plugin = (isDev) => { diff --git a/packages/build/vite-config/src/localCdnFile/copyImportMap.js b/packages/build/vite-config/src/localCdnFile/copyImportMap.js index 476d204854..74b0c309fd 100644 --- a/packages/build/vite-config/src/localCdnFile/copyImportMap.js +++ b/packages/build/vite-config/src/localCdnFile/copyImportMap.js @@ -26,9 +26,11 @@ export const copyLocalImportMap = ({ } return getCdnPathNpmInfoForSingleFile(libPath, originCdnPrefix, base, dir, false, bundleTempDir) }) + .filter(Boolean) const styleFiles = styleUrls .filter((styleUrl) => styleUrl.startsWith(originCdnPrefix)) .map((url) => getCdnPathNpmInfoForSingleFile(url, originCdnPrefix, base, dir, false), bundleTempDir) + .filter(Boolean) const { packages: packageNeedToInstall, files } = getPackageNeedToInstallAndFilesUsingSameVersion( importMapFiles.concat(styleFiles) diff --git a/packages/build/vite-config/src/localCdnFile/index.js b/packages/build/vite-config/src/localCdnFile/index.js index daa97291e0..c9f0dca49d 100644 --- a/packages/build/vite-config/src/localCdnFile/index.js +++ b/packages/build/vite-config/src/localCdnFile/index.js @@ -4,3 +4,4 @@ export * from './copyPreviewImportMap.js' export * from './utils.js' export * from './locateCdnNpmInfo.js' export * from './replaceImportPath.mjs' +export * from './localCdnPlugin.js' diff --git a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js new file mode 100644 index 0000000000..c4a928475b --- /dev/null +++ b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js @@ -0,0 +1,378 @@ +import path from 'node:path' +import fs from 'fs-extra' +import fg from 'fast-glob' +import { dedupeCopyFiles } from './locateCdnNpmInfo.js' +import { installPackageTemporary } from '../vite-plugins/installPackageTemporary.js' +import { babelReplaceImportPathWithCertainFileName } from './replaceImportPath.mjs' + +const logger = console + +const defaultCopyConfig = { + '@opentiny/vue-theme': { + filePathInPackage: '/' + }, + '@opentiny/vue-renderless': { + filePathInPackage: '/' + }, + '@opentiny/vue-runtime': { + filePathInPackage: '/dist3/' + }, + '@vue/devtools-api': { + filePathInPackage: '/' + } +} + +/** + * 对文件内容进行转换处理 + * @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 +} + +/** + * 从importMapUrl字符串中提取包名、版本和文件路径 + * @param {string} str - 导入字符串 + * @returns {Object} - 提取的信息对象 + * @returns {string} packageName - 包名 + * @returns {string} version - 版本 + * @returns {string} filePathInPackage - 包内文件路径 + * */ +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) + } +} + +/** + * 创建环境变量替换插件 + * @param {string} cdnDir - 本地CDN目录名 + * @returns {Object} - Vite插件对象 + */ +function createEnvReplacementPlugin(cdnDir) { + return { + name: 'vite-replace-cdn-env', + config(config) { + // 在构建时替换环境变量,将CDN域名替换为本地路径 + if (!config.define) { + config.define = {} + } + + 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') + } + } +} + +/** + * 复制文件或目录到目标路径 + * @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() + + // 为每个目标路径执行复制 + for (const destPath of destPaths) { + const fullDestPath = path.resolve(outDir, destPath) + + try { + // 确保目标目录存在 + await fs.ensureDir(path.dirname(fullDestPath)) + + logger.log(`[vite-cdn-copy-plugin]: Copying from ${srcPath} to ${fullDestPath}`) + + if (isDirectory) { + // 如果是目录,使用 fast-glob 遍历所有文件并处理 + logger.log(`[vite-cdn-copy-plugin]: Copying directory recursively: ${srcPath} -> ${fullDestPath}`) + + // 确保目标路径存在 + await fs.ensureDir(fullDestPath) + + // 使用绝对路径 + const absoluteSrcPath = path.resolve(process.cwd(), srcPath) + + // 使用 fast-glob 查找所有文件 + 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 = transform(content, file) + + // 写入转换后的内容 + await fs.writeFile(destFilePath, transformedContent) + } + } 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 = transform(content, srcPath) + + // 写入转换后的内容 + await fs.writeFile(finalDestPath, transformedContent) + } + + logger.log(`[vite-cdn-copy-plugin]: Successfully copied: ${srcPath} -> ${fullDestPath}`) + } catch (err) { + logger.error(`[vite-cdn-copy-plugin]: Failed to copy ${srcPath} to ${fullDestPath}`, err) + } + } +} + +/** + * 创建复制插件 + * @param {Array} targets - 复制目标配置数组 + * @param {string|Array} targets[].src - 源文件路径或路径数组 + * @param {string|Array} targets[].dest - 目标文件路径或路径数组 + * @returns {Object} Vite插件对象 + */ + +function copyPlugin(targets) { + let resolvedConfig = null + let copiedFiles = new Set() + + return { + name: 'vite-cdn-copy-plugin', + configResolved(getResolvedConfig) { + resolvedConfig = getResolvedConfig + }, + async writeBundle() { + if (!targets || !targets.length) { + return + } + + const outDir = resolvedConfig.build.outDir || 'dist' + + logger.log('[vite-cdn-copy-plugin]: Start copying files to dist directory') + + // 遍历所有复制目标 + for (const target of targets) { + const { src, dest } = target + + if (!src || !dest) { + logger.warn('[vite-cdn-copy-plugin]: Skipping target with missing src or dest', target) + continue + } + + // 处理源路径,支持数组形式 + // const srcPaths = (Array.isArray(src) ? src : [src]).map(item => path.resolve(process.cwd(), item)) + const srcPaths = Array.isArray(src) ? src : [src] + // 处理目标路径,支持数组形式 + const destPaths = Array.isArray(dest) ? dest : [dest] + + for (const srcPath of srcPaths) { + await copyFileOrDirectory(srcPath, destPaths, copiedFiles, outDir) + } + } + + logger.log('[vite-cdn-copy-plugin]: Finished copying files') + } + } +} + +/** + * 比较两个版本号是否相同 + * @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 +} + +function getCdnPathNpmInfo( + cdnDependencyItem, + base, // build构建的base(BASE_URL)参数 + cdnDir, // 复制到目标的文件目录 + tempDir = 'bundle-deps', // 新安装包的安装目录 + copyConfig = {} // 复制配置 +) { + let { packageName, version, filePathInPackage } = cdnDependencyItem + const originVersion = version + + if (copyConfig[packageName]) { + const { version: copyVersion, filePathInPackage: copyFilePathInPackage } = copyConfig[packageName] + + if (copyVersion) { + version = copyVersion + } + if (copyFilePathInPackage) { + filePathInPackage = copyFilePathInPackage + } + } + + let isFolder = filePathInPackage.endsWith('/') + let src = `node_modules/${packageName}${filePathInPackage}` + const pkgFilePath = `node_modules/${packageName}/package.json` + let isSameVersion = false + + if (fs.existsSync(pkgFilePath)) { + try { + const pkg = JSON.parse(fs.readFileSync(path.resolve(pkgFilePath))) + isSameVersion = compareIsSameVersion(version, pkg.version) + } catch (error) { + // ignore + } + } + // 只有包存在 且 版本号一致 才认为源文件存在 + const sourceExist = fs.existsSync(path.resolve(src)) && isSameVersion + + if (sourceExist) { + const stat = fs.statSync(path.resolve(src)) + if (stat.isDirectory()) { + isFolder = true + } + } else { + src = tempDir + '/' + src + } + + const destPackageDir = `${cdnDir}/${packageName}@${originVersion}` + const destFullPath = `${destPackageDir}${filePathInPackage}` + const destFullPathWithoutTailSlash = destFullPath + const dest = destFullPathWithoutTailSlash + let destDir = dest + + // 不是文件夹,则取文件所在目录 + if (!isFolder) { + destDir = path.dirname(destFullPathWithoutTailSlash) + } + + return { + src, + packageName, + version, + sourceExist, + dest: destDir + } +} + +/** + * 创建本地化CDN插件 + * @param {Object} options - 插件配置选项 + * @returns {Array} - Vite插件数组 + */ +export function localCdnPlugin({ + importMapConfig = { imports: {} }, + base = './', + cdnDir = 'local-cdn-static', // 构建目录中的CDN文件夹名称 + bundleTempDir = 'bundle-deps/local-cdn', // 临时存放下载的包的目录 + copyConfig = {} +}) { + const defaultImportMapConfig = JSON.parse( + fs.readFileSync(path.resolve(process.cwd(), './node_modules/@opentiny/tiny-engine/dist/import-map.json'), 'utf-8') + ) + 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] + + if (combinedImportMapConfig.length === 0) { + logger.warn('[local-cdn-plugin]: No CDN dependencies found or configured') + return [] + } + const combinedCopyConfig = { ...defaultCopyConfig, ...copyConfig } + + // 处理每个CDN URL,获取复制信息 + const cdnFiles = combinedImportMapConfig.map((cdnDependencyItem) => + getCdnPathNpmInfo(cdnDependencyItem, base, cdnDir, bundleTempDir, combinedCopyConfig) + ) + + // 获取需要安装的包列表和文件列表 + const packageNeedToInstall = cdnFiles + .filter((item) => !item.sourceExist) + .map(({ packageName, version }) => ({ packageName, version })) + .reduce((acc, cur) => { + // 同个包避免多个版本只保留一个版本 + if (!acc.some(({ packageName }) => cur.packageName === packageName)) { + acc.push(cur) + } + return acc + }, []) + + // 日志一下将要处理的内容 + logger.log( + `[local-cdn-plugin]: Processing ${combinedImportMapConfig.length} CDN dependencies to local directory: ${cdnDir}` + ) + logger.log(`[local-cdn-plugin]: Need to install ${packageNeedToInstall.length} packages`) + + const targetFiles = dedupeCopyFiles(cdnFiles) + // 返回插件数组 + return [ + // 创建环境变量替换插件,替换CDN域名为本地路径 + createEnvReplacementPlugin(cdnDir), + // 安装需要的包 + ...installPackageTemporary(packageNeedToInstall, bundleTempDir), + // 使用自定义的copyPlugin替代直接调用copy + copyPlugin(targetFiles) + ] +} diff --git a/packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js b/packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js index 2477809f44..c345678fbf 100644 --- a/packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js +++ b/packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js @@ -48,9 +48,30 @@ export function getCdnPathNpmInfoForSingleFile( tempDir = 'bundle-deps' // 新安装包的安装目录 ) { const baseSlash = base.endsWith('/') ? '' : '/' - const { packageName, versionDemand, filePathInPackage } = url.match( - new RegExp(`^${originCdnPrefix}/?(?.+?)@(?[^/]+)(?.*?)$`) - ).groups + + // 分别匹配 unpkg 和 npmmirror 格式 + let unpkgMatch = null + let npmmirrorMatch = null + + try { + unpkgMatch = url.match( + new RegExp(`^${originCdnPrefix}/?(?.+?)@(?[^/]+)(?.*?)$`) + ) + npmmirrorMatch = url.match( + new RegExp(`^${originCdnPrefix}/?(?.+?)/(?[^/]+)/files(?.*?)$`) + ) + } catch (error) { + // ignore + } + + // 使用匹配到的结果 + const match = npmmirrorMatch || unpkgMatch + + if (!match) { + return null + } + + const { packageName, versionDemand, filePathInPackage } = match.groups let version = versionDemand let isFolder = filePathInPackage.endsWith('/') let src = replaceTailSlash(`node_modules/${packageName}${filePathInPackage}`) @@ -111,9 +132,22 @@ export function getCdnPathNpmInfoForPackage( tempDir = 'bundle-deps' // 新安装包的安装目录 ) { const baseSlash = base.endsWith('/') ? '' : '/' - const { packageName, versionDemand, filePathInPackage } = url.match( + + // 分别匹配 unpkg 和 npmmirror 格式 + const unpkgMatch = url.match( new RegExp(`^${originCdnPrefix}/?(?.+?)@(?[^/]+)(?.*?)$`) - ).groups + ) + const npmmirrorMatch = url.match( + new RegExp(`^${originCdnPrefix}/?(?.+?)/(?[^/]+)/files(?.*?)$`) + ) + + // 使用匹配到的结果 + const match = npmmirrorMatch || unpkgMatch + if (!match) { + return null + } + + const { packageName, versionDemand, filePathInPackage } = match.groups let version = versionDemand let src = `node_modules/${packageName}` const sourceExist = fs.existsSync(path.resolve(src)) diff --git a/packages/build/vite-config/src/localCdnFile/replaceImportPath.mjs b/packages/build/vite-config/src/localCdnFile/replaceImportPath.mjs index 877945d54b..afe341f36d 100644 --- a/packages/build/vite-config/src/localCdnFile/replaceImportPath.mjs +++ b/packages/build/vite-config/src/localCdnFile/replaceImportPath.mjs @@ -6,10 +6,21 @@ import generatePkg from '@babel/generator' const traverse = traversePkg.default const generate = generatePkg.default +/** + * 将相对路径转换为以'./'开头的格式,并确保使用Unix风格的路径分隔符 + * @param {string} relativePath - 相对路径 + * @returns {string} 转换后的相对路径 + */ export function relativePathPattern(relativePath) { return './' + (path.sep === '/' ? relativePath : relativePath.replace(/\\/g, '/')) } +/** + * 根据导入路径和当前文件路径,尝试找到实际的文件路径 + * @param {string} importPath - 导入路径 + * @param {string} currentFilePath - 当前文件的完整路径 + * @returns {string|null} 返回解析后的路径,如果找不到则返回null + */ export function resolvePath(importPath, currentFilePath) { if (['js', 'mjs'].some((suffix) => importPath.endsWith(suffix))) { // 文件名已经带有.js,.mjs后缀 @@ -47,7 +58,14 @@ export function resolvePath(importPath, currentFilePath) { return null } -// babel 替换 js的相对地址引用为确定文件后缀 +/** + * 使用Babel替换JavaScript文件中的相对路径引用为确定的文件后缀 + * @param {string} content - 要处理的代码内容 + * @param {string} currentFilePath - 当前文件的完整路径 + * @param {Console} [logger=console] - 用于记录警告和错误的Logger对象 + * @returns {{code: string|null, success: Array<{before: string, after: string}>, error: Array}} + * 包含处理后的代码、成功替换的路径和失败的路径 + */ export function babelReplaceImportPathWithCertainFileName(content, currentFilePath, logger = console) { let fileChangedMark = false let result = { diff --git a/packages/canvas/DesignCanvas/src/DesignCanvas.vue b/packages/canvas/DesignCanvas/src/DesignCanvas.vue index 01fab5a399..217cefd006 100644 --- a/packages/canvas/DesignCanvas/src/DesignCanvas.vue +++ b/packages/canvas/DesignCanvas/src/DesignCanvas.vue @@ -78,7 +78,7 @@ export default { return } - const { importMap, importStyles } = getImportMapData(getMergeMeta('engine.config')?.importMapVersion, deps) + const { importMap, importStyles } = getImportMapData(deps) canvasSrcDoc.value = initCanvas(importMap, importStyles).html } diff --git a/packages/canvas/DesignCanvas/src/importMap.ts b/packages/canvas/DesignCanvas/src/importMap.ts index 60393b2752..8674888328 100644 --- a/packages/canvas/DesignCanvas/src/importMap.ts +++ b/packages/canvas/DesignCanvas/src/importMap.ts @@ -1,38 +1,48 @@ -import { VITE_CDN_DOMAIN, VITE_CDN_TYPE } from '@opentiny/tiny-engine-common/js/environments' - -export function getImportMapData(overrideVersions = {}, canvasDeps = { scripts: [], styles: [] }) { - const importMapVersions = Object.assign( - { - vue: '3.4.23', - tinyVue: '~3.20', - vueI18n: '^9.9.0' - }, - overrideVersions - ) +import { useEnv, getMergeMeta } from '@opentiny/tiny-engine-meta-register' +import { importMapConfig } from '@opentiny/tiny-engine-common/js/importMap/index' +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) + } +} + +export function getImportMapData(canvasDeps = { scripts: [], styles: [] }) { // 以下内容由于区块WebComponent加载需要补充 const blockRequire = { imports: { - '@opentiny/vue': `${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}${importMapVersions.tinyVue}${fileDelimiter}/dist3/tiny-vue-pc.mjs`, - '@opentiny/vue-icon': `${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}${importMapVersions.tinyVue}${fileDelimiter}/dist3/tiny-vue-icon.mjs`, - 'element-plus': `${VITE_CDN_DOMAIN}/element-plus${versionDelimiter}2.4.2${fileDelimiter}/dist/index.full.mjs`, - '@opentiny/tiny-engine-builtin-component': `${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-builtin-component${versionDelimiter}^2.0.0${fileDelimiter}/dist/index.mjs` + // 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: [ - `${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: [] } // 以下内容由于物料协议不支持声明子依赖而@opentiny/vue需要依赖所以需要补充 + // TODO: 删除 const tinyVueRequire = { imports: { - '@opentiny/vue-common': `${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}${importMapVersions.tinyVue}${fileDelimiter}/dist3/tiny-vue-common.mjs`, - '@opentiny/vue-locale': `${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}${importMapVersions.tinyVue}${fileDelimiter}/dist3/tiny-vue-locale.mjs`, - echarts: `${VITE_CDN_DOMAIN}/echarts${versionDelimiter}5.4.1${fileDelimiter}/dist/echarts.esm.js` + '@opentiny/vue-common': getImportUrl('@opentiny/vue-common'), + '@opentiny/vue-locale': getImportUrl('@opentiny/vue-locale'), + echarts: getImportUrl('echarts') } } @@ -46,8 +56,8 @@ export function getImportMapData(overrideVersions = {}, canvasDeps = { scripts: const importMap = { imports: { - vue: `${VITE_CDN_DOMAIN}/vue${versionDelimiter}${importMapVersions.vue}${fileDelimiter}/dist/vue.runtime.esm-browser.prod.js`, - 'vue-i18n': `${VITE_CDN_DOMAIN}/vue-i18n${versionDelimiter}${importMapVersions.vueI18n}${fileDelimiter}/dist/vue-i18n.esm-browser.js`, + vue: getImportUrl('vue'), + 'vue-i18n': getImportUrl('vue-i18n'), ...blockRequire.imports, ...tinyVueRequire.imports, ...materialsAndUtilsRequire diff --git a/packages/design-core/src/preview/src/preview/importMap.json b/packages/common/js/importMap/import-map.json similarity index 76% rename from packages/design-core/src/preview/src/preview/importMap.json rename to packages/common/js/importMap/import-map.json index fd90ca3efc..a10d4b9c3b 100644 --- a/packages/design-core/src/preview/src/preview/importMap.json +++ b/packages/common/js/importMap/import-map.json @@ -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", "vue/server-renderer": "${VITE_CDN_DOMAIN}/@vue/server-renderer${versionDelimiter}3.4.23${fileDelimiter}/dist/server-renderer.esm-browser.js", - "vue-i18n": "${VITE_CDN_DOMAIN}/vue-i18n${versionDelimiter}9.2.0-beta.36${fileDelimiter}/dist/vue-i18n.esm-browser.js", + "vue-i18n": "${VITE_CDN_DOMAIN}/vue-i18n${versionDelimiter}^9.9.0${fileDelimiter}/dist/vue-i18n.esm-browser.js", "vue-router": "${VITE_CDN_DOMAIN}/vue-router${versionDelimiter}4.0.16${fileDelimiter}/dist/vue-router.esm-browser.js", "@vue/devtools-api": "${VITE_CDN_DOMAIN}/@vue/devtools-api${versionDelimiter}6.5.1${fileDelimiter}/lib/esm/index.js", "@vueuse/core": "${VITE_CDN_DOMAIN}/@vueuse/core${versionDelimiter}9.6.0${fileDelimiter}/index.mjs", @@ -13,11 +13,11 @@ "@opentiny/tiny-engine-builtin-component": "${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-builtin-component${versionDelimiter}^2.0.0${fileDelimiter}/dist/index.mjs", "vue-demi": "${VITE_CDN_DOMAIN}/vue-demi${versionDelimiter}0.13.11${fileDelimiter}/lib/index.mjs", "pinia": "${VITE_CDN_DOMAIN}/pinia${versionDelimiter}2.0.22${fileDelimiter}/dist/pinia.esm-browser.js", - "@opentiny/vue": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}${opentinyVueVersion}${fileDelimiter}/dist3/tiny-vue-pc.mjs", - "@opentiny/vue-icon": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}${opentinyVueVersion}${fileDelimiter}/dist3/tiny-vue-icon.mjs", - "@opentiny/vue-common": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}${opentinyVueVersion}${fileDelimiter}/dist3/tiny-vue-common.mjs", - "@opentiny/vue-locale": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}${opentinyVueVersion}${fileDelimiter}/dist3/tiny-vue-locale.mjs", - "@opentiny/vue-renderless/": "${VITE_CDN_DOMAIN}/@opentiny/vue-renderless${versionDelimiter}${opentinyVueVersion}${fileDelimiter}/", + "@opentiny/vue": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-pc.mjs", + "@opentiny/vue-icon": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-icon.mjs", + "@opentiny/vue-common": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-common.mjs", + "@opentiny/vue-locale": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-locale.mjs", + "@opentiny/vue-renderless/": "${VITE_CDN_DOMAIN}/@opentiny/vue-renderless${versionDelimiter}~3.20${fileDelimiter}/", "echarts": "${VITE_CDN_DOMAIN}/echarts${versionDelimiter}5.4.1${fileDelimiter}/dist/echarts.esm.js" } } diff --git a/packages/common/js/importMap/index.js b/packages/common/js/importMap/index.js new file mode 100644 index 0000000000..844734b797 --- /dev/null +++ b/packages/common/js/importMap/index.js @@ -0,0 +1 @@ +export { default as importMapConfig } from './import-map.json' diff --git a/packages/common/package.json b/packages/common/package.json index c8b795e9b8..5f9fe7f840 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -52,7 +52,8 @@ "@vitejs/plugin-vue": "^5.1.2", "@vitejs/plugin-vue-jsx": "^4.0.1", "glob": "^10.3.4", - "vite": "^5.4.2" + "vite": "^5.4.2", + "vite-plugin-static-copy": "^1.0.6" }, "peerDependencies": { "@opentiny/vue": "^3.20.0", diff --git a/packages/common/vite.config.ts b/packages/common/vite.config.ts index 49ecd7e4cb..3c42c52aba 100644 --- a/packages/common/vite.config.ts +++ b/packages/common/vite.config.ts @@ -17,6 +17,7 @@ import vueJsx from '@vitejs/plugin-vue-jsx' import { glob } from 'glob' import { fileURLToPath } from 'node:url' import generateComments from '@opentiny/tiny-engine-vite-plugin-meta-comments' +import { viteStaticCopy } from 'vite-plugin-static-copy' const jsEntries = glob.sync('./js/**/*.js').map((file) => { return [file.slice(0, file.length - path.extname(file).length), fileURLToPath(new URL(file, import.meta.url))] @@ -24,7 +25,20 @@ const jsEntries = glob.sync('./js/**/*.js').map((file) => { // https://vitejs.dev/config/ export default defineConfig({ - plugins: [generateComments(), vue(), vueJsx()], + plugins: [ + generateComments(), + vue(), + vueJsx(), + // 复制 import-map.json到产物,提供给构建插件读取 + viteStaticCopy({ + targets: [ + { + src: './js/importMap/import-map.json', + dest: '.' + } + ] + }) + ], publicDir: false, resolve: {}, base: './', diff --git a/packages/design-core/package.json b/packages/design-core/package.json index 98e296d954..daae2ed6d9 100644 --- a/packages/design-core/package.json +++ b/packages/design-core/package.json @@ -116,7 +116,8 @@ "less": "^4.1.2", "lint-staged": "^13.2.0", "rollup-plugin-polyfill-node": "^0.13.0", - "vite": "^5.4.2" + "vite": "^5.4.2", + "vite-plugin-static-copy": "^1.0.6" }, "peerDependencies": { "@opentiny/vue": "^3.20.0", diff --git a/packages/design-core/src/preview/src/preview/importMap.js b/packages/design-core/src/preview/src/preview/importMap.js index 11e0d70a83..b6a282d634 100644 --- a/packages/design-core/src/preview/src/preview/importMap.js +++ b/packages/design-core/src/preview/src/preview/importMap.js @@ -11,7 +11,7 @@ */ import { useEnv } from '@opentiny/tiny-engine-meta-register' -import importMapJSON from './importMap.json' +import { importMapConfig as importMapJSON } from '@opentiny/tiny-engine-common/js/importMap/index' const importMap = {} diff --git a/packages/design-core/vite.config.js b/packages/design-core/vite.config.js index d39cfe09f5..b10dec2713 100644 --- a/packages/design-core/vite.config.js +++ b/packages/design-core/vite.config.js @@ -6,6 +6,7 @@ import nodeGlobalsPolyfillPluginCjs from '@esbuild-plugins/node-globals-polyfill import nodeModulesPolyfillPluginCjs from '@esbuild-plugins/node-modules-polyfill' import nodePolyfill from 'rollup-plugin-polyfill-node' import { fileURLToPath } from 'node:url' +import { viteStaticCopy } from 'vite-plugin-static-copy' const nodeGlobalsPolyfillPlugin = nodeGlobalsPolyfillPluginCjs.default const nodeModulesPolyfillPlugin = nodeModulesPolyfillPluginCjs.default @@ -31,7 +32,19 @@ const addViteIgnorePlugin = () => { } export default defineConfig({ - plugins: [vue(), vueJsx()], + plugins: [ + vue(), + vueJsx(), + // 复制 import-map.json到产物,提供给构建插件读取 + viteStaticCopy({ + targets: [ + { + src: './node_modules/@opentiny/tiny-engine-common/dist/import-map.json', + dest: '.' + } + ] + }) + ], publicDir: false, optimizeDeps: { esbuildOptions: { From b7a230d4c03b2fcccaaaa93eed90185928172885 Mon Sep 17 00:00:00 2001 From: chilingling Date: Mon, 24 Mar 2025 23:53:51 +0800 Subject: [PATCH 02/27] fix: remove irrelevant code --- designer-demo/env/.env.development | 2 +- designer-demo/public/mock/bundle.json | 23 ----------------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/designer-demo/env/.env.development b/designer-demo/env/.env.development index 709f9394ab..67d094d8c6 100644 --- a/designer-demo/env/.env.development +++ b/designer-demo/env/.env.development @@ -7,4 +7,4 @@ VITE_CDN_TYPE=npmmirror VITE_LOCAL_IMPORT_MAPS=false VITE_LOCAL_BUNDLE_DEPS=false # request data via alpha service -VITE_ORIGIN=http://localhost:9090 +# VITE_ORIGIN= diff --git a/designer-demo/public/mock/bundle.json b/designer-demo/public/mock/bundle.json index 1ea0de5fd5..4ce436270e 100644 --- a/designer-demo/public/mock/bundle.json +++ b/designer-demo/public/mock/bundle.json @@ -9958,29 +9958,6 @@ } ], "events": { - "onEditClosed": { - "label": { - "zh_CN": "单元格编辑状态下被关闭时会触发该事件" - }, - "description": { - "zh_CN": "" - }, - "type": "event", - "functionInfo": { - "params": [ - { - "name": "table", - "type": "Object", - "defaultValue": "", - "description": { - "zh_CN": "{$table,filters} 包含 table 实例对象和过滤条件的对象" - } - } - ], - "returns": {} - }, - "defaultValue": "function onClick(e) {}" - }, "onFilterChange": { "label": { "zh_CN": "筛选条件改变时触发改事件" From 00bbf7bef551cefa4759573026c59214d91079ed Mon Sep 17 00:00:00 2001 From: chilingling Date: Tue, 25 Mar 2025 09:02:23 +0800 Subject: [PATCH 03/27] fix: change config layer --- docs/solutions/local-cdn.md | 27 ++++++++++--------- .../build/vite-config/src/default-config.js | 5 ++-- .../src/localCdnFile/localCdnPlugin.js | 18 +++++++++---- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/docs/solutions/local-cdn.md b/docs/solutions/local-cdn.md index 3837c80902..5db2d96696 100644 --- a/docs/solutions/local-cdn.md +++ b/docs/solutions/local-cdn.md @@ -30,8 +30,10 @@ VITE_LOCAL_BUNDLE_DEPS=true ```javascript const baseConfig = useTinyEngineBaseConfig({ - importMapConfig: { imports: { ... } }, - copyConfig: { ... } + localCdnConfig: { + importMap: { imports: { ... } }, + copy: { ... } + } // ...otherConfig }) ``` @@ -42,12 +44,13 @@ CDN 本地化接受以下配置选项: | 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| -| importMapConfig | Object | `{ imports: {} }` | 导入映射配置,定义需要本地化的CDN依赖 | -| copyConfig | Object | `{}` | 自定义复制配置,可以覆盖特定包的默认配置 | +| localCdnConfig | Object | `{ importMap: { imports: {} }, copy: {} }` | 本地CDN配置对象 | +| localCdnConfig.importMap | Object | `{ imports: {} }` | 导入映射配置,定义需要本地化的CDN依赖 | +| localCdnConfig.copy | Object | `{}` | 自定义复制配置,可以覆盖特定包的默认配置 | -#### importMapConfig 详细说明 +#### importMap 详细说明 -`importMapConfig` 是一个包含 `imports` 属性的对象,它定义了需要本地化的CDN依赖。在插件内部,它会与默认的导入映射配置合并。 +`localCdnConfig.importMap` 是一个包含 `imports` 属性的对象,它定义了需要本地化的CDN依赖。在插件内部,它会与默认的导入映射配置合并。 import-map.json 的格式示例: ```json @@ -67,14 +70,14 @@ URL格式说明: 插件将解析这些URL,提取包名、版本和文件路径,然后在构建时将它们替换为本地路径。 -**重要说明**:如果您在 Vite 配置中传递了 `importMapConfig`,还需要在 registry 注册表的 config 中传入同样的配置,以确保应用在运行时能正确读取自定义的 importMap 配置。例如: +**重要说明**:如果您在 Vite 配置中传递了 `localCdnConfig.importMap`,还需要在 registry 注册表的 config 中传入同样的配置,以确保应用在运行时能正确读取自定义的 importMap 配置。例如: ```javascript // 在注册表配置中 { config: { id: 'engine.config', - importMap: importMapConfig, + importMap: localCdnConfig.importMap, // ... 其他配置 } } @@ -82,9 +85,9 @@ URL格式说明: 这是因为画布和页面预览默认会从注册表 `getMergeMeta('engine.config')?.importMap` 中读取自定义的映射配置,如果获取失败,则会读取默认的映射。 -#### copyConfig 详细说明 +#### copy 详细说明 -`copyConfig` 是一个可选的配置对象,用于覆盖特定包的默认复制配置。它的结构是一个对象,键是包名,值是该包的复制配置。 +`localCdnConfig.copy` 是一个可选的配置对象,用于覆盖特定包的默认复制配置。它的结构是一个对象,键是包名,值是该包的复制配置。 默认配置如下: ```javascript @@ -167,7 +170,7 @@ VITE_CDN_DOMAIN=https://unpkg.com ### 1. 分析导入映射并收集依赖 -`localCdnPlugin`会分析提供的`importMapConfig`和默认的导入映射,识别所有需要本地化的CDN依赖。这个过程包括: +`localCdnPlugin`会分析提供的`localCdnConfig`和默认的导入映射,识别所有需要本地化的CDN依赖。这个过程包括: - 解析CDN URL获取包名、版本和文件路径 - 合并用户配置和默认配置 @@ -201,4 +204,4 @@ VITE_CDN_DOMAIN=https://unpkg.com 1. 确保所有需要本地化的CDN依赖在package.json中有相应的版本定义 2. 本地化CDN会增加构建输出的大小,但会提高应用的可靠性和性能 -3. 某些特定格式的CDN URL可能需要在`copyConfig`中进行特别配置 \ No newline at end of file +3. 某些特定格式的CDN URL可能需要在`copy`中进行特别配置 \ No newline at end of file diff --git a/packages/build/vite-config/src/default-config.js b/packages/build/vite-config/src/default-config.js index 42ca09344a..27aa7bcce0 100644 --- a/packages/build/vite-config/src/default-config.js +++ b/packages/build/vite-config/src/default-config.js @@ -169,10 +169,9 @@ export function useTinyEngineBaseConfig(engineConfig) { logger.log('[local-cdn-plugin]: Initializing local CDN plugin') const cdnPlugins = localCdnPlugin({ - importMapConfig: engineConfig.importMapConfig, + localCdnConfig: engineConfig.localCdnConfig, base: getBaseUrlFromCli(config.base), - cdnDir: 'local-cdn-static', - copyConfig: engineConfig.copyConfig + cdnDir: 'local-cdn-static' }) if (cdnPlugins && cdnPlugins.length > 0) { diff --git a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js index c4a928475b..84d0a3dad3 100644 --- a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js +++ b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js @@ -315,17 +315,25 @@ function getCdnPathNpmInfo( } /** - * 创建本地化CDN插件 - * @param {Object} options - 插件配置选项 + * 本地化CDN插件 + * @param {Object} options - 配置选项 + * @param {Object} options.localCdnConfig - 本地CDN配置 + * @param {Object} options.localCdnConfig.importMap - 导入映射配置,定义需要本地化的CDN依赖 + * @param {Object} options.localCdnConfig.copy - 自定义复制配置,可以覆盖特定包的默认配置 + * @param {string} options.base - 构建的base URL + * @param {string} options.cdnDir - 构建目录中的CDN文件夹名称 + * @param {string} options.bundleTempDir - 临时存放下载的包的目录 * @returns {Array} - Vite插件数组 */ export function localCdnPlugin({ - importMapConfig = { imports: {} }, + localCdnConfig = { importMap: { imports: {} }, copy: {} }, base = './', cdnDir = 'local-cdn-static', // 构建目录中的CDN文件夹名称 - bundleTempDir = 'bundle-deps/local-cdn', // 临时存放下载的包的目录 - copyConfig = {} + bundleTempDir = 'bundle-deps/local-cdn' // 临时存放下载的包的目录 }) { + const importMapConfig = localCdnConfig.importMap || { imports: {} } + const copyConfig = localCdnConfig.copy || {} + const defaultImportMapConfig = JSON.parse( fs.readFileSync(path.resolve(process.cwd(), './node_modules/@opentiny/tiny-engine/dist/import-map.json'), 'utf-8') ) From 38ea67d4450930025b42bf6c934f71b3ea596a45 Mon Sep 17 00:00:00 2001 From: chilingling Date: Tue, 25 Mar 2025 23:02:13 +0800 Subject: [PATCH 04/27] fix: add test case --- designer-demo/package.json | 7 +- designer-demo/tests/localCdnBasic.test.js | 88 +++++++++ .../tests/localCdnBundleDeps.test.js | 140 ++++++++++++++ .../tests/localCdnCustomConfig.test.js | 173 ++++++++++++++++++ designer-demo/tests/utils/envHelpers.js | 69 +++++++ designer-demo/vitest.config.js | 19 ++ 6 files changed, 494 insertions(+), 2 deletions(-) create mode 100644 designer-demo/tests/localCdnBasic.test.js create mode 100644 designer-demo/tests/localCdnBundleDeps.test.js create mode 100644 designer-demo/tests/localCdnCustomConfig.test.js create mode 100644 designer-demo/tests/utils/envHelpers.js create mode 100644 designer-demo/vitest.config.js diff --git a/designer-demo/package.json b/designer-demo/package.json index b201f06da9..11293a6191 100644 --- a/designer-demo/package.json +++ b/designer-demo/package.json @@ -6,7 +6,9 @@ "scripts": { "dev": "cross-env vite", "build:alpha": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build --mode alpha", - "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build" + "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { "@opentiny/tiny-engine": "workspace:^", @@ -25,6 +27,7 @@ "@opentiny/tiny-engine-vite-config": "workspace:^", "@vitejs/plugin-vue": "^5.1.2", "cross-env": "^7.0.3", - "vite": "^5.4.2" + "vite": "^5.4.2", + "vitest": "^3.0.9" } } diff --git a/designer-demo/tests/localCdnBasic.test.js b/designer-demo/tests/localCdnBasic.test.js new file mode 100644 index 0000000000..dd8a02a8ba --- /dev/null +++ b/designer-demo/tests/localCdnBasic.test.js @@ -0,0 +1,88 @@ +/** + * localCDN 功能测试 + * 这个测试文件用于验证 localCDN 本地化功能是否正常工作 + */ +import { describe, it, expect, beforeAll, afterAll } from 'vitest' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { execSync } from 'node:child_process' +import { ensureEnvVarEnabled, backupEnvFile, restoreEnvFile } from './utils/envHelpers.js' + +// 获取当前文件目录 +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const projectRoot = path.resolve(__dirname, '..') +const distDir = path.resolve(projectRoot, 'dist') +const localCdnDir = path.resolve(distDir, 'local-cdn-static') +const envAlphaPath = path.resolve(projectRoot, 'env', '.env.alpha') + +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) + }) + + it('应该在构建后生成 local-cdn-static 目录', () => { + expect(fs.existsSync(localCdnDir)).toBe(true) + }) + + it('应该正确复制 @vue/devtools-api 依赖', () => { + // 寻找 @vue/devtools-api 文件夹 + const devToolsDirs = fs.readdirSync(path.resolve(localCdnDir, '@vue')) + .find(dir => dir.startsWith('devtools-api@')) + + expect(devToolsDirs).toBeDefined() + + // 检查 index.js 是否存在 + const indexJsExists = fs.existsSync(path.resolve(localCdnDir, '@vue', devToolsDirs, 'lib/esm/index.js')) + + expect(indexJsExists).toBe(true) + }) + + it('应该正确复制 vue 依赖', () => { + // 寻找 vue 文件夹 + const runtimeDirs = fs.readdirSync(localCdnDir).find(dir => dir.startsWith('vue@')) + + expect(runtimeDirs).toBeDefined() + + const vueProdDist = path.resolve(localCdnDir, runtimeDirs, 'dist/vue.runtime.esm-browser.prod.js') + + expect(fs.existsSync(vueProdDist)).toBe(true) + }) + + 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') + }) +}) \ No newline at end of file diff --git a/designer-demo/tests/localCdnBundleDeps.test.js b/designer-demo/tests/localCdnBundleDeps.test.js new file mode 100644 index 0000000000..0afdb11de9 --- /dev/null +++ b/designer-demo/tests/localCdnBundleDeps.test.js @@ -0,0 +1,140 @@ +/** + * localCDN bundle依赖本地化测试 + * 测试物料需要的CDN资源本地化功能 + */ +import { describe, it, expect, beforeAll, afterAll } from 'vitest' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { execSync } from 'node:child_process' +import { ensureEnvVarEnabled, updateCdnDomain, backupEnvFile, restoreEnvFile } from './utils/envHelpers.js' + +// 获取当前文件目录 +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const projectRoot = path.resolve(__dirname, '..') +const publicDir = path.resolve(projectRoot, 'public') +const bundleJsonDir = path.resolve(publicDir, 'mock') +const envAlphaPath = path.resolve(projectRoot, 'env', '.env.alpha') +const distDir = path.resolve(projectRoot, 'dist') +const bundleJsonPath = path.resolve(bundleJsonDir, 'bundle.json') + +// 准备测试用的 bundle.json 文件 +const testBundleJson = { + data: { + materials: { + packages: [ + { + "name": "TinyVue组件库", + "package": "@opentiny/vue", + "version": "3.20.0", + "script": "https://unpkg.com/@opentiny/vue-runtime@3.20/dist3/tiny-vue-pc.mjs", + "css": "https://unpkg.com/@opentiny/vue-theme@3.20/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" + } + ] + } + } +} + +describe('localCDN bundle依赖本地化测试', () => { + let originalBundleJson = null + + beforeAll(() => { + // 备份环境变量 + backupEnvFile(envAlphaPath) + + // 确保目录存在 + if (!fs.existsSync(bundleJsonDir)) { + fs.mkdirSync(bundleJsonDir, { recursive: true }) + } + + // 备份原始的 bundle.json 文件(如果存在) + if (fs.existsSync(bundleJsonPath)) { + originalBundleJson = fs.readFileSync(bundleJsonPath, 'utf-8') + } + + // 创建测试用的 bundle.json + fs.writeFileSync(bundleJsonPath, JSON.stringify(testBundleJson, null, 2)) + + // 设置环境变量 + let envContent = fs.readFileSync(envAlphaPath, 'utf-8') + + // 更新CDN域名 + envContent = updateCdnDomain(envContent, 'https://unpkg.com') + + // 确保启用了 bundle 依赖本地化 + envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_DEPS') + + fs.writeFileSync(envAlphaPath, envContent) + + // 执行构建 + execSync('pnpm run build:alpha', { + cwd: projectRoot, + stdio: 'inherit' + }) + }) + + // 测试完成后清理测试文件并恢复环境变量 + afterAll(() => { + // 恢复原始的 bundle.json 文件 + if (originalBundleJson) { + fs.writeFileSync(bundleJsonPath, originalBundleJson) + } else if (fs.existsSync(bundleJsonPath)) { + // 如果原始文件不存在,则删除测试创建的文件 + fs.unlinkSync(bundleJsonPath) + } + + // 恢复环境变量 + restoreEnvFile(envAlphaPath) + }) + + it('应该将物料CDN依赖本地化', () => { + const distBundleJsonPath = path.resolve(distDir, 'mock', 'bundle.json') + + // 检查构建后的 bundle.json 是否存在 + expect(fs.existsSync(distBundleJsonPath)).toBe(true) + + // 检查构建后的 bundle.json 中是否已将远程URL替换为本地路径 + const packages = JSON.parse(fs.readFileSync(distBundleJsonPath, 'utf-8')).data.materials.packages + + // 检查 vue 的路径是否已本地化 + expect(packages[0].script).not.toContain('https://unpkg.com') + expect(packages[0].script).toContain('./material-static/') + + // 检查 element-plus 的路径是否已本地化 + expect(packages[1].script).toContain('https://registry.npmmirror.com') + expect(packages[1].script).not.toContain('./material-static/') + expect(packages[1].css).toContain('https://registry.npmmirror.com') + expect(packages[1].css).not.toContain('./material-static/') + }) + + it('应该将物料依赖包复制到产物CDN目录', () => { + const localCdnDir = path.resolve(distDir, 'material-static/@opentiny') + + // 检查 vue 是否已复制 + const tinyVueDir = fs.readdirSync(localCdnDir) + .find(dir => dir.startsWith('vue-runtime@')) + const tinyVueThemeDir = fs.readdirSync(localCdnDir) + .find(dir => dir.startsWith('vue-theme@')) + expect(tinyVueDir).toBeDefined() + + // 检查 tiny-vue-pc.mjs 是否存在 + const tinyVueJsPath = path.resolve(localCdnDir, tinyVueDir, 'dist3', 'tiny-vue-pc.mjs') + const tinyVueCssPath = path.resolve(localCdnDir, tinyVueThemeDir, 'index.css') + + expect(fs.existsSync(tinyVueJsPath)).toBe(true) + expect(fs.existsSync(tinyVueCssPath)).toBe(true) + + // 检查 element-plus 是否已复制 + const elementDir = fs.readdirSync(localCdnDir) + .find(dir => dir.startsWith('element-plus@')) + + expect(elementDir).not.toBeDefined() + }) +}) \ No newline at end of file diff --git a/designer-demo/tests/localCdnCustomConfig.test.js b/designer-demo/tests/localCdnCustomConfig.test.js new file mode 100644 index 0000000000..d33b6808b4 --- /dev/null +++ b/designer-demo/tests/localCdnCustomConfig.test.js @@ -0,0 +1,173 @@ +/** + * localCDN 自定义配置测试 + * 测试文档中描述的自定义配置功能 + */ +import { describe, it, expect, beforeAll, afterAll } from 'vitest' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { execSync } from 'node:child_process' +import { ensureEnvVarEnabled, backupEnvFile, restoreEnvFile } from './utils/envHelpers.js' + +// 获取当前文件目录 +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const projectRoot = path.resolve(__dirname, '..') +const viteConfigPath = path.resolve(projectRoot, 'vite.config.js') +const envAlphaPath = path.resolve(projectRoot, 'env', '.env.alpha') +const originalViteConfig = fs.readFileSync(viteConfigPath, 'utf-8') +const distDir = path.resolve(projectRoot, 'dist') + +/** + * 更新 registry.js 文件以支持自定义 importMap + */ +function updateRegistryFile() { + const registryPath = path.resolve(projectRoot, 'registry.js') + + if (!fs.existsSync(registryPath)) { + return + } + + // 备份原始文件 + 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) +} + +/** + * 恢复 registry.js 文件 + */ +function restoreRegistryFile() { + const registryPath = path.resolve(projectRoot, 'registry.js') + const backupPath = registryPath + '.bak' + + if (fs.existsSync(backupPath)) { + fs.copyFileSync(backupPath, registryPath) + fs.unlinkSync(backupPath) + } +} + +describe('localCDN 自定义配置测试', () => { + beforeAll(() => { + // 备份原始的 vite.config.js + fs.writeFileSync(viteConfigPath + '.bak', originalViteConfig) + + // 备份环境变量文件 + backupEnvFile(envAlphaPath) + + // 修改 vite.config.js 添加自定义配置 + const updatedViteConfig = originalViteConfig.replace( + 'const baseConfig = useTinyEngineBaseConfig({', + `const baseConfig = useTinyEngineBaseConfig({ + localCdnConfig: { + importMap: { + imports: { + 'vue': "\${VITE_CDN_DOMAIN}/vue\${versionDelimiter}3.4.21\${fileDelimiter}/dist/vue.runtime.esm-browser.js" + } + }, + copy: { + 'vue': { + filePathInPackage: '/dist/' + } + } + },` + ) + + fs.writeFileSync(viteConfigPath, updatedViteConfig) + + // 确保环境变量设置 + 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) + + // 修改 registry.js 以支持自定义 importMap + updateRegistryFile() + + // 执行构建 + execSync('pnpm run build:alpha', { + cwd: projectRoot, + stdio: 'inherit' + }) + }) + + // 测试完成后恢复原始配置 + afterAll(() => { + // 恢复 vite.config.js + if (fs.existsSync(viteConfigPath + '.bak')) { + fs.copyFileSync(viteConfigPath + '.bak', viteConfigPath) + fs.unlinkSync(viteConfigPath + '.bak') + } + + // 恢复环境变量 + restoreEnvFile(envAlphaPath) + + // 恢复 registry.js + restoreRegistryFile() + }) + + it('应该正确应用自定义 importMap 配置', () => { + const localCdnDir = path.resolve(distDir, 'local-cdn-static') + + // 检查 vue 是否被正确复制 + const vueDirs = fs.readdirSync(localCdnDir) + .filter(dir => dir.startsWith('vue@')) + .map(dir => path.resolve(localCdnDir, dir)) + + expect(vueDirs.length).toBeGreaterThan(0) + + // 检查 dist 目录是否存在 + const distExists = vueDirs.some(dir => { + return fs.existsSync(path.resolve(dir, 'dist')) + }) + + expect(distExists).toBe(true) + + // 检查 vue.global.prod.js 文件是否存在 + const vueJsExists = vueDirs.some(dir => { + return fs.existsSync(path.resolve(dir, 'dist', 'vue.runtime.esm-browser.js')) + }) + + expect(vueJsExists).toBe(true) + + // 检查 dist 目录下的文件数量是否大于1 (因为我们的复制配置是复制整个文件夹) + const distFileCount = vueDirs.some(dir => { + const distPath = path.resolve(dir, 'dist') + return fs.existsSync(distPath) && fs.readdirSync(distPath).length > 1 + }) + + expect(distFileCount).toBe(true) + }) + + it('should modify the environment variable to set VITE_CDN_DOMAIN to ./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(jsFiles[0], 'utf-8') + + // 检查 VITE_CDN_DOMAIN 环境变量是否被替换为本地路径 + expect(mainJsContent).toContain('VITE_CDN_DOMAIN:"./local-cdn-static"') + }) +}) \ No newline at end of file diff --git a/designer-demo/tests/utils/envHelpers.js b/designer-demo/tests/utils/envHelpers.js new file mode 100644 index 0000000000..2ce46683ef --- /dev/null +++ b/designer-demo/tests/utils/envHelpers.js @@ -0,0 +1,69 @@ +/** + * 环境变量处理工具函数 + */ +import fs from 'node:fs' + +/** + * 更新环境变量,如果变量不存在或值为false则设置为true + * @param {string} content - 环境变量文件内容 + * @param {string} key - 环境变量名 + * @returns {string} - 更新后的内容 + */ +export function ensureEnvVarEnabled(content, key) { + // 检查是否包含该环境变量 + const regex = new RegExp(`${key}\\s*=\\s*(.*)`, 'm') + const match = content.match(regex) + + if (!match) { + // 变量不存在,添加 + return `${content}\n${key}=true` + } else if (match[1].trim() === 'false') { + // 变量存在但值为false,替换为true + return content.replace(regex, `${key}=true`) + } + + // 变量已存在且不是false,保持不变 + return content +} + +/** + * 更新环境变量中的CDN域名 + * @param {string} content - 环境变量文件内容 + * @param {string} cdnDomain - CDN域名 + * @returns {string} - 更新后的内容 + */ +export function updateCdnDomain(content, cdnDomain) { + const regex = /VITE_CDN_DOMAIN\s*=\s*(.*)/m + const match = content.match(regex) + + if (!match) { + return `${content}\nVITE_CDN_DOMAIN=${cdnDomain}` + } else { + return content.replace(regex, `VITE_CDN_DOMAIN=${cdnDomain}`) + } +} + +/** + * 备份环境变量文件 + * @param {string} envFilePath - 环境变量文件路径 + * @returns {void} + */ +export function backupEnvFile(envFilePath) { + if (fs.existsSync(envFilePath)) { + const backupPath = envFilePath + '.bak' + fs.copyFileSync(envFilePath, backupPath) + } +} + +/** + * 恢复环境变量文件 + * @param {string} envFilePath - 环境变量文件路径 + * @returns {void} + */ +export function restoreEnvFile(envFilePath) { + const backupPath = envFilePath + '.bak' + if (fs.existsSync(backupPath)) { + fs.copyFileSync(backupPath, envFilePath) + fs.unlinkSync(backupPath) + } +} diff --git a/designer-demo/vitest.config.js b/designer-demo/vitest.config.js new file mode 100644 index 0000000000..2ec24703db --- /dev/null +++ b/designer-demo/vitest.config.js @@ -0,0 +1,19 @@ +import { defineConfig } from 'vitest/config' +import path from 'node:path' + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + testTimeout: 1000000, // 10分钟超时,因为构建可能需要较长时间 + include: ['tests/**/*.test.js'], + hookTimeout: 1000000, // 10分钟超时,因为构建可能需要较长时间 + // 这里需要串行执行,否则构建会相互覆盖,无法测试 + fileParallelism: false + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + } +}) \ No newline at end of file From 1997c5d8cf9465acdb1ead8afc9170bc84af75d3 Mon Sep 17 00:00:00 2001 From: chilingling Date: Wed, 2 Apr 2025 14:42:12 +0800 Subject: [PATCH 05/27] fix: add base path to local cdn path --- designer-demo/package.json | 4 ++-- .../build/vite-config/src/localCdnFile/localCdnPlugin.js | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/designer-demo/package.json b/designer-demo/package.json index 11293a6191..ed1f2cb616 100644 --- a/designer-demo/package.json +++ b/designer-demo/package.json @@ -5,8 +5,8 @@ "type": "module", "scripts": { "dev": "cross-env vite", - "build:alpha": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build --mode alpha", - "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build", + "build:alpha": "cross-env NODE_OPTIONS=--max-old-space-size=10240 vite build --mode alpha", + "build": "cross-env NODE_OPTIONS=--max-old-space-size=10240 vite build", "test": "vitest run", "test:watch": "vitest" }, diff --git a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js index 84d0a3dad3..8760962429 100644 --- a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js +++ b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js @@ -65,7 +65,7 @@ function extractInfo(str) { * @param {string} cdnDir - 本地CDN目录名 * @returns {Object} - Vite插件对象 */ -function createEnvReplacementPlugin(cdnDir) { +function createEnvReplacementPlugin(cdnDir, base) { return { name: 'vite-replace-cdn-env', config(config) { @@ -74,7 +74,9 @@ function createEnvReplacementPlugin(cdnDir) { config.define = {} } - config.define['import.meta.env.VITE_CDN_DOMAIN'] = JSON.stringify(`./${cdnDir}`) + config.define['import.meta.env.VITE_CDN_DOMAIN'] = JSON.stringify( + `${base.endsWith('/') ? base : base + '/'}${cdnDir}` + ) // 使用本地 CDN 时,强制设置CDN类型为 local config.define['import.meta.env.VITE_CDN_TYPE'] = JSON.stringify('local') } @@ -377,7 +379,7 @@ export function localCdnPlugin({ // 返回插件数组 return [ // 创建环境变量替换插件,替换CDN域名为本地路径 - createEnvReplacementPlugin(cdnDir), + createEnvReplacementPlugin(cdnDir, base), // 安装需要的包 ...installPackageTemporary(packageNeedToInstall, bundleTempDir), // 使用自定义的copyPlugin替代直接调用copy From 39ca9562ee6c56f6be7e6eedacadf945ca681200 Mon Sep 17 00:00:00 2001 From: chilingling Date: Tue, 8 Apr 2025 10:45:33 +0800 Subject: [PATCH 06/27] fix: merge unpkg regexp and npmmirror regexp --- .../src/localCdnFile/locateCdnNpmInfo.js | 59 ++++++++++--------- packages/common/js/importMap/import-map.json | 2 +- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js b/packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js index c345678fbf..7ef6b900b0 100644 --- a/packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js +++ b/packages/build/vite-config/src/localCdnFile/locateCdnNpmInfo.js @@ -38,6 +38,34 @@ export function copyfileToDynamicSrcMapper({ src, dest, transform, rename, folde } } +function extractPackageInfo(url, originCdnPrefix) { + let match = null + + try { + const mergedRegex = new RegExp( + `^${originCdnPrefix}/?` + + // 包名捕获组(支持作用域包 @scope/name 格式) + // (?:@[^/]+/)? 匹配 @scope/ 格式 + // [^/@]+ 匹配包名,不包含 '/' 和 '@' + `(?(?:@[^/]+/)?[^/@]+)` + + // 版本号部分(@或/分隔) + // /(?=.*/files) 匹配斜杠的分割的文件路径,但是需满足正向预查,确保后续路径包含 /files + `(?:@|/(?=.*/files))` + + // 捕获版本号 + `(?[^/]+)` + + // 路径部分 处理/files前缀(npmmirror) + `(?:/files)?` + + // 路径部分 匹配文件路径 + `(?.*?)$` + ) + match = url.match(mergedRegex) + } catch (error) { + // ignore + } + + return match +} + // 生成复制单个文件所需要的信息 export function getCdnPathNpmInfoForSingleFile( url, // cdn托管的npm文件地址数组 @@ -48,24 +76,7 @@ export function getCdnPathNpmInfoForSingleFile( tempDir = 'bundle-deps' // 新安装包的安装目录 ) { const baseSlash = base.endsWith('/') ? '' : '/' - - // 分别匹配 unpkg 和 npmmirror 格式 - let unpkgMatch = null - let npmmirrorMatch = null - - try { - unpkgMatch = url.match( - new RegExp(`^${originCdnPrefix}/?(?.+?)@(?[^/]+)(?.*?)$`) - ) - npmmirrorMatch = url.match( - new RegExp(`^${originCdnPrefix}/?(?.+?)/(?[^/]+)/files(?.*?)$`) - ) - } catch (error) { - // ignore - } - - // 使用匹配到的结果 - const match = npmmirrorMatch || unpkgMatch + const match = extractPackageInfo(url, originCdnPrefix) if (!match) { return null @@ -132,17 +143,9 @@ export function getCdnPathNpmInfoForPackage( tempDir = 'bundle-deps' // 新安装包的安装目录 ) { const baseSlash = base.endsWith('/') ? '' : '/' - - // 分别匹配 unpkg 和 npmmirror 格式 - const unpkgMatch = url.match( - new RegExp(`^${originCdnPrefix}/?(?.+?)@(?[^/]+)(?.*?)$`) - ) - const npmmirrorMatch = url.match( - new RegExp(`^${originCdnPrefix}/?(?.+?)/(?[^/]+)/files(?.*?)$`) - ) - // 使用匹配到的结果 - const match = npmmirrorMatch || unpkgMatch + const match = extractPackageInfo(url, originCdnPrefix) + if (!match) { return null } diff --git a/packages/common/js/importMap/import-map.json b/packages/common/js/importMap/import-map.json index a10d4b9c3b..a006bbbff6 100644 --- a/packages/common/js/importMap/import-map.json +++ b/packages/common/js/importMap/import-map.json @@ -1,6 +1,6 @@ { "imports": { - "vue": "${VITE_CDN_DOMAIN}/vue${versionDelimiter}3.4.23${fileDelimiter}/dist/vue.runtime.esm-browser.prod.js", + "vue": "${VITE_CDN_DOMAIN}/vue${versionDelimiter}3.4.23${fileDelimiter}/dist/vue.runtime.esm-browser.js", "vue/server-renderer": "${VITE_CDN_DOMAIN}/@vue/server-renderer${versionDelimiter}3.4.23${fileDelimiter}/dist/server-renderer.esm-browser.js", "vue-i18n": "${VITE_CDN_DOMAIN}/vue-i18n${versionDelimiter}^9.9.0${fileDelimiter}/dist/vue-i18n.esm-browser.js", "vue-router": "${VITE_CDN_DOMAIN}/vue-router${versionDelimiter}4.0.16${fileDelimiter}/dist/vue-router.esm-browser.js", From ee64a2d322cf3fb2eb97542894c7267376e53b42 Mon Sep 17 00:00:00 2001 From: chilingling Date: Tue, 8 Apr 2025 10:47:24 +0800 Subject: [PATCH 07/27] fix: generate docs catalog --- docs/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/README.md b/docs/README.md index f9c9061ac1..430923eec5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -46,6 +46,7 @@ - [区块局域网发布方案(Node.js服务端)](./solutions/block-lan-release-solution.md) - [设计器中引入第三方组件库](./solutions/third-party-library-in-designer.md) - [物料同步方案](./solutions/material-sync-solution.md) + - [本地化CDN方案](./solutions/local-cdn.md) - 扩展能力介绍 - [新架构介绍](./extension-capabilities-overview/new-architecture.md) - [注册表](./extension-capabilities-overview/registry.md) From 72db54d72cb579440c250fafa03dd11ee62e0526 Mon Sep 17 00:00:00 2001 From: chilingling Date: Tue, 8 Apr 2025 10:53:43 +0800 Subject: [PATCH 08/27] fix: add comment for url filter logic --- packages/build/vite-config/src/localCdnFile/copyBundleDeps.js | 2 ++ packages/build/vite-config/src/localCdnFile/copyImportMap.js | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js b/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js index a4c023d1f8..6d684ba2c9 100644 --- a/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js +++ b/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js @@ -53,6 +53,8 @@ export function copyBundleDeps({ }) { const cdnFiles = extraBundleCdnLink(bundleFile, originCdnPrefix) .map((url) => getCdnPathNpmInfoForSingleFile(url, originCdnPrefix, base, dir, false, bundleTempDir)) + // 比如 url 前缀跟 originCdnPrefix 不匹配,或者 url 格式不正确 的场景有可能会导致匹配失败 + // 匹配失败时,会返回 null,需要过滤掉 .filter(Boolean) const { packages: packageNeedToInstall, files } = getPackageNeedToInstallAndFilesUsingSameVersion(cdnFiles) diff --git a/packages/build/vite-config/src/localCdnFile/copyImportMap.js b/packages/build/vite-config/src/localCdnFile/copyImportMap.js index 74b0c309fd..cad4a5a92a 100644 --- a/packages/build/vite-config/src/localCdnFile/copyImportMap.js +++ b/packages/build/vite-config/src/localCdnFile/copyImportMap.js @@ -26,10 +26,14 @@ export const copyLocalImportMap = ({ } return getCdnPathNpmInfoForSingleFile(libPath, originCdnPrefix, base, dir, false, bundleTempDir) }) + // 比如 url 前缀跟 originCdnPrefix 不匹配,或者 url 格式不正确 的场景有可能会导致匹配失败 + // 匹配失败时,会返回 null,需要过滤掉 .filter(Boolean) const styleFiles = styleUrls .filter((styleUrl) => styleUrl.startsWith(originCdnPrefix)) .map((url) => getCdnPathNpmInfoForSingleFile(url, originCdnPrefix, base, dir, false), bundleTempDir) + // 比如 url 前缀跟 originCdnPrefix 不匹配,或者 url 格式不正确 的场景有可能会导致匹配失败 + // 匹配失败时,会返回 null,需要过滤掉 .filter(Boolean) const { packages: packageNeedToInstall, files } = getPackageNeedToInstallAndFilesUsingSameVersion( From 94d2da3ce273682fe4186885381cc54d2551e763 Mon Sep 17 00:00:00 2001 From: chilingling Date: Tue, 8 Apr 2025 10:58:23 +0800 Subject: [PATCH 09/27] refactor: optimize function naming in local CDN plugin rename transform function to replaceJsImportPaths to improve code readability and semantics. The new name more clearly expresses the actual functionality of the function: replacing import paths in JS files. --- .../build/vite-config/src/localCdnFile/localCdnPlugin.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js index 8760962429..4b6e29ac06 100644 --- a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js +++ b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js @@ -28,7 +28,7 @@ const defaultCopyConfig = { * @param {string} filename - 文件名 * @returns {string} - 处理后的内容 */ -function transform(content, filename) { +function replaceJsImportPaths(content, filename) { if (filename.endsWith('.js')) { const result = babelReplaceImportPathWithCertainFileName(content, filename, console) return result.code || content @@ -144,7 +144,7 @@ async function copyFileOrDirectory(srcPath, destPaths, copiedFiles, outDir) { const content = await fs.readFile(file, 'utf-8') // 应用转换 - const transformedContent = transform(content, file) + const transformedContent = replaceJsImportPaths(content, file) // 写入转换后的内容 await fs.writeFile(destFilePath, transformedContent) @@ -162,7 +162,7 @@ async function copyFileOrDirectory(srcPath, destPaths, copiedFiles, outDir) { const content = await fs.readFile(srcPath, 'utf-8') // 应用转换 - const transformedContent = transform(content, srcPath) + const transformedContent = replaceJsImportPaths(content, srcPath) // 写入转换后的内容 await fs.writeFile(finalDestPath, transformedContent) From 5ff2c34533d8d5005758416ce43d3c3c884a70dc Mon Sep 17 00:00:00 2001 From: chilingling Date: Tue, 8 Apr 2025 11:37:26 +0800 Subject: [PATCH 10/27] refactor: optimize localCDN plugin implementation. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 提取独立的插件 cdnCopyPlugin、createEnvReplacementPlugin 到 vite-plugin 文件夹中。 --- .../src/localCdnFile/localCdnPlugin.js | 187 +----------------- .../src/vite-plugins/cdnCopyPlugin.js | 162 +++++++++++++++ .../createEnvReplacementPlugin.js | 22 +++ 3 files changed, 188 insertions(+), 183 deletions(-) create mode 100644 packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js create mode 100644 packages/build/vite-config/src/vite-plugins/createEnvReplacementPlugin.js diff --git a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js index 4b6e29ac06..a1ed2f6987 100644 --- a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js +++ b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js @@ -1,12 +1,13 @@ import path from 'node:path' import fs from 'fs-extra' -import fg from 'fast-glob' -import { dedupeCopyFiles } from './locateCdnNpmInfo.js' import { installPackageTemporary } from '../vite-plugins/installPackageTemporary.js' -import { babelReplaceImportPathWithCertainFileName } from './replaceImportPath.mjs' +import { createEnvReplacementPlugin } from '../vite-plugins/createEnvReplacementPlugin.js' +import { copyPlugin } from '../vite-plugins/cdnCopyPlugin.js' +import { dedupeCopyFiles } from './locateCdnNpmInfo.js' const logger = console +// 默认的复制配置,这几个 package 需要复制整个目录,所以需要有默认配置进行复制整个目录 const defaultCopyConfig = { '@opentiny/vue-theme': { filePathInPackage: '/' @@ -22,20 +23,6 @@ const defaultCopyConfig = { } } -/** - * 对文件内容进行转换处理 - * @param {string} content - 文件内容 - * @param {string} filename - 文件名 - * @returns {string} - 处理后的内容 - */ -function replaceJsImportPaths(content, filename) { - if (filename.endsWith('.js')) { - const result = babelReplaceImportPathWithCertainFileName(content, filename, console) - return result.code || content - } - return content -} - /** * 从importMapUrl字符串中提取包名、版本和文件路径 * @param {string} str - 导入字符串 @@ -60,172 +47,6 @@ function extractInfo(str) { } } -/** - * 创建环境变量替换插件 - * @param {string} cdnDir - 本地CDN目录名 - * @returns {Object} - Vite插件对象 - */ -function createEnvReplacementPlugin(cdnDir, base) { - return { - name: 'vite-replace-cdn-env', - config(config) { - // 在构建时替换环境变量,将CDN域名替换为本地路径 - if (!config.define) { - config.define = {} - } - - config.define['import.meta.env.VITE_CDN_DOMAIN'] = JSON.stringify( - `${base.endsWith('/') ? base : base + '/'}${cdnDir}` - ) - // 使用本地 CDN 时,强制设置CDN类型为 local - config.define['import.meta.env.VITE_CDN_TYPE'] = JSON.stringify('local') - } - } -} - -/** - * 复制文件或目录到目标路径 - * @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() - - // 为每个目标路径执行复制 - for (const destPath of destPaths) { - const fullDestPath = path.resolve(outDir, destPath) - - try { - // 确保目标目录存在 - await fs.ensureDir(path.dirname(fullDestPath)) - - logger.log(`[vite-cdn-copy-plugin]: Copying from ${srcPath} to ${fullDestPath}`) - - if (isDirectory) { - // 如果是目录,使用 fast-glob 遍历所有文件并处理 - logger.log(`[vite-cdn-copy-plugin]: Copying directory recursively: ${srcPath} -> ${fullDestPath}`) - - // 确保目标路径存在 - await fs.ensureDir(fullDestPath) - - // 使用绝对路径 - const absoluteSrcPath = path.resolve(process.cwd(), srcPath) - - // 使用 fast-glob 查找所有文件 - 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) - } - } 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) - } - - logger.log(`[vite-cdn-copy-plugin]: Successfully copied: ${srcPath} -> ${fullDestPath}`) - } catch (err) { - logger.error(`[vite-cdn-copy-plugin]: Failed to copy ${srcPath} to ${fullDestPath}`, err) - } - } -} - -/** - * 创建复制插件 - * @param {Array} targets - 复制目标配置数组 - * @param {string|Array} targets[].src - 源文件路径或路径数组 - * @param {string|Array} targets[].dest - 目标文件路径或路径数组 - * @returns {Object} Vite插件对象 - */ - -function copyPlugin(targets) { - let resolvedConfig = null - let copiedFiles = new Set() - - return { - name: 'vite-cdn-copy-plugin', - configResolved(getResolvedConfig) { - resolvedConfig = getResolvedConfig - }, - async writeBundle() { - if (!targets || !targets.length) { - return - } - - const outDir = resolvedConfig.build.outDir || 'dist' - - logger.log('[vite-cdn-copy-plugin]: Start copying files to dist directory') - - // 遍历所有复制目标 - for (const target of targets) { - const { src, dest } = target - - if (!src || !dest) { - logger.warn('[vite-cdn-copy-plugin]: Skipping target with missing src or dest', target) - continue - } - - // 处理源路径,支持数组形式 - // const srcPaths = (Array.isArray(src) ? src : [src]).map(item => path.resolve(process.cwd(), item)) - const srcPaths = Array.isArray(src) ? src : [src] - // 处理目标路径,支持数组形式 - const destPaths = Array.isArray(dest) ? dest : [dest] - - for (const srcPath of srcPaths) { - await copyFileOrDirectory(srcPath, destPaths, copiedFiles, outDir) - } - } - - logger.log('[vite-cdn-copy-plugin]: Finished copying files') - } - } -} - /** * 比较两个版本号是否相同 * @param {string} versionOrigin - 源版本号, 可能包含 ^ 或 ~ 开头 diff --git a/packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js b/packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js new file mode 100644 index 0000000000..a80bdbe10e --- /dev/null +++ b/packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js @@ -0,0 +1,162 @@ +import path from 'node:path' +import fs from 'fs-extra' +import fg from 'fast-glob' +import { babelReplaceImportPathWithCertainFileName } from '../localCdnFile/replaceImportPath.mjs' + +/** + * 对文件内容进行转换处理 + * @param {string} content - 文件内容 + * @param {string} filename - 文件名 + * @returns {string} - 处理后的内容 + */ +function replaceJsImportPaths(content, filename) { + if (filename.endsWith('.js')) { + const result = babelReplaceImportPathWithCertainFileName(content, filename, console) + return result.code || content + } + return content +} + +const logger = console + +/** + * 复制文件或目录到目标路径 + * @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() + + // 为每个目标路径执行复制 + for (const destPath of destPaths) { + const fullDestPath = path.resolve(outDir, destPath) + + try { + // 确保目标目录存在 + await fs.ensureDir(path.dirname(fullDestPath)) + + logger.log(`[vite-cdn-copy-plugin]: Copying from ${srcPath} to ${fullDestPath}`) + + if (isDirectory) { + // 如果是目录,使用 fast-glob 遍历所有文件并处理 + logger.log(`[vite-cdn-copy-plugin]: Copying directory recursively: ${srcPath} -> ${fullDestPath}`) + + // 确保目标路径存在 + await fs.ensureDir(fullDestPath) + + // 使用绝对路径 + const absoluteSrcPath = path.resolve(process.cwd(), srcPath) + + // 使用 fast-glob 查找所有文件 + 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) + } + } 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) + } + + logger.log(`[vite-cdn-copy-plugin]: Successfully copied: ${srcPath} -> ${fullDestPath}`) + } catch (err) { + logger.error(`[vite-cdn-copy-plugin]: Failed to copy ${srcPath} to ${fullDestPath}`, err) + } + } +} + +/** + * 创建复制插件 + * @param {Array} targets - 复制目标配置数组 + * @param {string|Array} targets[].src - 源文件路径或路径数组 + * @param {string|Array} targets[].dest - 目标文件路径或路径数组 + * @returns {Object} Vite插件对象 + */ + +export function copyPlugin(targets) { + let resolvedConfig = null + let copiedFiles = new Set() + + return { + name: 'vite-cdn-copy-plugin', + configResolved(getResolvedConfig) { + resolvedConfig = getResolvedConfig + }, + async writeBundle() { + if (!targets || !targets.length) { + return + } + + const outDir = resolvedConfig.build.outDir || 'dist' + + logger.log('[vite-cdn-copy-plugin]: Start copying files to dist directory') + + // 遍历所有复制目标 + for (const target of targets) { + const { src, dest } = target + + if (!src || !dest) { + logger.warn('[vite-cdn-copy-plugin]: Skipping target with missing src or dest', target) + continue + } + + // 处理源路径,支持数组形式 + const srcPaths = Array.isArray(src) ? src : [src] + // 处理目标路径,支持数组形式 + const destPaths = Array.isArray(dest) ? dest : [dest] + + for (const srcPath of srcPaths) { + await copyFileOrDirectory(srcPath, destPaths, copiedFiles, outDir) + } + } + + logger.log('[vite-cdn-copy-plugin]: Finished copying files') + } + } +} diff --git a/packages/build/vite-config/src/vite-plugins/createEnvReplacementPlugin.js b/packages/build/vite-config/src/vite-plugins/createEnvReplacementPlugin.js new file mode 100644 index 0000000000..f4c550b3bf --- /dev/null +++ b/packages/build/vite-config/src/vite-plugins/createEnvReplacementPlugin.js @@ -0,0 +1,22 @@ +/** + * 创建环境变量替换插件 + * @param {string} cdnDir - 本地CDN目录名 + * @returns {Object} - Vite插件对象 + */ +export function createEnvReplacementPlugin(cdnDir, base) { + return { + name: 'vite-replace-cdn-env', + config(config) { + // 在构建时替换环境变量,将CDN域名替换为本地路径 + if (!config.define) { + config.define = {} + } + + config.define['import.meta.env.VITE_CDN_DOMAIN'] = JSON.stringify( + `${base.endsWith('/') ? base : base + '/'}${cdnDir}` + ) + // 使用本地 CDN 时,强制设置CDN类型为 local + config.define['import.meta.env.VITE_CDN_TYPE'] = JSON.stringify('local') + } + } +} From 0324c1127e5706ba8c5e114b413bc45d100a049b Mon Sep 17 00:00:00 2001 From: chilingling Date: Tue, 8 Apr 2025 11:50:02 +0800 Subject: [PATCH 11/27] fix: support legacy npm potocol in CDN link extraction --- .../vite-config/src/localCdnFile/copyBundleDeps.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js b/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js index 6d684ba2c9..ee4f4e8eba 100644 --- a/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js +++ b/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js @@ -15,6 +15,18 @@ const { readJsonSync } = fs export function extraBundleCdnLink(filename, originCdnPrefix) { const result = new Set() const bundle = readJsonSync(filename) + // 兼容旧版的 npm 协议 + bundle.data?.materials?.components?.forEach((component) => { + if (component.npm) { + const possibleUrl = [component.npm.script, component.npm.css] + possibleUrl.forEach((url) => { + if (url?.startsWith(originCdnPrefix) && !result.has(url)) { + result.add(url) + } + }) + } + }) + bundle.data?.materials?.packages?.forEach((packageItem) => { if (packageItem) { const possibleUrl = [packageItem.script, packageItem.css] From 19dba5780c291ebe559a41b9decb2bf1ae539e31 Mon Sep 17 00:00:00 2001 From: chilingling Date: Tue, 8 Apr 2025 19:40:09 +0800 Subject: [PATCH 12/27] fix: optimize local CDN plugin implementation --- .../build/vite-config/src/default-config.js | 9 +++++ .../src/localCdnFile/import-map.js | 33 +++++++++++++++++ .../src/localCdnFile/localCdnPlugin.js | 9 +---- .../createDynamicImportMapPlugin.js | 37 +++++++++++++++++++ .../createEnvReplacementPlugin.js | 22 ----------- packages/common/js/importMap/import-map.json | 23 ------------ packages/common/js/importMap/index.js | 2 +- packages/common/vite.config.ts | 19 ++-------- packages/design-core/vite.config.js | 17 +-------- 9 files changed, 87 insertions(+), 84 deletions(-) create mode 100644 packages/build/vite-config/src/localCdnFile/import-map.js create mode 100644 packages/build/vite-config/src/vite-plugins/createDynamicImportMapPlugin.js delete mode 100644 packages/build/vite-config/src/vite-plugins/createEnvReplacementPlugin.js delete mode 100644 packages/common/js/importMap/import-map.json diff --git a/packages/build/vite-config/src/default-config.js b/packages/build/vite-config/src/default-config.js index 27aa7bcce0..a3b65749fc 100644 --- a/packages/build/vite-config/src/default-config.js +++ b/packages/build/vite-config/src/default-config.js @@ -14,6 +14,7 @@ import { getBaseUrlFromCli, copyBundleDeps, localCdnPlugin } from './localCdnFil import { devAliasPlugin } from './vite-plugins/devAliasPlugin.js' import { htmlUpgradeHttpsPlugin } from './vite-plugins/upgradeHttpsPlugin.js' import { canvasDevExternal } from './canvas-dev-external.js' +import { createDynamicImportMapPlugin } from './vite-plugins/createDynamicImportMapPlugin.js' const monacoEditorPlugin = monacoEditorPluginCjs.default const nodeGlobalsPolyfillPlugin = nodeGlobalsPolyfillPluginCjs.default @@ -179,6 +180,14 @@ export function useTinyEngineBaseConfig(engineConfig) { } } + config.plugins.push( + createDynamicImportMapPlugin({ + base: getBaseUrlFromCli(config.base), + cdnDir: 'local-cdn-static', + isLocalImportMap + }) + ) + config.plugins.push(devAliasPlugin(env, engineConfig.useSourceAlias)) if (engineConfig.useSourceAlias && command === 'serve') { diff --git a/packages/build/vite-config/src/localCdnFile/import-map.js b/packages/build/vite-config/src/localCdnFile/import-map.js new file mode 100644 index 0000000000..2326ad24d1 --- /dev/null +++ b/packages/build/vite-config/src/localCdnFile/import-map.js @@ -0,0 +1,33 @@ +export const importMapConfig = { + imports: { + vue: '${VITE_CDN_DOMAIN}/vue${versionDelimiter}3.4.23${fileDelimiter}/dist/vue.runtime.esm-browser.js', + 'vue/server-renderer': + '${VITE_CDN_DOMAIN}/@vue/server-renderer${versionDelimiter}3.4.23${fileDelimiter}/dist/server-renderer.esm-browser.js', + 'vue-i18n': '${VITE_CDN_DOMAIN}/vue-i18n${versionDelimiter}^9.9.0${fileDelimiter}/dist/vue-i18n.esm-browser.js', + 'vue-router': + '${VITE_CDN_DOMAIN}/vue-router${versionDelimiter}4.0.16${fileDelimiter}/dist/vue-router.esm-browser.js', + '@vue/devtools-api': + '${VITE_CDN_DOMAIN}/@vue/devtools-api${versionDelimiter}6.5.1${fileDelimiter}/lib/esm/index.js', + '@vueuse/core': '${VITE_CDN_DOMAIN}/@vueuse/core${versionDelimiter}9.6.0${fileDelimiter}/index.mjs', + '@vueuse/shared': '${VITE_CDN_DOMAIN}/@vueuse/shared${versionDelimiter}9.6.0${fileDelimiter}/index.mjs', + axios: '${VITE_CDN_DOMAIN}/axios${versionDelimiter}1.0.0${fileDelimiter}/dist/esm/axios.js', + '@opentiny/tiny-engine-webcomponent-core': + '${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-webcomponent-core${versionDelimiter}1${fileDelimiter}/dist/tiny-engine-webcomponent-core.es.js', + '@opentiny/tiny-engine-i18n-host': + '${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-i18n-host${versionDelimiter}1${fileDelimiter}/dist/lowcode-design-i18n-host.es.js', + '@opentiny/tiny-engine-builtin-component': + '${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-builtin-component${versionDelimiter}^2.0.0${fileDelimiter}/dist/index.mjs', + 'vue-demi': '${VITE_CDN_DOMAIN}/vue-demi${versionDelimiter}0.13.11${fileDelimiter}/lib/index.mjs', + pinia: '${VITE_CDN_DOMAIN}/pinia${versionDelimiter}2.0.22${fileDelimiter}/dist/pinia.esm-browser.js', + '@opentiny/vue': + '${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-pc.mjs', + '@opentiny/vue-icon': + '${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-icon.mjs', + '@opentiny/vue-common': + '${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-common.mjs', + '@opentiny/vue-locale': + '${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-locale.mjs', + '@opentiny/vue-renderless/': '${VITE_CDN_DOMAIN}/@opentiny/vue-renderless${versionDelimiter}~3.20${fileDelimiter}/', + echarts: '${VITE_CDN_DOMAIN}/echarts${versionDelimiter}5.4.1${fileDelimiter}/dist/echarts.esm.js' + } +} diff --git a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js index a1ed2f6987..fb213cec23 100644 --- a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js +++ b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js @@ -1,9 +1,9 @@ import path from 'node:path' import fs from 'fs-extra' import { installPackageTemporary } from '../vite-plugins/installPackageTemporary.js' -import { createEnvReplacementPlugin } from '../vite-plugins/createEnvReplacementPlugin.js' import { copyPlugin } from '../vite-plugins/cdnCopyPlugin.js' import { dedupeCopyFiles } from './locateCdnNpmInfo.js' +import { importMapConfig as importMapConfigFile } from './import-map.js' const logger = console @@ -156,10 +156,7 @@ export function localCdnPlugin({ }) { const importMapConfig = localCdnConfig.importMap || { imports: {} } const copyConfig = localCdnConfig.copy || {} - - const defaultImportMapConfig = JSON.parse( - fs.readFileSync(path.resolve(process.cwd(), './node_modules/@opentiny/tiny-engine/dist/import-map.json'), 'utf-8') - ) + const defaultImportMapConfig = importMapConfigFile 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) => { @@ -199,8 +196,6 @@ export function localCdnPlugin({ const targetFiles = dedupeCopyFiles(cdnFiles) // 返回插件数组 return [ - // 创建环境变量替换插件,替换CDN域名为本地路径 - createEnvReplacementPlugin(cdnDir, base), // 安装需要的包 ...installPackageTemporary(packageNeedToInstall, bundleTempDir), // 使用自定义的copyPlugin替代直接调用copy diff --git a/packages/build/vite-config/src/vite-plugins/createDynamicImportMapPlugin.js b/packages/build/vite-config/src/vite-plugins/createDynamicImportMapPlugin.js new file mode 100644 index 0000000000..0f5a425f46 --- /dev/null +++ b/packages/build/vite-config/src/vite-plugins/createDynamicImportMapPlugin.js @@ -0,0 +1,37 @@ +import { importMapConfig } from '../localCdnFile/import-map.js' + +/** + * 创建环境变量替换插件 + * @param {string} cdnDir - 本地CDN目录名 + * @param {string} base - 基础路径 + * @param {boolean} isLocalImportMap - 是否是本地CDN + * @returns {Object} - Vite插件对象 + */ +export function createDynamicImportMapPlugin({ cdnDir, base, isLocalImportMap = false }) { + const virtualModuleId = 'virtual:import-map' + const resolvedVirtualModuleId = '\0' + virtualModuleId + + return { + name: 'vite-plugin-dynamic-import-map', + resolveId(id) { + if (id === virtualModuleId) { + return resolvedVirtualModuleId + } + }, + load(id) { + if (id === resolvedVirtualModuleId) { + if (isLocalImportMap) { + const cdnDomain = `${base.endsWith('/') ? base : base + '/'}${cdnDir}` + const newContent = JSON.stringify(importMapConfig) + .replaceAll(/\${VITE_CDN_DOMAIN}/g, cdnDomain) + .replaceAll(/\${versionDelimiter}/g, '@') + .replaceAll(/\${fileDelimiter}/g, '') + + return `export default ${newContent}` + } + + return `export default ${JSON.stringify(importMapConfig)}` + } + } + } +} diff --git a/packages/build/vite-config/src/vite-plugins/createEnvReplacementPlugin.js b/packages/build/vite-config/src/vite-plugins/createEnvReplacementPlugin.js deleted file mode 100644 index f4c550b3bf..0000000000 --- a/packages/build/vite-config/src/vite-plugins/createEnvReplacementPlugin.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * 创建环境变量替换插件 - * @param {string} cdnDir - 本地CDN目录名 - * @returns {Object} - Vite插件对象 - */ -export function createEnvReplacementPlugin(cdnDir, base) { - return { - name: 'vite-replace-cdn-env', - config(config) { - // 在构建时替换环境变量,将CDN域名替换为本地路径 - if (!config.define) { - config.define = {} - } - - config.define['import.meta.env.VITE_CDN_DOMAIN'] = JSON.stringify( - `${base.endsWith('/') ? base : base + '/'}${cdnDir}` - ) - // 使用本地 CDN 时,强制设置CDN类型为 local - config.define['import.meta.env.VITE_CDN_TYPE'] = JSON.stringify('local') - } - } -} diff --git a/packages/common/js/importMap/import-map.json b/packages/common/js/importMap/import-map.json deleted file mode 100644 index a006bbbff6..0000000000 --- a/packages/common/js/importMap/import-map.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "imports": { - "vue": "${VITE_CDN_DOMAIN}/vue${versionDelimiter}3.4.23${fileDelimiter}/dist/vue.runtime.esm-browser.js", - "vue/server-renderer": "${VITE_CDN_DOMAIN}/@vue/server-renderer${versionDelimiter}3.4.23${fileDelimiter}/dist/server-renderer.esm-browser.js", - "vue-i18n": "${VITE_CDN_DOMAIN}/vue-i18n${versionDelimiter}^9.9.0${fileDelimiter}/dist/vue-i18n.esm-browser.js", - "vue-router": "${VITE_CDN_DOMAIN}/vue-router${versionDelimiter}4.0.16${fileDelimiter}/dist/vue-router.esm-browser.js", - "@vue/devtools-api": "${VITE_CDN_DOMAIN}/@vue/devtools-api${versionDelimiter}6.5.1${fileDelimiter}/lib/esm/index.js", - "@vueuse/core": "${VITE_CDN_DOMAIN}/@vueuse/core${versionDelimiter}9.6.0${fileDelimiter}/index.mjs", - "@vueuse/shared": "${VITE_CDN_DOMAIN}/@vueuse/shared${versionDelimiter}9.6.0${fileDelimiter}/index.mjs", - "axios": "${VITE_CDN_DOMAIN}/axios${versionDelimiter}1.0.0${fileDelimiter}/dist/esm/axios.js", - "@opentiny/tiny-engine-webcomponent-core": "${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-webcomponent-core${versionDelimiter}1${fileDelimiter}/dist/tiny-engine-webcomponent-core.es.js", - "@opentiny/tiny-engine-i18n-host": "${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-i18n-host${versionDelimiter}1${fileDelimiter}/dist/lowcode-design-i18n-host.es.js", - "@opentiny/tiny-engine-builtin-component": "${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-builtin-component${versionDelimiter}^2.0.0${fileDelimiter}/dist/index.mjs", - "vue-demi": "${VITE_CDN_DOMAIN}/vue-demi${versionDelimiter}0.13.11${fileDelimiter}/lib/index.mjs", - "pinia": "${VITE_CDN_DOMAIN}/pinia${versionDelimiter}2.0.22${fileDelimiter}/dist/pinia.esm-browser.js", - "@opentiny/vue": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-pc.mjs", - "@opentiny/vue-icon": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-icon.mjs", - "@opentiny/vue-common": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-common.mjs", - "@opentiny/vue-locale": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-locale.mjs", - "@opentiny/vue-renderless/": "${VITE_CDN_DOMAIN}/@opentiny/vue-renderless${versionDelimiter}~3.20${fileDelimiter}/", - "echarts": "${VITE_CDN_DOMAIN}/echarts${versionDelimiter}5.4.1${fileDelimiter}/dist/echarts.esm.js" - } -} diff --git a/packages/common/js/importMap/index.js b/packages/common/js/importMap/index.js index 844734b797..ae83e9a1cf 100644 --- a/packages/common/js/importMap/index.js +++ b/packages/common/js/importMap/index.js @@ -1 +1 @@ -export { default as importMapConfig } from './import-map.json' +export { default as importMapConfig } from 'virtual:import-map' diff --git a/packages/common/vite.config.ts b/packages/common/vite.config.ts index 3c42c52aba..aaf0a56833 100644 --- a/packages/common/vite.config.ts +++ b/packages/common/vite.config.ts @@ -17,7 +17,6 @@ import vueJsx from '@vitejs/plugin-vue-jsx' import { glob } from 'glob' import { fileURLToPath } from 'node:url' import generateComments from '@opentiny/tiny-engine-vite-plugin-meta-comments' -import { viteStaticCopy } from 'vite-plugin-static-copy' const jsEntries = glob.sync('./js/**/*.js').map((file) => { return [file.slice(0, file.length - path.extname(file).length), fileURLToPath(new URL(file, import.meta.url))] @@ -25,20 +24,7 @@ const jsEntries = glob.sync('./js/**/*.js').map((file) => { // https://vitejs.dev/config/ export default defineConfig({ - plugins: [ - generateComments(), - vue(), - vueJsx(), - // 复制 import-map.json到产物,提供给构建插件读取 - viteStaticCopy({ - targets: [ - { - src: './js/importMap/import-map.json', - dest: '.' - } - ] - }) - ], + plugins: [generateComments(), vue(), vueJsx()], publicDir: false, resolve: {}, base: './', @@ -80,7 +66,8 @@ export default defineConfig({ /@opentiny\/tiny-engine.*/, /@opentiny\/vue.*/, /^prettier.*/, - /^@babel.*/ + /^@babel.*/, + /^virtual:import-map$/ ] } } diff --git a/packages/design-core/vite.config.js b/packages/design-core/vite.config.js index b10dec2713..692f75e9ed 100644 --- a/packages/design-core/vite.config.js +++ b/packages/design-core/vite.config.js @@ -6,7 +6,6 @@ import nodeGlobalsPolyfillPluginCjs from '@esbuild-plugins/node-globals-polyfill import nodeModulesPolyfillPluginCjs from '@esbuild-plugins/node-modules-polyfill' import nodePolyfill from 'rollup-plugin-polyfill-node' import { fileURLToPath } from 'node:url' -import { viteStaticCopy } from 'vite-plugin-static-copy' const nodeGlobalsPolyfillPlugin = nodeGlobalsPolyfillPluginCjs.default const nodeModulesPolyfillPlugin = nodeModulesPolyfillPluginCjs.default @@ -32,19 +31,7 @@ const addViteIgnorePlugin = () => { } export default defineConfig({ - plugins: [ - vue(), - vueJsx(), - // 复制 import-map.json到产物,提供给构建插件读取 - viteStaticCopy({ - targets: [ - { - src: './node_modules/@opentiny/tiny-engine-common/dist/import-map.json', - dest: '.' - } - ] - }) - ], + plugins: [vue(), vueJsx()], publicDir: false, optimizeDeps: { esbuildOptions: { @@ -92,7 +79,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*/] } } }) From 08c2de2a1057ef5e41955853fa3a84b81ddfad02 Mon Sep 17 00:00:00 2001 From: chilingling Date: Wed, 23 Apr 2025 21:30:38 +0800 Subject: [PATCH 13/27] fix: replace bundle cdnLink support old protocol --- .../vite-config/src/localCdnFile/copyBundleDeps.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js b/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js index ee4f4e8eba..2e5256dc81 100644 --- a/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js +++ b/packages/build/vite-config/src/localCdnFile/copyBundleDeps.js @@ -42,6 +42,19 @@ export function extraBundleCdnLink(filename, originCdnPrefix) { } export function replaceBundleCdnLink(bundle, fileMap) { + // 兼容旧版的 npm 协议 + bundle.data?.materials?.components?.forEach((component) => { + if (component.npm) { + const possibleUrl = ['script', 'css'] + possibleUrl.forEach((key) => { + const matchRule = fileMap.find((rule) => component.npm[key] === rule.originUrl) + if (matchRule) { + component.npm[key] = matchRule.newUrl + } + }) + } + }) + bundle.data?.materials?.packages?.forEach((packageItem) => { if (packageItem) { const possibleUrl = ['script', 'css'] From 6863039adeba40dfe522fb678960e476355ae416 Mon Sep 17 00:00:00 2001 From: chilingling Date: Fri, 25 Apr 2025 14:04:47 +0800 Subject: [PATCH 14/27] feat: optimize local cdn implementation --- designer-demo/env/.env.alpha | 5 +++ designer-demo/env/.env.localCDN.example | 16 ++++++++ .../build/vite-config/src/default-config.js | 18 ++++----- .../src/localCdnFile/import-map.js | 33 ----------------- .../src/localCdnFile/localCdnPlugin.js | 5 ++- .../createDynamicImportMapPlugin.js | 37 ------------------- packages/canvas/DesignCanvas/src/importMap.ts | 15 ++++---- packages/common/js/importMap/import-map.json | 23 ++++++++++++ packages/common/js/importMap/index.js | 2 +- packages/common/vite.config.ts | 19 ++++++++-- .../src/preview/src/preview/importMap.js | 7 ++-- .../src/preview/src/preview/srcFiles.js | 12 +++--- packages/design-core/vite.config.js | 20 ++++++++-- 13 files changed, 106 insertions(+), 106 deletions(-) create mode 100644 designer-demo/env/.env.localCDN.example delete mode 100644 packages/build/vite-config/src/localCdnFile/import-map.js delete mode 100644 packages/build/vite-config/src/vite-plugins/createDynamicImportMapPlugin.js create mode 100644 packages/common/js/importMap/import-map.json diff --git a/designer-demo/env/.env.alpha b/designer-demo/env/.env.alpha index 272c70f80b..e7f7b4b703 100644 --- a/designer-demo/env/.env.alpha +++ b/designer-demo/env/.env.alpha @@ -6,6 +6,11 @@ VITE_CDN_DOMAIN=https://registry.npmmirror.com VITE_CDN_TYPE=npmmirror VITE_LOCAL_IMPORT_MAPS=true VITE_LOCAL_BUNDLE_DEPS=true +# 将 VITE_LOCAL_BUNDLE_DEPS 复制到构建产物中的目录名称 +VITE_LOCAL_BUNDLE_PATH=local-cdn-static + +# VITE_LOCAL_BUNDLE_DEPS 的完整访问路径 +VITE_LOCAL_CDN_PATH=./local-cdn-static # VITE_ORIGIN= # 错误监控上报 url diff --git a/designer-demo/env/.env.localCDN.example b/designer-demo/env/.env.localCDN.example new file mode 100644 index 0000000000..2ce03c9556 --- /dev/null +++ b/designer-demo/env/.env.localCDN.example @@ -0,0 +1,16 @@ +# CDN 本地化配置示例 + +# 将本地物料 bundle.json 的 script 和 css 复制到构建产物中 +VITE_LOCAL_IMPORT_MAPS=true + +# 将画布、页面预览需要的 vue、vue-i18n 等等依赖复制到构建产物中 +VITE_LOCAL_BUNDLE_DEPS=true + +# 将 VITE_LOCAL_BUNDLE_DEPS 复制到构建产物中的目录名称 +VITE_LOCAL_BUNDLE_PATH=local-cdn-static + +# VITE_LOCAL_BUNDLE_DEPS 的完整访问路径,如果 vite.config.js 中 base 配置为 ./,则为 base + 复制的目录名称 +VITE_LOCAL_CDN_PATH=./local-cdn-static + +# ⚠️注意:如果 vite.config.js 中 base 不为 ./,则需要加上 base的路径,比如 base 为:http://opentiny.design +# VITE_LOCAL_CDN_PATH=http://opentiny.design/local-cdn-static diff --git a/packages/build/vite-config/src/default-config.js b/packages/build/vite-config/src/default-config.js index a3b65749fc..e4e59ae993 100644 --- a/packages/build/vite-config/src/default-config.js +++ b/packages/build/vite-config/src/default-config.js @@ -14,7 +14,6 @@ import { getBaseUrlFromCli, copyBundleDeps, localCdnPlugin } from './localCdnFil import { devAliasPlugin } from './vite-plugins/devAliasPlugin.js' import { htmlUpgradeHttpsPlugin } from './vite-plugins/upgradeHttpsPlugin.js' import { canvasDevExternal } from './canvas-dev-external.js' -import { createDynamicImportMapPlugin } from './vite-plugins/createDynamicImportMapPlugin.js' const monacoEditorPlugin = monacoEditorPluginCjs.default const nodeGlobalsPolyfillPlugin = nodeGlobalsPolyfillPluginCjs.default @@ -135,7 +134,12 @@ export function useTinyEngineBaseConfig(engineConfig) { const { envDir = '', viteConfigEnv } = engineConfig const { command = 'serve', mode = 'serve' } = viteConfigEnv const env = loadEnv(mode, envDir) - const { VITE_CDN_DOMAIN = 'https://unpkg.com', VITE_LOCAL_IMPORT_MAPS, VITE_LOCAL_BUNDLE_DEPS } = env + const { + VITE_CDN_DOMAIN = 'https://unpkg.com', + VITE_LOCAL_IMPORT_MAPS, + VITE_LOCAL_BUNDLE_DEPS, + VITE_LOCAL_BUNDLE_PATH + } = env const isLocalImportMap = VITE_LOCAL_IMPORT_MAPS === 'true' // true公共依赖库使用本地打包文件,false公共依赖库使用公共CDN const isCopyBundleDeps = VITE_LOCAL_BUNDLE_DEPS === 'true' // true bundle里的cdn依赖处理成本地依赖, false 不处理 const monacoPublicPath = 'editor/monaco-workers' @@ -172,7 +176,7 @@ export function useTinyEngineBaseConfig(engineConfig) { const cdnPlugins = localCdnPlugin({ localCdnConfig: engineConfig.localCdnConfig, base: getBaseUrlFromCli(config.base), - cdnDir: 'local-cdn-static' + cdnDir: VITE_LOCAL_BUNDLE_PATH }) if (cdnPlugins && cdnPlugins.length > 0) { @@ -180,14 +184,6 @@ export function useTinyEngineBaseConfig(engineConfig) { } } - config.plugins.push( - createDynamicImportMapPlugin({ - base: getBaseUrlFromCli(config.base), - cdnDir: 'local-cdn-static', - isLocalImportMap - }) - ) - config.plugins.push(devAliasPlugin(env, engineConfig.useSourceAlias)) if (engineConfig.useSourceAlias && command === 'serve') { diff --git a/packages/build/vite-config/src/localCdnFile/import-map.js b/packages/build/vite-config/src/localCdnFile/import-map.js deleted file mode 100644 index 2326ad24d1..0000000000 --- a/packages/build/vite-config/src/localCdnFile/import-map.js +++ /dev/null @@ -1,33 +0,0 @@ -export const importMapConfig = { - imports: { - vue: '${VITE_CDN_DOMAIN}/vue${versionDelimiter}3.4.23${fileDelimiter}/dist/vue.runtime.esm-browser.js', - 'vue/server-renderer': - '${VITE_CDN_DOMAIN}/@vue/server-renderer${versionDelimiter}3.4.23${fileDelimiter}/dist/server-renderer.esm-browser.js', - 'vue-i18n': '${VITE_CDN_DOMAIN}/vue-i18n${versionDelimiter}^9.9.0${fileDelimiter}/dist/vue-i18n.esm-browser.js', - 'vue-router': - '${VITE_CDN_DOMAIN}/vue-router${versionDelimiter}4.0.16${fileDelimiter}/dist/vue-router.esm-browser.js', - '@vue/devtools-api': - '${VITE_CDN_DOMAIN}/@vue/devtools-api${versionDelimiter}6.5.1${fileDelimiter}/lib/esm/index.js', - '@vueuse/core': '${VITE_CDN_DOMAIN}/@vueuse/core${versionDelimiter}9.6.0${fileDelimiter}/index.mjs', - '@vueuse/shared': '${VITE_CDN_DOMAIN}/@vueuse/shared${versionDelimiter}9.6.0${fileDelimiter}/index.mjs', - axios: '${VITE_CDN_DOMAIN}/axios${versionDelimiter}1.0.0${fileDelimiter}/dist/esm/axios.js', - '@opentiny/tiny-engine-webcomponent-core': - '${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-webcomponent-core${versionDelimiter}1${fileDelimiter}/dist/tiny-engine-webcomponent-core.es.js', - '@opentiny/tiny-engine-i18n-host': - '${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-i18n-host${versionDelimiter}1${fileDelimiter}/dist/lowcode-design-i18n-host.es.js', - '@opentiny/tiny-engine-builtin-component': - '${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-builtin-component${versionDelimiter}^2.0.0${fileDelimiter}/dist/index.mjs', - 'vue-demi': '${VITE_CDN_DOMAIN}/vue-demi${versionDelimiter}0.13.11${fileDelimiter}/lib/index.mjs', - pinia: '${VITE_CDN_DOMAIN}/pinia${versionDelimiter}2.0.22${fileDelimiter}/dist/pinia.esm-browser.js', - '@opentiny/vue': - '${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-pc.mjs', - '@opentiny/vue-icon': - '${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-icon.mjs', - '@opentiny/vue-common': - '${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-common.mjs', - '@opentiny/vue-locale': - '${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-locale.mjs', - '@opentiny/vue-renderless/': '${VITE_CDN_DOMAIN}/@opentiny/vue-renderless${versionDelimiter}~3.20${fileDelimiter}/', - echarts: '${VITE_CDN_DOMAIN}/echarts${versionDelimiter}5.4.1${fileDelimiter}/dist/echarts.esm.js' - } -} diff --git a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js index fb213cec23..3114e2d683 100644 --- a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js +++ b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js @@ -3,7 +3,6 @@ import fs from 'fs-extra' import { installPackageTemporary } from '../vite-plugins/installPackageTemporary.js' import { copyPlugin } from '../vite-plugins/cdnCopyPlugin.js' import { dedupeCopyFiles } from './locateCdnNpmInfo.js' -import { importMapConfig as importMapConfigFile } from './import-map.js' const logger = console @@ -156,7 +155,9 @@ export function localCdnPlugin({ }) { const importMapConfig = localCdnConfig.importMap || { imports: {} } const copyConfig = localCdnConfig.copy || {} - const defaultImportMapConfig = importMapConfigFile + const defaultImportMapConfig = JSON.parse( + fs.readFileSync(path.resolve(process.cwd(), './node_modules/@opentiny/tiny-engine/dist/import-map.json'), 'utf-8') + ) 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) => { diff --git a/packages/build/vite-config/src/vite-plugins/createDynamicImportMapPlugin.js b/packages/build/vite-config/src/vite-plugins/createDynamicImportMapPlugin.js deleted file mode 100644 index 0f5a425f46..0000000000 --- a/packages/build/vite-config/src/vite-plugins/createDynamicImportMapPlugin.js +++ /dev/null @@ -1,37 +0,0 @@ -import { importMapConfig } from '../localCdnFile/import-map.js' - -/** - * 创建环境变量替换插件 - * @param {string} cdnDir - 本地CDN目录名 - * @param {string} base - 基础路径 - * @param {boolean} isLocalImportMap - 是否是本地CDN - * @returns {Object} - Vite插件对象 - */ -export function createDynamicImportMapPlugin({ cdnDir, base, isLocalImportMap = false }) { - const virtualModuleId = 'virtual:import-map' - const resolvedVirtualModuleId = '\0' + virtualModuleId - - return { - name: 'vite-plugin-dynamic-import-map', - resolveId(id) { - if (id === virtualModuleId) { - return resolvedVirtualModuleId - } - }, - load(id) { - if (id === resolvedVirtualModuleId) { - if (isLocalImportMap) { - const cdnDomain = `${base.endsWith('/') ? base : base + '/'}${cdnDir}` - const newContent = JSON.stringify(importMapConfig) - .replaceAll(/\${VITE_CDN_DOMAIN}/g, cdnDomain) - .replaceAll(/\${versionDelimiter}/g, '@') - .replaceAll(/\${fileDelimiter}/g, '') - - return `export default ${newContent}` - } - - return `export default ${JSON.stringify(importMapConfig)}` - } - } - } -} diff --git a/packages/canvas/DesignCanvas/src/importMap.ts b/packages/canvas/DesignCanvas/src/importMap.ts index 8674888328..9e02487864 100644 --- a/packages/canvas/DesignCanvas/src/importMap.ts +++ b/packages/canvas/DesignCanvas/src/importMap.ts @@ -4,20 +4,20 @@ import { importMapConfig } from '@opentiny/tiny-engine-common/js/importMap/index 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' : '' + const { VITE_CDN_TYPE, VITE_CDN_DOMAIN, 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' : '' if (customImportMap?.imports?.[pkgName]) { return customImportMap.imports[pkgName] - .replace('${VITE_CDN_DOMAIN}', VITE_CDN_DOMAIN) + .replace('${VITE_CDN_DOMAIN}', VITE_LOCAL_CDN_PATH || 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('${VITE_CDN_DOMAIN}', VITE_LOCAL_CDN_PATH || VITE_CDN_DOMAIN) .replace('${versionDelimiter}', versionDelimiter) .replace('${fileDelimiter}', fileDelimiter) } @@ -27,17 +27,16 @@ export function getImportMapData(canvasDeps = { scripts: [], styles: [] }) { // 以下内容由于区块WebComponent加载需要补充 const blockRequire = { imports: { - // TODO: 删除 + // 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: [] } // 以下内容由于物料协议不支持声明子依赖而@opentiny/vue需要依赖所以需要补充 - // TODO: 删除 + // TODO: 后续版本发通知,不再内置物料,需要用户自行引入 const tinyVueRequire = { imports: { '@opentiny/vue-common': getImportUrl('@opentiny/vue-common'), diff --git a/packages/common/js/importMap/import-map.json b/packages/common/js/importMap/import-map.json new file mode 100644 index 0000000000..a006bbbff6 --- /dev/null +++ b/packages/common/js/importMap/import-map.json @@ -0,0 +1,23 @@ +{ + "imports": { + "vue": "${VITE_CDN_DOMAIN}/vue${versionDelimiter}3.4.23${fileDelimiter}/dist/vue.runtime.esm-browser.js", + "vue/server-renderer": "${VITE_CDN_DOMAIN}/@vue/server-renderer${versionDelimiter}3.4.23${fileDelimiter}/dist/server-renderer.esm-browser.js", + "vue-i18n": "${VITE_CDN_DOMAIN}/vue-i18n${versionDelimiter}^9.9.0${fileDelimiter}/dist/vue-i18n.esm-browser.js", + "vue-router": "${VITE_CDN_DOMAIN}/vue-router${versionDelimiter}4.0.16${fileDelimiter}/dist/vue-router.esm-browser.js", + "@vue/devtools-api": "${VITE_CDN_DOMAIN}/@vue/devtools-api${versionDelimiter}6.5.1${fileDelimiter}/lib/esm/index.js", + "@vueuse/core": "${VITE_CDN_DOMAIN}/@vueuse/core${versionDelimiter}9.6.0${fileDelimiter}/index.mjs", + "@vueuse/shared": "${VITE_CDN_DOMAIN}/@vueuse/shared${versionDelimiter}9.6.0${fileDelimiter}/index.mjs", + "axios": "${VITE_CDN_DOMAIN}/axios${versionDelimiter}1.0.0${fileDelimiter}/dist/esm/axios.js", + "@opentiny/tiny-engine-webcomponent-core": "${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-webcomponent-core${versionDelimiter}1${fileDelimiter}/dist/tiny-engine-webcomponent-core.es.js", + "@opentiny/tiny-engine-i18n-host": "${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-i18n-host${versionDelimiter}1${fileDelimiter}/dist/lowcode-design-i18n-host.es.js", + "@opentiny/tiny-engine-builtin-component": "${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-builtin-component${versionDelimiter}^2.0.0${fileDelimiter}/dist/index.mjs", + "vue-demi": "${VITE_CDN_DOMAIN}/vue-demi${versionDelimiter}0.13.11${fileDelimiter}/lib/index.mjs", + "pinia": "${VITE_CDN_DOMAIN}/pinia${versionDelimiter}2.0.22${fileDelimiter}/dist/pinia.esm-browser.js", + "@opentiny/vue": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-pc.mjs", + "@opentiny/vue-icon": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-icon.mjs", + "@opentiny/vue-common": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-common.mjs", + "@opentiny/vue-locale": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-locale.mjs", + "@opentiny/vue-renderless/": "${VITE_CDN_DOMAIN}/@opentiny/vue-renderless${versionDelimiter}~3.20${fileDelimiter}/", + "echarts": "${VITE_CDN_DOMAIN}/echarts${versionDelimiter}5.4.1${fileDelimiter}/dist/echarts.esm.js" + } +} diff --git a/packages/common/js/importMap/index.js b/packages/common/js/importMap/index.js index ae83e9a1cf..844734b797 100644 --- a/packages/common/js/importMap/index.js +++ b/packages/common/js/importMap/index.js @@ -1 +1 @@ -export { default as importMapConfig } from 'virtual:import-map' +export { default as importMapConfig } from './import-map.json' diff --git a/packages/common/vite.config.ts b/packages/common/vite.config.ts index aaf0a56833..3c42c52aba 100644 --- a/packages/common/vite.config.ts +++ b/packages/common/vite.config.ts @@ -17,6 +17,7 @@ import vueJsx from '@vitejs/plugin-vue-jsx' import { glob } from 'glob' import { fileURLToPath } from 'node:url' import generateComments from '@opentiny/tiny-engine-vite-plugin-meta-comments' +import { viteStaticCopy } from 'vite-plugin-static-copy' const jsEntries = glob.sync('./js/**/*.js').map((file) => { return [file.slice(0, file.length - path.extname(file).length), fileURLToPath(new URL(file, import.meta.url))] @@ -24,7 +25,20 @@ const jsEntries = glob.sync('./js/**/*.js').map((file) => { // https://vitejs.dev/config/ export default defineConfig({ - plugins: [generateComments(), vue(), vueJsx()], + plugins: [ + generateComments(), + vue(), + vueJsx(), + // 复制 import-map.json到产物,提供给构建插件读取 + viteStaticCopy({ + targets: [ + { + src: './js/importMap/import-map.json', + dest: '.' + } + ] + }) + ], publicDir: false, resolve: {}, base: './', @@ -66,8 +80,7 @@ export default defineConfig({ /@opentiny\/tiny-engine.*/, /@opentiny\/vue.*/, /^prettier.*/, - /^@babel.*/, - /^virtual:import-map$/ + /^@babel.*/ ] } } diff --git a/packages/design-core/src/preview/src/preview/importMap.js b/packages/design-core/src/preview/src/preview/importMap.js index b6a282d634..92d98d9c29 100644 --- a/packages/design-core/src/preview/src/preview/importMap.js +++ b/packages/design-core/src/preview/src/preview/importMap.js @@ -18,11 +18,12 @@ const importMap = {} const opentinyVueVersion = '~3.20' function replacePlaceholder(v) { - const versionDelimiter = useEnv().VITE_CDN_TYPE === 'npmmirror' ? '/' : '@' - const fileDelimiter = useEnv().VITE_CDN_TYPE === 'npmmirror' ? '/files' : '' + const { VITE_CDN_TYPE, VITE_CDN_DOMAIN, 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' : '' return v - .replace('${VITE_CDN_DOMAIN}', useEnv().VITE_CDN_DOMAIN) + .replace('${VITE_CDN_DOMAIN}', VITE_LOCAL_CDN_PATH || VITE_CDN_DOMAIN) .replace('${opentinyVueVersion}', opentinyVueVersion) .replace('${versionDelimiter}', versionDelimiter) .replace('${fileDelimiter}', fileDelimiter) diff --git a/packages/design-core/src/preview/src/preview/srcFiles.js b/packages/design-core/src/preview/src/preview/srcFiles.js index 1d9572b640..a416ed4c8b 100644 --- a/packages/design-core/src/preview/src/preview/srcFiles.js +++ b/packages/design-core/src/preview/src/preview/srcFiles.js @@ -10,6 +10,7 @@ * */ +import { useEnv } from '@opentiny/tiny-engine-meta-register' import appVue from './srcFiles/App.vue?raw' import injectGlobalJS from './srcFiles/injectGlobal.js?raw' import constantJS from './srcFiles/constant/index.js?raw' @@ -25,15 +26,16 @@ import storesJS from './srcFiles/stores.js?raw' import storesHelperJS from './srcFiles/storesHelper.js?raw' const srcFiles = {} - -const versionDelimiter = import.meta.env.VITE_CDN_TYPE === 'npmmirror' ? '/' : '@' -const fileDelimiter = import.meta.env.VITE_CDN_TYPE === 'npmmirror' ? '/files' : '' +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' : '' srcFiles['App.vue'] = appVue srcFiles['Main.vue'] = mainVue srcFiles['constant.js'] = constantJS srcFiles['app.js'] = appJS - .replaceAll('${VITE_CDN_DOMAIN}', import.meta.env.VITE_CDN_DOMAIN) + .replaceAll('${VITE_CDN_DOMAIN}', import.meta.env.VITE_LOCAL_CDN_PATH || import.meta.env.VITE_CDN_DOMAIN) .replaceAll('${versionDelimiter}', versionDelimiter) .replaceAll('${fileDelimiter}', fileDelimiter) @@ -62,7 +64,7 @@ export const genPreviewTemplate = () => { { fileName: 'app.js', path: '', - fileContent: appJS.replace(/VITE_CDN_DOMAIN/g, import.meta.env.VITE_CDN_DOMAIN) + fileContent: appJS.replace(/VITE_CDN_DOMAIN/g, useEnv().VITE_LOCAL_CDN_PATH || useEnv().VITE_CDN_DOMAIN) }, { fileName: 'injectGlobal.js', diff --git a/packages/design-core/vite.config.js b/packages/design-core/vite.config.js index 692f75e9ed..b0ae2ded57 100644 --- a/packages/design-core/vite.config.js +++ b/packages/design-core/vite.config.js @@ -6,6 +6,7 @@ import nodeGlobalsPolyfillPluginCjs from '@esbuild-plugins/node-globals-polyfill import nodeModulesPolyfillPluginCjs from '@esbuild-plugins/node-modules-polyfill' import nodePolyfill from 'rollup-plugin-polyfill-node' import { fileURLToPath } from 'node:url' +import { viteStaticCopy } from 'vite-plugin-static-copy' const nodeGlobalsPolyfillPlugin = nodeGlobalsPolyfillPluginCjs.default const nodeModulesPolyfillPlugin = nodeModulesPolyfillPluginCjs.default @@ -31,7 +32,19 @@ const addViteIgnorePlugin = () => { } export default defineConfig({ - plugins: [vue(), vueJsx()], + plugins: [ + vue(), + vueJsx(), + // 复制 import-map.json到产物,提供给构建插件读取 + viteStaticCopy({ + targets: [ + { + src: './node_modules/@opentiny/tiny-engine-common/dist/import-map.json', + dest: '.' + } + ] + }) + ], publicDir: false, optimizeDeps: { esbuildOptions: { @@ -53,7 +66,8 @@ export default defineConfig({ 'import.meta.env.VITE_ORIGIN': 'import.meta.env.VITE_ORIGIN', 'import.meta.env.VITE_CDN_DOMAIN': 'import.meta.env.VITE_CDN_DOMAIN', 'import.meta.env.VITE_API_MOCK': 'import.meta.env.VITE_API_MOCK', - 'import.meta.env.VITE_CDN_TYPE': 'import.meta.env.VITE_CDN_TYPE' + 'import.meta.env.VITE_CDN_TYPE': 'import.meta.env.VITE_CDN_TYPE', + 'import.meta.env.VITE_LOCAL_CDN_PATH': 'import.meta.env.VITE_LOCAL_CDN_PATH' }, build: { commonjsOptions: { @@ -79,7 +93,7 @@ export default defineConfig({ } } }, - external: ['vue', 'monaco-editor', 'prettier', /@opentiny\/vue.*/, /^@opentiny\/tiny-engine*/] + external: ['vue', 'monaco-editor', 'prettier', /@opentiny\/vue.*/, '@opentiny/tiny-engine-meta-register'] } } }) From 87bf6c2aaa8e61ce6d2b64996e5e6789212546e0 Mon Sep 17 00:00:00 2001 From: chilingling Date: Fri, 25 Apr 2025 18:08:09 +0800 Subject: [PATCH 15/27] fix: del useless test case --- designer-demo/env/.env.alpha | 7 ------- designer-demo/package.json | 2 +- designer-demo/tests/localCdnBasic.test.js | 19 +++---------------- .../tests/localCdnCustomConfig.test.js | 19 +++---------------- designer-demo/tests/utils/envHelpers.js | 10 +++++----- designer-demo/vitest.config.js | 4 ++-- 6 files changed, 14 insertions(+), 47 deletions(-) diff --git a/designer-demo/env/.env.alpha b/designer-demo/env/.env.alpha index e7f7b4b703..ef7fa0443c 100644 --- a/designer-demo/env/.env.alpha +++ b/designer-demo/env/.env.alpha @@ -4,13 +4,6 @@ NODE_ENV=production VITE_CDN_DOMAIN=https://registry.npmmirror.com # 使用npmmirror的cdn 时,需要声明 VITE_CDN_TYPE=npmmirror VITE_CDN_TYPE=npmmirror -VITE_LOCAL_IMPORT_MAPS=true -VITE_LOCAL_BUNDLE_DEPS=true -# 将 VITE_LOCAL_BUNDLE_DEPS 复制到构建产物中的目录名称 -VITE_LOCAL_BUNDLE_PATH=local-cdn-static - -# VITE_LOCAL_BUNDLE_DEPS 的完整访问路径 -VITE_LOCAL_CDN_PATH=./local-cdn-static # VITE_ORIGIN= # 错误监控上报 url diff --git a/designer-demo/package.json b/designer-demo/package.json index ed1f2cb616..7ead5cc9fa 100644 --- a/designer-demo/package.json +++ b/designer-demo/package.json @@ -28,6 +28,6 @@ "@vitejs/plugin-vue": "^5.1.2", "cross-env": "^7.0.3", "vite": "^5.4.2", - "vitest": "^3.0.9" + "vitest": "3.0.9" } } diff --git a/designer-demo/tests/localCdnBasic.test.js b/designer-demo/tests/localCdnBasic.test.js index dd8a02a8ba..3763ced62c 100644 --- a/designer-demo/tests/localCdnBasic.test.js +++ b/designer-demo/tests/localCdnBasic.test.js @@ -27,6 +27,8 @@ describe('localCDN 功能测试', () => { // 确保关键环境变量已启用 envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_IMPORT_MAPS') envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_DEPS') + envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_CDN_PATH', './local-cdn-static') + envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_PATH', 'local-cdn-static') // 写回更新后的环境变量 fs.writeFileSync(envAlphaPath, envContent) @@ -66,23 +68,8 @@ describe('localCDN 功能测试', () => { expect(runtimeDirs).toBeDefined() - const vueProdDist = path.resolve(localCdnDir, runtimeDirs, 'dist/vue.runtime.esm-browser.prod.js') + const vueProdDist = path.resolve(localCdnDir, runtimeDirs, 'dist/vue.runtime.esm-browser.js') expect(fs.existsSync(vueProdDist)).toBe(true) }) - - 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') - }) }) \ No newline at end of file diff --git a/designer-demo/tests/localCdnCustomConfig.test.js b/designer-demo/tests/localCdnCustomConfig.test.js index d33b6808b4..b9296b2b4e 100644 --- a/designer-demo/tests/localCdnCustomConfig.test.js +++ b/designer-demo/tests/localCdnCustomConfig.test.js @@ -75,7 +75,7 @@ describe('localCDN 自定义配置测试', () => { localCdnConfig: { importMap: { imports: { - 'vue': "\${VITE_CDN_DOMAIN}/vue\${versionDelimiter}3.4.21\${fileDelimiter}/dist/vue.runtime.esm-browser.js" + 'vue': "\${VITE_CDN_DOMAIN}/vue\${versionDelimiter}3.4.21\${fileDelimiter}/dist/vue.runtime.esm-browser.prod.js" } }, copy: { @@ -94,6 +94,8 @@ describe('localCDN 自定义配置测试', () => { // 确保关键环境变量已启用 envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_IMPORT_MAPS') envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_DEPS') + envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_CDN_PATH', './local-cdn-static') + envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_PATH', 'local-cdn-static') // 写回更新后的环境变量 fs.writeFileSync(envAlphaPath, envContent) @@ -155,19 +157,4 @@ describe('localCDN 自定义配置测试', () => { expect(distFileCount).toBe(true) }) - - it('should modify the environment variable to set VITE_CDN_DOMAIN to ./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(jsFiles[0], 'utf-8') - - // 检查 VITE_CDN_DOMAIN 环境变量是否被替换为本地路径 - expect(mainJsContent).toContain('VITE_CDN_DOMAIN:"./local-cdn-static"') - }) }) \ No newline at end of file diff --git a/designer-demo/tests/utils/envHelpers.js b/designer-demo/tests/utils/envHelpers.js index 2ce46683ef..6d97f894eb 100644 --- a/designer-demo/tests/utils/envHelpers.js +++ b/designer-demo/tests/utils/envHelpers.js @@ -9,17 +9,17 @@ import fs from 'node:fs' * @param {string} key - 环境变量名 * @returns {string} - 更新后的内容 */ -export function ensureEnvVarEnabled(content, key) { +export function ensureEnvVarEnabled(content, key, value = 'true') { // 检查是否包含该环境变量 const regex = new RegExp(`${key}\\s*=\\s*(.*)`, 'm') const match = content.match(regex) if (!match) { // 变量不存在,添加 - return `${content}\n${key}=true` - } else if (match[1].trim() === 'false') { - // 变量存在但值为false,替换为true - return content.replace(regex, `${key}=true`) + return `${content}\n${key}=${value}` + } else if (match[1].trim() !== value) { + // 变量存在但值为跟 value 不相等,替换为提供的值 value + return content.replace(regex, `${key}=${value}`) } // 变量已存在且不是false,保持不变 diff --git a/designer-demo/vitest.config.js b/designer-demo/vitest.config.js index 2ec24703db..be7417831a 100644 --- a/designer-demo/vitest.config.js +++ b/designer-demo/vitest.config.js @@ -5,9 +5,9 @@ export default defineConfig({ test: { globals: true, environment: 'node', - testTimeout: 1000000, // 10分钟超时,因为构建可能需要较长时间 + testTimeout: 1_000 * 60 * 10, // 10分钟超时,因为构建可能需要较长时间 include: ['tests/**/*.test.js'], - hookTimeout: 1000000, // 10分钟超时,因为构建可能需要较长时间 + hookTimeout: 1_000 * 60 * 10, // 10分钟超时,因为构建可能需要较长时间 // 这里需要串行执行,否则构建会相互覆盖,无法测试 fileParallelism: false }, From 3a063d50b9a1744a3a87442c85b38e2e5be396d6 Mon Sep 17 00:00:00 2001 From: chilingling Date: Sun, 27 Apr 2025 09:17:41 +0800 Subject: [PATCH 16/27] doc: update localCDN docs --- docs/solutions/local-cdn.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/solutions/local-cdn.md b/docs/solutions/local-cdn.md index 5db2d96696..dbaf1bb33a 100644 --- a/docs/solutions/local-cdn.md +++ b/docs/solutions/local-cdn.md @@ -2,7 +2,7 @@ ## 概述 -本地化CDN是一种在生产环境中将远程CDN资源替换为本地文件的解决方案。它解决了以下问题: +本地化CDN是一种在生产环境中将远程CDN资源替换为本地文件的解决方案。它具有以下优势: 1. 减少对外部CDN的依赖,提高应用的可靠性 2. 在离线环境或内网环境中使用CDN资源 @@ -24,6 +24,15 @@ VITE_LOCAL_IMPORT_MAPS=true # 将物料需要的CDN 资源进行本地化。注意⚠️:这里需要您的物料package需要能够通过 npm 的方式进行下载,否则会失效。 VITE_LOCAL_BUNDLE_DEPS=true + +# 将 VITE_LOCAL_BUNDLE_DEPS 复制到构建产物中的目录名称 +VITE_LOCAL_BUNDLE_PATH=local-cdn-static + +# VITE_LOCAL_BUNDLE_DEPS 的完整访问路径,如果 vite.config.js 中 base 配置为 ./,则为 base + 复制的目录名称 +VITE_LOCAL_CDN_PATH=./local-cdn-static + +# ⚠️注意:如果 vite.config.js 中 base 不为 ./,则需要加上 base的路径,比如 base 为:http://opentiny.design +# VITE_LOCAL_CDN_PATH=http://opentiny.design/local-cdn-static ``` 2. 【可选】 在 `vite.config.js` 中传入自定义配置 @@ -180,11 +189,7 @@ VITE_CDN_DOMAIN=https://unpkg.com 插件会检查所需的依赖包是否已在本地存在,对于不存在或版本不匹配的包,会通过`installPackageTemporary`函数临时安装到指定目录。 -### 3. 替换环境变量 - -通过创建`createEnvReplacementPlugin`插件,将环境变量中的CDN域名替换为本地路径,确保应用在构建时使用本地资源而不是远程CDN。 - -### 4. 复制并转换文件 +### 3. 复制并转换文件 对于已识别的CDN依赖,插件会: @@ -192,7 +197,7 @@ VITE_CDN_DOMAIN=https://unpkg.com - 对JavaScript文件进行路径替换转换 (比如: `import push from './push'` 需要改成 `import push from './push.js'`) - 保持目录结构与原始CDN一致 -### 5. 处理Bundle文件 +### 4. 处理Bundle文件 `copyBundleDeps`功能专门处理bundle文件中的CDN链接: @@ -202,6 +207,5 @@ VITE_CDN_DOMAIN=https://unpkg.com ## 注意事项 -1. 确保所有需要本地化的CDN依赖在package.json中有相应的版本定义 -2. 本地化CDN会增加构建输出的大小,但会提高应用的可靠性和性能 -3. 某些特定格式的CDN URL可能需要在`copy`中进行特别配置 \ No newline at end of file +1. 本地化CDN会增加构建输出的大小,但会提高应用的可靠性和性能 +2. 某些特定格式的CDN URL可能需要在`copy`中进行特别配置 From 632b5991265ba5a8b66071c61e7f499a9fe01a34 Mon Sep 17 00:00:00 2001 From: chilingling Date: Sun, 27 Apr 2025 09:34:37 +0800 Subject: [PATCH 17/27] feat: use semver to compare version --- packages/build/vite-config/package.json | 1 + .../vite-config/src/localCdnFile/localCdnPlugin.js | 13 ++----------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/build/vite-config/package.json b/packages/build/vite-config/package.json index c02b2c0765..f42f804929 100644 --- a/packages/build/vite-config/package.json +++ b/packages/build/vite-config/package.json @@ -40,6 +40,7 @@ "path": "^0.12.7", "rollup-plugin-polyfill-node": "^0.13.0", "rollup-plugin-visualizer": "^5.8.3", + "semver": "^7.7.1", "shelljs": "^0.8.5", "svg-sprite-loader": "^6.0.11", "vite": "^5.4.2", diff --git a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js index 3114e2d683..496088bb9d 100644 --- a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js +++ b/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js @@ -1,5 +1,6 @@ import path from 'node:path' import fs from 'fs-extra' +import semver from 'semver' import { installPackageTemporary } from '../vite-plugins/installPackageTemporary.js' import { copyPlugin } from '../vite-plugins/cdnCopyPlugin.js' import { dedupeCopyFiles } from './locateCdnNpmInfo.js' @@ -57,17 +58,7 @@ const compareIsSameVersion = (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 + return semver.satisfies(versionTarget, versionOrigin) } function getCdnPathNpmInfo( From 319fd69c7b6879347d7fa4c7d486324a51d2bd58 Mon Sep 17 00:00:00 2001 From: chilingling Date: Sun, 27 Apr 2025 10:46:09 +0800 Subject: [PATCH 18/27] fix: only process .js or .mjs file content --- .../src/vite-plugins/cdnCopyPlugin.js | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js b/packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js index a80bdbe10e..d2670f7caf 100644 --- a/packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js +++ b/packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js @@ -4,21 +4,37 @@ import fg from 'fast-glob' import { babelReplaceImportPathWithCertainFileName } from '../localCdnFile/replaceImportPath.mjs' /** - * 对文件内容进行转换处理 + * 对.js和.mjs文件内容进行转换处理,将import路径如 import { a } from './b' 转换为 import { a} from './b.js' * @param {string} content - 文件内容 * @param {string} filename - 文件名 * @returns {string} - 处理后的内容 */ function replaceJsImportPaths(content, filename) { - if (filename.endsWith('.js')) { - const result = babelReplaceImportPathWithCertainFileName(content, filename, console) - return result.code || content - } - return content + const result = babelReplaceImportPathWithCertainFileName(content, filename, console) + + return result.code || content } const logger = console +async function copyFile(srcPath, destPath) { + // 确保目标文件的目录存在 + await fs.ensureDir(path.dirname(destPath)) + + if (srcPath.endsWith('.js') || srcPath.endsWith('.mjs')) { + // 读取文件内容 + const content = await fs.readFile(srcPath, 'utf-8') + + // 应用转换 + const transformedContent = replaceJsImportPaths(content, srcPath) + // 写入转换后的内容 + await fs.writeFile(destPath, transformedContent) + } else { + // 复制文件 + await fs.copyFile(srcPath, destPath) + } +} + /** * 复制文件或目录到目标路径 * @param {string} srcPath - 源文件/目录路径 @@ -73,17 +89,7 @@ async function copyFileOrDirectory(srcPath, destPaths, copiedFiles, outDir) { 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) + await copyFile(file, destFilePath) } } else { // 如果是单个文件 @@ -91,17 +97,7 @@ async function copyFileOrDirectory(srcPath, destPaths, copiedFiles, outDir) { 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) + await copyFile(srcPath, finalDestPath) } logger.log(`[vite-cdn-copy-plugin]: Successfully copied: ${srcPath} -> ${fullDestPath}`) From 59f81b39cf9e881d18b46abde2877da3b994f41f Mon Sep 17 00:00:00 2001 From: chilingling Date: Sun, 27 Apr 2025 14:18:30 +0800 Subject: [PATCH 19/27] fix: making some variables more semantic. --- .../tests/localCdnCustomConfig.test.js | 2 +- docs/README.md | 2 +- .../{local-cdn.md => import-map-local.md} | 37 ++++++++------ .../build/vite-config/src/default-config.js | 10 ++-- ...alCdnPlugin.js => importMapLocalPlugin.js} | 50 +++++++++++++++---- .../vite-config/src/localCdnFile/index.js | 2 +- .../src/vite-plugins/cdnCopyPlugin.js | 4 -- 7 files changed, 69 insertions(+), 38 deletions(-) rename docs/solutions/{local-cdn.md => import-map-local.md} (72%) rename packages/build/vite-config/src/localCdnFile/{localCdnPlugin.js => importMapLocalPlugin.js} (75%) diff --git a/designer-demo/tests/localCdnCustomConfig.test.js b/designer-demo/tests/localCdnCustomConfig.test.js index b9296b2b4e..448581f6ee 100644 --- a/designer-demo/tests/localCdnCustomConfig.test.js +++ b/designer-demo/tests/localCdnCustomConfig.test.js @@ -72,7 +72,7 @@ describe('localCDN 自定义配置测试', () => { const updatedViteConfig = originalViteConfig.replace( 'const baseConfig = useTinyEngineBaseConfig({', `const baseConfig = useTinyEngineBaseConfig({ - localCdnConfig: { + importMapLocalConfig: { importMap: { imports: { 'vue': "\${VITE_CDN_DOMAIN}/vue\${versionDelimiter}3.4.21\${fileDelimiter}/dist/vue.runtime.esm-browser.prod.js" diff --git a/docs/README.md b/docs/README.md index 430923eec5..032c7fbb53 100644 --- a/docs/README.md +++ b/docs/README.md @@ -46,7 +46,7 @@ - [区块局域网发布方案(Node.js服务端)](./solutions/block-lan-release-solution.md) - [设计器中引入第三方组件库](./solutions/third-party-library-in-designer.md) - [物料同步方案](./solutions/material-sync-solution.md) - - [本地化CDN方案](./solutions/local-cdn.md) + - [本地化CDN方案](./solutions/import-map-local.md) - 扩展能力介绍 - [新架构介绍](./extension-capabilities-overview/new-architecture.md) - [注册表](./extension-capabilities-overview/registry.md) diff --git a/docs/solutions/local-cdn.md b/docs/solutions/import-map-local.md similarity index 72% rename from docs/solutions/local-cdn.md rename to docs/solutions/import-map-local.md index dbaf1bb33a..3a758417ef 100644 --- a/docs/solutions/local-cdn.md +++ b/docs/solutions/import-map-local.md @@ -1,18 +1,25 @@ -# 本地化CDN方案 +# 本地化 import-map CDN方案 ## 概述 -本地化CDN是一种在生产环境中将远程CDN资源替换为本地文件的解决方案。它具有以下优势: +TinyEngine 在画布和预览都使用了 import-map 的方案来依赖 vue、vue-i18n 以及物料等依赖,这些 import-map 默认会使用 npmmirror 的 CDN。 +但是,在一些企业化的场景中,我们无法依赖这种外部的 CDN。(可用性和稳定性要求。) + +当前可以采取的方案有: +- 搭建私网的 unpkg +- 使用本文档介绍的 import-map CDN 本地化方案 + +本地化 import-map CDN是一种在构建时,将 import-map 所需的远程CDN资源替换为构建产物中的文件的解决方案。它具有以下优势: 1. 减少对外部CDN的依赖,提高应用的可靠性 -2. 在离线环境或内网环境中使用CDN资源 -3. 加快资源加载速度,提升应用性能 +2. 在离线环境或内网环境中访问 import-map 所需的 CDN 资源。 +3. 加快资源加载速度,提升应用性能。 ## 使用方法 -### 修改环境变量使用本地化CDN +### 修改环境变量使用 import-map CDN 本地化方案 -要启用本地化CDN功能,请按照以下步骤操作: +要启用 import-map CDN 本地化功能,请按照以下步骤操作: 1. 修改环境变量 @@ -39,7 +46,7 @@ VITE_LOCAL_CDN_PATH=./local-cdn-static ```javascript const baseConfig = useTinyEngineBaseConfig({ - localCdnConfig: { + importMapLocalConfig: { importMap: { imports: { ... } }, copy: { ... } } @@ -53,13 +60,13 @@ CDN 本地化接受以下配置选项: | 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| -| localCdnConfig | Object | `{ importMap: { imports: {} }, copy: {} }` | 本地CDN配置对象 | -| localCdnConfig.importMap | Object | `{ imports: {} }` | 导入映射配置,定义需要本地化的CDN依赖 | -| localCdnConfig.copy | Object | `{}` | 自定义复制配置,可以覆盖特定包的默认配置 | +| importMapLocalConfig | Object | `{ importMap: { imports: {} }, copy: {} }` | 本地CDN配置对象 | +| importMapLocalConfig.importMap | Object | `{ imports: {} }` | 导入映射配置,定义需要本地化的CDN依赖 | +| importMapLocalConfig.copy | Object | `{}` | 自定义复制配置,可以覆盖特定包的默认配置 | #### importMap 详细说明 -`localCdnConfig.importMap` 是一个包含 `imports` 属性的对象,它定义了需要本地化的CDN依赖。在插件内部,它会与默认的导入映射配置合并。 +`importMapLocalConfig.importMap` 是一个包含 `imports` 属性的对象,它定义了需要本地化的CDN依赖。在插件内部,它会与默认的导入映射配置合并。 import-map.json 的格式示例: ```json @@ -79,14 +86,14 @@ URL格式说明: 插件将解析这些URL,提取包名、版本和文件路径,然后在构建时将它们替换为本地路径。 -**重要说明**:如果您在 Vite 配置中传递了 `localCdnConfig.importMap`,还需要在 registry 注册表的 config 中传入同样的配置,以确保应用在运行时能正确读取自定义的 importMap 配置。例如: +**重要说明**:如果您在 Vite 配置中传递了 `importMapLocalConfig.importMap`,还需要在 registry 注册表的 config 中传入同样的配置,以确保应用在运行时能正确读取自定义的 importMap 配置。例如: ```javascript // 在注册表配置中 { config: { id: 'engine.config', - importMap: localCdnConfig.importMap, + importMap: importMapLocalConfig.importMap, // ... 其他配置 } } @@ -96,7 +103,7 @@ URL格式说明: #### copy 详细说明 -`localCdnConfig.copy` 是一个可选的配置对象,用于覆盖特定包的默认复制配置。它的结构是一个对象,键是包名,值是该包的复制配置。 +`importMapLocalConfig.copy` 是一个可选的配置对象,用于覆盖特定包的默认复制配置。它的结构是一个对象,键是包名,值是该包的复制配置。 默认配置如下: ```javascript @@ -179,7 +186,7 @@ VITE_CDN_DOMAIN=https://unpkg.com ### 1. 分析导入映射并收集依赖 -`localCdnPlugin`会分析提供的`localCdnConfig`和默认的导入映射,识别所有需要本地化的CDN依赖。这个过程包括: +`importMapLocalPlugin`会分析提供的`importMapLocalConfig`和默认的导入映射,识别所有需要本地化的CDN依赖。这个过程包括: - 解析CDN URL获取包名、版本和文件路径 - 合并用户配置和默认配置 diff --git a/packages/build/vite-config/src/default-config.js b/packages/build/vite-config/src/default-config.js index e4e59ae993..a21f6bc39a 100644 --- a/packages/build/vite-config/src/default-config.js +++ b/packages/build/vite-config/src/default-config.js @@ -10,7 +10,7 @@ import esbuildCopy from 'esbuild-plugin-copy' import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' import visualizerCjs from 'rollup-plugin-visualizer' import generateComment from '@opentiny/tiny-engine-vite-plugin-meta-comments' -import { getBaseUrlFromCli, copyBundleDeps, localCdnPlugin } from './localCdnFile/index.js' +import { getBaseUrlFromCli, copyBundleDeps, importMapLocalPlugin } from './localCdnFile/index.js' import { devAliasPlugin } from './vite-plugins/devAliasPlugin.js' import { htmlUpgradeHttpsPlugin } from './vite-plugins/upgradeHttpsPlugin.js' import { canvasDevExternal } from './canvas-dev-external.js' @@ -173,14 +173,14 @@ export function useTinyEngineBaseConfig(engineConfig) { const logger = console logger.log('[local-cdn-plugin]: Initializing local CDN plugin') - const cdnPlugins = localCdnPlugin({ - localCdnConfig: engineConfig.localCdnConfig, + const importMapPlugins = importMapLocalPlugin({ + importMapLocalConfig: engineConfig.importMapLocalConfig, base: getBaseUrlFromCli(config.base), cdnDir: VITE_LOCAL_BUNDLE_PATH }) - if (cdnPlugins && cdnPlugins.length > 0) { - config.plugins.push(...cdnPlugins) + if (importMapPlugins && importMapPlugins.length > 0) { + config.plugins.push(...importMapPlugins) } } diff --git a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js b/packages/build/vite-config/src/localCdnFile/importMapLocalPlugin.js similarity index 75% rename from packages/build/vite-config/src/localCdnFile/localCdnPlugin.js rename to packages/build/vite-config/src/localCdnFile/importMapLocalPlugin.js index 496088bb9d..e0cf5b5777 100644 --- a/packages/build/vite-config/src/localCdnFile/localCdnPlugin.js +++ b/packages/build/vite-config/src/localCdnFile/importMapLocalPlugin.js @@ -25,6 +25,15 @@ const defaultCopyConfig = { /** * 从importMapUrl字符串中提取包名、版本和文件路径 + * ${versionDelimiter} 和 ${fileDelimiter} 是默认的 importMapUrl 中的占位符, + * 例如: + * importMapUrl = '${VITE_CDN_DOMAIN}/${packageName}${versionDelimiter}${version}${fileDelimiter}${filePath}' + * 提取的信息对象: + * { + * packageName: '${packageName}', + * version: '${version}', + * filePathInPackage: '${filePath}' + * } * @param {string} str - 导入字符串 * @returns {Object} - 提取的信息对象 * @returns {string} packageName - 包名 @@ -43,7 +52,7 @@ function extractInfo(str) { filePathInPackage: filePath || '/' } } catch (error) { - logger.error(`[local-cdn-plugin]: Failed to extract info from ${str} 提取 importMap 信息失败`, error) + logger.error(`[import-map-local-plugin]: Failed to extract info from ${str} 提取 importMap 信息失败`, error) } } @@ -130,22 +139,41 @@ function getCdnPathNpmInfo( /** * 本地化CDN插件 * @param {Object} options - 配置选项 - * @param {Object} options.localCdnConfig - 本地CDN配置 - * @param {Object} options.localCdnConfig.importMap - 导入映射配置,定义需要本地化的CDN依赖 - * @param {Object} options.localCdnConfig.copy - 自定义复制配置,可以覆盖特定包的默认配置 + * @param {Object} options.importMapLocalConfig - 本地CDN配置 + * @param {Object} options.importMapLocalConfig.importMap - 导入映射配置,定义需要本地化的CDN依赖 + * @param {Object} options.importMapLocalConfig.copy - 自定义复制配置,可以覆盖特定包的默认配置 * @param {string} options.base - 构建的base URL * @param {string} options.cdnDir - 构建目录中的CDN文件夹名称 * @param {string} options.bundleTempDir - 临时存放下载的包的目录 * @returns {Array} - Vite插件数组 */ -export function localCdnPlugin({ - localCdnConfig = { importMap: { imports: {} }, copy: {} }, +export function importMapLocalPlugin({ + importMapLocalConfig = { importMap: { imports: {} }, copy: {} }, base = './', cdnDir = 'local-cdn-static', // 构建目录中的CDN文件夹名称 bundleTempDir = 'bundle-deps/local-cdn' // 临时存放下载的包的目录 }) { - const importMapConfig = localCdnConfig.importMap || { imports: {} } - const copyConfig = localCdnConfig.copy || {} + let importMapConfig = importMapLocalConfig.importMap + + if (!importMapConfig || typeof importMapConfig !== 'object') { + logger.warn('[import-map-local-plugin]: Invalid importMapLocalConfig, using defaults') + + importMapConfig = { imports: {}, copy: {} } + } + + if (!importMapConfig.imports || typeof importMapConfig.imports !== 'object') { + logger.warn('[import-map-local-plugin]: Invalid importMapConfig, using defaults') + + importMapConfig.imports = {} + } + + if (!importMapConfig.copy || typeof importMapConfig.copy !== 'object') { + logger.warn('[import-map-local-plugin]: Invalid copyConfig, using defaults') + + importMapConfig.copy = {} + } + + const copyConfig = importMapConfig.copy || {} const defaultImportMapConfig = JSON.parse( fs.readFileSync(path.resolve(process.cwd(), './node_modules/@opentiny/tiny-engine/dist/import-map.json'), 'utf-8') ) @@ -157,7 +185,7 @@ export function localCdnPlugin({ const combinedImportMapConfig = [...overriddenImportMap, ...parsedImportMapConfig] if (combinedImportMapConfig.length === 0) { - logger.warn('[local-cdn-plugin]: No CDN dependencies found or configured') + logger.warn('[import-map-local-plugin]: No CDN dependencies found or configured') return [] } const combinedCopyConfig = { ...defaultCopyConfig, ...copyConfig } @@ -181,9 +209,9 @@ export function localCdnPlugin({ // 日志一下将要处理的内容 logger.log( - `[local-cdn-plugin]: Processing ${combinedImportMapConfig.length} CDN dependencies to local directory: ${cdnDir}` + `[import-map-local-plugin]: Processing ${combinedImportMapConfig.length} CDN dependencies to local directory: ${cdnDir}` ) - logger.log(`[local-cdn-plugin]: Need to install ${packageNeedToInstall.length} packages`) + logger.log(`[import-map-local-plugin]: Need to install ${packageNeedToInstall.length} packages`) const targetFiles = dedupeCopyFiles(cdnFiles) // 返回插件数组 diff --git a/packages/build/vite-config/src/localCdnFile/index.js b/packages/build/vite-config/src/localCdnFile/index.js index c9f0dca49d..8becd9c4e2 100644 --- a/packages/build/vite-config/src/localCdnFile/index.js +++ b/packages/build/vite-config/src/localCdnFile/index.js @@ -4,4 +4,4 @@ export * from './copyPreviewImportMap.js' export * from './utils.js' export * from './locateCdnNpmInfo.js' export * from './replaceImportPath.mjs' -export * from './localCdnPlugin.js' +export * from './importMapLocalPlugin.js' diff --git a/packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js b/packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js index d2670f7caf..8d77cedcfe 100644 --- a/packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js +++ b/packages/build/vite-config/src/vite-plugins/cdnCopyPlugin.js @@ -69,8 +69,6 @@ async function copyFileOrDirectory(srcPath, destPaths, copiedFiles, outDir) { // 确保目标目录存在 await fs.ensureDir(path.dirname(fullDestPath)) - logger.log(`[vite-cdn-copy-plugin]: Copying from ${srcPath} to ${fullDestPath}`) - if (isDirectory) { // 如果是目录,使用 fast-glob 遍历所有文件并处理 logger.log(`[vite-cdn-copy-plugin]: Copying directory recursively: ${srcPath} -> ${fullDestPath}`) @@ -99,8 +97,6 @@ async function copyFileOrDirectory(srcPath, destPaths, copiedFiles, outDir) { await copyFile(srcPath, finalDestPath) } - - logger.log(`[vite-cdn-copy-plugin]: Successfully copied: ${srcPath} -> ${fullDestPath}`) } catch (err) { logger.error(`[vite-cdn-copy-plugin]: Failed to copy ${srcPath} to ${fullDestPath}`, err) } From b9a25a3f14da22e82c0076f9dbaddb58d7c55f52 Mon Sep 17 00:00:00 2001 From: chilingling Date: Sun, 27 Apr 2025 14:37:59 +0800 Subject: [PATCH 20/27] fix: change by review comment --- .../src/localCdnFile/importMapLocalPlugin.js | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/packages/build/vite-config/src/localCdnFile/importMapLocalPlugin.js b/packages/build/vite-config/src/localCdnFile/importMapLocalPlugin.js index e0cf5b5777..0a2bf2caba 100644 --- a/packages/build/vite-config/src/localCdnFile/importMapLocalPlugin.js +++ b/packages/build/vite-config/src/localCdnFile/importMapLocalPlugin.js @@ -136,6 +136,30 @@ function getCdnPathNpmInfo( } } +function parseImportMapLocalConfig(importMapLocalConfig) { + let parsedImportMapLocalConfig = importMapLocalConfig + + if (!parsedImportMapLocalConfig || typeof parsedImportMapLocalConfig !== 'object') { + logger.warn('[import-map-local-plugin]: Invalid importMapLocalConfig, using defaults') + + parsedImportMapLocalConfig = { importMap: { imports: {} }, copy: {} } + } + + if (!parsedImportMapLocalConfig.importMap || typeof parsedImportMapLocalConfig.importMap !== 'object') { + logger.warn('[import-map-local-plugin]: Invalid importMapConfig, using defaults') + + parsedImportMapLocalConfig.importMap = { imports: {} } + } + + if (!parsedImportMapLocalConfig.copy || typeof parsedImportMapLocalConfig.copy !== 'object') { + logger.warn('[import-map-local-plugin]: Invalid copyConfig, using defaults') + + parsedImportMapLocalConfig.copy = {} + } + + return parsedImportMapLocalConfig +} + /** * 本地化CDN插件 * @param {Object} options - 配置选项 @@ -153,32 +177,18 @@ export function importMapLocalPlugin({ cdnDir = 'local-cdn-static', // 构建目录中的CDN文件夹名称 bundleTempDir = 'bundle-deps/local-cdn' // 临时存放下载的包的目录 }) { - let importMapConfig = importMapLocalConfig.importMap - - if (!importMapConfig || typeof importMapConfig !== 'object') { - logger.warn('[import-map-local-plugin]: Invalid importMapLocalConfig, using defaults') - - importMapConfig = { imports: {}, copy: {} } - } - - if (!importMapConfig.imports || typeof importMapConfig.imports !== 'object') { - logger.warn('[import-map-local-plugin]: Invalid importMapConfig, using defaults') - - importMapConfig.imports = {} - } - - if (!importMapConfig.copy || typeof importMapConfig.copy !== 'object') { - logger.warn('[import-map-local-plugin]: Invalid copyConfig, using defaults') - - importMapConfig.copy = {} - } + let parsedImportMapLocalConfig = parseImportMapLocalConfig(importMapLocalConfig) - const copyConfig = importMapConfig.copy || {} + const copyConfig = parsedImportMapLocalConfig.copy || {} const defaultImportMapConfig = JSON.parse( fs.readFileSync(path.resolve(process.cwd(), './node_modules/@opentiny/tiny-engine/dist/import-map.json'), 'utf-8') ) - 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) + const parsedImportMapConfig = Object.values(parsedImportMapLocalConfig.importMap.imports) + .map((item) => extractInfo(item)) + .filter(Boolean) const overriddenImportMap = parsedDefaultImportMapConfig.filter((item) => { return !parsedImportMapConfig.find((parsedItem) => parsedItem.packageName === item.packageName) }) From a682998594d739db693a620813b9de2cfa4559b0 Mon Sep 17 00:00:00 2001 From: chilingling Date: Sun, 27 Apr 2025 14:58:11 +0800 Subject: [PATCH 21/27] fix: adjust some code catalog. --- packages/canvas/DesignCanvas/src/importMap.ts | 2 +- packages/common/js/{importMap => }/import-map.json | 0 packages/common/js/{importMap/index.js => importMap.js} | 0 packages/common/vite.config.ts | 2 +- packages/design-core/src/preview/src/preview/importMap.js | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename packages/common/js/{importMap => }/import-map.json (100%) rename packages/common/js/{importMap/index.js => importMap.js} (100%) diff --git a/packages/canvas/DesignCanvas/src/importMap.ts b/packages/canvas/DesignCanvas/src/importMap.ts index 9e02487864..73cc291773 100644 --- a/packages/canvas/DesignCanvas/src/importMap.ts +++ b/packages/canvas/DesignCanvas/src/importMap.ts @@ -1,5 +1,5 @@ import { useEnv, getMergeMeta } from '@opentiny/tiny-engine-meta-register' -import { importMapConfig } from '@opentiny/tiny-engine-common/js/importMap/index' +import { importMapConfig } from '@opentiny/tiny-engine-common/js/importMap' const getImportUrl = (pkgName: string) => { // 自定义的 importMap diff --git a/packages/common/js/importMap/import-map.json b/packages/common/js/import-map.json similarity index 100% rename from packages/common/js/importMap/import-map.json rename to packages/common/js/import-map.json diff --git a/packages/common/js/importMap/index.js b/packages/common/js/importMap.js similarity index 100% rename from packages/common/js/importMap/index.js rename to packages/common/js/importMap.js diff --git a/packages/common/vite.config.ts b/packages/common/vite.config.ts index 3c42c52aba..2f183e5ed8 100644 --- a/packages/common/vite.config.ts +++ b/packages/common/vite.config.ts @@ -33,7 +33,7 @@ export default defineConfig({ viteStaticCopy({ targets: [ { - src: './js/importMap/import-map.json', + src: './js/import-map.json', dest: '.' } ] diff --git a/packages/design-core/src/preview/src/preview/importMap.js b/packages/design-core/src/preview/src/preview/importMap.js index 92d98d9c29..e3119bc7ef 100644 --- a/packages/design-core/src/preview/src/preview/importMap.js +++ b/packages/design-core/src/preview/src/preview/importMap.js @@ -11,7 +11,7 @@ */ import { useEnv } from '@opentiny/tiny-engine-meta-register' -import { importMapConfig as importMapJSON } from '@opentiny/tiny-engine-common/js/importMap/index' +import { importMapConfig as importMapJSON } from '@opentiny/tiny-engine-common/js/importMap' const importMap = {} From 3cf50176239144ae6a6ea9393bbe0df2e79b1f2c Mon Sep 17 00:00:00 2001 From: chilingling Date: Sun, 27 Apr 2025 15:42:42 +0800 Subject: [PATCH 22/27] fix: support importmap styles --- .../src/localCdnFile/importMapLocalPlugin.js | 6 +++++- packages/canvas/DesignCanvas/src/importMap.ts | 16 +++++++++++++++- packages/common/js/import-map.json | 3 +++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/build/vite-config/src/localCdnFile/importMapLocalPlugin.js b/packages/build/vite-config/src/localCdnFile/importMapLocalPlugin.js index 0a2bf2caba..8ad636c55d 100644 --- a/packages/build/vite-config/src/localCdnFile/importMapLocalPlugin.js +++ b/packages/build/vite-config/src/localCdnFile/importMapLocalPlugin.js @@ -189,10 +189,14 @@ export function importMapLocalPlugin({ const parsedImportMapConfig = Object.values(parsedImportMapLocalConfig.importMap.imports) .map((item) => extractInfo(item)) .filter(Boolean) + // 处理内置的物料样式,后续不再内置物料后,需要用户自行引入,相关逻辑也需要同步删除 + const parsedImportMapStylesConfig = Object.values(defaultImportMapConfig.importStyles || {}) + .map((item) => extractInfo(item)) + .filter(Boolean) const overriddenImportMap = parsedDefaultImportMapConfig.filter((item) => { return !parsedImportMapConfig.find((parsedItem) => parsedItem.packageName === item.packageName) }) - const combinedImportMapConfig = [...overriddenImportMap, ...parsedImportMapConfig] + const combinedImportMapConfig = [...overriddenImportMap, ...parsedImportMapConfig, ...parsedImportMapStylesConfig] if (combinedImportMapConfig.length === 0) { logger.warn('[import-map-local-plugin]: No CDN dependencies found or configured') diff --git a/packages/canvas/DesignCanvas/src/importMap.ts b/packages/canvas/DesignCanvas/src/importMap.ts index 73cc291773..4eed246e22 100644 --- a/packages/canvas/DesignCanvas/src/importMap.ts +++ b/packages/canvas/DesignCanvas/src/importMap.ts @@ -23,6 +23,20 @@ const getImportUrl = (pkgName: string) => { } } +// 获取样式文件的URL,后续去除物料内置逻辑之后,需要用户自行引入,相关逻辑也需要同步删除 +const getImportStyleUrl = (pkgName: string) => { + const { VITE_CDN_TYPE, VITE_CDN_DOMAIN, 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' : '' + + if (importMapConfig.importStyles[pkgName]) { + return importMapConfig.importStyles[pkgName] + .replace('${VITE_CDN_DOMAIN}', VITE_LOCAL_CDN_PATH || VITE_CDN_DOMAIN) + .replace('${versionDelimiter}', versionDelimiter) + .replace('${fileDelimiter}', fileDelimiter) + } +} + export function getImportMapData(canvasDeps = { scripts: [], styles: [] }) { // 以下内容由于区块WebComponent加载需要补充 const blockRequire = { @@ -32,7 +46,7 @@ export function getImportMapData(canvasDeps = { scripts: [], styles: [] }) { '@opentiny/vue-icon': getImportUrl('@opentiny/vue-icon'), '@opentiny/tiny-engine-builtin-component': getImportUrl('@opentiny/tiny-engine-builtin-component') }, - importStyles: [] + importStyles: [getImportStyleUrl('@opentiny/vue-theme')] } // 以下内容由于物料协议不支持声明子依赖而@opentiny/vue需要依赖所以需要补充 diff --git a/packages/common/js/import-map.json b/packages/common/js/import-map.json index a006bbbff6..3bbb9959fa 100644 --- a/packages/common/js/import-map.json +++ b/packages/common/js/import-map.json @@ -19,5 +19,8 @@ "@opentiny/vue-locale": "${VITE_CDN_DOMAIN}/@opentiny/vue-runtime${versionDelimiter}~3.20${fileDelimiter}/dist3/tiny-vue-locale.mjs", "@opentiny/vue-renderless/": "${VITE_CDN_DOMAIN}/@opentiny/vue-renderless${versionDelimiter}~3.20${fileDelimiter}/", "echarts": "${VITE_CDN_DOMAIN}/echarts${versionDelimiter}5.4.1${fileDelimiter}/dist/echarts.esm.js" + }, + "importStyles": { + "@opentiny/vue-theme": "${VITE_CDN_DOMAIN}/@opentiny/vue-theme${versionDelimiter}~3.20${fileDelimiter}/index.css" } } From f74a3431bd076da6b0f7d8aaf680167f85bcdf2c Mon Sep 17 00:00:00 2001 From: chilingling Date: Sun, 27 Apr 2025 21:03:26 +0800 Subject: [PATCH 23/27] fix: del duplicated config --- designer-demo/env/.env.localCDN.example | 8 +---- designer-demo/tests/localCdnBasic.test.js | 1 - .../tests/localCdnCustomConfig.test.js | 1 - docs/solutions/import-map-local.md | 8 +---- packages/canvas/DesignCanvas/src/importMap.ts | 34 ++++++++++++++----- .../src/preview/src/preview/http.js | 5 --- .../src/preview/src/preview/importMap.js | 16 ++++++--- .../src/preview/src/preview/srcFiles.js | 22 ++++++++---- .../src/preview/src/preview/usePreviewData.ts | 20 +---------- packages/design-core/vite.config.js | 3 +- 10 files changed, 58 insertions(+), 60 deletions(-) diff --git a/designer-demo/env/.env.localCDN.example b/designer-demo/env/.env.localCDN.example index 2ce03c9556..cc9a557056 100644 --- a/designer-demo/env/.env.localCDN.example +++ b/designer-demo/env/.env.localCDN.example @@ -6,11 +6,5 @@ VITE_LOCAL_IMPORT_MAPS=true # 将画布、页面预览需要的 vue、vue-i18n 等等依赖复制到构建产物中 VITE_LOCAL_BUNDLE_DEPS=true -# 将 VITE_LOCAL_BUNDLE_DEPS 复制到构建产物中的目录名称 +# 将 VITE_LOCAL_BUNDLE_DEPS 复制到构建产物中的目录名称,默认为 local-cdn-static VITE_LOCAL_BUNDLE_PATH=local-cdn-static - -# VITE_LOCAL_BUNDLE_DEPS 的完整访问路径,如果 vite.config.js 中 base 配置为 ./,则为 base + 复制的目录名称 -VITE_LOCAL_CDN_PATH=./local-cdn-static - -# ⚠️注意:如果 vite.config.js 中 base 不为 ./,则需要加上 base的路径,比如 base 为:http://opentiny.design -# VITE_LOCAL_CDN_PATH=http://opentiny.design/local-cdn-static diff --git a/designer-demo/tests/localCdnBasic.test.js b/designer-demo/tests/localCdnBasic.test.js index 3763ced62c..da571504cb 100644 --- a/designer-demo/tests/localCdnBasic.test.js +++ b/designer-demo/tests/localCdnBasic.test.js @@ -27,7 +27,6 @@ describe('localCDN 功能测试', () => { // 确保关键环境变量已启用 envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_IMPORT_MAPS') envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_DEPS') - envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_CDN_PATH', './local-cdn-static') envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_PATH', 'local-cdn-static') // 写回更新后的环境变量 diff --git a/designer-demo/tests/localCdnCustomConfig.test.js b/designer-demo/tests/localCdnCustomConfig.test.js index 448581f6ee..d4243850fa 100644 --- a/designer-demo/tests/localCdnCustomConfig.test.js +++ b/designer-demo/tests/localCdnCustomConfig.test.js @@ -94,7 +94,6 @@ describe('localCDN 自定义配置测试', () => { // 确保关键环境变量已启用 envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_IMPORT_MAPS') envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_DEPS') - envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_CDN_PATH', './local-cdn-static') envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_PATH', 'local-cdn-static') // 写回更新后的环境变量 diff --git a/docs/solutions/import-map-local.md b/docs/solutions/import-map-local.md index 3a758417ef..847bd78609 100644 --- a/docs/solutions/import-map-local.md +++ b/docs/solutions/import-map-local.md @@ -32,14 +32,8 @@ VITE_LOCAL_IMPORT_MAPS=true # 将物料需要的CDN 资源进行本地化。注意⚠️:这里需要您的物料package需要能够通过 npm 的方式进行下载,否则会失效。 VITE_LOCAL_BUNDLE_DEPS=true -# 将 VITE_LOCAL_BUNDLE_DEPS 复制到构建产物中的目录名称 +# 将 VITE_LOCAL_BUNDLE_DEPS 复制到构建产物中的目录名称,默认为 local-cdn-static VITE_LOCAL_BUNDLE_PATH=local-cdn-static - -# VITE_LOCAL_BUNDLE_DEPS 的完整访问路径,如果 vite.config.js 中 base 配置为 ./,则为 base + 复制的目录名称 -VITE_LOCAL_CDN_PATH=./local-cdn-static - -# ⚠️注意:如果 vite.config.js 中 base 不为 ./,则需要加上 base的路径,比如 base 为:http://opentiny.design -# VITE_LOCAL_CDN_PATH=http://opentiny.design/local-cdn-static ``` 2. 【可选】 在 `vite.config.js` 中传入自定义配置 diff --git a/packages/canvas/DesignCanvas/src/importMap.ts b/packages/canvas/DesignCanvas/src/importMap.ts index 4eed246e22..ffff040ea6 100644 --- a/packages/canvas/DesignCanvas/src/importMap.ts +++ b/packages/canvas/DesignCanvas/src/importMap.ts @@ -4,20 +4,28 @@ import { importMapConfig } from '@opentiny/tiny-engine-common/js/importMap' const getImportUrl = (pkgName: string) => { // 自定义的 importMap const customImportMap = getMergeMeta('engine.config')?.importMap - const { VITE_CDN_TYPE, VITE_CDN_DOMAIN, 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' : '' + const { + VITE_CDN_TYPE, + VITE_CDN_DOMAIN, + VITE_LOCAL_BUNDLE_PATH = 'local-cdn-static', + BASE_URL, + VITE_LOCAL_BUNDLE_DEPS + } = useEnv() + const isLocalBundle = VITE_LOCAL_BUNDLE_DEPS === 'true' + const versionDelimiter = VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/' : '@' + const fileDelimiter = VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/files' : '' + const cdnDomain = isLocalBundle ? BASE_URL + VITE_LOCAL_BUNDLE_PATH : VITE_CDN_DOMAIN if (customImportMap?.imports?.[pkgName]) { return customImportMap.imports[pkgName] - .replace('${VITE_CDN_DOMAIN}', VITE_LOCAL_CDN_PATH || VITE_CDN_DOMAIN) + .replace('${VITE_CDN_DOMAIN}', cdnDomain) .replace('${versionDelimiter}', versionDelimiter) .replace('${fileDelimiter}', fileDelimiter) } if (importMapConfig.imports[pkgName]) { return importMapConfig.imports[pkgName] - .replace('${VITE_CDN_DOMAIN}', VITE_LOCAL_CDN_PATH || VITE_CDN_DOMAIN) + .replace('${VITE_CDN_DOMAIN}', cdnDomain) .replace('${versionDelimiter}', versionDelimiter) .replace('${fileDelimiter}', fileDelimiter) } @@ -25,13 +33,21 @@ const getImportUrl = (pkgName: string) => { // 获取样式文件的URL,后续去除物料内置逻辑之后,需要用户自行引入,相关逻辑也需要同步删除 const getImportStyleUrl = (pkgName: string) => { - const { VITE_CDN_TYPE, VITE_CDN_DOMAIN, 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' : '' + const { + VITE_CDN_TYPE, + VITE_CDN_DOMAIN, + VITE_LOCAL_BUNDLE_PATH = 'local-cdn-static', + BASE_URL, + VITE_LOCAL_BUNDLE_DEPS + } = useEnv() + const isLocalBundle = VITE_LOCAL_BUNDLE_DEPS === 'true' + const versionDelimiter = VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/' : '@' + const fileDelimiter = VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/files' : '' + const cdnDomain = isLocalBundle ? BASE_URL + VITE_LOCAL_BUNDLE_PATH : VITE_CDN_DOMAIN if (importMapConfig.importStyles[pkgName]) { return importMapConfig.importStyles[pkgName] - .replace('${VITE_CDN_DOMAIN}', VITE_LOCAL_CDN_PATH || VITE_CDN_DOMAIN) + .replace('${VITE_CDN_DOMAIN}', cdnDomain) .replace('${versionDelimiter}', versionDelimiter) .replace('${fileDelimiter}', fileDelimiter) } diff --git a/packages/design-core/src/preview/src/preview/http.js b/packages/design-core/src/preview/src/preview/http.js index d354fa4aef..79a8792698 100644 --- a/packages/design-core/src/preview/src/preview/http.js +++ b/packages/design-core/src/preview/src/preview/http.js @@ -36,11 +36,6 @@ export const fetchMetaData = async ({ platform, app, type, id, history, tenant } }) : {} -export const fetchImportMap = async () => { - const baseUrl = new URL(import.meta.env.BASE_URL, location.href) - return fetch(new URL('./preview-import-map-static/preview-importmap.json', baseUrl).href).then((res) => res.json()) -} - export const fetchAppSchema = async (id) => getMetaApi(META_SERVICE.Http).get(`/app-center/v1/api/apps/schema/${id}`) export const fetchBlockSchema = async (blockName) => getMetaApi(META_SERVICE.Http).get(`/material-center/api/block?label=${blockName}`) diff --git a/packages/design-core/src/preview/src/preview/importMap.js b/packages/design-core/src/preview/src/preview/importMap.js index e3119bc7ef..8f055678f8 100644 --- a/packages/design-core/src/preview/src/preview/importMap.js +++ b/packages/design-core/src/preview/src/preview/importMap.js @@ -18,12 +18,20 @@ const importMap = {} const opentinyVueVersion = '~3.20' function replacePlaceholder(v) { - const { VITE_CDN_TYPE, VITE_CDN_DOMAIN, 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' : '' + const { + VITE_CDN_TYPE, + VITE_CDN_DOMAIN, + VITE_LOCAL_BUNDLE_PATH = 'local-cdn-static', + BASE_URL, + VITE_LOCAL_BUNDLE_DEPS + } = useEnv() + const isLocalBundle = VITE_LOCAL_BUNDLE_DEPS === 'true' + const versionDelimiter = VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/' : '@' + const fileDelimiter = VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/files' : '' + const cdnDomain = isLocalBundle ? BASE_URL + VITE_LOCAL_BUNDLE_PATH : VITE_CDN_DOMAIN return v - .replace('${VITE_CDN_DOMAIN}', VITE_LOCAL_CDN_PATH || VITE_CDN_DOMAIN) + .replace('${VITE_CDN_DOMAIN}', cdnDomain) .replace('${opentinyVueVersion}', opentinyVueVersion) .replace('${versionDelimiter}', versionDelimiter) .replace('${fileDelimiter}', fileDelimiter) diff --git a/packages/design-core/src/preview/src/preview/srcFiles.js b/packages/design-core/src/preview/src/preview/srcFiles.js index a416ed4c8b..bf35b38940 100644 --- a/packages/design-core/src/preview/src/preview/srcFiles.js +++ b/packages/design-core/src/preview/src/preview/srcFiles.js @@ -26,16 +26,20 @@ import storesJS from './srcFiles/stores.js?raw' import storesHelperJS from './srcFiles/storesHelper.js?raw' const srcFiles = {} -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 isLocalBundle = import.meta.env.VITE_LOCAL_BUNDLE_DEPS === 'true' +const versionDelimiter = import.meta.env.VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/' : '@' +const fileDelimiter = import.meta.env.VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/files' : '' srcFiles['App.vue'] = appVue srcFiles['Main.vue'] = mainVue srcFiles['constant.js'] = constantJS srcFiles['app.js'] = appJS - .replaceAll('${VITE_CDN_DOMAIN}', import.meta.env.VITE_LOCAL_CDN_PATH || import.meta.env.VITE_CDN_DOMAIN) + .replaceAll( + '${VITE_CDN_DOMAIN}', + isLocalBundle + ? import.meta.env.BASE_URL + (import.meta.env.VITE_LOCAL_BUNDLE_PATH || 'local-cdn-static') + : import.meta.env.VITE_CDN_DOMAIN + ) .replaceAll('${versionDelimiter}', versionDelimiter) .replaceAll('${fileDelimiter}', fileDelimiter) @@ -50,6 +54,9 @@ srcFiles['stores.js'] = storesJS srcFiles['storesHelper.js'] = storesHelperJS export const genPreviewTemplate = () => { + const { VITE_CDN_DOMAIN, VITE_LOCAL_BUNDLE_PATH = 'local-cdn-static', BASE_URL, VITE_LOCAL_BUNDLE_DEPS } = useEnv() + const isLocalBundle = VITE_LOCAL_BUNDLE_DEPS === 'true' + return [ { fileName: 'App.vue', @@ -64,7 +71,10 @@ export const genPreviewTemplate = () => { { fileName: 'app.js', path: '', - fileContent: appJS.replace(/VITE_CDN_DOMAIN/g, useEnv().VITE_LOCAL_CDN_PATH || useEnv().VITE_CDN_DOMAIN) + fileContent: appJS.replace( + /VITE_CDN_DOMAIN/g, + isLocalBundle ? BASE_URL + VITE_LOCAL_BUNDLE_PATH : VITE_CDN_DOMAIN + ) }, { fileName: 'injectGlobal.js', diff --git a/packages/design-core/src/preview/src/preview/usePreviewData.ts b/packages/design-core/src/preview/src/preview/usePreviewData.ts index 081920e0a6..632a7e9370 100644 --- a/packages/design-core/src/preview/src/preview/usePreviewData.ts +++ b/packages/design-core/src/preview/src/preview/usePreviewData.ts @@ -4,15 +4,7 @@ import vueJsx from '@vue/babel-plugin-jsx' import { constants } from '@opentiny/tiny-engine-utils' import { getImportMap as getInitImportMap } from './importMap' import { getMetaApi } from '@opentiny/tiny-engine-meta-register' -import { - fetchMetaData, - fetchImportMap, - fetchAppSchema, - fetchBlockSchema, - getPageById, - getBlockById, - fetchPageHistory -} from './http' +import { fetchMetaData, fetchAppSchema, fetchBlockSchema, getPageById, getBlockById, fetchPageHistory } from './http' import { PanelType } from '../constant' import generateMetaFiles, { processAppJsCode } from './generate' import srcFiles from './srcFiles' @@ -154,16 +146,6 @@ const getPageOrBlockByApi = async (): Promise<{ currentPage: IPage | null; ances } } const getImportMap = async (scripts = {}) => { - if (import.meta.env.VITE_LOCAL_BUNDLE_DEPS === 'true') { - const mapJSON = await fetchImportMap() - - return { - imports: { - ...mapJSON.imports, - ...scripts - } - } - } return getInitImportMap(scripts || {}) } diff --git a/packages/design-core/vite.config.js b/packages/design-core/vite.config.js index b0ae2ded57..035c85fe56 100644 --- a/packages/design-core/vite.config.js +++ b/packages/design-core/vite.config.js @@ -67,7 +67,8 @@ export default defineConfig({ 'import.meta.env.VITE_CDN_DOMAIN': 'import.meta.env.VITE_CDN_DOMAIN', 'import.meta.env.VITE_API_MOCK': 'import.meta.env.VITE_API_MOCK', 'import.meta.env.VITE_CDN_TYPE': 'import.meta.env.VITE_CDN_TYPE', - 'import.meta.env.VITE_LOCAL_CDN_PATH': 'import.meta.env.VITE_LOCAL_CDN_PATH' + 'import.meta.env.VITE_LOCAL_BUNDLE_PATH': 'import.meta.env.VITE_LOCAL_BUNDLE_PATH', + 'import.meta.env.VITE_LOCAL_BUNDLE_DEPS': 'import.meta.env.VITE_LOCAL_BUNDLE_DEPS' }, build: { commonjsOptions: { From f61383cef84828bf6f4c95862f03f880423375b0 Mon Sep 17 00:00:00 2001 From: chilingling Date: Mon, 28 Apr 2025 16:34:38 +0800 Subject: [PATCH 24/27] fix: update updateTemplate --- scripts/updateTemplate.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/updateTemplate.mjs b/scripts/updateTemplate.mjs index ec4d9b12ce..0d991384cd 100644 --- a/scripts/updateTemplate.mjs +++ b/scripts/updateTemplate.mjs @@ -12,7 +12,7 @@ const __dirname = path.dirname(__filename) const templateSrcPath = path.resolve(__dirname, '../designer-demo') const templateDistPath = path.resolve(__dirname, '../packages/engine-cli/template/designer') -const ignoreFolder = ['node_modules', 'dist', 'temp', 'tmp'] +const ignoreFolder = ['node_modules', 'dist', 'temp', 'tmp', 'vitest.config.js', 'tests', 'bundle-deps'] const filter = (src, _dest) => { if (ignoreFolder.some((item) => src.includes(item))) { From c157be49c2f1d5f26e2d5f6a204ddb8a29b71cb5 Mon Sep 17 00:00:00 2001 From: chilingling Date: Mon, 28 Apr 2025 16:36:33 +0800 Subject: [PATCH 25/27] fix: update doc file name --- docs/catalog.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/catalog.json b/docs/catalog.json index 35287ed2d7..d68f52ac1a 100644 --- a/docs/catalog.json +++ b/docs/catalog.json @@ -79,7 +79,7 @@ { "title": "区块局域网发布方案(Node.js服务端)", "name": "block-lan-release-solution.md" }, { "title": "设计器中引入第三方组件库", "name": "third-party-library-in-designer.md" }, { "title": "物料同步方案", "name": "material-sync-solution.md" }, - { "title": "本地化CDN方案", "name": "local-cdn.md" } + { "title": "本地化CDN方案", "name": "import-map-local.md" } ] }, { From b15abe418b1a07ea642e9c818688a32cf19b74ea Mon Sep 17 00:00:00 2001 From: chilingling Date: Mon, 28 Apr 2025 20:30:11 +0800 Subject: [PATCH 26/27] docs: optimize local-cdn docs --- docs/solutions/import-map-local.md | 88 ++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/docs/solutions/import-map-local.md b/docs/solutions/import-map-local.md index 847bd78609..11877b67a5 100644 --- a/docs/solutions/import-map-local.md +++ b/docs/solutions/import-map-local.md @@ -1,43 +1,45 @@ -# 本地化 import-map CDN方案 +# CDN 依赖本地化方案 ## 概述 -TinyEngine 在画布和预览都使用了 import-map 的方案来依赖 vue、vue-i18n 以及物料等依赖,这些 import-map 默认会使用 npmmirror 的 CDN。 -但是,在一些企业化的场景中,我们无法依赖这种外部的 CDN。(可用性和稳定性要求。) +TinyEngine 在画布和预览都使用了 import-map 的方式来加载依赖,例如 `vue`、`vue-i18n` 以及物料等,这些 import-map 默认会依赖于 npmmirror 的 CDN 服务。 +然而,在一些企业场景中,由于可用性和稳定性要求,无法依赖外部的 CDN服务。 当前可以采取的方案有: -- 搭建私网的 unpkg -- 使用本文档介绍的 import-map CDN 本地化方案 +- 搭建企业私有网络的 unpkg 服务。 +- 使用本文档介绍的CDN依赖本地化方案 -本地化 import-map CDN是一种在构建时,将 import-map 所需的远程CDN资源替换为构建产物中的文件的解决方案。它具有以下优势: +CDN依赖本地化的核心思想是:在构建过程中,将 TinyEngine 所需的远程CDN资源替换为构建产物中的本地文件。其主要优点包括: 1. 减少对外部CDN的依赖,提高应用的可靠性 -2. 在离线环境或内网环境中访问 import-map 所需的 CDN 资源。 +2. 支持离线环境或内网环境中对 import-map 所需资源的访问。 3. 加快资源加载速度,提升应用性能。 ## 使用方法 -### 修改环境变量使用 import-map CDN 本地化方案 +### 启用 CDN 依赖本地化 -要启用 import-map CDN 本地化功能,请按照以下步骤操作: +请按照以下步骤操作启用 CDN 依赖本地化功能: -1. 修改环境变量 +1. **修改环境变量** -```bash -# .env.alpha、.env.production、.env.development +在 `.env.alpha`、`.env.production` 文件中添加以下配置 -# 将画布、页面预览需要的 CDN 资源进行本地化。 +```bash +# 启用 CDN 依赖本地化功能 VITE_LOCAL_IMPORT_MAPS=true -# 将物料需要的CDN 资源进行本地化。注意⚠️:这里需要您的物料package需要能够通过 npm 的方式进行下载,否则会失效。 +# 启用物料的资源本地化功能。注意⚠️:这里需要您的物料package需要能够通过 npm 的方式进行下载,否则会失效。 VITE_LOCAL_BUNDLE_DEPS=true -# 将 VITE_LOCAL_BUNDLE_DEPS 复制到构建产物中的目录名称,默认为 local-cdn-static +# 定义本地化资源存放目录,默认为 local-cdn-static VITE_LOCAL_BUNDLE_PATH=local-cdn-static ``` 2. 【可选】 在 `vite.config.js` 中传入自定义配置 +可通过 importMapLocalConfig 选项配置导入映射规则和文件复制行为: + ```javascript const baseConfig = useTinyEngineBaseConfig({ importMapLocalConfig: { @@ -58,15 +60,15 @@ CDN 本地化接受以下配置选项: | importMapLocalConfig.importMap | Object | `{ imports: {} }` | 导入映射配置,定义需要本地化的CDN依赖 | | importMapLocalConfig.copy | Object | `{}` | 自定义复制配置,可以覆盖特定包的默认配置 | -#### importMap 详细说明 +#### 导入映射(importMap) 详细说明 `importMapLocalConfig.importMap` 是一个包含 `imports` 属性的对象,它定义了需要本地化的CDN依赖。在插件内部,它会与默认的导入映射配置合并。 -import-map.json 的格式示例: +importMap 的格式示例: ```json { "imports": { - "vue": "${VITE_CDN_DOMAIN}/vue${versionDelimiter}3.2.37${fileDelimiter}/dist/vue.global.prod.js", + "vue": "${VITE_CDN_DOMAIN}/vue${versionDelimiter}3.2.37${fileDelimiter}/dist/vue.runtime.esm-browser.js", "vue-router": "${VITE_CDN_DOMAIN}/vue-router${versionDelimiter}4.1.5${fileDelimiter}/dist/vue-router.global.js", "pinia": "${VITE_CDN_DOMAIN}/pinia${versionDelimiter}2.0.23${fileDelimiter}/dist/pinia.iife.prod.js" } @@ -78,7 +80,36 @@ URL格式说明: - `${versionDelimiter}` - 版本分隔符,用于分隔包名和版本 - `${fileDelimiter}` - 文件路径分隔符,用于分隔版本和文件路径 -插件将解析这些URL,提取包名、版本和文件路径,然后在构建时将它们替换为本地路径。 +例如: +unpkg 的 cdn 链接为:`https://unpkg.com/vue@3.5.13/dist/vue.runtime.esm-browser.js`。 +那么我们根据这个地址,可以拆分成以下几个部分: +- `https://unpkg.com` CDN 服务域名,即 `VITE_CDN_DOMAIN` +- `vue` 依赖的名称(packageName) +- `@` 包名与版本号的分隔符 `versionDelimiter` +- `3.5.13` 版本号 +- `/dist/vue.runtime.global.prod.js` 具体依赖的文件路径 + +如果是 npmmirror CDN 服务,则链接为:`https://registry.npmmirror.com/vue/3.5.13/files/dist/vue.runtime.esm-browser.js`。 +我们可以拆分成以下几个部分: +- `https://registry.npmmirror.com` CDN 服务域名。即 `VITE_CDN_DOMAIN` +- `vue` 依赖的名称 (packageName) +- `/` 包名与版本的分隔符 `versionDelimiter` +- `3.5.13` 版本号 +- `/files` 版本号与具体文件路径的分隔符,即 `${fileDelimiter}` +- `/dist/vue.runtime.global.prod.js` 具体依赖的文件路径 + +假如我们希望依赖 vue 的 3.5+ 版本,那么我们就可以传入约定的 importMap 配置: + +```json +{ + "imports": { + "vue": "${VITE_CDN_DOMAIN}/vue${versionDelimiter}^3.5${fileDelimiter}/dist/vue.runtime.esm-browser.js" + } +} +``` + +传入配置之后,插件将解析这些URL,提取包名、版本和文件路径,然后在构建时将它们替换为本地路径。 +即:`./local-cdn-static/vue@^3.5/dist/vue.runtime.esm-browser.js` **重要说明**:如果您在 Vite 配置中传递了 `importMapLocalConfig.importMap`,还需要在 registry 注册表的 config 中传入同样的配置,以确保应用在运行时能正确读取自定义的 importMap 配置。例如: @@ -95,11 +126,10 @@ URL格式说明: 这是因为画布和页面预览默认会从注册表 `getMergeMeta('engine.config')?.importMap` 中读取自定义的映射配置,如果获取失败,则会读取默认的映射。 -#### copy 详细说明 +#### 文件复制配置 copy 说明【可选】 -`importMapLocalConfig.copy` 是一个可选的配置对象,用于覆盖特定包的默认复制配置。它的结构是一个对象,键是包名,值是该包的复制配置。 +`importMapLocalConfig.copy` 可自定义特定包的文件复制配置。 默认配置如下 -默认配置如下: ```javascript { '@opentiny/vue-theme': { @@ -117,9 +147,9 @@ URL格式说明: } ``` -每个包的配置支持以下选项: -- `filePathInPackage`: 指定要复制的包内路径,如果想要复制整个 package,则配置为 '/' -- `version`: (可选) 覆盖包的版本号(注意:实际请求的 url 路径的版本号不会变,只是实际下载的版本号变了) +配置支持以下选项: +- `filePathInPackage`: 指定要复制的包内路径,如果想要复制整个包,则配置为 `'/'` +- `version`: (可选) 覆盖包的版本号(注意:只影响下载的版本号,不影响实际请求 URL 的版本号) 例如,自定义配置: ```javascript @@ -136,13 +166,13 @@ URL格式说明: ### 处理bundle文件中的CDN链接 -如果需要处理bundle文件中的CDN链接,可以在 env 文件中配置 `VITE_LOCAL_BUNDLE_DEPS=true`: +如果需要处理bundle文件中的CDN链接,可以在 `.env.xxx` 文件中配置 `VITE_LOCAL_BUNDLE_DEPS=true`: 注意⚠️:物料文件 bundle.json 中 packages 数组中,只有前缀与 env 文件配置的 `VITE_CDN_DOMAIN` 一致,才会被复制打包并改写 bundle.json。 比如有如下物料配置和 .env 配置 -`bundle.json`: +1. **`bundle.json`**: ```json { "packages": [ @@ -165,7 +195,7 @@ URL格式说明: } ``` -`.env.alpha`: +2. **`.env.alpha`**: ```bash VITE_CDN_DOMAIN=https://unpkg.com @@ -176,7 +206,7 @@ VITE_CDN_DOMAIN=https://unpkg.com ## 实现原理 -本地化CDN的实现原理可以分为以下几个核心步骤: +本地化CDN依赖可以分为以下几个核心步骤: ### 1. 分析导入映射并收集依赖 From 9a11d7c83e2bfad728b1b9e6eb2113e2e83366f8 Mon Sep 17 00:00:00 2001 From: chilingling Date: Mon, 28 Apr 2025 20:40:20 +0800 Subject: [PATCH 27/27] fix: update bundle variable --- designer-demo/env/.env.development | 2 -- designer-demo/env/.env.localCDN.example | 6 +++--- designer-demo/env/.env.production | 2 -- designer-demo/tests/localCdnBasic.test.js | 2 +- designer-demo/tests/localCdnCustomConfig.test.js | 2 +- docs/solutions/import-map-local.md | 2 +- packages/build/vite-config/src/default-config.js | 4 ++-- packages/canvas/DesignCanvas/src/importMap.ts | 16 ++++++++-------- .../src/preview/src/preview/importMap.js | 8 ++++---- .../src/preview/src/preview/srcFiles.js | 10 +++++----- packages/design-core/vite.config.js | 4 ++-- .../engine-cli/template/designer/env/.env.alpha | 2 -- .../template/designer/env/.env.development | 2 -- .../template/designer/env/.env.production | 2 -- 14 files changed, 27 insertions(+), 37 deletions(-) diff --git a/designer-demo/env/.env.development b/designer-demo/env/.env.development index 67d094d8c6..25a3fd1e9d 100644 --- a/designer-demo/env/.env.development +++ b/designer-demo/env/.env.development @@ -4,7 +4,5 @@ NODE_ENV=development VITE_CDN_DOMAIN=https://registry.npmmirror.com # 使用npmmirror的cdn 时,需要声明 VITE_CDN_TYPE=npmmirror VITE_CDN_TYPE=npmmirror -VITE_LOCAL_IMPORT_MAPS=false -VITE_LOCAL_BUNDLE_DEPS=false # request data via alpha service # VITE_ORIGIN= diff --git a/designer-demo/env/.env.localCDN.example b/designer-demo/env/.env.localCDN.example index cc9a557056..802e49e30c 100644 --- a/designer-demo/env/.env.localCDN.example +++ b/designer-demo/env/.env.localCDN.example @@ -1,10 +1,10 @@ # CDN 本地化配置示例 -# 将本地物料 bundle.json 的 script 和 css 复制到构建产物中 +# 将画布、页面预览需要的 vue、vue-i18n 等等依赖复制到构建产物中 VITE_LOCAL_IMPORT_MAPS=true -# 将画布、页面预览需要的 vue、vue-i18n 等等依赖复制到构建产物中 +# 将本地物料 bundle.json 的 script 和 css 复制到构建产物中 VITE_LOCAL_BUNDLE_DEPS=true # 将 VITE_LOCAL_BUNDLE_DEPS 复制到构建产物中的目录名称,默认为 local-cdn-static -VITE_LOCAL_BUNDLE_PATH=local-cdn-static +VITE_LOCAL_IMPORT_PATH=local-cdn-static diff --git a/designer-demo/env/.env.production b/designer-demo/env/.env.production index 4d9d9f2f36..73e6739cf0 100644 --- a/designer-demo/env/.env.production +++ b/designer-demo/env/.env.production @@ -5,6 +5,4 @@ NODE_ENV=production VITE_CDN_DOMAIN=https://registry.npmmirror.com # 使用npmmirror的cdn 时,需要声明 VITE_CDN_TYPE=npmmirror VITE_CDN_TYPE=npmmirror -VITE_LOCAL_IMPORT_MAPS=false -VITE_LOCAL_BUNDLE_DEPS=false #VITE_ORIGIN= diff --git a/designer-demo/tests/localCdnBasic.test.js b/designer-demo/tests/localCdnBasic.test.js index da571504cb..32e687ddd2 100644 --- a/designer-demo/tests/localCdnBasic.test.js +++ b/designer-demo/tests/localCdnBasic.test.js @@ -27,7 +27,7 @@ describe('localCDN 功能测试', () => { // 确保关键环境变量已启用 envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_IMPORT_MAPS') envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_DEPS') - envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_PATH', 'local-cdn-static') + envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_IMPORT_PATH', 'local-cdn-static') // 写回更新后的环境变量 fs.writeFileSync(envAlphaPath, envContent) diff --git a/designer-demo/tests/localCdnCustomConfig.test.js b/designer-demo/tests/localCdnCustomConfig.test.js index d4243850fa..d5229e053d 100644 --- a/designer-demo/tests/localCdnCustomConfig.test.js +++ b/designer-demo/tests/localCdnCustomConfig.test.js @@ -94,7 +94,7 @@ describe('localCDN 自定义配置测试', () => { // 确保关键环境变量已启用 envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_IMPORT_MAPS') envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_DEPS') - envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_BUNDLE_PATH', 'local-cdn-static') + envContent = ensureEnvVarEnabled(envContent, 'VITE_LOCAL_IMPORT_PATH', 'local-cdn-static') // 写回更新后的环境变量 fs.writeFileSync(envAlphaPath, envContent) diff --git a/docs/solutions/import-map-local.md b/docs/solutions/import-map-local.md index 11877b67a5..bf14ed6388 100644 --- a/docs/solutions/import-map-local.md +++ b/docs/solutions/import-map-local.md @@ -33,7 +33,7 @@ VITE_LOCAL_IMPORT_MAPS=true VITE_LOCAL_BUNDLE_DEPS=true # 定义本地化资源存放目录,默认为 local-cdn-static -VITE_LOCAL_BUNDLE_PATH=local-cdn-static +VITE_LOCAL_IMPORT_PATH=local-cdn-static ``` 2. 【可选】 在 `vite.config.js` 中传入自定义配置 diff --git a/packages/build/vite-config/src/default-config.js b/packages/build/vite-config/src/default-config.js index a21f6bc39a..67a9d3c9ce 100644 --- a/packages/build/vite-config/src/default-config.js +++ b/packages/build/vite-config/src/default-config.js @@ -138,7 +138,7 @@ export function useTinyEngineBaseConfig(engineConfig) { VITE_CDN_DOMAIN = 'https://unpkg.com', VITE_LOCAL_IMPORT_MAPS, VITE_LOCAL_BUNDLE_DEPS, - VITE_LOCAL_BUNDLE_PATH + VITE_LOCAL_IMPORT_PATH } = env const isLocalImportMap = VITE_LOCAL_IMPORT_MAPS === 'true' // true公共依赖库使用本地打包文件,false公共依赖库使用公共CDN const isCopyBundleDeps = VITE_LOCAL_BUNDLE_DEPS === 'true' // true bundle里的cdn依赖处理成本地依赖, false 不处理 @@ -176,7 +176,7 @@ export function useTinyEngineBaseConfig(engineConfig) { const importMapPlugins = importMapLocalPlugin({ importMapLocalConfig: engineConfig.importMapLocalConfig, base: getBaseUrlFromCli(config.base), - cdnDir: VITE_LOCAL_BUNDLE_PATH + cdnDir: VITE_LOCAL_IMPORT_PATH }) if (importMapPlugins && importMapPlugins.length > 0) { diff --git a/packages/canvas/DesignCanvas/src/importMap.ts b/packages/canvas/DesignCanvas/src/importMap.ts index ffff040ea6..499154c47d 100644 --- a/packages/canvas/DesignCanvas/src/importMap.ts +++ b/packages/canvas/DesignCanvas/src/importMap.ts @@ -7,14 +7,14 @@ const getImportUrl = (pkgName: string) => { const { VITE_CDN_TYPE, VITE_CDN_DOMAIN, - VITE_LOCAL_BUNDLE_PATH = 'local-cdn-static', + VITE_LOCAL_IMPORT_PATH = 'local-cdn-static', BASE_URL, - VITE_LOCAL_BUNDLE_DEPS + VITE_LOCAL_IMPORT_MAPS } = useEnv() - const isLocalBundle = VITE_LOCAL_BUNDLE_DEPS === 'true' + const isLocalBundle = VITE_LOCAL_IMPORT_MAPS === 'true' const versionDelimiter = VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/' : '@' const fileDelimiter = VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/files' : '' - const cdnDomain = isLocalBundle ? BASE_URL + VITE_LOCAL_BUNDLE_PATH : VITE_CDN_DOMAIN + const cdnDomain = isLocalBundle ? BASE_URL + VITE_LOCAL_IMPORT_PATH : VITE_CDN_DOMAIN if (customImportMap?.imports?.[pkgName]) { return customImportMap.imports[pkgName] @@ -36,14 +36,14 @@ const getImportStyleUrl = (pkgName: string) => { const { VITE_CDN_TYPE, VITE_CDN_DOMAIN, - VITE_LOCAL_BUNDLE_PATH = 'local-cdn-static', + VITE_LOCAL_IMPORT_PATH = 'local-cdn-static', BASE_URL, - VITE_LOCAL_BUNDLE_DEPS + VITE_LOCAL_IMPORT_MAPS } = useEnv() - const isLocalBundle = VITE_LOCAL_BUNDLE_DEPS === 'true' + const isLocalBundle = VITE_LOCAL_IMPORT_MAPS === 'true' const versionDelimiter = VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/' : '@' const fileDelimiter = VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/files' : '' - const cdnDomain = isLocalBundle ? BASE_URL + VITE_LOCAL_BUNDLE_PATH : VITE_CDN_DOMAIN + const cdnDomain = isLocalBundle ? BASE_URL + VITE_LOCAL_IMPORT_PATH : VITE_CDN_DOMAIN if (importMapConfig.importStyles[pkgName]) { return importMapConfig.importStyles[pkgName] diff --git a/packages/design-core/src/preview/src/preview/importMap.js b/packages/design-core/src/preview/src/preview/importMap.js index 8f055678f8..c99c1c117f 100644 --- a/packages/design-core/src/preview/src/preview/importMap.js +++ b/packages/design-core/src/preview/src/preview/importMap.js @@ -21,14 +21,14 @@ function replacePlaceholder(v) { const { VITE_CDN_TYPE, VITE_CDN_DOMAIN, - VITE_LOCAL_BUNDLE_PATH = 'local-cdn-static', + VITE_LOCAL_IMPORT_PATH = 'local-cdn-static', BASE_URL, - VITE_LOCAL_BUNDLE_DEPS + VITE_LOCAL_IMPORT_MAPS } = useEnv() - const isLocalBundle = VITE_LOCAL_BUNDLE_DEPS === 'true' + const isLocalBundle = VITE_LOCAL_IMPORT_MAPS === 'true' const versionDelimiter = VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/' : '@' const fileDelimiter = VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/files' : '' - const cdnDomain = isLocalBundle ? BASE_URL + VITE_LOCAL_BUNDLE_PATH : VITE_CDN_DOMAIN + const cdnDomain = isLocalBundle ? BASE_URL + VITE_LOCAL_IMPORT_PATH : VITE_CDN_DOMAIN return v .replace('${VITE_CDN_DOMAIN}', cdnDomain) diff --git a/packages/design-core/src/preview/src/preview/srcFiles.js b/packages/design-core/src/preview/src/preview/srcFiles.js index bf35b38940..a517e70a4d 100644 --- a/packages/design-core/src/preview/src/preview/srcFiles.js +++ b/packages/design-core/src/preview/src/preview/srcFiles.js @@ -26,7 +26,7 @@ import storesJS from './srcFiles/stores.js?raw' import storesHelperJS from './srcFiles/storesHelper.js?raw' const srcFiles = {} -const isLocalBundle = import.meta.env.VITE_LOCAL_BUNDLE_DEPS === 'true' +const isLocalBundle = import.meta.env.VITE_LOCAL_IMPORT_MAPS === 'true' const versionDelimiter = import.meta.env.VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/' : '@' const fileDelimiter = import.meta.env.VITE_CDN_TYPE === 'npmmirror' && !isLocalBundle ? '/files' : '' @@ -37,7 +37,7 @@ srcFiles['app.js'] = appJS .replaceAll( '${VITE_CDN_DOMAIN}', isLocalBundle - ? import.meta.env.BASE_URL + (import.meta.env.VITE_LOCAL_BUNDLE_PATH || 'local-cdn-static') + ? import.meta.env.BASE_URL + (import.meta.env.VITE_LOCAL_IMPORT_PATH || 'local-cdn-static') : import.meta.env.VITE_CDN_DOMAIN ) .replaceAll('${versionDelimiter}', versionDelimiter) @@ -54,8 +54,8 @@ srcFiles['stores.js'] = storesJS srcFiles['storesHelper.js'] = storesHelperJS export const genPreviewTemplate = () => { - const { VITE_CDN_DOMAIN, VITE_LOCAL_BUNDLE_PATH = 'local-cdn-static', BASE_URL, VITE_LOCAL_BUNDLE_DEPS } = useEnv() - const isLocalBundle = VITE_LOCAL_BUNDLE_DEPS === 'true' + const { VITE_CDN_DOMAIN, VITE_LOCAL_IMPORT_PATH = 'local-cdn-static', BASE_URL, VITE_LOCAL_IMPORT_MAPS } = useEnv() + const isLocalBundle = VITE_LOCAL_IMPORT_MAPS === 'true' return [ { @@ -73,7 +73,7 @@ export const genPreviewTemplate = () => { path: '', fileContent: appJS.replace( /VITE_CDN_DOMAIN/g, - isLocalBundle ? BASE_URL + VITE_LOCAL_BUNDLE_PATH : VITE_CDN_DOMAIN + isLocalBundle ? BASE_URL + VITE_LOCAL_IMPORT_PATH : VITE_CDN_DOMAIN ) }, { diff --git a/packages/design-core/vite.config.js b/packages/design-core/vite.config.js index 035c85fe56..456a2caf73 100644 --- a/packages/design-core/vite.config.js +++ b/packages/design-core/vite.config.js @@ -67,8 +67,8 @@ export default defineConfig({ 'import.meta.env.VITE_CDN_DOMAIN': 'import.meta.env.VITE_CDN_DOMAIN', 'import.meta.env.VITE_API_MOCK': 'import.meta.env.VITE_API_MOCK', 'import.meta.env.VITE_CDN_TYPE': 'import.meta.env.VITE_CDN_TYPE', - 'import.meta.env.VITE_LOCAL_BUNDLE_PATH': 'import.meta.env.VITE_LOCAL_BUNDLE_PATH', - 'import.meta.env.VITE_LOCAL_BUNDLE_DEPS': 'import.meta.env.VITE_LOCAL_BUNDLE_DEPS' + 'import.meta.env.VITE_LOCAL_IMPORT_PATH': 'import.meta.env.VITE_LOCAL_IMPORT_PATH', + 'import.meta.env.VITE_LOCAL_IMPORT_MAPS': 'import.meta.env.VITE_LOCAL_IMPORT_MAPS' }, build: { commonjsOptions: { diff --git a/packages/engine-cli/template/designer/env/.env.alpha b/packages/engine-cli/template/designer/env/.env.alpha index d1784a37aa..dcb2546a5b 100644 --- a/packages/engine-cli/template/designer/env/.env.alpha +++ b/packages/engine-cli/template/designer/env/.env.alpha @@ -5,8 +5,6 @@ NODE_ENV=production VITE_CDN_DOMAIN=https://registry.npmmirror.com # 使用npmmirror的cdn 时,需要声明 VITE_CDN_TYPE=npmmirror VITE_CDN_TYPE=npmmirror -VITE_LOCAL_IMPORT_MAPS=false -VITE_LOCAL_BUNDLE_DEPS=false # VITE_ORIGIN= # 错误监控上报 url diff --git a/packages/engine-cli/template/designer/env/.env.development b/packages/engine-cli/template/designer/env/.env.development index 2642f040e7..edb637579a 100644 --- a/packages/engine-cli/template/designer/env/.env.development +++ b/packages/engine-cli/template/designer/env/.env.development @@ -5,7 +5,5 @@ NODE_ENV=development VITE_CDN_DOMAIN=https://registry.npmmirror.com # 使用npmmirror的cdn 时,需要声明 VITE_CDN_TYPE=npmmirror VITE_CDN_TYPE=npmmirror -VITE_LOCAL_IMPORT_MAPS=false -VITE_LOCAL_BUNDLE_DEPS=false # request data via alpha service # VITE_ORIGIN= diff --git a/packages/engine-cli/template/designer/env/.env.production b/packages/engine-cli/template/designer/env/.env.production index 4d9d9f2f36..73e6739cf0 100644 --- a/packages/engine-cli/template/designer/env/.env.production +++ b/packages/engine-cli/template/designer/env/.env.production @@ -5,6 +5,4 @@ NODE_ENV=production VITE_CDN_DOMAIN=https://registry.npmmirror.com # 使用npmmirror的cdn 时,需要声明 VITE_CDN_TYPE=npmmirror VITE_CDN_TYPE=npmmirror -VITE_LOCAL_IMPORT_MAPS=false -VITE_LOCAL_BUNDLE_DEPS=false #VITE_ORIGIN=