diff --git a/packages/taro-components-react/src/components/image/index.tsx b/packages/taro-components-react/src/components/image/index.tsx index 88b1e7ca83f..81f34e3a90c 100644 --- a/packages/taro-components-react/src/components/image/index.tsx +++ b/packages/taro-components-react/src/components/image/index.tsx @@ -18,31 +18,59 @@ interface IProps extends React.HTMLAttributes { lang?: string } -// CDN脚本URL -const LEGO_CDN_URL = 'http://ossin.jd.com/swm-plus/h5Tag/tag.js' +// CDN脚本URL(按环境与可覆盖配置) +const LEGO_CDN_URL_DEV = 'http://ossin.jd.com/swm-plus/h5Tag/tag.js' +const LEGO_CDN_URL_PROD = 'https://storage.jd.com/static-frontend/h5-tag/1.0.0/tag.min.js' + +const getLegoCdnUrl = (): string => { + // 允许通过 Taro 全局对象覆盖(最高优先级) + const taroOverride = (typeof window !== 'undefined' && (window as any).Taro && (window as any).Taro.__TARO_IMAGE_LEGO_CDN_URL__) + if (taroOverride && typeof taroOverride === 'string') return taroOverride + + // 允许通过 window 全局变量覆盖 + const windowOverride = (typeof window !== 'undefined' && (window as any).__TARO_IMAGE_LEGO_CDN_URL__) + if (windowOverride && typeof windowOverride === 'string') return windowOverride + + // 基于环境选择 + const isProd = (typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'production') + return isProd ? LEGO_CDN_URL_PROD : LEGO_CDN_URL_DEV +} // 检查CDN脚本是否已加载 const isLegoScriptLoaded = (): boolean => { - return document.querySelector(`script[src="${LEGO_CDN_URL}"]`) !== null + const url = getLegoCdnUrl() + return document.querySelector(`script[src="${url}"]`) !== null } // 插入CDN脚本 const insertLegoScript = (): void => { + if (typeof document === 'undefined') return if (isLegoScriptLoaded()) return + // 警告:组件内部加载资源是不推荐的行为 + console.error( + '[Taro Image] 警告:组件正在动态加载 LEGO 脚本资源,这是不推荐的行为。\n' + + '推荐在项目入口文件(如 app.js/app.tsx)中预先引入相关脚本,或通过 CDN 预加载。\n' + + '其次可通过全局变量配置:\n' + + ' - Taro.__TARO_IMAGE_LEGO_CDN_URL__(推荐)\n' + + ' - window.__TARO_IMAGE_LEGO_CDN_URL__\n' + + '这样可以避免重复加载,提升性能并减少网络请求。' + ) + const script = document.createElement('script') script.type = 'module' - script.src = LEGO_CDN_URL + script.src = getLegoCdnUrl() document.head.appendChild(script) } // 解析lego协议URL const parseLegoUrl = (src: string): { tagId: string, text: string } | null => { - if (!src.startsWith('lego://')) return null + const LEGO_PROTOCOL = 'lego://' + if (!src.startsWith(LEGO_PROTOCOL)) return null try { // 移除 'lego://' 前缀 - const urlWithoutProtocol = src.substring(7) + const urlWithoutProtocol = src.slice(LEGO_PROTOCOL.length) // 分割tagId和参数 const [tagId, params] = urlWithoutProtocol.split('?') diff --git a/packages/taro-components/src/components/image/image.tsx b/packages/taro-components/src/components/image/image.tsx index 6cc2e84859d..fd61d312bf2 100644 --- a/packages/taro-components/src/components/image/image.tsx +++ b/packages/taro-components/src/components/image/image.tsx @@ -17,38 +17,66 @@ export type Mode = | 'bottom left' | 'bottom right' -// CDN脚本URL -const LEGO_CDN_URL = 'http://ossin.jd.com/swm-plus/h5Tag/tag.js' +// CDN脚本URL(按环境与可覆盖配置) +const LEGO_CDN_URL_DEV = 'http://ossin.jd.com/swm-plus/h5Tag/tag.js' +const LEGO_CDN_URL_PROD = 'https://storage.jd.com/static-frontend/h5-tag/1.0.0/tag.min.js' + +const getLegoCdnUrl = (): string => { + // 允许通过 Taro 全局对象覆盖(最高优先级) + const taroOverride = (typeof window !== 'undefined' && (window as any).Taro && (window as any).Taro.__TARO_IMAGE_LEGO_CDN_URL__) + if (taroOverride && typeof taroOverride === 'string') return taroOverride + + // 允许通过 window 全局变量覆盖 + const windowOverride = (typeof window !== 'undefined' && (window as any).__TARO_IMAGE_LEGO_CDN_URL__) + if (windowOverride && typeof windowOverride === 'string') return windowOverride + + // 基于环境选择 + const isProd = (typeof process !== 'undefined' && (process as any).env && (process as any).env.NODE_ENV === 'production') + return isProd ? LEGO_CDN_URL_PROD : LEGO_CDN_URL_DEV +} // 检查CDN脚本是否已加载 const isLegoScriptLoaded = (): boolean => { - return document.querySelector(`script[src="${LEGO_CDN_URL}"]`) !== null + const url = getLegoCdnUrl() + return document.querySelector(`script[src="${url}"]`) !== null } // 插入CDN脚本 const insertLegoScript = (): void => { + if (typeof document === 'undefined') return if (isLegoScriptLoaded()) return + // 警告:组件内部加载资源是不推荐的行为 + console.error( + '[Taro Image] 警告:组件正在动态加载 LEGO 脚本资源,这是不推荐的行为。\n' + + '推荐在项目入口文件(如 app.js/app.tsx)中预先引入相关脚本,或通过 CDN 预加载。\n' + + '其次可通过全局变量配置:\n' + + ' - Taro.__TARO_IMAGE_LEGO_CDN_URL__(推荐)\n' + + ' - window.__TARO_IMAGE_LEGO_CDN_URL__\n' + + '这样可以避免重复加载,提升性能并减少网络请求。' + ) + const script = document.createElement('script') script.type = 'module' - script.src = LEGO_CDN_URL + script.src = getLegoCdnUrl() document.head.appendChild(script) } // 解析lego协议URL const parseLegoUrl = (src: string): { tagId: string; text: string } | null => { - if (!src.startsWith('lego://')) return null + const LEGO_PROTOCOL = 'lego://' + if (!src.startsWith(LEGO_PROTOCOL)) return null try { // 移除 'lego://' 前缀 - const urlWithoutProtocol = src.substring(7) - + const urlWithoutProtocol = src.slice(LEGO_PROTOCOL.length) + // 分割tagId和参数 const [tagId, params] = urlWithoutProtocol.split('?') - + // 解析参数 const text = params ? new URLSearchParams(params).get('text') || '' : '' - + return { tagId, text } } catch (error) { console.warn('Failed to parse lego URL:', src, error) @@ -154,8 +182,8 @@ export class Image implements ComponentInterface { if (isLegoMode && legoData) { return ( - `,而是渲染 ``: + +- 解析规则:`lego://{tagId}?text=...` → 传递 `tagId`、`text`、`lang` 到 ``。 +- 事件差异:不触发 `` 的 `load/error`,请使用 `` 自身事件(若有)。 + +脚本注入:组件在 LEGO 模式下会按需为 H5 端注入 `tag.js`(type="module"),默认 CDN: + +- 开发:`http://ossin.jd.com/swm-plus/h5Tag/tag.js` +- 生产:`https://storage.jd.com/static-frontend/h5-tag/1.0.0/tag.min.js` + +可通过全局变量覆盖 CDN 地址(优先级从上到下): + +1. `window.__TARO_IMAGE_LEGO_CDN_URL__` +2. `Taro.__TARO_IMAGE_LEGO_CDN_URL__` + +示例: + +```js +// 在应用启动时设置(H5) +window.__TARO_IMAGE_LEGO_CDN_URL__ = 'https://your-cdn.com/tag.min.js' +// 或 +Taro.__TARO_IMAGE_LEGO_CDN_URL__ = 'https://your-cdn.com/tag.min.js' +``` + +说明: + +- 若未设置覆盖变量,则根据 `process.env.NODE_ENV` 在内置 dev/prod CDN 之间选择。 +- 组件已做脚本重复注入检查;在 SSR 场景访问 `window/document` 前有守卫。