You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Chrome扩展Manifest V3+Vite+TS环境下,内容脚本与后台脚本共享代码的实现方案(避免ESM导入错误)

Chrome扩展Manifest V3+Vite+TS环境下,内容脚本与后台脚本共享代码的实现方案(避免ESM导入错误)

兄弟我太懂你这个坑了!当初在Manifest V3+Vite+TS的环境里折腾共享代码的时候,也踩过一模一样的ESM导入错误——内容脚本不认import语句,Vite自动抽的共享chunk直接把整个扩展干崩了。下面给你几个亲测有效的解决方案,按需选就行:

方案一:手动控制Rollup代码分割,强制内联共享代码

这个方法最直接,就是告诉Vite:别给内容脚本搞什么共享chunk,把所有依赖都直接塞到内容脚本的打包文件里,后台脚本该怎么分割怎么分割。

你只需要在vite.config.ts里配置Rollup的代码分割规则,把共享的config或者工具代码分别绑定到后台和内容脚本的chunk里:

import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        background: './src/background.ts',
        content: './src/content.ts',
      },
      output: {
        manualChunks: (id) => {
          // 把config和内容脚本的依赖都打包到content chunk里,不抽共享
          if (id.includes('src/config.ts') || id.includes('src/content.ts')) {
            return 'content';
          }
          // 后台脚本的依赖单独打包
          if (id.includes('src/background.ts')) {
            return 'background';
          }
        },
      },
    },
  },
});

配置完之后,Vite会把config.ts的代码分别内联到background和content的打包文件里,不会生成共享chunk,内容脚本里自然就不会出现讨厌的import语句了。

方案二:用Vite的define全局注入纯常量配置

如果你共享的只是纯常量配置(比如api地址、版本号这种),那用Vite的define选项更简单——直接把配置注入成全局变量,两个脚本不用导入就能用。

第一步在vite.config.ts里定义全局变量:

import { defineConfig } from 'vite';

// 这里写你的共享配置
const APP_CONFIG = {
  apiUrl: "https://api.example.com",
  featureFlag: true,
  version: "1.0.0",
};

export default defineConfig({
  define: {
    // 转成JSON字符串,Vite会自动替换成常量
    __APP_CONFIG__: JSON.stringify(APP_CONFIG),
  },
});

第二步在tsconfig.json里声明全局变量类型,避免TS报错:

{
  "compilerOptions": {
    "types": ["vite/client"],
    "lib": ["ESNext", "DOM"]
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

然后在src下新建global.d.ts文件,补充类型声明:

declare const __APP_CONFIG__: {
  apiUrl: string;
  featureFlag: boolean;
  version: string;
};

现在不管是background.ts还是content.ts,直接写__APP_CONFIG__.apiUrl就能用了,打包后Vite会把这个全局变量直接替换成具体的常量值,完全没有导入语句。

方案三:把共享代码打包成IIFE格式(适合复杂函数/工具类)

如果共享的是函数、工具类这种有逻辑的代码,那可以把共享代码单独打包成IIFE(立即执行函数表达式),挂载到全局变量上,内容脚本直接通过window访问,后台脚本正常用ESM导入。

先改vite.config.ts,给共享代码单独配置打包格式:

import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        background: './src/background.ts',
        content: './src/content.ts',
        sharedUtils: './src/shared/utils.ts',
      },
      output: {
        entryFileNames: '[name].js',
        // 给不同入口指定打包格式
        format: (chunkInfo) => {
          // 后台脚本用ESM格式
          if (chunkInfo.name === 'background') {
            return 'es';
          }
          // 内容脚本和共享代码用IIFE格式
          return 'iife';
        },
        // 共享代码打包成IIFE后,挂载到window的SharedUtils变量上
        name: (chunkInfo) => {
          if (chunkInfo.name === 'sharedUtils') {
            return 'SharedUtils';
          }
          return chunkInfo.name;
        },
      },
    },
  },
});

然后在manifest.json里的内容脚本配置里,要先加载共享代码的脚本,再加载内容脚本:

"content_scripts": [
  {
    "matches": ["<all_urls>"],
    "js": ["dist/sharedUtils.js", "dist/content.js"]
  }
]

使用的时候,content.ts里直接用window.SharedUtils.myToolFunction(),background.ts里正常import { myToolFunction } from './shared/utils.ts'就行。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 08:58:01