Keywords: TypeScript | top-level await | module configuration
Abstract: This article delves into the common top-level await expression error in TypeScript development, often caused by improper module and target configuration. Based on a Stripe integration case study, it analyzes the error causes and provides three solutions: modifying tsconfig.json settings, using command-line arguments to specify compilation options, and adopting modern tools like esrun. The focus is on correctly setting module to esnext or system and target to es2017 or higher to support top-level await, while comparing the pros and cons of different approaches to help developers efficiently resolve similar issues.
Problem Background and Error Analysis
In TypeScript or JavaScript projects, when attempting to use top-level await expressions, developers may encounter the following error message: Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.ts(1378). This error is common in cases like integrating third-party services such as Stripe, for example, when executing the following code in a Node.js environment:
const stripe = require('stripe')('someID');
const account = await stripe.accounts.create({
type: 'express',
});
The core issue lies in the configuration limitations of the TypeScript compiler. Top-level await is a feature introduced in ECMAScript 2022 (ES2022), allowing direct use of await at the module level without wrapping it in an async function. However, TypeScript's default compilation settings may not support this feature, leading to compilation failures. Specifically, the module option must be set to esnext or system, and the target option needs to be es2017 or higher to ensure the generated code is compatible with top-level await.
Solution 1: Modify tsconfig.json Configuration
The most straightforward solution is to adjust the project's tsconfig.json file to correctly configure compilation options. Here is an example configuration that supports top-level await:
{
"compilerOptions": {
"esModuleInterop": true,
"lib": ["es2020"],
"module": "es2022",
"preserveConstEnums": true,
"moduleResolution": "node",
"strict": true,
"sourceMap": true,
"target": "es2022",
"types": ["node"],
"outDir": "dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
The key is to set module to es2022 (or esnext) and target to es2022. This way, the TypeScript compiler generates code compatible with top-level await. After configuration, run npx tsc to compile the project, producing output files like dist/runme.mjs. This method is suitable for long-term projects, ensuring code standardization and maintainability.
Solution 2: Use Command-Line Arguments to Specify Options
If the project temporarily needs to compile a single file, or if tsconfig.json is not correctly applied, you can specify compilation options directly via command-line arguments. For example, when using tsc to compile a specific file, note that tsc ignores tsconfig.json configuration when a filename is provided, as shown in the help information: tsc app.ts util.ts Ignoring tsconfig.json, compiles the specified files with default compiler options.. Therefore, you can run the following command:
npx tsc -t es2022 -m es2022 --moduleResolution node --outDir dist src/runme.mts
Here, -t es2022 sets target to es2022, and -m es2022 sets module to es2022. This approach is flexible and quick but best suited for temporary debugging or simple scripts; for complex projects, using tsconfig.json is recommended.
Solution 3: Adopt Modern Tools like esrun
For developers looking to simplify the process, consider using modern tools like esrun. esrun is a tool that runs TypeScript files directly without configuration, naturally supporting top-level await and avoiding complex setup steps. After installation, simply run:
npm i esrun
npx esrun file.ts
The advantages of esrun include: no need for tsconfig.json or package.json, support for top-level await, and clearer error messages. Compared to tools like ts-node, esrun offers a lighter solution, ideal for rapid prototyping or educational purposes.
Alternative Solutions and Considerations
If configuration changes or new tools are not feasible, a common alternative is to wrap the top-level await in an async function. For example, modify the original code to:
const account = async () => {
await stripe.accounts.create({
type: "express",
});
};
This method resolves the compilation error but alters the code structure, potentially affecting subsequent logic, so it should only be used as a temporary workaround. In practice, prioritize configuration support to ensure code modernity and performance. Additionally, developers should note that top-level await is only applicable to ES modules (.mjs files or .js files with type: "module") and may not work in CommonJS environments.
Summary and Best Practices
The key to resolving top-level await errors lies in correctly configuring TypeScript's module and target options. It is recommended to set module to es2022 or esnext and target to es2017 or higher in tsconfig.json to fully leverage modern JavaScript features. For quick testing, tools like esrun can simplify the process. By understanding these configuration principles, developers can handle similar errors more efficiently and improve project development productivity.