'Getting "Uncaught ReferenceError: i is not defined" error whenever I want to use an exported element from another file

I'm developing a chrome extension using webpack and babel.

I have this popup.js:

import 'regenerator-runtime/runtime' // This is required in every JS files that has an async-await function!!
const { NEW_MAPPING_BUTTON } = require('./constants/mapping-page-elements')

const gen_traffic = document.querySelector('.gen-traffic')

gen_traffic.addEventListener('click', async (e) => {
    let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

    chrome.scripting.executeScript({
        target: { tabId: tab.id },
        function: getPageData,
    });
})

const getPageData = () => {
    console.log('okk?')
    console.log( NEW_MAPPING_BUTTON )
}

mapping-page-elements.js:

exports.NEW_MAPPING_BUTTON = () => {
    return document.querySelector("app-mappings")
} 

webpack.common.js:

const path = require("path");
const ESLintPlugin = require("eslint-webpack-plugin");

module.exports = {
  entry: ["regenerator-runtime/runtime.js", "./src/popup.js"],
  module: {
    rules: [
      {
        test: /\.(js)$/,
        exclude: /node_modules/,
        use: ["babel-loader"],
      },
    ],
    noParse: /(mapbox-gl)\.js$/
  },
  resolve: {
    extensions: ["*", ".js"],
  },
  output: {
    path: path.resolve(__dirname, "./dist"),
    filename: "bundle.js",
  },
  devServer: {
    static: path.resolve(__dirname, "./dist"),
  },
  plugins: [new ESLintPlugin()],
};

For some reason, whenever I use the "NEW_MAPPING_BUTTON" in the popup.js file, it gives me "i is not defined" error. I tested the same element that i exported locally in popup.js and it was returning the element just fine, so I believe this is a problem with webpack's exporting system. Thanks in advance.



Solution 1:[1]

Since the function is executed in the context of the page, its original context is lost as explained the Chrome extension docs:

This function will be executed in the context of injection target. However, this will not carry over any of the current execution context of the function. As such, bound parameters (including the this object) and externally-referenced variables will result in errors.

For a start, referencing variables from outside the definition of the function will fail, including imports.

As Webpack (and or Babel/Typescript) bundle and transpile your file, some language features are replaced by polyfills. In my case an error instanceof Error check was transpiled to _instanceof(error,Error) or something along those lines, where _instanceof is a custom function defined in the file. Consequently, this custom functions will not be available when the function is eventually executed. This exacerbates the problem described above.

Possible solutions

Create new file

Create a new file instead with the contents of the function, with a matching webpack entrypoint/bundle. You can then use the files argument of chrome.scripting.executeScript:

const tabs = await chrome.tabs.query({ active: true, currentWindow: true })
const tabId = tabs[0].id

chrome.scripting.executeScript(
    {
      target: {tabId: tabId},
      files: ['getPageData.js'],
    }

Pros:

  • You can import values into the new file "normally" and it will get bundled properly

Cons:

  • Getting a value back from the file is brittle. According to the docs, the last expression in the file is returned. However, the same "bundling problem" described above means you cannot ascertain what the last line in the file will be.

Inject a script

You can inject a script in other ways besides chrome.scripting as explained here. The answer also has some links elaborating how you can set up two-way communication with the injected script.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Neville Omangi