
本文详解如何绕过 esm 导入限制,在 chrome 扩展 content script 中成功集成 floating ui,实现动态 tooltip 定位,并提供可直接运行的模块化配置与调用方案。
在 Chrome 扩展中使用 Floating UI 构建响应式 tooltip 时,直接引入其 ESM 版本(如 @floating-ui/dom/+esm)会触发 Uncaught SyntaxError: Cannot use import statement outside a module 错误——这是因为 Chrome 内容脚本默认不支持顶层 import 语句,即使 manifest 中声明 “type”: “module”,若依赖的第三方库自身以裸 import 形式发布(如 Floating UI 的 ESM cdn 链接),仍无法被直接 import 加载。
✅ 正确解法是:改用 UMD(Universal Module Definition)构建版本,通过
✅ 推荐配置步骤
- 在 content_scripts 中按顺序加载 UMD 脚本(非 ESM)
修改 manifest.json,移除 “type”: “module”(该字段对 js 数组中的多个脚本不生效),改用显式
"content_scripts": [ { "matches": ["*://*/*"], "js": ["content.js"], "css": ["tooltips.css"] } ]
- 在 content.js 开头动态加载 UMD 版本(CDN 推荐)
使用 Floating UI 官方提供的 UMD 构建(v1.x 支持 UMD):
// content.js —— 首先确保 Floating UI 全局可用 if (!window.FloatingUICore || !window.FloatingUIDOM) { const scriptCore = document.createElement('script'); scriptCore.src = 'https://unpkg.com/@floating-ui/core@1.6.5/dist/floating-ui.core.umd.js'; scriptCore.async = false; const scriptDOM = document.createElement('script'); scriptDOM.src = 'https://unpkg.com/@floating-ui/dom@1.6.11/dist/floating-ui.dom.umd.js'; scriptDOM.async = false; document.head.append(scriptCore, scriptDOM); } // 等待脚本加载完成后再执行 tooltip 逻辑(推荐 Promise 化或使用 load 事件) Promise.all([ new Promise(r => scriptCore.onload = r), new Promise(r => scriptDOM.onload = r) ]).then(() => { // ✅ 此时 window.FloatingUICore / window.FloatingUIDOM 已就绪 initTooltips(); });
- 创建 tooltip 并调用 computePosition
示例:为页面中所有 .tooltip-trigger 元素添加顶部 tooltip:
function initTooltips() { const triggers = document.querySelectorAll('.tooltip-trigger'); triggers.forEach(trigger => { const tooltip = document.createElement('div'); tooltip.className = 'floating-tooltip'; tooltip.textContent = 'This is a tooltip!'; const arrow = document.createElement('div'); arrow.className = 'tooltip-arrow'; tooltip.appendChild(arrow); document.body.appendChild(tooltip); // 关键:使用挂载到 window 的 UMD 实例 function updatePosition() { window.FloatingUIDOM.computePosition(trigger, tooltip, { placement: 'top', middleware: [ window.FloatingUICore.offset(6), window.FloatingUIDOM.flip(), window.FloatingUIDOM.shift({ padding: 5 }), window.FloatingUIDOM.arrow({ element: arrow }), ], }).then(({ x, y, middlewareData, placement }) => { Object.assign(tooltip.style, { left: `${x}px`, top: `${y}px`, }); // 处理箭头定位(需配合 CSS transform) const { x: arrowX, y: arrowY } = middlewaredata.arrow || {}; if (arrowX != null && arrowY != null) { Object.assign(arrow.style, { left: `${arrowX}px`, top: `${arrowY}px`, }); } }); } // 初始定位 + 监听滚动/resize updatePosition(); window.addEventListener('scroll', updatePosition, { passive: true }); window.addEventListener('resize', updatePosition); }); }
⚠️ 注意事项
- ❌ 不要将 floating-esm.js 直接写入 manifest.json 的 js 数组并设 “type”: “module” —— Chrome 不允许 content script 同时加载多个模块脚本且跨脚本共享 import 作用域。
- ✅ UMD 版本(.umd.js)是专为浏览器全局环境设计的,无 import/export,天然兼容 content script。
- ? 建议锁定 CDN 版本号(如 @1.6.11),避免因 Floating UI 主版本升级(如 v2+ 移除 UMD)导致扩展失效。
- ? 若需更小体积或定制构建,可自行用 vite/webpack 打包 Floating UI 为 IIFE 格式并内联到扩展中。
通过以上方式,你即可在 Chrome 扩展中稳定、高效地利用 Floating UI 实现像素级精准的 tooltip 定位,兼顾性能、兼容性与可维护性。