javascript - Hur man importerar ES6-moduler i innehållsskript för Chrome-tillägg


I Chrome 61 lades stöd för moduler i JavaScript till. Just nu kör jag Chrome 63.

Jag försöker använda import/export-syntax i Chrome-tilläggs innehållsskript för att använda moduler.

I manifest.json:

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

I my-script.js (samma katalog som content.js):

'use strict';

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

export default injectFunction;

I content.js:

'use strict';

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

Jag får detta fel: Uncaught SyntaxError: Unexpected identifier

Om jag ändrar importsatsen till import {injectFunction} from './my-script.js'; får jag detta fel: Uncaught SyntaxError: Unexpected token {

Finns det något problem med att använda denna syntax i content.js i ett Chrome-tillägg (eftersom man i HTML måste använda <script type="module" src="script.js">-syntaxen), eller gör jag något fel? Det verkar konstigt att Google skulle strunta i stöd för tillägg.

Svar #1

Asynkron dynamisk import()-funktion för ES-moduler

Till skillnad från den osäkra lösningen som använder ett <script>-element körs denna i samma säkra JS-miljö (förutsatt att du använder standardvärlden, dvs. inte MAIN), där din importerade modul fortfarande kan komma åt de globala variablerna och funktionerna i det ursprungliga innehållsskriptet, inklusive den inbyggda chrome-API:en som chrome.runtime.sendMessage.

☢ Var försiktig, det kan blockeras av webbplatsens service worker: https://crbug.com/352364581

I content_script.js ser det ut som

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

Du måste också deklarera de importerade skripten i manifestets Webbåtkomliga resurser:

// ManifestV3

  "web_accessible_resources": [{
     "matches": ["<all_urls>"],
     "resources": ["your/content_main.js"]
   }],

// ManifestV2

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

För mer information:

  • Hur man använder ES6 “import” med Chrome-tillägg
  • Fungerande exempel på ES6-import i Chrome-tillägg
  • chrome.runtime.getURL

Synkron "import" lösning för vanliga icke-modulskript

Använd ett normalt skript utan modul, lägg till dess namn i "js" i "content_scripts" före ditt huvudsakliga innehållsskript, och använd sedan de globala variablerna/funktionerna direkt (exempel).

Svar #2

Ansvarsfriskrivning

För det första är det viktigt att säga att innehållsskript inte stödjer moduler från och med januari 2018. Denna lösning kringgår begränsningen genom att bädda in ett modul-script-tagg på sidan som leder tillbaka till din tillägg.

DETTA ÄR EN OSÄKER LÖSNING!

Ett webbsideskript (eller en annan tillägg) kan utnyttja din kod och extrahera/förfalska data genom att använda setters/getters på Object.prototype och andra prototyper, proxya funktioner och/eller globala objekt, eftersom koden inuti ett script-element körs i sidans JS-kontent, inte i den säkra isolerade JS-miljön där innehållsskript normalt körs.

En säker lösning är den dynamiska import() som visas i ett annat svar här.

Tillfällig lösning

Det här är min manifest.json:

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

Observera att jag har två skript i web_accessible_resources.

Det här är min 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);

Detta kommer att infoga main.js i webbsidan som ett modulscrip.

All min affärslogik finns nu i main.js.

För att denna metod ska fungera måste main.js (samt alla skript som jag kommer att importera) finnas i web_accessible_resources i manifestet.

Exempel på användning: my-script.js

    'use strict';
    
    const injectFunction = () => window.alert('hello world');
    
    export {injectFunction};

Och i main.js är detta ett exempel på att importera skriptet:

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

Det här fungerar! Inga fel uppstår, och jag är glad. :)

Svar #3

Statisk import-sats är inte tillgänglig i innehållsskript.

En lösning med globalt omfång

Innehållsskript lever i sin egen 'isolerade värld' och delar samma isolerade globala namnrymd som endast är tillgänglig för innehållsskript i ditt tillägg (antingen deklarerat i manifest.json eller injicerat programmässigt) och inte för sidskript.

Varning: om ditt innehållsskript deklarerades/injicerades i MAIN-världen i ett ManifestV3-tillägg, kan denna metod inte användas eftersom dina globala variabler kommer att exponeras för sidan. Istället, kombinera alla beroende skript i ett enda paket med en IIFE.

Här är implementeringen:

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

På samma sätt kan du faktorisera ut återanvändbara funktioner och andra aktörer som du annars skulle importera i innehållsskriptfiler.

N.B.: det globala namnområdet för innehållsskript är inte tillgängligt för några sidor utöver innehållsskripten - så det finns liten eller ingen global omfångsförorening.

Om du behöver importera några bibliotek - kommer du att behöva använda en bundlare som Parcel för att paketera dina innehållsskriptfiler tillsammans med de nödvändiga biblioteken till en huge-content-script.js och sedan nämna det i manifest.json.

P.S.: dokumentation om globalThis

Svar #4

Det bästa sättet skulle vara att använda bundlare som webpack eller Rollup.

Jag klarade mig med grundläggande konfiguration

const path = require('path');

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

Kör filen med kommandot

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

Bundlers kombinerar de relaterade filerna och vi kan använda modularisering i Chrome-tillägg! :D

Du kommer att behöva behålla alla andra filer som manifest och statiska filer i byggmappen.

Lek med det så kommer du så småningom att hitta ett sätt att få det att fungera!

Svar #5

Jag råkade just snubbla över den här frågan medan jag försökte lösa samma sak själv.

Hur som helst, jag tror att det finns en enklare lösning för att injicera dina egna anpassade moduler i ditt innehållsskript. Jag tittade på hur Jquery injiceras och det slog mig att du kan göra samma sak genom att skapa en IIFE (Immediately Invoked Function Expression), och deklarera den i din manifest.json

Det går ungefär så här:

I din manifest.json:

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

Skapa sedan bara en IIFE i din helpers/helpers.js:

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

Nu kan du fritt använda dina hjälpfunktioner i ditt innehållsskript:

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

Jag tycker att det är bra om du använder denna metod för att refaktorera några av dina generiska funktioner. Hoppas detta hjälper!

Relaterade artiklar