Evelina K
Evelina K

Reputation: 131

Chrome extension: Uncaught SyntaxError: Cannot use import statement outside a module

There are a bunch of similar threads on here however I cannot seem to find anything that works for me. I am building a chrome extension where I'd like to connect to a websocket. I have found a project, https://github.com/matthewlawson/lnm-socket.io, which I try to use as inspiration although I cannot make it work for me.

The server is not in the chrome extension, however I try to access it from my background.js file. That's when I get the Cannot use import statement outside a module error message. Adding "type": "module" to my package.json file doesn't seem to fix it and since it is the background file I do not have it in a script tag to place the "type": "module" in. What can I do?

Upvotes: 13

Views: 12143

Answers (2)

user29820840
user29820840

Reputation: 11

I painfully had to go through some trial and error to find a solution for "Cannot use import statement outside a module" globally in my entire chrome extension setup with a contentscript, service-worker and background in manifest v3, but I have to admit that I'm also not trying to setup sth fancy like websockets, just importing some external libs getting this error too.

  • "type": "module" for content script didn't fix the issue for me What I have tho:
  • My manifest: has background.type = "module"
  • My popup: has a script tag with <script type="module" ...>

Solution in short: as far as I assume, you cannot have any import statements in any script that is invoked by your content script in your production build. (Maybe I'm also just to dump to make imports work in content scripts?) Building your content script separately from popup & service worker in my package manager fixed all of these issues. Service worker and popup can import from the same module.

Solution Extended Setup: Building your content scripts separately means means you resolve any import statements and therefore your content scripts will contain anything that you import and encapsulated within itself. I personally use vite as my build tool and set up a base config file with settings for every component of my extension, while also having 2 more config files that split the build for content script (1) and service worker/popup (2).

While content scripts (1) run within the context of a certain webpage, I believe the service worker and popup (2) operate in the same "environment" that belongs to the extension itself, which is the reason why if popup and service worker share certain imports/ modules, it didn't lead to any errors for me. Only if one of their imports intersected with an import in content scripts.

This is my setup in vite:

  • my vite base config: basically is just a function exporting the config object that is needed in vite for configuring the build (at the end, I think this this can be just a normal ts file exporting this function)
  • my two other configs for (1) and (2) use this base config function and extend it by their own settings which are specific to (1) and (2)
  • my package.json:
    • under "scripts", I have a custom script named "buildCustom" which value is essentially npm run buildOne && npm run buildTwo which is how I separated those builds (for buildOne and Two you can choose anything you define)
    • "scripts" -> "buildOne" : is building according to the (1) config, conretely: tsc -b && vite build --config TheConfigFileNameForOne.config.ts
    • "scripts" -> "buildTwo": analogue to (1)

the code roughly: base.config.ts

  import { UserConfig } from 'vite';
import path, { resolve, parse } from 'path';

export function createBaseConfig(...files: string[]): UserConfig {

  const root = resolve(__dirname, 'src') 

  const input: Record<string,string> = {}
  files.forEach( (file) => {
    input[parse(file).name] = resolve(root,file)  //input-object format example:  "background : resolve(root, 'background.ts')",
  })

  return {
    plugins: ..., //your base configs
    server: ...,
    build: {
      ...
    }
  }
}

buildOne.config.ts (and analogue buildTwo):

import { defineConfig } from 'vite';
import { createBaseConfig } from './base.config.ts'

export default defineConfig( ('pathToContentScript.ts') => { 
  // for Two: ('pathToServiceWorker.ts','pathToPopup.html')    

  const baseConfig = createBaseConfig()
    
      return {
        ...baseConfig,
    
        build: {
          ...baseConfig.build,
          // ie: some content script specific build settings here 
        }
    
  }
})

Now I can run everything with npm run buildCustom from CLI without any problems.

Note about args: you can also pass arguments (commands, modes) between those for further customization.

This solved all the issues for me. Dynamic imports don't work anyway at all in v3.

The downsides are potentially:

  • duplicated code for cs and sw/popup
  • bigger production files
  • less modularity in production
  • longer build time

Maybe there is a better solution, idk!

Upvotes: 1

Shadoath
Shadoath

Reputation: 1071

The solution I found was not to change the manifest.json; instead, it was how the javascript file was loaded.

I went from <script src="/dist/popup.js"></script>

to <script type="module" src="/dist/popup.js"></script>

Upvotes: 2

Related Questions