如何在 Chrome 扩展的内容脚本中导入 ES6 module

IT技术 javascript google-chrome google-chrome-extension ecmascript-6
2021-01-21 23:06:24

Chrome 61 中,添加了对 JavaScript module的支持。现在我正在运行 Chrome 63。

我正在尝试在 Chrome 扩展内容脚本中使用import/export语法来使用module。

manifest.json

"content_scripts": [
    {
        "js": [
            "content.js"
        ],
    }
]

my-script.js(与 相同的目录中content.js):

'use strict';

const injectFunction = () => window.alert('hello world');

export default injectFunction;

content.js

'use strict';

import injectFunction from './my-script.js';
injectFunction();

我收到此错误: Uncaught SyntaxError: Unexpected identifier

如果我将导入语法更改为出现import {injectFunction} from './my-script.js';此错误:Uncaught SyntaxError: Unexpected token {

content.js在 Chrome 扩展程序中使用这种语法有什么问题(因为在 HTML 中你必须使用<script type="module" src="script.js">语法),还是我做错了什么?Google 会忽略对扩展的支持,这似乎很奇怪。

6个回答

正如已经提到的,对于后台脚本,使用background.page和使用<script type="module">来踢你的 JavaScript是个好主意

问题是content script,注入<script>带有type属性的标签可以是一个解决方案。

除了注入脚本标签之外,另一种方法是使用dynamic import函数。通过这种方法,您不需要松散chromemodule的范围,仍然可以使用chrome.runtime或其他module。

content_script.js,它看起来像

(async () => {
  const src = chrome.runtime.getURL("your/content_main.js");
  const contentMain = await import(src);
  contentMain.main();
})();

您还需要在清单的Web Accessible Resources 中声明导入的脚本

{
  "web_accessible_resources": [
    "your/content_main.js"
  ]
}

更多细节:

希望能帮助到你。

这适用于 Chrome 但不适用于 Firefox:bugzilla.mozilla.org/show_bug.cgi?id=1536094
2021-03-13 23:06:24
上面链接的错误已经解决,这个策略应该可以在 Firefox 89 中使用。它已经可以在最新的 Nightly 中使用了!
2021-03-29 23:06:24
有什么快速的方法可以让导入功能同步工作?否则,您不能将其包装在一个函数中并依赖于导入库。
2021-03-29 23:06:24
几周前这对我Request scheme 'chrome-extension' is unsupported at sw.js:42有用,现在投入Chrome >= 71。我通过逐步执行代码看到 sw.js 正在使用 fetch 从扩展沙箱加载我的 js 文件。试图确保我没有更改其他内容,但如果其他人看到相同的行为,请确认。
2021-04-10 23:06:24
不错的解决方案。值得一提的是,使用此技术导入的脚本需要像web_accessible_resources清单一样列入许可名单不将它们列入许可名单将导致GET chrome-extension://invalid/ net::ERR_FAILED错误。这可能并不明显,因为常规内容脚本不需要列入许可名单。
2021-04-11 23:06:24

我设法找到了解决方法


免责声明

首先,重要的是要说明内容脚本自 2018 年 1 月起不支持module。此变通方法通过将modulescript标记嵌入到返回扩展程序的页面中来避开限制


解决方法

这是我的manifest.json

    "content_scripts": [ {
       "js": [
         "content.js"
       ]
    }],
    "web_accessible_resources": [
       "main.js",
       "my-script.js"
    ]

请注意,我在web_accessible_resources.

这是我的content.js

    'use strict';

    const script = document.createElement('script');
    script.setAttribute("type", "module");
    script.setAttribute("src", chrome.extension.getURL('main.js'));
    const head = document.head || document.getElementsByTagName("head")[0] || document.documentElement;
    head.insertBefore(script, head.lastChild);

这将main.js作为module脚本插入到网页中。

我所有的业务逻辑现在都在main.js.

要使此方法起作用,main.js(以及我将使用的所有脚本 import必须web_accessible_resources在清单中。

示例用法: my-script.js

    'use strict';

    const injectFunction = () => window.alert('hello world');

    export {injectFunction};

而在main.js这导入脚本的例子:

    'use strict';

    import {injectFunction} from './my-script.js';
    injectFunction();

这有效!没有抛出任何错误,我很高兴。:)

不错的解决方案。唯一的问题是 chrome.runtime API 变得不可用:(
2021-03-13 23:06:24
↑ 没有人推荐它,这是一种解决方法,直到浏览器实现迎头赶上。@ ragnar:感谢您提供的清晰示例;作为记录,这种技术在 Firefox 56 中对我来说似乎也很有效(使用dom.moduleScripts.enabled
2021-03-22 23:06:24
另一个缺点:“module”是异步的。如果您尝试注入应该在目标页面自己的 js 之前执行的 js,那将不再起作用。当然,这可能并不重要,这取决于个人的需要。
2021-03-27 23:06:24
例如,如果在上面的 main.js 中,您chrome.runtime.onMessage.addListener((request, sender, sendResponse), () => ...)的处理程序将永远不会被调用。没有解决方法,因为这是设计使然。但是我使用 Webpack 将我的扩展编译为一个文件,所以这对我来说不是问题。
2021-04-03 23:06:24
我不推荐这种模式,因为它从您的内容脚本中删除了上下文隔离。除此之外,它还允许网站检测您的扩展名。这是一个安全漏洞。
2021-04-05 23:06:24

imports 在内容脚本中不可用。

这是使用全局范围的解决方法。

由于内容脚本存在于它们自己的“孤立世界”中- 它们共享相同的全局命名空间。它只能被 中声明的内容脚本访问manifest.json

这是实现:

清单文件.json

"content_scripts": [
  {
    "matches": ["<all_urls>"],
    "js": [
      "content-scripts/globals.js",
      "content-scripts/script1.js",
      "content-scripts/script2.js"
    ]
  }
],

globals.js

globalThis.foo = 123;

脚本1.js

some_fn_that_needs_foo(globalThis.foo);

以同样的方式,您可以将可重用的功能和其他演员分解import出内容脚本文件中的其他角色

注意:内容脚本的全局命名空间对除内容脚本外的任何页面都不可用 - 因此几乎没有全局范围污染。

如果您需要导入一些库 - 您将不得不使用一个Parcel打包器,比如将您的内容脚本文件与所需的库huge-content-script.js打包成一个文件,然后在manifest.json.

PS:关于 globalThis 的文档

优秀的。如果您不习惯,要进入这种心态并不容易。
2021-03-16 23:06:24
重要的!manifest.json 中的脚本顺序至关重要。如果您依赖的脚本低于您发出呼叫的脚本,则该脚本将不可用。
2021-03-16 23:06:24
这对我不起作用,我收到一个错误 Uncaught SyntaxError: Unexpected token 'export'
2021-03-20 23:06:24
@fguillen,export从您的文件中删除关键字
2021-04-02 23:06:24

最好的方法是使用像 webpack 或 Rollup 这样的打包器。

我摆脱了基本配置

const path = require('path');

module.exports = {
  entry: {
    background: './background.js',
    content: './content.js',
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, '../build')
  }
};

使用命令运行文件

webpack --config ./ext/webpack-ext.config.js

捆绑器组合相关文件,我们可以在 chrome 扩展中使用module化!:D

您需要在构建文件夹中保留所有其他文件,如清单和静态文件。

玩弄它,你最终会找到让它工作的方法!

你有机会在 Vite 上试过这个吗?它使用 Rollup,但我的捆绑文件中仍然有一个import
2021-03-12 23:06:24
这怎么不被点赞?比动态导入和其他技巧要好得多:)
2021-04-10 23:06:24

我只是在尝试自己解决同样的问题时偶然发现了这个问题。

无论如何,我认为有一个更简单的解决方案可以将您自己的自定义module注入您的内容脚本我正在研究 Jquery 是如何注入的,我发现你可以通过创建一个IIFE(立即调用的函数表达式)并在你的manifest.json 中声明它来做同样的事情

它是这样的:

在您的 manifest.json 中:

"content_scripts": [
{
  "matches": ["https://*"],
  "css": ["css/popup.css"],
  "js": ["helpers/helpers.js"]
}],

然后在 helpers/helpers.js 中创建一个 IIFE:

var Helpers = (function() {
  var getRandomArbitrary = function(min, max) {
    return Math.floor(Math.random() * (max - min)) + min;
  }
  return {
    getRandomArbitrary: getRandomArbitrary
  }
})()

现在,您可以在内容脚本中自由使用辅助函数:

Helpers.getRandomArbitrary(0, 10) // voila!

我认为如果您使用这种方法来重构您的一些通用功能,那就太好了。希望这可以帮助!

好主意!只是说如果我错了,但是每次用户加载页面时都会立即包含这些内容脚本?
2021-04-10 23:06:24