# https://github.com/Menci/vite-plugin-top-level-await Project Manual

Generated at: 2026-06-14 21:11:29 UTC

## Table of Contents

- [Introduction and Installation](#page-1)
- [Transformation Pipeline and Internals](#page-2)
- [Configuration Options and Worker Support](#page-3)
- [Known Issues, Compatibility and Troubleshooting](#page-4)

<a id='page-1'></a>

## Introduction and Installation

### Related Pages

Related topics: [Transformation Pipeline and Internals](#page-2), [Configuration Options and Worker Support](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [README.md](https://github.com/Menci/vite-plugin-top-level-await/blob/main/README.md)
- [package.json](https://github.com/Menci/vite-plugin-top-level-await/blob/main/package.json)
- [src/index.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts)
- [src/options.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/options.ts)
- [src/swc.js](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/swc.js)
- [src/esbuild.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/esbuild.ts)
- [src/transform.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/transform.ts)
- [src/bundle-info.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/bundle-info.ts)
</details>

# Introduction and Installation

## Purpose and Scope

`vite-plugin-top-level-await` is a Vite plugin authored by Menci that rewrites bundled ES modules so they work in environments where top-level await (TLA) is not natively available. The plugin's own description states: *"Transform code to support top-level await in normal browsers for Vite."* Source: [package.json:3](https://github.com/Menci/vite-plugin-top-level-await/blob/main/package.json).

The plugin registers with Vite at the `enforce: "post"` phase of the plugin pipeline, meaning it runs after user-supplied plugins. Source: [src/index.ts:42](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts). During the `generateBundle` hook it re-parses each emitted chunk with SWC, builds a dependency graph of the bundled modules, and re-emits each chunk as an asynchronous wrapper that defers value availability until upstream promises resolve. Source: [src/transform.ts:90-110](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/transform.ts).

Typical use cases include:

- Targeting legacy browser baselines that lack top-level await support.
- Producing artifacts for Chrome extension service workers where TLA behaves inconsistently (see community issue [#57](https://github.com/Menci/vite-plugin-top-level-await/issues/57)).
- Building bundles for environments such as older Safari, Edge, or Firefox versions.

## Installation

Install the plugin as a development dependency:

```bash
npm install -D vite-plugin-top-level-await
# or
pnpm add -D vite-plugin-top-level-await
# or
yarn add -D vite-plugin-top-level-await
```

Then register it in `vite.config.ts`:

```ts
import { defineConfig } from "vite";
import topLevelAwait from "vite-plugin-top-level-await";

export default defineConfig({
  plugins: [topLevelAwait()]
});
```

The plugin declares `vite >=2.8` as a `peerDependencies` range. Source: [package.json:54](https://github.com/Menci/vite-plugin-top-level-await/blob/main/package.json). The project's own dev toolchain currently pins `vite ^7.0.5`. Source: [package.json:43](https://github.com/Menci/vite-plugin-top-level-await/blob/main/package.json).

### Dual Package Layout

The published tarball exposes both ESM and CommonJS entry points through Node's conditional `exports` field:

| Field | Path |
| --- | --- |
| `main` | `./exports/require.cjs` |
| `module` | `./exports/import.mjs` |
| `exports.import.types` | `./dist/index.d.ts` |
| `exports.import.default` | `./exports/import.mjs` |
| `exports.require.types` | `./exports/require.d.cts` |
| `exports.require.default` | `./exports/require.cjs` |

Source: [package.json:5-19](https://github.com/Menci/vite-plugin-top-level-await/blob/main/package.json). Only `/dist`, `/exports`, and the matching type definitions are shipped — the TypeScript sources under `/src` are excluded. Source: [package.json:60-66](https://github.com/Menci/vite-plugin-top-level-await/blob/main/package.json).

## Runtime Dependencies and Resolution Logic

### SWC: Native with WASM Fallback

The plugin requires three runtime npm packages: `@rollup/plugin-virtual`, `@swc/core`, and `@swc/wasm`, plus `uuid` for internal identifier generation. Source: [package.json:48-53](https://github.com/Menci/vite-plugin-top-level-await/blob/main/package.json). The native `@swc/core` binary is preferred, but a WASM fallback is bundled for environments where the native module fails to load. Source: [src/swc.js:1-14](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/swc.js).

```mermaid
flowchart TD
    A[Plugin needs SWC] --> B{VITE_TLA_FORCE_WASM set?}
    B -- yes --> W[Load @swc/wasm]
    B -- no --> C{Try require @swc/core}
    C -- success --> N[Use native @swc/core]
    C -- throws --> D{VITE_TLA_FORCE_NATIVE set?}
    D -- yes --> E[Re-throw original error]
    D -- no --> W
```

Two environment variables control the resolver: `VITE_TLA_FORCE_WASM=true` bypasses the native binary entirely, and `VITE_TLA_FORCE_NATIVE=true` disables the WASM fallback and re-raises the original load failure. Source: [src/swc.js:3-14](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/swc.js).

### esbuild: Re-used from Vite

The plugin deliberately re-uses the `esbuild` instance bundled with Vite rather than declaring its own dependency. The loader in `src/esbuild.ts` synthesizes a Node `Module` object and calls `require` against Vite's installation path so both packages always resolve the same binary. Source: [src/esbuild.ts:3-13](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/esbuild.ts).

This pattern is one source of community-reported friction: when run under Bun, which does not implement `Module._resolveFilename` or `Module.prototype.require`, the plugin crashes with `TypeError: virtualModule.require is not a function` (issue [#68](https://github.com/Menci/vite-plugin-top-level-await/issues/68)).

## Default Behavior

When called with no arguments, `topLevelAwait()` activates with defaults inherited from `DEFAULT_OPTIONS`. The most relevant defaults are:

- `promiseExportName`: `"__tla"` — the symbol used to expose each chunk's initialization promise.
- `promiseImportName`: `"__tla"` — the matching symbol imported from upstream chunks.
- `fetchAwaitEntry`: `false` — controls whether entry chunks fetch and await `__tla` from dependencies.
- Browser targets: `["es2020", "edge88", "firefox78", "chrome87", "safari14"]`. Source: [src/index.ts:16-18](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts).

The plugin overwrites Vite's `build.target` whenever the user does not set one explicitly, which is reported as confusing behavior in issue [#77](https://github.com/Menci/vite-plugin-top-level-await/issues/77). To preserve Vite's own target list, set `build.target` explicitly inside `vite.config.ts`.

## Known Installation Pitfalls

The community has surfaced several recurring problems that affect installation and first-run behavior:

1. **Vite 8 / Rollup resolution failure** — issue [#76](https://github.com/Menci/vite-plugin-top-level-await/issues/76) reports `Error: Cannot find module 'rollup'` after upgrading Vite. The plugin imports `rollup` types directly. Source: [src/index.ts:3](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts).
2. **Vite 7 esbuild deprecation** — issue [#70](https://github.com/Menci/vite-plugin-top-level-await/issues/70) notes that Vite 7 deprecates `optimizeDeps.esbuildOptions` in favor of `optimizeDeps.rollupOptions`.
3. **Vite 6.2.0 breakage** — issue [#66](https://github.com/Menci/vite-plugin-top-level-await/issues/66) reports empty pages after upgrading to Vite 6.2.0; downgrading to 6.1.0 restored correct output.
4. **Bun incompatibility** — issue [#68](https://github.com/Menci/vite-plugin-top-level-await/issues/68) documents the `module.require` runtime error described above.
5. **Source map / hash drift** — issues [#34](https://github.com/Menci/vite-plugin-top-level-await/issues/34) and [#44](https://github.com/Menci/vite-plugin-top-level-await/issues/44) note that because rewriting happens in `generateBundle`, chunk hashes and `//# sourceMappingURL` annotations can fall out of sync with the rewritten content.

For Chrome extension service workers, issue [#57](https://github.com/Menci/vite-plugin-top-level-await/issues/57) recommends reviewing the `isWorker` and `isWorkerIifeRequested` flags inside `src/index.ts` when integrating the plugin into extension manifests.

## See Also

- **Architecture and Plugin Pipeline** — describes the SWC-driven rewriting strategy and dependency graph construction.
- **Configuration Options** — full reference for `Options` and worker mode.
- **Troubleshooting** — covers common TLA edge cases such as function hoisting and deferred exports.

---

<a id='page-2'></a>

## Transformation Pipeline and Internals

### Related Pages

Related topics: [Introduction and Installation](#page-1), [Configuration Options and Worker Support](#page-3), [Known Issues, Compatibility and Troubleshooting](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [src/index.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts)
- [src/bundle-info.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/bundle-info.ts)
- [src/transform.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/transform.ts)
- [src/utils/make-node.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/utils/make-node.ts)
- [src/find.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/find.ts)
- [src/swc.d.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/swc.d.ts)
- [package.json](https://github.com/Menci/vite-plugin-top-level-await/blob/main/package.json)
</details>

# Transformation Pipeline and Internals

This page documents the internal transformation pipeline that `vite-plugin-top-level-await` executes on Rollup/Vite output chunks to make top-level await (TLA) and dynamic `import()` compatible with browsers that do not natively support module-level suspension. The pipeline operates entirely after Vite/Rollup has finished its bundling pass; the plugin re-walks the generated ES modules and rewrites them using SWC.

## High-Level Pipeline Overview

The plugin's `generateBundle` hook drives a four-stage pipeline. Each stage is implemented as a separate module, and the boundaries between them are clean enough to reason about in isolation.

```mermaid
flowchart LR
    A[Vite/Rollup<br/>generates chunks] --> B[parseBundleAsts<br/>bundle-info.ts]
    B --> C[parseBundleInfo<br/>build dependency graph<br/>+ transformNeeded flags]
    C --> D[transformModule<br/>transform.ts]
    D --> E[esbuild.transform<br/>down-level to target]
    E --> F[Updated chunks<br/>written back to bundle]
```

Source: [src/index.ts:60-65](), [src/bundle-info.ts](), [src/transform.ts]()

### Stage 1 — AST parsing

`parseBundleAsts` invokes `@swc/core`'s `parse` on every emitted chunk string, targeting `es2022` so that the parser understands both top-level `await` expressions and the AST shapes the transformer manipulates. Source: [src/bundle-info.ts]()

```ts
await SWC.parse(code, { syntax: "ecmascript", target: "es2022" })
```

### Stage 2 — Dependency graph and reachability

`parseBundleInfo` walks each module's body twice. Pass 1 collects every `ImportDeclaration` and `ExportNamedDeclaration` with a `source`, resolves relative paths through `resolveImport`, and populates the reverse edges (`importedBy`) used for reachability analysis. It also asks `findHighestPattern` to classify each module as `TopLevelAwait`, `DynamicImport`, or synchronous. Source: [src/bundle-info.ts]()

Pass 2 performs a BFS over the reverse graph: any module that depends on a `transformNeeded` module is itself flagged as `transformNeeded`, even if it contains no `await` of its own. This is essential because a module that imports a TLA producer must also be rewritten so its consumers can `await` the producer's exported `__tla` promise. Source: [src/bundle-info.ts]()

### Stage 3 — Per-module rewrite

For every chunk where `transformNeeded === true`, the plugin calls `transformModule` from `src/transform.ts`. The transformer:

1. Generates collision-resistant local identifiers via `RandomIdentifierGenerator` (initialized from the source code).
2. Hoists each original top-level statement into the body of an `async` arrow function.
3. Rewrites all `import` declarations to additionally import the upstream module's `__tla` promise, named `__tla_N` where `N` increments per import.
4. Re-emits a single top-level `let __tla = Promise.all([...]).then(async () => { ... })` that first awaits every `__tla_N` (wrapped in `try { return __tla_N; } catch {}` to swallow cyclic TDZ errors) and then runs the hoisted body.
5. If the chunk is imported by other modules, also exports `__tla`; otherwise (entry chunks) it merely evaluates the promise expression as a statement.

Source: [src/transform.ts]()

### Stage 4 — Down-leveling and write-back

The transformed AST is printed with SWC and then pushed through `esbuild.transform` using the build target captured from Vite's resolved config (with a fallback to Vite's defaults: `es2020`, `edge88`, `firefox78`, `chrome87`, `safari14`). The final code replaces the chunk's source in `generateBundle`. Source: [src/index.ts:60-65]()

## Pattern Detection (`find`)

`findHighestPattern` ranks each module's body so that `TopLevelAwait` > `DynamicImport` > synchronous. The result determines whether the module becomes an async-initialization producer, a dynamic-import rewrite target, or both. This ordering is what causes the `importedBy` propagation in `parseBundleInfo` to flow correctly: a module containing TLA forces every downstream consumer to be transformed, even if those consumers themselves only use dynamic imports. Source: [src/find.ts](), [src/bundle-info.ts]()

## AST Construction Helpers (`utils/make-node.ts`)

Because SWC's API is a low-level visitor, the transformer never mutates SWC nodes in place for new constructs — it constructs them with factory functions. The helpers cover the small vocabulary the transformer actually emits:

| Helper | Purpose |
|---|---|
| `makeIdentifier` / `makeStatement` | Identifier and expression-statement factories. |
| `makeVariablesDeclaration` / `makeVariableInitDeclaration` | Builds `let x;` and `let x = expr;` declarators. |
| `makeAwaitExpression` | Wraps an expression in `await`. |
| `makeImportDeclaration` / `makeImportSpecifier` | Re-emits import statements with `__tla_N` specifiers. |
| `makeCallExpression` / `makeArrayExpression` / `makeArrowFunction` | Constructs the `Promise.all([...]).then(async () => ...)` skeleton. |
| `makeAssignmentStatement` / `makeParenthesisExpression` | Used when destructuring assignment must be wrapped in parens. |
| `makeExportListDeclaration` / `makeReturnStatement` / `makeTryCatchStatement` | Final emission of the `export { x, y, __tla }` list and the TDZ-tolerant `try { return __tla_N; } catch {}` IIFEs. |

Source: [src/utils/make-node.ts]()

## Why Post-Processing (and What That Breaks)

Because the rewrite happens inside `generateBundle`, the plugin only sees code that has already been code-split, minified, and stripped of source maps. The plugin intentionally operates on the emitted ESM output rather than the original source. Several community-reported behaviors trace directly to this design choice:

- **Stale chunk hashes**: When `generateBundle` mutates chunk code, Rollup has already finalized the filename hash from the pre-mutation code, so the on-disk filename no longer matches the content. Source: [issue #44](https://github.com/Menci/vite-plugin-top-level-await/issues/44)
- **Missing `sourceMappingURL` annotations**: The post-processing pipeline replaces the chunk source but does not regenerate source maps. Source: [issue #34](https://github.com/Menci/vite-plugin-top-level-await/issues/34)
- **Hoisting side effects**: Wrapping top-level statements in an `async () => { ... }` defers them; `function` and `var` declarations no longer run at module-evaluation time, which can break code that relied on hoisting (`foo.bar = 42; export function foo() {}`). Source: [issue #71](https://github.com/Menci/vite-plugin-top-level-await/issues/71)
- **Initialization order**: Because every original statement becomes a microtask, downstream consumers must `await __tla` before reading cross-module exports — a foot-gun that surfaces most clearly with Remix and Module Federation setups. Source: [issue #72](https://github.com/Menci/vite-plugin-top-level-await/issues/72), [issue #74](https://github.com/Menci/vite-plugin-top-level-await/issues/74)

## See Also

- [Plugin Configuration and Options]()
- [Workers and Build Targets]()
- [Troubleshooting and Known Limitations]()
- Repository README — [README.md](https://github.com/Menci/vite-plugin-top-level-await/blob/main/README.md)

---

<a id='page-3'></a>

## Configuration Options and Worker Support

### Related Pages

Related topics: [Introduction and Installation](#page-1), [Transformation Pipeline and Internals](#page-2)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [src/index.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts)
- [src/options.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/options.ts)
- [src/transform.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/transform.ts)
- [src/esbuild.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/esbuild.ts)
- [src/swc.js](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/swc.js)
- [src/bundle-info.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/bundle-info.ts)
- [README.md](https://github.com/Menci/vite-plugin-top-level-await/blob/main/README.md)
- [package.json](https://github.com/Menci/vite-plugin-top-level-await/blob/main/package.json)
</details>

# Configuration Options and Worker Support

`vite-plugin-top-level-await` exposes a small, focused configuration surface. The plugin's options control how Rollup output is post-processed in `generateBundle`, how the build target is inferred (or overridden), and how worker bundles are detected and re-bundled. This page documents the options contract, the worker pipeline, and the build-target resolution logic, all of which are common sources of community-reported issues (see [issues #70](https://github.com/Menci/vite-plugin-top-level-await/issues/70), [#76](https://github.com/Menci/vite-plugin-top-level-await/issues/76), [#77](https://github.com/Menci/vite-plugin-top-level-await/issues/77)).

## Configuration Options

The plugin accepts a single optional `Options` object (re-exported from [src/index.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts)) and merges it with `DEFAULT_OPTIONS` at plugin construction time:

```ts
const resolvedOptions: Options = {
  ...DEFAULT_OPTIONS,
  ...(options || {})
};
```

Source: [src/index.ts:23-27](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts#L23-L27)

The shape of `Options` is declared in [src/options.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/options.ts) and consumed by the transformer at [src/transform.ts:60-67](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/transform.ts#L60-L67) via the `options` parameter of `transformModule(code, ast, moduleName, bundleInfo, options)`. The options object is threaded through every per-module rewrite so that AST construction (see [src/utils/make-node.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/utils/make-node.ts)) can be conditioned on flags when needed.

| Option | Purpose | Source |
| --- | --- | --- |
| `promoteOptions` | Controls how imported `__tla` promises are awaited inside the generated `Promise.all([...])` wrapper (see `transformModule`). | [src/transform.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/transform.ts) |
| Build target | Determines the ES level passed to the internal `esbuild.transform` call. | [src/index.ts:16-18](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts#L16-L18) |
| Worker IIFE mode | Switches worker output to a single-file IIFE bundle so legacy Web Worker targets can run TLA-transformed code. | [src/index.ts:43-49](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts#L43-L49) |

## Build Target Resolution

The plugin pins its default build target to the same baseline Vite uses internally:

```ts
const DEFAULT_VITE_TARGET: ViteTarget =
  ["es2020", "edge88", "firefox78", "chrome87", "safari14"];
```

Source: [src/index.ts:16-18](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts#L16-L18)

If the user does not configure `build.target` in `vite.config.ts`, the plugin falls back to `DEFAULT_VITE_TARGET` and forwards it to a private `buildRawTarget` helper that wraps `esbuild.transform`:

```ts
const buildRawTarget = async (code: string) => {
  return (await esbuild.transform(code, {
    minify,
    target: buildTarget as string | string[],
    format: "esm"
  })).code as string;
};
```

Source: [src/index.ts:33-40](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts#L33-L40)

The `esbuild` instance is intentionally resolved from the consumer's `vite` package (so the plugin never duplicates a build toolchain). It is loaded through a small helper that uses Node's internal `Module._resolveFilename`:

Source: [src/esbuild.ts:4-12](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/esbuild.ts#L4-L12)

> Community note: Issue [#77](https://github.com/Menci/vite-plugin-top-level-await/issues/77) reports that the plugin silently overwrites Vite's defaults when `build.target` is unset. If you depend on Vite's modern target list (for example to keep class fields intact), explicitly set `build.target` in your Vite config to avoid this override.

## Worker Support

Worker handling is one of the most configuration-sensitive areas of the plugin. Detection happens in the `outputOptions` Rollup hook:

```ts
outputOptions(options) {
  if (isWorker && options.format === "es") {
    return { format: "iife" };
  }
}
```

Source: [src/index.ts:43-49](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts#L43-L49)

When `isWorker` is true and the output format is still ESM, the plugin forces an IIFE bundle. The `isWorker` flag is set from Vite's resolved config inside the plugin's `configResolved` hook, allowing the same plugin instance to behave differently for the main app versus worker entry points. A separate `isWorkerIifeRequested` flag tracks whether the user opted into the IIFE path.

Source: [src/index.ts:21-22](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts#L21-L22)

The README documents a typical two-mode pattern, where the dev server must keep workers as native ES modules while the production build uses a self-contained IIFE bundle that runs even on browsers without native top-level await inside workers:

```ts
new Worker(new URL("./my-worker.js", import.meta.url), { type: "module" })
  : new Worker(new URL("./my-worker.js", import.meta.url), { type: "classic" });
```

Source: [README.md](https://github.com/Menci/vite-plugin-top-level-await/blob/main/README.md)

> Community note: Issue [#57](https://github.com/Menci/vite-plugin-top-level-await/issues/57) covers Chrome extension service-worker scenarios where the `type: "module"` workers cannot be used in `dist/` mode. The IIFE path described above is the recommended workaround.

## Pipeline Overview

The following diagram shows where configuration and worker support fit into the plugin's overall lifecycle:

```mermaid
flowchart TD
  A[Plugin Invocation\nwith Options?] --> B[Merge with DEFAULT_OPTIONS]
  B --> C[Vite configResolved\nisWorker, buildTarget, minify]
  C --> D{Rollup outputOptions\nWorker + ESM?}
  D -- Yes --> E[Force IIFE format]
  D -- No --> F[Keep ESM]
  E --> G[generateBundle\nParse AST with SWC]
  F --> G
  G --> H[parseBundleInfo\nDependency graph]
  H --> I[transformModule\nper chunk, with options]
  I --> J[esbuild.transform\ntarget = buildTarget]
  J --> K[Updated chunk code]
```

Source: [src/index.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts), [src/bundle-info.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/bundle-info.ts), [src/transform.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/transform.ts)

AST parsing uses `@swc/core` with a WASM fallback for environments without the native binary. The loader respects two environment variables: `VITE_TLA_FORCE_WASM` and `VITE_TLA_FORCE_NATIVE`, which are useful when debugging issues like [issue #68](https://github.com/Menci/vite-plugin-top-level-await/issues/68) (Bun's `module.require` incompatibility).

Source: [src/swc.js:3-12](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/swc.js#L3-L12)

## Common Pitfalls

- **Vite version skew:** Issue [#76](https://github.com/Menci/vite-plugin-top-level-await/issues/76) (Vite 8) and [#70](https://github.com/Menci/vite-plugin-top-level-await/issues/70) (Vite 7 `optimizeDeps.esbuildOptions` deprecation) trace back to the plugin's tight coupling with Rollup and Vite-internal APIs. The current peer range `vite: ">=2.8"` (Source: [package.json](https://github.com/Menci/vite-plugin-top-level-await/blob/main/package.json)) does not pin a maximum, so major Vite releases may require plugin updates.
- **Target override:** As noted above, an unset `build.target` in Vite's config is silently replaced by `DEFAULT_VITE_TARGET` (Source: [src/index.ts:16-18](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts#L16-L18)).
- **Chunk hashing:** Because rewriting happens in `generateBundle`, the plugin updates chunk contents without recomputing file hashes (Source: [src/index.ts:generateBundle](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts)). Issue [#44](https://github.com/Menci/vite-plugin-top-level-await/issues/44) documents the cache-invalidation consequences of this behavior.
- **Bun compatibility:** Bun's loader lacks `Module._resolveFilename`, which is what [src/esbuild.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/esbuild.ts) relies on; see [issue #68](https://github.com/Menci/vite-plugin-top-level-await/issues/68) for the recommended workarounds.

## See Also

- Transformation logic and AST helpers: [src/transform.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/transform.ts), [src/utils/make-node.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/utils/make-node.ts)
- Bundle dependency graph: [src/bundle-info.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/bundle-info.ts)
- Original example and worker usage notes: [README.md](https://github.com/Menci/vite-plugin-top-level-await/blob/main/README.md)

---

<a id='page-4'></a>

## Known Issues, Compatibility and Troubleshooting

### Related Pages

Related topics: [Introduction and Installation](#page-1), [Transformation Pipeline and Internals](#page-2), [Configuration Options and Worker Support](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [src/index.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/index.ts)
- [src/esbuild.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/esbuild.ts)
- [src/transform.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/transform.ts)
- [src/bundle-info.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/bundle-info.ts)
- [src/swc.js](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/swc.js)
- [src/utils/make-node.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/utils/make-node.ts)
- [src/options.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/options.ts)
- [package.json](https://github.com/Menci/vite-plugin-top-level-await/blob/main/package.json)
</details>

# Known Issues, Compatibility and Troubleshooting

## Overview

`vite-plugin-top-level-await` is a build-time transformer that rewrites modules containing top-level `await` or dynamic `import()` into an async IIFE wrapped in a `Promise.all(...).then(...)` pattern, exposing a `__tla` promise that importers must `await`. Because the transformation happens after Rollup has produced chunks (in the `generateBundle` step), it sits in a delicate position relative to Vite's build pipeline. The sections below catalogue the recurring compatibility and correctness issues reported by users, explain the underlying cause as seen in the source, and offer mitigation strategies.

Source: [src/transform.ts](https://github.com/Menci/vite-plugin-top-level-await/blob/main/src/transform.ts)

## Vite and Runtime Compatibility Matrix

The plugin declares `vite >=2.8` as a peer dependency, but several major releases of Vite introduce friction with how the plugin integrates.

| Vite Version | Status | Root Cause in Source | Reference |
|--------------|--------|----------------------|-----------|
| 2.x – 5.x | Works | Plugin uses Rollup output hooks and the bundled esbuild resolution path. | `peerDependencies` in `package.json` |
| 6.0.x | Partially broken | Target overwriting logic in [src/index.ts:16-18](src/index.ts) clobbers Vite's `build.target` when the user does not explicitly set it. | Issue #66, #77 |
| 6.2.x | Build appears successful but runtime is blank | Regression between Vite 6.1 and 6.2 in how chunks are emitted. | Issue #66 |
| 7.x | Deprecation warning | Plugin sets `optimizeDeps.esbuildOptions`, removed in Vite 7 in favour of `optimizeDeps.rollupOptions` (Rolldown). | Issue #70 |
| 8.x | Hard error: `Cannot find module 'rollup'` | Vite 8 no longer re-exports Rollup as a transitive dependency; [src/index.ts](src/index.ts) imports `rollup` directly without declaring it. | Issue #76 |

The plugin loads SWC and esbuild through a hand-rolled module resolver that calls `Module._resolveFilename` and `virtualModule.require`, which is a CommonJS idiom. Source: [src/esbuild.ts:5-12](src/esbuild.ts). This means the plugin is **not compatible with Bun**, which lacks `module.require`, producing the error `TypeError: virtualModule.require is not a function`. Source: Issue #68.

A graceful fallback exists for SWC: if `@swc/core` cannot be loaded, the plugin switches to the WASM build `@swc/wasm`, controllable through the `VITE_TLA_FORCE_WASM` and `VITE_TLA_FORCE_NATIVE` environment variables. Source: [src/swc.js:1-15](src/swc.js).

## Known Correctness Issues

### Initialization ordering and hoisting

The transformer converts top-level statements into a `then` callback. Any pattern that relies on synchronous top-level evaluation order — including `function` hoisting, `class` declarations, or `const`/`let` TDZ semantics — can break after transformation. Source: [src/transform.ts](src/transform.ts). Concrete user reports include:

- **Function hoisting** (issue #71): A statement like `foo.bar = 42; export function foo() {}` is rewritten so that `foo = function ...` runs *inside* the `then` block, so `foo` is `undefined` at the moment `foo.bar = 42` is executed.
- **Deferred exports** (issue #74): Because the module's bindings become available only after the `__tla` promise resolves, importers that statically read these bindings get `undefined`. The error manifests as `Cannot read properties of undefined (reading 'GREEN')`.
- **Remix interop** (issue #72): A chunk that exports `__tla` is not always recognised by sibling chunks, so the importer does not insert the `await __tla_0` wrapper. The plugin tracks this through a reverse-dependency walk in [src/bundle-info.ts](src/bundle-info.ts), but the analysis runs on the *post-Rollup* chunk graph and can miss plugins that re-bundle.

### Chunk hashing and source maps

Because the plugin mutates chunk source in `generateBundle` (rather than letting Rollup re-emit), the content hash is not recomputed, breaking long-term HTTP caching and CDN purging. Source: Issue #44. The same mutation strategy strips or desynchronises the `//# sourceMappingURL=...` annotation, leading to broken stack traces in production. Source: Issue #34.

### `importedBy` undefined crash

If a chunk is generated by another plugin without populating the `importedBy` reverse-edge that [src/bundle-info.ts](src/bundle-info.ts) expects during Pass 2 of the graph walk, the plugin throws `Cannot read properties of undefined (reading 'importedBy')`. Source: Issue #33. This commonly occurs with multi-page HTML entries that import mixed CSS/TS chunks.

## Troubleshooting Workflow

```mermaid
flowchart TD
    A[Build or runtime error] --> B{Is Vite >= 7?}
    B -- Yes --> C[Remove optimizeDeps.esbuildOptions<br/>or downgrade to Vite 6.x]
    B -- No --> D{Is runtime 'Cannot find module rollup'?}
    D -- Yes --> E[Vite 8 detected.<br/>Pin Vite to 7 or lower]
    D -- No --> F{Is runtime on Bun?}
    F -- Yes --> G[Switch to Node.<br/>Bun lacks module.require]
    F -- No --> H{Is the error a<br/>'reading X of undefined' on an export?}
    H -- Yes --> I[Module depends on synchronous<br/>init order. Refactor to avoid<br/>top-level await or use dynamic import]
    H -- No --> J{Is hash or source map wrong?}
    J -- Yes --> K[Disable plugin for that entry<br/>or wait for upstream fix]
    J -- No --> L[Open an issue with a<br/>minimal reproduction]
```

### Diagnostic steps

1. **Identify the failing chunk** by enabling the verbose log: the plugin warns to `console.warn` when an import target is missing from the bundle graph. Source: [src/bundle-info.ts](src/bundle-info.ts).
2. **Check `peerDependencies`** — `package.json` requires `vite >=2.8`, but versions 6.2+ and 7+ have documented regressions. Source: [package.json](https://github.com/Menci/vite-plugin-top-level-await/blob/main/package.json).
3. **Test with `VITE_TLA_FORCE_WASM=true`** to rule out native SWC binary issues. Source: [src/swc.js:1-15](src/swc.js).
4. **Reduce scope** — the long-standing feature request to add `include`/`exclude` options (issue #73) reflects the practical advice: only transform modules that actually require it. Until shipped, isolate `await` to dedicated entry points.
5. **Workers and service-workers** — the plugin exposes a worker mode (configured through `outputOptions` in [src/index.ts](src/index.ts)). For Chrome extension service workers, the dev server's unbundled HMR does not run the transformer; you must build and load `dist/` as a packaged extension. Source: Issue #57.

## Workarounds Summary

- Pin Vite to a known-good minor (≤ 6.1.x for production stability, ≤ 6.x for esbuild support).
- Do not use Bun as the Vite runner.
- Avoid mixing top-level `await` with code that depends on synchronous evaluation order; prefer dynamic `import()` to defer the work.
- When using Module Federation, the MFE host must also run the plugin to ensure the shared `__tla` promises are wired correctly. Source: Issue #58.
- For a failed Rust `napi` string conversion, ensure SWC's native binary matches the host architecture; the WASM fallback bypasses `napi` entirely. Source: [src/swc.js](src/swc.js).

## See Also

- [Architecture and Transform Pipeline](./Architecture-and-Transform-Pipeline.md)
- [Configuration Options](./Configuration-Options.md)

---

<!-- evidence_pipeline_checked: true -->
<!-- evidence_injected: true -->

---

## Pitfall Log

Project: Menci/vite-plugin-top-level-await

Summary: Found 18 structured pitfall item(s), including 5 high/blocking item(s). Top priority: Installation risk - Installation risk requires verification.

## 1. Installation risk - Installation risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/Menci/vite-plugin-top-level-await/issues/64

## 2. Installation risk - Installation risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/Menci/vite-plugin-top-level-await/issues/68

## 3. Installation risk - Installation risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/Menci/vite-plugin-top-level-await/issues/72

## 4. Installation risk - Installation risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/Menci/vite-plugin-top-level-await/issues/76

## 5. Runtime risk - Runtime risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a runtime risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/Menci/vite-plugin-top-level-await/issues/71

## 6. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/Menci/vite-plugin-top-level-await/issues/57

## 7. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/Menci/vite-plugin-top-level-await/issues/77

## 8. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/Menci/vite-plugin-top-level-await/issues/65

## 9. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/Menci/vite-plugin-top-level-await/issues/70

## 10. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/Menci/vite-plugin-top-level-await/issues/58

## 11. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/Menci/vite-plugin-top-level-await/issues/74

## 12. Capability evidence risk - Capability evidence risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.assumptions | github_repo:464408338 | https://github.com/Menci/vite-plugin-top-level-await

## 13. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/Menci/vite-plugin-top-level-await/issues/69

## 14. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | github_repo:464408338 | https://github.com/Menci/vite-plugin-top-level-await

## 15. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: downstream_validation.risk_items | github_repo:464408338 | https://github.com/Menci/vite-plugin-top-level-await

## 16. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: risks.scoring_risks | github_repo:464408338 | https://github.com/Menci/vite-plugin-top-level-await

## 17. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | github_repo:464408338 | https://github.com/Menci/vite-plugin-top-level-await

## 18. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | github_repo:464408338 | https://github.com/Menci/vite-plugin-top-level-await

<!-- canonical_name: Menci/vite-plugin-top-level-await; human_manual_source: deepwiki_human_wiki -->
