如何在Chrome Extension的内容脚本中导入ES6模块

时间:2018-01-04 22:22:00

标签: javascript google-chrome google-chrome-extension ecmascript-6

Chrome 61 中,增加了对JavaScript模块的支持。现在我正在运行Chrome 63。

我正在尝试弄清楚如何在Chrome扩展内容脚本中使用导入/导出语法来使用模块。

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();

我收到此错误: 未捕获的SyntaxError:意外的标识符

如果我将导入语法更改为import {injectFunction} from './my-script.js';,我会收到此错误:未捕获的SyntaxError:意外的令牌{

在Chrome扩展中的content.js中使用此语法是否存在问题,因为在HTML中您必须使用<script type="module" src="script.js">语法,或者我做错了什么?谷歌会忽略对扩展的支持,这似乎很奇怪。

7 个答案:

答案 0 :(得分:15)

我设法找到解决方法

首先,重要的是说内容脚本自2018年1月起不支持模块。但是您可以通过将模块脚本标记嵌入到返回扩展的页面中来使其工作。

这是我的清单

TypeError                                 Traceback (most recent call last)
<ipython-input-25-6f5cf163fcaf> in <module>()
     23 # tfidf = vectorizer.fit_transform(line)
     24 # print(tfidf)
---> 25 lda = LatentDirichletAllocation(n_components = 100)
     26 lda.fit(bag_of_words)
     27 tf_feature_names = vector.get_feature_names()

TypeError: __init__() got an unexpected keyword argument 'n_components'

请注意,我在Web可访问资源中有两个脚本。

我的 content.js 仅包含以下逻辑:

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

这会将main.js作为模块脚本插入到网页中。我的所有业务逻辑现在都在main.js和main中,而且我要导入的所有脚本都必须位于清单中的web_accessible_resources中。

这是 my-script.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 中,这是导入脚本的示例:

'use strict';

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

export {injectFunction};

这样可行,不会抛出任何错误,我很高兴:)

答案 1 :(得分:7)

如前所述,对于后台脚本,最好使用background.page并使用<script type="module">来踢您的JavaScript。

问题出在content script上,并且可以注入具有<script>属性的type标签。

除注入脚本标记外的另一种方法是使用dynamic import函数。通过这种方法,您无需放宽chrome模块的范围,仍然可以使用chrome.runtime或其他模块。

content_script.js中,看起来像

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

有关更多详细信息:

希望有帮助。

答案 2 :(得分:2)

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

无论如何,我认为将自己的自定义模块注入内容脚本是一个更简单的解决方案。我正在研究如何注入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!

我认为如果你使用这种方法来重构一些泛型函数,那就太好了。希望这有帮助!

答案 3 :(得分:2)

import在内容脚本中不可用。

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

由于内容脚本位于自己的'isolated world'中-它们共享相同的全局名称空间。 manifest.json中声明的内容脚本只能访问它。

这是实现:

manifest.json

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

globals.js

globalThis.foo = 123;

script1.js

some_fn_that_needs_foo(globalThis.foo);

以同样的方式可以排除可重复使用的功能和其他参与者,否则,它们会import出现在内容脚本文件中。

N.B .:内容脚本的全局名称空间除了内容脚本之外对任何页面均不可用-因此几乎没有污染全局范围。

如果您需要导入一些库,则必须使用Parcel之类的捆绑程序将内容脚本文件和所需的库打包到一个huge-content-script.js中,然后将其提供给{ {1}}。

P.S .: docs on globalThis

答案 4 :(得分:0)

最好的方法是使用捆绑包,例如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扩展程序中使用模块化! :D

您需要将所有其他文件(如清单文件和静态文件)保留在build文件夹中。

试用它,您最终会找到一种使之起作用的方法!

答案 5 :(得分:-1)

简答:

您可以通过创建以下文件并在import的早期列出它来模仿某些功能,并在浏览器扩展程序中获得export / manifest.json的一些好处:

let exportVars, importVarsFrom;
{
  const modules = {};
  exportVars = varsObj => ({
    from(nameSpace) {
      modules[nameSpace] || (modules[nameSpace] = {});
      for (let [k,v] of Object.entries(varsObj)) {
        modules[nameSpace][k] = v;
      }
    }
  });
  importVarsFrom = nameSpace => modules[nameSpace];
}

然后,从这样的文件/模块导出:

exportVars({ var1, var2, var3 }).from('my-utility');

导入到另一个文件/模块中:

const { var1, var3: newNameForVar3 } = importVarsFrom('my-utility');

<强>讨论:

这个策略:

  • 允许在浏览器扩展程序中使用模块化代码,这样您就可以将代码拆分为多个文件,但由于不同文件之间的共享全局范围,因此不会发生变量冲突,
  • 仍然允许您导出和导入变量来自不同的JavaScript文件/模块,
  • 仅引入两个全局变量,即导出功能和导入功能
  • 在每个文件中保留完整的浏览器扩展功能(例如chrome.runtime等),例如,使用模块脚本通过另一个答案中的方法(当前接受的答案)消除标签嵌入,
  • 使用与JavaScript中真正的importexport 函数类似的简洁语法,
  • 允许名称间距,它可以是导出模块的文件名,其方式类似于真正的importexport命令在JavaScript中的工作方式,但不是& #39;必须是(即名称 - 空间名称可以是你想要的任何东西)和
  • 允许在导入时使用变量重命名,类似于import { fn as myFn }...的工作方式。

要执行此操作,您的manifest.json需要加载JavaScript,如下所示:

  • 首先建立导出/导入功能的文件(在下面的示例中名为modules-start.js),
  • 下一步导出文件,
  • 最后导入文件。

当然,您可能有一个同时导入和导出的文件。在这种情况下,只需确保它在导入的文件之后但在导出的文件之前列出。

工作示例

以下代码演示了此策略。

请务必注意,每个模块/文件中的所有代码都包含在花括号中。唯一的例外是modules-start.js中的第一行,它将导出和导入函数建立为全局变量。

以下代码段中的代码必须包含在单个&#34;地点&#34;中。但是,在实际项目中,代码可以拆分为单独的文件。但请注意,即使在此处的人工上下文中(即在下面的单个代码片段中),此策略也允许其包含的代码的不同部分是模块化的,但仍然是互连的。

&#13;
&#13;
// modules-start.js:
let exportVars, importVarsFrom; // the only line NOT within curly braces
{
  const modules = {};
  exportVars = varsObj => ({
    from(nameSpace) {
      modules[nameSpace] || (modules[nameSpace] = {});
      for (let [k,v] of Object.entries(varsObj)) {
        modules[nameSpace][k] = v;
      }
    }
  });
  importVarsFrom = nameSpace => modules[nameSpace];
}


// *** All of the following is just demo code
// *** showing how to use this export/import functionality:

// my-general-utilities.js (an example file that exports):
{
  const wontPolluteTheGlobalScope = 'f';
  const myString = wontPolluteTheGlobalScope + 'oo';
  const myFunction = (a, b) => a + b;
  
  // the export statement:
  exportVars({ myString, myFunction }).from('my-general-utilities');
}

// content.js (an example file that imports):
{
  // the import statement:
  const { myString, myFunction: sum } = importVarsFrom('my-general-utilities');

  console.log(`The imported string is "${myString}".`);
  console.log(`The renamed imported function shows that 2 + 3 = ${sum(2,3)}.`);
}
&#13;
&#13;
&#13;

通过此示例,您的manifest.json应按以下顺序列出文件:

{ ...
  "content_scripts": [
    {
      "js": [
        "modules-start.js",
        "my-general-utilities.js",
        "content.js"
      ]
    }
  ], ...
}

答案 6 :(得分:-4)

将模块导出为对象:

'use strict';

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

export {injectFunction};

然后你可以导入它的属性:

'use strict';
import {injectFunction} from './my-script.js';