Repository: wxt-dev/wxt (TypeScript)

Issue:
window not defined in content shared file in v0.19
### Describe the bug

I have a content script which has a react code.
I have created constant file from which i export window variable.
the `wxt prepare` command fails 

<details>
  <summary>Error logs</summary>

```
 > wxt prepare


WXT 0.19.0                                                                                         5:51:11 PM
ℹ Generating types...                                                                             5:51:13 PM
✖ Command failed after 3.837 s                                                                    5:51:15 PM

 ERROR  window is not defined                                                                      5:51:15 PM

  at entrypoints/content/constant.ts:1:21
  at ViteNodeRunner.runModule (node_modules/.pnpm/vite-node@2.0.4_@types+node@20.14.12/node_modules/vite-node/dist/client.mjs:399:11)
  at ViteNodeRunner.directRequest (node_modules/.pnpm/vite-node@2.0.4_@types+node@20.14.12/node_modules/vite-node/dist/client.mjs:381:16)
  at ViteNodeRunner.cachedRequest (node_modules/.pnpm/vite-node@2.0.4_@types+node@20.14.12/node_modules/vite-node/dist/client.mjs:206:14)
  at ViteNodeRunner.dependencyRequest (node_modules/.pnpm/vite-node@2.0.4_@types+node@20.14.12/node_modules/vite-node/dist/client.mjs:259:12)
  at entrypoints/content/App.tsx:3:31
  at ViteNodeRunner.runModule (node_modules/.pnpm/vite-node@2.0.4_@types+node@20.14.12/node_modules/vite-node/dist/client.mjs:399:5)
  at ViteNodeRunner.directRequest (node_modules/.pnpm/vite-node@2.0.4_@types+node@20.14.12/node_modules/vite-node/dist/client.mjs:381:5)
  at ViteNodeRunner.cachedRequest (node_modules/.pnpm/vite-node@2.0.4_@types+node@20.14.12/node_modules/vite-node/dist/client.mjs:206:14)
  at ViteNodeRunner.dependencyRequest (node_modules/.pnpm/vite-node@2.0.4_@types+node@20.14.12/node_modules/vite-node/dist/client.mjs:259:12)
```
</details>

### Reproduction

https://github.com/mehimanshupatil/wxt-example

### Steps to reproduce

pnpm i

### System Info

```shell
System:
    OS: macOS 14.5
    CPU: (8) arm64 Apple M1
    Memory: 65.75 MB / 8.00 GB
    Shell: 5.9 - /bin/zsh
Binaries:
    Node: 20.15.0 - ~/.nvm/versions/node/v20.15.0/bin/node
    npm: 10.8.2 - ~/.nvm/versions/node/v20.15.0/bin/npm
    pnpm: 9.6.0 - ~/.nvm/versions/node/v20.15.0/bin/pnpm
    Watchman: 2024.06.10.00 - /opt/homebrew/bin/watchman
Browsers:
    Brave Browser: 127.1.68.128
    Edge: 127.0.2651.74
    Safari: 17.5
npmPackages:
    wxt: ^0.19.0 => 0.19.0
```


### Used Package Manager

pnpm

### Validations

- [X] Read the [Contributing Guidelines](https://github.com/wxt-dev/wxt/blob/main/CONTRIBUTING.md).
- [X] Read the [docs](https://wxt.dev/guide/installation.html).
- [X] Check that there isn't [already an issue](https://github.com/wxt-dev/wxt/issues) that reports the same bug to avoid creating a duplicate.
- [X] Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/wxt-dev/wxt/discussions) or join our [Discord Chat Server](https://discord.gg/ZFsZqGery9).
- [X] The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.


Discussion: I have the same issue, the side effect of the import statement is triggered in v0.19.0, but not in v.0.18.0.
Pinged @aklinker1 about this in the Discord but seems to be an issue from the move to tsup, same behaviour with `self`
@aiktb @Timeraa @mehimanshupatil Can you guys try out `wxt@0.19.1-alpha1` and let me know if it works for you?

Just an FYI - you shouldn't be using browser environment globals, like `window` or `self`, outside the `main` function of an entrypoint. In this new version, I made it so most browser environment globals are at least defined, but you will still run into issues with things like `window.location.href`, because I didn't implement all the APIs.


> @aiktb @Timeraa @mehimanshupatil Can you guys try out `wxt@0.19.1-alpha1` and let me know if it works for you?
> 
> Just an FYI - you shouldn't be using browser environment globals, like `window` or `self`, outside the `main` function of an entrypoint. In this new version, I made it so most browser environment globals are at least defined, but you will still run into issues with things like `window.location.href`, because I didn't implement all the APIs.

Fixed.👍

Thank you for your answer, I have a Selector Class, in which the static member new a Class with a `document.querySelector` operation, and that is the problem.



Fix (patch):
diff --git a/packages/wxt/package.json b/packages/wxt/package.json
index 89ba57ae8..271d3db01 100644
--- a/packages/wxt/package.json
+++ b/packages/wxt/package.json
@@ -1,7 +1,7 @@
 {
   "name": "wxt",
   "type": "module",
-  "version": "0.19.0",
+  "version": "0.19.1-alpha2",
   "description": "Next gen framework for developing web extensions",
   "repository": {
     "type": "git",
diff --git a/packages/wxt/src/core/builders/vite/index.ts b/packages/wxt/src/core/builders/vite/index.ts
index a15a55304..1291eab8c 100644
--- a/packages/wxt/src/core/builders/vite/index.ts
+++ b/packages/wxt/src/core/builders/vite/index.ts
@@ -230,10 +230,7 @@ export async function createViteBuilder(
           baseConfig.optimizeDeps.noDiscovery = true;
           baseConfig.optimizeDeps.include = [];
           const envConfig: vite.InlineConfig = {
-            plugins: [
-              wxtPlugins.extensionApiMock(wxtConfig),
-              wxtPlugins.removeEntrypointMainFunction(wxtConfig, path),
-            ],
+            plugins: [wxtPlugins.removeEntrypointMainFunction(wxtConfig, path)],
           };
           const config = vite.mergeConfig(baseConfig, envConfig);
           const server = await vite.createServer(config);
diff --git a/packages/wxt/src/core/builders/vite/plugins/removeEntrypointMainFunction.ts b/packages/wxt/src/core/builders/vite/plugins/removeEntrypointMainFunction.ts
index 979523c6b..5146fe85a 100644
--- a/packages/wxt/src/core/builders/vite/plugins/removeEntrypointMainFunction.ts
+++ b/packages/wxt/src/core/builders/vite/plugins/removeEntrypointMainFunction.ts
@@ -14,8 +14,11 @@ export function removeEntrypointMainFunction(
   const absPath = normalizePath(resolve(config.root, path));
   return {
     name: 'wxt:remove-entrypoint-main-function',
-    transform(code, id) {
-      if (id === absPath) return removeMainFunctionCode(code);
+    transform: {
+      order: 'pre',
+      handler(code, id) {
+        if (id === absPath) return removeMainFunctionCode(code);
+      },
     },
   };
 }
diff --git a/packages/wxt/src/core/utils/building/find-entrypoints.ts b/packages/wxt/src/core/utils/building/find-entrypoints.ts
index c023d4224..2fd2f42f3 100644
--- a/packages/wxt/src/core/utils/building/find-entrypoints.ts
+++ b/packages/wxt/src/core/utils/building/find-entrypoints.ts
@@ -28,6 +28,7 @@ import { VIRTUAL_NOOP_BACKGROUND_MODULE_ID } from '../../utils/constants';
 import { CSS_EXTENSIONS_PATTERN } from '../../utils/paths';
 import pc from 'picocolors';
 import { wxt } from '../../wxt';
+import { createExtensionEnvironment } from '../environments';
 
 /**
  * Return entrypoints and their configuration by looking through the project's files.
@@ -72,47 +73,50 @@ export async function findEntrypoints(): Promise<Entrypoint[]> {
 
   // Import entrypoints to get their config
   let hasBackground = false;
-  const entrypoints: Entrypoint[] = await Promise.all(
-    entrypointInfos.map(async (info): Promise<Entrypoint> => {
-      const { type } = info;
-      switch (type) {
-        case 'popup':
-          return await getPopupEntrypoint(info);
-        case 'sidepanel':
-          return await getSidepanelEntrypoint(info);
-        case 'options':
-          return await getOptionsEntrypoint(info);
-        case 'background':
-          hasBackground = true;
-          return await getBackgroundEntrypoint(info);
-        case 'content-script':
-          return await getContentScriptEntrypoint(info);
-        case 'unlisted-page':
-          return await getUnlistedPageEntrypoint(info);
-        case 'unlisted-script':
-          return await getUnlistedScriptEntrypoint(info);
-        case 'content-script-style':
-          return {
-            ...info,
-            type,
-            outputDir: resolve(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
-            options: {
-              include: undefined,
-              exclude: undefined,
-            },
-          };
-        default:
-          return {
-            ...info,
-            type,
-            outputDir: wxt.config.outDir,
-            options: {
-              include: undefined,
-              exclude: undefined,
-            },
-          };
-      }
-    }),
+  const env = createExtensionEnvironment();
+  const entrypoints: Entrypoint[] = await env.run(() =>
+    Promise.all(
+      entrypointInfos.map(async (info): Promise<Entrypoint> => {
+        const { type } = info;
+        switch (type) {
+          case 'popup':
+            return await getPopupEntrypoint(info);
+          case 'sidepanel':
+            return await getSidepanelEntrypoint(info);
+          case 'options':
+            return await getOptionsEntrypoint(info);
+          case 'background':
+            hasBackground = true;
+            return await getBackgroundEntrypoint(info);
+          case 'content-script':
+            return await getContentScriptEntrypoint(info);
+          case 'unlisted-page':
+            return await getUnlistedPageEntrypoint(info);
+          case 'unlisted-script':
+            return await getUnlistedScriptEntrypoint(info);
+          case 'content-script-style':
+            return {
+              ...info,
+              type,
+              outputDir: resolve(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
+              options: {
+                include: undefined,
+                exclude: undefined,
+              },
+            };
+          default:
+            return {
+              ...info,
+              type,
+              outputDir: wxt.config.outDir,
+              options: {
+                include: undefined,
+                exclude: undefined,
+              },
+            };
+        }
+      }),
+    ),
   );
 
   if (wxt.config.command === 'serve' && !hasBackground) {
diff --git a/packages/wxt/src/core/utils/environments/browser-environment.ts b/packages/wxt/src/core/utils/environments/browser-environment.ts
new file mode 100644
index 000000000..6b9d3d7b9
--- /dev/null
+++ b/packages/wxt/src/core/utils/environments/browser-environment.ts
@@ -0,0 +1,21 @@
+import { parseHTML } from 'linkedom';
+import { createEnvironment, Environment, EnvGlobals } from './environment';
+
+export function createBrowserEnvironment(): Environment {
+  return createEnvironment(getBrowserEnvironmentGlobals);
+}
+
+export function getBrowserEnvironmentGlobals(): EnvGlobals {
+  const { window, document, global } = parseHTML(`
+    <html>
+      <head></head>
+      <body></body>
+    </html>
+  `);
+  return {
+    ...global,
+    window,
+    document,
+    self: global,
+  };
+}
diff --git a/packages/wxt/src/core/utils/environments/environment.ts b/packages/wxt/src/core/utils/environments/environment.ts
new file mode 100644
index 000000000..8e62c9cea
--- /dev/null
+++ b/packages/wxt/src/core/utils/environments/environment.ts
@@ -0,0 +1,49 @@
+export interface Environment {
+  setup: () => () => void;
+  run: <T>(fn: () => Promise<T>) => Promise<T>;
+}
+
+export function createEnvironment(getGlobals: () => EnvGlobals): Environment {
+  const setup = () => {
+    const envGlobals = getGlobals();
+    const ogGlobals = getOgGlobals(envGlobals);
+    applyGlobals(envGlobals);
+
+    return () => {
+      applyGlobals(ogGlobals);
+    };
+  };
+  const run = async (fn: () => any) => {
+    const teardown = setup();
+    try {
+      return await fn();
+    } finally {
+      teardown();
+    }
+  };
+  return {
+    setup,
+    run,
+  };
+}
+
+export type EnvGlobals = Record<string, any>;
+
+export function getOgGlobals(envGlobals: EnvGlobals): EnvGlobals {
+  return Object.keys(envGlobals).reduce<typeof envGlobals>((acc, key) => {
+    // @ts-expect-error: Untyped key on globalThis
+    acc[key] = globalThis[key];
+    return acc;
+  }, {});
+}
+
+export function applyGlobals(globals: EnvGlobals): void {
+  Object.entries(globals).forEach(([key, envValue]) => {
+    try {
+      // @ts-expect-error: Untyped key on globalThis
+      globalThis[key] = envValue;
+    } catch (err) {
+      // ignore any globals that can't be set
+    }
+  });
+}
diff --git a/packages/wxt/src/core/utils/environments/extension-environment.ts b/packages/wxt/src/core/utils/environments/extension-environment.ts
new file mode 100644
index 000000000..1f5644a41
--- /dev/null
+++ b/packages/wxt/src/core/utils/environments/extension-environment.ts
@@ -0,0 +1,15 @@
+import { fakeBrowser } from '@webext-core/fake-browser';
+import { getBrowserEnvironmentGlobals } from './browser-environment';
+import { createEnvironment, Environment } from './environment';
+
+export function createExtensionEnvironment(): Environment {
+  return createEnvironment(getExtensionEnvironmentGlobals);
+}
+
+export function getExtensionEnvironmentGlobals() {
+  return {
+    ...getBrowserEnvironmentGlobals(),
+    chrome: fakeBrowser,
+    browser: fakeBrowser,
+  };
+}
diff --git a/packages/wxt/src/core/utils/environments/index.ts b/packages/wxt/src/core/utils/environments/index.ts
new file mode 100644
index 000000000..ea141e0b3
--- /dev/null
+++ b/packages/wxt/src/core/utils/environments/index.ts
@@ -0,0 +1,2 @@
+export * from './browser-environment';
+export * from './extension-environment';