繁体   English   中英

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

[英]How to import ES6 modules in content script for Chrome Extension

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

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

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 {

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

正如已经提到的,对于后台脚本,最好使用background.page并使用<script type="module">来启动您的 JavaScript。

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

注入脚本标签的另一种方法是使用dynamic import功能。 通过这种方法,您不需要放松chrome模块的范围,仍然可以使用chrome.runtime或其他模块。

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"
  ]
}

更多细节:

希望能帮助到你。

我设法找到了解决方法


免责声明

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


解决方法

这是我的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作为模块脚本插入到网页中。

我所有的业务逻辑现在都在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();

这行得通! 没有错误被抛出,我很高兴。 :)

import在内容脚本中不可用。

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

由于内容脚本存在于它们自己的“孤立世界”中——它们共享相同的全局命名空间。 它只能被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;

script1.js

some_fn_that_needs_foo(globalThis.foo);

同样的方式,您可以分解出可重用的函数和其他您将在内容脚本文件中import的演员。

注意:除了内容脚本之外,任何页面都无法使用内容脚本的全局命名空间——因此几乎没有全局范围污染。

如果您需要导入一些库 - 您将不得不使用像Parcel这样的捆绑器将您的内容脚本文件与所需的库一起打包到一个huge-content-script.js中,然后在manifest.json中引用它。

PS: 关于 globalThis 的文档

最好的方法是使用 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

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

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

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

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

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

简短的回答:

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

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 中的工作方式,但不是必须的(即命名空间名称可以是任何你想要的) , 和
  • 允许在导入时重命名变量,类似于import { fn as myFn }...工作方式。

为此,您的manifest.json需要按如下方式加载您的 JavaScript:

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

当然,您可能有一个既可以导入又可以导出的文件。 在这种情况下,只需确保它列在它从中导入的文件之后但在它导出到的文件之前。

工作示例

下面的代码演示了这种策略。

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

下面片段中的代码必须包含在一个“位置”中。 但是,在实际项目中,代码可以拆分为单独的文件。 但是请注意,即使在此处的人工上下文中(即在下面的单个代码片段中),此策略也允许它包含的不同代码部分是模块化的,但仍然相互关联。

 // 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)}.`); }

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

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

使用汇总捆绑器

完整教程: https://www.extend-chrome.dev/rollup-plugin#usage


TL;博士

npm i -D rollup\
   rollup-plugin-chrome-extension@latest\
   @rollup/plugin-node-resolve\
   @rollup/plugin-commonjs

rollup.config.js

import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'

import { chromeExtension, simpleReloader } from 'rollup-plugin-chrome-extension'

export default {
  input: 'src/manifest.json',
  output: {
    dir: 'dist',
    format: 'esm',
  },
  plugins: [
    // always put chromeExtension() before other plugins
    chromeExtension(),
    simpleReloader(),
    // the plugins below are optional
    resolve(),
    commonjs(),
  ],
}

package.json


{
  "scripts": {
    "build": "rollup -c",
    "start": "rollup -c -w"
  }
}

只需在 V2 中的 manifest.json 中添加

笔记。 在 manifest,json 中更改后,确保重新加载扩展和浏览器选项卡

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

对于 Vite 用户

有一个很棒的插件叫做crxjs ,你只需要在vite.config.ts中更新它并给你的 manifest.json 提供路径(它只适用于 mv3)

请按照以下步骤运行您的脚本

1.将crxjs添加到你的项目中

npm install @crxjs/vite-plugin -D

2.创建或更新manifest.json

{
  "manifest_version": 3,
  "name": "CRXJS React Vite Example",
  "version": "1.0.0",
  "action": { "default_popup": "index.html" }
}

3.使用清单路径更新您的 vite.config.ts 文件

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { crx } from '@crxjs/vite-plugin'
import manifest from './manifest.json'

export default defineConfig({
  plugins: [
    react(),
    crx({ manifest }),
  ],
})

运行你的项目之后,现在 config.js 将被捆绑,你可以在其中导入包

使用 esbuild

Dhruvil 的回答之后,这里有一个GitHub 存储库,展示了如何使用 esbuild 来捆绑用 TypeScript 和 React 编写的内容脚本——从而使您能够导入 es6 模块。

它还包括捆绑后台服务工作者和弹出窗口,以及在本地运行弹出窗口时启用热模块重新加载的脚本。

将模块导出为对象:

'use strict';

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

export {injectFunction};

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM